mTLS (Mutual TLS) Authentication for MCP Servers
mTLS moves the credential out of the request and into the connection: the agent presents an X.509 certificate during the TLS handshake, so no header carries a secret.
- Method
- mTLS
- Platform
- Any platform
- MCP Specification
- 2026-07-28
How it works
The certificate goes on the HTTP client the transport uses, never in the MCP payload: a tls.Config on an http.Client in Go, an ssl.SSLContext passed as verify in Python, an undici Agent behind a custom fetch in TypeScript. The request itself carries no credential — the server, or the proxy terminating TLS in front of it, knows the caller from the certificate the connection was established with. That is also the limit. No chat or desktop MCP client config exposes a certificate field, so mTLS reaches servers from agents you write and from nothing else.
- Best for
- Workload-to-workload calls where both ends are yours and a CA already issues certificates
- MCP spec
- Transport-layer authentication, outside the spec's OAuth profile — which the spec makes OPTIONAL
- Works with
- Your own agents only; no chat or desktop client config exposes a certificate field
- Effort
- An afternoon in code, longer if the PKI does not exist yet
The exchange
Each step in the exchange, in order.
- Your agent holds cert and key
- MCP server or the proxy in front of it
-
The exchange happens once per connection, before any MCP message exists. A server that never sends CertificateRequest never sees a certificate, however well the client is configured.
Your agent to MCP server
TLS ClientHello SNI: mcp.example.com -
MCP server to Your agent
CertificateRequest acceptable CAs -
Your agent to MCP server
Certificate, CertificateVerify CN=orders-agent, issued by your CA -
Inside MCP server
Verify the chain, then read the subject -
Every message on this connection is already authenticated, so a request copied out of a log carries nothing to replay. The cost is that nothing in MCP advertises the requirement — each caller is configured by hand.
Your agent to MCP server
POST /mcp initialize — no Authorization header -
MCP server to Your agent
200 OK initialize result — tools listed
Connect your agent
Pick your language and SDK.
When to use something else
Use this when
- Both ends are workloads you run and a CA already issues their certificates, so there is no shared secret in an environment variable.
- A network or compliance rule requires a client certificate on every connection, and TLS terminates in a proxy or mesh you control.
- You want the credential bound to the connection rather than to a header, so nothing replayable appears in a request or a log.
Use something else when
- A person has to reach this server from a chat client. Claude, ChatGPT, Cursor and VS Code cannot present a certificate you supply, so mTLS is the wrong answer — check this before anything else.
- The call is made on behalf of a named user. A certificate identifies a workload, and per-user identity needs the spec's OAuth flow.
- Nobody owns rotation. Expiry fails the handshake, so every agent stops at once and no MCP error says why.
Authorization is OPTIONAL, and implementations using an HTTP-based transport SHOULD rather than MUST conform to the specification's OAuth profile, so a client certificate sits outside the specification rather than in conflict with it. Both live revisions say exactly that, and neither one's standards list mentions TLS client authentication — the 2026-07-28 list reaches OAuth 2.1, RFC 9728, RFC 8707, RFC 6750, RFC 8414, RFC 7591, RFC 9207 and Client ID Metadata Documents and stops there — which is why no client can discover the requirement.
What lands on you next
A certificate answers one question — which workload opened this connection — and it answers it only for workloads you run. What arrives next is about people, tools, and what the server does with the identity.
-
Six analysts need two of these tools in Claude. They cannot install a certificate.
-
mcp-oauth-inboundThe inbound method is a policy on the route rather than server code, so the same server can accept an OAuth sign-in against the identity provider you already run. Exactly one such policy per project.
-
The certificate proves it is our agent. Which tools may our agent call?
-
mcp-capability-filter-inboundA static per-route allow-list of tools, prompts and resources. A call to a hidden tool is refused with
MethodNotFoundbefore it reaches your server, and a read-only view of the same upstream is a second route. -
Our TLS log has a subject and a timestamp. Which tool actually ran?
-
capability_invocationEach tool call emits an event carrying the caller, the capability, the upstream, the outcome and the latency, rather than one connection-level line in an access log.
-
The API behind the server needs its own credential. Do the agents hold that too?
-
mcp-token-exchange-inboundNo. What the caller presented is stripped and an independent upstream credential is minted per user or per gateway; the spec forbids forwarding an inbound token with a MUST NOT.
All of these attach to one MCP route's policies.inbound — the same policy engine on the way in and on the way out.
Common questions
Can Claude, ChatGPT or Cursor connect to a server that requires a client certificate?
No. Claude's connector documentation lists six supported authentication types — OAuth with DCR, OAuth with a Client ID Metadata Document, Anthropic-held client credentials, custom connection, static headers in beta, and none — and a certificate is not among them. Cursor documents url, headers and auth for a remote server; VS Code documents type, url, headers and oauth. OpenAI states plainly that ChatGPT cannot present customer-provided mTLS certificates.
Doesn't ChatGPT already use mTLS with MCP servers?
Yes, in the other direction. OpenAI documents that ChatGPT presents an OpenAI-managed client certificate when establishing TLS connections to MCP servers, and tells you to verify the leaf certificate's SAN dnsName is mtls.prod.connectors.openai.com. That proves the caller is OpenAI's infrastructure, not your user, and you cannot substitute your own certificate. OAuth 2.1 still authenticates the person.
Claude Code documents CLAUDE_CODE_CLIENT_CERT. Does that cover MCP servers?
Treat it as unverified for MCP. Claude Code documents CLAUDE_CODE_CLIENT_CERT and CLAUDE_CODE_CLIENT_KEY as enterprise network configuration, read at startup and re-read when settings change, and /status shows an mTLS client cert row once the files load. That page discusses these variables in terms of the connection to the API provider and never mentions MCP, and the documented fields for an HTTP MCP server entry are url, headers, headersHelper, timeout and alwaysLoad. Test it against your own server before designing around it.
Does mTLS violate the MCP specification?
No. Authorization is OPTIONAL, and HTTP transports SHOULD rather than MUST conform to the OAuth profile, so transport-layer client authentication is out of scope rather than in conflict. What you give up is discovery: there is no way to advertise that a server expects a certificate, so each caller is configured directly.
Which specification revision applies here?
2026-07-28, the current protocol version. For mTLS it makes no difference. Neither it nor 2025-11-25 describes client certificates, both make authorization OPTIONAL, and both say HTTP transports SHOULD rather than MUST follow the OAuth profile.
Is client certificate authentication the same thing as mTLS?
Yes. mTLS, mutual TLS, two-way SSL and X.509 client certificate authentication all name the same handshake. It never touches the MCP protocol, so support depends only on whether your SDK lets you supply the HTTP client — which the Go, Python and TypeScript SDKs all do.
Can mTLS and OAuth be combined?
They sit at different layers, so yes: terminate mTLS at the edge and run the spec's OAuth flow behind it. RFC 8705 goes further and binds an access token to the client's certificate, but the MCP authorization spec's standards list does not include RFC 8705, so nothing in MCP asks a client to produce a certificate-bound token.
What happens when a certificate expires?
The handshake fails before any MCP message exists. The spec's error table covers 401, 403 and 400, and a TLS failure becomes none of them, so agents report a transport error and the reason is in your server's TLS log rather than in the protocol. Short-lived certificates need automated renewal on both ends.
One policy engine for APIs, AI, and MCP
Put your MCP servers behind a gateway that speaks every identity provider, filters tools per role, and logs every call.