laive-mcp 0.1.2 → 0.1.4
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/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v0.1.4 - 2026-03-22
|
|
6
|
+
|
|
7
|
+
- Fixed MCP tool schema advertising so argument-bearing tools like `set_tempo`, `get_track_details`, `get_device_tree`, `create_clip`, and `set_parameter` now publish explicit JSON Schemas through `tools/list` instead of empty input objects, allowing Codex clients to send required parameters.
|
|
8
|
+
|
|
9
|
+
## v0.1.3 - 2026-03-22
|
|
10
|
+
|
|
11
|
+
- Fixed MCP `tools/call` responses to return proper `CallToolResult` envelopes with `content`, `structuredContent`, and `isError`, so Codex clients accept the responses instead of rejecting them as an unexpected type.
|
|
12
|
+
|
|
5
13
|
## v0.1.2 - 2026-03-22
|
|
6
14
|
|
|
7
15
|
- Fixed an MCP transport crash when the Live bridge socket is unreachable by preventing the bridge client from raising an unhandled `error` event during lazy connection attempts. Tool calls now return structured MCP errors instead of closing the server process.
|
package/package.json
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { McpServerError } from "./errors.js";
|
|
2
2
|
|
|
3
|
+
const EMPTY_OBJECT_SCHEMA = {
|
|
4
|
+
type: "object",
|
|
5
|
+
properties: {},
|
|
6
|
+
additionalProperties: false
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function createObjectSchema({ properties = {}, required = [] } = {}) {
|
|
10
|
+
return {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties,
|
|
13
|
+
required,
|
|
14
|
+
additionalProperties: false
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
3
18
|
function requireString(value, fieldName) {
|
|
4
19
|
if (typeof value !== "string" || value.length === 0) {
|
|
5
20
|
throw new McpServerError(
|
|
@@ -25,6 +40,7 @@ export function buildDefaultTools({ stateAdapter, bridgeAdapter, policyAdapter }
|
|
|
25
40
|
{
|
|
26
41
|
name: "get_project_summary",
|
|
27
42
|
description: "Return a compact summary of the current Live set state.",
|
|
43
|
+
inputSchema: EMPTY_OBJECT_SCHEMA,
|
|
28
44
|
async execute() {
|
|
29
45
|
const summary = await stateAdapter.getProjectSummary();
|
|
30
46
|
return {
|
|
@@ -41,6 +57,7 @@ export function buildDefaultTools({ stateAdapter, bridgeAdapter, policyAdapter }
|
|
|
41
57
|
{
|
|
42
58
|
name: "get_selected_context",
|
|
43
59
|
description: "Return the selected track, scene, clip, and device context.",
|
|
60
|
+
inputSchema: EMPTY_OBJECT_SCHEMA,
|
|
44
61
|
async execute() {
|
|
45
62
|
const context = await stateAdapter.getSelectedContext();
|
|
46
63
|
return {
|
|
@@ -59,6 +76,7 @@ export function buildDefaultTools({ stateAdapter, bridgeAdapter, policyAdapter }
|
|
|
59
76
|
{
|
|
60
77
|
name: "list_tracks",
|
|
61
78
|
description: "List tracks in compact form.",
|
|
79
|
+
inputSchema: EMPTY_OBJECT_SCHEMA,
|
|
62
80
|
async execute() {
|
|
63
81
|
const tracks = await stateAdapter.listTracks();
|
|
64
82
|
return {
|
|
@@ -75,6 +93,23 @@ export function buildDefaultTools({ stateAdapter, bridgeAdapter, policyAdapter }
|
|
|
75
93
|
{
|
|
76
94
|
name: "get_track_details",
|
|
77
95
|
description: "Return detailed state for a track identified by ID, name, or index.",
|
|
96
|
+
inputSchema: createObjectSchema({
|
|
97
|
+
properties: {
|
|
98
|
+
id: {
|
|
99
|
+
type: "string",
|
|
100
|
+
description: "Track identifier, for example `track:7`."
|
|
101
|
+
},
|
|
102
|
+
name: {
|
|
103
|
+
type: "string",
|
|
104
|
+
description: "Exact track name."
|
|
105
|
+
},
|
|
106
|
+
index: {
|
|
107
|
+
type: "integer",
|
|
108
|
+
minimum: 0,
|
|
109
|
+
description: "Zero-based visible-track index."
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}),
|
|
78
113
|
async execute(args) {
|
|
79
114
|
const target = args.id ?? args.name ?? args.index;
|
|
80
115
|
if (target === undefined) {
|
|
@@ -99,6 +134,15 @@ export function buildDefaultTools({ stateAdapter, bridgeAdapter, policyAdapter }
|
|
|
99
134
|
{
|
|
100
135
|
name: "get_device_tree",
|
|
101
136
|
description: "Return device state for a track.",
|
|
137
|
+
inputSchema: createObjectSchema({
|
|
138
|
+
properties: {
|
|
139
|
+
trackId: {
|
|
140
|
+
type: "string",
|
|
141
|
+
description: "Track identifier, for example `track:7`."
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
required: ["trackId"]
|
|
145
|
+
}),
|
|
102
146
|
async execute(args) {
|
|
103
147
|
const trackId = args.trackId ?? args.track ?? args.id;
|
|
104
148
|
requireString(trackId, "trackId");
|
|
@@ -117,6 +161,20 @@ export function buildDefaultTools({ stateAdapter, bridgeAdapter, policyAdapter }
|
|
|
117
161
|
{
|
|
118
162
|
name: "set_tempo",
|
|
119
163
|
description: "Update the current song tempo.",
|
|
164
|
+
inputSchema: createObjectSchema({
|
|
165
|
+
properties: {
|
|
166
|
+
tempo: {
|
|
167
|
+
type: "number",
|
|
168
|
+
exclusiveMinimum: 0,
|
|
169
|
+
description: "Target song tempo in BPM."
|
|
170
|
+
},
|
|
171
|
+
dryRun: {
|
|
172
|
+
type: "boolean",
|
|
173
|
+
description: "If true, preview the action without mutating Live."
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
required: ["tempo"]
|
|
177
|
+
}),
|
|
120
178
|
async execute(args) {
|
|
121
179
|
const nextTempo = Number(args.tempo);
|
|
122
180
|
if (!Number.isFinite(nextTempo) || nextTempo <= 0) {
|
|
@@ -139,6 +197,19 @@ export function buildDefaultTools({ stateAdapter, bridgeAdapter, policyAdapter }
|
|
|
139
197
|
{
|
|
140
198
|
name: "create_track",
|
|
141
199
|
description: "Create a new track.",
|
|
200
|
+
inputSchema: createObjectSchema({
|
|
201
|
+
properties: {
|
|
202
|
+
kind: {
|
|
203
|
+
type: "string",
|
|
204
|
+
enum: ["midi", "audio"],
|
|
205
|
+
description: "Track type to create."
|
|
206
|
+
},
|
|
207
|
+
dryRun: {
|
|
208
|
+
type: "boolean",
|
|
209
|
+
description: "If true, preview the action without mutating Live."
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}),
|
|
142
213
|
async execute(args) {
|
|
143
214
|
const kind = args.kind ?? "midi";
|
|
144
215
|
await policyAdapter.assertAllowed("create_track", args);
|
|
@@ -157,6 +228,33 @@ export function buildDefaultTools({ stateAdapter, bridgeAdapter, policyAdapter }
|
|
|
157
228
|
{
|
|
158
229
|
name: "create_clip",
|
|
159
230
|
description: "Create a MIDI clip on a target track and slot.",
|
|
231
|
+
inputSchema: createObjectSchema({
|
|
232
|
+
properties: {
|
|
233
|
+
trackId: {
|
|
234
|
+
type: "string",
|
|
235
|
+
description: "Track identifier, for example `track:7`."
|
|
236
|
+
},
|
|
237
|
+
slotIndex: {
|
|
238
|
+
type: "integer",
|
|
239
|
+
minimum: 0,
|
|
240
|
+
description: "Zero-based session slot index on the target track."
|
|
241
|
+
},
|
|
242
|
+
lengthBeats: {
|
|
243
|
+
type: "number",
|
|
244
|
+
exclusiveMinimum: 0,
|
|
245
|
+
description: "Clip length in beats. Defaults to 4."
|
|
246
|
+
},
|
|
247
|
+
name: {
|
|
248
|
+
type: "string",
|
|
249
|
+
description: "Optional clip name."
|
|
250
|
+
},
|
|
251
|
+
dryRun: {
|
|
252
|
+
type: "boolean",
|
|
253
|
+
description: "If true, preview the action without mutating Live."
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
required: ["trackId", "slotIndex"]
|
|
257
|
+
}),
|
|
160
258
|
async execute(args) {
|
|
161
259
|
requireString(args.trackId, "trackId");
|
|
162
260
|
if (!Number.isInteger(args.slotIndex) || args.slotIndex < 0) {
|
|
@@ -185,6 +283,31 @@ export function buildDefaultTools({ stateAdapter, bridgeAdapter, policyAdapter }
|
|
|
185
283
|
{
|
|
186
284
|
name: "set_parameter",
|
|
187
285
|
description: "Set a device parameter by track/device/parameter identifiers.",
|
|
286
|
+
inputSchema: createObjectSchema({
|
|
287
|
+
properties: {
|
|
288
|
+
trackId: {
|
|
289
|
+
type: "string",
|
|
290
|
+
description: "Track identifier containing the target device."
|
|
291
|
+
},
|
|
292
|
+
deviceId: {
|
|
293
|
+
type: "string",
|
|
294
|
+
description: "Device identifier containing the target parameter."
|
|
295
|
+
},
|
|
296
|
+
parameterId: {
|
|
297
|
+
type: "string",
|
|
298
|
+
description: "Parameter identifier to update."
|
|
299
|
+
},
|
|
300
|
+
value: {
|
|
301
|
+
type: "number",
|
|
302
|
+
description: "Target numeric parameter value."
|
|
303
|
+
},
|
|
304
|
+
dryRun: {
|
|
305
|
+
type: "boolean",
|
|
306
|
+
description: "If true, preview the action without mutating Live."
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
required: ["trackId", "deviceId", "parameterId", "value"]
|
|
310
|
+
}),
|
|
188
311
|
async execute(args) {
|
|
189
312
|
requireString(args.trackId, "trackId");
|
|
190
313
|
requireString(args.deviceId, "deviceId");
|
|
@@ -219,6 +342,14 @@ export function buildDefaultTools({ stateAdapter, bridgeAdapter, policyAdapter }
|
|
|
219
342
|
{
|
|
220
343
|
name: "refresh_state",
|
|
221
344
|
description: "Force a state refresh for a target scope.",
|
|
345
|
+
inputSchema: createObjectSchema({
|
|
346
|
+
properties: {
|
|
347
|
+
target: {
|
|
348
|
+
type: "string",
|
|
349
|
+
description: "Refresh scope, for example `project`, `song`, or `track:7`."
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}),
|
|
222
353
|
async execute(args) {
|
|
223
354
|
const target = args.target ?? "project";
|
|
224
355
|
const refreshed = await stateAdapter.refreshState(target);
|
|
@@ -236,6 +367,7 @@ export function buildDefaultTools({ stateAdapter, bridgeAdapter, policyAdapter }
|
|
|
236
367
|
{
|
|
237
368
|
name: "get_capabilities",
|
|
238
369
|
description: "Return bridge and server capabilities.",
|
|
370
|
+
inputSchema: EMPTY_OBJECT_SCHEMA,
|
|
239
371
|
async execute() {
|
|
240
372
|
const capabilities = await bridgeAdapter.getCapabilities();
|
|
241
373
|
return {
|
|
@@ -86,15 +86,23 @@ export class LaiveMcpServer {
|
|
|
86
86
|
|
|
87
87
|
if (message.method === "tools/call") {
|
|
88
88
|
const params = message.params ?? {};
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
try {
|
|
90
|
+
const result = await this.invokeTool(params.name, params.arguments ?? {}, {
|
|
91
|
+
requestId: message.id ?? null
|
|
92
|
+
});
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
return {
|
|
95
|
+
jsonrpc: "2.0",
|
|
96
|
+
id: message.id ?? null,
|
|
97
|
+
result: toToolResult(result)
|
|
98
|
+
};
|
|
99
|
+
} catch (error) {
|
|
100
|
+
return {
|
|
101
|
+
jsonrpc: "2.0",
|
|
102
|
+
id: message.id ?? null,
|
|
103
|
+
result: toToolErrorResult(error)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
98
106
|
}
|
|
99
107
|
|
|
100
108
|
throw new McpServerError("method_not_found", `Unsupported method: ${message.method}`);
|
|
@@ -113,6 +121,38 @@ export class LaiveMcpServer {
|
|
|
113
121
|
}
|
|
114
122
|
}
|
|
115
123
|
|
|
124
|
+
function toToolResult(result) {
|
|
125
|
+
return {
|
|
126
|
+
content: [
|
|
127
|
+
{
|
|
128
|
+
type: "text",
|
|
129
|
+
text:
|
|
130
|
+
typeof result?.summary === "string" && result.summary.length > 0
|
|
131
|
+
? result.summary
|
|
132
|
+
: JSON.stringify(result, null, 2)
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
structuredContent: result,
|
|
136
|
+
isError: false
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function toToolErrorResult(error) {
|
|
141
|
+
const shape = toErrorShape(error);
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: shape.message
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
structuredContent: {
|
|
150
|
+
error: shape
|
|
151
|
+
},
|
|
152
|
+
isError: true
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
116
156
|
function createUnsupportedAdapter(name) {
|
|
117
157
|
return new Proxy(
|
|
118
158
|
{},
|