touchdesigner-mcp-server 1.3.0 → 1.4.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.
- package/README.ja.md +33 -310
- package/README.md +40 -313
- package/dist/cli.js +132 -16
- package/dist/core/compatibility.js +236 -0
- package/dist/core/logger.js +21 -1
- package/dist/core/version.js +21 -1
- package/dist/features/tools/presenter/operationFormatter.js +17 -10
- package/dist/server/touchDesignerServer.js +21 -2
- package/dist/tdClient/touchDesignerClient.js +203 -83
- 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 +17 -7
|
@@ -1,12 +1,8 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { getCompatibilityPolicy, getCompatibilityPolicyType, } from "../core/compatibility.js";
|
|
1
3
|
import { createErrorResult, createSuccessResult } from "../core/result.js";
|
|
2
|
-
import {
|
|
4
|
+
import { MCP_SERVER_VERSION, MIN_COMPATIBLE_API_VERSION, } from "../core/version.js";
|
|
3
5
|
import { createNode as apiCreateNode, deleteNode as apiDeleteNode, execNodeMethod as apiExecNodeMethod, execPythonScript as apiExecPythonScript, getModuleHelp as apiGetModuleHelp, getNodeDetail as apiGetNodeDetail, getNodeErrors as apiGetNodeErrors, getNodes as apiGetNodes, getTdInfo as apiGetTdInfo, getTdPythonClassDetails as apiGetTdPythonClassDetails, getTdPythonClasses as apiGetTdPythonClasses, updateNode as apiUpdateNode, } from "../gen/endpoints/TouchDesignerAPI.js";
|
|
4
|
-
const updateGuide = `
|
|
5
|
-
1. Download the latest [touchdesigner-mcp-td.zip](https://github.com/8beeeaaat/touchdesigner-mcp/releases/latest/download/touchdesigner-mcp-td.zip) from the releases page.
|
|
6
|
-
2. Delete the existing \`touchdesigner-mcp-td\` folder and replace it with the newly extracted contents.
|
|
7
|
-
3. Remove the old \`mcp_webserver_base\` component from your TouchDesigner project and import the \`.tox\` from the new folder.
|
|
8
|
-
4. Restart TouchDesigner and the AI agent running the MCP server (e.g., Claude Desktop).
|
|
9
|
-
`;
|
|
10
6
|
/**
|
|
11
7
|
* Default implementation of ITouchDesignerApi using generated API clients
|
|
12
8
|
*/
|
|
@@ -24,6 +20,7 @@ const defaultApiClient = {
|
|
|
24
20
|
getTdPythonClasses: apiGetTdPythonClasses,
|
|
25
21
|
updateNode: apiUpdateNode,
|
|
26
22
|
};
|
|
23
|
+
export const ERROR_CACHE_TTL_MS = 5000; // 5 seconds
|
|
27
24
|
/**
|
|
28
25
|
* Null logger implementation that discards all logs
|
|
29
26
|
*/
|
|
@@ -68,6 +65,21 @@ export class TouchDesignerClient {
|
|
|
68
65
|
logger;
|
|
69
66
|
api;
|
|
70
67
|
verifiedCompatibilityError;
|
|
68
|
+
cachedCompatibilityCheck;
|
|
69
|
+
errorCacheTimestamp;
|
|
70
|
+
/**
|
|
71
|
+
* Initialize TouchDesigner client with optional dependencies
|
|
72
|
+
*/
|
|
73
|
+
constructor(params = {}) {
|
|
74
|
+
this.logger = params.logger || nullLogger;
|
|
75
|
+
this.api = params.httpClient || defaultApiClient;
|
|
76
|
+
this.verifiedCompatibilityError = null;
|
|
77
|
+
this.cachedCompatibilityCheck = false;
|
|
78
|
+
this.errorCacheTimestamp = null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Log debug message
|
|
82
|
+
*/
|
|
71
83
|
logDebug(message, context) {
|
|
72
84
|
const data = context ? { message, ...context } : { message };
|
|
73
85
|
this.logger.sendLog({
|
|
@@ -76,176 +88,284 @@ export class TouchDesignerClient {
|
|
|
76
88
|
logger: "TouchDesignerClient",
|
|
77
89
|
});
|
|
78
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Check if the cached error should be cleared (TTL expired)
|
|
93
|
+
*/
|
|
94
|
+
shouldClearErrorCache() {
|
|
95
|
+
if (!this.errorCacheTimestamp) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
return now - this.errorCacheTimestamp >= ERROR_CACHE_TTL_MS;
|
|
100
|
+
}
|
|
79
101
|
/**
|
|
80
102
|
* Verify compatibility with the TouchDesigner server
|
|
81
103
|
*/
|
|
82
104
|
async verifyCompatibility() {
|
|
105
|
+
// If we've already verified compatibility successfully, skip re-verification
|
|
106
|
+
if (this.cachedCompatibilityCheck && !this.verifiedCompatibilityError) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// Clear cached error if TTL has expired
|
|
110
|
+
if (this.verifiedCompatibilityError && this.shouldClearErrorCache()) {
|
|
111
|
+
this.logDebug("Clearing cached connection error (TTL expired), retrying...");
|
|
112
|
+
this.verifiedCompatibilityError = null;
|
|
113
|
+
this.errorCacheTimestamp = null;
|
|
114
|
+
this.cachedCompatibilityCheck = false;
|
|
115
|
+
}
|
|
83
116
|
if (this.verifiedCompatibilityError) {
|
|
117
|
+
// Re-log the cached error so users know it's still failing
|
|
118
|
+
const ttlRemaining = this.errorCacheTimestamp
|
|
119
|
+
? Math.max(0, Math.ceil((ERROR_CACHE_TTL_MS - (Date.now() - this.errorCacheTimestamp)) /
|
|
120
|
+
1000))
|
|
121
|
+
: 0;
|
|
122
|
+
this.logDebug(`Using cached connection error (retry in ${ttlRemaining} seconds)`, {
|
|
123
|
+
cacheAge: this.errorCacheTimestamp
|
|
124
|
+
? Date.now() - this.errorCacheTimestamp
|
|
125
|
+
: 0,
|
|
126
|
+
cachedError: this.verifiedCompatibilityError.message,
|
|
127
|
+
});
|
|
84
128
|
throw this.verifiedCompatibilityError;
|
|
85
129
|
}
|
|
86
130
|
const result = await this.verifyVersionCompatibility();
|
|
87
131
|
if (result.success) {
|
|
88
132
|
this.verifiedCompatibilityError = null;
|
|
133
|
+
this.errorCacheTimestamp = null;
|
|
134
|
+
this.cachedCompatibilityCheck = true;
|
|
135
|
+
this.logDebug("Compatibility verified successfully");
|
|
89
136
|
return;
|
|
90
137
|
}
|
|
138
|
+
// Log when we're caching a NEW error
|
|
139
|
+
this.logDebug(`Caching connection error for ${ERROR_CACHE_TTL_MS / 1000} seconds`, {
|
|
140
|
+
error: result.error.message,
|
|
141
|
+
});
|
|
91
142
|
this.verifiedCompatibilityError = result.error;
|
|
143
|
+
this.errorCacheTimestamp = Date.now();
|
|
144
|
+
this.cachedCompatibilityCheck = false;
|
|
92
145
|
throw result.error;
|
|
93
146
|
}
|
|
94
147
|
/**
|
|
95
|
-
*
|
|
148
|
+
* Wrapper for API calls that require compatibility verification
|
|
149
|
+
* @private
|
|
96
150
|
*/
|
|
97
|
-
|
|
98
|
-
this.
|
|
99
|
-
this.
|
|
100
|
-
|
|
151
|
+
async apiCall(message, call, context) {
|
|
152
|
+
this.logDebug(message, context);
|
|
153
|
+
await this.verifyCompatibility();
|
|
154
|
+
const result = await call();
|
|
155
|
+
return handleApiResponse(result);
|
|
101
156
|
}
|
|
102
157
|
/**
|
|
103
158
|
* Execute a node method
|
|
104
159
|
*/
|
|
105
160
|
async execNodeMethod(params) {
|
|
106
|
-
this.
|
|
161
|
+
return this.apiCall("Executing node method", () => this.api.execNodeMethod(params), {
|
|
107
162
|
method: params.method,
|
|
108
163
|
nodePath: params.nodePath,
|
|
109
164
|
});
|
|
110
|
-
await this.verifyCompatibility();
|
|
111
|
-
const result = await this.api.execNodeMethod(params);
|
|
112
|
-
return handleApiResponse(result);
|
|
113
165
|
}
|
|
114
166
|
/**
|
|
115
167
|
* Execute a script in TouchDesigner
|
|
116
168
|
*/
|
|
117
169
|
async execPythonScript(params) {
|
|
118
|
-
this.
|
|
119
|
-
await this.verifyCompatibility();
|
|
120
|
-
const result = await this.api.execPythonScript(params);
|
|
121
|
-
return handleApiResponse(result);
|
|
170
|
+
return this.apiCall("Executing Python script", () => this.api.execPythonScript(params), { params });
|
|
122
171
|
}
|
|
123
172
|
/**
|
|
124
173
|
* Get TouchDesigner server information
|
|
125
174
|
*/
|
|
126
175
|
async getTdInfo() {
|
|
127
|
-
this.
|
|
128
|
-
await this.verifyCompatibility();
|
|
129
|
-
const result = await this.api.getTdInfo();
|
|
130
|
-
return handleApiResponse(result);
|
|
176
|
+
return this.apiCall("Getting server info", () => this.api.getTdInfo());
|
|
131
177
|
}
|
|
132
178
|
/**
|
|
133
179
|
* Get list of nodes
|
|
134
180
|
*/
|
|
135
181
|
async getNodes(params) {
|
|
136
|
-
this.
|
|
137
|
-
parentPath: params.parentPath,
|
|
138
|
-
});
|
|
139
|
-
await this.verifyCompatibility();
|
|
140
|
-
const result = await this.api.getNodes(params);
|
|
141
|
-
return handleApiResponse(result);
|
|
182
|
+
return this.apiCall("Getting nodes for parent", () => this.api.getNodes(params), { parentPath: params.parentPath });
|
|
142
183
|
}
|
|
143
184
|
/**
|
|
144
185
|
* Get node properties
|
|
145
186
|
*/
|
|
146
187
|
async getNodeDetail(params) {
|
|
147
|
-
this.
|
|
148
|
-
nodePath: params.nodePath,
|
|
149
|
-
});
|
|
150
|
-
await this.verifyCompatibility();
|
|
151
|
-
const result = await this.api.getNodeDetail(params);
|
|
152
|
-
return handleApiResponse(result);
|
|
188
|
+
return this.apiCall("Getting properties for node", () => this.api.getNodeDetail(params), { nodePath: params.nodePath });
|
|
153
189
|
}
|
|
154
190
|
/**
|
|
155
191
|
* Get node error information
|
|
156
192
|
*/
|
|
157
193
|
async getNodeErrors(params) {
|
|
158
|
-
this.
|
|
159
|
-
nodePath: params.nodePath,
|
|
160
|
-
});
|
|
161
|
-
await this.verifyCompatibility();
|
|
162
|
-
const result = await this.api.getNodeErrors(params);
|
|
163
|
-
return handleApiResponse(result);
|
|
194
|
+
return this.apiCall("Checking node errors", () => this.api.getNodeErrors(params), { nodePath: params.nodePath });
|
|
164
195
|
}
|
|
165
196
|
/**
|
|
166
197
|
* Create a new node
|
|
167
198
|
*/
|
|
168
199
|
async createNode(params) {
|
|
169
|
-
this.
|
|
200
|
+
return this.apiCall("Creating node", () => this.api.createNode(params), {
|
|
170
201
|
nodeName: params.nodeName,
|
|
171
202
|
nodeType: params.nodeType,
|
|
172
203
|
parentPath: params.parentPath,
|
|
173
204
|
});
|
|
174
|
-
await this.verifyCompatibility();
|
|
175
|
-
const result = await this.api.createNode(params);
|
|
176
|
-
return handleApiResponse(result);
|
|
177
205
|
}
|
|
178
206
|
/**
|
|
179
207
|
* Update node properties
|
|
180
208
|
*/
|
|
181
209
|
async updateNode(params) {
|
|
182
|
-
this.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return handleApiResponse(result);
|
|
210
|
+
return this.apiCall("Updating node", () => this.api.updateNode(params), {
|
|
211
|
+
nodePath: params.nodePath,
|
|
212
|
+
});
|
|
186
213
|
}
|
|
187
214
|
/**
|
|
188
215
|
* Delete a node
|
|
189
216
|
*/
|
|
190
217
|
async deleteNode(params) {
|
|
191
|
-
this.
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return handleApiResponse(result);
|
|
218
|
+
return this.apiCall("Deleting node", () => this.api.deleteNode(params), {
|
|
219
|
+
nodePath: params.nodePath,
|
|
220
|
+
});
|
|
195
221
|
}
|
|
196
222
|
/**
|
|
197
223
|
* Get list of available Python classes/modules in TouchDesigner
|
|
198
224
|
*/
|
|
199
225
|
async getClasses() {
|
|
200
|
-
this.
|
|
201
|
-
await this.verifyCompatibility();
|
|
202
|
-
const result = await this.api.getTdPythonClasses();
|
|
203
|
-
return handleApiResponse(result);
|
|
226
|
+
return this.apiCall("Getting Python classes", () => this.api.getTdPythonClasses());
|
|
204
227
|
}
|
|
205
228
|
/**
|
|
206
229
|
* Get details of a specific class/module
|
|
207
230
|
*/
|
|
208
231
|
async getClassDetails(className) {
|
|
209
|
-
this.
|
|
210
|
-
await this.verifyCompatibility();
|
|
211
|
-
const result = await this.api.getTdPythonClassDetails(className);
|
|
212
|
-
return handleApiResponse(result);
|
|
232
|
+
return this.apiCall("Getting class details", () => this.api.getTdPythonClassDetails(className), { className });
|
|
213
233
|
}
|
|
214
234
|
/**
|
|
215
235
|
* Retrieve Python help() documentation for modules/classes
|
|
216
236
|
*/
|
|
217
237
|
async getModuleHelp(params) {
|
|
218
|
-
this.
|
|
219
|
-
await this.verifyCompatibility();
|
|
220
|
-
const result = await this.api.getModuleHelp(params);
|
|
221
|
-
return handleApiResponse(result);
|
|
238
|
+
return this.apiCall("Getting module help", () => this.api.getModuleHelp(params), { moduleName: params.moduleName });
|
|
222
239
|
}
|
|
223
240
|
async verifyVersionCompatibility() {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
241
|
+
let tdInfoResult;
|
|
242
|
+
try {
|
|
243
|
+
tdInfoResult = await this.api.getTdInfo();
|
|
227
244
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
245
|
+
catch (error) {
|
|
246
|
+
// Use axios.isAxiosError() for robust network/HTTP error detection
|
|
247
|
+
// AxiosError includes connection refused, timeout, network errors, etc.
|
|
248
|
+
// All other errors (TypeError, etc.) are programming errors and should propagate
|
|
249
|
+
if (!axios.isAxiosError(error)) {
|
|
250
|
+
// This is a programming error (e.g., TypeError, ReferenceError), not a connection error
|
|
251
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
252
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
253
|
+
this.logger.sendLog({
|
|
254
|
+
data: {
|
|
255
|
+
error: errorMessage,
|
|
256
|
+
errorType: "programming_error",
|
|
257
|
+
stack: errorStack,
|
|
258
|
+
},
|
|
259
|
+
level: "error",
|
|
260
|
+
logger: "TouchDesignerClient",
|
|
261
|
+
});
|
|
262
|
+
throw error;
|
|
263
|
+
}
|
|
264
|
+
// Handle AxiosError (network/HTTP errors)
|
|
265
|
+
const rawMessage = error.message || "Unknown network error";
|
|
266
|
+
const errorMessage = this.formatConnectionError(rawMessage);
|
|
267
|
+
this.logger.sendLog({
|
|
268
|
+
data: { error: rawMessage, errorType: "connection" },
|
|
269
|
+
level: "error",
|
|
270
|
+
logger: "TouchDesignerClient",
|
|
271
|
+
});
|
|
272
|
+
return createErrorResult(new Error(errorMessage));
|
|
231
273
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (normalizedServerVersion !== normalizedApiVersion) {
|
|
274
|
+
if (!tdInfoResult.success) {
|
|
275
|
+
const errorMessage = this.formatConnectionError(tdInfoResult.error);
|
|
235
276
|
this.logger.sendLog({
|
|
236
|
-
data: {
|
|
237
|
-
message: "MCP server and TouchDesigner API server versions are incompatible",
|
|
238
|
-
touchDesignerApiVersion: normalizedApiVersion,
|
|
239
|
-
touchDesignerServerVersion: normalizedServerVersion,
|
|
240
|
-
},
|
|
277
|
+
data: { error: tdInfoResult.error, errorType: "api_response" },
|
|
241
278
|
level: "error",
|
|
242
279
|
logger: "TouchDesignerClient",
|
|
243
280
|
});
|
|
244
|
-
return createErrorResult(new Error(
|
|
281
|
+
return createErrorResult(new Error(errorMessage));
|
|
282
|
+
}
|
|
283
|
+
const apiVersionRaw = tdInfoResult.data?.mcpApiVersion?.trim() || "";
|
|
284
|
+
const result = this.checkVersionCompatibility(MCP_SERVER_VERSION, apiVersionRaw);
|
|
285
|
+
this.logger.sendLog({
|
|
286
|
+
data: {
|
|
287
|
+
apiVersion: result.details.apiVersion,
|
|
288
|
+
mcpVersion: result.details.mcpVersion,
|
|
289
|
+
message: result.message,
|
|
290
|
+
minRequired: result.details.minRequired,
|
|
291
|
+
},
|
|
292
|
+
level: result.level,
|
|
293
|
+
logger: "TouchDesignerClient",
|
|
294
|
+
});
|
|
295
|
+
if (result.level === "error") {
|
|
296
|
+
return createErrorResult(new Error(result.message));
|
|
245
297
|
}
|
|
246
298
|
return createSuccessResult(undefined);
|
|
247
299
|
}
|
|
248
|
-
|
|
249
|
-
|
|
300
|
+
/**
|
|
301
|
+
* Format connection errors with helpful messages
|
|
302
|
+
*/
|
|
303
|
+
formatConnectionError(error) {
|
|
304
|
+
if (!error) {
|
|
305
|
+
return "Failed to connect to TouchDesigner API server (unknown error)";
|
|
306
|
+
}
|
|
307
|
+
// Check for common connection errors
|
|
308
|
+
if (error.includes("ECONNREFUSED") ||
|
|
309
|
+
error.toLowerCase().includes("connect refused")) {
|
|
310
|
+
return `🔌 TouchDesigner Connection Failed
|
|
311
|
+
|
|
312
|
+
Cannot connect to TouchDesigner API server at the configured address.
|
|
313
|
+
|
|
314
|
+
Possible causes:
|
|
315
|
+
1. TouchDesigner is not running
|
|
316
|
+
→ Please start TouchDesigner
|
|
317
|
+
|
|
318
|
+
2. WebServer DAT is not active
|
|
319
|
+
→ Import 'mcp_webserver_base.tox' and ensure it's active
|
|
320
|
+
|
|
321
|
+
3. Wrong port configuration
|
|
322
|
+
→ Default port is 9981, check your configuration
|
|
323
|
+
|
|
324
|
+
For setup instructions, visit:
|
|
325
|
+
https://github.com/8beeeaaat/touchdesigner-mcp/releases/latest
|
|
326
|
+
|
|
327
|
+
Original error: ${error}`;
|
|
328
|
+
}
|
|
329
|
+
if (error.includes("ETIMEDOUT") || error.includes("timeout")) {
|
|
330
|
+
return `⏱️ TouchDesigner Connection Timeout
|
|
331
|
+
|
|
332
|
+
The connection to TouchDesigner timed out.
|
|
333
|
+
|
|
334
|
+
Possible causes:
|
|
335
|
+
1. TouchDesigner is slow to respond
|
|
336
|
+
2. Network issues
|
|
337
|
+
3. WebServer DAT is overloaded
|
|
338
|
+
|
|
339
|
+
Try restarting TouchDesigner or check the network connection.
|
|
340
|
+
|
|
341
|
+
Original error: ${error}`;
|
|
342
|
+
}
|
|
343
|
+
if (error.includes("ENOTFOUND") || error.includes("getaddrinfo")) {
|
|
344
|
+
return `🌐 Invalid Host Configuration
|
|
345
|
+
|
|
346
|
+
Cannot resolve the TouchDesigner API server hostname.
|
|
347
|
+
|
|
348
|
+
Please check your host configuration (default: 127.0.0.1)
|
|
349
|
+
|
|
350
|
+
Original error: ${error}`;
|
|
351
|
+
}
|
|
352
|
+
// Generic error message
|
|
353
|
+
return `Failed to connect to TouchDesigner API server: ${error}`;
|
|
354
|
+
}
|
|
355
|
+
checkVersionCompatibility(mcpVersion, apiVersion) {
|
|
356
|
+
const policyType = getCompatibilityPolicyType({ apiVersion, mcpVersion });
|
|
357
|
+
const policy = getCompatibilityPolicy(policyType);
|
|
358
|
+
const details = {
|
|
359
|
+
apiVersion,
|
|
360
|
+
mcpVersion,
|
|
361
|
+
minRequired: MIN_COMPATIBLE_API_VERSION,
|
|
362
|
+
};
|
|
363
|
+
const message = policy.message(details);
|
|
364
|
+
return {
|
|
365
|
+
compatible: policy.compatible,
|
|
366
|
+
details,
|
|
367
|
+
level: policy.level,
|
|
368
|
+
message,
|
|
369
|
+
};
|
|
250
370
|
}
|
|
251
371
|
}
|
|
@@ -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
|
+
};
|