@mixofreality/live-mcp 1.0.0 → 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/dist/index.js CHANGED
@@ -1,32 +1,350 @@
1
1
  #!/usr/bin/env node
2
- // MCP server entry point
3
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
- import { BridgeClient } from './bridge-client.js';
6
- import { registerAllTools } from './tools/index.js';
7
- const BRIDGE_HOST = process.env['LIVE_MCP_BRIDGE_HOST'] ?? '127.0.0.1';
8
- const BRIDGE_PORT = parseInt(process.env['LIVE_MCP_BRIDGE_PORT'] ?? '19740', 10);
9
- const server = new McpServer({
10
- name: 'live-mcp',
11
- version: '1.0.0',
12
- }, {
2
+
3
+ // src/index.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+
7
+ // src/bridge-client.ts
8
+ import * as net from "net";
9
+ import { randomUUID } from "crypto";
10
+
11
+ // ../../../shared/max4live-nodescript-ts/dist/bridge-protocol.js
12
+ function isBridgeNotification(msg) {
13
+ return "notification" in msg;
14
+ }
15
+
16
+ // src/bridge-client.ts
17
+ var BridgeClient = class {
18
+ constructor(host, port) {
19
+ this.host = host;
20
+ this.port = port;
21
+ }
22
+ socket = null;
23
+ buffer = "";
24
+ pending = /* @__PURE__ */ new Map();
25
+ notificationListeners = [];
26
+ async connect() {
27
+ return new Promise((resolve, reject) => {
28
+ const socket = net.createConnection({ host: this.host, port: this.port });
29
+ socket.once("connect", () => {
30
+ this.socket = socket;
31
+ resolve();
32
+ });
33
+ socket.once("error", reject);
34
+ socket.on("data", (data) => this.handleData(data.toString()));
35
+ socket.on("close", () => {
36
+ this.socket = null;
37
+ for (const [, { reject: rej }] of this.pending) {
38
+ rej(new Error("Bridge connection closed"));
39
+ }
40
+ this.pending.clear();
41
+ });
42
+ });
43
+ }
44
+ async disconnect() {
45
+ if (this.socket) {
46
+ return new Promise((resolve) => {
47
+ this.socket.end(() => resolve());
48
+ });
49
+ }
50
+ }
51
+ isConnected() {
52
+ return this.socket !== null && !this.socket.destroyed;
53
+ }
54
+ async request(method, params) {
55
+ if (!this.isConnected()) {
56
+ throw new Error("Not connected to bridge");
57
+ }
58
+ const id = randomUUID();
59
+ const request = JSON.stringify({ id, method, params });
60
+ return new Promise((resolve, reject) => {
61
+ this.pending.set(id, { resolve, reject });
62
+ this.socket.write(request + "\n");
63
+ });
64
+ }
65
+ onNotification(listener) {
66
+ this.notificationListeners.push(listener);
67
+ }
68
+ handleData(data) {
69
+ this.buffer += data;
70
+ const lines = this.buffer.split("\n");
71
+ this.buffer = lines.pop();
72
+ for (const line of lines) {
73
+ if (!line.trim()) continue;
74
+ let msg;
75
+ try {
76
+ msg = JSON.parse(line);
77
+ } catch {
78
+ continue;
79
+ }
80
+ if (isBridgeNotification(msg)) {
81
+ for (const listener of this.notificationListeners) {
82
+ listener(msg);
83
+ }
84
+ } else {
85
+ const response = msg;
86
+ const pendingReq = this.pending.get(response.id);
87
+ if (pendingReq) {
88
+ this.pending.delete(response.id);
89
+ if (response.error) {
90
+ pendingReq.reject(new Error(response.error.message));
91
+ } else if (response.result) {
92
+ pendingReq.resolve(response.result);
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+ };
99
+
100
+ // src/tools/get-property.ts
101
+ import { z } from "zod";
102
+ function registerGetProperty(server2, bridge2) {
103
+ server2.registerTool(
104
+ "get_property",
105
+ {
106
+ title: "Get LOM Property",
107
+ description: 'Read a property from a Live Object Model object. Use LOM paths like "live_set", "live_set tracks 0", "live_set tracks 0 devices 1 parameters 2".',
108
+ inputSchema: {
109
+ path: z.string().describe('LOM path (e.g. "live_set tracks 0")'),
110
+ property: z.string().describe('Property name (e.g. "tempo", "name")')
111
+ }
112
+ },
113
+ async ({ path, property }) => {
114
+ try {
115
+ const result = await bridge2.request("get_property", {
116
+ path,
117
+ property
118
+ });
119
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
120
+ } catch (error) {
121
+ const message = error instanceof Error ? error.message : String(error);
122
+ return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
123
+ }
124
+ }
125
+ );
126
+ }
127
+
128
+ // src/tools/set-property.ts
129
+ import { z as z2 } from "zod";
130
+ function registerSetProperty(server2, bridge2) {
131
+ server2.registerTool(
132
+ "set_property",
133
+ {
134
+ title: "Set LOM Property",
135
+ description: 'Set a property on a Live Object Model object. The value should be a JSON string that will be parsed (e.g. "120.0", "\\"hello\\"", "true").',
136
+ inputSchema: {
137
+ path: z2.string().describe('LOM path (e.g. "live_set tracks 0")'),
138
+ property: z2.string().describe('Property name (e.g. "tempo", "name")'),
139
+ value: z2.string().describe('Value as JSON string (e.g. "120.0", "\\"hello\\"", "true")')
140
+ }
141
+ },
142
+ async ({ path, property, value }) => {
143
+ try {
144
+ const parsed = JSON.parse(value);
145
+ const result = await bridge2.request("set_property", {
146
+ path,
147
+ property,
148
+ value: parsed
149
+ });
150
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
151
+ } catch (error) {
152
+ const message = error instanceof Error ? error.message : String(error);
153
+ return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
154
+ }
155
+ }
156
+ );
157
+ }
158
+
159
+ // src/tools/call-function.ts
160
+ import { z as z3 } from "zod";
161
+ function registerCallFunction(server2, bridge2) {
162
+ server2.registerTool(
163
+ "call_function",
164
+ {
165
+ title: "Call LOM Function",
166
+ description: 'Call a function on a Live Object Model object. Args is an optional JSON array string (e.g. "[1, 2]").',
167
+ inputSchema: {
168
+ path: z3.string().describe('LOM path (e.g. "live_set tracks 0")'),
169
+ function: z3.string().describe('Function name (e.g. "fire", "stop")'),
170
+ args: z3.string().optional().describe('Arguments as JSON array string (e.g. "[1, \\"hello\\"]")')
171
+ }
172
+ },
173
+ async ({ path, function: fn, args }) => {
174
+ try {
175
+ const parsedArgs = args ? JSON.parse(args) : void 0;
176
+ const result = await bridge2.request("call_function", {
177
+ path,
178
+ function: fn,
179
+ args: parsedArgs
180
+ });
181
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
182
+ } catch (error) {
183
+ const message = error instanceof Error ? error.message : String(error);
184
+ return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
185
+ }
186
+ }
187
+ );
188
+ }
189
+
190
+ // src/tools/get-children.ts
191
+ import { z as z4 } from "zod";
192
+ function registerGetChildren(server2, bridge2) {
193
+ server2.registerTool(
194
+ "get_children",
195
+ {
196
+ title: "Get LOM Children",
197
+ description: "List the children of a Live Object Model object. Returns child paths for navigation.",
198
+ inputSchema: {
199
+ path: z4.string().describe('LOM path (e.g. "live_set", "live_set tracks 0")')
200
+ }
201
+ },
202
+ async ({ path }) => {
203
+ try {
204
+ const result = await bridge2.request("get_children", {
205
+ path
206
+ });
207
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
208
+ } catch (error) {
209
+ const message = error instanceof Error ? error.message : String(error);
210
+ return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
211
+ }
212
+ }
213
+ );
214
+ }
215
+
216
+ // src/tools/observe.ts
217
+ import { z as z5 } from "zod";
218
+ function registerObserve(server2, bridge2) {
219
+ server2.registerTool(
220
+ "observe",
221
+ {
222
+ title: "Observe LOM Property",
223
+ description: "Subscribe to changes on a Live Object Model property. Returns a subscriptionId for later unobserve.",
224
+ inputSchema: {
225
+ path: z5.string().describe('LOM path (e.g. "live_set tracks 0")'),
226
+ property: z5.string().describe('Property name to observe (e.g. "tempo", "name")')
227
+ }
228
+ },
229
+ async ({ path, property }) => {
230
+ try {
231
+ const result = await bridge2.request("observe", {
232
+ path,
233
+ property
234
+ });
235
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
236
+ } catch (error) {
237
+ const message = error instanceof Error ? error.message : String(error);
238
+ return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
239
+ }
240
+ }
241
+ );
242
+ }
243
+
244
+ // src/tools/unobserve.ts
245
+ import { z as z6 } from "zod";
246
+ function registerUnobserve(server2, bridge2) {
247
+ server2.registerTool(
248
+ "unobserve",
249
+ {
250
+ title: "Unobserve LOM Property",
251
+ description: "Unsubscribe from a previously observed Live Object Model property using its subscriptionId.",
252
+ inputSchema: {
253
+ subscriptionId: z6.string().describe("Subscription ID returned from observe")
254
+ }
255
+ },
256
+ async ({ subscriptionId }) => {
257
+ try {
258
+ const result = await bridge2.request("unobserve", { subscriptionId });
259
+ return { content: [{ type: "text", text: JSON.stringify(result) }] };
260
+ } catch (error) {
261
+ const message = error instanceof Error ? error.message : String(error);
262
+ return { content: [{ type: "text", text: `Error: ${message}` }], isError: true };
263
+ }
264
+ }
265
+ );
266
+ }
267
+
268
+ // src/tools/index.ts
269
+ function registerAllTools(server2, bridge2) {
270
+ registerGetProperty(server2, bridge2);
271
+ registerSetProperty(server2, bridge2);
272
+ registerCallFunction(server2, bridge2);
273
+ registerGetChildren(server2, bridge2);
274
+ registerObserve(server2, bridge2);
275
+ registerUnobserve(server2, bridge2);
276
+ }
277
+
278
+ // src/index.ts
279
+ var BRIDGE_HOST = process.env["LIVE_MCP_BRIDGE_HOST"] ?? "127.0.0.1";
280
+ var BRIDGE_PORT = parseInt(process.env["LIVE_MCP_BRIDGE_PORT"] ?? "19740", 10);
281
+ var server = new McpServer(
282
+ {
283
+ name: "live-mcp",
284
+ version: "1.0.0"
285
+ },
286
+ {
13
287
  capabilities: { logging: {} },
14
- });
15
- const bridge = new BridgeClient(BRIDGE_HOST, BRIDGE_PORT);
288
+ instructions: `Live MCP controls Ableton Live through the Live Object Model (LOM).
289
+ Full API reference: https://docs.cycling74.com/apiref/lom/
290
+ User guide: https://docs.cycling74.com/userguide/
291
+
292
+ ## Path Syntax
293
+
294
+ Paths use space-separated navigation from root objects:
295
+ - \`live_set\` \u2014 the Song
296
+ - \`live_app\` \u2014 the Application
297
+ - \`live_set tracks 0\` \u2014 first track
298
+ - \`live_set tracks 0 clip_slots 2 clip\` \u2014 clip in slot 2 of track 0
299
+ - \`live_set tracks 0 devices 1 parameters 3\` \u2014 parameter 3 of device 1
300
+ - \`live_set master_track\` \u2014 master track
301
+ - \`live_set return_tracks 0\` \u2014 first return track
302
+ - \`live_set scenes 0\` \u2014 first scene
303
+
304
+ ## Object Hierarchy
305
+
306
+ Song (live_set)
307
+ \u251C\u2500\u2500 tracks[] \u2192 Track
308
+ \u2502 \u251C\u2500\u2500 clip_slots[] \u2192 ClipSlot \u2192 clip \u2192 Clip
309
+ \u2502 \u251C\u2500\u2500 devices[] \u2192 Device (or RackDevice, SimplerDevice, PluginDevice, MaxDevice)
310
+ \u2502 \u2502 \u251C\u2500\u2500 parameters[] \u2192 DeviceParameter
311
+ \u2502 \u2502 \u251C\u2500\u2500 chains[] \u2192 Chain \u2192 devices[], mixer_device (racks only)
312
+ \u2502 \u2502 \u251C\u2500\u2500 drum_pads[] \u2192 DrumPad \u2192 chains[] \u2192 DrumChain (drum racks only)
313
+ \u2502 \u2502 \u2514\u2500\u2500 return_chains[] \u2192 Chain (racks only)
314
+ \u2502 \u2514\u2500\u2500 mixer_device \u2192 MixerDevice (volume, panning, sends)
315
+ \u251C\u2500\u2500 return_tracks[] \u2192 Track
316
+ \u251C\u2500\u2500 master_track \u2192 Track
317
+ \u251C\u2500\u2500 scenes[] \u2192 Scene
318
+ \u251C\u2500\u2500 cue_points[] \u2192 CuePoint
319
+ \u2514\u2500\u2500 groove_pool \u2192 GroovePool \u2192 grooves[]
320
+ Application (live_app)
321
+ \u251C\u2500\u2500 view \u2192 Application.View
322
+ \u2514\u2500\u2500 control_surfaces[]
323
+
324
+ ## Workflow
325
+
326
+ 1. Use get_children to discover what's available at any path
327
+ 2. Use get_property / set_property to read/write values
328
+ 3. Use call_function to invoke actions (fire, stop, create, delete, etc.)
329
+ 4. Consult https://docs.cycling74.com/apiref/lom/<classname>/ for property and function details on specific classes`
330
+ }
331
+ );
332
+ var bridge = new BridgeClient(BRIDGE_HOST, BRIDGE_PORT);
16
333
  registerAllTools(server, bridge);
17
334
  async function main() {
18
- try {
19
- await bridge.connect();
20
- console.error('Connected to M4L bridge');
21
- }
22
- catch {
23
- console.error(`Warning: Could not connect to M4L bridge at ${BRIDGE_HOST}:${BRIDGE_PORT}. Tools will fail until bridge is available.`);
24
- }
25
- const transport = new StdioServerTransport();
26
- await server.connect(transport);
27
- console.error('Live MCP Server running on stdio');
335
+ try {
336
+ await bridge.connect();
337
+ console.error("Connected to M4L bridge");
338
+ } catch {
339
+ console.error(
340
+ `Warning: Could not connect to M4L bridge at ${BRIDGE_HOST}:${BRIDGE_PORT}. Tools will fail until bridge is available.`
341
+ );
342
+ }
343
+ const transport = new StdioServerTransport();
344
+ await server.connect(transport);
345
+ console.error("Live MCP Server running on stdio");
28
346
  }
29
347
  main().catch((error) => {
30
- console.error('Fatal error:', error);
31
- process.exit(1);
348
+ console.error("Fatal error:", error);
349
+ process.exit(1);
32
350
  });
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@mixofreality/live-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {
7
7
  "live-mcp": "./dist/index.js"
8
8
  },
9
- "files": ["dist"],
9
+ "files": [
10
+ "dist"
11
+ ],
10
12
  "scripts": {
11
- "build": "tsc",
13
+ "build": "tsup",
12
14
  "dev": "node --watch dist/index.js",
13
15
  "typecheck": "tsc --noEmit",
14
16
  "test": "vitest run",
@@ -16,7 +18,13 @@
16
18
  "check": "npm run typecheck && npm run test",
17
19
  "prepublishOnly": "npm run check && npm run build"
18
20
  },
19
- "keywords": ["mcp", "ableton", "live", "max4live", "lom"],
21
+ "keywords": [
22
+ "mcp",
23
+ "ableton",
24
+ "live",
25
+ "max4live",
26
+ "lom"
27
+ ],
20
28
  "license": "MIT",
21
29
  "description": "MCP server for controlling Ableton Live via the Live Object Model",
22
30
  "dependencies": {
@@ -24,9 +32,10 @@
24
32
  "zod": "^3.25.0"
25
33
  },
26
34
  "devDependencies": {
27
- "typescript": "~5.9.3",
28
- "vitest": "^4.0.16",
35
+ "@mixofreality/max4live-nodescript-ts": "file:../../../shared/max4live-nodescript-ts",
29
36
  "@types/node": "^22.0.0",
30
- "@mixofreality/max4live-nodescript-ts": "file:../../../shared/max4live-nodescript-ts"
37
+ "tsup": "^8.5.1",
38
+ "typescript": "~5.9.3",
39
+ "vitest": "^4.0.16"
31
40
  }
32
41
  }
@@ -1 +0,0 @@
1
- export {};
@@ -1,110 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import * as net from 'node:net';
3
- import { BridgeClient } from '../bridge-client.js';
4
- describe('BridgeClient', () => {
5
- let mockServer;
6
- let serverPort;
7
- beforeEach(async () => {
8
- mockServer = net.createServer();
9
- await new Promise((resolve) => {
10
- mockServer.listen(0, '127.0.0.1', () => resolve());
11
- });
12
- const addr = mockServer.address();
13
- serverPort = addr.port;
14
- });
15
- afterEach(async () => {
16
- await new Promise((resolve) => {
17
- mockServer.close(() => resolve());
18
- });
19
- });
20
- it('should connect to the bridge server', async () => {
21
- const serverGotConnection = new Promise((resolve) => {
22
- mockServer.on('connection', () => resolve());
23
- });
24
- const client = new BridgeClient('127.0.0.1', serverPort);
25
- await client.connect();
26
- await serverGotConnection;
27
- expect(client.isConnected()).toBe(true);
28
- await client.disconnect();
29
- });
30
- it('should send a request and receive a response', async () => {
31
- mockServer.on('connection', (socket) => {
32
- let buffer = '';
33
- socket.on('data', (data) => {
34
- buffer += data.toString();
35
- const lines = buffer.split('\n');
36
- buffer = lines.pop();
37
- for (const line of lines) {
38
- if (line.trim()) {
39
- const req = JSON.parse(line);
40
- const response = JSON.stringify({ id: req.id, result: { value: 120 } });
41
- socket.write(response + '\n');
42
- }
43
- }
44
- });
45
- });
46
- const client = new BridgeClient('127.0.0.1', serverPort);
47
- await client.connect();
48
- const result = await client.request('get_property', {
49
- path: 'live_set',
50
- property: 'tempo',
51
- });
52
- expect(result).toEqual({ value: 120 });
53
- await client.disconnect();
54
- });
55
- it('should handle bridge error responses', async () => {
56
- mockServer.on('connection', (socket) => {
57
- let buffer = '';
58
- socket.on('data', (data) => {
59
- buffer += data.toString();
60
- const lines = buffer.split('\n');
61
- buffer = lines.pop();
62
- for (const line of lines) {
63
- if (line.trim()) {
64
- const req = JSON.parse(line);
65
- const response = JSON.stringify({
66
- id: req.id,
67
- error: { code: -1, message: 'Object not found' },
68
- });
69
- socket.write(response + '\n');
70
- }
71
- }
72
- });
73
- });
74
- const client = new BridgeClient('127.0.0.1', serverPort);
75
- await client.connect();
76
- await expect(client.request('get_property', { path: 'live_set tracks 99', property: 'name' })).rejects.toThrow('Object not found');
77
- await client.disconnect();
78
- });
79
- it('should emit notifications for property changes', async () => {
80
- mockServer.on('connection', (socket) => {
81
- setTimeout(() => {
82
- const notification = JSON.stringify({
83
- notification: 'property_changed',
84
- subscriptionId: 'sub-1',
85
- path: 'live_set',
86
- property: 'tempo',
87
- value: 140,
88
- });
89
- socket.write(notification + '\n');
90
- }, 50);
91
- });
92
- const client = new BridgeClient('127.0.0.1', serverPort);
93
- await client.connect();
94
- const notification = await new Promise((resolve) => {
95
- client.onNotification((n) => resolve(n));
96
- });
97
- expect(notification).toEqual({
98
- notification: 'property_changed',
99
- subscriptionId: 'sub-1',
100
- path: 'live_set',
101
- property: 'tempo',
102
- value: 140,
103
- });
104
- await client.disconnect();
105
- });
106
- it('should report disconnected when not connected', () => {
107
- const client = new BridgeClient('127.0.0.1', 0);
108
- expect(client.isConnected()).toBe(false);
109
- });
110
- });
@@ -1,16 +0,0 @@
1
- import type { BridgeMethod, BridgeRequestParams, BridgeNotification, BridgeResult } from '@mixofreality/max4live-nodescript-ts';
2
- export declare class BridgeClient {
3
- private readonly host;
4
- private readonly port;
5
- private socket;
6
- private buffer;
7
- private pending;
8
- private notificationListeners;
9
- constructor(host: string, port: number);
10
- connect(): Promise<void>;
11
- disconnect(): Promise<void>;
12
- isConnected(): boolean;
13
- request(method: BridgeMethod, params: BridgeRequestParams): Promise<BridgeResult>;
14
- onNotification(listener: (notification: BridgeNotification) => void): void;
15
- private handleData;
16
- }
@@ -1,91 +0,0 @@
1
- import * as net from 'node:net';
2
- import { randomUUID } from 'node:crypto';
3
- import { isBridgeNotification } from '@mixofreality/max4live-nodescript-ts';
4
- export class BridgeClient {
5
- host;
6
- port;
7
- socket = null;
8
- buffer = '';
9
- pending = new Map();
10
- notificationListeners = [];
11
- constructor(host, port) {
12
- this.host = host;
13
- this.port = port;
14
- }
15
- async connect() {
16
- return new Promise((resolve, reject) => {
17
- const socket = net.createConnection({ host: this.host, port: this.port });
18
- socket.once('connect', () => {
19
- this.socket = socket;
20
- resolve();
21
- });
22
- socket.once('error', reject);
23
- socket.on('data', (data) => this.handleData(data.toString()));
24
- socket.on('close', () => {
25
- this.socket = null;
26
- for (const [, { reject: rej }] of this.pending) {
27
- rej(new Error('Bridge connection closed'));
28
- }
29
- this.pending.clear();
30
- });
31
- });
32
- }
33
- async disconnect() {
34
- if (this.socket) {
35
- return new Promise((resolve) => {
36
- this.socket.end(() => resolve());
37
- });
38
- }
39
- }
40
- isConnected() {
41
- return this.socket !== null && !this.socket.destroyed;
42
- }
43
- async request(method, params) {
44
- if (!this.isConnected()) {
45
- throw new Error('Not connected to bridge');
46
- }
47
- const id = randomUUID();
48
- const request = JSON.stringify({ id, method, params });
49
- return new Promise((resolve, reject) => {
50
- this.pending.set(id, { resolve, reject });
51
- this.socket.write(request + '\n');
52
- });
53
- }
54
- onNotification(listener) {
55
- this.notificationListeners.push(listener);
56
- }
57
- handleData(data) {
58
- this.buffer += data;
59
- const lines = this.buffer.split('\n');
60
- this.buffer = lines.pop();
61
- for (const line of lines) {
62
- if (!line.trim())
63
- continue;
64
- let msg;
65
- try {
66
- msg = JSON.parse(line);
67
- }
68
- catch {
69
- continue;
70
- }
71
- if (isBridgeNotification(msg)) {
72
- for (const listener of this.notificationListeners) {
73
- listener(msg);
74
- }
75
- }
76
- else {
77
- const response = msg;
78
- const pendingReq = this.pending.get(response.id);
79
- if (pendingReq) {
80
- this.pending.delete(response.id);
81
- if (response.error) {
82
- pendingReq.reject(new Error(response.error.message));
83
- }
84
- else if (response.result) {
85
- pendingReq.resolve(response.result);
86
- }
87
- }
88
- }
89
- }
90
- }
91
- }
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
@@ -1,3 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { BridgeClient } from '../bridge-client.js';
3
- export declare function registerCallFunction(server: McpServer, bridge: BridgeClient): void;
@@ -1,31 +0,0 @@
1
- import { z } from 'zod';
2
- export function registerCallFunction(server, bridge) {
3
- server.registerTool('call_function', {
4
- title: 'Call LOM Function',
5
- description: 'Call a function on a Live Object Model object. Args is an optional JSON array string (e.g. "[1, 2]").',
6
- inputSchema: {
7
- path: z.string().describe('LOM path (e.g. "live_set tracks 0")'),
8
- function: z.string().describe('Function name (e.g. "fire", "stop")'),
9
- args: z
10
- .string()
11
- .optional()
12
- .describe('Arguments as JSON array string (e.g. "[1, \\"hello\\"]")'),
13
- },
14
- }, async ({ path, function: fn, args }) => {
15
- try {
16
- const parsedArgs = args
17
- ? JSON.parse(args)
18
- : undefined;
19
- const result = await bridge.request('call_function', {
20
- path: path,
21
- function: fn,
22
- args: parsedArgs,
23
- });
24
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
25
- }
26
- catch (error) {
27
- const message = error instanceof Error ? error.message : String(error);
28
- return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
29
- }
30
- });
31
- }
@@ -1,3 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { BridgeClient } from '../bridge-client.js';
3
- export declare function registerGetChildren(server: McpServer, bridge: BridgeClient): void;
@@ -1,21 +0,0 @@
1
- import { z } from 'zod';
2
- export function registerGetChildren(server, bridge) {
3
- server.registerTool('get_children', {
4
- title: 'Get LOM Children',
5
- description: 'List the children of a Live Object Model object. Returns child paths for navigation.',
6
- inputSchema: {
7
- path: z.string().describe('LOM path (e.g. "live_set", "live_set tracks 0")'),
8
- },
9
- }, async ({ path }) => {
10
- try {
11
- const result = await bridge.request('get_children', {
12
- path: path,
13
- });
14
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
15
- }
16
- catch (error) {
17
- const message = error instanceof Error ? error.message : String(error);
18
- return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
19
- }
20
- });
21
- }
@@ -1,3 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { BridgeClient } from '../bridge-client.js';
3
- export declare function registerGetProperty(server: McpServer, bridge: BridgeClient): void;
@@ -1,23 +0,0 @@
1
- import { z } from 'zod';
2
- export function registerGetProperty(server, bridge) {
3
- server.registerTool('get_property', {
4
- title: 'Get LOM Property',
5
- description: 'Read a property from a Live Object Model object. Use LOM paths like "live_set", "live_set tracks 0", "live_set tracks 0 devices 1 parameters 2".',
6
- inputSchema: {
7
- path: z.string().describe('LOM path (e.g. "live_set tracks 0")'),
8
- property: z.string().describe('Property name (e.g. "tempo", "name")'),
9
- },
10
- }, async ({ path, property }) => {
11
- try {
12
- const result = await bridge.request('get_property', {
13
- path: path,
14
- property,
15
- });
16
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
17
- }
18
- catch (error) {
19
- const message = error instanceof Error ? error.message : String(error);
20
- return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
21
- }
22
- });
23
- }
@@ -1,3 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { BridgeClient } from '../bridge-client.js';
3
- export declare function registerAllTools(server: McpServer, bridge: BridgeClient): void;
@@ -1,14 +0,0 @@
1
- import { registerGetProperty } from './get-property.js';
2
- import { registerSetProperty } from './set-property.js';
3
- import { registerCallFunction } from './call-function.js';
4
- import { registerGetChildren } from './get-children.js';
5
- import { registerObserve } from './observe.js';
6
- import { registerUnobserve } from './unobserve.js';
7
- export function registerAllTools(server, bridge) {
8
- registerGetProperty(server, bridge);
9
- registerSetProperty(server, bridge);
10
- registerCallFunction(server, bridge);
11
- registerGetChildren(server, bridge);
12
- registerObserve(server, bridge);
13
- registerUnobserve(server, bridge);
14
- }
@@ -1,3 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { BridgeClient } from '../bridge-client.js';
3
- export declare function registerObserve(server: McpServer, bridge: BridgeClient): void;
@@ -1,23 +0,0 @@
1
- import { z } from 'zod';
2
- export function registerObserve(server, bridge) {
3
- server.registerTool('observe', {
4
- title: 'Observe LOM Property',
5
- description: 'Subscribe to changes on a Live Object Model property. Returns a subscriptionId for later unobserve.',
6
- inputSchema: {
7
- path: z.string().describe('LOM path (e.g. "live_set tracks 0")'),
8
- property: z.string().describe('Property name to observe (e.g. "tempo", "name")'),
9
- },
10
- }, async ({ path, property }) => {
11
- try {
12
- const result = await bridge.request('observe', {
13
- path: path,
14
- property,
15
- });
16
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
17
- }
18
- catch (error) {
19
- const message = error instanceof Error ? error.message : String(error);
20
- return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
21
- }
22
- });
23
- }
@@ -1,3 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { BridgeClient } from '../bridge-client.js';
3
- export declare function registerSetProperty(server: McpServer, bridge: BridgeClient): void;
@@ -1,26 +0,0 @@
1
- import { z } from 'zod';
2
- export function registerSetProperty(server, bridge) {
3
- server.registerTool('set_property', {
4
- title: 'Set LOM Property',
5
- description: 'Set a property on a Live Object Model object. The value should be a JSON string that will be parsed (e.g. "120.0", "\\"hello\\"", "true").',
6
- inputSchema: {
7
- path: z.string().describe('LOM path (e.g. "live_set tracks 0")'),
8
- property: z.string().describe('Property name (e.g. "tempo", "name")'),
9
- value: z.string().describe('Value as JSON string (e.g. "120.0", "\\"hello\\"", "true")'),
10
- },
11
- }, async ({ path, property, value }) => {
12
- try {
13
- const parsed = JSON.parse(value);
14
- const result = await bridge.request('set_property', {
15
- path: path,
16
- property,
17
- value: parsed,
18
- });
19
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
20
- }
21
- catch (error) {
22
- const message = error instanceof Error ? error.message : String(error);
23
- return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
24
- }
25
- });
26
- }
@@ -1,3 +0,0 @@
1
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { BridgeClient } from '../bridge-client.js';
3
- export declare function registerUnobserve(server: McpServer, bridge: BridgeClient): void;
@@ -1,19 +0,0 @@
1
- import { z } from 'zod';
2
- export function registerUnobserve(server, bridge) {
3
- server.registerTool('unobserve', {
4
- title: 'Unobserve LOM Property',
5
- description: 'Unsubscribe from a previously observed Live Object Model property using its subscriptionId.',
6
- inputSchema: {
7
- subscriptionId: z.string().describe('Subscription ID returned from observe'),
8
- },
9
- }, async ({ subscriptionId }) => {
10
- try {
11
- const result = await bridge.request('unobserve', { subscriptionId });
12
- return { content: [{ type: 'text', text: JSON.stringify(result) }] };
13
- }
14
- catch (error) {
15
- const message = error instanceof Error ? error.message : String(error);
16
- return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
17
- }
18
- });
19
- }