# How OpenWork Implements MCP OAuth for Remote Servers: Enterprise Mock Server Deep Dive

> Learn how OpenWork implements MCP OAuth for remote servers using OAuth 2.0 resource server standards. Explore authorization-code grants dynamic client registration and JWT token issuance.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: deep-dive
- Published: 2026-08-16

---

**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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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 `InstanceState` registry
- The **redirect URI** appears on the allow-list, validated via `isSafeOAuthRedirectUri` in [`src/contracts/oauth.ts`](https://github.com/different-ai/openwork/blob/main/src/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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:

```typescript
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 `handleOAuthRequest` function enforces strict validation of redirect URIs via `isSafeOAuthRedirectUri` and scopes before issuing authorization codes.
- **Synthetic JWT-like tokens** are issued and stored in `OAuthClientRecord`, verified by the MCP handler in [`mcp-handler.ts`](https://github.com/different-ai/openwork/blob/main/mcp-handler.ts) for 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_client` and `invalid_scope` to 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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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`](https://github.com/different-ai/openwork/blob/main/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.