← Public packages
@charlesmatte/granola
Search and read Granola meeting notes, summaries and transcripts from any agent; manage webhooks and legal holds safely.
src/mcp/query.ts
47 lines · 2.3 KB · TypeScriptimport { callMcpTool, DEFAULT_MCP_SERVER, extractCitations, MCP_SERVER_PATTERN, mcpText } from '../lib/mcp.ts'
import { parseInput } from '../lib/validate.ts'
/**
* Ask Granola a natural-language question about your meetings ("What did we decide about pricing
* with Acme?", "Action items from last week?") through Granola's official MCP tool
* `query_granola_meetings`. Granola answers from your notes with inline citations like `[[0]](url)`
* pointing to the source meetings — keep them when relaying the answer. Semantic, so it finds
* meetings that keyword search (./notes/search) misses; read-only. Needs the one-time MCP
* connection (./mcp/connect, browser sign-in); works on every Granola plan (Basic: last 30 days).
*
* @param input.question - The question, in plain language. Required.
* @param input.documentIds - Optional MCP meeting ids (from the MCP `list_meetings` tool) to limit context.
* @param input.server - Kody MCP server name (default `granola`).
* @param input.includeRaw - Also return the raw MCP result (default false).
* @returns `{ server, question, answer, citations: [{ ref, url }], isError, raw? }`
*
* @example
* import askGranola from 'kody:@charlesmatte/granola/mcp/query'
*
* const { answer, citations } = await askGranola({ question: 'What action items came out of meetings this week?' })
*/
export default async function queryGranolaMeetings(input: unknown) {
const p = parseInput<{ question: string; documentIds?: Array<string>; server?: string; includeRaw?: boolean }>(
input,
{
question: { type: 'string', required: true, maxLength: 4000 },
documentIds: { type: 'stringArray', maxItems: 50 },
server: { type: 'string', pattern: MCP_SERVER_PATTERN },
includeRaw: { type: 'boolean' },
},
'mcp/query',
)
const server = p.server ?? DEFAULT_MCP_SERVER
const args: Record<string, unknown> = { query: p.question, ...(p.documentIds?.length ? { document_ids: p.documentIds } : {}) }
const raw = await callMcpTool(server, 'query_granola_meetings', args)
const { text, isError } = mcpText(raw)
return {
server,
question: p.question,
answer: text,
citations: extractCitations(text),
isError,
...(isError ? { hint: 'Granola returned an error. If it mentions arguments, inspect the tool schema with Kody search({ entity: "mcp-server:granola" }) and use ./mcp/call.' } : {}),
...(p.includeRaw ? { raw } : {}),
}
}