@openfairygui/mcp 0.3.0 → 0.3.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/dist/index.cjs +1 -1
- package/dist/index.d.cts +519 -106
- package/dist/index.d.mts +520 -107
- package/dist/index.mjs +1 -1
- package/dist/{stdio-48jCJzS3.mjs → stdio-HOHWH0x4.mjs} +460 -122
- package/dist/{stdio-9ewjcCag.cjs → stdio-N6Yxacus.cjs} +482 -121
- package/dist/stdio.cjs +1 -1
- package/dist/stdio.mjs +1 -1
- package/package.json +7 -4
- package/src/server.ts +5 -1
- package/src/stdio.ts +6 -1
- package/src/tool-definitions.ts +105 -10
- package/src/tool-handler.ts +52 -10
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as OPENFAIRYGUI_BACKEND_TOOL_NAMES, c as OPENFAIRYGUI_BACKEND_CAPABILITIES_RESOURCE_URI, d as OPENFAIRYGUI_BACKEND_PROMPT_NAMES, i as OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS, l as OPENFAIRYGUI_BACKEND_RESOURCE_TEMPLATES, n as createOpenFairyGuiMcpServer, o as OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA, r as callOpenFairyGuiBackendTool, s as OPENFAIRYGUI_BACKEND_TOOL_PREFIX, t as connectOpenFairyGuiMcpStdio, u as OPENFAIRYGUI_BACKEND_PROMPT_DEFINITIONS } from "./stdio-HOHWH0x4.mjs";
|
|
2
2
|
export { OPENFAIRYGUI_BACKEND_CAPABILITIES_RESOURCE_URI, OPENFAIRYGUI_BACKEND_PROMPT_DEFINITIONS, OPENFAIRYGUI_BACKEND_PROMPT_NAMES, OPENFAIRYGUI_BACKEND_RESOURCE_TEMPLATES, OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS, OPENFAIRYGUI_BACKEND_TOOL_NAMES, OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA, OPENFAIRYGUI_BACKEND_TOOL_PREFIX, callOpenFairyGuiBackendTool, connectOpenFairyGuiMcpStdio, createOpenFairyGuiMcpServer };
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { createNodeBackendRuntime } from "@openfairygui/backend/node";
|
|
4
|
+
import { BACKEND_CAPABILITY_SCHEMA_VERSION, BACKEND_CONTRACT_VERSION } from "@openfairygui/backend";
|
|
4
5
|
import { z } from "zod";
|
|
5
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import path from "node:path";
|
|
6
8
|
import { pathToFileURL } from "node:url";
|
|
7
9
|
//#region src/prompt-definitions.ts
|
|
8
10
|
const OPENFAIRYGUI_BACKEND_PROMPT_NAMES = [
|
|
@@ -144,114 +146,6 @@ function registerOpenFairyGuiBackendResources(server, runtime) {
|
|
|
144
146
|
})));
|
|
145
147
|
}
|
|
146
148
|
//#endregion
|
|
147
|
-
//#region src/tool-handler.ts
|
|
148
|
-
function jsonResult(payload, isError = false) {
|
|
149
|
-
return {
|
|
150
|
-
content: [{
|
|
151
|
-
type: "text",
|
|
152
|
-
text: JSON.stringify(payload, null, 2)
|
|
153
|
-
}],
|
|
154
|
-
structuredContent: { backendResult: payload },
|
|
155
|
-
isError
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
function isBackendFailure(value) {
|
|
159
|
-
return typeof value === "object" && value !== null && "ok" in value && value.ok === false;
|
|
160
|
-
}
|
|
161
|
-
async function callOpenFairyGuiBackendTool(runtime, name, input) {
|
|
162
|
-
let result;
|
|
163
|
-
switch (name) {
|
|
164
|
-
case "openfairygui_backend_get_capabilities":
|
|
165
|
-
result = runtime.getCapabilities();
|
|
166
|
-
break;
|
|
167
|
-
case "openfairygui_backend_open_session":
|
|
168
|
-
result = await runtime.openSession({ projectPath: String(input.projectPath) });
|
|
169
|
-
break;
|
|
170
|
-
case "openfairygui_backend_open_project_session":
|
|
171
|
-
result = runtime.openProjectSession({
|
|
172
|
-
project: input.project,
|
|
173
|
-
sessionId: input.sessionId === void 0 ? void 0 : String(input.sessionId),
|
|
174
|
-
canonicalProjectPath: input.canonicalProjectPath === void 0 ? void 0 : String(input.canonicalProjectPath),
|
|
175
|
-
canonicalPathKey: input.canonicalPathKey === void 0 ? void 0 : String(input.canonicalPathKey)
|
|
176
|
-
});
|
|
177
|
-
break;
|
|
178
|
-
case "openfairygui_backend_get_session":
|
|
179
|
-
result = runtime.getSession({ sessionId: String(input.sessionId) });
|
|
180
|
-
break;
|
|
181
|
-
case "openfairygui_backend_get_project_outline":
|
|
182
|
-
result = runtime.getProjectOutline({ sessionId: String(input.sessionId) });
|
|
183
|
-
break;
|
|
184
|
-
case "openfairygui_backend_validate_session":
|
|
185
|
-
result = runtime.validateSession({ sessionId: String(input.sessionId) });
|
|
186
|
-
break;
|
|
187
|
-
case "openfairygui_backend_apply_transaction":
|
|
188
|
-
result = await runtime.applyTransaction({
|
|
189
|
-
sessionId: String(input.sessionId),
|
|
190
|
-
expectedRevision: Number(input.expectedRevision),
|
|
191
|
-
operations: input.operations
|
|
192
|
-
});
|
|
193
|
-
break;
|
|
194
|
-
case "openfairygui_backend_save_session":
|
|
195
|
-
result = await runtime.saveSession({
|
|
196
|
-
sessionId: String(input.sessionId),
|
|
197
|
-
expectedRevision: input.expectedRevision === void 0 ? void 0 : Number(input.expectedRevision),
|
|
198
|
-
targetPath: input.targetPath === void 0 ? void 0 : String(input.targetPath),
|
|
199
|
-
force: input.force === void 0 ? void 0 : Boolean(input.force),
|
|
200
|
-
mode: input.mode
|
|
201
|
-
});
|
|
202
|
-
break;
|
|
203
|
-
case "openfairygui_backend_materialize_session":
|
|
204
|
-
result = await runtime.materializeSession({
|
|
205
|
-
sessionId: String(input.sessionId),
|
|
206
|
-
expectedRevision: input.expectedRevision === void 0 ? void 0 : Number(input.expectedRevision),
|
|
207
|
-
mode: input.mode,
|
|
208
|
-
reason: input.reason === void 0 ? void 0 : String(input.reason)
|
|
209
|
-
});
|
|
210
|
-
break;
|
|
211
|
-
case "openfairygui_backend_close_session":
|
|
212
|
-
result = await runtime.closeSession({ sessionId: String(input.sessionId) });
|
|
213
|
-
break;
|
|
214
|
-
case "openfairygui_backend_get_events":
|
|
215
|
-
result = runtime.getEvents({
|
|
216
|
-
sessionId: String(input.sessionId),
|
|
217
|
-
after: input.after === void 0 ? void 0 : String(input.after),
|
|
218
|
-
limit: input.limit === void 0 ? void 0 : Number(input.limit)
|
|
219
|
-
});
|
|
220
|
-
break;
|
|
221
|
-
case "openfairygui_backend_get_job":
|
|
222
|
-
result = runtime.getJob({
|
|
223
|
-
sessionId: String(input.sessionId),
|
|
224
|
-
jobId: String(input.jobId)
|
|
225
|
-
});
|
|
226
|
-
break;
|
|
227
|
-
case "openfairygui_backend_list_jobs":
|
|
228
|
-
result = runtime.listJobs({
|
|
229
|
-
sessionId: String(input.sessionId),
|
|
230
|
-
status: input.status,
|
|
231
|
-
kind: input.kind,
|
|
232
|
-
limit: input.limit === void 0 ? void 0 : Number(input.limit)
|
|
233
|
-
});
|
|
234
|
-
break;
|
|
235
|
-
case "openfairygui_backend_cancel_job":
|
|
236
|
-
result = runtime.cancelJob({
|
|
237
|
-
sessionId: String(input.sessionId),
|
|
238
|
-
jobId: String(input.jobId)
|
|
239
|
-
});
|
|
240
|
-
break;
|
|
241
|
-
case "openfairygui_backend_get_cache_snapshot":
|
|
242
|
-
result = runtime.getCacheSnapshot({ sessionId: String(input.sessionId) });
|
|
243
|
-
break;
|
|
244
|
-
case "openfairygui_backend_refresh_cache":
|
|
245
|
-
result = runtime.refreshCache({
|
|
246
|
-
sessionId: String(input.sessionId),
|
|
247
|
-
reason: input.reason
|
|
248
|
-
});
|
|
249
|
-
break;
|
|
250
|
-
default: throw new Error(`Unknown OpenFairyGUI backend MCP tool: ${name}`);
|
|
251
|
-
}
|
|
252
|
-
return jsonResult(result, isBackendFailure(result));
|
|
253
|
-
}
|
|
254
|
-
//#endregion
|
|
255
149
|
//#region src/tool-definitions.ts
|
|
256
150
|
const OPENFAIRYGUI_BACKEND_TOOL_PREFIX = "openfairygui_backend_";
|
|
257
151
|
const OPENFAIRYGUI_BACKEND_TOOL_NAMES = [
|
|
@@ -276,12 +170,316 @@ const sessionId = z.string().min(1);
|
|
|
276
170
|
const jobId = z.string().min(1);
|
|
277
171
|
const expectedRevision = z.number().int().nonnegative();
|
|
278
172
|
const limit = z.number().int().nonnegative().optional();
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
173
|
+
const identifier = z.string().min(1).max(256);
|
|
174
|
+
function isOpenFairyGuiMcpPayloadWithinBudget(root) {
|
|
175
|
+
const pending = [{
|
|
176
|
+
value: root,
|
|
177
|
+
depth: 0
|
|
178
|
+
}];
|
|
179
|
+
let nodes = 0;
|
|
180
|
+
while (pending.length > 0) {
|
|
181
|
+
const { value, depth } = pending.pop();
|
|
182
|
+
nodes += 1;
|
|
183
|
+
if (nodes > 1e5 || depth > 32) return false;
|
|
184
|
+
if (value === null || typeof value === "boolean") continue;
|
|
185
|
+
if (typeof value === "number") {
|
|
186
|
+
if (!Number.isFinite(value)) return false;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (typeof value === "string") {
|
|
190
|
+
if (value.length > 1e6) return false;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (value instanceof Uint8Array) {
|
|
194
|
+
if (value.byteLength > 8 * 1024 * 1024) return false;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
if (Array.isArray(value)) {
|
|
198
|
+
if (value.length > 1e4) return false;
|
|
199
|
+
for (const child of value) pending.push({
|
|
200
|
+
value: child,
|
|
201
|
+
depth: depth + 1
|
|
202
|
+
});
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (typeof value !== "object") return false;
|
|
206
|
+
const entries = Object.entries(value);
|
|
207
|
+
if (entries.length > 1e4 || entries.some(([key]) => key.length > 256)) return false;
|
|
208
|
+
for (const [, child] of entries) pending.push({
|
|
209
|
+
value: child,
|
|
210
|
+
depth: depth + 1
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
const boundedPayload = z.json();
|
|
216
|
+
const bytes = z.array(z.number().int().min(0).max(255)).max(8 * 1024 * 1024);
|
|
217
|
+
const packageSelector = z.object({ packageId: identifier });
|
|
218
|
+
const resourceSelector = z.object({
|
|
219
|
+
packageId: identifier,
|
|
220
|
+
resourceId: identifier
|
|
221
|
+
});
|
|
222
|
+
const componentSelector = z.object({
|
|
223
|
+
packageId: identifier,
|
|
224
|
+
componentResourceId: identifier
|
|
225
|
+
});
|
|
226
|
+
const displayNodeSelector = componentSelector.extend({ displayNodeId: identifier });
|
|
227
|
+
const controllerSelector = componentSelector.extend({ controllerName: identifier });
|
|
228
|
+
const transitionSelector = componentSelector.extend({ transitionName: identifier });
|
|
229
|
+
const folderSelector = packageSelector.extend({
|
|
230
|
+
branch: z.string().max(256).optional(),
|
|
231
|
+
path: z.string().min(1).max(4096)
|
|
232
|
+
});
|
|
233
|
+
const operationBase = { opId: identifier.optional() };
|
|
234
|
+
const operation = z.discriminatedUnion("kind", [
|
|
235
|
+
z.object({
|
|
236
|
+
...operationBase,
|
|
237
|
+
kind: z.literal("updateProjectSettings"),
|
|
238
|
+
settings: boundedPayload
|
|
239
|
+
}),
|
|
240
|
+
z.object({
|
|
241
|
+
...operationBase,
|
|
242
|
+
kind: z.literal("updatePackageSettings"),
|
|
243
|
+
selector: packageSelector,
|
|
244
|
+
settings: boundedPayload
|
|
245
|
+
}),
|
|
246
|
+
z.object({
|
|
247
|
+
...operationBase,
|
|
248
|
+
kind: z.literal("renameResource"),
|
|
249
|
+
selector: resourceSelector,
|
|
250
|
+
newName: identifier
|
|
251
|
+
}),
|
|
252
|
+
z.object({
|
|
253
|
+
...operationBase,
|
|
254
|
+
kind: z.literal("moveResource"),
|
|
255
|
+
selector: resourceSelector,
|
|
256
|
+
toPath: z.string().max(4096)
|
|
257
|
+
}),
|
|
258
|
+
z.object({
|
|
259
|
+
...operationBase,
|
|
260
|
+
kind: z.literal("setResourceFavorite"),
|
|
261
|
+
selector: resourceSelector,
|
|
262
|
+
favorite: z.boolean()
|
|
263
|
+
}),
|
|
264
|
+
z.object({
|
|
265
|
+
...operationBase,
|
|
266
|
+
kind: z.literal("setResourceFolderFavorite"),
|
|
267
|
+
selector: folderSelector,
|
|
268
|
+
favorite: z.boolean()
|
|
269
|
+
}),
|
|
270
|
+
z.object({
|
|
271
|
+
...operationBase,
|
|
272
|
+
kind: z.literal("setResourceFolderAtlas"),
|
|
273
|
+
selector: folderSelector,
|
|
274
|
+
atlas: z.string().max(32)
|
|
275
|
+
}),
|
|
276
|
+
z.object({
|
|
277
|
+
...operationBase,
|
|
278
|
+
kind: z.literal("setResourceExported"),
|
|
279
|
+
selector: resourceSelector,
|
|
280
|
+
exported: z.boolean()
|
|
281
|
+
}),
|
|
282
|
+
z.object({
|
|
283
|
+
...operationBase,
|
|
284
|
+
kind: z.literal("addResourceFolder"),
|
|
285
|
+
selector: packageSelector,
|
|
286
|
+
path: z.string().max(4096),
|
|
287
|
+
branch: z.string().max(256).optional(),
|
|
288
|
+
favorite: z.boolean().optional(),
|
|
289
|
+
atlas: z.string().max(32).optional()
|
|
290
|
+
}),
|
|
291
|
+
z.object({
|
|
292
|
+
...operationBase,
|
|
293
|
+
kind: z.literal("renameResourceFolder"),
|
|
294
|
+
selector: folderSelector,
|
|
295
|
+
newName: identifier
|
|
296
|
+
}),
|
|
297
|
+
z.object({
|
|
298
|
+
...operationBase,
|
|
299
|
+
kind: z.literal("moveResourceFolder"),
|
|
300
|
+
selector: folderSelector,
|
|
301
|
+
toPath: z.string().max(4096)
|
|
302
|
+
}),
|
|
303
|
+
z.object({
|
|
304
|
+
...operationBase,
|
|
305
|
+
kind: z.literal("removeResourceFolder"),
|
|
306
|
+
selector: folderSelector
|
|
307
|
+
}),
|
|
308
|
+
z.object({
|
|
309
|
+
...operationBase,
|
|
310
|
+
kind: z.literal("setImageResourceProps"),
|
|
311
|
+
selector: resourceSelector,
|
|
312
|
+
props: boundedPayload
|
|
313
|
+
}),
|
|
314
|
+
z.object({
|
|
315
|
+
...operationBase,
|
|
316
|
+
kind: z.literal("addResource"),
|
|
317
|
+
selector: packageSelector,
|
|
318
|
+
resource: boundedPayload,
|
|
319
|
+
atIndex: z.number().int().nonnegative().optional()
|
|
320
|
+
}),
|
|
321
|
+
z.object({
|
|
322
|
+
...operationBase,
|
|
323
|
+
kind: z.literal("addBranch"),
|
|
324
|
+
branch: identifier
|
|
325
|
+
}),
|
|
326
|
+
z.object({
|
|
327
|
+
...operationBase,
|
|
328
|
+
kind: z.literal("renameBranch"),
|
|
329
|
+
selector: z.object({ branch: identifier }),
|
|
330
|
+
newName: identifier
|
|
331
|
+
}),
|
|
332
|
+
z.object({
|
|
333
|
+
...operationBase,
|
|
334
|
+
kind: z.literal("removeBranch"),
|
|
335
|
+
selector: z.object({ branch: identifier })
|
|
336
|
+
}),
|
|
337
|
+
z.object({
|
|
338
|
+
...operationBase,
|
|
339
|
+
kind: z.literal("addPackage"),
|
|
340
|
+
package: boundedPayload,
|
|
341
|
+
atIndex: z.number().int().nonnegative()
|
|
342
|
+
}),
|
|
343
|
+
z.object({
|
|
344
|
+
...operationBase,
|
|
345
|
+
kind: z.literal("renamePackage"),
|
|
346
|
+
selector: packageSelector,
|
|
347
|
+
newName: identifier
|
|
348
|
+
}),
|
|
349
|
+
z.object({
|
|
350
|
+
...operationBase,
|
|
351
|
+
kind: z.literal("removePackage"),
|
|
352
|
+
selector: packageSelector
|
|
353
|
+
}),
|
|
354
|
+
z.object({
|
|
355
|
+
...operationBase,
|
|
356
|
+
kind: z.literal("addComponent"),
|
|
357
|
+
selector: packageSelector,
|
|
358
|
+
component: boundedPayload,
|
|
359
|
+
atIndex: z.number().int().nonnegative()
|
|
360
|
+
}),
|
|
361
|
+
z.object({
|
|
362
|
+
...operationBase,
|
|
363
|
+
kind: z.literal("removeComponent"),
|
|
364
|
+
selector: componentSelector
|
|
365
|
+
}),
|
|
366
|
+
z.object({
|
|
367
|
+
...operationBase,
|
|
368
|
+
kind: z.literal("moveComponent"),
|
|
369
|
+
selector: componentSelector,
|
|
370
|
+
toPackageId: identifier,
|
|
371
|
+
toIndex: z.number().int().nonnegative()
|
|
372
|
+
}),
|
|
373
|
+
z.object({
|
|
374
|
+
...operationBase,
|
|
375
|
+
kind: z.literal("replaceResourceBytes"),
|
|
376
|
+
selector: resourceSelector,
|
|
377
|
+
sourceBytes: bytes
|
|
378
|
+
}),
|
|
379
|
+
z.object({
|
|
380
|
+
...operationBase,
|
|
381
|
+
kind: z.literal("removeResource"),
|
|
382
|
+
selector: resourceSelector
|
|
383
|
+
}),
|
|
384
|
+
z.object({
|
|
385
|
+
...operationBase,
|
|
386
|
+
kind: z.literal("setDisplayNodeProps"),
|
|
387
|
+
selector: displayNodeSelector,
|
|
388
|
+
props: boundedPayload
|
|
389
|
+
}),
|
|
390
|
+
z.object({
|
|
391
|
+
...operationBase,
|
|
392
|
+
kind: z.literal("setComponentProps"),
|
|
393
|
+
selector: componentSelector,
|
|
394
|
+
props: boundedPayload
|
|
395
|
+
}),
|
|
396
|
+
z.object({
|
|
397
|
+
...operationBase,
|
|
398
|
+
kind: z.literal("attachDisplayNode"),
|
|
399
|
+
selector: componentSelector,
|
|
400
|
+
atIndex: z.number().int().nonnegative(),
|
|
401
|
+
node: boundedPayload
|
|
402
|
+
}),
|
|
403
|
+
z.object({
|
|
404
|
+
...operationBase,
|
|
405
|
+
kind: z.literal("detachDisplayNode"),
|
|
406
|
+
selector: displayNodeSelector
|
|
407
|
+
}),
|
|
408
|
+
...["addController", "updateController"].map((kind) => z.object({
|
|
409
|
+
...operationBase,
|
|
410
|
+
kind: z.literal(kind),
|
|
411
|
+
selector: controllerSelector,
|
|
412
|
+
controller: boundedPayload
|
|
413
|
+
})),
|
|
414
|
+
z.object({
|
|
415
|
+
...operationBase,
|
|
416
|
+
kind: z.literal("removeController"),
|
|
417
|
+
selector: controllerSelector
|
|
418
|
+
}),
|
|
419
|
+
...["addTransition", "updateTransition"].map((kind) => z.object({
|
|
420
|
+
...operationBase,
|
|
421
|
+
kind: z.literal(kind),
|
|
422
|
+
selector: transitionSelector,
|
|
423
|
+
transition: boundedPayload
|
|
424
|
+
})),
|
|
425
|
+
z.object({
|
|
426
|
+
...operationBase,
|
|
427
|
+
kind: z.literal("removeTransition"),
|
|
428
|
+
selector: transitionSelector
|
|
429
|
+
}),
|
|
430
|
+
...[
|
|
431
|
+
"addLookGear",
|
|
432
|
+
"updateLookGear",
|
|
433
|
+
"addGear",
|
|
434
|
+
"updateGear"
|
|
435
|
+
].map((kind) => z.object({
|
|
436
|
+
...operationBase,
|
|
437
|
+
kind: z.literal(kind),
|
|
438
|
+
selector: displayNodeSelector.extend({
|
|
439
|
+
kind: identifier,
|
|
440
|
+
controllerName: identifier
|
|
441
|
+
}),
|
|
442
|
+
gear: boundedPayload
|
|
443
|
+
})),
|
|
444
|
+
...["removeLookGear", "removeGear"].map((kind) => z.object({
|
|
445
|
+
...operationBase,
|
|
446
|
+
kind: z.literal(kind),
|
|
447
|
+
selector: displayNodeSelector.extend({
|
|
448
|
+
kind: identifier,
|
|
449
|
+
controllerName: identifier
|
|
450
|
+
})
|
|
451
|
+
}))
|
|
452
|
+
]);
|
|
453
|
+
const project = z.object({
|
|
454
|
+
projectId: identifier,
|
|
455
|
+
projectType: z.number().int(),
|
|
456
|
+
version: z.string().max(256),
|
|
457
|
+
branches: z.array(z.string().max(256)).max(256),
|
|
458
|
+
settings: boundedPayload,
|
|
459
|
+
packages: z.array(z.object({
|
|
460
|
+
id: identifier,
|
|
461
|
+
name: identifier,
|
|
462
|
+
compressPNG: z.boolean().nullable(),
|
|
463
|
+
jpegQuality: z.number().finite().nullable(),
|
|
464
|
+
publish: boundedPayload.nullable(),
|
|
465
|
+
branchNames: z.array(z.string().max(256)).max(256),
|
|
466
|
+
folders: z.array(boundedPayload).max(1e4),
|
|
467
|
+
resources: z.array(boundedPayload).max(1e5)
|
|
468
|
+
})).max(1e3)
|
|
469
|
+
});
|
|
470
|
+
const OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA = z.object({ backendResult: z.discriminatedUnion("ok", [z.object({
|
|
471
|
+
ok: z.literal(true),
|
|
472
|
+
data: boundedPayload,
|
|
473
|
+
meta: boundedPayload
|
|
474
|
+
}), z.object({
|
|
475
|
+
ok: z.literal(false),
|
|
476
|
+
error: z.object({
|
|
477
|
+
code: identifier,
|
|
478
|
+
message: z.string().max(1e6)
|
|
479
|
+
}).passthrough(),
|
|
480
|
+
meta: boundedPayload,
|
|
481
|
+
session: boundedPayload.optional()
|
|
482
|
+
})]) });
|
|
285
483
|
const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
286
484
|
{
|
|
287
485
|
name: "openfairygui_backend_get_capabilities",
|
|
@@ -315,7 +513,7 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
315
513
|
title: "Open Project Session",
|
|
316
514
|
description: "Open a browser-safe backend session from an already loaded UAM project without filesystem access.",
|
|
317
515
|
inputSchema: z.object({
|
|
318
|
-
project
|
|
516
|
+
project,
|
|
319
517
|
sessionId: z.string().min(1).optional(),
|
|
320
518
|
canonicalProjectPath: z.string().min(1).optional(),
|
|
321
519
|
canonicalPathKey: z.string().min(1).optional()
|
|
@@ -370,11 +568,11 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
370
568
|
name: "openfairygui_backend_apply_transaction",
|
|
371
569
|
backendMethod: "applyTransaction",
|
|
372
570
|
title: "Apply UAM Transaction",
|
|
373
|
-
description: "Apply a
|
|
571
|
+
description: "Apply a bounded, revision-checked UAM operation batch using the Core transaction discriminants.",
|
|
374
572
|
inputSchema: z.object({
|
|
375
573
|
sessionId,
|
|
376
574
|
expectedRevision,
|
|
377
|
-
operations: z.array(
|
|
575
|
+
operations: z.array(operation).min(1).max(1e3)
|
|
378
576
|
}),
|
|
379
577
|
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
380
578
|
annotations: {
|
|
@@ -388,7 +586,7 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
388
586
|
name: "openfairygui_backend_save_session",
|
|
389
587
|
backendMethod: "saveSession",
|
|
390
588
|
title: "Save Backend Session",
|
|
391
|
-
description: "Write the current backend session
|
|
589
|
+
description: "Write the current backend session through its coordinated save path; Node uses an atomic staged directory swap.",
|
|
392
590
|
inputSchema: z.object({
|
|
393
591
|
sessionId,
|
|
394
592
|
expectedRevision: expectedRevision.optional(),
|
|
@@ -546,10 +744,149 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
546
744
|
}
|
|
547
745
|
];
|
|
548
746
|
//#endregion
|
|
747
|
+
//#region src/tool-handler.ts
|
|
748
|
+
function jsonResult(payload, isError = false) {
|
|
749
|
+
const text = JSON.stringify(payload, null, 2);
|
|
750
|
+
const wirePayload = JSON.parse(text);
|
|
751
|
+
return {
|
|
752
|
+
content: [{
|
|
753
|
+
type: "text",
|
|
754
|
+
text
|
|
755
|
+
}],
|
|
756
|
+
structuredContent: { backendResult: wirePayload },
|
|
757
|
+
isError
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
function isBackendFailure(value) {
|
|
761
|
+
return typeof value === "object" && value !== null && "ok" in value && value.ok === false;
|
|
762
|
+
}
|
|
763
|
+
function unhandledBackendFailure(startedAt) {
|
|
764
|
+
return {
|
|
765
|
+
ok: false,
|
|
766
|
+
meta: {
|
|
767
|
+
requestId: crypto.randomUUID(),
|
|
768
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
769
|
+
warnings: [],
|
|
770
|
+
diagnostics: [],
|
|
771
|
+
stage: "runtime",
|
|
772
|
+
contractVersion: BACKEND_CONTRACT_VERSION,
|
|
773
|
+
capabilitySchemaVersion: BACKEND_CAPABILITY_SCHEMA_VERSION
|
|
774
|
+
},
|
|
775
|
+
error: {
|
|
776
|
+
code: "backend_unhandled_error",
|
|
777
|
+
message: "Backend tool execution failed."
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
async function callOpenFairyGuiBackendTool(runtime, name, input) {
|
|
782
|
+
if (!isOpenFairyGuiMcpPayloadWithinBudget(input)) throw new RangeError("MCP input exceeds the depth, node, key, string, or byte budget.");
|
|
783
|
+
const startedAt = Date.now();
|
|
784
|
+
let result;
|
|
785
|
+
try {
|
|
786
|
+
switch (name) {
|
|
787
|
+
case "openfairygui_backend_get_capabilities":
|
|
788
|
+
result = runtime.getCapabilities();
|
|
789
|
+
break;
|
|
790
|
+
case "openfairygui_backend_open_session":
|
|
791
|
+
result = await runtime.openSession({ projectPath: String(input.projectPath) });
|
|
792
|
+
break;
|
|
793
|
+
case "openfairygui_backend_open_project_session":
|
|
794
|
+
result = runtime.openProjectSession({
|
|
795
|
+
project: input.project,
|
|
796
|
+
sessionId: input.sessionId === void 0 ? void 0 : String(input.sessionId),
|
|
797
|
+
canonicalProjectPath: input.canonicalProjectPath === void 0 ? void 0 : String(input.canonicalProjectPath),
|
|
798
|
+
canonicalPathKey: input.canonicalPathKey === void 0 ? void 0 : String(input.canonicalPathKey)
|
|
799
|
+
});
|
|
800
|
+
break;
|
|
801
|
+
case "openfairygui_backend_get_session":
|
|
802
|
+
result = runtime.getSession({ sessionId: String(input.sessionId) });
|
|
803
|
+
break;
|
|
804
|
+
case "openfairygui_backend_get_project_outline":
|
|
805
|
+
result = runtime.getProjectOutline({ sessionId: String(input.sessionId) });
|
|
806
|
+
break;
|
|
807
|
+
case "openfairygui_backend_validate_session":
|
|
808
|
+
result = runtime.validateSession({ sessionId: String(input.sessionId) });
|
|
809
|
+
break;
|
|
810
|
+
case "openfairygui_backend_apply_transaction": {
|
|
811
|
+
const operations = input.operations.map((operation) => operation.kind === "replaceResourceBytes" ? {
|
|
812
|
+
...operation,
|
|
813
|
+
sourceBytes: new Uint8Array(operation.sourceBytes)
|
|
814
|
+
} : operation);
|
|
815
|
+
result = await runtime.applyTransaction({
|
|
816
|
+
sessionId: String(input.sessionId),
|
|
817
|
+
expectedRevision: Number(input.expectedRevision),
|
|
818
|
+
operations
|
|
819
|
+
});
|
|
820
|
+
break;
|
|
821
|
+
}
|
|
822
|
+
case "openfairygui_backend_save_session":
|
|
823
|
+
result = await runtime.saveSession({
|
|
824
|
+
sessionId: String(input.sessionId),
|
|
825
|
+
expectedRevision: input.expectedRevision === void 0 ? void 0 : Number(input.expectedRevision),
|
|
826
|
+
targetPath: input.targetPath === void 0 ? void 0 : String(input.targetPath),
|
|
827
|
+
force: input.force === void 0 ? void 0 : Boolean(input.force),
|
|
828
|
+
mode: input.mode
|
|
829
|
+
});
|
|
830
|
+
break;
|
|
831
|
+
case "openfairygui_backend_materialize_session":
|
|
832
|
+
result = await runtime.materializeSession({
|
|
833
|
+
sessionId: String(input.sessionId),
|
|
834
|
+
expectedRevision: input.expectedRevision === void 0 ? void 0 : Number(input.expectedRevision),
|
|
835
|
+
mode: input.mode,
|
|
836
|
+
reason: input.reason === void 0 ? void 0 : String(input.reason)
|
|
837
|
+
});
|
|
838
|
+
break;
|
|
839
|
+
case "openfairygui_backend_close_session":
|
|
840
|
+
result = await runtime.closeSession({ sessionId: String(input.sessionId) });
|
|
841
|
+
break;
|
|
842
|
+
case "openfairygui_backend_get_events":
|
|
843
|
+
result = runtime.getEvents({
|
|
844
|
+
sessionId: String(input.sessionId),
|
|
845
|
+
after: input.after === void 0 ? void 0 : String(input.after),
|
|
846
|
+
limit: input.limit === void 0 ? void 0 : Number(input.limit)
|
|
847
|
+
});
|
|
848
|
+
break;
|
|
849
|
+
case "openfairygui_backend_get_job":
|
|
850
|
+
result = runtime.getJob({
|
|
851
|
+
sessionId: String(input.sessionId),
|
|
852
|
+
jobId: String(input.jobId)
|
|
853
|
+
});
|
|
854
|
+
break;
|
|
855
|
+
case "openfairygui_backend_list_jobs":
|
|
856
|
+
result = runtime.listJobs({
|
|
857
|
+
sessionId: String(input.sessionId),
|
|
858
|
+
status: input.status,
|
|
859
|
+
kind: input.kind,
|
|
860
|
+
limit: input.limit === void 0 ? void 0 : Number(input.limit)
|
|
861
|
+
});
|
|
862
|
+
break;
|
|
863
|
+
case "openfairygui_backend_cancel_job":
|
|
864
|
+
result = runtime.cancelJob({
|
|
865
|
+
sessionId: String(input.sessionId),
|
|
866
|
+
jobId: String(input.jobId)
|
|
867
|
+
});
|
|
868
|
+
break;
|
|
869
|
+
case "openfairygui_backend_get_cache_snapshot":
|
|
870
|
+
result = runtime.getCacheSnapshot({ sessionId: String(input.sessionId) });
|
|
871
|
+
break;
|
|
872
|
+
case "openfairygui_backend_refresh_cache":
|
|
873
|
+
result = runtime.refreshCache({
|
|
874
|
+
sessionId: String(input.sessionId),
|
|
875
|
+
reason: input.reason
|
|
876
|
+
});
|
|
877
|
+
break;
|
|
878
|
+
default: throw new Error(`Unknown OpenFairyGUI backend MCP tool: ${name}`);
|
|
879
|
+
}
|
|
880
|
+
} catch {
|
|
881
|
+
return jsonResult(unhandledBackendFailure(startedAt), true);
|
|
882
|
+
}
|
|
883
|
+
return jsonResult(result, isBackendFailure(result));
|
|
884
|
+
}
|
|
885
|
+
//#endregion
|
|
549
886
|
//#region src/server.ts
|
|
550
887
|
const require = createRequire(import.meta.url);
|
|
551
888
|
function getInjectedPackageVersion() {
|
|
552
|
-
const version = "0.3.
|
|
889
|
+
const version = "0.3.1";
|
|
553
890
|
return typeof version === "string" && true ? version : null;
|
|
554
891
|
}
|
|
555
892
|
function readPackageVersion() {
|
|
@@ -563,7 +900,7 @@ function readPackageVersion() {
|
|
|
563
900
|
}
|
|
564
901
|
const PACKAGE_VERSION = readPackageVersion();
|
|
565
902
|
function createOpenFairyGuiMcpServer(options = {}) {
|
|
566
|
-
const runtime = options.runtime ?? createNodeBackendRuntime();
|
|
903
|
+
const runtime = options.runtime ?? createNodeBackendRuntime({ allowedProjectRoots: options.allowedProjectRoots ?? [process.cwd()] });
|
|
567
904
|
const server = new McpServer({
|
|
568
905
|
name: options.name ?? "openfairygui-mcp",
|
|
569
906
|
version: options.version ?? PACKAGE_VERSION
|
|
@@ -586,11 +923,12 @@ function createOpenFairyGuiMcpServer(options = {}) {
|
|
|
586
923
|
//#endregion
|
|
587
924
|
//#region src/stdio.ts
|
|
588
925
|
async function connectOpenFairyGuiMcpStdio() {
|
|
589
|
-
|
|
926
|
+
const configuredRoots = process.env.OPENFAIRYGUI_ALLOWED_PROJECT_ROOTS?.split(path.delimiter).map((value) => value.trim()).filter(Boolean);
|
|
927
|
+
await createOpenFairyGuiMcpServer({ allowedProjectRoots: configuredRoots }).connect(new StdioServerTransport());
|
|
590
928
|
}
|
|
591
929
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) connectOpenFairyGuiMcpStdio().catch((error) => {
|
|
592
930
|
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
593
931
|
process.exitCode = 1;
|
|
594
932
|
});
|
|
595
933
|
//#endregion
|
|
596
|
-
export {
|
|
934
|
+
export { OPENFAIRYGUI_BACKEND_TOOL_NAMES as a, OPENFAIRYGUI_BACKEND_CAPABILITIES_RESOURCE_URI as c, OPENFAIRYGUI_BACKEND_PROMPT_NAMES as d, OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS as i, OPENFAIRYGUI_BACKEND_RESOURCE_TEMPLATES as l, createOpenFairyGuiMcpServer as n, OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA as o, callOpenFairyGuiBackendTool as r, OPENFAIRYGUI_BACKEND_TOOL_PREFIX as s, connectOpenFairyGuiMcpStdio as t, OPENFAIRYGUI_BACKEND_PROMPT_DEFINITIONS as u };
|