Skip to main content
This guide demonstrates how to build a production-ready MCP server with OAuth2 authentication using Okta as the identity provider. You’ll learn how to configure Okta, deploy an MCP server with OAuth validation, and integrate it with the TrueFoundry AI Gateway. This setup supports two authentication scenarios:
  • User Authentication: Authenticate specific users through the AI Gateway using the Authorization Code flow with refresh tokens
  • Machine-to-Machine Authentication: Enable programmatic access without user interaction using the Client Credentials grant flow
OAuth2 authentication ensures that users can only access resources they are authorized to use, making it ideal for production MCP servers that handle sensitive data or operations.

Prerequisites

Before you begin, ensure you have:
  • An Okta account with admin access
  • A TrueFoundry account with a workspace
  • Basic understanding of OAuth2 flows
  • Python 3.13 installed for local testing

1. Setup on Okta (User Authentication)

This section covers the setup for user authentication through the AI Gateway. The authorization server created here will also be used for machine-to-machine authentication (see Section 4).

Create an OAuth Application

1

Create an OAuth2 Application

Create the OAuth2 application that will authenticate users.
  1. Navigate to Applications > Applications in the Okta dashboard
  2. Click Create App Integration
  3. Select OIDC - OpenID Connect
  4. Select Web Application as the application type
  5. Configure the application:
    • App integration name: MCP Server Client
    • Grant type: Check:
      • Authorization Code (required for user authentication via Gateway)
      • Refresh Token (required to enable automatic token refresh)
    • Sign-in redirect URIs: Add https://<your-tfy-control-plane-url>/api/svc/v1/llm-gateway/mcp-servers/oauth2/callback
Authorization Code and Refresh Token are required for the Gateway OAuth integration to enable automatic token refresh. For machine-to-machine authentication, see Section 4.
  1. Click Save
  2. Note the Client ID and Client Secret from the application page
Keep your Client Secret secure. Never commit it to version control or expose it in client-side code.

Create an Authorization Server

1

Create an Authorization Server

An authorization server issues tokens that your MCP server will validate. This authorization server will be used for both user authentication (via the Gateway) and machine-to-machine authentication.
The authorization server is common for both user authentication and machine-to-machine authentication. You only need to create it once.
  1. Navigate to Security > API in the Okta dashboard
  2. Click Add Authorization Server
  3. Configure with the following:
    • Name: MCP Server Auth (or your preferred name)
    • Audience: https://your-mcp-server.example.com (this will be your MCP server’s identifier)
    • Description: Authorization server for MCP servers
  4. Click Save
  5. Note the Issuer URI from the Settings tab (e.g., https://dev-12345678.okta.com/oauth2/aus123abc)
The audience value is an identifier for your API/resource. It doesn’t have to be an actual URL, but using a URL format is a common convention.
2

Configure Scopes

Define what permissions your application can request.
  1. In your Authorization Server, go to the Scopes tab
  2. Add custom scopes if needed (e.g., read:data, write:data)
For production systems, define granular scopes that map to specific permissions in your MCP server.
3

Create Access Policy and Rule

Control who can get tokens from your authorization server.
  1. In your Authorization Server, go to the Access Policies tab
  2. Click Add New Access Policy
  3. Configure:
    • Name: MCP Access Policy
    • Description: Policy for MCP server access
    • Assign to: Select your OAuth application
Important: The Assign to field is critical. You must select the OAuth application you created in the previous step. If you don’t assign the policy to your application, the application won’t be able to obtain tokens from this authorization server.
  1. Click Create Policy
  2. Click Add Rule to create a default rule:
    • Rule Name: Default Rule
    • Grant type is: Check Authorization Code and Refresh Token
    • User is: Any user assigned the app
    • Scopes requested: Any scopes
    • Access token lifetime: 1 hour (or as per your requirements)
  3. Click Create Rule
If you plan to use machine-to-machine authentication, you’ll need to add Client Credentials grant type to this rule or create a separate access policy. See Section 4 for machine-to-machine setup instructions.
Refresh Token must be enabled in the access policy rule to allow the Gateway to refresh expired access tokens automatically.

Collect Necessary Information

1

Collect Configuration Values

Once you have the OAUTH_ISSUER from your authorization server Settings tab (e.g., https://dev-12345678.okta.com/oauth2/aus123abc), you can access the well-known URL:OAUTH_WELL_KNOWN_URL: {OAUTH_ISSUER}/.well-known/oauth-authorization-serverThe well-known endpoint provides the JWKS URI for token verification.You’ll also need:
  • OAUTH_AUDIENCE: The audience value you configured in the authorization server (e.g., https://your-mcp-server.example.com)
  • CLIENT_ID and CLIENT_SECRET: From your user-facing OAuth application (for Gateway integration)
Store these securely - you’ll use them in subsequent steps.

2. Deploy to TrueFoundry

Now let’s deploy the OAuth-authenticated MCP server as a TrueFoundry service.
1

Navigate to Service Deployment

  1. Go to your TrueFoundry dashboard
  2. Navigate to Deployments > New Deployment
  3. Select your workspace
  4. Choose Service as the deployment type
2

Configure Source Repository

Use the GitHub repository as the source:
  1. Select GitHub as the source
  2. Repository URL: https://github.com/truefoundry/mcp-servers
  3. Branch: main
  4. Build Context Path: ./sample-oauth-mcp/
If you’re deploying from your own fork, make sure the repository is accessible to TrueFoundry via GitHub integration.
3

Configure Build Settings

Specify how to build and run the service:
  1. Build Type: Python
  2. Python Version: 3.13
  3. Command: python server.py
  4. Requirements Path: requirements.txt
  5. Port: 8000
4

Add Environment Variables

Configure the OAuth settings using the values from Step 1:Add these environment variables:
VariableValueDescription
OAUTH_WELL_KNOWN_URLhttps://dev-12345678.okta.com/oauth2/aus123abc/.well-known/oauth-authorization-serverOAuth authorization server well-known endpoint for auto-discovery
OAUTH_JWKS_URIhttps://dev-12345678.okta.com/oauth2/aus123abc/v1/keysJSON Web Key Set URI for token verification
OAUTH_ISSUERhttps://dev-12345678.okta.com/oauth2/aus123abcAuthorization server issuer URI
OAUTH_AUDIENCEhttps://your-mcp-server.example.comAudience identifier for your API
The MCP server only needs these four environment variables to validate OAuth tokens. It doesn’t need the Client ID or Client Secret since it’s only validating tokens, not generating them.The OAUTH_WELL_KNOWN_URL enables the MCP server to expose the /.well-known/oauth-authorization-server endpoint, which allows the AI Gateway to auto-discover OAuth configuration details. This endpoint redirects to your Okta authorization server’s well-known endpoint.
5

Configure Networking

Set up the service endpoint:
  1. Port: 8000
  2. Expose: Enable this to make the service accessible
After deployment, note the service endpoint URL (e.g., https://oauth-mcp-server.apps.yourcluster.truefoundry.cloud).
6

Deploy the Service

  1. Review all configurations
  2. Click Submit to start the deployment
  3. Monitor the deployment status in the dashboard
  4. Wait for the status to show Active (green)
Once deployed, your OAuth-protected MCP server is ready to integrate with the AI Gateway!
You can also deploy programmatically using the TrueFoundry Python SDK:
import logging
from truefoundry.deploy import Build, PythonBuild, Service, Port, GitSource

logging.basicConfig(level=logging.INFO)

service = Service(
    name="oauth-mcp-server",
    image=Build(
        build_source=GitSource(
            repo_url="https://github.com/truefoundry/mcp-servers",
            ref="main"
        ),
        build_spec=PythonBuild(
            build_context_path="./sample-oauth-mcp/",
            command="python server.py",
            requirements_path="requirements.txt",
            python_version="3.13"
        ),
    ),
    ports=[
        Port(
            port=8000,
            protocol="TCP",
            expose=True,
            app_protocol="http",
        )
    ],
    env={
        "OAUTH_WELL_KNOWN_URL": "https://dev-12345678.okta.com/oauth2/aus123abc/.well-known/oauth-authorization-server",
        "OAUTH_JWKS_URI": "https://dev-12345678.okta.com/oauth2/aus123abc/v1/keys",
        "OAUTH_ISSUER": "https://dev-12345678.okta.com/oauth2/aus123abc",
        "OAUTH_AUDIENCE": "https://your-mcp-server.example.com",
    },
)

service.deploy(workspace_fqn="your-workspace-fqn")
More details in the Deploy Service Programmatically guide.

3. Integrate with AI Gateway

Now that your OAuth MCP server is deployed, let’s add it to the TrueFoundry AI Gateway.
1

Create an MCP Server Group

If you don’t have an MCP Server Group yet:
  1. Navigate to MCP Servers in the AI Gateway
  2. Click Add New MCP Group
  3. Configure:
    • Name: oauth-protected-servers (or your preferred name)
    • Managers: Select teams/users who can manage servers in this group
  4. Click Create
Learn more about MCP Server Groups in the Getting Started guide.
2

Add Your MCP Server

  1. In your MCP Server Group, click Add MCP Server
  2. Select Remote MCP
  3. Configure the server:
    • Name: oauth-mcp-server
    • Description: OAuth-authenticated MCP server with Okta
    • URL: Your deployed service endpoint (e.g., https://oauth-mcp-server.apps.yourcluster.truefoundry.cloud/mcp)
    • Transport: streamable-http
    • Authentication Type: Select OAuth2
3

Configure OAuth2 Settings

In the OAuth2 configuration section, provide the Okta credentials:
  • OAuth2 Client ID: Your Okta application client ID
  • OAuth2 Client Secret: Your Okta application client secret
The AI Gateway will automatically discover the OAuth2 Authorization URL, Token URL, and other configuration details from your MCP server’s /.well-known/oauth-authorization-server endpoint once you provide the MCP server URL. This endpoint is enabled by the OAUTH_WELL_KNOWN_URL environment variable configured during deployment (see Section 2).You can optionally configure:
  • OAuth2 Scopes: The scopes are prefilled, but you can change them if needed to use your custom scopes.
Include offline_access in the scopes to enable refresh tokens. This allows the Gateway to automatically refresh expired access tokens without requiring users to re-authenticate.
Store the Client Secret in TrueFoundry secrets and reference it by FQN for enhanced security.
4

Set Access Control

Define who can use this MCP server:
  • Access Control: Select teams or users who should have access to this server
Managers of the MCP Server Group automatically have access to all servers in the group.
5

Save and Test

  1. Click Save to add the MCP server
  2. The server will appear in your MCP Server Group
  3. Users can now connect and use the server through the AI Gateway

Using the OAuth MCP Server in Playground

To test your OAuth-protected MCP server:
1

Open the Playground

Navigate to the Playground in the AI Gateway.
2

Add MCP Server

  1. Click Add Tool/MCP Servers
  2. Find your oauth-mcp-server in the list
  3. Click Connect Now to initiate OAuth authorization
3

Authorize Access

You’ll be redirected to Okta to authorize access:
  1. Sign in with your Okta credentials
  2. Review the requested permissions (scopes)
  3. Click Allow to grant access
  4. You’ll be redirected back to the Gateway
The AI Gateway will store your OAuth tokens securely and refresh them automatically when they expire.
4

Select Tools and Test

  1. Once connected, you’ll see the get_me tool from your MCP server
  2. Select the tool and click Done
  3. Try sending a prompt like “Get my user details” or “What information do you have about me?”
  4. The tool will return details from your JWT token, such as your email, name, and other claims from Okta

4. Machine-to-Machine Authentication

This section is optional and only needed if you want to access your MCP server programmatically without user interaction (machine-to-machine communication). For normal user-facing workflows through the AI Gateway, the standard OAuth flow handled by the Gateway (covered in Section 3) is sufficient.
For machine-to-machine communication (e.g., programmatic access without user interaction), you can use the OAuth2 Client Credentials grant type to obtain access tokens directly.

When to Use Machine-to-Machine Authentication

Use machine-to-machine authentication when:
  • Your application needs to access the MCP server programmatically
  • No user interaction is required

Setup

1

Create an API Service Integration

Create a separate API Service Integration app for machine-to-machine access. API Service integrations provide more granular control over policies and are specifically designed for service-to-service communication.The following diagram illustrates how multiple services can access the same MCP server, each with their own Okta app and different scopes for fine-grained access control:
Key Points:
  • One App Per Service: Each service accessing the MCP server has its own API Service Integration app, providing isolation and granular access control
  • Shared MCP Server: Multiple services can access the same MCP server, but each uses different Okta apps with different scopes
  • Custom Authorization Server: All apps use the same custom authorization server (not the Org Authorization Server, which is only for Okta APIs)
Important: You must create at least one custom scope before creating API Service Integration apps. OIDC scopes like openid, profile, and email won’t work for API Service Integrations.
Create Custom Scopes (required for API Service Integrations):
  1. Go to your Authorization Server (created in Section 1)
  2. Navigate to the Scopes tab
  3. Click Add Scope
  4. Configure each custom scope:
    • Name: Enter a scope name (e.g., read, write, read:data, write:data)
    • Description: Enter a description of what this scope allows (e.g., “Read access to MCP server resources”)
  5. Click Create to add the scope
  6. Repeat steps 3-5 for each custom scope you need
For production systems, define granular scopes that map to specific permissions in your MCP server. For example:
  • read - Read-only access
  • write - Write access
  • read:data - Read access to data resources
  • write:data - Write access to data resources
Create the API Service Integration:
  1. Navigate to Applications > Applications in the Okta dashboard
  2. Click Create App Integration
  3. Select API Services
  4. Configure the integration:
    • Integration name: MCP Server Machine-to-Machine
    • Description: API service integration for MCP server machine-to-machine authentication
  5. Click Save
  6. Note the Client ID and Client Secret from the integration page
  7. Go to the General tab and disable DPoP (Demonstrate Proof of Possession) if it’s enabled
DPoP (Demonstrate Proof of Possession): DPoP is not covered in these docs. Make sure to disable DPoP for your API Service Integration app if it’s enabled by default.
2

Add Machine-to-Machine App to Access Policy

To use your machine-to-machine application with your custom authorization server:
  1. Go to your Authorization Server’s Access Policies tab
  2. Edit your existing MCP Access Policy (or create a new one)
  3. In the Assign to field, select:
    • Your user-facing OAuth application (for user authentication)
    • Your machine-to-machine application (OIDC app with Client Credentials or API Service Integration)
  4. Edit the access policy rule (or create a new rule):
    • Grant type is: Check Client Credentials
    • Scopes requested: Select your custom scopes
    • Access token lifetime: Set as per your requirements
  5. Click Save
You can create separate access policies for user authentication and machine-to-machine authentication if you need different rules or token lifetimes for each use case.

Collect Machine-to-Machine Credentials

After setting up your machine-to-machine application, collect these values:
  • M2M_CLIENT_ID: The Client ID from your machine-to-machine application (OIDC app or API Service Integration)
  • M2M_CLIENT_SECRET: The Client Secret from your machine-to-machine application
All other values (OAUTH_ISSUER, OAUTH_AUDIENCE) are already collected in Section 1. You only need the Client ID and Secret for machine-to-machine authentication.

Getting an Access Token

When using a custom authorization server with your API Service Integration, use the token endpoint and custom scopes. The token endpoint can be obtained from the well-known configuration at {OAUTH_ISSUER}/.well-known/oauth-authorization-server. The response includes the token_endpoint:
{
  "issuer": "https://dev-12345678.okta.com/oauth2/aus123abc",
  "authorization_endpoint": "https://dev-12345678.okta.com/oauth2/aus123abc/v1/authorize",
  "token_endpoint": "https://dev-12345678.okta.com/oauth2/aus123abc/v1/token",
  "jwks_uri": "https://dev-12345678.okta.com/oauth2/aus123abc/v1/keys",
  "grant_types_supported": [
    "authorization_code",
    "refresh_token",
    "client_credentials",
    ...
  ],
  "scopes_supported": [
    "backend",
    "openid",
    "profile",
    "email",
    ...
  ],
  ...
}
Use the token_endpoint value from this response in your token requests.
  • Using cURL
  • Using Python
  • Using Python with requests-oauthlib
# Set your values
TOKEN_ENDPOINT="https://dev-12345678.okta.com/oauth2/aus123abc/v1/token"
M2M_CLIENT_ID="0oa123abc..."
M2M_CLIENT_SECRET="secret123..."
AUDIENCE="https://your-mcp-server.example.com"

# Encode client credentials
CREDENTIALS=$(echo -n "${M2M_CLIENT_ID}:${M2M_CLIENT_SECRET}" | base64)

# Request access token
curl -X POST "${TOKEN_ENDPOINT}" \
  -H "Authorization: Basic ${CREDENTIALS}" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "scope=my_custom_scope" \
  -d "audience=${AUDIENCE}"
Response:
{
  "access_token": "eyJraWQiOiJxMmFt...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "my_custom_scope"
}
When using a custom authorization server, you can use custom scopes defined in your authorization server. Make sure to include the audience parameter matching the audience configured in your authorization server.

Token Management Best Practices

Since Client Credentials doesn’t support refresh tokens, you’ll need to request a new access token when the current one expires. Implement automatic token renewal logic to handle token expiration gracefully.
  1. Cache tokens: Access tokens are valid for the duration specified in expires_in. Cache them and reuse until expiry.
  2. Handle expiration: Implement automatic token renewal logic (request a new token when needed). See the TokenManager implementation in the MCP server README for a complete example.
  3. Secure storage: Never hardcode credentials. Use environment variables or secret management systems.
  4. Monitor usage: Track token requests to detect unusual patterns that might indicate compromised credentials.

5. Understanding the MCP Server Implementation

For details about the MCP server implementation, including how OAuth token validation works, local development, and testing instructions, see the README.md in the repository.