---
title: "Protect a FastMCP App"
url: "https://docs.caracal.run/v1.0/guides/protect-fastmcp/"
markdown_url: "https://docs.caracal.run/markdown/v1.0/guides/protect-fastmcp.md"
description: "Verify Caracal mandates in a FastMCP server so tool calls are checked before handlers run."
page_type: "page"
concepts: []
requires: []
---

# Protect a FastMCP App

Canonical URL: https://docs.caracal.run/v1.0/guides/protect-fastmcp/
Markdown URL: https://docs.caracal.run/markdown/v1.0/guides/protect-fastmcp.md
Description: Verify Caracal mandates in a FastMCP server so tool calls are checked before handlers run.
Page type: page
Concepts: none
Requires: none

---

Use the FastMCP adapter when a FastMCP app should reject invalid, expired, insufficient, or revoked Caracal mandates. Python uses `caracalai-fastmcp`; TypeScript uses `@caracalai/fastmcp`.

## Prerequisites

* A mandate-aware MCP resource, stable audience, tool scopes, issuer, and zone ID.
* A shared production revocation store.
* A FastMCP hook that can reject before tool code runs; verification after dispatch is too late.

## Install

```bash
pip install "caracalai-fastmcp[fastmcp]" caracalai-revocation
```

```bash
npm install @caracalai/fastmcp @caracalai/verify @caracalai/revocation
```

Use `caracalai-revocation-redis` (Python) or the Redis store in `@caracalai/revocation` (TypeScript) for shared production revocation. The in-memory store is only for local development and tests.

## Create an authenticator (Python)

```python
from caracalai_fastmcp import CaracalAuth
from caracalai_revocation import InMemoryRevocationStore

auth = CaracalAuth(
    issuer="https://sts.pipernet.example",
    audience="resource://pipernet",
    expected_zone_id="0195f2a9-1b22-7c3d-9e4f-5a6b7c8d9e0f",
    required_scopes=["mcp:tool:call"],
    required_targets=["resource://pipernet"],
    revocations=InMemoryRevocationStore(),
    require_session=True,
)

async def startup():
    await auth.warmup()
```

## Verify before running a tool (Python)

```python
from caracalai_fastmcp import CaracalAuthError

async def handle_tool_call(token: str, payload: dict):
    try:
        claims = await auth(token)
    except CaracalAuthError as err:
        return {"error": err.code, "error_description": err.description, "error_hint": err.hint}

    return {
        "subject": claims.sub,
        "result": await run_tool(payload),
    }
```

Wire this check into the FastMCP auth or request hook used by your server. The important boundary is that the mandate is verified before the tool handler performs work.

## Verify before running a tool (TypeScript)

```ts
import { extractBearer, verifyFastMcpToken, FastMcpAuthError } from '@caracalai/fastmcp'
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(),
})

async function handleToolCall(authorization: string, payload: unknown) {
  const token = extractBearer(authorization)
  if (!token) return { error: 'missing_token' }

  try {
    const context = await verifyFastMcpToken(token, verifier, {
      requiredScopes: ['mcp:tool:call'],
      requiredTargets: ['resource://pipernet'],
      requireSession: true,
    })
    return { subject: context.sub, result: await runTool(payload) }
  } catch (err) {
    if (err instanceof FastMcpAuthError) return { error: err.code }
    throw err
  }
}
```

The boundary is the same in both languages: verify the mandate before the tool handler performs work.

## Validate

| Test                                                             | Expected result                   |
| ---------------------------------------------------------------- | --------------------------------- |
| Missing bearer token                                             | `missing_token` or framework 401. |
| Wrong audience                                                   | `invalid_token`.                  |
| Missing scope                                                    | `insufficient_scope`.             |
| Revoked session                                                  | `session_revoked`.                |
| Mandate without a Session with `require_session=True` / `requireSession: true` | `session_required`.               |

Expected result: the tool handler runs only for a mandate matching issuer, audience, zone, target, scope, Session requirements, and current revocation state.

:::caution[Failure point: adapter scope]
The adapter verifies one token; it does not start a Session, mint authority, or authorize a later outbound call. Use the SDK inside verified context for governed downstream work.
:::

For exact call signatures, use [FastMCP Adapter reference](/v1.0/sdks/adapters/fastmcp/).

## Next Step

Use [Protect an MCP Server](/v1.0/guides/protect-mcp/) only when the framework hook cannot use this dedicated adapter.
