Model Context Protocol (MCP) defines how language models call external tools. It does not define who is allowed to call them.
Tool invocation is a security boundary. MCP does not enforce it.
Most agent systems rely on a shared API key that is checked once at connection time. After that, every tool behind that key is reachable. This works when one agent process owns the tools and runs under a single identity. It collapses when a centralized MCP server executes tools for many users, because the shared key cannot distinguish who is allowed to do what.
With this approach you cannot express least privilege a key tenet in security postures such as defense-in-depth. You cannot bind tool usage to a real cryptographic identity. Your audit log shows that the agent called database_query, but it does not show which user caused that call to happen.
I built a prototype that enforces identity-based tool authorization for MCP using X.509 certificates.
This started as an exploration of whether user identity in the context of Public Key Infrastructure (PKI) could be cleanly mapped onto MCP’s tool model. In this prototype, that identity mapping is direct.
The Pattern
Each user holds a private and a public X.509 certificate that encodes the MCP tools they may invoke. The MCP server enforces authorization directly from that certificate on every tool call initiated on behalf of an LLM.
- No external permission lookup.
- No shared API key.
- No blanket access controls at the session level.
Tool authorization on a happens per-call basis.
This implementation is a working MCP server called by an LLM running in Ollama. There are three users and tree permission sets. One LLM selecting tools in real time. Every tool access attempt is logged.
Architecture
A local RSA 2048-bit certificate authority is generated at demo startup and signs user certificates. The MCP server trusts only that CA.
Each certificate contains a custom extension with OID 1.3.6.1.4.1.99999.1. This OID lives under the IANA Private Enterprise Number arc (1.3.6.1.4.1). The value 99999 is used here as a placeholder enterprise identifier for the prototype. A production system would register an official Private Enterprise Number with IANA and define the extension beneath that assigned branch.
The extension value is UTF-8 encoded JSON:
{"allowed_tools": ["web_search", "summarization"]}
The extension is stored as an UnrecognizedExtension containing raw bytes. In this case there is no ASN.1 wrapper.
In this prototype, the parser and writer live in the same codebase. Using raw JSON keeps the traffic legible. A production system should define a proper ASN.1 structure for RFC 5280 compliance and interoperability with standard tooling.
The LLM communicates with MCP server over JSON-RPC using stdio transport. Each demo scenario spawns a fresh server process. There is no persistent session no concurrent connections, and no session state between calls.
A production deployment would use HTTP with mutual TLS, validating the client certificate at the transport layer before processing any tool request.
The client orchestrates the entire exchange. The LLM never connects directly to the MCP server. When the model selects a tool, it returns that selection to the client. The client then issues the MCP request on the model’s behalf, signs it with the user’s private key, and returns the tool result back to the model.
The interaction looks like this:
The client presents the certificate and proves possession of the corresponding private key by signing the MCP request payload. The server verifies the signature before evaluating permissions.
On every tool invocation the MCP server:
- Validates the certificate signature against the trusted CA
- Verifies the request signature using the public key in the certificate
- Extracts the allowed_tools list from the extension
- Checks whether the requested tool is permitted
- Appends an entry to audit.jsonl
- If request validation fails, access is denied.
- If the extension is missing or malformed, access is denied.
- If the tool is not listed in the set of allowed tools, access is denied.
There are no fallback paths.
The Tools
The MCP server registers four tools:
- web_search
- summarization
- database_query
- file_operations
All return mock data. Enforcement is performed by the MCP server.
The Demo Identities
The demo provisions three users:
| User | Allowed Tools |
|---|---|
| alice |
web_search, summarization
|
| bob |
web_search, summarization, database_query, file_operations
|
| carol | summarization |
A concrete flow:
Alice submits a request: "Search for quantum computing."
The client sends this task to Ollama with the four tool definitions. The model selects web_search. The client signs the MCP request with Alice's private key and includes her certificate.
The MCP server validates the certificate chain. It verifies the request signature. It extracts the extension and sees that web_search is permitted. The call proceeds. An audit entry is written.
Carol submits a task that leads the model to select database_query. The client signs the request with her key and presents her certificate to the MCP server. The server validates the chain, verifies the signature, extracts the extension, and finds that only summarization is allowed. The MCP server rejects the call. The audit log records both the attempted tool and the SQL query the LLM tried to execute on Alice's behalf.
Every call to the MCP server is evaluated independently. Bob can invoke two tools four milliseconds apart and both are validated based on Bob's authorized operations.
Tool Selection Versus Authorization
The model decides what tools it wants to call. The MCP server decides whether those tool calls are allowed.
The client loads a user's certificate and private key, sends the task to Ollama, receives a tool selection, and then performs the MCP call. Authorization never depends on model behavior.
Tool calling support depends on the specific llama3 build. For example, llama3.1:8b supports it, while earlier llama3 variants may not. The naming does not make that distinction explicit.
Audit Trail
In this demo, each access attempt to a tools hosted by the MCP server is appended to audit.jsonl:
{"timestamp": "2026-02-23T02:33:47.374168Z", "user": "alice", "tool_name": "web_search", "allowed": true, "reason": "permitted by certificate", "params": {"query": "quantum computing"}}
{"timestamp": "2026-02-23T02:33:53.250178Z", "user": "carol", "tool_name": "database_query", "allowed": false, "reason": "not in cert permissions (allowed: ['summarization'])", "params": {"sql": "SELECT * FROM users WHERE department = 'engineering'"}}
The log captures identity, tool, parameters, and decision. Authorization is not cached at the session level.
Why PKI
API keys grant access to a service. They do not encode structured permissions. They are rarely tied to a cryptographic identity that survives distribution across services.
OAuth access tokens can embed scopes and be validated locally when implemented as JWTs. That model assumes an authorization server and short lived tokens as the norm. It fits web applications where a central authority is reachable.
In a distributed agent architecture, tool servers may be isolated, air gapped, or not collocated with an authorization service. Requiring an external call on every validation becomes a structural dependency.
PKI fits machine identity and trust domains that already operate on certificates. A certificate binds identity and permissions into a single signed object. Any system that trusts the issuing CA can verify both server and clients without contacting an external system.
In a distributed agent architecture where multiple services enforce tool access, that property matters.
Federal PKI shows this at scale
The United States federal government operates one of the largest PKI deployments in existence.
Federal employees use Personal Identity Verification (PIV) cards backed by X.509 certificates. The Department of Defense issues Common Access Cards under DoD Instruction 8520.02. These certificates authenticate users to systems, sign and encrypt email with S/MIME, and protect internal services across agencies.
The Federal PKI framework establishes cross agency trust. Certificates issued by one authority are trusted by others through established certification paths. Identity and authorization decisions are made from cryptographic credentials, not shared secrets.
This operates at national scale.
The same mechanism should apply to MCP tool access. A certificate binds identity. An extension binds permissions. Any MCP server that trusts the issuing CA can enforce tool policy locally without calling an external system.
Enterprise PKI infrastructure already exists. Agent systems can integrate with this instead of inventing a parallel identity model.
Tradeoffs
Changing a user's permissions requires issuing a new certificate. That is acceptable in environments where privilege changes are controlled and audited. If permissions change dynamically at runtime, use the certificate for identity and resolve authorization policy externally.
This prototype does not implement revocation via CRL or OCSP. It does not protect private keys with an HSM. The CA is local and ephemeral. Transport is stdio JSON-RPC rather than HTTP with mutual TLS. The extension encoding should be formal ASN.1 for production use.
These omissions are deliberate. The prototype answers a single question.
Can certificate based per identity tool authorization work cleanly with MCP?
The Gap
Agent frameworks treat tool access as configuration. They do not treat tool access as a security boundary that should be enforced using cryptographic identity.
As agents move into multi-tenant systems and enterprise workflows, tool invocation must become a privileged operation. A database_query tool has a potential blast radius.
Binding that capability to an X.509 identity is straightforward. The core enforcement logic in this prototype is under one hundred lines of Python. The complexity sits where it belongs, in PKI and trust management.
The code runs locally with Python and Ollama. It logs every decision.
The main takeaways from this are:
- Issue certificates that encode tool permissions.
- Validate them on every call.
- Treat tool access as a security boundary.
The prototype is available at https://github.com/davidculver/pki-mcp-core/tree/main


Top comments (0)