get_content
Retrieve detailed metadata for Khan Academy educational content including videos, articles, and exercises by providing a slug or URL.
Instructions
Get details about a specific Khan Academy content item (video, article, or exercise). Accepts a slug or full URL. Returns title, description, type, and metadata.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| slug | Yes | Content slug or full URL (e.g., 'math/algebra/v/intro-to-algebra', 'https://www.khanacademy.org/science/biology/a/intro-to-biology') |
Implementation Reference
- src/tools/content.ts:18-72 (handler)The async handler function that executes the get_content tool logic. It calls client.getContent(slug), handles not-found cases, and formats the response with title, type, URL, description, duration, YouTube link, authors, and date.
async ({ slug }) => { try { const content = await client.getContent(slug); if (!content) { return { content: [ { type: "text" as const, text: `Content not found for "${slug}". Check the slug/URL and try again. Use \`search\` to find content.`, }, ], }; } let text = `## ${content.title}\n`; text += `**Type:** ${content.kind}\n`; text += `**URL:** ${content.kaUrl}\n`; if (content.description) { text += `\n${content.description}\n`; } if (content.duration) { text += `\n**Duration:** ${formatDuration(content.duration)}\n`; } if (content.youtubeId) { text += `**YouTube:** https://www.youtube.com/watch?v=${content.youtubeId}\n`; text += `\n*Use \`get_transcript\` with this slug to get the video transcript.*\n`; } if (content.authorNames?.length) { text += `**Authors:** ${content.authorNames.join(", ")}\n`; } if (content.dateAdded) { text += `**Date Added:** ${content.dateAdded}\n`; } return { content: [{ type: "text" as const, text }], }; } catch (error) { return { content: [ { type: "text" as const, text: `Error fetching content: ${error instanceof Error ? error.message : "Unknown error"}`, }, ], isError: true, }; } } - src/tools/content.ts:12-16 (schema)Zod schema defining the 'slug' input parameter for get_content tool. Accepts a string that can be a content slug or full URL.
slug: z .string() .describe( "Content slug or full URL (e.g., 'math/algebra/v/intro-to-algebra', 'https://www.khanacademy.org/science/biology/a/intro-to-biology')" ), - src/tools/content.ts:8-73 (registration)Registration of the get_content tool with the MCP server using server.tool(). Includes tool name, description, input schema, and handler function.
server.tool( "get_content", "Get details about a specific Khan Academy content item (video, article, or exercise). Accepts a slug or full URL. Returns title, description, type, and metadata.", { slug: z .string() .describe( "Content slug or full URL (e.g., 'math/algebra/v/intro-to-algebra', 'https://www.khanacademy.org/science/biology/a/intro-to-biology')" ), }, async ({ slug }) => { try { const content = await client.getContent(slug); if (!content) { return { content: [ { type: "text" as const, text: `Content not found for "${slug}". Check the slug/URL and try again. Use \`search\` to find content.`, }, ], }; } let text = `## ${content.title}\n`; text += `**Type:** ${content.kind}\n`; text += `**URL:** ${content.kaUrl}\n`; if (content.description) { text += `\n${content.description}\n`; } if (content.duration) { text += `\n**Duration:** ${formatDuration(content.duration)}\n`; } if (content.youtubeId) { text += `**YouTube:** https://www.youtube.com/watch?v=${content.youtubeId}\n`; text += `\n*Use \`get_transcript\` with this slug to get the video transcript.*\n`; } if (content.authorNames?.length) { text += `**Authors:** ${content.authorNames.join(", ")}\n`; } if (content.dateAdded) { text += `**Date Added:** ${content.dateAdded}\n`; } return { content: [{ type: "text" as const, text }], }; } catch (error) { return { content: [ { type: "text" as const, text: `Error fetching content: ${error instanceof Error ? error.message : "Unknown error"}`, }, ], isError: true, }; } } ); - src/khan-api/types.ts:32-51 (schema)TypeScript interface defining KhanContent - the return type for get_content. Contains id, slug, title, kind, url, description, thumbnailUrl, youtubeId, duration, articleContent, exerciseLength, authorNames, dateAdded, and kaUrl fields.
export interface KhanContent { id: string; slug: string; title: string; kind: ContentKind; url: string; description: string; thumbnailUrl?: string; // Video-specific youtubeId?: string; duration?: number; // Article-specific articleContent?: string; // Exercise-specific exerciseLength?: number; // Common authorNames?: string[]; dateAdded?: string; kaUrl: string; } - src/khan-api/client.ts:459-510 (helper)The underlying getContent method in KhanClient that fetches content data. Normalizes the slug, checks cache, queries the API via contentForPath(), constructs KhanContent objects, and falls back to scraping if needed.
async getContent(slugOrUrl: string): Promise<KhanContent | null> { const slug = normalizeSlug(slugOrUrl); const cacheKey = `content:${slug}`; const cached = this.cache.get<KhanContent>(cacheKey); if (cached) return cached; try { const result = await this.contentForPath(slug); if (result?.content) { const raw = result.content; const content: KhanContent = { id: raw.id ?? slug, slug: raw.slug ?? raw.nodeSlug ?? slug, title: raw.translatedTitle ?? slug, kind: (raw.contentKind as ContentKind) ?? detectContentKind(raw.relativeUrl ?? slug), url: buildKAUrl(raw.relativeUrl ?? raw.kaUrl ?? slug), description: raw.translatedDescription ?? raw.description ?? "", thumbnailUrl: raw.imageUrl, youtubeId: raw.youtubeId, duration: raw.duration, authorNames: raw.authorNames, dateAdded: raw.dateAdded, kaUrl: raw.kaUrl ?? buildKAUrl(raw.relativeUrl ?? slug), }; this.cache.set(cacheKey, content, CACHE_TTL); return content; } // If it's a course, return basic info if (result?.course) { const c = result.course; const content: KhanContent = { id: c.id ?? slug, slug: c.slug ?? slug, title: c.translatedTitle ?? slug, kind: "Unknown", url: buildKAUrl(c.relativeUrl ?? slug), description: c.translatedDescription ?? "", thumbnailUrl: c.iconPath, kaUrl: buildKAUrl(c.relativeUrl ?? slug), }; this.cache.set(cacheKey, content, CACHE_TTL); return content; } } catch { // Fall through } // Fallback: scrape return await this.scrapeContentPage(slug); }