touchdesigner-mcp-server 1.1.1 → 1.1.2

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.
Files changed (27) hide show
  1. package/README.ja.md +75 -26
  2. package/README.md +74 -25
  3. package/dist/core/constants.js +1 -0
  4. package/dist/features/tools/handlers/tdTools.js +162 -50
  5. package/dist/features/tools/metadata/touchDesignerToolMetadata.js +402 -0
  6. package/dist/features/tools/presenter/classListFormatter.js +187 -0
  7. package/dist/features/tools/presenter/index.js +11 -0
  8. package/dist/features/tools/presenter/markdownRenderer.js +25 -0
  9. package/dist/features/tools/presenter/nodeDetailsFormatter.js +140 -0
  10. package/dist/features/tools/presenter/nodeListFormatter.js +124 -0
  11. package/dist/features/tools/presenter/operationFormatter.js +117 -0
  12. package/dist/features/tools/presenter/presenter.js +62 -0
  13. package/dist/features/tools/presenter/responseFormatter.js +66 -0
  14. package/dist/features/tools/presenter/scriptResultFormatter.js +171 -0
  15. package/dist/features/tools/presenter/templates/markdown/classDetailsSummary.md +13 -0
  16. package/dist/features/tools/presenter/templates/markdown/classListSummary.md +7 -0
  17. package/dist/features/tools/presenter/templates/markdown/default.md +3 -0
  18. package/dist/features/tools/presenter/templates/markdown/detailedPayload.md +5 -0
  19. package/dist/features/tools/presenter/templates/markdown/nodeDetailsSummary.md +10 -0
  20. package/dist/features/tools/presenter/templates/markdown/nodeListSummary.md +8 -0
  21. package/dist/features/tools/presenter/templates/markdown/scriptSummary.md +15 -0
  22. package/dist/features/tools/presenter/toolMetadataFormatter.js +118 -0
  23. package/dist/features/tools/types.js +26 -1
  24. package/dist/gen/endpoints/TouchDesignerAPI.js +1 -1
  25. package/dist/gen/mcp/touchDesignerAPI.zod.js +1 -1
  26. package/dist/server/touchDesignerServer.js +1 -1
  27. package/package.json +6 -5
@@ -1,18 +1,83 @@
1
+ import { z } from "zod";
1
2
  import { REFERENCE_COMMENT, TOOL_NAMES } from "../../../core/constants.js";
2
3
  import { handleToolError } from "../../../core/errorHandling.js";
3
4
  import { createNodeBody, deleteNodeQueryParams, execNodeMethodBody, execPythonScriptBody, getNodeDetailQueryParams, getNodesQueryParams, getTdPythonClassDetailsParams, updateNodeBody, } from "../../../gen/mcp/touchDesignerAPI.zod.js";
5
+ import { getTouchDesignerToolMetadata } from "../metadata/touchDesignerToolMetadata.js";
6
+ import { formatClassDetails, formatClassList, formatCreateNodeResult, formatDeleteNodeResult, formatExecNodeMethodResult, formatNodeDetails, formatNodeList, formatScriptResult, formatTdInfo, formatToolMetadata, formatUpdateNodeResult, } from "../presenter/index.js";
7
+ import { detailOnlyFormattingSchema, formattingOptionsSchema, } from "../types.js";
8
+ const execPythonScriptToolSchema = execPythonScriptBody.extend(detailOnlyFormattingSchema.shape);
9
+ const tdInfoToolSchema = detailOnlyFormattingSchema;
10
+ const getNodesToolSchema = getNodesQueryParams.extend(formattingOptionsSchema.shape);
11
+ const getNodeDetailToolSchema = getNodeDetailQueryParams.extend(formattingOptionsSchema.shape);
12
+ const createNodeToolSchema = createNodeBody.extend(detailOnlyFormattingSchema.shape);
13
+ const updateNodeToolSchema = updateNodeBody.extend(detailOnlyFormattingSchema.shape);
14
+ const deleteNodeToolSchema = deleteNodeQueryParams.extend(detailOnlyFormattingSchema.shape);
15
+ const classListToolSchema = formattingOptionsSchema;
16
+ const classDetailToolSchema = getTdPythonClassDetailsParams.extend(formattingOptionsSchema.shape);
17
+ const execNodeMethodToolSchema = execNodeMethodBody.extend(detailOnlyFormattingSchema.shape);
18
+ const describeToolsSchema = detailOnlyFormattingSchema.extend({
19
+ filter: z
20
+ .string()
21
+ .min(1)
22
+ .describe("Optional keyword to filter by tool name, module path, or parameter description")
23
+ .optional(),
24
+ });
4
25
  export function registerTdTools(server, logger, tdClient) {
5
- server.tool(TOOL_NAMES.GET_TD_INFO, "Get server information from TouchDesigner", async () => {
26
+ const toolMetadataEntries = getTouchDesignerToolMetadata();
27
+ server.tool(TOOL_NAMES.DESCRIBE_TD_TOOLS, "Generate a filesystem-oriented manifest of available TouchDesigner tools", describeToolsSchema.strict().shape, async (params = {}) => {
6
28
  try {
29
+ const { detailLevel, responseFormat, filter } = params;
30
+ const normalizedFilter = filter?.trim().toLowerCase();
31
+ const filteredEntries = normalizedFilter
32
+ ? toolMetadataEntries.filter((entry) => matchesMetadataFilter(entry, normalizedFilter))
33
+ : toolMetadataEntries;
34
+ if (filteredEntries.length === 0) {
35
+ const message = filter
36
+ ? `No TouchDesigner tools matched filter "${filter}".`
37
+ : "No TouchDesigner tools are registered.";
38
+ return {
39
+ content: [
40
+ {
41
+ type: "text",
42
+ text: message,
43
+ },
44
+ ],
45
+ };
46
+ }
47
+ const formattedText = formatToolMetadata(filteredEntries, {
48
+ detailLevel: detailLevel ?? (filter ? "summary" : "minimal"),
49
+ responseFormat,
50
+ filter: normalizedFilter,
51
+ });
52
+ return {
53
+ content: [
54
+ {
55
+ type: "text",
56
+ text: formattedText,
57
+ },
58
+ ],
59
+ };
60
+ }
61
+ catch (error) {
62
+ return handleToolError(error, logger, TOOL_NAMES.DESCRIBE_TD_TOOLS);
63
+ }
64
+ });
65
+ server.tool(TOOL_NAMES.GET_TD_INFO, "Get server information from TouchDesigner", tdInfoToolSchema.strict().shape, async (params = {}) => {
66
+ try {
67
+ const { detailLevel, responseFormat } = params;
7
68
  const result = await tdClient.getTdInfo();
8
69
  if (!result.success) {
9
70
  throw result.error;
10
71
  }
72
+ const formattedText = formatTdInfo(result.data, {
73
+ detailLevel: detailLevel ?? "summary",
74
+ responseFormat,
75
+ });
11
76
  return {
12
77
  content: [
13
78
  {
14
79
  type: "text",
15
- text: `Server information: ${JSON.stringify(result, null, 2)}`,
80
+ text: formattedText,
16
81
  },
17
82
  ],
18
83
  };
@@ -21,19 +86,24 @@ export function registerTdTools(server, logger, tdClient) {
21
86
  return handleToolError(error, logger, TOOL_NAMES.GET_TD_INFO);
22
87
  }
23
88
  });
24
- server.tool(TOOL_NAMES.EXECUTE_PYTHON_SCRIPT, "Execute Python script directly in TouchDesigner", execPythonScriptBody.strict().shape, async (params) => {
89
+ server.tool(TOOL_NAMES.EXECUTE_PYTHON_SCRIPT, "Execute a Python script in TouchDesigner (detailLevel=minimal|summary|detailed, responseFormat=json|yaml|markdown)", execPythonScriptToolSchema.strict().shape, async (params) => {
25
90
  try {
26
- const { script } = params;
27
- logger.debug(`Executing script: ${script}`);
28
- const result = await tdClient.execPythonScript(params);
91
+ const { detailLevel, responseFormat, ...scriptParams } = params;
92
+ logger.debug(`Executing script: ${scriptParams.script}`);
93
+ const result = await tdClient.execPythonScript(scriptParams);
29
94
  if (!result.success) {
30
95
  throw result.error;
31
96
  }
97
+ // Use formatter for token-optimized response
98
+ const formattedText = formatScriptResult(result, scriptParams.script, {
99
+ detailLevel: detailLevel ?? "summary",
100
+ responseFormat,
101
+ });
32
102
  return {
33
103
  content: [
34
104
  {
35
105
  type: "text",
36
- text: `Script executed successfully. Result: ${JSON.stringify(result, null, 2)}`,
106
+ text: formattedText,
37
107
  },
38
108
  ],
39
109
  };
@@ -42,22 +112,22 @@ export function registerTdTools(server, logger, tdClient) {
42
112
  return handleToolError(error, logger, TOOL_NAMES.EXECUTE_PYTHON_SCRIPT);
43
113
  }
44
114
  });
45
- server.tool(TOOL_NAMES.CREATE_TD_NODE, "Create a new node in TouchDesigner", createNodeBody.strict().shape, async (params) => {
115
+ server.tool(TOOL_NAMES.CREATE_TD_NODE, "Create a new node in TouchDesigner", createNodeToolSchema.strict().shape, async (params) => {
46
116
  try {
47
- const { parentPath, nodeType, nodeName } = params;
48
- const result = await tdClient.createNode({
49
- parentPath,
50
- nodeType,
51
- nodeName,
52
- });
117
+ const { detailLevel, responseFormat, ...createParams } = params;
118
+ const result = await tdClient.createNode(createParams);
53
119
  if (!result.success) {
54
120
  throw result.error;
55
121
  }
122
+ const formattedText = formatCreateNodeResult(result.data, {
123
+ detailLevel: detailLevel ?? "summary",
124
+ responseFormat,
125
+ });
56
126
  return {
57
127
  content: [
58
128
  {
59
129
  type: "text",
60
- text: `Node created successfully: ${JSON.stringify(result, null, 2)}`,
130
+ text: formattedText,
61
131
  },
62
132
  ],
63
133
  };
@@ -66,17 +136,22 @@ export function registerTdTools(server, logger, tdClient) {
66
136
  return handleToolError(error, logger, TOOL_NAMES.CREATE_TD_NODE, REFERENCE_COMMENT);
67
137
  }
68
138
  });
69
- server.tool(TOOL_NAMES.DELETE_TD_NODE, "Delete an existing node in TouchDesigner", deleteNodeQueryParams.strict().shape, async (params) => {
139
+ server.tool(TOOL_NAMES.DELETE_TD_NODE, "Delete an existing node in TouchDesigner", deleteNodeToolSchema.strict().shape, async (params) => {
70
140
  try {
71
- const result = await tdClient.deleteNode(params);
141
+ const { detailLevel, responseFormat, ...deleteParams } = params;
142
+ const result = await tdClient.deleteNode(deleteParams);
72
143
  if (!result.success) {
73
144
  throw result.error;
74
145
  }
146
+ const formattedText = formatDeleteNodeResult(result.data, {
147
+ detailLevel: detailLevel ?? "summary",
148
+ responseFormat,
149
+ });
75
150
  return {
76
151
  content: [
77
152
  {
78
153
  type: "text",
79
- text: `Node deleted successfully: ${JSON.stringify(result, null, 2)}`,
154
+ text: formattedText,
80
155
  },
81
156
  ],
82
157
  };
@@ -85,22 +160,27 @@ export function registerTdTools(server, logger, tdClient) {
85
160
  return handleToolError(error, logger, TOOL_NAMES.DELETE_TD_NODE, REFERENCE_COMMENT);
86
161
  }
87
162
  });
88
- server.tool(TOOL_NAMES.GET_TD_NODES, "Get all nodes in the parent path (lightweight by default for better performance)", getNodesQueryParams.strict().shape, async (params) => {
163
+ server.tool(TOOL_NAMES.GET_TD_NODES, "List nodes under a path with token-optimized output (detailLevel+limit supported)", getNodesToolSchema.strict().shape, async (params) => {
89
164
  try {
90
- const result = await tdClient.getNodes(params);
165
+ const { detailLevel, limit, responseFormat, ...queryParams } = params;
166
+ const result = await tdClient.getNodes(queryParams);
91
167
  if (!result.success) {
92
168
  throw result.error;
93
169
  }
94
- const nodeCount = result.data?.nodes?.length || 0;
95
- const isLightweight = !params.includeProperties;
96
- const performanceNote = isLightweight
97
- ? " (lightweight mode - set includeProperties=true for full node details)"
98
- : " (full mode with properties)";
170
+ // Use formatter for token-optimized response
171
+ const fallbackMode = queryParams.includeProperties
172
+ ? "detailed"
173
+ : "summary";
174
+ const formattedText = formatNodeList(result.data, {
175
+ detailLevel: detailLevel ?? fallbackMode,
176
+ limit,
177
+ responseFormat,
178
+ });
99
179
  return {
100
180
  content: [
101
181
  {
102
182
  type: "text",
103
- text: `Project nodes retrieved (${nodeCount} nodes)${performanceNote}: ${JSON.stringify(result, null, 2)}`,
183
+ text: formattedText,
104
184
  },
105
185
  ],
106
186
  };
@@ -109,18 +189,24 @@ export function registerTdTools(server, logger, tdClient) {
109
189
  return handleToolError(error, logger, TOOL_NAMES.GET_TD_NODES, REFERENCE_COMMENT);
110
190
  }
111
191
  });
112
- server.tool(TOOL_NAMES.GET_TD_NODE_PARAMETERS, "Get parameters of a specific node in TouchDesigner", getNodeDetailQueryParams.strict().shape, async (params) => {
192
+ server.tool(TOOL_NAMES.GET_TD_NODE_PARAMETERS, "Get node parameters with concise/detailed formatting (detailLevel+limit supported)", getNodeDetailToolSchema.strict().shape, async (params) => {
113
193
  try {
114
- const { nodePath } = params;
115
- const result = await tdClient.getNodeDetail(params);
194
+ const { detailLevel, limit, responseFormat, ...queryParams } = params;
195
+ const result = await tdClient.getNodeDetail(queryParams);
116
196
  if (!result.success) {
117
197
  throw result.error;
118
198
  }
199
+ // Use formatter for token-optimized response
200
+ const formattedText = formatNodeDetails(result.data, {
201
+ detailLevel: detailLevel ?? "summary",
202
+ limit,
203
+ responseFormat,
204
+ });
119
205
  return {
120
206
  content: [
121
207
  {
122
208
  type: "text",
123
- text: `Node parameters for node at path ${nodePath}: ${JSON.stringify(result, null, 2)}`,
209
+ text: formattedText,
124
210
  },
125
211
  ],
126
212
  };
@@ -129,17 +215,22 @@ export function registerTdTools(server, logger, tdClient) {
129
215
  return handleToolError(error, logger, TOOL_NAMES.GET_TD_NODE_PARAMETERS, REFERENCE_COMMENT);
130
216
  }
131
217
  });
132
- server.tool(TOOL_NAMES.UPDATE_TD_NODE_PARAMETERS, "Update parameters of a specific node in TouchDesigner", updateNodeBody.strict().shape, async (params) => {
218
+ server.tool(TOOL_NAMES.UPDATE_TD_NODE_PARAMETERS, "Update parameters of a specific node in TouchDesigner", updateNodeToolSchema.strict().shape, async (params) => {
133
219
  try {
134
- const result = await tdClient.updateNode(params);
220
+ const { detailLevel, responseFormat, ...updateParams } = params;
221
+ const result = await tdClient.updateNode(updateParams);
135
222
  if (!result.success) {
136
223
  throw result.error;
137
224
  }
225
+ const formattedText = formatUpdateNodeResult(result.data, {
226
+ detailLevel: detailLevel ?? "summary",
227
+ responseFormat,
228
+ });
138
229
  return {
139
230
  content: [
140
231
  {
141
232
  type: "text",
142
- text: `Node parameters updated successfully: ${JSON.stringify(result, null, 2)}`,
233
+ text: formattedText,
143
234
  },
144
235
  ],
145
236
  };
@@ -148,26 +239,20 @@ export function registerTdTools(server, logger, tdClient) {
148
239
  return handleToolError(error, logger, TOOL_NAMES.UPDATE_TD_NODE_PARAMETERS, REFERENCE_COMMENT);
149
240
  }
150
241
  });
151
- server.tool(TOOL_NAMES.EXECUTE_NODE_METHOD, "Execute a method on a specific node in TouchDesigner", execNodeMethodBody.strict().shape, async (params) => {
242
+ server.tool(TOOL_NAMES.EXECUTE_NODE_METHOD, "Execute a method on a specific node in TouchDesigner", execNodeMethodToolSchema.strict().shape, async (params) => {
152
243
  try {
153
- const { nodePath, method, args } = params;
154
- const result = await tdClient.execNodeMethod(params);
244
+ const { detailLevel, responseFormat, ...execParams } = params;
245
+ const { nodePath, method, args, kwargs } = execParams;
246
+ const result = await tdClient.execNodeMethod(execParams);
155
247
  if (!result.success) {
156
248
  throw result.error;
157
249
  }
250
+ const formattedText = formatExecNodeMethodResult(result.data, { nodePath, method, args, kwargs }, { detailLevel: detailLevel ?? "summary", responseFormat });
158
251
  return {
159
252
  content: [
160
253
  {
161
254
  type: "text",
162
- text: `op('${nodePath}').${method}(${args?.map((v) => `"${v}"`).join(",")}${params.kwargs
163
- ? Object.entries(params.kwargs)
164
- .map(([key, value]) => `${key}=${JSON.stringify(value)}`)
165
- .join(", ")
166
- : ""})`,
167
- },
168
- {
169
- type: "text",
170
- text: JSON.stringify(result, null, 2),
255
+ text: formattedText,
171
256
  },
172
257
  ],
173
258
  };
@@ -177,17 +262,23 @@ export function registerTdTools(server, logger, tdClient) {
177
262
  return handleToolError(error, logger, TOOL_NAMES.EXECUTE_NODE_METHOD, REFERENCE_COMMENT);
178
263
  }
179
264
  });
180
- server.tool(TOOL_NAMES.GET_TD_CLASSES, "Get list of classes and modules in TouchDesigner", async () => {
265
+ server.tool(TOOL_NAMES.GET_TD_CLASSES, "List TouchDesigner Python classes/modules (detailLevel+limit supported)", classListToolSchema.strict().shape, async (params = {}) => {
181
266
  try {
182
267
  const result = await tdClient.getClasses();
183
268
  if (!result.success) {
184
269
  throw result.error;
185
270
  }
271
+ // Use formatter for token-optimized response
272
+ const formattedText = formatClassList(result.data, {
273
+ detailLevel: params.detailLevel ?? "summary",
274
+ limit: params.limit ?? 50,
275
+ responseFormat: params.responseFormat,
276
+ });
186
277
  return {
187
278
  content: [
188
279
  {
189
280
  type: "text",
190
- text: `TouchDesigner classes list: ${JSON.stringify(result, null, 2)}`,
281
+ text: formattedText,
191
282
  },
192
283
  ],
193
284
  };
@@ -196,18 +287,24 @@ export function registerTdTools(server, logger, tdClient) {
196
287
  return handleToolError(error, logger, TOOL_NAMES.GET_TD_CLASSES, REFERENCE_COMMENT);
197
288
  }
198
289
  });
199
- server.tool(TOOL_NAMES.GET_TD_CLASS_DETAILS, "Get detailed information about a specific TouchDesigner class or module", getTdPythonClassDetailsParams.strict().shape, async (params) => {
290
+ server.tool(TOOL_NAMES.GET_TD_CLASS_DETAILS, "Get information about a TouchDesigner class/module (detailLevel+limit supported)", classDetailToolSchema.strict().shape, async (params) => {
200
291
  try {
201
- const { className } = params;
292
+ const { className, detailLevel, limit, responseFormat } = params;
202
293
  const result = await tdClient.getClassDetails(className);
203
294
  if (!result.success) {
204
295
  throw result.error;
205
296
  }
297
+ // Use formatter for token-optimized response
298
+ const formattedText = formatClassDetails(result.data, {
299
+ detailLevel: detailLevel ?? "summary",
300
+ limit: limit ?? 30,
301
+ responseFormat,
302
+ });
206
303
  return {
207
304
  content: [
208
305
  {
209
306
  type: "text",
210
- text: `Details for ${className}: ${JSON.stringify(result, null, 2)}`,
307
+ text: formattedText,
211
308
  },
212
309
  ],
213
310
  };
@@ -217,3 +314,18 @@ export function registerTdTools(server, logger, tdClient) {
217
314
  }
218
315
  });
219
316
  }
317
+ function matchesMetadataFilter(entry, keyword) {
318
+ const normalizedKeyword = keyword.toLowerCase();
319
+ const haystacks = [
320
+ entry.functionName,
321
+ entry.modulePath,
322
+ entry.description,
323
+ entry.category,
324
+ entry.tool,
325
+ entry.notes ?? "",
326
+ ];
327
+ if (haystacks.some((value) => value.toLowerCase().includes(normalizedKeyword))) {
328
+ return true;
329
+ }
330
+ return entry.parameters.some((param) => [param.name, param.type, param.description ?? ""].some((value) => value.toLowerCase().includes(normalizedKeyword)));
331
+ }