Context Propagation Flow for workers-oauth-provider in MCP Tool Calls: How OrgScope Affects Authorization

The workers-oauth-provider injects OAuth grant properties into HTTP requests, which the Open-SEO server merges into a ToolContext via createMcpToolContext; the orgScope flag then determines whether the credential is locked to a specific organization ("pinned") or is a portable user-scoped key ("user"), directly controlling role inference and cross-organizational access boundaries.

The every-app/open-seo repository implements the Model-Code-Protocol (MCP) using Cloudflare's workers-oauth-provider to secure tool invocations with OAuth 2.0 grants. Understanding the exact context propagation flow from initial consent through to tool execution—and specifically how the orgScope parameter governs authorization decisions—is essential for developers building secure, multi-tenant MCP integrations.

Step-by-Step Context Propagation Through workers-oauth-provider

The MCP integration follows a strict seven-phase pipeline to move identity data from the OAuth provider into the executing tool's authorization context.

When a user authorizes an MCP tool, the consent handler constructs MCP-specific identity data by invoking createWorkersOAuthMcpProps in src/server/mcp/oauth-provider.ts (lines 36-55). This function packages the userId, userEmail, organizationId, and requested scopes into a metadata object that will accompany the grant.

Phase 2: Token Creation and Metadata Embedding

The workers-oauth-provider stores the MCP properties directly inside the access-token grant. In oauth-provider.ts (lines 45-55), the code calls oauth.completeAuthorization, passing the props object alongside the clientId and mandatory MCP_SCOPE. The provider embeds these values into the token payload, ensuring they are cryptographically bound to the credential.

Phase 3: Request Validation and authInfo Population

Upon receiving a request at the /mcp/* endpoint, the workers-oauth-provider validates the Bearer token from the Authorization header. Internally, the provider extracts the embedded clientId and scopes, injecting them into request.http.authInfo. This step occurs entirely within the provider's internal validation logic before the request reaches the application layer.

Phase 4: ToolContext Assembly via createMcpToolContext

The MCP transport handler, handleAuthenticatedOpenSeoMcpRequest, retrieves the raw token properties from ctx.props and merges them with the authInfo provided by the provider. It then calls createMcpToolContext in src/server/mcp/context.ts (lines 84-119) to instantiate a stable ToolContext. This function reconciles the original grant metadata with the current request's authentication state, preparing the final values for orgScope, role, and clientId.

Phase 5: OrgScope and Role Resolution

Inside createMcpToolContext (lines 99-105), the logic branches based on the orgScope value:

  • If orgScope is "pinned" (the default), the token is organization-scoped. The function infers the role as "owner" without performing a membership database lookup, as the organization ID is immutable for this credential.
  • If orgScope is "user", the credential is user-scoped (such as an API key generated via src/server/mcp/api-key-auth.ts). The tool must resolve the target organization from the request parameters (e.g., a project ID), and the effective role is determined by the user's membership record in that specific organization.

Phase 6: Tool-Level Authorization Enforcement

Each MCP tool receives the assembled ToolContext through ctx.auth and enforces fine-grained permissions. For example, src/server/mcp/tools/list-projects.ts (lines 14-16) validates that auth.orgScope === "user" before proceeding, rejecting pinned tokens for operations that require cross-organizational flexibility. Tools acting on organization-wide resources (like billing) accept the pinned scope and rely on the pre-computed role.

Phase 7: Audit Logging

After successful authorization, the server records the resolved orgScope, clientId, and granted scopes for telemetry. The function recordMcpAuthorized is invoked in src/server/mcp/oauth-provider.ts (lines 56-58) to persist this audit trail.

How OrgScope Affects Authorization Boundaries

The orgScope property acts as a capability boundary flag within the ToolAuthContext, determining how the system resolves the organization context and user permissions.

Pinned Scope ("pinned")

When orgScope is pinned, the OAuth token is cryptographically bound to a specific organizationId encoded at issuance. The authorization system treats this as a single-tenant credential:

  • Role Inference: Defaults to "owner" unless explicitly overridden in the token, avoiding additional database queries.
  • Usage Constraint: The token cannot be used to act upon other organizations, effectively sandboxing the credential to the tenant specified during the OAuth consent flow.

User Scope ("user")

When orgScope is set to "user", the credential represents a portable identity (such as an API key) not locked to a specific organization:

  • Role Resolution: Requires a live lookup of the user's membership in the target organization, returning "owner", "admin", or "member" based on the database record.
  • Cross-Org Capability: A single user-scoped credential can operate across any organization the user belongs to, enabling tools to dynamically infer the organization from request parameters like projectId.

Implementing Context Propagation in Open-SEO

The following patterns from the every-app/open-seo codebase demonstrate how to work with the propagation flow and orgScope validation.

When handling OAuth callbacks, construct the MCP property bundle using createWorkersOAuthMcpProps:

// src/server/mcp/oauth-provider.ts (lines 36-55)
import { createWorkersOAuthMcpProps } from "@/server/mcp/context";

const mcpProps = createWorkersOAuthMcpProps({
  userId: ctx.userId,
  userEmail: ctx.userEmail,
  organizationId: ctx.organizationId,
  baseUrl: getHostedBaseUrl(),
  clientId: authRequest.clientId,
  scopes, // ["mcp", "read:projects", …]
});

// Pass to workers-oauth-provider for token embedding
await oauth.completeAuthorization({
  request: authRequest,
  userId: ctx.userId,
  metadata: { clientId: authRequest.clientId, organizationId: ctx.organizationId },
  scope: scopes,
  props: mcpProps,
});

Re-assembling Context on Tool Invocation

When a request reaches the MCP endpoint, reconstruct the authorization context:

// src/server/mcp/context.ts (lines 84-119)
import { createMcpToolContext } from "@/server/mcp/context";

export async function handleMcpRequest(
  serverCtx: ServerContext,
  mcpProps: McpProps,
) {
  // serverCtx.http.authInfo is populated by workers-oauth-provider
  const toolCtx = createMcpToolContext(serverCtx, mcpProps);

  // toolCtx.auth contains: userId, userEmail, organizationId, 
  // role, orgScope ("pinned" | "user"), clientId, scopes, baseUrl
  return someMcpTool(toolCtx);
}

Enforcing OrgScope in Tool Implementations

Validate the authorization scope before executing business logic:

// src/server/mcp/tools/list-projects.ts (lines 14-16)
export async function listProjects(toolCtx: ToolContext) {
  const { auth } = toolCtx;

  // Require user-scoped credentials for organization-spanning operations
  if (auth.orgScope !== "user") {
    throw new Error("list-projects requires a user-scoped credential");
  }

  // Check resolved role for fine-grained permissions
  if (!["owner", "admin"].includes(auth.role)) {
    throw new Error("Insufficient permissions");
  }

  // Proceed with project listing...
}

Summary

  • The workers-oauth-provider manages the OAuth flow, embedding MCP metadata into tokens via createWorkersOAuthMcpProps in src/server/mcp/oauth-provider.ts.
  • During request processing, the provider populates request.http.authInfo, which the application merges with stored props using createMcpToolContext in src/server/mcp/context.ts.
  • orgScope defaults to "pinned", locking the credential to a single organization and defaulting the role to "owner".
  • orgScope: "user" creates portable credentials where the organization and role are resolved dynamically from request parameters and membership lookups.
  • Tools enforce boundaries by checking auth.orgScope and auth.role before accessing resources, as demonstrated in tools like src/server/mcp/tools/list-projects.ts.

Frequently Asked Questions

What is the default orgScope value in workers-oauth-provider MCP tokens?

The default value is "pinned". According to the implementation in src/server/mcp/context.ts (lines 99-105), when a token does not explicitly specify an organization scope, the system assumes a pinned organization context, which binds the credential to the organizationId encoded during the OAuth consent flow.

How does the application determine the user's role when using a user-scoped credential?

When orgScope is set to "user", the application cannot infer the role from the token alone. Instead, the specific tool implementation must identify the target organization from request parameters (such as a project ID) and then query the user's membership record in that organization to determine whether they hold the "owner", "admin", or "member" role.

Can a pinned-scope token be used to access resources across multiple organizations?

No. A pinned-scope token is cryptographically bound to a specific organizationId established during the initial OAuth authorization. The createMcpToolContext function treats this organization as immutable, and tools that require cross-organizational access (such as list-projects) explicitly reject pinned tokens by validating auth.orgScope !== "user".

Where does workers-oauth-provider store the MCP metadata during the token lifecycle?

The provider stores the metadata inside the access token grant itself. During the consent phase in src/server/mcp/oauth-provider.ts (lines 45-55), the mcpProps object is passed to oauth.completeAuthorization and embedded into the token payload. When the token is later presented, the provider extracts these properties into request.http.authInfo for the application to consume.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →