minutes-mcp 0.13.0 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ export declare const CRASH_LOG_PATH: string;
2
+ export declare function crashTrace(event: string, detail?: unknown): void;
@@ -0,0 +1,67 @@
1
+ // Crash tracer for the MCP server.
2
+ //
3
+ // Must only import from Node builtins. This module is loaded FIRST by
4
+ // index.ts so our process.on handlers and initial trace line land on
5
+ // disk before any other import (including the MCP SDK) evaluates. If
6
+ // an import later in the chain throws, `process.on("uncaughtException")`
7
+ // fires and we get a real trace instead of a silent exit.
8
+ //
9
+ // Issue #149: Claude Desktop 1.3109.0 with MCP protocol 2025-11-25
10
+ // shows the extension runtime killing the server without emitting any
11
+ // stderr to the host log. Synchronous file writes sidestep whatever is
12
+ // eating stderr.
13
+ import { appendFileSync, mkdirSync } from "fs";
14
+ import { homedir, tmpdir } from "os";
15
+ import { join, dirname } from "path";
16
+ export const CRASH_LOG_PATH = (() => {
17
+ const preferred = join(homedir(), ".minutes", "logs", "mcp-crash.log");
18
+ try {
19
+ mkdirSync(dirname(preferred), { recursive: true });
20
+ return preferred;
21
+ }
22
+ catch {
23
+ return join(tmpdir(), "minutes-mcp-crash.log");
24
+ }
25
+ })();
26
+ export function crashTrace(event, detail) {
27
+ try {
28
+ const line = JSON.stringify({
29
+ ts: new Date().toISOString(),
30
+ event,
31
+ pid: process.pid,
32
+ ppid: process.ppid,
33
+ cwd: process.cwd(),
34
+ argv: process.argv,
35
+ execPath: process.execPath,
36
+ nodeVersion: process.version,
37
+ platform: process.platform,
38
+ arch: process.arch,
39
+ MCP_EXTENSION_ID: process.env.MCP_EXTENSION_ID ?? null,
40
+ MEETINGS_DIR: process.env.MEETINGS_DIR ?? null,
41
+ MINUTES_HOME: process.env.MINUTES_HOME ?? null,
42
+ detail: detail === undefined
43
+ ? null
44
+ : detail instanceof Error
45
+ ? { message: detail.message, name: detail.name, stack: detail.stack }
46
+ : detail,
47
+ }) + "\n";
48
+ appendFileSync(CRASH_LOG_PATH, line);
49
+ }
50
+ catch {
51
+ // Best-effort. Never throw from the tracer.
52
+ }
53
+ }
54
+ crashTrace("module-load-start");
55
+ process.on("uncaughtException", (err) => {
56
+ crashTrace("uncaughtException", err);
57
+ });
58
+ process.on("unhandledRejection", (reason) => {
59
+ crashTrace("unhandledRejection", reason);
60
+ });
61
+ process.on("exit", (code) => {
62
+ crashTrace("process-exit", { code });
63
+ });
64
+ process.on("SIGTERM", () => crashTrace("signal-SIGTERM"));
65
+ process.on("SIGINT", () => crashTrace("signal-SIGINT"));
66
+ process.on("SIGHUP", () => crashTrace("signal-SIGHUP"));
67
+ process.on("SIGPIPE", () => crashTrace("signal-SIGPIPE"));
package/dist/index.js CHANGED
@@ -25,6 +25,12 @@
25
25
  * All tools use execFile (not exec) to shell out to the `minutes` CLI binary.
26
26
  * No shell interpolation — safe from injection.
27
27
  */
28
+ // ── Crash tracer must load before any other import (see ./crashTracer.ts) ──
29
+ // Issue #149 — Claude Desktop 1.3109.0 with MCP protocol 2025-11-25 kills
30
+ // the extension server with no stderr visible in the host log. The tracer
31
+ // writes synchronously to ~/.minutes/logs/mcp-crash.log so a reinstall
32
+ // produces a real trace instead of a silent exit.
33
+ import { crashTrace } from "./crashTracer.js";
28
34
  import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
29
35
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
30
36
  import { registerAppTool, registerAppResource, RESOURCE_MIME_TYPE, EXTENSION_ID, } from "@modelcontextprotocol/ext-apps/server";
@@ -38,6 +44,7 @@ import { fileURLToPath } from "url";
38
44
  import { homedir } from "os";
39
45
  import * as reader from "minutes-sdk";
40
46
  import { canonicalizeRoot, expandHomeLikePath, validatePathInDirectories, validatePathInDirectory, } from "./paths.js";
47
+ crashTrace("imports-complete");
41
48
  const UI_RESOURCE_URI = "ui://minutes/dashboard";
42
49
  const MCP_TOOLS_DOCS_BASE_URL = "https://useminutes.app/docs/mcp/tools";
43
50
  export const MEETING_INSIGHT_KINDS = ["decision", "commitment", "question"];
@@ -192,7 +199,7 @@ function findMinutesBinary() {
192
199
  }
193
200
  let MINUTES_BIN = findMinutesBinary();
194
201
  // ── Expected CLI version (must match this MCP server release) ──
195
- const MCP_SERVER_VERSION = "0.13.0";
202
+ const MCP_SERVER_VERSION = "0.13.2";
196
203
  const EXPECTED_CLI_VERSION = MCP_SERVER_VERSION;
197
204
  export function parseKnowledgeConfig(configContent) {
198
205
  const knowledgeMatch = configContent.match(/\[knowledge\][\s\S]*?(?=\n\[|$)/);
@@ -555,10 +562,12 @@ function parseJsonOutput(stdout) {
555
562
  }
556
563
  }
557
564
  // ── MCP Server ──────────────────────────────────────────────
565
+ crashTrace("pre-mcp-server-construct");
558
566
  const server = new McpServer({
559
567
  name: "minutes",
560
568
  version: MCP_SERVER_VERSION,
561
569
  });
570
+ crashTrace("post-mcp-server-construct");
562
571
  // Declare MCP Apps extension support so hosts classify this server as interactive.
563
572
  // The `extensions` field is part of the draft MCP spec (SEP-1724) — not yet in the
564
573
  // stable SDK types, so we cast through `any`.
@@ -604,7 +613,7 @@ registerAppResource(server, "Minutes Dashboard", UI_RESOURCE_URI, { description:
604
613
  };
605
614
  });
606
615
  // ── Tool: start_recording ───────────────────────────────────
607
- registerTool("start_recording", "Start recording audio with call-aware preflight. When a known call app is active, Minutes can infer call intent and block silent mic-only call captures unless explicitly allowed.", {
616
+ registerTool("start_recording", "Start recording audio with call-aware preflight. When a known call app is active, Minutes can infer call intent and block silent mic-only call captures unless explicitly allowed. Note: this server does not listen to audio content. Recordings are stopped by invoking stop_recording after the user types a request in chat — never promise the user they can speak a 'stop recording' voice command.", {
608
617
  title: z.string().optional().describe("Optional title for this recording"),
609
618
  mode: z
610
619
  .enum(["meeting", "quick-thought"])
@@ -691,7 +700,7 @@ registerTool("start_recording", "Start recording audio with call-aware preflight
691
700
  {
692
701
  type: "text",
693
702
  text: result.recording
694
- ? `Recording started in the running Minutes desktop app (PID: ${result.pid}).${Array.isArray(preflight.warnings) && preflight.warnings.length ? ` ${preflight.warnings[0]}` : ""}${desktopLiveMsg} Say "stop recording" when done.`
703
+ ? `Recording started in the running Minutes desktop app (PID: ${result.pid}).${Array.isArray(preflight.warnings) && preflight.warnings.length ? ` ${preflight.warnings[0]}` : ""}${desktopLiveMsg} When the user asks to finish (typed in chat), invoke stop_recording to process the transcript and summary. This server does not listen to audio content, so do not tell the user they can speak a stop command.`
695
704
  : response.detail,
696
705
  },
697
706
  ],
@@ -767,7 +776,7 @@ registerTool("start_recording", "Start recording audio with call-aware preflight
767
776
  {
768
777
  type: "text",
769
778
  text: result.recording
770
- ? `${result.recording_mode === "quick-thought" ? "Quick thought" : "Recording"} started (PID: ${result.pid}).${Array.isArray(preflight.warnings) && preflight.warnings.length ? ` ${preflight.warnings[0]}` : ""}${liveMsg} Say "stop recording" when done.`
779
+ ? `${result.recording_mode === "quick-thought" ? "Quick thought" : "Recording"} started (PID: ${result.pid}).${Array.isArray(preflight.warnings) && preflight.warnings.length ? ` ${preflight.warnings[0]}` : ""}${liveMsg} When the user asks to finish (typed in chat), invoke stop_recording to process the transcript and summary. This server does not listen to audio content, so do not tell the user they can speak a stop command.`
771
780
  : "Recording failed to start. Check `minutes logs` for details.",
772
781
  },
773
782
  ],
@@ -2114,13 +2123,26 @@ registerTool("open_dashboard", "Open the Meeting Intelligence Dashboard in the b
2114
2123
  });
2115
2124
  // ── Start server ────────────────────────────────────────────
2116
2125
  async function main() {
2126
+ crashTrace("main-start");
2117
2127
  const transport = new StdioServerTransport();
2128
+ crashTrace("transport-created");
2118
2129
  await server.connect(transport);
2130
+ crashTrace("transport-connected");
2119
2131
  console.error("Minutes MCP server running on stdio");
2120
2132
  }
2133
+ crashTrace("pre-main-guard", {
2134
+ argv1: process.argv[1] ?? null,
2135
+ resolvedArgv1: process.argv[1] ? resolve(process.argv[1]) : null,
2136
+ __filename,
2137
+ match: process.argv[1] ? resolve(process.argv[1]) === __filename : false,
2138
+ });
2121
2139
  if (process.argv[1] && resolve(process.argv[1]) === __filename) {
2122
2140
  main().catch((error) => {
2141
+ crashTrace("main-rejected", error);
2123
2142
  console.error("Fatal error:", error);
2124
2143
  process.exit(1);
2125
2144
  });
2126
2145
  }
2146
+ else {
2147
+ crashTrace("main-skipped-argv-mismatch");
2148
+ }
@@ -80,7 +80,7 @@ Boolean requesting whether a visible border and background is provided by the ho
80
80
  - omitted: host decides border`)});p({method:l("ui/request-display-mode"),params:p({mode:Te.describe("The display mode being requested.")})});var gu=p({mode:Te.describe("The display mode that was actually set. May differ from requested if not supported.")}).passthrough(),_u=S([l("model"),l("app")]).describe("Tool visibility scope - who can access the tool.");p({resourceUri:d().optional(),visibility:b(_u).optional().describe(`Who can access this tool. Default: ["model", "app"]
81
81
  - "model": Tool visible to and callable by the agent
82
82
  - "app": Tool callable by the app from this server only`)});p({mimeTypes:b(d()).optional().describe('Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.')});p({method:l("ui/download-file"),params:p({contents:b(S([co,lo])).describe("Resource contents to download — embedded (inline data) or linked (host fetches). Uses standard MCP resource types.")})});p({method:l("ui/message"),params:p({role:l("user").describe('Message role, currently only "user" is supported.'),content:b(Ne).describe("Message content blocks (text, image, etc.).")})});p({method:l("ui/notifications/sandbox-resource-ready"),params:p({html:d().describe("HTML content to load into the inner iframe."),sandbox:d().optional().describe("Optional override for the inner iframe's sandbox attribute."),csp:At.optional().describe("CSP configuration from resource metadata."),permissions:Mt.optional().describe("Sandbox permissions from resource metadata.")})});var vu=p({method:l("ui/notifications/tool-result"),params:et.describe("Standard MCP tool execution result.")}),fo=p({toolInfo:p({id:xe.optional().describe("JSON-RPC id of the tools/call request."),tool:Nt.describe("Tool definition including name, inputSchema, etc.")}).optional().describe("Metadata of the tool call that instantiated this App."),theme:nu.optional().describe("Current color theme preference."),styles:pu.optional().describe("Style configuration for theming the app."),displayMode:Te.optional().describe("How the UI is currently displayed."),availableDisplayModes:b(Te).optional().describe("Display modes the host supports."),containerDimensions:S([p({height:w().describe("Fixed container height in pixels.")}),p({maxHeight:S([w(),it()]).optional().describe("Maximum container height in pixels.")})]).and(S([p({width:w().describe("Fixed container width in pixels.")}),p({maxWidth:S([w(),it()]).optional().describe("Maximum container width in pixels.")})])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
83
- container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:d().optional().describe("User's language and region preference in BCP 47 format."),timeZone:d().optional().describe("User's timezone in IANA format."),userAgent:d().optional().describe("Host application identifier."),platform:S([l("web"),l("desktop"),l("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:p({touch:x().optional().describe("Whether the device supports touch input."),hover:x().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:p({top:w().describe("Top safe area inset in pixels."),right:w().describe("Right safe area inset in pixels."),bottom:w().describe("Bottom safe area inset in pixels."),left:w().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),bu=p({method:l("ui/notifications/host-context-changed"),params:fo.describe("Partial context update containing only changed fields.")});p({method:l("ui/update-model-context"),params:p({content:b(Ne).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:T(d(),I().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})});p({method:l("ui/initialize"),params:p({appInfo:Ye.describe("App identification (name and version)."),appCapabilities:fu.describe("Features and capabilities this app provides."),protocolVersion:d().describe("Protocol version this app supports.")})});var yu=p({protocolVersion:d().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Ye.describe("Host application identification and version."),hostCapabilities:mu.describe("Features and capabilities provided by the host."),hostContext:fo.describe("Rich context about the host environment.")}).passthrough();function wu(e){let t=document.documentElement;t.setAttribute("data-theme",e),t.style.colorScheme=e}function ku(e,t=document.documentElement){for(let[n,o]of Object.entries(e))o!==void 0&&t.style.setProperty(n,o)}class Su extends Yl{_appInfo;_capabilities;options;_hostCapabilities;_hostInfo;_hostContext;eventSchemas={toolinput:cu,toolinputpartial:lu,toolresult:vu,toolcancelled:uu,hostcontextchanged:bu};onEventDispatch(t,n){t==="hostcontextchanged"&&(this._hostContext={...this._hostContext,...n})}constructor(t,n={},o={autoResize:!0}){super(o),this._appInfo=t,this._capabilities=n,this.options=o,this.setRequestHandler(Xe,s=>(console.log("Received ping:",s.params),{})),this.setEventHandler("hostcontextchanged",void 0)}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}get ontoolinput(){return this.getEventHandler("toolinput")}set ontoolinput(t){this.setEventHandler("toolinput",t)}get ontoolinputpartial(){return this.getEventHandler("toolinputpartial")}set ontoolinputpartial(t){this.setEventHandler("toolinputpartial",t)}get ontoolresult(){return this.getEventHandler("toolresult")}set ontoolresult(t){this.setEventHandler("toolresult",t)}get ontoolcancelled(){return this.getEventHandler("toolcancelled")}set ontoolcancelled(t){this.setEventHandler("toolcancelled",t)}get onhostcontextchanged(){return this.getEventHandler("hostcontextchanged")}set onhostcontextchanged(t){this.setEventHandler("hostcontextchanged",t)}_onteardown;get onteardown(){return this._onteardown}set onteardown(t){this.warnIfRequestHandlerReplaced("onteardown",this._onteardown,t),this._onteardown=t,this.replaceRequestHandler(hu,(n,o)=>{if(!this._onteardown)throw Error("No onteardown handler set");return this._onteardown(n.params,o)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(t){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,t),this._oncalltool=t,this.replaceRequestHandler(po,(n,o)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(n.params,o)})}_onlisttools;get onlisttools(){return this._onlisttools}set onlisttools(t){this.warnIfRequestHandlerReplaced("onlisttools",this._onlisttools,t),this._onlisttools=t,this.replaceRequestHandler(uo,(n,o)=>{if(!this._onlisttools)throw Error("No onlisttools handler set");return this._onlisttools(n.params,o)})}assertCapabilityForMethod(t){}assertRequestHandlerCapability(t){switch(t){case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Client does not support tool capability (required for ${t})`);return;case"ping":case"ui/resource-teardown":return;default:throw Error(`No handler for method ${t} registered`)}}assertNotificationCapability(t){}assertTaskCapability(t){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(t){throw Error("Task handlers are not supported in MCP Apps")}async callServerTool(t,n){if(typeof t=="string")throw Error(`callServerTool() expects an object as its first argument, but received a string ("${t}"). Did you mean: callServerTool({ name: "${t}", arguments: { ... } })?`);return await this.request({method:"tools/call",params:t},et,n)}async readServerResource(t,n){return await this.request({method:"resources/read",params:t},ao,n)}async listServerResources(t,n){return await this.request({method:"resources/list",params:t},io,n)}sendMessage(t,n){return this.request({method:"ui/message",params:t},au,n)}sendLog(t){return this.notification({method:"notifications/message",params:t})}updateModelContext(t,n){return this.request({method:"ui/update-model-context",params:t},yt,n)}openLink(t,n){return this.request({method:"ui/open-link",params:t},ru,n)}sendOpenLink=this.openLink;downloadFile(t,n){return this.request({method:"ui/download-file",params:t},iu,n)}requestTeardown(t={}){return this.notification({method:"ui/notifications/request-teardown",params:t})}requestDisplayMode(t,n){return this.request({method:"ui/request-display-mode",params:t},gu,n)}sendSizeChanged(t){return this.notification({method:"ui/notifications/size-changed",params:t})}setupSizeChangedNotifications(){let t=!1,n=0,o=0,s=()=>{t||(t=!0,requestAnimationFrame(()=>{t=!1;let i=document.documentElement,a=i.style.height;i.style.height="max-content";let c=Math.ceil(i.getBoundingClientRect().height);i.style.height=a;let u=Math.ceil(window.innerWidth);(u!==n||c!==o)&&(n=u,o=c,this.sendSizeChanged({width:u,height:c}))}))};s();let r=new ResizeObserver(s);return r.observe(document.documentElement),r.observe(document.body),()=>r.disconnect()}async connect(t=new tu(window.parent,window.parent),n){if(this.transport)throw Error("App is already connected. Call close() before connecting again.");await super.connect(t);try{let o=await this.request({method:"ui/initialize",params:{appCapabilities:this._capabilities,appInfo:this._appInfo,protocolVersion:Xl}},yu,n);if(o===void 0)throw Error(`Server sent invalid initialize result: ${o}`);this._hostCapabilities=o.hostCapabilities,this._hostInfo=o.hostInfo,this._hostContext=o.hostContext,await this.notification({method:"ui/notifications/initialized"}),this.options?.autoResize&&this.setupSizeChangedNotifications()}catch(o){throw this.close(),o}}}const k=e=>document.getElementById(e);function y(e){const t=document.createElement("span");return t.textContent=e,t.innerHTML}function tt(e){return e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'")}let Re=null,lt=null,Ve="all",ne=null;const $u=["loading","error","dashboard","detail","person","people-map","report"];function Q(e){for(const t of $u)k(t).style.display=t===e?"":"none";requestAnimationFrame(()=>{B.sendSizeChanged({height:document.documentElement.scrollHeight})})}function we(e){k("error-message").textContent=e,Q("error")}function zu(e){const t=e.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(!t)return{frontmatter:{},body:e};const n=t[1],o=t[2],s={};let r=null,i=null;for(const a of n.split(`
83
+ container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:d().optional().describe("User's language and region preference in BCP 47 format."),timeZone:d().optional().describe("User's timezone in IANA format."),userAgent:d().optional().describe("Host application identifier."),platform:S([l("web"),l("desktop"),l("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:p({touch:x().optional().describe("Whether the device supports touch input."),hover:x().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:p({top:w().describe("Top safe area inset in pixels."),right:w().describe("Right safe area inset in pixels."),bottom:w().describe("Bottom safe area inset in pixels."),left:w().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),bu=p({method:l("ui/notifications/host-context-changed"),params:fo.describe("Partial context update containing only changed fields.")});p({method:l("ui/update-model-context"),params:p({content:b(Ne).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:T(d(),I().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})});p({method:l("ui/initialize"),params:p({appInfo:Ye.describe("App identification (name and version)."),appCapabilities:fu.describe("Features and capabilities this app provides."),protocolVersion:d().describe("Protocol version this app supports.")})});var yu=p({protocolVersion:d().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Ye.describe("Host application identification and version."),hostCapabilities:mu.describe("Features and capabilities provided by the host."),hostContext:fo.describe("Rich context about the host environment.")}).passthrough();function wu(e){let t=document.documentElement;t.setAttribute("data-theme",e),t.style.colorScheme=e}function ku(e,t=document.documentElement){for(let[n,o]of Object.entries(e))o!==void 0&&t.style.setProperty(n,o)}class Su extends Yl{_appInfo;_capabilities;options;_hostCapabilities;_hostInfo;_hostContext;eventSchemas={toolinput:cu,toolinputpartial:lu,toolresult:vu,toolcancelled:uu,hostcontextchanged:bu};onEventDispatch(t,n){t==="hostcontextchanged"&&(this._hostContext={...this._hostContext,...n})}constructor(t,n={},o={autoResize:!0}){super(o),this._appInfo=t,this._capabilities=n,this.options=o,this.setRequestHandler(Xe,s=>(console.log("Received ping:",s.params),{})),this.setEventHandler("hostcontextchanged",void 0)}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}get ontoolinput(){return this.getEventHandler("toolinput")}set ontoolinput(t){this.setEventHandler("toolinput",t)}get ontoolinputpartial(){return this.getEventHandler("toolinputpartial")}set ontoolinputpartial(t){this.setEventHandler("toolinputpartial",t)}get ontoolresult(){return this.getEventHandler("toolresult")}set ontoolresult(t){this.setEventHandler("toolresult",t)}get ontoolcancelled(){return this.getEventHandler("toolcancelled")}set ontoolcancelled(t){this.setEventHandler("toolcancelled",t)}get onhostcontextchanged(){return this.getEventHandler("hostcontextchanged")}set onhostcontextchanged(t){this.setEventHandler("hostcontextchanged",t)}_onteardown;get onteardown(){return this._onteardown}set onteardown(t){this.warnIfRequestHandlerReplaced("onteardown",this._onteardown,t),this._onteardown=t,this.replaceRequestHandler(hu,(n,o)=>{if(!this._onteardown)throw Error("No onteardown handler set");return this._onteardown(n.params,o)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(t){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,t),this._oncalltool=t,this.replaceRequestHandler(po,(n,o)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(n.params,o)})}_onlisttools;get onlisttools(){return this._onlisttools}set onlisttools(t){this.warnIfRequestHandlerReplaced("onlisttools",this._onlisttools,t),this._onlisttools=t,this.replaceRequestHandler(uo,(n,o)=>{if(!this._onlisttools)throw Error("No onlisttools handler set");return this._onlisttools(n.params,o)})}assertCapabilityForMethod(t){}assertRequestHandlerCapability(t){switch(t){case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Client does not support tool capability (required for ${t})`);return;case"ping":case"ui/resource-teardown":return;default:throw Error(`No handler for method ${t} registered`)}}assertNotificationCapability(t){}assertTaskCapability(t){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(t){throw Error("Task handlers are not supported in MCP Apps")}async callServerTool(t,n){if(typeof t=="string")throw Error(`callServerTool() expects an object as its first argument, but received a string ("${t}"). Did you mean: callServerTool({ name: "${t}", arguments: { ... } })?`);return await this.request({method:"tools/call",params:t},et,{onprogress:()=>{},resetTimeoutOnProgress:!0,...n})}async readServerResource(t,n){return await this.request({method:"resources/read",params:t},ao,n)}async listServerResources(t,n){return await this.request({method:"resources/list",params:t},io,n)}sendMessage(t,n){return this.request({method:"ui/message",params:t},au,n)}sendLog(t){return this.notification({method:"notifications/message",params:t})}updateModelContext(t,n){return this.request({method:"ui/update-model-context",params:t},yt,n)}openLink(t,n){return this.request({method:"ui/open-link",params:t},ru,n)}sendOpenLink=this.openLink;downloadFile(t,n){return this.request({method:"ui/download-file",params:t},iu,n)}requestTeardown(t={}){return this.notification({method:"ui/notifications/request-teardown",params:t})}requestDisplayMode(t,n){return this.request({method:"ui/request-display-mode",params:t},gu,n)}sendSizeChanged(t){return this.notification({method:"ui/notifications/size-changed",params:t})}setupSizeChangedNotifications(){let t=!1,n=0,o=0,s=()=>{t||(t=!0,requestAnimationFrame(()=>{t=!1;let i=document.documentElement,a=i.style.height;i.style.height="max-content";let c=Math.ceil(i.getBoundingClientRect().height);i.style.height=a;let u=Math.ceil(window.innerWidth);(u!==n||c!==o)&&(n=u,o=c,this.sendSizeChanged({width:u,height:c}))}))};s();let r=new ResizeObserver(s);return r.observe(document.documentElement),r.observe(document.body),()=>r.disconnect()}async connect(t=new tu(window.parent,window.parent),n){if(this.transport)throw Error("App is already connected. Call close() before connecting again.");await super.connect(t);try{let o=await this.request({method:"ui/initialize",params:{appCapabilities:this._capabilities,appInfo:this._appInfo,protocolVersion:Xl}},yu,n);if(o===void 0)throw Error(`Server sent invalid initialize result: ${o}`);this._hostCapabilities=o.hostCapabilities,this._hostInfo=o.hostInfo,this._hostContext=o.hostContext,await this.notification({method:"ui/notifications/initialized"}),this.options?.autoResize&&this.setupSizeChangedNotifications()}catch(o){throw this.close(),o}}}const k=e=>document.getElementById(e);function y(e){const t=document.createElement("span");return t.textContent=e,t.innerHTML}function tt(e){return e.replace(/&/g,"&").replace(/"/g,""").replace(/'/g,"'")}let Re=null,lt=null,Ve="all",ne=null;const $u=["loading","error","dashboard","detail","person","people-map","report"];function Q(e){for(const t of $u)k(t).style.display=t===e?"":"none";requestAnimationFrame(()=>{B.sendSizeChanged({height:document.documentElement.scrollHeight})})}function we(e){k("error-message").textContent=e,Q("error")}function zu(e){const t=e.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(!t)return{frontmatter:{},body:e};const n=t[1],o=t[2],s={};let r=null,i=null;for(const a of n.split(`
84
84
  `)){const c=a.match(/^\s{2,4}- (\w+):\s*(.*)$/);if(c&&r!==null){i={[c[1]]:c[2].trim()},r.push(i);continue}const u=a.match(/^\s{4,}(\w+):\s*(.*)$/);if(u&&i!==null){i[u[1]]=u[2].trim();continue}const m=a.match(/^(\w[\w_]*):\s*\[(.+)\]$/);if(m){r=null,i=null,s[m[1]]=m[2].split(",").map(_=>_.trim().replace(/^["']|["']$/g,""));continue}const f=a.match(/^\s{2,4}-\s+(.+)$/);if(f&&r!==null){i=null,r.push(f[1].trim());continue}const g=a.match(/^(\w[\w_]*):\s*(.*)$/);if(g){i=null;const _=g[1],E=g[2].trim();E===""||E==="[]"?(r=[],s[_]=r):(r=null,s[_]=E.replace(/^["']|["']$/g,""));continue}}return{frontmatter:s,body:o}}function Tu(e){let t=y(e);return t=t.replace(/^### (.+)$/gm,"<h3>$1</h3>"),t=t.replace(/^## (.+)$/gm,"<h2>$1</h2>"),t=t.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>"),t=t.replace(/\*(.+?)\*/g,"<em>$1</em>"),t=t.replace(/`([^`]+)`/g,"<code>$1</code>"),t=t.replace(/^&gt; (.+)$/gm,"<blockquote>$1</blockquote>"),t=t.replace(/^- (.+)$/gm,"<li>$1</li>"),t=t.replace(/(<li>[\s\S]*?<\/li>)/g,"<ul>$1</ul>"),t=t.replace(/<\/ul>\s*<ul>/g,""),t=t.replace(/^(?!<[hublop])((?!<).+)$/gm,"<p>$1</p>"),t=t.replace(/<p>\s*<\/p>/g,""),t}function J(e,t){e.innerHTML=t}const B=new Su({name:"Minutes Dashboard",version:"1.0.0"},{},{autoResize:!0});let fe=null;async function Ru(){try{const t=(await B.callServerTool({name:"get_status",arguments:{}})).content?.map(s=>"text"in s?s.text:"").join("")||"",n=t.includes("in progress")&&!t.includes("No recording"),o=k("recording-banner");n?(fe||(fe=Date.now()),o.style.display="flex",un(),ne||(ne=setInterval(un,1e3))):(o.style.display="none",fe=null,ne&&(clearInterval(ne),ne=null))}catch{k("recording-banner").style.display="none"}}function un(){if(!fe)return;const e=Math.floor((Date.now()-fe)/1e3),t=Math.floor(e/60),n=e%60;k("rec-elapsed").textContent=`${t}:${String(n).padStart(2,"0")}`}document.addEventListener("click",async e=>{if(e.target.id==="rec-stop-btn"){const t=e.target;t.disabled=!0,t.textContent="Stopping...";try{await B.callServerTool({name:"stop_recording",arguments:{}}),k("recording-banner").style.display="none",fe=null,ne&&(clearInterval(ne),ne=null)}catch{t.disabled=!1,t.textContent="Stop"}}});function go(){const e=k("filter-input").value.toLowerCase();document.querySelectorAll(".meeting-card").forEach(t=>{const n=t,o=n.querySelector(".meeting-title")?.textContent?.toLowerCase()||"",s=n.querySelector(".meeting-date")?.textContent?.toLowerCase()||"",r=n.querySelector(".badge")?.textContent?.toLowerCase()||"",i=!e||o.includes(e)||s.includes(e),a=Ve==="all"||r===Ve;n.style.display=i&&a?"":"none"})}k("filter-input").addEventListener("input",go);document.querySelectorAll(".filter-btn").forEach(e=>{e.addEventListener("click",async()=>{const t=e.dataset.filter;if(t==="people"){document.querySelectorAll(".filter-btn").forEach(n=>n.classList.remove("active")),e.classList.add("active"),Q("loading"),k("loading-text").textContent="Loading people...";try{const n=await B.callServerTool({name:"relationship_map",arguments:{}});ke(n)}catch(n){we(n.message||"Failed to load people")}return}document.querySelectorAll(".filter-btn").forEach(n=>n.classList.remove("active")),e.classList.add("active"),Ve=t,go()})});function Iu(){if(!lt)return;const{title:e,content:t}=lt,n=t.length>15e3?t.slice(0,15e3)+`
85
85
 
86
86
  [truncated]`:t;B.updateModelContext({content:[{type:"text",text:`Meeting: ${e}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minutes-mcp",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "description": "MCP server for minutes — conversation memory for AI assistants. Works with Claude Desktop, Mistral Vibe, Cursor, Windsurf, and any MCP client.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -38,9 +38,9 @@
38
38
  "dev": "tsx src/index.ts"
39
39
  },
40
40
  "dependencies": {
41
- "@modelcontextprotocol/ext-apps": "^1.5.0",
41
+ "@modelcontextprotocol/ext-apps": "^1.6.0",
42
42
  "@modelcontextprotocol/sdk": "^1.29.0",
43
- "minutes-sdk": "^0.13.0",
43
+ "minutes-sdk": "^0.13.1",
44
44
  "yaml": "^2.8.3",
45
45
  "zod": "^3.25 || ^4.0"
46
46
  },