MCP Host Canary

Managed-host debugging guide

Connected isn’t called.

A managed MCP host can show a server as connected while the server’s tool callback never runs. That is not necessarily a contradiction. “Connected” is a host-visible state; callback execution is a server-observed event. The useful question is: what is the last protocol boundary the test server actually observed?

MCP Host Canary creates a disposable known server, records only allowlisted protocol boundaries, and freezes them in a privacy-minimized receipt. It does not inspect a host’s internals or assign a compatibility grade.

Why a connection indicator is not callback evidence

After a host accepts an MCP URL and reports a connected state, several materially different server-side results can follow:

Only the final case proves callback execution at this test server. A tool appearing in a host UI is weaker evidence: it can be consistent with manifest delivery, but it does not establish that the host later dispatched a call. Canary therefore records tools/call.request separately from tools/call.executed.

Read the server-observed waterfall

2026-07-28
server/discover?  (optional)
tools/list.request? → tools/list.response
tools/call.request? → tools/call.executed

2025-11-25 and earlier
initialize.request → initialize.response → initialized
tools/list.request? → tools/list.response
tools/call.request? → tools/call.executed

These are two protocol-era paths, not one required sequence. MCP 2026-07-28 removed the initialize / initialized handshake. Its server/discover method is optional, so a modern host can reach tools/list or tools/call without either of those earlier rows. MCP 2025-11-25 and earlier use the legacy handshake path. The current behavior is defined by the official MCP specification and discovery documentation.

On the legacy path, initialize.request means the initialize message reached Canary. initialize.response is added only when the handler completes the response successfully; it does not prove the closed host accepted or used that response.

Likewise, tools/list.request proves a manifest request arrived. The stronger tools/list.response stage is recorded only after Canary finds a returned tool list in the successful response, which is also when it retains the safe tool count.

A tools/call.request stage proves a call request arrived. If its name matches a registered fixture, the allowlisted name can be retained. tools/call.executed is stronger because the registered callback ran. It still does not prove that the host presented the result correctly or that a user’s own MCP server would behave the same way.

An absent stage means only “not observed by this server.” Missing modern discovery or legacy-only initialization is not itself a failed prerequisite. No absent row proves a specific cache, policy, parser, model choice, network component, or product bug inside the host. Stage counts are aggregates, not success rates.

Use two fixtures to separate common boundaries

The baseline profile exposes three compact zero-input tools for a normal discovery and dispatch path. The limits profile exposes 258 tools, including sentinel_257 at the ordering boundary and a schema-boundary fixture with a 16,385-byte description.

The project’s historical controlled runs illustrate why the distinction matters. Both Claude Web and ChatGPT Developer mode reached an executed canary_ping callback in the baseline fixture. In the limits fixture, the retained Claude observation reached successful 258-tool manifest responses without a sentinel callback, while the retained ChatGPT observation reached executed sentinel_257 callbacks. Attempts were not uniform, so these are boundary case notes—not rates, rankings, or current host-wide claims. Read the four-run evidence summary and its limitations before comparing the rows.

Reproduce a boundary safely

  1. Create a fresh baseline run and connect its temporary URL to one managed host.
  2. Use a fresh host conversation and the exact direct, indirect, and negative-control prompts shown by the canary.
  3. Record the callback-count change after each prompt. Disable other connections where the host permits it.
  4. Create a separate limits run and request sentinel_257 once in a fresh conversation.
  5. Finalize each run before copying its Markdown or JSON receipt. Finalization closes the endpoint.

Runs expire after at most 30 minutes and live only in one in-memory process. A deployment, restart, or free-instance suspension can remove active runs and receipts. This is an experimental diagnostic fixture—not a conformance suite, security scanner, proxy, or production gateway.

Share the receipt, not the capability

A finalized receipt can contain the run profile, status, timestamps, stage counts, safe HTTP status, normalized client family, valid protocol version, manifest tool count, and recognized canary tool name. It excludes active MCP URLs, owner identity, sessions, tokens, Authorization values, prompts, responses, bodies, tool arguments, IP addresses, and raw User-Agent strings.

Never publish a live /mcp/:id capability URL or an owner-only API URL. Treat the receipt fingerprint as a correlation identifier, not a digital signature. Pair the finalized receipt with explicitly labeled operator context and use it to narrow the next direct test or support ticket without claiming a cause the server could not observe.

Test your own managed host

Create a disposable 30-minute endpoint without signup or installation, then compare the observed boundary.

Run MCP Host Canary

Prefer to inspect the implementation first? Read the MIT-licensed source or report a finalized safe receipt.