@goonnguyen/human-mcp 1.0.2 → 1.2.0

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.
@@ -0,0 +1,905 @@
1
+ # Implementation Plan: Adding Streamable HTTP Transport to Human MCP Server
2
+
3
+ ## Overview
4
+
5
+ This document outlines the comprehensive plan for adding Streamable HTTP transport support to the Human MCP server while maintaining backward compatibility with the existing stdio transport. The implementation will follow MCP specification version 2025-03-26 for Streamable HTTP transport.
6
+
7
+ ## Current State Analysis
8
+
9
+ ### Existing Architecture
10
+ - **Transport**: Currently only supports stdio transport via `StdioServerTransport`
11
+ - **Entry Point**: `src/index.ts` directly calls `startStdioServer()`
12
+ - **Server Creation**: `src/server.ts` contains `createServer()` and `startStdioServer()` functions
13
+ - **Configuration**: Environment-based config via `src/utils/config.ts`
14
+ - **Tools**: Two vision analysis tools (`eyes.analyze` and `eyes.compare`)
15
+ - **Dependencies**: Uses `@modelcontextprotocol/sdk` version 1.4.0
16
+
17
+ ### Key Findings
18
+ - No Express or HTTP server infrastructure exists
19
+ - Configuration already supports server settings (port, timeouts, security)
20
+ - Clean separation between server creation and transport initialization
21
+ - TypeScript with ESNext modules and Bun runtime
22
+
23
+ ## Requirements
24
+
25
+ ### Functional Requirements
26
+ 1. **Dual Transport Support**: Support both stdio and Streamable HTTP transports
27
+ 2. **Session Management**: Implement stateful session handling with resumability
28
+ 3. **SSE Support**: Enable Server-Sent Events for notifications
29
+ 4. **Backward Compatibility**: Maintain existing stdio functionality
30
+ 5. **Stateless Mode**: Support stateless operation for serverless deployments
31
+ 6. **Security**: Implement CORS, DNS rebinding protection, and optional authentication
32
+
33
+ ### Non-Functional Requirements
34
+ 1. **Performance**: Handle concurrent sessions efficiently
35
+ 2. **Scalability**: Support horizontal scaling with external session storage
36
+ 3. **Maintainability**: Clean code architecture with separation of concerns
37
+ 4. **Testing**: Comprehensive test coverage for all transport modes
38
+ 5. **Documentation**: Clear documentation for configuration and usage
39
+
40
+ ## Architecture Design
41
+
42
+ ### High-Level Architecture
43
+
44
+ ```mermaid
45
+ graph TB
46
+ subgraph "Client Layer"
47
+ C1[Stdio Client]
48
+ C2[HTTP Client]
49
+ C3[Legacy SSE Client]
50
+ end
51
+
52
+ subgraph "Transport Layer"
53
+ T1[Transport Manager]
54
+ T2[Stdio Transport]
55
+ T3[Streamable HTTP Transport]
56
+ T4[SSE Fallback]
57
+ end
58
+
59
+ subgraph "Server Layer"
60
+ S1[MCP Server Core]
61
+ S2[Session Manager]
62
+ S3[Event Store]
63
+ end
64
+
65
+ subgraph "Application Layer"
66
+ A1[Eyes Tools]
67
+ A2[Prompts]
68
+ A3[Resources]
69
+ end
70
+
71
+ C1 --> T2
72
+ C2 --> T3
73
+ C3 --> T4
74
+ T1 --> S1
75
+ T2 --> T1
76
+ T3 --> T1
77
+ T4 --> T1
78
+ S1 --> S2
79
+ S2 --> S3
80
+ S1 --> A1
81
+ S1 --> A2
82
+ S1 --> A3
83
+ ```
84
+
85
+ ### Component Design
86
+
87
+ #### 1. Transport Manager
88
+ - Handles transport selection based on startup mode
89
+ - Manages transport lifecycle
90
+ - Provides unified interface for different transports
91
+
92
+ #### 2. HTTP Server Module
93
+ - Express-based HTTP server
94
+ - Route handlers for MCP endpoints
95
+ - Middleware for security and logging
96
+
97
+ #### 3. Session Manager
98
+ - In-memory session storage (default)
99
+ - Interface for external storage adapters
100
+ - Session lifecycle management
101
+
102
+ #### 4. Security Module
103
+ - CORS configuration
104
+ - DNS rebinding protection
105
+ - Rate limiting
106
+ - Optional authentication
107
+
108
+ ## Implementation Approaches
109
+
110
+ ### Approach 1: Modular Transport System (Recommended)
111
+
112
+ **Description**: Create a modular transport system with pluggable transports and a unified startup mechanism.
113
+
114
+ **Pros**:
115
+ - Clean separation of concerns
116
+ - Easy to add new transports in the future
117
+ - Testable components
118
+ - Supports dynamic transport selection
119
+ - Better code organization
120
+
121
+ **Cons**:
122
+ - More initial setup complexity
123
+ - Requires refactoring existing code structure
124
+ - More files to manage
125
+
126
+ **Implementation Structure**:
127
+ ```
128
+ src/
129
+ ├── transports/
130
+ │ ├── index.ts # Transport manager
131
+ │ ├── stdio.ts # Stdio transport wrapper
132
+ │ ├── http/
133
+ │ │ ├── server.ts # Express server setup
134
+ │ │ ├── routes.ts # Route handlers
135
+ │ │ ├── middleware.ts # Security & logging
136
+ │ │ └── session.ts # Session management
137
+ │ └── types.ts # Transport interfaces
138
+ ├── server.ts # Refactored server creation
139
+ └── index.ts # Unified entry point
140
+ ```
141
+
142
+ ### Approach 2: Minimal Integration
143
+
144
+ **Description**: Add HTTP support directly in existing files with minimal structural changes.
145
+
146
+ **Pros**:
147
+ - Minimal changes to existing code
148
+ - Faster initial implementation
149
+ - Less file reorganization
150
+
151
+ **Cons**:
152
+ - Less maintainable long-term
153
+ - Harder to test components independently
154
+ - Mixed concerns in single files
155
+ - Limited extensibility
156
+
157
+ **Implementation Structure**:
158
+ ```
159
+ src/
160
+ ├── server.ts # Add HTTP functions here
161
+ ├── http.ts # All HTTP-related code
162
+ └── index.ts # Modified entry point
163
+ ```
164
+
165
+ ## Detailed Implementation Plan (Approach 1 - Recommended)
166
+
167
+ ### Phase 1: Foundation (Week 1)
168
+
169
+ #### 1.1 Install Dependencies
170
+ ```json
171
+ {
172
+ "dependencies": {
173
+ "express": "^4.21.0",
174
+ "cors": "^2.8.5",
175
+ "compression": "^1.7.4",
176
+ "helmet": "^7.1.0"
177
+ },
178
+ "devDependencies": {
179
+ "@types/express": "^4.17.21",
180
+ "@types/cors": "^2.8.17",
181
+ "@types/compression": "^1.7.5"
182
+ }
183
+ }
184
+ ```
185
+
186
+ #### 1.2 Create Transport Interfaces
187
+ **File**: `src/transports/types.ts`
188
+ ```typescript
189
+ export interface TransportConfig {
190
+ type: 'stdio' | 'http' | 'both';
191
+ http?: HttpTransportConfig;
192
+ }
193
+
194
+ export interface HttpTransportConfig {
195
+ port: number;
196
+ host?: string;
197
+ sessionMode: 'stateful' | 'stateless';
198
+ enableSse?: boolean;
199
+ enableJsonResponse?: boolean;
200
+ security?: SecurityConfig;
201
+ }
202
+
203
+ export interface SecurityConfig {
204
+ enableCors?: boolean;
205
+ corsOrigins?: string[];
206
+ enableDnsRebindingProtection?: boolean;
207
+ allowedHosts?: string[];
208
+ enableRateLimiting?: boolean;
209
+ secret?: string;
210
+ }
211
+
212
+ export interface SessionStore {
213
+ get(sessionId: string): Promise<TransportSession | null>;
214
+ set(sessionId: string, session: TransportSession): Promise<void>;
215
+ delete(sessionId: string): Promise<void>;
216
+ cleanup(): Promise<void>;
217
+ }
218
+ ```
219
+
220
+ #### 1.3 Create Transport Manager
221
+ **File**: `src/transports/index.ts`
222
+ ```typescript
223
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
224
+ import { startStdioTransport } from "./stdio.js";
225
+ import { startHttpTransport } from "./http/server.js";
226
+ import type { TransportConfig } from "./types.js";
227
+
228
+ export class TransportManager {
229
+ private server: McpServer;
230
+ private config: TransportConfig;
231
+
232
+ constructor(server: McpServer, config: TransportConfig) {
233
+ this.server = server;
234
+ this.config = config;
235
+ }
236
+
237
+ async start(): Promise<void> {
238
+ switch (this.config.type) {
239
+ case 'stdio':
240
+ await startStdioTransport(this.server);
241
+ break;
242
+ case 'http':
243
+ await startHttpTransport(this.server, this.config.http!);
244
+ break;
245
+ case 'both':
246
+ await Promise.all([
247
+ startStdioTransport(this.server),
248
+ startHttpTransport(this.server, this.config.http!)
249
+ ]);
250
+ break;
251
+ }
252
+ }
253
+ }
254
+ ```
255
+
256
+ ### Phase 2: HTTP Server Implementation (Week 1-2)
257
+
258
+ #### 2.1 Express Server Setup
259
+ **File**: `src/transports/http/server.ts`
260
+ ```typescript
261
+ import express from "express";
262
+ import cors from "cors";
263
+ import compression from "compression";
264
+ import helmet from "helmet";
265
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
266
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
267
+ import { createRoutes } from "./routes.js";
268
+ import { SessionManager } from "./session.js";
269
+ import { createSecurityMiddleware } from "./middleware.js";
270
+ import type { HttpTransportConfig } from "../types.js";
271
+
272
+ export async function startHttpTransport(
273
+ mcpServer: McpServer,
274
+ config: HttpTransportConfig
275
+ ): Promise<void> {
276
+ const app = express();
277
+ const sessionManager = new SessionManager(config.sessionMode);
278
+
279
+ // Apply middleware
280
+ app.use(express.json({ limit: '50mb' }));
281
+ app.use(compression());
282
+ app.use(helmet());
283
+
284
+ if (config.security?.enableCors) {
285
+ app.use(cors({
286
+ origin: config.security.corsOrigins || '*',
287
+ exposedHeaders: ['Mcp-Session-Id'],
288
+ allowedHeaders: ['Content-Type', 'mcp-session-id'],
289
+ }));
290
+ }
291
+
292
+ app.use(createSecurityMiddleware(config.security));
293
+
294
+ // Create routes
295
+ const routes = createRoutes(mcpServer, sessionManager, config);
296
+ app.use('/mcp', routes);
297
+
298
+ // Health check endpoint
299
+ app.get('/health', (req, res) => {
300
+ res.json({ status: 'healthy', transport: 'streamable-http' });
301
+ });
302
+
303
+ // Start server
304
+ const port = config.port || 3000;
305
+ const host = config.host || '0.0.0.0';
306
+
307
+ app.listen(port, host, () => {
308
+ console.log(`MCP HTTP Server listening on http://${host}:${port}`);
309
+ });
310
+ }
311
+ ```
312
+
313
+ #### 2.2 Route Handlers
314
+ **File**: `src/transports/http/routes.ts`
315
+ ```typescript
316
+ import { Router } from "express";
317
+ import { randomUUID } from "node:crypto";
318
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
319
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
320
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
321
+ import { SessionManager } from "./session.js";
322
+ import type { HttpTransportConfig } from "../types.js";
323
+
324
+ export function createRoutes(
325
+ mcpServer: McpServer,
326
+ sessionManager: SessionManager,
327
+ config: HttpTransportConfig
328
+ ): Router {
329
+ const router = Router();
330
+
331
+ // POST /mcp - Handle client requests
332
+ router.post('/', async (req, res) => {
333
+ try {
334
+ const sessionId = req.headers['mcp-session-id'] as string | undefined;
335
+
336
+ if (config.sessionMode === 'stateless') {
337
+ await handleStatelessRequest(mcpServer, req, res);
338
+ } else {
339
+ await handleStatefulRequest(mcpServer, sessionManager, sessionId, req, res);
340
+ }
341
+ } catch (error) {
342
+ handleError(res, error);
343
+ }
344
+ });
345
+
346
+ // GET /mcp - SSE endpoint for notifications
347
+ router.get('/', async (req, res) => {
348
+ if (config.sessionMode === 'stateless') {
349
+ res.status(405).json({
350
+ jsonrpc: "2.0",
351
+ error: {
352
+ code: -32000,
353
+ message: "SSE not supported in stateless mode"
354
+ },
355
+ id: null
356
+ });
357
+ return;
358
+ }
359
+
360
+ const sessionId = req.headers['mcp-session-id'] as string;
361
+ const transport = await sessionManager.getTransport(sessionId);
362
+
363
+ if (!transport) {
364
+ res.status(400).send('Invalid or missing session ID');
365
+ return;
366
+ }
367
+
368
+ await transport.handleRequest(req, res);
369
+ });
370
+
371
+ // DELETE /mcp - Session termination
372
+ router.delete('/', async (req, res) => {
373
+ if (config.sessionMode === 'stateless') {
374
+ res.status(405).json({
375
+ jsonrpc: "2.0",
376
+ error: {
377
+ code: -32000,
378
+ message: "Session termination not applicable in stateless mode"
379
+ },
380
+ id: null
381
+ });
382
+ return;
383
+ }
384
+
385
+ const sessionId = req.headers['mcp-session-id'] as string;
386
+ await sessionManager.terminateSession(sessionId);
387
+ res.status(204).send();
388
+ });
389
+
390
+ return router;
391
+ }
392
+
393
+ async function handleStatelessRequest(
394
+ mcpServer: McpServer,
395
+ req: any,
396
+ res: any
397
+ ): Promise<void> {
398
+ const transport = new StreamableHTTPServerTransport({
399
+ sessionIdGenerator: undefined,
400
+ });
401
+
402
+ res.on('close', () => {
403
+ transport.close();
404
+ });
405
+
406
+ await mcpServer.connect(transport);
407
+ await transport.handleRequest(req, res, req.body);
408
+ }
409
+
410
+ async function handleStatefulRequest(
411
+ mcpServer: McpServer,
412
+ sessionManager: SessionManager,
413
+ sessionId: string | undefined,
414
+ req: any,
415
+ res: any
416
+ ): Promise<void> {
417
+ let transport = sessionId ?
418
+ await sessionManager.getTransport(sessionId) : null;
419
+
420
+ if (!transport && isInitializeRequest(req.body)) {
421
+ transport = await sessionManager.createSession(mcpServer);
422
+ res.setHeader('Mcp-Session-Id', transport.sessionId);
423
+ } else if (!transport) {
424
+ res.status(400).json({
425
+ jsonrpc: '2.0',
426
+ error: {
427
+ code: -32000,
428
+ message: 'Bad Request: No valid session ID provided',
429
+ },
430
+ id: null,
431
+ });
432
+ return;
433
+ }
434
+
435
+ await transport.handleRequest(req, res, req.body);
436
+ }
437
+
438
+ function handleError(res: any, error: any): void {
439
+ console.error('MCP request error:', error);
440
+ if (!res.headersSent) {
441
+ res.status(500).json({
442
+ jsonrpc: '2.0',
443
+ error: {
444
+ code: -32603,
445
+ message: 'Internal server error',
446
+ },
447
+ id: null,
448
+ });
449
+ }
450
+ }
451
+ ```
452
+
453
+ #### 2.3 Session Management
454
+ **File**: `src/transports/http/session.ts`
455
+ ```typescript
456
+ import { randomUUID } from "node:crypto";
457
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
458
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
459
+ import type { SessionStore } from "../types.js";
460
+
461
+ export class SessionManager {
462
+ private transports: Map<string, StreamableHTTPServerTransport>;
463
+ private sessionMode: 'stateful' | 'stateless';
464
+ private store?: SessionStore;
465
+
466
+ constructor(sessionMode: 'stateful' | 'stateless', store?: SessionStore) {
467
+ this.transports = new Map();
468
+ this.sessionMode = sessionMode;
469
+ this.store = store;
470
+ }
471
+
472
+ async createSession(mcpServer: McpServer): Promise<StreamableHTTPServerTransport> {
473
+ const sessionId = randomUUID();
474
+
475
+ const transport = new StreamableHTTPServerTransport({
476
+ sessionIdGenerator: () => sessionId,
477
+ enableJsonResponse: true,
478
+ enableDnsRebindingProtection: true,
479
+ allowedHosts: ['127.0.0.1', 'localhost'],
480
+ });
481
+
482
+ transport.onclose = () => {
483
+ this.terminateSession(sessionId);
484
+ };
485
+
486
+ this.transports.set(sessionId, transport);
487
+
488
+ if (this.store) {
489
+ await this.store.set(sessionId, {
490
+ id: sessionId,
491
+ createdAt: Date.now(),
492
+ transport: transport
493
+ });
494
+ }
495
+
496
+ await mcpServer.connect(transport);
497
+ return transport;
498
+ }
499
+
500
+ async getTransport(sessionId: string): Promise<StreamableHTTPServerTransport | null> {
501
+ let transport = this.transports.get(sessionId);
502
+
503
+ if (!transport && this.store) {
504
+ const session = await this.store.get(sessionId);
505
+ if (session) {
506
+ transport = session.transport;
507
+ this.transports.set(sessionId, transport);
508
+ }
509
+ }
510
+
511
+ return transport || null;
512
+ }
513
+
514
+ async terminateSession(sessionId: string): Promise<void> {
515
+ const transport = this.transports.get(sessionId);
516
+ if (transport) {
517
+ transport.close();
518
+ this.transports.delete(sessionId);
519
+ }
520
+
521
+ if (this.store) {
522
+ await this.store.delete(sessionId);
523
+ }
524
+ }
525
+
526
+ async cleanup(): Promise<void> {
527
+ for (const [sessionId, transport] of this.transports) {
528
+ transport.close();
529
+ }
530
+ this.transports.clear();
531
+
532
+ if (this.store) {
533
+ await this.store.cleanup();
534
+ }
535
+ }
536
+ }
537
+ ```
538
+
539
+ ### Phase 3: Configuration & Integration (Week 2)
540
+
541
+ #### 3.1 Update Configuration
542
+ **File**: `src/utils/config.ts` (additions)
543
+ ```typescript
544
+ transport: z.object({
545
+ type: z.enum(["stdio", "http", "both"]).default("stdio"),
546
+ http: z.object({
547
+ enabled: z.boolean().default(false),
548
+ port: z.number().default(3000),
549
+ host: z.string().default("0.0.0.0"),
550
+ sessionMode: z.enum(["stateful", "stateless"]).default("stateful"),
551
+ enableSse: z.boolean().default(true),
552
+ enableJsonResponse: z.boolean().default(true),
553
+ cors: z.object({
554
+ enabled: z.boolean().default(true),
555
+ origins: z.array(z.string()).optional(),
556
+ }).optional(),
557
+ dnsRebinding: z.object({
558
+ enabled: z.boolean().default(true),
559
+ allowedHosts: z.array(z.string()).default(["127.0.0.1", "localhost"]),
560
+ }).optional(),
561
+ }).optional(),
562
+ }),
563
+ ```
564
+
565
+ #### 3.2 Update Entry Point
566
+ **File**: `src/index.ts`
567
+ ```typescript
568
+ #!/usr/bin/env bun
569
+
570
+ import { createServer } from "./server.js";
571
+ import { TransportManager } from "./transports/index.js";
572
+ import { loadConfig } from "./utils/config.js";
573
+ import { logger } from "./utils/logger.js";
574
+
575
+ async function main() {
576
+ try {
577
+ const config = loadConfig();
578
+ const server = await createServer();
579
+
580
+ const transportConfig = {
581
+ type: config.transport.type,
582
+ http: config.transport.http
583
+ };
584
+
585
+ const transportManager = new TransportManager(server, transportConfig);
586
+ await transportManager.start();
587
+
588
+ logger.info(`Human MCP Server started with ${config.transport.type} transport`);
589
+
590
+ // Graceful shutdown
591
+ process.on('SIGINT', async () => {
592
+ logger.info('Shutting down server...');
593
+ process.exit(0);
594
+ });
595
+
596
+ } catch (error) {
597
+ logger.error('Failed to start server:', error);
598
+ process.exit(1);
599
+ }
600
+ }
601
+
602
+ main();
603
+ ```
604
+
605
+ #### 3.3 Update Environment Variables
606
+ **File**: `.env.example` (additions)
607
+ ```bash
608
+ # Transport Configuration
609
+ TRANSPORT_TYPE=http # stdio, http, or both
610
+ HTTP_PORT=3000
611
+ HTTP_HOST=0.0.0.0
612
+ HTTP_SESSION_MODE=stateful # stateful or stateless
613
+ HTTP_ENABLE_SSE=true
614
+ HTTP_ENABLE_JSON_RESPONSE=true
615
+
616
+ # CORS Configuration
617
+ HTTP_CORS_ENABLED=true
618
+ HTTP_CORS_ORIGINS=http://localhost:3000,https://app.example.com
619
+
620
+ # DNS Rebinding Protection
621
+ HTTP_DNS_REBINDING_ENABLED=true
622
+ HTTP_ALLOWED_HOSTS=127.0.0.1,localhost
623
+ ```
624
+
625
+ ### Phase 4: Security & Middleware (Week 2-3)
626
+
627
+ #### 4.1 Security Middleware
628
+ **File**: `src/transports/http/middleware.ts`
629
+ ```typescript
630
+ import { Request, Response, NextFunction } from "express";
631
+ import type { SecurityConfig } from "../types.js";
632
+
633
+ export function createSecurityMiddleware(config?: SecurityConfig) {
634
+ return async (req: Request, res: Response, next: NextFunction) => {
635
+ // DNS Rebinding Protection
636
+ if (config?.enableDnsRebindingProtection) {
637
+ const host = req.headers.host?.split(':')[0];
638
+ const allowedHosts = config.allowedHosts || ['127.0.0.1', 'localhost'];
639
+
640
+ if (host && !allowedHosts.includes(host)) {
641
+ res.status(403).json({
642
+ error: 'Forbidden: Invalid host'
643
+ });
644
+ return;
645
+ }
646
+ }
647
+
648
+ // Rate Limiting (basic implementation)
649
+ if (config?.enableRateLimiting) {
650
+ // Implement rate limiting logic here
651
+ // Could use express-rate-limit package
652
+ }
653
+
654
+ // Secret-based authentication (optional)
655
+ if (config?.secret) {
656
+ const authHeader = req.headers.authorization;
657
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
658
+ res.status(401).json({
659
+ error: 'Unauthorized: Missing authentication'
660
+ });
661
+ return;
662
+ }
663
+
664
+ const token = authHeader.substring(7);
665
+ if (token !== config.secret) {
666
+ res.status(401).json({
667
+ error: 'Unauthorized: Invalid token'
668
+ });
669
+ return;
670
+ }
671
+ }
672
+
673
+ next();
674
+ };
675
+ }
676
+ ```
677
+
678
+ ### Phase 5: Testing Strategy (Week 3)
679
+
680
+ #### 5.1 Unit Tests
681
+ ```typescript
682
+ // tests/transports/session.test.ts
683
+ import { describe, it, expect } from "bun:test";
684
+ import { SessionManager } from "@/transports/http/session";
685
+
686
+ describe("SessionManager", () => {
687
+ it("should create and retrieve sessions", async () => {
688
+ const manager = new SessionManager('stateful');
689
+ const transport = await manager.createSession(mockServer);
690
+ expect(transport.sessionId).toBeDefined();
691
+
692
+ const retrieved = await manager.getTransport(transport.sessionId);
693
+ expect(retrieved).toBe(transport);
694
+ });
695
+
696
+ it("should terminate sessions", async () => {
697
+ const manager = new SessionManager('stateful');
698
+ const transport = await manager.createSession(mockServer);
699
+ await manager.terminateSession(transport.sessionId);
700
+
701
+ const retrieved = await manager.getTransport(transport.sessionId);
702
+ expect(retrieved).toBeNull();
703
+ });
704
+ });
705
+ ```
706
+
707
+ #### 5.2 Integration Tests
708
+ ```typescript
709
+ // tests/integration/http-transport.test.ts
710
+ import { describe, it, expect } from "bun:test";
711
+ import request from "supertest";
712
+
713
+ describe("HTTP Transport", () => {
714
+ it("should handle initialize request", async () => {
715
+ const response = await request(app)
716
+ .post('/mcp')
717
+ .send({
718
+ jsonrpc: "2.0",
719
+ method: "initialize",
720
+ params: {
721
+ protocolVersion: "2025-03-26",
722
+ capabilities: {}
723
+ },
724
+ id: 1
725
+ });
726
+
727
+ expect(response.status).toBe(200);
728
+ expect(response.headers['mcp-session-id']).toBeDefined();
729
+ });
730
+
731
+ it("should handle tool calls", async () => {
732
+ // Initialize session first
733
+ const initResponse = await request(app)
734
+ .post('/mcp')
735
+ .send(initializeRequest);
736
+
737
+ const sessionId = initResponse.headers['mcp-session-id'];
738
+
739
+ // Call tool
740
+ const toolResponse = await request(app)
741
+ .post('/mcp')
742
+ .set('mcp-session-id', sessionId)
743
+ .send({
744
+ jsonrpc: "2.0",
745
+ method: "tools/call",
746
+ params: {
747
+ name: "eyes.analyze",
748
+ arguments: {
749
+ source: "test.jpg",
750
+ type: "image"
751
+ }
752
+ },
753
+ id: 2
754
+ });
755
+
756
+ expect(toolResponse.status).toBe(200);
757
+ });
758
+ });
759
+ ```
760
+
761
+ ### Phase 6: Documentation & Deployment (Week 3-4)
762
+
763
+ #### 6.1 Update README.md
764
+ ```markdown
765
+ ## Transport Options
766
+
767
+ Human MCP supports multiple transport mechanisms:
768
+
769
+ ### Stdio Transport (Default)
770
+ ```bash
771
+ bun run start
772
+ ```
773
+
774
+ ### HTTP Transport
775
+ ```bash
776
+ TRANSPORT_TYPE=http bun run start
777
+ ```
778
+
779
+ ### Both Transports
780
+ ```bash
781
+ TRANSPORT_TYPE=both bun run start
782
+ ```
783
+
784
+ ## HTTP API Endpoints
785
+
786
+ - `POST /mcp` - Handle client requests
787
+ - `GET /mcp` - SSE endpoint for notifications (stateful mode only)
788
+ - `DELETE /mcp` - Terminate session (stateful mode only)
789
+ - `GET /health` - Health check endpoint
790
+
791
+ ## Session Modes
792
+
793
+ ### Stateful Mode (Default)
794
+ - Maintains session state between requests
795
+ - Supports SSE notifications
796
+ - Enables session resumability
797
+ - Requires session ID management
798
+
799
+ ### Stateless Mode
800
+ - No session persistence
801
+ - Each request is independent
802
+ - Suitable for serverless deployments
803
+ - No SSE support
804
+ ```
805
+
806
+ #### 6.2 Docker Support
807
+ **File**: `Dockerfile.http`
808
+ ```dockerfile
809
+ FROM oven/bun:1-alpine
810
+
811
+ WORKDIR /app
812
+
813
+ COPY package.json bun.lockb ./
814
+ RUN bun install --frozen-lockfile
815
+
816
+ COPY . .
817
+ RUN bun run build
818
+
819
+ ENV TRANSPORT_TYPE=http
820
+ ENV HTTP_PORT=3000
821
+ ENV HTTP_HOST=0.0.0.0
822
+
823
+ EXPOSE 3000
824
+
825
+ CMD ["bun", "run", "start"]
826
+ ```
827
+
828
+ ## Testing & Validation Strategy
829
+
830
+ ### 1. Unit Testing
831
+ - Transport manager logic
832
+ - Session management
833
+ - Security middleware
834
+ - Route handlers
835
+
836
+ ### 2. Integration Testing
837
+ - End-to-end HTTP requests
838
+ - Session lifecycle
839
+ - SSE notifications
840
+ - Error handling
841
+
842
+ ### 3. Compatibility Testing
843
+ - Stdio transport regression
844
+ - HTTP client compatibility
845
+ - SSE fallback scenarios
846
+
847
+ ### 4. Performance Testing
848
+ - Concurrent session handling
849
+ - Memory usage under load
850
+ - Response time metrics
851
+
852
+ ### 5. Security Testing
853
+ - CORS validation
854
+ - DNS rebinding protection
855
+ - Rate limiting effectiveness
856
+ - Authentication mechanisms
857
+
858
+ ## Risk Mitigation
859
+
860
+ ### Technical Risks
861
+ 1. **Breaking Changes**: Mitigated by maintaining backward compatibility and phased rollout
862
+ 2. **Performance Impact**: Addressed through proper session management and optional stateless mode
863
+ 3. **Security Vulnerabilities**: Mitigated with comprehensive security middleware and testing
864
+
865
+ ### Implementation Risks
866
+ 1. **Complexity**: Managed through modular architecture and clear separation of concerns
867
+ 2. **Testing Coverage**: Ensured through comprehensive test suite at multiple levels
868
+ 3. **Documentation**: Maintained through inline comments and updated README
869
+
870
+ ## Success Metrics
871
+
872
+ 1. **Functionality**: All existing stdio functionality preserved
873
+ 2. **Performance**: HTTP response time < 100ms for tool calls
874
+ 3. **Reliability**: 99.9% uptime for HTTP server
875
+ 4. **Security**: Zero security vulnerabilities in OWASP top 10
876
+ 5. **Adoption**: Successful integration with at least 3 different MCP clients
877
+
878
+ ## Implementation Timeline
879
+
880
+ ### Week 1: Foundation
881
+ - [ ] Install dependencies
882
+ - [ ] Create transport interfaces and manager
883
+ - [ ] Basic HTTP server setup
884
+
885
+ ### Week 2: Core Implementation
886
+ - [ ] Route handlers implementation
887
+ - [ ] Session management
888
+ - [ ] Configuration updates
889
+
890
+ ### Week 3: Security & Testing
891
+ - [ ] Security middleware
892
+ - [ ] Unit tests
893
+ - [ ] Integration tests
894
+
895
+ ### Week 4: Documentation & Polish
896
+ - [ ] Documentation updates
897
+ - [ ] Docker support
898
+ - [ ] Performance optimization
899
+ - [ ] Final testing and validation
900
+
901
+ ## Conclusion
902
+
903
+ This implementation plan provides a comprehensive approach to adding Streamable HTTP transport to the Human MCP server. The modular architecture ensures maintainability and extensibility while preserving backward compatibility. The phased approach allows for iterative development and testing, reducing implementation risks.
904
+
905
+ The recommended Approach 1 (Modular Transport System) provides the best balance of functionality, maintainability, and extensibility, setting up the project for future enhancements and transport options.