@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.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA, c as OPENFAIRYGUI_BACKEND_CAPABILITIES_RESOURCE_URI, d as OPENFAIRYGUI_BACKEND_PROMPT_NAMES, i as OPENFAIRYGUI_BACKEND_TOOL_NAMES, l as OPENFAIRYGUI_BACKEND_RESOURCE_TEMPLATES, n as createOpenFairyGuiMcpServer, o as OPENFAIRYGUI_BACKEND_TOOL_PREFIX, r as OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS, s as callOpenFairyGuiBackendTool, t as connectOpenFairyGuiMcpStdio, u as OPENFAIRYGUI_BACKEND_PROMPT_DEFINITIONS } from "./stdio-48jCJzS3.mjs";
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 OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA = z.object({ backendResult: z.object({
280
- ok: z.boolean(),
281
- data: z.unknown().optional(),
282
- error: z.unknown().optional(),
283
- meta: z.unknown().optional()
284
- }).passthrough() });
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: z.unknown(),
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 backend revision-checked UAM operation batch without redefining selector or operation grammar.",
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(z.unknown())
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 back through the backend coordinated non-atomic save path.",
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.0";
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
- await createOpenFairyGuiMcpServer().connect(new StdioServerTransport());
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 { OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA as a, OPENFAIRYGUI_BACKEND_CAPABILITIES_RESOURCE_URI as c, OPENFAIRYGUI_BACKEND_PROMPT_NAMES as d, OPENFAIRYGUI_BACKEND_TOOL_NAMES as i, OPENFAIRYGUI_BACKEND_RESOURCE_TEMPLATES as l, createOpenFairyGuiMcpServer as n, OPENFAIRYGUI_BACKEND_TOOL_PREFIX as o, OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS as r, callOpenFairyGuiBackendTool as s, connectOpenFairyGuiMcpStdio as t, OPENFAIRYGUI_BACKEND_PROMPT_DEFINITIONS as u };
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 };