Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0bc9857
feat(client): speak MCP 2026-07-28
TheGreatAxios Oct 24, 2026
a14acc4
fix(client): restore mcpCallTool public return-type contract
TheGreatAxios Oct 24, 2026
528833c
fix(client): unify legacy initialize client identity with modern clie…
TheGreatAxios Oct 24, 2026
7e759d4
fix(client): enforce stateless metadata for session-less core calls
TheGreatAxios Oct 24, 2026
63f0c7d
docs: cover stateless client calls, options, and upgrade path
TheGreatAxios Oct 24, 2026
47c1772
fix(client): do not fall back on authz/server/rate-limit or malformed…
TheGreatAxios Oct 24, 2026
4fd8ef9
fix(oauth): reject dot-segment client metadata urls
TheGreatAxios Oct 24, 2026
3757b23
fix: fall back on unrecognized HTTP 400 discovery errors
TheGreatAxios Oct 25, 2026
1fdac37
fix: omit stale state from MRTR continuations
TheGreatAxios Oct 25, 2026
8217114
docs: explain union result migration and protocol test scope
TheGreatAxios Oct 25, 2026
f6d2b99
feat(client)!: validate shared protocol and continuation contracts
TheGreatAxios Oct 25, 2026
13e83a8
feat(extensions): implement tasks skills and apps runtimes
TheGreatAxios Oct 25, 2026
9e49b07
feat(auth): guard discovery login and resource authorization
TheGreatAxios Oct 25, 2026
c7c4932
feat(server): serve modern requests and opt-in legacy sessions
TheGreatAxios Oct 25, 2026
a6b15ed
feat(interchange): bridge selected bindings and host lifecycles
TheGreatAxios Oct 25, 2026
46fddd0
build!: expose role entries and require explicit oauth peer installs
TheGreatAxios Oct 25, 2026
66b3079
test(package): exercise installed bridges and standalone examples
TheGreatAxios Oct 25, 2026
dacf1b1
docs: document bridge contracts and qualified conformance evidence
TheGreatAxios Oct 25, 2026
ffb3b76
fix(tasks): reject input when the task is not waiting
TheGreatAxios Oct 25, 2026
2c7da45
fix(skills): keep distinct directory children and snapshot holds
TheGreatAxios Oct 25, 2026
5ccd8c9
fix(auth): do not persist a cancelled token exchange
TheGreatAxios Oct 25, 2026
3c30ff2
fix(auth): require iss for cross-origin token exchange
TheGreatAxios Oct 25, 2026
e8679d9
fix(tasks): narrow inputRequests to the waiting state
TheGreatAxios Oct 25, 2026
c26068d
fix(server): authorize discover and the exposure pair
TheGreatAxios Oct 25, 2026
84e0b4b
fix(server): deny unknown tool calls like rejected bindings
TheGreatAxios Oct 25, 2026
dab6c9e
fix(server): deny tool calls before schema checks
TheGreatAxios Oct 25, 2026
ae759b8
refactor: simplify the six review-fix commits
TheGreatAxios Oct 25, 2026
bd6dbed
ci: smoke the neutral client and the peer-backed root entry
TheGreatAxios Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ jobs:
run: |
set -euo pipefail
TARBALL="$PWD/$(npm pack --silent)"
mkdir -p "$RUNNER_TEMP/c" && cd "$RUNNER_TEMP/c"
mkdir -p "$RUNNER_TEMP/neutral" && cd "$RUNNER_TEMP/neutral"
npm init -y >/dev/null && npm pkg set type=module >/dev/null
npm install "$TARBALL"
node -e 'import("@corbits/mcp/client").then((m) => { for (const n of ["mcpInitialize", "mcpListTools", "mcpCallTool"]) if (typeof m[n] !== "function") throw new Error("missing export: " + n); })'
mkdir -p "$RUNNER_TEMP/host" && cd "$RUNNER_TEMP/host"
npm init -y >/dev/null && npm pkg set type=module >/dev/null
npm install "$TARBALL" @intx/agent@0.5.0 @corbits/oauth-core@0.3.2
node -e 'import("@corbits/mcp").then((m) => { for (const n of ["mcpTools", "mcpInitialize", "mcpListTools", "mcpCallTool"]) if (typeof m[n] !== "function") throw new Error("missing export: " + n); })'
71 changes: 52 additions & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

## Purpose

`@corbits/mcp` turns each tool on a remote MCP server (streamable HTTP, 2025-03-26 spec) into its own `@intx/agent` tool for the Interchange sidecar. It owns the MCP client, tool naming and the ask floor, the sidecar-bundle factory, the hub catalog-discovery route and MCP OAuth discovery. It does not own the OAuth login itself (`@corbits/oauth-core`) or credential storage (the hub).
`@corbits/mcp` turns each tool on a remote MCP server into its own `@intx/agent` tool for the Interchange sidecar. The client speaks MCP **2026-07-28** (stateless `server/discover`, per-request `_meta` and `Mcp-*` headers). A server that does not implement `server/discover`, times out, or only speaks `2025-11-25` / `2025-06-18` / `2025-03-26` / `2024-11-05` falls back to `initialize` plus `Mcp-Session-Id`. It owns the MCP client, tool naming and the ask floor, the sidecar-bundle factory, the hub catalog-discovery route and MCP OAuth discovery. It does not own the OAuth login itself (`@corbits/oauth-core`) or credential storage (the hub).

## Layout

- `src/client.ts`: streamable-HTTP JSON-RPC client (`initialize`, `tools/list`, `tools/call`).
- `src/client.ts`: streamable-HTTP JSON-RPC client. `mcpConnect` probes `server/discover`. Modern calls are `tools/list`, `tools/call`, `prompts/list`, `prompts/get`, `resources/list`, `resources/read`, `resources/templates/list`, `completion/complete`, and `subscriptions/listen`. `mcpInitialize` is the explicit legacy handshake. Extensions (`io.modelcontextprotocol/tasks`, `io.modelcontextprotocol/ui`, `io.modelcontextprotocol/skills`) are advertised and sent only when the caller opts in by extension id.
- `src/tool.ts`: `mcpTools`, one `@intx/agent` tool per remote MCP tool, named `<server>.<tool>`.
- `src/naming.ts`: qualified names and the ask floor, shared by `mcpTools` and the sidecar bundle.
- `src/url.ts`: which MCP endpoints a token may be sent to.
- `src/oauth-discovery.ts`: MCP OAuth metadata discovery and dynamic client registration.
- `src/oauth-discovery.ts`: MCP OAuth metadata discovery, Client ID Metadata Documents, dynamic client registration, issuer-keyed stored credentials, and RFC 9207 `iss` checks before a code is read.
- `src/sidecar-bundle.ts`: the `./sidecar-bundle` entry a deployed agent's bundle loads.
- `src/hub/discover.ts`, `src/hub/index.ts`: the `./hub` entry, server-side catalog discovery.
- `src/test-server.ts`: in-test MCP server used by the tests.
Expand All @@ -28,31 +28,40 @@ never hold a server's bearer.
route this package mounts. The browser never holds an MCP token, so this
is the only way an OAuth-protected server's `tools/list` can be read.
2. **The catalog is stored** with the rest of the agent's deploy config.
3. **`mcpTools` turns it into tools.** It is synchronous and does no
network work at construction: the names and the `ask` marks come from the
stored catalog.
3. **`mcpServers` turns the stored catalog into tools.** Construction is
synchronous and does no network work: names and `ask` marks come from the
stored catalog. By contrast, `mcpTools` is asynchronous and discovers the
remote catalog live before building tools.
4. **At run time the agent only ever sees a mediated handle.** Each server's
credential resolves to an `http` handle (a fetch pinned to the credential's
origin that injects the bearer per request), so the token never reaches
agent code, and a relative path is all the bundle ever asks for.

### Why not `@modelcontextprotocol/sdk`

The client in `src/client.ts` is hand-rolled on purpose. It only sends
`initialize`, `tools/list` and `tools/call`, each as one request awaiting one
reply, plus the `notifications/initialized` notification; in the sidecar bundle that request goes through a fetch the mediated
credential handle pins to the server's origin. That needs only JSON-RPC
framing and reading the matching `data:` frame from an event-stream reply.
The client in `src/client.ts` is hand-rolled on purpose. A 2026 server gets
one POST per call after `server/discover`: protocol version, client info, and
client capabilities travel in `_meta`, and `Mcp-Method` / `Mcp-Name` /
`MCP-Protocol-Version` travel as headers. There is no session id, no
`initialize`, and no `notifications/initialized` on that path. A call made with
no session speaks this stateless path directly. `input_required`
comes back as a typed result and `mcpContinue` retries the original method
with `inputResponses`. `subscriptions/listen` is the long-lived stream.
`notifications/progress` stays on the response stream of the call that asked
for it. Modern HTTP cancellation closes the response stream; it does not post `notifications/cancelled`.

A legacy server still gets `initialize`, `notifications/initialized`, and
`Mcp-Session-Id`. In the sidecar bundle every request goes through a fetch
the mediated credential handle pins to the server's origin.

The SDK's streamable-HTTP client transport is built for long-lived sessions
(resumable streams, server-initiated requests) and installs `zod`, `ajv`,
`express` and more, none of which this package uses.

The session a stateful server issues on `initialize` is carried in
`McpSession` and sent as `Mcp-Session-Id` with `MCP-Protocol-Version` on
every later request. Resumable streams and server-to-client requests are not
supported. `e2e/` runs discovery and tool calls against the SDK's stateless
and stateful server transports; the SDK is a devDependency only.
(resumable streams, server-initiated requests) and installs server-framework dependencies this client does not need. Shared wire/schema validation now uses a directly declared AJV dependency, not the SDK runtime. Resumable streams
(`Last-Event-ID`), the removed HTTP+SSE transport, modern `ping`, and the deprecated
roots, sampling, and logging methods are not implemented. `e2e/` runs
discovery and tool calls against the SDK's stateless and stateful 2025
transports and against a small 2026 fixture; the SDK is a devDependency only.
Prompts, resources, and subscriptions are client methods. They are not
projected into agent tools, and tools stay the only grantable unit.

## Local development

Expand All @@ -65,3 +74,27 @@ bun install && bun run check && bun run test:e2e
- Read `README.md` before changing public behavior.
- Keep changes focused; update docs and tests alongside code changes.
- Run `bun run check` before declaring work complete.

## Bidirectional package boundaries

- `./client` and `./server` are standalone entries. Never import agent, harness, hub, DB or Hono runtime code into their dependency graph.
- `./interchange/client` consumes remote MCP; `./interchange/server` exposes only explicitly selected host bindings. Preserve the root, sidecar-bundle and hub facades.
- Shared wire validation belongs in `src/protocol`; extension runtimes accept transport/provider interfaces instead of importing client facades.
- Runtime JSON Schema validation uses declared AJV; skill YAML parsing uses declared YAML. The MCP SDK remains development-only and only tests legacy transport interoperability.
- `docs/conformance.md` tracks role/revision/capability-qualified evidence; never infer complete conformance from aggregate test counts.
- Package gates: `bun run check`, `bun run test:e2e`, `bun run build`, `bun run test:package`. The packed smoke suite requires npm, registry access and Node 24+.

## Server compatibility and capability gates

- `createMcpServer` is modern/stateless by default. Explicit `legacy` options
enable the three 2025 Streamable HTTP revisions; see docs/legacy-server.md for
session bounds, affinity/restart behavior, initialization authorization and
March-only batches. Do not describe this implemented shim as missing.
- Apps metadata is not host support. Advertisement requires explicit host bridge,
sandbox/CSP/authenticated-bridge support and presentation; actual rendering is
host-owned. Keep metadata-only hosts unadvertised.
- OAuth-core is an optional peer required by root/guarded login, not by neutral
client/server. Keep stock dependency defects CL11479/CL11480 distinct from
local guarded behavior and from application deployment obligations.
- Shared-worktree build/pack gates must run serially; record exact runtime
versions and do not infer final Node26 evidence from earlier Node24 results.
Loading
Loading