mcp-context-card 1.8.0 → 1.9.1

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.
@@ -11,7 +11,7 @@
11
11
  "data": {
12
12
  "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json",
13
13
  "name": "io.github.Wolfe-Jam/mcp-context-card",
14
- "version": "1.8.0",
14
+ "version": "1.9.1",
15
15
  "title": "MCP Context Card",
16
16
  "description": "MCP server for a project's context (AGENTS.md), memory, and identity — base or drop-in extension.",
17
17
  "websiteUrl": "https://github.com/Wolfe-Jam/mcp-context-card",
package/.well-known/fafa CHANGED
@@ -13,7 +13,7 @@ agent:
13
13
  # a deployment for your own project uses your domain.
14
14
  id: "urn:air:faf.one:agent:mcp-context-card"
15
15
  vendor: "io.github.Wolfe-Jam"
16
- version: "1.8.0"
16
+ version: "1.9.1"
17
17
  description: >-
18
18
  The essential MCP components for a project's context (AGENTS.md),
19
19
  cross-session memory, and identity — a base MCP on its own, or a
package/CHANGELOG.md CHANGED
@@ -2,6 +2,52 @@
2
2
 
3
3
  All notable changes to this project. Adheres to [Semantic Versioning](https://semver.org).
4
4
 
5
+ ## 1.9.1
6
+
7
+ - **The card as Markdown in any chat.** `render_context_card` takes
8
+ `format: "markdown"` (default `auto`) and returns the text card even where
9
+ the host draws the card, and the line the model gets in an MCP Apps host
10
+ names it. Before, a user who asked goose for the card as text got it
11
+ stitched together from three other tools, without the remembered facts.
12
+ - **`list_agents_md_sections` says level 1 is the file's title**, which the
13
+ card doesn't count, so its list can be one longer than the card's.
14
+
15
+ ## 1.9.0
16
+
17
+ One command, three surfaces: `render_context_card` gives the mini card in an MCP Apps host and the card as Markdown everywhere else. HTML goes only to a browser.
18
+
19
+ - **`render_context_card` never returns HTML.** A client without MCP Apps
20
+ (a terminal) gets the card as Markdown: identity, `AGENTS.md` sections,
21
+ memory, discovery (`detail: "full"` for every fact whole). That's the text
22
+ `save_context_card` already returned, now without writing a file or opening
23
+ a browser. Before, it got the whole HTML page (about 30 KB, 7,400 tokens)
24
+ that no terminal can show. An MCP Apps client is unchanged apart from the
25
+ line the model gets. The style arguments (`theme`, `accent`, `layout`,
26
+ `expanded`) stay on `save_context_card`; `render_context_card` ignores them.
27
+ - **One section count.** The line the model gets, the mini card, the text
28
+ card and the HTML card share one filter (`bodySections`): level-1 headings,
29
+ such as the `# AGENTS.md` title, are not sections (the line said 10 where
30
+ the card said 9). `list_agents_md_sections` still lists every heading,
31
+ title included.
32
+ - **The line says the card is drawn in the chat**, and to call
33
+ `save_context_card` if the user can't see it. With no AGENTS.md it says
34
+ "no AGENTS.md yet", not "0 sections". It names the project the way the
35
+ card does (the business card's display name first).
36
+ - **A 2025 MCP Apps client over HTTP gets the Markdown card**, not the line:
37
+ the stateless transport doesn't carry its `initialize` capabilities to the
38
+ tool call. The host still draws the card. Over stdio it gets the line.
39
+ - **The server instructions are 1.8.0's, unchanged.** They still steer a
40
+ client without MCP Apps to `save_context_card` rather than the new Markdown
41
+ reply; they change once a changed version passes a goose desktop load
42
+ check (a build that changed them didn't load in goose desktop 1.52; the
43
+ cause isn't found yet). A test pins the text.
44
+
45
+ Breaking for tool callers that read HTML from `render_context_card`: it
46
+ returns a line or Markdown now, and its `inputSchema` has only `detail`
47
+ (`theme`, `accent`, `layout`, `expanded` are ignored, no error). For the HTML
48
+ use `save_context_card` (a file), `GET /card` (HTTP) or
49
+ `mcp-context-card card` (CLI); all three are unchanged.
50
+
5
51
  ## 1.8.0
6
52
 
7
53
  Speaks MCP 2026-07-28 as well as 2025, on the MCP SDK v2, and the card fits a chat panel. Library users: see the breaking note at the end.
package/README.md CHANGED
@@ -122,9 +122,12 @@ GET /card # live, on the HTTP transport
122
122
  GET /card?layout=portrait&theme=light&accent=%230066cc
123
123
  ```
124
124
 
125
- In a chat, hosts that support [MCP Apps](https://modelcontextprotocol.io/extensions/apps)
126
- show the card inline when `render_context_card` runs; the model gets a short
127
- summary instead of the page. Everywhere else, `save_context_card` writes
125
+ `render_context_card` is the one command, with three outcomes. In a chat
126
+ whose host supports [MCP Apps](https://modelcontextprotocol.io/extensions/apps),
127
+ the host draws the card (the mini card in a panel) and the model gets a short
128
+ line. Anywhere else, a terminal for one, the reply is the text card: the card
129
+ as Markdown. Ask for it as text and `format: "markdown"` gives the text card in
130
+ any chat. HTML goes only to a browser: `save_context_card` writes
128
131
  `context-card.html` into the project, opens it in your browser when the
129
132
  server runs locally, and replies with the card as Markdown (identity, the
130
133
  `AGENTS.md` sections, memory, discovery), a link to the full card, and its
@@ -275,7 +278,7 @@ The wire‑level detail is in [docs/MECHANISMS.md](./docs/MECHANISMS.md).
275
278
  | `forget` | drop or correct a stale fact |
276
279
  | `whoami` | this server's name, vendor, version, status, license |
277
280
  | `list_context_sources` | what this project publishes, in what media types, via which surface |
278
- | `render_context_card` | the whole card as one self‑contained HTML page (also `GET /card`); hosts that support MCP Apps show it inline |
281
+ | `render_context_card` | shows the card: drawn in the chat by hosts that support MCP Apps, the card as Markdown everywhere else (the HTML card: `save_context_card`, `GET /card`) |
279
282
  | `save_context_card` | write the card to `context-card.html` in the project and open it in your browser; returns the card as Markdown plus a link to the full version |
280
283
 
281
284
  Seven tools only read. `remember` and `forget` write the memory file, and
@@ -296,7 +299,7 @@ over both transports:
296
299
  4. **Discovery** — `list_context_sources()`, then the same server over stateless
297
300
  HTTP with its `.well-known` routes and `GET /card`.
298
301
 
299
- 245 tests on Linux, macOS, and Windows, coverage‑gated in CI.
302
+ 252 tests on Linux, macOS, and Windows, coverage‑gated in CI.
300
303
  `npm run wjttc` runs the WJTTC certification suite (seven tiers, from protocol
301
304
  and Server Card conformance to stdio/HTTP parity and the shipped package). Two spawn a real
302
305
  child process and check a remembered fact survives the restart — one against
@@ -13,6 +13,9 @@ export interface AgentsMd {
13
13
  preamble: string;
14
14
  sections: AgentsSection[];
15
15
  }
16
+ /** The sections the card shows and counts: every heading below level 1.
17
+ * Level-1 headings (the `# AGENTS.md` title) are not sections. */
18
+ export declare function bodySections(agents: AgentsMd | null): AgentsSection[];
16
19
  export declare function parseAgentsMd(path: string): AgentsMd | null;
17
20
  export declare function fromString(raw: string): AgentsMd;
18
21
  /**
package/dist/agents-md.js CHANGED
@@ -12,6 +12,11 @@
12
12
  import { readFileSync } from "node:fs";
13
13
  const HEADING = /^(#{1,6})\s+(.+?)\s*#*\s*$/;
14
14
  const FENCE = /^\s*(```|~~~)/;
15
+ /** The sections the card shows and counts: every heading below level 1.
16
+ * Level-1 headings (the `# AGENTS.md` title) are not sections. */
17
+ export function bodySections(agents) {
18
+ return agents?.sections.filter((s) => s.level > 1) ?? [];
19
+ }
15
20
  export function parseAgentsMd(path) {
16
21
  try {
17
22
  return fromString(readFileSync(path, "utf8"));
package/dist/bin.d.ts CHANGED
@@ -8,7 +8,7 @@ export interface Launch {
8
8
  root: string;
9
9
  }
10
10
  /** what a bare `--help` / `help` prints. */
11
- export declare const HELP = "mcp-context-card 1.8.0\nServe a project's context (AGENTS.md), memory, and identity over MCP.\n\nUSAGE\n mcp-context-card stdio MCP server \u2014 what an MCP host spawns (default)\n mcp-context-card --http stateless Streamable HTTP on PORT (default 3000)\n mcp-context-card --stdio force stdio even when PORT is set\n mcp-context-card card this dir's context card \u2014 opens it in your browser\n at a terminal; HTML to stdout when piped ( > f.html )\n --theme light|dark --accent #hex\n --expanded (all sections open) --portrait --stdout\n mcp-context-card --help this text\n mcp-context-card --version print version\n\nENV\n MCP_CONTEXT_CARD_ROOT read AGENTS.md / project.fafm / .well-known/ from here\n PORT if set, run HTTP instead of stdio\n HOST HTTP bind address (default 127.0.0.1, this machine\n only); 0.0.0.0 exposes it, e.g. in a container\n MCP_CONTEXT_CARD_ALLOWED_HOSTS / _ALLOWED_ORIGINS\n comma-separated Host names / browser origins to\n accept besides this machine's own (reverse proxy, web app)\n MCP_CONTEXT_CARD_PUBLISH_MEMORY=1\n HTTP: serve project.fafm, list it in the AI Catalog,\n and show its facts on an exposed /card (off: memory\n is session data). HTTP has no auth\n\nA bare run is an stdio server: it waits for a host to speak JSON-RPC on stdin,\nso it looks idle at a terminal. Try `card` (opens your context in a browser) or `--http`.\nhttps://github.com/Wolfe-Jam/mcp-context-card\n";
11
+ export declare const HELP = "mcp-context-card 1.9.1\nServe a project's context (AGENTS.md), memory, and identity over MCP.\n\nUSAGE\n mcp-context-card stdio MCP server \u2014 what an MCP host spawns (default)\n mcp-context-card --http stateless Streamable HTTP on PORT (default 3000)\n mcp-context-card --stdio force stdio even when PORT is set\n mcp-context-card card this dir's context card \u2014 opens it in your browser\n at a terminal; HTML to stdout when piped ( > f.html )\n --theme light|dark --accent #hex\n --expanded (all sections open) --portrait --stdout\n mcp-context-card --help this text\n mcp-context-card --version print version\n\nENV\n MCP_CONTEXT_CARD_ROOT read AGENTS.md / project.fafm / .well-known/ from here\n PORT if set, run HTTP instead of stdio\n HOST HTTP bind address (default 127.0.0.1, this machine\n only); 0.0.0.0 exposes it, e.g. in a container\n MCP_CONTEXT_CARD_ALLOWED_HOSTS / _ALLOWED_ORIGINS\n comma-separated Host names / browser origins to\n accept besides this machine's own (reverse proxy, web app)\n MCP_CONTEXT_CARD_PUBLISH_MEMORY=1\n HTTP: serve project.fafm, list it in the AI Catalog,\n and show its facts on an exposed /card (off: memory\n is session data). HTTP has no auth\n\nA bare run is an stdio server: it waits for a host to speak JSON-RPC on stdin,\nso it looks idle at a terminal. Try `card` (opens your context in a browser) or `--http`.\nhttps://github.com/Wolfe-Jam/mcp-context-card\n";
12
12
  /**
13
13
  * The address the HTTP server binds: `HOST`, else 127.0.0.1. Local by default,
14
14
  * as the MCP transports spec recommends; a hosted deploy sets HOST=0.0.0.0.
@@ -100,4 +100,4 @@ Boolean requesting whether a visible border and background is provided by the ho
100
100
  - omitted: host decides border`)}),RA=p.object({method:p.literal("ui/request-display-mode"),params:p.object({mode:Tc.describe("The display mode being requested.")})}),vE=p.object({mode:Tc.describe("The display mode that was actually set. May differ from requested if not supported.")}).passthrough(),_E=p.union([p.literal("model"),p.literal("app")]).describe("Tool visibility scope - who can access the tool."),PA=p.object({resourceUri:p.string().optional(),visibility:p.array(_E).optional().describe(`Who can access this tool. Default: ["model", "app"]
101
101
  - "model": Tool visible to and callable by the agent
102
102
  - "app": Tool callable by the app from this server only`),csp:p.never().optional(),permissions:p.never().optional()}),TA=p.object({mimeTypes:p.array(p.string()).optional().describe('Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.')}),EA=p.object({method:p.literal("ui/download-file"),params:p.object({contents:p.array(p.union([zn,wn])).describe("Resource contents to download \u2014 embedded (inline data) or linked (host fetches). Uses standard MCP resource types.")})}),OA=p.object({method:p.literal("ui/message"),params:p.object({role:p.literal("user").describe('Message role, currently only "user" is supported.'),content:p.array(Ht).describe("Message content blocks (text, image, etc.).")})}),CA=p.object({method:p.literal("ui/notifications/sandbox-resource-ready"),params:p.object({html:p.string().describe("HTML content to load into the inner iframe."),sandbox:p.string().optional().describe("Optional override for the inner iframe's sandbox attribute."),csp:jv.optional().describe("CSP configuration from resource metadata."),permissions:Av.optional().describe("Sandbox permissions from resource metadata.")})}),SE=p.object({method:p.literal("ui/notifications/tool-result"),params:jr.describe("Standard MCP tool execution result.")}),az=p.object({toolInfo:p.object({id:wt.optional().describe("JSON-RPC id of the tools/call request."),tool:Nr.describe("Tool definition including name, inputSchema, etc.")}).optional().describe("Metadata of the tool call that instantiated this App."),theme:rE.optional().describe("Current color theme preference."),styles:mE.optional().describe("Style configuration for theming the app."),displayMode:Tc.optional().describe("How the UI is currently displayed."),availableDisplayModes:p.array(Tc).optional().describe("Display modes the host supports."),containerDimensions:p.union([p.object({height:p.number().describe("Fixed container height in pixels.")}),p.object({maxHeight:p.union([p.number(),p.undefined()]).optional().describe("Maximum container height in pixels.")})]).and(p.union([p.object({width:p.number().describe("Fixed container width in pixels.")}),p.object({maxWidth:p.union([p.number(),p.undefined()]).optional().describe("Maximum container width in pixels.")})])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
103
- container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:p.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:p.string().optional().describe("User's timezone in IANA format."),userAgent:p.string().optional().describe("Host application identifier."),platform:p.union([p.literal("web"),p.literal("desktop"),p.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:p.object({touch:p.boolean().optional().describe("Whether the device supports touch input."),hover:p.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:p.object({top:p.number().describe("Top safe area inset in pixels."),right:p.number().describe("Right safe area inset in pixels."),bottom:p.number().describe("Bottom safe area inset in pixels."),left:p.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),bE=p.object({method:p.literal("ui/notifications/host-context-changed"),params:az.describe("Partial context update containing only changed fields.")}),NA=p.object({method:p.literal("ui/update-model-context"),params:p.object({content:p.array(Ht).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:p.record(p.string(),p.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})}),jA=p.object({method:p.literal("ui/initialize"),params:p.object({appInfo:Ft.describe("App identification (name and version)."),appCapabilities:gE.describe("Features and capabilities this app provides."),protocolVersion:p.string().describe("Protocol version this app supports.")})}),yE=p.object({protocolVersion:p.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Ft.describe("Host application identification and version."),hostCapabilities:hE.describe("Features and capabilities provided by the host."),hostContext:az.describe("Rich context about the host environment.")}).passthrough(),$E={target:"draft-2020-12"};async function iz(e,t){let n=e["~standard"];if(n.jsonSchema)return n.jsonSchema[t]($E);throw Error(`Schema (vendor: ${n.vendor}) does not implement Standard JSON Schema (~standard.jsonSchema). Use a library that does (zod v4, ArkType, Valibot) or wrap your schema accordingly.`)}async function oz(e,t,n=""){let o=await e["~standard"].validate(t);if(o.issues){let r=o.issues.map(i=>{let a=i.path?.map(s=>typeof s=="object"?s.key:s).join(".");return a?`${a}: ${i.message}`:i.message}).join("; ");throw Error(n+r)}return o.value}function zE(e,t){let n={...e,...t};return t.experimental!==void 0&&(n.experimental={...e.experimental,...t.experimental}),t.tools!==void 0&&(n.tools={...e.tools,...t.tools}),n}var Ud=class e extends Rc{_appInfo;_appCapabilities;options;_hostCapabilities;_hostInfo;_hostContext;_registeredTools={};_initializedSent=!1;_registeredEvents=new Set;_events=new Ov;_methods=new Cv;setRequestHandler=(...t)=>{let[n,...o]=rz("request",t)??t;this._methods.claim(n,"setRequestHandler"),super.setRequestHandler.call(this,n,...o)};setNotificationHandler=(...t)=>{let[n,...o]=rz("notification",t)??t;this._methods.claim(n,"setNotificationHandler"),super.setNotificationHandler.call(this,n,...o)};removeRequestHandler=t=>{this._methods.release(t),super.removeRequestHandler(t)};removeNotificationHandler=t=>{this._methods.release(t),super.removeNotificationHandler(t)};replaceRequestHandler=(t,...n)=>{this._methods.replace(t),super.setRequestHandler.call(this,t,...n)};_assertInitialized(t){if(this._initializedSent)return;let n=`[ext-apps] App.${t}() called before connect() completed the ui/initialize handshake. Await app.connect() before calling this method, or move data loading to an ontoolresult handler.`;if(this.options?.strict)throw Error(n);console.warn(`${n}. This will throw in a future release.`)}eventSchemas={toolinput:cE,toolinputpartial:lE,toolresult:SE,toolcancelled:uE,hostcontextchanged:bE};static ONE_SHOT_EVENTS=new Set(["toolinput","toolinputpartial","toolresult","toolcancelled"]);_everHadListener=new Set;_assertHandlerTiming(t){if(!e.ONE_SHOT_EVENTS.has(t)||this._everHadListener.has(t)||(this._everHadListener.add(t),!this._initializedSent))return;let n=`[ext-apps] "${String(t)}" handler registered after connect() completed the ui/initialize handshake. The host may have already sent this notification. Register handlers before calling app.connect().`;if(this.options?.strict)throw Error(n);console.warn(n)}setEventHandler(t,n){n&&this._assertHandlerTiming(t),this._ensureEventSlot(t),this._events.setHandler(t,n)}addEventListener(t,n){this._assertHandlerTiming(t),this._ensureEventSlot(t),this._events.addEventListener(t,n)}removeEventListener(t,n){this._events.removeEventListener(t,n)}_ensureEventSlot(t){if(this._registeredEvents.has(t))return;let n=this.eventSchemas[t];if(!n)throw Error(`Unknown event: ${String(t)}`);this.setNotificationHandler(n.shape.method.value,{params:n.shape.params},o=>{let r=o;t==="hostcontextchanged"&&(this._hostContext={...this._hostContext,...r}),this._events.dispatch(t,r)}),this._registeredEvents.add(t)}getEventHandler(t){return this._events.getHandler(t)}warnIfRequestHandlerReplaced(t,n,o){n&&o&&console.warn(`[MCP Apps] ${t} handler replaced. Previous handler will no longer be called.`)}constructor(t,n={},o={autoResize:!0}){super(o),this._appInfo=t,this._appCapabilities=n,this.options=o,this._methods.replace("notifications/cancelled"),this._methods.replace("notifications/progress"),!o.allowUnsafeEval&&p.config({jitless:!0}),this.replaceRequestHandler("ping",r=>(console.log("Received ping:",r.params),{})),this.setEventHandler("hostcontextchanged",void 0)}buildContext(t){return t}registerAppCapabilities(t){if(this.transport)throw Error("Cannot register capabilities after transport is established");this._appCapabilities=zE(this._appCapabilities,t)}registerTool(t,n,o){if(this._registeredTools[t])throw Error(`Tool ${t} is already registered`);let r=this,i=()=>{r._initializedSent&&r._appCapabilities.tools?.listChanged&&r.sendToolListChanged()},a=n.inputSchema!==void 0,s={title:n.title,description:n.description,inputSchema:n.inputSchema,outputSchema:n.outputSchema,annotations:n.annotations,_meta:n._meta,enabled:!0,enable(){this.enabled=!0,i()},disable(){this.enabled=!1,i()},update(c){Object.assign(this,c),i()},remove(){r._registeredTools[t]===s&&(delete r._registeredTools[t],i())},handler:async(c,l)=>{if(!s.enabled)throw Error(`Tool ${t} is disabled`);let d;if(a){let m=s.inputSchema,f=m?await oz(m,c??{},`Invalid input for tool ${t}: `):c??{};d=await o(f,l)}else d=await o(l);return s.outputSchema&&!d.isError&&(d.structuredContent=await oz(s.outputSchema,d.structuredContent,`Invalid output for tool ${t}: `)),d}};return this._registeredTools[t]=s,!this._appCapabilities.tools&&!this.transport&&this.registerAppCapabilities({tools:{listChanged:!0}}),this.ensureToolHandlersInitialized(),i(),s}_toolHandlersInitialized=!1;ensureToolHandlersInitialized(){this._toolHandlersInitialized||(this._toolHandlersInitialized=!0,this.oncalltool=async(t,n)=>{let o=this._registeredTools[t.name];if(!o)throw Error(`Tool ${t.name} not found`);return o.handler(t.arguments,n)},this.onlisttools=async(t,n)=>({tools:await Promise.all(Object.entries(this._registeredTools).filter(([o,r])=>r.enabled).map(async([o,r])=>{let i={name:o,title:r.title,description:r.description,inputSchema:r.inputSchema?await iz(r.inputSchema,"input"):{type:"object",properties:{}}};return r.outputSchema&&(i.outputSchema=await iz(r.outputSchema,"output")),r.annotations&&(i.annotations=r.annotations),r._meta&&(i._meta=r._meta),i}))}))}async sendToolListChanged(t={}){this._assertInitialized("sendToolListChanged"),await this.notification({method:"notifications/tools/list_changed",params:t})}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("ui/resource-teardown",{params:pE.shape.params,result:fE},(n,o)=>{if(!this._onteardown)throw Error("No onteardown handler set");return this._onteardown(n,o)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(t){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,t),this._oncalltool=t,this.replaceRequestHandler("tools/call",(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("tools/list",(n,o)=>{if(!this._onlisttools)throw Error("No onlisttools handler set");return this._onlisttools(n.params,o)})}assertCapabilityForMethod(t){switch(t){case"sampling/createMessage":if(!this._hostCapabilities?.sampling)throw Error(`Host does not support sampling (required for ${t})`);break}}assertRequestHandlerCapability(t){switch(t){case"tools/call":case"tools/list":if(!this._appCapabilities.tools)throw Error(`Client does not support tool capability (required for ${t})`);return;default:return}}assertNotificationCapability(t){}async callServerTool(t,n){if(this._assertInitialized("callServerTool"),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},{onprogress:()=>{},resetTimeoutOnProgress:!0,...n})}async readServerResource(t,n){return this._assertInitialized("readServerResource"),await this.request({method:"resources/read",params:t},n)}async listServerResources(t,n){return this._assertInitialized("listServerResources"),await this.request({method:"resources/list",params:t},n)}async createSamplingMessage(t,n){return this._assertInitialized("createSamplingMessage"),await this.request({method:"sampling/createMessage",params:t},n)}sendMessage(t,n){return this._assertInitialized("sendMessage"),this.request({method:"ui/message",params:t},sE,n)}sendLog(t){return this.notification({method:"notifications/message",params:t})}updateModelContext(t,n){return this._assertInitialized("updateModelContext"),this.request({method:"ui/update-model-context",params:t},Or,n)}openLink(t,n){return this._assertInitialized("openLink"),this.request({method:"ui/open-link",params:t},oE,n)}sendOpenLink=this.openLink;downloadFile(t,n){return this._assertInitialized("downloadFile"),this.request({method:"ui/download-file",params:t},aE,n)}requestTeardown(t={}){return this.notification({method:"ui/notifications/request-teardown",params:t})}requestDisplayMode(t,n){return this._assertInitialized("requestDisplayMode"),this.request({method:"ui/request-display-mode",params:t},vE,n)}sendSizeChanged(t){return this.notification({method:"ui/notifications/size-changed",params:t})}setupSizeChangedNotifications(){let t=!1,n=0,o=0,r=()=>{t||(t=!0,requestAnimationFrame(()=>{t=!1;let a=document.documentElement,s=a.style.height;a.style.height="max-content";let c=Math.ceil(a.getBoundingClientRect().height);a.style.height=s;let l=Math.ceil(window.innerWidth);(l!==n||c!==o)&&(n=l,o=c,this.sendSizeChanged({width:l,height:c}))}))};r();let i=new ResizeObserver(r);return i.observe(document.documentElement),i.observe(document.body),()=>i.disconnect()}async connect(t=new Nv(window.parent,window.parent),n){if(this.transport)throw Error("App is already connected. Call close() before connecting again.");this._initializedSent=!1;try{await super.connect(t);let o=await this.request({method:"ui/initialize",params:{appCapabilities:this._appCapabilities,appInfo:this._appInfo,protocolVersion:eE}},yE,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._initializedSent=!0,this.options?.autoResize&&this.setupSizeChangedNotifications()}catch(o){throw this.close(),o}}};function wE(e){return e==="inline"||e==="pip"}function Dd(e){let t=document.getElementById("inline-view");if(!t)return;let n=document.documentElement;if(wE(e?.displayMode))n.dataset.view="inline",t.hidden=!1;else{delete n.dataset.view,t.hidden=!0;let a=document.getElementById("inline-saved");a&&(a.hidden=!0)}let r=e?.theme,i=document.body;i?.dataset&&(r==="light"||r==="dark")&&(i.dataset.theme=r)}function Uv(){let e=document.getElementById("inline-saved");e&&(e.hidden=!1)}async function kE(e){if(!e.getHostContext()?.availableDisplayModes?.includes("fullscreen")){Uv();return}try{if((await e.requestDisplayMode({mode:"fullscreen"})).mode!=="fullscreen"){Uv(),Dd(e.getHostContext());return}let o=document.getElementById("inline-saved");o&&(o.hidden=!0),Dd({...e.getHostContext()??{},displayMode:"fullscreen"})}catch{Uv()}}var IE=window.parent;if(IE!==window){let e=new Ud({name:"mcp-context-card",version:"1.8.0"},{availableDisplayModes:["inline","pip","fullscreen"]},{autoResize:!0});e.addEventListener("hostcontextchanged",()=>{Dd(e.getHostContext())}),document.getElementById("open-full-card")?.addEventListener("click",()=>{kE(e)}),e.connect().then(()=>Dd(e.getHostContext())).catch(()=>{})}})();
103
+ container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:p.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:p.string().optional().describe("User's timezone in IANA format."),userAgent:p.string().optional().describe("Host application identifier."),platform:p.union([p.literal("web"),p.literal("desktop"),p.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:p.object({touch:p.boolean().optional().describe("Whether the device supports touch input."),hover:p.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:p.object({top:p.number().describe("Top safe area inset in pixels."),right:p.number().describe("Right safe area inset in pixels."),bottom:p.number().describe("Bottom safe area inset in pixels."),left:p.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),bE=p.object({method:p.literal("ui/notifications/host-context-changed"),params:az.describe("Partial context update containing only changed fields.")}),NA=p.object({method:p.literal("ui/update-model-context"),params:p.object({content:p.array(Ht).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:p.record(p.string(),p.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})}),jA=p.object({method:p.literal("ui/initialize"),params:p.object({appInfo:Ft.describe("App identification (name and version)."),appCapabilities:gE.describe("Features and capabilities this app provides."),protocolVersion:p.string().describe("Protocol version this app supports.")})}),yE=p.object({protocolVersion:p.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Ft.describe("Host application identification and version."),hostCapabilities:hE.describe("Features and capabilities provided by the host."),hostContext:az.describe("Rich context about the host environment.")}).passthrough(),$E={target:"draft-2020-12"};async function iz(e,t){let n=e["~standard"];if(n.jsonSchema)return n.jsonSchema[t]($E);throw Error(`Schema (vendor: ${n.vendor}) does not implement Standard JSON Schema (~standard.jsonSchema). Use a library that does (zod v4, ArkType, Valibot) or wrap your schema accordingly.`)}async function oz(e,t,n=""){let o=await e["~standard"].validate(t);if(o.issues){let r=o.issues.map(i=>{let a=i.path?.map(s=>typeof s=="object"?s.key:s).join(".");return a?`${a}: ${i.message}`:i.message}).join("; ");throw Error(n+r)}return o.value}function zE(e,t){let n={...e,...t};return t.experimental!==void 0&&(n.experimental={...e.experimental,...t.experimental}),t.tools!==void 0&&(n.tools={...e.tools,...t.tools}),n}var Ud=class e extends Rc{_appInfo;_appCapabilities;options;_hostCapabilities;_hostInfo;_hostContext;_registeredTools={};_initializedSent=!1;_registeredEvents=new Set;_events=new Ov;_methods=new Cv;setRequestHandler=(...t)=>{let[n,...o]=rz("request",t)??t;this._methods.claim(n,"setRequestHandler"),super.setRequestHandler.call(this,n,...o)};setNotificationHandler=(...t)=>{let[n,...o]=rz("notification",t)??t;this._methods.claim(n,"setNotificationHandler"),super.setNotificationHandler.call(this,n,...o)};removeRequestHandler=t=>{this._methods.release(t),super.removeRequestHandler(t)};removeNotificationHandler=t=>{this._methods.release(t),super.removeNotificationHandler(t)};replaceRequestHandler=(t,...n)=>{this._methods.replace(t),super.setRequestHandler.call(this,t,...n)};_assertInitialized(t){if(this._initializedSent)return;let n=`[ext-apps] App.${t}() called before connect() completed the ui/initialize handshake. Await app.connect() before calling this method, or move data loading to an ontoolresult handler.`;if(this.options?.strict)throw Error(n);console.warn(`${n}. This will throw in a future release.`)}eventSchemas={toolinput:cE,toolinputpartial:lE,toolresult:SE,toolcancelled:uE,hostcontextchanged:bE};static ONE_SHOT_EVENTS=new Set(["toolinput","toolinputpartial","toolresult","toolcancelled"]);_everHadListener=new Set;_assertHandlerTiming(t){if(!e.ONE_SHOT_EVENTS.has(t)||this._everHadListener.has(t)||(this._everHadListener.add(t),!this._initializedSent))return;let n=`[ext-apps] "${String(t)}" handler registered after connect() completed the ui/initialize handshake. The host may have already sent this notification. Register handlers before calling app.connect().`;if(this.options?.strict)throw Error(n);console.warn(n)}setEventHandler(t,n){n&&this._assertHandlerTiming(t),this._ensureEventSlot(t),this._events.setHandler(t,n)}addEventListener(t,n){this._assertHandlerTiming(t),this._ensureEventSlot(t),this._events.addEventListener(t,n)}removeEventListener(t,n){this._events.removeEventListener(t,n)}_ensureEventSlot(t){if(this._registeredEvents.has(t))return;let n=this.eventSchemas[t];if(!n)throw Error(`Unknown event: ${String(t)}`);this.setNotificationHandler(n.shape.method.value,{params:n.shape.params},o=>{let r=o;t==="hostcontextchanged"&&(this._hostContext={...this._hostContext,...r}),this._events.dispatch(t,r)}),this._registeredEvents.add(t)}getEventHandler(t){return this._events.getHandler(t)}warnIfRequestHandlerReplaced(t,n,o){n&&o&&console.warn(`[MCP Apps] ${t} handler replaced. Previous handler will no longer be called.`)}constructor(t,n={},o={autoResize:!0}){super(o),this._appInfo=t,this._appCapabilities=n,this.options=o,this._methods.replace("notifications/cancelled"),this._methods.replace("notifications/progress"),!o.allowUnsafeEval&&p.config({jitless:!0}),this.replaceRequestHandler("ping",r=>(console.log("Received ping:",r.params),{})),this.setEventHandler("hostcontextchanged",void 0)}buildContext(t){return t}registerAppCapabilities(t){if(this.transport)throw Error("Cannot register capabilities after transport is established");this._appCapabilities=zE(this._appCapabilities,t)}registerTool(t,n,o){if(this._registeredTools[t])throw Error(`Tool ${t} is already registered`);let r=this,i=()=>{r._initializedSent&&r._appCapabilities.tools?.listChanged&&r.sendToolListChanged()},a=n.inputSchema!==void 0,s={title:n.title,description:n.description,inputSchema:n.inputSchema,outputSchema:n.outputSchema,annotations:n.annotations,_meta:n._meta,enabled:!0,enable(){this.enabled=!0,i()},disable(){this.enabled=!1,i()},update(c){Object.assign(this,c),i()},remove(){r._registeredTools[t]===s&&(delete r._registeredTools[t],i())},handler:async(c,l)=>{if(!s.enabled)throw Error(`Tool ${t} is disabled`);let d;if(a){let m=s.inputSchema,f=m?await oz(m,c??{},`Invalid input for tool ${t}: `):c??{};d=await o(f,l)}else d=await o(l);return s.outputSchema&&!d.isError&&(d.structuredContent=await oz(s.outputSchema,d.structuredContent,`Invalid output for tool ${t}: `)),d}};return this._registeredTools[t]=s,!this._appCapabilities.tools&&!this.transport&&this.registerAppCapabilities({tools:{listChanged:!0}}),this.ensureToolHandlersInitialized(),i(),s}_toolHandlersInitialized=!1;ensureToolHandlersInitialized(){this._toolHandlersInitialized||(this._toolHandlersInitialized=!0,this.oncalltool=async(t,n)=>{let o=this._registeredTools[t.name];if(!o)throw Error(`Tool ${t.name} not found`);return o.handler(t.arguments,n)},this.onlisttools=async(t,n)=>({tools:await Promise.all(Object.entries(this._registeredTools).filter(([o,r])=>r.enabled).map(async([o,r])=>{let i={name:o,title:r.title,description:r.description,inputSchema:r.inputSchema?await iz(r.inputSchema,"input"):{type:"object",properties:{}}};return r.outputSchema&&(i.outputSchema=await iz(r.outputSchema,"output")),r.annotations&&(i.annotations=r.annotations),r._meta&&(i._meta=r._meta),i}))}))}async sendToolListChanged(t={}){this._assertInitialized("sendToolListChanged"),await this.notification({method:"notifications/tools/list_changed",params:t})}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("ui/resource-teardown",{params:pE.shape.params,result:fE},(n,o)=>{if(!this._onteardown)throw Error("No onteardown handler set");return this._onteardown(n,o)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(t){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,t),this._oncalltool=t,this.replaceRequestHandler("tools/call",(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("tools/list",(n,o)=>{if(!this._onlisttools)throw Error("No onlisttools handler set");return this._onlisttools(n.params,o)})}assertCapabilityForMethod(t){switch(t){case"sampling/createMessage":if(!this._hostCapabilities?.sampling)throw Error(`Host does not support sampling (required for ${t})`);break}}assertRequestHandlerCapability(t){switch(t){case"tools/call":case"tools/list":if(!this._appCapabilities.tools)throw Error(`Client does not support tool capability (required for ${t})`);return;default:return}}assertNotificationCapability(t){}async callServerTool(t,n){if(this._assertInitialized("callServerTool"),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},{onprogress:()=>{},resetTimeoutOnProgress:!0,...n})}async readServerResource(t,n){return this._assertInitialized("readServerResource"),await this.request({method:"resources/read",params:t},n)}async listServerResources(t,n){return this._assertInitialized("listServerResources"),await this.request({method:"resources/list",params:t},n)}async createSamplingMessage(t,n){return this._assertInitialized("createSamplingMessage"),await this.request({method:"sampling/createMessage",params:t},n)}sendMessage(t,n){return this._assertInitialized("sendMessage"),this.request({method:"ui/message",params:t},sE,n)}sendLog(t){return this.notification({method:"notifications/message",params:t})}updateModelContext(t,n){return this._assertInitialized("updateModelContext"),this.request({method:"ui/update-model-context",params:t},Or,n)}openLink(t,n){return this._assertInitialized("openLink"),this.request({method:"ui/open-link",params:t},oE,n)}sendOpenLink=this.openLink;downloadFile(t,n){return this._assertInitialized("downloadFile"),this.request({method:"ui/download-file",params:t},aE,n)}requestTeardown(t={}){return this.notification({method:"ui/notifications/request-teardown",params:t})}requestDisplayMode(t,n){return this._assertInitialized("requestDisplayMode"),this.request({method:"ui/request-display-mode",params:t},vE,n)}sendSizeChanged(t){return this.notification({method:"ui/notifications/size-changed",params:t})}setupSizeChangedNotifications(){let t=!1,n=0,o=0,r=()=>{t||(t=!0,requestAnimationFrame(()=>{t=!1;let a=document.documentElement,s=a.style.height;a.style.height="max-content";let c=Math.ceil(a.getBoundingClientRect().height);a.style.height=s;let l=Math.ceil(window.innerWidth);(l!==n||c!==o)&&(n=l,o=c,this.sendSizeChanged({width:l,height:c}))}))};r();let i=new ResizeObserver(r);return i.observe(document.documentElement),i.observe(document.body),()=>i.disconnect()}async connect(t=new Nv(window.parent,window.parent),n){if(this.transport)throw Error("App is already connected. Call close() before connecting again.");this._initializedSent=!1;try{await super.connect(t);let o=await this.request({method:"ui/initialize",params:{appCapabilities:this._appCapabilities,appInfo:this._appInfo,protocolVersion:eE}},yE,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._initializedSent=!0,this.options?.autoResize&&this.setupSizeChangedNotifications()}catch(o){throw this.close(),o}}};function wE(e){return e==="inline"||e==="pip"}function Dd(e){let t=document.getElementById("inline-view");if(!t)return;let n=document.documentElement;if(wE(e?.displayMode))n.dataset.view="inline",t.hidden=!1;else{delete n.dataset.view,t.hidden=!0;let a=document.getElementById("inline-saved");a&&(a.hidden=!0)}let r=e?.theme,i=document.body;i?.dataset&&(r==="light"||r==="dark")&&(i.dataset.theme=r)}function Uv(){let e=document.getElementById("inline-saved");e&&(e.hidden=!1)}async function kE(e){if(!e.getHostContext()?.availableDisplayModes?.includes("fullscreen")){Uv();return}try{if((await e.requestDisplayMode({mode:"fullscreen"})).mode!=="fullscreen"){Uv(),Dd(e.getHostContext());return}let o=document.getElementById("inline-saved");o&&(o.hidden=!0),Dd({...e.getHostContext()??{},displayMode:"fullscreen"})}catch{Uv()}}var IE=window.parent;if(IE!==window){let e=new Ud({name:"mcp-context-card",version:"1.9.1"},{availableDisplayModes:["inline","pip","fullscreen"]},{autoResize:!0});e.addEventListener("hostcontextchanged",()=>{Dd(e.getHostContext())}),document.getElementById("open-full-card")?.addEventListener("click",()=>{kE(e)}),e.connect().then(()=>Dd(e.getHostContext())).catch(()=>{})}})();
package/dist/card-gen.js CHANGED
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * card-gen - write docs/card.html from the three sources, so the card is
3
3
  * browsable on GitHub and screenshot-able for the README. Same renderer as
4
- * GET /card, the CLI, and render_context_card for a client without MCP Apps:
5
- * the plain card, without the MCP Apps helper. The ui:// resource is
4
+ * GET /card, the CLI, and save_context_card: the plain card, without the MCP Apps helper. The ui:// resource is
6
5
  * rendered at read time and is not one of these files. A plain card over
7
6
  * 64 KB fails here, which fails card:check.
8
7
  *
@@ -24,7 +24,7 @@
24
24
  import { writeFileSync } from "node:fs";
25
25
  import { dirname, join } from "node:path";
26
26
  import { fileURLToPath, pathToFileURL } from "node:url";
27
- import { parseAgentsMd } from "./agents-md.js";
27
+ import { bodySections, parseAgentsMd } from "./agents-md.js";
28
28
  import { parseFafm } from "./faf/parse-fafm.js";
29
29
  import { parseFafa } from "./faf/parse-fafa.js";
30
30
  import { META_NS, catalogPublisher, fafaFile } from "./identity.js";
@@ -62,7 +62,7 @@ export function buildCatalog(root, opts = {}) {
62
62
  type: "text/markdown",
63
63
  description: agents
64
64
  ? (() => {
65
- const h = agents.sections.filter((s) => s.level > 1).map((s) => s.heading);
65
+ const h = bodySections(agents).map((s) => s.heading);
66
66
  return `Agent instructions for this project — ${h.length} section(s): ${h
67
67
  .slice(0, 6)
68
68
  .join(", ")}${h.length > 6 ? ", …" : ""}.`;
@@ -1,7 +1,7 @@
1
1
  /** Server identity constants, in their own module so any file can import
2
2
  * them without pulling in the whole server. */
3
3
  export declare const NAME = "mcp-context-card";
4
- export declare const VERSION = "1.8.0";
4
+ export declare const VERSION = "1.9.1";
5
5
  export declare const SERVER_CARD_URI = "mcp-context-card://server-card";
6
6
  /** The server's registry identity: reverse-DNS, the same as server.json `name`.
7
7
  * It is also `serverInfo.name`, so a Server Card never contradicts the live
package/dist/constants.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /** Server identity constants, in their own module so any file can import
2
2
  * them without pulling in the whole server. */
3
3
  export const NAME = "mcp-context-card";
4
- export const VERSION = "1.8.0";
4
+ export const VERSION = "1.9.1";
5
5
  export const SERVER_CARD_URI = "mcp-context-card://server-card";
6
6
  /** The server's registry identity: reverse-DNS, the same as server.json `name`.
7
7
  * It is also `serverInfo.name`, so a Server Card never contradicts the live
@@ -38,6 +38,19 @@ export declare function cardOneLiner(id: AgentIdentity | null, max?: number): st
38
38
  export declare const PLAIN_CARD_LIMIT_BYTES: number;
39
39
  /** True when a plain card exceeds 64 KB. card:check fails on that. */
40
40
  export declare function plainCardExceedsLimit(html: string): boolean;
41
+ /** The name the card shows: the business card's display name, else its
42
+ * name, else the folder. */
43
+ export declare function cardName(id: AgentIdentity | null, root: string): string;
44
+ /** What the card counts: one source for the mini card and the line the model
45
+ * gets, so they agree. Sections are `bodySections` (level-1 headings are not
46
+ * sections); `agents` is false when there is no AGENTS.md. */
47
+ export declare function cardCounts(root: string): {
48
+ name: string;
49
+ agents: boolean;
50
+ sections: number;
51
+ facts: number;
52
+ identity: boolean;
53
+ };
41
54
  /** The project's card: read its files and draw. No helper, no inline view. */
42
55
  export declare function renderCard(root: string, opts?: CardOptions): string;
43
56
  /**
@@ -15,7 +15,7 @@
15
15
  import { readFileSync } from "node:fs";
16
16
  import { basename, dirname, join, resolve } from "node:path";
17
17
  import { fileURLToPath } from "node:url";
18
- import { parseAgentsMd } from "./agents-md.js";
18
+ import { bodySections, parseAgentsMd } from "./agents-md.js";
19
19
  import { parseFafm } from "./memory.js";
20
20
  import { resolveIdentity, serverCardMeta, META_NS } from "./identity.js";
21
21
  import { PUBLISH_MEMORY_ENV, SERVER_CARD_URI } from "./constants.js";
@@ -76,13 +76,22 @@ function withCardApp(html) {
76
76
  const end = close + "</script>".length;
77
77
  return `${html.slice(0, end)}\n<script data-card-app>\n${CARD_APP_JS}\n</script>${html.slice(end)}`;
78
78
  }
79
- /** The same sections, facts and identity the full card is built from. */
80
- function inlineCounts(root) {
79
+ /** The name the card shows: the business card's display name, else its
80
+ * name, else the folder. */
81
+ export function cardName(id, root) {
82
+ return id?.displayName ?? id?.name ?? basename(resolve(root));
83
+ }
84
+ /** What the card counts: one source for the mini card and the line the model
85
+ * gets, so they agree. Sections are `bodySections` (level-1 headings are not
86
+ * sections); `agents` is false when there is no AGENTS.md. */
87
+ export function cardCounts(root) {
81
88
  const agents = parseAgentsMd(join(root, "AGENTS.md"));
82
89
  const mem = parseFafm(join(root, "project.fafm"));
83
90
  const id = resolveIdentity(root);
84
91
  return {
85
- sections: agents?.sections.filter((s) => s.level > 1).length ?? 0,
92
+ name: cardName(id, root),
93
+ agents: agents !== null,
94
+ sections: bodySections(agents).length,
86
95
  facts: mem.facts.length,
87
96
  identity: id !== null,
88
97
  };
@@ -138,7 +147,7 @@ export function renderCard(root, opts = {}) {
138
147
  */
139
148
  export function renderAppsCard(root, opts = {}) {
140
149
  const card = readCard(root, opts);
141
- const drawn = withInlineView(renderBusinessCard(card, opts), inlineView(card, inlineCounts(root)));
150
+ const drawn = withInlineView(renderBusinessCard(card, opts), inlineView(card, cardCounts(root)));
142
151
  return withCardApp(drawn);
143
152
  }
144
153
  /** The (i) panel for an agent or server card. */
@@ -167,9 +176,9 @@ export function readCard(root, opts = {}) {
167
176
  // CONTEXT — one <details> per AGENTS.md section, collapsed by default;
168
177
  // `expanded` renders them all open. The "# AGENTS.md" top-level heading is
169
178
  // dropped; its intro rides above the sections.
170
- const bodySections = agents?.sections.filter((s) => s.level > 1) ?? [];
171
- const toc = bodySections.length
172
- ? `<ul class="toc">${bodySections
179
+ const body = bodySections(agents);
180
+ const toc = body.length
181
+ ? `<ul class="toc">${body
173
182
  .map((s) => `<li><a href="#${slug(s.heading)}">${escapeHtml(s.heading)}</a></li>`)
174
183
  .join("")}</ul>`
175
184
  : "";
@@ -177,11 +186,11 @@ export function readCard(root, opts = {}) {
177
186
  .filter(Boolean)
178
187
  .join("\n\n");
179
188
  const openAttr = flat ? " open" : "";
180
- const sections = bodySections
189
+ const sections = body
181
190
  .map((s) => `<details class="ctx-section"${openAttr} id="${slug(s.heading)}"><summary>${escapeHtml(s.heading)}</summary><div class="md">${renderMarkdown(s.body)}</div></details>`)
182
191
  .join("");
183
192
  const contextBody = agents
184
- ? `<div class="ctx-nav">${toc}${bodySections.length
193
+ ? `<div class="ctx-nav">${toc}${body.length
185
194
  ? `<button type="button" class="xall" hidden>${flat ? "Collapse all" : "Expand all"}</button>`
186
195
  : ""}</div>
187
196
  ${preamble ? `<div class="ctx-preamble md">${renderMarkdown(preamble)}</div>` : ""}
@@ -289,7 +298,7 @@ export function renderCardText(root, opts = {}) {
289
298
  const mem = parseFafm(join(root, "project.fafm"));
290
299
  const id = resolveIdentity(root);
291
300
  const meta = serverCardMeta();
292
- const name = id?.displayName ?? id?.name ?? basename(resolve(root));
301
+ const name = cardName(id, root);
293
302
  const plural = (n, w) => `${n} ${w}${n === 1 ? "" : "s"}`;
294
303
  const out = [`### ${name} — business card`];
295
304
  const title = cardTitle(id);
@@ -305,7 +314,7 @@ export function renderCardText(root, opts = {}) {
305
314
  out.push(pills.join(" · "));
306
315
  if (id?.description)
307
316
  out.push(id.description);
308
- const sections = agents?.sections.filter((s) => s.level > 1) ?? [];
317
+ const sections = bodySections(agents);
309
318
  out.push(agents
310
319
  ? `**Context — AGENTS.md** · ${plural(sections.length, "section")}\n${sections.map((s) => s.heading).join(" · ")}`
311
320
  : "**Context — AGENTS.md** · none yet. `author_agents_md` drafts one from this repo's real build and test commands, nothing invented.");
package/dist/server.js CHANGED
@@ -20,13 +20,13 @@
20
20
  import { serveStdio } from "@modelcontextprotocol/server/stdio";
21
21
  import { CLIENT_CAPABILITIES_META_KEY, inputRequired, inputResponse, isInputRequiredResult, Server } from "@modelcontextprotocol/server";
22
22
  import { existsSync, statSync, writeFileSync } from "node:fs";
23
- import { basename, dirname, join } from "node:path";
23
+ import { dirname, join } from "node:path";
24
24
  import { fileURLToPath, pathToFileURL } from "node:url";
25
25
  import { findSection, parseAgentsMd } from "./agents-md.js";
26
26
  import { authorAgentsMd } from "./author.js";
27
27
  import { forget, parseFafm, recall, remember } from "./memory.js";
28
- import { identity, resolveIdentity, whoami } from "./identity.js";
29
- import { renderAppsCard, renderCard, renderCardText, safeAccent } from "./render-card.js";
28
+ import { identity, whoami } from "./identity.js";
29
+ import { cardCounts, renderAppsCard, renderCard, renderCardText, safeAccent } from "./render-card.js";
30
30
  export { NAME, VERSION, SERVER_CARD_URI } from "./constants.js";
31
31
  import { VERSION, SERVER_CARD_URI, CARD_UI_URI, MCP_APP_MIME, UI_EXTENSION, REGISTRY_NAME, TITLE, SERVER_CARD_MEDIA_TYPE, cardAppFromEnv, toCardApp } from "./constants.js";
32
32
  import { serverCard } from "./server-card.js";
@@ -246,7 +246,7 @@ export function createServer(root = ROOT, opts = {}) {
246
246
  name: "list_agents_md_sections",
247
247
  title: "List AGENTS.md Sections",
248
248
  annotations: { title: "List AGENTS.md Sections", readOnlyHint: true, idempotentHint: true, openWorldHint: false },
249
- description: "List the headings in this project's AGENTS.md, so a client can pull one section instead of spending context on the whole file.",
249
+ description: "List the headings in this project's AGENTS.md, with their levels, so a client can pull one section instead of spending context on the whole file. Level 1 is the file's title; the context card counts level 2 and below as its sections, so this list can be one longer than the card's count.",
250
250
  inputSchema: { type: "object", properties: {} },
251
251
  },
252
252
  {
@@ -322,14 +322,28 @@ export function createServer(root = ROOT, opts = {}) {
322
322
  // resource. Both key forms, as the official ext-apps helper writes them.
323
323
  _meta: { ui: { resourceUri: CARD_UI_URI }, "ui/resourceUri": CARD_UI_URI },
324
324
  annotations: { title: "Render Context Card", readOnlyHint: true, idempotentHint: true, openWorldHint: false },
325
- description: "Render the project's business card as one self-contained HTML page a person can read or screenshot. Front: name, title, one-liner. Flip it for the back: About, Skills (when the .fafa lists any), Context (AGENTS.md), Memory, Discovery. layout:\"portrait\" for the tall view; expanded:true renders both faces flat with every section open. Also served at GET /card (?expand=all) over the HTTP transport.",
326
- inputSchema: { type: "object", properties: CARD_ARGS },
325
+ description: "Show the project's context card. In a client that displays MCP Apps, the host draws it in the chat (a mini card in a chat panel). Anywhere else the reply is the card as Markdown: identity, AGENTS.md sections, memory, discovery. format: \"markdown\" returns the Markdown card in any client, for a user who asks for the card as text. For the original card in a browser, use save_context_card (also GET /card over HTTP).",
326
+ inputSchema: {
327
+ type: "object",
328
+ properties: {
329
+ format: {
330
+ type: "string",
331
+ enum: ["auto", "markdown"],
332
+ description: "auto (default): the host draws the card if it displays MCP Apps, else the Markdown card; markdown: the Markdown card in any client",
333
+ },
334
+ detail: {
335
+ type: "string",
336
+ enum: ["tldr", "full"],
337
+ description: "the Markdown card: tldr (default) shows five facts, each cut short; full shows every fact whole",
338
+ },
339
+ },
340
+ },
327
341
  },
328
342
  {
329
343
  name: "save_context_card",
330
344
  title: "Save Context Card",
331
345
  annotations: { title: "Save Context Card", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
332
- description: "Write the context card to context-card.html in the project and, when this server runs locally, open it in the person's browser. Returns the card as Markdown (identity, AGENTS.md sections, memory, discovery) plus a clickable link to the saved file. Use this instead of pasting render_context_card's HTML into a chat that can't display it. Replaces any earlier context-card.html.",
346
+ description: "Write the context card to context-card.html in the project and, when this server runs locally, open it in the person's browser. Returns the card as Markdown (identity, AGENTS.md sections, memory, discovery) plus a clickable link to the saved file. Use this when the user wants the full card in a browser. Replaces any earlier context-card.html.",
333
347
  inputSchema: {
334
348
  type: "object",
335
349
  properties: {
@@ -397,18 +411,21 @@ export function createServer(root = ROOT, opts = {}) {
397
411
  case "whoami":
398
412
  return text(whoami(root));
399
413
  case "render_context_card": {
400
- if (hostRendersApps(server, ctx, opts.era)) {
401
- // The host renders the card itself from CARD_UI_URI, so the model
402
- // gets a short summary instead of a whole HTML page.
403
- const doc = parseAgentsMd(AGENTS);
404
- const mem = parseFafm(FAFM);
405
- const sections = doc?.sections.length ?? 0;
406
- const facts = mem.facts.length;
407
- return text(`Showing the context card for ${resolveIdentity(root)?.name ?? basename(root)}: AGENTS.md with ${sections} section${sections === 1 ? "" : "s"}, ` +
408
- `${facts} remembered fact${facts === 1 ? "" : "s"}, and this server's identity. ` +
409
- "The card is displayed to the user; call read_agents_md or recall for the text itself.");
414
+ // One command, three surfaces: an MCP Apps host draws the card from
415
+ // CARD_UI_URI (the mini card in a panel); anywhere else the reply is
416
+ // the text card (Markdown). HTML only goes to a browser: save_context_card,
417
+ // GET /card, the CLI. format: "markdown" asks for the text card anywhere.
418
+ if (args.format !== "markdown" && hostRendersApps(server, ctx, opts.era)) {
419
+ const c = cardCounts(root);
420
+ const n = (k, w) => `${k} ${w}${k === 1 ? "" : "s"}`;
421
+ const context = c.agents ? `AGENTS.md with ${n(c.sections, "section")}` : "no AGENTS.md yet";
422
+ return text(`Showing the context card for ${c.name} in the chat: ` +
423
+ `${context}, ${n(c.facts, "remembered fact")}, and this server's identity. ` +
424
+ "This client displays MCP Apps, so the card is drawn here. If the user says they can't see it, " +
425
+ "call save_context_card. For the card as Markdown, call render_context_card with format: \"markdown\".");
410
426
  }
411
- return text(renderCard(root, cardOptions(args)));
427
+ return text(`${renderCardText(root, { detail: args.detail === "full" ? "full" : "tldr" })}\n\n` +
428
+ "_The original card: save_context_card writes context-card.html and, when this server runs locally, opens it in the browser._");
412
429
  }
413
430
  case "save_context_card": {
414
431
  const out = join(root, "context-card.html");
@@ -32,7 +32,7 @@ Streamable HTTP endpoint at `<origin>/mcp` and its protocol versions):
32
32
  {
33
33
  "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json",
34
34
  "name": "io.github.Wolfe-Jam/mcp-context-card",
35
- "version": "1.8.0",
35
+ "version": "1.9.1",
36
36
  "title": "MCP Context Card",
37
37
  "description": "MCP server for a project's context (AGENTS.md), memory, and identity — base or drop-in extension.",
38
38
  "websiteUrl": "https://github.com/Wolfe-Jam/mcp-context-card",
@@ -85,7 +85,7 @@ publisher lists artifacts, each entry keyed by its **media type** (`type`).
85
85
  GET /.well-known/ai-catalog.json
86
86
  ```
87
87
 
88
- Served from `https://ctx.example.com` (1.8.0, this repo's own files):
88
+ Served from `https://ctx.example.com` (1.9.1, this repo's own files):
89
89
 
90
90
  ```jsonc
91
91
  {
@@ -137,10 +137,10 @@ with `MCP_CONTEXT_CARD_PUBLISH_MEMORY=1`.
137
137
  ## The invariant
138
138
 
139
139
  The same three sources also render as **the card** — `GET /card` /
140
- `render_context_card` / `docs/card.html` — the human view of exactly what a
141
- machine reads below. Hosts that support MCP Apps show it inline from the
142
- `ui://mcp-context-card/card.html` resource; elsewhere `save_context_card`
143
- writes it to `context-card.html` and returns it as Markdown.
140
+ `save_context_card` / `docs/card.html` — the human view of exactly what a
141
+ machine reads below. Hosts that support MCP Apps draw it in the chat from the
142
+ `ui://mcp-context-card/card.html` resource when `render_context_card` runs;
143
+ elsewhere that tool returns the card as Markdown.
144
144
 
145
145
  The same three sources back **both** discovery mechanisms:
146
146
 
package/docs/TRANSPORT.md CHANGED
@@ -46,10 +46,23 @@ the document's size changes. The helper is bundled into that resource at
46
46
  build time; it makes no network request. A page with no host
47
47
  (`window.parent === window`) does not start the exchange.
48
48
 
49
- `render_context_card` for a client without MCP Apps, `save_context_card`,
50
- `GET /card`, and `mcp-context-card card` return the plain card. That page
51
- does not include the helper or the inline view. `card:check` fails if it
52
- exceeds 64 KB.
49
+ `save_context_card`, `GET /card`, and `mcp-context-card card` return the
50
+ plain card. That page does not include the helper or the inline view.
51
+ `card:check` fails if it exceeds 64 KB.
52
+
53
+ `render_context_card` returns no HTML. For a client that declared MCP Apps it
54
+ returns one line (the counts, and `save_context_card` in case the host
55
+ doesn't draw it); for any other client, the card as Markdown (the same text
56
+ `save_context_card` returns, with no file written). `format: "markdown"`
57
+ returns that Markdown card for any client, MCP Apps or not. Over the stateless HTTP
58
+ transport a 2025 client's `initialize` capabilities don't reach the later
59
+ `tools/call` (each POST is a fresh server), so a 2025 MCP Apps host there
60
+ gets the Markdown card; it still draws the card from the tool's `_meta.ui`.
61
+ The mini card and that line share one count (`cardCounts`); level-1
62
+ headings, such as the `# AGENTS.md` title, are not sections. The server
63
+ instructions are unchanged from 1.8.0 on purpose: goose
64
+ desktop 1.52 stopped loading a build whose instructions changed (cause not
65
+ found), and a test pins them.
53
66
 
54
67
  After `ui/initialize`, `App.getHostContext()` is the host context
55
68
  (`McpUiHostContext` in `@modelcontextprotocol/ext-apps`). The fields this
@@ -172,7 +185,7 @@ they carry no private data.
172
185
  | `MCP_CONTEXT_CARD_ALLOWED_HOSTS=ctx.example.com` | extra Host names to accept, e.g. a reverse proxy's public name in front of a local bind |
173
186
  | `MCP_CONTEXT_CARD_ALLOWED_ORIGINS=https://app.example.com` | browser origins allowed to call the server |
174
187
  | `MCP_CONTEXT_CARD_PUBLISH_MEMORY=1` | serve `project.fafm`, list it in the AI Catalog, and show memory facts on an exposed `/card` |
175
- | `MCP_CONTEXT_CARD_APP=plain` | the `ui://mcp-context-card/card.html` card an MCP Apps host reads is the plain card (~30 KB, no MCP Apps messages, the host's default frame) instead of the card with the MCP Apps helper (`helper`, the default, ~700 KB). Tool text, `save_context_card`, `GET /card` and the CLI are the plain card either way. Any case is read (`PLAIN` is `plain`); any other value prints one line to stderr and uses `helper`. `list_context_sources` reports the active value (`surfaces.mcp.cardApp`). |
188
+ | `MCP_CONTEXT_CARD_APP=plain` | the `ui://mcp-context-card/card.html` card an MCP Apps host reads is the plain card (~30 KB, no MCP Apps messages, the host's default frame) instead of the card with the MCP Apps helper (`helper`, the default, ~700 KB). `save_context_card`, `GET /card` and the CLI are the plain card either way; `render_context_card`'s reply (a line or Markdown) doesn't change. Any case is read (`PLAIN` is `plain`); any other value prints one line to stderr and uses `helper`. `list_context_sources` reports the active value (`surfaces.mcp.cardApp`). |
176
189
 
177
190
  ### Where memory shows
178
191
 
@@ -205,7 +205,7 @@ details.ctx-section>.md{padding:0 0 16px}
205
205
  </details>
206
206
  <div class="id">
207
207
  <h1>mcp-context-card</h1>
208
- <p class="title">MCP server · v1.8.0</p>
208
+ <p class="title">MCP server · v1.9.1</p>
209
209
  <p class="oneliner">The essential MCP components for a project's context (AGENTS.md), cross-session memory, and identity</p>
210
210
  </div>
211
211
  <div class="foot-front"><span class="domain">faf.one</span><div class="pills"><span class="pill">io.github.Wolfe-Jam</span><span class="pill accent">published</span><span class="pill">MIT</span></div></div>
@@ -223,7 +223,7 @@ details.ctx-section>.md{padding:0 0 16px}
223
223
  </div>
224
224
  </details>
225
225
  <div class="tabbar"><label for="t-about">About</label><label for="t-context">Context</label><label for="t-memory">Memory</label><label for="t-discovery">Discovery</label></div>
226
- <div class="panes"><div class="pane p-about"><p class="label">About</p><p class="lead">The essential MCP components for a project's context (AGENTS.md), cross-session memory, and identity — a base MCP on its own, or a drop-in extension for any existing MCP server, discoverable through the Server Card _meta block and a self-published ai-catalog.json.</p><table class="kv"><tbody><tr><th>Agent ID</th><td><code>urn:air:faf.one:agent:mcp-context-card</code></td></tr><tr><th>Publisher</th><td>io.github.Wolfe-Jam</td></tr><tr><th>Version</th><td>v1.8.0</td></tr><tr><th>Status</th><td>published</td></tr><tr><th>License</th><td>MIT</td></tr><tr><th>Package (npm)</th><td><code>mcp-context-card</code></td></tr></tbody></table></div><div class="pane p-context"><p class="label">Context — AGENTS.md</p><div class="ctx-nav"><ul class="toc"><li><a href="#setup">Setup</a></li><li><a href="#build">Build</a></li><li><a href="#test">Test</a></li><li><a href="#layout">Layout</a></li><li><a href="#conventions">Conventions</a></li><li><a href="#the-invariant">The invariant</a></li><li><a href="#safety">Safety</a></li><li><a href="#definition-of-done">Definition of done</a></li><li><a href="#authoring-this-file">Authoring this file</a></li></ul><button type="button" class="xall" hidden>Expand all</button></div>
226
+ <div class="panes"><div class="pane p-about"><p class="label">About</p><p class="lead">The essential MCP components for a project's context (AGENTS.md), cross-session memory, and identity — a base MCP on its own, or a drop-in extension for any existing MCP server, discoverable through the Server Card _meta block and a self-published ai-catalog.json.</p><table class="kv"><tbody><tr><th>Agent ID</th><td><code>urn:air:faf.one:agent:mcp-context-card</code></td></tr><tr><th>Publisher</th><td>io.github.Wolfe-Jam</td></tr><tr><th>Version</th><td>v1.9.1</td></tr><tr><th>Status</th><td>published</td></tr><tr><th>License</th><td>MIT</td></tr><tr><th>Package (npm)</th><td><code>mcp-context-card</code></td></tr></tbody></table></div><div class="pane p-context"><p class="label">Context — AGENTS.md</p><div class="ctx-nav"><ul class="toc"><li><a href="#setup">Setup</a></li><li><a href="#build">Build</a></li><li><a href="#test">Test</a></li><li><a href="#layout">Layout</a></li><li><a href="#conventions">Conventions</a></li><li><a href="#the-invariant">The invariant</a></li><li><a href="#safety">Safety</a></li><li><a href="#definition-of-done">Definition of done</a></li><li><a href="#authoring-this-file">Authoring this file</a></li></ul><button type="button" class="xall" hidden>Expand all</button></div>
227
227
  <div class="ctx-preamble md"><p><code>mcp-context-card</code> is the essential MCP server for a project's <strong>context</strong> (this file), <strong>memory</strong>, and <strong>identity</strong> — usable as your base MCP, or dropped into any existing MCP server as an extension. Discoverable to any MCP client through the two surfaces already in the ecosystem: the Server Card <code>_meta</code> block and <code>ai-catalog.json</code> sibling entries.</p>
228
228
  <p><code>read_agents_md</code> serves this file, section by section, over the same MCP connection.</p></div>
229
229
  <div class="ctx-body"><details class="ctx-section" id="setup"><summary>Setup</summary><div class="md"><pre><code class="language-bash">npm ci</code></pre>
@@ -205,7 +205,7 @@ details.ctx-section>.md{padding:0 0 16px}
205
205
  </details>
206
206
  <div class="id">
207
207
  <h1>mcp-context-card</h1>
208
- <p class="title">MCP server · v1.8.0</p>
208
+ <p class="title">MCP server · v1.9.1</p>
209
209
  <p class="oneliner">The essential MCP components for a project's context (AGENTS.md), cross-session memory, and identity</p>
210
210
  </div>
211
211
  <div class="foot-front"><span class="domain">faf.one</span><div class="pills"><span class="pill">io.github.Wolfe-Jam</span><span class="pill accent">published</span><span class="pill">MIT</span></div></div>
@@ -223,7 +223,7 @@ details.ctx-section>.md{padding:0 0 16px}
223
223
  </div>
224
224
  </details>
225
225
  <div class="tabbar"><label for="t-about">About</label><label for="t-context">Context</label><label for="t-memory">Memory</label><label for="t-discovery">Discovery</label></div>
226
- <div class="panes"><div class="pane p-about"><p class="label">About</p><p class="lead">The essential MCP components for a project's context (AGENTS.md), cross-session memory, and identity — a base MCP on its own, or a drop-in extension for any existing MCP server, discoverable through the Server Card _meta block and a self-published ai-catalog.json.</p><table class="kv"><tbody><tr><th>Agent ID</th><td><code>urn:air:faf.one:agent:mcp-context-card</code></td></tr><tr><th>Publisher</th><td>io.github.Wolfe-Jam</td></tr><tr><th>Version</th><td>v1.8.0</td></tr><tr><th>Status</th><td>published</td></tr><tr><th>License</th><td>MIT</td></tr><tr><th>Package (npm)</th><td><code>mcp-context-card</code></td></tr></tbody></table></div><div class="pane p-context"><p class="label">Context — AGENTS.md</p><div class="ctx-nav"><ul class="toc"><li><a href="#setup">Setup</a></li><li><a href="#build">Build</a></li><li><a href="#test">Test</a></li><li><a href="#layout">Layout</a></li><li><a href="#conventions">Conventions</a></li><li><a href="#the-invariant">The invariant</a></li><li><a href="#safety">Safety</a></li><li><a href="#definition-of-done">Definition of done</a></li><li><a href="#authoring-this-file">Authoring this file</a></li></ul><button type="button" class="xall" hidden>Expand all</button></div>
226
+ <div class="panes"><div class="pane p-about"><p class="label">About</p><p class="lead">The essential MCP components for a project's context (AGENTS.md), cross-session memory, and identity — a base MCP on its own, or a drop-in extension for any existing MCP server, discoverable through the Server Card _meta block and a self-published ai-catalog.json.</p><table class="kv"><tbody><tr><th>Agent ID</th><td><code>urn:air:faf.one:agent:mcp-context-card</code></td></tr><tr><th>Publisher</th><td>io.github.Wolfe-Jam</td></tr><tr><th>Version</th><td>v1.9.1</td></tr><tr><th>Status</th><td>published</td></tr><tr><th>License</th><td>MIT</td></tr><tr><th>Package (npm)</th><td><code>mcp-context-card</code></td></tr></tbody></table></div><div class="pane p-context"><p class="label">Context — AGENTS.md</p><div class="ctx-nav"><ul class="toc"><li><a href="#setup">Setup</a></li><li><a href="#build">Build</a></li><li><a href="#test">Test</a></li><li><a href="#layout">Layout</a></li><li><a href="#conventions">Conventions</a></li><li><a href="#the-invariant">The invariant</a></li><li><a href="#safety">Safety</a></li><li><a href="#definition-of-done">Definition of done</a></li><li><a href="#authoring-this-file">Authoring this file</a></li></ul><button type="button" class="xall" hidden>Expand all</button></div>
227
227
  <div class="ctx-preamble md"><p><code>mcp-context-card</code> is the essential MCP server for a project's <strong>context</strong> (this file), <strong>memory</strong>, and <strong>identity</strong> — usable as your base MCP, or dropped into any existing MCP server as an extension. Discoverable to any MCP client through the two surfaces already in the ecosystem: the Server Card <code>_meta</code> block and <code>ai-catalog.json</code> sibling entries.</p>
228
228
  <p><code>read_agents_md</code> serves this file, section by section, over the same MCP connection.</p></div>
229
229
  <div class="ctx-body"><details class="ctx-section" id="setup"><summary>Setup</summary><div class="md"><pre><code class="language-bash">npm ci</code></pre>
package/docs/card.html CHANGED
@@ -205,7 +205,7 @@ details.ctx-section>.md{padding:0 0 16px}
205
205
  </details>
206
206
  <div class="id">
207
207
  <h1>mcp-context-card</h1>
208
- <p class="title">MCP server · v1.8.0</p>
208
+ <p class="title">MCP server · v1.9.1</p>
209
209
  <p class="oneliner">The essential MCP components for a project's context (AGENTS.md), cross-session memory, and identity</p>
210
210
  </div>
211
211
  <div class="foot-front"><span class="domain">faf.one</span><div class="pills"><span class="pill">io.github.Wolfe-Jam</span><span class="pill accent">published</span><span class="pill">MIT</span></div></div>
@@ -223,7 +223,7 @@ details.ctx-section>.md{padding:0 0 16px}
223
223
  </div>
224
224
  </details>
225
225
  <div class="tabbar"><label for="t-about">About</label><label for="t-context">Context</label><label for="t-memory">Memory</label><label for="t-discovery">Discovery</label></div>
226
- <div class="panes"><div class="pane p-about"><p class="label">About</p><p class="lead">The essential MCP components for a project's context (AGENTS.md), cross-session memory, and identity — a base MCP on its own, or a drop-in extension for any existing MCP server, discoverable through the Server Card _meta block and a self-published ai-catalog.json.</p><table class="kv"><tbody><tr><th>Agent ID</th><td><code>urn:air:faf.one:agent:mcp-context-card</code></td></tr><tr><th>Publisher</th><td>io.github.Wolfe-Jam</td></tr><tr><th>Version</th><td>v1.8.0</td></tr><tr><th>Status</th><td>published</td></tr><tr><th>License</th><td>MIT</td></tr><tr><th>Package (npm)</th><td><code>mcp-context-card</code></td></tr></tbody></table></div><div class="pane p-context"><p class="label">Context — AGENTS.md</p><div class="ctx-nav"><ul class="toc"><li><a href="#setup">Setup</a></li><li><a href="#build">Build</a></li><li><a href="#test">Test</a></li><li><a href="#layout">Layout</a></li><li><a href="#conventions">Conventions</a></li><li><a href="#the-invariant">The invariant</a></li><li><a href="#safety">Safety</a></li><li><a href="#definition-of-done">Definition of done</a></li><li><a href="#authoring-this-file">Authoring this file</a></li></ul><button type="button" class="xall" hidden>Expand all</button></div>
226
+ <div class="panes"><div class="pane p-about"><p class="label">About</p><p class="lead">The essential MCP components for a project's context (AGENTS.md), cross-session memory, and identity — a base MCP on its own, or a drop-in extension for any existing MCP server, discoverable through the Server Card _meta block and a self-published ai-catalog.json.</p><table class="kv"><tbody><tr><th>Agent ID</th><td><code>urn:air:faf.one:agent:mcp-context-card</code></td></tr><tr><th>Publisher</th><td>io.github.Wolfe-Jam</td></tr><tr><th>Version</th><td>v1.9.1</td></tr><tr><th>Status</th><td>published</td></tr><tr><th>License</th><td>MIT</td></tr><tr><th>Package (npm)</th><td><code>mcp-context-card</code></td></tr></tbody></table></div><div class="pane p-context"><p class="label">Context — AGENTS.md</p><div class="ctx-nav"><ul class="toc"><li><a href="#setup">Setup</a></li><li><a href="#build">Build</a></li><li><a href="#test">Test</a></li><li><a href="#layout">Layout</a></li><li><a href="#conventions">Conventions</a></li><li><a href="#the-invariant">The invariant</a></li><li><a href="#safety">Safety</a></li><li><a href="#definition-of-done">Definition of done</a></li><li><a href="#authoring-this-file">Authoring this file</a></li></ul><button type="button" class="xall" hidden>Expand all</button></div>
227
227
  <div class="ctx-preamble md"><p><code>mcp-context-card</code> is the essential MCP server for a project's <strong>context</strong> (this file), <strong>memory</strong>, and <strong>identity</strong> — usable as your base MCP, or dropped into any existing MCP server as an extension. Discoverable to any MCP client through the two surfaces already in the ecosystem: the Server Card <code>_meta</code> block and <code>ai-catalog.json</code> sibling entries.</p>
228
228
  <p><code>read_agents_md</code> serves this file, section by section, over the same MCP connection.</p></div>
229
229
  <div class="ctx-body"><details class="ctx-section" id="setup"><summary>Setup</summary><div class="md"><pre><code class="language-bash">npm ci</code></pre>
Binary file
Binary file
Binary file
package/docs/index.html CHANGED
@@ -205,7 +205,7 @@ details.ctx-section>.md{padding:0 0 16px}
205
205
  </details>
206
206
  <div class="id">
207
207
  <h1>mcp-context-card</h1>
208
- <p class="title">MCP server · v1.8.0</p>
208
+ <p class="title">MCP server · v1.9.1</p>
209
209
  <p class="oneliner">The essential MCP components for a project's context (AGENTS.md), cross-session memory, and identity</p>
210
210
  </div>
211
211
  <div class="foot-front"><span class="domain">faf.one</span><div class="pills"><span class="pill">io.github.Wolfe-Jam</span><span class="pill accent">published</span><span class="pill">MIT</span></div></div>
@@ -223,7 +223,7 @@ details.ctx-section>.md{padding:0 0 16px}
223
223
  </div>
224
224
  </details>
225
225
  <div class="tabbar"><label for="t-about">About</label><label for="t-context">Context</label><label for="t-memory">Memory</label><label for="t-discovery">Discovery</label></div>
226
- <div class="panes"><div class="pane p-about"><p class="label">About</p><p class="lead">The essential MCP components for a project's context (AGENTS.md), cross-session memory, and identity — a base MCP on its own, or a drop-in extension for any existing MCP server, discoverable through the Server Card _meta block and a self-published ai-catalog.json.</p><table class="kv"><tbody><tr><th>Agent ID</th><td><code>urn:air:faf.one:agent:mcp-context-card</code></td></tr><tr><th>Publisher</th><td>io.github.Wolfe-Jam</td></tr><tr><th>Version</th><td>v1.8.0</td></tr><tr><th>Status</th><td>published</td></tr><tr><th>License</th><td>MIT</td></tr><tr><th>Package (npm)</th><td><code>mcp-context-card</code></td></tr></tbody></table></div><div class="pane p-context"><p class="label">Context — AGENTS.md</p><div class="ctx-nav"><ul class="toc"><li><a href="#setup">Setup</a></li><li><a href="#build">Build</a></li><li><a href="#test">Test</a></li><li><a href="#layout">Layout</a></li><li><a href="#conventions">Conventions</a></li><li><a href="#the-invariant">The invariant</a></li><li><a href="#safety">Safety</a></li><li><a href="#definition-of-done">Definition of done</a></li><li><a href="#authoring-this-file">Authoring this file</a></li></ul><button type="button" class="xall" hidden>Expand all</button></div>
226
+ <div class="panes"><div class="pane p-about"><p class="label">About</p><p class="lead">The essential MCP components for a project's context (AGENTS.md), cross-session memory, and identity — a base MCP on its own, or a drop-in extension for any existing MCP server, discoverable through the Server Card _meta block and a self-published ai-catalog.json.</p><table class="kv"><tbody><tr><th>Agent ID</th><td><code>urn:air:faf.one:agent:mcp-context-card</code></td></tr><tr><th>Publisher</th><td>io.github.Wolfe-Jam</td></tr><tr><th>Version</th><td>v1.9.1</td></tr><tr><th>Status</th><td>published</td></tr><tr><th>License</th><td>MIT</td></tr><tr><th>Package (npm)</th><td><code>mcp-context-card</code></td></tr></tbody></table></div><div class="pane p-context"><p class="label">Context — AGENTS.md</p><div class="ctx-nav"><ul class="toc"><li><a href="#setup">Setup</a></li><li><a href="#build">Build</a></li><li><a href="#test">Test</a></li><li><a href="#layout">Layout</a></li><li><a href="#conventions">Conventions</a></li><li><a href="#the-invariant">The invariant</a></li><li><a href="#safety">Safety</a></li><li><a href="#definition-of-done">Definition of done</a></li><li><a href="#authoring-this-file">Authoring this file</a></li></ul><button type="button" class="xall" hidden>Expand all</button></div>
227
227
  <div class="ctx-preamble md"><p><code>mcp-context-card</code> is the essential MCP server for a project's <strong>context</strong> (this file), <strong>memory</strong>, and <strong>identity</strong> — usable as your base MCP, or dropped into any existing MCP server as an extension. Discoverable to any MCP client through the two surfaces already in the ecosystem: the Server Card <code>_meta</code> block and <code>ai-catalog.json</code> sibling entries.</p>
228
228
  <p><code>read_agents_md</code> serves this file, section by section, over the same MCP connection.</p></div>
229
229
  <div class="ctx-body"><details class="ctx-section" id="setup"><summary>Setup</summary><div class="md"><pre><code class="language-bash">npm ci</code></pre>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-context-card",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "mcpName": "io.github.Wolfe-Jam/mcp-context-card",
5
5
  "description": "The essential MCP components for a project's context (AGENTS.md), cross-session memory, and identity — a base MCP on its own, or a drop-in extension for any existing MCP server. Discoverable through the Server Card _meta block and ai-catalog.json sibling entries.",
6
6
  "keywords": [
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.Wolfe-Jam/mcp-context-card",
4
4
  "title": "MCP Context Card",
5
5
  "description": "MCP server for a project's context (AGENTS.md), memory, and identity — base or drop-in extension.",
6
- "version": "1.8.0",
6
+ "version": "1.9.1",
7
7
  "repository": {
8
8
  "url": "https://github.com/Wolfe-Jam/mcp-context-card",
9
9
  "source": "github"
@@ -14,7 +14,7 @@
14
14
  "registryType": "npm",
15
15
  "registryBaseUrl": "https://registry.npmjs.org",
16
16
  "identifier": "mcp-context-card",
17
- "version": "1.8.0",
17
+ "version": "1.9.1",
18
18
  "runtimeHint": "npx",
19
19
  "transport": {
20
20
  "type": "stdio"