mcp-context-card 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.well-known/ai-catalog.json +39 -0
- package/.well-known/fafa +18 -0
- package/AGENTS.md +85 -0
- package/CHANGELOG.md +75 -0
- package/LICENSE +21 -0
- package/README.md +192 -0
- package/dist/agents-md.d.ts +22 -0
- package/dist/agents-md.js +65 -0
- package/dist/author.d.ts +11 -0
- package/dist/author.js +27 -0
- package/dist/bin.d.ts +21 -0
- package/dist/bin.js +70 -0
- package/dist/card-gen.d.ts +1 -0
- package/dist/card-gen.js +14 -0
- package/dist/catalog-gen.d.ts +26 -0
- package/dist/catalog-gen.js +72 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +5 -0
- package/dist/faf/parse-fafa.d.ts +2 -0
- package/dist/faf/parse-fafa.js +32 -0
- package/dist/faf/parse-fafm.d.ts +7 -0
- package/dist/faf/parse-fafm.js +119 -0
- package/dist/faf/types.d.ts +34 -0
- package/dist/faf/types.js +7 -0
- package/dist/identity.d.ts +35 -0
- package/dist/identity.js +82 -0
- package/dist/md.d.ts +22 -0
- package/dist/md.js +186 -0
- package/dist/memory.d.ts +13 -0
- package/dist/memory.js +12 -0
- package/dist/render-card.d.ts +10 -0
- package/dist/render-card.js +175 -0
- package/dist/server.d.ts +56 -0
- package/dist/server.js +250 -0
- package/dist/transport/http.d.ts +2 -0
- package/dist/transport/http.js +74 -0
- package/docs/MECHANISMS.md +137 -0
- package/docs/TRANSPORT.md +86 -0
- package/docs/WIRING.md +97 -0
- package/docs/card.html +121 -0
- package/docs/img/card.png +0 -0
- package/package.json +77 -0
- package/project.faf +28 -0
- package/project.fafm +46 -0
- package/server.json +49 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"specVersion": "1.0",
|
|
3
|
+
"host": {
|
|
4
|
+
"displayName": "mcp-context-card",
|
|
5
|
+
"identifier": "https://github.com/Wolfe-Jam/mcp-context-card"
|
|
6
|
+
},
|
|
7
|
+
"entries": [
|
|
8
|
+
{
|
|
9
|
+
"identifier": "urn:air:mcp-context-card:context",
|
|
10
|
+
"displayName": "mcp-context-card — project context (AGENTS.md)",
|
|
11
|
+
"type": "text/markdown",
|
|
12
|
+
"mediaType": "text/markdown",
|
|
13
|
+
"description": "Agent instructions for this project — 9 section(s): Setup, Build, Test, Layout, Conventions, The invariant, ….",
|
|
14
|
+
"url": "./AGENTS.md"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"identifier": "urn:air:mcp-context-card:memory",
|
|
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 — 3 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",
|
|
29
|
+
"displayName": "mcp-context-card — agent identity (.fafa)",
|
|
30
|
+
"type": "application/vnd.fafa+yaml",
|
|
31
|
+
"mediaType": "application/vnd.fafa+yaml",
|
|
32
|
+
"description": "Reference MCP server that makes a project's context (AGENTS.md), memory, and identity discoverable to any MCP client, through the Server Card _meta block and a self-published ai-catalog.json.",
|
|
33
|
+
"url": "./.well-known/fafa",
|
|
34
|
+
"_meta": {
|
|
35
|
+
"io.github.wolfe-jam.mcp-context-card/iana": "https://www.iana.org/assignments/media-types/application/vnd.fafa+yaml"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
package/.well-known/fafa
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Agent identity card (.fafa) — mcp-context-card's own identity
|
|
2
|
+
# Media type: application/vnd.fafa+yaml
|
|
3
|
+
# This is the identity concern's worked example: `whoami` reads this file,
|
|
4
|
+
# and it's the third entry in the Server Card _meta block and ai-catalog.
|
|
5
|
+
# A deployment pointed at a real project replaces it with that agent's identity.
|
|
6
|
+
|
|
7
|
+
version: "1.0"
|
|
8
|
+
agent:
|
|
9
|
+
name: "mcp-context-card"
|
|
10
|
+
displayName: "mcp-context-card"
|
|
11
|
+
vendor: "reference"
|
|
12
|
+
version: "0.5.0"
|
|
13
|
+
description: >-
|
|
14
|
+
Reference MCP server that makes a project's context (AGENTS.md), memory,
|
|
15
|
+
and identity discoverable to any MCP client, through the Server Card _meta
|
|
16
|
+
block and a self-published ai-catalog.json.
|
|
17
|
+
status: "reference"
|
|
18
|
+
license: "MIT"
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
`mcp-context-card` is an MCP server that makes a project's **context**
|
|
4
|
+
(this file), **memory**, and **identity** discoverable to any MCP client —
|
|
5
|
+
through the two surfaces already in the ecosystem: the Server Card `_meta`
|
|
6
|
+
block and `ai-catalog.json` sibling entries.
|
|
7
|
+
|
|
8
|
+
`read_agents_md` serves this file, section by section, over the same MCP
|
|
9
|
+
connection.
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm ci
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Node 22 or newer. No other system dependencies.
|
|
18
|
+
|
|
19
|
+
## Build
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm run build # tsc -p tsconfig.build.json → dist/
|
|
23
|
+
npm run typecheck # tsc --noEmit over src/ + test/
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Test
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm test # node:test — every test/*.test.ts
|
|
30
|
+
npm run test:coverage # + the coverage gate (lines 90 / funcs 85 / branches 80, src/ only)
|
|
31
|
+
npm run demo # end to end: all tools over stdio, then over stateless HTTP
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
CI runs `typecheck → build → test:coverage → demo` on Linux, macOS, and
|
|
35
|
+
Windows for every push and PR to `main` (`.github/workflows/ci.yml`).
|
|
36
|
+
|
|
37
|
+
## Layout
|
|
38
|
+
|
|
39
|
+
| Path | What |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `src/server.ts` | the MCP server — the nine tools + the Server Card resource |
|
|
42
|
+
| `src/agents-md.ts` | reads and section-splits this file |
|
|
43
|
+
| `src/author.ts` | `author_agents_md` — wraps `agents-md-facts` (the AGENTS.md authoring engine) |
|
|
44
|
+
| `src/md.ts` | a minimal dependency-free Markdown → HTML renderer |
|
|
45
|
+
| `src/render-card.ts` | the card — identity + this file + memory + discovery, as one HTML page |
|
|
46
|
+
| `src/memory.ts` → `src/faf/parse-fafm.ts` | file-backed `remember` / `recall` / `forget` |
|
|
47
|
+
| `src/identity.ts` | `whoami` (`.fafa` → `package.json` fallback) + the `_meta` context block |
|
|
48
|
+
| `src/catalog-gen.ts` | writes `.well-known/ai-catalog.json` from the same three sources |
|
|
49
|
+
| `src/transport/http.ts` | the stateless Streamable HTTP app (Hono) |
|
|
50
|
+
| `src/bin.ts` | the entry point (`resolveLaunch`) — `stdio` · `--http` · `card` |
|
|
51
|
+
| `src/faf/parse-fafm.ts` · `parse-fafa.ts` | the `.fafm` / `.fafa` parsers |
|
|
52
|
+
|
|
53
|
+
## Conventions
|
|
54
|
+
|
|
55
|
+
- TypeScript strict, ESM only (`"type": "module"`, `.js` import specifiers).
|
|
56
|
+
- Tests use `node:test` + `node:assert/strict` — no test framework.
|
|
57
|
+
- Every source file opens with a comment stating what it is and why.
|
|
58
|
+
- Conventional Commit messages (`feat:`, `fix:`, `chore:`, `test:`, `docs:`).
|
|
59
|
+
|
|
60
|
+
## The invariant
|
|
61
|
+
|
|
62
|
+
`src/identity.ts`, `src/catalog-gen.ts`, and `src/render-card.ts` all describe
|
|
63
|
+
the **same three sources**: this file, `project.fafm`, `.well-known/fafa`.
|
|
64
|
+
Change what one exposes and you must change the others. `npm run catalog:check`
|
|
65
|
+
and `npm run card:check` enforce it in CI — each regenerates its surface and
|
|
66
|
+
fails on any diff.
|
|
67
|
+
|
|
68
|
+
## Safety
|
|
69
|
+
|
|
70
|
+
- Branch off `main`; CI must be green before merge.
|
|
71
|
+
- `npm run demo` writes a fact to `project.fafm` and restores the file on
|
|
72
|
+
exit — don't kill it mid-run.
|
|
73
|
+
- No secrets live in this repo; never add any.
|
|
74
|
+
|
|
75
|
+
## Definition of done
|
|
76
|
+
|
|
77
|
+
`npm run typecheck && npm run build && npm test && npm run demo` all green,
|
|
78
|
+
plus `npm run catalog:check` and `npm run card:check` clean if you touched
|
|
79
|
+
`AGENTS.md`, `project.fafm`, or `.well-known/fafa`.
|
|
80
|
+
|
|
81
|
+
## Authoring this file
|
|
82
|
+
|
|
83
|
+
`AGENTS.md` here is maintained by hand. It can also be generated from the
|
|
84
|
+
repo's `project.faf` with `faf export --agents` — the server doesn't care how
|
|
85
|
+
the file was authored, only that it's valid Markdown.
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project. Adheres to [Semantic Versioning](https://semver.org).
|
|
4
|
+
|
|
5
|
+
## 0.5.0
|
|
6
|
+
|
|
7
|
+
The first public release — installable, and settling in the open before a
|
|
8
|
+
`1.0.0` cut. `faf-trinity` v0.1.0 was a private demo; this is the versioned,
|
|
9
|
+
tested server.
|
|
10
|
+
|
|
11
|
+
### The tools
|
|
12
|
+
|
|
13
|
+
- **context** — `read_agents_md` (whole file or one section by heading),
|
|
14
|
+
`list_agents_md_sections`, and `author_agents_md` (draft one when there
|
|
15
|
+
isn't one, via `agents-md-facts`). The register the other two serve is
|
|
16
|
+
`AGENTS.md`.
|
|
17
|
+
- **memory** — `remember`, `recall`, `forget`. File-backed against a `.fafm`;
|
|
18
|
+
a fact survives a full server-process restart.
|
|
19
|
+
- **identity** — `whoami`. From the server's `.well-known/fafa`, or
|
|
20
|
+
`package.json` when there isn't one.
|
|
21
|
+
- **discovery** — `list_context_sources`. What the project publishes, in what
|
|
22
|
+
media types, through which surface.
|
|
23
|
+
- **the card** — `render_context_card`, `GET /card`, and `npx mcp-context-card
|
|
24
|
+
card` (renders the current directory's card to stdout — no host, no config).
|
|
25
|
+
Identity + `AGENTS.md` + memory + discovery as one self-contained HTML page
|
|
26
|
+
(inline CSS, no JS, no external anything). Light / dark / auto; the accent
|
|
27
|
+
defaults to the AAIF palette and takes any hex. `npm run card` writes
|
|
28
|
+
`docs/card.html`; `card:check` fails on drift, in CI. Rendered by `src/md.ts`
|
|
29
|
+
— a ~200-line dependency-free Markdown renderer.
|
|
30
|
+
|
|
31
|
+
### Exposure
|
|
32
|
+
|
|
33
|
+
- Server Card `_meta` block — publisher-namespaced keys
|
|
34
|
+
(`io.github.wolfe-jam.mcp-context-card/{context,memory,identity}`), one per
|
|
35
|
+
concern. `context` points at `AGENTS.md` / `text/markdown`.
|
|
36
|
+
- `mcp-context-card://server-card` MCP resource (in-band) +
|
|
37
|
+
`GET /.well-known/mcp/server-card` (out-of-band, HTTP transport).
|
|
38
|
+
- `GET /.well-known/ai-catalog.json` — three sibling entries, keyed by media
|
|
39
|
+
type, derived from the same three sources. `npm run catalog:check` fails on
|
|
40
|
+
drift, in CI.
|
|
41
|
+
|
|
42
|
+
### Transport
|
|
43
|
+
|
|
44
|
+
- stdio (default) and stateless Streamable HTTP (`--http` / `PORT`);
|
|
45
|
+
`src/bin.ts` `resolveLaunch()` selects the mode. `MCP_CONTEXT_CARD_ROOT` points
|
|
46
|
+
the server at any project.
|
|
47
|
+
|
|
48
|
+
### Engineering
|
|
49
|
+
|
|
50
|
+
- 88 tests across Linux / macOS / Windows, coverage-gated
|
|
51
|
+
(lines 90 / funcs 85 / branches 80, `src/` only). A real `child_process`
|
|
52
|
+
spawn proves memory across a genuine process boundary; stdio/HTTP
|
|
53
|
+
tool-surface parity is asserted.
|
|
54
|
+
- Build emits `dist/` (`tsconfig.build.json`); `exports` map, shipped type
|
|
55
|
+
declarations. `docs/MECHANISMS.md`, `docs/WIRING.md`, `docs/TRANSPORT.md`;
|
|
56
|
+
`examples/` (Dockerfile, compose, client configs). `server.json` registry
|
|
57
|
+
manifest.
|
|
58
|
+
- `author_agents_md` wraps [`agents-md-facts`](https://github.com/Wolfe-Jam/agents-md-facts)
|
|
59
|
+
— a published, standalone AGENTS.md authoring engine (real commands / entry
|
|
60
|
+
points / conventions, nothing invented; `--check` keeps it true).
|
|
61
|
+
- `whoami` falls back to `package.json` when a project has no `.well-known/fafa`.
|
|
62
|
+
|
|
63
|
+
### Since v0.1.0
|
|
64
|
+
|
|
65
|
+
- Renamed `faf-trinity` → `mcp-context-card`.
|
|
66
|
+
- Context concern now leads with `AGENTS.md`, not a FAF format; the FAF formats
|
|
67
|
+
are the worked examples for memory and identity, where no standard exists.
|
|
68
|
+
- The `_meta` block and `catalog-gen` carry real data, not a `console.log` and
|
|
69
|
+
a hardcoded object.
|
|
70
|
+
|
|
71
|
+
## v0.1.0 (2026-08-12)
|
|
72
|
+
|
|
73
|
+
- Initial private reference implementation (as `faf-trinity`): project context,
|
|
74
|
+
persistent memory, and agent identity in one MCP server, through two
|
|
75
|
+
mechanisms already live in production. `demo.ts` proved all three.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 James Wolfe
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# mcp-context-card
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Wolfe-Jam/mcp-context-card/actions/workflows/ci.yml)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
|
|
6
|
+
An MCP server that makes a project's context, memory, and identity discoverable
|
|
7
|
+
to any MCP client — and renders them as one card you can read.
|
|
8
|
+
|
|
9
|
+
- **context** — the project's `AGENTS.md`, served whole or one section at a time
|
|
10
|
+
- **memory** — facts that persist across sessions, in a file
|
|
11
|
+
- **identity** — what this server is, from its own agent card
|
|
12
|
+
|
|
13
|
+
Discovery goes through two surfaces already in the ecosystem: the Server Card
|
|
14
|
+
`_meta` block and `ai-catalog.json` sibling entries.
|
|
15
|
+
|
|
16
|
+
## What it is / what it is not
|
|
17
|
+
|
|
18
|
+
**It is** — an MCP server for a project's `AGENTS.md`, memory, and identity: nine
|
|
19
|
+
tools, two discovery surfaces (Server Card `_meta`, `ai-catalog.json`), and a
|
|
20
|
+
rendered [card](#the-card). Small, MIT — read it, `npx` it, or fork it.
|
|
21
|
+
|
|
22
|
+
**It is not**
|
|
23
|
+
|
|
24
|
+
- a framework or a platform — three concerns, nothing more
|
|
25
|
+
- a file, shell, or search tool — it never touches your files or runs commands
|
|
26
|
+
- tied to FAF — context is plain Markdown (`AGENTS.md`); the memory and identity
|
|
27
|
+
formats are swappable examples
|
|
28
|
+
|
|
29
|
+
It composes:
|
|
30
|
+
|
|
31
|
+
- **serve · discover · render** — this server
|
|
32
|
+
- **author · keep true** — [`agents-md-facts`](https://github.com/Wolfe-Jam/agents-md-facts) (the `author_agents_md` tool wraps it)
|
|
33
|
+
- **files · shell · git** — [`server-filesystem`](https://github.com/modelcontextprotocol/servers), [`server-git`](https://github.com/modelcontextprotocol/servers) / github‑mcp‑server, your test runner's MCP
|
|
34
|
+
|
|
35
|
+
A piece, not the toolbox.
|
|
36
|
+
|
|
37
|
+
## The card
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
The same three sources render as one self‑contained HTML page — identity,
|
|
42
|
+
`AGENTS.md`, memory, and how a machine fetches it. The view for people:
|
|
43
|
+
screenshot it, drop it in a PR, put it on a status page.
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
GET /card # live, on the HTTP transport
|
|
47
|
+
GET /card?theme=light&accent=%230066cc
|
|
48
|
+
npx mcp-context-card card # or: npm run card → docs/card.html
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Light, dark, or auto; the accent defaults to the AAIF palette and takes any hex.
|
|
52
|
+
[docs/card.html](./docs/card.html) is this repo's, rendered.
|
|
53
|
+
|
|
54
|
+
## Who it's for
|
|
55
|
+
|
|
56
|
+
| You want… | Reach for |
|
|
57
|
+
|---|---|
|
|
58
|
+
| an `AGENTS.md` and you don't have one | `author_agents_md` — or [`npx agents-md-facts`](https://github.com/Wolfe-Jam/agents-md-facts) |
|
|
59
|
+
| your agent to pull *one* `AGENTS.md` section on demand, not the whole file | `read_agents_md` · `list_agents_md_sections` |
|
|
60
|
+
| a persistent notepad for your agent — survives restarts, no setup | `remember` · `recall` · `forget` |
|
|
61
|
+
| a shareable view of what your MCP server exposes to agents | `GET /card` · `npx mcp-context-card card` |
|
|
62
|
+
| the two‑surface discovery pattern to copy into your own server | read `src/` |
|
|
63
|
+
|
|
64
|
+
## Add it to your setup
|
|
65
|
+
|
|
66
|
+
### No `AGENTS.md` yet?
|
|
67
|
+
|
|
68
|
+
The `author_agents_md` tool authors one from your repo's facts — real
|
|
69
|
+
build/test commands, entry points, toolchain conventions — and hands the agent
|
|
70
|
+
the draft. It's a thin wrapper over
|
|
71
|
+
[`agents-md-facts`](https://github.com/Wolfe-Jam/agents-md-facts); to author or
|
|
72
|
+
keep one true outside a session:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx agents-md-facts # author / refresh AGENTS.md
|
|
76
|
+
npx agents-md-facts --check # fail if missing or stale (CI, pre-commit)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
A `project.faf` is the next rung — a structured source that refreshes the file.
|
|
80
|
+
Short model: [`agents-md-facts/docs/BETTER-BEST.md`](https://github.com/Wolfe-Jam/agents-md-facts/blob/main/docs/BETTER-BEST.md).
|
|
81
|
+
|
|
82
|
+
### See the card
|
|
83
|
+
|
|
84
|
+
One command, no host, no config:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npx mcp-context-card card > card.html
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Wire it into a host
|
|
91
|
+
|
|
92
|
+
Claude Desktop, Cursor, or any stdio host:
|
|
93
|
+
|
|
94
|
+
```jsonc
|
|
95
|
+
{
|
|
96
|
+
"mcpServers": {
|
|
97
|
+
"context-card": {
|
|
98
|
+
"command": "npx",
|
|
99
|
+
"args": ["-y", "mcp-context-card"],
|
|
100
|
+
"env": { "MCP_CONTEXT_CARD_ROOT": "/abs/path/to/your/project" }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`MCP_CONTEXT_CARD_ROOT` points at the directory with your `AGENTS.md`. The
|
|
107
|
+
memory tools work with or without it; identity is optional. Over HTTP instead:
|
|
108
|
+
`PORT=8080 npx mcp-context-card`. Full wiring is in
|
|
109
|
+
[docs/WIRING.md](./docs/WIRING.md); transport choice in
|
|
110
|
+
[docs/TRANSPORT.md](./docs/TRANSPORT.md).
|
|
111
|
+
|
|
112
|
+
## Why
|
|
113
|
+
|
|
114
|
+
`AGENTS.md` is the de-facto standard for telling a coding agent how to work in a
|
|
115
|
+
repo. But a client has to *know the file exists* and read the whole thing into
|
|
116
|
+
context. There is no standard way for a server to say "here is my AGENTS.md,
|
|
117
|
+
here is what I remember, here is who I am" — so every server that wants this
|
|
118
|
+
grows its own shape.
|
|
119
|
+
|
|
120
|
+
`mcp-context-card` answers all three through mechanisms that already exist:
|
|
121
|
+
|
|
122
|
+
1. **Server Card `_meta`** ([SEP‑2127](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127)) —
|
|
123
|
+
one reverse‑DNS‑namespaced key per concern, readable in‑band as an MCP
|
|
124
|
+
resource and at `GET /.well-known/mcp/server-card`.
|
|
125
|
+
2. **`ai-catalog.json`** — sibling entries keyed by media type, at
|
|
126
|
+
`GET /.well-known/ai-catalog.json`.
|
|
127
|
+
|
|
128
|
+
The context concern points at `AGENTS.md` (`text/markdown`). Memory and identity
|
|
129
|
+
have no de‑facto standard yet, so the examples here use
|
|
130
|
+
[`.fafm`](https://doi.org/10.5281/zenodo.20348942) and
|
|
131
|
+
[`.fafa`](https://doi.org/10.5281/zenodo.21951641) — one instantiation each,
|
|
132
|
+
swap in your own.
|
|
133
|
+
|
|
134
|
+
The wire‑level detail is in [docs/MECHANISMS.md](./docs/MECHANISMS.md).
|
|
135
|
+
|
|
136
|
+
## Tools
|
|
137
|
+
|
|
138
|
+
| Tool | What it's for |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `author_agents_md` | draft an `AGENTS.md` from the repo's facts (via `agents-md-facts`) — a managed block, ready to drop in |
|
|
141
|
+
| `read_agents_md` | return the project's `AGENTS.md` — whole, or one section by heading |
|
|
142
|
+
| `list_agents_md_sections` | the headings, so a client pulls one section instead of the whole file |
|
|
143
|
+
| `remember` | write a fact that will still be there next session |
|
|
144
|
+
| `recall` | read a fact stored in a previous session |
|
|
145
|
+
| `forget` | drop or correct a stale fact |
|
|
146
|
+
| `whoami` | this server's name, vendor, version, status, license |
|
|
147
|
+
| `list_context_sources` | what this project publishes, in what media types, via which surface |
|
|
148
|
+
| `render_context_card` | the whole card as one self‑contained HTML page (also `GET /card`) |
|
|
149
|
+
|
|
150
|
+
## The demo
|
|
151
|
+
|
|
152
|
+
`npm run demo` runs every tool over both transports:
|
|
153
|
+
|
|
154
|
+
1. **Context** — list the `AGENTS.md` sections, then pull just `## Test`.
|
|
155
|
+
2. **Memory** — `remember()` a fact, stop the server process, start a new one,
|
|
156
|
+
`recall()` the same fact. Only the file carries it across.
|
|
157
|
+
3. **Identity** — `whoami()`, and the Server Card `_meta` block read back from a
|
|
158
|
+
live client.
|
|
159
|
+
4. **Discovery** — `list_context_sources()`, then the same server over stateless
|
|
160
|
+
HTTP with its `.well-known` routes and `GET /card`.
|
|
161
|
+
|
|
162
|
+
88 tests on Linux, macOS, and Windows, coverage‑gated in CI. One spawns a real
|
|
163
|
+
child process and checks a remembered fact survives the restart; another checks
|
|
164
|
+
the stdio and HTTP tool surfaces match.
|
|
165
|
+
|
|
166
|
+
## Layout
|
|
167
|
+
|
|
168
|
+
| Path | What |
|
|
169
|
+
|---|---|
|
|
170
|
+
| `src/server.ts` | the nine tools + the Server Card resource |
|
|
171
|
+
| `src/agents-md.ts` | reads and section‑splits `AGENTS.md` |
|
|
172
|
+
| `src/author.ts` | `author_agents_md` — wraps [`agents-md-facts`](https://github.com/Wolfe-Jam/agents-md-facts) |
|
|
173
|
+
| `src/md.ts` | a minimal dependency‑free Markdown → HTML renderer |
|
|
174
|
+
| `src/render-card.ts` | the card — identity + `AGENTS.md` + memory + discovery, as one HTML page |
|
|
175
|
+
| `src/memory.ts` | file‑backed `remember` / `recall` / `forget` |
|
|
176
|
+
| `src/identity.ts` | `whoami` (`.fafa` → `package.json` fallback) + the `_meta` block |
|
|
177
|
+
| `src/catalog-gen.ts` | writes `ai-catalog.json` from the same three sources |
|
|
178
|
+
| `src/transport/http.ts` | the stateless Streamable HTTP app (Hono) |
|
|
179
|
+
| `src/bin.ts` | the entry point — `stdio` · `--http` · `card` |
|
|
180
|
+
|
|
181
|
+
## Related
|
|
182
|
+
|
|
183
|
+
- [Server Card SEP‑2127](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) · [ai-catalog](https://github.com/Agent-Card/ai-catalog) · [AGENTS.md](https://agents.md)
|
|
184
|
+
- [`mcp-project-context`](https://github.com/Wolfe-Jam/mcp-project-context) — an earlier take on the context concern alone
|
|
185
|
+
- `text/markdown` (AGENTS.md) · `application/vnd.fafm+yaml` · `application/vnd.fafa+yaml`
|
|
186
|
+
|
|
187
|
+
## License
|
|
188
|
+
|
|
189
|
+
MIT.
|
|
190
|
+
|
|
191
|
+
This repo dogfoods what it serves — its `AGENTS.md` is a real, current file, and
|
|
192
|
+
it ships a `project.faf` as the structured source behind it.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface AgentsSection {
|
|
2
|
+
/** heading text, verbatim, without the leading `#`s */
|
|
3
|
+
heading: string;
|
|
4
|
+
/** heading depth, 1–6 */
|
|
5
|
+
level: number;
|
|
6
|
+
/** everything under this heading up to the next heading, trimmed */
|
|
7
|
+
body: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AgentsMd {
|
|
10
|
+
/** the file, byte-for-byte */
|
|
11
|
+
raw: string;
|
|
12
|
+
/** any text before the first heading (often a one-line intro) */
|
|
13
|
+
preamble: string;
|
|
14
|
+
sections: AgentsSection[];
|
|
15
|
+
}
|
|
16
|
+
export declare function parseAgentsMd(path: string): AgentsMd | null;
|
|
17
|
+
export declare function fromString(raw: string): AgentsMd;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve a section by heading: exact (case-insensitive) first, then prefix,
|
|
20
|
+
* then substring. Returns null if nothing matches.
|
|
21
|
+
*/
|
|
22
|
+
export declare function findSection(doc: AgentsMd, query: string): AgentsSection | null;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agents-md — read a project's AGENTS.md.
|
|
3
|
+
*
|
|
4
|
+
* AGENTS.md is the de-facto standard for telling a coding agent how to work
|
|
5
|
+
* in a repo: setup, build, test, conventions, safety. It is plain Markdown.
|
|
6
|
+
*
|
|
7
|
+
* A client today has to know the file exists and read the whole thing into
|
|
8
|
+
* context. This splits it into addressable sections by heading so a client
|
|
9
|
+
* can pull just "## Testing" on demand. No Markdown-parser dependency —
|
|
10
|
+
* a heading is a `^#{1,6} ` line outside a fenced code block.
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
const HEADING = /^(#{1,6})\s+(.+?)\s*#*\s*$/;
|
|
14
|
+
const FENCE = /^\s*(```|~~~)/;
|
|
15
|
+
export function parseAgentsMd(path) {
|
|
16
|
+
try {
|
|
17
|
+
return fromString(readFileSync(path, "utf8"));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function fromString(raw) {
|
|
24
|
+
const sections = [];
|
|
25
|
+
const preamble = [];
|
|
26
|
+
let cur = null;
|
|
27
|
+
let inFence = false;
|
|
28
|
+
const flush = () => {
|
|
29
|
+
if (cur) {
|
|
30
|
+
sections.push({ heading: cur.heading, level: cur.level, body: cur.body.join("\n").trim() });
|
|
31
|
+
cur = null;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
35
|
+
if (FENCE.test(line))
|
|
36
|
+
inFence = !inFence;
|
|
37
|
+
const m = inFence ? null : line.match(HEADING);
|
|
38
|
+
if (m) {
|
|
39
|
+
flush();
|
|
40
|
+
cur = { heading: m[2].trim(), level: m[1].length, body: [] };
|
|
41
|
+
}
|
|
42
|
+
else if (cur) {
|
|
43
|
+
cur.body.push(line);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
preamble.push(line);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
flush();
|
|
50
|
+
return { raw, preamble: preamble.join("\n").trim(), sections };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a section by heading: exact (case-insensitive) first, then prefix,
|
|
54
|
+
* then substring. Returns null if nothing matches.
|
|
55
|
+
*/
|
|
56
|
+
export function findSection(doc, query) {
|
|
57
|
+
const q = query.trim().toLowerCase();
|
|
58
|
+
if (!q)
|
|
59
|
+
return null;
|
|
60
|
+
const h = (s) => s.heading.toLowerCase();
|
|
61
|
+
return (doc.sections.find((s) => h(s) === q) ??
|
|
62
|
+
doc.sections.find((s) => h(s).startsWith(q)) ??
|
|
63
|
+
doc.sections.find((s) => h(s).includes(q)) ??
|
|
64
|
+
null);
|
|
65
|
+
}
|
package/dist/author.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The markers `agents-md-facts` uses to bound its managed block. */
|
|
2
|
+
export declare const BLOCK_START = "<!-- agents:from-facts:start -->";
|
|
3
|
+
export declare const BLOCK_END = "<!-- agents:from-facts:end -->";
|
|
4
|
+
export interface Authored {
|
|
5
|
+
/** the AGENTS.md text — a managed block, ready to drop in */
|
|
6
|
+
markdown: string;
|
|
7
|
+
/** whether an AGENTS.md already exists at the target */
|
|
8
|
+
exists: boolean;
|
|
9
|
+
}
|
|
10
|
+
/** Author a managed AGENTS.md block for `root`, from its repo facts. */
|
|
11
|
+
export declare function authorAgentsMd(root: string): Authored;
|
package/dist/author.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* author — author an AGENTS.md for a project from its repo facts.
|
|
3
|
+
*
|
|
4
|
+
* The engine is `agents-md-facts` (a published, standalone tool, AGENTS.md
|
|
5
|
+
* BETTER tier): it detects real build/test commands, entry points, toolchain
|
|
6
|
+
* conventions and nothing invented. This wraps it in the managed-block
|
|
7
|
+
* markers so `agents-md-facts --check` (or its Action / pre-commit hook) can
|
|
8
|
+
* keep the result true afterwards.
|
|
9
|
+
*
|
|
10
|
+
* A `project.faf` is the BEST tier — the same discipline plus a structured
|
|
11
|
+
* source of truth that refreshes the file. This wrapper stays at BETTER; the
|
|
12
|
+
* README points at the BEST path.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { authorAgentsMd as authorBlock, buildRepoContext } from "agents-md-facts";
|
|
17
|
+
/** The markers `agents-md-facts` uses to bound its managed block. */
|
|
18
|
+
export const BLOCK_START = "<!-- agents:from-facts:start -->";
|
|
19
|
+
export const BLOCK_END = "<!-- agents:from-facts:end -->";
|
|
20
|
+
/** Author a managed AGENTS.md block for `root`, from its repo facts. */
|
|
21
|
+
export function authorAgentsMd(root) {
|
|
22
|
+
const block = authorBlock(buildRepoContext(root)).trim();
|
|
23
|
+
return {
|
|
24
|
+
markdown: `${BLOCK_START}\n${block}\n${BLOCK_END}\n`,
|
|
25
|
+
exists: existsSync(join(root, "AGENTS.md")),
|
|
26
|
+
};
|
|
27
|
+
}
|
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export type Mode = "stdio" | "http" | "card";
|
|
3
|
+
export interface Launch {
|
|
4
|
+
mode: Mode;
|
|
5
|
+
/** port for http mode (ignored otherwise). */
|
|
6
|
+
port: number;
|
|
7
|
+
/** directory to read from — cwd for `card`, else MCP_CONTEXT_CARD_ROOT ?? package root. */
|
|
8
|
+
root: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Decide how to launch, from argv + env. Pure — so the mode matrix is unit
|
|
12
|
+
* tested without spawning a process.
|
|
13
|
+
*
|
|
14
|
+
* card → render the cwd's card to stdout
|
|
15
|
+
* (nothing) → stdio
|
|
16
|
+
* --http | PORT=<n> → http
|
|
17
|
+
* --stdio → stdio, even when PORT is set
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveLaunch(argv: readonly string[], env?: NodeJS.ProcessEnv): Launch;
|
|
20
|
+
/** value of `--flag <value>` in argv, or undefined. */
|
|
21
|
+
export declare function flagValue(argv: readonly string[], flag: string): string | undefined;
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* bin — the entry point.
|
|
4
|
+
*
|
|
5
|
+
* mcp-context-card → stdio (the default; what an MCP host spawns)
|
|
6
|
+
* mcp-context-card --http → stateless Streamable HTTP on PORT (default 3000)
|
|
7
|
+
* PORT=8080 mcp-context-card → HTTP too (a hosted deploy sets PORT)
|
|
8
|
+
* mcp-context-card --stdio → force stdio even when PORT is set
|
|
9
|
+
* mcp-context-card card → render THIS directory's context card to stdout
|
|
10
|
+
* ( > card.html · --theme light|dark · --accent #hex )
|
|
11
|
+
*
|
|
12
|
+
* MCP_CONTEXT_CARD_ROOT=/path/to/project → read AGENTS.md / project.fafm /
|
|
13
|
+
* .well-known/ from there instead of the package's own bundled copies.
|
|
14
|
+
*/
|
|
15
|
+
import { resolve } from "node:path";
|
|
16
|
+
import { pathToFileURL } from "node:url";
|
|
17
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
18
|
+
import { ROOT, serve } from "./server.js";
|
|
19
|
+
/**
|
|
20
|
+
* Decide how to launch, from argv + env. Pure — so the mode matrix is unit
|
|
21
|
+
* tested without spawning a process.
|
|
22
|
+
*
|
|
23
|
+
* card → render the cwd's card to stdout
|
|
24
|
+
* (nothing) → stdio
|
|
25
|
+
* --http | PORT=<n> → http
|
|
26
|
+
* --stdio → stdio, even when PORT is set
|
|
27
|
+
*/
|
|
28
|
+
export function resolveLaunch(argv, env = process.env) {
|
|
29
|
+
if (argv[0] === "card") {
|
|
30
|
+
return {
|
|
31
|
+
mode: "card",
|
|
32
|
+
port: 0,
|
|
33
|
+
root: env.MCP_CONTEXT_CARD_ROOT ? resolve(env.MCP_CONTEXT_CARD_ROOT) : process.cwd(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const n = Number(env.PORT);
|
|
37
|
+
const portEnv = env.PORT && Number.isFinite(n) && n > 0 ? n : undefined;
|
|
38
|
+
const forceStdio = argv.includes("--stdio");
|
|
39
|
+
const http = !forceStdio && (argv.includes("--http") || portEnv !== undefined);
|
|
40
|
+
const root = env.MCP_CONTEXT_CARD_ROOT ? resolve(env.MCP_CONTEXT_CARD_ROOT) : ROOT;
|
|
41
|
+
return { mode: http ? "http" : "stdio", port: portEnv ?? 3000, root };
|
|
42
|
+
}
|
|
43
|
+
/** value of `--flag <value>` in argv, or undefined. */
|
|
44
|
+
export function flagValue(argv, flag) {
|
|
45
|
+
const i = argv.indexOf(flag);
|
|
46
|
+
return i >= 0 && i + 1 < argv.length ? argv[i + 1] : undefined;
|
|
47
|
+
}
|
|
48
|
+
/** Direct run only — importing this module (e.g. from a test) must not launch. */
|
|
49
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
50
|
+
const argv = process.argv.slice(2);
|
|
51
|
+
const { mode, port, root } = resolveLaunch(argv);
|
|
52
|
+
if (mode === "card") {
|
|
53
|
+
const { renderCard, safeAccent } = await import("./render-card.js");
|
|
54
|
+
const theme = flagValue(argv, "--theme");
|
|
55
|
+
process.stdout.write(renderCard(root, {
|
|
56
|
+
theme: theme === "light" || theme === "dark" ? theme : "auto",
|
|
57
|
+
accent: safeAccent(flagValue(argv, "--accent")),
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
else if (mode === "http") {
|
|
61
|
+
const { httpApp } = await import("./transport/http.js");
|
|
62
|
+
const { serve: serveHttp } = await import("@hono/node-server");
|
|
63
|
+
serveHttp({ fetch: httpApp(root).fetch, port });
|
|
64
|
+
// stderr, not stdout — stdout is the MCP wire in stdio mode.
|
|
65
|
+
console.error(`mcp-context-card · http · :${port} (POST /mcp · GET /card · GET /.well-known/*)`);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
await serve(new StdioServerTransport(), root);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|