An MCP client cannot connect
Connect's MCP endpoint is POST /mcp and nothing else. Four refusals cover almost every failure: a GET or DELETE gets 405, a present but unrecognised Origin gets 403, a protocol version that disagrees between header and body gets JSON-RPC error -32020, and a workspace tool without an integration key is refused while the documentation tools need no credential at all.
The four refusals#
| Symptom | Status | Cause | Fix |
|---|---|---|---|
405, with Allow: POST | 405 | The client opened a GET stream or sent DELETE. Revision 2026-07-28 removed the standalone GET stream and protocol-level sessions | Send every message as a POST. Do not wait for a stream to carry anything |
| Origin not allowed | 403 | An Origin header was present and is not recognised. A client that sends no Origin at all is accepted | Send no Origin from a desktop client; from a browser, use an origin the server knows |
| Header does not match the body | 400, code -32020 | MCP-Protocol-Version, Mcp-Method or Mcp-Name disagreed with the body | Send the header and the body value from one source, or send only one of them |
| Unsupported protocol version | 400, with the supported list | A revision this server does not speak | Use one of the four supported revisions returned in the error data |
The header-agreement rule is the one that surprises implementers, and the reason is worth knowing: a load balancer that routes on the header and a server that executes on the body must not be able to see two different requests. A mismatch is therefore a refusal rather than a preference for one of the two.
What the endpoint is#
POST /mcp HTTP/1.1
Host: connectbyjbrh.com
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
{"jsonrpc":"2.0","id":1,"method":"tools/list",
"params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}- Transport
- Streamable HTTP, one endpoint, POST only.
- Supported revisions
- 2026-07-28, and the three initialization-based revisions before it. An older client that sends
initializegets a normal result and its later requests are accepted without per-request metadata, because refusing them would cut off the connector directories that speak those revisions. - Sessions
- None.
Mcp-Session-Idis ignored and never minted, andLast-Event-IDis ignored because streams are not resumable. A client waiting to be given a session id waits for something that will not arrive. - Batches
- Not accepted. Send one JSON-RPC message per request.
- Size and rate
- A body over 256 KB is refused, and the endpoint allows 120 calls per minute per client before answering 429.
- Discovery
GET /.well-known/mcp.jsondescribes the endpoint, its transport, the supported revisions and the public tool names.
Authentication, which is narrower than it looks#
The public documentation tools need no credential and touch no workspace: they read the same generated corpus the website serves. So a connection that fails while calling search_public_docs is not an authentication problem, whatever the client's error text says — there is nothing to authenticate against.
- Ten public tools are available without a key: searching and fetching documentation pages, listing and getting a capability, getting a workflow, searching the glossary, getting a technology or a protocol page, the product status, and the changelog.
- Workspace tools require an integration key and are scoped to that key's single workspace.
- Workspace tools call the existing domain services, so autonomy, approval, suppression, sales limits, metering and the audit trail all still apply to anything an agent does through them.
- There is deliberately no tool that runs a query, none that proxies an arbitrary internal route, and no write that bypasses the service owning the record.
What Connect completed#
Every one of these refusals is a complete, deliberate answer. The server validated the request, decided, and replied with a JSON-RPC error carrying a code and, where it helps, the data needed to correct the call — the list of supported revisions, for instance. Nothing was left open and no workspace was touched, which is why a refused request costs nothing and can be corrected and retried immediately.
What Connect did not complete#
It did not establish a session, because it never establishes one. It also does not negotiate downward mid-conversation: a request naming an unsupported revision is refused rather than answered under a different one. And it does not tell a browser-based client which origins are acceptable — an unrecognised origin is refused without enumerating the alternatives, since publishing that list would defeat the check.
What you can do#
Fetch the discovery document first.
Result It names the endpoint, the transport, the supported revisions and the public tools, so most configuration mistakes are visible before you send a single JSON-RPC message.
Send
tools/listas a plain POST with no Origin header.Result If that works, the transport and the version are fine and the problem is in whatever your client adds — an Origin, a session expectation, or a mismatched header.
If you get -32020, remove the headers and rely on the body.
Result The disagreement disappears. Add the headers back one at a time to find which one your client is filling in differently.
If you get 429, slow down and retry.
Result The limit is per client per minute; it is a rate refusal, not a block, and nothing needs to be reconfigured.
When to escalate#
Escalate when a correctly formed POST on a supported revision, with no Origin, is refused for a reason not in the table above — and quote the JSON-RPC error code, which is the part that identifies the check that fired. A workspace tool refusing an integration key that works elsewhere is also worth raising, since keys are scoped to one workspace and a scope mismatch reads as an authentication failure.
Questions#
My client keeps opening a GET stream and hanging. Is the server down?
No. The endpoint answers GET with 405 and an Allow: POST header, deliberately, because the revision it speaks removed the standalone GET stream. A client that waits on that stream is waiting for something the protocol no longer has.
Do I need credentials to read the documentation tools?
No. They are public and touch no workspace, which is also why they cannot leak anything: they read the same generated corpus the website serves, so they cannot answer differently from the published pages.
Why is a missing Origin allowed when a wrong one is refused?
Because a desktop client is not a browser and sends none. What the specification requires is that a *present* origin be validated, so that a page on any website cannot drive the server through a visitor's browser.