parse_kindle_clippings
Extract and organize Kindle highlights from HTML or text files into structured book groupings for analysis and review.
Instructions
Parse a Kindle HTML export or My Clippings.txt and return highlights grouped by book. Use this when you only need the raw highlights without generating summaries.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| rawText | Yes | Full raw content of the Kindle file |
Implementation Reference
- Main handler function that executes the parse_kindle_clippings tool logic. It validates input using Zod schema, detects format (HTML vs plain text), and delegates to appropriate parser.
export function parseKindleClippings( input: ParseKindleClippingsInput ): ParsedClippings { const { rawText } = ParseKindleClippingsInputSchema.parse(input); return isHtmlExport(rawText) ? parseHtmlExport(rawText) : parsePlainTextClippings(rawText); } - Zod input validation schema and TypeScript type definition for the parse_kindle_clippings tool. Validates that rawText is a non-empty string.
export const ParseKindleClippingsInputSchema = z.object({ rawText: z.string().min(1, "rawText must not be empty"), }); export type ParseKindleClippingsInput = z.infer< typeof ParseKindleClippingsInputSchema >; - src/index.ts:90-104 (registration)Tool registration in the MCP server - defines the tool name, description, and input schema for the parse_kindle_clippings tool in the tool list.
{ name: "parse_kindle_clippings", description: "Parse a Kindle HTML export or My Clippings.txt and return highlights grouped by book. Use this when you only need the raw highlights without generating summaries.", inputSchema: { type: "object", properties: { rawText: { type: "string", description: "Full raw content of the Kindle file", }, }, required: ["rawText"], }, }, - src/index.ts:150-155 (registration)Tool handler registration - the case statement that routes parse_kindle_clippings calls to the actual implementation function.
case "parse_kindle_clippings": { const result = parseKindleClippings(args as { rawText: string }); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], }; } - Helper function that parses plain-text My Clippings.txt format, extracting book titles, authors, and highlights grouped by book using a Map for deduplication.
// ─── Plain-text My Clippings.txt parser ────────────────────────────────────── const CLIPPING_SEPARATOR = "=========="; function parseAuthorAndTitle(header: string): { title: string; author: string } { const match = header.match(/^(.+?)\s*\((.+)\)\s*$/); if (match) { return { title: match[1].trim(), author: match[2].trim() }; } return { title: header.trim(), author: "Unknown Author" }; } function normalizeKey(title: string, author: string): string { return `${title.toLowerCase()}|||${author.toLowerCase()}`; } function parsePlainTextClippings(rawText: string): ParsedClippings { const clippings = rawText .split(CLIPPING_SEPARATOR) .map((block) => block.trim()) .filter((block) => block.length > 0); const bookMap = new Map<string, KindleHighlight>(); for (const clipping of clippings) { const lines = clipping .split("\n") .map((l) => l.trim()) .filter((l) => l.length > 0); if (lines.length < 3) continue; const headerLine = lines[0]; // lines[1] is metadata (location, date) — skip const highlightText = lines.slice(2).join(" ").trim(); if (!highlightText) continue; const { title, author } = parseAuthorAndTitle(headerLine); const key = normalizeKey(title, author); if (!bookMap.has(key)) { bookMap.set(key, { title, author, highlights: [] }); } bookMap.get(key)!.highlights.push(highlightText); } return { books: Array.from(bookMap.values()) }; }