Public: anyone can read this; the crew can change it.

/.well-known/mcp.json names the commit it runs, so an agent on a preview knows which build it is talking to #7051

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/6070-well-known-mcp-json-names-the-commit-it → main 1 commit fork

  • Merged. scout/6070-well-known-mcp-json-names-the-commit-it was merged into main as de6ef1e.

Review

Sign in to review this change.
3 files changed +25 −3
docs/MCP.md +9
@@ -243,6 +243,15 @@ claude mcp add --transport http forge https://<your-forge>/mcp \
243243
244244 Any MCP client that can send a header works the same way.
245245
246 ## The server card
247
248 `/.well-known/mcp.json` names the server (`name`, `version`, `endpoint`,
249 `transport`, `protectedResourceMetadata`, `documentation`, `tokenFreeTools`)
250 and the build that answers: `commit`, the full commit the forge runs
251 (`FORGE_COMMIT`, or `FORGE_APP_COMMIT` on a preview), or `null` when nothing
252 says. `version` is `dev` on a preview, so `commit` is how an agent tells two
253 previews apart.
254
246255 ## Attaching with OAuth
247256
248257 Clients that cannot paste a header — the Claude apps, Grok's connectors —
src/core/mcp_card.ts +4 −3
@@ -6,7 +6,7 @@
66 * The MCP server-card format is still a draft, so the card may not match a
77 * later standard's field names; when one settles, the card follows it.
88 */
9 import { running } from "../kernel/version.ts";
9 import { running, type Running } from "../kernel/version.ts";
1010
1111 /** The endpoint and the protected-resource metadata that names how to sign in. */
1212 export function mcpPointers(origin: string): { url: string; oauth: string } {
@@ -21,11 +21,12 @@ export function mcpPointers(origin: string): { url: string; oauth: string } {
2121 * true is named: who answers, where, over what transport, how to sign in,
2222 * and the one tool that needs no token.
2323 */
24 export function mcpCard(origin: string) {
24 export function mcpCard(origin: string, now: Running = running()) {
2525 const { url, oauth } = mcpPointers(origin);
2626 return {
2727 name: "forge",
28 version: running().version,
28 version: now.version,
29 commit: now.commit,
2930 endpoint: url,
3031 transport: "streamable-http",
3132 protectedResourceMetadata: oauth,
test/mcp-card.test.ts +12
@@ -5,6 +5,7 @@ import { tmpdir } from "node:os";
55 import { join } from "node:path";
66 import { createApp, type App } from "../src/app.ts";
77 import { serve, type Servers } from "../src/http/server.ts";
8 import { mcpCard } from "../src/core/mcp_card.ts";
89 import { running } from "../src/kernel/version.ts";
910
1011 describe("the MCP server card", () => {
@@ -59,6 +60,17 @@ describe("the MCP server card", () => {
5960 expect((await fetch(card.documentation)).status).toBe(200);
6061 });
6162
63 test("carries the commit it runs, or null when nothing says", () => {
64 const sha = "a1b2c3d4e5f60718293a4b5c6d7e8f9012345678";
65 expect(mcpCard("https://x", running({ FORGE_APP_COMMIT: sha })).commit).toBe(sha);
66 expect(mcpCard("https://x", running({})).commit).toBeNull();
67 });
68
69 test("the live card's commit is what running() says", async () => {
70 const card = (await (await fetch(`${base}/.well-known/mcp.json`)).json()) as any;
71 expect(card.commit).toBe(running().commit);
72 });
73
6274 test("the `mcp` block of /api/v1/here gives the same url and oauth", async () => {
6375 const card = (await (await fetch(`${base}/.well-known/mcp.json`)).json()) as any;
6476 const here = (await (await fetch(`${base}/api/v1/here?path=/`)).json()) as any;

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs