Skip to content

feat(probe): App-side bridge / host SEP-1865 conformance probe#675

Closed
chelojimenez wants to merge 1 commit into
modelcontextprotocol:mainfrom
MCPJam:feat/bridge-probe
Closed

feat(probe): App-side bridge / host SEP-1865 conformance probe#675
chelojimenez wants to merge 1 commit into
modelcontextprotocol:mainfrom
MCPJam:feat/bridge-probe

Conversation

@chelojimenez
Copy link
Copy Markdown

Summary

Adds @modelcontextprotocol/ext-apps/probe — an optional, App-side module that lets any view snapshot which bridge methods and notifications the host actually supports, beyond what App.getHostCapabilities() claims. Hosts vary in which optional MCP Apps methods/notifications they actually implement, and capability claims can diverge from reality — this probe library makes that surface readable and assertable.

What it does

  • attachBridgeProbe(app) (call before app.connect()): observes the full Host → View notification timeline (tool-input, tool-input-partial, tool-result, tool-cancelled, host-context-changed) plus the ui/resource-teardown request — without clobbering any handlers the consumer installs. Chains onto onteardown and uses addEventListener for notifications.
  • probe.capture({ activeProbes: true }): issues each known View → Host method (ping, tools/list, tools/call, resources/list, resources/read, prompts/list, sampling/createMessage, ui/open-link, ui/download-file, ui/message, ui/request-display-mode, ui/update-model-context) with a benign payload and classifies the response:
    • -32601 Method not foundnot-supported
    • any other JSON-RPC error → supported (non-mnf-error) — host understood, refused this call
    • success → supported
  • Pure assertion helpers (assertBridgeMethods, assertHostCapabilities, assertHostContext) return { pass, checks[] }. No test-runner coupling; callers decide whether failure logs/throws/uploads.
  • Named host presets (spec-minimal, chatgpt, claude-desktop, copilot, mcpjam-inspector) for one-line conformance checks. Seeded from public docs; marked for refinement after cross-host runs.

Usage

import { App } from \"@modelcontextprotocol/ext-apps\";
import {
  attachBridgeProbe,
  assertBridgeMethods,
} from \"@modelcontextprotocol/ext-apps/probe\";

const app = new App({ name: \"My App\", version: \"1.0.0\" });
const probe = attachBridgeProbe(app); // before connect — catches one-shot notifs
await app.connect(transport);
// … normal app lifecycle …
const snapshot = await probe.capture({ activeProbes: true });
console.table(snapshot.methods);

const report = assertBridgeMethods(snapshot, { preset: \"chatgpt\" });
if (!report.pass) console.warn(\"Bridge conformance failed:\", report.checks);

Wiring

  • New `./probe` package export entry in `package.json`.
  • New entry in `build.bun.ts` so `dist/src/probe/{index.js,*.d.ts}` ship (≈7.5KB minified ESM).
  • No new runtime dependencies; only uses `@modelcontextprotocol/sdk` types already needed by `App`.

Tests

  • 9 new unit tests in `src/probe/probe.test.ts` using `InMemoryTransport` + a real `AppBridge` fixture covering:
    • Claimed capability / hostInfo / hostContext capture
    • One-shot notification observation when attached pre-connect
    • No-clobber of user-installed `on*` handlers
    • `-32601` → `not-supported` classification
    • Non-MNF errors → `supported (non-mnf-error)`
    • All three assertion helpers (methods, host capabilities, host context)
  • Existing 284 ext-apps tests pass unchanged.

Test plan

  • `npx tsc --noEmit` clean
  • `bun test src` — 293 pass, 0 fail
  • `npm run build` produces `dist/src/probe/index.js` and `.d.ts` files
  • Cross-host smoke: drop probe into a real App in ChatGPT, Claude Desktop, M365 Copilot, MCPJam Inspector and refine preset definitions based on observed differences

🤖 Generated with Claude Code

Add `@modelcontextprotocol/ext-apps/probe` — an optional, App-side module
that lets any view snapshot which bridge methods + notifications the host
actually supports, beyond what `App.getHostCapabilities()` claims.

What it does:
- `attachBridgeProbe(app)` (call before `app.connect()`) observes the
  full Host -> View notification timeline (`tool-input`,
  `tool-input-partial`, `tool-result`, `tool-cancelled`,
  `host-context-changed`) plus the `ui/resource-teardown` request,
  without clobbering any handlers the consumer installs.
- `probe.capture({ activeProbes: true })` issues each known View -> Host
  method (`ping`, `tools/*`, `resources/*`, `prompts/list`,
  `sampling/createMessage`, `ui/open-link`, `ui/download-file`,
  `ui/message`, `ui/request-display-mode`, `ui/update-model-context`)
  with a benign payload and classifies the response:
    - `-32601 Method not found` -> `not-supported`
    - any other JSON-RPC error  -> `supported` (host understood, refused)
    - success                   -> `supported`
- Pure assertion helpers (`assertBridgeMethods`, `assertHostCapabilities`,
  `assertHostContext`) return `{ pass, checks[] }` matching the existing
  per-check shape used by adjacent tooling — no test-runner coupling.
- Named host presets (`spec-minimal`, `chatgpt`, `claude-desktop`,
  `copilot`, `mcpjam-inspector`) for one-line conformance checks; values
  seeded from public docs, refine after cross-host runs.

Why:
Hosts vary in which optional MCP Apps methods and notifications they
actually implement, and capability claims and reality can diverge. The
existing App SDK gives consumers no portable way to read this — they
have to write per-method probes by hand. Library-izing the probe lets
App authors verify host behavior and lets ecosystem tooling (e.g.
mcpjam-learn's host-probe) drop ad-hoc tracking.

Wiring:
- New `./probe` package export.
- New entry in `build.bun.ts`.
- 9 unit tests using `InMemoryTransport` + fake `AppBridge` cover the
  claim/capability capture, one-shot notification observation,
  `-32601` -> `not-supported` vs other-error classification,
  no-clobber of user handlers, and the three assertion helpers.
- Existing 284 ext-apps tests pass unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant