Skip to content

feat(mcp)!: bridge clients and servers with Interchange - #28

Merged
TheGreatAxios merged 28 commits into
mainfrom
cl-11364-speak-mcp-2026-07-28-from-the-corbits-mcp-client
Oct 26, 2026
Merged

TheGreatAxios merged 28 commits into
mainfrom
cl-11364-speak-mcp-2026-07-28-from-the-corbits-mcp-client

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a bidirectional MCP ↔ Interchange bridge with four role entries:

  • @corbits/mcp/client
  • @corbits/mcp/server
  • @corbits/mcp/interchange/client
  • @corbits/mcp/interchange/server

Historical root/hub/sidecar facades remain. The standalone client and server have dependency-isolated runtime graphs; OAuth-core is now an optional peer, explicitly installed when using root/login APIs. This is a breaking installation/API change.

Delivered

  • Shared wire, method/result, header, content and JSON Schema validation; modern stateless MCP 2026-07-28 client/server behavior.
  • Explicitly selected Interchange tool, operation, agent and workflow exports; reverse MCP consumption with mediated credentials, approval floors and rich-result/lifecycle handling.
  • MRTR continuations, elicitation host contracts, progress/cancellation, subscriptions and task runtime/storage primitives.
  • Verified static Skills publication and consumption, identity/content-bound approvals, streaming resource limits and inclusive 16 MiB support.
  • Explicit Apps host capability gating; metadata-only consumers do not advertise rendering support.
  • Guarded authorization discovery, registration, issuer/state/resource binding, token-only grants and resource-server verification hooks.
  • Opt-in legacy server compatibility for 2025-11-25, 2025-06-18 and 2025-03-26. Modern behavior stays stateless. Legacy sessions are bounded, in-memory and instance-affine; March batching is isolated from later revisions.
  • Version/maturity metadata follows upstream specifications; standard wire identifiers are not renamed with experimental prefixes.

Verification

At dacf1b15f3ad7e3643dd10ccc9f7b4eb47c7b150:

  • bun run check: exit 0, 423 tests / 2,116 assertions; typecheck, lint and formatting pass.
  • bun run build: exit 0.
  • Node 24.22.1 bun run test:e2e: exit 0, 22 tests.
  • Node 24.22.1 bun run test:package: exit 0, 13 tests, including real build/pack/ordinary npm installs and Bun/Node factory behavior.
  • Independent scoped reviews approved core, auth, extensions, Interchange adapters, modern/legacy server and bounded large-resource fixes after reproducing and repairing findings.
  • Independent QA exercised both directions using installed Interchange tool runners/credential capabilities and runnable standalone examples.

Eight new commits separate shared/client, extensions, auth, server, adapters, packaging, artifact tests/examples and documentation. Earlier pushed history was retained.

Explicit limits

This delivers the repository's bridge scope, not exhaustive MCP certification or proof of a deployed host. Applications supply trusted identity/grants, cryptographic verification, persistence/recovery, consent/context/execution gates and browser rendering/sandbox enforcement.

The verified Skills consumer declines dynamic manifests and requires a complete resource read with one matching text/blob item; it does not orchestrate resource MRTR. Modern server default and optional legacy behavior are documented separately.

External companions remain open:

  • CL-11479: stock OAuth-core token-only/storage and transport/parameter protections; guarded MCP paths do not repair stock helpers.
  • CL-11480: credential-http RequestInit/scheme handling and published harness/types peer incompatibility with the 0.5 baseline. Neutral installs pass; full pinned hub installation remains blocked by that dependency.

See docs/conformance.md, docs/migration.md, docs/public-api.md, docs/legacy-server.md and src/auth/INTEGRATION.md for evidence and responsibilities.

Closes CL-11364
Closes CL-11472

Implementation buckets: CL-11473, CL-11474, CL-11475, CL-11476, CL-11477, CL-11478.

@linear-code

linear-code Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

CL-11364

CL-11472

@corbits-triage

corbits-triage Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Triage: Fail

@TheGreatAxios, please address the following:

  • required checks are failing: check

Notes:

  • pull request is over the files limit: 120 files (max 30)
  • pull request is over the lines limit: 20935 lines changed (max 5000)

Waiting on a maintainer to approve: request changes.

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: PR #28 — feat(client): speak MCP 2026-07-28

Verdict: Request changes.

Verified clean

The MCP 2026-07-28 client work itself is in good shape. Five independent review lanes plus two verification/convergence lanes cross-checked the PR against the branch code and temporary repro tests:

  • Stateless server/discover probe + per-call _meta / MCP-Protocol-Version / Mcp-Method / Mcp-Name headers, no session on that path — verified.
  • Legacy fallback ladder (-32020 / -32021 no-fallback, -32022 fallback-only-when-legacy-listed) — verified.
  • input_required → mcpContinue, progress, cancel, tasks/skill gating, hub discovery dual-mode — verified.
  • First-wave claims that the -32022 fallback branch was dead code and that the fallback could re-enter the ladder were disproved — no fixes needed there.

Findings (deduplicated, conflict-resolved)

  1. Blocking — public API return-type drift (VERIFIED). mcpCallTool moved from returning Promise<McpToolResult> to Promise<McpToolCallResult> (McpToolResult | McpInputRequired | McpTaskHandle), and McpToolResult was repurposed to require resultType: "complete". Downstream consumers typing results as McpToolResult or reading result.isError / result.content no longer compile. Fix landed: resultType made optional on resolved results + regression test.

  2. Should-fix HIGH — VERIFIED. Legacy initialize handshake advertised stale client identity (0.2.0) vs. modern path (0.3.1). Fix landed.

  3. Should-fix HIGH — VERIFIED. Session-less core calls bypassed the modern stateless path and sent no protocol metadata. Fix landed.

4-7. Should-fix HIGH — docs (confirmed). README client-options sentence, README reference table, AGENTS.md method list, Mcp-Name scope, Upgrading framing. Fix landed.

  1. Should-fix HIGH — coverage (confirmed). Agent-tool result shaping, mcpContinue beyond tools/call, task-state validation, no-version error path, -32021 on connect, listen/extensions gating. Regression tests landed for the blocking and stateless paths; remainder recommended.

Verification

  • bun run check → exit 0 (typecheck / lint / format + unit tests) on the fixed branch.

Ask

Fixes for the converged findings landed as individual commits on cl-11364-speak-mcp-2026-07-28-from-the-corbits-mcp-client (fix(client): … ×3, docs: …).

@TheGreatAxios TheGreatAxios left a comment •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Adds a bidirectional MCP ↔ Interchange bridge with four role entries:

  • @corbits/mcp/client
  • @corbits/mcp/server
  • @corbits/mcp/interchange/client
  • @corbits/mcp/interchange/server

Historical root/hub/sidecar facades remain. The standalone client and server have dependency-isolated runtime graphs; OAuth-core is now an optional peer, explicitly installed when using root/login APIs. This is a breaking installation/API change.

Delivered

  • Shared wire, method/result, header, content and JSON Schema validation; modern stateless MCP 2026-07-28 client/server behavior.
  • Explicitly selected Interchange tool, operation, agent and workflow exports; reverse MCP consumption with mediated credentials, approval floors and rich-result/lifecycle handling.
  • MRTR continuations, elicitation host contracts, progress/cancellation, subscriptions and task runtime/storage primitives.
  • Verified static Skills publication and consumption, identity/content-bound approvals, streaming resource limits and inclusive 16 MiB support.
  • Explicit Apps host capability gating; metadata-only consumers do not advertise rendering support.
  • Guarded authorization discovery, registration, issuer/state/resource binding, token-only grants and resource-server verification hooks.
  • Opt-in legacy server compatibility for 2025-11-25, 2025-06-18 and 2025-03-26. Modern behavior stays stateless. Legacy sessions are bounded, in-memory and instance-affine; March batching is isolated from later revisions.
  • Version/maturity metadata follows upstream specifications; standard wire identifiers are not renamed with experimental prefixes.

Verification

At dacf1b15f3ad7e3643dd10ccc9f7b4eb47c7b150:

  • bun run check: exit 0, 423 tests / 2,116 assertions; typecheck, lint and formatting pass.
  • bun run build: exit 0.
  • Node 24.22.1 bun run test:e2e: exit 0, 22 tests.
  • Node 24.22.1 bun run test:package: exit 0, 13 tests, including real build/pack/ordinary npm installs and Bun/Node factory behavior.
  • Independent scoped reviews approved core, auth, extensions, Interchange adapters, modern/legacy server and bounded large-resource fixes after reproducing and repairing findings.
  • Independent QA exercised both directions using installed Interchange tool runners/credential capabilities and runnable standalone examples.

Eight new commits separate shared/client, extensions, auth, server, adapters, packaging, artifact tests/examples and documentation. Earlier pushed history was retained.

Explicit limits

This delivers the repository's bridge scope, not exhaustive MCP certification or proof of a deployed host. Applications supply trusted identity/grants, cryptographic verification, persistence/recovery, consent/context/execution gates and browser rendering/sandbox enforcement.

The verified Skills consumer declines dynamic manifests and requires a complete resource read with one matching text/blob item; it does not orchestrate resource MRTR. Modern server default and optional legacy behavior are documented separately.

External companions remain open:

  • CL-11479: stock OAuth-core token-only/storage and transport/parameter protections; guarded MCP paths do not repair stock helpers.
  • CL-11480: credential-http RequestInit/scheme handling and published harness/types peer incompatibility with the 0.5 baseline. Neutral installs pass; full pinned hub installation remains blocked by that dependency.

See docs/conformance.md, docs/migration.md, docs/public-api.md, docs/legacy-server.md and src/auth/INTEGRATION.md for evidence and responsibilities.

Closes CL-11364
Closes CL-11472

Implementation buckets: CL-11473, CL-11474, CL-11475, CL-11476, CL-11477, CL-11478.

@TheGreatAxios TheGreatAxios changed the title feat(client): speak MCP 2026-07-28 feat(client)!: speak MCP 2026-07-28 Oct 25, 2026
Centralize wire schemas, headers, result applicability and resource bounds. Harden stateless transport and legacy negotiation with regression coverage. Expose repeated continuation and polymorphic resource results explicitly.
Add opt-in task persistence and recovery, bounded skill publication and verification, and explicit Apps host capability gates with lifecycle tests.
Bind OAuth discovery and callbacks to issuer and resource policy, isolate neutral authorization contracts, and enforce mediated credential boundaries with security regressions.
Add fetch-compatible providers, authorization and extension dispatch. Bound identity-scoped legacy sessions across three 2025 revisions, including cancellation, shutdown and initialization regression coverage.
Share live and stored-catalog result projection, preserve mediated tools and ask floors, and connect explicit host continuation, task, skill and Apps services. Expose only selected authorized operations with adapter tests.
Publish standalone client/server and Interchange entries plus the explicit file task store. Declare AJV and YAML runtimes and make host peers optional for neutral consumers. Root and guarded login consumers must now install OAuth-core explicitly; no version bump is included.
Test packed neutral, host, auth and legacy entry points through ordinary npm installs and add runnable standalone examples. Cover public declarations, runtime isolation and bidirectional integration.
Retain the architecture scope and document migration, role APIs, legacy deployment and final serial verification. Preserve companion dependency defects and host obligations without claiming exhaustive certification.
@TheGreatAxios TheGreatAxios changed the title feat(client)!: speak MCP 2026-07-28 feat(mcp)!: bridge clients and servers with Interchange Oct 25, 2026
@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Review · Comment

API architecture lens. PR #28 at dacf1b1 adds four role entries and keeps the historical facades. This is not the merge verdict.

Findings

  • src/interchange/server.ts:226 and src/server/tools.ts:45-68 — Exposing an Interchange operation authorizes exposure.resource / exposure.action, then the server authorizes a second hardcoded pair, mcp:tools / list or call. docs/migration.md:36-38 only tells the host to bind the exposure pair and approve. A policy that allows only the exposure resource is rejected before approval. auth: "public" on the provider side can also skip the exposure check while the server still requires mcp:tools. Consider one documented authorization contract, or have the provider resource be the exposure resource rather than a second implicit name.
  • 46fddd0 / CONTRIBUTING.md:20 — Moving @corbits/oauth-core to an optional peer is a real install break and is documented, but the commit is build!: …. CONTRIBUTING reserves ! for removed or renamed exports, changed signatures, and newly required parameters. Peer-range changes are build(deps): without !. The signature break is already marked by feat(client)!. The package remains 0.3.1; the minor bump belongs to the manual release, not this PR.

What holds

package.json exports match @corbits/mcp/client, /server, /interchange/client, /interchange/server, and /server/task-store. The standalone client does not import Interchange or OAuth. mcpServers stays synchronous and mcpTools stays asynchronous. mcpCallTool returning Promise<McpToolCallResult> is an intentional signature break and is covered by feat(client)! plus the migration notes.

@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Review · Request changes

PR #28 at dacf1b1 adds the bidirectional MCP and Interchange bridge. bun run check exits 0 (423 tests) and bun run test:e2e exits 0 (22 tests). Three new logic bugs remain.

Findings

  • src/extensions/tasks/index.ts:159-185 — submitInput returns resultType: "complete" when the task is no longer input_required. answered stays empty, the executor is not called, and revision still increments. A host that treats success as accepted input will not retry, so a concurrent status change drops the response. submitInput should fail without a revision bump unless the task is still waiting for that input.
  • src/extensions/skills/index.ts:415-423 — directoryRead keys children by URI. Publishing skill://demo/scripts and skill://demo/scripts/run.sh, then reading skill://demo, yields one scripts entry; the later visitor replaces the earlier one. A file and a directory that share a prefix cannot both appear. Directory entries need distinct keys for the resource and its child directory.
  • src/auth/login.ts:160-199 — complete marks the session consumed before exchangeCode returns. If cancel() runs while the token endpoint is stalled, persist still stores the grant. If persist then throws, the session is already consumed and the tokens are not returned. Check cancellation after the exchange and before persistence, and do not consume the session until the grant is stored.

Verification

Temporary probes under tmp/critique-tests/ reproduced all three and were removed. The existing suite does not cover these sequences.

Commit subjects

47c1772 (85 characters) and 528833c (76 characters) exceed the 72-character subject limit in CONTRIBUTING.md. They are already pushed; a squash can use a shorter subject. The diff stat has no binary files.

@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Review · Comment

Security lens. PR #28 at dacf1b1 is not safe to close on these trust boundaries. This is not the merge verdict.

Findings

  • src/oauth-discovery.ts:348-369 and src/auth/login.ts:155-186 — A protected-resource document can name an honest authorization endpoint and an attacker token endpoint. When that metadata omits iss support, complete() without iss posts the authorization code and PKCE verifier to the attacker origin. Reproduced: authorize URL origin https://idp.honest.example, token POST to https://attacker.example/token. Supplying iss for the honest issuer correctly refused the exchange. If the attacker metadata lists offline_access, that scope is added even when the resource only advertised read. Reject the exchange when either endpoint origin differs from the issuer origin unless the callback iss is present and equals the recorded issuer.
  • src/extensions/skills/index.ts:168-198 and :264 — hold() fingerprints the caller’s object, awaits digest, and clones only afterward. Mutating frontmatter, digest, and size before that promise resolves, while source.read returns the mutated bytes, keeps the previous execution approval. Reproduced: one approval, identical fingerprints, description Malicious, and assertExecutionApproved() succeeded. Snapshot and check the entry before the first await.
  • src/server.ts:188-211 — After authentication, server/discover returns capabilities and serverInfo before authorize(). With authorize() always false, discover was HTTP 200 and never called authorize; tools/call was 403. A denied principal learns the server name and capability flags. Run the same authorization check unless discovery is an explicit exemption.

Checked

Resource-server authentication rejects query tokens, requires exact issuer and audience, and fails closed on verifier or scope errors. OAuth metadata and token fetches use redirect: "error". Legacy sessions are partitioned by tenant, principal, and authorization context. Hub discovery refuses a stored resource mismatch before attaching a bearer. Callback state and iss mismatches do not consume the login.

Stock @corbits/oauth-core, @corbits/credential-http, and deployment controls were not certified. The temporary probe was removed.

submitInput no longer CAS-writes a task that is not input_required or not waiting for the submitted keys, including after a concurrent transition.
Consume the authorization session only after the grant is stored, and
skip persistence when cancel lands during the token request.
Refuse to send the code and verifier when an authorization or token
endpoint origin differs from the issuer unless iss matches, and do not
add offline_access from that cross-origin metadata.
Denied principals no longer receive server/discover. Tool catalog and
invocation use the exposure resource and action, not mcp:tools.
@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Review · Request changes

The six local review-fix commits on PR #28 hold, and bun run check exits 0 (434 tests). c26068d adds a catalog-membership oracle.

Findings

  • src/server/tools.ts:99, src/server.ts:259, and src/server/legacy.ts:225-231 — When tools/call names a tool the caller may not use, authorization returns a pair and the call is HTTP 403, "Operation forbidden", with no execution. When the name is absent from the catalog, authorization returns null, the gate is skipped, and the modern path answers HTTP 200 while the legacy path says "Tool unavailable". A principal who cannot see the catalog can still tell a hidden tool from an unknown name. tools/list correctly stays an empty catalog rather than a 403. Unknown tools/call names need the same status and error text as a denied binding, on both the modern and legacy paths, with zero executions.

Verified

submitInput fails before the revision bump when the task is not waiting. Skill directory children stay distinct, and hold snapshots the skill before hashing. A cancelled token exchange does not persist. A cross-origin token endpoint cannot receive the code and verifier without a matching iss. Private server/discover stays empty when no pair is allowed, and tool calls use the exposure resource and action rather than mcp:tools.

The temporary probe was removed. The working tree was clean.

A missing tools/call name skipped the gate, so it did not match a
denied binding. Private modern and legacy calls now share that denial,
while tools/list stays a filtered catalog.
A rejected tools/call validated the catalog schema before the gate, so
a missing required field differed from an unknown name. Denial now
precedes schema and header checks.
@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Review-fix follow-up

Pushed dacf1b1..dab6c9e. The six review repairs hold, and the catalog oracle found in that range is closed.

  • Task input is rejected, without a revision bump, unless the task is still waiting.
  • A skill directory listing keeps both a file and a same-prefix directory. hold snapshots the skill before hashing.
  • Cancelling a token exchange does not persist the grant.
  • A cross-origin token endpoint cannot receive the code and verifier without a matching iss.
  • Private server/discover is authorized, and tool calls use the exposure resource and action.
  • A private tools/call for a hidden name and an unknown name now match before schema or header checks: modern HTTP 403 with an empty body, legacy Operation forbidden, no execution. tools/list stays an empty catalog. An allowed call still validates its schema.

Independent recheck at dab6c9e approved the oracle closure, including a required-field payload and a schema-valid payload. The local simplification commit 0b475fb is not in this push.

Drop the one-use origin helper and method gate, the dead dynamic-skill
check, the repeated exchange expiry check, and a restated scope comment.
Authorization and protocol behavior is unchanged.
@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Simplification

Pushed ae759b8 on top of dab6c9e. It only removes a dead dynamic-skill check, a one-use origin helper, an unused bindings array, and a repeated expiry check that already runs before the grant is stored. The denial-before-schema gate in src/server.ts was kept. bun run check passed 435 tests.

A bare install cannot load the root entry because agent and oauth-core
are optional peers. Check the standalone client without them, and the
historical root exports with those peers installed.
@TheGreatAxios
TheGreatAxios merged commit 37e4c8d into main Oct 26, 2026
2 checks passed
@TheGreatAxios
TheGreatAxios deleted the cl-11364-speak-mcp-2026-07-28-from-the-corbits-mcp-client branch October 26, 2026 00:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant