touchdesigner-mcp-server 1.3.1 → 1.4.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/README.ja.md +4 -307
- package/README.md +9 -308
- package/dist/cli.js +132 -16
- package/dist/core/logger.js +13 -0
- package/dist/gen/endpoints/TouchDesignerAPI.js +1 -1
- package/dist/gen/mcp/touchDesignerAPI.zod.js +1 -1
- package/dist/server/touchDesignerServer.js +19 -0
- package/dist/transport/config.js +75 -0
- package/dist/transport/expressHttpManager.js +235 -0
- package/dist/transport/factory.js +198 -0
- package/dist/transport/index.js +12 -0
- package/dist/transport/sessionManager.js +276 -0
- package/dist/transport/transportRegistry.js +272 -0
- package/dist/transport/validator.js +78 -0
- package/package.json +4 -1
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { ConsoleLogger } from "./core/logger.js";
|
|
3
3
|
import { TouchDesignerServer } from "./server/touchDesignerServer.js";
|
|
4
|
+
import { isStreamableHttpTransportConfig } from "./transport/config.js";
|
|
5
|
+
import { ExpressHttpManager } from "./transport/expressHttpManager.js";
|
|
6
|
+
import { TransportFactory } from "./transport/factory.js";
|
|
7
|
+
import { SessionManager } from "./transport/sessionManager.js";
|
|
4
8
|
// Note: Environment variables should be set by the MCP Bundle runtime or CLI arguments
|
|
5
9
|
const DEFAULT_HOST = "http://127.0.0.1";
|
|
6
10
|
const DEFAULT_PORT = 9981;
|
|
11
|
+
const DEFAULT_MCP_ENDPOINT = "/mcp";
|
|
7
12
|
/**
|
|
8
|
-
* Parse command line arguments
|
|
13
|
+
* Parse command line arguments for TouchDesigner connection
|
|
9
14
|
*/
|
|
10
15
|
export function parseArgs(args) {
|
|
11
16
|
const argsToProcess = args || process.argv.slice(2);
|
|
@@ -25,38 +30,149 @@ export function parseArgs(args) {
|
|
|
25
30
|
return parsed;
|
|
26
31
|
}
|
|
27
32
|
/**
|
|
28
|
-
*
|
|
33
|
+
* Parse transport configuration from command line arguments
|
|
34
|
+
*
|
|
35
|
+
* Detects if HTTP mode is requested via --mcp-http-port flag.
|
|
36
|
+
* If not specified, defaults to stdio mode.
|
|
37
|
+
*
|
|
38
|
+
* @param args - Command line arguments (defaults to process.argv.slice(2))
|
|
39
|
+
* @returns Transport configuration (stdio or streamable-http)
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```bash
|
|
43
|
+
* # Stdio mode (default)
|
|
44
|
+
* touchdesigner-mcp-server --host=http://localhost --port=9981
|
|
45
|
+
*
|
|
46
|
+
* # HTTP mode
|
|
47
|
+
* touchdesigner-mcp-server --mcp-http-port=6280 --mcp-http-host=127.0.0.1
|
|
48
|
+
* ```
|
|
29
49
|
*/
|
|
30
|
-
export function
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
50
|
+
export function parseTransportConfig(args) {
|
|
51
|
+
const argsToProcess = args || process.argv.slice(2);
|
|
52
|
+
// Check for HTTP mode
|
|
53
|
+
const httpPortArg = argsToProcess.find((arg) => arg.startsWith("--mcp-http-port="));
|
|
54
|
+
if (httpPortArg) {
|
|
55
|
+
const portStr = httpPortArg.split("=")[1];
|
|
56
|
+
const port = Number.parseInt(portStr, 10);
|
|
57
|
+
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
58
|
+
console.error(`Invalid value for --mcp-http-port: "${portStr}". Please specify a valid port number (1-65535).`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
const hostArg = argsToProcess.find((arg) => arg.startsWith("--mcp-http-host="));
|
|
62
|
+
const host = hostArg ? hostArg.split("=")[1] : "127.0.0.1";
|
|
63
|
+
const config = {
|
|
64
|
+
endpoint: DEFAULT_MCP_ENDPOINT,
|
|
65
|
+
host,
|
|
66
|
+
port,
|
|
67
|
+
sessionConfig: { enabled: true },
|
|
68
|
+
type: "streamable-http",
|
|
69
|
+
};
|
|
70
|
+
return config;
|
|
71
|
+
}
|
|
72
|
+
// Default to stdio mode
|
|
73
|
+
return { type: "stdio" };
|
|
34
74
|
}
|
|
35
75
|
/**
|
|
36
76
|
* Start TouchDesigner MCP server
|
|
77
|
+
*
|
|
78
|
+
* Supports both stdio and HTTP transport modes based on command line arguments.
|
|
79
|
+
*
|
|
80
|
+
* @param params - Server startup parameters
|
|
81
|
+
* @param params.argv - Command line arguments
|
|
82
|
+
* @param params.nodeEnv - Node environment
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```bash
|
|
86
|
+
* # Stdio mode (default)
|
|
87
|
+
* touchdesigner-mcp-server --host=http://localhost --port=9981
|
|
88
|
+
*
|
|
89
|
+
* # HTTP mode
|
|
90
|
+
* touchdesigner-mcp-server --mcp-http-port=6280 --host=http://localhost --port=9981
|
|
91
|
+
* ```
|
|
37
92
|
*/
|
|
38
93
|
export async function startServer(params) {
|
|
39
94
|
try {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
// Parse command line arguments and set environment variables
|
|
95
|
+
// Parse transport configuration
|
|
96
|
+
const transportConfig = parseTransportConfig(params?.argv);
|
|
97
|
+
// Parse TouchDesigner connection arguments
|
|
45
98
|
const args = parseArgs(params?.argv);
|
|
46
99
|
process.env.TD_WEB_SERVER_HOST = args.host;
|
|
47
100
|
process.env.TD_WEB_SERVER_PORT = args.port.toString();
|
|
101
|
+
// Create MCP server
|
|
48
102
|
const server = new TouchDesignerServer();
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
103
|
+
// Handle stdio mode
|
|
104
|
+
if (transportConfig.type === "stdio") {
|
|
105
|
+
const transportResult = TransportFactory.create(transportConfig);
|
|
106
|
+
if (!transportResult.success) {
|
|
107
|
+
throw transportResult.error;
|
|
108
|
+
}
|
|
109
|
+
const result = await server.connect(transportResult.data);
|
|
110
|
+
if (!result.success) {
|
|
111
|
+
throw new Error(`Failed to connect: ${result.error.message}`);
|
|
112
|
+
}
|
|
113
|
+
console.error("MCP server started in stdio mode");
|
|
114
|
+
return;
|
|
53
115
|
}
|
|
116
|
+
// Handle HTTP mode
|
|
117
|
+
if (isStreamableHttpTransportConfig(transportConfig)) {
|
|
118
|
+
// Use ConsoleLogger for HTTP manager and session manager
|
|
119
|
+
// This avoids "Not connected" errors since HTTP mode doesn't have a global MCP connection
|
|
120
|
+
const logger = new ConsoleLogger();
|
|
121
|
+
// Create session manager if enabled
|
|
122
|
+
const sessionManager = transportConfig.sessionConfig?.enabled
|
|
123
|
+
? new SessionManager(transportConfig.sessionConfig, logger)
|
|
124
|
+
: null;
|
|
125
|
+
// Server factory for creating per-session instances
|
|
126
|
+
// Each session gets its own TouchDesignerServer with independent MCP protocol state
|
|
127
|
+
const serverFactory = () => TouchDesignerServer.create();
|
|
128
|
+
// Create Express HTTP manager with server factory
|
|
129
|
+
const httpManager = new ExpressHttpManager(transportConfig, serverFactory, sessionManager, logger);
|
|
130
|
+
// Start HTTP server
|
|
131
|
+
const startResult = await httpManager.start();
|
|
132
|
+
if (!startResult.success) {
|
|
133
|
+
throw startResult.error;
|
|
134
|
+
}
|
|
135
|
+
console.error(`MCP server started in HTTP mode on ${transportConfig.host}:${transportConfig.port}${transportConfig.endpoint}`);
|
|
136
|
+
// Start session cleanup if enabled
|
|
137
|
+
if (sessionManager) {
|
|
138
|
+
sessionManager.startTTLCleanup();
|
|
139
|
+
}
|
|
140
|
+
// Set up graceful shutdown
|
|
141
|
+
const shutdown = async () => {
|
|
142
|
+
console.error("\nShutting down server...");
|
|
143
|
+
// Stop session cleanup
|
|
144
|
+
if (sessionManager) {
|
|
145
|
+
sessionManager.stopTTLCleanup();
|
|
146
|
+
}
|
|
147
|
+
// Stop HTTP server
|
|
148
|
+
const stopResult = await httpManager.stop();
|
|
149
|
+
if (!stopResult.success) {
|
|
150
|
+
console.error(`Error during shutdown: ${stopResult.error.message}`);
|
|
151
|
+
}
|
|
152
|
+
console.error("Server shutdown complete");
|
|
153
|
+
process.exit(0);
|
|
154
|
+
};
|
|
155
|
+
process.on("SIGINT", shutdown);
|
|
156
|
+
process.on("SIGTERM", shutdown);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
// Type-safe exhaustive check using never type
|
|
160
|
+
// This ensures all cases of the TransportConfig discriminated union are handled
|
|
161
|
+
// If a new transport type is added, TypeScript will error at compile time
|
|
162
|
+
assertNever(transportConfig);
|
|
54
163
|
}
|
|
55
164
|
catch (error) {
|
|
56
165
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
57
166
|
throw new Error(`Failed to initialize server: ${errorMessage}`);
|
|
58
167
|
}
|
|
59
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Helper function for exhaustive type checking
|
|
171
|
+
* TypeScript will error if called with a non-never type, ensuring all cases are handled
|
|
172
|
+
*/
|
|
173
|
+
function assertNever(value) {
|
|
174
|
+
throw new Error(`Unsupported transport type: ${value.type}`);
|
|
175
|
+
}
|
|
60
176
|
// Start server if this file is executed directly
|
|
61
177
|
startServer({
|
|
62
178
|
argv: process.argv,
|
package/dist/core/logger.js
CHANGED
|
@@ -28,3 +28,16 @@ export class McpLogger {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Console Logger implementation for standalone use (e.g., HTTP mode setup)
|
|
33
|
+
* Outputs to stderr to avoid interfering with stdio transport
|
|
34
|
+
*/
|
|
35
|
+
export class ConsoleLogger {
|
|
36
|
+
sendLog(args) {
|
|
37
|
+
const timestamp = new Date().toISOString();
|
|
38
|
+
const level = args.level?.toUpperCase() || "INFO";
|
|
39
|
+
const logger = args.logger || "unknown";
|
|
40
|
+
const data = args.data;
|
|
41
|
+
console.error(`[${timestamp}] [${level}] [${logger}] ${data}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Do not edit manually.
|
|
4
4
|
* TouchDesigner API
|
|
5
5
|
* OpenAPI schema for generating TouchDesigner API client code
|
|
6
|
-
* OpenAPI spec version: 1.
|
|
6
|
+
* OpenAPI spec version: 1.4.1
|
|
7
7
|
*/
|
|
8
8
|
import { customInstance } from '../../api/customInstance.js';
|
|
9
9
|
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
|
@@ -32,6 +32,25 @@ export class TouchDesignerServer {
|
|
|
32
32
|
this.connectionManager = new ConnectionManager(this.server, this.logger);
|
|
33
33
|
this.registerAllFeatures();
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Create a new TouchDesignerServer instance
|
|
37
|
+
*
|
|
38
|
+
* Factory method for creating server instances in multi-session scenarios.
|
|
39
|
+
* Each session should have its own server instance to maintain independent MCP protocol state.
|
|
40
|
+
*
|
|
41
|
+
* @returns McpServer instance ready for connection to a transport
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* // In TransportRegistry
|
|
46
|
+
* const serverFactory = () => TouchDesignerServer.create();
|
|
47
|
+
* const transport = await registry.getOrCreate(sessionId, body, serverFactory);
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
static create() {
|
|
51
|
+
const instance = new TouchDesignerServer();
|
|
52
|
+
return instance.server;
|
|
53
|
+
}
|
|
35
54
|
/**
|
|
36
55
|
* Connect to MCP transport
|
|
37
56
|
*/
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for SessionConfig validation
|
|
4
|
+
*/
|
|
5
|
+
const SessionConfigSchema = z
|
|
6
|
+
.object({
|
|
7
|
+
cleanupInterval: z.number().int().positive().optional(),
|
|
8
|
+
enabled: z.boolean(),
|
|
9
|
+
ttl: z.number().int().positive().optional(),
|
|
10
|
+
})
|
|
11
|
+
.strict();
|
|
12
|
+
/**
|
|
13
|
+
* Zod schema for StdioTransportConfig validation
|
|
14
|
+
*/
|
|
15
|
+
const StdioTransportConfigSchema = z
|
|
16
|
+
.object({
|
|
17
|
+
type: z.literal("stdio"),
|
|
18
|
+
})
|
|
19
|
+
.strict();
|
|
20
|
+
/**
|
|
21
|
+
* Zod schema for StreamableHttpTransportConfig validation
|
|
22
|
+
*/
|
|
23
|
+
const StreamableHttpTransportConfigSchema = z
|
|
24
|
+
.object({
|
|
25
|
+
endpoint: z
|
|
26
|
+
.string()
|
|
27
|
+
.min(1, "Endpoint cannot be empty")
|
|
28
|
+
.regex(/^\//, "Endpoint must start with /"),
|
|
29
|
+
host: z.string().min(1, "Host cannot be empty"),
|
|
30
|
+
port: z
|
|
31
|
+
.number()
|
|
32
|
+
.int()
|
|
33
|
+
.positive()
|
|
34
|
+
.min(1)
|
|
35
|
+
.max(65535, "Port must be between 1 and 65535"),
|
|
36
|
+
retryInterval: z.number().int().positive().optional(),
|
|
37
|
+
sessionConfig: SessionConfigSchema.optional(),
|
|
38
|
+
type: z.literal("streamable-http"),
|
|
39
|
+
})
|
|
40
|
+
.strict();
|
|
41
|
+
/**
|
|
42
|
+
* Zod schema for TransportConfig validation (discriminated union)
|
|
43
|
+
*/
|
|
44
|
+
export const TransportConfigSchema = z.discriminatedUnion("type", [
|
|
45
|
+
StdioTransportConfigSchema,
|
|
46
|
+
StreamableHttpTransportConfigSchema,
|
|
47
|
+
]);
|
|
48
|
+
/**
|
|
49
|
+
* Type guard to check if config is StdioTransportConfig
|
|
50
|
+
*/
|
|
51
|
+
export function isStdioTransportConfig(config) {
|
|
52
|
+
return config.type === "stdio";
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Type guard to check if config is StreamableHttpTransportConfig
|
|
56
|
+
*/
|
|
57
|
+
export function isStreamableHttpTransportConfig(config) {
|
|
58
|
+
return config.type === "streamable-http";
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Default values for SessionConfig
|
|
62
|
+
*/
|
|
63
|
+
export const DEFAULT_SESSION_CONFIG = {
|
|
64
|
+
cleanupInterval: 5 * 60 * 1000, // 5 minutes
|
|
65
|
+
enabled: true,
|
|
66
|
+
ttl: 60 * 60 * 1000, // 1 hour
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Default values for StreamableHttpTransportConfig (excluding required fields)
|
|
70
|
+
*/
|
|
71
|
+
export const DEFAULT_HTTP_CONFIG = {
|
|
72
|
+
endpoint: "/mcp",
|
|
73
|
+
host: "127.0.0.1",
|
|
74
|
+
sessionConfig: DEFAULT_SESSION_CONFIG,
|
|
75
|
+
};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
2
|
+
import { createErrorResult, createSuccessResult } from "../core/result.js";
|
|
3
|
+
import { TransportRegistry } from "./transportRegistry.js";
|
|
4
|
+
/**
|
|
5
|
+
* Express HTTP Manager
|
|
6
|
+
*
|
|
7
|
+
* Manages HTTP server lifecycle for Streamable HTTP transport.
|
|
8
|
+
* Handles multiple concurrent sessions by creating per-session transport and server instances.
|
|
9
|
+
*
|
|
10
|
+
* Key Features:
|
|
11
|
+
* - /mcp endpoint → routes to appropriate transport via TransportRegistry
|
|
12
|
+
* - /health endpoint → reports active session count
|
|
13
|
+
* - Per-session isolation → each client gets independent MCP protocol state
|
|
14
|
+
* - Graceful shutdown → cleans up all active sessions
|
|
15
|
+
*
|
|
16
|
+
* Architecture:
|
|
17
|
+
* ```
|
|
18
|
+
* Client 1 → POST /mcp → TransportRegistry.getOrCreate() → Transport 1 + Server 1
|
|
19
|
+
* Client 2 → POST /mcp → TransportRegistry.getOrCreate() → Transport 2 + Server 2
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const manager = new ExpressHttpManager(
|
|
25
|
+
* config,
|
|
26
|
+
* () => TouchDesignerServer.create(), // Server factory
|
|
27
|
+
* sessionManager,
|
|
28
|
+
* logger
|
|
29
|
+
* );
|
|
30
|
+
*
|
|
31
|
+
* // Start server
|
|
32
|
+
* const result = await manager.start();
|
|
33
|
+
*
|
|
34
|
+
* // Graceful shutdown
|
|
35
|
+
* await manager.stop();
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export class ExpressHttpManager {
|
|
39
|
+
config;
|
|
40
|
+
serverFactory;
|
|
41
|
+
logger;
|
|
42
|
+
registry;
|
|
43
|
+
server = null;
|
|
44
|
+
/**
|
|
45
|
+
* Create ExpressHttpManager with server factory
|
|
46
|
+
*
|
|
47
|
+
* @param config - Streamable HTTP transport configuration
|
|
48
|
+
* @param serverFactory - Factory function to create new Server instances per session
|
|
49
|
+
* @param sessionManager - Session manager for TTL tracking (optional)
|
|
50
|
+
* @param logger - Logger instance
|
|
51
|
+
*/
|
|
52
|
+
constructor(config, serverFactory, sessionManager, logger) {
|
|
53
|
+
this.config = config;
|
|
54
|
+
this.serverFactory = serverFactory;
|
|
55
|
+
this.logger = logger;
|
|
56
|
+
this.registry = new TransportRegistry(config, sessionManager, logger);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Start HTTP server with Express app from SDK
|
|
60
|
+
*
|
|
61
|
+
* @returns Result indicating success or failure
|
|
62
|
+
*/
|
|
63
|
+
async start() {
|
|
64
|
+
try {
|
|
65
|
+
if (this.server?.listening) {
|
|
66
|
+
return createErrorResult(new Error("Express HTTP server is already running"));
|
|
67
|
+
}
|
|
68
|
+
// Create Express app using SDK's factory
|
|
69
|
+
// This automatically includes DNS rebinding protection for localhost
|
|
70
|
+
const app = createMcpExpressApp({
|
|
71
|
+
host: this.config.host,
|
|
72
|
+
});
|
|
73
|
+
// MCP endpoint handler - routes to appropriate transport via registry
|
|
74
|
+
const handleMcpRequest = async (req, res) => {
|
|
75
|
+
try {
|
|
76
|
+
// Extract session ID from header
|
|
77
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
78
|
+
// Get or create transport for this session
|
|
79
|
+
const transport = await this.registry.getOrCreate(sessionId, req.body, this.serverFactory);
|
|
80
|
+
if (!transport) {
|
|
81
|
+
// Invalid session (session ID provided but not found, or non-initialize without session)
|
|
82
|
+
res.status(400).json({
|
|
83
|
+
error: {
|
|
84
|
+
code: -32000,
|
|
85
|
+
message: "Invalid session",
|
|
86
|
+
},
|
|
87
|
+
id: null,
|
|
88
|
+
jsonrpc: "2.0",
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Delegate request to transport
|
|
93
|
+
await transport.handleRequest(req, res, req.body);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
97
|
+
this.logger.sendLog({
|
|
98
|
+
data: `Error handling MCP request: ${errorMessage}`,
|
|
99
|
+
level: "error",
|
|
100
|
+
logger: "ExpressHttpManager",
|
|
101
|
+
});
|
|
102
|
+
if (!res.headersSent) {
|
|
103
|
+
res.status(500).json({
|
|
104
|
+
error: "Internal server error",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
// Configure /mcp endpoints
|
|
110
|
+
// POST: JSON-RPC requests (initialize, tool calls, etc.)
|
|
111
|
+
// GET: SSE streaming for notifications
|
|
112
|
+
// DELETE: Session termination
|
|
113
|
+
app.post(this.config.endpoint, handleMcpRequest);
|
|
114
|
+
app.get(this.config.endpoint, handleMcpRequest);
|
|
115
|
+
app.delete(this.config.endpoint, handleMcpRequest);
|
|
116
|
+
// Configure /health endpoint
|
|
117
|
+
app.get("/health", (_req, res) => {
|
|
118
|
+
const sessionCount = this.registry.getCount();
|
|
119
|
+
res.json({
|
|
120
|
+
sessions: sessionCount,
|
|
121
|
+
status: "ok",
|
|
122
|
+
timestamp: new Date().toISOString(),
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
// Start HTTP server
|
|
126
|
+
await new Promise((resolve, reject) => {
|
|
127
|
+
try {
|
|
128
|
+
this.server = app.listen(this.config.port, this.config.host, () => {
|
|
129
|
+
this.logger.sendLog({
|
|
130
|
+
data: `Express HTTP server listening on ${this.config.host}:${this.config.port}`,
|
|
131
|
+
level: "info",
|
|
132
|
+
logger: "ExpressHttpManager",
|
|
133
|
+
});
|
|
134
|
+
this.logger.sendLog({
|
|
135
|
+
data: `MCP endpoint: ${this.config.endpoint}`,
|
|
136
|
+
level: "info",
|
|
137
|
+
logger: "ExpressHttpManager",
|
|
138
|
+
});
|
|
139
|
+
this.logger.sendLog({
|
|
140
|
+
data: "Health check: GET /health",
|
|
141
|
+
level: "info",
|
|
142
|
+
logger: "ExpressHttpManager",
|
|
143
|
+
});
|
|
144
|
+
resolve();
|
|
145
|
+
});
|
|
146
|
+
this.server.on("error", (error) => {
|
|
147
|
+
reject(error);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
reject(error);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return createSuccessResult(undefined);
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
158
|
+
return createErrorResult(new Error(`Failed to start Express HTTP server: ${err.message}`));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Graceful shutdown
|
|
163
|
+
*
|
|
164
|
+
* Stops HTTP server and cleans up all active sessions.
|
|
165
|
+
*
|
|
166
|
+
* @returns Result indicating success or failure
|
|
167
|
+
*/
|
|
168
|
+
async stop() {
|
|
169
|
+
try {
|
|
170
|
+
if (!this.server) {
|
|
171
|
+
return createSuccessResult(undefined);
|
|
172
|
+
}
|
|
173
|
+
this.logger.sendLog({
|
|
174
|
+
data: "Stopping Express HTTP server...",
|
|
175
|
+
level: "info",
|
|
176
|
+
logger: "ExpressHttpManager",
|
|
177
|
+
});
|
|
178
|
+
// Cleanup all active sessions first
|
|
179
|
+
const cleanupResult = await this.registry.cleanup();
|
|
180
|
+
if (!cleanupResult.success) {
|
|
181
|
+
this.logger.sendLog({
|
|
182
|
+
data: `Warning: Session cleanup failed: ${cleanupResult.error.message}`,
|
|
183
|
+
level: "warning",
|
|
184
|
+
logger: "ExpressHttpManager",
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
// Close HTTP server
|
|
188
|
+
await new Promise((resolve, reject) => {
|
|
189
|
+
this.server?.close((error) => {
|
|
190
|
+
if (error) {
|
|
191
|
+
reject(error);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
resolve();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
this.logger.sendLog({
|
|
199
|
+
data: "Express HTTP server stopped",
|
|
200
|
+
level: "info",
|
|
201
|
+
logger: "ExpressHttpManager",
|
|
202
|
+
});
|
|
203
|
+
this.server = null;
|
|
204
|
+
return createSuccessResult(undefined);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
208
|
+
return createErrorResult(new Error(`Failed to stop Express HTTP server: ${err.message}`));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Check if server is running
|
|
213
|
+
*
|
|
214
|
+
* @returns True if server is running
|
|
215
|
+
*/
|
|
216
|
+
isRunning() {
|
|
217
|
+
return this.server?.listening ?? false;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get active session count
|
|
221
|
+
*
|
|
222
|
+
* @returns Number of active sessions
|
|
223
|
+
*/
|
|
224
|
+
getActiveSessionCount() {
|
|
225
|
+
return this.registry.getCount();
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get all active session IDs
|
|
229
|
+
*
|
|
230
|
+
* @returns Array of session IDs
|
|
231
|
+
*/
|
|
232
|
+
getActiveSessionIds() {
|
|
233
|
+
return this.registry.getSessionIds();
|
|
234
|
+
}
|
|
235
|
+
}
|