Skip to content

Run the example

You can watch a caller’s tool list change before you configure anything. The Node example mints its own tokens with a local dev key, so no authorization server has to exist yet.

  1. Clone and install.

    Terminal window
    git clone https://github.com/jagreehal/mcp-authz
    cd mcp-authz
    pnpm install
  2. Start the server and mint two tokens.

    Terminal window
    pnpm --filter mcp-authz-node-example dev # :8200, local dev key
    pnpm --filter mcp-authz-node-example token dana@acme.com # reader
    pnpm --filter mcp-authz-node-example token alice@acme.com # editor
    pnpm --filter mcp-authz-node-example token sam@other.com # nobody
  3. Paste one into the MCP Inspector, under Authentication → Bearer Token, and list the tools.

Token tools/list prompts/list update_case
dana@acme.com whoami, get_case empty not there to call
alice@acme.com whoami, get_case, update_case triage_case works, and audits case:C1234
sam@other.com HTTP 403 forbidden/policy_denied 403 —

Dana and Alice both see the cases://all resource, because both hold cases:read. Prompts and resources go through the same gate as tools.

Dana does not have update_case hidden. It was never registered for her request.

Terminal window
pnpm --filter mcp-authz-node-example token priya@acme.com --group qa-leads
pnpm --filter mcp-authz-node-example token alice@acme.com --scope mcp

The first promotes Priya through an IdP group claim, with no policy edit. The second shows the second axis: Alice keeps the permission, so update_case stays visible, and calling it returns 403 insufficient_scope. A scope gap is something the client can re-authorise for, and the challenge says so. A permission gap is an administrator’s job.

src/e2e.test.ts asserts every row of that table, so this page cannot quietly stop being true.

Configure the AS first. It has to accept resource=<your MCP URL> and mint tokens whose aud equals it, or the client finishes the whole OAuth flow and never sends the token it just received. The ordered steps are in Point your AS at it.

Terminal window
cp apps/node-example/.env.example apps/node-example/.env
# fill MCP_PUBLIC_URL and OAUTH_*; optionally set MCP_POLICY
pnpm --filter mcp-authz-node-example start

Default port 8200. Add https://…/mcp in Claude as a custom connector.

apps/helpscout-example serves Help Scout tools to one org agent, such as Claude Tag, that presents a static bearer. You run no authorization server. A custom tokenVerifier compares the key in constant time, and the policy grants helpscout:read to the subject it returns.

Terminal window
export MCP_PUBLIC_URL=https://mcp.example.com/mcp
export MCP_BEARER=$(openssl rand -hex 32)
export HELPSCOUT_DOCS_API_KEY=…
export HELPSCOUT_APP_ID=… # optional: Mailbox tools
export HELPSCOUT_APP_SECRET=…
pnpm --filter mcp-authz-helpscout-example dev # :8400

Without the Mailbox credentials the server lists the three Docs tools. With them it adds four Mailbox tools. The Help Scout keys never leave the process. See the example README.