Skip to content
Guide

Protect your MCP server

Let AI clients call your own MCP server as the signed-in user: protected-resource metadata, OAuth sign-in from Claude, Cursor and VS Code, the token audience, scopes, and tokens for clients that send a header.

You get
An MCP server that AI clients call as the signed-in user, within the scopes you allow.
For
Backend developer
Time
About 25 min
Products
Machines and AI
Sign-in

Your MCP server is an API whose callers are AI clients (Claude, Cursor, Copilot and others). Protect it like any API: Anchr.ing signs the tokens, your server verifies them offline and acts as the user they belong to.

1. Register it

In the dashboard, Apps, add an app of kind resource whose API identifier is the endpoint's public URL, e.g. https://mcp.acme.example/mcp. That URL is the audience your server checks. In Settings, turn on Let MCP clients register themselves.

2. Serve the metadata and the challenge

mcp/server.ts
import { createAuth } from '@anchring/auth/server';

const resource = 'https://mcp.acme.example/mcp';
const auth = createAuth({ project: 'acme', env: 'prod', audience: resource, pat: { audience: resource } });
const mcp = auth.mcp.protectedResource({ resource, scopes: ['notes:read', 'notes:write'], resourceName: 'Notes' });

app.get(mcp.metadataPath, mcp.metadataHandler()); // /.well-known/oauth-protected-resource/mcp
app.all('/mcp', mcp.middleware(), (c) => handleMcp(c.req.raw, c.var.user)); // tools act as the user, within its scopes

The metadata (RFC 9728) names your project's issuer and your scopes; the 401 challenge points clients at it. Tools check user.scopes before acting, and user.sub is the signed-in user.

3. Connect a client

Claude, Claude Code, Cursor and VS Code add the server by its URL and sign the user in themselves:

terminal
claude mcp add --transport http notes https://mcp.acme.example/mcp

The client follows the 401 to your metadata, reads the issuer's OAuth server metadata (RFC 8414), registers itself (dynamic client registration, or a client ID metadata document) and opens your project's sign-in with resource set to your URL (RFC 8707), so the token's audience is your server. Self-registered clients always show the sign-in page; your plan caps how many may register in production.

Clients that send a header

A personal access token with your scopes (from the user's account page), or an org token for a team's CI or shared agent, sent as a header. Cursor, in .cursor/mcp.json:

.cursor/mcp.json
{
  "mcpServers": {
    "notes": { "url": "https://mcp.acme.example/mcp", "headers": { "Authorization": "Bearer ${env:NOTES_TOKEN}" } }
  }
}

With pat in createAuth, your server exchanges the token for a 10-minute access token and caches it. A revoked token stops within that window.

The full template, with DPoP, scoped tools and tests against a local test issuer: MCP server example.

Every option, error and token type: the SDK README.

Try it in your own test environment

Every feature works in test on the free plan, so the code above runs against your real sign-in.