mcp-context-card 1.9.1 → 1.9.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.
- package/.well-known/ai-catalog.json +1 -1
- package/.well-known/fafa +1 -1
- package/CHANGELOG.md +13 -0
- package/README.md +11 -9
- package/dist/bin.d.ts +1 -1
- package/dist/card-app.bundle.js +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/server.js +37 -6
- package/docs/MECHANISMS.md +2 -2
- package/docs/TRANSPORT.md +6 -0
- package/docs/card-dark.html +2 -2
- package/docs/card-light.html +2 -2
- package/docs/card.html +2 -2
- package/docs/img/card-dark.png +0 -0
- package/docs/img/card-identity.png +0 -0
- package/docs/img/card-light.png +0 -0
- package/docs/index.html +2 -2
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -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.9.
|
|
14
|
+
"version": "1.9.2",
|
|
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.9.
|
|
16
|
+
version: "1.9.2"
|
|
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,19 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project. Adheres to [Semantic Versioning](https://semver.org).
|
|
4
4
|
|
|
5
|
+
## 1.9.2
|
|
6
|
+
|
|
7
|
+
- **The Cursor editor's project is found.** Cursor answers `roots/list` with
|
|
8
|
+
a bare path (`/Users/me/project`) where the spec says `file://`. The MCP
|
|
9
|
+
SDK's schema rejected that reply, so the server fell back to its own
|
|
10
|
+
package folder. It now reads `roots/list` with its own check: a `file://`
|
|
11
|
+
URI or an absolute path to an existing folder is the project; another
|
|
12
|
+
scheme, a relative path, a `..` segment or a `file://` URI with a host is
|
|
13
|
+
skipped, never fatal.
|
|
14
|
+
- **README:** the line-by-line check against 1.9.1 (#93): the instructions
|
|
15
|
+
sentence, the tools table (`format: "markdown"`, section levels, the browser
|
|
16
|
+
opens only locally), and `src/browser/card-app.ts` in the layout.
|
|
17
|
+
|
|
5
18
|
## 1.9.1
|
|
6
19
|
|
|
7
20
|
- **The card as Markdown in any chat.** `render_context_card` takes
|
package/README.md
CHANGED
|
@@ -133,9 +133,9 @@ server runs locally, and replies with the card as Markdown (identity, the
|
|
|
133
133
|
`AGENTS.md` sections, memory, discovery), a link to the full card, and its
|
|
134
134
|
address to copy. The Markdown is a tl;dr by default: five facts, each cut to
|
|
135
135
|
its first sentence as stored, so it stays small however much a project
|
|
136
|
-
remembers. `detail: "full"` lists every fact whole.
|
|
137
|
-
|
|
138
|
-
the
|
|
136
|
+
remembers. `detail: "full"` lists every fact whole. For a client without
|
|
137
|
+
MCP Apps, the server's instructions point the model at `save_context_card`
|
|
138
|
+
when the user wants to see the card.
|
|
139
139
|
|
|
140
140
|
Light, dark, or auto. The colour is the owner's: set it in the project's
|
|
141
141
|
`.fafa` (`metadata.cards.accent: "#0066cc"`), or pass any hex with `--accent`
|
|
@@ -193,8 +193,9 @@ goose, Claude Desktop, Cursor, or any stdio host:
|
|
|
193
193
|
```
|
|
194
194
|
|
|
195
195
|
No path to configure: the server asks the host which project you're in (MCP
|
|
196
|
-
roots — goose sends its session's working directory
|
|
197
|
-
directory it was started in if that has an
|
|
196
|
+
roots — goose sends its session's working directory, the Cursor editor its open
|
|
197
|
+
folder), then falls back to the directory it was started in if that has an
|
|
198
|
+
`AGENTS.md`. `list_context_sources`
|
|
198
199
|
reports which project it picked and how. To pin one project instead, set
|
|
199
200
|
`"env": { "MCP_CONTEXT_CARD_ROOT": "/abs/path/to/your/project" }`; that always
|
|
200
201
|
wins. Identity is optional. Over HTTP instead:
|
|
@@ -272,14 +273,14 @@ The wire‑level detail is in [docs/MECHANISMS.md](./docs/MECHANISMS.md).
|
|
|
272
273
|
|---|---|
|
|
273
274
|
| `author_agents_md` | draft an `AGENTS.md` — BETTER from the repo's facts (via `agents-md-facts`), BEST when a `project.faf` exists — ready to drop in |
|
|
274
275
|
| `read_agents_md` | return the project's `AGENTS.md` — whole, or one section by heading |
|
|
275
|
-
| `list_agents_md_sections` | the headings, so a client pulls one section instead of the whole file |
|
|
276
|
+
| `list_agents_md_sections` | the headings and their levels, so a client pulls one section instead of the whole file (level 1 is the title, which the card doesn't count) |
|
|
276
277
|
| `remember` | write a fact that will still be there next session |
|
|
277
278
|
| `recall` | read a fact stored in a previous session |
|
|
278
279
|
| `forget` | drop or correct a stale fact |
|
|
279
280
|
| `whoami` | this server's name, vendor, version, status, license |
|
|
280
281
|
| `list_context_sources` | what this project publishes, in what media types, via which surface |
|
|
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`) |
|
|
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 |
|
|
282
|
+
| `render_context_card` | shows the card: drawn in the chat by hosts that support MCP Apps, the card as Markdown everywhere else; `format: "markdown"` gives the Markdown in any chat (the HTML card: `save_context_card`, `GET /card`) |
|
|
283
|
+
| `save_context_card` | write the card to `context-card.html` in the project and, when the server runs locally, open it in your browser; returns the card as Markdown plus a link to the full version |
|
|
283
284
|
|
|
284
285
|
Seven tools only read. `remember` and `forget` write the memory file, and
|
|
285
286
|
`save_context_card` writes `context-card.html`, so those three are marked
|
|
@@ -299,7 +300,7 @@ over both transports:
|
|
|
299
300
|
4. **Discovery** — `list_context_sources()`, then the same server over stateless
|
|
300
301
|
HTTP with its `.well-known` routes and `GET /card`.
|
|
301
302
|
|
|
302
|
-
|
|
303
|
+
254 tests on Linux, macOS, and Windows, coverage‑gated in CI.
|
|
303
304
|
`npm run wjttc` runs the WJTTC certification suite (seven tiers, from protocol
|
|
304
305
|
and Server Card conformance to stdio/HTTP parity and the shipped package). Two spawn a real
|
|
305
306
|
child process and check a remembered fact survives the restart — one against
|
|
@@ -318,6 +319,7 @@ transport security against the specs, one MUST or SHOULD at a time.
|
|
|
318
319
|
| `src/author.ts` | `author_agents_md` — BETTER via [`agents-md-facts`](https://github.com/Wolfe-Jam/agents-md-facts), BEST when `project.faf` exists |
|
|
319
320
|
| `src/md.ts` | a minimal dependency‑free Markdown → HTML renderer |
|
|
320
321
|
| `src/render-card.ts` | the card — reads identity + `AGENTS.md` + memory + discovery into a card; [`agent-business-card`](https://github.com/Wolfe-Jam/agent-business-card) draws it as one HTML page |
|
|
322
|
+
| `src/browser/card-app.ts` | the card's MCP Apps script, bundled into the `ui://` resource only |
|
|
321
323
|
| `src/memory.ts` | file‑backed `remember` / `recall` / `forget` |
|
|
322
324
|
| `src/identity.ts` | `whoami` (`.fafa` → `package.json` fallback) + the `_meta` block |
|
|
323
325
|
| `src/server-card.ts` | the MCP Server Card (SEP-2127, schema v1) |
|
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.9.
|
|
11
|
+
export declare const HELP = "mcp-context-card 1.9.2\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.
|
package/dist/card-app.bundle.js
CHANGED
|
@@ -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.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(()=>{})}})();
|
|
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.2"},{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/constants.d.ts
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 declare const NAME = "mcp-context-card";
|
|
4
|
-
export declare const VERSION = "1.9.
|
|
4
|
+
export declare const VERSION = "1.9.2";
|
|
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.9.
|
|
4
|
+
export const VERSION = "1.9.2";
|
|
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
|
package/dist/server.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
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 { dirname, join } from "node:path";
|
|
23
|
+
import { dirname, isAbsolute, join, normalize, sep } 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";
|
|
@@ -42,6 +42,20 @@ export const ROOT = join(here, "..");
|
|
|
42
42
|
*/
|
|
43
43
|
export { serverCard } from "./server-card.js";
|
|
44
44
|
const text = (s) => ({ content: [{ type: "text", text: s }] });
|
|
45
|
+
/** A roots/list result read leniently: any string uri, checked later by
|
|
46
|
+
* rootDir(). A Standard Schema, so no schema library is needed. */
|
|
47
|
+
const LENIENT_ROOTS = {
|
|
48
|
+
"~standard": {
|
|
49
|
+
version: 1,
|
|
50
|
+
vendor: "mcp-context-card",
|
|
51
|
+
validate(value) {
|
|
52
|
+
const roots = value?.roots;
|
|
53
|
+
if (!Array.isArray(roots))
|
|
54
|
+
return { issues: [{ message: "roots/list result has no roots array" }] };
|
|
55
|
+
return { value: { roots: roots.filter((r) => typeof r?.uri === "string") } };
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
45
59
|
/** Sent to every client at initialize. Hosts that support MCP Apps show the
|
|
46
60
|
* card inline; for the rest, this steers the model to save the card as a
|
|
47
61
|
* file instead of pasting a whole HTML page into the chat. */
|
|
@@ -110,12 +124,27 @@ export function createServer(root = ROOT, opts = {}) {
|
|
|
110
124
|
return false;
|
|
111
125
|
}
|
|
112
126
|
};
|
|
127
|
+
/** A root's folder. The spec says a root uri MUST be file://; some clients
|
|
128
|
+
* (the Cursor editor) send a bare absolute path, so that is read as the
|
|
129
|
+
* same folder. Anything else (another scheme, a relative path, a `..`
|
|
130
|
+
* segment, a file:// URI with a host) is skipped, never fatal. */
|
|
131
|
+
const rootDir = (uri) => {
|
|
132
|
+
if (uri.startsWith("file:")) {
|
|
133
|
+
try {
|
|
134
|
+
return fileURLToPath(uri);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!isAbsolute(uri) || normalize(uri).split(sep).includes("..") || uri.split(/[\\/]/).includes(".."))
|
|
141
|
+
return undefined;
|
|
142
|
+
return normalize(uri);
|
|
143
|
+
};
|
|
113
144
|
const firstDir = (roots) => {
|
|
114
145
|
for (const r of roots) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const dir = fileURLToPath(r.uri);
|
|
118
|
-
if (isDir(dir))
|
|
146
|
+
const dir = rootDir(r.uri);
|
|
147
|
+
if (dir && isDir(dir))
|
|
119
148
|
return dir;
|
|
120
149
|
}
|
|
121
150
|
return undefined;
|
|
@@ -134,7 +163,9 @@ export function createServer(root = ROOT, opts = {}) {
|
|
|
134
163
|
return undefined;
|
|
135
164
|
}
|
|
136
165
|
try {
|
|
137
|
-
|
|
166
|
+
// Not server.listRoots(): its schema rejects a uri without file://, and
|
|
167
|
+
// the Cursor editor sends bare paths. rootDir() does the checking.
|
|
168
|
+
const { roots } = (await server.request({ method: "roots/list" }, LENIENT_ROOTS, { timeout: 5000 }));
|
|
138
169
|
return firstDir(roots);
|
|
139
170
|
}
|
|
140
171
|
catch {
|
package/docs/MECHANISMS.md
CHANGED
|
@@ -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.9.
|
|
35
|
+
"version": "1.9.2",
|
|
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.9.
|
|
88
|
+
Served from `https://ctx.example.com` (1.9.2, this repo's own files):
|
|
89
89
|
|
|
90
90
|
```jsonc
|
|
91
91
|
{
|
package/docs/TRANSPORT.md
CHANGED
|
@@ -23,6 +23,12 @@ The server speaks both MCP protocol eras, on both transports:
|
|
|
23
23
|
A client that negotiates (Claude Code and goose already do) gets 2026-07-28;
|
|
24
24
|
an older client gets 2025, unchanged from 1.x. The Server Card lists both.
|
|
25
25
|
|
|
26
|
+
**A root's `uri` should be `file://`.** The spec says it MUST be; the Cursor
|
|
27
|
+
editor (3.29.9) sends a bare absolute path (`/Users/me/project`). The server
|
|
28
|
+
reads either as the same folder, and skips anything else (another scheme, a
|
|
29
|
+
relative path, a `..` segment, a `file://` URI with a host) without failing
|
|
30
|
+
the call.
|
|
31
|
+
|
|
26
32
|
**Roots differ by era.** On 2025 the server asks the client with a
|
|
27
33
|
`roots/list` request; if the client fails to answer, the server falls back to
|
|
28
34
|
the start directory. On 2026-07-28 there are no server-to-client requests: the
|
package/docs/card-dark.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.9.
|
|
208
|
+
<p class="title">MCP server · v1.9.2</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.9.
|
|
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.2</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-light.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.9.
|
|
208
|
+
<p class="title">MCP server · v1.9.2</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.9.
|
|
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.2</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.9.
|
|
208
|
+
<p class="title">MCP server · v1.9.2</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.9.
|
|
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.2</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/img/card-dark.png
CHANGED
|
Binary file
|
|
Binary file
|
package/docs/img/card-light.png
CHANGED
|
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.9.
|
|
208
|
+
<p class="title">MCP server · v1.9.2</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.9.
|
|
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.2</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.9.
|
|
3
|
+
"version": "1.9.2",
|
|
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.9.
|
|
6
|
+
"version": "1.9.2",
|
|
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.9.
|
|
17
|
+
"version": "1.9.2",
|
|
18
18
|
"runtimeHint": "npx",
|
|
19
19
|
"transport": {
|
|
20
20
|
"type": "stdio"
|