agent-planner-mcp 0.3.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_GUIDE.md +257 -0
- package/LICENSE +21 -0
- package/README.md +128 -247
- package/SKILL.md +438 -0
- package/package.json +24 -7
- package/src/api-client.js +506 -115
- package/src/index.js +60 -27
- package/src/integrations/search-integration.js +3 -5
- package/src/server-http.js +569 -0
- package/src/session-manager.js +223 -0
- package/src/setup.js +1 -1
- package/src/tools/search-wrapper.js +12 -6
- package/src/tools.js +1983 -159
- package/claude-code/AUTONOMOUS_EXECUTION_GUIDE.md +0 -335
- package/claude-code/commands/README.md +0 -112
- package/claude-code/commands/create-plan.md +0 -174
- package/claude-code/commands/execute-plan.md +0 -202
- package/claude-code/commands/plan-status.md +0 -145
- package/claude-code/settings.template.json +0 -12
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP HTTP/SSE Server
|
|
3
|
+
*
|
|
4
|
+
* Implements the MCP Streamable HTTP transport specification (2025-06-18)
|
|
5
|
+
* https://modelcontextprotocol.io/specification/2025-06-18/basic/transports
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Single endpoint supporting POST and GET methods
|
|
9
|
+
* - Session management via Mcp-Session-Id header
|
|
10
|
+
* - Server-Sent Events (SSE) for streaming responses
|
|
11
|
+
* - JSON-RPC 2.0 protocol
|
|
12
|
+
* - Origin validation for security
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const express = require('express');
|
|
16
|
+
const { SessionManager } = require('./session-manager');
|
|
17
|
+
const { setupTools } = require('./tools');
|
|
18
|
+
const { createApiClient } = require('./api-client');
|
|
19
|
+
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
20
|
+
const { version } = require('../package.json');
|
|
21
|
+
require('dotenv').config();
|
|
22
|
+
|
|
23
|
+
// MCP Protocol Version
|
|
24
|
+
const MCP_PROTOCOL_VERSION = '2025-03-26';
|
|
25
|
+
|
|
26
|
+
class MCPHTTPServer {
|
|
27
|
+
constructor(options = {}) {
|
|
28
|
+
this.port = options.port || process.env.PORT || 3100;
|
|
29
|
+
this.host = options.host || process.env.HOST || '127.0.0.1';
|
|
30
|
+
|
|
31
|
+
// Session manager
|
|
32
|
+
this.sessionManager = new SessionManager({
|
|
33
|
+
sessionTimeout: options.sessionTimeout || 30 * 60 * 1000,
|
|
34
|
+
cleanupInterval: options.cleanupInterval || 5 * 60 * 1000
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Store for pending SSE streams per session
|
|
38
|
+
this.sseStreams = new Map(); // sessionId -> { res, req }
|
|
39
|
+
|
|
40
|
+
// Create Express app
|
|
41
|
+
this.app = express();
|
|
42
|
+
|
|
43
|
+
// Setup middleware and routes
|
|
44
|
+
this.setupMiddleware();
|
|
45
|
+
this.setupRoutes();
|
|
46
|
+
|
|
47
|
+
console.error('MCPHTTPServer initialized');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Setup Express middleware
|
|
52
|
+
*/
|
|
53
|
+
setupMiddleware() {
|
|
54
|
+
// Parse JSON bodies
|
|
55
|
+
this.app.use(express.json());
|
|
56
|
+
|
|
57
|
+
// Logging middleware
|
|
58
|
+
this.app.use((req, res, next) => {
|
|
59
|
+
console.error(`${req.method} ${req.path} - ${req.get('MCP-Protocol-Version') || 'no version'}`);
|
|
60
|
+
next();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Protocol version validation
|
|
64
|
+
this.app.use((req, res, next) => {
|
|
65
|
+
// Skip version check for health and discovery endpoints
|
|
66
|
+
if (req.path === '/health' || req.path === '/.well-known/mcp.json') {
|
|
67
|
+
return next();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const version = req.get('MCP-Protocol-Version');
|
|
71
|
+
|
|
72
|
+
// Backwards compatibility: assume 2025-03-26 if not provided
|
|
73
|
+
if (!version) {
|
|
74
|
+
req.mcpVersion = '2025-03-26';
|
|
75
|
+
return next();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
req.mcpVersion = version;
|
|
79
|
+
next();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Authentication — require Authorization header on /mcp
|
|
83
|
+
this.app.use((req, res, next) => {
|
|
84
|
+
if (req.path === '/health' || req.path === '/.well-known/mcp.json') return next();
|
|
85
|
+
|
|
86
|
+
const authHeader = req.get('Authorization');
|
|
87
|
+
if (!authHeader) {
|
|
88
|
+
return res.status(401).json({
|
|
89
|
+
jsonrpc: '2.0',
|
|
90
|
+
error: { code: -32000, message: 'Authorization header required. Use "Authorization: Bearer <token>" or "Authorization: ApiKey <token>".' }
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const parts = authHeader.split(' ');
|
|
95
|
+
if (parts.length !== 2 || !['Bearer', 'ApiKey'].includes(parts[0])) {
|
|
96
|
+
return res.status(401).json({
|
|
97
|
+
jsonrpc: '2.0',
|
|
98
|
+
error: { code: -32000, message: 'Invalid Authorization format. Use "Bearer <token>" or "ApiKey <token>".' }
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Store the raw token for per-session API client creation
|
|
103
|
+
req.userToken = parts[1];
|
|
104
|
+
next();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Origin validation for security (DNS rebinding protection)
|
|
108
|
+
this.app.use((req, res, next) => {
|
|
109
|
+
// Skip origin check for health and discovery endpoints
|
|
110
|
+
if (req.path === '/health' || req.path === '/.well-known/mcp.json') {
|
|
111
|
+
return next();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Skip origin check when running on 0.0.0.0 (production container behind nginx)
|
|
115
|
+
// Origin validation is DNS rebinding protection, not relevant for server-to-server MCP
|
|
116
|
+
if (this.host === '0.0.0.0') return next();
|
|
117
|
+
|
|
118
|
+
const origin = req.get('Origin');
|
|
119
|
+
|
|
120
|
+
// If Origin header is present, validate it
|
|
121
|
+
if (origin) {
|
|
122
|
+
// Accept localhost and production origins
|
|
123
|
+
const allowedOrigins = [
|
|
124
|
+
'http://localhost',
|
|
125
|
+
'http://127.0.0.1',
|
|
126
|
+
`http://localhost:${this.port}`,
|
|
127
|
+
`http://127.0.0.1:${this.port}`,
|
|
128
|
+
'https://agentplanner.io'
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
const isAllowed = allowedOrigins.some(allowed => origin.startsWith(allowed));
|
|
132
|
+
|
|
133
|
+
if (!isAllowed) {
|
|
134
|
+
console.error(`Rejected request from origin: ${origin}`);
|
|
135
|
+
return res.status(403).json({
|
|
136
|
+
jsonrpc: '2.0',
|
|
137
|
+
error: {
|
|
138
|
+
code: -32000,
|
|
139
|
+
message: 'Forbidden origin'
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
next();
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Setup Express routes
|
|
151
|
+
*/
|
|
152
|
+
setupRoutes() {
|
|
153
|
+
// MCP discovery endpoint (no auth required)
|
|
154
|
+
this.app.get('/.well-known/mcp.json', (req, res) => {
|
|
155
|
+
res.json({
|
|
156
|
+
mcp_version: '2025-03-26',
|
|
157
|
+
server: {
|
|
158
|
+
name: 'agent-planner-mcp',
|
|
159
|
+
version,
|
|
160
|
+
description: 'AI agent orchestration with planning, dependencies, knowledge graphs, and human oversight'
|
|
161
|
+
},
|
|
162
|
+
endpoints: { mcp: '/mcp' },
|
|
163
|
+
authentication: {
|
|
164
|
+
type: 'api_key',
|
|
165
|
+
header: 'Authorization',
|
|
166
|
+
format: 'ApiKey <token>'
|
|
167
|
+
},
|
|
168
|
+
capabilities: { tools: true }
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Health check endpoint
|
|
173
|
+
this.app.get('/health', (req, res) => {
|
|
174
|
+
const stats = this.sessionManager.getStats();
|
|
175
|
+
res.json({
|
|
176
|
+
status: 'ok',
|
|
177
|
+
version: MCP_PROTOCOL_VERSION,
|
|
178
|
+
server: {
|
|
179
|
+
name: process.env.MCP_SERVER_NAME || 'planning-tools',
|
|
180
|
+
version: process.env.MCP_SERVER_VERSION || version
|
|
181
|
+
},
|
|
182
|
+
sessions: {
|
|
183
|
+
total: stats.total,
|
|
184
|
+
initialized: stats.initialized
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Main MCP endpoint - handles both POST and GET
|
|
190
|
+
this.app.post('/mcp', this.handleMCPPost.bind(this));
|
|
191
|
+
this.app.get('/mcp', this.handleMCPGet.bind(this));
|
|
192
|
+
|
|
193
|
+
// Session termination endpoint
|
|
194
|
+
this.app.delete('/mcp', this.handleMCPDelete.bind(this));
|
|
195
|
+
|
|
196
|
+
// 404 handler
|
|
197
|
+
this.app.use((req, res) => {
|
|
198
|
+
res.status(404).json({
|
|
199
|
+
jsonrpc: '2.0',
|
|
200
|
+
error: {
|
|
201
|
+
code: -32000,
|
|
202
|
+
message: 'Not found'
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Error handler
|
|
208
|
+
this.app.use((err, req, res, next) => {
|
|
209
|
+
console.error('Express error:', err);
|
|
210
|
+
res.status(500).json({
|
|
211
|
+
jsonrpc: '2.0',
|
|
212
|
+
error: {
|
|
213
|
+
code: -32603,
|
|
214
|
+
message: 'Internal server error',
|
|
215
|
+
data: err.message
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Handle POST requests (client-to-server messages)
|
|
223
|
+
*/
|
|
224
|
+
async handleMCPPost(req, res) {
|
|
225
|
+
try {
|
|
226
|
+
// Get or create session
|
|
227
|
+
let sessionId = req.get('Mcp-Session-Id');
|
|
228
|
+
let session = sessionId ? this.sessionManager.getSession(sessionId) : null;
|
|
229
|
+
|
|
230
|
+
// Validate session exists if session ID provided
|
|
231
|
+
if (sessionId && !session) {
|
|
232
|
+
return res.status(404).json({
|
|
233
|
+
jsonrpc: '2.0',
|
|
234
|
+
error: {
|
|
235
|
+
code: -32000,
|
|
236
|
+
message: 'Session not found'
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Parse JSON-RPC message
|
|
242
|
+
const message = req.body;
|
|
243
|
+
|
|
244
|
+
if (!message || !message.jsonrpc || message.jsonrpc !== '2.0') {
|
|
245
|
+
return res.status(400).json({
|
|
246
|
+
jsonrpc: '2.0',
|
|
247
|
+
error: {
|
|
248
|
+
code: -32600,
|
|
249
|
+
message: 'Invalid JSON-RPC request'
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Handle different message types
|
|
255
|
+
const isRequest = message.method && message.id !== undefined;
|
|
256
|
+
const isNotification = message.method && message.id === undefined;
|
|
257
|
+
const isResponse = message.result !== undefined || message.error !== undefined;
|
|
258
|
+
|
|
259
|
+
if (isNotification || isResponse) {
|
|
260
|
+
// For notifications and responses, return 202 Accepted
|
|
261
|
+
return res.status(202).send();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (isRequest) {
|
|
265
|
+
// Handle JSON-RPC request
|
|
266
|
+
const response = await this.handleRequest(message, session, sessionId, req.userToken);
|
|
267
|
+
|
|
268
|
+
// If this is an initialize request, create session and include session ID
|
|
269
|
+
if (message.method === 'initialize' && response.result) {
|
|
270
|
+
sessionId = this.sessionManager.createSession();
|
|
271
|
+
this.sessionManager.initializeSession(sessionId, message.params?.capabilities);
|
|
272
|
+
|
|
273
|
+
// Create a per-session API client bound to this user's token
|
|
274
|
+
const sessionApiClient = createApiClient(req.userToken);
|
|
275
|
+
this.sessionManager.setApiClient(sessionId, sessionApiClient);
|
|
276
|
+
|
|
277
|
+
// Set session ID header in response
|
|
278
|
+
res.setHeader('Mcp-Session-Id', sessionId);
|
|
279
|
+
|
|
280
|
+
console.error(`Session initialized: ${sessionId} (per-user token)`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Check if we should stream the response via SSE
|
|
284
|
+
const acceptHeader = req.get('Accept') || '';
|
|
285
|
+
const supportsSSE = acceptHeader.includes('text/event-stream');
|
|
286
|
+
|
|
287
|
+
// For now, we'll send simple JSON responses
|
|
288
|
+
// SSE streaming can be added later for long-running operations
|
|
289
|
+
if (supportsSSE && this.shouldStreamResponse(message)) {
|
|
290
|
+
// Send SSE stream
|
|
291
|
+
return this.streamResponse(req, res, response, sessionId);
|
|
292
|
+
} else {
|
|
293
|
+
// Send simple JSON response
|
|
294
|
+
res.setHeader('Content-Type', 'application/json');
|
|
295
|
+
return res.json(response);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Unknown message type
|
|
300
|
+
return res.status(400).json({
|
|
301
|
+
jsonrpc: '2.0',
|
|
302
|
+
error: {
|
|
303
|
+
code: -32600,
|
|
304
|
+
message: 'Invalid request'
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
} catch (error) {
|
|
308
|
+
console.error('Error handling POST request:', error);
|
|
309
|
+
return res.status(500).json({
|
|
310
|
+
jsonrpc: '2.0',
|
|
311
|
+
error: {
|
|
312
|
+
code: -32603,
|
|
313
|
+
message: 'Internal error',
|
|
314
|
+
data: error.message
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Handle GET requests (SSE streams for server-to-client messages)
|
|
322
|
+
*/
|
|
323
|
+
handleMCPGet(req, res) {
|
|
324
|
+
try {
|
|
325
|
+
// Validate Accept header
|
|
326
|
+
const acceptHeader = req.get('Accept') || '';
|
|
327
|
+
if (!acceptHeader.includes('text/event-stream')) {
|
|
328
|
+
return res.status(405).json({
|
|
329
|
+
jsonrpc: '2.0',
|
|
330
|
+
error: {
|
|
331
|
+
code: -32000,
|
|
332
|
+
message: 'Method not allowed. GET requires Accept: text/event-stream'
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Get session
|
|
338
|
+
const sessionId = req.get('Mcp-Session-Id');
|
|
339
|
+
if (!sessionId) {
|
|
340
|
+
return res.status(400).json({
|
|
341
|
+
jsonrpc: '2.0',
|
|
342
|
+
error: {
|
|
343
|
+
code: -32000,
|
|
344
|
+
message: 'Mcp-Session-Id header required'
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const session = this.sessionManager.getSession(sessionId);
|
|
350
|
+
if (!session) {
|
|
351
|
+
return res.status(404).json({
|
|
352
|
+
jsonrpc: '2.0',
|
|
353
|
+
error: {
|
|
354
|
+
code: -32000,
|
|
355
|
+
message: 'Session not found'
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Setup SSE stream
|
|
361
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
362
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
363
|
+
res.setHeader('Connection', 'keep-alive');
|
|
364
|
+
res.setHeader('X-Accel-Buffering', 'no'); // Disable buffering in nginx
|
|
365
|
+
|
|
366
|
+
// Store SSE stream for this session
|
|
367
|
+
this.sseStreams.set(sessionId, { res, req });
|
|
368
|
+
|
|
369
|
+
console.error(`SSE stream opened for session: ${sessionId}`);
|
|
370
|
+
|
|
371
|
+
// Send initial comment to establish connection
|
|
372
|
+
res.write(': connected\n\n');
|
|
373
|
+
|
|
374
|
+
// Handle client disconnect
|
|
375
|
+
req.on('close', () => {
|
|
376
|
+
console.error(`SSE stream closed for session: ${sessionId}`);
|
|
377
|
+
this.sseStreams.delete(sessionId);
|
|
378
|
+
});
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.error('Error handling GET request:', error);
|
|
381
|
+
res.status(500).json({
|
|
382
|
+
jsonrpc: '2.0',
|
|
383
|
+
error: {
|
|
384
|
+
code: -32603,
|
|
385
|
+
message: 'Internal error',
|
|
386
|
+
data: error.message
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Handle DELETE requests (session termination)
|
|
394
|
+
*/
|
|
395
|
+
handleMCPDelete(req, res) {
|
|
396
|
+
const sessionId = req.get('Mcp-Session-Id');
|
|
397
|
+
|
|
398
|
+
if (!sessionId) {
|
|
399
|
+
return res.status(400).json({
|
|
400
|
+
jsonrpc: '2.0',
|
|
401
|
+
error: {
|
|
402
|
+
code: -32000,
|
|
403
|
+
message: 'Mcp-Session-Id header required'
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Close any SSE streams for this session
|
|
409
|
+
const stream = this.sseStreams.get(sessionId);
|
|
410
|
+
if (stream) {
|
|
411
|
+
stream.res.end();
|
|
412
|
+
this.sseStreams.delete(sessionId);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Delete session
|
|
416
|
+
const deleted = this.sessionManager.deleteSession(sessionId);
|
|
417
|
+
|
|
418
|
+
if (deleted) {
|
|
419
|
+
return res.status(204).send();
|
|
420
|
+
} else {
|
|
421
|
+
return res.status(404).json({
|
|
422
|
+
jsonrpc: '2.0',
|
|
423
|
+
error: {
|
|
424
|
+
code: -32000,
|
|
425
|
+
message: 'Session not found'
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Handle JSON-RPC request
|
|
433
|
+
*/
|
|
434
|
+
async handleRequest(message, session, sessionId, userToken) {
|
|
435
|
+
// Create MCP server instance for this request
|
|
436
|
+
const mcpServer = new Server({
|
|
437
|
+
name: process.env.MCP_SERVER_NAME || 'planning-tools',
|
|
438
|
+
version: process.env.MCP_SERVER_VERSION || version
|
|
439
|
+
}, {
|
|
440
|
+
capabilities: {
|
|
441
|
+
tools: {}
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Get per-session API client (bound to user's token), or create one for initialize requests
|
|
446
|
+
const sessionApiClient = session
|
|
447
|
+
? this.sessionManager.getApiClient(sessionId)
|
|
448
|
+
: (userToken ? createApiClient(userToken) : null);
|
|
449
|
+
|
|
450
|
+
// Setup tools with the per-session API client
|
|
451
|
+
setupTools(mcpServer, sessionApiClient);
|
|
452
|
+
|
|
453
|
+
// Process the request through MCP server
|
|
454
|
+
try {
|
|
455
|
+
// Get the appropriate request handler
|
|
456
|
+
const handlers = mcpServer._requestHandlers;
|
|
457
|
+
const handler = handlers.get(message.method);
|
|
458
|
+
|
|
459
|
+
if (!handler) {
|
|
460
|
+
return {
|
|
461
|
+
jsonrpc: '2.0',
|
|
462
|
+
id: message.id,
|
|
463
|
+
error: {
|
|
464
|
+
code: -32601,
|
|
465
|
+
message: `Method not found: ${message.method}`
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Call the handler with the full request format expected by SDK
|
|
471
|
+
const result = await handler(message);
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
jsonrpc: '2.0',
|
|
475
|
+
id: message.id,
|
|
476
|
+
result
|
|
477
|
+
};
|
|
478
|
+
} catch (error) {
|
|
479
|
+
console.error(`Error handling method ${message.method}:`, error);
|
|
480
|
+
|
|
481
|
+
return {
|
|
482
|
+
jsonrpc: '2.0',
|
|
483
|
+
id: message.id,
|
|
484
|
+
error: {
|
|
485
|
+
code: -32603,
|
|
486
|
+
message: 'Internal error',
|
|
487
|
+
data: error.message
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Determine if response should be streamed via SSE
|
|
495
|
+
*/
|
|
496
|
+
shouldStreamResponse(message) {
|
|
497
|
+
// For now, we don't need streaming for planning tools
|
|
498
|
+
// All operations are relatively quick
|
|
499
|
+
// This can be enabled later for long-running operations
|
|
500
|
+
return false;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Stream response via SSE
|
|
505
|
+
*/
|
|
506
|
+
streamResponse(req, res, response, sessionId) {
|
|
507
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
508
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
509
|
+
res.setHeader('Connection', 'keep-alive');
|
|
510
|
+
|
|
511
|
+
// Send the response as an SSE event
|
|
512
|
+
res.write(`data: ${JSON.stringify(response)}\n\n`);
|
|
513
|
+
|
|
514
|
+
// Close the stream
|
|
515
|
+
res.end();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Start the HTTP server
|
|
520
|
+
*/
|
|
521
|
+
async start() {
|
|
522
|
+
return new Promise((resolve, reject) => {
|
|
523
|
+
try {
|
|
524
|
+
this.server = this.app.listen(this.port, this.host, () => {
|
|
525
|
+
console.error(`MCP HTTP Server listening on ${this.host}:${this.port}`);
|
|
526
|
+
console.error(`MCP endpoint: http://${this.host}:${this.port}/mcp`);
|
|
527
|
+
console.error(`Health check: http://${this.host}:${this.port}/health`);
|
|
528
|
+
console.error(`Protocol version: ${MCP_PROTOCOL_VERSION}`);
|
|
529
|
+
resolve();
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
this.server.on('error', (error) => {
|
|
533
|
+
console.error('Server error:', error);
|
|
534
|
+
reject(error);
|
|
535
|
+
});
|
|
536
|
+
} catch (error) {
|
|
537
|
+
reject(error);
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Stop the HTTP server
|
|
544
|
+
*/
|
|
545
|
+
async stop() {
|
|
546
|
+
return new Promise((resolve) => {
|
|
547
|
+
// Close all SSE streams
|
|
548
|
+
for (const [sessionId, stream] of this.sseStreams.entries()) {
|
|
549
|
+
stream.res.end();
|
|
550
|
+
}
|
|
551
|
+
this.sseStreams.clear();
|
|
552
|
+
|
|
553
|
+
// Destroy session manager
|
|
554
|
+
this.sessionManager.destroy();
|
|
555
|
+
|
|
556
|
+
// Close HTTP server
|
|
557
|
+
if (this.server) {
|
|
558
|
+
this.server.close(() => {
|
|
559
|
+
console.error('MCP HTTP Server stopped');
|
|
560
|
+
resolve();
|
|
561
|
+
});
|
|
562
|
+
} else {
|
|
563
|
+
resolve();
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
module.exports = { MCPHTTPServer };
|