# How do I connect TubeTranscript to AI agents with the Model Context Protocol?

TubeTranscript runs two stateless **remote MCP servers** over **Streamable HTTP**: **`/api/mcp`** fetches YouTube transcripts and **`/api/mcp/tools`** turns them into hooks, mindmaps and blog drafts. Register both in Cursor, Claude Code, Windsurf or n8n with the same **Pro plan API key**. Captioned videos return in seconds via the **Tier-1 Fast Path**; videos without captions are transcribed from audio.

To fetch transcripts from scripts or n8n HTTP nodes instead of an agent, use the [REST API](https://tubetranscript.io/youtube-transcript-api) with the same key.

| Server     | URL                                       | Tools                                                          |
| ---------- | ----------------------------------------- | -------------------------------------------------------------- |
| Transcript | `https://tubetranscript.io/api/mcp`       | `get_transcript`                                               |
| AI tools   | `https://tubetranscript.io/api/mcp/tools` | `extract_hooks`, `create_mindmap`, `generate_brand_voice_blog` |

## Which MCP tools does TubeTranscript expose?

**`get_transcript`** fetches a video and stores its transcript as a **job** owned by your Pro team, then returns the transcript and that job's **`job_id`**. The AI tools take that **`job_id`**, read the job's transcript and generate output with **Google Gemini**. They never fetch or transcribe videos themselves.

| Tool                        | Server           | Input                               | Output (`structuredContent`)                                                   |
| --------------------------- | ---------------- | ----------------------------------- | ------------------------------------------------------------------------------ |
| `get_transcript`            | `/api/mcp`       | `video_url` or `video_id`           | `{ "video_id", "status", "job_id", "transcript" }`                             |
| `extract_hooks`             | `/api/mcp/tools` | `job_id`                            | `{ "hooks": [{ "start_time", "end_time", "hook_title", "summary" }] }`, max 10 |
| `create_mindmap`            | `/api/mcp/tools` | `job_id`                            | `{ "mermaid": "<Mermaid mindmap source>" }`                                    |
| `generate_brand_voice_blog` | `/api/mcp/tools` | `job_id`, `brand_voice_id` (UUID)   | `{ "blog": "<Markdown>" }`                                                     |
| `generate_chapters`         | `/api/mcp/tools` | `job_id`, `style`, `count`          | `{ "chapters": [{ "start", "clock", "title" }], "text" }`                     |
| `generate_description`      | `/api/mcp/tools` | `job_id`, `tone`, `cta`, `chapters` | `{ "description", "tags", "hashtags", "chars" }`                            |
| `find_moments`              | `/api/mcp/tools` | `job_id`, `length`                  | `{ "moments": [{ "start", "end", "hook", "reason", "quote" }] }`            |
| `analyze_hook`              | `/api/mcp/tools` | `job_id`, `topic`                   | `{ "opening", "hookType", "parts", "pace", "rewrites" }`                   |
| `translate_subtitles`       | `/api/mcp/tools` | `job_id`, `target_languages`, `format`, `glossary` | `{ "outputs": [{ "language", "text" }], "failed" }`         |

The five creator tools (`generate_chapters` to `translate_subtitles`) take the same options as the [REST creator endpoint](/docs/api) and follow its limits: one creator-tool AI run each (30 per hour per team, one per language for translations), charged only when a result is returned.

The typical agent workflow takes two or three calls:

1. Call `get_transcript` with the video URL or ID.
2. If `status` is `"processing"`, wait about **60 seconds** and call `get_transcript` again with the same video until `status` is `"completed"`.
3. Pass the returned `job_id` to any AI tool, such as `extract_hooks`, `generate_chapters` or `translate_subtitles`.

### What does get_transcript return?

**`get_transcript`** accepts **`video_url`** (any YouTube URL, max **2,048 characters**) or **`video_id`** (the **11-character** YouTube ID), or both if they match. It returns **`structuredContent`** with four fields, plus text content blocks for clients without structured-content support: the transcript as **clean plain text** (one paragraph per caption chunk, no timestamps, normalized for **RAG**), then a `job_id: …` line. For timed chunks, call the REST endpoint [`POST /api/v1/extract`](https://tubetranscript.io/docs/api.md) instead.

```json
{ "name": "get_transcript", "arguments": { "video_url": "https://www.youtube.com/watch?v=jNQXAC9IVRw" } }
```

```json
{
  "video_id": "jNQXAC9IVRw",
  "status": "completed",
  "job_id": "71d2b675-3bc5-48a8-8b10-7e2b7f7f0011",
  "transcript": "First paragraph of the transcript…

Second paragraph…"
}
```

| Field        | Type           | Description                                                                                       |
| ------------ | -------------- | ------------------------------------------------------------------------------------------------- |
| `video_id`   | string         | The 11-character YouTube ID.                                                                      |
| `status`     | string         | `completed`, or `processing` while a video without captions is transcribed from audio.           |
| `job_id`     | string \| null | Team job holding the transcript; input for the AI tools. `null` only if storing the job failed.  |
| `transcript` | string         | Plain-text transcript; empty while `status` is `processing`.                                      |

### What happens when I call get_transcript for the same video again?

Repeat calls are cheap and safe. TubeTranscript resolves the video in this order:

| Situation                                                  | `status`     | `job_id`                                           |
| ---------------------------------------------------------- | ------------ | -------------------------------------------------- |
| Your team already has a completed job for the video        | `completed`  | That job; the same `job_id` on every call          |
| Another user published a public transcript of the video    | `completed`  | A new private copy in your team                    |
| Your team's audio transcription is still running           | `processing` | That job, so no second transcription is started    |
| The video has captions                                     | `completed`  | A new job holding the captions                     |
| The video has no captions                                  | `processing` | A new audio transcription job                      |

Two calls for the same new video that arrive **at the same time** fetch the captions once but each store their own job, so they return **different `job_id`s with the same transcript**; without captions they share one audio job. If an audio transcription fails, the next call starts a new one. A worker outage returns **`isError: true`** with a plain-text message; retry later.

### When can I call the AI tools?

Call them with a **`job_id`** whose `status` was `"completed"`: one returned by **`get_transcript`**, or the ID of a job created in the web app, or a non-null `data.job_id` from [`POST /api/v1/extract`](https://tubetranscript.io/docs/api.md). The job must belong to the API key's team; a video ID or URL in place of `job_id` is rejected as an invalid argument. Each result is **stored per job**: calling a tool again with the same `job_id` returns the stored result without calling the model (a blog is regenerated when another Brand Voice profile is used or the profile text changed). Outputs are returned as **`structuredContent`** plus a JSON text block.

| Tool                        | Behavior on thin source material                              |
| --------------------------- | ------------------------------------------------------------- |
| `extract_hooks`             | Empty `hooks` array (not an error).                           |
| `create_mindmap`            | Root node states that the source information is insufficient. |
| `generate_brand_voice_blog` | Empty `blog` string.                                          |

### How are invalid arguments and server errors reported?

Both servers validate **`tools/call`** arguments before any quota is spent. A call with missing, mistyped or unknown arguments, or an unknown tool name, returns **HTTP 400** with JSON-RPC error **`-32602`**; **`error.data.issues`** lists each problem (path and message) so an agent can correct the call and retry.

```json
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32602, "message": "Invalid arguments for tool extract_hooks: job_id: Required", "data": { "code": "INVALID_ARGUMENTS", "tool": "extract_hooks", "issues": [{ "code": "invalid_type", "expected": "string", "received": "undefined", "path": ["job_id"], "message": "Required" }], "formErrors": [], "fieldErrors": { "job_id": ["Required"] } } } }
```

A tool that runs but cannot complete returns **HTTP 200** with **`result.isError: true`** and a JSON text block `{ "error": "<CODE>", "message": "…" }`, so always check `isError`:

| `error`                 | Meaning                                                                           |
| ----------------------- | --------------------------------------------------------------------------------- |
| `JOB_NOT_FOUND`         | Unknown `job_id`, or a job of another team.                                       |
| `JOB_NOT_READY`         | Still being transcribed; call `get_transcript` again until `status` is `completed`. |
| `INVALID_TRANSCRIPT`    | The job has no usable timed transcript, or it is too long.                        |
| `BRAND_VOICE_NOT_FOUND` | `brand_voice_id` is not a saved profile of your team.                             |
| `INVALID_BRAND_VOICE`   | The profile text is empty or longer than 20,000 characters.                       |
| `AI_TIMEOUT` and other `AI_*` codes | Model or validation failure; retry later and do not use partial output. |

An unexpected server failure returns **HTTP 500** with a fixed JSON body and is safe to retry later.

```json
{ "error": true, "message": "Internal Server Error", "code": "MCP_CRASH" }
```

---

## How do I add TubeTranscript to Claude Code?

Register the server with the **Claude Code CLI** using **`claude mcp add --transport http`**, the server URL, and an **`Authorization`** header carrying your **Pro API key**. Add the AI tools server the same way under a second name. Run **`claude mcp list`** afterwards to confirm both servers connect. Tools are loaded when a session starts, so open a **new Claude Code session** after adding the servers.

1. Export your key as `TUBETRANSCRIPT_API_KEY` (see [Quickstart](https://tubetranscript.io/docs/quickstart.md)).
2. Run the first `claude mcp add` command below to register `tubetranscript`.
3. Run the second `claude mcp add` command below to register `tubetranscript-ai`.
4. Run `claude mcp list`.
5. Confirm that both servers are listed as connected.
6. Start a new session so the agent can see `get_transcript` and the AI tools.

```bash
claude mcp add --transport http tubetranscript https://tubetranscript.io/api/mcp \
  --header "Authorization: Bearer $TUBETRANSCRIPT_API_KEY"
claude mcp add --transport http tubetranscript-ai https://tubetranscript.io/api/mcp/tools \
  --header "Authorization: Bearer $TUBETRANSCRIPT_API_KEY"
```

---

## How do I add TubeTranscript to Cursor?

Add both servers to **`.cursor/mcp.json`** in the project, or **`~/.cursor/mcp.json`** for every project, under **`mcpServers`**. Each entry needs a **`url`** and a **`headers`** object with **`Authorization: Bearer <API_KEY>`**. Cursor detects the **Streamable HTTP** transport from the URL. Keep the file out of version control because it contains the key.

1. Create `.cursor/mcp.json` in the project root (or `~/.cursor/mcp.json` for all projects).
2. Paste the JSON below into the file.
3. Replace both `tt_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` placeholders with your API key.
4. Save the file.
5. Add `.cursor/mcp.json` to `.gitignore`.
6. Confirm that `tubetranscript` and `tubetranscript-ai` appear in Cursor's MCP settings.

```json
{
  "mcpServers": {
    "tubetranscript": {
      "url": "https://tubetranscript.io/api/mcp",
      "headers": { "Authorization": "Bearer tt_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }
    },
    "tubetranscript-ai": {
      "url": "https://tubetranscript.io/api/mcp/tools",
      "headers": { "Authorization": "Bearer tt_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }
    }
  }
}
```

---

## How do I add TubeTranscript to Windsurf?

Add both servers to **`~/.codeium/windsurf/mcp_config.json`** under **`mcpServers`**. Windsurf uses **`serverUrl`** for remote servers and a **`headers`** object for the **`Authorization: Bearer <API_KEY>`** header. After saving the file, refresh the MCP server list in **Cascade** so the **`get_transcript`** and AI tools appear before prompting the agent.

1. Open `~/.codeium/windsurf/mcp_config.json`.
2. Paste the JSON below into the file.
3. Replace both `tt_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` placeholders with your API key.
4. Save the file.
5. Refresh the MCP server list in **Cascade**.
6. Confirm that `get_transcript` and the AI tools appear in the tool list.

```json
{
  "mcpServers": {
    "tubetranscript": {
      "serverUrl": "https://tubetranscript.io/api/mcp",
      "headers": { "Authorization": "Bearer tt_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }
    },
    "tubetranscript-ai": {
      "serverUrl": "https://tubetranscript.io/api/mcp/tools",
      "headers": { "Authorization": "Bearer tt_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }
    }
  }
}
```

---

## How do I use TubeTranscript in n8n?

Add an **MCP Client Tool** node to an **AI Agent** workflow. Set the endpoint to **`https://tubetranscript.io/api/mcp`**, the server transport to **HTTP Streamable**, and authentication to **Bearer Auth** with your **Pro API key** as the token. For single-step automations without an agent, call **`POST /api/v1/extract`** from an **HTTP Request** node.

1. Open a workflow that contains an **AI Agent** node.
2. Add an **MCP Client Tool** node to the agent's tool input.
3. Set **Endpoint** to `https://tubetranscript.io/api/mcp`.
4. Set **Server Transport** to **HTTP Streamable**.
5. Set **Authentication** to **Bearer Auth**.
6. Create a Bearer credential whose token is your `tt_live_…` API key.
7. Save the workflow.
8. Repeat steps 2–7 with endpoint `https://tubetranscript.io/api/mcp/tools` to add the AI tools; the agent needs both, because the AI tools take the `job_id` that `get_transcript` returns.

| n8n field        | Value                                                     |
| ---------------- | --------------------------------------------------------- |
| Endpoint         | `https://tubetranscript.io/api/mcp` (or `/api/mcp/tools`) |
| Server Transport | HTTP Streamable                                           |
| Authentication   | Bearer Auth                                               |
| Credential token | Your `tt_live_…` API key                                  |

---

## How are MCP requests authenticated and rate limited?

Every MCP request needs **`Authorization: Bearer <API_KEY>`** from a **Pro** team; otherwise the server returns **`402 Payment Required`** with a machine-readable invoice. Protocol handshakes (**`initialize`**, **`tools/list`**, **`ping`**) are free. Each **`tools/call`** counts once against the **1,000 requests per 24 hours** limit shared with the REST API key, including a `get_transcript` call that returns `processing`. The guest limit of 10 transcripts per IP does not apply to the MCP servers. Details: [Security & Rate Limits](https://tubetranscript.io/docs/security.md).

| Condition                               | Response                                            |
| --------------------------------------- | --------------------------------------------------- |
| `GET` or `DELETE` on the server URL     | `405 METHOD_NOT_ALLOWED` (stateless, no SSE stream) |
| Missing key                             | `402`, `reason: MISSING_API_KEY`                    |
| Unknown or revoked key                  | `402`, `reason: INVALID_API_KEY`                    |
| Team not on Pro                         | `402`, `reason: PLAN_UPGRADE_REQUIRED`              |
| Limit exceeded on `tools/call`          | `402`, `reason: RATE_LIMIT_EXCEEDED`, `Retry-After` |
| Quota store unreachable on `tools/call` | `503 RATE_LIMIT_UNAVAILABLE` with `Retry-After: 60` |

The **`402`** body differs by the **`reason`** field and the **`invoice`**. A caller without a usable Pro key is invoiced the Pro plan (shown below). A **Pro** key that used up its daily quota is invoiced a top-up instead: **`amount: 2.0`**, **`description: "TubeTranscript Overage Top-up - 500 additional requests"`**. An exhausted quota also resets on its own after **`Retry-After`** seconds.

```json
{
  "error": "Payment Required",
  "code": "M2M_INSUFFICIENT_FUNDS",
  "message": "Your agent has exhausted its quota or requires a Pro API key to execute this tool.",
  "reason": "MISSING_API_KEY",
  "payment_protocols_supported": ["stripe_metered", "crypto_x402"],
  "invoice": {
    "amount": 9.0,
    "currency": "USD",
    "description": "TubeTranscript Pro Tier - 1,000 requests/day",
    "payment_url": "https://tubetranscript.io/billing/agent-checkout",
    "crypto_wallet_mock": "DOGE: D8vxyz... (For future implementation)"
  }
}
```

| MCP method   | Counts against quota |
| ------------ | -------------------- |
| `initialize` | No                   |
| `tools/list` | No                   |
| `ping`       | No                   |
| `tools/call` | Yes, once per call   |
