MCP Security Review Checklist: How to Assess Tool-Connected LLM Systems
A security checklist for MCP servers, clients, OAuth flows, tokens, tools, permissions, trust boundaries, logging, and blast radius.
A useful MCP security review follows one tool call all the way to the system it changes. The client, protocol server, credential broker and downstream API may each make a different assumption about who the user is and what they are allowed to do.
I would begin with a narrow operation, such as reading one company document. Establish how that operation is authorized, what the server can actually reach, and what happens when access is revoked. Then repeat the exercise for writes and external actions. A successful connection only establishes that the components can communicate.
Pin the protocol and the implemented transport
This checklist is reviewed against MCP 2026-07-28, which the official versioning page lists as current on September 9, 2026. Current revisions can receive compatible updates. Record the implementation release and supported protocol versions alongside this date.
The current authorization specification covers HTTP-based transports. It directs stdio implementations to obtain credentials from their environment instead of using that HTTP flow. The specification still references an OAuth 2.1 Internet-Draft; “OAuth 2.1” here is not a claim that the draft is an RFC.
| Deployment | Boundary to review | Practical checks |
|---|---|---|
| Local stdio subprocess | The launched process and the user's workstation | Approved executable/version, startup arguments, minimal inherited environment, filesystem scope, network reachability and process isolation |
| Remote Streamable HTTP server | Client, server, authorization service and network | Server identity, TLS, token validation, resource audience, scopes, consent and downstream resource policy |
| Local HTTP service | HTTP controls plus local endpoint exposure | Binding address, authenticated requests, Origin validation where applicable and protection against unintended local/browser access |
| Legacy MCP compatibility | Older protocol paths retained by the deployment | Identify supported revisions and test their handshake, session and stream behavior separately |
In the current protocol, requests carry their version and capabilities, while earlier revisions used connection-scoped initialization and sessions. The transport overview documents that distinction. Do not copy a legacy session checklist into a current deployment without checking which paths still exist.
Inventory authority before reviewing prompts
Include direct SDK calls, browser tools and maintenance endpoints as well as registered MCP tools. They may provide alternate paths to the same resource.
| Component | Record | Example question |
|---|---|---|
| Client | Application owner, release, policy configuration | Can a user add an unreviewed server? |
| Server | Operator, origin, package/version, transport | What executable or service handles the request? |
| Tool | Schema, action, destination and business effect | Does manage_document also expose sharing or deletion? |
| Identity | Initiating user and workload identity | Is the downstream call attributable to the right tenant? |
| Credential | Issuer, audience, scope, lifetime, storage owner | Can the credential reach more than the tool exposes? |
| Resource | Document, ticket, mailbox or environment | Where is current resource authorization enforced? |
| Recovery | Disable path and responsible operator | Can queued work continue after the tool is disabled? |
Draw the call path and mark where model-visible text, credentials and approval records travel. Credentials should have a different path from prompts and tool output.
Authenticated user -> agent proposes tool arguments
-> input validation -> current resource authorization -> approval if required
-> trusted credential broker -> MCP/downstream request
-> redacted result and structured audit event
Separate authorization to the MCP server from downstream access
A token accepted by the MCP server does not automatically authorize every resource behind it. For example, docs:read may permit the read operation, while the document service must still verify Maya's tenant and ACL for DOC-001.
The protected server must validate the intended token audience and must not accept or transit unrelated tokens. That is a requirement in the MCP authorization specification, not a prompt convention. The resource parameter binds the token request to the target resource under RFC 8707.
Review OAuth authorization separately from tool execution:
| Area | Check to demonstrate |
|---|---|
| Server discovery | Metadata identifies the expected issuer and resource; unexpected endpoints are rejected |
| Redirect handling | Registered redirect URI matching and protection of the authorization response |
| Authorization-code flow | PKCE verification and transaction binding; state validation where state is used |
| Client consent | The user understands which client is receiving which access |
| Token validation | Trusted issuer, signature or introspection as appropriate, expected audience, expiry and scope |
| Resource authorization | Tenant, subject, document and operation checked against current state |
| Scope change | Additional authority requires a defined authorization process; the model cannot silently grant it |
For current response-validation details, follow the versioned specification rather than treating this table as an OAuth implementation. RFC 9700, the OAuth 2.0 security best current practice, is a separate reference for authorization-flow and token protections.
Keep credentials outside model context throughout their lifecycle
A credential broker is a trusted service that selects or obtains a downstream credential after authorization. It is an architectural pattern; MCP does not make an arbitrary API key safe simply because a broker stores it.
For a hypothetical document connector, I would use this lifecycle:
- The user completes the approved authorization flow. Transport code stores the resulting grant in a secret store or protected token cache, indexed by subject, tenant, issuer and target. The model receives a connection status, not the grant.
- The agent proposes
read_documentwith a document ID. The server loads the authenticated subject and current document policy. A tenant supplied in model arguments is not trusted. - The broker selects credentials for the approved downstream target and minimum required operation. A provider's supported scopes may be coarser than one document; the application must enforce the remaining restriction.
- The transport attaches the credential outside the model-facing request body. It returns permitted document fields, safe errors and a correlation identifier. Authorization headers and refresh tokens never belong in the tool result.
- Rotation installs a replacement grant through the authorized process. Verify the new grant against the intended target, invalidate the old grant as policy requires, and confirm that cached workers stop using it. Rotation does not necessarily revoke already-issued access tokens.
- Revocation disables local use immediately at the policy boundary and invokes the provider's applicable revocation process. Check refresh grants, cached tokens, in-flight calls, background queues and fallback identities. Record the measured propagation time.
A self-contained token can remain valid until expiry unless the resource server has a revocation mechanism or another current policy check. Do not promise immediate global revocation merely because a database row was removed from the application.
Errors deserve the same care as successful output. Return a stable code such as access_denied and a safe trace ID. Keep diagnostic details in a restricted log with deliberate redaction. Never echo a rejected bearer token, raw provider response, secret-store path or full request headers to the model.
Run a local revoked-token check
The credential lifecycle reference program, version 1.0 dated September 9, 2026, demonstrates a small reference monitor with synthetic opaque handles. Its state represents issued fixture grants. It is not an OAuth implementation or an MCP server.
Download and run with Python 3.10 or later:
python3 credential_lifecycle.py
The September 9 local run passed 8 tests:
| Case | Observed fixture result |
|---|---|
| Valid read grant | Accepted; downstream-call record added |
| Same grant after revocation | 401 invalid_token; no second downstream call |
| Replacement grant after rotation | Replacement accepted; old grant denied |
| Wrong audience | 401 invalid_token; no downstream call |
| Expired grant | 401 invalid_token |
| Wrong tenant | 403 access_denied |
| Read grant used for a write | 403 access_denied |
| Unknown handle and redaction | Unknown handle denied; returned error and call log omit the fixture handle |
These are simulated status values returned by Python dictionaries. There is no HTTP request, JWT verification, discovery, authorization code, provider revocation or secret manager. The check demonstrates where denial belongs and that it precedes the synthetic downstream operation. To assess a real connector, repeat the sequence with its actual authorized test environment and record the request identifier, revocation time, denied result and downstream state.
The reference README documents setup, limitations and cleanup. Everything is in memory; there are no provider-side fixtures to remove.
Review tools by operational effect
A valid JSON request can still be unauthorized or operationally wrong. A useful schema narrows the possible request; policy evaluates the permitted subject, target and effect.
| Tool | Effect | Additional control |
|---|---|---|
docs.search | Returns potentially sensitive content | Mandatory tenant and document ACLs before context |
docs.update | Changes a document | Current editor permission, exact fields, version precondition and approval for the defined workflow |
email.send | Changes the recipient's information state | Bind audience and content to approval; handle ambiguous retries |
shell.execute | Runs code under process authority | Isolated runtime, minimal credentials, constrained filesystem and egress |
billing.refund | Moves value | Separate finance policy and authorized approval; no model self-approval |
Tool metadata and output remain untrusted inputs. Pin expected server identities, review material schema and description changes, and test whether a returned error or document can redirect the next call. The official MCP security guidance discusses confused deputies, token passthrough, network access and local-server risk. It provides review context, not a finding about a particular server.
Local servers inherit real endpoint privileges
A stdio server launched as a developer can often access whatever that process identity can access unless the host constrains it. “Local” does not mean isolated. Inspect the approved package, launch configuration, environment inheritance, mount/path access and outbound network access.
Avoid passing the entire developer environment into a connector that needs one credential. Use the host's protected credential mechanism or narrowly constructed environment and keep it out of logs. A path allowlist needs canonical path and symlink handling; a command allowlist is weak if an allowed interpreter can run arbitrary code. Verify the actual sandbox or process restrictions instead of inferring them from a UI label.
For local HTTP, review listener exposure and browser-origin behavior as well. A server bound to a local address still needs a deliberate authentication and request-validation model.
Preserve the action path and test shutdown
Record the initiating subject and tenant, client/server versions, tool and safe argument fields or digest, resource identifier, policy version and result, approval ID, downstream request ID, and completion state. Record denied and partial operations too. A missing result after an attempted send is an ambiguous state, not proof that nothing was sent.
Raw prompts and tool payloads can contain confidential documents. Apply retention, redaction and access rules; a content digest is useful for correlation but cannot reconstruct missing text. Test whether the audit stream loses events during disconnects and whether the application detects those losses.
Before release, perform the disable procedure under the normal operator role. Stop new tool calls, revoke the relevant grants, account for in-flight actions and return queued work to people. Then confirm that an ordinary worker and a stale worker both receive denials. That result is more useful than a checklist row saying “kill switch available.”
Use the MCP threat model to document the remaining attack paths. Keep the inventory, protocol version, denied-call results and recovery owner together so the next connector change has a clear review baseline.