How to Configure OAuth Authentication for External MCP Servers in MetaMCP
MetaMCP enables OAuth 2.1 authentication for external MCP servers by setting the enable_oauth flag during endpoint creation, which triggers bearer token validation in the api-key-oauth.middleware.ts while maintaining session state via the OAuth router.
MetaMCP acts as a unified gateway that exposes external MCP servers (STDIO, HTTP, or SSE) through managed endpoints with configurable security policies. When you configure OAuth authentication for external MCP servers in MetaMCP, the platform implements the MCP Specification 2025-06-18 flow, storing encrypted session data in the database and validating access tokens on every request.
Understanding MetaMCP Authentication Modes
MetaMCP endpoints support three distinct authentication strategies controlled by boolean flags in the database schema:
- API-Key: Simple token passed in headers or query strings. Controlled by
enable_api_key_auth. - OAuth 2.1: Full authorization code flow with refresh tokens. Controlled by
enable_oauth. - Both: Accepts either authentication method; API-Key takes precedence if present.
The flags are defined in packages/zod-types/src/endpoints.zod.ts and persist in the enable_oauth column of the endpoints table defined in apps/backend/src/db/schema.ts.
Enabling OAuth for Your Endpoint
Via Bootstrap Configuration
For infrastructure-as-code deployments, set OAuth during initialization using the BOOTSTRAP_ENDPOINTS environment variable. Note that the environment file uses the legacy key enable_auth_oauth, which BootstrapService maps to the internal enable_oauth column.
# example.env
BOOTSTRAP_ENDPOINTS=[
{
"name": "SecureMCPEndpoint",
"description": "External server with OAuth protection",
"enable_auth": false,
"enable_auth_query": false,
"enable_auth_oauth": true,
"is_public": true,
"update": true
}
]
The mapping logic resides in apps/backend/src/lib/bootstrap.service.ts at line 770, where the service normalizes the legacy environment variable names before persisting to the database.
Via TRPC API
For dynamic endpoint creation, pass enable_oauth: true to the createEndpoint mutation. The implementation in apps/backend/src/trpc/endpoints.impl.ts forwards this flag to the repository layer, which inserts it into the enable_oauth boolean column.
// Client-side TRPC call
await trpc.endpoints.createEndpoint.mutate({
name: 'SecureMCPEndpoint',
description: 'OAuth-protected external MCP',
enable_oauth: true, // Activates OAuth flow
enable_api_key_auth: false, // Optional: disable API-Key for OAuth-only
});
How OAuth Authentication Works in MetaMCP
The authentication flow involves three core components: the middleware gate, the database schema, and the OAuth router.
Middleware Decision Logic
Incoming requests first hit apps/backend/src/middleware/api-key-oauth.middleware.ts (lines 181-306). The middleware checks the endpoint's configuration and applies the following logic:
if (!endpoint.enable_api_key_auth && !endpoint.enable_oauth) {
// Open endpoint - no authentication required
} else if (endpoint.enable_api_key_auth && !endpoint.enable_oauth) {
// API-Key only - validate X-API-Key header
} else if (endpoint.enable_api_key_auth && endpoint.enable_oauth) {
// Dual mode - prefer API-Key if present, fallback to OAuth bearer
} else {
// OAuth-only - require Authorization: Bearer <token>
}
When enable_oauth is true but no valid bearer token is present, the middleware returns 401 Unauthorized with the message "Authentication required via OAuth bearer token or API key".
Database Schema
The enable_oauth flag persists in the endpoints table defined in apps/backend/src/db/schema.ts at line 254 as a boolean column defaulting to false. Additional OAuth data models in packages/zod-types/src/oauth.zod.ts define the OAuthSessionSchema (lines 102-108), which stores client credentials, access tokens, refresh tokens, and expiration timestamps.
Managing OAuth Sessions
Once OAuth is enabled, clients interact with the OAuth router at packages/trpc/src/routers/frontend/oauth.ts to manage authentication sessions.
Retrieving Sessions
Call GetOAuthSession to fetch existing tokens for a specific endpoint UUID:
const session = await trpc.oauth.getOAuthSession.query({
endpointUuid: 'uuid-here'
});
Storing Refreshed Tokens
After completing the OAuth authorization code exchange, persist the tokens using UpsertOAuthSession:
await trpc.oauth.upsertOAuthSession.mutate({
endpointUuid: 'uuid-here',
client_information: {
client_id: 'your-client-id',
client_secret: 'your-client-secret'
},
tokens: {
access_token: 'eyJhbG...',
refresh_token: 'dGhpcyBpcyBh...',
expires_at: 1234567890
}
});
The router validates tokens against OAuthSessionSchema on every request. If validation fails, it returns 401 Unauthorized with "The provided OAuth token is invalid or has expired."
Summary
- Enable OAuth by setting
enable_oauth: true(orenable_auth_oauthin environment files) when creating an endpoint. - Middleware at
apps/backend/src/middleware/api-key-oauth.middleware.tsgates requests based on theenable_oauthflag and bearer token presence. - Data persistence uses the
enable_oauthcolumn inapps/backend/src/db/schema.tsand session data stored viapackages/trpc/src/routers/frontend/oauth.ts. - Client flow involves calling
GetOAuthSessionto check status andUpsertOAuthSessionto store tokens after the authorization code exchange. - Error handling returns specific 401 messages for missing tokens or expired sessions.
Frequently Asked Questions
What is the difference between enable_oauth and enable_auth_oauth?
enable_auth_oauth is the legacy environment variable name used in BOOTSTRAP_ENDPOINTS configuration files, while enable_oauth is the canonical column name in the database and TRPC API. The BootstrapService in apps/backend/src/lib/bootstrap.service.ts automatically maps the legacy name to the modern column name during initialization.
Can I use both API-Key and OAuth on the same endpoint?
Yes. When both enable_api_key_auth and enable_oauth are true, the middleware in apps/backend/src/middleware/api-key-oauth.middleware.ts accepts either method. If the request includes a valid API-Key, authentication succeeds immediately; otherwise, the middleware checks for an OAuth bearer token.
Where are OAuth tokens stored in MetaMCP?
MetaMCP stores OAuth session data—including access tokens, refresh tokens, and client credentials—in the database using the schema defined in packages/zod-types/src/oauth.zod.ts. The OAuthSessionSchema validates the shape of this data, while the actual persistence logic resides in the OAuth router at packages/trpc/src/routers/frontend/oauth.ts.
What happens if an OAuth token expires?
When the middleware detects an expired or invalid token, it returns a 401 Unauthorized response with the message "The provided OAuth token is invalid or has expired." The client must then initiate a new OAuth flow or use a stored refresh token to call UpsertOAuthSession and obtain new credentials via the TRPC OAuth router.
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 →