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.
- 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 -5
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { TOOL_NAMES } from "../../../core/constants.js";
|
|
2
|
+
const MODULE_ROOT = "servers/touchdesigner";
|
|
3
|
+
export const TOUCH_DESIGNER_TOOL_METADATA = [
|
|
4
|
+
{
|
|
5
|
+
tool: TOOL_NAMES.GET_TD_INFO,
|
|
6
|
+
modulePath: `${MODULE_ROOT}/getTdInfo.ts`,
|
|
7
|
+
functionName: "getTdInfo",
|
|
8
|
+
description: "Get server information from TouchDesigner",
|
|
9
|
+
category: "system",
|
|
10
|
+
parameters: [
|
|
11
|
+
{
|
|
12
|
+
name: "detailLevel",
|
|
13
|
+
type: "'minimal' | 'summary' | 'detailed'",
|
|
14
|
+
required: false,
|
|
15
|
+
description: "Optional presenter granularity for formatted output.",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: "responseFormat",
|
|
19
|
+
type: "'json' | 'yaml' | 'markdown'",
|
|
20
|
+
required: false,
|
|
21
|
+
description: "Overrides the formatter output format for automation.",
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
returns: "TouchDesigner build metadata (server, version, operating system).",
|
|
25
|
+
example: `import { getTdInfo } from './servers/touchdesigner/getTdInfo';
|
|
26
|
+
|
|
27
|
+
const info = await getTdInfo();
|
|
28
|
+
console.log(\`\${info.server} \${info.version}\`);`,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
tool: TOOL_NAMES.EXECUTE_PYTHON_SCRIPT,
|
|
32
|
+
modulePath: `${MODULE_ROOT}/executePythonScript.ts`,
|
|
33
|
+
functionName: "executePythonScript",
|
|
34
|
+
description: "Execute arbitrary Python against the TouchDesigner session",
|
|
35
|
+
category: "python",
|
|
36
|
+
parameters: [
|
|
37
|
+
{
|
|
38
|
+
name: "script",
|
|
39
|
+
type: "string",
|
|
40
|
+
required: true,
|
|
41
|
+
description: "Python source that TouchDesigner will eval. Multiline scripts supported.",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "detailLevel",
|
|
45
|
+
type: "'minimal' | 'summary' | 'detailed'",
|
|
46
|
+
required: false,
|
|
47
|
+
description: "Choose how much of the execution metadata to surface back to the agent.",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "responseFormat",
|
|
51
|
+
type: "'json' | 'yaml' | 'markdown'",
|
|
52
|
+
required: false,
|
|
53
|
+
description: "Structured response encoding for downstream tooling.",
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
returns: "Result payload that mirrors `result` from the executed script (if set).",
|
|
57
|
+
example: `import { executePythonScript } from './servers/touchdesigner/executePythonScript';
|
|
58
|
+
|
|
59
|
+
await executePythonScript({
|
|
60
|
+
script: "op('/project1/text1').par.text = 'Hello MCP'",
|
|
61
|
+
});`,
|
|
62
|
+
notes: "Wrap long-running scripts with logging so the agent can stream intermediate checkpoints.",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
tool: TOOL_NAMES.GET_TD_NODES,
|
|
66
|
+
modulePath: `${MODULE_ROOT}/getTdNodes.ts`,
|
|
67
|
+
functionName: "getTdNodes",
|
|
68
|
+
description: "List nodes below a parent path",
|
|
69
|
+
category: "nodes",
|
|
70
|
+
parameters: [
|
|
71
|
+
{
|
|
72
|
+
name: "parentPath",
|
|
73
|
+
type: "string",
|
|
74
|
+
required: true,
|
|
75
|
+
description: "Root operator path (e.g. /project1).",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "pattern",
|
|
79
|
+
type: "string",
|
|
80
|
+
required: false,
|
|
81
|
+
description: "Glob pattern to filter node names (default '*').",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "includeProperties",
|
|
85
|
+
type: "boolean",
|
|
86
|
+
required: false,
|
|
87
|
+
description: "Include expensive property blobs when you truly need them.",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "detailLevel",
|
|
91
|
+
type: "'minimal' | 'summary' | 'detailed'",
|
|
92
|
+
required: false,
|
|
93
|
+
description: "Formatter verbosity for the returned list.",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "limit",
|
|
97
|
+
type: "number",
|
|
98
|
+
required: false,
|
|
99
|
+
description: "Optional cap on how many nodes to return.",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "responseFormat",
|
|
103
|
+
type: "'json' | 'yaml' | 'markdown'",
|
|
104
|
+
required: false,
|
|
105
|
+
description: "Structured export for writing to disk.",
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
returns: "Set of nodes (id, opType, name, path, optional properties) under parentPath.",
|
|
109
|
+
example: `import { getTdNodes } from './servers/touchdesigner/getTdNodes';
|
|
110
|
+
|
|
111
|
+
const nodes = await getTdNodes({
|
|
112
|
+
parentPath: '/project1',
|
|
113
|
+
pattern: 'geo*',
|
|
114
|
+
});
|
|
115
|
+
console.log(nodes.nodes?.map(node => node.path));`,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
tool: TOOL_NAMES.GET_TD_NODE_PARAMETERS,
|
|
119
|
+
modulePath: `${MODULE_ROOT}/getTdNodeParameters.ts`,
|
|
120
|
+
functionName: "getTdNodeParameters",
|
|
121
|
+
description: "Inspect an individual node with formatter-aware output",
|
|
122
|
+
category: "nodes",
|
|
123
|
+
parameters: [
|
|
124
|
+
{
|
|
125
|
+
name: "nodePath",
|
|
126
|
+
type: "string",
|
|
127
|
+
required: true,
|
|
128
|
+
description: "Absolute path to the operator (e.g. /project1/text1).",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "detailLevel",
|
|
132
|
+
type: "'minimal' | 'summary' | 'detailed'",
|
|
133
|
+
required: false,
|
|
134
|
+
description: "Controls how many parameters and properties are shown.",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "limit",
|
|
138
|
+
type: "number",
|
|
139
|
+
required: false,
|
|
140
|
+
description: "Trim parameter listings to the first N entries.",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "responseFormat",
|
|
144
|
+
type: "'json' | 'yaml' | 'markdown'",
|
|
145
|
+
required: false,
|
|
146
|
+
description: "Switch between machine vs human friendly layouts.",
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
returns: "Full node record with parameters, paths, and metadata.",
|
|
150
|
+
example: `import { getTdNodeParameters } from './servers/touchdesigner/getTdNodeParameters';
|
|
151
|
+
|
|
152
|
+
const node = await getTdNodeParameters({ nodePath: '/project1/text1' });
|
|
153
|
+
console.log(node.properties?.Text);`,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
tool: TOOL_NAMES.CREATE_TD_NODE,
|
|
157
|
+
modulePath: `${MODULE_ROOT}/createTdNode.ts`,
|
|
158
|
+
functionName: "createTdNode",
|
|
159
|
+
description: "Create an operator under a parent path",
|
|
160
|
+
category: "nodes",
|
|
161
|
+
parameters: [
|
|
162
|
+
{
|
|
163
|
+
name: "parentPath",
|
|
164
|
+
type: "string",
|
|
165
|
+
required: true,
|
|
166
|
+
description: "Where the new node should be created.",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "nodeType",
|
|
170
|
+
type: "string",
|
|
171
|
+
required: true,
|
|
172
|
+
description: "OP type (e.g. textTOP, constantCHOP).",
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: "nodeName",
|
|
176
|
+
type: "string",
|
|
177
|
+
required: false,
|
|
178
|
+
description: "Optional custom name. When omitted TouchDesigner assigns one.",
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: "detailLevel",
|
|
182
|
+
type: "'minimal' | 'summary' | 'detailed'",
|
|
183
|
+
required: false,
|
|
184
|
+
description: "Formatter verbosity for the creation result.",
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "responseFormat",
|
|
188
|
+
type: "'json' | 'yaml' | 'markdown'",
|
|
189
|
+
required: false,
|
|
190
|
+
description: "Switch result serialization to JSON for scripts.",
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
returns: "Created node metadata including resolved path and properties.",
|
|
194
|
+
example: `import { createTdNode } from './servers/touchdesigner/createTdNode';
|
|
195
|
+
|
|
196
|
+
const created = await createTdNode({
|
|
197
|
+
parentPath: '/project1',
|
|
198
|
+
nodeType: 'textTOP',
|
|
199
|
+
nodeName: 'title',
|
|
200
|
+
});
|
|
201
|
+
console.log(created.result?.path);`,
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
tool: TOOL_NAMES.UPDATE_TD_NODE_PARAMETERS,
|
|
205
|
+
modulePath: `${MODULE_ROOT}/updateTdNodeParameters.ts`,
|
|
206
|
+
functionName: "updateTdNodeParameters",
|
|
207
|
+
description: "Patch node properties in bulk",
|
|
208
|
+
category: "nodes",
|
|
209
|
+
parameters: [
|
|
210
|
+
{
|
|
211
|
+
name: "nodePath",
|
|
212
|
+
type: "string",
|
|
213
|
+
required: true,
|
|
214
|
+
description: "Target operator path.",
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: "properties",
|
|
218
|
+
type: "Record<string, unknown>",
|
|
219
|
+
required: true,
|
|
220
|
+
description: "Key/value pairs to update on the node.",
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: "detailLevel",
|
|
224
|
+
type: "'minimal' | 'summary' | 'detailed'",
|
|
225
|
+
required: false,
|
|
226
|
+
description: "Controls how many updated keys are echoed back.",
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "responseFormat",
|
|
230
|
+
type: "'json' | 'yaml' | 'markdown'",
|
|
231
|
+
required: false,
|
|
232
|
+
description: "Choose JSON when writing audit logs to disk.",
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
returns: "Lists of updated vs failed parameters so the agent can retry selectively.",
|
|
236
|
+
example: `import { updateTdNodeParameters } from './servers/touchdesigner/updateTdNodeParameters';
|
|
237
|
+
|
|
238
|
+
await updateTdNodeParameters({
|
|
239
|
+
nodePath: '/project1/text1',
|
|
240
|
+
properties: { text: 'Hello TouchDesigner' },
|
|
241
|
+
});`,
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
tool: TOOL_NAMES.DELETE_TD_NODE,
|
|
245
|
+
modulePath: `${MODULE_ROOT}/deleteTdNode.ts`,
|
|
246
|
+
functionName: "deleteTdNode",
|
|
247
|
+
description: "Remove an operator safely",
|
|
248
|
+
category: "nodes",
|
|
249
|
+
parameters: [
|
|
250
|
+
{
|
|
251
|
+
name: "nodePath",
|
|
252
|
+
type: "string",
|
|
253
|
+
required: true,
|
|
254
|
+
description: "Absolute path of the operator to delete.",
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: "detailLevel",
|
|
258
|
+
type: "'minimal' | 'summary' | 'detailed'",
|
|
259
|
+
required: false,
|
|
260
|
+
description: "Sends only boolean flags when set to minimal.",
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: "responseFormat",
|
|
264
|
+
type: "'json' | 'yaml' | 'markdown'",
|
|
265
|
+
required: false,
|
|
266
|
+
description: "Structured payload when you need audit logs.",
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
returns: "Deletion status plus previous node metadata when available.",
|
|
270
|
+
example: `import { deleteTdNode } from './servers/touchdesigner/deleteTdNode';
|
|
271
|
+
|
|
272
|
+
const result = await deleteTdNode({ nodePath: '/project1/tmp1' });
|
|
273
|
+
console.log(result.deleted);`,
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
tool: TOOL_NAMES.EXECUTE_NODE_METHOD,
|
|
277
|
+
modulePath: `${MODULE_ROOT}/execNodeMethod.ts`,
|
|
278
|
+
functionName: "execNodeMethod",
|
|
279
|
+
description: "Call TouchDesigner node methods directly",
|
|
280
|
+
category: "nodes",
|
|
281
|
+
parameters: [
|
|
282
|
+
{
|
|
283
|
+
name: "nodePath",
|
|
284
|
+
type: "string",
|
|
285
|
+
required: true,
|
|
286
|
+
description: "OP to target.",
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "method",
|
|
290
|
+
type: "string",
|
|
291
|
+
required: true,
|
|
292
|
+
description: "Name of the method to call on that operator.",
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
name: "args",
|
|
296
|
+
type: "Array<string | number | boolean>",
|
|
297
|
+
required: false,
|
|
298
|
+
description: "Positional arguments forwarded to the TouchDesigner API.",
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: "kwargs",
|
|
302
|
+
type: "Record<string, unknown>",
|
|
303
|
+
required: false,
|
|
304
|
+
description: "Keyword arguments for the method call.",
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
name: "detailLevel",
|
|
308
|
+
type: "'minimal' | 'summary' | 'detailed'",
|
|
309
|
+
required: false,
|
|
310
|
+
description: "How much of the result payload to echo back.",
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: "responseFormat",
|
|
314
|
+
type: "'json' | 'yaml' | 'markdown'",
|
|
315
|
+
required: false,
|
|
316
|
+
description: "Switch to JSON when storing method responses.",
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
returns: "Raw method return payload including any serializable values.",
|
|
320
|
+
example: `import { execNodeMethod } from './servers/touchdesigner/execNodeMethod';
|
|
321
|
+
|
|
322
|
+
const renderStatus = await execNodeMethod({
|
|
323
|
+
nodePath: '/project1/render1',
|
|
324
|
+
method: 'par',
|
|
325
|
+
kwargs: { enable: true },
|
|
326
|
+
});
|
|
327
|
+
console.log(renderStatus.result);`,
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
tool: TOOL_NAMES.GET_TD_CLASSES,
|
|
331
|
+
modulePath: `${MODULE_ROOT}/getTdClasses.ts`,
|
|
332
|
+
functionName: "getTdClasses",
|
|
333
|
+
description: "List TouchDesigner Python classes/modules",
|
|
334
|
+
category: "classes",
|
|
335
|
+
parameters: [
|
|
336
|
+
{
|
|
337
|
+
name: "detailLevel",
|
|
338
|
+
type: "'minimal' | 'summary' | 'detailed'",
|
|
339
|
+
required: false,
|
|
340
|
+
description: "Minimal returns only names, summary adds short descriptions.",
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: "limit",
|
|
344
|
+
type: "number",
|
|
345
|
+
required: false,
|
|
346
|
+
description: "Restrict the number of classes returned to save tokens.",
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: "responseFormat",
|
|
350
|
+
type: "'json' | 'yaml' | 'markdown'",
|
|
351
|
+
required: false,
|
|
352
|
+
description: "Return the catalog as JSON when writing caches.",
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
returns: "Python class catalogue with names, types, and optional summaries.",
|
|
356
|
+
example: `import { getTdClasses } from './servers/touchdesigner/getTdClasses';
|
|
357
|
+
|
|
358
|
+
const classes = await getTdClasses({ limit: 20 });
|
|
359
|
+
console.log(classes.classes?.map(cls => cls.name));`,
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
tool: TOOL_NAMES.GET_TD_CLASS_DETAILS,
|
|
363
|
+
modulePath: `${MODULE_ROOT}/getTdClassDetails.ts`,
|
|
364
|
+
functionName: "getTdClassDetails",
|
|
365
|
+
description: "Fetch detailed docs for a TouchDesigner class or module",
|
|
366
|
+
category: "classes",
|
|
367
|
+
parameters: [
|
|
368
|
+
{
|
|
369
|
+
name: "className",
|
|
370
|
+
type: "string",
|
|
371
|
+
required: true,
|
|
372
|
+
description: "Class/module name like textTOP or CHOP.",
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
name: "detailLevel",
|
|
376
|
+
type: "'minimal' | 'summary' | 'detailed'",
|
|
377
|
+
required: false,
|
|
378
|
+
description: "Switch to detailed when generating docs.",
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: "limit",
|
|
382
|
+
type: "number",
|
|
383
|
+
required: false,
|
|
384
|
+
description: "Cap how many methods/properties are surfaced.",
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: "responseFormat",
|
|
388
|
+
type: "'json' | 'yaml' | 'markdown'",
|
|
389
|
+
required: false,
|
|
390
|
+
description: "Emit YAML or JSON for caching results to disk.",
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
returns: "Deep description of a Python class including methods and properties.",
|
|
394
|
+
example: `import { getTdClassDetails } from './servers/touchdesigner/getTdClassDetails';
|
|
395
|
+
|
|
396
|
+
const textTop = await getTdClassDetails({ className: 'textTOP' });
|
|
397
|
+
console.log(textTop.methods?.length);`,
|
|
398
|
+
},
|
|
399
|
+
];
|
|
400
|
+
export function getTouchDesignerToolMetadata() {
|
|
401
|
+
return TOUCH_DESIGNER_TOOL_METADATA;
|
|
402
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class List Formatter
|
|
3
|
+
*
|
|
4
|
+
* Formats TouchDesigner Python class/module lists with token optimization.
|
|
5
|
+
* Used by GET_TD_CLASSES and GET_TD_CLASS_DETAILS tools.
|
|
6
|
+
*/
|
|
7
|
+
import { DEFAULT_PRESENTER_FORMAT, presentStructuredData, } from "./presenter.js";
|
|
8
|
+
import { finalizeFormattedText, limitArray, mergeFormatterOptions, } from "./responseFormatter.js";
|
|
9
|
+
/**
|
|
10
|
+
* Format class/module list
|
|
11
|
+
*/
|
|
12
|
+
export function formatClassList(data, options) {
|
|
13
|
+
const opts = mergeFormatterOptions(options);
|
|
14
|
+
if (!data) {
|
|
15
|
+
return "No classes or modules found.";
|
|
16
|
+
}
|
|
17
|
+
const classes = data.classes || [];
|
|
18
|
+
const modules = data.modules || [];
|
|
19
|
+
const total = classes.length + modules.length;
|
|
20
|
+
if (total === 0) {
|
|
21
|
+
return "No classes or modules found.";
|
|
22
|
+
}
|
|
23
|
+
if (opts.detailLevel === "detailed") {
|
|
24
|
+
return formatDetailed(data, opts.responseFormat);
|
|
25
|
+
}
|
|
26
|
+
const { text, context } = opts.detailLevel === "minimal"
|
|
27
|
+
? formatClassListMinimal(classes, modules, opts.limit)
|
|
28
|
+
: formatClassListSummary(classes, modules, opts.limit);
|
|
29
|
+
const ctx = context;
|
|
30
|
+
return finalizeFormattedText(text, opts, {
|
|
31
|
+
template: "classListSummary",
|
|
32
|
+
context: ctx,
|
|
33
|
+
structured: ctx,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Format class details
|
|
38
|
+
*/
|
|
39
|
+
export function formatClassDetails(data, options) {
|
|
40
|
+
const opts = mergeFormatterOptions(options);
|
|
41
|
+
if (!data || !data.name) {
|
|
42
|
+
return "No class details available.";
|
|
43
|
+
}
|
|
44
|
+
if (opts.detailLevel === "detailed") {
|
|
45
|
+
return formatDetailed(data, opts.responseFormat);
|
|
46
|
+
}
|
|
47
|
+
const { text, context } = opts.detailLevel === "minimal"
|
|
48
|
+
? formatClassDetailsMinimal(data)
|
|
49
|
+
: formatClassDetailsSummary(data, opts.limit);
|
|
50
|
+
const ctx = context;
|
|
51
|
+
return finalizeFormattedText(text, opts, {
|
|
52
|
+
template: "classDetailsSummary",
|
|
53
|
+
context: ctx,
|
|
54
|
+
structured: ctx,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function formatClassListMinimal(classes, modules, limit) {
|
|
58
|
+
const { items: limitedClasses, truncated: classTruncated } = limitArray(classes, limit);
|
|
59
|
+
let text = `Classes (${classes.length}): ${limitedClasses
|
|
60
|
+
.map((c) => c.name)
|
|
61
|
+
.join(", ")}`;
|
|
62
|
+
if (classTruncated) {
|
|
63
|
+
text += `\n💡 ${classes.length - limitedClasses.length} more classes omitted.`;
|
|
64
|
+
}
|
|
65
|
+
if (modules.length > 0) {
|
|
66
|
+
text += `\nModules (${modules.length}): ${modules.join(", ")}`;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
text,
|
|
70
|
+
context: buildClassListContext(classes, modules),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function formatClassListSummary(classes, modules, limit) {
|
|
74
|
+
const { items: limitedClasses } = limitArray(classes, limit);
|
|
75
|
+
const text = `Classes (${classes.length}):\n${limitedClasses
|
|
76
|
+
.map((c) => `- ${c.name} — ${c.description || ""}`)
|
|
77
|
+
.join("\n")}\n\nModules (${modules.length}):\n${modules
|
|
78
|
+
.map((m) => `- ${m}`)
|
|
79
|
+
.join("\n")}`;
|
|
80
|
+
return {
|
|
81
|
+
text,
|
|
82
|
+
context: buildClassListContext(classes, modules),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function buildClassListContext(classes, modules) {
|
|
86
|
+
return {
|
|
87
|
+
classCount: classes.length,
|
|
88
|
+
moduleCount: modules.length,
|
|
89
|
+
classes: classes.map((cls) => ({
|
|
90
|
+
name: cls.name,
|
|
91
|
+
description: cls.description,
|
|
92
|
+
})),
|
|
93
|
+
modules,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function formatClassDetailsMinimal(data) {
|
|
97
|
+
const text = `Class: ${data.name}\nType: ${data.type}`;
|
|
98
|
+
return {
|
|
99
|
+
text,
|
|
100
|
+
context: {
|
|
101
|
+
name: data.name,
|
|
102
|
+
type: data.type,
|
|
103
|
+
description: data.description,
|
|
104
|
+
methodsShown: 0,
|
|
105
|
+
methodsTotal: data.methods?.length ?? 0,
|
|
106
|
+
methods: [],
|
|
107
|
+
propertiesShown: 0,
|
|
108
|
+
propertiesTotal: data.properties?.length ?? 0,
|
|
109
|
+
properties: [],
|
|
110
|
+
truncated: false,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function formatClassDetailsSummary(data, limit) {
|
|
115
|
+
const methods = data.methods || [];
|
|
116
|
+
const properties = data.properties || [];
|
|
117
|
+
const { items: limitedMethods, truncated: methodsTruncated } = limitArray(methods, limit);
|
|
118
|
+
const { items: limitedProps, truncated: propsTruncated } = limitArray(properties, limit);
|
|
119
|
+
let text = `${data.name}`;
|
|
120
|
+
if (data.type)
|
|
121
|
+
text += ` (${data.type})`;
|
|
122
|
+
text += "\n";
|
|
123
|
+
if (data.description) {
|
|
124
|
+
text += `\n${data.description}\n`;
|
|
125
|
+
}
|
|
126
|
+
if (limitedMethods.length > 0) {
|
|
127
|
+
text += `\nMETHODS (${methods.length}):\n`;
|
|
128
|
+
for (const method of limitedMethods) {
|
|
129
|
+
const sig = method.signature || `${method.name}()`;
|
|
130
|
+
const doc = method.description
|
|
131
|
+
? ` - ${method.description.split("\n")[0]}`
|
|
132
|
+
: "";
|
|
133
|
+
text += ` • ${sig}${doc}\n`;
|
|
134
|
+
}
|
|
135
|
+
if (methodsTruncated) {
|
|
136
|
+
text += ` 💡 ${methods.length - limitedMethods.length} more methods omitted.\n`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (limitedProps.length > 0) {
|
|
140
|
+
text += `\nPROPERTIES (${properties.length}):\n`;
|
|
141
|
+
for (const prop of limitedProps) {
|
|
142
|
+
const typeInfo = prop.type ? `: ${prop.type}` : "";
|
|
143
|
+
const valueInfo = prop.value !== undefined ? ` = ${JSON.stringify(prop.value)}` : "";
|
|
144
|
+
text += ` • ${prop.name}${typeInfo}${valueInfo}\n`;
|
|
145
|
+
}
|
|
146
|
+
if (propsTruncated) {
|
|
147
|
+
text += ` 💡 ${properties.length - limitedProps.length} more properties omitted.\n`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
text,
|
|
152
|
+
context: {
|
|
153
|
+
name: data.name,
|
|
154
|
+
type: data.type,
|
|
155
|
+
description: data.description,
|
|
156
|
+
methodsShown: limitedMethods.length,
|
|
157
|
+
methodsTotal: methods.length,
|
|
158
|
+
methods: limitedMethods.map((method) => ({
|
|
159
|
+
signature: method.signature || `${method.name}()`,
|
|
160
|
+
summary: method.description?.split("\n")[0] ?? "",
|
|
161
|
+
})),
|
|
162
|
+
propertiesShown: limitedProps.length,
|
|
163
|
+
propertiesTotal: properties.length,
|
|
164
|
+
properties: limitedProps.map((prop) => ({
|
|
165
|
+
name: prop.name,
|
|
166
|
+
type: prop.type,
|
|
167
|
+
})),
|
|
168
|
+
truncated: methodsTruncated || propsTruncated,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function formatDetailed(data, format) {
|
|
173
|
+
const title = "name" in data
|
|
174
|
+
? `Class ${data.name ?? "details"}`
|
|
175
|
+
: "TouchDesigner Classes";
|
|
176
|
+
const payloadFormat = format ?? DEFAULT_PRESENTER_FORMAT;
|
|
177
|
+
return presentStructuredData({
|
|
178
|
+
text: title,
|
|
179
|
+
detailLevel: "detailed",
|
|
180
|
+
structured: data,
|
|
181
|
+
context: {
|
|
182
|
+
title,
|
|
183
|
+
payloadFormat,
|
|
184
|
+
},
|
|
185
|
+
template: "detailedPayload",
|
|
186
|
+
}, payloadFormat);
|
|
187
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response Formatters Index
|
|
3
|
+
*
|
|
4
|
+
* Central export point for all response formatters
|
|
5
|
+
*/
|
|
6
|
+
export { formatClassDetails, formatClassList } from "./classListFormatter.js";
|
|
7
|
+
export { formatNodeDetails } from "./nodeDetailsFormatter.js";
|
|
8
|
+
export { formatNodeList } from "./nodeListFormatter.js";
|
|
9
|
+
export { formatCreateNodeResult, formatDeleteNodeResult, formatExecNodeMethodResult, formatTdInfo, formatUpdateNodeResult, } from "./operationFormatter.js";
|
|
10
|
+
export { formatScriptResult } from "./scriptResultFormatter.js";
|
|
11
|
+
export { formatToolMetadata } from "./toolMetadataFormatter.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import Mustache from "mustache";
|
|
5
|
+
const templatesDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "./templates/markdown");
|
|
6
|
+
const templateCache = new Map();
|
|
7
|
+
export function renderMarkdownTemplate(templateName, context = {}) {
|
|
8
|
+
const template = loadTemplate(templateName);
|
|
9
|
+
return Mustache.render(template, context).trim();
|
|
10
|
+
}
|
|
11
|
+
function loadTemplate(name) {
|
|
12
|
+
const fileName = `${name}.md`;
|
|
13
|
+
const fullPath = path.join(templatesDir, fileName);
|
|
14
|
+
if (!templateCache.has(fileName)) {
|
|
15
|
+
let content;
|
|
16
|
+
try {
|
|
17
|
+
content = readFileSync(fullPath, "utf-8");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
content = readFileSync(path.join(templatesDir, "default.md"), "utf-8");
|
|
21
|
+
}
|
|
22
|
+
templateCache.set(fileName, content);
|
|
23
|
+
}
|
|
24
|
+
return templateCache.get(fileName) ?? "";
|
|
25
|
+
}
|