touchdesigner-mcp-server 0.4.7 → 1.1.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 +75 -26
- package/README.md +74 -25
- package/dist/core/constants.js +1 -0
- package/dist/features/tools/handlers/tdTools.js +162 -50
- package/dist/features/tools/metadata/touchDesignerToolMetadata.js +402 -0
- package/dist/features/tools/presenter/classListFormatter.js +187 -0
- package/dist/features/tools/presenter/index.js +11 -0
- package/dist/features/tools/presenter/markdownRenderer.js +25 -0
- package/dist/features/tools/presenter/nodeDetailsFormatter.js +140 -0
- package/dist/features/tools/presenter/nodeListFormatter.js +124 -0
- package/dist/features/tools/presenter/operationFormatter.js +117 -0
- package/dist/features/tools/presenter/presenter.js +62 -0
- package/dist/features/tools/presenter/responseFormatter.js +66 -0
- package/dist/features/tools/presenter/scriptResultFormatter.js +171 -0
- package/dist/features/tools/presenter/templates/markdown/classDetailsSummary.md +13 -0
- package/dist/features/tools/presenter/templates/markdown/classListSummary.md +7 -0
- package/dist/features/tools/presenter/templates/markdown/default.md +3 -0
- package/dist/features/tools/presenter/templates/markdown/detailedPayload.md +5 -0
- package/dist/features/tools/presenter/templates/markdown/nodeDetailsSummary.md +10 -0
- package/dist/features/tools/presenter/templates/markdown/nodeListSummary.md +8 -0
- package/dist/features/tools/presenter/templates/markdown/scriptSummary.md +15 -0
- package/dist/features/tools/presenter/toolMetadataFormatter.js +118 -0
- package/dist/features/tools/types.js +26 -1
- package/dist/gen/endpoints/TouchDesignerAPI.js +1 -1
- package/dist/gen/mcp/touchDesignerAPI.zod.js +1 -1
- package/dist/server/touchDesignerServer.js +1 -1
- package/package.json +6 -4
|
@@ -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
|
-
|
|
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:
|
|
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
|
|
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 {
|
|
27
|
-
logger.debug(`Executing script: ${script}`);
|
|
28
|
-
const result = await tdClient.execPythonScript(
|
|
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:
|
|
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",
|
|
115
|
+
server.tool(TOOL_NAMES.CREATE_TD_NODE, "Create a new node in TouchDesigner", createNodeToolSchema.strict().shape, async (params) => {
|
|
46
116
|
try {
|
|
47
|
-
const {
|
|
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:
|
|
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",
|
|
139
|
+
server.tool(TOOL_NAMES.DELETE_TD_NODE, "Delete an existing node in TouchDesigner", deleteNodeToolSchema.strict().shape, async (params) => {
|
|
70
140
|
try {
|
|
71
|
-
const
|
|
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:
|
|
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, "
|
|
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
|
|
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
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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:
|
|
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
|
|
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 {
|
|
115
|
-
const result = await tdClient.getNodeDetail(
|
|
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:
|
|
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",
|
|
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
|
|
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:
|
|
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",
|
|
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 {
|
|
154
|
-
const
|
|
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:
|
|
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, "
|
|
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:
|
|
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
|
|
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:
|
|
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
|
+
}
|