mcp-authz 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -254,6 +254,11 @@ provider-specific assurance claim, or explicitly set `requireEmailVerified:
254
254
  false` only when the issuer contract guarantees the email another way. For
255
255
  opaque tokens, pass any SDK-compatible `tokenVerifier` plus `identityFromAuth`.
256
256
 
257
+ An agent that signs in as itself, with an OAuth client-credentials token,
258
+ carries a `sub` and no email. Set `verifier.requireEmail: false` to accept it,
259
+ and name its `sub` in a rule to grant it roles. Email and domain rules never
260
+ match it and `allowedDomain` refuses it, but a rule with no `match` admits it.
261
+
257
262
  ### `definePolicy(spec)`
258
263
 
259
264
  ```ts
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { _ as Identity, a as PermissionCatalog, c as PolicySpec, d as createPrincipal, f as definePermissions, g as DenialReason, h as AccessDeniedError, i as MatchedRule, l as Principal, m as reconcile, n as Explanation, o as PermissionOf, p as definePolicy, r as Match, s as Policy, u as Rule } from "./policy-DuZbwrKf.js";
2
2
  import { a as AuditDeliveryFailure, c as AuditSink, d as PromptConfig, f as ResourceConfig, h as authz, i as ApprovalSink, l as Capability, m as ToolConfig, n as ApprovalRefusedError, o as AuditErrorSink, p as ServerOptions, r as ApprovalRequest, s as AuditEvent, t as ApprovalDecision, u as Definition } from "./tools-BQE1O-7P.js";
3
- import { a as AuthorizationDecisionSink, i as AuthorizationDecisionEvent, n as identityFromAuth, r as jwksVerifier, t as VerifierOptions } from "./verifier-rQlS6rYR.js";
3
+ import { a as AuthorizationDecisionSink, i as AuthorizationDecisionEvent, n as identityFromAuth, r as jwksVerifier, t as VerifierOptions } from "./verifier-DF6gUMQ6.js";
4
4
  import { a as scopesForCapability, i as decodeMcpNameHeader, n as ScopeRequirement, o as scopesFromMcpHeaders, r as ToolScopeMap, s as TrustedMcpRoute, t as CapabilityScopeMap } from "./ladder-D18eJ7tD.js";
5
5
  import { AuthInfo, McpServer, OAuthMetadata, OAuthTokenVerifier } from "@modelcontextprotocol/server";
6
6
  //#region src/discovery.d.ts
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { a as policyDenied, i as emitDecision, n as jwksVerifier, o as principalLabel, r as verifierFor, s as AccessDeniedError, t as identityFromAuth } from "./verifier-BruW4XGR.js";
2
- import { d as scopesForCapability, f as scopesFromMcpHeaders, i as runScopedGate, r as permissionForRoute, u as decodeMcpNameHeader } from "./ladder-BhG1S7nd.js";
1
+ import { a as policyDenied, i as emitDecision, n as jwksVerifier, o as principalLabel, r as verifierFor, s as AccessDeniedError, t as identityFromAuth } from "./verifier-D6VIAYuT.js";
2
+ import { d as scopesForCapability, f as scopesFromMcpHeaders, i as runScopedGate, r as permissionForRoute, u as decodeMcpNameHeader } from "./ladder-CUzOKudC.js";
3
3
  import { i as reconcile, n as definePermissions, r as definePolicy, t as createPrincipal } from "./policy-BBp3Jq6G.js";
4
4
  import { McpServer, UriTemplate, createMcpHandler, getOAuthProtectedResourceMetadataUrl, oauthMetadataResponse, requireBearerAuth } from "@modelcontextprotocol/server";
5
5
  //#region src/discovery.ts
@@ -1,4 +1,4 @@
1
- import { a as policyDenied, i as emitDecision, o as principalLabel, s as AccessDeniedError } from "./verifier-BruW4XGR.js";
1
+ import { a as policyDenied, i as emitDecision, o as principalLabel, s as AccessDeniedError } from "./verifier-D6VIAYuT.js";
2
2
  import { OAuthError, OAuthErrorCode, bearerAuthChallengeResponse, classifyInboundRequest, isJsonContentType } from "@modelcontextprotocol/server";
3
3
  //#region src/scopes.ts
4
4
  const BASE64_SENTINEL = /^=\?base64\?([A-Za-z0-9+/]*(?:={0,2}))\?=$/;
package/dist/openapi.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { _ as Identity, l as Principal, s as Policy } from "./policy-DuZbwrKf.js";
2
2
  import { c as AuditSink, o as AuditErrorSink } from "./tools-BQE1O-7P.js";
3
- import { a as AuthorizationDecisionSink, t as VerifierOptions } from "./verifier-rQlS6rYR.js";
3
+ import { a as AuthorizationDecisionSink, t as VerifierOptions } from "./verifier-DF6gUMQ6.js";
4
4
  import { t as PermissionMapRecord } from "./permissions-module-DxCHuE-N.js";
5
5
  import { AuthInfo, OAuthMetadata, OAuthTokenVerifier } from "@modelcontextprotocol/server";
6
6
  //#region src/openapi.d.ts
package/dist/openapi.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as policyDenied, i as emitDecision, o as principalLabel, r as verifierFor, s as AccessDeniedError } from "./verifier-BruW4XGR.js";
1
+ import { a as policyDenied, i as emitDecision, o as principalLabel, r as verifierFor, s as AccessDeniedError } from "./verifier-D6VIAYuT.js";
2
2
  import { i as reconcile } from "./policy-BBp3Jq6G.js";
3
3
  import { getOAuthProtectedResourceMetadataUrl, oauthMetadataResponse, requireBearerAuth } from "@modelcontextprotocol/server";
4
4
  //#region src/openapi.ts
package/dist/proxy.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { _ as Identity, s as Policy } from "./policy-DuZbwrKf.js";
2
- import { a as AuthorizationDecisionSink, t as VerifierOptions } from "./verifier-rQlS6rYR.js";
2
+ import { a as AuthorizationDecisionSink, t as VerifierOptions } from "./verifier-DF6gUMQ6.js";
3
3
  import { t as CapabilityScopeMap } from "./ladder-D18eJ7tD.js";
4
4
  import { AuthInfo, OAuthMetadata, OAuthTokenVerifier } from "@modelcontextprotocol/server";
5
5
  //#region src/upstream.d.ts
package/dist/proxy.js CHANGED
@@ -1,5 +1,5 @@
1
- import { a as policyDenied, i as emitDecision, n as jwksVerifier, o as principalLabel, s as AccessDeniedError, t as identityFromAuth } from "./verifier-BruW4XGR.js";
2
- import { a as forwardToUpstream, c as isJsonResponse, d as scopesForCapability, i as runScopedGate, l as readCappedBody, n as permissionForFlatMap, o as headersForRewrittenBody, s as isEventStream, t as capabilityLabel } from "./ladder-BhG1S7nd.js";
1
+ import { a as policyDenied, i as emitDecision, n as jwksVerifier, o as principalLabel, s as AccessDeniedError, t as identityFromAuth } from "./verifier-D6VIAYuT.js";
2
+ import { a as forwardToUpstream, c as isJsonResponse, d as scopesForCapability, i as runScopedGate, l as readCappedBody, n as permissionForFlatMap, o as headersForRewrittenBody, s as isEventStream, t as capabilityLabel } from "./ladder-CUzOKudC.js";
3
3
  import { i as reconcile } from "./policy-BBp3Jq6G.js";
4
4
  import { UriTemplate, getOAuthProtectedResourceMetadataUrl, oauthMetadataResponse, requireBearerAuth } from "@modelcontextprotocol/server";
5
5
  import { err, isUnexpectedError, ok, run } from "awaitly";
@@ -69,9 +69,10 @@ function jwksVerifier(options) {
69
69
  throw invalidToken(`Token rejected: ${error instanceof Error ? error.message : String(error)}`);
70
70
  }
71
71
  if (typeof payload.exp !== "number") throw invalidToken("Token has no `exp` claim.");
72
- const email = payload[emailClaim];
73
- if (typeof email !== "string" || !email) throw invalidToken(`Token carries no '${emailClaim}' claim, so there is no identity to map.`);
74
- if ((options.requireEmailVerified ?? true) && payload[emailVerifiedClaim] !== true) throw invalidToken(`Token does not prove '${emailClaim}' with '${emailVerifiedClaim}: true'.`);
72
+ const claimed = payload[emailClaim];
73
+ const email = typeof claimed === "string" && claimed ? claimed : void 0;
74
+ if (!email && (options.requireEmail ?? true)) throw invalidToken(`Token carries no '${emailClaim}' claim, so there is no identity to map.`);
75
+ if (email && (options.requireEmailVerified ?? true) && payload[emailVerifiedClaim] !== true) throw invalidToken(`Token does not prove '${emailClaim}' with '${emailVerifiedClaim}: true'.`);
75
76
  const sub = payload.sub;
76
77
  if (typeof sub !== "string" || !sub) throw invalidToken("Token has no `sub` claim, so there is no stable subject to bind to.");
77
78
  const domain = typeof payload.hd === "string" ? payload.hd : void 0;
@@ -86,7 +87,7 @@ function jwksVerifier(options) {
86
87
  issuer: options.issuer,
87
88
  sub,
88
89
  email,
89
- emailVerified: options.requireEmailVerified === false || payload[emailVerifiedClaim] === true,
90
+ emailVerified: email !== void 0 && (options.requireEmailVerified === false || payload[emailVerifiedClaim] === true),
90
91
  domain,
91
92
  claims: payload
92
93
  }
@@ -62,6 +62,14 @@ type VerifierOptions = {
62
62
  emailVerifiedClaim?: string;
63
63
  /** Require an explicit `true` verified-email claim. Defaults to true. */
64
64
  requireEmailVerified?: boolean;
65
+ /**
66
+ * Require an email in every token. Defaults to true. Set false to accept an
67
+ * agent that signs in as itself, such as an OAuth client-credentials token:
68
+ * its identity is its `sub`, which a policy rule names. Email and domain
69
+ * rules never match it, and neither does `allowedDomain`; a rule with no
70
+ * `match` does. A token that does carry an email is checked as before.
71
+ */
72
+ requireEmail?: boolean;
65
73
  };
66
74
  /**
67
75
  * A JWKS-backed verifier. Keys are fetched once and cached by `jose`, which
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-authz",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Authorization for MCP servers: OAuth 2.1 resource server, roles/permissions policy, permission-gated tools (2026-07-28)",
5
5
  "repository": {
6
6
  "type": "git",