mcp-context-card 0.5.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.
@@ -0,0 +1,137 @@
1
+ # Mechanisms
2
+
3
+ `mcp-context-card` exposes three concerns — context, memory, identity — through two
4
+ mechanisms that already exist in the MCP ecosystem. This is the wire‑level
5
+ detail.
6
+
7
+ The **context** concern points at `AGENTS.md` (`text/markdown`). Memory and
8
+ identity have no de‑facto standard, so the reference points them at `.fafm` and
9
+ `.fafa`. Everything below is about the *shape* — swap the artifacts and the
10
+ mechanism is unchanged.
11
+
12
+ ---
13
+
14
+ ## Mechanism 1 — the Server Card `_meta` block
15
+
16
+ A **Server Card**
17
+ ([SEP‑2127](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127))
18
+ describes a server. It is *not* a field on the `initialize` result — the SDK
19
+ client keeps only `serverInfo` / `capabilities` / `instructions` and discards a
20
+ top‑level `_meta`. So the card is served the two ways a client can consume it:
21
+
22
+ ```
23
+ resources/read mcp-context-card://server-card # in band
24
+ GET /.well-known/mcp/server-card # out of band (http transport)
25
+ ```
26
+
27
+ Both return:
28
+
29
+ ```jsonc
30
+ {
31
+ "name": "mcp-context-card",
32
+ "version": "0.2.0",
33
+ "_meta": {
34
+ "io.github.wolfe-jam.mcp-context-card/context": {
35
+ "source": "AGENTS.md",
36
+ "mediaType": "text/markdown"
37
+ },
38
+ "io.github.wolfe-jam.mcp-context-card/memory": {
39
+ "source": "project.fafm",
40
+ "mediaType": "application/vnd.fafm+yaml",
41
+ "iana": "https://www.iana.org/assignments/media-types/application/vnd.fafm+yaml",
42
+ "note": "no de-facto standard for agent memory yet — one instantiation"
43
+ },
44
+ "io.github.wolfe-jam.mcp-context-card/identity": {
45
+ "source": ".well-known/fafa",
46
+ "mediaType": "application/vnd.fafa+yaml",
47
+ "iana": "https://www.iana.org/assignments/media-types/application/vnd.fafa+yaml"
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ **Why it looks like this:**
54
+
55
+ - **`_meta` is the extension point.** SEP‑2127 defines it as
56
+ `additionalProperties: {}`. A consumer that doesn't know a key ignores it.
57
+ - **Keys are reverse‑DNS‑namespaced to the publisher**
58
+ (`io.github.wolfe-jam.mcp-context-card/context`, not `context`). No collisions, and
59
+ the key's owner is unambiguous. Use a domain or GitHub identity you control.
60
+ - **One key per concern**, each self‑describing: the source file, its media
61
+ type, and — where the media type is IANA‑registered — the anchor. `context`
62
+ carries no `iana` field because `text/markdown` needs none.
63
+ - **`note` is honest.** There is no de‑facto memory format the way `AGENTS.md`
64
+ is for instructions; the block says so rather than implying `.fafm` is a
65
+ standard.
66
+
67
+ Built by `trinityMeta()` in [`src/identity.ts`](../src/identity.ts).
68
+
69
+ ---
70
+
71
+ ## Mechanism 2 — `ai-catalog.json` sibling entries
72
+
73
+ [ai-catalog](https://github.com/Agent-Card/ai-catalog) is a discovery format: a
74
+ publisher lists artifacts, each entry keyed by its **media type** (`type`).
75
+ `mcp-context-card` publishes one entry per concern.
76
+
77
+ ```
78
+ GET /.well-known/ai-catalog.json
79
+ ```
80
+
81
+ ```jsonc
82
+ {
83
+ "specVersion": "1.0",
84
+ "host": { "displayName": "mcp-context-card", "identifier": "https://github.com/Wolfe-Jam/mcp-context-card" },
85
+ "entries": [
86
+ {
87
+ "identifier": "urn:air:mcp-context-card:context",
88
+ "type": "text/markdown",
89
+ "mediaType": "text/markdown",
90
+ "description": "…derived from the real AGENTS.md — section count + headings…",
91
+ "url": "./AGENTS.md"
92
+ },
93
+ { "identifier": "urn:air:mcp-context-card:memory", "type": "application/vnd.fafm+yaml", "…": "…" },
94
+ { "identifier": "urn:air:mcp-context-card:identity", "type": "application/vnd.fafa+yaml", "…": "…" }
95
+ ]
96
+ }
97
+ ```
98
+
99
+ **Why it looks like this:**
100
+
101
+ - **`type` is the routing key.** A consumer scanning catalogs for
102
+ `text/markdown` context, or `application/vnd.fafm+yaml` memory, finds the
103
+ entry without knowing this publisher.
104
+ - **`identifier` is a `urn:air:` URN** scoped to the publisher
105
+ (`urn:air:<host>:<concern>`). In ai-catalog's
106
+ [trust‑manifest ADRs](https://github.com/Agent-Card/ai-catalog/tree/main/adr),
107
+ `urn:air` identifiers carry a publisher‑domain‑aligned trust manifest — the
108
+ entries here align to `github.com/Wolfe-Jam/mcp-context-card`.
109
+ - **`description` is derived from real content** — the live AGENTS.md heading
110
+ list, the current fact count, the agent's own description — not a blurb that
111
+ drifts. See `buildCatalog()` in [`src/catalog-gen.ts`](../src/catalog-gen.ts).
112
+ - **`url` is relative.** Served over HTTP it resolves against the origin; in a
113
+ repo browser, against the tree.
114
+
115
+ ---
116
+
117
+ ## The invariant
118
+
119
+ The same three sources also render as **the card** — `GET /card` /
120
+ `render_context_card` / `docs/card.html` — the human view of exactly what a
121
+ machine reads below.
122
+
123
+ The same three sources back **both** discovery mechanisms:
124
+
125
+ ```
126
+ AGENTS.md ─┐
127
+ project.fafm ─┼─→ Server Card _meta (context · memory · identity)
128
+ .well-known/ ─┘ └─→ ai-catalog.json (3 sibling entries, keyed by media type)
129
+ fafa
130
+ ```
131
+
132
+ `catalog-gen.ts` reads exactly the files `trinityMeta()` names. The CI job
133
+ `npm run catalog:check` regenerates `ai-catalog.json` and fails on any drift —
134
+ change a source, both surfaces move together.
135
+
136
+ **Describe the artifacts once; expose them through whatever mechanism the
137
+ consumer speaks.**
@@ -0,0 +1,86 @@
1
+ # Transport
2
+
3
+ `mcp-context-card` runs the same server over two transports. The tool surface,
4
+ the Server Card `_meta` block, and the memory file are identical either way
5
+ — only the wire changes.
6
+
7
+ ```
8
+ mcp-context-card → stdio (default; an MCP host spawns this)
9
+ mcp-context-card --http → Streamable HTTP :3000
10
+ PORT=8080 mcp-context-card → Streamable HTTP :8080 (a hosted deploy sets PORT)
11
+ mcp-context-card --stdio → force stdio even when PORT is set
12
+ ```
13
+
14
+ ## stdio
15
+
16
+ `StdioServerTransport` — one process, one client, JSON-RPC over stdin/stdout.
17
+ This is what Claude Desktop, Cursor, and `npx`-style hosts use. `stdout` is
18
+ the wire, so all logging goes to `stderr`.
19
+
20
+ ```jsonc
21
+ // claude_desktop_config.json
22
+ {
23
+ "mcpServers": {
24
+ "context-card": { "command": "npx", "args": ["-y", "mcp-context-card"] }
25
+ }
26
+ }
27
+ ```
28
+
29
+ ## Streamable HTTP — stateless
30
+
31
+ `POST /mcp` is the MCP endpoint. It runs **stateless**:
32
+
33
+ - `sessionIdGenerator: undefined` — no session IDs issued, no session
34
+ validation, no `Mcp-Session-Id` header.
35
+ - `enableJsonResponse: true` — every response is a complete JSON body. No
36
+ SSE stream is opened, so there is nothing to hold open and nothing to
37
+ leak.
38
+ - A **fresh `Server` + transport per request**. Two concurrent requests
39
+ never share state or collide on JSON-RPC ids.
40
+
41
+ ```
42
+ GET / → index (endpoints)
43
+ POST /mcp → MCP (initialize, tools/list, tools/call, …)
44
+ GET /.well-known/mcp/server-card → the Server Card + _meta trinity block
45
+ GET /.well-known/ai-catalog.json → the three sibling entries
46
+ GET /.well-known/fafa → the agent identity card
47
+ ```
48
+
49
+ ```jsonc
50
+ {
51
+ "mcpServers": {
52
+ "context-card": { "url": "https://your-host.example/mcp" }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### Why stateless
58
+
59
+ The default should be the one that scales and can't rot. Stateless
60
+ Streamable HTTP:
61
+
62
+ - **scales horizontally** — any replica can serve any request; no sticky
63
+ sessions, no shared session store.
64
+ - **has no per-connection state** to grow unbounded or leak on a dropped
65
+ client.
66
+ - **is trivial to reason about** — request in, response out.
67
+
68
+ ### When you'd want stateful instead
69
+
70
+ Set a `sessionIdGenerator` and keep transports in a `Map<sessionId, …>`
71
+ when the server needs to:
72
+
73
+ - **push** server-initiated notifications to a specific client mid-session
74
+ (`notifications/*` over a held-open SSE stream), or
75
+ - support **resumability** — a client reconnecting with `Last-Event-ID` to
76
+ replay missed events (needs an `EventStore`).
77
+
78
+ `mcp-context-card` needs neither: its tools are request/response, and its
79
+ "memory" is a file on disk, not a live subscription. A fork that adds
80
+ streaming tools would flip this. See `src/transport/http.ts`.
81
+
82
+ ### DNS-rebinding protection
83
+
84
+ Off by default (a reference server should run anywhere with no config). For
85
+ a real deployment, pass `allowedHosts` / `allowedOrigins` to the transport
86
+ and set `enableDnsRebindingProtection: true`.
package/docs/WIRING.md ADDED
@@ -0,0 +1,97 @@
1
+ # Wiring
2
+
3
+ 1. [Running it in a host](#1-running-it-in-a-host)
4
+ 2. [The tools in practice](#2-the-tools-in-practice)
5
+ 3. [Adapting it for your own artifacts](#3-adapting-it)
6
+
7
+ ---
8
+
9
+ ## 1. Running it in a host
10
+
11
+ ### stdio (local)
12
+
13
+ ```jsonc
14
+ // claude_desktop_config.json · ~/.cursor/mcp.json · etc.
15
+ {
16
+ "mcpServers": {
17
+ "context-card": {
18
+ "command": "npx",
19
+ "args": ["-y", "mcp-context-card"],
20
+ "env": { "MCP_CONTEXT_CARD_ROOT": "/abs/path/to/your/project" }
21
+ }
22
+ }
23
+ }
24
+ ```
25
+
26
+ - **`MCP_CONTEXT_CARD_ROOT`** — directory holding `AGENTS.md`, `project.fafm`, and
27
+ `.well-known/fafa`. Omit it and the server uses its own bundled copies.
28
+ - `stdout` is the JSON‑RPC wire; logging is on `stderr`.
29
+
30
+ ### Streamable HTTP (remote)
31
+
32
+ ```bash
33
+ PORT=8080 npx mcp-context-card # or: npx mcp-context-card --http
34
+ ```
35
+
36
+ ```jsonc
37
+ { "mcpServers": { "context-card": { "url": "https://your-host.example/mcp" } } }
38
+ ```
39
+
40
+ Stateless — any replica serves any request, no session store. Rationale in
41
+ [TRANSPORT.md](./TRANSPORT.md).
42
+
43
+ ---
44
+
45
+ ## 2. The tools in practice
46
+
47
+ A client that just connected wants the project's conventions — but not the whole
48
+ `AGENTS.md` in its context window:
49
+
50
+ ```ts
51
+ // what's documented?
52
+ await client.callTool({ name: "list_agents_md_sections", arguments: {} });
53
+ // → [{ "heading": "Setup", "level": 2 }, { "heading": "Test", "level": 2 }, …]
54
+
55
+ // pull just the one it needs
56
+ await client.callTool({ name: "read_agents_md", arguments: { section: "Test" } });
57
+ // → "## Test\n\n```bash\nnpm test\n```\n…"
58
+ ```
59
+
60
+ Between sessions, carry a fact forward:
61
+
62
+ ```ts
63
+ await client.callTool({ name: "remember", arguments: { id: "db-migration", text: "run `npm run migrate` before tests since #412" } });
64
+ // next session, different process:
65
+ await client.callTool({ name: "recall", arguments: { id: "db-migration" } });
66
+ // → "run `npm run migrate` before tests since #412"
67
+ ```
68
+
69
+ And to discover what a server offers before committing to it:
70
+
71
+ ```ts
72
+ await client.callTool({ name: "list_context_sources", arguments: {} });
73
+ // → { context: { source: "AGENTS.md", mediaType: "text/markdown", present: true, sections: 9 },
74
+ // memory: { … }, identity: { … },
75
+ // surfaces: { serverCard: [...], aiCatalog: [...] } }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 3. Adapting it
81
+
82
+ To serve *your* artifacts:
83
+
84
+ 1. **Replace the three files** — `AGENTS.md`, `project.fafm`, `.well-known/fafa`
85
+ — with your own, or point `MCP_CONTEXT_CARD_ROOT` at a directory that has them.
86
+ `AGENTS.md` is the one with a real standard; the other two are swappable.
87
+ 2. **Rename the namespace.** `trinityMeta()` in `src/identity.ts` uses
88
+ `io.github.wolfe-jam.mcp-context-card/*` keys, and `buildCatalog()` in
89
+ `src/catalog-gen.ts` uses `urn:air:mcp-context-card:*` identifiers. Change both to
90
+ a domain or GitHub identity you control ([MECHANISMS.md](./MECHANISMS.md)).
91
+ 3. **Swap the media types** in `trinityMeta()` if your memory / identity
92
+ artifacts aren't `.fafm` / `.fafa`. Drop the `iana` field for any that isn't
93
+ a registered type.
94
+ 4. `npm run catalog` to regenerate, `npm run demo` to confirm all three still
95
+ round‑trip, `npm test` for the suite.
96
+
97
+ ---
package/docs/card.html ADDED
@@ -0,0 +1,121 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>mcp-context-card — context card</title>
7
+ <style>
8
+ :root{
9
+ --accent:#FF702D;
10
+ --bg:#f4f4f5; --card:#fff; --fg:#0a0a0a; --muted:#6b6b70;
11
+ --line:rgba(0,0,0,.09); --chip:rgba(0,0,0,.05);
12
+ }
13
+ :root[data-theme="dark"]{
14
+ --bg:#000; --card:#0d0d0d; --fg:#fafafa; --muted:#9a9aa0;
15
+ --line:rgba(255,255,255,.13); --chip:rgba(255,255,255,.07);
16
+ }
17
+ @media (prefers-color-scheme:dark){
18
+ :root:not([data-theme="light"]){
19
+ --bg:#000; --card:#0d0d0d; --fg:#fafafa; --muted:#9a9aa0;
20
+ --line:rgba(255,255,255,.13); --chip:rgba(255,255,255,.07);
21
+ }
22
+ }
23
+ *{box-sizing:border-box}
24
+ body{margin:0;background:var(--bg);color:var(--fg);
25
+ font:15px/1.6 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
26
+ padding:40px 18px}
27
+ .card{max-width:760px;margin:0 auto;background:var(--card);border:1px solid var(--line);
28
+ border-radius:14px;overflow:hidden}
29
+ .card>*{padding:26px 30px}
30
+ .top{border-top:4px solid var(--accent);border-bottom:1px solid var(--line)}
31
+ h1{margin:0 0 10px;font-size:1.7rem;letter-spacing:-.02em}
32
+ .pills{display:flex;flex-wrap:wrap;gap:6px}
33
+ .pill{font-size:.74rem;font-weight:600;padding:3px 9px;border-radius:20px;background:var(--chip);color:var(--muted)}
34
+ .pill.accent{background:color-mix(in srgb,var(--accent) 16%,transparent);color:var(--accent)}
35
+ section{border-bottom:1px solid var(--line)}
36
+ section:last-child{border-bottom:0}
37
+ .label{font-size:.7rem;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
38
+ color:var(--accent);margin:0 0 14px}
39
+ .toc{display:flex;flex-wrap:wrap;gap:6px 14px;margin:0 0 20px;padding:0;list-style:none}
40
+ .toc a{font-size:.82rem;color:var(--muted);text-decoration:none}
41
+ .toc a:hover{color:var(--accent)}
42
+ .md h1,.md h2,.md h3,.md h4{margin:22px 0 8px;font-size:1rem;letter-spacing:-.01em}
43
+ .md h1{font-size:1.15rem}
44
+ .md p{margin:8px 0}
45
+ .md ul,.md ol{margin:8px 0;padding-left:22px}
46
+ .md li{margin:3px 0}
47
+ .md code{background:var(--chip);padding:1px 5px;border-radius:5px;
48
+ font:.86em ui-monospace,SFMono-Regular,Menlo,monospace}
49
+ .md pre{background:var(--chip);padding:14px 16px;border-radius:9px;overflow:auto}
50
+ .md pre code{background:none;padding:0}
51
+ .md table{border-collapse:collapse;width:100%;margin:12px 0;font-size:.88rem;display:block;overflow:auto}
52
+ .md th,.md td{border:1px solid var(--line);padding:6px 10px;text-align:left}
53
+ .md blockquote{margin:10px 0;padding-left:14px;border-left:3px solid var(--line);color:var(--muted)}
54
+ .md a{color:var(--accent)}
55
+ .fact{padding:12px 0;border-bottom:1px solid var(--line)}
56
+ .fact:last-child{border-bottom:0}
57
+ .fact p{margin:0 0 7px}
58
+ .meta{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
59
+ .tag{font-size:.72rem;padding:2px 8px;border-radius:5px;background:var(--chip);color:var(--muted)}
60
+ .dot{width:7px;height:7px;border-radius:50%;background:var(--accent);display:inline-block}
61
+ .dot.pending{background:var(--muted)}
62
+ .disc{width:100%;border-collapse:collapse;font-size:.84rem}
63
+ .disc th,.disc td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--line)}
64
+ .disc th{color:var(--muted);font-weight:600}
65
+ .disc code{font:.86em ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--muted)}
66
+ .fetch{margin:14px 0 0;font-size:.82rem;color:var(--muted)}
67
+ .fetch code{background:var(--chip);padding:1px 5px;border-radius:5px}
68
+ .foot{color:var(--muted);font-size:.78rem;text-align:center;border-top:1px solid var(--line)}
69
+ .none{color:var(--muted);font-style:italic}
70
+ </style>
71
+ </head>
72
+ <body>
73
+ <main class="card">
74
+ <div class="top">
75
+ <h1>mcp-context-card</h1>
76
+ <div class="pills"><span class="pill">v0.5.0</span><span class="pill accent">reference</span><span class="pill">MIT</span></div>
77
+ </div>
78
+ <section>
79
+ <p class="label">Context — AGENTS.md</p>
80
+ <ul class="toc"><li><a href="#setup">Setup</a></li><li><a href="#build">Build</a></li><li><a href="#test">Test</a></li><li><a href="#layout">Layout</a></li><li><a href="#conventions">Conventions</a></li><li><a href="#the-invariant">The invariant</a></li><li><a href="#safety">Safety</a></li><li><a href="#definition-of-done">Definition of done</a></li><li><a href="#authoring-this-file">Authoring this file</a></li></ul><div class="md"><p><code>mcp-context-card</code> is an MCP server that makes a project's <strong>context</strong> (this file), <strong>memory</strong>, and <strong>identity</strong> 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>
81
+ <p><code>read_agents_md</code> serves this file, section by section, over the same MCP connection.</p>
82
+ <h2 id="setup">Setup</h2>
83
+ <pre><code class="language-bash">npm ci</code></pre>
84
+ <p>Node 22 or newer. No other system dependencies.</p>
85
+ <h2 id="build">Build</h2>
86
+ <pre><code class="language-bash">npm run build # tsc -p tsconfig.build.json → dist/
87
+ npm run typecheck # tsc --noEmit over src/ + test/</code></pre>
88
+ <h2 id="test">Test</h2>
89
+ <pre><code class="language-bash">npm test # node:test — every test/*.test.ts
90
+ npm run test:coverage # + the coverage gate (lines 90 / funcs 85 / branches 80, src/ only)
91
+ npm run demo # end to end: all tools over stdio, then over stateless HTTP</code></pre>
92
+ <p>CI runs <code>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>).</p>
93
+ <h2 id="layout">Layout</h2>
94
+ <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> — wraps <code>agents-md-facts</code> (the AGENTS.md authoring engine)</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></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>
95
+ <h2 id="conventions">Conventions</h2>
96
+ <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>
97
+ <h2 id="the-invariant">The invariant</h2>
98
+ <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>
99
+ <h2 id="safety">Safety</h2>
100
+ <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>
101
+ <h2 id="definition-of-done">Definition of done</h2>
102
+ <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>.</p>
103
+ <h2 id="authoring-this-file">Authoring this file</h2>
104
+ <p><code>AGENTS.md</code> here is maintained by hand. It can also be generated from the repo's <code>project.faf</code> with <code>faf export --agents</code> — the server doesn't care how the file was authored, only that it's valid Markdown.</p></div>
105
+ </section>
106
+ <section>
107
+ <p class="label">Memory — 3 facts</p>
108
+ <div class="fact"><p>The context concern points at AGENTS.md — the de-facto standard for agent instructions. read_agents_md serves it whole or one section at a time, so a client pulls '## Test' without spending its context window on the whole file. Memory and identity have no de-facto standard, so the reference uses .fafm and .fafa as one instantiation each.</p><div class="meta"><span class="tag">scope</span><span class="tag">agents-md</span><span class="dot" title="verified"></span></div></div><div class="fact"><p>Both exposure mechanisms — the Server Card _meta block and a self-published ai-catalog.json — already exist in the MCP ecosystem. This server wires all three concerns through them, from one set of source files, with a CI check (catalog:check) that fails if the two surfaces drift apart.</p><div class="meta"><span class="tag">mcp</span><span class="tag">server-card</span><span class="tag">ai-catalog</span><span class="dot" title="verified"></span></div></div><div class="fact"><p>Distribution: a published npm package (bin <code>mcp-context-card</code>, dual transport). The three source files (AGENTS.md, project.fafm, .well-known/fafa) ship with the package so it is a working discovery target on install. MCP_CONTEXT_CARD_ROOT points it at a real project.</p><div class="meta"><span class="tag">distribution</span><span class="tag">mit</span><span class="tag">npm</span><span class="dot" title="verified"></span></div></div>
109
+ </section>
110
+ <section>
111
+ <p class="label">Discovery</p>
112
+ <table class="disc"><thead><tr><th>concern</th><th>source</th><th>media type</th></tr></thead><tbody><tr><td>context</td><td><code>AGENTS.md</code></td><td><code>text/markdown</code></td></tr><tr><td>memory</td><td><code>project.fafm</code></td><td><code>application/vnd.fafm+yaml</code></td></tr><tr><td>identity</td><td><code>.well-known/fafa</code></td><td><code>application/vnd.fafa+yaml</code></td></tr></tbody></table>
113
+ <p class="fetch">A machine reads this over <b>MCP</b> from the
114
+ <code>mcp-context-card://server-card</code> resource; over <b>HTTP</b> also
115
+ from <code>GET /.well-known/mcp/server-card</code> and
116
+ <code>GET /.well-known/ai-catalog.json</code>.</p>
117
+ </section>
118
+ <div class="foot">mcp-context-card · context card</div>
119
+ </main>
120
+ </body>
121
+ </html>
Binary file
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "mcp-context-card",
3
+ "version": "0.5.0",
4
+ "mcpName": "io.github.wolfe-jam/mcp-context-card",
5
+ "description": "Reference MCP server: project context, persistent memory, and agent identity — through the two exposure surfaces already in the ecosystem (Server Card _meta, ai-catalog sibling entries).",
6
+ "keywords": [
7
+ "mcp",
8
+ "model-context-protocol",
9
+ "server-card",
10
+ "ai-catalog",
11
+ "project-context",
12
+ "agent-memory",
13
+ "agent-identity"
14
+ ],
15
+ "author": "wolfejam",
16
+ "license": "MIT",
17
+ "type": "module",
18
+ "private": false,
19
+ "bin": {
20
+ "mcp-context-card": "dist/bin.js"
21
+ },
22
+ "main": "dist/server.js",
23
+ "types": "dist/server.d.ts",
24
+ "exports": {
25
+ ".": "./dist/server.js",
26
+ "./server": "./dist/server.js",
27
+ "./package.json": "./package.json"
28
+ },
29
+ "engines": {
30
+ "node": ">=22"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "docs",
35
+ "AGENTS.md",
36
+ "project.faf",
37
+ "project.fafm",
38
+ ".well-known",
39
+ "server.json",
40
+ "README.md",
41
+ "CHANGELOG.md",
42
+ "LICENSE"
43
+ ],
44
+ "scripts": {
45
+ "build": "tsc -p tsconfig.build.json",
46
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
47
+ "prepublishOnly": "npm run clean && npm run build && npm run typecheck && npm test",
48
+ "start": "node dist/bin.js",
49
+ "start:http": "node dist/bin.js --http",
50
+ "dev": "tsx src/bin.ts",
51
+ "demo": "tsx demo.ts",
52
+ "catalog": "tsx src/catalog-gen.ts",
53
+ "catalog:check": "tsx src/catalog-gen.ts && git diff --exit-code -- .well-known/ai-catalog.json",
54
+ "card": "tsx src/card-gen.ts",
55
+ "card:check": "tsx src/card-gen.ts && git diff --exit-code -- docs/card.html",
56
+ "test": "node --import tsx --test \"test/*.test.ts\"",
57
+ "test:coverage": "node --import tsx --test --experimental-test-coverage --test-coverage-exclude=\"test/**\" --test-coverage-exclude=\"demo.ts\" --test-coverage-lines=90 --test-coverage-functions=85 --test-coverage-branches=80 \"test/*.test.ts\"",
58
+ "typecheck": "tsc --noEmit"
59
+ },
60
+ "dependencies": {
61
+ "@hono/node-server": "^1.19.17",
62
+ "@modelcontextprotocol/sdk": "^1.29.0",
63
+ "agents-md-facts": "^0.1.0",
64
+ "hono": "^4.13.5",
65
+ "yaml": "^2.9.0"
66
+ },
67
+ "devDependencies": {
68
+ "@types/node": "^26.2.0",
69
+ "tsx": "^4.22.4",
70
+ "typescript": "^6.0.3"
71
+ },
72
+ "repository": {
73
+ "type": "git",
74
+ "url": "git+https://github.com/Wolfe-Jam/mcp-context-card.git"
75
+ },
76
+ "homepage": "https://github.com/Wolfe-Jam/mcp-context-card"
77
+ }
package/project.faf ADDED
@@ -0,0 +1,28 @@
1
+ faf_version: "3.0"
2
+ project:
3
+ name: mcp-context-card
4
+ goal: An MCP server that makes a project's context (its AGENTS.md), memory, and identity discoverable to any MCP client — through the Server Card _meta block and ai-catalog.json sibling entries, the two surfaces already in the ecosystem.
5
+ main_language: TypeScript
6
+ type: mcp
7
+ stack:
8
+ runtime: Node.js
9
+ build: TypeScript (tsc)
10
+ backend: Node.js (MCP server, dual transport)
11
+ api_type: MCP (JSON-RPC 2.0 — stdio + stateless Streamable HTTP)
12
+ frontend: slotignored # no UI layer
13
+ css_framework: slotignored # no UI
14
+ ui_library: slotignored # no UI
15
+ state_management: slotignored # stateless server; memory is a file
16
+ database: slotignored # memory is a file, not a DB
17
+ connection: slotignored # no database
18
+ hosting: Docker / any Node host — stdio for local, stateless Streamable HTTP for remote
19
+ cicd: GitHub Actions (typecheck + build + test + demo, 3 OSes)
20
+ tech_stack: [TypeScript, "@modelcontextprotocol/sdk", hono, yaml]
21
+ human_context:
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: An MCP server with seven tools — read_agents_md / list_agents_md_sections (context), remember / recall / forget (memory), whoami (identity), list_context_sources (discovery) — exposed through the Server Card _meta block and a self-published ai-catalog.json. Dual transport (stdio + stateless Streamable HTTP).
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". This does it through mechanisms that already exist.
25
+ where: github.com/Wolfe-Jam/mcp-context-card
26
+ when: "2026-08-12"
27
+ how: TypeScript on the MCP SDK. Dual transport in src/bin.ts. The Server Card _meta block and ai-catalog.json are generated from the same three sources (AGENTS.md, project.fafm, .well-known/fafa) — a CI check fails on drift. remember/recall are real file-backed reads/writes proven across a process boundary in demo.ts and the test suite. See docs/MECHANISMS.md, docs/WIRING.md, docs/TRANSPORT.md.
28
+ key_files: [package.json, README.md, AGENTS.md, docs/MECHANISMS.md, src/server.ts, src/agents-md.ts, src/catalog-gen.ts, src/transport/http.ts]
package/project.fafm ADDED
@@ -0,0 +1,46 @@
1
+ # application/vnd.fafm+yaml — mcp-context-card project memory
2
+ # Real, dogfooded memory about this repo's own build — not a static example.
3
+ # This file itself is one of the three sources the server exposes:
4
+ # `remember` / `recall` read and write it, and the demo proves a fact
5
+ # survives a full server-process restart (then restores this file exactly).
6
+ version: "1.1"
7
+ profile: "knowledge"
8
+ namepoint: "@mcp-context-card:public"
9
+ created: "2026-08-12T00:00:00Z"
10
+ last_etched: "2026-08-12T00:00:00Z"
11
+ retention: "forever"
12
+
13
+ index:
14
+ - "mcp-context-card — an MCP server that publishes a project's context, memory & identity"
15
+ - "context concern leads with AGENTS.md (text/markdown); memory & identity use .fafm/.fafa as worked examples"
16
+ - "two exposure surfaces: Server Card _meta block + self-published ai-catalog.json"
17
+
18
+ memory:
19
+ facts:
20
+ - text: "The context concern points at AGENTS.md — the de-facto standard for agent instructions. read_agents_md serves it whole or one section at a time, so a client pulls '## Test' without spending its context window on the whole file. Memory and identity have no de-facto standard, so the reference uses .fafm and .fafa as one instantiation each."
21
+ id: "mcp-context-card-scope"
22
+ type: "reference"
23
+ priority: "high"
24
+ tags: ["scope", "agents-md"]
25
+ source: "README.md"
26
+ verification_status: "verified"
27
+
28
+ - text: "Both exposure mechanisms — the Server Card _meta block and a self-published ai-catalog.json — already exist in the MCP ecosystem. This server wires all three concerns through them, from one set of source files, with a CI check (catalog:check) that fails if the two surfaces drift apart."
29
+ id: "mechanisms-already-proven"
30
+ type: "reference"
31
+ priority: "high"
32
+ tags: ["mcp", "server-card", "ai-catalog"]
33
+ links: ["mcp-context-card-scope"]
34
+ source: "docs/MECHANISMS.md"
35
+ verification_status: "verified"
36
+
37
+ - text: "Distribution: a published npm package (bin `mcp-context-card`, dual transport). The three source files (AGENTS.md, project.fafm, .well-known/fafa) ship with the package so it is a working discovery target on install. MCP_CONTEXT_CARD_ROOT points it at a real project."
38
+ id: "distribution-model"
39
+ type: "reference"
40
+ priority: "standard"
41
+ tags: ["distribution", "mit", "npm"]
42
+ source: "README.md"
43
+ verification_status: "verified"
44
+ sessions: []
45
+ preferences: {}
46
+ custom: {}
package/server.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.wolfe-jam/mcp-context-card",
4
+ "title": "mcp-context-card",
5
+ "description": "Reference MCP server that makes a project's context (AGENTS.md), memory, and identity discoverable to any MCP client — through the Server Card _meta block and ai-catalog.json sibling entries.",
6
+ "version": "0.5.0",
7
+ "repository": {
8
+ "url": "https://github.com/Wolfe-Jam/mcp-context-card",
9
+ "source": "github"
10
+ },
11
+ "packages": [
12
+ {
13
+ "registryType": "npm",
14
+ "registryBaseUrl": "https://registry.npmjs.org",
15
+ "identifier": "mcp-context-card",
16
+ "version": "0.5.0",
17
+ "runtimeHint": "npx",
18
+ "transport": {
19
+ "type": "stdio"
20
+ },
21
+ "environmentVariables": [
22
+ {
23
+ "name": "MCP_CONTEXT_CARD_ROOT",
24
+ "description": "Directory holding project.faf / project.fafm / .well-known/fafa. Omitted → the server's own bundled reference copies.",
25
+ "isRequired": false
26
+ }
27
+ ]
28
+ }
29
+ ],
30
+ "_meta": {
31
+ "io.modelcontextprotocol.registry/publisher-provided": {
32
+ "io.github.wolfe-jam.mcp-context-card/context": {
33
+ "source": "./AGENTS.md",
34
+ "mediaType": "text/markdown"
35
+ },
36
+ "io.github.wolfe-jam.mcp-context-card/memory": {
37
+ "source": "./project.fafm",
38
+ "mediaType": "application/vnd.fafm+yaml",
39
+ "iana": "https://www.iana.org/assignments/media-types/application/vnd.fafm+yaml",
40
+ "note": "no de-facto standard for agent memory yet — one instantiation"
41
+ },
42
+ "io.github.wolfe-jam.mcp-context-card/identity": {
43
+ "source": "./.well-known/fafa",
44
+ "mediaType": "application/vnd.fafa+yaml",
45
+ "iana": "https://www.iana.org/assignments/media-types/application/vnd.fafa+yaml"
46
+ }
47
+ }
48
+ }
49
+ }