-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathindex.ts
More file actions
112 lines (96 loc) · 4.06 KB
/
index.ts
File metadata and controls
112 lines (96 loc) · 4.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/**
* MCP Module - Self-contained MCP server module
*
* This module provides all MCP protocol functionality (tools, resources, prompts).
* It depends only on the ITokenValidator interface for authentication, not on
* any specific auth implementation.
*
* This clean separation means the MCP module works identically whether auth
* is internal (in-process) or external (HTTP).
*/
import { Router, Request, Response, NextFunction } from 'express';
import cors from 'cors';
import path from 'path';
import { fileURLToPath } from 'url';
import rateLimit from 'express-rate-limit';
import { BearerAuthMiddlewareOptions, requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';
import { getOAuthProtectedResourceMetadataUrl } from '@modelcontextprotocol/sdk/server/auth/router.js';
import { ITokenValidator } from '../../interfaces/auth-validator.js';
import { handleStreamableHTTP } from './handlers/shttp.js';
import { handleMessage, handleSSEConnection } from './handlers/sse.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export interface MCPConfig {
baseUri: string;
redisUrl?: string;
}
export class MCPModule {
private router: Router;
constructor(
private config: MCPConfig,
private tokenValidator: ITokenValidator
) {
this.router = this.setupRouter();
}
/**
* Get Express router with all MCP endpoints
*/
getRouter(): Router {
return this.router;
}
private setupRouter(): Router {
const router = Router();
// Rate limiter for static assets
const staticAssetLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 500, // 500 requests per minute for static assets
message: 'Too many requests for static assets',
standardHeaders: true,
legacyHeaders: false,
});
// CORS configuration for MCP endpoints
// Intentionally permissive CORS for public MCP reference server
// This allows any MCP client to test against this reference implementation
const corsOptions = {
origin: true, // Allow any origin
methods: ['GET', 'POST', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization', 'Mcp-Protocol-Version', 'Mcp-Protocol-Id'],
exposedHeaders: ['Mcp-Protocol-Version', 'Mcp-Protocol-Id'],
credentials: true
};
// Security headers for MCP endpoints
const securityHeaders = (req: Request, res: Response, next: NextFunction) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
next();
};
// SSE-specific headers
const sseHeaders = (req: Request, res: Response, next: NextFunction) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-store, max-age=0');
res.setHeader('Connection', 'keep-alive');
next();
};
// Bearer auth middleware using our token validator
// This works the same whether the validator is internal or external
const bearerAuthOptions: BearerAuthMiddlewareOptions = {
verifier: this.tokenValidator,
resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(new URL(this.config.baseUri))
};
const bearerAuth = requireBearerAuth(bearerAuthOptions);
// MCP endpoints - Streamable HTTP transport (recommended)
router.get('/mcp', cors(corsOptions), bearerAuth, securityHeaders, handleStreamableHTTP);
router.post('/mcp', cors(corsOptions), bearerAuth, securityHeaders, handleStreamableHTTP);
router.delete('/mcp', cors(corsOptions), bearerAuth, securityHeaders, handleStreamableHTTP);
// MCP endpoints - SSE transport (legacy)
router.get('/sse', cors(corsOptions), bearerAuth, sseHeaders, handleSSEConnection);
router.post('/message', cors(corsOptions), bearerAuth, securityHeaders, handleMessage);
// Static files for MCP
router.get('/styles.css', staticAssetLimiter, (req, res) => {
const cssPath = path.join(__dirname, '../../static', 'styles.css');
res.setHeader('Content-Type', 'text/css');
res.sendFile(cssPath);
});
return router;
}
}