TKOResearch
Menu
Back to insights
MCP SecurityMCP SecurityReview checklist

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.

By Kevin O'Connor

Published Last reviewed 9 min read

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.

DeploymentBoundary to reviewPractical checks
Local stdio subprocessThe launched process and the user's workstationApproved executable/version, startup arguments, minimal inherited environment, filesystem scope, network reachability and process isolation
Remote Streamable HTTP serverClient, server, authorization service and networkServer identity, TLS, token validation, resource audience, scopes, consent and downstream resource policy
Local HTTP serviceHTTP controls plus local endpoint exposureBinding address, authenticated requests, Origin validation where applicable and protection against unintended local/browser access
Legacy MCP compatibilityOlder protocol paths retained by the deploymentIdentify 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.

ComponentRecordExample question
ClientApplication owner, release, policy configurationCan a user add an unreviewed server?
ServerOperator, origin, package/version, transportWhat executable or service handles the request?
ToolSchema, action, destination and business effectDoes manage_document also expose sharing or deletion?
IdentityInitiating user and workload identityIs the downstream call attributable to the right tenant?
CredentialIssuer, audience, scope, lifetime, storage ownerCan the credential reach more than the tool exposes?
ResourceDocument, ticket, mailbox or environmentWhere is current resource authorization enforced?
RecoveryDisable path and responsible operatorCan 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:

AreaCheck to demonstrate
Server discoveryMetadata identifies the expected issuer and resource; unexpected endpoints are rejected
Redirect handlingRegistered redirect URI matching and protection of the authorization response
Authorization-code flowPKCE verification and transaction binding; state validation where state is used
Client consentThe user understands which client is receiving which access
Token validationTrusted issuer, signature or introspection as appropriate, expected audience, expiry and scope
Resource authorizationTenant, subject, document and operation checked against current state
Scope changeAdditional 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:

  1. 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.
  2. The agent proposes read_document with a document ID. The server loads the authenticated subject and current document policy. A tenant supplied in model arguments is not trusted.
  3. 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.
  4. 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.
  5. 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.
  6. 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:

CaseObserved fixture result
Valid read grantAccepted; downstream-call record added
Same grant after revocation401 invalid_token; no second downstream call
Replacement grant after rotationReplacement accepted; old grant denied
Wrong audience401 invalid_token; no downstream call
Expired grant401 invalid_token
Wrong tenant403 access_denied
Read grant used for a write403 access_denied
Unknown handle and redactionUnknown 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.

ToolEffectAdditional control
docs.searchReturns potentially sensitive contentMandatory tenant and document ACLs before context
docs.updateChanges a documentCurrent editor permission, exact fields, version precondition and approval for the defined workflow
email.sendChanges the recipient's information stateBind audience and content to approval; handle ambiguous retries
shell.executeRuns code under process authorityIsolated runtime, minimal credentials, constrained filesystem and egress
billing.refundMoves valueSeparate 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.