---
title: "Protect an MCP Server"
url: "https://docs.caracal.run/v1.0/guides/protect-mcp/"
markdown_url: "https://docs.caracal.run/markdown/v1.0/guides/protect-mcp.md"
description: "Gate MCP tool calls with Caracal mandate verification using the framework-neutral verify packages."
page_type: "page"
concepts: []
requires: []
---

# Protect an MCP Server

Canonical URL: https://docs.caracal.run/v1.0/guides/protect-mcp/
Markdown URL: https://docs.caracal.run/markdown/v1.0/guides/protect-mcp.md
Description: Gate MCP tool calls with Caracal mandate verification using the framework-neutral verify packages.
Page type: page
Concepts: none
Requires: none

---

import { Tabs, TabItem } from '@astrojs/starlight/components'

Use the verify packages when your MCP framework does not have a dedicated Caracal adapter or when you want to build your own boundary.

:::note[Two ways to protect an MCP server]
This guide covers in-process verification, where the MCP server checks mandates itself. To route an MCP-over-HTTP server through the Caracal Gateway with no code in the server, model it as a resource with operation enforcement set to **Any operation**, bound to a credential provider - see [Protect an MCP server over the Gateway](/v1.0/guides/resources-providers/#protect-an-mcp-server-over-the-gateway).
:::

## Prerequisites

* A resource mandate audience and tool-scope map.
* Issuer, zone ID, shared revocation storage, and a hook that runs before tool dispatch.
* A consistent mapping from verification errors to the framework's unauthorized/forbidden response.

## Build the Verifier

<Tabs syncKey="lang">
  <TabItem label="TypeScript">
    ```bash
    npm install @caracalai/verify @caracalai/revocation
    ```

    ```ts
    import { createMandateVerifier } from '@caracalai/verify'
    import { InMemoryRevocationStore } from '@caracalai/revocation'

    const verifier = createMandateVerifier({
      issuer: 'https://sts.pipernet.example',
      audience: 'resource://pipernet',
      zoneId: '0195f2a9-1b22-7c3d-9e4f-5a6b7c8d9e0f',
      revocations: new InMemoryRevocationStore(),
    })

    export async function verifyToolRequest(authorization: string | undefined) {
      const result = await verifier.authorization(authorization, {
        requiredScopes: ['mcp:tool:call'],
        requiredTargets: ['resource://pipernet'],
        requireSession: true,
      })

      if (!result.ok) {
        throw new Error(`${result.error.code}: ${result.error.description}`)
      }

      return result.principal
    }
    ```
  </TabItem>

  <TabItem label="Python">
    ```bash
    pip install caracalai-verify caracalai-revocation
    ```

    ```python
    from caracalai_verify import authenticate, extract_bearer
    from caracalai_revocation import InMemoryRevocationStore

    revocations = InMemoryRevocationStore()

    async def verify_tool_request(authorization: str | None):
        token = extract_bearer(authorization)
        result = await authenticate(
            token or "",
            issuer="https://sts.pipernet.example",
            audience="resource://pipernet",
            required_scopes=["mcp:tool:call"],
            expected_zone_id="0195f2a9-1b22-7c3d-9e4f-5a6b7c8d9e0f",
            revocations=revocations,
            require_session=True,
            required_targets=["resource://pipernet"],
        )
        if result.error is not None:
            raise RuntimeError(f"{result.error.code}: {result.error.description}")
        return result.principal
    ```
  </TabItem>
</Tabs>

## Verification checklist

| Check                              | Why it matters                                             |
| ---------------------------------- | ---------------------------------------------------------- |
| Issuer and audience                | Prevents accepting mandates from the wrong zone or target. |
| Required scopes                    | Enforces tool-level authority.                             |
| Required targets                   | Prevents cross-resource token reuse.                       |
| Session or Delegation requirements | Keeps application-root and delegated calls separate.       |
| Revocation store                   | Rejects revoked sessions and Delegations.                  |

## Production revocation

Use Redis-backed revocation packages for multi-instance MCP servers:

* TypeScript: `@caracalai/revocation-redis`
* Python: `caracalai-revocation-redis`
* Go: `github.com/garudex-labs/caracal/packages/backends/redis/go`

Run a consumer for the `caracal.sessions.revoke` stream so every resource server instance learns about revoked anchors.

## Validate the boundary

Test missing token, wrong audience, wrong target, missing scope, required Session, required Delegation, hop limit, and revoked Session before enabling traffic. Expected result: no rejected call reaches tool code and every allowed principal is derived from verified claims rather than request metadata.

:::caution[Failure point: unsigned context]
Never authorize from baggage, trace headers, tool arguments, or a caller-provided Subject. Verified mandate claims are authoritative; other fields are correlation only.
:::

For exact verifier functions and error types, use [Verify Package reference](/v1.0/sdks/verify/).

## Next Step

Wrap the framework hook, then run [Test Caracal Integrations](/v1.0/guides/testing/) with a revoked-anchor case.
