{"_id":"@browserid-ng/mcp-auth","_rev":"9-e2f47a40e0a18531c2a954006de889c3","name":"@browserid-ng/mcp-auth","dist-tags":{"latest":"0.5.4"},"versions":{"0.1.0":{"name":"@browserid-ng/mcp-auth","version":"0.1.0","keywords":["browserid","mcp","oauth","agent","warrant","authorization","rfc7521"],"license":"MPL-2.0","_id":"@browserid-ng/mcp-auth@0.1.0","maintainers":[{"name":"thunder","email":"code@sandmill.org"}],"homepage":"https://github.com/vthunder/browserid-ng#readme","bugs":{"url":"https://github.com/vthunder/browserid-ng/issues"},"dist":{"shasum":"e88deafdf77462d178efd2365037fdb629dd5da2","tarball":"https://registry.npmjs.org/@browserid-ng/mcp-auth/-/mcp-auth-0.1.0.tgz","fileCount":4,"integrity":"sha512-UPyjkVb0haYS8FFADANwALdgHY1TuSrelyjYpATDmO8fLh8WU9YTdMjJa3XKdhV3i6LPTt/Up38xPxIJdi8MyA==","signatures":[{"sig":"MEYCIQCUQw3X0N6/Eth5t7CUMrXQvWXJ7t4U2vYE1ktHvG1lRQIhAIt4TyZw/46fPFdl06usvVvtnX3Kq4/ca9x7v23jQtiV","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":19595},"main":"./index.mjs","type":"module","types":"./index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./index.d.ts","import":"./index.mjs"}},"gitHead":"1b2bafa414d4e67cec471bfd85e2a089ea5f8259","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"thunder","email":"code@sandmill.org"},"deprecated":"Calls the retired browserid.me/verify-access endpoint (removed 2026-08-26); upgrade to the latest version, which calls /verify.","repository":{"url":"git+https://github.com/vthunder/browserid-ng.git","type":"git"},"_npmVersion":"11.6.2","description":"Warrant-gated MCP tools over MCP's own OAuth 2.1: a BrowserID 7521 assertion-grant AS + fail-closed per-call revocation checks. Every tool call attributable to 'agent X on behalf of human Y', revocable in one click.","directories":{},"_nodeVersion":"25.2.1","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mcp-auth_0.1.0_1786517258754_0.16361478460110046","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@browserid-ng/mcp-auth","version":"0.2.0","keywords":["browserid","mcp","oauth","agent","warrant","authorization","rfc7521"],"license":"MPL-2.0","_id":"@browserid-ng/mcp-auth@0.2.0","maintainers":[{"name":"thunder","email":"code@sandmill.org"}],"homepage":"https://github.com/vthunder/browserid-ng#readme","bugs":{"url":"https://github.com/vthunder/browserid-ng/issues"},"dist":{"shasum":"84284791133bf14e3aaa53fd3b037dd682feb4c0","tarball":"https://registry.npmjs.org/@browserid-ng/mcp-auth/-/mcp-auth-0.2.0.tgz","fileCount":4,"integrity":"sha512-Di5LcZiILcNWyaBY5R6yk+27t/Fu1NyKLxRadyuRW2hJeBvaPqMr49gAQ5WKr8zN6T2GgYeo2PNvO4GKwx36+g==","signatures":[{"sig":"MEUCIA92WaleyKj2oxGHT3qBXTJtIYY3d+99dlS6Zkqt71VyAiEAi34itlgycNzIU5xRU6wF2CbXoadnvLbsAht57RsGghY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":45564},"main":"./index.mjs","type":"module","types":"./index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./index.d.ts","import":"./index.mjs"}},"gitHead":"6fb5f2ddd6eddf66a8e4582f51fc3f1f9eb9e83c","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"thunder","email":"code@sandmill.org"},"deprecated":"Calls the retired browserid.me/verify-access endpoint (removed 2026-08-26); upgrade to the latest version, which calls /verify.","repository":{"url":"git+https://github.com/vthunder/browserid-ng.git","type":"git"},"_npmVersion":"11.6.2","description":"Warrant-gated MCP tools over MCP's own OAuth 2.1: a BrowserID 7521 assertion-grant AS + fail-closed per-call revocation checks. Every tool call attributable to 'agent X on behalf of human Y', revocable in one click.","directories":{},"_nodeVersion":"25.2.1","dependencies":{"@browserid-ng/agent":"^0.4.1"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mcp-auth_0.2.0_1786546902216_0.7158055292453582","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@browserid-ng/mcp-auth","version":"0.4.0","keywords":["browserid","mcp","oauth","agent","warrant","authorization","rfc7521"],"license":"MPL-2.0","_id":"@browserid-ng/mcp-auth@0.4.0","maintainers":[{"name":"thunder","email":"code@sandmill.org"}],"homepage":"https://github.com/vthunder/browserid-ng#readme","bugs":{"url":"https://github.com/vthunder/browserid-ng/issues"},"dist":{"shasum":"166b411e8b4135c7c0660d4660ac8861a091ce52","tarball":"https://registry.npmjs.org/@browserid-ng/mcp-auth/-/mcp-auth-0.4.0.tgz","fileCount":4,"integrity":"sha512-A+UaWOTjdvG023d3F4GcCJUpAUQhS6OHx5jR4KCdcAexh4TUKHxA8PhNoxDtY51pCuHZDuev5RqhTHyVgUXYUg==","signatures":[{"sig":"MEUCIFZmgQJtG65ZOHE4Yt0jhht2IWcDvOtw2VWFGkRdcY3fAiEAraDvOXlZvhB8MGm0s0/R/AineCbQ+JYVX0Pc2aLTv8k=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":75440},"main":"./index.mjs","type":"module","types":"./index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./index.d.ts","import":"./index.mjs"}},"gitHead":"f98aa6c1ad86e7918671c3b419e8069739a65264","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"thunder","email":"code@sandmill.org"},"deprecated":"Calls the retired browserid.me/verify-access endpoint (removed 2026-08-26); upgrade to the latest version, which calls /verify.","repository":{"url":"git+https://github.com/vthunder/browserid-ng.git","type":"git"},"_npmVersion":"11.6.2","description":"Warrant-gated MCP tools over MCP's own OAuth 2.1: a BrowserID 7521 assertion-grant AS + fail-closed per-call revocation checks. Every tool call attributable to 'agent X on behalf of human Y', revocable in one click.","directories":{},"_nodeVersion":"25.2.1","dependencies":{"@browserid-ng/agent":"^0.4.1"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mcp-auth_0.4.0_1786736955596_0.25075755176144154","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@browserid-ng/mcp-auth","version":"0.5.0","keywords":["browserid","mcp","oauth","agent","warrant","authorization","rfc7521"],"license":"MPL-2.0","_id":"@browserid-ng/mcp-auth@0.5.0","maintainers":[{"name":"thunder","email":"code@sandmill.org"}],"homepage":"https://github.com/vthunder/browserid-ng#readme","bugs":{"url":"https://github.com/vthunder/browserid-ng/issues"},"dist":{"shasum":"57207e9b98aa988db54ca93b7b509a806c916cf2","tarball":"https://registry.npmjs.org/@browserid-ng/mcp-auth/-/mcp-auth-0.5.0.tgz","fileCount":4,"integrity":"sha512-sntQcBssm9+JkCnHVZdyhwcQ9GidkOrbHRiFYlOsRVmKi/F9b3KxTqmUCWQHdd0fCZ+RKI54L0XeKPdy5XOwnw==","signatures":[{"sig":"MEQCIHgs2z1dOHYd+7cXdAuml/LHtgOUONX4D6exGYHTkRU4AiAPvryogbZdzG2Qd3OyIFHdtNtkqdKh4aO9yf6VcKl4Cg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":76613},"main":"./index.mjs","type":"module","types":"./index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./index.d.ts","import":"./index.mjs"}},"gitHead":"6ef89ac9f5b051f5905d99b20d05375691c3807f","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"thunder","email":"code@sandmill.org"},"deprecated":"Calls the retired browserid.me/verify-access endpoint (removed 2026-08-26); upgrade to the latest version, which calls /verify.","repository":{"url":"git+https://github.com/vthunder/browserid-ng.git","type":"git"},"_npmVersion":"11.6.2","description":"Warrant-gated MCP tools over MCP's own OAuth 2.1: a BrowserID 7521 assertion-grant AS + fail-closed per-call revocation checks. Every tool call attributable to 'agent X on behalf of human Y', revocable in one click.","directories":{},"_nodeVersion":"25.2.1","dependencies":{"@browserid-ng/agent":"^0.4.1"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mcp-auth_0.5.0_1786770202456_0.15164243257587273","host":"s3://npm-registry-packages-npm-production"}},"0.5.1":{"name":"@browserid-ng/mcp-auth","version":"0.5.1","keywords":["browserid","mcp","oauth","agent","warrant","authorization","rfc7521"],"license":"MPL-2.0","_id":"@browserid-ng/mcp-auth@0.5.1","maintainers":[{"name":"thunder","email":"code@sandmill.org"}],"homepage":"https://github.com/vthunder/browserid-ng#readme","bugs":{"url":"https://github.com/vthunder/browserid-ng/issues"},"dist":{"shasum":"0585d59abe0f8900850004294ac67d12c197defc","tarball":"https://registry.npmjs.org/@browserid-ng/mcp-auth/-/mcp-auth-0.5.1.tgz","fileCount":4,"integrity":"sha512-4XZ5XkOwZ0jpGTfCpkgpl+rUZ0dBcprceyW1IbMhdxElS5y65ACqIUEAUlCEame94LFFO/QoZjEE4V2DcGE0Dg==","signatures":[{"sig":"MEYCIQCHVgS1uR6v8XXJtgefM5sqzbx7Ga3dGUjOgHudyIv3rgIhAJ+m0PckgRng4yJSTYpcIDkqKckt4sBex8tiCmPU5gsP","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":77048},"main":"./index.mjs","type":"module","types":"./index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./index.d.ts","import":"./index.mjs"}},"gitHead":"86f47d45d8230589446b36286c2513b24aa37895","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"thunder","email":"code@sandmill.org"},"deprecated":"Calls the retired browserid.me/verify-access endpoint (removed 2026-08-26); upgrade to the latest version, which calls /verify.","repository":{"url":"git+https://github.com/vthunder/browserid-ng.git","type":"git"},"_npmVersion":"11.6.2","description":"Warrant-gated MCP tools over MCP's own OAuth 2.1: a BrowserID 7521 assertion-grant AS + fail-closed per-call revocation checks. Every tool call attributable to 'agent X on behalf of human Y', revocable in one click.","directories":{},"_nodeVersion":"25.2.1","dependencies":{"@browserid-ng/agent":"^0.4.1"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mcp-auth_0.5.1_1786814259746_0.6726617736059837","host":"s3://npm-registry-packages-npm-production"}},"0.5.2":{"name":"@browserid-ng/mcp-auth","version":"0.5.2","keywords":["browserid","mcp","oauth","agent","warrant","authorization","rfc7521"],"license":"MPL-2.0","_id":"@browserid-ng/mcp-auth@0.5.2","maintainers":[{"name":"thunder","email":"code@sandmill.org"}],"homepage":"https://github.com/vthunder/browserid-ng#readme","bugs":{"url":"https://github.com/vthunder/browserid-ng/issues"},"dist":{"shasum":"62ff3ef3326dea45fe60ecbf55390e73b199841c","tarball":"https://registry.npmjs.org/@browserid-ng/mcp-auth/-/mcp-auth-0.5.2.tgz","fileCount":4,"integrity":"sha512-MWGHogplXlft648PJ2HJ6cPDHOojxszJA5H37YPOOmcNOPfzpwAejo9NwQexh4/o87ufti3ahYT+58OtEe5B2A==","signatures":[{"sig":"MEUCIDZFdW87Dw7RYWW5Rx7ZHOIqgEgIiZgA/ANSuoT3iFpvAiEAls6UncqKE9XAs2+ETOwowKYYO5eo+SAuQnIFQ63H/lY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":76986},"main":"./index.mjs","type":"module","types":"./index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./index.d.ts","import":"./index.mjs"}},"gitHead":"bb9417dbcc7c1b8880139135c2d244a2b74b88aa","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"thunder","email":"code@sandmill.org"},"deprecated":"Calls the retired browserid.me/verify-access endpoint (removed 2026-08-26); upgrade to the latest version, which calls /verify.","repository":{"url":"git+https://github.com/vthunder/browserid-ng.git","type":"git"},"_npmVersion":"11.6.2","description":"Warrant-gated MCP tools over MCP's own OAuth 2.1: a BrowserID 7521 assertion-grant AS + fail-closed per-call revocation checks. Every tool call attributable to 'agent X on behalf of human Y', revocable in one click.","directories":{},"_nodeVersion":"25.2.1","dependencies":{"@browserid-ng/agent":"^0.4.1"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mcp-auth_0.5.2_1787727268012_0.3409308358726233","host":"s3://npm-registry-packages-npm-production"}},"0.5.3":{"name":"@browserid-ng/mcp-auth","version":"0.5.3","keywords":["browserid","mcp","oauth","agent","warrant","authorization","rfc7521"],"license":"MPL-2.0","_id":"@browserid-ng/mcp-auth@0.5.3","maintainers":[{"name":"thunder","email":"code@sandmill.org"}],"homepage":"https://github.com/vthunder/browserid-ng#readme","bugs":{"url":"https://github.com/vthunder/browserid-ng/issues"},"dist":{"shasum":"bba051610e07a916ef9739b774e218231cc2b742","tarball":"https://registry.npmjs.org/@browserid-ng/mcp-auth/-/mcp-auth-0.5.3.tgz","fileCount":4,"integrity":"sha512-7DaYdBs8d2tLo9AlQRE94WLejN5GWWqyHF1Htjdu0SoJCqD+Cn47cVB8khSvmu+iolHGkWfmMeYrPsfLA40Yyg==","signatures":[{"sig":"MEQCIA8n0YbaOF+AZpJEyV71BZFanacfPfsiE/k3Yw38qBFHAiBWmRxG/xWdFSlf5YXymeRaNHXBE/uUK87MKNrL0RLscQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@browserid-ng%2fmcp-auth@0.5.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":76994},"main":"./index.mjs","type":"module","types":"./index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./index.d.ts","import":"./index.mjs"}},"gitHead":"ba50fed7f8b9021bd75016479f64f9f291c50533","scripts":{"test":"node --test test/*.test.mjs"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:155e7445-8707-4067-8d26-c67816d1b2e6"}},"repository":{"url":"git+https://github.com/vthunder/browserid-ng.git","type":"git"},"_npmVersion":"12.0.2","description":"Warrant-gated MCP tools over MCP's own OAuth 2.1: a BrowserID 7521 assertion-grant AS + fail-closed per-call revocation checks. Every tool call attributable to 'agent X on behalf of human Y', revocable in one click.","directories":{},"_nodeVersion":"24.19.0","dependencies":{"@browserid-ng/agent":"^0.4.1"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/mcp-auth_0.5.3_1787730634119_0.9229854862826969","host":"s3://npm-registry-packages-npm-production"}},"0.5.4":{"_id":"@browserid-ng/mcp-auth@0.5.4","bugs":{"url":"https://github.com/vthunder/browserid-ng/issues"},"dist":{"shasum":"60461a0bc3334cb397f3184b82773a5f1ed340c8","tarball":"https://registry.npmjs.org/@browserid-ng/mcp-auth/-/mcp-auth-0.5.4.tgz","fileCount":4,"integrity":"sha512-LZ5N+E+Q0NKHbCCHphnPr3OnGnVSFIzT9ypHan8/8vFqDd0CAtAb5KPpJQF0WCXwPsHQ6jpIAFVSHz/W4iWy8g==","signatures":[{"sig":"MEUCIQDjingD8/mF3x7f3VglRMt1fM0AfIm4eWZzFdwNw8ukJAIgGOEbeXvlK7CpVFwR2WV+eY7tP95QSCGrIcOeWHD2KSA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC6iZSOjmx72m3M/+iEmLKDYbUKB2h+kGzs0gjuz59l0AIhAN/BO+n+lP/wmiNH6USmgGOJKUj/Lkie1vkRe8yI++BD"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@browserid-ng%2fmcp-auth@0.5.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":77405},"main":"./index.mjs","name":"@browserid-ng/mcp-auth","type":"module","types":"./index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./index.d.ts","import":"./index.mjs"}},"gitHead":"78eb8a01e672003fe71e325dc71cb0013aee55bb","license":"MPL-2.0","scripts":{"test":"node --test test/*.test.mjs"},"version":"0.5.4","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:155e7445-8707-4067-8d26-c67816d1b2e6"}},"homepage":"https://github.com/vthunder/browserid-ng#readme","keywords":["browserid","mcp","oauth","agent","warrant","authorization","rfc7521"],"repository":{"url":"git+https://github.com/vthunder/browserid-ng.git","type":"git"},"_npmVersion":"12.0.2","description":"Warrant-gated MCP tools over MCP's own OAuth 2.1: a BrowserID 7521 assertion-grant AS + fail-closed per-call revocation checks. Every tool call attributable to 'agent X on behalf of human Y', revocable in one click.","directories":{},"maintainers":[{"name":"thunder","email":"code@sandmill.org"}],"_nodeVersion":"24.20.0","dependencies":{"@browserid-ng/agent":"^0.4.1"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mcp-auth_0.5.4_1789137567353_0.49512934242042284"}}},"time":{"created":"2026-08-12T06:47:38.583Z","modified":"2026-09-11T14:39:27.796Z","0.1.0":"2026-08-12T06:47:38.900Z","0.2.0":"2026-08-12T15:01:42.354Z","0.4.0":"2026-08-14T19:49:15.724Z","0.5.0":"2026-08-15T05:03:22.657Z","0.5.1":"2026-08-15T17:17:39.894Z","0.5.2":"2026-08-26T06:54:28.146Z","0.5.3":"2026-08-26T07:50:34.271Z","0.5.4":"2026-09-11T14:39:27.432Z"},"bugs":{"url":"https://github.com/vthunder/browserid-ng/issues"},"license":"MPL-2.0","homepage":"https://github.com/vthunder/browserid-ng#readme","keywords":["browserid","mcp","oauth","agent","warrant","authorization","rfc7521"],"repository":{"url":"git+https://github.com/vthunder/browserid-ng.git","type":"git"},"description":"Warrant-gated MCP tools over MCP's own OAuth 2.1: a BrowserID 7521 assertion-grant AS + fail-closed per-call revocation checks. Every tool call attributable to 'agent X on behalf of human Y', revocable in one click.","maintainers":[{"name":"thunder","email":"code@sandmill.org"}],"readme":"# @browserid-ng/mcp-auth\n\nWarrant-gated MCP tools over MCP's own OAuth 2.1 — **no API keys**. An agent\nredeems its human's short-lived, scoped, **revocable** BrowserID warrant for a\nbearer, and every tool call is attributable to \"agent X on behalf of human Y\".\nRevoke at `browserid.me/account` and the agent dies on its next call.\n\nHosts run their existing MCP OAuth client unmodified and never learn BrowserID\nexists: the middleware embeds a tiny **authorization server** whose only grant\ntype is the RFC 7521 `jwt-bearer` assertion grant (a BrowserID warrant\npresentation in, a bearer out), plus a **resource-server** guard that\nre-checks the warrant's revocation status **fail-closed on every call**.\n\nVerification is delegated to the broker's DNSSEC-rooted hosted verifier\n(`POST /verify`) and `POST /status/check` — no crypto in JS.\n\n## Warrant-gated tools in ten lines\n\n```js\nimport { createMcpAuth, McpAuthError } from \"@browserid-ng/mcp-auth\";\n\nconst auth = createMcpAuth({\n  resource: \"https://mcp.example.com\",          // this server (OAuth resource + audience)\n  scopesForTool: { create_issue: [\"issues:create\"] },\n});\n\n// 1. Serve discovery so the host's OAuth client finds the AS:\n//    GET /.well-known/oauth-protected-resource   -> auth.protectedResourceMetadata()\n//    GET /.well-known/oauth-authorization-server  -> auth.authorizationServerMetadata()\n\n// 2. The token endpoint (the embedded AS):\n//    POST /token  ->  auth.handleToken(body)     // body = {grant_type, assertion, scope?}\n\n// 3. Gate a tool call (per-call fail-closed status re-check + scope enforcement):\nconst ctx = await auth.requireWarrant(req.headers.authorization, \"create_issue\");\n// ctx = { grantor, grantee, holder, issuer, scopes } — attribute every action.\n```\n\nOn any failure `requireWarrant` / `handleToken` throw `McpAuthError` with an\n`oauthError` code and `httpStatus`; render `err.toTokenErrorResponse()` at the\ntoken endpoint and `auth.challenge()` in the `WWW-Authenticate` header on a 401.\n\n## Why this shape\n\n- **Revocation stays at the registrar.** Any number of independent MCP-server\n  ASes share ONE revocation surface — the human's `browserid.me/account`.\n  Every AS consults the same status list and fails closed.\n- **Attribution is intrinsic.** `ctx.grantor` (human) and `ctx.grantee` (agent)\n  come from the warrant, so tools log who really acted.\n- **Least privilege.** The warrant's scopes are the ceiling; a requested\n  `scope` may narrow but never widen them, and each tool declares what it needs.\n\nSee `mcp-demo/` for a runnable reference server, and\n`docs/plans/2026-08-10-mcp-auth-flight-build-spec.md` for the design.\n\n## The authorization-code lane (Lane B, optional)\n\nEverything above is the **assertion lane**: an agent that already has a\nBrowserID wallet POSTs its warrant presentation to `/token`. A *generic* OAuth\nhost (claude.ai, Cursor, a phone) has no wallet — it expects the ordinary\nredirect dance: discover → register → open `/authorize` in a browser →\napprove → code → token. `createAuthCodeLane` adds exactly that, WITHOUT\nchanging Lane A:\n\n```js\nimport { readFileSync } from \"node:fs\";\nimport { createMcpAuth, createAuthCodeLane } from \"@browserid-ng/mcp-auth\";\n\nconst auth = createMcpAuth({ resource, broker, scopesForTool });\n// The gateway's OWN agent identity (the wallet's ~/.browserid shape) —\n// provision one with `npx -y @browserid-ng/wallet provision <name>`.\nconst { credential } = JSON.parse(readFileSync(`${process.env.HOME}/.browserid/agent-credential.json`));\nconst lane = createAuthCodeLane({ mcpAuth: auth, credential, label: \"my gateway\" });\n\n// Serve (any framework; every handler is transport-agnostic):\n//   GET  /.well-known/oauth-authorization-server -> lane.authorizationServerMetadata()\n//   GET  /.well-known/oauth-protected-resource   -> auth.protectedResourceMetadata()\n//   POST /register          -> lane.handleRegister(jsonBody)                     // RFC 7591 DCR\n//   GET  /authorize         -> { redirect } = await lane.handleAuthorize(query);  // 302 redirect\n//   GET  /authorize/return  -> { redirect } = await lane.handleAuthorizeReturn(query);\n//   POST /token             -> lane.handleToken(body)   // auth-code + jwt-bearer\n// Tool calls stay exactly as before: auth.requireWarrant(header, tool).\n```\n\nHow it works: `/authorize` (PKCE **S256 required**) raises a warrant request\nas the gateway agent — audience pinned to this `resource` — and sends the\nbrowser to the broker's consent page with an **origin-validated** `return_url`\nback to `/authorize/return`. The human approves (picking which of their\nidentities delegates); the browser bounces back; the lane picks up the\napproved `warrant~config_cert` and mints a **single-use, ~60s** OAuth code\nbound to `client_id` + `redirect_uri` + the PKCE challenge. `/token` verifies\nall of that, mints a presentation with the gateway's DeviceAgent, and feeds it\nthrough the SAME `/verify` + bearer mint as Lane A — so revocation and\nper-call fail-closed status checks are identical across both lanes.\n\n`@browserid-ng/agent` is loaded lazily — Lane-A-only users never pull it in.\n\n### curl walkthrough (local broker)\n\n```sh\n# 0. A broker on localhost:3000 with an account, and a provisioned gateway\n#    identity (approve the printed link in your browser):\nBROWSERID_BROKER=http://localhost:3000 npx -y @browserid-ng/wallet provision gate\n\n# 1. Run your lane-enabled server, e.g. resource http://localhost:8787, then:\ncurl -s http://localhost:8787/.well-known/oauth-authorization-server | jq\n\n# 2. Register a client (what an MCP host does automatically):\nCLIENT=$(curl -s -X POST http://localhost:8787/register \\\n  -H 'content-type: application/json' \\\n  -d '{\"redirect_uris\":[\"http://localhost:9999/cb\"],\"client_name\":\"curl\"}')\nCLIENT_ID=$(echo \"$CLIENT\" | jq -r .client_id)\n\n# 3. PKCE pair:\nVERIFIER=$(openssl rand -base64 48 | tr '+/' '-_' | tr -d '=\\n')\nCHALLENGE=$(printf %s \"$VERIFIER\" | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '=\\n')\n\n# 4. Authorize — follow the 302 to the consent page IN A BROWSER and approve;\n#    the browser lands back on /authorize/return and then on your\n#    redirect_uri with ?code=…&state=…:\nopen \"http://localhost:8787/authorize?response_type=code&client_id=$CLIENT_ID\\\n&redirect_uri=http://localhost:9999/cb&code_challenge=$CHALLENGE\\\n&code_challenge_method=S256&scope=notes:read&state=xyz\"\n\n# 5. Exchange the code (from the redirect) within its ~60s TTL:\ncurl -s -X POST http://localhost:8787/token \\\n  -d \"grant_type=authorization_code&code=$CODE&client_id=$CLIENT_ID\\\n&redirect_uri=http://localhost:9999/cb&code_verifier=$VERIFIER\" | jq\n# -> { \"access_token\": \"bat_…\", … } — the same bearer Lane A mints.\n\n# 6. Call a gated tool with it; revoke at the broker's /account page and the\n#    next call fails closed.\n```\n\n## API\n\n- `createMcpAuth(opts) -> auth` — `opts`: `resource` (required), `broker`\n  (default `https://browserid.me`), `scopesForTool`, `tokenTtlS` (3600),\n  `statusCacheS` (60), `acceptedFallbacks`, `store`, `fetch`.\n- `auth.handleToken(params)` — redeem a presentation for a bearer.\n- `auth.redeemPresentation(presentation, scope?)` — the shared verify+mint\n  both grants terminate in.\n- `auth.authenticate(header)` — validate a bearer, re-check status, return ctx.\n- `auth.requireWarrant(header, toolNameOrScopes)` — authenticate + enforce scopes.\n- `auth.protectedResourceMetadata()` / `auth.authorizationServerMetadata()`.\n- `auth.challenge()` — `WWW-Authenticate` value.\n- `createMemoryStore()` — the default bearer store (swap for Redis/db in prod).\n- `createAuthCodeLane({ mcpAuth, credential, broker?, fetch?, label?,\n  codeTtlS?, pendingTtlS? }) -> lane` — the optional authorization-code lane:\n  `lane.authorizationServerMetadata()`, `lane.handleRegister(body)`,\n  `lane.handleAuthorize(query)`, `lane.handleAuthorizeReturn(query)`,\n  `lane.handleToken(params)`.\n- `verifyPkceS256(verifier, challenge)` — RFC 7636 S256 check.\n\nMPL-2.0.\n","readmeFilename":"README.md"}