touchdesigner-mcp-server 1.2.0 → 1.3.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.
@@ -1,27 +1,34 @@
1
- import { createNode as apiCreateNode, deleteNode as apiDeleteNode, execNodeMethod as apiExecNodeMethod, execPythonScript as apiExecPythonScript, getNodeDetail as apiGetNodeDetail, getNodes as apiGetNodes, getTdInfo as apiGetTdInfo, getTdPythonClassDetails as apiGetTdPythonClassDetails, getTdPythonClasses as apiGetTdPythonClasses, updateNode as apiUpdateNode, } from "../gen/endpoints/TouchDesignerAPI.js";
1
+ import { createErrorResult, createSuccessResult } from "../core/result.js";
2
+ import { PACKAGE_VERSION } from "../core/version.js";
3
+ 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
+ `;
2
10
  /**
3
11
  * Default implementation of ITouchDesignerApi using generated API clients
4
12
  */
5
13
  const defaultApiClient = {
14
+ createNode: apiCreateNode,
15
+ deleteNode: apiDeleteNode,
6
16
  execNodeMethod: apiExecNodeMethod,
7
17
  execPythonScript: apiExecPythonScript,
8
- getTdInfo: apiGetTdInfo,
9
- getNodes: apiGetNodes,
18
+ getModuleHelp: apiGetModuleHelp,
10
19
  getNodeDetail: apiGetNodeDetail,
11
- createNode: apiCreateNode,
12
- updateNode: apiUpdateNode,
13
- deleteNode: apiDeleteNode,
14
- getTdPythonClasses: apiGetTdPythonClasses,
20
+ getNodeErrors: apiGetNodeErrors,
21
+ getNodes: apiGetNodes,
22
+ getTdInfo: apiGetTdInfo,
15
23
  getTdPythonClassDetails: apiGetTdPythonClassDetails,
24
+ getTdPythonClasses: apiGetTdPythonClasses,
25
+ updateNode: apiUpdateNode,
16
26
  };
17
27
  /**
18
28
  * Null logger implementation that discards all logs
19
29
  */
20
30
  const nullLogger = {
21
- debug: () => { },
22
- log: () => { },
23
- warn: () => { },
24
- error: () => { },
31
+ sendLog: () => { },
25
32
  };
26
33
  /**
27
34
  * Handle API error response
@@ -31,9 +38,9 @@ const nullLogger = {
31
38
  function handleError(response) {
32
39
  if (response.error) {
33
40
  const errorMessage = response.error;
34
- return { success: false, error: new Error(errorMessage) };
41
+ return { error: new Error(errorMessage), success: false };
35
42
  }
36
- return { success: false, error: new Error("Unknown error occurred") };
43
+ return { error: new Error("Unknown error occurred"), success: false };
37
44
  }
38
45
  /**
39
46
  * Handle API response and return a structured result
@@ -46,12 +53,12 @@ function handleApiResponse(response) {
46
53
  return handleError(response);
47
54
  }
48
55
  if (data === null) {
49
- return { success: false, error: new Error("No data received") };
56
+ return { error: new Error("No data received"), success: false };
50
57
  }
51
58
  if (data === undefined) {
52
- return { success: false, error: new Error("No data received") };
59
+ return { error: new Error("No data received"), success: false };
53
60
  }
54
- return { success: true, data };
61
+ return { data, success: true };
55
62
  }
56
63
  /**
57
64
  * TouchDesigner client implementation with dependency injection
@@ -60,18 +67,47 @@ function handleApiResponse(response) {
60
67
  export class TouchDesignerClient {
61
68
  logger;
62
69
  api;
70
+ verifiedCompatibilityError;
71
+ logDebug(message, context) {
72
+ const data = context ? { message, ...context } : { message };
73
+ this.logger.sendLog({
74
+ data,
75
+ level: "debug",
76
+ logger: "TouchDesignerClient",
77
+ });
78
+ }
79
+ /**
80
+ * Verify compatibility with the TouchDesigner server
81
+ */
82
+ async verifyCompatibility() {
83
+ if (this.verifiedCompatibilityError) {
84
+ throw this.verifiedCompatibilityError;
85
+ }
86
+ const result = await this.verifyVersionCompatibility();
87
+ if (result.success) {
88
+ this.verifiedCompatibilityError = null;
89
+ return;
90
+ }
91
+ this.verifiedCompatibilityError = result.error;
92
+ throw result.error;
93
+ }
63
94
  /**
64
95
  * Initialize TouchDesigner client with optional dependencies
65
96
  */
66
97
  constructor(params = {}) {
67
98
  this.logger = params.logger || nullLogger;
68
99
  this.api = params.httpClient || defaultApiClient;
100
+ this.verifiedCompatibilityError = null;
69
101
  }
70
102
  /**
71
103
  * Execute a node method
72
104
  */
73
105
  async execNodeMethod(params) {
74
- this.logger.debug(`Executing node method: ${params.method} on ${params.nodePath}`);
106
+ this.logDebug("Executing node method", {
107
+ method: params.method,
108
+ nodePath: params.nodePath,
109
+ });
110
+ await this.verifyCompatibility();
75
111
  const result = await this.api.execNodeMethod(params);
76
112
  return handleApiResponse(result);
77
113
  }
@@ -79,7 +115,8 @@ export class TouchDesignerClient {
79
115
  * Execute a script in TouchDesigner
80
116
  */
81
117
  async execPythonScript(params) {
82
- this.logger.debug(`Executing Python script: ${params}`);
118
+ this.logDebug("Executing Python script", { params });
119
+ await this.verifyCompatibility();
83
120
  const result = await this.api.execPythonScript(params);
84
121
  return handleApiResponse(result);
85
122
  }
@@ -87,7 +124,8 @@ export class TouchDesignerClient {
87
124
  * Get TouchDesigner server information
88
125
  */
89
126
  async getTdInfo() {
90
- this.logger.debug("Getting server info");
127
+ this.logDebug("Getting server info");
128
+ await this.verifyCompatibility();
91
129
  const result = await this.api.getTdInfo();
92
130
  return handleApiResponse(result);
93
131
  }
@@ -95,7 +133,10 @@ export class TouchDesignerClient {
95
133
  * Get list of nodes
96
134
  */
97
135
  async getNodes(params) {
98
- this.logger.debug(`Getting nodes for parent: ${params.parentPath}`);
136
+ this.logDebug("Getting nodes for parent", {
137
+ parentPath: params.parentPath,
138
+ });
139
+ await this.verifyCompatibility();
99
140
  const result = await this.api.getNodes(params);
100
141
  return handleApiResponse(result);
101
142
  }
@@ -103,15 +144,34 @@ export class TouchDesignerClient {
103
144
  * Get node properties
104
145
  */
105
146
  async getNodeDetail(params) {
106
- this.logger.debug(`Getting properties for node: ${params.nodePath}`);
147
+ this.logDebug("Getting properties for node", {
148
+ nodePath: params.nodePath,
149
+ });
150
+ await this.verifyCompatibility();
107
151
  const result = await this.api.getNodeDetail(params);
108
152
  return handleApiResponse(result);
109
153
  }
154
+ /**
155
+ * Get node error information
156
+ */
157
+ async getNodeErrors(params) {
158
+ this.logDebug("Checking node errors", {
159
+ nodePath: params.nodePath,
160
+ });
161
+ await this.verifyCompatibility();
162
+ const result = await this.api.getNodeErrors(params);
163
+ return handleApiResponse(result);
164
+ }
110
165
  /**
111
166
  * Create a new node
112
167
  */
113
168
  async createNode(params) {
114
- this.logger.debug(`Creating node: ${params.nodeName} of type ${params.nodeType} under ${params.parentPath}`);
169
+ this.logDebug("Creating node", {
170
+ nodeName: params.nodeName,
171
+ nodeType: params.nodeType,
172
+ parentPath: params.parentPath,
173
+ });
174
+ await this.verifyCompatibility();
115
175
  const result = await this.api.createNode(params);
116
176
  return handleApiResponse(result);
117
177
  }
@@ -119,7 +179,8 @@ export class TouchDesignerClient {
119
179
  * Update node properties
120
180
  */
121
181
  async updateNode(params) {
122
- this.logger.debug(`Updating node: ${params.nodePath}`);
182
+ this.logDebug("Updating node", { nodePath: params.nodePath });
183
+ await this.verifyCompatibility();
123
184
  const result = await this.api.updateNode(params);
124
185
  return handleApiResponse(result);
125
186
  }
@@ -127,7 +188,8 @@ export class TouchDesignerClient {
127
188
  * Delete a node
128
189
  */
129
190
  async deleteNode(params) {
130
- this.logger.debug(`Deleting node: ${params.nodePath}`);
191
+ this.logDebug("Deleting node", { nodePath: params.nodePath });
192
+ await this.verifyCompatibility();
131
193
  const result = await this.api.deleteNode(params);
132
194
  return handleApiResponse(result);
133
195
  }
@@ -135,7 +197,8 @@ export class TouchDesignerClient {
135
197
  * Get list of available Python classes/modules in TouchDesigner
136
198
  */
137
199
  async getClasses() {
138
- this.logger.debug("Getting Python classes");
200
+ this.logDebug("Getting Python classes");
201
+ await this.verifyCompatibility();
139
202
  const result = await this.api.getTdPythonClasses();
140
203
  return handleApiResponse(result);
141
204
  }
@@ -143,8 +206,46 @@ export class TouchDesignerClient {
143
206
  * Get details of a specific class/module
144
207
  */
145
208
  async getClassDetails(className) {
146
- this.logger.debug(`Getting class details for: ${className}`);
209
+ this.logDebug("Getting class details", { className });
210
+ await this.verifyCompatibility();
147
211
  const result = await this.api.getTdPythonClassDetails(className);
148
212
  return handleApiResponse(result);
149
213
  }
214
+ /**
215
+ * Retrieve Python help() documentation for modules/classes
216
+ */
217
+ async getModuleHelp(params) {
218
+ this.logDebug("Getting module help", { moduleName: params.moduleName });
219
+ await this.verifyCompatibility();
220
+ const result = await this.api.getModuleHelp(params);
221
+ return handleApiResponse(result);
222
+ }
223
+ async verifyVersionCompatibility() {
224
+ const tdInfoResult = await this.api.getTdInfo();
225
+ if (!tdInfoResult.success) {
226
+ return createErrorResult(new Error(`Failed to retrieve TouchDesigner info for version check: ${tdInfoResult.error}`));
227
+ }
228
+ const apiVersion = tdInfoResult.data?.mcpApiVersion?.trim();
229
+ if (!apiVersion) {
230
+ return createErrorResult(new Error(`TouchDesigner API server did not report its version. Please reinstall the TouchDesigner components from the latest release.\n${updateGuide}`));
231
+ }
232
+ const normalizedServerVersion = this.normalizeVersion(PACKAGE_VERSION);
233
+ const normalizedApiVersion = this.normalizeVersion(apiVersion);
234
+ if (normalizedServerVersion !== normalizedApiVersion) {
235
+ this.logger.sendLog({
236
+ data: {
237
+ message: "MCP server and TouchDesigner API server versions are incompatible",
238
+ touchDesignerApiVersion: normalizedApiVersion,
239
+ touchDesignerServerVersion: normalizedServerVersion,
240
+ },
241
+ level: "error",
242
+ logger: "TouchDesignerClient",
243
+ });
244
+ return createErrorResult(new Error(`Version mismatch detected between MCP server (${normalizedServerVersion}) and TouchDesigner API server (${normalizedApiVersion}). Update both components to the same release.\n${updateGuide}`));
245
+ }
246
+ return createSuccessResult(undefined);
247
+ }
248
+ normalizeVersion(version) {
249
+ return version.trim().replace(/^v/i, "");
250
+ }
150
251
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "touchdesigner-mcp-server",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "MCP server for TouchDesigner",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,30 +23,31 @@
23
23
  "touchdesigner-mcp-server": "dist/cli.js"
24
24
  },
25
25
  "dependencies": {
26
- "@modelcontextprotocol/sdk": "^1.18.0",
26
+ "@modelcontextprotocol/sdk": "^1.23.0",
27
27
  "@mozilla/readability": "^0.6.0",
28
28
  "@types/axios": "^0.14.4",
29
29
  "@types/ws": "^8.18.1",
30
- "@types/yargs": "^17.0.33",
31
- "axios": "^1.12.2",
30
+ "@types/yargs": "^17.0.35",
31
+ "axios": "^1.13.2",
32
32
  "mustache": "^4.2.0",
33
- "yaml": "^2.8.1",
34
- "zod": "3.25.76"
33
+ "yaml": "^2.8.2",
34
+ "zod": "4.1.13"
35
35
  },
36
36
  "devDependencies": {
37
- "@biomejs/biome": "2.2.4",
38
- "@openapitools/openapi-generator-cli": "^2.23.1",
39
- "@types/jsdom": "^21.1.7",
37
+ "@biomejs/biome": "2.3.8",
38
+ "@openapitools/openapi-generator-cli": "^2.25.2",
39
+ "@types/jsdom": "^27.0.0",
40
40
  "@types/mustache": "^4.2.6",
41
- "@types/node": "^24.4.0",
42
- "@vitest/coverage-v8": "^3.2.4",
41
+ "@types/node": "^24.10.1",
42
+ "@vitest/coverage-v8": "^4.0.14",
43
43
  "archiver": "^7.0.1",
44
- "msw": "^2.11.2",
44
+ "msw": "^2.12.3",
45
45
  "npm-run-all": "^4.1.5",
46
- "orval": "^7.11.2",
46
+ "orval": "^7.17.0",
47
+ "prettier": "^3.7.3",
47
48
  "shx": "^0.4.0",
48
- "typescript": "^5.9.2",
49
- "vitest": "^3.2.4"
49
+ "typescript": "^5.9.3",
50
+ "vitest": "^4.0.14"
50
51
  },
51
52
  "type": "module",
52
53
  "exports": {
@@ -67,13 +68,19 @@
67
68
  "lint": "run-p lint:*",
68
69
  "lint:biome": "biome check",
69
70
  "lint:tsc": "tsc --noEmit",
70
- "format": "biome check --fix",
71
+ "lint:python": "ruff check td/",
72
+ "lint:yaml": "prettier --check \"**/*.{yml,yaml}\"",
73
+ "format": "run-p format:*",
74
+ "format:biome": "biome check --fix",
75
+ "format:python": "ruff format td/ && ruff check --fix td/",
76
+ "format:yaml": "prettier --write \"**/*.{yml,yaml}\"",
71
77
  "dev": "npx @modelcontextprotocol/inspector node dist/cli.js --stdio",
72
78
  "test": "run-p test:*",
73
79
  "test:integration": "vitest run ./tests/integration",
74
80
  "test:unit": "vitest run ./tests/unit",
75
81
  "coverage": "vitest run --coverage",
76
82
  "gen": "run-s gen:*",
83
+ "gen:version": "node ./scripts/syncVersions.ts",
77
84
  "gen:webserver": "openapi-generator-cli generate -i ./src/api/index.yml -g python-flask -o ./td/modules/td_server",
78
85
  "gen:handlers": "node td/genHandlers.js",
79
86
  "gen:mcp": "orval --config ./orval.config.ts"
@@ -87,6 +94,6 @@
87
94
  ]
88
95
  },
89
96
  "overrides": {
90
- "axios": "^1.12.2"
97
+ "axios": "^1.13.2"
91
98
  }
92
99
  }