Skip to content

Latest commit

 

History

History
571 lines (409 loc) · 19.2 KB

File metadata and controls

571 lines (409 loc) · 19.2 KB

OAuth: Python vs TypeScript Implementation

This document compares the OAuth implementations between Python FastMCP and TypeScript FastMCP, covering both server-side (OAuth Proxy) and client-side (OAuth Client) functionality.

Executive Summary

This comparison covers two distinct areas:

Part A: OAuth Proxy (Server-Side) - ✅ Full Parity

The TypeScript implementation is a comprehensive and faithful port of the Python FastMCP OAuth proxy for protecting MCP servers. Both versions provide:

  • OAuth 2.1 proxy functionality
  • Dynamic Client Registration (DCR)
  • Two-tier PKCE security
  • User consent flow
  • Token swap pattern
  • Pre-configured providers
  • Flexible storage backends

For MCP server development, TypeScript and Python have complete feature parity.

Part B: OAuth Client (Client-Side) - ℹ️ Python Only

Python FastMCP includes an OAuth Client component for building client applications (CLI tools, desktop apps) that connect to OAuth-protected servers. TypeScript does not include this client-side tooling.

Key distinction: OAuth Proxy (server) and OAuth Client (client) serve different purposes:

  • Server-Side (OAuthProxy): Protects your MCP server with OAuth authentication
  • Client-Side (OAuthClient): Helps client apps authenticate to protected servers

The main differences lie in dependency management and client-side tooling availability.


Part A: OAuth Proxy (Server-Side) Comparison

This section compares server-side OAuth Proxy functionality for protecting MCP servers.

Server-Side Feature Comparison Matrix

Feature Python FastMCP TypeScript FastMCP Notes
Core Proxy Identical functionality
Dynamic Client Registration RFC 7591 compliant
Authorization Code Flow Full OAuth 2.1 support
PKCE Support ✅ (S256 only) ✅ (S256 + plain) TypeScript supports both methods
Refresh Token Flow Identical
Token Swap Pattern ✅ (default) ✅ (default) Both enabled by default
Consent Screen Both have full HTML UI
Pre-configured Providers ✅ Multiple ✅ Google, GitHub, Azure Similar approach
Storage Interface AsyncKeyValue TokenStorage Different interface names, same concept
In-Memory Storage Available in both
Disk Storage DiskStore DiskStore Similar implementation
Encrypted Storage ✅ Fernet (default) ✅ AES-256-GCM (default) Both encrypt by default
JWT Issuer ✅ python-jose ✅ Custom HS256 Different libraries, same functionality
JWT Algorithms ✅ HS256, RS256 ✅ HS256 (RS256 via jose) Python built-in, TypeScript optional
JWKS Support ✅ Built-in ✅ Optional (requires jose) Both supported, TypeScript opt-in
Automatic Route Registration Seamless integration in both
Discovery Metadata RFC 8414 compliant
Error Handling ✅ authlib errors OAuthProxyError Similar standardized errors
Token Rotation Tracking ✅ Advanced ⚠️ Basic Python has more sophisticated tracking

Result: TypeScript has complete server-side parity with Python for MCP server development.

Architecture Comparison

Python Implementation

fastmcp-python/src/fastmcp/server/auth/
├── oauth_proxy.py          # Main proxy class
├── jwt_issuer.py           # JWT token handling
├── providers/              # Pre-configured providers
│   ├── google.py
│   ├── github.py
│   └── ...
└── storage/                # Storage backends
    ├── disk_store.py
    └── ...

Dependencies:
- authlib (OAuth client mechanics)
- httpx (HTTP requests)
- cryptography (Fernet encryption)
- pydantic (data validation)
- py-key-value-aio (storage abstraction)

TypeScript Implementation

fastmcp/src/auth/
├── OAuthProxy.ts           # Main proxy class
├── types.ts                # Type definitions
├── utils/
│   ├── pkce.ts            # PKCE utilities
│   ├── tokenStore.ts      # Storage implementations
│   ├── diskStore.ts       # Disk storage
│   ├── jwtIssuer.ts       # JWT handling
│   └── consent.ts         # Consent management
└── providers/              # Pre-configured providers
    ├── GoogleProvider.ts
    ├── GitHubProvider.ts
    └── AzureProvider.ts

Dependencies:
- crypto (Node.js built-in)
- fs/promises (Node.js built-in)
- undici (HTTP, already in dependencies)

Key Difference: TypeScript uses mostly built-in Node.js modules, while Python relies on external battle-tested libraries.

API Comparison

Creating an OAuth Server

Python:

from fastmcp import FastMCP
from fastmcp.server.auth import OAuthProxy

auth = OAuthProxy(
    upstream_authorization_endpoint="https://provider.com/oauth/authorize",
    upstream_token_endpoint="https://provider.com/oauth/token",
    upstream_client_id="client-id",
    upstream_client_secret="client-secret",
    base_url="https://your-server.com"
)

mcp = FastMCP(name="My Server", auth=auth)

TypeScript:

import { FastMCP, OAuthProvider } from "fastmcp";

const server = new FastMCP({
  auth: new OAuthProvider({
    authorizationEndpoint: "https://provider.com/oauth/authorize",
    baseUrl: "https://your-server.com",
    clientId: "client-id",
    clientSecret: "client-secret",
    tokenEndpoint: "https://provider.com/oauth/token",
  }),
  name: "My Server",
  version: "1.0.0",
});

Differences: Minimal - both use a simple auth option with camelCase vs snake_case naming.

Using Pre-configured Providers

Python:

from fastmcp.server.auth import GoogleProvider

auth = GoogleProvider(
    client_id="xxx.apps.googleusercontent.com",
    client_secret="secret",
    base_url="https://your-server.com",
    scopes=["openid", "profile", "email"]
)

mcp = FastMCP(name="My Server", auth=auth)

TypeScript:

import { FastMCP, GoogleProvider } from "fastmcp";

const server = new FastMCP({
  auth: new GoogleProvider({
    baseUrl: "https://your-server.com",
    clientId: "xxx.apps.googleusercontent.com",
    clientSecret: "secret",
    scopes: ["openid", "profile", "email"],
  }),
  name: "My Server",
  version: "1.0.0",
});

Differences: Minimal - both use auth option with camelCase vs snake_case naming.

Token Swap Pattern

Python (Default Behavior):

auth = OAuthProxy(
    # ... config
    # Token swap enabled by default!
)

# Upstream tokens automatically stored
# Clients receive FastMCP JWTs

TypeScript (Same - Enabled by Default):

const auth = new OAuthProxy({
  // ... config
  // Token swap enabled by default!
  // jwtSigningKey auto-generated if not provided (recommended to provide your own)
  jwtSigningKey: "signing-key", // Optional but recommended
});

// Clients receive FastMCP JWTs
// Load upstream tokens when needed:
const upstreamTokens = await auth.loadUpstreamTokens(clientToken);

Parity: Both Python and TypeScript now enable token swap by default for enhanced security.

Storage Configuration

Python (Encrypted by Default):

from py_key_value.providers import DiskStore
from py_key_value.middlewares import FernetEncryptionWrapper

# Encrypted disk storage is default
auth = OAuthProxy(
    # ... config
    storage=DiskStore(directory="/var/lib/fastmcp")
)

TypeScript (Encrypted by Default):

import { DiskStore } from "fastmcp/auth";

// Encryption is automatic! Just provide storage
const auth = new OAuthProxy({
  // ... config
  tokenStorage: new DiskStore({ directory: "/var/lib/fastmcp" }),
  // encryptionKey: "custom-key", // Optional - auto-generated if not provided
});

// Or use in-memory with encryption (default)
const auth2 = new OAuthProxy({
  // ... config
  // Storage defaults to encrypted MemoryTokenStorage
});

Parity: Both Python and TypeScript now encrypt storage by default. TypeScript auto-generates encryption keys.

JWT Key Derivation

Python:

from fastmcp.server.auth import derive_key

jwt_key = derive_key(secret, iterations=100000)

TypeScript:

import { JWTIssuer } from "fastmcp/auth";

const jwtKey = await JWTIssuer.deriveKey(secret, 100000);

Similarity: Same PBKDF2 key derivation approach.

Default Behaviors

Aspect Python TypeScript Recommendation
Token Swap Enabled Enabled ✅ Secure by default
Storage Encrypted Disk In-Memory (encrypted) Use DiskStore for production
Encryption Enabled (Fernet) Enabled (AES-256-GCM) ✅ Secure by default
Consent Screen Required Required ✅ Keep enabled
PKCE S256 only S256 + plain Use S256
Cleanup Interval 60s 60s ✅ Same default

Migration Guide: Python to TypeScript

Step 1: Install Dependencies

Python:

pip install fastmcp

TypeScript:

npm install fastmcp

Step 2: Update Imports

Python:

from fastmcp import FastMCP
from fastmcp.server.auth import OAuthProxy, GoogleProvider

TypeScript:

import { FastMCP, OAuthProvider, GoogleProvider, requireAuth } from "fastmcp";

Step 3: Convert Configuration

Python:

auth = GoogleProvider(
    client_id=os.environ["GOOGLE_CLIENT_ID"],
    client_secret=os.environ["GOOGLE_CLIENT_SECRET"],
    base_url="https://example.com",
    scopes=["openid", "profile"]
)

mcp = FastMCP(name="My Server", auth=auth)

TypeScript:

const server = new FastMCP({
  auth: new GoogleProvider({
    baseUrl: "https://example.com",
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    scopes: ["openid", "profile"],
  }),
  name: "My Server",
  version: "1.0.0",
});

Step 4: Update Token Access

Python:

@mcp.tool()
async def protected_tool(session: Session):
    # Access user token
    token = session.auth_token
    # Use token to call upstream API

TypeScript:

import { requireAuth, getAuthSession } from "fastmcp";

server.addTool({
  canAccess: requireAuth,
  name: "protected-tool",
  execute: async (_args, { session }) => {
    const { accessToken } = getAuthSession(session);
    const response = await fetch("https://api.provider.com/user", {
      headers: { Authorization: `Bearer ${accessToken}` },
    });
    return JSON.stringify(await response.json());
  },
});

Step 5: Adjust Storage (If Using Disk)

Python:

from py_key_value.providers import DiskStore

auth = OAuthProxy(
    # ... config
    storage=DiskStore(directory="/var/lib/fastmcp")
)

TypeScript:

import { OAuthProvider, DiskStore } from "fastmcp/auth";

const server = new FastMCP({
  auth: new OAuthProvider({
    // ... config
    tokenStorage: new DiskStore({ directory: "/var/lib/fastmcp" }),
  }),
  name: "My Server",
  version: "1.0.0",
});

Part B: OAuth Client (Client-Side) Comparison

This section compares client-side OAuth functionality for building applications that connect to OAuth-protected MCP servers (CLI tools, desktop apps, etc.).

Client-Side Feature Comparison Matrix

Feature Python FastMCP TypeScript FastMCP Notes
OAuth Client Class OAuthClient Python includes complete client implementation
Browser Launching ✅ Automatic Opens browser for authorization
Local Callback Server ✅ Automatic Handles OAuth redirects with auto port selection
Token Management ✅ Automatic Storage and retrieval
Token Refresh ✅ Automatic Background refresh handling
PKCE Flow ✅ Client-side Automatic verifier generation
Timeout Handling ✅ 5 min default Callback timeout management

Result: Python provides client-side OAuth tooling. TypeScript does not (client developers must implement OAuth manually).

Python Client-Side Implementation

Python includes a complete OAuth client for connecting to protected servers:

from fastmcp.client.auth import OAuthClient

client = OAuthClient(
    authorization_endpoint="https://server.com/oauth/authorize",
    token_endpoint="https://server.com/oauth/token"
)

# Automatically handles:
# - Browser launching for user authorization
# - Local callback server (auto port selection)
# - PKCE generation and validation
# - Token storage and refresh
tokens = await client.authenticate()

TypeScript: Not available. Client developers must implement their own OAuth flow or use third-party libraries.

Server-Side Features (Python-Specific)

These features are server-side but Python-specific:

1. Multiple JWT Algorithms (Built-in)

Python: Supports HS256, RS256, ES256, etc. via python-jose (built-in)

TypeScript: HS256 built-in, RS256/ES256 available via optional jose package

2. More Storage Backends Out-of-the-Box

Python: Via py-key-value: Redis, DynamoDB, Elasticsearch, etc.

TypeScript: Memory and Disk only (custom implementations required for others)

What TypeScript Has That Python Doesn't

1. Plain PKCE Method

TypeScript: Supports both S256 and plain PKCE challenges

Python: S256 only

2. More Granular Token Swap Control

TypeScript: Opt-in token swap with explicit configuration

Python: Token swap is always enabled

3. TypeScript Type Safety

TypeScript: Full compile-time type checking

Python: Runtime validation with Pydantic

4. Minimal Dependencies

TypeScript: Built on Node.js core modules

Python: Requires authlib, httpx, cryptography, etc.

Performance Characteristics

Metric Python TypeScript Notes
Startup Time Fast Instant Both are quick
Memory (Base) ~15MB ~10MB TypeScript slightly lighter
OAuth Flow <100ms <100ms Similar performance
Storage Overhead Depends on backend Depends on backend Similar
Encryption Overhead Fernet (~5%) AES-GCM (~3%) Negligible difference

Security Comparison

Both implementations provide equivalent security:

Security Feature Python TypeScript
Two-tier PKCE
User Consent
Encrypted Storage ✅ (default) ✅ (default)
HMAC-signed Cookies
State Validation
Redirect URI Validation
One-time Auth Codes
OAuth 2.1 Compliance

Parity: Both implementations provide equivalent security with encryption enabled by default.

Testing Coverage

Python:

  • pytest-based test suite
  • Comprehensive unit and integration tests
  • Mock-based OAuth provider testing

TypeScript:

  • Vitest-based test suite
  • 29+ tests covering all core functionality
  • PKCE, storage, JWT, consent, and integration tests

Both have solid test coverage.

Conclusion

Server-Side (OAuth Proxy): ✅ Full Parity

Both Python and TypeScript implementations are production-ready and provide complete feature parity for OAuth proxy functionality. For MCP server development, choose based on:

  1. Language/Runtime Preference - Python vs Node.js/TypeScript
  2. Dependency Philosophy - Python uses external libraries (authlib), TypeScript uses built-in modules
  3. Storage Defaults - Python defaults to disk, TypeScript defaults to in-memory (both support both)
  4. JWT Algorithms - Python has all built-in, TypeScript has HS256 built-in (RS256/ES256 via optional jose)

Both implementations encrypt by default and enable token swap by default.

Client-Side (OAuth Client): ℹ️ Python Only

Python FastMCP includes an OAuth Client component for building client applications (CLI tools, desktop apps) that connect to OAuth-protected servers. TypeScript does not include this client-side tooling.

For client application development: Choose Python if you want automatic browser-based OAuth flow handling, or implement your own OAuth client in TypeScript using third-party libraries.

Migration from Python to TypeScript

For server-side OAuth proxy, migration is straightforward with minimal code changes, primarily adjusting for camelCase naming. Both implementations now have matching defaults (token swap enabled, encryption enabled).

Resources

Python FastMCP

TypeScript FastMCP