# How to Configure OAuth Authentication for External MCP Servers in MetaMCP

> Learn to configure OAuth authentication for external MCP servers in MetaMCP. Enable bearer token validation and maintain session state easily.

- Repository: [metatool-ai/metamcp](https://github.com/metatool-ai/metamcp)
- Tags: how-to-guide
- Published: 2026-03-07

---

**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`](https://github.com/metatool-ai/metamcp/blob/main/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`](https://github.com/metatool-ai/metamcp/blob/main/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`](https://github.com/metatool-ai/metamcp/blob/main/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.

```dotenv

# 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`](https://github.com/metatool-ai/metamcp/blob/main/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`](https://github.com/metatool-ai/metamcp/blob/main/apps/backend/src/trpc/endpoints.impl.ts) forwards this flag to the repository layer, which inserts it into the `enable_oauth` boolean column.

```typescript
// 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`](https://github.com/metatool-ai/metamcp/blob/main/apps/backend/src/middleware/api-key-oauth.middleware.ts) (lines 181-306). The middleware checks the endpoint's configuration and applies the following logic:

```typescript
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`](https://github.com/metatool-ai/metamcp/blob/main/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`](https://github.com/metatool-ai/metamcp/blob/main/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`](https://github.com/metatool-ai/metamcp/blob/main/packages/trpc/src/routers/frontend/oauth.ts) to manage authentication sessions.

### Retrieving Sessions

Call `GetOAuthSession` to fetch existing tokens for a specific endpoint UUID:

```typescript
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`:

```typescript
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` (or `enable_auth_oauth` in environment files) when creating an endpoint.
- **Middleware** at [`apps/backend/src/middleware/api-key-oauth.middleware.ts`](https://github.com/metatool-ai/metamcp/blob/main/apps/backend/src/middleware/api-key-oauth.middleware.ts) gates requests based on the `enable_oauth` flag and bearer token presence.
- **Data persistence** uses the `enable_oauth` column in [`apps/backend/src/db/schema.ts`](https://github.com/metatool-ai/metamcp/blob/main/apps/backend/src/db/schema.ts) and session data stored via [`packages/trpc/src/routers/frontend/oauth.ts`](https://github.com/metatool-ai/metamcp/blob/main/packages/trpc/src/routers/frontend/oauth.ts).
- **Client flow** involves calling `GetOAuthSession` to check status and `UpsertOAuthSession` to 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`](https://github.com/metatool-ai/metamcp/blob/main/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`](https://github.com/metatool-ai/metamcp/blob/main/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`](https://github.com/metatool-ai/metamcp/blob/main/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`](https://github.com/metatool-ai/metamcp/blob/main/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.