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

@growth-labs/mcp-auth

v0.3.0

Published

Authentication and identity layer for MCP services built on Growth Labs substrate. Resolves an inbound HTTP request into a typed Actor object that downstream code uses for authorization, audit, and per-user scoping.

Readme

@growth-labs/mcp-auth

Authentication and identity layer for MCP services built on Growth Labs substrate. Resolves an inbound HTTP request into a typed Actor object that downstream code uses for authorization, audit, and per-user scoping.

This package is generic substrate — it makes no assumptions about which realm, issuer, or service is using it. Realm-specific configuration (which OAuth issuer to verify against, which token map to use) is injected by the consuming service.

Install

pnpm add @growth-labs/mcp-auth

Required peer runtime: a Cloudflare-Workers-compatible JS runtime (Workers, Node 22+) with Web Crypto and Fetch APIs available.

Concepts

Actor

The single output of every successful auth check.

interface Actor {
  id: string                    // 'user:grizzle' | 'service:hermes-runner'
  kind: 'user' | 'service'
  email?: string
  display_name?: string
  roles: Role[]                 // 'operator' | 'submitter' | 'viewer' | 'service'
  source: AuthSource            // 'token-map' | 'oauth-jwt' | 'service-binding'
  authenticated_at: number      // ms epoch
  raw_token_id?: string         // sha256(token), 16 hex chars
}

AuthResolver

(request, env) => Promise<Actor | null>. Resolvers compose: first one to return a non-null Actor wins, others short-circuit.

Two ship:

  • tokenMapResolver({ envVarName }) — reads a JSON token map from the named env var and resolves bearer tokens to Actors. Used for service tokens.
  • oauthJwtResolver({ issuer, audience, ...claimOverrides? }) — verifies an OAuth access-token JWT against the issuer's JWKS, validates iss/aud/exp, and maps payload claims to an Actor with source: 'oauth-jwt'. Used for human-user OAuth flows.

createCompositeResolver(sources) (added in 0.3.0) composes an ordered list of heterogeneous credential sources into a single resolver. Accepts any mix of TokenMapResolverOptions (env-var JSON token-map) and SecretsStoreResolverEntry (Cloudflare Secrets Store binding paired with a fixed actor identity). First match wins. Used when a service accepts both realm-specific service tokens AND a shared agent token whose value lives only in Cloudflare Secrets Store.

const resolver = createCompositeResolver([
  { envVarName: 'FOUNDRY_MCP_TOKENS_JSON' },
  {
    binding: env.AGENT_RW_TOKEN, // Cloudflare Secrets Store binding
    actor: { id: 'agent:fulcrum-agent', kind: 'service', roles: ['operator'] },
  },
])

Secrets Store reads are cached per-request inside a WeakMap<Request, ...>, so multiple auth checks within one request invoke binding.get() once per (request, binding). The incoming bearer is compared constant-time against the resolved secret via the exported timingSafeEqual(a, b) helper. Actors resolved via Secrets Store carry source: 'secrets-store' and a raw_token_id (sha256(bearer)[..16]) for audit correlation.

AuditWriter

The package emits audit events; storage is realm-specific. Implement AuditWriter.write(event: AuthAuditEvent) against whatever storage you use (D1, Logpush, Tail Worker, etc.).

Usage

import {
  mcpAuthMiddleware,
  oauthJwtResolver,
  requireRole,
  tokenMapResolver,
} from '@growth-labs/mcp-auth'

const auth = mcpAuthMiddleware({
  resolvers: [
    oauthJwtResolver({
      issuer: 'https://auth.your-realm.tld',
      audience: 'https://mcp.your-service.tld/mcp',
    }),
    tokenMapResolver({ envVarName: 'FOUNDRY_MCP_TOKENS_JSON' }), // realm-specific service tokens
  ],
  required: true,
  audit: {
    async write(event) {
      // persist to your realm's audit table
    },
  },
})

const operatorOnly = requireRole('operator')

mcpAuthMiddleware populates ctx.actor and audits every attempt. On no actor with required: true it returns a 401 with WWW-Authenticate: Bearer realm="<realm>", error="invalid_token" and the documented envelope:

{
  "error": {
    "code": "unauthenticated" | "forbidden" | "invalid-token",
    "message": "...",
    "request_id": "req_..."
  }
}

Token map shape

The token-map resolver expects a JSON object keyed by bearer token or by hashTokenMapKey(token). Prefer hashTokenMapKey(token) for service secrets; it returns sha256:<raw_token_id> so the public audit raw_token_id value is never itself a valid bearer token.

{
  "sha256:e7b412c5f301a6de": {
    "id": "user:grizzle",
    "kind": "user",
    "email": "[email protected]",
    "display_name": "Grizzle",
    "roles": ["operator"]
  },
  "sha256:2f85c14f548c92df": {
    "id": "service:hermes-runner",
    "kind": "service",
    "roles": ["service"]
  }
}

Set the JSON via wrangler secret put <ENV_VAR_NAME>. The token value never appears in logs, errors, or audit rows — only the SHA-256 hash truncated to 16 hex chars (raw_token_id) is recorded.

Token rotation = redeploy with a new map. v1 does not hot-reload.

Error envelope

Errors returned by middleware:

| Status | code | When | |--------|--------------------|-------------------------------------| | 401 | unauthenticated | No actor + required: true | | 403 | forbidden | requireRole / requireAnyRole failed |

Token leakage

The package is structurally hardened against leakage: the bearer token's plaintext is never written to logs, error messages, or audit rows. A property test in __tests__/token-leakage.test.ts enforces this across every code path.

Versioning

v1 surface is stable. Adding a new resolver, audit event field, or role value requires a spec amendment.

Public API

// Types
export type { Actor, AuditWriter, AuthAuditEvent, AuthContext, AuthResolver, AuthSource, Middleware, Role }

// Resolvers
export { tokenMapResolver, oauthJwtResolver, createCompositeResolver }
export type {
  TokenMapResolverOptions,
  OAuthJwtResolverConfig,
  CompositeResolverSource,
  SecretsStoreResolverEntry,
  SecretsStoreActorIdentity,
  SecretsStoreBinding,
}

// Middleware
export { mcpAuthMiddleware, requireRole, requireAnyRole }
export type { McpAuthMiddlewareOptions }

// Errors
export { errorResponse }
export type { ErrorCode }

// Utilities
export { generateToken, hashTokenId, hashTokenMapKey, timingSafeEqual }
export type { GenerateTokenOptions }