This document compares the OAuth implementations between Python FastMCP and TypeScript FastMCP, covering both server-side (OAuth Proxy) and client-side (OAuth Client) functionality.
This comparison covers two distinct areas:
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.
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.
This section compares server-side OAuth Proxy functionality for protecting MCP servers.
| 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 | Python has more sophisticated tracking |
Result: TypeScript has complete server-side parity with Python for MCP server development.
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)
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.
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.
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.
Python (Default Behavior):
auth = OAuthProxy(
# ... config
# Token swap enabled by default!
)
# Upstream tokens automatically stored
# Clients receive FastMCP JWTsTypeScript (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.
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.
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.
| 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 |
Python:
pip install fastmcpTypeScript:
npm install fastmcpPython:
from fastmcp import FastMCP
from fastmcp.server.auth import OAuthProxy, GoogleProviderTypeScript:
import { FastMCP, OAuthProvider, GoogleProvider, requireAuth } from "fastmcp";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",
});Python:
@mcp.tool()
async def protected_tool(session: Session):
# Access user token
token = session.auth_token
# Use token to call upstream APITypeScript:
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());
},
});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",
});This section compares client-side OAuth functionality for building applications that connect to OAuth-protected MCP servers (CLI tools, desktop apps, etc.).
| 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 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.
These features are server-side but Python-specific:
Python: Supports HS256, RS256, ES256, etc. via python-jose (built-in)
TypeScript: HS256 built-in, RS256/ES256 available via optional jose package
Python: Via py-key-value: Redis, DynamoDB, Elasticsearch, etc.
TypeScript: Memory and Disk only (custom implementations required for others)
TypeScript: Supports both S256 and plain PKCE challenges
Python: S256 only
TypeScript: Opt-in token swap with explicit configuration
Python: Token swap is always enabled
TypeScript: Full compile-time type checking
Python: Runtime validation with Pydantic
TypeScript: Built on Node.js core modules
Python: Requires authlib, httpx, cryptography, etc.
| 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 |
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.
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.
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.
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.
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).
- Repository: jlowin/fastmcp
- Documentation: fastmcp.io
- Repository: FastMCP TypeScript
- Documentation: See
docs/directory