save_to_library
Save a video to your library: bookmark for ASR, upload a summary, or both. Updates existing entry on re-save.
Instructions
Save a video to the authenticated user's Library. Three modes via kind: 'asr' bookmarks the video and flips has_asr (use after a successful transcribe_video → fetch_transcript flow); 'summary' uploads a summary blob; 'both' does both at once. Idempotent: saving the same video twice updates the existing entry.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| video_id | Yes | YouTube video ID (11 chars). | |
| kind | Yes | 'asr' (bookmark + flip has_asr), 'summary' (upload summary text), or 'both'. | |
| title | No | Video title (for display in the user's Library list). | |
| author | No | Channel / author name. | |
| thumbnail | No | Thumbnail URL. | |
| video_url | No | Full YouTube URL. | |
| language | No | Video language code (ISO 639-1). | |
| text | No | Summary text. REQUIRED when kind='summary' or kind='both'. Plain text or markdown — use the `format` param to declare which. | |
| locale | No | Summary locale (e.g. 'en', 'zh'). Used with kind='summary' or kind='both'. | |
| format | No | Summary format: 'markdown' (default) or 'text'. Use 'markdown' if your text contains **bold**, bullets, headings, or code fences so the web UI renders it; use 'text' for plain prose. | |
| model | No | Optional model identifier, e.g. 'claude-opus-4'. |
Implementation Reference
- src/index.js:293-355 (schema)Input schema and tool definition for save_to_library. Defines the name, description, annotations (LIB_WRITE), and inputSchema with all parameters: video_id (required), kind (required, enum: asr/summary/both), title, author, thumbnail, video_url, language, text, locale, format (markdown/text), model.
{ name: "save_to_library", description: "Save a video to the authenticated user's Library. Three modes via `kind`: 'asr' bookmarks the video and flips has_asr (use after a successful transcribe_video → fetch_transcript flow); 'summary' uploads a summary blob; 'both' does both at once. Idempotent: saving the same video twice updates the existing entry.", annotations: { title: "Save to Library", ...ANN.LIB_WRITE }, inputSchema: { type: "object", properties: { video_id: { type: "string", description: "YouTube video ID (11 chars).", minLength: 5, }, kind: { type: "string", description: "'asr' (bookmark + flip has_asr), 'summary' (upload summary text), or 'both'.", enum: ["asr", "summary", "both"], }, title: { type: "string", description: "Video title (for display in the user's Library list).", }, author: { type: "string", description: "Channel / author name.", }, thumbnail: { type: "string", description: "Thumbnail URL.", }, video_url: { type: "string", description: "Full YouTube URL.", }, language: { type: "string", description: "Video language code (ISO 639-1).", }, text: { type: "string", description: "Summary text. REQUIRED when kind='summary' or kind='both'. Plain text or markdown — use the `format` param to declare which.", }, locale: { type: "string", description: "Summary locale (e.g. 'en', 'zh'). Used with kind='summary' or kind='both'.", }, format: { type: "string", description: "Summary format: 'markdown' (default) or 'text'. Use 'markdown' if your text contains **bold**, bullets, headings, or code fences so the web UI renders it; use 'text' for plain prose.", enum: ["markdown", "text"], }, model: { type: "string", description: "Optional model identifier, e.g. 'claude-opus-4'.", }, }, required: ["video_id", "kind"], }, }, - src/index.js:293-355 (registration)Tool registration as part of the TOOLS array. The tool is registered by being included in the TOOLS array (line 73) which is returned by the ListToolsRequestSchema handler (line 448). All tool calls are forwarded via the generic CallToolRequestSchema handler (line 450) that invokes callUpstream with the tool name and arguments.
{ name: "save_to_library", description: "Save a video to the authenticated user's Library. Three modes via `kind`: 'asr' bookmarks the video and flips has_asr (use after a successful transcribe_video → fetch_transcript flow); 'summary' uploads a summary blob; 'both' does both at once. Idempotent: saving the same video twice updates the existing entry.", annotations: { title: "Save to Library", ...ANN.LIB_WRITE }, inputSchema: { type: "object", properties: { video_id: { type: "string", description: "YouTube video ID (11 chars).", minLength: 5, }, kind: { type: "string", description: "'asr' (bookmark + flip has_asr), 'summary' (upload summary text), or 'both'.", enum: ["asr", "summary", "both"], }, title: { type: "string", description: "Video title (for display in the user's Library list).", }, author: { type: "string", description: "Channel / author name.", }, thumbnail: { type: "string", description: "Thumbnail URL.", }, video_url: { type: "string", description: "Full YouTube URL.", }, language: { type: "string", description: "Video language code (ISO 639-1).", }, text: { type: "string", description: "Summary text. REQUIRED when kind='summary' or kind='both'. Plain text or markdown — use the `format` param to declare which.", }, locale: { type: "string", description: "Summary locale (e.g. 'en', 'zh'). Used with kind='summary' or kind='both'.", }, format: { type: "string", description: "Summary format: 'markdown' (default) or 'text'. Use 'markdown' if your text contains **bold**, bullets, headings, or code fences so the web UI renders it; use 'text' for plain prose.", enum: ["markdown", "text"], }, model: { type: "string", description: "Optional model identifier, e.g. 'claude-opus-4'.", }, }, required: ["video_id", "kind"], }, }, - src/index.js:408-441 (helper)The callUpstream function proxies tool calls to the upstream SubDownload API (https://api.subdownload.com/mcp). This is the execution path for all tools including save_to_library. It sends a JSON-RPC request with Bearer token auth and returns the result.
async function callUpstream(name, args) { if (!API_KEY) { throw new Error( "SUBDOWNLOAD_API_KEY env var is not set. Get one at https://subdownload.com/account, then run with -e SUBDOWNLOAD_API_KEY=<your-key>." ); } const res = await fetch(UPSTREAM_URL, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream", Authorization: `Bearer ${API_KEY}`, }, body: JSON.stringify({ jsonrpc: "2.0", id: Date.now(), method: "tools/call", params: { name, arguments: args }, }), }); const text = await res.text(); let body; try { body = JSON.parse(text); } catch { throw new Error( `Upstream returned non-JSON response (HTTP ${res.status}): ${text.slice(0, 200)}` ); } if (body.error) { throw new Error(body.error.message || JSON.stringify(body.error)); } return body.result; } - src/index.js:63-71 (registration)LIB_WRITE annotation constant used by save_to_library. Defines readOnlyHint=false, destructiveHint=false, idempotentHint=true, openWorldHint=false.
// Library write — overwrites latest summary on same (video, locale), // intended "latest-only" UX, not destructive LIB_WRITE: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false, }, };