@fastmcp-oauth/rest-api-delegation
Version:
REST API delegation module for FastMCP OAuth framework - HTTP/JSON API integration with token exchange support
388 lines (293 loc) • 10.6 kB
Markdown
# @fastmcp-oauth/rest-api-delegation
REST API delegation module for the MCP OAuth framework - provides HTTP/JSON API integration with token exchange support.
## Overview
This package provides a **production-ready delegation module** for integrating MCP tools with external REST APIs. It's the most common use case for the MCP OAuth framework, enabling AI agents to securely interact with backend services.
### Key Features
- ✅ **Token Exchange Support** - Exchange requestor JWT for API-specific tokens
- ✅ **API Key Fallback** - Use static API keys when token exchange unavailable
- ✅ **Comprehensive Error Handling** - Graceful degradation and detailed audit logging
- ✅ **Timeout Support** - Configurable request timeouts
- ✅ **Custom Headers** - Add default headers to all requests
- ✅ **Multiple HTTP Methods** - GET, POST, PUT, PATCH, DELETE support
- ✅ **Session Context Propagation** - Automatic user ID and role headers
## Installation
```bash
npm install @fastmcp-oauth/rest-api-delegation
```
This package is an **optional** dependency of `fastmcp-oauth`. The core framework works without REST API support.
## Quick Start
### Basic Usage with Token Exchange
```typescript
import { RestAPIDelegationModule } from '@fastmcp-oauth/rest-api-delegation';
import { FastMCPOAuthServer } from 'fastmcp-oauth';
// Create server
const server = new FastMCPOAuthServer({
configPath: './config.json'
});
// Get core context
const coreContext = server.getCoreContext();
// Create and register REST API module
const restApiModule = new RestAPIDelegationModule();
await restApiModule.initialize({
baseUrl: 'https://api.example.com',
useTokenExchange: true,
tokenExchangeAudience: 'urn:api:example'
});
coreContext.delegationRegistry.register(restApiModule);
```
### Using API Key (Fallback)
```typescript
await restApiModule.initialize({
baseUrl: 'https://api.example.com',
useTokenExchange: false,
apiKey: process.env.API_KEY
});
```
### Creating MCP Tools
Use the `createDelegationTool()` factory to create OAuth-secured tools in 5 lines:
```typescript
import { createDelegationTool } from 'fastmcp-oauth';
import { z } from 'zod';
// Tool 1: Get user profile
const getUserProfileTool = createDelegationTool('rest-api', {
name: 'get-user-profile',
description: 'Get user profile from backend API',
parameters: z.object({
userId: z.string().describe('User ID to fetch')
}),
action: 'users/profile',
requiredPermission: 'api:read',
// Transform params for API
transformParams: (params) => ({
endpoint: `users/${params.userId}/profile`,
method: 'GET'
}),
// Transform API response for LLM
transformResult: (apiResponse: any) => ({
displayName: apiResponse.fullName,
email: apiResponse.email,
department: apiResponse.department
// Hide sensitive fields
})
}, coreContext);
// Tool 2: Update user settings
const updateUserSettingsTool = createDelegationTool('rest-api', {
name: 'update-user-settings',
description: 'Update user settings',
parameters: z.object({
userId: z.string(),
settings: z.record(z.any())
}),
action: 'users/settings',
requiredPermission: 'api:write',
transformParams: (params) => ({
endpoint: `users/${params.userId}/settings`,
method: 'PUT',
data: params.settings
})
}, coreContext);
// Register tools
server.registerTools([getUserProfileTool, updateUserSettingsTool]);
```
## Configuration
### RestAPIConfig Interface
```typescript
interface RestAPIConfig {
/** Base URL of the REST API (e.g., 'https://api.example.com') */
baseUrl: string;
/** Optional API key for fallback authentication */
apiKey?: string;
/** Whether to use token exchange for authentication */
useTokenExchange: boolean;
/** Audience for token exchange requests (default: 'urn:api:rest') */
tokenExchangeAudience?: string;
/** Optional OAuth scopes to request during token exchange (space-separated) */
scope?: string;
/** Optional default request timeout in milliseconds */
timeout?: number;
/** Optional custom headers to include in all requests */
defaultHeaders?: Record<string, string>;
}
```
### Example Configuration
```typescript
await restApiModule.initialize({
baseUrl: 'https://api.example.com',
useTokenExchange: true,
tokenExchangeAudience: 'urn:api:example',
scope: 'openid profile api:read api:write', // Request specific OAuth scopes
timeout: 30000, // 30 seconds
defaultHeaders: {
'X-API-Version': 'v2',
'X-Client-ID': 'mcp-server'
}
});
```
**OAuth Scope Support:**
- Request fine-grained permissions during token exchange
- Example scopes: `api:read`, `api:write`, `api:admin`
- IDP determines which scopes to grant based on user roles
- Enables least-privilege access patterns
## Token Exchange Flow
When `useTokenExchange: true`, the module performs RFC 8693 token exchange:
1. **Requestor JWT** - User's JWT from OAuth provider (e.g., Keycloak)
2. **Exchange** - Module exchanges requestor JWT for API-specific token at IDP
3. **Delegation Token (TE-JWT)** - IDP returns token scoped for your API
4. **API Request** - Module calls your API with `Authorization: Bearer <TE-JWT>`
**Benefits:**
- API receives tokens with correct audience binding
- IDP controls API permissions (privilege elevation/reduction)
- Centralized token revocation
- Cached tokens reduce IDP load by 90%
## API Request Parameters
The `delegate()` method accepts these parameters:
```typescript
const result = await coreContext.delegationRegistry.delegate(
'rest-api',
session,
'action-name',
{
// Optional: Override endpoint (default: uses action name)
endpoint: 'users/123/profile',
// Optional: HTTP method (default: 'POST')
method: 'GET',
// Optional: Request body data (for POST/PUT/PATCH)
data: { key: 'value' },
// Optional: Additional headers
headers: { 'X-Custom': 'header' }
}
);
```
## Security Features
### Automatic Headers
The module automatically adds:
- `Authorization: Bearer <token>` - Token exchange or API key
- `X-User-ID` - User ID from session
- `X-User-Role` - User role from session
- `Content-Type: application/json` - JSON content type
### Audit Logging
All requests are logged with:
- Timestamp
- User ID
- Action name
- HTTP method and endpoint
- Authentication method (token-exchange or api-key)
- Success/failure status
- Error details (if failed)
### Error Handling
- Network errors caught and returned as `DelegationResult`
- Timeout errors with clear messaging
- HTTP error responses with status code and body
- No sensitive data exposed in error messages
## Health Check
The module provides a health check endpoint:
```typescript
const healthy = await restApiModule.healthCheck();
if (!healthy) {
console.log('API is not responding');
}
```
Health check attempts `GET /health` with optional API key authentication.
## Complete Example
```typescript
import { FastMCPOAuthServer, createDelegationTool } from 'fastmcp-oauth';
import { RestAPIDelegationModule } from '@fastmcp-oauth/rest-api-delegation';
import { z } from 'zod';
async function main() {
// 1. Create server
const server = new FastMCPOAuthServer({
configPath: './config.json'
});
const coreContext = server.getCoreContext();
// 2. Create and register REST API module
const restApiModule = new RestAPIDelegationModule();
await restApiModule.initialize({
baseUrl: 'https://api.example.com',
useTokenExchange: true,
tokenExchangeAudience: 'urn:api:example',
timeout: 30000
});
coreContext.delegationRegistry.register(restApiModule);
// 3. Create tools
const getUserTool = createDelegationTool('rest-api', {
name: 'get-user',
description: 'Get user data',
parameters: z.object({
userId: z.string()
}),
action: 'users',
requiredPermission: 'api:read',
transformParams: (params) => ({
endpoint: `users/${params.userId}`,
method: 'GET'
})
}, coreContext);
// 4. Register tools
server.registerTools([getUserTool]);
// 5. Start server
await server.start({
transportType: 'httpStream',
httpStream: { port: 3000, endpoint: '/mcp' },
stateless: true
});
console.log('MCP OAuth Server running on http://localhost:3000/mcp');
}
main().catch(console.error);
```
## API Reference
### RestAPIDelegationModule
#### Methods
##### `initialize(config: RestAPIConfig): Promise<void>`
Initialize module with configuration.
##### `delegate<T>(session: UserSession, action: string, params: any, context?: { sessionId?: string; coreContext?: any }): Promise<DelegationResult<T>>`
Delegate operation to REST API.
##### `healthCheck(): Promise<boolean>`
Check if API is accessible.
##### `destroy(): Promise<void>`
Cleanup resources.
##### `setTokenExchangeService(service: any, config: any): void`
Set token exchange service (called by ConfigOrchestrator).
## Use Cases
### 1. AI Agent → Internal API
LLM agents querying internal REST APIs with user context
### 2. Multi-Service Orchestration
Coordinate calls to multiple REST APIs with single OAuth token
### 3. Legacy System Integration
Connect modern AI tools to legacy REST/SOAP services
### 4. Third-Party API Integration
Integrate with external SaaS APIs using token exchange
## Best Practices
1. **Use Token Exchange** - Preferred over API keys for production
2. **Set Timeouts** - Prevent hung requests (recommended: 30 seconds)
3. **Transform Results** - Hide sensitive data before returning to LLM
4. **Use Custom Headers** - Add versioning and client identification
5. **Health Checks** - Monitor API availability in production
6. **Cache Tokens** - Enable session-based token caching (81% latency reduction)
## Troubleshooting
### "No authentication method configured"
Either enable token exchange or provide an API key:
```typescript
apiKey: process.env.API_KEY,
useTokenExchange: false
```
### "TokenExchangeService not available"
Ensure token exchange is configured in your IDP settings:
```json
{
"trustedIDPs": [{
"tokenExchange": {
"tokenEndpoint": "https://idp.com/token",
"clientId": "mcp-server",
"clientSecret": "SECRET"
}
}]
}
```
### "Session missing access_token"
User session must include `access_token` claim for token exchange. Verify JWT contains access token.
## License
MIT
## Support
- **Documentation**: [MCP OAuth Framework Docs](../../README.md)
- **Issues**: [GitHub Issues](https://github.com/your-org/mcp-oauth/issues)
- **Extension Guide**: [Docs/EXTENDING.md](../../Docs/EXTENDING.md)