Field report · Deep dive

Securing an MCP server in practice

A field report from VamiGRC

A secure MCP server keeps every decision that matters outside the AI agent. Building the MCP server that connects AI assistants such as Claude to VamiGRC, we learned that a confirmation inside the chat is not a security control: a manipulated agent can answer it itself. This report shows how we moved identity, approvals, autonomy and tenant boundaries into the backend, how we tested each of them, and where the limits still are.

Subject
The VamiGRC MCP server
Method
Threat model with STRIDE and the OWASP MCP Top 10
Verification
8 negative tests, end to end, all passed
Basis
MCP specification since 2025-06-18 · OAuth 2.1
Context

The rules exist, the practice is young

Since the June 2025 revision of the MCP specification, a protected MCP server is an OAuth 2.1 resource server. Practice lags behind, and a GRC platform raises the stakes.

8.5%

of more than 5,200 open-source MCP servers examined in October 2025 used OAuth.

State of MCP Server Security 2025: Research Report (October 2025) (opens external website)

What the specification requires

  • Metadata discovery via the server's protected resource metadata (RFC 9728)
  • PKCE in the authorization flow
  • Audience binding: a token is only valid for the server it was issued for
  • No token passthrough to downstream services

Specification, Revision 2025-06-18: Authorization (June 2025) (opens external website)

A GRC platform raises the stakes

The agent reads risks, incidents and supplier data, and it can change them. A wrong change is not only a bug: it can falsify the evidence an auditor relies on.

  • Create records
  • Set owners
  • Close findings
  • Delete entries
Research question and threat model

Three attackers, one common thread

How can an AI agent work with compliance data without becoming the weakest point of the platform?

We modeled three attackers, using STRIDE for the class of flaw and the OWASP MCP Top 10 as the risk catalog. Select an attacker to see which controls limit them, which negative tests relate to them and what remains.

Manipulated agent

Controls
Text the agent reads: a record description, a document, a tool result (indirect prompt injection)
Wants
Make the agent change or delete data the user never asked for
STRIDE
TamperingElevation of Privilege

Limited by

  • Text from records is passed to the agent marked as untrusted data
  • Autonomy level per action class, deletes and decisions never autonomous
  • Approval in the application for deletes and decisions, not in the chat
  • Never more rights than the person: a contained token and the person's own role

Related negative tests

  • PassedPlain “yes” on a delete preview
  • PassedPerson's own limit stricter than the organisation's

What remains: In a hijacked session, the agent can still confirm at L2 in the chat, and it can type the delete reference itself. That is why deletes and decisions require approval in the application. Prompt injection itself cannot be fully prevented.

The common thread: a manipulated agent does not break in. It uses permissions it legitimately holds. Every control therefore has to sit where the agent cannot reach it.

Identity

No token passthrough, short-lived tokens

The agent never holds a token the backend accepts as a normal user session. The login chain in four steps:

  1. 1

    Discovery

    The assistant discovers the authorization server from the MCP server's protected resource metadata (RFC 9728).

  2. 2

    Sign-in and consent

    The user signs in to VamiGRC itself and sees a consent screen: which assistant, read or read and change, which tenant.

  3. 3

    Short-lived tokens

    The assistant receives an access token valid for minutes, not days, bound to the MCP server as its audience, plus a refresh token that changes on every use.

  4. 4

    Contained API token

    The backend accepts that token only like a contained API token: public API only, never admin functions, and never more than the user's own role.

Two lessons from the way there

1

The application as the authorization server

Our earlier design exchanged the user's login for a backend token at an external identity provider. It worked, but long-lived sessions and token exchange did not mix well, and users saw “login expired” after idle periods. Letting VamiGRC be the authorization server removed that class of problems.

2

Discovery details matter

One missing scope, offline_access, made a major AI client fail before it ever showed a login page.

Foundations: RFC 9728: OAuth 2.0 Protected Resource Metadata (April 2025) (opens external website) · RFC 8707: Resource Indicators for OAuth 2.0 (February 2020) (opens external website) · RFC 9700: Best Current Practice for OAuth 2.0 Security (January 2025) (opens external website) · The OAuth 2.1 Authorization Framework (Internet-Draft) (opens external website)

Approvals

Why a confirmation in the chat is not enough

Our first version asked the user to confirm every change in the conversation. It felt safe, but the confirmation was answered inside the same channel the attacker controls.

First version: confirmation in the chat

  1. Agentproposes a change and asks in the chat
  2. Chatexpects a “yes” as confirmation
  3. Manipulated agenttypes the “yes” itself
  4. Stolen tokenwrites directly against the API and never sees the chat

The control sits in the attacker's channel.

Today: enforced by the server

  1. MCP serverstores the preview: single-use, valid for 15 minutes, bound to token, tenant and company
  2. Confirmationapplies only to exactly this preview; if the record changed meanwhile, it is refused
  3. MCP serversigns the exact request as a confirmation proof, valid for two minutes
  4. Backendwrites only with a valid proof and refuses everything else

The agent cannot fake these layers. Deletes and decisions also need approval in the app.

Three layers the agent cannot fake

01

Previews enforced by the server

Every write is a two-step call. The first returns a preview and stores it on the server, and only its confirmation writes. If the record changed meanwhile, the confirmation is refused.

  • single-use
  • 15 minutes
  • token · tenant · company
02

A confirmation proof

At the confirm step, the MCP server signs the exact request. The backend rejects a write without it, so a stolen access token used directly against the API cannot write.

  • token id · method · path · body hash
  • 2 minutes
03

Typed references for deletes

A plain “yes” deletes nothing. The user must type the record's reference, and the preview names everything linked to the record. Records that can be archived are archived rather than erased.

  • reference instead of “yes”
  • archive rather than erase
Autonomy

Autonomy levels per action, enforced in the backend

Not every action deserves the same trust. Every write route of the public API belongs to an action class, and the organisation sets the highest autonomy level per class, following VamiGRC's levels L0 to L3.

  • L0

    Manual

    The agent may not act.

    In the MCP serverRefused with a clear reason, the person acts in VamiGRC.

  • L1

    AI-assisted

    The agent prepares, the person acts.

    In the MCP serverA draft the person reviews and saves.

  • L2

    AI-executed with mandatory approval

    The agent acts after a human approves.

    In the MCP serverPreview and confirmation, or approval in the app for deletes and decisions.

  • L3

    Fully autonomous, fully audited

    The agent acts alone, everything is recorded.

    In the MCP serverOnly for low-risk classes such as notes.

Three rules that make it hold

Stricter, never looser

The organisation sets the maximum, and each person can only narrow it for their own assistants. What applies is always the strictest setting.

Hard caps

Deletes and decisions are never autonomous, and signed decisions such as approvals never go through the chat.

Fail closed

A route without a class counts as blocked. A test fails as soon as a new write route has no class.

Autonomy calculator: which level applies in the end?

Choose an action class, the organisation's maximum and the personal limit. The calculator applies the three rules.

Action class
Organisation's maximum
Personal limit

Effective level

L2AI-executed with mandatory approval

Preview and confirmation, or approval in the app for deletes and decisions.

Limited by the organisation's maximum.

Simplified model based on this field report, not a copy of the product configuration.

Tenants and audit

Tenant binding and an audit trail outside the agent

Consultants work for several clients, so one person often has access to several tenants. The MCP server follows the tenant the person has selected in the VamiGRC app, checked on every call, with no caching. That convenience creates a new risk.

  1. Preview

    Preview in tenant A

    The preview stores its tenant and company, and the answer names them.

  2. Switch

    Switch to tenant B in the app

    The MCP server follows the switch on the next call.

  3. Refused

    Confirming the old preview

    The confirmation is refused before anything is sent.

  4. No fallback

    Tenant no longer reachable

    The server refuses rather than silently falling back to another tenant.

Verification

Every control has a negative test

Each test tries to break a control, run end to end against a real backend and a real MCP client, not only in unit tests.

Negative tests of the controls and their results
CheckExpectedResult
Authorization code used twiceRefused, and the connection it created is endedPassed
Old refresh token reused after rotationRefused, and the whole connection is endedPassed
Plain “yes” on a delete previewNothing deleted, the reference is requestedPassed
Tenant switched between preview and confirmationConfirmation refused, nothing writtenPassed
Several tenant switches in a rowThe connection follows each switchPassed
Delete in another company by idNot found, the record survivesPassed
Write without the confirmation proofRefusedPassed
Person's own limit stricter than the organisation'sThe stricter limit appliesPassed

What the tests found before release

  • A permission check that judged requests in the wrong scope
  • A client that dropped connections when one OAuth scope was missing

Testing the controls as an attacker would, not only the features, is what found them.

Takeaways and limits

What we learned and where the limits are

Five takeaways that carry over to other MCP servers, and five limits, stated openly.

Five takeaways

  1. 1A confirmation the agent can answer is not a control.Put approvals where the agent cannot reach them: in the backend, or in the application's own session.
  2. 2Bind the approval to the exact action.Token, request, record version and tenant must match what the person approved. Anything else is refused.
  3. 3Classify actions, then fail closed.Autonomy per action class, with an unclassified action blocked by default, scales better than per-tool settings.
  4. 4The agent never gets more than the person.Contained tokens, the person's own role and the organisation's limits stack, and the strictest wins.
  5. 5Test the controls like an attacker.Replay, reuse, switch and bypass each control on purpose.

Limits, openly

  • Prompt injection cannot be fully prevented.Text from records is passed to the agent marked as untrusted data, but a model can still follow it. The controls limit the damage, but they do not stop the manipulation itself.
  • A hijacked conversation can still confirm at L2 in the chat.The confirmation proof stops a stolen token, not a manipulated agent inside a live session. That is why deletes and decisions require approval in the application.
  • The agent can type the delete reference itself.The typed reference protects against mistakes, not against a determined manipulated agent. Approval in the application closes that gap.
  • Rollback is partial.Archived records can be restored by an administrator, but an undo for every agent change is still on our roadmap.
  • The standards are young.The OWASP MCP Top 10 is a beta (v0.1) and the Agent Control Standard is at version 0.1.0, so our mapping will change as they mature.
Control check

Where do the controls of your MCP server sit?

Ten questions from this field report. The more often you answer “yes”, the less your security depends on what the agent does.

  1. 01Do approvals sit outside the channel the agent controls, in the backend or in your application's own session?
  2. 02Does the server enforce previews that are single-use, expire and are bound to token, tenant and record version?
  3. 03Does your backend refuse writes that carry no proof bound to the exact request?
  4. 04Are access tokens short-lived and bound to your MCP server, do refresh tokens change on every use, and is token passthrough ruled out?
  5. 05Does the agent's token reach only the public API, never admin functions and never more than the person's role?
  6. 06Does every write route belong to an action class, and does a route without a class count as blocked?
  7. 07Are deletes and decisions never autonomous, and do they require approval in the application?
  8. 08Does every preview store its tenant, and is the tenant checked on every call without a silent fallback?
  9. 09Does the backend write the audit trail and mark every change made through an assistant?
  10. 10Does every control have a negative test that tries to break it end to end?

Answered: 0/10

The result is an orientation, not audit evidence. Your answers are not transmitted.

Mapping

Mapping to the OWASP risk catalogs

This is how we map the controls to the risk catalogs. Both catalogs are young, and the mapping will change with them.

The OWASP MCP Top 10 is a beta, and the Agent Control Standard is at version 0.1.0. All ten MCP risks in our deep dive

Whitepaper · free

Securing an MCP server in practice – Agent controls for CISOs

The field report prepared for decision-makers: threat model, identity without token passthrough, approvals in the backend, autonomy levels and governance, tenant binding and audit trail, negative tests, questions for vendors and a 30-60-90-day plan.

Cover of the whitepaper “Securing an MCP server in practice – Agent controls for CISOs”
PDF · 23 pagesGerman & EnglishAuthor: Houssem Bouarada
  • Ten key messages for the board and internal audit
  • Controls for tokens, approvals, autonomy and tenants
  • Eight negative tests and acceptance criteria
  • Questions for vendors and a 30-60-90-day plan
Free Download

Request Whitepaper

Securing an MCP server in practice – Agent controls for CISOs

Language version *
Support

From the field report to your MCP server

We review MCP servers and agent integrations the way this report describes: with a threat model, controls outside the agent and negative tests.

Houssem Bouarada
Author

Houssem Bouarada

Security Researcher, Vami Research

Houssem Bouarada is a security researcher at Vami Research. He focuses on offensive security and on securing AI-assisted tools such as MCP servers.

Ask the author
Keep exploring

Related research

Deep dives 6

Agent Control Standard (ACS)The OWASP Agent Control Standard (ACS) is an open specification under which a Guardian Agent checks an AI agent’s actions at defined hooks and, based on policy, allows, blocks, modifies, submits them for approval or defers them. This deep dive shows what version 0.1.0 delivers today — and where its limits lie.Threat-Model-Guided Security TestingA shared trace only shows that events belong together. It does not show whether an AI agent respected approval, target, authorization and data provenance along the way. Our paper, accepted at ICSPIS 2026 (IEEE), shows which telemetry that requires. This page maps the results to the OWASP Agent Control Standard (ACS).Zero Trust for AI AgentsAutonomous AI agents act with legitimately granted privileges at machine speed. Zero Trust transfers proven principles — verify instead of trust, Least Privilege, assume breach — to agentic systems.MCP & OAuth 2.1Since the June 2025 specification revision, a protected MCP server is an OAuth 2.1 resource server with clearly separated roles. This deep dive explains the discovery chain, client registration, token rules and audience binding – and shows where implementations fail in practice.OWASP MCP Top 10The Model Context Protocol has become the integration layer between language models and enterprise systems — and thus a new, often unexamined trust boundary. The OWASP MCP Top 10 name the ten risks of this layer.Secure Server DevelopmentMCP-Servers connect AI assistants to tools and data sources and thereby become an attack target themselves. We support architecture, Tool design, authentication, and deployment along established security guidelines.

Frequently asked questions

Short answers

Why is a confirmation in the chat not enough?

Because it runs in the same channel an attacker controls. An agent manipulated through prompt injection can answer the question with “yes” itself, and anyone holding a stolen access token bypasses the chat entirely. More effective are controls the agent cannot fake: previews enforced by the server, a confirmation proof bound to the exact request and, for deletes and decisions, approval in the application.

What does the MCP specification require for authorization?

Since the June 2025 revision, a protected MCP server is an OAuth 2.1 resource server. It requires metadata discovery through protected resource metadata (RFC 9728), PKCE, and tokens issued for the MCP server as their audience; token passthrough is prohibited. The field report shows what else is needed as soon as an agent may write.

What do the autonomy levels L0 to L3 mean?

They define what an agent may do alone: L0 manual, L1 AI-assisted, L2 AI-executed with mandatory approval, L3 fully autonomous and fully audited. In the VamiGRC MCP server, the organisation sets the highest level per action class, each person can only narrow it, and a route without a class is blocked. Deletes and decisions are never autonomous.

Do these controls prevent prompt injection?

No. Text from records is marked as untrusted, but a model can still follow it. The controls limit what a manipulated agent can do, but they do not stop the manipulation itself. That is why deletes and decisions require approval in the application.

How do you test an MCP server like an attacker?

With a negative test for every control that tries to break it on purpose: redeem an authorization code twice, reuse an old refresh token, switch tenants between preview and confirmation, write without the confirmation proof, delete by id in another company. End to end against a real backend and a real MCP client, not only in unit tests.

How mature are the OWASP MCP Top 10 and the Agent Control Standard?

Both are early: the OWASP MCP Top 10 is a beta at version 0.1, and the Agent Control Standard is at version 0.1.0. We map our controls to Token Mismanagement & Secret Exposure, Context Injection & Over-Sharing and Excessive Agency, and will adjust the mapping as the standards mature.

Connect agents without giving up control

We review MCP servers and agent integrations like an attacker and show which decisions are still within the agent's reach.