mcp-context-card 1.5.0 → 1.6.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.
@@ -11,7 +11,7 @@
11
11
  "data": {
12
12
  "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json",
13
13
  "name": "io.github.Wolfe-Jam/mcp-context-card",
14
- "version": "1.5.0",
14
+ "version": "1.6.0",
15
15
  "title": "MCP Context Card",
16
16
  "description": "MCP server for a project's context (AGENTS.md), memory, and identity — base or drop-in extension.",
17
17
  "websiteUrl": "https://github.com/Wolfe-Jam/mcp-context-card",
package/.well-known/fafa CHANGED
@@ -13,7 +13,7 @@ agent:
13
13
  # a deployment for your own project uses your domain.
14
14
  id: "urn:air:faf.one:agent:mcp-context-card"
15
15
  vendor: "io.github.Wolfe-Jam"
16
- version: "1.5.0"
16
+ version: "1.6.0"
17
17
  description: >-
18
18
  The essential MCP components for a project's context (AGENTS.md),
19
19
  cross-session memory, and identity — a base MCP on its own, or a
@@ -21,3 +21,8 @@ agent:
21
21
  Server Card _meta block and a self-published ai-catalog.json.
22
22
  status: "published"
23
23
  license: "MIT"
24
+ metadata:
25
+ cards:
26
+ packages:
27
+ - registryType: "npm"
28
+ identifier: "mcp-context-card"
package/CHANGELOG.md CHANGED
@@ -2,9 +2,50 @@
2
2
 
3
3
  All notable changes to this project. Adheres to [Semantic Versioning](https://semver.org).
4
4
 
5
+ ## 1.6.0
6
+
7
+ The card is a business card.
8
+
9
+ `npx mcp-context-card card` used to open a technical page. It now opens a
10
+ **business card**: what you'd hand someone on the front, and the full detail
11
+ one flip away.
12
+
13
+ - **Front:** a monogram logo (the first letters of the name: **MC**), the
14
+ name, a title, and a one-liner.
15
+ - The **title** says what it is and its version ("MCP server · v1.6.0",
16
+ "A2A agent · v0.3.0"), from where the project's `.fafa` says it runs
17
+ (`endpoints`, or `metadata.cards.packages`). With nothing declared, it
18
+ claims nothing.
19
+ - The **one-liner** is the start of the description, stopping at its first
20
+ natural pause rather than mid-phrase.
21
+ - Domain, publisher, status and licence sit along the bottom.
22
+ - **Back:** tabs for About (the identity in full), Skills (when the `.fafa`
23
+ lists capabilities), Context (AGENTS.md), Memory and Discovery. Long content
24
+ scrolls inside the card, including by mouse wheel or trackpad on macOS.
25
+ - **Controls:** the same two on both faces. **(i)** top-right explains
26
+ Business Cards for Agents; the outline flip button bottom-right (↻ on the
27
+ front, ↺ on the back) has an instant tooltip.
28
+ - **Views:** ▭ ▯ switches landscape or portrait (`--portrait`,
29
+ `?layout=portrait`, `layout` on the tools). ◐ switches the **card** between
30
+ light and dark. `--theme` / `?theme=` set the card's starting colour, and
31
+ the page around it follows your OS on its own.
32
+ - **Works with scripts off:** flip, tabs and both toggles are CSS. The one
33
+ small script adds Expand all, `#section` links on the back, and printing.
34
+ `--expanded` and print lay both faces out flat.
35
+ - **The `.fafa` reader** now reads `endpoints`, `capabilities` and
36
+ `metadata.cards.packages`, as `faf card init` writes them. This repo's own
37
+ `.fafa` declares its npm package.
38
+ - **Read and render are separate:** `readCard` turns a project's files into a
39
+ neutral business card; `renderBusinessCard` draws any card, so a person's
40
+ card or an A2A Agent Card needs no renderer change.
41
+ - The saved file is still `context-card.html`: underneath, it's the project's
42
+ context.
43
+
44
+ No tool or protocol change. 209 tests, all green.
45
+
5
46
  ## 1.5.0
6
47
 
7
- MCP Server Cards (SEP-2127, Final) and AI Catalog 1.0, and HTTP that is local by default.
48
+ MCP Server Cards (SEP-2127, Final) and the AI Catalog (specVersion 1.0), and HTTP that is local by default.
8
49
 
9
50
  **Breaking for exposed deploys:** HTTP mode now binds `127.0.0.1`. A container or
10
51
  hosted deploy must set `HOST=0.0.0.0` (`examples/Dockerfile` does). Local use is
@@ -21,7 +62,7 @@ unchanged.
21
62
  spec's CORS (`Access-Control-Allow-Origin: *`, GET, `Content-Type` and
22
63
  `If-None-Match` allowed, `ETag` exposed), `Cache-Control: public,
23
64
  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`,
65
+ - **AI Catalog (specVersion 1.0).** The Server Card is the first entry. Entries use `type`,
25
66
  custom data sits in `extensions`, and identifiers are
26
67
  `urn:air:{domain}:{namespace}:{name}` with the domain taken from the project's
27
68
  own `.fafa` (none is ever invented). `/AGENTS.md` is served, so every catalog
package/README.md CHANGED
@@ -11,10 +11,11 @@ rendered as one card you can read.
11
11
 
12
12
  | Light | Dark |
13
13
  |---|---|
14
- | ![the context card, light theme](./docs/img/card-light.png) | ![the context card, dark theme](./docs/img/card-dark.png) |
14
+ | ![the business card front, light theme: name, title, one-liner](./docs/img/card-light.png) | ![the business card front, dark theme](./docs/img/card-dark.png) |
15
15
 
16
- **See your own project's card** — run this in its folder. It writes
17
- `context-card.html` and opens it in your browser:
16
+ **See your own project's business card** — run this in its folder. It writes
17
+ `context-card.html` and opens it in your browser. The front is the card: name,
18
+ title, one-liner. Flip it for the back: About, Context, Memory, Discovery.
18
19
 
19
20
  ```
20
21
  npx mcp-context-card card
@@ -22,15 +23,15 @@ npx mcp-context-card card
22
23
 
23
24
  **context** — the project's `AGENTS.md`, served whole or one section at a time.
24
25
 
25
- ![the card's context section — AGENTS.md, section nav, read_agents_md](./docs/img/card-context.png)
26
+ ![the back's Context tab — AGENTS.md, section nav, read_agents_md](./docs/img/card-context.png)
26
27
 
27
28
  **memory** — facts that persist across sessions, in a file.
28
29
 
29
- ![the card's memory section — a real, tagged, verified fact](./docs/img/card-memory.png)
30
+ ![the back's Memory tab — real, tagged, verified facts](./docs/img/card-memory.png)
30
31
 
31
32
  **identity** — what this server is, from its own `.fafa` (`agent.fafa`, else `.well-known/fafa`).
32
33
 
33
- ![the card's identity — name and pills](./docs/img/card-identity.png)
34
+ ![the back's About tab — the identity in full](./docs/img/card-identity.png)
34
35
 
35
36
  Discovery goes through two surfaces already in the ecosystem: the Server Card
36
37
  `_meta` block and `ai-catalog.json` sibling entries.
@@ -76,23 +77,34 @@ no search.
76
77
 
77
78
  ## The card
78
79
 
79
- The screenshot at the top of this page is exactly this — the same three
80
- sources rendered as one self‑contained HTML page: identity, `AGENTS.md`,
81
- memory, and how a machine fetches it. The view for people: read it, screenshot
82
- it, drop it in a PR, put it on a status page.
83
-
84
- `AGENTS.md` sections are collapsed by default, so the card scans in one screen;
85
- a sticky index jumps to any section, **Expand all** opens everything. Sections
86
- toggle natively — the one small inline script only adds the bulk button and the
87
- print handler. `--expanded` / `?expand=all` renders it fully open, script-free,
88
- for a screenshot.
80
+ The screenshot at the top of this page is exactly this: the same sources
81
+ rendered as a **business card** in one self‑contained HTML page. The view for
82
+ people: read it, screenshot it, drop it in a PR, put it on a status page.
83
+
84
+ - **Front:** the name, a title (what it is and its version: "MCP server", "A2A
85
+ agent", from where the project's `.fafa` says it runs) and a one-liner (the
86
+ start of its description).
87
+ - **Back (Flip):** tabs for **About** (the identity in full),
88
+ **Skills** (when the `.fafa` lists capabilities), **Context** (`AGENTS.md`),
89
+ **Memory** and **Discovery**. Long content scrolls inside the card.
90
+ - **Landscape or portrait:** the ▭ ▯ button switches the view; `--portrait` /
91
+ `?layout=portrait` sets it.
92
+ - **Light or dark card:** the card's colour is its own, like a printed card.
93
+ `--theme` / `?theme=` sets it (default: follow your OS), and ◐ switches it.
94
+ The page around it always follows your OS.
95
+
96
+ Flip, tabs and the view switch are plain CSS, so they work with scripts off.
97
+ The one small inline script adds Expand all, opens a `#section` link on the
98
+ back, and opens every section for printing. `--expanded` / `?expand=all`
99
+ renders both faces flat, every section open, for a screenshot or print.
89
100
 
90
101
  ```
91
102
  npx mcp-context-card card # at a terminal: writes context-card.html and opens it
92
- npx mcp-context-card card --expanded # every section open
103
+ npx mcp-context-card card --portrait # the tall view
104
+ npx mcp-context-card card --expanded # both faces flat, every section open
93
105
  npx mcp-context-card card > x.html # piped/redirected: raw HTML to stdout
94
106
  GET /card # live, on the HTTP transport
95
- GET /card?expand=all&theme=light&accent=%230066cc
107
+ GET /card?layout=portrait&theme=light&accent=%230066cc
96
108
  ```
97
109
 
98
110
  In a chat, hosts that support [MCP Apps](https://modelcontextprotocol.io/extensions/apps)
@@ -188,6 +200,19 @@ Extending an MCP you already run: most hosts accept more than one
188
200
  `server-git`, or your own, and every agent in that host gains context,
189
201
  memory, and identity discovery without anything else changing.
190
202
 
203
+ ### Your identifiers
204
+
205
+ The AI Catalog names each entry `urn:air:{domain}:{namespace}:{name}`, and
206
+ the domain is yours. It comes from your project's `.fafa`: run
207
+ `npx faf-cli card init` (or `--domain example.com`), and it writes
208
+ `agent.id: urn:air:example.com:agent:<short-name>`. Your catalog then publishes
209
+ `urn:air:example.com:context:<short-name>` and so on, with no code to change.
210
+ This repo's own `.fafa` says `faf.one`; that's our example, and yours says yours.
211
+
212
+ With no `.fafa`, no domain is invented: the served catalog uses the host it's
213
+ served from (`localhost` when you run it locally), and the static
214
+ `.well-known/ai-catalog.json` uses plain `<short-name>:<namespace>` IDs.
215
+
191
216
  ## Why
192
217
 
193
218
  `AGENTS.md` is the de-facto standard for telling a coding agent how to work in a
@@ -246,7 +271,7 @@ over both transports:
246
271
  4. **Discovery** — `list_context_sources()`, then the same server over stateless
247
272
  HTTP with its `.well-known` routes and `GET /card`.
248
273
 
249
- 200 tests on Linux, macOS, and Windows, coverage‑gated in CI.
274
+ 209 tests on Linux, macOS, and Windows, coverage‑gated in CI.
250
275
  `npm run wjttc` runs the WJTTC certification suite (seven tiers, from protocol
251
276
  and Server Card conformance to stdio/HTTP parity and the shipped package). Two spawn a real
252
277
  child process and check a remembered fact survives the restart — one against
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.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";
11
+ export declare const HELP = "mcp-context-card 1.6.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) --portrait --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
12
  /**
13
13
  * The address the HTTP server binds: `HOST`, else 127.0.0.1. Local by default,
14
14
  * as the MCP transports spec recommends; a hosted deploy sets HOST=0.0.0.0.
package/dist/bin.js CHANGED
@@ -10,7 +10,7 @@
10
10
  * writes context-card.html and opens it. Piped
11
11
  * or redirected: HTML to stdout ( > card.html ).
12
12
  * --theme light|dark · --accent #hex
13
- * --expanded (all sections open) · --stdout
13
+ * --expanded (all sections open) · --portrait · --stdout
14
14
  * mcp-context-card --help → usage
15
15
  * mcp-context-card --version → version
16
16
  *
@@ -34,7 +34,7 @@ USAGE
34
34
  mcp-context-card card this dir's context card — opens it in your browser
35
35
  at a terminal; HTML to stdout when piped ( > f.html )
36
36
  --theme light|dark --accent #hex
37
- --expanded (all sections open) --stdout
37
+ --expanded (all sections open) --portrait --stdout
38
38
  mcp-context-card --help this text
39
39
  mcp-context-card --version print version
40
40
 
@@ -128,6 +128,7 @@ if (entryPath && import.meta.url === pathToFileURL(entryPath).href) {
128
128
  theme: theme === "light" || theme === "dark" ? theme : "auto",
129
129
  accent: safeAccent(flagValue(argv, "--accent")),
130
130
  expanded: argv.includes("--expanded"),
131
+ layout: argv.includes("--portrait") ? "portrait" : "landscape",
131
132
  });
132
133
  // Piped / redirected (or --stdout) → raw HTML on stdout, unchanged.
133
134
  // A bare run at a terminal → the HTML is noise; write a file and open it.
@@ -1,7 +1,7 @@
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.5.0";
4
+ export declare const VERSION = "1.6.0";
5
5
  export declare const SERVER_CARD_URI = "mcp-context-card://server-card";
6
6
  /** The server's registry identity: reverse-DNS, the same as server.json `name`.
7
7
  * It is also `serverInfo.name`, so a Server Card never contradicts the live
package/dist/constants.js CHANGED
@@ -1,7 +1,7 @@
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.5.0";
4
+ export const VERSION = "1.6.0";
5
5
  export const SERVER_CARD_URI = "mcp-context-card://server-card";
6
6
  /** The server's registry identity: reverse-DNS, the same as server.json `name`.
7
7
  * It is also `serverInfo.name`, so a Server Card never contradicts the live
@@ -23,6 +23,7 @@ export function parseFafa(path) {
23
23
  description: str(agent.description),
24
24
  status: str(agent.status),
25
25
  license: str(agent.license),
26
+ ...extras(doc),
26
27
  };
27
28
  }
28
29
  function str(v) {
@@ -31,3 +32,23 @@ function str(v) {
31
32
  const s = String(v).trim().replace(/\s+/g, " ");
32
33
  return s.length ? s : undefined;
33
34
  }
35
+ /** Where it runs, what it can do, and what installs it: `.fafa` fields outside `agent`. */
36
+ function extras(doc) {
37
+ const list = (v) => Array.isArray(v) ? v.filter((x) => !!x && typeof x === "object") : [];
38
+ const endpoints = list(doc.endpoints)
39
+ .filter((e) => typeof e.protocol === "string" && e.protocol.trim())
40
+ .map((e) => ({ protocol: String(e.protocol).trim().toLowerCase(), ...(typeof e.location === "string" ? { location: e.location } : {}) }));
41
+ const skills = list(doc.capabilities)
42
+ .filter((c) => typeof c.name === "string" && c.name.trim())
43
+ .map((c) => ({ name: String(c.name), ...(typeof c.description === "string" ? { description: c.description } : {}) }));
44
+ const meta = doc.metadata && typeof doc.metadata === "object" ? doc.metadata : {};
45
+ const cards = meta.cards && typeof meta.cards === "object" ? meta.cards : {};
46
+ const packages = list(cards.packages)
47
+ .filter((p) => typeof p.registryType === "string" && typeof p.identifier === "string")
48
+ .map((p) => ({ registryType: String(p.registryType), identifier: String(p.identifier) }));
49
+ return {
50
+ ...(endpoints.length ? { endpoints } : {}),
51
+ ...(skills.length ? { skills } : {}),
52
+ ...(packages.length ? { packages } : {}),
53
+ };
54
+ }
@@ -33,4 +33,19 @@ export interface AgentIdentity {
33
33
  description?: string;
34
34
  status?: string;
35
35
  license?: string;
36
+ /** Where it runs (`.fafa` `endpoints`), as `faf card init` writes them: protocol `a2a` or `mcp`. */
37
+ endpoints?: {
38
+ protocol: string;
39
+ location?: string;
40
+ }[];
41
+ /** What it can do (`.fafa` `capabilities`). */
42
+ skills?: {
43
+ name: string;
44
+ description?: string;
45
+ }[];
46
+ /** Installable packages (`.fafa` `metadata.cards.packages`), e.g. an npm MCP server. */
47
+ packages?: {
48
+ registryType: string;
49
+ identifier: string;
50
+ }[];
36
51
  }
@@ -1,3 +1,4 @@
1
+ import type { AgentIdentity } from "./faf/types.js";
1
2
  export type Theme = "light" | "dark" | "auto";
2
3
  export interface CardOptions {
3
4
  theme?: Theme;
@@ -14,11 +15,56 @@ export interface CardOptions {
14
15
  * served beyond this machine without the publish opt-in. Default: `full`.
15
16
  */
16
17
  memory?: "full" | "private";
18
+ /** Business-card view. Default `landscape`; the reader can switch it too. */
19
+ layout?: "landscape" | "portrait";
17
20
  }
18
21
  /** AAIF brand orange (aaif.io). The default accent. */
19
22
  export declare const AAIF_ACCENT = "#FF702D";
20
23
  export declare function safeAccent(a?: string): string;
24
+ /** What the front calls this thing, from where it runs: "MCP server", "A2A agent", or both. */
25
+ export declare function cardKind(id: AgentIdentity | null): string[];
26
+ /** The front's title line: what it is, then its version. Empty when nothing is known. */
27
+ export declare function cardTitle(id: AgentIdentity | null): string;
28
+ /** The front's logo: the first letters of the name's first two words ("mcp-context-card" → "MC"). */
29
+ export declare function cardInitials(name: string): string;
30
+ /** The front's one-liner: the first sentence of the description. */
31
+ export declare function cardOneLiner(id: AgentIdentity | null, max?: number): string;
32
+ /**
33
+ * A business card, independent of where its facts came from: everything the
34
+ * renderer draws. `readCard` builds one from a project's files; another reader
35
+ * (an A2A Agent Card, an MCP Server Card, a person's card) can build one too,
36
+ * and `renderBusinessCard` draws any of them the same way.
37
+ */
38
+ export interface BusinessCard {
39
+ name: string;
40
+ /** What it is (and its version): "MCP server · v1.5.0", or a person's role. */
41
+ title?: string;
42
+ oneLiner?: string;
43
+ /** Shown bottom-left on the front, e.g. the publisher domain. */
44
+ domain?: string;
45
+ /** Footer chips; `accent` highlights one (e.g. the status). Plain text. */
46
+ chips: {
47
+ text: string;
48
+ accent?: boolean;
49
+ }[];
50
+ /** The back's tabs, in order. `key` is [a-z0-9-]; `html` is already-safe markup. */
51
+ tabs: {
52
+ key: string;
53
+ label: string;
54
+ heading: string;
55
+ html: string;
56
+ }[];
57
+ /** The (i) panel, as already-safe markup. */
58
+ about: string;
59
+ }
60
+ /** How to draw a card: the owner's colour and accent, the view, and the flat (print) layout. */
61
+ export type RenderOptions = Pick<CardOptions, "theme" | "accent" | "layout" | "expanded">;
62
+ /** The project's card: read its files, then draw. */
21
63
  export declare function renderCard(root: string, opts?: CardOptions): string;
64
+ /** Read a project's AGENTS.md, memory and identity into a business card. */
65
+ export declare function readCard(root: string, opts?: Pick<CardOptions, "expanded" | "memory">): BusinessCard;
66
+ /** Draw any business card as one self-contained HTML page. */
67
+ export declare function renderBusinessCard(card: BusinessCard, opts?: RenderOptions): string;
22
68
  /** tl;dr: the first few facts, each cut to about a line. `full`: every fact, whole. */
23
69
  export type Detail = "tldr" | "full";
24
70
  /**