mcp-context-card 1.1.0 → 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.0"
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
@@ -15,7 +15,7 @@ connection.
15
15
  npm ci
16
16
  ```
17
17
 
18
- Node 22 or newer. No other system dependencies.
18
+ Node 20 or newer. No other system dependencies.
19
19
 
20
20
  ## Build
21
21
 
@@ -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,66 @@
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
+
42
+ ## 1.1.1
43
+
44
+ Every tool now says what it is and what it does to your project.
45
+
46
+ Each of the nine tools carries a human-readable `title` and MCP tool
47
+ annotations, so a host can label it properly and decide whether to ask
48
+ before running it:
49
+
50
+ - **Read-only (7):** `read_agents_md`, `list_agents_md_sections`,
51
+ `author_agents_md` (returns a draft, writes nothing), `recall`, `whoami`,
52
+ `list_context_sources`, `render_context_card`.
53
+ - **Writes the memory file (2):** `remember` and `forget` are marked
54
+ `destructiveHint: true`. `remember` replaces a fact when an id is reused;
55
+ `forget` removes one.
56
+ - All nine are `idempotentHint: true` (repeating a call changes nothing more)
57
+ and `openWorldHint: false` (they only touch the local project).
58
+
59
+ A new test checks every tool's title and hints against what it actually does,
60
+ so a tool added later can't ship without them. 106 tests, all green on
61
+ Linux, macOS, and Windows.
62
+
63
+ No API change to the nine tools: same names, same inputs, same behaviour.
64
+
5
65
  ## 1.1.0
6
66
 
7
67
  The card scans in one screen — AGENTS.md sections collapse by default.
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,11 +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 |
216
+
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
220
+ title and MCP tool annotations.
195
221
 
196
222
  ## The demo
197
223
 
198
- `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:
199
226
 
200
227
  1. **Context** — list the `AGENTS.md` sections, then pull just `## Test`.
201
228
  2. **Memory** — `remember()` a fact, stop the server process, start a new one,
@@ -205,16 +232,17 @@ The wire‑level detail is in [docs/MECHANISMS.md](./docs/MECHANISMS.md).
205
232
  4. **Discovery** — `list_context_sources()`, then the same server over stateless
206
233
  HTTP with its `.well-known` routes and `GET /card`.
207
234
 
208
- 104 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
209
236
  child process and check a remembered fact survives the restart — one against
210
237
  an existing `project.fafm`, one starting from a project that has never had
211
- one; another checks the stdio and HTTP tool surfaces match.
238
+ one; another checks the stdio and HTTP tool surfaces match, and another checks
239
+ every tool's title and behaviour hints against what it actually does.
212
240
 
213
241
  ## Layout
214
242
 
215
243
  | Path | What |
216
244
  |---|---|
217
- | `src/server.ts` | the nine tools + the Server Card resource |
245
+ | `src/server.ts` | the ten tools + the Server Card and card resources |
218
246
  | `src/agents-md.ts` | reads and section‑splits `AGENTS.md` |
219
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 |
220
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.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";
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.0";
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.0";
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
  }
@@ -82,6 +127,8 @@ export function createServer(root = ROOT) {
82
127
  tools: [
83
128
  {
84
129
  name: "read_agents_md",
130
+ title: "Read AGENTS.md",
131
+ annotations: { title: "Read AGENTS.md", readOnlyHint: true, idempotentHint: true, openWorldHint: false },
85
132
  description: "Return this project's AGENTS.md — the whole file, or one section by heading. The instructions a client would otherwise have to know to look for and read wholesale.",
86
133
  inputSchema: {
87
134
  type: "object",
@@ -95,16 +142,22 @@ export function createServer(root = ROOT) {
95
142
  },
96
143
  {
97
144
  name: "author_agents_md",
145
+ title: "Draft an AGENTS.md",
146
+ annotations: { title: "Draft an AGENTS.md", readOnlyHint: true, idempotentHint: true, openWorldHint: false },
98
147
  description: "Author an AGENTS.md for this project and return the draft — BETTER from repo facts alone (via agents-md-facts: real build/test commands, entry points, toolchain conventions, nothing invented), or BEST when a project.faf exists (facts plus its structured goal/who/why as a second managed block ahead of them). Does not write a file.",
99
148
  inputSchema: { type: "object", properties: {} },
100
149
  },
101
150
  {
102
151
  name: "list_agents_md_sections",
152
+ title: "List AGENTS.md Sections",
153
+ annotations: { title: "List AGENTS.md Sections", readOnlyHint: true, idempotentHint: true, openWorldHint: false },
103
154
  description: "List the headings in this project's AGENTS.md, so a client can pull one section instead of spending context on the whole file.",
104
155
  inputSchema: { type: "object", properties: {} },
105
156
  },
106
157
  {
107
158
  name: "remember",
159
+ title: "Remember a Fact",
160
+ annotations: { title: "Remember a Fact", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
108
161
  description: "Persist a fact past the session boundary — written to a .fafm file, not held in memory. Reusing an existing id replaces that fact's text in place (no duplicate); a new id appends. Facts are written verification_status: unverified.",
109
162
  inputSchema: {
110
163
  type: "object",
@@ -123,6 +176,8 @@ export function createServer(root = ROOT) {
123
176
  },
124
177
  {
125
178
  name: "recall",
179
+ title: "Recall a Fact",
180
+ annotations: { title: "Recall a Fact", readOnlyHint: true, idempotentHint: true, openWorldHint: false },
126
181
  description: "Retrieve a fact stored in a previous session by id. Exact lookup — not fuzzy or substring.",
127
182
  inputSchema: {
128
183
  type: "object",
@@ -137,6 +192,8 @@ export function createServer(root = ROOT) {
137
192
  },
138
193
  {
139
194
  name: "forget",
195
+ title: "Forget a Fact",
196
+ annotations: { title: "Forget a Fact", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
140
197
  description: "Remove a fact by id — to correct or drop something stale. A missing id is reported, not an error.",
141
198
  inputSchema: {
142
199
  type: "object",
@@ -151,23 +208,46 @@ export function createServer(root = ROOT) {
151
208
  },
152
209
  {
153
210
  name: "whoami",
211
+ title: "Who Am I",
212
+ annotations: { title: "Who Am I", readOnlyHint: true, idempotentHint: true, openWorldHint: false },
154
213
  description: "This server's own identity — name, vendor, version, status, license — from its .fafa card.",
155
214
  inputSchema: { type: "object", properties: {} },
156
215
  },
157
216
  {
158
217
  name: "list_context_sources",
218
+ title: "List Context Sources",
219
+ annotations: { title: "List Context Sources", readOnlyHint: true, idempotentHint: true, openWorldHint: false },
159
220
  description: "What context does this project publish (AGENTS.md, memory, identity), in what media types, and through which discovery surface. For a client connecting cold.",
160
221
  inputSchema: { type: "object", properties: {} },
161
222
  },
162
223
  {
163
224
  name: "render_context_card",
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 },
229
+ annotations: { title: "Render Context Card", readOnlyHint: true, idempotentHint: true, openWorldHint: false },
164
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.",
165
238
  inputSchema: {
166
239
  type: "object",
167
240
  properties: {
168
- theme: { type: "string", enum: ["light", "dark", "auto"], description: "default: auto" },
169
- accent: { type: "string", description: "CSS hex colour, e.g. #FF702D (default: the AAIF palette)" },
170
- 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
+ },
171
251
  },
172
252
  },
173
253
  },
@@ -217,19 +297,33 @@ export function createServer(root = ROOT) {
217
297
  case "whoami":
218
298
  return text(whoami(root));
219
299
  case "render_context_card": {
220
- const rawExpanded = args.expanded;
221
- return {
222
- content: [
223
- {
224
- type: "text",
225
- text: renderCard(root, {
226
- theme: (["light", "dark", "auto"].includes(args.theme) ? args.theme : "auto"),
227
- accent: safeAccent(args.accent),
228
- expanded: rawExpanded === true || rawExpanded === "true",
229
- }),
230
- },
231
- ],
232
- };
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}`);
233
327
  }
234
328
  case "list_context_sources": {
235
329
  const doc = parseAgentsMd(AGENTS);
@@ -253,7 +347,10 @@ export function createServer(root = ROOT) {
253
347
  present: identity(root) !== null,
254
348
  },
255
349
  surfaces: {
256
- 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
+ },
257
354
  http: {
258
355
  serverCard: "GET /.well-known/mcp/server-card",
259
356
  aiCatalog: "GET /.well-known/ai-catalog.json",
@@ -269,13 +366,14 @@ export function createServer(root = ROOT) {
269
366
  return server;
270
367
  }
271
368
  /** Connect a server instance to a transport (stdio or http). */
272
- export async function serve(transport, root = ROOT) {
273
- const server = createServer(root);
369
+ export async function serve(transport, root = ROOT, opts = {}) {
370
+ const server = createServer(root, opts);
274
371
  await server.connect(transport);
275
372
  return server;
276
373
  }
277
374
  // Direct run (incl. the demo's spawned child) → stdio. pathToFileURL keeps
278
375
  // this correct on Windows, where argv[1] is a `C:\...` path.
279
376
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
280
- await serve(new StdioServerTransport());
377
+ const { openInBrowser } = await import("./open.js");
378
+ await serve(new StdioServerTransport(), ROOT, { openFile: openInBrowser });
281
379
  }
@@ -29,7 +29,7 @@ Both return:
29
29
  ```jsonc
30
30
  {
31
31
  "name": "mcp-context-card",
32
- "version": "1.1.0",
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.0</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>
@@ -108,13 +108,13 @@ details.ctx-section>.md{padding:0 0 16px}
108
108
  <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>
109
109
  <p><code>read_agents_md</code> serves this file, section by section, over the same MCP connection.</p></div>
110
110
  <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>
111
- <p>Node 22 or newer. No other system dependencies.</p></div></details><details class="ctx-section" id="build"><summary>Build</summary><div class="md"><pre><code class="language-bash">npm run build # tsc -p tsconfig.build.json → dist/
111
+ <p>Node 20 or newer. No other system dependencies.</p></div></details><details class="ctx-section" id="build"><summary>Build</summary><div class="md"><pre><code class="language-bash">npm run build # tsc -p tsconfig.build.json → dist/
112
112
  npm run typecheck # tsc --noEmit over src/ + test/</code></pre></div></details><details class="ctx-section" id="test"><summary>Test</summary><div class="md"><pre><code class="language-bash">npm test # node:test — every test/*.test.ts
113
113
  npm run test:coverage # + the coverage gate (lines 90 / funcs 85 / branches 80, src/ only)
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.0</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>
@@ -108,13 +108,13 @@ details.ctx-section>.md{padding:0 0 16px}
108
108
  <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>
109
109
  <p><code>read_agents_md</code> serves this file, section by section, over the same MCP connection.</p></div>
110
110
  <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>
111
- <p>Node 22 or newer. No other system dependencies.</p></div></details><details class="ctx-section" id="build"><summary>Build</summary><div class="md"><pre><code class="language-bash">npm run build # tsc -p tsconfig.build.json → dist/
111
+ <p>Node 20 or newer. No other system dependencies.</p></div></details><details class="ctx-section" id="build"><summary>Build</summary><div class="md"><pre><code class="language-bash">npm run build # tsc -p tsconfig.build.json → dist/
112
112
  npm run typecheck # tsc --noEmit over src/ + test/</code></pre></div></details><details class="ctx-section" id="test"><summary>Test</summary><div class="md"><pre><code class="language-bash">npm test # node:test — every test/*.test.ts
113
113
  npm run test:coverage # + the coverage gate (lines 90 / funcs 85 / branches 80, src/ only)
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.0</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>
@@ -108,13 +108,13 @@ details.ctx-section>.md{padding:0 0 16px}
108
108
  <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>
109
109
  <p><code>read_agents_md</code> serves this file, section by section, over the same MCP connection.</p></div>
110
110
  <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>
111
- <p>Node 22 or newer. No other system dependencies.</p></div></details><details class="ctx-section" id="build"><summary>Build</summary><div class="md"><pre><code class="language-bash">npm run build # tsc -p tsconfig.build.json → dist/
111
+ <p>Node 20 or newer. No other system dependencies.</p></div></details><details class="ctx-section" id="build"><summary>Build</summary><div class="md"><pre><code class="language-bash">npm run build # tsc -p tsconfig.build.json → dist/
112
112
  npm run typecheck # tsc --noEmit over src/ + test/</code></pre></div></details><details class="ctx-section" id="test"><summary>Test</summary><div class="md"><pre><code class="language-bash">npm test # node:test — every test/*.test.ts
113
113
  npm run test:coverage # + the coverage gate (lines 90 / funcs 85 / branches 80, src/ only)
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.0</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>
@@ -108,13 +108,13 @@ details.ctx-section>.md{padding:0 0 16px}
108
108
  <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>
109
109
  <p><code>read_agents_md</code> serves this file, section by section, over the same MCP connection.</p></div>
110
110
  <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>
111
- <p>Node 22 or newer. No other system dependencies.</p></div></details><details class="ctx-section" id="build"><summary>Build</summary><div class="md"><pre><code class="language-bash">npm run build # tsc -p tsconfig.build.json → dist/
111
+ <p>Node 20 or newer. No other system dependencies.</p></div></details><details class="ctx-section" id="build"><summary>Build</summary><div class="md"><pre><code class="language-bash">npm run build # tsc -p tsconfig.build.json → dist/
112
112
  npm run typecheck # tsc --noEmit over src/ + test/</code></pre></div></details><details class="ctx-section" id="test"><summary>Test</summary><div class="md"><pre><code class="language-bash">npm test # node:test — every test/*.test.ts
113
113
  npm run test:coverage # + the coverage gate (lines 90 / funcs 85 / branches 80, src/ only)
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.0",
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.0",
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.0",
16
+ "version": "1.2.0",
17
17
  "runtimeHint": "npx",
18
18
  "transport": {
19
19
  "type": "stdio"