@absolutejs/mcp 0.17.0 → 0.17.1
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/CHANGELOG.md +6 -0
- package/canary/results/2026-09-11-terminal.jsonl +20 -0
- package/canary/server.ts +154 -0
- package/changelog.json +10 -0
- package/docs/host-canaries.md +60 -0
- package/docs/mcp-apps.md +4 -0
- package/package.json +9 -5
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ This file is generated by `absolute-changelog` from the entries in
|
|
|
6
6
|
`changelog/`. Edit an entry, not this file — and add new ones under
|
|
7
7
|
`changelog/unreleased/`.
|
|
8
8
|
|
|
9
|
+
## 0.17.1 — 2026-09-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Ship a preflight-validated synthetic host canary and record Claude Code and Codex terminal interoperability evidence**
|
|
14
|
+
|
|
9
15
|
## 0.17.0 — 2026-09-11
|
|
10
16
|
|
|
11
17
|
### Added
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{"ready":true,"url":"http://127.0.0.1:4428/mcp","data":"synthetic","payments":false}
|
|
2
|
+
{"method":"server/discover","status":400}
|
|
3
|
+
{"method":"initialize","status":200,"client":"claude-code","version":"2.1.265","requestedProtocol":"2025-11-25","negotiatedProtocol":"2025-06-18","appsMimeTypes":[],"elicitation":true}
|
|
4
|
+
{"method":"notifications/initialized","status":202}
|
|
5
|
+
{"method":"GET","status":405}
|
|
6
|
+
{"method":"tools/list","status":200,"tools":[{"name":"get_billing_status","ui":false},{"name":"list_receipts","ui":false},{"name":"get_usage_report","ui":false}]}
|
|
7
|
+
{"method":"resources/list","status":200}
|
|
8
|
+
{"method":"tools/call","status":200,"tool":"get_billing_status","isError":false}
|
|
9
|
+
{"method":"tools/call","status":200,"tool":"get_usage_report","isError":false}
|
|
10
|
+
{"method":"tools/call","status":200,"tool":"list_receipts","isError":false}
|
|
11
|
+
{"method":"tools/call","status":200,"tool":"list_receipts","isError":false}
|
|
12
|
+
{"method":"initialize","status":200,"client":"codex-mcp-client","version":"0.154.0","requestedProtocol":"2025-06-18","negotiatedProtocol":"2025-06-18","appsMimeTypes":[],"elicitation":true}
|
|
13
|
+
{"method":"notifications/initialized","status":202}
|
|
14
|
+
{"method":"GET","status":405}
|
|
15
|
+
{"method":"tools/list","status":200,"tools":[{"name":"get_billing_status","ui":false},{"name":"list_receipts","ui":false},{"name":"get_usage_report","ui":false}]}
|
|
16
|
+
{"method":"tools/call","status":200,"tool":"get_billing_status","isError":false}
|
|
17
|
+
{"method":"tools/call","status":200,"tool":"get_usage_report","isError":false}
|
|
18
|
+
{"method":"tools/call","status":200,"tool":"list_receipts","isError":false}
|
|
19
|
+
{"method":"tools/call","status":200,"tool":"list_receipts","isError":false}
|
|
20
|
+
{"method":"DELETE","status":204}
|
package/canary/server.ts
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/** Synthetic host canary. Never mount this handler in a customer application. */
|
|
2
|
+
import {
|
|
3
|
+
createMcpHandler,
|
|
4
|
+
createBillingApps,
|
|
5
|
+
createBillingReportTools,
|
|
6
|
+
} from "@absolutejs/mcp";
|
|
7
|
+
import { encodeReceiptCursor } from "@absolutejs/billing/reports";
|
|
8
|
+
const apps = createBillingApps();
|
|
9
|
+
const id = "00000000-0000-4000-8000-000000000001";
|
|
10
|
+
const at = "2026-09-01T00:00:00.000Z";
|
|
11
|
+
const readers = createBillingReportTools({
|
|
12
|
+
status: async () => ({
|
|
13
|
+
automaticRefill: false,
|
|
14
|
+
credits: {
|
|
15
|
+
remaining: 0,
|
|
16
|
+
reserved: 12,
|
|
17
|
+
purchased: 0,
|
|
18
|
+
promotional: 0,
|
|
19
|
+
debt: 0,
|
|
20
|
+
},
|
|
21
|
+
portalAccess: false,
|
|
22
|
+
subscription: null,
|
|
23
|
+
}),
|
|
24
|
+
receipts: async ({ cursor }) => ({
|
|
25
|
+
receipts: cursor
|
|
26
|
+
? []
|
|
27
|
+
: [
|
|
28
|
+
{
|
|
29
|
+
id,
|
|
30
|
+
issuedAt: at,
|
|
31
|
+
source: "credit_purchase",
|
|
32
|
+
amountCents: 1000,
|
|
33
|
+
refundedAmountCents: 200,
|
|
34
|
+
currency: "USD",
|
|
35
|
+
status: "partially_refunded",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
nextCursor: cursor ? null : encodeReceiptCursor({ at, id }),
|
|
39
|
+
}),
|
|
40
|
+
usage: async (range) => ({
|
|
41
|
+
...range,
|
|
42
|
+
creditsConsumed: 8,
|
|
43
|
+
events: 2,
|
|
44
|
+
byFeature: [{ feature: "fixture_workflow", credits: 8, events: 2 }],
|
|
45
|
+
byDay: [{ day: range.from, credits: 8, events: 2 }],
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
const handler = createMcpHandler({
|
|
49
|
+
issuer: "http://127.0.0.1:4428",
|
|
50
|
+
path: "/mcp",
|
|
51
|
+
serverInfo: { name: "absolute-billing-canary", version: "1" },
|
|
52
|
+
supportedProtocols: ["2025-06-18"],
|
|
53
|
+
authorize: async () => ({
|
|
54
|
+
ok: true,
|
|
55
|
+
caller: "synthetic-fixture",
|
|
56
|
+
scopes: [],
|
|
57
|
+
}),
|
|
58
|
+
commerce: () => ({ profiles: ["unknown"] }),
|
|
59
|
+
apps: { resources: apps.resources },
|
|
60
|
+
tools: () => apps.decorateTools(readers),
|
|
61
|
+
});
|
|
62
|
+
// Fail before accepting a host if synthetic records violate the public contract.
|
|
63
|
+
for (const [name, args] of [
|
|
64
|
+
["get_billing_status", {}],
|
|
65
|
+
["get_usage_report", { from: "2026-09-01", to: "2026-09-11" }],
|
|
66
|
+
["list_receipts", {}],
|
|
67
|
+
["list_receipts", { cursor: encodeReceiptCursor({ at, id }) }],
|
|
68
|
+
] as const) {
|
|
69
|
+
const response = await handler(
|
|
70
|
+
new Request("http://127.0.0.1:4428/mcp", {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: {
|
|
73
|
+
"content-type": "application/json",
|
|
74
|
+
"mcp-protocol-version": "2025-06-18",
|
|
75
|
+
},
|
|
76
|
+
body: JSON.stringify({
|
|
77
|
+
jsonrpc: "2.0",
|
|
78
|
+
id: 1,
|
|
79
|
+
method: "tools/call",
|
|
80
|
+
params: { name, arguments: args },
|
|
81
|
+
}),
|
|
82
|
+
}),
|
|
83
|
+
);
|
|
84
|
+
const body = await response?.json();
|
|
85
|
+
if (!response?.ok || !body?.result || body.error || body.result.isError)
|
|
86
|
+
throw Error(`Canary preflight failed: ${name}`);
|
|
87
|
+
}
|
|
88
|
+
Bun.serve({
|
|
89
|
+
hostname: "127.0.0.1",
|
|
90
|
+
port: 4428,
|
|
91
|
+
fetch: async (request) => {
|
|
92
|
+
let rpc;
|
|
93
|
+
try {
|
|
94
|
+
if (request.method === "POST") rpc = await request.clone().json();
|
|
95
|
+
} catch {
|
|
96
|
+
/* Handler owns malformed input. */
|
|
97
|
+
}
|
|
98
|
+
const response =
|
|
99
|
+
(await handler(request)) ?? new Response("Not found", { status: 404 });
|
|
100
|
+
let body;
|
|
101
|
+
if (response.headers.get("content-type")?.includes("application/json")) {
|
|
102
|
+
try {
|
|
103
|
+
body = await response.clone().json();
|
|
104
|
+
} catch {
|
|
105
|
+
/* No raw response logging. */
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Only explicit protocol fields: never headers, tokens, session IDs, arguments or report bodies.
|
|
109
|
+
console.log(
|
|
110
|
+
JSON.stringify({
|
|
111
|
+
method: rpc?.method ?? request.method,
|
|
112
|
+
status: response.status,
|
|
113
|
+
...(rpc?.method === "initialize"
|
|
114
|
+
? {
|
|
115
|
+
client: rpc.params?.clientInfo?.name,
|
|
116
|
+
version: rpc.params?.clientInfo?.version,
|
|
117
|
+
requestedProtocol: rpc.params?.protocolVersion,
|
|
118
|
+
negotiatedProtocol: body?.result?.protocolVersion,
|
|
119
|
+
appsMimeTypes:
|
|
120
|
+
rpc.params?.capabilities?.extensions?.[
|
|
121
|
+
"io.modelcontextprotocol/ui"
|
|
122
|
+
]?.mimeTypes ?? [],
|
|
123
|
+
elicitation: !!rpc.params?.capabilities?.elicitation,
|
|
124
|
+
}
|
|
125
|
+
: {}),
|
|
126
|
+
...(rpc?.method === "tools/list"
|
|
127
|
+
? {
|
|
128
|
+
tools: body?.result?.tools?.map(
|
|
129
|
+
(tool: { name: string; _meta?: { ui?: unknown } }) => ({
|
|
130
|
+
name: tool.name,
|
|
131
|
+
ui: !!tool._meta?.ui,
|
|
132
|
+
}),
|
|
133
|
+
),
|
|
134
|
+
}
|
|
135
|
+
: {}),
|
|
136
|
+
...(rpc?.method === "tools/call"
|
|
137
|
+
? {
|
|
138
|
+
tool: rpc.params?.name,
|
|
139
|
+
isError: body?.result?.isError ?? !!body?.error,
|
|
140
|
+
}
|
|
141
|
+
: {}),
|
|
142
|
+
}),
|
|
143
|
+
);
|
|
144
|
+
return response;
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
console.log(
|
|
148
|
+
JSON.stringify({
|
|
149
|
+
ready: true,
|
|
150
|
+
url: "http://127.0.0.1:4428/mcp",
|
|
151
|
+
data: "synthetic",
|
|
152
|
+
payments: false,
|
|
153
|
+
}),
|
|
154
|
+
);
|
package/changelog.json
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
"contract": 1,
|
|
3
3
|
"name": "@absolutejs/mcp",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"version": "0.17.1",
|
|
7
|
+
"date": "2026-09-11",
|
|
8
|
+
"changes": [
|
|
9
|
+
{
|
|
10
|
+
"kind": "added",
|
|
11
|
+
"summary": "Ship a preflight-validated synthetic host canary and record Claude Code and Codex terminal interoperability evidence"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
},
|
|
5
15
|
{
|
|
6
16
|
"version": "0.17.0",
|
|
7
17
|
"date": "2026-09-11",
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Repeatable MCP host canaries
|
|
2
|
+
|
|
3
|
+
The package ships `canary/server.ts`: a loopback-only, synthetic billing endpoint using the built package handler, report tools and Apps resources. It requires Bun. It has no customer database, authentication credentials, checkout tool or payment mutations. Never mount this unauthenticated fixture in an application or expose it as a production endpoint.
|
|
4
|
+
|
|
5
|
+
From an installed package, run `bun node_modules/@absolutejs/mcp/canary/server.ts`. From this repository, build first, then `bun run canary`. The fixed endpoint is `http://127.0.0.1:4428/mcp`. Startup validates all four report calls before listening. Stop it with Ctrl-C after the test. A port conflict fails startup rather than replacing another service.
|
|
6
|
+
|
|
7
|
+
The JSONL log records protocol methods, status, client name/version, negotiated protocol, advertised Apps MIME types and tool success. It omits headers, session IDs, tool arguments and report bodies. Keep host transcripts local: host logs can contain unrelated account or environment information. Commit only reviewed evidence.
|
|
8
|
+
|
|
9
|
+
## Host connections
|
|
10
|
+
|
|
11
|
+
Claude Code supports a per-invocation config, so testing need not modify an existing connector:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
claude --strict-mcp-config --mcp-config '{"mcpServers":{"billing_canary":{"type":"http","url":"http://127.0.0.1:4428/mcp"}}}'
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Codex CLI supports invocation-scoped configuration:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
codex --config 'mcp_servers.billing_canary.url="http://127.0.0.1:4428/mcp"' --config 'mcp_servers.billing_canary.required=true'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For VS Code, use an isolated test workspace with this `.vscode/mcp.json`, then start the server from the MCP configuration UI:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"servers": {
|
|
28
|
+
"billing_canary": { "type": "http", "url": "http://127.0.0.1:4428/mcp" }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
These commands describe connection setup, not proof that a host renders Apps. Conversational services that require an externally reachable endpoint need a dedicated synthetic staging endpoint and a signed-in test account. The loopback fixture is not directly reachable from those hosted services; do not point them at a customer's billing account to substitute for this test.
|
|
34
|
+
|
|
35
|
+
## Test sequence and acceptance
|
|
36
|
+
|
|
37
|
+
Ask the host to call `get_billing_status`, `get_usage_report` with `from=2026-09-01` and `to=2026-09-11`, `list_receipts`, then `list_receipts` with the returned `nextCursor`.
|
|
38
|
+
|
|
39
|
+
Expected: zero available credits, 12 reserved, no subscription or portal access; 8 credits consumed across 2 events; one USD receipt for 1000 cents with 200 refunded; an empty final page and null cursor. All four calls must succeed. Check actual tool results and server logs, not just the model's success statement.
|
|
40
|
+
|
|
41
|
+
Record the exact host version/surface, transport, requested and negotiated protocol, Apps MIME capability, tool discovery metadata, tool-call results, and visible rendering. If MIME negotiation is absent, all three tools must omit UI metadata and remain usable as text/structured results. If present, check the three views, refresh, pagination, narrow widths and absence of extra initial tool calls. Separately exercise expired-session reconnect and account isolation against an authenticated staging deployment; the synthetic endpoint does not certify OAuth, tenant isolation, payment permission or migration behavior.
|
|
42
|
+
|
|
43
|
+
## Observed September 11, 2026
|
|
44
|
+
|
|
45
|
+
Tests ran against the built `0.17.0` runtime, with the reusable canary distributed starting in `0.17.1`.
|
|
46
|
+
|
|
47
|
+
| Host | Negotiation | Observed result |
|
|
48
|
+
| ------------------------------------------------ | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- |
|
|
49
|
+
| Claude Code 2.1.265, print mode, Streamable HTTP | Requested 2025-11-25; accepted server 2025-06-18; no Apps MIME extension | Four read calls passed, including cursor pagination; text/structured fallback; no embedded rendering claim |
|
|
50
|
+
| Codex CLI 0.154.0, exec mode, Streamable HTTP | Requested and accepted 2025-06-18; no Apps MIME extension | Four read calls passed, including cursor pagination; text/structured fallback; no embedded rendering claim |
|
|
51
|
+
| Claude web | Browser redirected to sign-in | Not tested; requires signed-in test session and reachable synthetic staging endpoint |
|
|
52
|
+
| VS Code 1.135.0 | Installed version confirmed | Interactive Copilot/MCP Apps UI not exercised |
|
|
53
|
+
| Cursor, Gemini CLI, goose | Executables unavailable in this environment | Not tested |
|
|
54
|
+
| ChatGPT hosted surfaces | No host canary performed | Not tested |
|
|
55
|
+
|
|
56
|
+
Both tested terminal clients tolerated GET 405 (no standalone SSE stream) and discovered all three tools. Claude Code also sent a `server/discover` probe that received 400 before successful initialization. These observations do not establish support in other versions or distributions. Initial fixture-development attempts failed receipt validation; the final fixture uses the billing package cursor encoder, supplies a valid receipt ID, and validates its records before listening.
|
|
57
|
+
|
|
58
|
+
The remaining release gates are real conversational/IDE rendering, authenticated staging reconnect/isolation, then the product's migration and commerce approval gates. This evidence does not enable any host commerce profile.
|
|
59
|
+
|
|
60
|
+
Sources: [Codex MCP configuration](https://learn.chatgpt.com/docs/extend/mcp?surface=cli), [VS Code MCP servers](https://code.visualstudio.com/docs/copilot/customization/mcp-servers), and the installed `claude --help` / `claude mcp --help` output. Host observations above come from actual local runs, not documentation claims.
|
package/docs/mcp-apps.md
CHANGED
|
@@ -54,3 +54,7 @@ Protocol tests cover negotiation, text fallback, spoofed later capabilities, aut
|
|
|
54
54
|
Passing this fixture does not certify a particular conversational, IDE or terminal host. Real-host capability/refresh/session-expiry checks remain required before activation. Rendering support never supplies checkout permission; apply [commerce-host-rules.md](commerce-host-rules.md) independently.
|
|
55
55
|
|
|
56
56
|
Primary references checked September 11, 2026: [stable Apps specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx), [official quickstart](https://apps.extensions.modelcontextprotocol.io/api/documents/quickstart.html), [App SDK](https://apps.extensions.modelcontextprotocol.io/api/classes/app.App.html), [AppBridge SDK](https://apps.extensions.modelcontextprotocol.io/api/classes/app-bridge.AppBridge.html).
|
|
57
|
+
|
|
58
|
+
## Real-host canaries
|
|
59
|
+
|
|
60
|
+
See [the reusable host canary and observed results](host-canaries.md) for isolated connection instructions, acceptance criteria and remaining rollout gates.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "Alex Kahn",
|
|
3
|
-
"description": "Serve a remote Model Context Protocol endpoint (streamable HTTP, stateless) from a tool/prompt/resource registry
|
|
3
|
+
"description": "Serve a remote Model Context Protocol endpoint (streamable HTTP, stateless) from a tool/prompt/resource registry — OAuth bearer auth, RFC 9728 discovery, and per-call guards are yours to wire, the JSON-RPC protocol is done correctly by construction.",
|
|
4
4
|
"devDependencies": {
|
|
5
5
|
"@absolutejs/agency": "0.7.1",
|
|
6
6
|
"@absolutejs/changelog": "^0.6.0",
|
|
@@ -69,7 +69,9 @@
|
|
|
69
69
|
"dist",
|
|
70
70
|
"docs/commerce-host-rules.md",
|
|
71
71
|
"docs/mcp-apps.md",
|
|
72
|
-
"docs/third-party"
|
|
72
|
+
"docs/third-party",
|
|
73
|
+
"canary",
|
|
74
|
+
"docs/host-canaries.md"
|
|
73
75
|
],
|
|
74
76
|
"scripts": {
|
|
75
77
|
"build": "bun run build:apps && rm -rf dist && bun build src/index.ts src/manifest.ts src/commerce.ts src/apps.ts --outdir dist --root ./src --target=bun --external @absolutejs/agency --external '@absolutejs/agency/*' --external elysia && tsc --emitDeclarationOnly --project tsconfig.json && absolute-manifest emit",
|
|
@@ -77,10 +79,12 @@
|
|
|
77
79
|
"release": "bun run format && bun run test && bun run build && bun publish",
|
|
78
80
|
"test": "bun run build:apps && bun test",
|
|
79
81
|
"typecheck": "bun run build:apps && tsc --noEmit --project tsconfig.json",
|
|
80
|
-
"check:package": "bun run typecheck && bun run test && bun run build && absolute-changelog check",
|
|
82
|
+
"check:package": "bun run typecheck && bun run test && bun run build && bun run check:canary && absolute-changelog check",
|
|
81
83
|
"prepublishOnly": "bun run check:package",
|
|
82
|
-
"build:apps": "bun scripts/build-apps.ts"
|
|
84
|
+
"build:apps": "bun scripts/build-apps.ts",
|
|
85
|
+
"canary": "bun canary/server.ts",
|
|
86
|
+
"check:canary": "tsc --noEmit --strict --skipLibCheck --moduleResolution bundler --module esnext --target esnext --types bun canary/server.ts"
|
|
83
87
|
},
|
|
84
88
|
"types": "./dist/src/index.d.ts",
|
|
85
|
-
"version": "0.17.
|
|
89
|
+
"version": "0.17.1"
|
|
86
90
|
}
|