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 +1 -1
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +60 -0
- package/README.md +36 -8
- package/dist/bin.d.ts +1 -1
- package/dist/bin.js +5 -10
- package/dist/constants.d.ts +7 -1
- package/dist/constants.js +7 -1
- package/dist/open.d.ts +1 -0
- package/dist/open.js +17 -0
- package/dist/render-card.d.ts +21 -0
- package/dist/render-card.js +61 -0
- package/dist/server.d.ts +12 -8
- package/dist/server.js +123 -25
- package/docs/MECHANISMS.md +4 -2
- package/docs/WIRING.md +2 -1
- package/docs/card-dark.html +3 -3
- package/docs/card-light.html +3 -3
- package/docs/card.html +3 -3
- package/docs/img/card-dark.png +0 -0
- package/docs/img/card-identity.png +0 -0
- package/docs/img/card-light.png +0 -0
- package/docs/index.html +3 -3
- package/package.json +1 -1
- package/project.faf +1 -1
- package/server.json +2 -2
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.
|
|
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
|
|
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
|
|
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
|
|  |  |
|
|
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
|

|
|
@@ -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
|
-
|
|
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`)
|
|
67
|
-
file access, no shell,
|
|
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`
|
|
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
|
-
|
|
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
|
|
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.
|
|
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 {
|
|
124
|
+
const { openInBrowser } = await import("./open.js");
|
|
125
125
|
const out = join(process.cwd(), "context-card.html");
|
|
126
126
|
writeFileSync(out, html);
|
|
127
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/constants.d.ts
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 declare const NAME = "mcp-context-card";
|
|
4
|
-
export declare const VERSION = "1.
|
|
4
|
+
export declare const VERSION = "1.2.0";
|
|
5
5
|
export declare const SERVER_CARD_URI = "mcp-context-card://server-card";
|
|
6
|
+
/** MCP Apps (io.modelcontextprotocol/ui): the card as an inline UI resource.
|
|
7
|
+
* A host that supports MCP Apps fetches this resource and renders it in a
|
|
8
|
+
* sandboxed iframe next to the conversation. */
|
|
9
|
+
export declare const CARD_UI_URI = "ui://mcp-context-card/card.html";
|
|
10
|
+
export declare const MCP_APP_MIME = "text/html;profile=mcp-app";
|
|
11
|
+
export declare const UI_EXTENSION = "io.modelcontextprotocol/ui";
|
package/dist/constants.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/** Server identity constants, in their own module so any file can import
|
|
2
2
|
* them without pulling in the whole server. */
|
|
3
3
|
export const NAME = "mcp-context-card";
|
|
4
|
-
export const VERSION = "1.
|
|
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
|
+
}
|
package/dist/render-card.d.ts
CHANGED
|
@@ -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;
|
package/dist/render-card.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
51
|
-
*
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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: {
|
|
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
|
|
377
|
+
const { openInBrowser } = await import("./open.js");
|
|
378
|
+
await serve(new StdioServerTransport(), ROOT, { openFile: openInBrowser });
|
|
281
379
|
}
|
package/docs/MECHANISMS.md
CHANGED
|
@@ -29,7 +29,7 @@ Both return:
|
|
|
29
29
|
```jsonc
|
|
30
30
|
{
|
|
31
31
|
"name": "mcp-context-card",
|
|
32
|
-
"version": "1.
|
|
32
|
+
"version": "1.2.0",
|
|
33
33
|
"_meta": {
|
|
34
34
|
"io.github.Wolfe-Jam.mcp-context-card/context": {
|
|
35
35
|
"source": "AGENTS.md",
|
|
@@ -118,7 +118,9 @@ GET /.well-known/ai-catalog.json
|
|
|
118
118
|
|
|
119
119
|
The same three sources also render as **the card** — `GET /card` /
|
|
120
120
|
`render_context_card` / `docs/card.html` — the human view of exactly what a
|
|
121
|
-
machine reads below.
|
|
121
|
+
machine reads below. Hosts that support MCP Apps show it inline from the
|
|
122
|
+
`ui://mcp-context-card/card.html` resource; elsewhere `save_context_card`
|
|
123
|
+
writes it to `context-card.html` and returns it as Markdown.
|
|
122
124
|
|
|
123
125
|
The same three sources back **both** discovery mechanisms:
|
|
124
126
|
|
package/docs/WIRING.md
CHANGED
|
@@ -83,7 +83,8 @@ And to discover what a server offers before committing to it:
|
|
|
83
83
|
await client.callTool({ name: "list_context_sources", arguments: {} });
|
|
84
84
|
// → { context: { source: "AGENTS.md", mediaType: "text/markdown", present: true, sections: 10 },
|
|
85
85
|
// memory: { … }, identity: { … },
|
|
86
|
-
// surfaces: { mcp: { serverCard: "resource mcp-context-card://server-card"
|
|
86
|
+
// surfaces: { mcp: { serverCard: "resource mcp-context-card://server-card",
|
|
87
|
+
// card: "resource ui://mcp-context-card/card.html (MCP App, text/html;profile=mcp-app)" },
|
|
87
88
|
// http: { serverCard: "GET /.well-known/mcp/server-card",
|
|
88
89
|
// aiCatalog: "GET /.well-known/ai-catalog.json",
|
|
89
90
|
// card: "GET /card" } } }
|
package/docs/card-dark.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.
|
|
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
|
|
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
|
|
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>"type": "module"</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 && npm run build && npm test && 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-light.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.
|
|
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
|
|
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
|
|
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>"type": "module"</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 && npm run build && npm test && 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.
|
|
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
|
|
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
|
|
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>"type": "module"</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 && npm run build && npm test && 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/img/card-dark.png
CHANGED
|
Binary file
|
|
Binary file
|
package/docs/img/card-light.png
CHANGED
|
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.
|
|
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
|
|
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
|
|
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>"type": "module"</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 && npm run build && npm test && 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.
|
|
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.
|
|
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.
|
|
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.
|
|
16
|
+
"version": "1.2.0",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|