Pre-release. v0.1 is not out yet, so there is nothing to install and no public source to clone — the quickstart builds from a checkout.
Protect an MCP server
@subactid/mcp applies the tool server rules to an MCP
server:
- every call carries a task token;
- every tool names the scope it needs;
- every call is logged with the human and the agent.
@subactid/mcp lists every option and method.
The guard
Section titled “The guard”Lines 20–24 of the SDK’s examples/mcp-jira/index.ts:
const guard = new SubactIdGuard({ issuer: process.env['SUBACTID_ISSUER'] ?? 'http://127.0.0.1:5100', audience: 'https://jira.internal', tools: { search: { scope: 'jira:read' }, comment: { scope: 'jira:comment', highRisk: true } },});tools is an allowlist. A tool not listed is refused with unknown_tool. A new tool cannot
be called until you add it here with its scope.
Wiring it up
Section titled “Wiring it up”Lines 25–29 of the same file, below its imports:
createServer((req, res) => serve(req, res).catch((e) => guard.reject(res, e))).listen(loopback());async function serve(req: IncomingMessage, res: ServerResponse): Promise<void> { const auth = await guard.authenticate(req.headers.authorization); await (await jira()).handleRequest(Object.assign(req, { auth }), res);}guard.authenticateverifies the token at the transport, before any MCP code runs.guard.rejectanswers a failure with the right status andWWW-Authenticateheader.- On Express with the MCP SDK’s middleware,
requireBearerAuth({ verifier: guard })does the same job.
@modelcontextprotocol/sdk is a peer dependency. Install it beside @subactid/mcp.
Then wrap the MCP server, so each tool call is checked against its policy. Lines 33–40 of the
SDK’s examples/mcp-jira/index.ts:
const server = guard.protect(new McpServer({ name: 'jira', version: '0.1.0' }));server.registerTool( 'search', { description: 'Find issues by text.', inputSchema: { query: z.string() } }, async ({ query }) => ({ content: [{ type: 'text', text: `PROJ-1: "${query}" reported by a user` }], }),);protect returns the same server. Register tools as usual. Call protect before registering
any tool: it throws if a tool is already registered.
High-risk tools
Section titled “High-risk tools”highRisk: true makes the guard introspect at the control plane on every call to that tool. A
revoked token is then refused at once, not when it expires. Mark tools that write or change
something, such as comment.
A token for an audience in the agent’s high_risk_audiences carries introspect_required, and
the guard introspects it on every call without any tool being marked. The introspection done at
the transport counts for the first tool call. Each later call on the same authentication is
introspected again.
The options
Section titled “The options”| Option | Default | What it does |
|---|---|---|
issuer |
— | The control plane’s issuer URL |
audience |
— | What this server is: the aud a token must carry |
tools |
— | Every tool and the scope it requires. Others are refused |
requireActor |
true |
Refuse tokens with no act claim, so only agents get in |
maxDelegationDepth |
1 |
Longest act chain accepted |
clockSkewSeconds |
60 |
Tolerance on exp, nbf and iat |
timeoutMs |
10000 |
Longest a call to the control plane may take |
realm |
the audience | Named in WWW-Authenticate |
log |
one JSON line on stderr | Where every call is logged |
The default log goes to stderr because an MCP server on a stdio transport uses stdout for the protocol.
The log line
Section titled “The log line”{ "event": "tool.call", "at": "2026-09-09T14:04:07.221Z", "tool": "comment", "decision": "allow", "sub": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "act": "agent:jira-triage", "depth": 1, "task_id": "task_01HQZX9K4M", "jti": "tok_01HQZX9K5P"}sub is the human the agent acted for. Log it even on a server whose tools only read.
What it does not decide
Section titled “What it does not decide”Subact ID decides by scope, audience, lifetime and delegation depth. Whether this user may touch a particular issue is your application’s decision. The claims are available in the handler to make it.
© 2026 Nikola Živković PR Agencija za programerske usluge Novi Sad. Subact ID is its product.