touchdesigner-mcp-server 1.1.2 → 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.
package/README.ja.md CHANGED
@@ -52,14 +52,14 @@ flowchart LR
52
52
  ## 利用方法
53
53
 
54
54
  <details>
55
- <summary>方法1: Claude Desktop + Desktop Extensions(推奨)</summary>
55
+ <summary>方法1: Claude Desktop + MCP Bundle(推奨)</summary>
56
56
 
57
57
  ##### 1. ファイルをダウンロード
58
58
 
59
59
  [リリースページ](https://github.com/8beeeaaat/touchdesigner-mcp/releases/latest)から以下をダウンロード:
60
60
 
61
61
  - **TouchDesigner Components**: `touchdesigner-mcp-td.zip`
62
- - **Desktop Extensions (.dxt)**: `touchdesigner-mcp.dxt`
62
+ - **[MCP Bundle](https://github.com/modelcontextprotocol/mcpb) (.mcpb)**: `touchdesigner-mcp.mcpb`
63
63
 
64
64
  ##### 2. TouchDesignerコンポーネントを設置
65
65
 
@@ -73,13 +73,13 @@ flowchart LR
73
73
 
74
74
  ![import](https://github.com/8beeeaaat/touchdesigner-mcp/blob/main/assets/textport.png)
75
75
 
76
- ##### 3. Desktop Extensionをインストール
76
+ ##### 3. MCP Bundleをインストール
77
77
 
78
- `touchdesigner-mcp.dxt`ファイルをダブルクリックしてClaude Desktopに拡張機能をインストール
78
+ `touchdesigner-mcp.mcpb`ファイルをダブルクリックしてClaude DesktopにMCP Bundleをインストール
79
79
 
80
80
  <https://github.com/user-attachments/assets/0786d244-8b82-4387-bbe4-9da048212854>
81
81
 
82
- ##### 4. 拡張機能が自動的にTouchDesignerサーバー接続を処理
82
+ ##### 4. MCP Bundleが自動的にTouchDesignerサーバー接続を処理
83
83
 
84
84
  **⚠️ 重要:** TouchDesignerコンポーネントのディレクトリ構造は展開した状態を正確に保持してください。`mcp_webserver_base.tox`コンポーネントは`modules/`ディレクトリやその他のファイルへの相対パスを参照しています。
85
85
 
@@ -250,9 +250,11 @@ td/
250
250
  | `delete_td_node` | 既存のノードを削除します。 |
251
251
  | `exec_node_method` | ノードに対してPythonメソッドを呼び出します。 |
252
252
  | `execute_python_script` | TD内で任意のPythonスクリプトを実行します。 |
253
+ | `get_module_help` | TouchDesignerモジュール/クラスのPython help()ドキュメントを取得します。 |
253
254
  | `get_td_class_details` | TD Pythonクラス/モジュールの詳細情報を取得します。 |
254
255
  | `get_td_classes` | TouchDesigner Pythonクラスのリストを取得します。 |
255
256
  | `get_td_info` | TDサーバー環境に関する情報を取得します。 |
257
+ | `get_td_node_errors` | 指定されたノードとその子ノードのエラーをチェックします。 |
256
258
  | `get_td_node_parameters` | 特定ノードのパラメータを取得します。 |
257
259
  | `get_td_nodes` | 親パス内のノードを取得します(オプションでフィルタリング)。 |
258
260
  | `update_td_node_parameters` | 特定ノードのパラメータを更新します。 |
@@ -354,6 +356,24 @@ td/
354
356
 
355
357
  ビルドプロセス (`npm run build`) は、必要なすべての生成ステップ (`npm run gen`) を実行し、その後にTypeScriptコンパイル (`tsc`) を行います。
356
358
 
359
+ ### バージョン管理
360
+
361
+ - `package.json` はすべてのコンポーネントバージョンの唯一の信頼できる情報源です(Node.js MCPサーバー、TouchDesigner Python API、MCPバンドル、および `server.json` メタデータ)。
362
+ - バージョンを更新する際は `npm version <patch|minor|major>`(または内部で使用される `npm run gen:version`)を実行してください。このスクリプトは `pyproject.toml`、`td/modules/utils/version.py`、`mcpb/manifest.json`、および `server.json` を書き換え、リリースワークフローがタグ値を信頼できるようにします。
363
+ - GitHubリリースワークフロー(`.github/workflows/release.yml`)はコミットを `v${version}` としてタグ付けし、同じバージョン番号から `touchdesigner-mcp-td.zip` / `touchdesigner-mcp.mcpb` を公開します。リリースをトリガーする前に必ず同期ステップを実行し、すべてのアーティファクトが整合するようにしてください。
364
+
365
+ ## トラブルシューティング
366
+
367
+ ### バージョン互換性のトラブルシューティング
368
+
369
+ - `ConnectionManager.connect` の実行中、MCPサーバーは自身のバージョンと `getTdInfo` が報告するTouchDesigner APIサーバーのバージョンを比較します。APIサーバーがバージョンを公開していない場合、またはバージョンが異なる場合(例:片方のみが更新された場合)、MCPサーバーはClaude/Codexコンソールおよび TouchDesigner ログ DAT に説明的なエラーメッセージを表示して接続を中止します。
370
+ - 不一致を解決するには、TouchDesignerコンポーネントを再インストールしてください:
371
+ 1. リリースページから最新の [touchdesigner-mcp-td.zip](https://github.com/8beeeaaat/touchdesigner-mcp/releases/latest/download/touchdesigner-mcp-td.zip) をダウンロードします。
372
+ 2. 既存の `touchdesigner-mcp-td` フォルダを削除し、新しく展開した内容に置き換えます。
373
+ 3. TouchDesignerプロジェクトから古い `mcp_webserver_base` コンポーネントを削除し、新しいフォルダから `.tox` をインポートします。
374
+ 4. TouchDesignerとMCPサーバーを実行しているAIエージェント(例:Claude Desktop)を再起動します。
375
+ - ローカルで開発している場合は、`package.json` を編集した後に `npm run gen:version` を実行してください(または単に `npm version ...` を使用してください)。これにより、Python API(`pyproject.toml` + `td/modules/utils/version.py`)、MCPバンドルマニフェスト、およびレジストリメタデータが同期され、ランタイム互換性チェックが成功するようになります。
376
+
357
377
  ## 開発で貢献
358
378
 
359
379
  ぜひ一緒に改善しましょう!
package/README.md CHANGED
@@ -52,14 +52,14 @@ flowchart LR
52
52
  ## Usage
53
53
 
54
54
  <details>
55
- <summary>Method 1: Using Claude Desktop and Desktop Extensions (Recommended)</summary>
55
+ <summary>Method 1: Using Claude Desktop and MCP Bundle (Recommended)</summary>
56
56
 
57
57
  ### 1. Download Files
58
58
 
59
59
  Download the following from the [releases page](https://github.com/8beeeaaat/touchdesigner-mcp/releases/latest):
60
60
 
61
61
  - **TouchDesigner Components**: `touchdesigner-mcp-td.zip`
62
- - **Desktop Extension (.dxt)**: `touchdesigner-mcp.dxt`
62
+ - **[MCP Bundle](https://github.com/modelcontextprotocol/mcpb) (.mcpb)**: `touchdesigner-mcp.mcpb`
63
63
 
64
64
  ### 2. Set up TouchDesigner Components
65
65
 
@@ -73,15 +73,15 @@ Download the following from the [releases page](https://github.com/8beeeaaat/tou
73
73
 
74
74
  ![import](https://github.com/8beeeaaat/touchdesigner-mcp/blob/main/assets/textport.png)
75
75
 
76
- ### 3. Install the Desktop Extension
76
+ ### 3. Install the MCP Bundle
77
77
 
78
- Double-click the `touchdesigner-mcp.dxt` file to install the extension in Claude Desktop.
78
+ Double-click the `touchdesigner-mcp.mcpb` file to install the bundle in Claude Desktop.
79
79
 
80
80
  <https://github.com/user-attachments/assets/0786d244-8b82-4387-bbe4-9da048212854>
81
81
 
82
82
  ### 4. Connect to the Server
83
83
 
84
- The extension will automatically handle the connection to the TouchDesigner server.
84
+ The MCP bundle will automatically handle the connection to the TouchDesigner server.
85
85
 
86
86
  **⚠️ Important:** The directory structure must be preserved exactly as extracted. The `mcp_webserver_base.tox` component references relative paths to the `modules/` directory and other files.
87
87
 
@@ -250,9 +250,11 @@ Tools allow AI agents to perform actions in TouchDesigner.
250
250
  | `delete_td_node` | Deletes an existing node. |
251
251
  | `exec_node_method` | Calls a Python method on a node. |
252
252
  | `execute_python_script` | Executes an arbitrary Python script in TouchDesigner. |
253
+ | `get_module_help` | Gets Python help() documentation for TouchDesigner modules/classes.|
253
254
  | `get_td_class_details` | Gets details of a TouchDesigner Python class or module. |
254
255
  | `get_td_classes` | Gets a list of TouchDesigner Python classes. |
255
256
  | `get_td_info` | Gets information about the TouchDesigner server environment. |
257
+ | `get_td_node_errors` | Checks for errors on a specified node and its children. |
256
258
  | `get_td_node_parameters`| Gets the parameters of a specific node. |
257
259
  | `get_td_nodes` | Gets nodes under a parent path, with optional filtering. |
258
260
  | `update_td_node_parameters` | Updates the parameters of a specific node. |
@@ -354,6 +356,24 @@ This project uses OpenAPI-based code generation tools (Orval and openapi-generat
354
356
 
355
357
  The build process (`npm run build`) runs all necessary generation steps (`npm run gen`), followed by TypeScript compilation (`tsc`).
356
358
 
359
+ ### Version management
360
+
361
+ - `package.json` is the single source of truth for every component version (Node.js MCP server, TouchDesigner Python API, MCP bundle, and `server.json` metadata).
362
+ - Run `npm version <patch|minor|major>` (or the underlying `npm run gen:version`) whenever you bump the version. The script rewrites `pyproject.toml`, `td/modules/utils/version.py`, `mcpb/manifest.json`, and `server.json` so that the release workflow can trust the tag value.
363
+ - The GitHub release workflow (`.github/workflows/release.yml`) tags the commit as `v${version}` and publishes `touchdesigner-mcp-td.zip` / `touchdesigner-mcp.mcpb` from the exact same version number. Always run the sync step before triggering a release so that every artifact stays aligned.
364
+
365
+ ## Troubleshooting
366
+
367
+ ### Troubleshooting version compatibility
368
+
369
+ - During `ConnectionManager.connect` the MCP server compares its own version with the TouchDesigner API server version reported by `getTdInfo`. If the API server does not expose a version or the versions differ (for example because only one side was updated), the MCP server aborts the connection with a descriptive error message in the Claude/Codex console and in the TouchDesigner log DAT.
370
+ - To resolve the mismatch, reinstall both the TouchDesigner components
371
+ 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.
372
+ 2. Delete the existing \`touchdesigner-mcp-td\` folder and replace it with the newly extracted contents.
373
+ 3. Remove the old \`mcp_webserver_base\` component from your TouchDesigner project and import the \`.tox\` from the new folder.
374
+ 4. Restart TouchDesigner and the AI agent running the MCP server (e.g., Claude Desktop).
375
+ - When developing locally, run `npm run gen:version` after editing `package.json` (or simply use `npm version ...`). This keeps the Python API (`pyproject.toml` + `td/modules/utils/version.py`), MCP bundle manifest, and registry metadata in sync so that the runtime compatibility check succeeds.
376
+
357
377
  ## Contributing
358
378
 
359
379
  We welcome your contributions!
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { TouchDesignerServer } from "./server/touchDesignerServer.js";
4
- // Note: Environment variables should be set by the Desktop Extensions runtime or CLI arguments
4
+ // Note: Environment variables should be set by the MCP Bundle runtime or CLI arguments
5
5
  const DEFAULT_HOST = "http://127.0.0.1";
6
6
  const DEFAULT_PORT = 9981;
7
7
  /**
@@ -59,8 +59,8 @@ export async function startServer(params) {
59
59
  }
60
60
  // Start server if this file is executed directly
61
61
  startServer({
62
- nodeEnv: process.env.NODE_ENV,
63
62
  argv: process.argv,
63
+ nodeEnv: process.env.NODE_ENV,
64
64
  }).catch((error) => {
65
65
  console.error("Failed to start server:", error);
66
66
  if (process.env.NODE_ENV === "test")
@@ -10,18 +10,20 @@ export const TOOL_NAMES = {
10
10
  CREATE_TD_NODE: "create_td_node",
11
11
  DELETE_TD_NODE: "delete_td_node",
12
12
  DESCRIBE_TD_TOOLS: "describe_td_tools",
13
- EXECUTE_PYTHON_SCRIPT: "execute_python_script",
14
13
  EXECUTE_NODE_METHOD: "exec_node_method",
15
- GET_TD_INFO: "get_td_info",
14
+ EXECUTE_PYTHON_SCRIPT: "execute_python_script",
16
15
  GET_TD_CLASS_DETAILS: "get_td_class_details",
17
16
  GET_TD_CLASSES: "get_td_classes",
17
+ GET_TD_INFO: "get_td_info",
18
+ GET_TD_MODULE_HELP: "get_td_module_help",
19
+ GET_TD_NODE_ERRORS: "get_td_node_errors",
18
20
  GET_TD_NODE_PARAMETERS: "get_td_node_parameters",
19
21
  GET_TD_NODES: "get_td_nodes",
20
22
  UPDATE_TD_NODE_PARAMETERS: "update_td_node_parameters",
21
23
  };
22
24
  export const REFERENCE_COMMENT = `Check reference resources: ${TD_PYTHON_CLASS_REFERENCE_INDEX_URL}`;
23
25
  export const PROMPT_NAMES = {
24
- SEARCH_NODE: "Search node",
25
26
  CHECK_NODE_ERRORS: "Check node errors",
26
27
  NODE_CONNECTION: "Node connection",
28
+ SEARCH_NODE: "Search node",
27
29
  };
@@ -5,15 +5,30 @@ export function handleToolError(error, logger, toolName, referenceComment) {
5
5
  const formattedError = error instanceof Error
6
6
  ? error
7
7
  : new Error(error === null ? "Null error received" : String(error));
8
- logger.error(toolName, formattedError);
8
+ const logData = {
9
+ error: formattedError.message,
10
+ message: "Tool execution failed",
11
+ toolName,
12
+ };
13
+ if (referenceComment) {
14
+ logData.referenceComment = referenceComment;
15
+ }
16
+ if (formattedError.stack) {
17
+ logData.stack = formattedError.stack;
18
+ }
19
+ logger.sendLog({
20
+ data: logData,
21
+ level: "error",
22
+ logger: "ErrorHandling",
23
+ });
9
24
  const errorMessage = `${toolName}: ${formattedError}${referenceComment ? `. ${referenceComment}` : ""}`;
10
25
  return {
11
- isError: true,
12
26
  content: [
13
27
  {
14
- type: "text",
15
28
  text: errorMessage,
29
+ type: "text",
16
30
  },
17
31
  ],
32
+ isError: true,
18
33
  };
19
34
  }
@@ -7,39 +7,17 @@ export class McpLogger {
7
7
  constructor(server) {
8
8
  this.server = server;
9
9
  }
10
- // biome-ignore lint/suspicious/noExplicitAny: logging accepts any type
11
- log(...args) {
12
- this.sendLog("info", args);
13
- }
14
- // biome-ignore lint/suspicious/noExplicitAny: logging accepts any type
15
- debug(...args) {
16
- this.sendLog("debug", args);
17
- }
18
- // biome-ignore lint/suspicious/noExplicitAny: logging accepts any type
19
- warn(...args) {
20
- this.sendLog("warning", args);
21
- }
22
- // biome-ignore lint/suspicious/noExplicitAny: logging accepts any type
23
- error(...args) {
24
- this.sendLog("error", args);
25
- }
26
- sendLog(level,
27
- // biome-ignore lint/suspicious/noExplicitAny: logging accepts any type
28
- args) {
29
- for (const arg of args) {
30
- try {
31
- this.server.server.sendLoggingMessage({
32
- level,
33
- data: arg,
34
- });
35
- }
36
- catch (error) {
37
- if (error instanceof Error && error.message === "Not connected") {
38
- return;
39
- }
40
- console.error(`Failed to send log to MCP server: ${error instanceof Error ? error.message : String(error)}`);
41
- console[level === "warning" ? "warn" : level]?.(arg);
10
+ sendLog(args) {
11
+ try {
12
+ this.server.server.sendLoggingMessage({
13
+ ...args,
14
+ });
15
+ }
16
+ catch (error) {
17
+ if (error instanceof Error && error.message === "Not connected") {
18
+ return;
42
19
  }
20
+ console.error(`Failed to send log to MCP server: ${error instanceof Error ? error.message : String(error)}`);
43
21
  }
44
22
  }
45
23
  }
@@ -2,11 +2,11 @@
2
2
  * Creates a success result with the provided data
3
3
  */
4
4
  export function createSuccessResult(data) {
5
- return { success: true, data };
5
+ return { data, success: true };
6
6
  }
7
7
  /**
8
8
  * Creates an error result with the provided error
9
9
  */
10
10
  export function createErrorResult(error) {
11
- return { success: false, error };
11
+ return { error, success: false };
12
12
  }
@@ -0,0 +1,4 @@
1
+ import { createRequire } from "node:module";
2
+ const requirePackage = createRequire(import.meta.url);
3
+ const packageJson = requirePackage("../../package.json");
4
+ export const PACKAGE_VERSION = packageJson.version ?? "0.0.0";
@@ -2,40 +2,40 @@ import { GetPromptRequestSchema, ListPromptsRequestSchema, } from "@modelcontext
2
2
  import { PROMPT_NAMES, TOOL_NAMES } from "../../../core/constants.js";
3
3
  const PROMPTS = [
4
4
  {
5
- name: PROMPT_NAMES.SEARCH_NODE,
6
- description: "Fuzzy search for node",
7
5
  arguments: [
8
6
  {
9
- name: "nodeName",
10
7
  description: "Name of the node to check",
8
+ name: "nodeName",
11
9
  required: true,
12
10
  },
13
11
  {
14
- name: "nodeFamily",
15
12
  description: "Family of the node to check",
13
+ name: "nodeFamily",
16
14
  required: false,
17
15
  },
18
16
  {
19
- name: "nodeType",
20
17
  description: "Type of the node to check",
18
+ name: "nodeType",
21
19
  required: false,
22
20
  },
23
21
  ],
22
+ description: "Fuzzy search for node",
23
+ name: PROMPT_NAMES.SEARCH_NODE,
24
24
  },
25
25
  {
26
- name: PROMPT_NAMES.CHECK_NODE_ERRORS,
27
- description: "Fuzzy search for node and return errors in TouchDesigner.",
28
26
  arguments: [
29
27
  {
30
- name: "nodePath",
31
28
  description: "Path to the node to check",
29
+ name: "nodePath",
32
30
  required: true,
33
31
  },
34
32
  ],
33
+ description: "Fuzzy search for node and return errors in TouchDesigner.",
34
+ name: PROMPT_NAMES.CHECK_NODE_ERRORS,
35
35
  },
36
36
  {
37
- name: PROMPT_NAMES.NODE_CONNECTION,
38
37
  description: "Connect nodes between each other in TouchDesigner.",
38
+ name: PROMPT_NAMES.NODE_CONNECTION,
39
39
  },
40
40
  ];
41
41
  /**
@@ -49,7 +49,10 @@ export function registerTdPrompts(server, logger) {
49
49
  });
50
50
  server.server.setRequestHandler(GetPromptRequestSchema, (request) => {
51
51
  try {
52
- logger.debug(`Handling GetPromptRequest: ${request.params.name}`);
52
+ logger.sendLog({
53
+ data: `Handling GetPromptRequest: ${request.params.name}`,
54
+ level: "debug",
55
+ });
53
56
  const prompt = getPrompt(request.params.name);
54
57
  if (!prompt) {
55
58
  throw new Error("Prompt name is required");
@@ -60,8 +63,8 @@ export function registerTdPrompts(server, logger) {
60
63
  }
61
64
  const { nodeName, nodeFamily, nodeType } = request.params.arguments;
62
65
  const messages = handleSearchNodePrompt({
63
- nodeName,
64
66
  nodeFamily,
67
+ nodeName,
65
68
  nodeType,
66
69
  });
67
70
  return { messages };
@@ -84,7 +87,10 @@ export function registerTdPrompts(server, logger) {
84
87
  }
85
88
  catch (error) {
86
89
  const errorMessage = error instanceof Error ? error.message : String(error);
87
- logger.error(`Error handling prompt request: ${errorMessage}`);
90
+ logger.sendLog({
91
+ data: `Error handling prompt request: ${errorMessage}`,
92
+ level: "error",
93
+ });
88
94
  throw new Error(errorMessage);
89
95
  }
90
96
  });
@@ -92,33 +98,33 @@ export function registerTdPrompts(server, logger) {
92
98
  function handleSearchNodePrompt(params) {
93
99
  return [
94
100
  {
95
- role: "user",
96
101
  content: {
97
- type: "text",
98
102
  text: `Use the "${TOOL_NAMES.GET_TD_NODES}", "${TOOL_NAMES.GET_TD_NODE_PARAMETERS}" tools to search nodes what named "${params.nodeName}" in the TouchDesigner project.${params.nodeType ? ` Node Type: ${params.nodeType}.` : ""}${params.nodeFamily ? ` Node Family: ${params.nodeFamily}.` : ""}`,
103
+ type: "text",
99
104
  },
105
+ role: "user",
100
106
  },
101
107
  ];
102
108
  }
103
109
  function handleCheckNodeErrorsPrompt(params) {
104
110
  return [
105
111
  {
106
- role: "user",
107
112
  content: {
113
+ text: `Use the "${TOOL_NAMES.GET_TD_NODE_ERRORS}" tool to inspect "${params.nodePath}" (and optionally its children) for error messages. If errors are returned, examine the affected nodes' parameters and connections to resolve them.`,
108
114
  type: "text",
109
- text: `Use the "${TOOL_NAMES.EXECUTE_NODE_METHOD}" like "op('${params.nodePath}').errors()" tool to check node errors. If there are any errors, please check the node parameters and connections. If the node has children, please check the child nodes as well. Please check the node connections and parameters. If the node has children, please check the child nodes as well.`,
110
115
  },
116
+ role: "user",
111
117
  },
112
118
  ];
113
119
  }
114
120
  function handleNodeConnectionPrompt() {
115
121
  return [
116
122
  {
117
- role: "user",
118
123
  content: {
119
- type: "text",
120
124
  text: `Use the "${TOOL_NAMES.EXECUTE_PYTHON_SCRIPT}" tool e.g. op('/project1/text_over_image').outputConnectors[0].connect(op('/project1/out1'))`,
125
+ type: "text",
121
126
  },
127
+ role: "user",
122
128
  },
123
129
  ];
124
130
  }
@@ -1,19 +1,21 @@
1
1
  import { z } from "zod";
2
2
  import { REFERENCE_COMMENT, TOOL_NAMES } from "../../../core/constants.js";
3
3
  import { handleToolError } from "../../../core/errorHandling.js";
4
- import { createNodeBody, deleteNodeQueryParams, execNodeMethodBody, execPythonScriptBody, getNodeDetailQueryParams, getNodesQueryParams, getTdPythonClassDetailsParams, updateNodeBody, } from "../../../gen/mcp/touchDesignerAPI.zod.js";
4
+ import { createNodeBody, deleteNodeQueryParams, execNodeMethodBody, execPythonScriptBody, getModuleHelpQueryParams, getNodeDetailQueryParams, getNodeErrorsQueryParams, getNodesQueryParams, getTdPythonClassDetailsParams, updateNodeBody, } from "../../../gen/mcp/touchDesignerAPI.zod.js";
5
5
  import { getTouchDesignerToolMetadata } from "../metadata/touchDesignerToolMetadata.js";
6
- import { formatClassDetails, formatClassList, formatCreateNodeResult, formatDeleteNodeResult, formatExecNodeMethodResult, formatNodeDetails, formatNodeList, formatScriptResult, formatTdInfo, formatToolMetadata, formatUpdateNodeResult, } from "../presenter/index.js";
6
+ import { formatClassDetails, formatClassList, formatCreateNodeResult, formatDeleteNodeResult, formatExecNodeMethodResult, formatModuleHelp, formatNodeDetails, formatNodeErrors, formatNodeList, formatScriptResult, formatTdInfo, formatToolMetadata, formatUpdateNodeResult, } from "../presenter/index.js";
7
7
  import { detailOnlyFormattingSchema, formattingOptionsSchema, } from "../types.js";
8
8
  const execPythonScriptToolSchema = execPythonScriptBody.extend(detailOnlyFormattingSchema.shape);
9
9
  const tdInfoToolSchema = detailOnlyFormattingSchema;
10
10
  const getNodesToolSchema = getNodesQueryParams.extend(formattingOptionsSchema.shape);
11
11
  const getNodeDetailToolSchema = getNodeDetailQueryParams.extend(formattingOptionsSchema.shape);
12
+ const getNodeErrorsToolSchema = getNodeErrorsQueryParams.extend(formattingOptionsSchema.shape);
12
13
  const createNodeToolSchema = createNodeBody.extend(detailOnlyFormattingSchema.shape);
13
14
  const updateNodeToolSchema = updateNodeBody.extend(detailOnlyFormattingSchema.shape);
14
15
  const deleteNodeToolSchema = deleteNodeQueryParams.extend(detailOnlyFormattingSchema.shape);
15
16
  const classListToolSchema = formattingOptionsSchema;
16
17
  const classDetailToolSchema = getTdPythonClassDetailsParams.extend(formattingOptionsSchema.shape);
18
+ const moduleHelpToolSchema = getModuleHelpQueryParams.extend(detailOnlyFormattingSchema.shape);
17
19
  const execNodeMethodToolSchema = execNodeMethodBody.extend(detailOnlyFormattingSchema.shape);
18
20
  const describeToolsSchema = detailOnlyFormattingSchema.extend({
19
21
  filter: z
@@ -38,22 +40,22 @@ export function registerTdTools(server, logger, tdClient) {
38
40
  return {
39
41
  content: [
40
42
  {
41
- type: "text",
42
43
  text: message,
44
+ type: "text",
43
45
  },
44
46
  ],
45
47
  };
46
48
  }
47
49
  const formattedText = formatToolMetadata(filteredEntries, {
48
50
  detailLevel: detailLevel ?? (filter ? "summary" : "minimal"),
49
- responseFormat,
50
51
  filter: normalizedFilter,
52
+ responseFormat,
51
53
  });
52
54
  return {
53
55
  content: [
54
56
  {
55
- type: "text",
56
57
  text: formattedText,
58
+ type: "text",
57
59
  },
58
60
  ],
59
61
  };
@@ -76,8 +78,8 @@ export function registerTdTools(server, logger, tdClient) {
76
78
  return {
77
79
  content: [
78
80
  {
79
- type: "text",
80
81
  text: formattedText,
82
+ type: "text",
81
83
  },
82
84
  ],
83
85
  };
@@ -89,7 +91,10 @@ export function registerTdTools(server, logger, tdClient) {
89
91
  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) => {
90
92
  try {
91
93
  const { detailLevel, responseFormat, ...scriptParams } = params;
92
- logger.debug(`Executing script: ${scriptParams.script}`);
94
+ logger.sendLog({
95
+ data: `Executing script: ${scriptParams.script}`,
96
+ level: "debug",
97
+ });
93
98
  const result = await tdClient.execPythonScript(scriptParams);
94
99
  if (!result.success) {
95
100
  throw result.error;
@@ -102,8 +107,8 @@ export function registerTdTools(server, logger, tdClient) {
102
107
  return {
103
108
  content: [
104
109
  {
105
- type: "text",
106
110
  text: formattedText,
111
+ type: "text",
107
112
  },
108
113
  ],
109
114
  };
@@ -126,8 +131,8 @@ export function registerTdTools(server, logger, tdClient) {
126
131
  return {
127
132
  content: [
128
133
  {
129
- type: "text",
130
134
  text: formattedText,
135
+ type: "text",
131
136
  },
132
137
  ],
133
138
  };
@@ -150,8 +155,8 @@ export function registerTdTools(server, logger, tdClient) {
150
155
  return {
151
156
  content: [
152
157
  {
153
- type: "text",
154
158
  text: formattedText,
159
+ type: "text",
155
160
  },
156
161
  ],
157
162
  };
@@ -179,8 +184,8 @@ export function registerTdTools(server, logger, tdClient) {
179
184
  return {
180
185
  content: [
181
186
  {
182
- type: "text",
183
187
  text: formattedText,
188
+ type: "text",
184
189
  },
185
190
  ],
186
191
  };
@@ -205,8 +210,8 @@ export function registerTdTools(server, logger, tdClient) {
205
210
  return {
206
211
  content: [
207
212
  {
208
- type: "text",
209
213
  text: formattedText,
214
+ type: "text",
210
215
  },
211
216
  ],
212
217
  };
@@ -215,6 +220,31 @@ export function registerTdTools(server, logger, tdClient) {
215
220
  return handleToolError(error, logger, TOOL_NAMES.GET_TD_NODE_PARAMETERS, REFERENCE_COMMENT);
216
221
  }
217
222
  });
223
+ server.tool(TOOL_NAMES.GET_TD_NODE_ERRORS, "Check node and descendant errors reported by TouchDesigner", getNodeErrorsToolSchema.strict().shape, async (params) => {
224
+ try {
225
+ const { detailLevel, limit, responseFormat, ...queryParams } = params;
226
+ const result = await tdClient.getNodeErrors(queryParams);
227
+ if (!result.success) {
228
+ throw result.error;
229
+ }
230
+ const formattedText = formatNodeErrors(result.data, {
231
+ detailLevel: detailLevel ?? "summary",
232
+ limit,
233
+ responseFormat,
234
+ });
235
+ return {
236
+ content: [
237
+ {
238
+ text: formattedText,
239
+ type: "text",
240
+ },
241
+ ],
242
+ };
243
+ }
244
+ catch (error) {
245
+ return handleToolError(error, logger, TOOL_NAMES.GET_TD_NODE_ERRORS, REFERENCE_COMMENT);
246
+ }
247
+ });
218
248
  server.tool(TOOL_NAMES.UPDATE_TD_NODE_PARAMETERS, "Update parameters of a specific node in TouchDesigner", updateNodeToolSchema.strict().shape, async (params) => {
219
249
  try {
220
250
  const { detailLevel, responseFormat, ...updateParams } = params;
@@ -229,8 +259,8 @@ export function registerTdTools(server, logger, tdClient) {
229
259
  return {
230
260
  content: [
231
261
  {
232
- type: "text",
233
262
  text: formattedText,
263
+ type: "text",
234
264
  },
235
265
  ],
236
266
  };
@@ -247,18 +277,21 @@ export function registerTdTools(server, logger, tdClient) {
247
277
  if (!result.success) {
248
278
  throw result.error;
249
279
  }
250
- const formattedText = formatExecNodeMethodResult(result.data, { nodePath, method, args, kwargs }, { detailLevel: detailLevel ?? "summary", responseFormat });
280
+ const formattedText = formatExecNodeMethodResult(result.data, { args, kwargs, method, nodePath }, { detailLevel: detailLevel ?? "summary", responseFormat });
251
281
  return {
252
282
  content: [
253
283
  {
254
- type: "text",
255
284
  text: formattedText,
285
+ type: "text",
256
286
  },
257
287
  ],
258
288
  };
259
289
  }
260
290
  catch (error) {
261
- logger.error(error);
291
+ logger.sendLog({
292
+ data: error,
293
+ level: "error",
294
+ });
262
295
  return handleToolError(error, logger, TOOL_NAMES.EXECUTE_NODE_METHOD, REFERENCE_COMMENT);
263
296
  }
264
297
  });
@@ -277,8 +310,8 @@ export function registerTdTools(server, logger, tdClient) {
277
310
  return {
278
311
  content: [
279
312
  {
280
- type: "text",
281
313
  text: formattedText,
314
+ type: "text",
282
315
  },
283
316
  ],
284
317
  };
@@ -303,8 +336,8 @@ export function registerTdTools(server, logger, tdClient) {
303
336
  return {
304
337
  content: [
305
338
  {
306
- type: "text",
307
339
  text: formattedText,
340
+ type: "text",
308
341
  },
309
342
  ],
310
343
  };
@@ -313,6 +346,30 @@ export function registerTdTools(server, logger, tdClient) {
313
346
  return handleToolError(error, logger, TOOL_NAMES.GET_TD_CLASS_DETAILS, REFERENCE_COMMENT);
314
347
  }
315
348
  });
349
+ server.tool(TOOL_NAMES.GET_TD_MODULE_HELP, "Retrieve Python help() text for a TouchDesigner module or class", moduleHelpToolSchema.strict().shape, async (params) => {
350
+ try {
351
+ const { detailLevel, moduleName, responseFormat } = params;
352
+ const result = await tdClient.getModuleHelp({ moduleName });
353
+ if (!result.success) {
354
+ throw result.error;
355
+ }
356
+ const formattedText = formatModuleHelp(result.data, {
357
+ detailLevel: detailLevel ?? "summary",
358
+ responseFormat,
359
+ });
360
+ return {
361
+ content: [
362
+ {
363
+ text: formattedText,
364
+ type: "text",
365
+ },
366
+ ],
367
+ };
368
+ }
369
+ catch (error) {
370
+ return handleToolError(error, logger, TOOL_NAMES.GET_TD_MODULE_HELP);
371
+ }
372
+ });
316
373
  }
317
374
  function matchesMetadataFilter(entry, keyword) {
318
375
  const normalizedKeyword = keyword.toLowerCase();