MCP Authentication

OAuth 2.1, OIDC Discovery, and API Key authentication per MCP 2025-11-25

MCP Authentication

PraisonAI MCP Server supports multiple authentication methods per the MCP 2025-11-25 specification, including OAuth 2.1 with PKCE, OpenID Connect Discovery, and API Key authentication.

Protocol Version

This feature implements MCP Protocol Version 2025-11-25.

Authentication Methods

MethodDescriptionUse Case
OAuth 2.1Full OAuth flow with PKCEWeb applications, user auth
OIDC DiscoveryAuto-discover OAuth endpointsEnterprise SSO
API KeySimple bearer tokenCLI tools, scripts

Python API

OIDC Discovery

python
import asyncio
from praisonai.mcp_server.auth.oidc import OIDCDiscovery

async def main():
    discovery = OIDCDiscovery()

    # Discover from any OIDC provider
    config = await discovery.discover("https://accounts.google.com")

    if config:
        print(f"Issuer: {config.issuer}")
        print(f"Auth endpoint: {config.authorization_endpoint}")
        print(f"Token endpoint: {config.token_endpoint}")
        print(f"JWKS URI: {config.jwks_uri}")

asyncio.run(main())

OAuth 2.1 with PKCE

python
from praisonai.mcp_server.auth.oauth import OAuthConfig, OAuthManager

# Configure OAuth
config = OAuthConfig(
    authorization_endpoint="https://auth.example.com/authorize",
    token_endpoint="https://auth.example.com/token",
    client_id="my-mcp-client",
    default_scopes=["openid", "profile", "tools:read"],
    use_pkce=True,  # Required for OAuth 2.1
)

oauth = OAuthManager(config)

# Create authorization URL with PKCE
auth_url, auth_request = oauth.create_authorization_url(
    scopes=["openid", "profile", "tools:call"],
)

print(f"Auth URL: {auth_url}")
print(f"State: {auth_request.state}")
print(f"Code verifier: {auth_request.code_verifier}")
print(f"Code challenge: {auth_request.code_challenge}")

# After user authorizes, exchange code for token
# token = await oauth.exchange_code(code, auth_request)

API Key Authentication

python
from praisonai.mcp_server.auth.api_key import APIKeyAuth

api_key_auth = APIKeyAuth()

# Generate a new API key
raw_key, api_key = api_key_auth.generate_key(
    name="my-api-key",
    scopes=["tools:read", "tools:call", "resources:read"],
)

print(f"Key: {raw_key}")  # mcp_xxx... (store securely!)
print(f"Key ID: {api_key.key_id}")
print(f"Scopes: {api_key.scopes}")

# Validate a key
is_valid, validated_key = api_key_auth.validate(raw_key)
print(f"Valid: {is_valid}")

# Validate from Authorization header
is_valid, key = api_key_auth.validate_header("Bearer mcp_xxx...")

Scope Management

python
from praisonai.mcp_server.auth.scopes import ScopeManager

scope_manager = ScopeManager()

# Check if required scopes are granted
required = ["tools:read"]
granted = ["tools:call"]  # tools:call implies tools:read

is_valid, challenge = scope_manager.validate_scopes(required, granted)
print(f"Valid: {is_valid}")

# Expand scopes (show implied scopes)
expanded = scope_manager.expand_scopes(["tools:call"])
print(f"Expanded: {expanded}")  # {'tools:call', 'tools:read'}

# Admin scope expands to all
admin_expanded = scope_manager.expand_scopes(["admin"])
print(f"Admin expands to: {list(admin_expanded)[:5]}...")

CLI Usage

Start Server with API Key

bash
praisonai mcp serve --transport http-stream --api-key YOUR_SECRET_KEY

Generate API Key

bash
praisonai mcp auth generate-key --name "my-key" --scopes "tools:read,tools:call"

Validate API Key

bash
praisonai mcp auth validate-key YOUR_KEY

Scoped API keys via --keys-file

Give each caller a least-privilege key. praisonai mcp serve --keys-file <path> loads a JSON array of per-key scopes and enforces them per JSON-RPC method.

bash
praisonai mcp serve --transport http-stream --keys-file ./keys.json

A minimal keys file:

json
[
  { "key": "read-only-monitor", "name": "monitor", "scopes": ["tools:read"] },
  { "key": "runner", "name": "ci-runner", "scopes": ["tools:read", "tools:call"] },
  { "key": "admin-key", "name": "ops", "scopes": ["*"] }
]
  • A key whose scopes field is omitted defaults to wildcard (["*"]).
  • An explicit empty list ("scopes": []) is honored as a no-permission key — it is not widened to wildcard.
  • A "*" grant satisfies every requirement.

Operation → scope map

Each JSON-RPC method maps to a required scope (OPERATION_SCOPES):

JSON-RPC methodRequired scope
tools/listtools:read
tools/calltools:call
resources/listresources:read
resources/readresources:read
resources/subscriberesources:subscribe
prompts/listprompts:read
prompts/getprompts:read
sampling/createMessagesampling:create
tasks/createtasks:write
tasks/gettasks:read
tasks/listtasks:read
tasks/canceltasks:write

Insufficient scope response

When a caller’s key lacks the required scope, the server returns JSON-RPC error -32001 insufficient_scope, and (over HTTP) a WWW-Authenticate challenge carrying the missing scope:

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": {
    "code": -32001,
    "message": "insufficient_scope",
    "data": {
      "required_scopes": ["tools:call"],
      "granted_scopes": ["tools:read"],
      "missing_scopes": ["tools:call"],
      "error": "insufficient_scope"
    }
  }
}
WWW-Authenticate: Bearer scope="tools:call", error="insufficient_scope"

One shared key or per-team scopes?

Diagram

One shared, fully trusted

Per-team, least-privilege

How many callers,
what trust level?

–api-key
(wildcard)

–keys-file
(scoped keys)


Available Scopes

ScopeDescriptionImplies
tools:readList and describe tools–
tools:callExecute toolstools:read
resources:readRead resources–
resources:subscribeSubscribe to resource updatesresources:read
prompts:readList and get prompts–
prompts:executeExecute promptsprompts:read
tasks:readView tasks–
tasks:writeCreate/cancel taskstasks:read
adminFull accessAll scopes

WWW-Authenticate Challenge

python
challenge = oauth.create_www_authenticate_challenge(
    required_scopes=["admin"],
    error="insufficient_scope",
    error_description="Admin access required",
)
# Bearer scope="admin", error="insufficient_scope", error_description="Admin access required"

Security Best Practices

  1. Always use PKCE – Required for OAuth 2.1
  2. Validate origins – Use --allowed-origins for HTTP transport
  3. Rotate API keys – Regularly rotate keys
  4. Minimal scopes – Request only needed scopes
  5. Secure storage – Never commit keys to version control

Environment Variables

bash
# OAuth configuration
export OAUTH_CLIENT_ID=your_client_id
export OAUTH_CLIENT_SECRET=your_client_secret  # Optional for public clients
export OIDC_ISSUER_URL=https://auth.example.com

# API key (for server)
export MCP_API_KEY=your_api_key

Related