Skip to main content
Glama
btn0s

Granola MCP Server

by btn0s

get_granola_document

Retrieve a specific document from Granola by its unique ID to access notes, transcripts, or meeting content.

Instructions

Get a specific Granola document by its ID.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
idYesThe document ID to retrieve

Implementation Reference

  • The main handler for the 'get_granola_document' tool. It retrieves the document by ID using the GranolaApiClient, extracts and converts the content (from last_viewed_panel or notes) to markdown using convertProseMirrorToMarkdown, and returns a JSON-formatted response with document details or an error if not found.
    case "get_granola_document": { const id = args?.id as string; const doc = await apiClient.getDocumentById(id); if (!doc) { return { content: [ { type: "text", text: JSON.stringify({ error: `Document with id ${id} not found`, }), }, ], isError: true, }; } let markdown = ""; if ( doc.last_viewed_panel && typeof doc.last_viewed_panel === "object" && doc.last_viewed_panel.content && typeof doc.last_viewed_panel.content === "object" && doc.last_viewed_panel.content.type === "doc" ) { markdown = convertProseMirrorToMarkdown( doc.last_viewed_panel.content ); } else if ( doc.notes && typeof doc.notes === "object" && doc.notes.type === "doc" ) { markdown = convertProseMirrorToMarkdown(doc.notes); } return { content: [ { type: "text", text: JSON.stringify( { id: doc.id, title: doc.title || "Untitled", markdown, created_at: doc.created_at, updated_at: doc.updated_at, metadata: { type: doc.type, people: doc.people, google_calendar_event: doc.google_calendar_event, }, }, null, 2 ), }, ], }; }
  • src/index.ts:108-121 (registration)
    Registration of the 'get_granola_document' tool in the tools array provided to the MCP server's ListToolsRequestHandler, including name, description, and input schema.
    { name: "get_granola_document", description: "Get a specific Granola document by its ID.", inputSchema: { type: "object", properties: { id: { type: "string", description: "The document ID to retrieve", }, }, required: ["id"], }, },
  • Supporting utility method in GranolaApiClient to retrieve a specific document by ID from all fetched documents.
    async getDocumentById(id: string): Promise<GranolaDocument | null> { const allDocs = await this.getAllDocuments(); return allDocs.find((doc) => doc.id === id) || null; }
  • Supporting utility to convert ProseMirror document structure (JSON) to Markdown format, used to extract readable content from Granola documents.
    export function convertProseMirrorToMarkdown( content: ProseMirrorNode | null | undefined ): string { if (!content || typeof content !== "object" || !content.content) { return ""; } function processNode(node: ProseMirrorNode): string { if (!node || typeof node !== "object") { return ""; } const nodeType = node.type || ""; const nodeContent = node.content || []; const text = node.text || ""; switch (nodeType) { case "heading": { const level = node.attrs?.level || 1; const headingText = nodeContent.map(processNode).join("").trim(); return `${"#".repeat(level)} ${headingText}\n\n`; } case "paragraph": { const paraText = nodeContent.map(processNode).join(""); return paraText ? `${paraText}\n\n` : "\n"; } case "bulletList": { const items: string[] = []; for (const item of nodeContent) { if (item.type === "listItem") { const itemContent = (item.content || []) .map(processNode) .join("") .trim(); if (itemContent) { items.push(`- ${itemContent}`); } } } return items.length > 0 ? items.join("\n") + "\n\n" : ""; } case "orderedList": { const items: string[] = []; for (let i = 0; i < nodeContent.length; i++) { const item = nodeContent[i]; if (item.type === "listItem") { const itemContent = (item.content || []) .map(processNode) .join("") .trim(); if (itemContent) { items.push(`${i + 1}. ${itemContent}`); } } } return items.length > 0 ? items.join("\n") + "\n\n" : ""; } case "listItem": { return nodeContent.map(processNode).join(""); } case "text": { let textContent = text; if (node.marks) { for (const mark of node.marks) { switch (mark.type) { case "bold": textContent = `**${textContent}**`; break; case "italic": textContent = `*${textContent}*`; break; case "code": textContent = `\`${textContent}\``; break; case "link": const href = mark.attrs?.href || ""; textContent = `[${textContent}](${href})`; break; } } } return textContent; } case "hardBreak": return "\n"; case "codeBlock": { const codeContent = nodeContent.map(processNode).join(""); const language = node.attrs?.language || ""; return `\`\`\`${language}\n${codeContent}\n\`\`\`\n\n`; } case "blockquote": { const quoteContent = nodeContent .map(processNode) .join("") .trim() .split("\n") .map((line) => `> ${line}`) .join("\n"); return `${quoteContent}\n\n`; } default: // For unknown node types, just process their content return nodeContent.map(processNode).join(""); } } return processNode(content).trim(); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/btn0s/granola-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server