@hybridlabor-api/bdb-antigravity-skills 1.0.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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/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
|