npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@apoa/mcp

v0.1.4

Published

APOA authorization for MCP servers — per-tool-call scoping, delegation chains, audit trails

Readme

APOA on MCP

@apoa/mcp

APOA authorization for MCP servers. Per-tool-call scoping, delegation chains, audit trails.

MCP has OAuth 2.1 for connection-level auth (who can connect). This package adds action-level auth (what they can do once connected).

MCP Client --> @apoa/mcp --> MCP Server
               (authorize every tool call)

Two Modes

Middleware (for servers you build)

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { withAPOA } from '@apoa/mcp';

const server = new Server({ name: 'my-server', version: '1.0.0' }, { capabilities: { tools: {} } });

withAPOA(server, {
  key: publicKey,
  mappings: {
    read_file:  'filesystem:files:read',
    write_file: 'filesystem:files:write',
    list_dir:   'filesystem:dirs:list',
  },
});

Three lines. Every tool call is authorized against an APOA token passed in _meta.apoa_token. No token, no access.

Proxy (for third-party servers you can't modify)

npx apoa-mcp --config gateway.config.json

Sits between client and server, intercepts every tools/call, authorizes, strips the token, forwards.

How It Works

  1. MCP client passes an APOA token in _meta.apoa_token on every tool call
  2. The tool name is mapped to an APOA service:scope pair
  3. The token is verified (signature, expiration, revocation, replay)
  4. The SDK's authorize() checks scope, constraints, and rules
  5. If authorized, the tool runs. If not, the client gets a denial with a reason.

The token is stripped before reaching the tool handler -- the tool never sees it.

Passing Tokens

MCP clients include an APOA token in the _meta field on each tool call:

import { APOA, generateKeyPair } from '@apoa/core';

const keys = await generateKeyPair();
const apoa = new APOA({ privateKey: keys.privateKey });

const grant = await apoa.tokens.createGrant({
  principal: 'did:apoa:alice',
  agent: 'did:apoa:research-agent',
  service: 'filesystem',
  scopes: ['files:read'],
  expiresIn: '1h',
});

Pass grant.raw as _meta.apoa_token:

{
  "method": "tools/call",
  "params": {
    "name": "read_file",
    "arguments": {
      "path": "/tmp/test.txt",
      "_meta": {
        "apoa_token": "<grant.raw>"
      }
    }
  }
}

On the MCP server side, configure withAPOA() with the matching public key:

withAPOA(server, {
  key: keys.publicKey,
  mappings: {
    read_file: 'filesystem:files:read',
  },
});

Tool Mappings

Simple format (one string per tool):

withAPOA(server, {
  key: publicKey,
  mappings: {
    read_file:  'filesystem:files:read',
    write_file: 'filesystem:files:write',
  },
});

Auto-mapping (no config needed):

withAPOA(server, { key: publicKey });
// read_file -> read_file:call, write_file -> write_file:call, etc.

Conditional mappings (argument-aware):

{
  "toolMappings": [
    { "tool": "write_file", "service": "filesystem", "scope": "files:write:sandbox",
      "when": { "path": { "startsWith": "/tmp" } }, "priority": 10 },
    { "tool": "write_file", "service": "filesystem", "scope": "files:write" }
  ]
}

apoa.check (Dry-Run Tool)

Enable it to inject a debugging tool that checks authorization without executing:

withAPOA(server, { key: publicKey, enableCheckTool: true });

Agents or humans can call apoa.check({ tool: 'write_file', path: '/home/data' }) to see whether the action would be authorized, what scope it maps to, and which rule would fire.

What This Adds to MCP

| Capability | MCP Native | @apoa/mcp | |---|---|---| | Connection-level auth (OAuth 2.1) | Yes | N/A (complementary) | | Per-tool-call authorization | No (SEP-1880 closed as NOT_PLANNED) | Yes | | Delegation chains with attenuation | No | Yes (via @apoa/core) | | Hard/soft rules engine | No | Yes | | Constraint checking per action | No | Yes | | Cascade revocation | No | Yes | | Audit trail | No (roadmap "pre-RFC") | Yes (hash-chained, tamper-evident) | | Replay protection | No | Yes (JTI-based) |

Persistent Stores

For production, use SQLite stores instead of the default in-memory stores:

import { SqliteRevocationStore, SqliteAuditStore, SqliteReplayStore } from '@apoa/mcp';

withAPOA(server, {
  key: publicKey,
  revocationStore: new SqliteRevocationStore('./auth.db'),
  auditStore: new SqliteAuditStore('./auth.db'),
  replayStore: new SqliteReplayStore('./auth.db'),
});

The audit store uses SHA-256 hash chaining for tamper evidence.

Development

pnpm install
pnpm test          # Run tests
pnpm run typecheck # Type check
pnpm run build     # Build

License

Apache-2.0

Part of the APOA Standard