diff --git a/crates/mcp/src/index.ts b/crates/mcp/src/index.ts index 1db6c49b..3c32f604 100644 --- a/crates/mcp/src/index.ts +++ b/crates/mcp/src/index.ts @@ -1498,7 +1498,13 @@ async function runMinutes( } const stderr = error.stderr?.trim() || ""; const stdout = error.stdout?.trim() || ""; - throw new Error(stderr || stdout || error.message); + // Preserve both streams on the thrown error: some commands (e.g. + // `resummarize --json`) print a structured failure envelope to stdout + // while anyhow's text lands on stderr — message alone would lose it. + const wrapped = new Error(stderr || stdout || error.message); + (wrapped as any).stdout = stdout; + (wrapped as any).stderr = stderr; + throw wrapped; } } @@ -1802,6 +1808,11 @@ function parseStructuredCliError(message: string): any | null { } } +function formatResummarizeFailure(data: any, fallback: string): string { + if (!data?.error) return fallback; + return `Resummarize failed${data.stage ? ` during ${data.stage}` : ""}: ${data.error}`; +} + async function latestEventSeqFromCli(): Promise { const events = await readRecentEventsFromCli(1); return maxEventSeq(events); @@ -4746,6 +4757,223 @@ registerTool( } ); +// ── Tool: resummarize_meeting ─────────────────────────────── + +registerTool( + "resummarize_meeting", + "Re-run the AI pass (summary, action items, decisions) on an edited meeting or memo. Preview by default: returns the regenerated content WITHOUT writing — note the summarization model IS still invoked (cost applies even in preview). Set apply=true to write the file (a timestamped backup is created and derived views — graph, search index, vault, QMD — refresh automatically). User edits outside AI-owned sections are never touched; checked action items and decision notes are carried forward by the merge.", + { + path: z.string().describe("Path to the meeting/memo .md file to resummarize."), + apply: z + .boolean() + .optional() + .default(false) + .describe("Write the regenerated content (default: preview only; the model is invoked either way)."), + engine: z + .string() + .optional() + .describe("Override the summarization engine for this run (e.g. 'ollama', 'apple', 'agent'). Default: the engine from config.toml."), + template: z + .string() + .optional() + .describe("Template slug to apply for this run. Default: the template recorded in the file's frontmatter."), + ingest: z + .boolean() + .optional() + .default(false) + .describe("With apply=true, also re-ingest the artifact into the knowledge base. Off by default because the knowledge log is append-only — every ingest adds a new log entry."), + include_restricted: z + .boolean() + .optional() + .default(false) + .describe("Resummarize a meeting designated `sensitivity: restricted` (refused by default; the override is logged)."), + }, + { title: "Resummarize Meeting", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, + async ({ path, apply, engine, template, ingest, include_restricted }) => { + if (!(await isCliAvailable())) { + return { content: [{ type: "text" as const, text: CLI_INSTALL_MSG }] }; + } + + let resolved: string; + try { + resolved = validatePathInDirectory(path, await getEffectiveMeetingsDir(), [".md"]); + } catch (error: any) { + return { + content: [{ type: "text" as const, text: error?.message || String(error) }], + isError: true, + }; + } + + let restrictedOverride = false; + try { + const rawContent = await readFile(resolved, "utf-8"); + const parsed = reader.parseFrontmatter(rawContent, resolved); + if (meetingSensitivity(parsed) === "restricted") { + if (!include_restricted) { + return { + content: [ + { + type: "text" as const, + text: "This meeting is designated `sensitivity: restricted`; preview output would expose derived content. Pass `include_restricted: true` for an explicit, logged override.", + }, + ], + isError: true, + }; + } + console.error( + `[Minutes] include_restricted override: resummarizing restricted meeting ${resolved} via resummarize_meeting` + ); + restrictedOverride = true; + } + } catch (error: any) { + return { + content: [ + { + type: "text" as const, + text: `Could not read: ${error?.message || String(error)}`, + }, + ], + isError: true, + }; + } + + const args = ["resummarize", resolved, "--json"]; + if (apply) args.push("--apply"); + if (engine) args.push("--engine", engine); + if (template) args.push("--template", template); + if (apply && ingest) args.push("--ingest"); + const ingestIgnored = ingest && !apply; + + try { + const { stdout } = await runMinutes(args, 300000); + const envelope = parseJsonOutput(stdout); + const data = envelope?.data; + + if (data?.error) { + return { + content: [ + { + type: "text" as const, + text: formatResummarizeFailure(data, stdout), + }, + ], + isError: true, + }; + } + if (!data || typeof data !== "object") { + return { + content: [ + { + type: "text" as const, + text: `Resummarize returned an unexpected response: ${stdout}`, + }, + ], + isError: true, + }; + } + + const lines = [ + apply ? `Applied: ${data.path || resolved}` : "Preview (no changes written)", + ]; + let engineLine = `engine: ${data.engine ?? "unknown"} (model: ${data.model ?? "unknown"})`; + if (data.template) engineLine += `, template: ${data.template}`; + lines.push(engineLine); + + const sectionsReplaced = Array.isArray(data.sections_replaced) + ? data.sections_replaced + : []; + lines.push( + sectionsReplaced.length > 0 + ? `sections replaced: ${sectionsReplaced.join(", ")}` + : "first AI pass" + ); + + if (Array.isArray(data.merge_notes) && data.merge_notes.length > 0) { + lines.push("merge notes needing eyes:"); + for (const note of data.merge_notes) { + lines.push( + `- ${note?.kind ?? "unknown"}: ${note?.previous ?? "unknown"} — ${note?.disposition ?? "unknown"}` + ); + } + } + + if (apply) { + lines.push(`backup: ${data.backup ?? "none"}`); + if (data.derived_views) { + const derived = data.derived_views; + const derivedWarnings = Array.isArray(derived.warnings) + ? derived.warnings + : []; + const refreshed: string[] = []; + if (derived.graph_rebuilt) { + refreshed.push( + derived.meetings_indexed == null + ? "graph" + : `graph (${derived.meetings_indexed} meetings indexed)` + ); + } + const searchRefreshed = + typeof derived.search_indexed === "boolean" + ? derived.search_indexed + : typeof derived.search_refreshed === "boolean" + ? derived.search_refreshed + : !derivedWarnings.some((warning: unknown) => + String(warning).startsWith("search index:") + ); + if (searchRefreshed) refreshed.push("search"); + if (derived.vault_path) refreshed.push(`vault (${derived.vault_path})`); + if (derived.qmd_refreshed) refreshed.push("qmd"); + if (derived.knowledge_ingested) { + refreshed.push( + derived.facts_written == null + ? "knowledge" + : `knowledge (${derived.facts_written} facts written)` + ); + } + lines.push(`refreshed views: ${refreshed.length > 0 ? refreshed.join(", ") : "none"}`); + + if (derivedWarnings.length > 0) { + lines.push("derived view warnings:"); + for (const warning of derivedWarnings) lines.push(`- ${warning}`); + } + } + } else { + if (ingestIgnored) lines.push("--ingest ignored in preview"); + lines.push("", "--- regenerated content ---", data.new_ai_body ?? ""); + } + + const { new_ai_body: _newAiBody, ...leanData } = data; + const structuredContent = apply ? leanData : data; + return { + content: [{ type: "text" as const, text: lines.join("\n") }], + structuredContent: restrictedOverride + ? { + ...structuredContent, + sensitivity_override: { applied: true, logged: "server-log" }, + } + : structuredContent, + }; + } catch (error: any) { + const message = error?.message || String(error); + // The CLI's failure envelope goes to stdout while anyhow's text goes to + // stderr (which wins in the thrown message) — check stdout first. + const envelope = + parseStructuredCliError(error?.stdout || "") ?? + parseStructuredCliError(message); + const data = envelope?.data; + return { + content: [ + { + type: "text" as const, + text: formatResummarizeFailure(data, message), + }, + ], + isError: true, + }; + } + } +); + // ── Tool: knowledge_status ────────────────────────────────── registerTool( diff --git a/crates/mcp/test/mcp_tools_test.mjs b/crates/mcp/test/mcp_tools_test.mjs index fc9accce..fc30e8f0 100644 --- a/crates/mcp/test/mcp_tools_test.mjs +++ b/crates/mcp/test/mcp_tools_test.mjs @@ -11,6 +11,8 @@ * 5. List tool returns array * 6. Path validation works on get_meeting * 7. Path validation works on process_audio + * 8. resummarize_meeting is present in the built server with its full schema + * 9. resummarize_meeting's path guard (validatePathInDirectory) rejects outside paths * * Run: node crates/mcp/test/mcp_tools_test.mjs */ @@ -19,6 +21,7 @@ import { execFileSync } from "child_process"; import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from "fs"; import { tmpdir } from "os"; import { join } from "path"; +import { validatePathInDirectory } from "../dist/paths.js"; let passed = 0; let failed = 0; @@ -228,6 +231,64 @@ test("minutes get --json applies speaker overlay from confirm", () => { rmSync(sandbox, { recursive: true, force: true }); }); +// ── Test 10: resummarize_meeting is registered with its schema ── +// Asserts against the compiled server source, not a live MCP round-trip — +// this harness has no protocol client, so registration + schema fields + +// the exact validation call are checked textually in dist/index.js. +test("resummarize_meeting is present in the built server with its full schema", () => { + const builtSource = readFileSync( + join(import.meta.dirname, "..", "dist", "index.js"), + "utf-8" + ); + const toolStart = builtSource.indexOf('"resummarize_meeting"'); + const nextTool = builtSource.indexOf("registerTool(", toolStart + 1); + const toolSource = builtSource.slice( + toolStart, + nextTool === -1 ? builtSource.length : nextTool + ); + + assert(toolStart !== -1, "resummarize_meeting must be present in the built server"); + for (const field of ["path", "apply", "engine", "template", "ingest", "include_restricted"]) { + assert( + new RegExp(`${field}: z\\s*\\.`).test(toolSource), + `resummarize_meeting schema must include ${field}` + ); + } + assert( + toolSource.includes( + 'validatePathInDirectory(path, await getEffectiveMeetingsDir(), [".md"])' + ), + "resummarize_meeting must validate paths against the effective meetings directory" + ); +}); + +// ── Test 11: resummarize_meeting's path guard rejects outside files ── +// Exercises the shared validator the tool calls (verified textually above), +// not the handler itself — same limitation as test 10. +test("resummarize_meeting's path guard (validatePathInDirectory) rejects outside paths", () => { + const sandbox = mkdtempSync(join(tmpdir(), "minutes-resummarize-path-")); + const meetingsDir = join(sandbox, "meetings"); + const outsidePath = join(sandbox, "outside.md"); + mkdirSync(meetingsDir, { recursive: true }); + writeFileSync(outsidePath, "# Outside\n"); + + try { + let error; + try { + validatePathInDirectory(outsidePath, meetingsDir, [".md"]); + } catch (caught) { + error = caught; + } + assert(error, "an outside path must be rejected"); + assert( + error.message.includes("Access denied: path must be within"), + "outside-path rejection must explain the meetings-directory boundary" + ); + } finally { + rmSync(sandbox, { recursive: true, force: true }); + } +}); + // ── Summary ── console.log(`\nResults: ${passed} passed, ${failed} failed, ${passed + failed} total`); process.exit(failed > 0 ? 1 : 0); diff --git a/manifest.json b/manifest.json index 420af81c..5b76925e 100644 --- a/manifest.json +++ b/manifest.json @@ -3,7 +3,7 @@ "name": "minutes", "display_name": "Minutes Conversation Memory", "description": "The private, owned conversation-memory layer for AI. Record, transcribe, and search every meeting, voice memo, and dictation entirely on your own machine — nothing uploaded — then let any AI you connect recall it. Plain-markdown output you keep forever; no cloud, no API key, no vendor to outlive.", - "long_description": "Minutes is the private, owned conversation-memory layer. Record meetings, voice memos, and dictation; transcribe them on your own machine; and get structured markdown in `~/meetings/` that Claude Desktop, Claude Code, Codex, Gemini CLI, Cursor, OpenCode, Pi, and any MCP-compatible client read from the same folder — no proprietary SDK, no API key. Nothing is uploaded: when a cloud memory tool gets acquired or subpoenaed, your recordings aren't theirs to hand over. No vendor to outlive — ten years from now, `grep` still works on your corpus.\n\n36 tools • 7 resources • Interactive MCP App dashboard • Prompt templates\n\nWhat you get:\n• On-device transcription (whisper.cpp or parakeet.cpp) and optional on-device summarization (Apple Foundation Models on macOS 26+) — your audio never leaves the machine.\n• A native chat panel that recalls your meetings, with token streaming and read-only search of your own corpus.\n• Dictation that types straight at your cursor, on the same local engine.\n• Live sessions that promote into diarized, summarized meetings when you stop.\n• Consent provenance in every file, and sensitivity gating agents must respect.\n• Diarized speaker labels, structured action items and decisions, and a cross-meeting relationship graph — all queryable via MCP.", + "long_description": "Minutes is the private, owned conversation-memory layer. Record meetings, voice memos, and dictation; transcribe them on your own machine; and get structured markdown in `~/meetings/` that Claude Desktop, Claude Code, Codex, Gemini CLI, Cursor, OpenCode, Pi, and any MCP-compatible client read from the same folder — no proprietary SDK, no API key. Nothing is uploaded: when a cloud memory tool gets acquired or subpoenaed, your recordings aren't theirs to hand over. No vendor to outlive — ten years from now, `grep` still works on your corpus.\n\n37 tools • 8 resources • Interactive MCP App dashboard • Prompt templates\n\nWhat you get:\n• On-device transcription (whisper.cpp or parakeet.cpp) and optional on-device summarization (Apple Foundation Models on macOS 26+) — your audio never leaves the machine.\n• Re-run the AI pass on edited meetings and memos with preview-first output, backup-on-apply writes, and edit-preserving merges.\n• A native chat panel that recalls your meetings, with token streaming and read-only search of your own corpus.\n• Dictation that types straight at your cursor, on the same local engine.\n• Live sessions that promote into diarized, summarized meetings when you stop.\n• Consent provenance in every file, and sensitivity gating agents must respect.\n• Diarized speaker labels, structured action items and decisions, and a cross-meeting relationship graph — all queryable via MCP.", "version": "0.24.0", "author": { "name": "Mat Silverstein", @@ -171,6 +171,10 @@ "name": "ingest_meeting", "description": "Extract facts from a meeting and update the knowledge base (person profiles, log, index)" }, + { + "name": "resummarize_meeting", + "description": "Re-run the AI pass on an edited meeting or memo, previewing by default and preserving user edits" + }, { "name": "knowledge_status", "description": "Show the current state of the knowledge base — configuration, adapter, people count, log entries" diff --git a/manifest.mcpb.json b/manifest.mcpb.json index 420af81c..5b76925e 100644 --- a/manifest.mcpb.json +++ b/manifest.mcpb.json @@ -3,7 +3,7 @@ "name": "minutes", "display_name": "Minutes Conversation Memory", "description": "The private, owned conversation-memory layer for AI. Record, transcribe, and search every meeting, voice memo, and dictation entirely on your own machine — nothing uploaded — then let any AI you connect recall it. Plain-markdown output you keep forever; no cloud, no API key, no vendor to outlive.", - "long_description": "Minutes is the private, owned conversation-memory layer. Record meetings, voice memos, and dictation; transcribe them on your own machine; and get structured markdown in `~/meetings/` that Claude Desktop, Claude Code, Codex, Gemini CLI, Cursor, OpenCode, Pi, and any MCP-compatible client read from the same folder — no proprietary SDK, no API key. Nothing is uploaded: when a cloud memory tool gets acquired or subpoenaed, your recordings aren't theirs to hand over. No vendor to outlive — ten years from now, `grep` still works on your corpus.\n\n36 tools • 7 resources • Interactive MCP App dashboard • Prompt templates\n\nWhat you get:\n• On-device transcription (whisper.cpp or parakeet.cpp) and optional on-device summarization (Apple Foundation Models on macOS 26+) — your audio never leaves the machine.\n• A native chat panel that recalls your meetings, with token streaming and read-only search of your own corpus.\n• Dictation that types straight at your cursor, on the same local engine.\n• Live sessions that promote into diarized, summarized meetings when you stop.\n• Consent provenance in every file, and sensitivity gating agents must respect.\n• Diarized speaker labels, structured action items and decisions, and a cross-meeting relationship graph — all queryable via MCP.", + "long_description": "Minutes is the private, owned conversation-memory layer. Record meetings, voice memos, and dictation; transcribe them on your own machine; and get structured markdown in `~/meetings/` that Claude Desktop, Claude Code, Codex, Gemini CLI, Cursor, OpenCode, Pi, and any MCP-compatible client read from the same folder — no proprietary SDK, no API key. Nothing is uploaded: when a cloud memory tool gets acquired or subpoenaed, your recordings aren't theirs to hand over. No vendor to outlive — ten years from now, `grep` still works on your corpus.\n\n37 tools • 8 resources • Interactive MCP App dashboard • Prompt templates\n\nWhat you get:\n• On-device transcription (whisper.cpp or parakeet.cpp) and optional on-device summarization (Apple Foundation Models on macOS 26+) — your audio never leaves the machine.\n• Re-run the AI pass on edited meetings and memos with preview-first output, backup-on-apply writes, and edit-preserving merges.\n• A native chat panel that recalls your meetings, with token streaming and read-only search of your own corpus.\n• Dictation that types straight at your cursor, on the same local engine.\n• Live sessions that promote into diarized, summarized meetings when you stop.\n• Consent provenance in every file, and sensitivity gating agents must respect.\n• Diarized speaker labels, structured action items and decisions, and a cross-meeting relationship graph — all queryable via MCP.", "version": "0.24.0", "author": { "name": "Mat Silverstein", @@ -171,6 +171,10 @@ "name": "ingest_meeting", "description": "Extract facts from a meeting and update the knowledge base (person profiles, log, index)" }, + { + "name": "resummarize_meeting", + "description": "Re-run the AI pass on an edited meeting or memo, previewing by default and preserving user edits" + }, { "name": "knowledge_status", "description": "Show the current state of the knowledge base — configuration, adapter, people count, log entries" diff --git a/scripts/generate_llms_txt.mjs b/scripts/generate_llms_txt.mjs index e7e8d107..11a33fbf 100644 --- a/scripts/generate_llms_txt.mjs +++ b/scripts/generate_llms_txt.mjs @@ -212,7 +212,7 @@ function categorizeTool(name) { Insights: new Set(["get_meeting_insights", "ingest_meeting", "knowledge_status"]), "Live and dictation": new Set(["start_live_transcript", "read_live_transcript", "start_dictation", "stop_dictation"]), "Real-time Coach": new Set(["start_copilot", "stop_copilot", "copilot_status", "read_copilot_nudges"]), - "Notes and processing": new Set(["add_note", "process_audio", "open_dashboard"]), + "Notes and processing": new Set(["add_note", "process_audio", "resummarize_meeting", "open_dashboard"]), "Voice and speaker ID": new Set(["list_voices", "confirm_speaker"]), Integration: new Set(["qmd_collection_status", "register_qmd_collection"]), "Agent Event Bus": new Set(["add_agent_annotation", "get_agent_annotations"]), diff --git a/site/app/docs/errors/data.json b/site/app/docs/errors/data.json index 09f71035..15fbb946 100644 --- a/site/app/docs/errors/data.json +++ b/site/app/docs/errors/data.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-07-27", + "generatedAt": "2026-07-28", "visibleCount": 67, "hiddenCount": 17, "groups": [ diff --git a/site/app/docs/mcp/tools/data.json b/site/app/docs/mcp/tools/data.json index 72e0216c..87181509 100644 --- a/site/app/docs/mcp/tools/data.json +++ b/site/app/docs/mcp/tools/data.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-07-27", + "generatedAt": "2026-07-28", "installCommand": "npx minutes-mcp", "documentationUrl": "https://useminutes.app/for-agents", "supportUrl": "https://github.com/silverstein/minutes/discussions", @@ -235,6 +235,13 @@ "anchorId": "tool-ingest-meeting", "docsUrl": "https://useminutes.app/docs/mcp/tools#tool-ingest-meeting" }, + { + "name": "resummarize_meeting", + "description": "Re-run the AI pass on an edited meeting or memo, previewing by default and preserving user edits", + "group": "Notes and processing", + "anchorId": "tool-resummarize-meeting", + "docsUrl": "https://useminutes.app/docs/mcp/tools#tool-resummarize-meeting" + }, { "name": "knowledge_status", "description": "Show the current state of the knowledge base — configuration, adapter, people count, log entries", diff --git a/site/lib/release.ts b/site/lib/release.ts index d87e7133..ad235cb4 100644 --- a/site/lib/release.ts +++ b/site/lib/release.ts @@ -5,7 +5,7 @@ export const MINUTES_RELEASE_VERSION = "0.24.0"; export const MINUTES_RELEASE_TAG = `v${MINUTES_RELEASE_VERSION}`; -export const MINUTES_MCP_TOOL_COUNT = 36; +export const MINUTES_MCP_TOOL_COUNT = 37; export const MINUTES_CLI_COMMAND_COUNT = 58; export const MINUTES_TEST_COUNT = 1671; diff --git a/site/public/docs/errors.md b/site/public/docs/errors.md index 42173a6e..3c76388b 100644 --- a/site/public/docs/errors.md +++ b/site/public/docs/errors.md @@ -2,7 +2,7 @@ > Generated file. Do not edit by hand. > Source: crates/core thiserror definitions -> Last generated: 2026-07-27 +> Last generated: 2026-07-28 This is the generated public catalog of stable Minutes core errors. It intentionally favors actionable, user-facing errors over generic wrapper variants. diff --git a/site/public/docs/mcp/tools.md b/site/public/docs/mcp/tools.md index ddd34259..bcc1329d 100644 --- a/site/public/docs/mcp/tools.md +++ b/site/public/docs/mcp/tools.md @@ -3,9 +3,9 @@ > Generated file. Do not edit by hand. > Source: manifest.json + crates/mcp/src/index.ts > Regenerate: node scripts/generate_llms_txt.mjs -> Last generated: 2026-07-27 +> Last generated: 2026-07-28 -Minutes exposes 36 tools, 8 resources, and 6 prompt templates through the MCP server. +Minutes exposes 37 tools, 8 resources, and 6 prompt templates through the MCP server. ## Install @@ -276,6 +276,14 @@ Open the Meeting Intelligence Dashboard in the browser — visual overview of co Reference URL: https://useminutes.app/docs/mcp/tools#tool-open-dashboard + + +#### `resummarize_meeting` + +Re-run the AI pass on an edited meeting or memo, previewing by default and preserving user edits + +Reference URL: https://useminutes.app/docs/mcp/tools#tool-resummarize-meeting + ### Voice and speaker ID diff --git a/site/public/llms-full.txt b/site/public/llms-full.txt index 824c3468..abe72ab9 100644 --- a/site/public/llms-full.txt +++ b/site/public/llms-full.txt @@ -2,16 +2,17 @@ > Generated file. Do not edit by hand. > Source: manifest.json + crates/mcp/src/index.ts + site/lib/skills-catalog.json -> Last generated: 2026-07-27 +> Last generated: 2026-07-28 ## Product Minutes is the private, owned conversation-memory layer. Record meetings, voice memos, and dictation; transcribe them on your own machine; and get structured markdown in `~/meetings/` that Claude Desktop, Claude Code, Codex, Gemini CLI, Cursor, OpenCode, Pi, and any MCP-compatible client read from the same folder — no proprietary SDK, no API key. Nothing is uploaded: when a cloud memory tool gets acquired or subpoenaed, your recordings aren't theirs to hand over. No vendor to outlive — ten years from now, `grep` still works on your corpus. -36 tools • 7 resources • Interactive MCP App dashboard • Prompt templates +37 tools • 8 resources • Interactive MCP App dashboard • Prompt templates What you get: • On-device transcription (whisper.cpp or parakeet.cpp) and optional on-device summarization (Apple Foundation Models on macOS 26+) — your audio never leaves the machine. +• Re-run the AI pass on edited meetings and memos with preview-first output, backup-on-apply writes, and edit-preserving merges. • A native chat panel that recalls your meetings, with token streaming and read-only search of your own corpus. • Dictation that types straight at your cursor, on the same local engine. • Live sessions that promote into diarized, summarized meetings when you stop. @@ -93,6 +94,7 @@ What you get: - `read_copilot_nudges` — Read observed copilot nudges incrementally by cursor or time window Docs: https://useminutes.app/docs/mcp/tools#tool-read-copilot-nudges - `open_dashboard` — Open the Meeting Intelligence Dashboard in the browser — visual overview of conversation memory Docs: https://useminutes.app/docs/mcp/tools#tool-open-dashboard - `ingest_meeting` — Extract facts from a meeting and update the knowledge base (person profiles, log, index) Docs: https://useminutes.app/docs/mcp/tools#tool-ingest-meeting +- `resummarize_meeting` — Re-run the AI pass on an edited meeting or memo, previewing by default and preserving user edits Docs: https://useminutes.app/docs/mcp/tools#tool-resummarize-meeting - `knowledge_status` — Show the current state of the knowledge base — configuration, adapter, people count, log entries Docs: https://useminutes.app/docs/mcp/tools#tool-knowledge-status - `add_agent_annotation` — Append attributed agent commentary as an agent.annotation event, never editing meeting markdown or frontmatter (allowlist-gated by ~/.minutes/agents.allow) Docs: https://useminutes.app/docs/mcp/tools#tool-add-agent-annotation - `get_agent_annotations` — Read append-only agent.annotation events separately from human-authored meeting markdown and frontmatter Docs: https://useminutes.app/docs/mcp/tools#tool-get-agent-annotations diff --git a/site/public/llms.txt b/site/public/llms.txt index 8e9b87d4..6098e091 100644 --- a/site/public/llms.txt +++ b/site/public/llms.txt @@ -2,7 +2,7 @@ > Generated file. Do not edit by hand. > Source: manifest.json + crates/mcp/src/index.ts + site/lib/skills-catalog.json -> Last generated: 2026-07-27 +> Last generated: 2026-07-28 Minutes is the private, owned conversation-memory layer. Record meetings, voice memos, and dictation; transcribe them on your own machine; and get structured markdown in `~/meetings/` that Claude Desktop, Claude Code, Codex, Gemini CLI, Cursor, OpenCode, Pi, and any MCP-compatible client read from the same folder — no proprietary SDK, no API key. Nothing is uploaded: when a cloud memory tool gets acquired or subpoenaed, your recordings aren't theirs to hand over. No vendor to outlive — ten years from now, `grep` still works on your corpus. @@ -18,7 +18,7 @@ Minutes is the private, owned conversation-memory layer. Record meetings, voice ## For AI Agents -minutes exposes a standard MCP server with 36 tools, 8 resources, and 6 prompt templates. Any MCP-compatible client can use it as a conversation memory layer. +minutes exposes a standard MCP server with 37 tools, 8 resources, and 6 prompt templates. Any MCP-compatible client can use it as a conversation memory layer. ## Choose Your Surface @@ -76,6 +76,7 @@ Recommended install: - `read_copilot_nudges` — Read observed copilot nudges incrementally by cursor or time window Docs: https://useminutes.app/docs/mcp/tools#tool-read-copilot-nudges - `open_dashboard` — Open the Meeting Intelligence Dashboard in the browser — visual overview of conversation memory Docs: https://useminutes.app/docs/mcp/tools#tool-open-dashboard - `ingest_meeting` — Extract facts from a meeting and update the knowledge base (person profiles, log, index) Docs: https://useminutes.app/docs/mcp/tools#tool-ingest-meeting +- `resummarize_meeting` — Re-run the AI pass on an edited meeting or memo, previewing by default and preserving user edits Docs: https://useminutes.app/docs/mcp/tools#tool-resummarize-meeting - `knowledge_status` — Show the current state of the knowledge base — configuration, adapter, people count, log entries Docs: https://useminutes.app/docs/mcp/tools#tool-knowledge-status - `add_agent_annotation` — Append attributed agent commentary as an agent.annotation event, never editing meeting markdown or frontmatter (allowlist-gated by ~/.minutes/agents.allow) Docs: https://useminutes.app/docs/mcp/tools#tool-add-agent-annotation - `get_agent_annotations` — Read append-only agent.annotation events separately from human-authored meeting markdown and frontmatter Docs: https://useminutes.app/docs/mcp/tools#tool-get-agent-annotations