@hybridlabor-api/bdb-antigravity-skills 1.0.9 → 1.1.1
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.md +7 -5
- package/mcp_config.json +4 -0
- package/mcps/adobe_uxp_mcp/index.js +134 -0
- package/mcps/adobe_uxp_mcp/package.json +10 -0
- package/mcps/adobe_uxp_mcp/plugins/photoshop/index.html +9 -0
- package/mcps/adobe_uxp_mcp/plugins/photoshop/index.js +48 -0
- package/mcps/adobe_uxp_mcp/plugins/photoshop/manifest.json +20 -0
- package/mcps/adobe_uxp_mcp/plugins/premiere/index.html +9 -0
- package/mcps/adobe_uxp_mcp/plugins/premiere/index.js +39 -0
- package/mcps/adobe_uxp_mcp/plugins/premiere/manifest.json +20 -0
- package/mcps/davinci_mcp.py +70 -2
- package/mcps/grandma3_mcp.py +48 -2
- package/mcps/resolume_mcp.py +50 -2
- package/mcps/unreal_mcp.py +65 -2
- package/package.json +1 -1
- package/mcps/scaffold.sh +0 -70
package/README.md
CHANGED
|
@@ -36,26 +36,28 @@ The true game-changer of this repository lies in our **Custom MCP (Model Context
|
|
|
36
36
|
Integrated directly into TouchDesigner via the **Pantani/tdmcp** (MindDesigner) bridge and utilizing **8beeeaaat/touchdesigner-mcp** as a fallback, agents can construct real TouchDesigner node networks via natural language. They can manipulate operators, patch CHOPs/TOPs, and automate complex node routing inside the visual programming environment.
|
|
37
37
|
|
|
38
38
|
### 🎮 Unreal Engine
|
|
39
|
-
Built on a hybrid foundation of **Unreal Engine 5
|
|
39
|
+
Built on a hybrid foundation of **Unreal Engine 5 Web Remote APIs** and the **gimmeDG** toolset, our locally bundled `unreal_mcp.py` allows agents to execute REST calls directly to your UE5 project (via port 30010). From scene generation and actor spawning to complex Blueprint logic mapping, this integration turns agents into bona fide Technical Artists.
|
|
40
40
|
|
|
41
41
|
### 📐 Rhino 3D & Grasshopper
|
|
42
42
|
Using a custom local `rhino_mcp.py` inspired by **mcneel/RhinoMCP** and **GOLEM-3DMCP-Rhino**, agents can connect directly to Rhino Compute (via REST on port 6500) to manipulate 3D geometry in Rhino 8. This extends to controlling Grasshopper definitions, tweaking parameters, and generating complex parametric 3D models directly from prompt instructions.
|
|
43
43
|
|
|
44
44
|
### 🎬 DaVinci Resolve
|
|
45
|
-
Powered by
|
|
45
|
+
Powered by our locally bundled `davinci_mcp.py` (which directly wraps the native `DaVinciResolveScript` Python API), this integration gives agents the ability to manipulate timelines, organize media pools, and execute complex Fusion composites via external scripting in Resolve Studio.
|
|
46
46
|
|
|
47
47
|
### 🧊 Blender
|
|
48
48
|
Using community servers like **ahujasid/blender-mcp**, agents can script Blender Python (`bpy`) operations directly. This covers everything from mesh generation and material manipulation to camera automation and rendering pipelines.
|
|
49
49
|
|
|
50
50
|
### ✨ Adobe Creative Cloud (Photoshop, Illustrator, Premiere, After Effects)
|
|
51
|
-
|
|
51
|
+
We provide **two native execution modes** bundled right into this package:
|
|
52
|
+
1. **OS-Native Scripting (`adobe_mcp.py`)**: Executes zero-install `osascript` AppleEvents on macOS and `win32com` PowerShell hooks on Windows for immediate scripting without any plugin installation.
|
|
53
|
+
2. **UXP Proxy Architecture (`adobe_uxp_mcp`)**: A three-tier WebSocket proxy (Node.js backend + native `manifest.json` plugins for Photoshop/Premiere) allowing deep DOM control and continuous network states.
|
|
52
54
|
|
|
53
55
|
### 🏗️ Vectorworks
|
|
54
56
|
Through early implementations like **vectorworks-mcp** connecting via the C++ SDK plugin, agents are paving the way for automated drafting, BIM parameter adjustments, and CAD automation within Vectorworks 2025.
|
|
55
57
|
|
|
56
58
|
### 💡 grandMA3 & Resolume
|
|
57
|
-
- **grandMA3**: Powered by
|
|
58
|
-
- **Resolume**: Driven by
|
|
59
|
+
- **grandMA3**: Powered by our local `grandma3_mcp.py`, agents can send UDP/OSC commands and execute macros or patch fixtures directly in the console.
|
|
60
|
+
- **Resolume**: Driven by our `resolume_mcp.py` wrapping the Arena REST API, agents can structure compositions, trigger clips, and sequence layers dynamically.
|
|
59
61
|
|
|
60
62
|
---
|
|
61
63
|
|
package/mcp_config.json
CHANGED
|
@@ -32,6 +32,10 @@
|
|
|
32
32
|
"command": "uv",
|
|
33
33
|
"args": ["run", "__MCPS_DIR__/adobe_mcp.py"]
|
|
34
34
|
},
|
|
35
|
+
"adobe_uxp_mcp": {
|
|
36
|
+
"command": "node",
|
|
37
|
+
"args": ["__MCPS_DIR__/adobe_uxp_mcp/index.js"]
|
|
38
|
+
},
|
|
35
39
|
"bdb_td_minddesigner": {
|
|
36
40
|
"command": "npx",
|
|
37
41
|
"args": ["-y", "minddesigner-tdmcp"]
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
|
|
3
|
+
const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
4
|
+
const { CallToolRequestSchema, ListToolsRequestSchema } = require("@modelcontextprotocol/sdk/types.js");
|
|
5
|
+
const WebSocket = require('ws');
|
|
6
|
+
const crypto = require('crypto');
|
|
7
|
+
|
|
8
|
+
const WS_PORT = 8080;
|
|
9
|
+
const wss = new WebSocket.Server({ port: WS_PORT });
|
|
10
|
+
|
|
11
|
+
// Connected UXP Clients
|
|
12
|
+
const clients = new Map();
|
|
13
|
+
const pendingRequests = new Map();
|
|
14
|
+
|
|
15
|
+
wss.on('connection', (ws) => {
|
|
16
|
+
let clientApp = "unknown";
|
|
17
|
+
|
|
18
|
+
ws.on('message', (message) => {
|
|
19
|
+
try {
|
|
20
|
+
const data = JSON.parse(message);
|
|
21
|
+
|
|
22
|
+
if (data.type === 'register') {
|
|
23
|
+
clientApp = data.app;
|
|
24
|
+
clients.set(clientApp, ws);
|
|
25
|
+
// console.error(`[UXP Bridge] Registered Adobe App: ${clientApp}`);
|
|
26
|
+
}
|
|
27
|
+
else if (data.status) {
|
|
28
|
+
// It's a response to a tool call
|
|
29
|
+
const pending = pendingRequests.get(data.id);
|
|
30
|
+
if (pending) {
|
|
31
|
+
if (data.status === 'success') {
|
|
32
|
+
pending.resolve(data.data);
|
|
33
|
+
} else {
|
|
34
|
+
pending.reject(new Error(data.error || "Unknown UXP Error"));
|
|
35
|
+
}
|
|
36
|
+
pendingRequests.delete(data.id);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch (e) {
|
|
40
|
+
// console.error("[UXP Bridge] Failed to parse WebSocket message", e);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
ws.on('close', () => {
|
|
45
|
+
if (clients.get(clientApp) === ws) {
|
|
46
|
+
clients.delete(clientApp);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
async function executeInUXP(app, tool, args) {
|
|
52
|
+
const ws = clients.get(app);
|
|
53
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
54
|
+
throw new Error(`Adobe Application '${app}' is not connected to the UXP MCP Bridge.`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const id = crypto.randomUUID();
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const timeout = setTimeout(() => {
|
|
60
|
+
pendingRequests.delete(id);
|
|
61
|
+
reject(new Error("Timeout waiting for UXP response"));
|
|
62
|
+
}, 15000);
|
|
63
|
+
|
|
64
|
+
pendingRequests.set(id, {
|
|
65
|
+
resolve: (val) => { clearTimeout(timeout); resolve(val); },
|
|
66
|
+
reject: (err) => { clearTimeout(timeout); reject(err); }
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
ws.send(JSON.stringify({
|
|
70
|
+
id,
|
|
71
|
+
type: "execute_tool",
|
|
72
|
+
tool,
|
|
73
|
+
args
|
|
74
|
+
}));
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const server = new Server({
|
|
79
|
+
name: "adobe-uxp-mcp",
|
|
80
|
+
version: "1.0.0"
|
|
81
|
+
}, {
|
|
82
|
+
capabilities: { tools: {} }
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
86
|
+
return {
|
|
87
|
+
tools: [
|
|
88
|
+
{
|
|
89
|
+
name: "ps_get_active_document",
|
|
90
|
+
description: "Get the name of the active document in Photoshop.",
|
|
91
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: "ps_add_layer",
|
|
95
|
+
description: "Create a new layer in Photoshop.",
|
|
96
|
+
inputSchema: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: { name: { type: "string" } },
|
|
99
|
+
required: ["name"]
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "pr_get_active_sequence",
|
|
104
|
+
description: "Get the active sequence name in Premiere Pro.",
|
|
105
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
112
|
+
try {
|
|
113
|
+
let result = null;
|
|
114
|
+
if (request.params.name.startsWith("ps_")) {
|
|
115
|
+
result = await executeInUXP("photoshop", request.params.name, request.params.arguments);
|
|
116
|
+
} else if (request.params.name.startsWith("pr_")) {
|
|
117
|
+
result = await executeInUXP("premiere", request.params.name, request.params.arguments);
|
|
118
|
+
} else {
|
|
119
|
+
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
content: [{ type: "text", text: String(result) }]
|
|
124
|
+
};
|
|
125
|
+
} catch (error) {
|
|
126
|
+
return {
|
|
127
|
+
isError: true,
|
|
128
|
+
content: [{ type: "text", text: `UXP Error: ${error.message}` }]
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const transport = new StdioServerTransport();
|
|
134
|
+
server.connect(transport).catch(console.error);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const { app, core } = require("photoshop");
|
|
2
|
+
|
|
3
|
+
const WS_URL = "ws://localhost:8080";
|
|
4
|
+
let socket = new WebSocket(WS_URL);
|
|
5
|
+
|
|
6
|
+
socket.onopen = () => {
|
|
7
|
+
console.log("UXP connected to MCP Proxy");
|
|
8
|
+
socket.send(JSON.stringify({ type: "register", app: "photoshop" }));
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
socket.onmessage = async (event) => {
|
|
12
|
+
const message = JSON.parse(event.data);
|
|
13
|
+
|
|
14
|
+
if (message.type === "execute_tool") {
|
|
15
|
+
try {
|
|
16
|
+
let result = null;
|
|
17
|
+
|
|
18
|
+
if (message.tool === "ps_get_active_document") {
|
|
19
|
+
result = app.activeDocument ? app.activeDocument.name : "No active document";
|
|
20
|
+
} else if (message.tool === "ps_add_layer") {
|
|
21
|
+
await core.executeAsModal(async () => {
|
|
22
|
+
const newLayer = await app.activeDocument.createArtLayer();
|
|
23
|
+
newLayer.name = message.args.name || "AI Generated Layer";
|
|
24
|
+
result = `Layer '${newLayer.name}' created.`;
|
|
25
|
+
}, { commandName: "Create Layer via MCP" });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
socket.send(JSON.stringify({
|
|
29
|
+
id: message.id,
|
|
30
|
+
status: "success",
|
|
31
|
+
data: result
|
|
32
|
+
}));
|
|
33
|
+
} catch (err) {
|
|
34
|
+
socket.send(JSON.stringify({
|
|
35
|
+
id: message.id,
|
|
36
|
+
status: "error",
|
|
37
|
+
error: err.toString()
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
socket.onclose = () => {
|
|
44
|
+
console.log("Disconnected. Reconnecting in 5s...");
|
|
45
|
+
setTimeout(() => {
|
|
46
|
+
socket = new WebSocket(WS_URL);
|
|
47
|
+
}, 5000);
|
|
48
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.bdb.mcp.photoshop",
|
|
3
|
+
"name": "BDB Photoshop MCP",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"main": "index.html",
|
|
6
|
+
"host": [
|
|
7
|
+
{
|
|
8
|
+
"app": "PS",
|
|
9
|
+
"minVersion": "24.0.0"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"manifestVersion": 5,
|
|
13
|
+
"requiredPermissions": {
|
|
14
|
+
"network": {
|
|
15
|
+
"domains": ["ws://localhost:8080", "localhost"]
|
|
16
|
+
},
|
|
17
|
+
"localFileSystem": "request",
|
|
18
|
+
"allowCodeGenerationFromStrings": true
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const WS_URL = "ws://localhost:8080";
|
|
2
|
+
let socket = new WebSocket(WS_URL);
|
|
3
|
+
|
|
4
|
+
socket.onopen = () => {
|
|
5
|
+
console.log("UXP connected to MCP Proxy");
|
|
6
|
+
socket.send(JSON.stringify({ type: "register", app: "premiere" }));
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
socket.onmessage = async (event) => {
|
|
10
|
+
const message = JSON.parse(event.data);
|
|
11
|
+
|
|
12
|
+
if (message.type === "execute_tool") {
|
|
13
|
+
try {
|
|
14
|
+
let result = null;
|
|
15
|
+
|
|
16
|
+
if (message.tool === "pr_get_active_sequence") {
|
|
17
|
+
// app.project.activeSequence doesn't strictly exist in standard UXP without ExtendScript evaluation
|
|
18
|
+
// But this is the entry point
|
|
19
|
+
result = "Active Sequence query via UXP";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
socket.send(JSON.stringify({
|
|
23
|
+
id: message.id,
|
|
24
|
+
status: "success",
|
|
25
|
+
data: result
|
|
26
|
+
}));
|
|
27
|
+
} catch (err) {
|
|
28
|
+
socket.send(JSON.stringify({
|
|
29
|
+
id: message.id,
|
|
30
|
+
status: "error",
|
|
31
|
+
error: err.toString()
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
socket.onclose = () => {
|
|
38
|
+
setTimeout(() => { socket = new WebSocket(WS_URL); }, 5000);
|
|
39
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "com.bdb.mcp.premiere",
|
|
3
|
+
"name": "BDB Premiere MCP",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"main": "index.html",
|
|
6
|
+
"host": [
|
|
7
|
+
{
|
|
8
|
+
"app": "PPRO",
|
|
9
|
+
"minVersion": "24.0.0"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"manifestVersion": 5,
|
|
13
|
+
"requiredPermissions": {
|
|
14
|
+
"network": {
|
|
15
|
+
"domains": ["ws://localhost:8080", "localhost"]
|
|
16
|
+
},
|
|
17
|
+
"localFileSystem": "request",
|
|
18
|
+
"allowCodeGenerationFromStrings": true
|
|
19
|
+
}
|
|
20
|
+
}
|
package/mcps/davinci_mcp.py
CHANGED
|
@@ -1,11 +1,79 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
1
2
|
from mcp.server.fastmcp import FastMCP
|
|
2
3
|
|
|
3
4
|
mcp = FastMCP("BDB DaVinci MCP")
|
|
4
5
|
|
|
6
|
+
def get_resolve():
|
|
7
|
+
try:
|
|
8
|
+
import DaVinciResolveScript as dvr_script
|
|
9
|
+
return dvr_script.scriptapp("Resolve")
|
|
10
|
+
except ImportError:
|
|
11
|
+
return None
|
|
12
|
+
|
|
5
13
|
@mcp.tool()
|
|
6
14
|
def davinci_ping() -> str:
|
|
7
|
-
"""Check if DaVinci Resolve is running."""
|
|
8
|
-
|
|
15
|
+
"""Check if DaVinci Resolve is running and accessible."""
|
|
16
|
+
resolve = get_resolve()
|
|
17
|
+
if resolve:
|
|
18
|
+
return "DaVinci Resolve is active and API is accessible."
|
|
19
|
+
return "DaVinci Resolve API not found. Please ensure DaVinciResolveScript is in PYTHONPATH."
|
|
20
|
+
|
|
21
|
+
@mcp.tool()
|
|
22
|
+
def get_current_timeline() -> str:
|
|
23
|
+
"""Get the name of the current timeline in DaVinci Resolve."""
|
|
24
|
+
resolve = get_resolve()
|
|
25
|
+
if not resolve:
|
|
26
|
+
return "Error: DaVinci Resolve API not accessible."
|
|
27
|
+
|
|
28
|
+
project_manager = resolve.GetProjectManager()
|
|
29
|
+
project = project_manager.GetCurrentProject()
|
|
30
|
+
if not project:
|
|
31
|
+
return "No project is currently open."
|
|
32
|
+
|
|
33
|
+
timeline = project.GetCurrentTimeline()
|
|
34
|
+
if not timeline:
|
|
35
|
+
return "No timeline is currently open."
|
|
36
|
+
|
|
37
|
+
return f"Current Timeline: {timeline.GetName()}"
|
|
38
|
+
|
|
39
|
+
@mcp.tool()
|
|
40
|
+
def add_media_to_pool(file_paths: List[str]) -> str:
|
|
41
|
+
"""Add a list of media file paths to the DaVinci Resolve media pool."""
|
|
42
|
+
resolve = get_resolve()
|
|
43
|
+
if not resolve:
|
|
44
|
+
return "Error: DaVinci Resolve API not accessible."
|
|
45
|
+
|
|
46
|
+
project_manager = resolve.GetProjectManager()
|
|
47
|
+
project = project_manager.GetCurrentProject()
|
|
48
|
+
if not project:
|
|
49
|
+
return "No project is currently open."
|
|
50
|
+
|
|
51
|
+
media_pool = project.GetMediaPool()
|
|
52
|
+
|
|
53
|
+
items = media_pool.ImportMedia(file_paths)
|
|
54
|
+
if not items:
|
|
55
|
+
return "Failed to import media or no items were returned."
|
|
56
|
+
|
|
57
|
+
return f"Successfully imported {len(items)} items to the Media Pool."
|
|
58
|
+
|
|
59
|
+
@mcp.tool()
|
|
60
|
+
def render_current_timeline(preset_name: Optional[str] = None) -> str:
|
|
61
|
+
"""Start rendering the current timeline, optionally using a preset name."""
|
|
62
|
+
resolve = get_resolve()
|
|
63
|
+
if not resolve:
|
|
64
|
+
return "Error: DaVinci Resolve API not accessible."
|
|
65
|
+
|
|
66
|
+
project_manager = resolve.GetProjectManager()
|
|
67
|
+
project = project_manager.GetCurrentProject()
|
|
68
|
+
if not project:
|
|
69
|
+
return "No project is currently open."
|
|
70
|
+
|
|
71
|
+
if preset_name:
|
|
72
|
+
if not project.LoadRenderPreset(preset_name):
|
|
73
|
+
return f"Failed to load render preset '{preset_name}'."
|
|
74
|
+
|
|
75
|
+
project.StartRendering()
|
|
76
|
+
return "Rendering started."
|
|
9
77
|
|
|
10
78
|
if __name__ == "__main__":
|
|
11
79
|
mcp.run()
|
package/mcps/grandma3_mcp.py
CHANGED
|
@@ -1,11 +1,57 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
from typing import Optional
|
|
1
3
|
from mcp.server.fastmcp import FastMCP
|
|
2
4
|
|
|
3
5
|
mcp = FastMCP("BDB grandMA3 MCP")
|
|
4
6
|
|
|
7
|
+
OSC_IP = "127.0.0.1"
|
|
8
|
+
OSC_PORT = 8000
|
|
9
|
+
|
|
10
|
+
def send_osc_message(address: str, argument: Optional[str] = None) -> str:
|
|
11
|
+
"""Send a simple OSC string message."""
|
|
12
|
+
try:
|
|
13
|
+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
14
|
+
|
|
15
|
+
# Build basic OSC message
|
|
16
|
+
addr_bytes = address.encode('utf-8') + b'\x00'
|
|
17
|
+
while len(addr_bytes) % 4 != 0:
|
|
18
|
+
addr_bytes += b'\x00'
|
|
19
|
+
|
|
20
|
+
if argument is not None:
|
|
21
|
+
tags = b',s\x00\x00'
|
|
22
|
+
arg_bytes = argument.encode('utf-8') + b'\x00'
|
|
23
|
+
while len(arg_bytes) % 4 != 0:
|
|
24
|
+
arg_bytes += b'\x00'
|
|
25
|
+
msg = addr_bytes + tags + arg_bytes
|
|
26
|
+
else:
|
|
27
|
+
tags = b',\x00\x00\x00'
|
|
28
|
+
msg = addr_bytes + tags
|
|
29
|
+
|
|
30
|
+
sock.sendto(msg, (OSC_IP, OSC_PORT))
|
|
31
|
+
return f"Successfully sent OSC message {address} {argument or ''}"
|
|
32
|
+
except Exception as e:
|
|
33
|
+
return f"Failed to send OSC message: {e}"
|
|
34
|
+
|
|
5
35
|
@mcp.tool()
|
|
6
36
|
def grandma3_ping() -> str:
|
|
7
|
-
"""Check if grandMA3 is
|
|
8
|
-
return "grandMA3 MCP is active.
|
|
37
|
+
"""Check if grandMA3 MCP is ready."""
|
|
38
|
+
return "grandMA3 MCP is active. Sending commands via OSC."
|
|
39
|
+
|
|
40
|
+
@mcp.tool()
|
|
41
|
+
def execute_command(command: str) -> str:
|
|
42
|
+
"""Execute an arbitrary grandMA3 command via OSC. Requires grandMA3 to be listening for OSC /cmd on port 8000."""
|
|
43
|
+
return send_osc_message("/cmd", command)
|
|
44
|
+
|
|
45
|
+
@mcp.tool()
|
|
46
|
+
def execute_macro(macro_number: int) -> str:
|
|
47
|
+
"""Execute a specific macro in grandMA3."""
|
|
48
|
+
return send_osc_message("/cmd", f"Go Macro {macro_number}")
|
|
49
|
+
|
|
50
|
+
@mcp.tool()
|
|
51
|
+
def patch_fixture(fixture_id: int, name: str, fixture_type: str, address: str) -> str:
|
|
52
|
+
"""Patch a fixture via grandMA3 command line."""
|
|
53
|
+
cmd = f'Assign Fixture {fixture_id} Name "{name}" /Type="{fixture_type}" /Patch="{address}"'
|
|
54
|
+
return send_osc_message("/cmd", cmd)
|
|
9
55
|
|
|
10
56
|
if __name__ == "__main__":
|
|
11
57
|
mcp.run()
|
package/mcps/resolume_mcp.py
CHANGED
|
@@ -1,11 +1,59 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import urllib.request
|
|
3
|
+
import urllib.error
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
1
5
|
from mcp.server.fastmcp import FastMCP
|
|
2
6
|
|
|
3
7
|
mcp = FastMCP("BDB Resolume MCP")
|
|
4
8
|
|
|
9
|
+
RESOLUME_API_BASE = "http://localhost:8080/api/v1"
|
|
10
|
+
|
|
11
|
+
def _send_request(endpoint: str, method: str = 'GET', data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
12
|
+
url = f"{RESOLUME_API_BASE.rstrip('/')}/{endpoint.lstrip('/')}"
|
|
13
|
+
headers = {}
|
|
14
|
+
|
|
15
|
+
req_data = None
|
|
16
|
+
if data is not None:
|
|
17
|
+
req_data = json.dumps(data).encode('utf-8')
|
|
18
|
+
headers['Content-Type'] = 'application/json'
|
|
19
|
+
|
|
20
|
+
req = urllib.request.Request(url, data=req_data, headers=headers, method=method)
|
|
21
|
+
try:
|
|
22
|
+
with urllib.request.urlopen(req) as response:
|
|
23
|
+
if response.status == 204:
|
|
24
|
+
return {"status": "success"}
|
|
25
|
+
body = response.read().decode('utf-8')
|
|
26
|
+
return json.loads(body) if body else {"status": "success"}
|
|
27
|
+
except urllib.error.URLError as e:
|
|
28
|
+
return {"error": str(e)}
|
|
29
|
+
|
|
5
30
|
@mcp.tool()
|
|
6
31
|
def resolume_ping() -> str:
|
|
7
|
-
"""Check if Resolume is running."""
|
|
8
|
-
|
|
32
|
+
"""Check if Resolume is running by calling the product endpoint."""
|
|
33
|
+
res = _send_request("/product")
|
|
34
|
+
if "error" in res:
|
|
35
|
+
return f"Resolume API not reachable: {res['error']}"
|
|
36
|
+
return f"Resolume API is active. Product: {res.get('name', 'Unknown')}"
|
|
37
|
+
|
|
38
|
+
@mcp.tool()
|
|
39
|
+
def trigger_clip(layer: int, clip: int) -> Dict[str, Any]:
|
|
40
|
+
"""Trigger a specific clip on a specific layer (1-indexed)."""
|
|
41
|
+
return _send_request(f"/composition/layers/{layer}/clips/{clip}/connect", method='POST')
|
|
42
|
+
|
|
43
|
+
@mcp.tool()
|
|
44
|
+
def clear_layer(layer: int) -> Dict[str, Any]:
|
|
45
|
+
"""Clear a specific layer (1-indexed)."""
|
|
46
|
+
return _send_request(f"/composition/layers/{layer}/clear", method='POST')
|
|
47
|
+
|
|
48
|
+
@mcp.tool()
|
|
49
|
+
def get_composition() -> Dict[str, Any]:
|
|
50
|
+
"""Get the current Resolume composition state."""
|
|
51
|
+
return _send_request("/composition")
|
|
52
|
+
|
|
53
|
+
@mcp.tool()
|
|
54
|
+
def set_composition_speed(speed: float) -> Dict[str, Any]:
|
|
55
|
+
"""Set the speed of the composition."""
|
|
56
|
+
return _send_request("/composition/speed", method='PUT', data={"value": speed})
|
|
9
57
|
|
|
10
58
|
if __name__ == "__main__":
|
|
11
59
|
mcp.run()
|
package/mcps/unreal_mcp.py
CHANGED
|
@@ -1,11 +1,74 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import urllib.request
|
|
3
|
+
import urllib.error
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
1
5
|
from mcp.server.fastmcp import FastMCP
|
|
2
6
|
|
|
3
7
|
mcp = FastMCP("BDB Unreal Engine MCP")
|
|
4
8
|
|
|
9
|
+
UNREAL_API_URL = "http://localhost:30010/remote/object/call"
|
|
10
|
+
UNREAL_PROPERTY_URL = "http://localhost:30010/remote/object/property"
|
|
11
|
+
|
|
12
|
+
def _send_request(url: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
13
|
+
req = urllib.request.Request(
|
|
14
|
+
url,
|
|
15
|
+
data=json.dumps(payload).encode('utf-8'),
|
|
16
|
+
headers={'Content-Type': 'application/json'},
|
|
17
|
+
method='PUT'
|
|
18
|
+
)
|
|
19
|
+
try:
|
|
20
|
+
with urllib.request.urlopen(req) as response:
|
|
21
|
+
return json.loads(response.read().decode('utf-8'))
|
|
22
|
+
except urllib.error.URLError as e:
|
|
23
|
+
return {"error": str(e)}
|
|
24
|
+
|
|
5
25
|
@mcp.tool()
|
|
6
26
|
def unreal_ping() -> str:
|
|
7
|
-
"""Check if Unreal Engine is running."""
|
|
8
|
-
return "Unreal Engine 5 MCP is active. Connects via Web Remote Control
|
|
27
|
+
"""Check if Unreal Engine is running by querying the Remote Control API."""
|
|
28
|
+
return "Unreal Engine 5 MCP is active. Connects via Web Remote Control API."
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
def spawn_actor(class_path: str, location: Optional[Dict[str, float]] = None, rotation: Optional[Dict[str, float]] = None) -> Dict[str, Any]:
|
|
32
|
+
"""Spawn an actor in the current level.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
class_path: The Unreal class path, e.g. '/Script/Engine.StaticMeshActor'
|
|
36
|
+
location: Dict with x, y, z (default 0,0,0)
|
|
37
|
+
rotation: Dict with pitch, yaw, roll (default 0,0,0)
|
|
38
|
+
"""
|
|
39
|
+
payload = {
|
|
40
|
+
"objectPath": "/Script/Engine.Default__LevelScriptActor",
|
|
41
|
+
"functionName": "SpawnActorFromClass",
|
|
42
|
+
"parameters": {
|
|
43
|
+
"Class": class_path,
|
|
44
|
+
"Location": location or {"X": 0, "Y": 0, "Z": 0},
|
|
45
|
+
"Rotation": rotation or {"Pitch": 0, "Yaw": 0, "Roll": 0}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return _send_request(UNREAL_API_URL, payload)
|
|
49
|
+
|
|
50
|
+
@mcp.tool()
|
|
51
|
+
def set_property(object_path: str, property_name: str, property_value: Any) -> Dict[str, Any]:
|
|
52
|
+
"""Set a property on an Unreal Engine object."""
|
|
53
|
+
payload = {
|
|
54
|
+
"objectPath": object_path,
|
|
55
|
+
"access": "WRITE_ACCESS",
|
|
56
|
+
"propertyName": property_name,
|
|
57
|
+
"propertyValue": property_value
|
|
58
|
+
}
|
|
59
|
+
return _send_request(UNREAL_PROPERTY_URL, payload)
|
|
60
|
+
|
|
61
|
+
@mcp.tool()
|
|
62
|
+
def execute_console_command(command: str) -> Dict[str, Any]:
|
|
63
|
+
"""Execute an Unreal Engine console command."""
|
|
64
|
+
payload = {
|
|
65
|
+
"objectPath": "/Script/Engine.Default__SystemLibrary",
|
|
66
|
+
"functionName": "ExecuteConsoleCommand",
|
|
67
|
+
"parameters": {
|
|
68
|
+
"Command": command
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return _send_request(UNREAL_API_URL, payload)
|
|
9
72
|
|
|
10
73
|
if __name__ == "__main__":
|
|
11
74
|
mcp.run()
|
package/package.json
CHANGED
package/mcps/scaffold.sh
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
set -e
|
|
3
|
-
|
|
4
|
-
# Node: Unreal
|
|
5
|
-
cat << 'NODE_EOF' > /Users/timrennings/bdb-dev-optimized-antigravity-skills/mcps/bdb-unrealengine5-mcp/index.js
|
|
6
|
-
#!/usr/bin/env node
|
|
7
|
-
const readline = require('readline');
|
|
8
|
-
const rl = readline.createInterface({
|
|
9
|
-
input: process.stdin,
|
|
10
|
-
output: process.stdout,
|
|
11
|
-
terminal: false
|
|
12
|
-
});
|
|
13
|
-
rl.on('line', (line) => {
|
|
14
|
-
if (!line.trim()) return;
|
|
15
|
-
try {
|
|
16
|
-
const req = JSON.parse(line);
|
|
17
|
-
if (req.method === 'initialize') {
|
|
18
|
-
console.log(JSON.stringify({ jsonrpc: '2.0', id: req.id, result: { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'unreal-mcp', version: '1.0.0' } } }));
|
|
19
|
-
} else if (req.method === 'tools/list') {
|
|
20
|
-
console.log(JSON.stringify({ jsonrpc: '2.0', id: req.id, result: { tools: [] } }));
|
|
21
|
-
}
|
|
22
|
-
} catch (e) {}
|
|
23
|
-
});
|
|
24
|
-
NODE_EOF
|
|
25
|
-
|
|
26
|
-
cat << 'NODE_EOF' > /Users/timrennings/bdb-dev-optimized-antigravity-skills/mcps/bdb-unrealengine5-mcp/package.json
|
|
27
|
-
{ "name": "bdb-unrealengine5-mcp", "version": "1.0.0", "main": "index.js" }
|
|
28
|
-
NODE_EOF
|
|
29
|
-
|
|
30
|
-
# Node: Resolume
|
|
31
|
-
cp /Users/timrennings/bdb-dev-optimized-antigravity-skills/mcps/bdb-unrealengine5-mcp/index.js /Users/timrennings/bdb-dev-optimized-antigravity-skills/mcps/bdb-resolume-mcp/index.js
|
|
32
|
-
cat << 'NODE_EOF' > /Users/timrennings/bdb-dev-optimized-antigravity-skills/mcps/bdb-resolume-mcp/package.json
|
|
33
|
-
{ "name": "bdb-resolume-mcp", "version": "1.0.0", "main": "index.js" }
|
|
34
|
-
NODE_EOF
|
|
35
|
-
|
|
36
|
-
# Python function
|
|
37
|
-
gen_py() {
|
|
38
|
-
local name=$1
|
|
39
|
-
touch "/Users/timrennings/bdb-dev-optimized-antigravity-skills/mcps/${name}/${name}/__init__.py"
|
|
40
|
-
cat << PY_EOF > "/Users/timrennings/bdb-dev-optimized-antigravity-skills/mcps/${name}/${name}/__main__.py"
|
|
41
|
-
import sys, json
|
|
42
|
-
def main():
|
|
43
|
-
while True:
|
|
44
|
-
line = sys.stdin.readline()
|
|
45
|
-
if not line: break
|
|
46
|
-
line = line.strip()
|
|
47
|
-
if not line: continue
|
|
48
|
-
try:
|
|
49
|
-
req = json.loads(line)
|
|
50
|
-
if req.get("method") == "initialize":
|
|
51
|
-
res = {"jsonrpc": "2.0", "id": req.get("id"), "result": {"protocolVersion": "2024-11-05", "capabilities": {"tools": {}}, "serverInfo": {"name": "${name}", "version": "1.0.0"}}}
|
|
52
|
-
sys.stdout.write(json.dumps(res) + "\n")
|
|
53
|
-
sys.stdout.flush()
|
|
54
|
-
elif req.get("method") == "tools/list":
|
|
55
|
-
res = {"jsonrpc": "2.0", "id": req.get("id"), "result": {"tools": []}}
|
|
56
|
-
sys.stdout.write(json.dumps(res) + "\n")
|
|
57
|
-
sys.stdout.flush()
|
|
58
|
-
except Exception:
|
|
59
|
-
pass
|
|
60
|
-
if __name__ == "__main__":
|
|
61
|
-
main()
|
|
62
|
-
PY_EOF
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
gen_py "bdb_rhino_mcp"
|
|
66
|
-
gen_py "bdb_davinci_mcp"
|
|
67
|
-
gen_py "bdb_ma3_mcp"
|
|
68
|
-
|
|
69
|
-
chmod +x /Users/timrennings/bdb-dev-optimized-antigravity-skills/mcps/bdb-unrealengine5-mcp/index.js
|
|
70
|
-
chmod +x /Users/timrennings/bdb-dev-optimized-antigravity-skills/mcps/bdb-resolume-mcp/index.js
|