Skip to content

Commit 45b5999

Browse files
committed
feat: add OAuth 2.1 authentication support
Add comprehensive OAuth 2.1 authentication implementation for MCP servers with RFC compliance (RFC 9728, RFC 6750, RFC 7662). Core Features: - OAuthAuthProvider with JWT validation and token introspection - Protected Resource Metadata endpoint (/.well-known/oauth-protected-resource) - Support for Auth0, Okta, AWS Cognito, Azure AD, and custom OAuth servers - JWKS key caching (15min) and token introspection caching (5min) - Comprehensive security validation (audience, issuer, expiration, signature) CLI Support: - Add --oauth flag to mcp create command - Generate OAuth-configured projects with environment templates - OAuth-aware example tools with authentication context access - Validation that --oauth requires --http Documentation: - Add OAuth 2.1 section to README.md - Create comprehensive docs/OAUTH.md with provider setup guides - Update CLAUDE.md with OAuth architecture details - Add complete oauth-server example project Testing: - 62 OAuth-specific tests (156 total tests passing) - OAuth Provider: 96.29% coverage - Protected Resource Metadata: 100% coverage - Mock OAuth server for testing
1 parent 0827bec commit 45b5999

File tree

28 files changed

+4349
-16
lines changed

28 files changed

+4349
-16
lines changed

CLAUDE.md

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
mcp-framework is a TypeScript framework for building Model Context Protocol (MCP) servers. It provides an opinionated architecture with automatic directory-based discovery for tools, resources, and prompts. The framework is used as a dependency in other projects (similar to Express.js) and runs from node_modules.
8+
9+
## Development Commands
10+
11+
### Build and Watch
12+
```bash
13+
npm run build # Compile TypeScript to dist/
14+
npm run watch # Watch mode for development
15+
```
16+
17+
### Testing
18+
```bash
19+
npm test # Run all tests
20+
npm run test:watch # Run tests in watch mode
21+
npm run test:coverage # Run tests with coverage report
22+
```
23+
24+
### Linting and Formatting
25+
```bash
26+
npm run lint # Run ESLint
27+
npm run lint:fix # Run ESLint with auto-fix
28+
npm run format # Format code with Prettier
29+
```
30+
31+
### Local Development with yalc
32+
```bash
33+
npm run dev:pub # Build and publish to yalc for local testing
34+
```
35+
36+
### CLI Commands (for projects using the framework)
37+
```bash
38+
mcp create <project-name> # Create new MCP server project
39+
mcp add tool <tool-name> # Add a new tool
40+
mcp add prompt <prompt-name> # Add a new prompt
41+
mcp add resource <resource-name> # Add a new resource
42+
mcp validate # Validate tool schemas
43+
mcp-build # Build project (used in build scripts)
44+
```
45+
46+
## Architecture
47+
48+
### Core Components
49+
50+
1. **MCPServer** ([src/core/MCPServer.ts](src/core/MCPServer.ts))
51+
- Main server class that orchestrates everything
52+
- Handles capability detection (tools, prompts, resources)
53+
- Manages transport configuration (stdio, SSE, HTTP stream)
54+
- Loads and validates tools/prompts/resources on startup
55+
- Resolves basePath from config or process.argv[1] or process.cwd()
56+
57+
2. **Loaders** ([src/loaders/](src/loaders/))
58+
- ToolLoader, PromptLoader, ResourceLoader
59+
- Automatically discover and load implementations from directories
60+
- Look for files in `<basePath>/tools`, `<basePath>/prompts`, `<basePath>/resources`
61+
- Load from compiled JS in dist/ (not from src/)
62+
63+
3. **Base Classes**
64+
- **MCPTool** ([src/tools/BaseTool.ts](src/tools/BaseTool.ts)) - Base for all tools
65+
- **BasePrompt** ([src/prompts/BasePrompt.ts](src/prompts/BasePrompt.ts)) - Base for prompts
66+
- **BaseResource** ([src/resources/BaseResource.ts](src/resources/BaseResource.ts)) - Base for resources
67+
68+
4. **Transport Layer** ([src/transports/](src/transports/))
69+
- stdio: Standard input/output (default)
70+
- SSE: Server-Sent Events transport
71+
- HTTP Stream: HTTP-based streaming with session management
72+
73+
### Tool Schema System
74+
75+
The framework uses Zod schemas with **mandatory descriptions** for all fields:
76+
77+
```typescript
78+
const schema = z.object({
79+
message: z.string().describe("Message to process"), // Description is required
80+
count: z.number().optional().describe("Optional count")
81+
});
82+
83+
class MyTool extends MCPTool {
84+
name = "my_tool";
85+
description = "Tool description";
86+
schema = schema;
87+
88+
async execute(input: MCPInput<this>) {
89+
// input is fully typed from schema
90+
}
91+
}
92+
```
93+
94+
**Validation occurs at multiple levels:**
95+
- Build-time: `npm run build` validates all schemas
96+
- Development: `defineSchema()` helper validates immediately
97+
- Standalone: `mcp validate` command
98+
- Runtime: Server validates on startup
99+
100+
Missing descriptions will cause build failures. Skip with `MCP_SKIP_TOOL_VALIDATION=true` (not recommended).
101+
102+
### Path Resolution
103+
104+
Since mcp-framework runs from node_modules:
105+
- `basePath` is resolved from config, process.argv[1], or process.cwd()
106+
- Loaders search for tools/prompts/resources relative to basePath
107+
- Framework code uses `import.meta.url` for its own files
108+
- Projects using the framework have tools/prompts/resources in their own directory structure
109+
110+
## Key Technical Details
111+
112+
### Module System
113+
- ESM modules (type: "module" in package.json)
114+
- TypeScript config: module="Node16", moduleResolution="Node16"
115+
- All imports use .js extensions (even for .ts files)
116+
- Jest configured for ESM with ts-jest
117+
118+
### Authentication
119+
120+
The framework supports three authentication providers for SSE and HTTP Stream transports:
121+
122+
#### OAuth 2.1 Authentication (Recommended for Production)
123+
124+
OAuth 2.1 authentication per MCP specification (2025-06-18) with RFC compliance:
125+
126+
**Components:**
127+
- **OAuthAuthProvider** ([src/auth/providers/oauth.ts](src/auth/providers/oauth.ts)): Main provider implementing AuthProvider interface
128+
- **JWTValidator** ([src/auth/validators/jwt-validator.ts](src/auth/validators/jwt-validator.ts)): Async JWT validation with JWKS support
129+
- **IntrospectionValidator** ([src/auth/validators/introspection-validator.ts](src/auth/validators/introspection-validator.ts)): OAuth token introspection (RFC 7662)
130+
- **ProtectedResourceMetadata** ([src/auth/metadata/protected-resource.ts](src/auth/metadata/protected-resource.ts)): RFC 9728 metadata generation
131+
132+
**Metadata Endpoint:**
133+
- Path: `/.well-known/oauth-protected-resource`
134+
- Public (no auth required)
135+
- Returns authorization server URLs and resource identifier
136+
- Automatically served by SSE and HTTP Stream transports when OAuth is configured
137+
138+
**Token Validation Strategies:**
139+
140+
1. **JWT Validation** (recommended for performance):
141+
- Fetches public keys from JWKS endpoint
142+
- Validates: signature, expiration, audience, issuer, nbf
143+
- JWKS key caching for 15 minutes (configurable)
144+
- Supports RS256 and ES256 algorithms
145+
- Fast: ~5-10ms per request (cached keys)
146+
147+
2. **Token Introspection** (recommended for real-time revocation):
148+
- Calls authorization server's introspection endpoint (RFC 7662)
149+
- Validates: active status, expiration, audience, issuer
150+
- Caches results for 5 minutes (configurable)
151+
- Allows immediate token revocation
152+
- Slower: ~20-50ms per request (cached)
153+
154+
**Security Features:**
155+
- Tokens must be in Authorization header (Bearer scheme)
156+
- Tokens in query strings rejected automatically (security requirement)
157+
- Audience validation prevents token reuse across services
158+
- WWW-Authenticate challenges per RFC 6750
159+
- Comprehensive logging of authentication events
160+
161+
**Configuration Example:**
162+
```typescript
163+
import { OAuthAuthProvider } from 'mcp-framework';
164+
165+
// JWT validation
166+
const provider = new OAuthAuthProvider({
167+
authorizationServers: ['https://auth.example.com'],
168+
resource: 'https://mcp.example.com',
169+
validation: {
170+
type: 'jwt',
171+
jwksUri: 'https://auth.example.com/.well-known/jwks.json',
172+
audience: 'https://mcp.example.com',
173+
issuer: 'https://auth.example.com'
174+
}
175+
});
176+
177+
// Token introspection
178+
const provider = new OAuthAuthProvider({
179+
authorizationServers: ['https://auth.example.com'],
180+
resource: 'https://mcp.example.com',
181+
validation: {
182+
type: 'introspection',
183+
audience: 'https://mcp.example.com',
184+
issuer: 'https://auth.example.com',
185+
introspection: {
186+
endpoint: 'https://auth.example.com/oauth/introspect',
187+
clientId: 'mcp-server',
188+
clientSecret: process.env.CLIENT_SECRET
189+
}
190+
}
191+
});
192+
```
193+
194+
**Integration:** Works with Auth0, Okta, AWS Cognito, Azure AD/Entra ID, and any RFC-compliant OAuth 2.1 server. See [docs/OAUTH.md](docs/OAUTH.md) for detailed setup guides.
195+
196+
#### JWT Authentication (Simple Token-Based)
197+
198+
- **JWTAuthProvider**: Token-based auth with configurable algorithms (HS256, RS256, etc.)
199+
- Simpler than OAuth, suitable for internal services
200+
- No automatic metadata endpoint
201+
202+
#### API Key Authentication
203+
204+
- **APIKeyAuthProvider**: Simple key-based auth
205+
- Good for development and testing
206+
- Not recommended for production
207+
208+
#### Custom Authentication
209+
210+
- **AuthProvider interface**: Implement custom authentication logic
211+
- Async authenticate method returns boolean or AuthResult with claims
212+
- getAuthError method provides error responses
213+
214+
### Transport Features
215+
- **SSE**: CORS configuration, optional auth on endpoints
216+
- **HTTP Stream**:
217+
- Response modes: "batch" (default) or "stream"
218+
- Session management with configurable headers
219+
- Stream resumability for missed messages
220+
- Batch request/response support
221+
222+
### CLI Templates
223+
The CLI uses templates ([src/cli/templates/](src/cli/templates/)) to scaffold new projects and components. These templates are used by the `mcp create` and `mcp add` commands.
224+
225+
### Logging
226+
- Logger utility in [src/core/Logger.ts](src/core/Logger.ts)
227+
- Environment variables:
228+
- `MCP_ENABLE_FILE_LOGGING`: Enable file logging (default: false)
229+
- `MCP_LOG_DIRECTORY`: Log directory (default: "logs")
230+
- `MCP_DEBUG_CONSOLE`: Show debug messages in console (default: false)
231+
232+
## Testing
233+
234+
Tests are in the `tests/` directory with the pattern `*.test.ts`. The project uses Jest with ts-jest for ESM support. Run tests with:
235+
- `NODE_OPTIONS='--experimental-vm-modules' jest`
236+
- Or use npm scripts: `npm test`, `npm run test:watch`, `npm run test:coverage`
237+
238+
## Important Notes
239+
240+
- All tool schema fields must have descriptions (enforced at build time)
241+
- The framework is meant to be used as a dependency, not modified directly
242+
- When testing locally, use `yalc` for linking instead of npm link
243+
- Transport layer is pluggable - choose stdio (default), SSE, or HTTP stream based on use case
244+
- Server performs validation on startup - tools with invalid schemas will prevent server start

README.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,146 @@ Clients must include a valid API key in the X-API-Key header:
623623
X-API-Key: your-api-key
624624
```
625625
626+
### OAuth 2.1 Authentication
627+
628+
MCP Framework supports OAuth 2.1 authentication per the MCP specification (2025-06-18), including Protected Resource Metadata (RFC 9728) and proper token validation with JWKS support.
629+
630+
OAuth authentication works with both SSE and HTTP Stream transports and supports two validation strategies:
631+
632+
#### JWT Validation (Recommended for Performance)
633+
634+
JWT validation fetches public keys from your authorization server's JWKS endpoint and validates tokens locally. This is the fastest option as it doesn't require a round-trip to the auth server for each request.
635+
636+
```typescript
637+
import { MCPServer, OAuthAuthProvider } from "mcp-framework";
638+
639+
const server = new MCPServer({
640+
transport: {
641+
type: "http-stream",
642+
options: {
643+
port: 8080,
644+
auth: {
645+
provider: new OAuthAuthProvider({
646+
authorizationServers: [
647+
process.env.OAUTH_AUTHORIZATION_SERVER
648+
],
649+
resource: process.env.OAUTH_RESOURCE,
650+
validation: {
651+
type: 'jwt',
652+
jwksUri: process.env.OAUTH_JWKS_URI,
653+
audience: process.env.OAUTH_AUDIENCE,
654+
issuer: process.env.OAUTH_ISSUER,
655+
algorithms: ['RS256', 'ES256'] // Optional (default: ['RS256', 'ES256'])
656+
}
657+
}),
658+
endpoints: {
659+
initialize: true, // Protect session initialization
660+
messages: true // Protect MCP messages
661+
}
662+
}
663+
}
664+
}
665+
});
666+
```
667+
668+
**Environment Variables:**
669+
```bash
670+
OAUTH_AUTHORIZATION_SERVER=https://auth.example.com
671+
OAUTH_RESOURCE=https://mcp.example.com
672+
OAUTH_JWKS_URI=https://auth.example.com/.well-known/jwks.json
673+
OAUTH_AUDIENCE=https://mcp.example.com
674+
OAUTH_ISSUER=https://auth.example.com
675+
```
676+
677+
#### Token Introspection (Recommended for Centralized Control)
678+
679+
Token introspection validates tokens by calling your authorization server's introspection endpoint. This provides centralized control and is useful when you need real-time token revocation.
680+
681+
```typescript
682+
import { MCPServer, OAuthAuthProvider } from "mcp-framework";
683+
684+
const server = new MCPServer({
685+
transport: {
686+
type: "sse",
687+
options: {
688+
auth: {
689+
provider: new OAuthAuthProvider({
690+
authorizationServers: [
691+
process.env.OAUTH_AUTHORIZATION_SERVER
692+
],
693+
resource: process.env.OAUTH_RESOURCE,
694+
validation: {
695+
type: 'introspection',
696+
audience: process.env.OAUTH_AUDIENCE,
697+
issuer: process.env.OAUTH_ISSUER,
698+
introspection: {
699+
endpoint: process.env.OAUTH_INTROSPECTION_ENDPOINT,
700+
clientId: process.env.OAUTH_CLIENT_ID,
701+
clientSecret: process.env.OAUTH_CLIENT_SECRET
702+
}
703+
}
704+
})
705+
}
706+
}
707+
}
708+
});
709+
```
710+
711+
**Environment Variables:**
712+
```bash
713+
OAUTH_AUTHORIZATION_SERVER=https://auth.example.com
714+
OAUTH_RESOURCE=https://mcp.example.com
715+
OAUTH_AUDIENCE=https://mcp.example.com
716+
OAUTH_ISSUER=https://auth.example.com
717+
OAUTH_INTROSPECTION_ENDPOINT=https://auth.example.com/oauth/introspect
718+
OAUTH_CLIENT_ID=mcp-server
719+
OAUTH_CLIENT_SECRET=your-client-secret
720+
```
721+
722+
#### OAuth Features
723+
724+
- **RFC 9728 Compliance**: Automatic Protected Resource Metadata endpoint at `/.well-known/oauth-protected-resource`
725+
- **RFC 6750 WWW-Authenticate Headers**: Proper OAuth error responses with challenge headers
726+
- **JWKS Key Caching**: Public keys cached for 15 minutes (configurable)
727+
- **Token Introspection Caching**: Introspection results cached for 5 minutes (configurable)
728+
- **Security**: Tokens in query strings are automatically rejected
729+
- **Claims Extraction**: Access token claims in your tool handlers via `AuthResult`
730+
731+
#### Popular OAuth Providers
732+
733+
The OAuth provider works with any RFC-compliant OAuth 2.1 authorization server:
734+
735+
- **Auth0**: Use your Auth0 tenant's JWKS URI and issuer
736+
- **Okta**: Use your Okta authorization server configuration
737+
- **AWS Cognito**: Use your Cognito user pool's JWKS endpoint
738+
- **Azure AD / Entra ID**: Use Microsoft Entra ID endpoints
739+
- **Custom**: Any OAuth 2.1 compliant authorization server
740+
741+
For detailed setup guides with specific providers, see the [OAuth Setup Guide](#oauth-setup-guide).
742+
743+
#### Client Usage
744+
745+
Clients must include a valid OAuth access token in the Authorization header:
746+
747+
```bash
748+
# Make a request with OAuth token
749+
curl -X POST http://localhost:8080/mcp \
750+
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
751+
-H "Content-Type: application/json" \
752+
-d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
753+
754+
# Discover OAuth configuration
755+
curl http://localhost:8080/.well-known/oauth-protected-resource
756+
```
757+
758+
#### Security Best Practices
759+
760+
- **Always use HTTPS in production** - OAuth tokens should never be transmitted over unencrypted connections
761+
- **Validate audience claims** - Prevents token reuse across different services
762+
- **Use short-lived tokens** - Reduces risk if tokens are compromised
763+
- **Enable token introspection caching** - Reduces load on authorization server while maintaining security
764+
- **Monitor token errors** - Track failed authentication attempts for security insights
765+
626766
### Custom Authentication
627767
628768
You can implement your own authentication provider by implementing the `AuthProvider` interface:

0 commit comments

Comments
 (0)