mcp-context-card 1.3.1 → 1.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.
- package/.well-known/ai-catalog.json +39 -17
- package/.well-known/fafa +5 -1
- package/CHANGELOG.md +61 -0
- package/README.md +25 -7
- package/dist/bin.d.ts +6 -1
- package/dist/bin.js +26 -2
- package/dist/catalog-gen.d.ts +58 -7
- package/dist/catalog-gen.js +53 -27
- package/dist/conformance/discovery.d.ts +60 -0
- package/dist/conformance/discovery.js +281 -0
- package/dist/constants.d.ts +30 -1
- package/dist/constants.js +30 -1
- package/dist/faf/parse-fafa.js +1 -0
- package/dist/faf/types.d.ts +2 -0
- package/dist/identity.d.ts +19 -1
- package/dist/identity.js +37 -7
- package/dist/render-card.d.ts +5 -0
- package/dist/render-card.js +18 -16
- package/dist/server-card.d.ts +39 -0
- package/dist/server-card.js +38 -0
- package/dist/server.d.ts +6 -25
- package/dist/server.js +13 -13
- package/dist/transport/guard.d.ts +35 -0
- package/dist/transport/guard.js +63 -0
- package/dist/transport/http.d.ts +19 -2
- package/dist/transport/http.js +94 -20
- package/docs/MECHANISMS.md +11 -4
- package/docs/TRANSPORT.md +40 -7
- package/docs/WIRING.md +1 -1
- package/docs/card-dark.html +3 -2
- package/docs/card-light.html +3 -2
- package/docs/card.html +3 -2
- package/docs/img/card-dark.png +0 -0
- package/docs/img/card-identity.png +0 -0
- package/docs/img/card-light.png +0 -0
- package/docs/index.html +3 -2
- package/package.json +7 -4
- package/server.json +2 -2
|
@@ -6,33 +6,55 @@
|
|
|
6
6
|
},
|
|
7
7
|
"entries": [
|
|
8
8
|
{
|
|
9
|
-
"identifier": "urn:air:mcp-context-card
|
|
9
|
+
"identifier": "urn:air:faf.one:mcp:mcp-context-card",
|
|
10
|
+
"type": "application/mcp-server-card+json",
|
|
11
|
+
"data": {
|
|
12
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json",
|
|
13
|
+
"name": "io.github.Wolfe-Jam/mcp-context-card",
|
|
14
|
+
"version": "1.5.0",
|
|
15
|
+
"title": "MCP Context Card",
|
|
16
|
+
"description": "MCP server for a project's context (AGENTS.md), memory, and identity — base or drop-in extension.",
|
|
17
|
+
"websiteUrl": "https://github.com/Wolfe-Jam/mcp-context-card",
|
|
18
|
+
"repository": {
|
|
19
|
+
"url": "https://github.com/Wolfe-Jam/mcp-context-card",
|
|
20
|
+
"source": "github"
|
|
21
|
+
},
|
|
22
|
+
"_meta": {
|
|
23
|
+
"io.github.Wolfe-Jam.mcp-context-card/context": {
|
|
24
|
+
"source": "AGENTS.md",
|
|
25
|
+
"mediaType": "text/markdown"
|
|
26
|
+
},
|
|
27
|
+
"io.github.Wolfe-Jam.mcp-context-card/memory": {
|
|
28
|
+
"source": "project.fafm",
|
|
29
|
+
"mediaType": "application/vnd.fafm+yaml",
|
|
30
|
+
"iana": "https://www.iana.org/assignments/media-types/application/vnd.fafm+yaml",
|
|
31
|
+
"note": "no de-facto standard for agent memory yet — this is one instantiation"
|
|
32
|
+
},
|
|
33
|
+
"io.github.Wolfe-Jam.mcp-context-card/identity": {
|
|
34
|
+
"source": ".well-known/fafa",
|
|
35
|
+
"mediaType": "application/vnd.fafa+yaml",
|
|
36
|
+
"iana": "https://www.iana.org/assignments/media-types/application/vnd.fafa+yaml"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"identifier": "urn:air:faf.one:context:mcp-context-card",
|
|
10
43
|
"displayName": "mcp-context-card — project context (AGENTS.md)",
|
|
11
44
|
"type": "text/markdown",
|
|
12
|
-
"mediaType": "text/markdown",
|
|
13
45
|
"description": "Agent instructions for this project — 9 section(s): Setup, Build, Test, Layout, Conventions, The invariant, ….",
|
|
14
46
|
"url": "./AGENTS.md"
|
|
15
47
|
},
|
|
16
48
|
{
|
|
17
|
-
"identifier": "urn:air:mcp-context-card
|
|
18
|
-
"displayName": "mcp-context-card — persistent memory (.fafm)",
|
|
19
|
-
"type": "application/vnd.fafm+yaml",
|
|
20
|
-
"mediaType": "application/vnd.fafm+yaml",
|
|
21
|
-
"description": "Cross-session memory — 4 fact(s), profile \"knowledge\". Recall survives a process restart. No de-facto standard for this concern yet.",
|
|
22
|
-
"url": "./project.fafm",
|
|
23
|
-
"_meta": {
|
|
24
|
-
"io.github.Wolfe-Jam.mcp-context-card/iana": "https://www.iana.org/assignments/media-types/application/vnd.fafm+yaml"
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
"identifier": "urn:air:mcp-context-card:identity",
|
|
49
|
+
"identifier": "urn:air:faf.one:identity:mcp-context-card",
|
|
29
50
|
"displayName": "mcp-context-card — agent identity (.fafa)",
|
|
30
51
|
"type": "application/vnd.fafa+yaml",
|
|
31
|
-
"mediaType": "application/vnd.fafa+yaml",
|
|
32
52
|
"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 a self-published ai-catalog.json.",
|
|
33
53
|
"url": "./.well-known/fafa",
|
|
34
|
-
"
|
|
35
|
-
"io.github.Wolfe-Jam.mcp-context-card
|
|
54
|
+
"extensions": {
|
|
55
|
+
"io.github.Wolfe-Jam.mcp-context-card": {
|
|
56
|
+
"iana": "https://www.iana.org/assignments/media-types/application/vnd.fafa+yaml"
|
|
57
|
+
}
|
|
36
58
|
}
|
|
37
59
|
}
|
|
38
60
|
]
|
package/.well-known/fafa
CHANGED
|
@@ -8,8 +8,12 @@ version: "1.0"
|
|
|
8
8
|
agent:
|
|
9
9
|
name: "mcp-context-card"
|
|
10
10
|
displayName: "mcp-context-card"
|
|
11
|
+
# Publisher identity in AI Catalog form, as `faf card init` writes it:
|
|
12
|
+
# urn:air:{your domain}:agent:{short name}. This example publishes as faf.one;
|
|
13
|
+
# a deployment for your own project uses your domain.
|
|
14
|
+
id: "urn:air:faf.one:agent:mcp-context-card"
|
|
11
15
|
vendor: "io.github.Wolfe-Jam"
|
|
12
|
-
version: "1.
|
|
16
|
+
version: "1.5.0"
|
|
13
17
|
description: >-
|
|
14
18
|
The essential MCP components for a project's context (AGENTS.md),
|
|
15
19
|
cross-session memory, and identity — a base MCP on its own, or a
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,67 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project. Adheres to [Semantic Versioning](https://semver.org).
|
|
4
4
|
|
|
5
|
+
## 1.5.0
|
|
6
|
+
|
|
7
|
+
MCP Server Cards (SEP-2127, Final) and AI Catalog 1.0, and HTTP that is local by default.
|
|
8
|
+
|
|
9
|
+
**Breaking for exposed deploys:** HTTP mode now binds `127.0.0.1`. A container or
|
|
10
|
+
hosted deploy must set `HOST=0.0.0.0` (`examples/Dockerfile` does). Local use is
|
|
11
|
+
unchanged.
|
|
12
|
+
|
|
13
|
+
- **Server Card (SEP-2127, Final).** Served at `GET /mcp/server-card`
|
|
14
|
+
(`<streamable-http-url>/server-card`). The 1.x `/.well-known/mcp/server-card`
|
|
15
|
+
still answers with the same card. The card validates against the official v1
|
|
16
|
+
schema: `$schema`, a reverse-DNS `name` (`io.github.Wolfe-Jam/mcp-context-card`,
|
|
17
|
+
as in `server.json`), `title`, `description`, `websiteUrl`, `repository`, and a
|
|
18
|
+
`streamable-http` remote when served over HTTP. `serverInfo` reports the same
|
|
19
|
+
name, title and version.
|
|
20
|
+
- **Discovery hosting.** The card, catalog and `.well-known/fafa` carry the
|
|
21
|
+
spec's CORS (`Access-Control-Allow-Origin: *`, GET, `Content-Type` and
|
|
22
|
+
`If-None-Match` allowed, `ETag` exposed), `Cache-Control: public,
|
|
23
|
+
max-age=3600`, and an `ETag` answered with `304 Not Modified`.
|
|
24
|
+
- **AI Catalog 1.0.** The Server Card is the first entry. Entries use `type`,
|
|
25
|
+
custom data sits in `extensions`, and identifiers are
|
|
26
|
+
`urn:air:{domain}:{namespace}:{name}` with the domain taken from the project's
|
|
27
|
+
own `.fafa` (none is ever invented). `/AGENTS.md` is served, so every catalog
|
|
28
|
+
link resolves with its declared type.
|
|
29
|
+
- **Memory stays private over HTTP.** `project.fafm` is session data, so it is
|
|
30
|
+
not served or listed in the AI Catalog, and an exposed `/card` shows only how
|
|
31
|
+
many facts there are, unless `MCP_CONTEXT_CARD_PUBLISH_MEMORY=1`. Local
|
|
32
|
+
surfaces (the CLI card, the MCP App, the tools, a local `/card`) are unchanged.
|
|
33
|
+
- **Local by default (MCP transports spec).** A foreign browser `Origin` gets
|
|
34
|
+
403 with a JSON-RPC error. A local server serves loopback `Host` names only,
|
|
35
|
+
so a DNS name rebound to this machine is refused. The server binds
|
|
36
|
+
`127.0.0.1` unless `HOST` is set. `MCP_CONTEXT_CARD_ALLOWED_HOSTS` and
|
|
37
|
+
`MCP_CONTEXT_CARD_ALLOWED_ORIGINS` admit a reverse proxy or a web app. The
|
|
38
|
+
startup line says whether the server is local or exposed, and now prints
|
|
39
|
+
once the socket is listening.
|
|
40
|
+
- **WJTTC suite (`npm run wjttc`).** Seven tiers: Protocol, Server Card,
|
|
41
|
+
Hosting, AI Catalog, Security, stdio/HTTP Parity, Ship. Tiers 2 to 4 and the
|
|
42
|
+
transport checks run `src/conformance/discovery.ts`, a self-contained checker
|
|
43
|
+
for any server URL that reports each requirement as MUST or SHOULD, worded
|
|
44
|
+
as the specs word it.
|
|
45
|
+
- Dependencies: `@modelcontextprotocol/sdk` 1.32.1 (GHSA-6qxp-vccf-f47h) and
|
|
46
|
+
`hono` 4.13.13. `npm audit` reports 0 vulnerabilities.
|
|
47
|
+
|
|
48
|
+
No tool change. 200 tests, all green.
|
|
49
|
+
|
|
50
|
+
## 1.4.0
|
|
51
|
+
|
|
52
|
+
The card reads the `.fafa` that `faf card init` writes, and shows the agent's ID.
|
|
53
|
+
|
|
54
|
+
- **Identity reads `agent.fafa`.** `faf card init` writes the agent's `.fafa`
|
|
55
|
+
to `./agent.fafa`; the card, `whoami`, the AI Catalog and the served
|
|
56
|
+
`/.well-known/fafa` now read it, and fall back to `.well-known/fafa` as
|
|
57
|
+
before. With both, `agent.fafa` wins.
|
|
58
|
+
- The README's identity line names the `.fafa`, not "its own Server Card"
|
|
59
|
+
(the MCP Server Card is the `_meta` block).
|
|
60
|
+
- **The agent ID shows.** `agent.id` (the `urn:air:…` that `faf card init`
|
|
61
|
+
writes) is read and shown in the card's Discovery section, the Markdown
|
|
62
|
+
card and `whoami`. No `id`, no line.
|
|
63
|
+
|
|
64
|
+
No API change. 140 tests, all green.
|
|
65
|
+
|
|
5
66
|
## 1.3.1
|
|
6
67
|
|
|
7
68
|
The registry listing catches up with 1.3.0.
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ npx mcp-context-card card
|
|
|
28
28
|
|
|
29
29
|

|
|
30
30
|
|
|
31
|
-
**identity** — what this server is, from its own
|
|
31
|
+
**identity** — what this server is, from its own `.fafa` (`agent.fafa`, else `.well-known/fafa`).
|
|
32
32
|
|
|
33
33
|

|
|
34
34
|
|
|
@@ -70,7 +70,7 @@ It composes:
|
|
|
70
70
|
|
|
71
71
|
Vendor-free — context is plain Markdown (`AGENTS.md`); the memory and
|
|
72
72
|
identity formats are swappable examples. It reads and writes only its own
|
|
73
|
-
three files (`AGENTS.md`, `project.fafm`, `.well-known/fafa`), plus the
|
|
73
|
+
three files (`AGENTS.md`, `project.fafm`, and `agent.fafa` or `.well-known/fafa`), plus the
|
|
74
74
|
`context-card.html` it saves on request — no general file access, no shell,
|
|
75
75
|
no search.
|
|
76
76
|
|
|
@@ -167,6 +167,16 @@ reports which project it picked and how. To pin one project instead, set
|
|
|
167
167
|
wins. Identity is optional. Over HTTP instead:
|
|
168
168
|
`PORT=8080 npx mcp-context-card`. Requires Node ≥20.
|
|
169
169
|
|
|
170
|
+
HTTP mode is local by default, as the MCP transports spec asks: it binds
|
|
171
|
+
`127.0.0.1`, refuses foreign browser origins with 403, and refuses DNS names
|
|
172
|
+
rebound to this machine. `HOST=0.0.0.0` exposes it (a container or hosted
|
|
173
|
+
deploy). There is no authentication or rate limiting, so put an exposed server
|
|
174
|
+
behind your own.
|
|
175
|
+
Memory is session data: an exposed server shows only its count on `/card`, and
|
|
176
|
+
never serves or lists `project.fafm`, unless you set
|
|
177
|
+
`MCP_CONTEXT_CARD_PUBLISH_MEMORY=1`. Details:
|
|
178
|
+
[docs/TRANSPORT.md](./docs/TRANSPORT.md#security-local-by-default).
|
|
179
|
+
|
|
170
180
|
If `command: "npx"` fails to spawn (`spawn npx ENOENT` — seen on Cursor, whose
|
|
171
181
|
host process doesn't inherit a shell `PATH`), point `command` at `node` and
|
|
172
182
|
the installed `dist/bin.js` instead — see
|
|
@@ -190,9 +200,10 @@ grows its own shape.
|
|
|
190
200
|
|
|
191
201
|
1. **Server Card `_meta`** ([SEP‑2127](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127)) —
|
|
192
202
|
one reverse‑DNS‑namespaced key per concern, readable in‑band as an MCP
|
|
193
|
-
resource and at `GET
|
|
194
|
-
|
|
195
|
-
|
|
203
|
+
resource and at `GET /mcp/server-card` (the 1.x `/.well-known/mcp/server-card`
|
|
204
|
+
still answers).
|
|
205
|
+
2. **`ai-catalog.json`** — the Server Card plus sibling entries keyed by media
|
|
206
|
+
type, at `GET /.well-known/ai-catalog.json`.
|
|
196
207
|
|
|
197
208
|
The context concern points at `AGENTS.md` (`text/markdown`). Memory and identity
|
|
198
209
|
have no de‑facto standard yet, so the examples here use
|
|
@@ -235,11 +246,15 @@ over both transports:
|
|
|
235
246
|
4. **Discovery** — `list_context_sources()`, then the same server over stateless
|
|
236
247
|
HTTP with its `.well-known` routes and `GET /card`.
|
|
237
248
|
|
|
238
|
-
|
|
249
|
+
200 tests on Linux, macOS, and Windows, coverage‑gated in CI.
|
|
250
|
+
`npm run wjttc` runs the WJTTC certification suite (seven tiers, from protocol
|
|
251
|
+
and Server Card conformance to stdio/HTTP parity and the shipped package). Two spawn a real
|
|
239
252
|
child process and check a remembered fact survives the restart — one against
|
|
240
253
|
an existing `project.fafm`, one starting from a project that has never had
|
|
241
254
|
one; another checks the stdio and HTTP tool surfaces match, and another checks
|
|
242
255
|
every tool's title and behaviour hints against what it actually does.
|
|
256
|
+
`src/conformance/discovery.ts` checks any server's Server Card, AI Catalog and
|
|
257
|
+
transport security against the specs, one MUST or SHOULD at a time.
|
|
243
258
|
|
|
244
259
|
## Layout
|
|
245
260
|
|
|
@@ -252,8 +267,11 @@ every tool's title and behaviour hints against what it actually does.
|
|
|
252
267
|
| `src/render-card.ts` | the card — identity + `AGENTS.md` + memory + discovery, as one HTML page |
|
|
253
268
|
| `src/memory.ts` | file‑backed `remember` / `recall` / `forget` |
|
|
254
269
|
| `src/identity.ts` | `whoami` (`.fafa` → `package.json` fallback) + the `_meta` block |
|
|
255
|
-
| `src/
|
|
270
|
+
| `src/server-card.ts` | the MCP Server Card (SEP-2127, schema v1) |
|
|
271
|
+
| `src/catalog-gen.ts` | writes `ai-catalog.json`: the Server Card + its sibling entries |
|
|
256
272
|
| `src/transport/http.ts` | the stateless Streamable HTTP app (Hono) |
|
|
273
|
+
| `src/transport/guard.ts` | Origin and Host checks (DNS-rebinding protection) |
|
|
274
|
+
| `src/conformance/discovery.ts` | a portable Server Card / AI Catalog / transport checker |
|
|
257
275
|
| `src/bin.ts` | the entry point — `stdio` · `--http` · `card` · `--help` · `--version` |
|
|
258
276
|
|
|
259
277
|
## Related
|
package/dist/bin.d.ts
CHANGED
|
@@ -8,7 +8,12 @@ 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.5.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 HOST HTTP bind address (default 127.0.0.1, this machine\n only); 0.0.0.0 exposes it, e.g. in a container\n MCP_CONTEXT_CARD_ALLOWED_HOSTS / _ALLOWED_ORIGINS\n comma-separated Host names / browser origins to\n accept besides this machine's own (reverse proxy, web app)\n MCP_CONTEXT_CARD_PUBLISH_MEMORY=1\n HTTP: serve project.fafm, list it in the AI Catalog,\n and show its facts on an exposed /card (off: memory\n is session data). HTTP has no auth\n\nA bare run is an stdio server: it waits for a host to speak JSON-RPC on stdin,\nso it looks idle at a terminal. Try `card` (opens your context in a browser) or `--http`.\nhttps://github.com/Wolfe-Jam/mcp-context-card\n";
|
|
12
|
+
/**
|
|
13
|
+
* The address the HTTP server binds: `HOST`, else 127.0.0.1. Local by default,
|
|
14
|
+
* as the MCP transports spec recommends; a hosted deploy sets HOST=0.0.0.0.
|
|
15
|
+
*/
|
|
16
|
+
export declare function bindHost(env?: NodeJS.ProcessEnv): string;
|
|
12
17
|
/**
|
|
13
18
|
* Decide how to launch, from argv + env. Pure — so the mode matrix is unit
|
|
14
19
|
* tested without spawning a process.
|
package/dist/bin.js
CHANGED
|
@@ -41,11 +41,27 @@ USAGE
|
|
|
41
41
|
ENV
|
|
42
42
|
MCP_CONTEXT_CARD_ROOT read AGENTS.md / project.fafm / .well-known/ from here
|
|
43
43
|
PORT if set, run HTTP instead of stdio
|
|
44
|
+
HOST HTTP bind address (default 127.0.0.1, this machine
|
|
45
|
+
only); 0.0.0.0 exposes it, e.g. in a container
|
|
46
|
+
MCP_CONTEXT_CARD_ALLOWED_HOSTS / _ALLOWED_ORIGINS
|
|
47
|
+
comma-separated Host names / browser origins to
|
|
48
|
+
accept besides this machine's own (reverse proxy, web app)
|
|
49
|
+
MCP_CONTEXT_CARD_PUBLISH_MEMORY=1
|
|
50
|
+
HTTP: serve project.fafm, list it in the AI Catalog,
|
|
51
|
+
and show its facts on an exposed /card (off: memory
|
|
52
|
+
is session data). HTTP has no auth
|
|
44
53
|
|
|
45
54
|
A bare run is an stdio server: it waits for a host to speak JSON-RPC on stdin,
|
|
46
55
|
so it looks idle at a terminal. Try \`card\` (opens your context in a browser) or \`--http\`.
|
|
47
56
|
https://github.com/Wolfe-Jam/mcp-context-card
|
|
48
57
|
`;
|
|
58
|
+
/**
|
|
59
|
+
* The address the HTTP server binds: `HOST`, else 127.0.0.1. Local by default,
|
|
60
|
+
* as the MCP transports spec recommends; a hosted deploy sets HOST=0.0.0.0.
|
|
61
|
+
*/
|
|
62
|
+
export function bindHost(env = process.env) {
|
|
63
|
+
return env.HOST?.trim() || "127.0.0.1";
|
|
64
|
+
}
|
|
49
65
|
/**
|
|
50
66
|
* Decide how to launch, from argv + env. Pure — so the mode matrix is unit
|
|
51
67
|
* tested without spawning a process.
|
|
@@ -131,9 +147,17 @@ if (entryPath && import.meta.url === pathToFileURL(entryPath).href) {
|
|
|
131
147
|
else if (mode === "http") {
|
|
132
148
|
const { httpApp } = await import("./transport/http.js");
|
|
133
149
|
const { serve: serveHttp } = await import("@hono/node-server");
|
|
134
|
-
|
|
150
|
+
const { isLoopbackBind } = await import("./transport/guard.js");
|
|
151
|
+
const host = bindHost();
|
|
152
|
+
const local = isLoopbackBind(host);
|
|
153
|
+
// The startup line prints once the socket is listening, so anything waiting
|
|
154
|
+
// for it (a test, a supervisor) can connect straight away.
|
|
155
|
+
serveHttp({ fetch: httpApp(root, { exposure: local ? "local" : "exposed" }).fetch, port, hostname: host }, () =>
|
|
135
156
|
// stderr, not stdout — stdout is the MCP wire in stdio mode.
|
|
136
|
-
console.error(`${NAME} · http · :${port} (POST /mcp · GET /card · GET /.well-known/*)`
|
|
157
|
+
console.error(`${NAME} · http · ${host.includes(":") ? `[${host}]` : host}:${port} (POST /mcp · GET /card · GET /.well-known/*)` +
|
|
158
|
+
(local
|
|
159
|
+
? " · local only (HOST=0.0.0.0 to expose)"
|
|
160
|
+
: " · EXPOSED beyond this machine, no authentication: put it behind your own")));
|
|
137
161
|
}
|
|
138
162
|
else {
|
|
139
163
|
// stderr so it never touches the JSON-RPC wire on stdout; a bare run at a
|
package/dist/catalog-gen.d.ts
CHANGED
|
@@ -1,26 +1,77 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface CatalogOptions {
|
|
2
|
+
/** Public origin the catalog is served from, e.g. `https://ctx.example.com`. */
|
|
3
|
+
origin?: string;
|
|
4
|
+
/** List the memory file (`project.fafm`). Default: off. */
|
|
5
|
+
publishMemory?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function buildCatalog(root: string, opts?: CatalogOptions): {
|
|
2
8
|
specVersion: string;
|
|
3
9
|
host: {
|
|
4
10
|
displayName: string;
|
|
5
11
|
identifier: string;
|
|
6
12
|
};
|
|
7
13
|
entries: ({
|
|
14
|
+
url: string;
|
|
15
|
+
identifier: string;
|
|
16
|
+
type: string;
|
|
17
|
+
displayName?: undefined;
|
|
18
|
+
description?: undefined;
|
|
19
|
+
} | {
|
|
20
|
+
data: {
|
|
21
|
+
_meta: {
|
|
22
|
+
readonly "io.github.Wolfe-Jam.mcp-context-card/context": {
|
|
23
|
+
readonly source: "AGENTS.md";
|
|
24
|
+
readonly mediaType: "text/markdown";
|
|
25
|
+
};
|
|
26
|
+
readonly "io.github.Wolfe-Jam.mcp-context-card/memory": {
|
|
27
|
+
readonly source: "project.fafm";
|
|
28
|
+
readonly mediaType: "application/vnd.fafm+yaml";
|
|
29
|
+
readonly iana: string;
|
|
30
|
+
readonly note: "no de-facto standard for agent memory yet — this is one instantiation";
|
|
31
|
+
};
|
|
32
|
+
readonly "io.github.Wolfe-Jam.mcp-context-card/identity": {
|
|
33
|
+
readonly source: ".well-known/fafa";
|
|
34
|
+
readonly mediaType: "application/vnd.fafa+yaml";
|
|
35
|
+
readonly iana: string;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
remotes?: {
|
|
39
|
+
type: "streamable-http";
|
|
40
|
+
url: string;
|
|
41
|
+
supportedProtocolVersions: string[];
|
|
42
|
+
}[] | undefined;
|
|
43
|
+
$schema: string;
|
|
44
|
+
name: string;
|
|
45
|
+
version: string;
|
|
46
|
+
title: string;
|
|
47
|
+
description: string;
|
|
48
|
+
websiteUrl: string;
|
|
49
|
+
repository: {
|
|
50
|
+
url: string;
|
|
51
|
+
source: string;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
identifier: string;
|
|
55
|
+
type: string;
|
|
56
|
+
displayName?: undefined;
|
|
57
|
+
description?: undefined;
|
|
58
|
+
url?: undefined;
|
|
59
|
+
} | {
|
|
8
60
|
identifier: string;
|
|
9
61
|
displayName: string;
|
|
10
62
|
type: string;
|
|
11
|
-
mediaType: string;
|
|
12
63
|
description: string;
|
|
13
64
|
url: string;
|
|
14
|
-
_meta?: undefined;
|
|
15
65
|
} | {
|
|
66
|
+
extensions: {
|
|
67
|
+
"io.github.Wolfe-Jam.mcp-context-card": {
|
|
68
|
+
iana: string;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
16
71
|
identifier: string;
|
|
17
72
|
displayName: string;
|
|
18
73
|
type: string;
|
|
19
|
-
mediaType: string;
|
|
20
74
|
description: string;
|
|
21
75
|
url: string;
|
|
22
|
-
_meta: {
|
|
23
|
-
"io.github.Wolfe-Jam.mcp-context-card/iana": string;
|
|
24
|
-
};
|
|
25
76
|
})[];
|
|
26
77
|
};
|
package/dist/catalog-gen.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* catalog-gen —
|
|
3
|
-
*
|
|
4
|
-
* Card `_meta` block.
|
|
2
|
+
* catalog-gen — the AI Catalog (`application/ai-catalog+json`, spec 1.0) for
|
|
3
|
+
* this server: its MCP Server Card, plus the sources that also back the Server
|
|
4
|
+
* Card `_meta` block: `AGENTS.md` and `.fafa`, and `project.fafm` only when the
|
|
5
|
+
* project opts in ({@link CatalogOptions.publishMemory}). Memory is written
|
|
6
|
+
* during sessions, and SEP-2127 rules user- or session-specific data out of
|
|
7
|
+
* public discovery documents, so it is left out by default.
|
|
8
|
+
*
|
|
9
|
+
* Identifiers follow `urn:air:{publisher}:{namespace}:{name}`, with the
|
|
10
|
+
* publisher domain and short name taken from the project's own `.fafa`
|
|
11
|
+
* ({@link catalogPublisher}). Served over HTTP without a `.fafa` domain, the
|
|
12
|
+
* request host is the publisher (whoever serves it controls that domain). The
|
|
13
|
+
* committed static file with no domain uses plain `{name}:{namespace}` ids:
|
|
14
|
+
* the spec treats unknown schemes as opaque, and no domain is ever invented.
|
|
15
|
+
*
|
|
16
|
+
* Served (an origin is known): entries point at absolute URLs and the card
|
|
17
|
+
* entry links `<origin>/mcp/server-card`. Static: relative URLs and the card
|
|
18
|
+
* inline as `data` (no remotes, since no origin is known).
|
|
5
19
|
*
|
|
6
20
|
* Descriptions are derived from real file content (section count, fact count,
|
|
7
|
-
* the agent's own description)
|
|
8
|
-
* job `catalog:check` regenerates
|
|
21
|
+
* the agent's own description), not hand-written blurbs that drift. The CI
|
|
22
|
+
* job `catalog:check` regenerates the static file and fails on any diff.
|
|
9
23
|
*/
|
|
10
24
|
import { writeFileSync } from "node:fs";
|
|
11
25
|
import { dirname, join } from "node:path";
|
|
@@ -13,13 +27,21 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
13
27
|
import { parseAgentsMd } from "./agents-md.js";
|
|
14
28
|
import { parseFafm } from "./faf/parse-fafm.js";
|
|
15
29
|
import { parseFafa } from "./faf/parse-fafa.js";
|
|
16
|
-
import { META_NS } from "./identity.js";
|
|
30
|
+
import { META_NS, catalogPublisher, fafaFile } from "./identity.js";
|
|
31
|
+
import { SERVER_CARD_MEDIA_TYPE, SERVER_CARD_PATH } from "./constants.js";
|
|
32
|
+
import { serverCard } from "./server-card.js";
|
|
17
33
|
const iana = (t) => `https://www.iana.org/assignments/media-types/${t}`;
|
|
18
|
-
export function buildCatalog(root) {
|
|
34
|
+
export function buildCatalog(root, opts = {}) {
|
|
19
35
|
const agents = parseAgentsMd(join(root, "AGENTS.md"));
|
|
20
36
|
const fafm = parseFafm(join(root, "project.fafm"));
|
|
21
|
-
const
|
|
22
|
-
const
|
|
37
|
+
const file = fafaFile(root);
|
|
38
|
+
const fafa = file ? parseFafa(file) : null;
|
|
39
|
+
const { domain, handle } = catalogPublisher(root);
|
|
40
|
+
const publisher = domain ?? (opts.origin ? new URL(opts.origin).hostname.toLowerCase() : undefined);
|
|
41
|
+
const id = (namespace) => publisher ? `urn:air:${publisher}:${namespace}:${handle}` : `${handle}:${namespace}`;
|
|
42
|
+
const at = (path) => (opts.origin ? `${opts.origin}/${path}` : `./${path}`);
|
|
43
|
+
const ext = (type) => ({ extensions: { [META_NS]: { iana: iana(type) } } });
|
|
44
|
+
const host = fafa?.displayName ?? fafa?.name ?? "mcp-context-card";
|
|
23
45
|
return {
|
|
24
46
|
specVersion: "1.0",
|
|
25
47
|
host: {
|
|
@@ -28,10 +50,16 @@ export function buildCatalog(root) {
|
|
|
28
50
|
},
|
|
29
51
|
entries: [
|
|
30
52
|
{
|
|
31
|
-
|
|
53
|
+
// The card carries its own title, description and version, so the
|
|
54
|
+
// entry repeats none of them (AI Catalog: avoid drift).
|
|
55
|
+
identifier: id("mcp"),
|
|
56
|
+
type: SERVER_CARD_MEDIA_TYPE,
|
|
57
|
+
...(opts.origin ? { url: `${opts.origin}${SERVER_CARD_PATH}` } : { data: serverCard() }),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
identifier: id("context"),
|
|
32
61
|
displayName: `${host} — project context (AGENTS.md)`,
|
|
33
62
|
type: "text/markdown",
|
|
34
|
-
mediaType: "text/markdown",
|
|
35
63
|
description: agents
|
|
36
64
|
? (() => {
|
|
37
65
|
const h = agents.sections.filter((s) => s.level > 1).map((s) => s.heading);
|
|
@@ -40,33 +68,31 @@ export function buildCatalog(root) {
|
|
|
40
68
|
.join(", ")}${h.length > 6 ? ", …" : ""}.`;
|
|
41
69
|
})()
|
|
42
70
|
: "Agent instructions for this project (AGENTS.md — not present).",
|
|
43
|
-
url: "
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
identifier: `urn:air:${host}:memory`,
|
|
47
|
-
displayName: `${host} — persistent memory (.fafm)`,
|
|
48
|
-
type: "application/vnd.fafm+yaml",
|
|
49
|
-
mediaType: "application/vnd.fafm+yaml",
|
|
50
|
-
description: `Cross-session memory — ${fafm.facts.length} fact(s), profile "${fafm.profile ?? "?"}". Recall survives a process restart. No de-facto standard for this concern yet.`,
|
|
51
|
-
url: "./project.fafm",
|
|
52
|
-
_meta: { [`${META_NS}/iana`]: iana("application/vnd.fafm+yaml") },
|
|
71
|
+
url: at("AGENTS.md"),
|
|
53
72
|
},
|
|
73
|
+
...(opts.publishMemory ? [{
|
|
74
|
+
identifier: id("memory"),
|
|
75
|
+
displayName: `${host} — persistent memory (.fafm)`,
|
|
76
|
+
type: "application/vnd.fafm+yaml",
|
|
77
|
+
description: `Cross-session memory — ${fafm.facts.length} fact(s), profile "${fafm.profile ?? "?"}". Recall survives a process restart. No de-facto standard for this concern yet.`,
|
|
78
|
+
url: at("project.fafm"),
|
|
79
|
+
...ext("application/vnd.fafm+yaml"),
|
|
80
|
+
}] : []),
|
|
54
81
|
{
|
|
55
|
-
identifier:
|
|
82
|
+
identifier: id("identity"),
|
|
56
83
|
displayName: `${host} — agent identity (.fafa)`,
|
|
57
84
|
type: "application/vnd.fafa+yaml",
|
|
58
|
-
mediaType: "application/vnd.fafa+yaml",
|
|
59
85
|
description: fafa?.description ??
|
|
60
86
|
`Agent identity card (status: ${fafa?.status ?? "unknown"}).`,
|
|
61
|
-
url: "
|
|
62
|
-
|
|
87
|
+
url: at(".well-known/fafa"),
|
|
88
|
+
...ext("application/vnd.fafa+yaml"),
|
|
63
89
|
},
|
|
64
90
|
],
|
|
65
91
|
};
|
|
66
92
|
}
|
|
67
|
-
// Direct run → write the file.
|
|
93
|
+
// Direct run → write the static file (no origin: relative URLs, card inline).
|
|
68
94
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
69
95
|
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
70
96
|
writeFileSync(join(root, ".well-known/ai-catalog.json"), JSON.stringify(buildCatalog(root), null, 2) + "\n");
|
|
71
|
-
console.log("wrote .well-known/ai-catalog.json —
|
|
97
|
+
console.log("wrote .well-known/ai-catalog.json — Server Card + AGENTS.md + .fafa entries (memory is opt-in, never in the static file)");
|
|
72
98
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* conformance/discovery — a portable checker for MCP Server Card discovery
|
|
3
|
+
* (SEP-2127, Final), the AI Catalog (spec 1.0), and the Streamable HTTP
|
|
4
|
+
* transport's security rules, run against any server URL.
|
|
5
|
+
*
|
|
6
|
+
* Self-contained on purpose: no imports from this package, only the global
|
|
7
|
+
* `fetch` (and `node:http` for the one check that must forge a Host header,
|
|
8
|
+
* which `fetch` drops), so the file can be lifted into another tool as-is.
|
|
9
|
+
* The transport checks send one JSON-RPC `ping`, which changes nothing. Point it at a
|
|
10
|
+
* server's Streamable HTTP endpoint and it returns one result per requirement,
|
|
11
|
+
* each tagged MUST or SHOULD exactly as the spec words it. Requirements the
|
|
12
|
+
* spec leaves at MAY (where the card lives, whether a catalog exists) are
|
|
13
|
+
* never failures: a missing optional document turns its checks into skips.
|
|
14
|
+
*
|
|
15
|
+
* JSON Schema validation is optional: pass `validateCard` (for example Ajv
|
|
16
|
+
* against the official v1 schema) and it runs as one more card check.
|
|
17
|
+
*/
|
|
18
|
+
export declare const SERVER_CARD_SCHEMA_URL = "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json";
|
|
19
|
+
export declare const SERVER_CARD_TYPE = "application/mcp-server-card+json";
|
|
20
|
+
export declare const AI_CATALOG_TYPE = "application/ai-catalog+json";
|
|
21
|
+
export type Tier = "card" | "hosting" | "catalog" | "transport";
|
|
22
|
+
export type Level = "must" | "should";
|
|
23
|
+
export type Status = "pass" | "fail" | "skip";
|
|
24
|
+
export interface CheckResult {
|
|
25
|
+
id: string;
|
|
26
|
+
tier: Tier;
|
|
27
|
+
level: Level;
|
|
28
|
+
title: string;
|
|
29
|
+
status: Status;
|
|
30
|
+
detail?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface DiscoveryTarget {
|
|
33
|
+
/** The server's Streamable HTTP endpoint, e.g. `https://ctx.example.com/mcp`. */
|
|
34
|
+
mcpUrl: string;
|
|
35
|
+
/** Where to read the card. Default: `<mcpUrl>/server-card`, then the catalog's card entry. */
|
|
36
|
+
cardUrl?: string;
|
|
37
|
+
/** Where to read the catalog. Default: `<origin>/.well-known/ai-catalog.json`. */
|
|
38
|
+
catalogUrl?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface DiscoveryOptions {
|
|
41
|
+
fetch?: typeof fetch;
|
|
42
|
+
/** Returns null when the card is valid, else a short error. */
|
|
43
|
+
validateCard?: (card: unknown) => string | null;
|
|
44
|
+
/** Per-request timeout in milliseconds (default 10000). */
|
|
45
|
+
timeoutMs?: number;
|
|
46
|
+
/** POST with exact headers (Host included); returns the status, or null. Default: node:http. */
|
|
47
|
+
rawStatus?: (url: string, headers: Record<string, string>, body: string, timeoutMs: number) => Promise<number | null>;
|
|
48
|
+
}
|
|
49
|
+
export interface DiscoveryReport {
|
|
50
|
+
mcpUrl: string;
|
|
51
|
+
cardUrl: string | null;
|
|
52
|
+
catalogUrl: string;
|
|
53
|
+
results: CheckResult[];
|
|
54
|
+
passed: number;
|
|
55
|
+
failed: number;
|
|
56
|
+
skipped: number;
|
|
57
|
+
/** Failed MUST checks: zero means nothing the spec requires is broken. */
|
|
58
|
+
mustFailures: number;
|
|
59
|
+
}
|
|
60
|
+
export declare function checkDiscovery(target: DiscoveryTarget, opts?: DiscoveryOptions): Promise<DiscoveryReport>;
|