# 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.

- **Status:** Available
- **Audience:** developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/troubleshooting/mcp-client-cannot-connect/

## 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

```http
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 `initialize` gets 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-Id` is ignored and never minted, and `Last-Event-ID` is 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.json` describes 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

1. 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.
2. Send `tools/list` as 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.
3. 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.
4. 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.

## Related

- [The Connect MCP server](https://connectbyjbrh.com/developers/mcp-server/)
- [Authenticating to the MCP server](https://connectbyjbrh.com/developers/mcp-authentication/)
- [MCP error shapes](https://connectbyjbrh.com/developers/mcp-errors/)
- [Using Connect from any MCP client](https://connectbyjbrh.com/developers/mcp-client-generic/)
- [An AI client calling a Connect MCP tool](https://connectbyjbrh.com/docs/workflows/mcp-tool-call/)
- [Interoperating over A2A](https://connectbyjbrh.com/developers/a2a-integration/)
- [Troubleshooting](https://connectbyjbrh.com/docs/troubleshooting/)

## What this page is based on

- `backend/app/mcp_server.py` — the endpoint, its refusals and the public tools
- MCP specification, revision 2026-07-28 — https://modelcontextprotocol.io/specification/2026-07-28
- `backend/app/a2a_server.py` — the neighbouring agent-discovery surface
