---
title: "Protect an MCP Server"
url: "https://docs.caracal.run/v0.2/guides/protect-mcp/"
markdown_url: "https://docs.caracal.run/markdown/v0.2/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/v0.2/guides/protect-mcp/
Markdown URL: https://docs.caracal.run/markdown/v0.2/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.

## 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](/v0.2/sdks/verify/).

## Next Step

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