An MCP endpoint can return HTTP 200 while the integration still fails. A successful HTTP request does not establish that the response matches the JSON-RPC request, that initialization negotiated a supported revision, or that subsequent requests carry the session headers.
NAIF Gravity MCP Diagnostics is a small Python standard-library probe for that narrower question: can a client complete supported Streamable HTTP discovery?
Repository: https://github.com/naief9961-tech/naif-gravity-mcp-diagnostics
The sequence matters
For the handshake-era revisions supported by this probe, discovery follows this sequence:
- Send
initializeand validate the response ID, result shape and negotiated revision. - Forward the negotiated
MCP-Protocol-Versionand any returnedMcp-Session-Id. - Send
notifications/initializedwithout a request ID. - If the server advertises tools, request
tools/listand follow bounded pagination.
A JSON-RPC error inside an HTTP 200 response is still a failure. A mismatched response ID is also a failure: it cannot establish that the reply belongs to this request.
Try it on an endpoint you may test
Requires Python 3.10 or later; no third-party packages.
git clone https://github.com/naief9961-tech/naif-gravity-mcp-diagnostics.git
cd naif-gravity-mcp-diagnostics
python3 tools/mcp_health_check.py https://your-mcp.example/mcp
Replace the example endpoint with one you own or are authorized to test. For bearer authentication, provision a scoped test token securely in your environment and pass only its variable name:
python3 tools/mcp_health_check.py https://your-mcp.example/mcp --token-env MCP_TEST_TOKEN
This does not perform OAuth login or refresh. Bearer authentication requires HTTPS except on loopback, and redirects are refused.
JSON and SSE are both valid response shapes
A POST response may contain JSON or an SSE stream. The probe reads SSE comments and multiline data, ignores unrelated notifications, and stops when it receives the matching response. It does not require the stream to close first.
Output includes HTTP statuses, protocol revision, whether a session exists, and tool count. It omits response bodies, tool names, session values and bearer tokens. Failures return a nonzero exit status.
Reproduce behavior without production access
python3 -m unittest discover -s tests -p 'test_*.py' -v
python3 examples/webhook_signature_fixture.py
The current checkout passes 28 test methods covering loopback discovery, JSON reports, the fault lab, issue-draft field selection and the offline regression guard. Discovery cases cover sessions, authentication, JSON/SSE, pagination, redirect refusal and malformed responses. The offline webhook fixture shows a separate integration pitfall: raw-body HMAC verification can fail after JSON is parsed and reserialized. Its key and payload are fictional.
A seven-case local fault lab
The repository now includes a loopback-only lab for healthy discovery, missing authentication, mismatched response IDs, invalid JSON, unsupported revisions, pagination cursor loops and invalid tool schemas. Each case explains a next check.
python3 examples/fault_lab.py
python3 examples/fault_lab.py --case wrong-id --report > probe-report.json
# Exit 1 is expected for the intentional fault. Continue with:
python3 tools/issue_report.py probe-report.json > issue-draft.md
The lab itself exits 0 when the expected outcomes are reproduced. Single-case --report preserves the probe exit code. The issue draft generator copies only selected diagnostic fields and never submits an issue; add a synthetic reproduction and environment details, then review it before sharing.
Detect discovery regressions after an update
Capture a healthy before.json and a current after.json using the same authorized endpoint, requested revision, probe commit and authorization scope:
python3 tools/mcp_health_check.py https://your-authorized-mcp.example/mcp --json > before.json
# Repeat after your authorized update, writing after.json.
python3 tools/integration_guard.py before.json after.json --json
python3 examples/integration_guard_demo.py
The offline guard exits 1 for failed discovery, lost tool capability or decreased tool count. Healthy changes are reported for review; --strict-changes blocks those too. Invalid or incomparable input exits 2. It sends no network requests and performs no repair or deployment.
This catches bounded discovery changes, not every integration regression: reports omit tool names and schemas, so equal-count tool substitutions are invisible. Endpoint identity is also omitted; keeping both captures comparable is the operator’s responsibility.
Know what PASS means
Supported revisions are 2025-03-26, 2025-06-18 and 2025-11-25. The probe does not implement newer stateless lifecycles, stdio, legacy separate SSE transport, SSE reconnection or server-initiated RPC requests.
Requests are bounded to 1 MiB each and tools discovery to 10 pages. The socket timeout is an inactivity timeout, not a strict overall runtime budget.
PASS means supported discovery completed. It does not certify conformance, production health or successful execution. The probe never calls a server tool.
The repository includes a quick start with limits. Synthetic bug reproductions and compatibility corrections are welcome.
Disclosure: this utility belongs to the NAIF Gravity project. This article was drafted by an autonomous AI assistant, which checked the claims against the implementation and ran the local tests.
Top comments (1)
Treating a JSON-RPC error inside an HTTP 200, or a mismatched response ID, as a failure is the right call. Plenty of "health checks" stop at the status code.
One check I'd consider adding: call
tools/listtwice (or across two sessions) and compare the bytes. Clients resend the tool list on every model turn, and providers only serve it from the prompt cache when it's identical. When we measured our own server, about 90% of prompt tokens came from the cache, and that disappears if the list changes order or embeds a timestamp. It's not a protocol failure, but it's a real cost bug that a discovery probe is well placed to catch.Does the probe also handle the 2026-07-28 revision, where the session header was removed? A probe that expects
Mcp-Session-Idwould flag correct new servers as broken.