OAuth Flow for Connecting External MCP Servers to OpenWork: Complete Technical Guide
OpenWork implements a provider-neutral OAuth 2.0 flow using PKCE and loopback callbacks to securely authenticate external MCP servers, involving discovery, browser-based authorization, token exchange, and automatic refresh handling.
The different-ai/openwork repository contains a complete Enterprise MCP Client library that orchestrates the full OAuth lifecycle when users connect external Model Control Plane (MCP) servers. This implementation uses standard OAuth 2.0 with Proof Key for Code Exchange (PKCE) to ensure secure authentication without exposing client secrets, storing tokens in the OS keychain or encrypted vaults for subsequent JSON-RPC communications.
OAuth Flow Architecture Overview
The OpenWork OAuth implementation follows a four-phase architecture designed to handle the complete authentication lifecycle for external MCP servers. Each phase is implemented in the packages/enterprise-mcp-client directory and enforced by type definitions in the core types package.
Discovery Phase: Well-Known Endpoint Probing
When a user initiates a connection to an external MCP URL, the client first determines whether OAuth is required and discovers the authorization server metadata. According to packages/enterprise-mcp-client/README.md, the client performs provider-neutral discovery by fetching standard OAuth metadata endpoints.
The process begins with HTTP probing of the MCP URL, followed by requests to /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server to retrieve resource and authorization server descriptors. This discovery mechanism is documented in the Enterprise MCP Client README, which states that the package "owns the provider-neutral MCP/OAuth lifecycle."
The type system enforces this through packages/types/src/den/mcp-connection-action.ts, which defines the authType: "oauth" discriminator for MCP connections requiring OAuth authentication. This ensures that the client code can programmatically determine when to initiate the OAuth flow versus using API key authentication.
Authorization Phase: PKCE Browser Flow with Loopback Callback
Once discovery completes, OpenWork initiates the authorization phase using PKCE (Proof Key for Code Exchange) to prevent authorization code interception attacks. The implementation generates a cryptographically random PKCE verifier and challenge pair, then starts a temporary HTTP listener on a local loopback address.
As documented in packages/enterprise-mcp-client/README.md, the client opens the user's default browser to the authorization endpoint with the PKCE challenge and requested scopes as query parameters. The authorization server redirects back to http://127.0.0.1:<port>/callback after the user grants consent, allowing the client to capture the authorization code from the query string.
This behavior is validated by the mock server implementation in scripts/mock-oauth-mcp-server.mjs, which simulates the complete authorization endpoint behavior including scope validation and redirect handling. The mock server demonstrates the expected request/response pattern that production MCP servers must implement.
Token Exchange and Session Establishment
After receiving the authorization code via the loopback callback, the client exchanges it for access and refresh tokens. The client POSTs the authorization code along with the PKCE verifier to the token endpoint discovered during the initial phase.
According to packages/enterprise-mcp-client/SECURITY.md, the implementation validates the OAuth token wire value and absolute expiry timestamps before storage. Tokens are persisted securely using the OS keychain on desktop clients or encrypted vaults in cloud deployments.
Upon successful token acquisition, the client establishes an MCP session by creating a signed session ID. All subsequent JSON-RPC calls to the MCP server include the bearer token in the Authorization: Bearer <token> header, as enforced by the session management logic in the Enterprise MCP Client library.
Token Refresh and Revocation Handling
The OAuth flow includes automatic token refresh when access tokens expire. The client stores the refresh token alongside the access token and uses it to obtain new credentials without requiring user interaction.
The evals/voiceovers/mcp-oauth-refresh-lifecycle.md specification documents the complete refresh lifecycle, including serial refresh operations and handling of revoked sessions. If a refresh token is invalidated or revoked, the client forces a complete re-authentication by restarting the OAuth flow from the discovery phase.
Implementation Walkthrough: Connecting an External MCP Server
Implementing the OAuth flow in your OpenWork configuration requires defining the MCP connection in your configuration file and invoking the connection methods from the Enterprise MCP Client library.
Configuration Setup
Define your external MCP server in the OpenWork configuration with the OAuth trigger enabled:
// opencode.json configuration snippet
{
"mcp": {
"external-data-service": {
"type": "remote",
"enabled": true,
"url": "https://api.external-mcp-provider.com/mcp/agent",
"oauth": {}
}
}
}
The presence of the oauth property with an empty object triggers the OAuth discovery and authorization flow when the connection is initialized.
Initiating the Connection
Use the Enterprise MCP Client library to trigger the OAuth flow programmatically:
import { connectMcp } from "@openwork/enterprise-mcp-client";
// This triggers discovery, opens the browser for authorization,
// handles the loopback callback, and stores the tokens
await connectMcp("external-data-service");
The connectMcp function handles the entire lifecycle: probing the well-known endpoints, generating PKCE parameters, starting the local HTTP listener, opening the system browser, capturing the redirect, exchanging the code for tokens, and securely persisting the credentials.
Making Authenticated Requests
Once authenticated, all MCP method calls automatically include the bearer token:
import { callMcp } from "@openwork/enterprise-mcp-client";
const result = await callMcp("external-data-service", {
method: "tool.run",
params: {
name: "query-database",
arguments: { table: "users", limit: 100 }
}
});
The client automatically attaches the session ID and Authorization header using the stored access token.
Manual Token Refresh
While the client handles automatic refresh, you can manually trigger token refresh when needed:
import { refreshMcpToken } from "@openwork/enterprise-mcp-client";
// Forces a token refresh using the stored refresh token
await refreshMcpToken("external-data-service");
If the refresh token is expired or revoked, this function throws an authentication error that signals the need to restart the full OAuth flow.
Security Architecture and Token Storage
The security model for OpenWork's OAuth implementation emphasizes secure token storage and validation. As specified in packages/enterprise-mcp-client/SECURITY.md, the implementation validates both the OAuth token wire format and absolute expiry timestamps before accepting tokens.
Access tokens and refresh tokens are never stored in plain text. Desktop implementations use the operating system's native keychain (Keychain on macOS, Credential Manager on Windows, Secret Service on Linux), while server deployments use encrypted vaults with hardware security module (HSM) support where available.
The PKCE implementation prevents authorization code interception attacks by ensuring that only the client possessing the original code verifier can exchange the authorization code for tokens, even if the authorization code is intercepted during the browser redirect.
Testing with Mock OAuth Servers
The repository includes a complete mock OAuth MCP server for testing the flow without external dependencies. The scripts/mock-oauth-mcp-server.mjs file implements a fully functional OAuth authorization server that validates PKCE parameters and simulates token issuance.
This mock server enables integration testing of the complete flow, including error handling for expired tokens and revocation scenarios. The test specifications in evals/voiceovers/mcp-connect-reliability.md define UI-level acceptance criteria for the connection button and OAuth hand-off, ensuring consistent behavior across different MCP providers.
Summary
-
Discovery Mechanism: OpenWork probes
/.well-known/oauth-protected-resourceand/.well-known/oauth-authorization-serverendpoints to determine OAuth requirements and server metadata before initiating authentication. -
PKCE Security: The implementation uses Proof Key for Code Exchange with cryptographically random verifiers and local loopback callbacks to prevent authorization code interception and eliminate the need for client secrets.
-
Automatic Token Management: The client handles access token storage in OS keychains or encrypted vaults, automatically refreshes expired tokens, and forces re-authentication when refresh tokens are revoked.
-
Type Safety: The
packages/types/src/den/mcp-connection-action.tsfile enforces OAuth configuration through theauthType: "oauth"discriminator, ensuring compile-time validation of authentication requirements. -
Comprehensive Testing: The
scripts/mock-oauth-mcp-server.mjsprovides a complete test harness for validating OAuth implementations, while specification documents inevals/voiceovers/define expected behaviors for refresh lifecycles and connection reliability.
Frequently Asked Questions
What triggers the OAuth flow when adding an external MCP server in OpenWork?
The OAuth flow triggers automatically when the MCP configuration includes an oauth property or when the discovery phase detects that the server requires OAuth authentication. Specifically, the type definition in packages/types/src/den/mcp-connection-action.ts uses authType: "oauth" to identify connections requiring OAuth, and the connectMcp function in the Enterprise MCP Client library initiates the browser-based PKCE flow when this type is detected.
How does OpenWork securely store OAuth tokens after authentication?
OpenWork stores OAuth tokens using platform-specific secure storage mechanisms. On desktop clients, tokens are stored in the OS keychain (macOS Keychain, Windows Credential Manager, or Linux Secret Service). Cloud deployments use encrypted vaults with strict access controls. According to packages/enterprise-mcp-client/SECURITY.md, the implementation validates token wire formats and absolute expiry timestamps before storage and never persists tokens in plain text or browser local storage.
What happens when an OAuth access token expires during an active MCP session?
When an access token expires, the Enterprise MCP Client automatically uses the stored refresh token to obtain new credentials without user intervention. The evals/voiceovers/mcp-oauth-refresh-lifecycle.md specification validates this behavior, ensuring that serial refreshes work correctly. If the refresh token is also expired or revoked, the client clears the stored credentials and forces a complete re-authentication through the browser-based OAuth flow.
Can the OAuth flow be tested without connecting to a live external MCP server?
Yes, OpenWork provides scripts/mock-oauth-mcp-server.mjs, which implements a complete OAuth 2.0 authorization server including discovery endpoints, PKCE validation, and token issuance. This mock server allows developers to test the full connection flow, including error handling for invalid scopes and token revocation, without requiring live external credentials or network access to third-party providers.
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 →