How OpenWork Implements MCP OAuth for Remote Servers: Enterprise Mock Server Deep Dive
OpenWork treats its Model Context Protocol (MCP) bridge as a standard OAuth 2.0 resource server, implementing the full authorization-code grant flow with dynamic client registration, synthetic JWT token issuance, and stateful scenario management for deterministic testing.
OpenWork's enterprise MCP mock server enables remote authentication for MCP-compatible clients through a standards-compliant OAuth 2.0 implementation. This architecture allows clients like Claude Desktop, HandsFree, or custom MCP tools to securely connect to remote OpenWork MCP servers using familiar OAuth patterns while maintaining isolated, reproducible test environments through synthetic client records and controlled token lifetimes.
The OAuth 2.0 Authorization Flow
OpenWork's MCP OAuth implementation follows the standard OAuth 2.0 authorization-code grant pattern across five distinct phases, from initial discovery to authenticated API calls. Each phase is handled by specific modules within the enterprise-mcp-mock-server package.
Discovery and Metadata Resolution
The authentication process begins with OAuth discovery. When an MCP client connects, it queries the /.well-known/openid-configuration endpoint to retrieve server metadata. According to the OpenWork source code in packages/enterprise-mcp-mock-server/src/protocol/oauth-handler.ts (lines 8-20), the server constructs this metadata dynamically, exposing critical fields including the issuer, authorization_endpoint, token_endpoint, and jwks_uri.
This discovery mechanism allows clients to automatically configure their OAuth endpoints without hardcoding URLs, ensuring compatibility with varying deployment configurations.
Dynamic Client Registration (DCR)
Before initiating authorization, MCP clients must register as OAuth clients. OpenWork supports Dynamic Client Registration (DCR), handled by the handleOAuthRequest function in the OAuth handler. During registration, the server generates a synthetic client ID and secret, storing these credentials along with allowed redirect URIs and permitted scopes in the server-side InstanceState.
As implemented in packages/enterprise-mcp-mock-server/src/runtime/instance-state.ts (lines 15-30), this in-memory store maintains the OAuthClientRecord map that tracks all registered clients and their associated tokens throughout the scenario lifecycle.
Authorization Request Validation
When a client redirects users to the /oauth/authorize endpoint, OpenWork performs rigorous validation before issuing authorization codes. The handleOAuthRequest function verifies three critical constraints:
- The client ID exists in the
InstanceStateregistry - The redirect URI appears on the allow-list, validated via
isSafeOAuthRedirectUriinsrc/contracts/oauth.ts - The requested scopes are non-empty subsets of the scenario's allowed scopes
If validation fails, the server invokes sendOAuthError (lines 72-94 and 272-278 in oauth-handler.ts) to return standard OAuth error responses such as invalid_request or invalid_client, ensuring clients receive predictable error handling per RFC 6749.
Token Exchange and Synthetic JWTs
After obtaining an authorization code, clients POST to /oauth/token to exchange it for an access token. The token exchange handler validates the client secret, grant type (authorization_code or refresh_token), and requested scopes. Upon successful validation, the server issues a synthetic JWT-like token that encodes the client identity and scenario context.
As documented in the source around lines 333-355 of oauth-handler.ts, these tokens are stored in the OAuthClientRecord map for subsequent verification. Unlike standard JWTs backed by public key infrastructure, OpenWork's synthetic tokens are self-contained within the mock server's state, enabling deterministic revocation and inspection during testing.
Authenticated MCP Calls
Subsequent MCP protocol calls include the bearer token in the Authorization: Bearer <token> header. The MCP protocol handler in packages/enterprise-mcp-mock-server/src/protocol/mcp-handler.ts extracts this token, verifies it against the stored OAuthClientRecord, and injects the authenticated identity into the MCP request context.
If the token is missing, malformed, or expired, the handler invokes emitFault (line 100 in mcp-handler.ts) to return an HTTP 401 response with an OAuth-specific error payload, halting the MCP request pipeline.
Scenario Management and Fault Injection
Beyond standard OAuth compliance, OpenWork's implementation includes sophisticated test orchestration capabilities that control OAuth state across scenario boundaries.
Continuity and Preservation
The mock server supports scenario restart scenarios through assertCompatibleOAuthContinuity in packages/enterprise-mcp-mock-server/src/runtime/mock-server.ts (lines 288-315). When a scenario restarts with the preserve-compatible-oauth flag, this logic ensures that synthetic client records and token URLs remain stable, maintaining authenticated sessions across test phases. Without this flag, the server clears all OAuth authority records to ensure clean state for new test runs.
Fault Simulation for Resilience Testing
OpenWork can simulate various OAuth fault conditions, including reject-client and invalid_scope scenarios, to test client resilience. These fault injections occur at each OAuth phase, allowing developers to verify that their MCP clients handle authorization failures, expired tokens, and scope violations gracefully without crashing or entering undefined states.
Practical Implementation: MCP Client OAuth Flow
The following TypeScript example demonstrates how a custom MCP client interacts with OpenWork's OAuth implementation, from discovery to authenticated tool calls:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const transport = new StdioClientTransport({
command: "npx",
args: ["-y", "openwork-mcp-server"],
});
const client = new Client({ name: "my-app", version: "1.0.0" });
await client.connect(transport);
// 1️⃣ Discover OAuth metadata
const discovery = await client.callTool({
name: "oauth_discover",
arguments: {}
});
const { issuer, authorization_endpoint, token_endpoint } = discovery;
// 2️⃣ Register a dynamic client
const registration = await client.callTool({
name: "oauth_register_client",
arguments: {
redirect_uris: ["http://localhost/callback"],
scopes: ["read", "write"]
}
});
const { client_id, client_secret } = registration;
// 3️⃣ Perform authorization request (headless simulation)
const auth = await client.callTool({
name: "oauth_authorize",
arguments: {
client_id,
redirect_uri: "http://localhost/callback",
scope: "read write",
response_type: "code"
}
});
const { code } = auth;
// 4️⃣ Exchange the code for an access token
const token = await client.callTool({
name: "oauth_token",
arguments: {
client_id,
client_secret,
grant_type: "authorization_code",
code,
redirect_uri: "http://localhost/callback"
}
});
const accessToken = token.access_token;
// 5️⃣ Use the token for subsequent MCP calls
await client.callTool({
name: "ui_snapshot",
arguments: {},
headers: { Authorization: `Bearer ${accessToken}` }
});
This implementation showcases the complete flow: discovery via /.well-known/openid-configuration, dynamic registration through oauth_register_client, authorization handling, token exchange, and finally authenticated MCP tool invocation using the Authorization header.
Summary
- OpenWork implements MCP OAuth as a standards-compliant OAuth 2.0 resource server, supporting the full authorization-code grant flow for remote MCP clients.
- Dynamic Client Registration stores synthetic client credentials in
InstanceState, enabling temporary but trackable OAuth identities for testing. - The
handleOAuthRequestfunction enforces strict validation of redirect URIs viaisSafeOAuthRedirectUriand scopes before issuing authorization codes. - Synthetic JWT-like tokens are issued and stored in
OAuthClientRecord, verified by the MCP handler inmcp-handler.tsfor every authenticated request. - Scenario continuity is managed through
assertCompatibleOAuthContinuity, which preserves or clears OAuth state across test restarts based on configuration flags. - Fault injection capabilities allow simulation of OAuth errors like
invalid_clientandinvalid_scopeto test client resilience.
Frequently Asked Questions
What is MCP OAuth and why does OpenWork use it?
MCP OAuth is the application of OAuth 2.0 authentication to the Model Context Protocol, allowing AI assistants and tools to securely access remote resources on behalf of users. OpenWork uses this standard to ensure that any MCP-compatible client—from Claude Desktop to custom automation scripts—can authenticate to remote OpenWork servers using established security patterns rather than custom authentication schemes, reducing integration friction and improving security auditability.
How does OpenWork handle OAuth client registration without a persistent database?
OpenWork uses Dynamic Client Registration (DCR) with an in-memory store called InstanceState (src/runtime/instance-state.ts). When an MCP client registers, OpenWork generates synthetic client IDs and secrets, storing them in the OAuthClientRecord map alongside allowed redirect URIs and scopes. Because this state exists only in memory, it provides deterministic, isolated test environments where OAuth clients can be created and destroyed instantly without database overhead or persistent security credentials.
What happens when an OAuth token expires during an MCP session?
When the MCP handler in src/protocol/mcp-handler.ts receives a request with an expired or invalid bearer token, it invokes emitFault (line 100) to return an HTTP 401 Unauthorized response containing an OAuth-specific error payload. The client must then re-authenticate by initiating a new authorization code flow or using a refresh token if the original token exchange included one. This behavior mirrors standard OAuth 2.0 resource server semantics, ensuring clients handle authentication failures predictably.
How does OpenWork maintain OAuth continuity across test scenario restarts?
The assertCompatibleOAuthContinuity function in src/runtime/mock-server.ts (lines 288-315) evaluates whether to preserve or clear OAuth state when scenarios restart. If the configuration specifies preserve-compatible-oauth, the function maintains existing synthetic client records and token validation URLs, allowing authenticated sessions to persist across test phases. Otherwise, it clears all OAuth authority records, ensuring each test run begins with a clean authentication state.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →