# Connecting AI assistants (MCP) (https://voicelabs.now/docs/connecting-ai-assistants)



VoiceLabs runs a remote **MCP (Model Context Protocol) server**, so an AI agent can work with your VoiceLabs data on your behalf after you sign in and consent. It is listed on the official MCP Registry as `now.voicelabs/voicelabs`.

* **Endpoint:** `https://app.voicelabs.now/api/mcp`
* **Transport:** streamable HTTP, stateless (no MCP sessions)
* **Auth:** OAuth 2.1 — authorization code with PKCE and dynamic client registration. The agent signs in as you and only gets what you consent to.

## Connect from any MCP client [#connect-from-any-mcp-client]

<div className="not-prose my-4 flex flex-wrap gap-2">
  <a href="https://chatgpt.com/plugins/plugin_asdk_app_6a7ae345f2dc8191bd52864d8358632a" target="_blank" rel="noopener noreferrer">
    <img className="dark:hidden" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-chatgpt-light.svg" alt="Add to ChatGPT" height="32" />

    <img className="hidden dark:inline-block" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-chatgpt.svg" aria-hidden="true" height="32" />
  </a>

  <a href="https://cursor.com/install-mcp?name=voicelabs&config=eyJ1cmwiOiJodHRwczovL2FwcC52b2ljZWxhYnMubm93L2FwaS9tY3AifQ%3D%3D" target="_blank" rel="noopener noreferrer">
    <img className="dark:hidden" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-cursor-light.svg" alt="Add to Cursor" height="32" />

    <img className="hidden dark:inline-block" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-cursor.svg" aria-hidden="true" height="32" />
  </a>

  <a href="https://insiders.vscode.dev/redirect/mcp/install?name=voicelabs&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fapp.voicelabs.now%2Fapi%2Fmcp%22%7D" target="_blank" rel="noopener noreferrer">
    <img className="dark:hidden" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-vscode-light.svg" alt="Add to VS Code" height="32" />

    <img className="hidden dark:inline-block" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-vscode.svg" aria-hidden="true" height="32" />
  </a>

  <a href="https://kiro.dev/launch/mcp/add?name=voicelabs&config=%7B%22url%22%3A%22https%3A%2F%2Fapp.voicelabs.now%2Fapi%2Fmcp%22%2C%22disabled%22%3Afalse%2C%22autoApprove%22%3A%5B%5D%7D" target="_blank" rel="noopener noreferrer">
    <img className="dark:hidden" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-kiro-light.svg" alt="Add to Kiro" height="32" />

    <img className="hidden dark:inline-block" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-kiro.svg" aria-hidden="true" height="32" />
  </a>

  <a href="https://replit.com/integrations?mcp=eyJkaXNwbGF5TmFtZSI6IlZvaWNlTGFicyIsImJhc2VVcmwiOiJodHRwczovL2FwcC52b2ljZWxhYnMubm93L2FwaS9tY3AifQ%3D%3D" target="_blank" rel="noopener noreferrer">
    <img className="dark:hidden" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-replit-light.svg" alt="Add to Replit" height="32" />

    <img className="hidden dark:inline-block" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-replit.svg" aria-hidden="true" height="32" />
  </a>

  <a href="#connect-antigravity">
    <img className="dark:hidden" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-antigravity-light.svg" alt="Add to Antigravity" height="32" />

    <img className="hidden dark:inline-block" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-antigravity.svg" aria-hidden="true" height="32" />
  </a>

  <a href="#connect-gemini">
    <img className="dark:hidden" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-gemini-light.svg" alt="Add to Gemini" height="32" />

    <img className="hidden dark:inline-block" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-gemini.svg" aria-hidden="true" height="32" />
  </a>

  <a href="#connect-chatgpt">
    <img className="dark:hidden" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-codex-light.svg" alt="Add to Codex" height="32" />

    <img className="hidden dark:inline-block" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-codex.svg" aria-hidden="true" height="32" />
  </a>

  <a href="#connect-chat-apps">
    <img className="dark:hidden" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-perplexity-light.svg" alt="Add to Perplexity" height="32" />

    <img className="hidden dark:inline-block" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-perplexity.svg" aria-hidden="true" height="32" />
  </a>

  <a href="#connect-chat-apps">
    <img className="dark:hidden" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-mistral-light.svg" alt="Add to Mistral" height="32" />

    <img className="hidden dark:inline-block" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-mistral.svg" aria-hidden="true" height="32" />
  </a>

  <a href="#connect-chat-apps">
    <img className="dark:hidden" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-grok-light.svg" alt="Add to Grok" height="32" />

    <img className="hidden dark:inline-block" src="https://cdn.jsdelivr.net/gh/DevinoSolutions/mcp-servers@main/badges/add-to-grok.svg" aria-hidden="true" height="32" />
  </a>
</div>

The Claude button opens claude.ai's custom-connector dialog pre-filled and the ChatGPT button opens VoiceLabs' published ChatGPT app; the others install directly or jump to the instructions.

VoiceLabs' MCP server is a hosted Streamable HTTP endpoint with OAuth 2.1 sign-in. There is nothing to install or run: point the client at the URL below and sign in to VoiceLabs when the browser opens. Every client that supports remote MCP servers with OAuth works; the ones below are the common ones.

```
https://app.voicelabs.now/api/mcp
```

Any such client discovers the authorization server via `/.well-known/oauth-protected-resource`, registers itself, and walks you through consent in the browser.

<Callout type="info" title="Native and hosted clients only for now">
  Browser-based (cross-origin) MCP clients that run inside a web page are not supported yet. Native clients (Claude Code, Claude Desktop, Cursor, VS Code and the like) and hosted connectors that call the server from their own backend (claude.ai, ChatGPT, Gemini, Perplexity) are not affected.
</Callout>

### Claude (claude.ai, Claude Desktop, Claude Code) [#claude-claudeai-claude-desktop-claude-code]

* **Fastest: [Add to Claude](https://claude.ai/customize/connectors?modal=add-custom-connector\&connectorName=VoiceLabs\&connectorUrl=https%3A%2F%2Fapp.voicelabs.now%2Fapi%2Fmcp)** opens claude.ai's *Add custom connector* dialog with VoiceLabs pre-filled. Click *Add*, then sign in. The connector syncs to Claude Desktop and mobile.
* **claude.ai / Claude Desktop:** Settings → Connectors → *Add custom connector*, paste the URL, then sign in. (If VoiceLabs is already in the [Claude connector directory](https://claude.ai/directory), click *Connect* there instead.)
* **Claude Code:** `claude mcp add --transport http voicelabs https://app.voicelabs.now/api/mcp` then `/mcp` to sign in.

### ChatGPT and Codex [#connect-chatgpt]

* **ChatGPT — fastest:** [open VoiceLabs' ChatGPT app](https://chatgpt.com/plugins/plugin_asdk_app_6a7ae345f2dc8191bd52864d8358632a), add it and sign in.
* **Manual (ChatGPT):** Settings → Apps & Connectors → *Create* (developer mode), name it VoiceLabs, paste the URL, authentication *OAuth*.
* **Codex CLI:** add to `~/.codex/config.toml`:

```toml
[mcp_servers.voicelabs]
url = "https://app.voicelabs.now/api/mcp"
```

### Cursor [#cursor]

One click: [Add to Cursor](https://cursor.com/install-mcp?name=voicelabs\&config=eyJ1cmwiOiJodHRwczovL2FwcC52b2ljZWxhYnMubm93L2FwaS9tY3AifQ%3D%3D). Or in `.cursor/mcp.json`:

```json
{ "mcpServers": { "voicelabs": { "url": "https://app.voicelabs.now/api/mcp" } } }
```

### VS Code and GitHub Copilot [#vs-code-and-github-copilot]

One click: [Add to VS Code](https://insiders.vscode.dev/redirect/mcp/install?name=voicelabs\&config=%7B%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fapp.voicelabs.now%2Fapi%2Fmcp%22%7D). Or run *MCP: Add Server* → *HTTP* and paste the URL, which writes to `.vscode/mcp.json`:

```json
{ "servers": { "voicelabs": { "type": "http", "url": "https://app.voicelabs.now/api/mcp" } } }
```

### Google Antigravity [#connect-antigravity]

Install the plugin from the repository (or from the Antigravity Marketplace once listed):

```bash
agy plugin install https://github.com/DevinoSolutions/mcp-servers/antigravity-plugins/voicelabs
```

Or add the server by hand in `~/.gemini/config/mcp_config.json` (Antigravity requires the `serverUrl` key):

```json
{ "mcpServers": { "voicelabs": { "serverUrl": "https://app.voicelabs.now/api/mcp" } } }
```

### Gemini [#connect-gemini]

* **Gemini app:** Settings → Connected apps → *Custom apps* → *Add a custom app*, paste the URL, then mention `@VoiceLabs` in a chat. (Custom apps are currently limited to personal Google accounts in the US.)
* **Gemini CLI:** `gemini extensions install https://github.com/DevinoSolutions/mcp-servers` then `/mcp auth voicelabs`, or add `{ "mcpServers": { "voicelabs": { "httpUrl": "https://app.voicelabs.now/api/mcp" } } }` to `~/.gemini/settings.json`.

### Kiro [#kiro]

One click: [Add to Kiro](https://kiro.dev/launch/mcp/add?name=voicelabs\&config=%7B%22url%22%3A%22https%3A%2F%2Fapp.voicelabs.now%2Fapi%2Fmcp%22%2C%22disabled%22%3Afalse%2C%22autoApprove%22%3A%5B%5D%7D). Or in `.kiro/settings/mcp.json`:

```json
{ "mcpServers": { "voicelabs": { "url": "https://app.voicelabs.now/api/mcp" } } }
```

### Perplexity, Mistral Le Chat, Grok [#connect-chat-apps]

* **Perplexity** (Pro, Max, Enterprise): Settings → Connectors → *Custom connector*, paste the URL, authentication *OAuth*.
* **Mistral Le Chat** (all plans): Intelligence → Connectors → *Add Connector* → *Custom MCP Connector*, paste the URL.
* **Grok:** grok.com → Connectors → *New connector* → *Custom*, paste the URL.

### Other clients [#other-clients]

* **Replit:** [Add to Replit](https://replit.com/integrations?mcp=eyJkaXNwbGF5TmFtZSI6IlZvaWNlTGFicyIsImJhc2VVcmwiOiJodHRwczovL2FwcC52b2ljZWxhYnMubm93L2FwaS9tY3AifQ%3D%3D), or Integrations → *Add MCP server*.
* **Devin, Windsurf, Zed, JetBrains Junie, Cline, OpenCode:** add a remote (HTTP) MCP server with the URL above; each prompts for the OAuth sign-in on first use. Cline can also install it from the [Cline marketplace entry](https://github.com/DevinoSolutions/mcp-servers/tree/main/voicelabs) using its `llms-install.md`.

Tokens are scoped to what you approve on the consent screen; a tool you did not grant is never shown to the assistant. Revoke access at any time from [app.voicelabs.now/connections](https://app.voicelabs.now/connections) — see [Consent, and revoke, at any time](#consent-and-revoke-at-any-time).

## Scopes [#scopes]

| Scope                | Grants                                                                                                                                  |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `voice:read`         | Read your voices, profiles, and past generations.                                                                                       |
| `voice:generate`     | Generate speech, transcribe audio, and add built-in (preset) voices to your account on your behalf. Cannot clone a real person's voice. |
| `voice:clone`        | Create a cloned voice from a recording you provide                                                                                      |
| `voice:delete`       | Delete voice profiles and captures from your account.                                                                                   |
| `billing:manage`     | See your plan and usage, start an upgrade, and cancel, resume or refund your subscription.                                              |
| `account:manage`     | Read and change your profile and notification preferences, export your data, and delete your account.                                   |
| `connections:manage` | See and disconnect connected apps (Slack, the browser extension, AI apps) and get links to connect more.                                |
| `support:write`      | Contact VoiceLabs support and report content on your behalf.                                                                            |
| `keys:manage`        | Create, rotate and revoke API keys.                                                                                                     |

The consent screen groups these into permission tiers with one checkbox each, all ticked by default; untick a tier to connect an agent without it. An agent that needs more later calls `request_scopes` and you approve the extra tier on the same screen.

OIDC scopes (`openid`, `profile`, `email`, `offline_access`) handle identity and token refresh; they grant no API access.

## Tools [#tools]

Read tools (scope `voice:read`, read-only and idempotent):

| Tool                  | What it does                                                                                                                      |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `list_voice_profiles` | Your voice profiles (cloned voices and presets) with language, engine, and usage counts.                                          |
| `list_captures`       | Your recent captures (dictations, recordings, uploads) with transcripts. Paginated: `limit` (1–200, default 50) and `offset`.     |
| `get_generation`      | Poll a generation by `generationId`; returns `status` (`generating`, `completed`, or `failed`) and, once complete, the audio URL. |

Generation tools (scope `voice:generate`; each call creates new data, none are destructive):

| Tool                   | What it does                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `speak`                | Synthesize speech from `text` in one of your voices (`profileId` or `profileName`, optional `language`). Async: returns a `generationId` with status `generating` — poll `get_generation` for the audio URL.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `transcribe`           | Transcribe an audio file with VoiceLabs' Whisper. Give the audio exactly one way: `audioUrl` (a public https URL serving the file itself — wav, mp3, m4a, aac, ogg, flac, aiff or webm; no redirects, up to 10 MiB) **or** `audioBase64` (the file's real bytes, up to 10 MiB of base64); optional `language`. Returns the transcript and the created capture id. A payload that is not real audio — under 1 KiB, a bare header, made-up placeholder bytes, or a recording with no sound in it — is refused as `INVALID_REQUEST` without being transcribed. Some assistants (ChatGPT among them) cannot pass a file you attached in the chat as base64; give them a public link to the file, or upload it on the Captures page of the [VoiceLabs studio](https://app.voicelabs.now/studio) instead. |
| `ensure_voice_profile` | Make sure a voice profile with the given name exists, creating it from a built-in preset voice if it does not. Idempotent — calling it again with the same name returns the same profile with `created: false` and creates nothing. Use it when `list_voice_profiles` comes back empty, or when `speak` reports `NOT_FOUND` for a voice. It cannot clone a real person's voice; that is `clone_voice_profile`, below.                                                                                                                                                                                                                                                                                                                                                                               |

Cloning tool (scope `voice:clone` — its own consent question, because it needs a recording of a real person; not idempotent, not destructive):

| Tool                  | What it does                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `clone_voice_profile` | Create a cloned voice from a recording: `name`, optional `language`, `referenceText` (the exact words spoken), and the audio as `audioBase64` (inline) **or** `audioUrl` (a public https URL, no redirects, up to 10 MiB). The recording must be 2–30 seconds of clear speech. Returns the new profile's `id`, `name`, `voiceType`, `engine`, `language` and `sampleId` — pass the id to `speak`. A duplicate name is refused, so check `list_voice_profiles` first. On the free plan the account keeps one saved cloned voice (a taken slot comes back as `QUOTA_EXCEEDED` naming the way out); Pro is unlimited. |

More tools, by scope:

| Scope                | Tools                                                                                                                                                                       |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `voice:read`         | `billing_status`, `list_plans`, `get_profile`, `get_referral_summary`, `list_support_tickets`, `list_connections`, `list_api_keys`, `list_granted_scopes`, `request_scopes` |
| `voice:delete`       | `delete_voice_profile`, `delete_capture` (both ask you to confirm first)                                                                                                    |
| `billing:manage`     | `upgrade_plan` (a one-time checkout link), `manage_subscription` (cancel, refund, resume, billing portal)                                                                   |
| `account:manage`     | `update_profile`, `update_notification_prefs`, `export_data`, `delete_account` (asks you to confirm first)                                                                  |
| `connections:manage` | `get_connect_links`, `disconnect` (asks you to confirm first), `reverify`                                                                                                   |
| `support:write`      | `create_support_ticket`, `report_content`                                                                                                                                   |
| `keys:manage`        | `create_api_key`, `rotate_api_key`, `revoke_api_key`, `set_api_key_ceiling`                                                                                                 |

Anything that deletes, cancels, refunds, disconnects, rotates or revokes asks for one confirmation first. Payment is never taken inside a tool call: upgrade and billing-portal tools return a single-use link you open yourself.

Because `speak` is async and polling happens via `get_generation` (a `voice:read` tool), clients that generate should request **both** `voice:generate` and `voice:read`.

**Code Mode.** Alongside the 33 tools, the server registers two that orchestrate several operations in one round trip: `search_tools` lists the tools your connection can reach as TypeScript declarations, and `execute_typescript` runs a short program that calls them. A call made that way enforces the same scopes and argument rules as calling the tool directly — it saves round trips and never grants more.

## Discovery endpoints [#discovery-endpoints]

An MCP client finds everything it needs from two metadata documents, so nothing has to be configured by hand:

| Endpoint                                                           | What it is                                                                                   |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
| `https://app.voicelabs.now/.well-known/oauth-protected-resource`   | RFC 9728 protected-resource metadata — points clients at the authorization server.           |
| `https://app.voicelabs.now/.well-known/oauth-authorization-server` | OAuth 2.1 authorization-server metadata, including the dynamic client registration endpoint. |

A request without a valid OAuth access token gets a `401` with a `WWW-Authenticate` header pointing at that resource metadata.

## Errors and quotas [#errors-and-quotas]

Generation requires a signed-in VoiceLabs account — there is no guest mode. Free accounts generate against a small monthly allowance (the exact figure comes back from `billing_status`), past which an active trial or subscription is needed.

Generation is metered server-side by your plan. Tool failures come back as structured execution errors, never as raw upstream responses. Each one has a `code` and a `message` that says what to change:

| Code                    | Meaning                                                                                                                                                                                                                                                                                               | What the agent should do                                                                            |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `SCOPE_MISSING`         | The connection was not granted the scope this tool needs (`requiredScope` names it).                                                                                                                                                                                                                  | Reconnect with that permission.                                                                     |
| `QUOTA_EXCEEDED`        | The account's monthly allowance is spent, or a plan limit (such as the free plan's one saved cloned voice) is reached. `docs` points at the connections page.                                                                                                                                         | Stop; the message says when the limit lifts, or what would lift it.                                 |
| `NOT_FOUND`             | The voice profile or generation named does not exist on this account. For `speak`, the message names what was looked up and lists the account's voices with their ids.                                                                                                                                | Call again with one of the listed ids (or the `generationId` `speak` returned).                     |
| `INVALID_REQUEST`       | VoiceLabs understood the call and refused it — no voice named, a voice the engine cannot use, a name already taken, audio it cannot decode. The message names which.                                                                                                                                  | Change the arguments. Retrying unchanged fails again.                                               |
| `PAYLOAD_TOO_LARGE`     | The inline recording (`audioBase64`) is bigger than the tool accepts. `maxBytes` gives the limit in bytes of base64 text. A whole request body over the transport limit never reaches a tool: it is answered with HTTP 413 at the transport, and the JSON-RPC error carries `maxBytes` in its `data`. | Send a shorter or re-encoded recording, or pass `audioUrl` instead. Retrying unchanged fails again. |
| `RATE_LIMITED`          | The account hit the per-minute or hourly request ceiling (it applies to transcription). `retryAfterSeconds` says how long to wait. Nothing was charged.                                                                                                                                               | Wait `retryAfterSeconds`, then slow down.                                                           |
| `UPSTREAM_UNAUTHORIZED` | The account's entitlement could not be confirmed.                                                                                                                                                                                                                                                     | Reconnect VoiceLabs.                                                                                |
| `TOOL_EXECUTION_FAILED` | A fault on VoiceLabs' side.                                                                                                                                                                                                                                                                           | Retry later.                                                                                        |

**Arguments are strict.** Each tool accepts exactly the arguments listed above, spelled as shown (camelCase: `profileId`, not `profile_id`). An argument a tool does not declare is refused before anything runs, with a message that names it and the accepted ones — for example ``Unknown argument `profile_id`; accepted: text, profileId, profileName, language. Did you mean `profile_id` → `profileId`?`` — rather than being silently ignored.

## Audio retrieval [#audio-retrieval]

The `audioUrl` returned by `get_generation` is a short-lived capability URL: its signature grants access to that one generation's audio only, for you only, for about 15 minutes. An expired or tampered link returns `403`, and polling `get_generation` again always mints a fresh one. No general credential ever appears in a URL.

## Consent, and revoke, at any time [#consent-and-revoke-at-any-time]

Every connection starts with an explicit consent screen in your browser that names the agent and the scopes it asked for. Disconnect an agent at any time from [app.voicelabs.now/connections](https://app.voicelabs.now/connections). Revocation is immediate: the next request from that agent is rejected, even if its access token has not yet expired.

Prefer to call the API yourself rather than through an agent? The same capabilities are available over plain HTTP — see the [Developer API](/api).
