npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@theproductivepixel/aittsm

v1.9.0

Published

MCP stdio server for AI TTS Microservice — connects AI agents, IDEs, and MCP-compatible clients to the unified TTS API via tool calls

Readme

@theproductivepixel/aittsm

npm license

MCP (Model Context Protocol) stdio server for AI TTS Microservice. Connects AI agents, IDEs, and any MCP-compatible client to the unified TTS API via tool calls.

Install

npm install -g @theproductivepixel/aittsm

Setup

export AITTSM_API_KEY=tts_your_api_key_here

Get your API key from aitts.theproductivepixel.com/dashboard/api.

Hosted OAuth 2.1

This npm package runs as a local MCP stdio server and still uses AITTSM_API_KEY for authentication. It does not perform browser-based OAuth.

OAuth 2.1 is available on the hosted remote MCP endpoint at https://aitts.theproductivepixel.com/api/v1/mcp. Clients can discover the protected resource at /.well-known/oauth-protected-resource and the authorization server at /.well-known/oauth-authorization-server. The hosted flow uses S256 PKCE, requires the exact canonical resource where client policy says so, and supports predefined, HTTPS metadata-document (CIMD), and controlled dynamic-registration clients. The registered client record selects public, secret, or private_key_jwt authentication. See the MCP documentation.

Usage

Claude Desktop

{
  "mcpServers": {
    "aitts": {
      "command": "npx",
      "args": ["@theproductivepixel/aittsm"],
      "env": { "AITTSM_API_KEY": "tts_your_api_key_here" }
    }
  }
}

Cursor / Windsurf

{
  "aitts": {
    "command": "npx",
    "args": ["@theproductivepixel/aittsm"],
    "env": { "AITTSM_API_KEY": "tts_your_api_key_here" }
  }
}

Direct CLI

aittsm

Tools (56)

Voice & Generation

| Tool | Permission | Bucket | Description | |------|-----------|--------|-------------| | search_voices | voices:list | read | Search available TTS voices (filters + free-text q) | | get_voice_details | voices:list | read | Get full capability details for a voice | | get_voice_sample_url | tts:generate | generate | Get a sample-audio URL (synthesizes on miss) | | generate_speech | tts:generate | generate | Generate TTS audio | | get_job_status | tts:status | read | Check job status and metadata | | get_audio_link | tts:status | read | Get fresh signed audio URL + metadata |

Jobs

| Tool | Permission | Bucket | Description | |------|-----------|--------|-------------| | list_jobs | jobs:read | read | List jobs with pagination | | get_job_text | jobs:read | read | Get input text of a job | | update_job_metadata | jobs:write | generate | Update tags/collection | | delete_job_audio | jobs:write | generate | Delete stored audio |

Shares

| Tool | Permission | Bucket | Description | |------|-----------|--------|-------------| | create_share | shares:write | generate | Create share link | | create_voice_share | shares:write | generate | Create a shareable voice collection | | list_shares | shares:read | read | List active shares | | get_share | shares:read | read | Get full share details | | update_share | shares:write | generate | Update share settings | | revoke_share | shares:write | generate | Revoke a share | | bulk_revoke_shares | shares:write | generate | Revoke multiple shares | | toggle_share_permanent | shares:write | generate | Toggle permanent status | | update_track_order | shares:write | generate | Reorder tracks |

Access Codes & QR

| Tool | Permission | Bucket | Description | |------|-----------|--------|-------------| | create_access_codes | shares:write | generate | Create access codes | | list_access_codes | shares:read | read | List codes (no raw values) | | update_access_code | shares:write | generate | Update a code | | delete_access_code | shares:write | generate | Delete a code | | export_access_codes | shares:read | read | Export as CSV format | | get_qr_code | shares:write | generate | Generate QR image |

Library & Storage

| Tool | Permission | Bucket | Description | |------|-----------|--------|-------------| | list_collections | library:read | read | List audio collections | | manage_collection | library:write | generate | Create/rename/delete collection | | list_tags | library:read | read | List tags with counts | | create_bookmark | library:write | generate | Bookmark a share | | list_bookmarks | library:read | read | List bookmarks | | delete_bookmark | library:write | generate | Delete a bookmark | | manage_bookmark_collection | library:write | generate | Manage bookmark collections | | get_storage | storage:read | read | Storage usage summary | | list_storage_items | storage:read | read | List stored items | | bulk_delete_storage | storage:write | generate | Bulk delete storage | | get_pricing | pricing:estimate | read | Get plan prices and rate summaries | | estimate_cost | pricing:estimate | read | Estimate credit cost | | get_usage | usage:read | read | Get balance and account type |

Personalization: Preferences, Modes & Projects

| Tool | Permission | Bucket | Description | |------|-----------|--------|-------------| | get_preferences | preferences:read | read | Get stored preference overrides | | set_preferences | preferences:write | generate | Patch preference overrides (also sets activeModeId) | | list_modes | modes:read | read | List system + user modes; optional explicit pagination | | get_mode | modes:read | read | Get a single mode by id | | preview_mode | preferences:read | read | Preview effective preferences under a given mode | | create_mode | modes:write | generate | Create a user mode | | update_mode | modes:write | generate | Update a user mode | | delete_mode | modes:write | generate | Delete a user mode | | list_projects | projects:read | read | List projects; optional explicit pagination | | get_project | projects:read | read | Get a project | | create_project | projects:write | generate | Create a project | | update_project | projects:write | generate | Update a project | | delete_project | projects:write | generate | Delete a project | | list_project_items | projects:read | read | List project items; optional explicit pagination | | get_project_item | projects:read | read | Get a project item | | add_project_item | projects:write | generate | Add an item to a project | | update_project_item | projects:write | generate | Update a project item | | remove_project_item | projects:write | generate | Remove a project item |

Note: apply_mode (an in-app-only read-modify-write composite that validates the mode exists before activating it) is intentionally not proxied here — it isn't a single HTTP request, so it has no REST route to map to. To activate a mode from a proxy client, call set_preferences({ updates: { activeModeId: '<mode_id>' }, ... }) instead. Caveat: set_preferences does not validate that activeModeId refers to an existing mode (unlike apply_mode), so call preview_mode or get_mode first to confirm the mode exists before activating it.

Personalization list compatibility: omitting page_size and page_token returns the complete legacy envelope and omits next_page_token when the raw list has at most 100 records; larger lists return PAGINATION_REQUIRED. Supplying either field enables pagination (page_size defaults to 20, maximum 100) and returns next_page_token: string | null. Tokens are owner-bound, and project-item tokens are also project-bound.

Examples

Async Generation Workflow

// 1. Generate audio
{
  "name": "generate_speech",
  "arguments": {
    "text": "Welcome to our podcast.",
    "voice_id": "google:en-US-Chirp3HD-Charon",
    "tags": ["podcast", "intro"]
  }
}
// Response:
// {
//   "job_id": "abc123",
//   "status": "pending",
//   ...
// }

// 2. Poll until completed
{
  "name": "get_job_status",
  "arguments": {
    "job_id": "abc123"
  }
}
// Response:
// {
//   "status": "completed",
//   "audio_bytes": 48000,
//   ...
// }

// 3. Get download URL
{
  "name": "get_audio_link",
  "arguments": {
    "job_id": "abc123"
  }
}
// Response (use this JSON body — it carries a fresh signed URL — do not follow the /audio 307 redirect):
// {
//   "signed_url": "https://...",   // deprecated alias of audio_url
//   "audio_url": "https://...",
//   "expires_at": "2026-07-05T21:00:00Z",
//   "expires_in": 900,
//   "content_type": "audio/mpeg",
//   "audio_bytes": 48000,
//   "audio_endpoint": "/api/v1/tts/abc123/audio"
// }

Streaming Generation

{
  "name": "generate_speech",
  "arguments": {
    "text": "Stream this audio.",
    "voice_id": "google:en-US-Chirp3HD-Charon",
    "delivery_mode": "stream",
    "output_format": "ogg_opus",
    "idempotency_key": "unique-key-abc"
  }
}

Stream supports all 7 formats. Async supports wav, mp3, ogg_opus only.

Voice Search

{
  "name": "search_voices",
  "arguments": {
    "q": "Leda",
    "language": "en-US",
    "provider": "google",
    "model_type": "ultra"
  }
}

q runs a fuzzy, typo-tolerant search across voice_id, family, name, language, and provider, so a partial term like Leda matches even without the full id.

Voice Sample URL

{
  "name": "get_voice_sample_url",
  "arguments": {
    "voice_id": "google:en-US-Gemini-Kore"
  }
}
// Ready: { voice_id, sample_url, voices_url, expires_at, expires_in, content_type }
// Pending (sample is being synthesized): { status: "pending", voices_url, retry_after: 3 }

Generation-class: a voice with no existing sample triggers synthesis, so poll again after retry_after seconds until the URL is ready.

Voice Share

{
  "name": "create_voice_share",
  "arguments": {
    "voiceRefs": [
      { "id": "google:en-US-Gemini-Kore", "language": "en-US", "ultraModel": "gemini-2.5-flash-tts" },
      { "id": "kokoro:en-US-Kokoro-Bella" }
    ]
  }
}
// Response: { code, url }. url is built from the configured app origin.

Share Workflow

// 1. Create a password-protected share from completed jobs
{
  "name": "create_share",
  "arguments": {
    "job_ids": ["job1", "job2"],
    "title": "Client Review",
    "auth_mode": "password",
    "password": "secret123",
    "allow_download": true
  }
}
// Response:
// {
//   "code": "xyz789",
//   "url": "https://aitts.theproductivepixel.com/share/audio/xyz789",
//   "item_count": 2
// }

// 2. Generate a QR code for the share
{
  "name": "get_qr_code",
  "arguments": {
    "code": "xyz789",
    "format": "png",
    "preset": "branded"
  }
}

// 3. Later, revoke access
{
  "name": "revoke_share",
  "arguments": {
    "code": "xyz789"
  }
}

Collection Management

// 1. Create a collection
{
  "name": "manage_collection",
  "arguments": {
    "action": "create",
    "name": "Podcast Episodes"
  }
}
// Response:
// {
//   "id": "col-abc",
//   "name": "Podcast Episodes"
// }

// 2. Assign jobs to it
{
  "name": "update_job_metadata",
  "arguments": {
    "job_id": "job1",
    "collection_id": "col-abc",
    "tags": ["episode-1"]
  }
}

// 3. Create a live share from the collection
{
  "name": "create_share",
  "arguments": {
    "source_type": "collection",
    "source_id": "col-abc",
    "share_mode": "live",
    "title": "All Episodes"
  }
}

Tool Reference

The summary table above lists all 56 proxied tools. The detailed examples below cover the highest-traffic generation, lookup, sharing, and library workflows.

search_voices

| Param | Type | Required | Description | |-------|------|----------|-------------| | q | string | — | Free-text fuzzy search across voice_id, family, name, language, provider. Partial and typo-tolerant (e.g. Leda) | | language | string | — | Filter by language code (e.g. en-US) | | provider | string | — | Filter by provider (google, polly, kokoro) | | model_type | premium | ultra | — | Filter by model type | | gender | male | female | neutral | unknown | — | Filter by gender | | voice_id | string | — | Exact voice_id filter (case-insensitive) |

Response:

{
  "voices": [
    {
      "voice_id": "google:en-US-Gemini-Charon",
      "name": "Charon",
      "family": "Gemini",
      "language": "en-US",
      "provider": "google",
      "model_type": "ultra",
      "gender": "male",
      "characteristics": { "styles": ["informative"], "roles": [], "age": null, "accent": null, "pitch": "lower", "use_case": null },
      "sample_url": null,
      "supports_ssml": true,
      "supports_markup": false,
      "supports_multispeaker": true
    }
  ],
  "count": 1
}

get_voice_details

| Param | Type | Required | Description | |-------|------|----------|-------------| | voice_id | string | ✓ | Voice ID (provider:language-Family-Name) | | model | string | — | Specific model ID for ultra voices with model selection |

Response:

{
  "voice_id": "kokoro:en-US-Kokoro-Bella",
  "provider": "kokoro",
  "language": "en-US",
  "family": "Kokoro",
  "name": "Bella",
  "model_type": "premium",
  "gender": "female",
  "characteristics": { "styles": ["friendly"], "roles": [], "age": null, "accent": null, "pitch": null, "use_case": null },
  "sample_url": null,
  "capabilities": {
    "max_text_bytes_async": 5000,
    "max_text_bytes_stream": 5000,
    "supports_streaming": true,
    "stream_formats": { "ogg_opus": [24000], "mp3": [24000] },
    "async_formats": ["wav", "mp3", "ogg_opus"],
    "speed": { "supported_modes": ["text", "markup"], "min": 0.25, "max": 4.0 },
    "prompt": null,
    "supports_ssml": false,
    "supports_markup": false,
    "supports_multispeaker": false
  }
}

Ultra voice with model selection (voice_id=google:en-US-Gemini-Kore):

{
  "voice_id": "google:en-US-Gemini-Kore",
  "provider": "google",
  "language": "en-US",
  "family": "Gemini",
  "name": "Kore",
  "model_type": "ultra",
  "gender": "female",
  "available_models": ["gemini-2.5-flash-tts", "gemini-2.5-pro-tts", "gemini-2.5-flash-lite-preview-tts", "gemini-3.1-flash-tts-preview"],
  "default_model": "gemini-2.5-flash-tts",
  "capabilities": { "..." : "resolved for default model" },
  "model_overrides": {
    "gemini-2.5-flash-lite-preview-tts": {
      "max_text_bytes_async": 300,
      "max_text_bytes_stream": 300,
      "supports_multispeaker": false
    }
  }
}

generate_speech

| Param | Type | Required | Description | |-------|------|----------|-------------| | text | string (1–500000) | ✓ | Text to synthesize | | voice_id | string | ✓ (single) | Voice ID (provider:lang-Family-Name). Required for single-speaker, forbidden for multi-speaker. | | delivery_mode | async | stream | — | Default: async | | model_type | premium | ultra | — | Model type | | model | string | — | Specific model ID | | speed | number (0.25–4) | — | Speaking rate multiplier | | format | text | ssml | markup | — | Input format | | speaker_type | single | multi | — | Speaker type | | voice_id_speaker_1 | string | ✓ (multi) | Speaker 1 voice ID. Required for multi-speaker only. | | voice_id_speaker_2 | string | ✓ (multi) | Speaker 2 voice ID. Required for multi-speaker only. | | output_format | wav | mp3 | ogg_opus | pcm | mulaw | alaw | ogg_vorbis | — | Audio format | | preview_transport_format | pcm | — | Stream only: the LIVE stream carries raw s16le mono PCM (Web Audio / DSP friendly) while the SAVED artifact keeps output_format or the provider default; with it set, output_format must be wav/mp3/ogg_opus | | prompt | string | — | Ultra model guidance prompt | | webhook_url | string (URL) | — | Completion webhook (enterprise) | | metadata | object | — | Custom metadata | | sample_rate_hertz | integer | — | Output sample rate in Hz | | output_bitrate_kbps | integer | — | Bitrate (async only) | | language | string | — | Language code override | | tags | string[] | — | Library tags | | collection_id | string | — | Collection ID | | idempotency_key | string (max 256) | — | Safe retry key |

Response (async):

{
  "job_id": "uuid",
  "status": "pending",
  "poll_url": "/api/v1/tts/{job_id}",
  "audio_endpoint": "/api/v1/tts/{job_id}/audio",
  "chars_charged": 16
}

Response (stream):

{
  "job_id": "uuid",
  "status": "pending",
  "stream_url": "https://...",
  "stream_url_expires_at": "...",
  "transport_format": "ogg_opus",
  "transport_mime_type": "audio/ogg; codecs=opus",
  "transport_sample_rate_hertz": 48000,
  "chars_charged": 30
}

With preview_transport_format: "pcm" the transport fields describe the raw PCM live stream (transport_format: "pcm", transport_mime_type: "audio/pcm") while the SAVED artifact keeps the requested or default format — fetch stream_url for raw s16le mono samples at transport_sample_rate_hertz, and the completed job's audio stays a normal playable file.


get_job_status

| Param | Type | Required | Description | |-------|------|----------|-------------| | job_id | string | ✓ | Job ID to check |

Response:

{
  "job_id": "uuid",
  "status": "completed",
  "created_at": "2026-05-20T10:00:00.000Z",
  "progress_message": null,
  "elapsed_seconds": 3,
  "provider": "google",
  "model_requested": "gemini-2.5-pro-preview-tts",
  "model_effective": "gemini-2.5-flash-tts",
  "output_format": "ogg_opus",
  "sample_rate_hertz": 48000,
  "output_bitrate_kbps": 64,
  "duration_seconds": 3.2,
  "estimated_duration_seconds": null,
  "prompt_bytes": 0,
  "chars_charged": 16,
  "audio_endpoint": "/api/v1/tts/uuid/audio",
  "audio_available": true,
  "retention_tier": "permanent",
  "retained_until": null,
  "voice_id": "en-US-Chirp3HD-Kore",
  "model_type": "ultra",
  "audio_bytes": 48000,
  "tags": [],
  "collection_id": null,
  "source": "api",
  "is_expired": false,
  "audio_url": "https://storage.example.com/...",
  "audio_url_expires_at": "2026-05-20T11:00:00.000Z"
}

get_audio_link

| Param | Type | Required | Description | |-------|------|----------|-------------| | job_id | string | ✓ | Job ID |

Response:

{
  "job_id": "uuid",
  "audio_url": "https://...",
  "signed_url": "https://...",
  "expires_at": "2026-05-08T12:00:00Z",
  "expires_in": 900,
  "content_type": "audio/mpeg",
  "audio_bytes": 48000,
  "audio_endpoint": "/api/v1/tts/{job_id}/audio"
}

signed_url is a deprecated alias of audio_url. Calling this tool on a job that is not completed, or whose audio has expired, returns an error (409/410) rather than a payload.


get_voice_sample_url

Generation-class. When a sample for the voice does not exist yet, this triggers sample synthesis and returns a pending status until the audio is ready. The language is derived from the voice_id.

| Param | Type | Required | Description | |-------|------|----------|-------------| | voice_id | string | ✓ | Voice ID (provider:language-Family-Name) | | model | string | — | Optional Gemini ultra sub-model id (honored only for Google ultra voices) |

Response (ready):

{
  "voice_id": "google:en-US-Gemini-Kore",
  "sample_url": "https://...",
  "voices_url": "https://aitts.theproductivepixel.com/voices?provider=google&voice=google:kore&lang=en-US&model=gemini-2.5-flash-tts",
  "expires_at": "2026-07-13T10:00:00Z",
  "expires_in": 604800,
  "content_type": "audio/mpeg"
}

voices_url is a /voices gallery deep-link pinned to the exact voice, returned beside sample_url (the raw audio URL) so you can hand the user both.

Format (v1.8.0+): voices_url uses the plain-name form: provider=X&family=Y&voice=<Name>&lang=<xx-YY>&model=<sub>?

Alternate: pass a full public voice_id as voice=<full> for a single-param deep-link (e.g. ?voice=google:en-US-Gemini-Kore).

Backward compat: legacy internal-canonical URLs still resolve — three-form cascade: full-public-id > legacy-canonical > plain-name.

Response (pending):

{
  "status": "pending",
  "voices_url": "https://aitts.theproductivepixel.com/voices?provider=google&voice=google:kore&lang=en-US&model=gemini-2.5-flash-tts",
  "retry_after": 3
}

The signed sample_url expiry is capped at 7 days (GCS V4 limit). The URL is returned with Cache-Control: no-store and is never logged.


get_job_text

| Param | Type | Required | Description | |-------|------|----------|-------------| | job_id | string | ✓ | Job ID |

Response:

{
  "text": "Hello world"
}

list_jobs

| Param | Type | Required | Description | |-------|------|----------|-------------| | page_size | integer (1–100) | — | Items per page (default 20) | | page_token | string | — | Pagination cursor | | source | api | ui | — | Filter by source | | status | completed | failed | pending | processing | — | Filter by status |

Response:

{
  "jobs": [
    {
      "job_id": "...",
      "status": "completed",
      "...": "..."
    }
  ],
  "next_page_token": "eyJ..."
}

delete_job_audio

| Param | Type | Required | Description | |-------|------|----------|-------------| | job_id | string | ✓ | Job ID |

Response:

{
  "shares_revoked": 0,
  "storage": null
}

update_job_metadata

| Param | Type | Required | Description | |-------|------|----------|-------------| | job_id | string | ✓ | Job ID | | tags | string[] | — | Replace tags array | | collection_id | string | null | — | Set or clear collection |

Response:

{
  "job_id": "uuid",
  "tags": ["podcast", "episode-1"],
  "collection_id": "col-abc"
}

create_share

| Param | Type | Required | Description | |-------|------|----------|-------------| | job_ids | string[] | — | Job IDs for snapshot share | | source_type | collection | tag | — | Source type | | source_id | string | — | Source ID | | share_mode | snapshot | live | — | Share mode | | auth_mode | none | password | access_code | — | Auth mode | | password | string | — | Password (if auth_mode=password) | | title | string | — | Share title | | allow_download | boolean | — | Allow download | | include_text | boolean | — | Include text excerpts | | show_voice | boolean | — | Show voice metadata | | show_model | boolean | — | Show model metadata | | show_provider | boolean | — | Show provider metadata | | show_language | boolean | — | Show language metadata | | show_expiry | boolean | — | Show expiry metadata | | show_track_meta | boolean | — | Show track metadata | | track_titles | object | — | Custom track titles | | track_order | string[] | — | Custom track order |

Response:

{
  "code": "abc123",
  "url": "https://aitts.theproductivepixel.com/share/audio/abc123",
  "item_count": 3
}

create_voice_share

Creates a shareable voice collection from caller-supplied voice references. The returned URL is built from the configured app origin, never the caller Origin header. Mirrors POST /api/v1/voice-shares.

| Param | Type | Required | Description | |-------|------|----------|-------------| | voiceRefs | VoiceRef[] (1–50) | ✓ | Voice references. Each is { id, language?, ultraModel? } |

Each VoiceRef field: id is a public voice_id (provider:language-Family-Name, as returned by search_voices / GET /api/v1/voices) — the share is canonicalized internally; language is an optional BCP-47 code the voice supports; ultraModel is an optional Gemini sub-model id.

Response:

{
  "code": "v1a2b3c4",
  "url": "https://aitts.theproductivepixel.com/voices/share/v1a2b3c4"
}

Errors: VALIDATION_ERROR, INVALID_VOICE_REF, INVALID_VOICE_LANGUAGE, INVALID_ULTRA_MODEL.


list_shares

| Param | Type | Required | Description | |-------|------|----------|-------------| | page_size | integer (1–100) | — | Items per page | | page_token | string | — | Pagination cursor |

Response:

{
  "shares": [
    {
      "code": "...",
      "title": "...",
      "item_count": 3,
      "auth_mode": "none",
      "share_mode": "snapshot",
      "is_permanent": false,
      "created_at": "..."
    }
  ],
  "next_page_token": null
}

get_share

| Param | Type | Required | Description | |-------|------|----------|-------------| | code | string | ✓ | Share code |

Response:

{
  "code": "abc123",
  "title": "Demo",
  "auth_mode": "none",
  "share_mode": "snapshot",
  "permanent": false,
  "allow_download": true,
  "item_count": 2,
  "created_at": "2026-05-20T10:00:00.000Z",
  "jobs": [
    {
      "job_id": "uuid",
      "voice_id": "en-US-Wavenet-D",
      "model_type": "premium",
      "created_at": "2026-05-20T09:00:00.000Z",
      "audio_bytes": 48000,
      "output_format": "mp3",
      "duration_seconds": 3.2,
      "estimated_duration_seconds": null,
      "duration_source": "probe",
      "duration_confidence": "exact",
      "audio_sample_rate_hertz": 44100,
      "sample_rate_hertz": 44100,
      "output_bitrate_kbps": 128
    }
  ]
}

update_share

| Param | Type | Required | Description | |-------|------|----------|-------------| | code | string | ✓ | Share code | | title | string | — | New title | | auth_mode | none | password | access_code | — | Auth mode | | password | string | — | Password | | allow_download | boolean | — | Allow download | | share_mode | snapshot | live | — | Share mode | | include_text | boolean | — | Include text | | show_voice | boolean | — | Show voice | | show_model | boolean | — | Show model | | show_provider | boolean | — | Show provider | | show_language | boolean | — | Show language | | show_expiry | boolean | — | Show expiry | | show_track_meta | boolean | — | Show track meta | | track_titles | object | — | Track titles | | track_order | string[] | null | — | Track order or null | | source_type | collection | tag | — | Source type | | source_id | string | — | Source ID |

Response:

{
  "code": "abc123",
  "title": "Updated",
  "auth_mode": "password",
  "updated_at": "..."
}

revoke_share

| Param | Type | Required | Description | |-------|------|----------|-------------| | code | string | ✓ | Share code |

Response:

{
  "revoked": true
}

bulk_revoke_shares

| Param | Type | Required | Description | |-------|------|----------|-------------| | codes | string[] (1–100) | ✓ | Share codes to revoke |

Response:

{
  "revoked_count": 3,
  "skipped": ["code-not-found"]
}

toggle_share_permanent

| Param | Type | Required | Description | |-------|------|----------|-------------| | code | string | ✓ | Share code |

Response:

{
  "code": "abc123",
  "permanent": true,
  "expires_at": null
}

update_track_order

| Param | Type | Required | Description | |-------|------|----------|-------------| | code | string | ✓ | Share code | | track_order | string[] | null | ✓ | Ordered job IDs or null to reset |

Response:

{
  "code": "abc123",
  "track_order": ["job-2", "job-1", "job-3"]
}

create_access_codes

| Param | Type | Required | Description | |-------|------|----------|-------------| | code | string | ✓ | Share code | | count | integer (1–100) | — | Number of codes (default 1) | | label | string | — | Label or label prefix | | expires_at | string (ISO date) | — | Expiration date | | max_uses | integer (≥1) | — | Max uses per code |

Response (count=1):

{
  "id": "doc-id",
  "code": "ABCD1234EFGH",
  "code_prefix": "ABCD",
  "label": "VIP",
  "active": true,
  "created_at": "...",
  "expires_at": null,
  "max_uses": 10
}

Response (count>1):

[
  {
    "id": "doc-1",
    "code": "ABCD1111AAAA"
  },
  {
    "id": "doc-2",
    "code": "ABCD2222BBBB"
  }
]

list_access_codes

| Param | Type | Required | Description | |-------|------|----------|-------------| | code | string | ✓ | Share code |

Response:

[
  {
    "id": "...",
    "code_prefix": "ABCD",
    "label": "VIP",
    "active": true,
    "uses": 3,
    "max_uses": 10,
    "created_at": "...",
    "expires_at": null,
    "last_used_at": null
  }
]

update_access_code

| Param | Type | Required | Description | |-------|------|----------|-------------| | code | string | ✓ | Share code | | access_code_id | string | ✓ | Access code document ID | | label | string | — | New label | | active | boolean | — | Active status | | expires_at | string | null | — | New expiry or null | | max_uses | integer | null | — | New max uses or null |

Response:

{
  "id": "doc-id",
  "code_prefix": "ABCD",
  "label": "Updated",
  "active": true,
  "uses": 3,
  "max_uses": 20,
  "created_at": "2026-05-01T00:00:00Z",
  "expires_at": null,
  "last_used_at": null
}

delete_access_code

| Param | Type | Required | Description | |-------|------|----------|-------------| | code | string | ✓ | Share code | | access_code_id | string | ✓ | Access code document ID |

Response:

{
  "deleted": true
}

export_access_codes

| Param | Type | Required | Description | |-------|------|----------|-------------| | code | string | ✓ | Share code |

Response:

[
  {
    "id": "ac-1",
    "code_prefix": "AB12",
    "label": "Reviewer",
    "active": true,
    "created_at": "...",
    "expires_at": null,
    "max_uses": 10,
    "uses": 3
  }
]

get_qr_code

| Param | Type | Required | Description | |-------|------|----------|-------------| | code | string | ✓ | Share code | | format | svg | png | ✓ | Image format | | preset | clean | branded | — | Visual preset | | include_access_code | boolean | — | Embed access code in URL | | access_code | string | — | Access code to embed |

Response:

{
  "data_uri": "data:image/png;base64,...",
  "format": "png"
}

list_collections

No parameters.

Response:

{
  "collections": [
    {
      "id": "col-abc",
      "name": "Podcast Episodes",
      "created_at": "...",
      "updated_at": "..."
    }
  ]
}

manage_collection

| Param | Type | Required | Description | |-------|------|----------|-------------| | action | create | rename | delete | ✓ | Operation | | name | string | — | Name (required for create/rename) | | collection_id | string | — | ID (required for rename/delete) |

Response (create/rename):

{
  "id": "col-id",
  "name": "New Name"
}

Response (delete):

{
  "deleted": true
}

list_tags

No parameters.

Response:

{
  "tags": [
    {
      "tag": "podcast",
      "count": 12
    },
    {
      "tag": "demo",
      "count": 3
    }
  ]
}

create_bookmark

| Param | Type | Required | Description | |-------|------|----------|-------------| | share_code | string | ✓ | Share code to bookmark |

Response:

{
  "created": true
}

list_bookmarks

| Param | Type | Required | Description | |-------|------|----------|-------------| | page_size | integer (1–100) | — | Items per page | | page_token | string | — | Pagination cursor |

Response:

{
  "bookmarks": [
    {
      "id": "bm-1",
      "share_code": "xyz789",
      "title": "Shared Audio",
      "personal_title": null,
      "collection_id": null,
      "created_at": "...",
      "last_opened_at": null,
      "effective_status": "active"
    }
  ],
  "next_page_token": null
}

delete_bookmark

| Param | Type | Required | Description | |-------|------|----------|-------------| | bookmark_id | string | ✓ | Bookmark ID (share code) |

Response:

{
  "deleted": true
}

manage_bookmark_collection

| Param | Type | Required | Description | |-------|------|----------|-------------| | action | create | rename | delete | ✓ | Operation | | name | string | — | Name (required for create/rename) | | collection_id | string | — | ID (required for rename/delete) |

Response (create/rename):

{
  "id": "col-id",
  "name": "Favorites"
}

Response (delete):

{
  "deleted": true
}

get_storage

No parameters.

Response:

{
  "used_bytes": 15728640,
  "cap_bytes": 104857600,
  "remaining_bytes": 89128960,
  "pending_reclaim_bytes": 0,
  "sync_status": "healthy"
}

list_storage_items

| Param | Type | Required | Description | |-------|------|----------|-------------| | page_size | integer (1–100) | — | Items per page | | page_token | string | — | Pagination cursor |

Response:

{
  "items": [
    {
      "job_id": "uuid",
      "status": "completed",
      "audio_bytes": 48000,
      "output_format": "ogg_opus",
      "created_at": "...",
      "storage_tier": "permanent"
    }
  ],
  "next_page_token": null
}

bulk_delete_storage

| Param | Type | Required | Description | |-------|------|----------|-------------| | job_ids | string[] (1–100) | ✓ | Job IDs to delete |

Response:

{
  "deleted_count": 3,
  "skipped_count": 1,
  "skipped": [
    {
      "job_id": "uuid",
      "reason": "job_in_progress"
    }
  ]
}

get_pricing

No parameters.

Response:

{
  "pricing_available": true,
  "currency": "USD",
  "plans": [
    {
      "id": "pro",
      "title": "Pro",
      "price": "$20/month",
      "highlights": ["Monthly credits"]
    }
  ],
  "usage_rates": [
    {
      "provider": "google",
      "voice_class": "premium",
      "rate_per_1k_bytes": 0.01
    }
  ],
  "pricing_url": "https://aitts.theproductivepixel.com/pricing",
  "guidance": "Use estimate_cost for an exact quote on a specific generation."
}

OAuth accounts that still need account pricing setup return pricing_available: false and requires_account_pricing_setup: true.


estimate_cost

| Param | Type | Required | Description | |-------|------|----------|-------------| | text | string (1–500000) | ✓ | Text to estimate | | model_type | premium | ultra | — | Model type | | voice_id | string | — | Voice ID | | output_format | wav | mp3 | ogg_opus | — | Output format |

Response:

{
  "estimated_cost": 2.5,
  "currency": "USD",
  "chars_charged": 500,
  "model_type": "ultra",
  "provider": "google",
  "output_format": "ogg_opus"
}

get_usage

No parameters.

Response:

{
  "account_type": "standard",
  "credits_balance": 4250,
  "balance": {
    "amount": 4250,
    "currency": "USD",
    "formatted": "$4,250.00"
  }
}

Pagination

Paginated tools accept page_size (1–100, default 20) and page_token (opaque cursor). Results include next_page_token (null when exhausted). The three personalization lists retain the omission compatibility contract described above.

Idempotency

Pass idempotency_key to generate_speech for safe retries:

  • Same key + same body → cached result (no duplicate generation)
  • Same key + different body → IDEMPOTENCY_KEY_REUSE (409)
  • Key still processing → REQUEST_IN_PROGRESS (409)

Rate Limiting

Two buckets: read (higher limits — queries, lists) and generate (lower limits — mutations, generation). Per-account enforcement. Exceeding returns an error message at tool level; HTTP 429 at transport level.

Permissions

| Permission | Tools | |-----------|-------| | voices:list | search_voices | | tts:generate | generate_speech, get_voice_sample_url | | tts:status | get_job_status, get_audio_link | | jobs:read | list_jobs, get_job_text | | jobs:write | delete_job_audio, update_job_metadata | | shares:read | list_shares, get_share, list_access_codes, export_access_codes | | shares:write | create_share, create_voice_share, update_share, revoke_share, bulk_revoke_shares, toggle_share_permanent, update_track_order, create_access_codes, update_access_code, delete_access_code, get_qr_code | | library:read | list_collections, list_tags, list_bookmarks | | library:write | manage_collection, create_bookmark, delete_bookmark, manage_bookmark_collection | | storage:read | get_storage, list_storage_items | | storage:write | bulk_delete_storage | | pricing:estimate | get_pricing, estimate_cost | | usage:read | get_usage |

Error Reference

| Code | Status | Description | |------|--------|-------------| | VALIDATION_ERROR | 400 | Invalid input parameters | | PAGINATION_REQUIRED | 400 | No-param personalization list exceeds 100 raw records; no partial list is returned | | INVALID_VOICE | 400 | Voice ID not found or invalid | | PROVIDER_DISABLED | 400 | Provider unknown or disabled | | INSUFFICIENT_CREDITS | 402 | Not enough credits | | STORAGE_CAP_EXCEEDED | 403 | Storage quota full | | FORBIDDEN | 403 | Enterprise-only or ownership failed | | INVALID_WEBHOOK_URL | 400 | Webhook URL validation failed | | ENTERPRISE_TIER_REQUIRED | 400 | Enterprise pricing without tier | | STREAM_NOT_SUPPORTED | 400 | Voice/provider doesn't support streaming | | SPEED_OUT_OF_RANGE | 400 | Speaking rate exceeds stream limit | | STREAM_BITRATE_NOT_SUPPORTED | 400 | Bitrate in stream mode | | STREAM_FORMAT_MISMATCH | 400 | Format not supported for stream | | MAINTENANCE | 503 | API in maintenance mode | | IDEMPOTENCY_KEY_REUSE | 409 | Key reused with different body | | REQUEST_IN_PROGRESS | 409 | Key still processing | | JOB_NOT_FOUND | 404 | Job doesn't exist or not owned | | JOB_IN_PROGRESS | 409 | Cannot delete pending/processing job | | TEXT_UNAVAILABLE | 410 | Job expired or text not stored | | SHARE_NOT_FOUND | 404 | Share doesn't exist or not owned | | AMBIGUOUS_SHARE_INPUT | 400 | Both job_ids and source provided | | INVALID_SHARE_SOURCE | 400 | Invalid source configuration | | PASSWORD_REQUIRED | 400 | auth_mode=password without password | | INVALID_AUTH_MODE_PASSWORD_COMBO | 400 | auth_mode and password combination invalid | | SOURCE_IMMUTABLE | 400 | Cannot change source on source-backed share | | ACCESS_CODE_NOT_FOUND | 404 | Access code doesn't exist | | COLLECTION_NOT_FOUND | 404 | Collection doesn't exist or not owned | | BOOKMARK_NOT_FOUND | 404 | Bookmark or collection doesn't exist | | ALREADY_EXISTS | 409 | Bookmark already exists | | FREE_TIER_LIMIT_EXCEEDED | 403 | Free tier limit reached (>1000 chars per request) |

Error envelope: { error: "message", code?: "ERROR_CODE", status?: 404 }

Configuration

| Variable | Required | Description | |----------|----------|-------------| | AITTSM_API_KEY | Yes | API key (starts with tts_) | | AITTSM_BASE_URL | No | Custom API base URL |

Requirements

Links

Changelog

1.8.0

  • Added get_pricing (pricing:estimate, read bucket) to the local MCP stdio proxy. It mirrors GET /api/v1/pricing and returns account-correct plan prices plus rate summaries.
  • Added 18 REST-backed personalization proxy tools for preferences, modes, projects, and project items. The hosted remote MCP endpoint exposes one additional native-only apply_mode tool.
  • Tool count 37 -> 56 for the local stdio proxy; hosted remote MCP exposes 57 tools.
  • get_voice_sample_url and create_voice_share now emit voices_url in the NEW plain-name form: provider=X&family=Y&voice=<Name>&lang=<xx-YY>[&model=<sub>]. Human-readable, stable across internal id changes.
  • The /voices deep-link now accepts three URL forms (cascade: full-public-id > legacy-canonical > plain-name). All existing URLs continue to resolve (backward compatible).
  • Full public voice_id as a single-param deep-link: ?voice=google:en-US-Gemini-Kore (self-qualifying, no other params needed).

1.7.0

  • Added create_voice_share (shares:write) — create a shareable voice collection from voiceRefs ({ id, language?, ultraModel? }); the returned URL is built from the configured app origin, never the caller Origin. Mirrors POST /api/v1/voice-shares.
  • Added get_voice_sample_url (tts:generate, generation-class) — fetch a sample-audio URL for a voice; synthesizes on a cache miss and returns a pending status until ready. Mirrors GET /api/v1/voices/{voice_id}/sample-url.
  • search_voices now accepts a free-text q param — fuzzy, typo-tolerant search across voice_id, family, name, language, and provider, so a partial term like Leda matches without the full id.
  • Tool count 35 → 37.

1.6.0

  • get_audio_link now returns audio_url (with a deprecated signed_url alias) plus expires_in, content_type, and audio_bytes alongside expires_at, job_id, and audio_endpoint.
  • get_audio_link now enforces the audio-expiry gate: calling it on a job that is not completed or whose audio has expired returns an error (409 AUDIO_NOT_READY / 410 AUDIO_EXPIRED) instead of a bare { audio_endpoint, job_id } payload.

License

MIT