#
tokens: 3708/50000 5/5 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── nuke_bridge.py
├── package-lock.json
├── package.json
├── README.md
└── src
    ├── index.js
    └── server.js
```

# Files

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
1 | # nuke-mcp-2
2 | nuke-mcp
3 | 
```

--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------

```javascript
 1 | #!/usr/bin/env node
 2 |     import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
 3 |     import { server } from './server.js';
 4 | 
 5 |     console.log('Starting Nuke MCP server...');
 6 | 
 7 |     // Start receiving messages on stdin and sending messages on stdout
 8 |     const transport = new StdioServerTransport();
 9 |     await server.connect(transport);
10 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |       "name": "mcp-nuke-server",
 3 |       "version": "1.0.0",
 4 |       "type": "module",
 5 |       "description": "MCP server for Nuke integration",
 6 |       "main": "src/index.js",
 7 |       "bin": {
 8 |         "mcp-nuke-server": "src/index.js"
 9 |       },
10 |       "scripts": {
11 |         "start": "node src/index.js",
12 |         "dev": "node --watch src/index.js",
13 |         "inspect": "npx -y @modelcontextprotocol/inspector node src/index.js"
14 |       },
15 |       "dependencies": {
16 |         "@modelcontextprotocol/sdk": "^1.0.0",
17 |         "zod": "^3.22.4"
18 |       }
19 |     }
20 | 
```

--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
  2 |     import { z } from 'zod';
  3 |     import { exec } from 'child_process';
  4 |     import { promisify } from 'util';
  5 | 
  6 |     const execAsync = promisify(exec);
  7 | 
  8 |     // Create an MCP server for Nuke integration
  9 |     const server = new McpServer({
 10 |       name: "Nuke MCP Server",
 11 |       version: "1.0.0",
 12 |       description: "MCP server for interacting with Nuke"
 13 |     });
 14 | 
 15 |     // Helper function to execute Python bridge script
 16 |     async function executeBridge(command, args = {}) {
 17 |       try {
 18 |         // Convert args to JSON string and escape quotes for command line
 19 |         const argsJson = JSON.stringify(args).replace(/"/g, '\\"');
 20 |         const { stdout, stderr } = await execAsync(`python nuke_bridge.py ${command} "${argsJson}"`);
 21 |         
 22 |         if (stderr) {
 23 |           console.error(`Bridge script error: ${stderr}`);
 24 |           return {
 25 |             content: [{ type: "text", text: `Error: ${stderr}` }],
 26 |             isError: true
 27 |           };
 28 |         }
 29 |         
 30 |         try {
 31 |           const result = JSON.parse(stdout);
 32 |           if (result.error) {
 33 |             return {
 34 |               content: [{ type: "text", text: `Error: ${result.error}` }],
 35 |               isError: true
 36 |             };
 37 |           }
 38 |           return {
 39 |             content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }]
 40 |           };
 41 |         } catch (parseError) {
 42 |           console.error(`Error parsing bridge output: ${parseError.message}`);
 43 |           return {
 44 |             content: [{ type: "text", text: `Error parsing bridge output: ${parseError.message}\nRaw output: ${stdout}` }],
 45 |             isError: true
 46 |           };
 47 |         }
 48 |       } catch (error) {
 49 |         console.error(`Error executing bridge script: ${error.message}`);
 50 |         return {
 51 |           content: [{ type: "text", text: `Error executing bridge script: ${error.message}` }],
 52 |           isError: true
 53 |         };
 54 |       }
 55 |     }
 56 | 
 57 |     // 1. createNode tool
 58 |     server.tool(
 59 |       "createNode",
 60 |       {
 61 |         nodeType: z.string().describe("Type of node to create (e.g., 'Read', 'Merge2')"),
 62 |         name: z.string().optional().describe("Optional name for the node"),
 63 |         inputs: z.array(z.string()).optional().describe("Optional array of input node names")
 64 |       },
 65 |       async ({ nodeType, name, inputs }) => {
 66 |         return await executeBridge("createNode", { nodeType, name, inputs });
 67 |       },
 68 |       { description: "Creates a node of the specified type in a Nuke script" }
 69 |     );
 70 | 
 71 |     // 2. setKnobValue tool
 72 |     server.tool(
 73 |       "setKnobValue",
 74 |       {
 75 |         nodeName: z.string().describe("Name of the node"),
 76 |         knobName: z.string().describe("Name of the knob to set"),
 77 |         value: z.union([z.string(), z.number()]).describe("Value to set the knob to")
 78 |       },
 79 |       async ({ nodeName, knobName, value }) => {
 80 |         return await executeBridge("setKnobValue", { nodeName, knobName, value });
 81 |       },
 82 |       { description: "Sets a knob on the specified node to the provided value" }
 83 |     );
 84 | 
 85 |     // 3. getNode tool
 86 |     server.tool(
 87 |       "getNode",
 88 |       {
 89 |         nodeName: z.string().describe("Name of the node to get information about")
 90 |       },
 91 |       async ({ nodeName }) => {
 92 |         return await executeBridge("getNode", { nodeName });
 93 |       },
 94 |       { description: "Returns basic info about a node (type, knob values, etc.)" }
 95 |     );
 96 | 
 97 |     // 4. execute tool
 98 |     server.tool(
 99 |       "execute",
100 |       {
101 |         writeNodeName: z.string().describe("Name of the Write node to render"),
102 |         frameRangeStart: z.number().describe("Start frame for rendering"),
103 |         frameRangeEnd: z.number().describe("End frame for rendering")
104 |       },
105 |       async ({ writeNodeName, frameRangeStart, frameRangeEnd }) => {
106 |         return await executeBridge("execute", { writeNodeName, frameRangeStart, frameRangeEnd });
107 |       },
108 |       { description: "Renders the specified Write node from start to end frames" }
109 |     );
110 | 
111 |     export { server };
112 | 
```

--------------------------------------------------------------------------------
/nuke_bridge.py:
--------------------------------------------------------------------------------

```python
  1 | #!/usr/bin/env python
  2 |     import sys
  3 |     import json
  4 |     import traceback
  5 | 
  6 |     # Function to handle response formatting
  7 |     def respond(success=True, data=None, error=None):
  8 |         response = {
  9 |             "success": success,
 10 |             "data": data
 11 |         }
 12 |         if error:
 13 |             response["error"] = error
 14 |         print(json.dumps(response))
 15 | 
 16 |     # Main function to process commands
 17 |     def main():
 18 |         if len(sys.argv) < 2:
 19 |             respond(False, None, "No command specified")
 20 |             return
 21 | 
 22 |         command = sys.argv[1]
 23 |         args = {}
 24 |         
 25 |         # Parse arguments if provided
 26 |         if len(sys.argv) > 2:
 27 |             try:
 28 |                 args = json.loads(sys.argv[2])
 29 |             except json.JSONDecodeError as e:
 30 |                 respond(False, None, f"Invalid JSON arguments: {str(e)}")
 31 |                 return
 32 | 
 33 |         try:
 34 |             # Import nuke here to avoid errors when running outside of Nuke
 35 |             try:
 36 |                 import nuke
 37 |             except ImportError:
 38 |                 respond(False, None, "Failed to import nuke module. Make sure this script is run within Nuke.")
 39 |                 return
 40 | 
 41 |             # Process the command
 42 |             if command == "createNode":
 43 |                 create_node(args, nuke)
 44 |             elif command == "setKnobValue":
 45 |                 set_knob_value(args, nuke)
 46 |             elif command == "getNode":
 47 |                 get_node(args, nuke)
 48 |             elif command == "execute":
 49 |                 execute(args, nuke)
 50 |             else:
 51 |                 respond(False, None, f"Unknown command: {command}")
 52 |         except Exception as e:
 53 |             respond(False, None, f"Error executing command: {str(e)}\n{traceback.format_exc()}")
 54 | 
 55 |     # Command implementations
 56 |     def create_node(args, nuke):
 57 |         if "nodeType" not in args:
 58 |             respond(False, None, "nodeType parameter is required")
 59 |             return
 60 |             
 61 |         node_type = args["nodeType"]
 62 |         name = args.get("name")
 63 |         inputs = args.get("inputs", [])
 64 |         
 65 |         try:
 66 |             # Create the node
 67 |             node = nuke.createNode(node_type, inpanel=False)
 68 |             
 69 |             # Set name if provided
 70 |             if name:
 71 |                 node["name"].setValue(name)
 72 |                 
 73 |             # Connect inputs if provided
 74 |             for i, input_name in enumerate(inputs):
 75 |                 input_node = nuke.toNode(input_name)
 76 |                 if input_node:
 77 |                     node.setInput(i, input_node)
 78 |                 else:
 79 |                     respond(False, None, f"Input node not found: {input_name}")
 80 |                     return
 81 |             
 82 |             # Return node info
 83 |             node_info = {
 84 |                 "name": node.name(),
 85 |                 "type": node.Class(),
 86 |                 "position": {"x": node.xpos(), "y": node.ypos()}
 87 |             }
 88 |             respond(True, node_info)
 89 |         except Exception as e:
 90 |             respond(False, None, f"Error creating node: {str(e)}")
 91 | 
 92 |     def set_knob_value(args, nuke):
 93 |         if "nodeName" not in args:
 94 |             respond(False, None, "nodeName parameter is required")
 95 |             return
 96 |         if "knobName" not in args:
 97 |             respond(False, None, "knobName parameter is required")
 98 |             return
 99 |         if "value" not in args:
100 |             respond(False, None, "value parameter is required")
101 |             return
102 |             
103 |         node_name = args["nodeName"]
104 |         knob_name = args["knobName"]
105 |         value = args["value"]
106 |         
107 |         try:
108 |             node = nuke.toNode(node_name)
109 |             if not node:
110 |                 respond(False, None, f"Node not found: {node_name}")
111 |                 return
112 |                 
113 |             if knob_name not in node.knobs():
114 |                 respond(False, None, f"Knob not found: {knob_name}")
115 |                 return
116 |                 
117 |             node[knob_name].setValue(value)
118 |             
119 |             respond(True, {
120 |                 "node": node_name,
121 |                 "knob": knob_name,
122 |                 "value": value
123 |             })
124 |         except Exception as e:
125 |             respond(False, None, f"Error setting knob value: {str(e)}")
126 | 
127 |     def get_node(args, nuke):
128 |         if "nodeName" not in args:
129 |             respond(False, None, "nodeName parameter is required")
130 |             return
131 |             
132 |         node_name = args["nodeName"]
133 |         
134 |         try:
135 |             node = nuke.toNode(node_name)
136 |             if not node:
137 |                 respond(False, None, f"Node not found: {node_name}")
138 |                 return
139 |                 
140 |             # Collect basic node info
141 |             node_info = {
142 |                 "name": node.name(),
143 |                 "type": node.Class(),
144 |                 "position": {"x": node.xpos(), "y": node.ypos()},
145 |                 "knobs": {}
146 |             }
147 |             
148 |             # Collect knob values (only for basic types that can be serialized)
149 |             for knob in node.knobs():
150 |                 try:
151 |                     k = node[knob]
152 |                     # Handle different knob types
153 |                     if k.Class() in ["String_Knob", "File_Knob", "Text_Knob"]:
154 |                         node_info["knobs"][knob] = k.value()
155 |                     elif k.Class() in ["Int_Knob", "Double_Knob", "Boolean_Knob", "XY_Knob"]:
156 |                         node_info["knobs"][knob] = k.value()
157 |                     # Skip complex knobs that can't be easily serialized
158 |                 except:
159 |                     pass
160 |                     
161 |             respond(True, node_info)
162 |         except Exception as e:
163 |             respond(False, None, f"Error getting node info: {str(e)}")
164 | 
165 |     def execute(args, nuke):
166 |         if "writeNodeName" not in args:
167 |             respond(False, None, "writeNodeName parameter is required")
168 |             return
169 |         if "frameRangeStart" not in args:
170 |             respond(False, None, "frameRangeStart parameter is required")
171 |             return
172 |         if "frameRangeEnd" not in args:
173 |             respond(False, None, "frameRangeEnd parameter is required")
174 |             return
175 |             
176 |         write_node_name = args["writeNodeName"]
177 |         frame_start = args["frameRangeStart"]
178 |         frame_end = args["frameRangeEnd"]
179 |         
180 |         try:
181 |             write_node = nuke.toNode(write_node_name)
182 |             if not write_node:
183 |                 respond(False, None, f"Write node not found: {write_node_name}")
184 |                 return
185 |                 
186 |             if write_node.Class() != "Write":
187 |                 respond(False, None, f"Node {write_node_name} is not a Write node")
188 |                 return
189 |                 
190 |             # Execute the write node
191 |             nuke.execute(write_node, int(frame_start), int(frame_end))
192 |             
193 |             respond(True, {
194 |                 "node": write_node_name,
195 |                 "rendered": True,
196 |                 "frameRange": {"start": frame_start, "end": frame_end},
197 |                 "outputPath": write_node["file"].value()
198 |             })
199 |         except Exception as e:
200 |             respond(False, None, f"Error executing write node: {str(e)}")
201 | 
202 |     if __name__ == "__main__":
203 |         main()
204 | 
```