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.
@@ -14,9 +14,9 @@ export function formatTdInfo(data, options) {
14
14
  ? `Server: ${data.server}, v${data.version}`
15
15
  : `${summary}${osLine}`;
16
16
  return finalizeFormattedText(text.trim(), opts, {
17
- template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
18
17
  context: { title: "TouchDesigner Info", ...data },
19
18
  structured: data,
19
+ template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
20
20
  });
21
21
  }
22
22
  export function formatCreateNodeResult(data, options) {
@@ -37,9 +37,9 @@ export function formatCreateNodeResult(data, options) {
37
37
  ? base
38
38
  : `${base}\nProperties detected: ${propCount}`;
39
39
  return finalizeFormattedText(text, opts, {
40
- template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
41
- context: { title: "Create Node", path, opType },
40
+ context: { opType, path, title: "Create Node" },
42
41
  structured: data,
42
+ template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
43
43
  });
44
44
  }
45
45
  export function formatUpdateNodeResult(data, options) {
@@ -51,15 +51,15 @@ export function formatUpdateNodeResult(data, options) {
51
51
  ? base
52
52
  : `${base}${failedCount ? `, ${failedCount} failed` : ""}`;
53
53
  const context = {
54
- title: "Update Node",
55
- updated: data?.updated,
56
54
  failed: data?.failed,
57
55
  message: data?.message,
56
+ title: "Update Node",
57
+ updated: data?.updated,
58
58
  };
59
59
  return finalizeFormattedText(text, opts, {
60
- template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
61
60
  context,
62
61
  structured: data,
62
+ template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
63
63
  });
64
64
  }
65
65
  export function formatDeleteNodeResult(data, options) {
@@ -71,9 +71,9 @@ export function formatDeleteNodeResult(data, options) {
71
71
  ? `🗑️ Deleted '${name}' at ${path}`
72
72
  : `Deletion status unknown for '${name}' at ${path}`;
73
73
  return finalizeFormattedText(text, opts, {
74
- template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
75
- context: { title: "Delete Node", path, deleted },
74
+ context: { deleted, path, title: "Delete Node" },
76
75
  structured: data,
76
+ template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
77
77
  });
78
78
  }
79
79
  export function formatExecNodeMethodResult(data, context, options) {
@@ -82,9 +82,9 @@ export function formatExecNodeMethodResult(data, context, options) {
82
82
  const resultPreview = summarizeValue(data?.result);
83
83
  const text = `${callSignature}\nResult: ${resultPreview}`;
84
84
  return finalizeFormattedText(text, opts, {
85
- template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
86
- context: { title: "Execute Node Method", callSignature },
85
+ context: { callSignature, title: "Execute Node Method" },
87
86
  structured: { ...context, result: data?.result },
87
+ template: opts.detailLevel === "detailed" ? "detailedPayload" : "default",
88
88
  });
89
89
  }
90
90
  function buildCallSignature(params) {
@@ -13,8 +13,8 @@ import { presentStructuredData } from "./presenter.js";
13
13
  */
14
14
  export const DEFAULT_FORMATTER_OPTIONS = {
15
15
  detailLevel: "summary",
16
- limit: undefined,
17
16
  includeHints: true,
17
+ limit: undefined,
18
18
  responseFormat: undefined,
19
19
  };
20
20
  /**
@@ -27,8 +27,8 @@ export function mergeFormatterOptions(options) {
27
27
  DEFAULT_FORMATTER_OPTIONS.detailLevel;
28
28
  return {
29
29
  detailLevel,
30
- limit: merged.limit,
31
30
  includeHints: merged.includeHints ?? true,
31
+ limit: merged.limit,
32
32
  responseFormat: merged.responseFormat,
33
33
  };
34
34
  }
@@ -36,11 +36,11 @@ export function finalizeFormattedText(text, opts, metadata) {
36
36
  const chosenFormat = opts.responseFormat ??
37
37
  (opts.detailLevel === "detailed" ? "yaml" : "markdown");
38
38
  return presentStructuredData({
39
- text,
39
+ context: metadata?.context,
40
40
  detailLevel: opts.detailLevel,
41
41
  structured: metadata?.structured,
42
- context: metadata?.context,
43
42
  template: metadata?.template,
43
+ text,
44
44
  }, chosenFormat);
45
45
  }
46
46
  /**
@@ -41,9 +41,9 @@ export function formatScriptResult(data, scriptSnippet, options) {
41
41
  }
42
42
  const ctx = context;
43
43
  return finalizeFormattedText(formattedText, opts, {
44
- template: "scriptSummary",
45
44
  context: ctx,
46
45
  structured: ctx,
46
+ template: "scriptSummary",
47
47
  });
48
48
  }
49
49
  /**
@@ -98,25 +98,25 @@ function formatSummary(result, output, scriptSnippet) {
98
98
  formatted += `\nOutput:\n${outputPreview}`;
99
99
  }
100
100
  return {
101
- text: formatted,
102
101
  context: {
103
- snippet,
104
- resultType: getValueType(result),
105
- resultPreview,
106
102
  hasOutput: Boolean(outputPreview),
107
- outputType: getValueType(output),
108
103
  outputPreview,
104
+ outputType: getValueType(output),
105
+ resultPreview,
106
+ resultType: getValueType(result),
107
+ snippet,
109
108
  },
109
+ text: formatted,
110
110
  };
111
111
  }
112
112
  function buildScriptContext(scriptSnippet, result, output) {
113
113
  return {
114
- snippet: scriptSnippet ? truncateScript(scriptSnippet) : "",
115
- resultType: getValueType(result),
116
- resultPreview: formatResultValue(result ?? "", 200),
117
114
  hasOutput: Boolean(output?.trim()),
118
- outputType: getValueType(output),
119
115
  outputPreview: output,
116
+ outputType: getValueType(output),
117
+ resultPreview: formatResultValue(result ?? "", 200),
118
+ resultType: getValueType(result),
119
+ snippet: scriptSnippet ? truncateScript(scriptSnippet) : "",
120
120
  };
121
121
  }
122
122
  /**
@@ -126,14 +126,14 @@ function formatDetailed(data, format) {
126
126
  const title = "Script Result";
127
127
  const payloadFormat = format ?? DEFAULT_PRESENTER_FORMAT;
128
128
  return presentStructuredData({
129
- text: title,
130
- detailLevel: "detailed",
131
- structured: data,
132
129
  context: {
133
- title,
134
130
  payloadFormat,
131
+ title,
135
132
  },
133
+ detailLevel: "detailed",
134
+ structured: data,
136
135
  template: "detailedPayload",
136
+ text: title,
137
137
  }, payloadFormat);
138
138
  }
139
139
  function getValueType(value) {
@@ -3,27 +3,27 @@ export function formatToolMetadata(entries, options) {
3
3
  const { detailLevel, responseFormat } = mergeFormatterOptions(options);
4
4
  if (entries.length === 0) {
5
5
  return finalizeFormattedText("No tools matched the requested criteria.", { detailLevel, responseFormat }, {
6
- context: { totalTools: 0, filter: options?.filter },
6
+ context: { filter: options?.filter, totalTools: 0 },
7
7
  });
8
8
  }
9
9
  const sortedEntries = [...entries].sort((a, b) => a.modulePath.localeCompare(b.modulePath));
10
10
  const structured = sortedEntries.map((entry) => ({
11
- tool: entry.tool,
12
- modulePath: entry.modulePath,
13
- functionName: entry.functionName,
14
- description: entry.description,
15
11
  category: entry.category,
12
+ description: entry.description,
13
+ functionName: entry.functionName,
14
+ modulePath: entry.modulePath,
15
+ notes: entry.notes,
16
16
  parameters: entry.parameters,
17
17
  returns: entry.returns,
18
- notes: entry.notes,
18
+ tool: entry.tool,
19
19
  }));
20
20
  const text = buildText(sortedEntries, detailLevel);
21
21
  return finalizeFormattedText(text, { detailLevel, responseFormat }, {
22
- structured,
23
22
  context: {
24
- totalTools: sortedEntries.length,
25
23
  filter: options?.filter,
24
+ totalTools: sortedEntries.length,
26
25
  },
26
+ structured,
27
27
  template: detailLevel === "detailed" ? "detailedPayload" : undefined,
28
28
  });
29
29
  }
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Generated by orval v7.11.2 🍺
2
+ * Generated by orval v7.17.0 🍺
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.1.2
6
+ * OpenAPI spec version: 1.3.0
7
7
  */
8
8
  import { customInstance } from '../../api/customInstance.js';
9
9
  // eslint-disable-next-line @typescript-eslint/no-redeclare
@@ -17,14 +17,14 @@ export const TdNodeFamilyType = {
17
17
  CUSTOM: 'CUSTOM',
18
18
  };
19
19
  // eslint-disable-next-line @typescript-eslint/no-redeclare
20
- export const TdPythonClassInfoType = {
20
+ export const TdPythonClassDetailsType = {
21
21
  class: 'class',
22
22
  module: 'module',
23
23
  function: 'function',
24
24
  object: 'object',
25
25
  };
26
26
  // eslint-disable-next-line @typescript-eslint/no-redeclare
27
- export const TdPythonClassDetailsType = {
27
+ export const TdPythonClassInfoType = {
28
28
  class: 'class',
29
29
  module: 'module',
30
30
  function: 'function',
@@ -73,6 +73,15 @@ export const updateNode = (updateNodeRequest, options) => {
73
73
  data: updateNodeRequest
74
74
  }, options);
75
75
  };
76
+ /**
77
+ * Collects TouchDesigner error messages for a node and its children
78
+ * @summary Get node errors
79
+ */
80
+ export const getNodeErrors = (params, options) => {
81
+ return customInstance({ url: `${process.env.TD_WEB_SERVER_HOST}:${process.env.TD_WEB_SERVER_PORT}/api/nodes/errors`, method: 'GET',
82
+ params
83
+ }, options);
84
+ };
76
85
  /**
77
86
  * Returns a list of Python classes, modules, and functions available in TouchDesigner
78
87
  * @summary Get a list of Python classes and modules
@@ -89,6 +98,15 @@ export const getTdPythonClassDetails = (className, options) => {
89
98
  return customInstance({ url: `${process.env.TD_WEB_SERVER_HOST}:${process.env.TD_WEB_SERVER_PORT}/api/td/classes/${className}`, method: 'GET'
90
99
  }, options);
91
100
  };
101
+ /**
102
+ * Retrieve Python help() documentation for TouchDesigner modules, classes, or utilities like tdu.
103
+ * @summary Get module/class Python help documentation
104
+ */
105
+ export const getModuleHelp = (params, options) => {
106
+ return customInstance({ url: `${process.env.TD_WEB_SERVER_HOST}:${process.env.TD_WEB_SERVER_PORT}/api/td/modules/help`, method: 'GET',
107
+ params
108
+ }, options);
109
+ };
92
110
  /**
93
111
  * Call a method on the node at the specified path (e.g., /project1).
94
112
  This allows operations equivalent to TouchDesigner's Python API such as
@@ -1,11 +1,11 @@
1
1
  /**
2
- * Generated by orval v7.11.2 🍺
2
+ * Generated by orval v7.17.0 🍺
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.1.2
6
+ * OpenAPI spec version: 1.3.0
7
7
  */
8
- import { z as zod } from 'zod';
8
+ import * as zod from 'zod';
9
9
  /**
10
10
  * @summary Delete an existing node
11
11
  */
@@ -21,7 +21,7 @@ export const deleteNodeResponse = zod.object({
21
21
  "opType": zod.string(),
22
22
  "name": zod.string(),
23
23
  "path": zod.string(),
24
- "properties": zod.record(zod.string(), zod.any())
24
+ "properties": zod.record(zod.string(), zod.unknown())
25
25
  }).optional().describe('Information about a TouchDesigner node')
26
26
  }).nullable(),
27
27
  "error": zod.string().nullable().describe('Error message if the operation was not successful')
@@ -44,7 +44,7 @@ export const getNodesResponse = zod.object({
44
44
  "opType": zod.string(),
45
45
  "name": zod.string(),
46
46
  "path": zod.string(),
47
- "properties": zod.record(zod.string(), zod.any())
47
+ "properties": zod.record(zod.string(), zod.unknown())
48
48
  }).describe('Information about a TouchDesigner node')).optional().describe('Result of the execution')
49
49
  }).nullable(),
50
50
  "error": zod.string().nullable().describe('Error message if the operation was not successful')
@@ -65,7 +65,7 @@ export const createNodeResponse = zod.object({
65
65
  "opType": zod.string(),
66
66
  "name": zod.string(),
67
67
  "path": zod.string(),
68
- "properties": zod.record(zod.string(), zod.any())
68
+ "properties": zod.record(zod.string(), zod.unknown())
69
69
  }).optional().describe('Information about a TouchDesigner node')
70
70
  }).nullable(),
71
71
  "error": zod.string().nullable().describe('Error message if the operation was not successful')
@@ -84,7 +84,7 @@ export const getNodeDetailResponse = zod.object({
84
84
  "opType": zod.string(),
85
85
  "name": zod.string(),
86
86
  "path": zod.string(),
87
- "properties": zod.record(zod.string(), zod.any())
87
+ "properties": zod.record(zod.string(), zod.unknown())
88
88
  }).describe('Information about a TouchDesigner node'),
89
89
  "error": zod.string().nullable().describe('Error message if the operation was not successful')
90
90
  });
@@ -93,7 +93,7 @@ export const getNodeDetailResponse = zod.object({
93
93
  */
94
94
  export const updateNodeBody = zod.object({
95
95
  "nodePath": zod.string().describe('Path to the node (e.g., /project1/null1)'),
96
- "properties": zod.record(zod.string(), zod.any())
96
+ "properties": zod.record(zod.string(), zod.unknown())
97
97
  });
98
98
  export const updateNodeResponse = zod.object({
99
99
  "success": zod.boolean().describe('Whether the update operation was successful'),
@@ -108,6 +108,30 @@ export const updateNodeResponse = zod.object({
108
108
  }).nullable(),
109
109
  "error": zod.string().nullable().describe('Error message if the operation was not successful')
110
110
  });
111
+ /**
112
+ * Collects TouchDesigner error messages for a node and its children
113
+ * @summary Get node errors
114
+ */
115
+ export const getNodeErrorsQueryParams = zod.object({
116
+ "nodePath": zod.string().describe('Absolute path to the node to inspect. e.g., \"/project1/text1\"')
117
+ });
118
+ export const getNodeErrorsResponse = zod.object({
119
+ "success": zod.boolean().describe('Whether the operation was successful'),
120
+ "data": zod.object({
121
+ "nodePath": zod.string().describe('Path that was inspected for errors'),
122
+ "nodeName": zod.string().describe('Name of the inspected node'),
123
+ "opType": zod.string().describe('Operator type of the inspected node'),
124
+ "errorCount": zod.number().describe('Number of errors that were discovered'),
125
+ "hasErrors": zod.boolean().describe('Convenience flag indicating if any errors were found'),
126
+ "errors": zod.array(zod.object({
127
+ "nodePath": zod.string().describe('Absolute operator path that reported the error'),
128
+ "nodeName": zod.string().describe('Name of the operator'),
129
+ "opType": zod.string().describe('TouchDesigner operator type'),
130
+ "message": zod.string().describe('Error message reported by TouchDesigner')
131
+ }).describe('Single node error entry')).describe('Collection of raw node errors')
132
+ }).describe('Aggregated node error report'),
133
+ "error": zod.string().nullable().describe('Error message if the operation was not successful')
134
+ });
111
135
  /**
112
136
  * Returns a list of Python classes, modules, and functions available in TouchDesigner
113
137
  * @summary Get a list of Python classes and modules
@@ -149,6 +173,21 @@ export const getTdPythonClassDetailsResponse = zod.object({
149
173
  }).describe('Detailed information about a Python class or module'),
150
174
  "error": zod.string().nullable().describe('Error message if the operation was not successful')
151
175
  });
176
+ /**
177
+ * Retrieve Python help() documentation for TouchDesigner modules, classes, or utilities like tdu.
178
+ * @summary Get module/class Python help documentation
179
+ */
180
+ export const getModuleHelpQueryParams = zod.object({
181
+ "moduleName": zod.string().describe('Module or class name (e.g., \"noiseCHOP\", \"td.noiseCHOP\", \"tdu\").')
182
+ });
183
+ export const getModuleHelpResponse = zod.object({
184
+ "success": zod.boolean().describe('Whether the operation was successful'),
185
+ "data": zod.object({
186
+ "moduleName": zod.string().describe('Normalized module/class name the help text was generated for'),
187
+ "helpText": zod.string().describe('Captured output from Python\'s help() function')
188
+ }).describe('Raw Python help() output for a TouchDesigner module or class'),
189
+ "error": zod.string().nullable().describe('Error message if the operation was not successful')
190
+ });
152
191
  /**
153
192
  * Call a method on the node at the specified path (e.g., /project1).
154
193
  This allows operations equivalent to TouchDesigner's Python API such as
@@ -160,7 +199,7 @@ export const execNodeMethodBody = zod.object({
160
199
  "nodePath": zod.string().describe('Path to the node (e.g., /project1/null1)'),
161
200
  "method": zod.string().describe('Name of the method to call'),
162
201
  "args": zod.array(zod.union([zod.string(), zod.number(), zod.boolean()])).optional().describe('List of arguments for the method call'),
163
- "kwargs": zod.record(zod.string(), zod.any()).optional().describe('Keyword arguments for the method call')
202
+ "kwargs": zod.record(zod.string(), zod.unknown()).optional().describe('Keyword arguments for the method call')
164
203
  });
165
204
  export const execNodeMethodResponse = zod.object({
166
205
  "success": zod.boolean().describe('Whether the operation was successful'),
@@ -196,10 +235,11 @@ export const execPythonScriptResponse = zod.object({
196
235
  export const getTdInfoResponse = zod.object({
197
236
  "success": zod.boolean().describe('Whether the operation was successful'),
198
237
  "data": zod.object({
199
- "server": zod.string().describe('Server name (typically \"TouchDesigner\")'),
200
- "version": zod.string().describe('TouchDesigner version number'),
238
+ "mcpApiVersion": zod.string().describe('Version of the TouchDesigner MCP API server'),
201
239
  "osName": zod.string().describe('Operating system name'),
202
- "osVersion": zod.string().describe('Operating system version')
240
+ "osVersion": zod.string().describe('Operating system version'),
241
+ "server": zod.string().describe('Server name (typically \"TouchDesigner\")'),
242
+ "version": zod.string().describe('TouchDesigner version number')
203
243
  }).nullable(),
204
244
  "error": zod.string().nullable().describe('Error message if the operation was not successful')
205
245
  });
@@ -5,31 +5,31 @@ import { createErrorResult, createSuccessResult } from "../core/result.js";
5
5
  export class ConnectionManager {
6
6
  server;
7
7
  logger;
8
- tdClient;
9
8
  transport = null;
10
- constructor(server, logger, tdClient) {
9
+ constructor(server, logger) {
11
10
  this.server = server;
12
11
  this.logger = logger;
13
- this.tdClient = tdClient;
14
12
  }
15
13
  /**
16
14
  * Connect to MCP transport
17
15
  */
18
16
  async connect(transport) {
19
17
  if (this.isConnected()) {
20
- this.logger.log("MCP server already connected");
18
+ this.logger.sendLog({
19
+ data: "MCP server already connected",
20
+ level: "info",
21
+ logger: "ConnectionManager",
22
+ });
21
23
  return createSuccessResult(undefined);
22
24
  }
23
25
  this.transport = transport;
24
26
  try {
25
27
  await this.server.connect(transport);
26
- this.logger.log(`Server connected and ready to process requests: ${process.env.TD_WEB_SERVER_HOST}:${process.env.TD_WEB_SERVER_PORT}`);
27
- // Connection will be checked when tools are actually used
28
- const connectionResult = await this.checkTDConnection();
29
- if (!connectionResult.success) {
30
- throw new Error(`Failed to connect to TouchDesigner. The mcp_webserver_base on TouchDesigner not currently available: ${connectionResult.error.message}`);
31
- }
32
- this.logger.log("TouchDesigner connection verified");
28
+ this.logger.sendLog({
29
+ data: `Server connected and ready to process requests: ${process.env.TD_WEB_SERVER_HOST}:${process.env.TD_WEB_SERVER_PORT}`,
30
+ level: "info",
31
+ logger: "ConnectionManager",
32
+ });
33
33
  return createSuccessResult(undefined);
34
34
  }
35
35
  catch (error) {
@@ -65,21 +65,4 @@ export class ConnectionManager {
65
65
  isConnected() {
66
66
  return this.transport !== null;
67
67
  }
68
- /**
69
- * Check connection to TouchDesigner
70
- */
71
- async checkTDConnection() {
72
- this.logger.log("Testing connection to TouchDesigner server...");
73
- try {
74
- const result = await this.tdClient.getTdInfo();
75
- if (!result.success) {
76
- throw result.error;
77
- }
78
- return createSuccessResult(result.data);
79
- }
80
- catch (error) {
81
- const err = error instanceof Error ? error : new Error(String(error));
82
- return createErrorResult(err);
83
- }
84
- }
85
68
  }
@@ -1,5 +1,6 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { McpLogger } from "../core/logger.js";
3
+ import { PACKAGE_VERSION } from "../core/version.js";
3
4
  import { registerPrompts } from "../features/prompts/index.js";
4
5
  import { registerTools } from "../features/tools/index.js";
5
6
  import { createTouchDesignerClient } from "../tdClient/index.js";
@@ -18,17 +19,17 @@ export class TouchDesignerServer {
18
19
  constructor() {
19
20
  this.server = new McpServer({
20
21
  name: "TouchDesigner",
21
- version: "1.1.2",
22
+ version: PACKAGE_VERSION,
22
23
  }, {
23
24
  capabilities: {
24
- prompts: {},
25
25
  logging: {},
26
+ prompts: {},
26
27
  tools: {},
27
28
  },
28
29
  });
29
30
  this.logger = new McpLogger(this.server);
30
31
  this.tdClient = createTouchDesignerClient({ logger: this.logger });
31
- this.connectionManager = new ConnectionManager(this.server, this.logger, this.tdClient);
32
+ this.connectionManager = new ConnectionManager(this.server, this.logger);
32
33
  this.registerAllFeatures();
33
34
  }
34
35
  /**