update_video_metadata
Update any YouTube video's metadata—title, description, tags, category, or privacy settings—by specifying only the fields you want to change.
Instructions
Update a video's metadata — title, description, tags, category, or privacy. Only provide fields you want changed.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| video_id | Yes | ||
| title | No | ||
| description | No | ||
| tags | No | ||
| category_id | No | YouTube category ID as a string (e.g. '22' = People & Blogs, '27' = Education, '28' = Science & Tech) | |
| privacy_status | No |
Implementation Reference
- src/tools/videos.ts:111-161 (handler)The tool handler for 'update_video_metadata'. Builds a partial patch object from provided args (title, description, tags, category_id, privacy_status), fetches missing required fields (categoryId, title) from YouTube if necessary, then calls client.updateVideo().
server.tool( "update_video_metadata", "Update a video's metadata — title, description, tags, category, or privacy. Only provide fields you want changed.", updateVideoMetadataSchema, async (args) => { const patch: Record<string, unknown> = {}; const snippet: Record<string, unknown> = {}; const status: Record<string, unknown> = {}; if (args.title !== undefined) snippet.title = args.title; if (args.description !== undefined) snippet.description = args.description; if (args.tags !== undefined) snippet.tags = args.tags; if (args.category_id !== undefined) snippet.categoryId = args.category_id; if (args.privacy_status !== undefined) status.privacyStatus = args.privacy_status; if (Object.keys(snippet).length > 0) { // Category is required when updating snippet — fetch current if user didn't supply one. if (!snippet.categoryId) { const current = await client.getVideo(args.video_id); const existingCategory = current.items[0]?.snippet?.categoryId; if (!existingCategory) { throw new Error("Video has no category — pass category_id explicitly"); } snippet.categoryId = existingCategory; } // Title is required for a snippet update. if (!snippet.title) { const current = await client.getVideo(args.video_id); const existingTitle = current.items[0]?.snippet?.title; if (!existingTitle) throw new Error("Cannot update snippet without a title"); snippet.title = existingTitle; } patch.snippet = snippet; } if (Object.keys(status).length > 0) patch.status = status; if (Object.keys(patch).length === 0) { return { content: [{ type: "text" as const, text: "No fields to update." }] }; } await client.updateVideo(args.video_id, patch); return { content: [ { type: "text" as const, text: `Updated video ${args.video_id} (${Object.keys(patch).join(", ")})`, }, ], }; }, ); - src/tools/videos.ts:23-35 (schema)Zod schema for 'update_video_metadata' tool input. Defines video_id (required), title, description, tags, category_id (all optional), and privacy_status (enum: public/unlisted/private).
const updateVideoMetadataSchema = { video_id: z.string(), title: z.string().optional(), description: z.string().optional(), tags: z.array(z.string()).optional(), category_id: z .string() .optional() .describe( "YouTube category ID as a string (e.g. '22' = People & Blogs, '27' = Education, '28' = Science & Tech)", ), privacy_status: z.enum(["public", "unlisted", "private"]).optional(), }; - src/tools/videos.ts:112-161 (registration)Registration of 'update_video_metadata' via server.tool() call inside registerVideoTools().
"update_video_metadata", "Update a video's metadata — title, description, tags, category, or privacy. Only provide fields you want changed.", updateVideoMetadataSchema, async (args) => { const patch: Record<string, unknown> = {}; const snippet: Record<string, unknown> = {}; const status: Record<string, unknown> = {}; if (args.title !== undefined) snippet.title = args.title; if (args.description !== undefined) snippet.description = args.description; if (args.tags !== undefined) snippet.tags = args.tags; if (args.category_id !== undefined) snippet.categoryId = args.category_id; if (args.privacy_status !== undefined) status.privacyStatus = args.privacy_status; if (Object.keys(snippet).length > 0) { // Category is required when updating snippet — fetch current if user didn't supply one. if (!snippet.categoryId) { const current = await client.getVideo(args.video_id); const existingCategory = current.items[0]?.snippet?.categoryId; if (!existingCategory) { throw new Error("Video has no category — pass category_id explicitly"); } snippet.categoryId = existingCategory; } // Title is required for a snippet update. if (!snippet.title) { const current = await client.getVideo(args.video_id); const existingTitle = current.items[0]?.snippet?.title; if (!existingTitle) throw new Error("Cannot update snippet without a title"); snippet.title = existingTitle; } patch.snippet = snippet; } if (Object.keys(status).length > 0) patch.status = status; if (Object.keys(patch).length === 0) { return { content: [{ type: "text" as const, text: "No fields to update." }] }; } await client.updateVideo(args.video_id, patch); return { content: [ { type: "text" as const, text: `Updated video ${args.video_id} (${Object.keys(patch).join(", ")})`, }, ], }; }, ); - src/server.ts:47-47 (registration)The tool is registered at server startup via registerVideoTools(s, youtube) call in buildContext().
registerVideoTools(s, youtube); - src/youtube/client.ts:167-170 (helper)Client.updateVideo() helper: sends a PUT request to YouTube Data API /videos endpoint with the partial patch body and part parameter matching the keys being updated.
updateVideo(videoId: string, patch: Partial<Video>): Promise<Video> { const body = { id: videoId, ...patch }; return this.dataPut<Video>("/videos", { part: Object.keys(patch).join(",") }, body); }