Skip to content
← 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 · TypeScript
Raw
import { 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 } : {}),
	}
}