Skip to content

olaafrossi/ClaudeChatTTSServer

Repository files navigation

Claude Chat Text to Speech Server

Azure Neural TTS as an MCP tool for Claude Code

Send text → get back a downloadable MP3 URL in seconds.

graph LR
    A[Claude Code] --> B[Local MCP Server<br/>Node.js stdio]
    A --> M[mcp-remote CLI]
    M -->|Google OAuth| O[Google Sign-In]
    M -->|Bearer token| B2[Remote MCP Server<br/>Next.js on Vercel]
    B2 -->|tokeninfo| O
    B --> C[App Service<br/>C#, Azure]
    B2 --> C
    C --> D[Azure Speech SDK]
    C --> E[Azure Blob Storage]
    E --> F[SAS URL<br/>1hr expiry]
    C --> G[Returns<br/>url, duration, voice]
Loading

What This Is

A C# ASP.NET Core minimal API running on Azure App Service (F1 free tier) that:

  1. Accepts a POST with text + voice + format
  2. Synthesizes speech via Azure Cognitive Services Neural TTS
  3. Uploads the MP3 to Azure Blob Storage
  4. Returns a time-limited SAS download URL

Two MCP server options wrap the HTTP call so Claude Code can use it as a native tool (mcp__tts__synthesize_speech):

  • Local (mcp-server/) — stdio-based, runs on your machine alongside Claude Code
  • Remote (mcp-remote/) — HTTP-based Next.js app deployed to Vercel, accessible from anywhere with optional Bearer token auth

Prerequisites


Azure Setup (PowerShell)

1. Set Variables

$RG = "rg-xxx"
$LOCATION = "eastus2"
$SPEECH_NAME = "speech-xxx"
$STORAGE_NAME = "xxx"
$APP_NAME = "app-xxx"
$PLAN_NAME = "plan-xxx"
$PLAN_LOCATION = "centralus"   # F1 free tier may not be available in all regions

2. Create Resource Group

az group create --name $RG --location $LOCATION

3. Create Speech Resource (F0 = free tier)

az --% cognitiveservices account create --name speech-claudetts --resource-group rg-claudettsserver --kind SpeechServices --sku F0 --location eastus2 --yes

Note: Use az --% (stop-parsing token) to prevent PowerShell from mangling arguments.

4. Create Storage Account

az storage account create --name $STORAGE_NAME --resource-group $RG --location $LOCATION --sku Standard_LRS --kind StorageV2

5. Create App Service Plan (F1 = free tier, Linux)

az appservice plan create --name $PLAN_NAME --resource-group $RG --location $PLAN_LOCATION --sku F1 --is-linux

Note: F1 free tier has quota limits per region. If you get a quota error, try a different --location (e.g., centralus, westus2, eastus).

6. Create Web App

az --% webapp create --name app-claudetts --resource-group rg-claudettsserver --plan plan-claudetts --runtime "DOTNETCORE:10.0"

7. Get Keys & Configure App Settings

$SPEECH_KEY = az cognitiveservices account keys list --name $SPEECH_NAME --resource-group $RG --query key1 -o tsv
$STORAGE_CONN = az storage account show-connection-string --name $STORAGE_NAME --resource-group $RG --query connectionString -o tsv

az webapp config appsettings set --name $APP_NAME --resource-group $RG --settings AZURE_SPEECH_KEY="$SPEECH_KEY" AZURE_SPEECH_REGION="$LOCATION" AZURE_STORAGE_CONNECTION_STRING="$STORAGE_CONN"

If az webapp config appsettings set fails with a version error (known Azure CLI bug on PowerShell), use the REST API instead:

# 1. List current settings
az --% rest --method POST --uri "/subscriptions/<SUB_ID>/resourceGroups/rg-claudettsserver/providers/Microsoft.Web/sites/app-claudetts/config/appsettings/list?api-version=2023-12-01" -o json > current_settings.json

# 2. PUT all settings (existing + custom) via REST
az --% rest --method PUT --uri "/subscriptions/<SUB_ID>/resourceGroups/rg-claudettsserver/providers/Microsoft.Web/sites/app-claudetts/config/appsettings?api-version=2023-12-01" --body "{\"properties\":{...merge existing + your 3 keys...}}"

8. Deploy

cd C:\Dev\ClaudeChatTTSServer
dotnet publish -c Release -o ./publish
Compress-Archive -Path ./publish/* -DestinationPath ./deploy.zip -Force
az webapp deploy --name $APP_NAME --resource-group $RG --src-path ./deploy.zip --type zip

9. Test

# Health check
Invoke-WebRequest -Uri "https://$APP_NAME.azurewebsites.net/"

# Synthesize speech
Invoke-WebRequest -Uri "https://$APP_NAME.azurewebsites.net/api/tts" -Method POST -ContentType "application/json" -Body '{"text":"Hello, this is a test."}'

MCP Server Setup (Claude Code Integration)

1. Build the MCP Server

cd C:\Dev\ClaudeChatTTSServer\mcp-server
npm install
npm run build

2. Add to Claude Code (Global)

Copy .mcp.json to your home directory so the TTS tool is available in every Claude Code project:

Copy-Item C:\Dev\ClaudeChatTTSServer\.mcp.json C:\Users\<YOUR_USERNAME>\.mcp.json

Or to ~/.claude/.mcp.json if the home root doesn't work.

Important: Edit .mcp.json and set TTS_ENDPOINT to your Azure App Service URL (e.g. https://your-app.azurewebsites.net/api/tts or http://localhost:7841/api/tts for local development). The MCP server will not start without it.

3. Restart Claude Code

Fully quit and reopen Claude Code. You should see tts in the MCP server list.

4. Use It

Just ask Claude:

"Generate audio for: Welcome to the show, folks!"

Claude will call mcp__tts__synthesize_speech and return a downloadable MP3 URL.


Remote MCP Server (Vercel)

A cloud-hosted alternative that requires no local setup — deploy once and connect from any machine. Uses Google OAuth 2.0 to restrict access to a single authorized email.

1. Deploy to Vercel

cd C:\Dev\ClaudeChatTTSServer\mcp-remote
npm install
vercel deploy --prod

2. Configure Environment Variables

In the Vercel dashboard (or via CLI), set:

Variable Required Description
TTS_ENDPOINT Yes URL of your C# TTS API (e.g. https://your-app.azurewebsites.net/api/tts)
ALLOWED_EMAIL No Google email authorized to use the server. If unset, the server is open (dev only)
vercel env add TTS_ENDPOINT production
vercel env add ALLOWED_EMAIL production

3. Connect in Claude Code

Add the remote MCP server to your .mcp.json with your Google OAuth client credentials:

{
  "mcpServers": {
    "tts": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://your-project.vercel.app/api/mcp",
        "--oauth-client-id", "<YOUR_GOOGLE_CLIENT_ID>",
        "--oauth-client-secret", "<YOUR_GOOGLE_CLIENT_SECRET>"
      ]
    }
  }
}

The mcp-remote CLI handles the OAuth browser flow automatically — it opens a Google sign-in page, exchanges the authorization code for an access token, and passes it as a Bearer token on every MCP request.

4. OAuth Flow

sequenceDiagram
    participant CC as Claude Code
    participant MR as mcp-remote CLI
    participant G as Google OAuth
    participant V as Vercel MCP Server

    CC->>MR: Start MCP session
    MR->>V: GET /.well-known/oauth-protected-resource
    V-->>MR: Auth server = accounts.google.com
    MR->>G: Authorization request (browser)
    G-->>MR: Access token
    MR->>V: MCP request + Bearer token
    V->>G: GET tokeninfo?access_token=...
    G-->>V: {email, email_verified, scope}
    V->>V: Verify email matches ALLOWED_EMAIL
    V-->>MR: MCP response
    MR-->>CC: Tool result
Loading

How it works:

  1. Discoverymcp-remote fetches /.well-known/oauth-protected-resource from the Vercel server, which advertises https://accounts.google.com as the authorization server
  2. Browser loginmcp-remote opens a Google sign-in page using the configured OAuth client ID/secret. The user signs in and grants access
  3. Token relaymcp-remote attaches the Google access token as a Bearer token on every MCP request
  4. Server-side verification — the Vercel server validates the token against Google's tokeninfo endpoint and checks that the email is verified and matches ALLOWED_EMAIL
  5. Access granted — if the email matches, the request proceeds to the TTS tool; otherwise it returns 401 Unauthorized

5. Authentication Modes

Mode Condition Behavior
Open (dev) ALLOWED_EMAIL not set All requests accepted, no token required
Google OAuth ALLOWED_EMAIL set Bearer token required, email must match

6. Google OAuth Setup

To create your own OAuth client credentials:

  1. Go to Google Cloud Console → Credentials
  2. Create an OAuth 2.0 Client ID (type: Desktop app or Web application)
  3. Note the Client ID and Client Secret
  4. Add them to your .mcp.json as --oauth-client-id and --oauth-client-secret
  5. Set ALLOWED_EMAIL in Vercel to the Google account you'll sign in with

API Reference

POST /api/tts

Field Type Default Description
text string (required) Text to synthesize (max 100K chars)
voice string en-US-AriaNeural Azure Neural TTS voice name
format string audio-16khz-128kbitrate-mono-mp3 Output audio format

Response:

{
  "url": "https://stclaudetts.blob.core.windows.net/tts-audio/abc123.mp3?sv=...",
  "durationSeconds": 3.2,
  "voice": "en-US-AriaNeural",
  "characterCount": 42
}

GET /

Health check. Returns {"status":"healthy","service":"ClaudeChatTTSServer"}.


Popular Voices

Voice Gender Style
en-US-AriaNeural Female Conversational (default)
en-US-GuyNeural Male Conversational
en-US-JennyNeural Female Warm
en-US-DavisNeural Male Calm
en-US-SaraNeural Female Cheerful
en-US-TonyNeural Male Friendly
en-US-NancyNeural Female Empathetic
en-GB-SoniaNeural Female British
en-GB-RyanNeural Male British
en-US-AvaMultilingualNeural Female Multilingual
en-US-AndrewMultilingualNeural Male Multilingual

Full voice list


Audio Formats

Format Quality Use Case
audio-16khz-128kbitrate-mono-mp3 Good Default, small file size
audio-24khz-160kbitrate-mono-mp3 Better Higher quality
audio-48khz-192kbitrate-mono-mp3 Best Studio quality

Running Tests

C# (xUnit)

dotnet test

Runs 21 tests covering API endpoint validation, service error handling, format parsing, and model defaults. Uses WebApplicationFactory with mocked dependencies — no Azure credentials needed.

TypeScript (Vitest)

cd mcp-server
npm test

Runs 8 tests covering the synthesizeSpeech function: request formatting, success/error responses, default parameters, and network error handling.


Project Structure

ClaudeChatTTSServer/
  Program.cs                  # Minimal API entry point
  Models/
    TtsRequest.cs             # Input model
    TtsResponse.cs            # Output model
  Services/
    ITtsService.cs            # Service interface
    TtsService.cs             # Speech synthesis + blob upload
  ClaudeChatTTSServer.Tests/
    ApiIntegrationTests.cs    # API endpoint integration tests (xUnit)
    TtsServiceTests.cs        # Format parsing unit tests
    ModelTests.cs             # Model default/property tests
  mcp-server/                 # Local MCP server (stdio)
    src/index.ts              # Stdio transport entry point
    src/server.ts             # MCP server + tool registration
    src/synthesize.ts         # Extracted synthesis logic
    src/synthesize.test.ts    # Vitest tests
    dist/                     # Built output (run npm run build)
  mcp-remote/                 # Remote MCP server (Vercel)
    app/api/[transport]/
      route.ts                # Next.js API route — MCP over HTTP + OAuth verification
    app/.well-known/
      oauth-protected-resource/
        route.ts              # OAuth metadata — advertises Google as auth server
    app/page.tsx              # Landing page
    vercel.json               # Vercel deployment config
    package.json
  .mcp.json                   # MCP config for Claude Code
  appsettings.json            # Local config (keys go in Azure app settings)

Cost

Resource Tier Cost
App Service F1 (free) $0/month
Speech Services F0 (free) 500K chars/month free
Blob Storage Standard LRS ~$0.02/GB/month
Vercel Hobby (free) $0/month

For occasional Claude-driven TTS, total cost is effectively $0/month.


Rotate Keys

If you exposed keys during setup (e.g., pasted them in chat), rotate them:

# Regenerate Speech key
az cognitiveservices account keys regenerate --name speech-claudetts --resource-group rg-claudettsserver --key-name key1

# Regenerate Storage key
az storage account keys renew --account-name stclaudetts --resource-group rg-claudettsserver --key primary

# Get new values and update app settings
$SPEECH_KEY = az cognitiveservices account keys list --name speech-claudetts --resource-group rg-claudettsserver --query key1 -o tsv
$STORAGE_CONN = az storage account show-connection-string --name stclaudetts --resource-group rg-claudettsserver --query connectionString -o tsv

az webapp config appsettings set --name app-claudetts --resource-group rg-claudettsserver --settings AZURE_SPEECH_KEY="$SPEECH_KEY" AZURE_STORAGE_CONNECTION_STRING="$STORAGE_CONN"

About

Simple Service for Claude to create neural audio files in chat & code sessions

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors