mcp-context-card 1.1.1 → 1.2.0

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/fafa CHANGED
@@ -9,7 +9,7 @@ agent:
9
9
  name: "mcp-context-card"
10
10
  displayName: "mcp-context-card"
11
11
  vendor: "io.github.Wolfe-Jam"
12
- version: "1.1.1"
12
+ version: "1.2.0"
13
13
  description: >-
14
14
  The essential MCP components for a project's context (AGENTS.md),
15
15
  cross-session memory, and identity — a base MCP on its own, or a
package/AGENTS.md CHANGED
@@ -45,7 +45,7 @@ code's shape moved without `project.faf`) on Linux.
45
45
 
46
46
  | Path | What |
47
47
  |---|---|
48
- | `src/server.ts` | the MCP server — the nine tools + the Server Card resource |
48
+ | `src/server.ts` | the MCP server — the ten tools + the Server Card and card resources |
49
49
  | `src/agents-md.ts` | reads and section-splits this file |
50
50
  | `src/author.ts` | `author_agents_md` — BETTER via `agents-md-facts`, BEST when `project.faf` exists |
51
51
  | `src/md.ts` | a minimal dependency-free Markdown → HTML renderer |
package/CHANGELOG.md CHANGED
@@ -2,6 +2,43 @@
2
2
 
3
3
  All notable changes to this project. Adheres to [Semantic Versioning](https://semver.org).
4
4
 
5
+ ## 1.2.0
6
+
7
+ The card reaches people in any host: inline where the host supports MCP
8
+ Apps, and as text in the chat plus the full card in the browser everywhere
9
+ else.
10
+
11
+ - **MCP App.** `render_context_card` links a `ui://mcp-context-card/card.html`
12
+ resource (`text/html;profile=mcp-app`). Hosts that support MCP Apps render
13
+ the card inline, and the model gets a one-line summary instead of the
14
+ whole HTML page. Hosts without MCP Apps get the full HTML, as before.
15
+ - **New tool: `save_context_card`.** Writes the card to `context-card.html`
16
+ in the project and replies with:
17
+ - the card as Markdown: identity, `AGENTS.md` section headings, memory,
18
+ discovery. About 1.2k characters for this repo, instead of about 17k of
19
+ HTML;
20
+ - a link to the full card, plus its `file://` address in a code block,
21
+ which hosts give a copy button (many won't follow a `file://` link).
22
+
23
+ When the server runs locally (stdio), it also opens the saved card in the
24
+ browser; `open: false` skips that, and over HTTP it never opens anything.
25
+ Same `theme`, `accent` and `expanded` inputs as `render_context_card`.
26
+ Marked `destructiveHint: true`, because it replaces an earlier
27
+ `context-card.html`.
28
+ - **A tl;dr by default.** The Markdown shows five facts, each cut to its
29
+ first sentence exactly as stored (or a word-boundary cut marked … when
30
+ that sentence is long), and counts the rest. It stays under about 3k
31
+ characters however much a project remembers. `detail: "full"` lists
32
+ every fact whole. The saved file always has everything.
33
+ - **Server instructions.** Sent at initialize: when the host can't display
34
+ the card, save it and show the user the Markdown card and its link,
35
+ not paste HTML into the chat.
36
+ - **`list_context_sources`** now lists the card resource under
37
+ `surfaces.mcp`.
38
+
39
+ Ten tools now. The nine existing tools keep their names, inputs and
40
+ behaviour. 117 tests, all green.
41
+
5
42
  ## 1.1.1
6
43
 
7
44
  Every tool now says what it is and what it does to your project.
package/README.md CHANGED
@@ -13,6 +13,13 @@ rendered as one card you can read.
13
13
  |---|---|
14
14
  | ![the context card, light theme](./docs/img/card-light.png) | ![the context card, dark theme](./docs/img/card-dark.png) |
15
15
 
16
+ **See your own project's card** — run this in its folder. It writes
17
+ `context-card.html` and opens it in your browser:
18
+
19
+ ```
20
+ npx mcp-context-card card
21
+ ```
22
+
16
23
  **context** — the project's `AGENTS.md`, served whole or one section at a time.
17
24
 
18
25
  ![the card's context section — AGENTS.md, section nav, read_agents_md](./docs/img/card-context.png)
@@ -52,7 +59,7 @@ say what it is. `mcp-context-card` is those three, done once:
52
59
  gains context, memory, and identity discovery it didn't have. Nothing to
53
60
  migrate; it composes.
54
61
 
55
- Nine tools, two discovery surfaces already in the ecosystem (Server Card
62
+ Ten tools, two discovery surfaces already in the ecosystem (Server Card
56
63
  `_meta`, `ai-catalog.json`), and a rendered [card](#the-card). MIT, on npm.
57
64
 
58
65
  It composes:
@@ -63,8 +70,9 @@ It composes:
63
70
 
64
71
  Vendor-free — context is plain Markdown (`AGENTS.md`); the memory and
65
72
  identity formats are swappable examples. It reads and writes only its own
66
- three files (`AGENTS.md`, `project.fafm`, `.well-known/fafa`) — no general
67
- file access, no shell, no search.
73
+ three files (`AGENTS.md`, `project.fafm`, `.well-known/fafa`), plus the
74
+ `context-card.html` it saves on request — no general file access, no shell,
75
+ no search.
68
76
 
69
77
  ## The card
70
78
 
@@ -87,6 +95,18 @@ GET /card # live, on the HTTP transport
87
95
  GET /card?expand=all&theme=light&accent=%230066cc
88
96
  ```
89
97
 
98
+ In a chat, hosts that support [MCP Apps](https://modelcontextprotocol.io/extensions/apps)
99
+ show the card inline when `render_context_card` runs; the model gets a short
100
+ summary instead of the page. Everywhere else, `save_context_card` writes
101
+ `context-card.html` into the project, opens it in your browser when the
102
+ server runs locally, and replies with the card as Markdown (identity, the
103
+ `AGENTS.md` sections, memory, discovery), a link to the full card, and its
104
+ address to copy. The Markdown is a tl;dr by default: five facts, each cut to
105
+ its first sentence as stored, so it stays small however much a project
106
+ remembers. `detail: "full"` lists every fact whole. The server's
107
+ instructions tell the model to show that rather than paste the HTML into
108
+ the chat.
109
+
90
110
  Light, dark, or auto; the accent defaults to the AAIF palette and takes any hex.
91
111
  This repo's own card, live: [auto](https://wolfe-jam.github.io/mcp-context-card/) ·
92
112
  [light](https://wolfe-jam.github.io/mcp-context-card/card-light.html) ·
@@ -191,15 +211,18 @@ The wire‑level detail is in [docs/MECHANISMS.md](./docs/MECHANISMS.md).
191
211
  | `forget` | drop or correct a stale fact |
192
212
  | `whoami` | this server's name, vendor, version, status, license |
193
213
  | `list_context_sources` | what this project publishes, in what media types, via which surface |
194
- | `render_context_card` | the whole card as one self‑contained HTML page (also `GET /card`) |
214
+ | `render_context_card` | the whole card as one self‑contained HTML page (also `GET /card`); hosts that support MCP Apps show it inline |
215
+ | `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 |
195
216
 
196
- Seven tools only read. `remember` and `forget` write the memory file, so they're
197
- marked destructive and a host can ask before running them. Every tool carries a
217
+ Seven tools only read. `remember` and `forget` write the memory file, and
218
+ `save_context_card` writes `context-card.html`, so those three are marked
219
+ destructive and a host can ask before running them. Every tool carries a
198
220
  title and MCP tool annotations.
199
221
 
200
222
  ## The demo
201
223
 
202
- `npm run demo` runs every tool over both transports:
224
+ `npm run demo` walks through context, memory, identity and discovery, live,
225
+ over both transports:
203
226
 
204
227
  1. **Context** — list the `AGENTS.md` sections, then pull just `## Test`.
205
228
  2. **Memory** — `remember()` a fact, stop the server process, start a new one,
@@ -209,7 +232,7 @@ title and MCP tool annotations.
209
232
  4. **Discovery** — `list_context_sources()`, then the same server over stateless
210
233
  HTTP with its `.well-known` routes and `GET /card`.
211
234
 
212
- 106 tests on Linux, macOS, and Windows, coverage‑gated in CI. Two spawn a real
235
+ 117 tests on Linux, macOS, and Windows, coverage‑gated in CI. Two spawn a real
213
236
  child process and check a remembered fact survives the restart — one against
214
237
  an existing `project.fafm`, one starting from a project that has never had
215
238
  one; another checks the stdio and HTTP tool surfaces match, and another checks
@@ -219,7 +242,7 @@ every tool's title and behaviour hints against what it actually does.
219
242
 
220
243
  | Path | What |
221
244
  |---|---|
222
- | `src/server.ts` | the nine tools + the Server Card resource |
245
+ | `src/server.ts` | the ten tools + the Server Card and card resources |
223
246
  | `src/agents-md.ts` | reads and section‑splits `AGENTS.md` |
224
247
  | `src/author.ts` | `author_agents_md` — BETTER via [`agents-md-facts`](https://github.com/Wolfe-Jam/agents-md-facts), BEST when `project.faf` exists |
225
248
  | `src/md.ts` | a minimal dependency‑free Markdown → HTML renderer |
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.1.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) --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\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.2.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) --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\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
  * Decide how to launch, from argv + env. Pure — so the mode matrix is unit
14
14
  * tested without spawning a process.
package/dist/bin.js CHANGED
@@ -121,17 +121,10 @@ if (entryPath && import.meta.url === pathToFileURL(entryPath).href) {
121
121
  else {
122
122
  const { writeFileSync } = await import("node:fs");
123
123
  const { join } = await import("node:path");
124
- const { spawn } = await import("node:child_process");
124
+ const { openInBrowser } = await import("./open.js");
125
125
  const out = join(process.cwd(), "context-card.html");
126
126
  writeFileSync(out, html);
127
- const opener = process.platform === "darwin"
128
- ? ["open", [out]]
129
- : process.platform === "win32"
130
- ? ["cmd", ["/c", "start", "", out]]
131
- : ["xdg-open", [out]];
132
- spawn(opener[0], opener[1], { stdio: "ignore", detached: true })
133
- .on("error", () => { })
134
- .unref();
127
+ openInBrowser(out);
135
128
  process.stderr.write(`${NAME} · wrote ${out} — opening in your browser (--stdout for raw HTML)\n`);
136
129
  }
137
130
  }
@@ -146,6 +139,8 @@ if (entryPath && import.meta.url === pathToFileURL(entryPath).href) {
146
139
  // stderr so it never touches the JSON-RPC wire on stdout; a bare run at a
147
140
  // terminal otherwise looks hung.
148
141
  console.error(`${NAME} · stdio · waiting for an MCP host on stdin (--help for usage · Ctrl-C to exit)`);
149
- await serve(new StdioServerTransport(), root);
142
+ // stdio = a host on this machine, so save_context_card can open the card.
143
+ const { openInBrowser } = await import("./open.js");
144
+ await serve(new StdioServerTransport(), root, { openFile: openInBrowser });
150
145
  }
151
146
  }
@@ -1,5 +1,11 @@
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.1.1";
4
+ export declare const VERSION = "1.2.0";
5
5
  export declare const SERVER_CARD_URI = "mcp-context-card://server-card";
6
+ /** MCP Apps (io.modelcontextprotocol/ui): the card as an inline UI resource.
7
+ * A host that supports MCP Apps fetches this resource and renders it in a
8
+ * sandboxed iframe next to the conversation. */
9
+ export declare const CARD_UI_URI = "ui://mcp-context-card/card.html";
10
+ export declare const MCP_APP_MIME = "text/html;profile=mcp-app";
11
+ export declare const UI_EXTENSION = "io.modelcontextprotocol/ui";
package/dist/constants.js CHANGED
@@ -1,5 +1,11 @@
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.1.1";
4
+ export const VERSION = "1.2.0";
5
5
  export const SERVER_CARD_URI = "mcp-context-card://server-card";
6
+ /** MCP Apps (io.modelcontextprotocol/ui): the card as an inline UI resource.
7
+ * A host that supports MCP Apps fetches this resource and renders it in a
8
+ * sandboxed iframe next to the conversation. */
9
+ export const CARD_UI_URI = "ui://mcp-context-card/card.html";
10
+ export const MCP_APP_MIME = "text/html;profile=mcp-app";
11
+ export const UI_EXTENSION = "io.modelcontextprotocol/ui";
package/dist/open.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function openInBrowser(path: string): void;
package/dist/open.js ADDED
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Open a local file in the default browser — what `mcp-context-card card`
3
+ * does at a terminal, and what save_context_card does when the server runs
4
+ * locally over stdio. Fire and forget: no display (SSH, CI) just means
5
+ * nothing opens, never an error.
6
+ */
7
+ import { spawn } from "node:child_process";
8
+ export function openInBrowser(path) {
9
+ const [cmd, args] = process.platform === "darwin"
10
+ ? ["open", [path]]
11
+ : process.platform === "win32"
12
+ ? ["cmd", ["/c", "start", "", path]]
13
+ : ["xdg-open", [path]];
14
+ spawn(cmd, args, { stdio: "ignore", detached: true })
15
+ .on("error", () => { })
16
+ .unref();
17
+ }
@@ -14,3 +14,24 @@ export interface CardOptions {
14
14
  export declare const AAIF_ACCENT = "#FF702D";
15
15
  export declare function safeAccent(a?: string): string;
16
16
  export declare function renderCard(root: string, opts?: CardOptions): string;
17
+ /** tl;dr: the first few facts, each cut to about a line. `full`: every fact, whole. */
18
+ export type Detail = "tldr" | "full";
19
+ /**
20
+ * Shorten a fact for the tl;dr without rewording it. The whole first sentence
21
+ * when it fits in `max` (a stored sentence, verbatim, so a model has no ragged
22
+ * end to "tidy"); otherwise a word-boundary cut marked with …. A dot inside a
23
+ * word (AGENTS.md, v1.2) is not a sentence end: it must be followed by space.
24
+ */
25
+ export declare function clip(s: string, max: number): string;
26
+ /**
27
+ * The same card as Markdown, for chats that can't display HTML: identity,
28
+ * the AGENTS.md section headings (bodies stay in the full card), memory,
29
+ * and the discovery table. Same sources as renderCard.
30
+ *
31
+ * The default tl;dr stays small however much memory a project holds: five
32
+ * facts, each cut short, and a count of the rest. `detail: "full"` lists
33
+ * every fact whole. The saved HTML card always has everything.
34
+ */
35
+ export declare function renderCardText(root: string, opts?: {
36
+ detail?: Detail;
37
+ }): string;
@@ -236,3 +236,64 @@ const TOGGLE_SCRIPT = `<script>
236
236
  sync();
237
237
  })();
238
238
  </script>`;
239
+ const TLDR_FACTS = 5;
240
+ const TLDR_CHARS = 160;
241
+ /**
242
+ * Shorten a fact for the tl;dr without rewording it. The whole first sentence
243
+ * when it fits in `max` (a stored sentence, verbatim, so a model has no ragged
244
+ * end to "tidy"); otherwise a word-boundary cut marked with …. A dot inside a
245
+ * word (AGENTS.md, v1.2) is not a sentence end: it must be followed by space.
246
+ */
247
+ export function clip(s, max) {
248
+ if (s.length <= max)
249
+ return s;
250
+ const first = /^(.+?[.!?])(?=\s)/.exec(s)?.[1];
251
+ if (first && first.length <= max)
252
+ return first;
253
+ const cut = s.slice(0, max);
254
+ const space = cut.lastIndexOf(" ");
255
+ return `${(space > max / 2 ? cut.slice(0, space) : cut).replace(/[\s,;:.—-]+$/, "")}…`;
256
+ }
257
+ /**
258
+ * The same card as Markdown, for chats that can't display HTML: identity,
259
+ * the AGENTS.md section headings (bodies stay in the full card), memory,
260
+ * and the discovery table. Same sources as renderCard.
261
+ *
262
+ * The default tl;dr stays small however much memory a project holds: five
263
+ * facts, each cut short, and a count of the rest. `detail: "full"` lists
264
+ * every fact whole. The saved HTML card always has everything.
265
+ */
266
+ export function renderCardText(root, opts = {}) {
267
+ const agents = parseAgentsMd(join(root, "AGENTS.md"));
268
+ const mem = parseFafm(join(root, "project.fafm"));
269
+ const id = resolveIdentity(root);
270
+ const meta = serverCardMeta();
271
+ const name = id?.displayName ?? id?.name ?? NAME;
272
+ const plural = (n, w) => `${n} ${w}${n === 1 ? "" : "s"}`;
273
+ const out = [`### ${name} — context card`];
274
+ const pills = [
275
+ id?.vendor && id.vendor !== id.status ? id.vendor : null,
276
+ id?.agentVersion ? `v${id.agentVersion}` : null,
277
+ id?.status,
278
+ id?.license,
279
+ ].filter(Boolean);
280
+ if (pills.length)
281
+ out.push(pills.join(" · "));
282
+ if (id?.description)
283
+ out.push(id.description);
284
+ const sections = agents?.sections.filter((s) => s.level > 1) ?? [];
285
+ out.push(agents
286
+ ? `**Context — AGENTS.md** · ${plural(sections.length, "section")}\n${sections.map((s) => s.heading).join(" · ")}`
287
+ : "**Context — AGENTS.md** · none in this project");
288
+ const full = opts.detail === "full";
289
+ const shown = full ? mem.facts : mem.facts.slice(0, TLDR_FACTS);
290
+ const rest = mem.facts.length - shown.length;
291
+ out.push(mem.facts.length
292
+ ? `**Memory** · ${plural(mem.facts.length, "fact")}\n${shown
293
+ .map((f) => `- ${full ? f.text : clip(f.text, TLDR_CHARS)}${f.verification_status === "verified" ? " ✓" : ""}`)
294
+ .join("\n")}${rest ? `\n\n…and ${plural(rest, "more fact")}, in the full card` : ""}`
295
+ : "**Memory** · no facts yet");
296
+ const rows = Object.entries(meta).map(([k, v]) => `| ${k.slice(META_NS.length + 1)} | \`${v.source}\` | \`${v.mediaType}\` |`);
297
+ out.push(["**Discovery**", "| concern | source | media type |", "|---|---|---|", ...rows].join("\n"));
298
+ return out.join("\n\n");
299
+ }
package/dist/server.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * context — read_agents_md · list_agents_md_sections · author_agents_md (this project's AGENTS.md)
6
6
  * memory — remember · recall · forget (a .fafm file)
7
7
  * identity — whoami (this server's .fafa)
8
- * discovery — list_context_sources · render_context_card (what's published, and how)
8
+ * discovery — list_context_sources · render_context_card · save_context_card (what's published, and how)
9
9
  *
10
10
  * ...exposed through the two mechanisms already in the ecosystem:
11
11
  *
@@ -46,11 +46,15 @@ export declare function serverCard(): {
46
46
  };
47
47
  };
48
48
  };
49
- /**
50
- * @param root directory holding `AGENTS.md`, `project.fafm`, `.well-known/`.
51
- * Defaults to the package root; a deploy points `MCP_CONTEXT_CARD_ROOT`
52
- * at a real project, a test points it at a fixture.
53
- */
54
- export declare function createServer(root?: string): Server;
49
+ /** Sent to every client at initialize. Hosts that support MCP Apps show the
50
+ * card inline; for the rest, this steers the model to save the card as a
51
+ * file instead of pasting a whole HTML page into the chat. */
52
+ export declare const INSTRUCTIONS: string;
53
+ export interface ServerOptions {
54
+ /** Open a saved file for the person. Set only for a local (stdio) server;
55
+ * over HTTP the browser would open on the server, so it stays unset. */
56
+ openFile?: (path: string) => void;
57
+ }
58
+ export declare function createServer(root?: string, opts?: ServerOptions): Server;
55
59
  /** Connect a server instance to a transport (stdio or http). */
56
- export declare function serve(transport: Transport, root?: string): Promise<Server>;
60
+ export declare function serve(transport: Transport, root?: string, opts?: ServerOptions): Promise<Server>;
package/dist/server.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * context — read_agents_md · list_agents_md_sections · author_agents_md (this project's AGENTS.md)
6
6
  * memory — remember · recall · forget (a .fafm file)
7
7
  * identity — whoami (this server's .fafa)
8
- * discovery — list_context_sources · render_context_card (what's published, and how)
8
+ * discovery — list_context_sources · render_context_card · save_context_card (what's published, and how)
9
9
  *
10
10
  * ...exposed through the two mechanisms already in the ecosystem:
11
11
  *
@@ -17,15 +17,16 @@
17
17
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
18
18
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
19
19
  import { CallToolRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
20
+ import { writeFileSync } from "node:fs";
20
21
  import { dirname, join } from "node:path";
21
22
  import { fileURLToPath, pathToFileURL } from "node:url";
22
23
  import { findSection, parseAgentsMd } from "./agents-md.js";
23
24
  import { authorAgentsMd } from "./author.js";
24
25
  import { forget, parseFafm, recall, remember } from "./memory.js";
25
26
  import { identity, serverCardMeta, whoami } from "./identity.js";
26
- import { renderCard, safeAccent } from "./render-card.js";
27
+ import { renderCard, renderCardText, safeAccent } from "./render-card.js";
27
28
  export { NAME, VERSION, SERVER_CARD_URI } from "./constants.js";
28
- import { NAME, VERSION, SERVER_CARD_URI } from "./constants.js";
29
+ import { NAME, VERSION, SERVER_CARD_URI, CARD_UI_URI, MCP_APP_MIME, UI_EXTENSION } from "./constants.js";
29
30
  const here = dirname(fileURLToPath(import.meta.url));
30
31
  /** Default package root — `dist/` at runtime, `src/` under tsx. Both are one up. */
31
32
  export const ROOT = join(here, "..");
@@ -39,16 +40,46 @@ export function serverCard() {
39
40
  return { name: NAME, version: VERSION, _meta: serverCardMeta() };
40
41
  }
41
42
  const text = (s) => ({ content: [{ type: "text", text: s }] });
43
+ /** Sent to every client at initialize. Hosts that support MCP Apps show the
44
+ * card inline; for the rest, this steers the model to save the card as a
45
+ * file instead of pasting a whole HTML page into the chat. */
46
+ export const INSTRUCTIONS = "This server publishes a project's context (AGENTS.md), memory (project.fafm) and identity (.well-known/fafa). " +
47
+ "Read them with read_agents_md, recall and whoami; list_context_sources says what is published and where. " +
48
+ "When the user wants to see the context card: hosts that support MCP Apps display it inline from render_context_card. " +
49
+ "Otherwise, don't paste the card's HTML into the conversation. Call save_context_card: it writes context-card.html " +
50
+ "into the project, opens it in the user's browser when this server runs locally, and returns the card as Markdown " +
51
+ "with a link to the saved file. Show the user that Markdown as returned, including the link, so they see the card " +
52
+ "in the chat and can open the full version in a browser.";
53
+ /** Theme / accent / expanded from tool arguments, shared by render and save. */
54
+ function cardOptions(args) {
55
+ const theme = args.theme;
56
+ return {
57
+ theme: (["light", "dark", "auto"].includes(theme) ? theme : "auto"),
58
+ accent: safeAccent(args.accent),
59
+ expanded: args.expanded === true || args.expanded === "true",
60
+ };
61
+ }
62
+ const CARD_ARGS = {
63
+ theme: { type: "string", enum: ["light", "dark", "auto"], description: "default: auto" },
64
+ accent: { type: "string", description: "CSS hex colour, e.g. #FF702D (default: the AAIF palette)" },
65
+ expanded: { type: "boolean", description: "render every AGENTS.md section open (default: collapsed)" },
66
+ };
42
67
  /**
43
68
  * @param root directory holding `AGENTS.md`, `project.fafm`, `.well-known/`.
44
69
  * Defaults to the package root; a deploy points `MCP_CONTEXT_CARD_ROOT`
45
70
  * at a real project, a test points it at a fixture.
46
71
  */
47
- export function createServer(root = ROOT) {
72
+ /** True when the connected client declared MCP Apps support
73
+ * (capabilities.extensions["io.modelcontextprotocol/ui"].mimeTypes). */
74
+ function hostRendersApps(server) {
75
+ const ext = server.getClientCapabilities()?.extensions?.[UI_EXTENSION];
76
+ return Array.isArray(ext?.mimeTypes) && ext.mimeTypes.includes(MCP_APP_MIME);
77
+ }
78
+ export function createServer(root = ROOT, opts = {}) {
48
79
  const AGENTS = join(root, "AGENTS.md");
49
80
  const FAFM = join(root, "project.fafm");
50
81
  const FAFA = join(root, ".well-known/fafa");
51
- const server = new Server({ name: NAME, version: VERSION }, { capabilities: { tools: {}, resources: {} } });
82
+ const server = new Server({ name: NAME, version: VERSION }, { capabilities: { tools: {}, resources: {} }, instructions: INSTRUCTIONS });
52
83
  // ── Mechanism 1: the Server Card resource + its _meta context block ───
53
84
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
54
85
  resources: [
@@ -58,9 +89,23 @@ export function createServer(root = ROOT) {
58
89
  description: "This server's identity + the _meta context block.",
59
90
  mimeType: "application/json",
60
91
  },
92
+ {
93
+ uri: CARD_UI_URI,
94
+ name: "Context Card",
95
+ description: "The context card as an MCP App: identity, AGENTS.md, memory and discovery, rendered inline by hosts that support MCP Apps.",
96
+ mimeType: MCP_APP_MIME,
97
+ },
61
98
  ],
62
99
  }));
63
100
  server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
101
+ if (req.params.uri === CARD_UI_URI) {
102
+ // Rendered at read time from the project's own files. The card is
103
+ // self-contained (inline CSS, no external requests), and its one script
104
+ // is progressive enhancement, so it still works in a strict sandbox.
105
+ return {
106
+ contents: [{ uri: CARD_UI_URI, mimeType: MCP_APP_MIME, text: renderCard(root, { theme: "auto" }) }],
107
+ };
108
+ }
64
109
  if (req.params.uri !== SERVER_CARD_URI) {
65
110
  throw new Error(`unknown resource: ${req.params.uri}`);
66
111
  }
@@ -178,14 +223,31 @@ export function createServer(root = ROOT) {
178
223
  {
179
224
  name: "render_context_card",
180
225
  title: "Render Context Card",
226
+ // MCP Apps: hosts that support it render the card inline from this
227
+ // resource. Both key forms, as the official ext-apps helper writes them.
228
+ _meta: { ui: { resourceUri: CARD_UI_URI }, "ui/resourceUri": CARD_UI_URI },
181
229
  annotations: { title: "Render Context Card", readOnlyHint: true, idempotentHint: true, openWorldHint: false },
182
230
  description: "Render the whole card — identity, AGENTS.md, memory, discovery — as one self-contained HTML page a person can read or screenshot. AGENTS.md sections collapse by default; pass expanded:true for the full render. Also served at GET /card (?expand=all) over the HTTP transport.",
231
+ inputSchema: { type: "object", properties: CARD_ARGS },
232
+ },
233
+ {
234
+ name: "save_context_card",
235
+ title: "Save Context Card",
236
+ annotations: { title: "Save Context Card", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
237
+ 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.",
183
238
  inputSchema: {
184
239
  type: "object",
185
240
  properties: {
186
- theme: { type: "string", enum: ["light", "dark", "auto"], description: "default: auto" },
187
- accent: { type: "string", description: "CSS hex colour, e.g. #FF702D (default: the AAIF palette)" },
188
- expanded: { type: "boolean", description: "render every AGENTS.md section open (default: collapsed)" },
241
+ ...CARD_ARGS,
242
+ open: {
243
+ type: "boolean",
244
+ description: "open the saved card in the person's browser when the server runs locally (default: true)",
245
+ },
246
+ detail: {
247
+ type: "string",
248
+ enum: ["tldr", "full"],
249
+ description: "the Markdown reply: tldr (default) shows five facts, each cut short; full shows every fact whole. The saved file always has everything.",
250
+ },
189
251
  },
190
252
  },
191
253
  },
@@ -235,19 +297,33 @@ export function createServer(root = ROOT) {
235
297
  case "whoami":
236
298
  return text(whoami(root));
237
299
  case "render_context_card": {
238
- const rawExpanded = args.expanded;
239
- return {
240
- content: [
241
- {
242
- type: "text",
243
- text: renderCard(root, {
244
- theme: (["light", "dark", "auto"].includes(args.theme) ? args.theme : "auto"),
245
- accent: safeAccent(args.accent),
246
- expanded: rawExpanded === true || rawExpanded === "true",
247
- }),
248
- },
249
- ],
250
- };
300
+ if (hostRendersApps(server)) {
301
+ // The host renders the card itself from CARD_UI_URI, so the model
302
+ // gets a short summary instead of a whole HTML page.
303
+ const doc = parseAgentsMd(AGENTS);
304
+ const mem = parseFafm(FAFM);
305
+ const sections = doc?.sections.length ?? 0;
306
+ const facts = mem.facts.length;
307
+ return text(`Showing the context card for ${NAME}: AGENTS.md with ${sections} section${sections === 1 ? "" : "s"}, ` +
308
+ `${facts} remembered fact${facts === 1 ? "" : "s"}, and this server's identity. ` +
309
+ "The card is displayed to the user; call read_agents_md or recall for the text itself.");
310
+ }
311
+ return text(renderCard(root, cardOptions(args)));
312
+ }
313
+ case "save_context_card": {
314
+ const out = join(root, "context-card.html");
315
+ writeFileSync(out, renderCard(root, cardOptions(args)));
316
+ // Many hosts won't follow a file:// link, so a local server opens it.
317
+ const raw = args.open;
318
+ const opened = !!opts.openFile && raw !== false && raw !== "false";
319
+ if (opened)
320
+ opts.openFile(out);
321
+ return text(`${renderCardText(root, { detail: args.detail === "full" ? "full" : "tldr" })}\n\n` +
322
+ "_In a host that supports MCP Apps, this card shows inline._\n\n---\n\n" +
323
+ (opened ? "Opened the full card in your browser.\n\n" : "") +
324
+ `**[Open the full card in your browser](${pathToFileURL(out).href})**\n\n` +
325
+ // Some hosts won't follow a file:// link; a code block gets a copy button.
326
+ `Or copy this into your browser's address bar:\n\n\`\`\`\n${pathToFileURL(out).href}\n\`\`\`\n\nSaved to ${out}`);
251
327
  }
252
328
  case "list_context_sources": {
253
329
  const doc = parseAgentsMd(AGENTS);
@@ -271,7 +347,10 @@ export function createServer(root = ROOT) {
271
347
  present: identity(root) !== null,
272
348
  },
273
349
  surfaces: {
274
- mcp: { serverCard: `resource ${SERVER_CARD_URI}` },
350
+ mcp: {
351
+ serverCard: `resource ${SERVER_CARD_URI}`,
352
+ card: `resource ${CARD_UI_URI} (MCP App, ${MCP_APP_MIME})`,
353
+ },
275
354
  http: {
276
355
  serverCard: "GET /.well-known/mcp/server-card",
277
356
  aiCatalog: "GET /.well-known/ai-catalog.json",
@@ -287,13 +366,14 @@ export function createServer(root = ROOT) {
287
366
  return server;
288
367
  }
289
368
  /** Connect a server instance to a transport (stdio or http). */
290
- export async function serve(transport, root = ROOT) {
291
- const server = createServer(root);
369
+ export async function serve(transport, root = ROOT, opts = {}) {
370
+ const server = createServer(root, opts);
292
371
  await server.connect(transport);
293
372
  return server;
294
373
  }
295
374
  // Direct run (incl. the demo's spawned child) → stdio. pathToFileURL keeps
296
375
  // this correct on Windows, where argv[1] is a `C:\...` path.
297
376
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
298
- await serve(new StdioServerTransport());
377
+ const { openInBrowser } = await import("./open.js");
378
+ await serve(new StdioServerTransport(), ROOT, { openFile: openInBrowser });
299
379
  }
@@ -29,7 +29,7 @@ Both return:
29
29
  ```jsonc
30
30
  {
31
31
  "name": "mcp-context-card",
32
- "version": "1.1.1",
32
+ "version": "1.2.0",
33
33
  "_meta": {
34
34
  "io.github.Wolfe-Jam.mcp-context-card/context": {
35
35
  "source": "AGENTS.md",
@@ -118,7 +118,9 @@ GET /.well-known/ai-catalog.json
118
118
 
119
119
  The same three sources also render as **the card** — `GET /card` /
120
120
  `render_context_card` / `docs/card.html` — the human view of exactly what a
121
- machine reads below.
121
+ machine reads below. Hosts that support MCP Apps show it inline from the
122
+ `ui://mcp-context-card/card.html` resource; elsewhere `save_context_card`
123
+ writes it to `context-card.html` and returns it as Markdown.
122
124
 
123
125
  The same three sources back **both** discovery mechanisms:
124
126
 
package/docs/WIRING.md CHANGED
@@ -83,7 +83,8 @@ And to discover what a server offers before committing to it:
83
83
  await client.callTool({ name: "list_context_sources", arguments: {} });
84
84
  // → { context: { source: "AGENTS.md", mediaType: "text/markdown", present: true, sections: 10 },
85
85
  // memory: { … }, identity: { … },
86
- // surfaces: { mcp: { serverCard: "resource mcp-context-card://server-card" },
86
+ // surfaces: { mcp: { serverCard: "resource mcp-context-card://server-card",
87
+ // card: "resource ui://mcp-context-card/card.html (MCP App, text/html;profile=mcp-app)" },
87
88
  // http: { serverCard: "GET /.well-known/mcp/server-card",
88
89
  // aiCatalog: "GET /.well-known/ai-catalog.json",
89
90
  // card: "GET /card" } } }
@@ -100,7 +100,7 @@ details.ctx-section>.md{padding:0 0 16px}
100
100
  <main class="card">
101
101
  <div class="top">
102
102
  <h1>mcp-context-card</h1>
103
- <div class="pills"><span class="pill">io.github.Wolfe-Jam</span><span class="pill">v1.1.1</span><span class="pill accent">published</span><span class="pill">MIT</span></div>
103
+ <div class="pills"><span class="pill">io.github.Wolfe-Jam</span><span class="pill">v1.2.0</span><span class="pill accent">published</span><span class="pill">MIT</span></div>
104
104
  </div>
105
105
  <section>
106
106
  <p class="label">Context — AGENTS.md</p>
@@ -114,7 +114,7 @@ npm run test:coverage # + the coverage gate (lines 90 / funcs 85 / branches 80,
114
114
  npm run demo # end to end: all tools over stdio, then over stateless HTTP
115
115
  npm run version:check # every version-bearing spot agrees with package.json
116
116
  npm run faf:check # project.faf / project.fafm / .well-known/fafa still describe the project</code></pre>
117
- <p>CI runs <code>version:check → faf:check → typecheck → build → test:coverage → demo</code> on Linux, macOS, and Windows for every push and PR to <code>main</code> (<code>.github/workflows/ci.yml</code>), plus <code>catalog:check</code> / <code>card:check</code>, <code>faf-cli check project.faf --strict</code> (the repo dogfoods a <code>project.faf</code> — this keeps it Trophy), and <code>faf:nudge</code> (PR-only, non-blocking — warns if the code's shape moved without <code>project.faf</code>) on Linux.</p></div></details><details class="ctx-section" id="layout"><summary>Layout</summary><div class="md"><table><thead><tr><th>Path</th><th>What</th></tr></thead><tbody><tr><td><code>src/server.ts</code></td><td>the MCP server — the nine tools + the Server Card resource</td></tr><tr><td><code>src/agents-md.ts</code></td><td>reads and section-splits this file</td></tr><tr><td><code>src/author.ts</code></td><td><code>author_agents_md</code> — BETTER via <code>agents-md-facts</code>, BEST when <code>project.faf</code> exists</td></tr><tr><td><code>src/md.ts</code></td><td>a minimal dependency-free Markdown → HTML renderer</td></tr><tr><td><code>src/render-card.ts</code></td><td>the card — identity + this file + memory + discovery, as one HTML page</td></tr><tr><td><code>src/memory.ts</code> → <code>src/faf/parse-fafm.ts</code></td><td>file-backed <code>remember</code> / <code>recall</code> / <code>forget</code></td></tr><tr><td><code>src/identity.ts</code></td><td><code>whoami</code> (<code>.fafa</code> → <code>package.json</code> fallback) + the <code>_meta</code> context block</td></tr><tr><td><code>src/catalog-gen.ts</code></td><td>writes <code>.well-known/ai-catalog.json</code> from the same three sources</td></tr><tr><td><code>src/transport/http.ts</code></td><td>the stateless Streamable HTTP app (Hono)</td></tr><tr><td><code>src/bin.ts</code></td><td>the entry point (<code>resolveLaunch</code>) — <code>stdio</code> · <code>--http</code> · <code>card</code> · <code>--help</code> · <code>--version</code></td></tr><tr><td><code>src/faf/parse-fafm.ts</code> · <code>parse-fafa.ts</code></td><td>the <code>.fafm</code> / <code>.fafa</code> parsers</td></tr></tbody></table></div></details><details class="ctx-section" id="conventions"><summary>Conventions</summary><div class="md"><ul><li>TypeScript strict, ESM only (<code>&quot;type&quot;: &quot;module&quot;</code>, <code>.js</code> import specifiers).</li><li>Tests use <code>node:test</code> + <code>node:assert/strict</code> — no test framework.</li><li>Every source file opens with a comment stating what it is and why.</li><li>Conventional Commit messages (<code>feat:</code>, <code>fix:</code>, <code>chore:</code>, <code>test:</code>, <code>docs:</code>).</li></ul></div></details><details class="ctx-section" id="the-invariant"><summary>The invariant</summary><div class="md"><p><code>src/identity.ts</code>, <code>src/catalog-gen.ts</code>, and <code>src/render-card.ts</code> all describe the <strong>same three sources</strong>: this file, <code>project.fafm</code>, <code>.well-known/fafa</code>. Change what one exposes and you must change the others. <code>npm run catalog:check</code> and <code>npm run card:check</code> enforce it in CI — each regenerates its surface and fails on any diff.</p></div></details><details class="ctx-section" id="safety"><summary>Safety</summary><div class="md"><ul><li>Branch off <code>main</code>; CI must be green before merge.</li><li><code>npm run demo</code> writes a fact to <code>project.fafm</code> and restores the file on exit — don't kill it mid-run.</li><li>No secrets live in this repo; never add any.</li></ul></div></details><details class="ctx-section" id="definition-of-done"><summary>Definition of done</summary><div class="md"><p><code>npm run typecheck &amp;&amp; npm run build &amp;&amp; npm test &amp;&amp; npm run demo</code> all green, plus <code>npm run catalog:check</code> and <code>npm run card:check</code> clean if you touched <code>AGENTS.md</code>, <code>project.fafm</code>, or <code>.well-known/fafa</code>, and <code>npm run faf:check</code> green if you changed the layout, a dependency, or the identity. On a version bump, <code>npm run version:check</code> green (it lists every spot that must move together) and <code>project.faf</code> still Trophy (<code>faf-cli check project.faf --strict</code>). If CI's <code>faf:nudge</code> warns on your PR, reconcile <code>project.faf</code> (and re-check <code>project.fafm</code> facts if <code>AGENTS.md</code> moved) or say why it's fine.</p></div></details><details class="ctx-section" id="authoring-this-file"><summary>Authoring this file</summary><div class="md"><p><code>AGENTS.md</code> here is maintained by hand. The <code>author_agents_md</code> tool (or <code>faf export --agents</code>) would draft a BEST version straight from this repo's own <code>project.faf</code> plus its detected facts — the server doesn't care how the file was authored, only that it's valid Markdown.</p></div></details></div>
117
+ <p>CI runs <code>version:check → faf:check → typecheck → build → test:coverage → demo</code> on Linux, macOS, and Windows for every push and PR to <code>main</code> (<code>.github/workflows/ci.yml</code>), plus <code>catalog:check</code> / <code>card:check</code>, <code>faf-cli check project.faf --strict</code> (the repo dogfoods a <code>project.faf</code> — this keeps it Trophy), and <code>faf:nudge</code> (PR-only, non-blocking — warns if the code's shape moved without <code>project.faf</code>) on Linux.</p></div></details><details class="ctx-section" id="layout"><summary>Layout</summary><div class="md"><table><thead><tr><th>Path</th><th>What</th></tr></thead><tbody><tr><td><code>src/server.ts</code></td><td>the MCP server — the ten tools + the Server Card and card resources</td></tr><tr><td><code>src/agents-md.ts</code></td><td>reads and section-splits this file</td></tr><tr><td><code>src/author.ts</code></td><td><code>author_agents_md</code> — BETTER via <code>agents-md-facts</code>, BEST when <code>project.faf</code> exists</td></tr><tr><td><code>src/md.ts</code></td><td>a minimal dependency-free Markdown → HTML renderer</td></tr><tr><td><code>src/render-card.ts</code></td><td>the card — identity + this file + memory + discovery, as one HTML page</td></tr><tr><td><code>src/memory.ts</code> → <code>src/faf/parse-fafm.ts</code></td><td>file-backed <code>remember</code> / <code>recall</code> / <code>forget</code></td></tr><tr><td><code>src/identity.ts</code></td><td><code>whoami</code> (<code>.fafa</code> → <code>package.json</code> fallback) + the <code>_meta</code> context block</td></tr><tr><td><code>src/catalog-gen.ts</code></td><td>writes <code>.well-known/ai-catalog.json</code> from the same three sources</td></tr><tr><td><code>src/transport/http.ts</code></td><td>the stateless Streamable HTTP app (Hono)</td></tr><tr><td><code>src/bin.ts</code></td><td>the entry point (<code>resolveLaunch</code>) — <code>stdio</code> · <code>--http</code> · <code>card</code> · <code>--help</code> · <code>--version</code></td></tr><tr><td><code>src/faf/parse-fafm.ts</code> · <code>parse-fafa.ts</code></td><td>the <code>.fafm</code> / <code>.fafa</code> parsers</td></tr></tbody></table></div></details><details class="ctx-section" id="conventions"><summary>Conventions</summary><div class="md"><ul><li>TypeScript strict, ESM only (<code>&quot;type&quot;: &quot;module&quot;</code>, <code>.js</code> import specifiers).</li><li>Tests use <code>node:test</code> + <code>node:assert/strict</code> — no test framework.</li><li>Every source file opens with a comment stating what it is and why.</li><li>Conventional Commit messages (<code>feat:</code>, <code>fix:</code>, <code>chore:</code>, <code>test:</code>, <code>docs:</code>).</li></ul></div></details><details class="ctx-section" id="the-invariant"><summary>The invariant</summary><div class="md"><p><code>src/identity.ts</code>, <code>src/catalog-gen.ts</code>, and <code>src/render-card.ts</code> all describe the <strong>same three sources</strong>: this file, <code>project.fafm</code>, <code>.well-known/fafa</code>. Change what one exposes and you must change the others. <code>npm run catalog:check</code> and <code>npm run card:check</code> enforce it in CI — each regenerates its surface and fails on any diff.</p></div></details><details class="ctx-section" id="safety"><summary>Safety</summary><div class="md"><ul><li>Branch off <code>main</code>; CI must be green before merge.</li><li><code>npm run demo</code> writes a fact to <code>project.fafm</code> and restores the file on exit — don't kill it mid-run.</li><li>No secrets live in this repo; never add any.</li></ul></div></details><details class="ctx-section" id="definition-of-done"><summary>Definition of done</summary><div class="md"><p><code>npm run typecheck &amp;&amp; npm run build &amp;&amp; npm test &amp;&amp; npm run demo</code> all green, plus <code>npm run catalog:check</code> and <code>npm run card:check</code> clean if you touched <code>AGENTS.md</code>, <code>project.fafm</code>, or <code>.well-known/fafa</code>, and <code>npm run faf:check</code> green if you changed the layout, a dependency, or the identity. On a version bump, <code>npm run version:check</code> green (it lists every spot that must move together) and <code>project.faf</code> still Trophy (<code>faf-cli check project.faf --strict</code>). If CI's <code>faf:nudge</code> warns on your PR, reconcile <code>project.faf</code> (and re-check <code>project.fafm</code> facts if <code>AGENTS.md</code> moved) or say why it's fine.</p></div></details><details class="ctx-section" id="authoring-this-file"><summary>Authoring this file</summary><div class="md"><p><code>AGENTS.md</code> here is maintained by hand. The <code>author_agents_md</code> tool (or <code>faf export --agents</code>) would draft a BEST version straight from this repo's own <code>project.faf</code> plus its detected facts — the server doesn't care how the file was authored, only that it's valid Markdown.</p></div></details></div>
118
118
  </section>
119
119
  <section>
120
120
  <p class="label">Memory — 4 facts</p>
@@ -100,7 +100,7 @@ details.ctx-section>.md{padding:0 0 16px}
100
100
  <main class="card">
101
101
  <div class="top">
102
102
  <h1>mcp-context-card</h1>
103
- <div class="pills"><span class="pill">io.github.Wolfe-Jam</span><span class="pill">v1.1.1</span><span class="pill accent">published</span><span class="pill">MIT</span></div>
103
+ <div class="pills"><span class="pill">io.github.Wolfe-Jam</span><span class="pill">v1.2.0</span><span class="pill accent">published</span><span class="pill">MIT</span></div>
104
104
  </div>
105
105
  <section>
106
106
  <p class="label">Context — AGENTS.md</p>
@@ -114,7 +114,7 @@ npm run test:coverage # + the coverage gate (lines 90 / funcs 85 / branches 80,
114
114
  npm run demo # end to end: all tools over stdio, then over stateless HTTP
115
115
  npm run version:check # every version-bearing spot agrees with package.json
116
116
  npm run faf:check # project.faf / project.fafm / .well-known/fafa still describe the project</code></pre>
117
- <p>CI runs <code>version:check → faf:check → typecheck → build → test:coverage → demo</code> on Linux, macOS, and Windows for every push and PR to <code>main</code> (<code>.github/workflows/ci.yml</code>), plus <code>catalog:check</code> / <code>card:check</code>, <code>faf-cli check project.faf --strict</code> (the repo dogfoods a <code>project.faf</code> — this keeps it Trophy), and <code>faf:nudge</code> (PR-only, non-blocking — warns if the code's shape moved without <code>project.faf</code>) on Linux.</p></div></details><details class="ctx-section" id="layout"><summary>Layout</summary><div class="md"><table><thead><tr><th>Path</th><th>What</th></tr></thead><tbody><tr><td><code>src/server.ts</code></td><td>the MCP server — the nine tools + the Server Card resource</td></tr><tr><td><code>src/agents-md.ts</code></td><td>reads and section-splits this file</td></tr><tr><td><code>src/author.ts</code></td><td><code>author_agents_md</code> — BETTER via <code>agents-md-facts</code>, BEST when <code>project.faf</code> exists</td></tr><tr><td><code>src/md.ts</code></td><td>a minimal dependency-free Markdown → HTML renderer</td></tr><tr><td><code>src/render-card.ts</code></td><td>the card — identity + this file + memory + discovery, as one HTML page</td></tr><tr><td><code>src/memory.ts</code> → <code>src/faf/parse-fafm.ts</code></td><td>file-backed <code>remember</code> / <code>recall</code> / <code>forget</code></td></tr><tr><td><code>src/identity.ts</code></td><td><code>whoami</code> (<code>.fafa</code> → <code>package.json</code> fallback) + the <code>_meta</code> context block</td></tr><tr><td><code>src/catalog-gen.ts</code></td><td>writes <code>.well-known/ai-catalog.json</code> from the same three sources</td></tr><tr><td><code>src/transport/http.ts</code></td><td>the stateless Streamable HTTP app (Hono)</td></tr><tr><td><code>src/bin.ts</code></td><td>the entry point (<code>resolveLaunch</code>) — <code>stdio</code> · <code>--http</code> · <code>card</code> · <code>--help</code> · <code>--version</code></td></tr><tr><td><code>src/faf/parse-fafm.ts</code> · <code>parse-fafa.ts</code></td><td>the <code>.fafm</code> / <code>.fafa</code> parsers</td></tr></tbody></table></div></details><details class="ctx-section" id="conventions"><summary>Conventions</summary><div class="md"><ul><li>TypeScript strict, ESM only (<code>&quot;type&quot;: &quot;module&quot;</code>, <code>.js</code> import specifiers).</li><li>Tests use <code>node:test</code> + <code>node:assert/strict</code> — no test framework.</li><li>Every source file opens with a comment stating what it is and why.</li><li>Conventional Commit messages (<code>feat:</code>, <code>fix:</code>, <code>chore:</code>, <code>test:</code>, <code>docs:</code>).</li></ul></div></details><details class="ctx-section" id="the-invariant"><summary>The invariant</summary><div class="md"><p><code>src/identity.ts</code>, <code>src/catalog-gen.ts</code>, and <code>src/render-card.ts</code> all describe the <strong>same three sources</strong>: this file, <code>project.fafm</code>, <code>.well-known/fafa</code>. Change what one exposes and you must change the others. <code>npm run catalog:check</code> and <code>npm run card:check</code> enforce it in CI — each regenerates its surface and fails on any diff.</p></div></details><details class="ctx-section" id="safety"><summary>Safety</summary><div class="md"><ul><li>Branch off <code>main</code>; CI must be green before merge.</li><li><code>npm run demo</code> writes a fact to <code>project.fafm</code> and restores the file on exit — don't kill it mid-run.</li><li>No secrets live in this repo; never add any.</li></ul></div></details><details class="ctx-section" id="definition-of-done"><summary>Definition of done</summary><div class="md"><p><code>npm run typecheck &amp;&amp; npm run build &amp;&amp; npm test &amp;&amp; npm run demo</code> all green, plus <code>npm run catalog:check</code> and <code>npm run card:check</code> clean if you touched <code>AGENTS.md</code>, <code>project.fafm</code>, or <code>.well-known/fafa</code>, and <code>npm run faf:check</code> green if you changed the layout, a dependency, or the identity. On a version bump, <code>npm run version:check</code> green (it lists every spot that must move together) and <code>project.faf</code> still Trophy (<code>faf-cli check project.faf --strict</code>). If CI's <code>faf:nudge</code> warns on your PR, reconcile <code>project.faf</code> (and re-check <code>project.fafm</code> facts if <code>AGENTS.md</code> moved) or say why it's fine.</p></div></details><details class="ctx-section" id="authoring-this-file"><summary>Authoring this file</summary><div class="md"><p><code>AGENTS.md</code> here is maintained by hand. The <code>author_agents_md</code> tool (or <code>faf export --agents</code>) would draft a BEST version straight from this repo's own <code>project.faf</code> plus its detected facts — the server doesn't care how the file was authored, only that it's valid Markdown.</p></div></details></div>
117
+ <p>CI runs <code>version:check → faf:check → typecheck → build → test:coverage → demo</code> on Linux, macOS, and Windows for every push and PR to <code>main</code> (<code>.github/workflows/ci.yml</code>), plus <code>catalog:check</code> / <code>card:check</code>, <code>faf-cli check project.faf --strict</code> (the repo dogfoods a <code>project.faf</code> — this keeps it Trophy), and <code>faf:nudge</code> (PR-only, non-blocking — warns if the code's shape moved without <code>project.faf</code>) on Linux.</p></div></details><details class="ctx-section" id="layout"><summary>Layout</summary><div class="md"><table><thead><tr><th>Path</th><th>What</th></tr></thead><tbody><tr><td><code>src/server.ts</code></td><td>the MCP server — the ten tools + the Server Card and card resources</td></tr><tr><td><code>src/agents-md.ts</code></td><td>reads and section-splits this file</td></tr><tr><td><code>src/author.ts</code></td><td><code>author_agents_md</code> — BETTER via <code>agents-md-facts</code>, BEST when <code>project.faf</code> exists</td></tr><tr><td><code>src/md.ts</code></td><td>a minimal dependency-free Markdown → HTML renderer</td></tr><tr><td><code>src/render-card.ts</code></td><td>the card — identity + this file + memory + discovery, as one HTML page</td></tr><tr><td><code>src/memory.ts</code> → <code>src/faf/parse-fafm.ts</code></td><td>file-backed <code>remember</code> / <code>recall</code> / <code>forget</code></td></tr><tr><td><code>src/identity.ts</code></td><td><code>whoami</code> (<code>.fafa</code> → <code>package.json</code> fallback) + the <code>_meta</code> context block</td></tr><tr><td><code>src/catalog-gen.ts</code></td><td>writes <code>.well-known/ai-catalog.json</code> from the same three sources</td></tr><tr><td><code>src/transport/http.ts</code></td><td>the stateless Streamable HTTP app (Hono)</td></tr><tr><td><code>src/bin.ts</code></td><td>the entry point (<code>resolveLaunch</code>) — <code>stdio</code> · <code>--http</code> · <code>card</code> · <code>--help</code> · <code>--version</code></td></tr><tr><td><code>src/faf/parse-fafm.ts</code> · <code>parse-fafa.ts</code></td><td>the <code>.fafm</code> / <code>.fafa</code> parsers</td></tr></tbody></table></div></details><details class="ctx-section" id="conventions"><summary>Conventions</summary><div class="md"><ul><li>TypeScript strict, ESM only (<code>&quot;type&quot;: &quot;module&quot;</code>, <code>.js</code> import specifiers).</li><li>Tests use <code>node:test</code> + <code>node:assert/strict</code> — no test framework.</li><li>Every source file opens with a comment stating what it is and why.</li><li>Conventional Commit messages (<code>feat:</code>, <code>fix:</code>, <code>chore:</code>, <code>test:</code>, <code>docs:</code>).</li></ul></div></details><details class="ctx-section" id="the-invariant"><summary>The invariant</summary><div class="md"><p><code>src/identity.ts</code>, <code>src/catalog-gen.ts</code>, and <code>src/render-card.ts</code> all describe the <strong>same three sources</strong>: this file, <code>project.fafm</code>, <code>.well-known/fafa</code>. Change what one exposes and you must change the others. <code>npm run catalog:check</code> and <code>npm run card:check</code> enforce it in CI — each regenerates its surface and fails on any diff.</p></div></details><details class="ctx-section" id="safety"><summary>Safety</summary><div class="md"><ul><li>Branch off <code>main</code>; CI must be green before merge.</li><li><code>npm run demo</code> writes a fact to <code>project.fafm</code> and restores the file on exit — don't kill it mid-run.</li><li>No secrets live in this repo; never add any.</li></ul></div></details><details class="ctx-section" id="definition-of-done"><summary>Definition of done</summary><div class="md"><p><code>npm run typecheck &amp;&amp; npm run build &amp;&amp; npm test &amp;&amp; npm run demo</code> all green, plus <code>npm run catalog:check</code> and <code>npm run card:check</code> clean if you touched <code>AGENTS.md</code>, <code>project.fafm</code>, or <code>.well-known/fafa</code>, and <code>npm run faf:check</code> green if you changed the layout, a dependency, or the identity. On a version bump, <code>npm run version:check</code> green (it lists every spot that must move together) and <code>project.faf</code> still Trophy (<code>faf-cli check project.faf --strict</code>). If CI's <code>faf:nudge</code> warns on your PR, reconcile <code>project.faf</code> (and re-check <code>project.fafm</code> facts if <code>AGENTS.md</code> moved) or say why it's fine.</p></div></details><details class="ctx-section" id="authoring-this-file"><summary>Authoring this file</summary><div class="md"><p><code>AGENTS.md</code> here is maintained by hand. The <code>author_agents_md</code> tool (or <code>faf export --agents</code>) would draft a BEST version straight from this repo's own <code>project.faf</code> plus its detected facts — the server doesn't care how the file was authored, only that it's valid Markdown.</p></div></details></div>
118
118
  </section>
119
119
  <section>
120
120
  <p class="label">Memory — 4 facts</p>
package/docs/card.html CHANGED
@@ -100,7 +100,7 @@ details.ctx-section>.md{padding:0 0 16px}
100
100
  <main class="card">
101
101
  <div class="top">
102
102
  <h1>mcp-context-card</h1>
103
- <div class="pills"><span class="pill">io.github.Wolfe-Jam</span><span class="pill">v1.1.1</span><span class="pill accent">published</span><span class="pill">MIT</span></div>
103
+ <div class="pills"><span class="pill">io.github.Wolfe-Jam</span><span class="pill">v1.2.0</span><span class="pill accent">published</span><span class="pill">MIT</span></div>
104
104
  </div>
105
105
  <section>
106
106
  <p class="label">Context — AGENTS.md</p>
@@ -114,7 +114,7 @@ npm run test:coverage # + the coverage gate (lines 90 / funcs 85 / branches 80,
114
114
  npm run demo # end to end: all tools over stdio, then over stateless HTTP
115
115
  npm run version:check # every version-bearing spot agrees with package.json
116
116
  npm run faf:check # project.faf / project.fafm / .well-known/fafa still describe the project</code></pre>
117
- <p>CI runs <code>version:check → faf:check → typecheck → build → test:coverage → demo</code> on Linux, macOS, and Windows for every push and PR to <code>main</code> (<code>.github/workflows/ci.yml</code>), plus <code>catalog:check</code> / <code>card:check</code>, <code>faf-cli check project.faf --strict</code> (the repo dogfoods a <code>project.faf</code> — this keeps it Trophy), and <code>faf:nudge</code> (PR-only, non-blocking — warns if the code's shape moved without <code>project.faf</code>) on Linux.</p></div></details><details class="ctx-section" id="layout"><summary>Layout</summary><div class="md"><table><thead><tr><th>Path</th><th>What</th></tr></thead><tbody><tr><td><code>src/server.ts</code></td><td>the MCP server — the nine tools + the Server Card resource</td></tr><tr><td><code>src/agents-md.ts</code></td><td>reads and section-splits this file</td></tr><tr><td><code>src/author.ts</code></td><td><code>author_agents_md</code> — BETTER via <code>agents-md-facts</code>, BEST when <code>project.faf</code> exists</td></tr><tr><td><code>src/md.ts</code></td><td>a minimal dependency-free Markdown → HTML renderer</td></tr><tr><td><code>src/render-card.ts</code></td><td>the card — identity + this file + memory + discovery, as one HTML page</td></tr><tr><td><code>src/memory.ts</code> → <code>src/faf/parse-fafm.ts</code></td><td>file-backed <code>remember</code> / <code>recall</code> / <code>forget</code></td></tr><tr><td><code>src/identity.ts</code></td><td><code>whoami</code> (<code>.fafa</code> → <code>package.json</code> fallback) + the <code>_meta</code> context block</td></tr><tr><td><code>src/catalog-gen.ts</code></td><td>writes <code>.well-known/ai-catalog.json</code> from the same three sources</td></tr><tr><td><code>src/transport/http.ts</code></td><td>the stateless Streamable HTTP app (Hono)</td></tr><tr><td><code>src/bin.ts</code></td><td>the entry point (<code>resolveLaunch</code>) — <code>stdio</code> · <code>--http</code> · <code>card</code> · <code>--help</code> · <code>--version</code></td></tr><tr><td><code>src/faf/parse-fafm.ts</code> · <code>parse-fafa.ts</code></td><td>the <code>.fafm</code> / <code>.fafa</code> parsers</td></tr></tbody></table></div></details><details class="ctx-section" id="conventions"><summary>Conventions</summary><div class="md"><ul><li>TypeScript strict, ESM only (<code>&quot;type&quot;: &quot;module&quot;</code>, <code>.js</code> import specifiers).</li><li>Tests use <code>node:test</code> + <code>node:assert/strict</code> — no test framework.</li><li>Every source file opens with a comment stating what it is and why.</li><li>Conventional Commit messages (<code>feat:</code>, <code>fix:</code>, <code>chore:</code>, <code>test:</code>, <code>docs:</code>).</li></ul></div></details><details class="ctx-section" id="the-invariant"><summary>The invariant</summary><div class="md"><p><code>src/identity.ts</code>, <code>src/catalog-gen.ts</code>, and <code>src/render-card.ts</code> all describe the <strong>same three sources</strong>: this file, <code>project.fafm</code>, <code>.well-known/fafa</code>. Change what one exposes and you must change the others. <code>npm run catalog:check</code> and <code>npm run card:check</code> enforce it in CI — each regenerates its surface and fails on any diff.</p></div></details><details class="ctx-section" id="safety"><summary>Safety</summary><div class="md"><ul><li>Branch off <code>main</code>; CI must be green before merge.</li><li><code>npm run demo</code> writes a fact to <code>project.fafm</code> and restores the file on exit — don't kill it mid-run.</li><li>No secrets live in this repo; never add any.</li></ul></div></details><details class="ctx-section" id="definition-of-done"><summary>Definition of done</summary><div class="md"><p><code>npm run typecheck &amp;&amp; npm run build &amp;&amp; npm test &amp;&amp; npm run demo</code> all green, plus <code>npm run catalog:check</code> and <code>npm run card:check</code> clean if you touched <code>AGENTS.md</code>, <code>project.fafm</code>, or <code>.well-known/fafa</code>, and <code>npm run faf:check</code> green if you changed the layout, a dependency, or the identity. On a version bump, <code>npm run version:check</code> green (it lists every spot that must move together) and <code>project.faf</code> still Trophy (<code>faf-cli check project.faf --strict</code>). If CI's <code>faf:nudge</code> warns on your PR, reconcile <code>project.faf</code> (and re-check <code>project.fafm</code> facts if <code>AGENTS.md</code> moved) or say why it's fine.</p></div></details><details class="ctx-section" id="authoring-this-file"><summary>Authoring this file</summary><div class="md"><p><code>AGENTS.md</code> here is maintained by hand. The <code>author_agents_md</code> tool (or <code>faf export --agents</code>) would draft a BEST version straight from this repo's own <code>project.faf</code> plus its detected facts — the server doesn't care how the file was authored, only that it's valid Markdown.</p></div></details></div>
117
+ <p>CI runs <code>version:check → faf:check → typecheck → build → test:coverage → demo</code> on Linux, macOS, and Windows for every push and PR to <code>main</code> (<code>.github/workflows/ci.yml</code>), plus <code>catalog:check</code> / <code>card:check</code>, <code>faf-cli check project.faf --strict</code> (the repo dogfoods a <code>project.faf</code> — this keeps it Trophy), and <code>faf:nudge</code> (PR-only, non-blocking — warns if the code's shape moved without <code>project.faf</code>) on Linux.</p></div></details><details class="ctx-section" id="layout"><summary>Layout</summary><div class="md"><table><thead><tr><th>Path</th><th>What</th></tr></thead><tbody><tr><td><code>src/server.ts</code></td><td>the MCP server — the ten tools + the Server Card and card resources</td></tr><tr><td><code>src/agents-md.ts</code></td><td>reads and section-splits this file</td></tr><tr><td><code>src/author.ts</code></td><td><code>author_agents_md</code> — BETTER via <code>agents-md-facts</code>, BEST when <code>project.faf</code> exists</td></tr><tr><td><code>src/md.ts</code></td><td>a minimal dependency-free Markdown → HTML renderer</td></tr><tr><td><code>src/render-card.ts</code></td><td>the card — identity + this file + memory + discovery, as one HTML page</td></tr><tr><td><code>src/memory.ts</code> → <code>src/faf/parse-fafm.ts</code></td><td>file-backed <code>remember</code> / <code>recall</code> / <code>forget</code></td></tr><tr><td><code>src/identity.ts</code></td><td><code>whoami</code> (<code>.fafa</code> → <code>package.json</code> fallback) + the <code>_meta</code> context block</td></tr><tr><td><code>src/catalog-gen.ts</code></td><td>writes <code>.well-known/ai-catalog.json</code> from the same three sources</td></tr><tr><td><code>src/transport/http.ts</code></td><td>the stateless Streamable HTTP app (Hono)</td></tr><tr><td><code>src/bin.ts</code></td><td>the entry point (<code>resolveLaunch</code>) — <code>stdio</code> · <code>--http</code> · <code>card</code> · <code>--help</code> · <code>--version</code></td></tr><tr><td><code>src/faf/parse-fafm.ts</code> · <code>parse-fafa.ts</code></td><td>the <code>.fafm</code> / <code>.fafa</code> parsers</td></tr></tbody></table></div></details><details class="ctx-section" id="conventions"><summary>Conventions</summary><div class="md"><ul><li>TypeScript strict, ESM only (<code>&quot;type&quot;: &quot;module&quot;</code>, <code>.js</code> import specifiers).</li><li>Tests use <code>node:test</code> + <code>node:assert/strict</code> — no test framework.</li><li>Every source file opens with a comment stating what it is and why.</li><li>Conventional Commit messages (<code>feat:</code>, <code>fix:</code>, <code>chore:</code>, <code>test:</code>, <code>docs:</code>).</li></ul></div></details><details class="ctx-section" id="the-invariant"><summary>The invariant</summary><div class="md"><p><code>src/identity.ts</code>, <code>src/catalog-gen.ts</code>, and <code>src/render-card.ts</code> all describe the <strong>same three sources</strong>: this file, <code>project.fafm</code>, <code>.well-known/fafa</code>. Change what one exposes and you must change the others. <code>npm run catalog:check</code> and <code>npm run card:check</code> enforce it in CI — each regenerates its surface and fails on any diff.</p></div></details><details class="ctx-section" id="safety"><summary>Safety</summary><div class="md"><ul><li>Branch off <code>main</code>; CI must be green before merge.</li><li><code>npm run demo</code> writes a fact to <code>project.fafm</code> and restores the file on exit — don't kill it mid-run.</li><li>No secrets live in this repo; never add any.</li></ul></div></details><details class="ctx-section" id="definition-of-done"><summary>Definition of done</summary><div class="md"><p><code>npm run typecheck &amp;&amp; npm run build &amp;&amp; npm test &amp;&amp; npm run demo</code> all green, plus <code>npm run catalog:check</code> and <code>npm run card:check</code> clean if you touched <code>AGENTS.md</code>, <code>project.fafm</code>, or <code>.well-known/fafa</code>, and <code>npm run faf:check</code> green if you changed the layout, a dependency, or the identity. On a version bump, <code>npm run version:check</code> green (it lists every spot that must move together) and <code>project.faf</code> still Trophy (<code>faf-cli check project.faf --strict</code>). If CI's <code>faf:nudge</code> warns on your PR, reconcile <code>project.faf</code> (and re-check <code>project.fafm</code> facts if <code>AGENTS.md</code> moved) or say why it's fine.</p></div></details><details class="ctx-section" id="authoring-this-file"><summary>Authoring this file</summary><div class="md"><p><code>AGENTS.md</code> here is maintained by hand. The <code>author_agents_md</code> tool (or <code>faf export --agents</code>) would draft a BEST version straight from this repo's own <code>project.faf</code> plus its detected facts — the server doesn't care how the file was authored, only that it's valid Markdown.</p></div></details></div>
118
118
  </section>
119
119
  <section>
120
120
  <p class="label">Memory — 4 facts</p>
Binary file
Binary file
Binary file
package/docs/index.html CHANGED
@@ -100,7 +100,7 @@ details.ctx-section>.md{padding:0 0 16px}
100
100
  <main class="card">
101
101
  <div class="top">
102
102
  <h1>mcp-context-card</h1>
103
- <div class="pills"><span class="pill">io.github.Wolfe-Jam</span><span class="pill">v1.1.1</span><span class="pill accent">published</span><span class="pill">MIT</span></div>
103
+ <div class="pills"><span class="pill">io.github.Wolfe-Jam</span><span class="pill">v1.2.0</span><span class="pill accent">published</span><span class="pill">MIT</span></div>
104
104
  </div>
105
105
  <section>
106
106
  <p class="label">Context — AGENTS.md</p>
@@ -114,7 +114,7 @@ npm run test:coverage # + the coverage gate (lines 90 / funcs 85 / branches 80,
114
114
  npm run demo # end to end: all tools over stdio, then over stateless HTTP
115
115
  npm run version:check # every version-bearing spot agrees with package.json
116
116
  npm run faf:check # project.faf / project.fafm / .well-known/fafa still describe the project</code></pre>
117
- <p>CI runs <code>version:check → faf:check → typecheck → build → test:coverage → demo</code> on Linux, macOS, and Windows for every push and PR to <code>main</code> (<code>.github/workflows/ci.yml</code>), plus <code>catalog:check</code> / <code>card:check</code>, <code>faf-cli check project.faf --strict</code> (the repo dogfoods a <code>project.faf</code> — this keeps it Trophy), and <code>faf:nudge</code> (PR-only, non-blocking — warns if the code's shape moved without <code>project.faf</code>) on Linux.</p></div></details><details class="ctx-section" id="layout"><summary>Layout</summary><div class="md"><table><thead><tr><th>Path</th><th>What</th></tr></thead><tbody><tr><td><code>src/server.ts</code></td><td>the MCP server — the nine tools + the Server Card resource</td></tr><tr><td><code>src/agents-md.ts</code></td><td>reads and section-splits this file</td></tr><tr><td><code>src/author.ts</code></td><td><code>author_agents_md</code> — BETTER via <code>agents-md-facts</code>, BEST when <code>project.faf</code> exists</td></tr><tr><td><code>src/md.ts</code></td><td>a minimal dependency-free Markdown → HTML renderer</td></tr><tr><td><code>src/render-card.ts</code></td><td>the card — identity + this file + memory + discovery, as one HTML page</td></tr><tr><td><code>src/memory.ts</code> → <code>src/faf/parse-fafm.ts</code></td><td>file-backed <code>remember</code> / <code>recall</code> / <code>forget</code></td></tr><tr><td><code>src/identity.ts</code></td><td><code>whoami</code> (<code>.fafa</code> → <code>package.json</code> fallback) + the <code>_meta</code> context block</td></tr><tr><td><code>src/catalog-gen.ts</code></td><td>writes <code>.well-known/ai-catalog.json</code> from the same three sources</td></tr><tr><td><code>src/transport/http.ts</code></td><td>the stateless Streamable HTTP app (Hono)</td></tr><tr><td><code>src/bin.ts</code></td><td>the entry point (<code>resolveLaunch</code>) — <code>stdio</code> · <code>--http</code> · <code>card</code> · <code>--help</code> · <code>--version</code></td></tr><tr><td><code>src/faf/parse-fafm.ts</code> · <code>parse-fafa.ts</code></td><td>the <code>.fafm</code> / <code>.fafa</code> parsers</td></tr></tbody></table></div></details><details class="ctx-section" id="conventions"><summary>Conventions</summary><div class="md"><ul><li>TypeScript strict, ESM only (<code>&quot;type&quot;: &quot;module&quot;</code>, <code>.js</code> import specifiers).</li><li>Tests use <code>node:test</code> + <code>node:assert/strict</code> — no test framework.</li><li>Every source file opens with a comment stating what it is and why.</li><li>Conventional Commit messages (<code>feat:</code>, <code>fix:</code>, <code>chore:</code>, <code>test:</code>, <code>docs:</code>).</li></ul></div></details><details class="ctx-section" id="the-invariant"><summary>The invariant</summary><div class="md"><p><code>src/identity.ts</code>, <code>src/catalog-gen.ts</code>, and <code>src/render-card.ts</code> all describe the <strong>same three sources</strong>: this file, <code>project.fafm</code>, <code>.well-known/fafa</code>. Change what one exposes and you must change the others. <code>npm run catalog:check</code> and <code>npm run card:check</code> enforce it in CI — each regenerates its surface and fails on any diff.</p></div></details><details class="ctx-section" id="safety"><summary>Safety</summary><div class="md"><ul><li>Branch off <code>main</code>; CI must be green before merge.</li><li><code>npm run demo</code> writes a fact to <code>project.fafm</code> and restores the file on exit — don't kill it mid-run.</li><li>No secrets live in this repo; never add any.</li></ul></div></details><details class="ctx-section" id="definition-of-done"><summary>Definition of done</summary><div class="md"><p><code>npm run typecheck &amp;&amp; npm run build &amp;&amp; npm test &amp;&amp; npm run demo</code> all green, plus <code>npm run catalog:check</code> and <code>npm run card:check</code> clean if you touched <code>AGENTS.md</code>, <code>project.fafm</code>, or <code>.well-known/fafa</code>, and <code>npm run faf:check</code> green if you changed the layout, a dependency, or the identity. On a version bump, <code>npm run version:check</code> green (it lists every spot that must move together) and <code>project.faf</code> still Trophy (<code>faf-cli check project.faf --strict</code>). If CI's <code>faf:nudge</code> warns on your PR, reconcile <code>project.faf</code> (and re-check <code>project.fafm</code> facts if <code>AGENTS.md</code> moved) or say why it's fine.</p></div></details><details class="ctx-section" id="authoring-this-file"><summary>Authoring this file</summary><div class="md"><p><code>AGENTS.md</code> here is maintained by hand. The <code>author_agents_md</code> tool (or <code>faf export --agents</code>) would draft a BEST version straight from this repo's own <code>project.faf</code> plus its detected facts — the server doesn't care how the file was authored, only that it's valid Markdown.</p></div></details></div>
117
+ <p>CI runs <code>version:check → faf:check → typecheck → build → test:coverage → demo</code> on Linux, macOS, and Windows for every push and PR to <code>main</code> (<code>.github/workflows/ci.yml</code>), plus <code>catalog:check</code> / <code>card:check</code>, <code>faf-cli check project.faf --strict</code> (the repo dogfoods a <code>project.faf</code> — this keeps it Trophy), and <code>faf:nudge</code> (PR-only, non-blocking — warns if the code's shape moved without <code>project.faf</code>) on Linux.</p></div></details><details class="ctx-section" id="layout"><summary>Layout</summary><div class="md"><table><thead><tr><th>Path</th><th>What</th></tr></thead><tbody><tr><td><code>src/server.ts</code></td><td>the MCP server — the ten tools + the Server Card and card resources</td></tr><tr><td><code>src/agents-md.ts</code></td><td>reads and section-splits this file</td></tr><tr><td><code>src/author.ts</code></td><td><code>author_agents_md</code> — BETTER via <code>agents-md-facts</code>, BEST when <code>project.faf</code> exists</td></tr><tr><td><code>src/md.ts</code></td><td>a minimal dependency-free Markdown → HTML renderer</td></tr><tr><td><code>src/render-card.ts</code></td><td>the card — identity + this file + memory + discovery, as one HTML page</td></tr><tr><td><code>src/memory.ts</code> → <code>src/faf/parse-fafm.ts</code></td><td>file-backed <code>remember</code> / <code>recall</code> / <code>forget</code></td></tr><tr><td><code>src/identity.ts</code></td><td><code>whoami</code> (<code>.fafa</code> → <code>package.json</code> fallback) + the <code>_meta</code> context block</td></tr><tr><td><code>src/catalog-gen.ts</code></td><td>writes <code>.well-known/ai-catalog.json</code> from the same three sources</td></tr><tr><td><code>src/transport/http.ts</code></td><td>the stateless Streamable HTTP app (Hono)</td></tr><tr><td><code>src/bin.ts</code></td><td>the entry point (<code>resolveLaunch</code>) — <code>stdio</code> · <code>--http</code> · <code>card</code> · <code>--help</code> · <code>--version</code></td></tr><tr><td><code>src/faf/parse-fafm.ts</code> · <code>parse-fafa.ts</code></td><td>the <code>.fafm</code> / <code>.fafa</code> parsers</td></tr></tbody></table></div></details><details class="ctx-section" id="conventions"><summary>Conventions</summary><div class="md"><ul><li>TypeScript strict, ESM only (<code>&quot;type&quot;: &quot;module&quot;</code>, <code>.js</code> import specifiers).</li><li>Tests use <code>node:test</code> + <code>node:assert/strict</code> — no test framework.</li><li>Every source file opens with a comment stating what it is and why.</li><li>Conventional Commit messages (<code>feat:</code>, <code>fix:</code>, <code>chore:</code>, <code>test:</code>, <code>docs:</code>).</li></ul></div></details><details class="ctx-section" id="the-invariant"><summary>The invariant</summary><div class="md"><p><code>src/identity.ts</code>, <code>src/catalog-gen.ts</code>, and <code>src/render-card.ts</code> all describe the <strong>same three sources</strong>: this file, <code>project.fafm</code>, <code>.well-known/fafa</code>. Change what one exposes and you must change the others. <code>npm run catalog:check</code> and <code>npm run card:check</code> enforce it in CI — each regenerates its surface and fails on any diff.</p></div></details><details class="ctx-section" id="safety"><summary>Safety</summary><div class="md"><ul><li>Branch off <code>main</code>; CI must be green before merge.</li><li><code>npm run demo</code> writes a fact to <code>project.fafm</code> and restores the file on exit — don't kill it mid-run.</li><li>No secrets live in this repo; never add any.</li></ul></div></details><details class="ctx-section" id="definition-of-done"><summary>Definition of done</summary><div class="md"><p><code>npm run typecheck &amp;&amp; npm run build &amp;&amp; npm test &amp;&amp; npm run demo</code> all green, plus <code>npm run catalog:check</code> and <code>npm run card:check</code> clean if you touched <code>AGENTS.md</code>, <code>project.fafm</code>, or <code>.well-known/fafa</code>, and <code>npm run faf:check</code> green if you changed the layout, a dependency, or the identity. On a version bump, <code>npm run version:check</code> green (it lists every spot that must move together) and <code>project.faf</code> still Trophy (<code>faf-cli check project.faf --strict</code>). If CI's <code>faf:nudge</code> warns on your PR, reconcile <code>project.faf</code> (and re-check <code>project.fafm</code> facts if <code>AGENTS.md</code> moved) or say why it's fine.</p></div></details><details class="ctx-section" id="authoring-this-file"><summary>Authoring this file</summary><div class="md"><p><code>AGENTS.md</code> here is maintained by hand. The <code>author_agents_md</code> tool (or <code>faf export --agents</code>) would draft a BEST version straight from this repo's own <code>project.faf</code> plus its detected facts — the server doesn't care how the file was authored, only that it's valid Markdown.</p></div></details></div>
118
118
  </section>
119
119
  <section>
120
120
  <p class="label">Memory — 4 facts</p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-context-card",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
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/project.faf CHANGED
@@ -20,7 +20,7 @@ stack:
20
20
  tech_stack: [TypeScript, "@modelcontextprotocol/sdk", "agents-md-facts", hono, "@hono/node-server", yaml]
21
21
  human_context:
22
22
  who: MCP host and server implementers who want a project's AGENTS.md, memory, and identity available over MCP without inventing an ad-hoc shape for each.
23
- what: The essential context, memory, and identity components for MCP — usable as a base MCP on its own, or dropped into any existing MCP server as an extension. Nine tools — read_agents_md / list_agents_md_sections / author_agents_md (context), remember / recall / forget (memory), whoami (identity), list_context_sources / render_context_card (discovery) — exposed through the Server Card _meta block and a self-published ai-catalog.json. Dual transport (stdio + stateless Streamable HTTP).
23
+ what: The essential context, memory, and identity components for MCP — usable as a base MCP on its own, or dropped into any existing MCP server as an extension. Ten tools — read_agents_md / list_agents_md_sections / author_agents_md (context), remember / recall / forget (memory), whoami (identity), list_context_sources / render_context_card / save_context_card (discovery) — exposed through the Server Card _meta block and a self-published ai-catalog.json. Dual transport (stdio + stateless Streamable HTTP).
24
24
  why: AGENTS.md is the de-facto standard for agent instructions, but a client has to know the file exists and read it whole. There is no standard way for a server to publish "here is my AGENTS.md, here is what I remember, here is who I am" — every server that wants this grows its own shape, or does without. This does it once, through mechanisms that already exist — as a base MCP, or dropped into what you've already built.
25
25
  where: github.com/Wolfe-Jam/mcp-context-card
26
26
  when: "2026-08-12"
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.1.1",
6
+ "version": "1.2.0",
7
7
  "repository": {
8
8
  "url": "https://github.com/Wolfe-Jam/mcp-context-card",
9
9
  "source": "github"
@@ -13,7 +13,7 @@
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "mcp-context-card",
16
- "version": "1.1.1",
16
+ "version": "1.2.0",
17
17
  "runtimeHint": "npx",
18
18
  "transport": {
19
19
  "type": "stdio"