OAuth: Python vs TypeScript Implementation
January 25, 2026 · View on GitHub
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:
- Language/Runtime Preference - Python vs Node.js/TypeScript
- Dependency Philosophy - Python uses external libraries (authlib), TypeScript uses built-in modules
- Storage Defaults - Python defaults to disk, TypeScript defaults to in-memory (both support both)
- 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
- Repository: jlowin/fastmcp
- Documentation: fastmcp.io
TypeScript FastMCP
- Repository: FastMCP TypeScript
- Documentation: See
docs/directory