@openfairygui/mcp 0.3.1 → 0.5.0-alpha.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.
@@ -21,10 +21,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  }) : target, mod));
22
22
  //#endregion
23
23
  let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
24
+ let _modelcontextprotocol_sdk_types_js = require("@modelcontextprotocol/sdk/types.js");
24
25
  let _openfairygui_backend_node = require("@openfairygui/backend/node");
25
26
  let node_module = require("node:module");
26
- let _openfairygui_backend = require("@openfairygui/backend");
27
27
  let zod = require("zod");
28
+ let _openfairygui_backend = require("@openfairygui/backend");
29
+ let _openfairygui_backend_docs = require("@openfairygui/backend/docs");
28
30
  let _modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
29
31
  let node_path = require("node:path");
30
32
  node_path = __toESM(node_path);
@@ -76,6 +78,11 @@ const OPENFAIRYGUI_BACKEND_PROMPT_DEFINITIONS = [
76
78
  description: "Guide a client through backend-owned revision checks without inventing operation grammar.",
77
79
  text: [
78
80
  "Use openfairygui_backend_get_session to read the current revision before mutation.",
81
+ "Use openfairygui_backend_get_project_outline for identities, then openfairygui_backend_query_entity for current properties at the returned revision.",
82
+ "For settings edits, query target {kind:\"project\"} or {kind:\"package\",selector:{packageId}}; copy entity.properties.settings, change the requested fields, and submit the complete settings to updateProjectSettings or updatePackageSettings.",
83
+ "Read openfairygui://contracts/operations and openfairygui://contracts/operations/{kind} for the current operation names and exact JSON parameters.",
84
+ "Call openfairygui_backend_preflight_transaction with the queried revision and planned operations to execute and discard an isolated preview; inspect the backend diagnostics.",
85
+ "A successful preview does not reserve a revision or guarantee save. Apply the same batch with expectedRevision set to the preview baseRevision; refresh properties and re-plan on stale revision.",
79
86
  "Call openfairygui_backend_apply_transaction with sessionId, expectedRevision, and backend/UAM-owned operations.",
80
87
  "If the backend returns a stale revision error, refresh the session snapshot and re-plan against the new revision.",
81
88
  "Do not invent selector grammar, transaction grammar, or operation payload semantics at the MCP layer."
@@ -119,6 +126,32 @@ function registerOpenFairyGuiBackendPrompts(server) {
119
126
  }, () => promptResult(definition.text));
120
127
  }
121
128
  //#endregion
129
+ //#region src/contract-schema.ts
130
+ const CONTRACT_SNAPSHOT = (0, _openfairygui_backend_docs.getInstalledContractSnapshot)();
131
+ function contractObjectSchema(schema) {
132
+ const result = zod.z.fromJSONSchema({
133
+ ...schema,
134
+ $defs: CONTRACT_SNAPSHOT.$defs
135
+ });
136
+ if (!(result instanceof zod.z.ZodObject)) throw new TypeError("Tool contract must be an object");
137
+ return result;
138
+ }
139
+ /** Decode only generated Uint8Array locations; arbitrary JSON metadata is not rewritten. */
140
+ function decodeToolBytes(input, paths) {
141
+ if (!paths.length) return input;
142
+ const result = structuredClone(input);
143
+ function visit(value, parts) {
144
+ if (!parts.length) return value === null ? value : Uint8Array.from(value);
145
+ if (!value || typeof value !== "object") return value;
146
+ const [key, ...rest] = parts;
147
+ const record = value;
148
+ for (const name of key === "*" ? Object.keys(record) : [key]) if (Object.hasOwn(record, name)) record[name] = visit(record[name], rest);
149
+ return value;
150
+ }
151
+ for (const parts of paths) visit(result, parts);
152
+ return result;
153
+ }
154
+ //#endregion
122
155
  //#region src/resource-definitions.ts
123
156
  const JSON_MIME_TYPE = "application/json";
124
157
  function firstVariable(value) {
@@ -133,12 +166,69 @@ function jsonResource(uri, backendResult) {
133
166
  }
134
167
  const OPENFAIRYGUI_BACKEND_CAPABILITIES_RESOURCE_URI = "openfairygui://backend/capabilities";
135
168
  const OPENFAIRYGUI_BACKEND_RESOURCE_TEMPLATES = [
169
+ "openfairygui://docs/methods/{method}",
170
+ "openfairygui://docs/cli/{command}",
171
+ _openfairygui_backend.BACKEND_DIAGNOSTIC_TEMPLATE,
172
+ _openfairygui_backend_docs.OPENFAIRYGUI_OPERATION_SCHEMA_TEMPLATE,
136
173
  "openfairygui://backend/session/{sessionId}",
137
174
  "openfairygui://backend/session/{sessionId}/outline",
138
175
  "openfairygui://backend/cache/{sessionId}",
139
176
  "openfairygui://backend/job/{sessionId}/{jobId}"
140
177
  ];
141
178
  function registerOpenFairyGuiBackendResources(server, runtime) {
179
+ server.registerResource("openfairygui_docs_index", _openfairygui_backend_docs.OPENFAIRYGUI_DOCS_INDEX_URI, {
180
+ title: "Installed Documentation",
181
+ description: "Offline documentation IDs, URIs, installed package version and contract digest shared with the CLI.",
182
+ mimeType: JSON_MIME_TYPE
183
+ }, (uri) => jsonResource(uri, (0, _openfairygui_backend_docs.getInstalledDocumentationIndex)()));
184
+ function installedDocument(uri, id) {
185
+ const document = (0, _openfairygui_backend_docs.readInstalledDocumentation)(id);
186
+ return { contents: [{
187
+ uri: uri.toString(),
188
+ mimeType: document.mimeType,
189
+ text: document.text
190
+ }] };
191
+ }
192
+ for (const id of [
193
+ "workflow",
194
+ "restore-limits",
195
+ "skill",
196
+ "contracts"
197
+ ]) server.registerResource(`openfairygui_docs_${id}`, `openfairygui://docs/${id}`, {
198
+ title: `Installed ${id}`,
199
+ description: "Read the installed-version corpus without repository or network access.",
200
+ mimeType: id === "contracts" ? JSON_MIME_TYPE : "text/markdown"
201
+ }, (uri) => installedDocument(uri, id));
202
+ server.registerResource("openfairygui_docs_method", new _modelcontextprotocol_sdk_server_mcp_js.ResourceTemplate("openfairygui://docs/methods/{method}", { list: void 0 }), {
203
+ title: "Installed Method Contract",
204
+ description: "Read self-contained Backend/MCP wire input/output schemas and metadata.",
205
+ mimeType: JSON_MIME_TYPE
206
+ }, (uri, variables) => installedDocument(uri, `methods/${firstVariable(variables.method)}`));
207
+ server.registerResource("openfairygui_diagnostic_catalog", _openfairygui_backend.BACKEND_DIAGNOSTICS_URI, {
208
+ title: "Diagnostic Recovery Catalog",
209
+ description: "Complete formal diagnostic ownership and recovery guidance; never automatic repair.",
210
+ mimeType: JSON_MIME_TYPE
211
+ }, (uri) => jsonResource(uri, (0, _openfairygui_backend.getBackendDiagnosticCatalog)()));
212
+ server.registerResource("openfairygui_docs_cli", new _modelcontextprotocol_sdk_server_mcp_js.ResourceTemplate("openfairygui://docs/cli/{command}", { list: void 0 }), {
213
+ title: "Installed CLI Output Contract",
214
+ description: "Read a generated, self-contained CLI JSON envelope schema.",
215
+ mimeType: JSON_MIME_TYPE
216
+ }, (uri, variables) => installedDocument(uri, `cli/${decodeURIComponent(firstVariable(variables.command))}`));
217
+ server.registerResource("openfairygui_diagnostic_guide", new _modelcontextprotocol_sdk_server_mcp_js.ResourceTemplate(_openfairygui_backend.BACKEND_DIAGNOSTIC_TEMPLATE, { list: void 0 }), {
218
+ title: "Diagnostic Recovery Guide",
219
+ description: "Read the recovery boundary for one stable diagnostic code.",
220
+ mimeType: JSON_MIME_TYPE
221
+ }, (uri, variables) => jsonResource(uri, (0, _openfairygui_backend.getBackendDiagnosticGuide)(firstVariable(variables.code))));
222
+ server.registerResource("openfairygui_operation_catalog", _openfairygui_backend_docs.OPENFAIRYGUI_OPERATION_CATALOG_URI, {
223
+ title: "UAM Operation Catalog",
224
+ description: "Discover current operations and their generated JSON schemas.",
225
+ mimeType: JSON_MIME_TYPE
226
+ }, (uri) => jsonResource(uri, (0, _openfairygui_backend_docs.getOpenFairyGuiOperationCatalog)()));
227
+ server.registerResource("openfairygui_operation_schema", new _modelcontextprotocol_sdk_server_mcp_js.ResourceTemplate(_openfairygui_backend_docs.OPENFAIRYGUI_OPERATION_SCHEMA_TEMPLATE, { list: void 0 }), {
228
+ title: "UAM Operation Schema",
229
+ description: "Read the precise Core-derived JSON wire schema for one operation. Structure is not semantic preflight.",
230
+ mimeType: JSON_MIME_TYPE
231
+ }, (uri, variables) => jsonResource(uri, (0, _openfairygui_backend_docs.getOpenFairyGuiOperationSchema)(firstVariable(variables.kind))));
142
232
  server.registerResource("openfairygui_backend_capabilities", OPENFAIRYGUI_BACKEND_CAPABILITIES_RESOURCE_URI, {
143
233
  title: "OpenFairyGUI Backend Capabilities",
144
234
  description: "Read the backend capability and version envelope as JSON.",
@@ -169,348 +259,13 @@ function registerOpenFairyGuiBackendResources(server, runtime) {
169
259
  })));
170
260
  }
171
261
  //#endregion
172
- //#region src/tool-definitions.ts
173
- const OPENFAIRYGUI_BACKEND_TOOL_PREFIX = "openfairygui_backend_";
174
- const OPENFAIRYGUI_BACKEND_TOOL_NAMES = [
175
- "openfairygui_backend_get_capabilities",
176
- "openfairygui_backend_open_session",
177
- "openfairygui_backend_open_project_session",
178
- "openfairygui_backend_get_session",
179
- "openfairygui_backend_get_project_outline",
180
- "openfairygui_backend_validate_session",
181
- "openfairygui_backend_apply_transaction",
182
- "openfairygui_backend_save_session",
183
- "openfairygui_backend_materialize_session",
184
- "openfairygui_backend_close_session",
185
- "openfairygui_backend_get_events",
186
- "openfairygui_backend_get_job",
187
- "openfairygui_backend_list_jobs",
188
- "openfairygui_backend_cancel_job",
189
- "openfairygui_backend_get_cache_snapshot",
190
- "openfairygui_backend_refresh_cache"
191
- ];
192
- const sessionId = zod.z.string().min(1);
193
- const jobId = zod.z.string().min(1);
194
- const expectedRevision = zod.z.number().int().nonnegative();
195
- const limit = zod.z.number().int().nonnegative().optional();
196
- const identifier = zod.z.string().min(1).max(256);
197
- function isOpenFairyGuiMcpPayloadWithinBudget(root) {
198
- const pending = [{
199
- value: root,
200
- depth: 0
201
- }];
202
- let nodes = 0;
203
- while (pending.length > 0) {
204
- const { value, depth } = pending.pop();
205
- nodes += 1;
206
- if (nodes > 1e5 || depth > 32) return false;
207
- if (value === null || typeof value === "boolean") continue;
208
- if (typeof value === "number") {
209
- if (!Number.isFinite(value)) return false;
210
- continue;
211
- }
212
- if (typeof value === "string") {
213
- if (value.length > 1e6) return false;
214
- continue;
215
- }
216
- if (value instanceof Uint8Array) {
217
- if (value.byteLength > 8 * 1024 * 1024) return false;
218
- continue;
219
- }
220
- if (Array.isArray(value)) {
221
- if (value.length > 1e4) return false;
222
- for (const child of value) pending.push({
223
- value: child,
224
- depth: depth + 1
225
- });
226
- continue;
227
- }
228
- if (typeof value !== "object") return false;
229
- const entries = Object.entries(value);
230
- if (entries.length > 1e4 || entries.some(([key]) => key.length > 256)) return false;
231
- for (const [, child] of entries) pending.push({
232
- value: child,
233
- depth: depth + 1
234
- });
235
- }
236
- return true;
237
- }
238
- const boundedPayload = zod.z.json();
239
- const bytes = zod.z.array(zod.z.number().int().min(0).max(255)).max(8 * 1024 * 1024);
240
- const packageSelector = zod.z.object({ packageId: identifier });
241
- const resourceSelector = zod.z.object({
242
- packageId: identifier,
243
- resourceId: identifier
244
- });
245
- const componentSelector = zod.z.object({
246
- packageId: identifier,
247
- componentResourceId: identifier
248
- });
249
- const displayNodeSelector = componentSelector.extend({ displayNodeId: identifier });
250
- const controllerSelector = componentSelector.extend({ controllerName: identifier });
251
- const transitionSelector = componentSelector.extend({ transitionName: identifier });
252
- const folderSelector = packageSelector.extend({
253
- branch: zod.z.string().max(256).optional(),
254
- path: zod.z.string().min(1).max(4096)
255
- });
256
- const operationBase = { opId: identifier.optional() };
257
- const operation = zod.z.discriminatedUnion("kind", [
258
- zod.z.object({
259
- ...operationBase,
260
- kind: zod.z.literal("updateProjectSettings"),
261
- settings: boundedPayload
262
- }),
263
- zod.z.object({
264
- ...operationBase,
265
- kind: zod.z.literal("updatePackageSettings"),
266
- selector: packageSelector,
267
- settings: boundedPayload
268
- }),
269
- zod.z.object({
270
- ...operationBase,
271
- kind: zod.z.literal("renameResource"),
272
- selector: resourceSelector,
273
- newName: identifier
274
- }),
275
- zod.z.object({
276
- ...operationBase,
277
- kind: zod.z.literal("moveResource"),
278
- selector: resourceSelector,
279
- toPath: zod.z.string().max(4096)
280
- }),
281
- zod.z.object({
282
- ...operationBase,
283
- kind: zod.z.literal("setResourceFavorite"),
284
- selector: resourceSelector,
285
- favorite: zod.z.boolean()
286
- }),
287
- zod.z.object({
288
- ...operationBase,
289
- kind: zod.z.literal("setResourceFolderFavorite"),
290
- selector: folderSelector,
291
- favorite: zod.z.boolean()
292
- }),
293
- zod.z.object({
294
- ...operationBase,
295
- kind: zod.z.literal("setResourceFolderAtlas"),
296
- selector: folderSelector,
297
- atlas: zod.z.string().max(32)
298
- }),
299
- zod.z.object({
300
- ...operationBase,
301
- kind: zod.z.literal("setResourceExported"),
302
- selector: resourceSelector,
303
- exported: zod.z.boolean()
304
- }),
305
- zod.z.object({
306
- ...operationBase,
307
- kind: zod.z.literal("addResourceFolder"),
308
- selector: packageSelector,
309
- path: zod.z.string().max(4096),
310
- branch: zod.z.string().max(256).optional(),
311
- favorite: zod.z.boolean().optional(),
312
- atlas: zod.z.string().max(32).optional()
313
- }),
314
- zod.z.object({
315
- ...operationBase,
316
- kind: zod.z.literal("renameResourceFolder"),
317
- selector: folderSelector,
318
- newName: identifier
319
- }),
320
- zod.z.object({
321
- ...operationBase,
322
- kind: zod.z.literal("moveResourceFolder"),
323
- selector: folderSelector,
324
- toPath: zod.z.string().max(4096)
325
- }),
326
- zod.z.object({
327
- ...operationBase,
328
- kind: zod.z.literal("removeResourceFolder"),
329
- selector: folderSelector
330
- }),
331
- zod.z.object({
332
- ...operationBase,
333
- kind: zod.z.literal("setImageResourceProps"),
334
- selector: resourceSelector,
335
- props: boundedPayload
336
- }),
337
- zod.z.object({
338
- ...operationBase,
339
- kind: zod.z.literal("addResource"),
340
- selector: packageSelector,
341
- resource: boundedPayload,
342
- atIndex: zod.z.number().int().nonnegative().optional()
343
- }),
344
- zod.z.object({
345
- ...operationBase,
346
- kind: zod.z.literal("addBranch"),
347
- branch: identifier
348
- }),
349
- zod.z.object({
350
- ...operationBase,
351
- kind: zod.z.literal("renameBranch"),
352
- selector: zod.z.object({ branch: identifier }),
353
- newName: identifier
354
- }),
355
- zod.z.object({
356
- ...operationBase,
357
- kind: zod.z.literal("removeBranch"),
358
- selector: zod.z.object({ branch: identifier })
359
- }),
360
- zod.z.object({
361
- ...operationBase,
362
- kind: zod.z.literal("addPackage"),
363
- package: boundedPayload,
364
- atIndex: zod.z.number().int().nonnegative()
365
- }),
366
- zod.z.object({
367
- ...operationBase,
368
- kind: zod.z.literal("renamePackage"),
369
- selector: packageSelector,
370
- newName: identifier
371
- }),
372
- zod.z.object({
373
- ...operationBase,
374
- kind: zod.z.literal("removePackage"),
375
- selector: packageSelector
376
- }),
377
- zod.z.object({
378
- ...operationBase,
379
- kind: zod.z.literal("addComponent"),
380
- selector: packageSelector,
381
- component: boundedPayload,
382
- atIndex: zod.z.number().int().nonnegative()
383
- }),
384
- zod.z.object({
385
- ...operationBase,
386
- kind: zod.z.literal("removeComponent"),
387
- selector: componentSelector
388
- }),
389
- zod.z.object({
390
- ...operationBase,
391
- kind: zod.z.literal("moveComponent"),
392
- selector: componentSelector,
393
- toPackageId: identifier,
394
- toIndex: zod.z.number().int().nonnegative()
395
- }),
396
- zod.z.object({
397
- ...operationBase,
398
- kind: zod.z.literal("replaceResourceBytes"),
399
- selector: resourceSelector,
400
- sourceBytes: bytes
401
- }),
402
- zod.z.object({
403
- ...operationBase,
404
- kind: zod.z.literal("removeResource"),
405
- selector: resourceSelector
406
- }),
407
- zod.z.object({
408
- ...operationBase,
409
- kind: zod.z.literal("setDisplayNodeProps"),
410
- selector: displayNodeSelector,
411
- props: boundedPayload
412
- }),
413
- zod.z.object({
414
- ...operationBase,
415
- kind: zod.z.literal("setComponentProps"),
416
- selector: componentSelector,
417
- props: boundedPayload
418
- }),
419
- zod.z.object({
420
- ...operationBase,
421
- kind: zod.z.literal("attachDisplayNode"),
422
- selector: componentSelector,
423
- atIndex: zod.z.number().int().nonnegative(),
424
- node: boundedPayload
425
- }),
426
- zod.z.object({
427
- ...operationBase,
428
- kind: zod.z.literal("detachDisplayNode"),
429
- selector: displayNodeSelector
430
- }),
431
- ...["addController", "updateController"].map((kind) => zod.z.object({
432
- ...operationBase,
433
- kind: zod.z.literal(kind),
434
- selector: controllerSelector,
435
- controller: boundedPayload
436
- })),
437
- zod.z.object({
438
- ...operationBase,
439
- kind: zod.z.literal("removeController"),
440
- selector: controllerSelector
441
- }),
442
- ...["addTransition", "updateTransition"].map((kind) => zod.z.object({
443
- ...operationBase,
444
- kind: zod.z.literal(kind),
445
- selector: transitionSelector,
446
- transition: boundedPayload
447
- })),
448
- zod.z.object({
449
- ...operationBase,
450
- kind: zod.z.literal("removeTransition"),
451
- selector: transitionSelector
452
- }),
453
- ...[
454
- "addLookGear",
455
- "updateLookGear",
456
- "addGear",
457
- "updateGear"
458
- ].map((kind) => zod.z.object({
459
- ...operationBase,
460
- kind: zod.z.literal(kind),
461
- selector: displayNodeSelector.extend({
462
- kind: identifier,
463
- controllerName: identifier
464
- }),
465
- gear: boundedPayload
466
- })),
467
- ...["removeLookGear", "removeGear"].map((kind) => zod.z.object({
468
- ...operationBase,
469
- kind: zod.z.literal(kind),
470
- selector: displayNodeSelector.extend({
471
- kind: identifier,
472
- controllerName: identifier
473
- })
474
- }))
475
- ]);
476
- const project = zod.z.object({
477
- projectId: identifier,
478
- projectType: zod.z.number().int(),
479
- version: zod.z.string().max(256),
480
- branches: zod.z.array(zod.z.string().max(256)).max(256),
481
- settings: boundedPayload,
482
- packages: zod.z.array(zod.z.object({
483
- id: identifier,
484
- name: identifier,
485
- compressPNG: zod.z.boolean().nullable(),
486
- jpegQuality: zod.z.number().finite().nullable(),
487
- publish: boundedPayload.nullable(),
488
- branchNames: zod.z.array(zod.z.string().max(256)).max(256),
489
- folders: zod.z.array(boundedPayload).max(1e4),
490
- resources: zod.z.array(boundedPayload).max(1e5)
491
- })).max(1e3)
492
- });
493
- const OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA = zod.z.object({ backendResult: zod.z.discriminatedUnion("ok", [zod.z.object({
494
- ok: zod.z.literal(true),
495
- data: boundedPayload,
496
- meta: boundedPayload
497
- }), zod.z.object({
498
- ok: zod.z.literal(false),
499
- error: zod.z.object({
500
- code: identifier,
501
- message: zod.z.string().max(1e6)
502
- }).passthrough(),
503
- meta: boundedPayload,
504
- session: boundedPayload.optional()
505
- })]) });
506
- const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
262
+ //#region src/tool-metadata.ts
263
+ const OPENFAIRYGUI_BACKEND_TOOL_METADATA = [
507
264
  {
508
265
  name: "openfairygui_backend_get_capabilities",
509
266
  backendMethod: "getCapabilities",
510
267
  title: "Get Backend Capabilities",
511
268
  description: "Return the OpenFairyGUI backend capability, version, and service-plane snapshot.",
512
- inputSchema: zod.z.object({}),
513
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
514
269
  annotations: {
515
270
  readOnlyHint: true,
516
271
  idempotentHint: true,
@@ -522,8 +277,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
522
277
  backendMethod: "openSession",
523
278
  title: "Open Backend Session",
524
279
  description: "Open a FairyGUI project through BackendRuntime and acquire its backend-local session lock.",
525
- inputSchema: zod.z.object({ projectPath: zod.z.string().min(1) }),
526
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
527
280
  annotations: {
528
281
  readOnlyHint: false,
529
282
  idempotentHint: false,
@@ -535,13 +288,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
535
288
  backendMethod: "openProjectSession",
536
289
  title: "Open Project Session",
537
290
  description: "Open a browser-safe backend session from an already loaded UAM project without filesystem access.",
538
- inputSchema: zod.z.object({
539
- project,
540
- sessionId: zod.z.string().min(1).optional(),
541
- canonicalProjectPath: zod.z.string().min(1).optional(),
542
- canonicalPathKey: zod.z.string().min(1).optional()
543
- }),
544
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
545
291
  annotations: {
546
292
  readOnlyHint: false,
547
293
  idempotentHint: false,
@@ -553,8 +299,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
553
299
  backendMethod: "getSession",
554
300
  title: "Get Backend Session",
555
301
  description: "Return a backend session snapshot by session id.",
556
- inputSchema: zod.z.object({ sessionId }),
557
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
558
302
  annotations: {
559
303
  readOnlyHint: true,
560
304
  idempotentHint: true,
@@ -566,8 +310,41 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
566
310
  backendMethod: "getProjectOutline",
567
311
  title: "Get Project Outline",
568
312
  description: "Return a revision-bound project/package/resource/component identity outline without source bytes or full property payloads.",
569
- inputSchema: zod.z.object({ sessionId }),
570
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
313
+ annotations: {
314
+ readOnlyHint: true,
315
+ idempotentHint: true,
316
+ openWorldHint: false
317
+ }
318
+ },
319
+ {
320
+ name: "openfairygui_backend_query_entity",
321
+ backendMethod: "queryEntity",
322
+ title: "Query Entity Properties",
323
+ description: "Read revision-bound project/package settings, resource, component-property, display-node, controller (including pages/actions), or transition (including items) snapshots. Project queries use only kind; other queries use formal selectors. Settings snapshots include the complete settings payload for updateProjectSettings/updatePackageSettings. No source bytes; fixed projection with explicit response limits.",
324
+ annotations: {
325
+ readOnlyHint: true,
326
+ idempotentHint: true,
327
+ openWorldHint: false
328
+ }
329
+ },
330
+ {
331
+ name: "openfairygui_backend_read_session_state",
332
+ backendMethod: "readSessionState",
333
+ title: "Read Session State",
334
+ description: "Read a detached copy of the currently committed public UAM model without primary asset sourceBytes, with revision, dirty state and source-read diagnostics. Optional expectedRevision rejects stale reads. Does not hydrate, write, reserve history or guarantee downstream usability. Complete tool response is limited to 16 MiB.",
335
+ maxResponseBytes: 16777216,
336
+ annotations: {
337
+ readOnlyHint: true,
338
+ idempotentHint: true,
339
+ openWorldHint: false
340
+ }
341
+ },
342
+ {
343
+ name: "openfairygui_backend_read_resource_bytes",
344
+ backendMethod: "readResourceBytes",
345
+ title: "Read Resource Bytes",
346
+ description: "Read a detached copy of one asset resource primary sourceBytes already held in the session, using exact packageId/resourceId and the required model edit revision. No filesystem hydration or auxiliary-file discovery. Stale reads require restarting the model/bytes read. Complete tool response is limited to 16 MiB.",
347
+ maxResponseBytes: 16777216,
571
348
  annotations: {
572
349
  readOnlyHint: true,
573
350
  idempotentHint: true,
@@ -579,8 +356,17 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
579
356
  backendMethod: "validateSession",
580
357
  title: "Validate Project Session",
581
358
  description: "Validate the current session project structure, references, paths, and available source bytes without writing files.",
582
- inputSchema: zod.z.object({ sessionId }),
583
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
359
+ annotations: {
360
+ readOnlyHint: true,
361
+ idempotentHint: true,
362
+ openWorldHint: false
363
+ }
364
+ },
365
+ {
366
+ name: "openfairygui_backend_preflight_transaction",
367
+ backendMethod: "preflightTransaction",
368
+ title: "Preview UAM Transaction",
369
+ description: "Execute a revision-checked operation batch on an isolated project snapshot and discard the result. Returns the base revision and Core diagnostics; does not write, reserve a revision, or guarantee a later apply/save.",
584
370
  annotations: {
585
371
  readOnlyHint: true,
586
372
  idempotentHint: true,
@@ -592,12 +378,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
592
378
  backendMethod: "applyTransaction",
593
379
  title: "Apply UAM Transaction",
594
380
  description: "Apply a bounded, revision-checked UAM operation batch using the Core transaction discriminants.",
595
- inputSchema: zod.z.object({
596
- sessionId,
597
- expectedRevision,
598
- operations: zod.z.array(operation).min(1).max(1e3)
599
- }),
600
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
601
381
  annotations: {
602
382
  readOnlyHint: false,
603
383
  destructiveHint: true,
@@ -610,14 +390,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
610
390
  backendMethod: "saveSession",
611
391
  title: "Save Backend Session",
612
392
  description: "Write the current backend session through its coordinated save path; Node uses an atomic staged directory swap.",
613
- inputSchema: zod.z.object({
614
- sessionId,
615
- expectedRevision: expectedRevision.optional(),
616
- targetPath: zod.z.string().min(1).optional(),
617
- force: zod.z.boolean().optional(),
618
- mode: zod.z.literal("materializeCleanSession").optional()
619
- }),
620
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
621
393
  annotations: {
622
394
  readOnlyHint: false,
623
395
  destructiveHint: true,
@@ -630,13 +402,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
630
402
  backendMethod: "materializeSession",
631
403
  title: "Materialize Backend Session",
632
404
  description: "Force materialize the current backend session project through the configured project storage without requiring a dirty edit revision.",
633
- inputSchema: zod.z.object({
634
- sessionId,
635
- expectedRevision: expectedRevision.optional(),
636
- mode: zod.z.literal("fullProject").optional(),
637
- reason: zod.z.string().min(1).optional()
638
- }),
639
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
640
405
  annotations: {
641
406
  readOnlyHint: false,
642
407
  destructiveHint: true,
@@ -649,8 +414,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
649
414
  backendMethod: "closeSession",
650
415
  title: "Close Backend Session",
651
416
  description: "Close a backend session and release its backend-local session lock.",
652
- inputSchema: zod.z.object({ sessionId }),
653
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
654
417
  annotations: {
655
418
  readOnlyHint: false,
656
419
  idempotentHint: false,
@@ -662,12 +425,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
662
425
  backendMethod: "getEvents",
663
426
  title: "Get Runtime Events",
664
427
  description: "Poll backend runtime events for a session using the backend P2 event cursor contract.",
665
- inputSchema: zod.z.object({
666
- sessionId,
667
- after: zod.z.string().optional(),
668
- limit
669
- }),
670
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
671
428
  annotations: {
672
429
  readOnlyHint: true,
673
430
  idempotentHint: true,
@@ -679,11 +436,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
679
436
  backendMethod: "getJob",
680
437
  title: "Get Runtime Job",
681
438
  description: "Return a backend runtime job snapshot by session and backend-local job id.",
682
- inputSchema: zod.z.object({
683
- sessionId,
684
- jobId
685
- }),
686
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
687
439
  annotations: {
688
440
  readOnlyHint: true,
689
441
  idempotentHint: true,
@@ -695,21 +447,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
695
447
  backendMethod: "listJobs",
696
448
  title: "List Runtime Jobs",
697
449
  description: "List backend runtime jobs for a session with backend P2 status/kind filters.",
698
- inputSchema: zod.z.object({
699
- sessionId,
700
- status: zod.z.enum([
701
- "queued",
702
- "running",
703
- "completed",
704
- "failed",
705
- "cancelled",
706
- "active",
707
- "terminal"
708
- ]).optional(),
709
- kind: zod.z.literal("cache.refresh").optional(),
710
- limit
711
- }),
712
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
713
450
  annotations: {
714
451
  readOnlyHint: true,
715
452
  idempotentHint: true,
@@ -721,11 +458,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
721
458
  backendMethod: "cancelJob",
722
459
  title: "Cancel Runtime Job",
723
460
  description: "Request cooperative cancellation for a backend runtime job.",
724
- inputSchema: zod.z.object({
725
- sessionId,
726
- jobId
727
- }),
728
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
729
461
  annotations: {
730
462
  readOnlyHint: false,
731
463
  idempotentHint: false,
@@ -737,8 +469,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
737
469
  backendMethod: "getCacheSnapshot",
738
470
  title: "Get Cache Snapshot",
739
471
  description: "Return the backend P2 derived read-only cache snapshot for a session.",
740
- inputSchema: zod.z.object({ sessionId }),
741
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
742
472
  annotations: {
743
473
  readOnlyHint: true,
744
474
  idempotentHint: true,
@@ -750,15 +480,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
750
480
  backendMethod: "refreshCache",
751
481
  title: "Refresh Cache",
752
482
  description: "Create a backend P2 cache.refresh job for the session cache snapshot.",
753
- inputSchema: zod.z.object({
754
- sessionId,
755
- reason: zod.z.enum([
756
- "manual",
757
- "session_open",
758
- "after_save"
759
- ]).optional()
760
- }),
761
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
762
483
  annotations: {
763
484
  readOnlyHint: false,
764
485
  idempotentHint: false,
@@ -767,9 +488,62 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
767
488
  }
768
489
  ];
769
490
  //#endregion
491
+ //#region src/tool-definitions.ts
492
+ const OPENFAIRYGUI_BACKEND_TOOL_PREFIX = "openfairygui_backend_";
493
+ const OPENFAIRYGUI_BACKEND_TOOL_NAMES = OPENFAIRYGUI_BACKEND_TOOL_METADATA.map((entry) => entry.name);
494
+ function isOpenFairyGuiMcpPayloadWithinBudget(root) {
495
+ const pending = [{
496
+ value: root,
497
+ depth: 0
498
+ }];
499
+ let nodes = 0;
500
+ while (pending.length > 0) {
501
+ const { value, depth } = pending.pop();
502
+ nodes += 1;
503
+ if (nodes > 1e5 || depth > 32) return false;
504
+ if (value === null || typeof value === "boolean") continue;
505
+ if (typeof value === "number") {
506
+ if (!Number.isFinite(value)) return false;
507
+ continue;
508
+ }
509
+ if (typeof value === "string") {
510
+ if (value.length > 1e6) return false;
511
+ continue;
512
+ }
513
+ if (value instanceof Uint8Array) {
514
+ if (value.byteLength > 8 * 1024 * 1024) return false;
515
+ continue;
516
+ }
517
+ if (Array.isArray(value)) {
518
+ if (value.length > 1e4) return false;
519
+ for (const child of value) pending.push({
520
+ value: child,
521
+ depth: depth + 1
522
+ });
523
+ continue;
524
+ }
525
+ if (typeof value !== "object") return false;
526
+ const entries = Object.entries(value);
527
+ if (entries.length > 1e4 || entries.some(([key]) => key.length > 256)) return false;
528
+ for (const [, child] of entries) pending.push({
529
+ value: child,
530
+ depth: depth + 1
531
+ });
532
+ }
533
+ return true;
534
+ }
535
+ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = OPENFAIRYGUI_BACKEND_TOOL_METADATA.map((metadata) => {
536
+ const contract = CONTRACT_SNAPSHOT.tools[metadata.backendMethod];
537
+ return {
538
+ ...metadata,
539
+ inputSchema: contractObjectSchema(contract.input),
540
+ outputSchema: contractObjectSchema(contract.output)
541
+ };
542
+ });
543
+ //#endregion
770
544
  //#region src/tool-handler.ts
771
- function jsonResult(payload, isError = false) {
772
- const text = JSON.stringify(payload, null, 2);
545
+ function jsonResult(payload, isError = false, compact = false) {
546
+ const text = JSON.stringify(payload, (_key, value) => value instanceof Uint8Array ? [...value] : value, compact ? void 0 : 2);
773
547
  const wirePayload = JSON.parse(text);
774
548
  return {
775
549
  content: [{
@@ -803,113 +577,32 @@ function unhandledBackendFailure(startedAt) {
803
577
  }
804
578
  async function callOpenFairyGuiBackendTool(runtime, name, input) {
805
579
  if (!isOpenFairyGuiMcpPayloadWithinBudget(input)) throw new RangeError("MCP input exceeds the depth, node, key, string, or byte budget.");
580
+ const definition = OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS.find((entry) => entry.name === name);
581
+ if (!definition) throw new RangeError(`Unknown OpenFairyGUI backend MCP tool: ${name}`);
582
+ const decoded = decodeToolBytes(definition.inputSchema.parse(input), CONTRACT_SNAPSHOT.tools[definition.backendMethod].bytePaths);
806
583
  const startedAt = Date.now();
807
- let result;
808
584
  try {
809
- switch (name) {
810
- case "openfairygui_backend_get_capabilities":
811
- result = runtime.getCapabilities();
812
- break;
813
- case "openfairygui_backend_open_session":
814
- result = await runtime.openSession({ projectPath: String(input.projectPath) });
815
- break;
816
- case "openfairygui_backend_open_project_session":
817
- result = runtime.openProjectSession({
818
- project: input.project,
819
- sessionId: input.sessionId === void 0 ? void 0 : String(input.sessionId),
820
- canonicalProjectPath: input.canonicalProjectPath === void 0 ? void 0 : String(input.canonicalProjectPath),
821
- canonicalPathKey: input.canonicalPathKey === void 0 ? void 0 : String(input.canonicalPathKey)
822
- });
823
- break;
824
- case "openfairygui_backend_get_session":
825
- result = runtime.getSession({ sessionId: String(input.sessionId) });
826
- break;
827
- case "openfairygui_backend_get_project_outline":
828
- result = runtime.getProjectOutline({ sessionId: String(input.sessionId) });
829
- break;
830
- case "openfairygui_backend_validate_session":
831
- result = runtime.validateSession({ sessionId: String(input.sessionId) });
832
- break;
833
- case "openfairygui_backend_apply_transaction": {
834
- const operations = input.operations.map((operation) => operation.kind === "replaceResourceBytes" ? {
835
- ...operation,
836
- sourceBytes: new Uint8Array(operation.sourceBytes)
837
- } : operation);
838
- result = await runtime.applyTransaction({
839
- sessionId: String(input.sessionId),
840
- expectedRevision: Number(input.expectedRevision),
841
- operations
842
- });
843
- break;
585
+ const result = await Reflect.apply(runtime[definition.backendMethod], runtime, definition.backendMethod === "getCapabilities" ? [] : [decoded]);
586
+ let response = jsonResult(result, isBackendFailure(result), definition.maxResponseBytes !== void 0);
587
+ if (definition.maxResponseBytes !== void 0 && new TextEncoder().encode(JSON.stringify(response)).byteLength > definition.maxResponseBytes) response = jsonResult({
588
+ ...unhandledBackendFailure(startedAt),
589
+ error: {
590
+ code: "mcp_response_budget_exceeded",
591
+ message: "The complete MCP tool response exceeds its byte limit.",
592
+ maxBytes: definition.maxResponseBytes
844
593
  }
845
- case "openfairygui_backend_save_session":
846
- result = await runtime.saveSession({
847
- sessionId: String(input.sessionId),
848
- expectedRevision: input.expectedRevision === void 0 ? void 0 : Number(input.expectedRevision),
849
- targetPath: input.targetPath === void 0 ? void 0 : String(input.targetPath),
850
- force: input.force === void 0 ? void 0 : Boolean(input.force),
851
- mode: input.mode
852
- });
853
- break;
854
- case "openfairygui_backend_materialize_session":
855
- result = await runtime.materializeSession({
856
- sessionId: String(input.sessionId),
857
- expectedRevision: input.expectedRevision === void 0 ? void 0 : Number(input.expectedRevision),
858
- mode: input.mode,
859
- reason: input.reason === void 0 ? void 0 : String(input.reason)
860
- });
861
- break;
862
- case "openfairygui_backend_close_session":
863
- result = await runtime.closeSession({ sessionId: String(input.sessionId) });
864
- break;
865
- case "openfairygui_backend_get_events":
866
- result = runtime.getEvents({
867
- sessionId: String(input.sessionId),
868
- after: input.after === void 0 ? void 0 : String(input.after),
869
- limit: input.limit === void 0 ? void 0 : Number(input.limit)
870
- });
871
- break;
872
- case "openfairygui_backend_get_job":
873
- result = runtime.getJob({
874
- sessionId: String(input.sessionId),
875
- jobId: String(input.jobId)
876
- });
877
- break;
878
- case "openfairygui_backend_list_jobs":
879
- result = runtime.listJobs({
880
- sessionId: String(input.sessionId),
881
- status: input.status,
882
- kind: input.kind,
883
- limit: input.limit === void 0 ? void 0 : Number(input.limit)
884
- });
885
- break;
886
- case "openfairygui_backend_cancel_job":
887
- result = runtime.cancelJob({
888
- sessionId: String(input.sessionId),
889
- jobId: String(input.jobId)
890
- });
891
- break;
892
- case "openfairygui_backend_get_cache_snapshot":
893
- result = runtime.getCacheSnapshot({ sessionId: String(input.sessionId) });
894
- break;
895
- case "openfairygui_backend_refresh_cache":
896
- result = runtime.refreshCache({
897
- sessionId: String(input.sessionId),
898
- reason: input.reason
899
- });
900
- break;
901
- default: throw new Error(`Unknown OpenFairyGUI backend MCP tool: ${name}`);
902
- }
594
+ }, true);
595
+ definition.outputSchema.parse(response.structuredContent);
596
+ return response;
903
597
  } catch {
904
598
  return jsonResult(unhandledBackendFailure(startedAt), true);
905
599
  }
906
- return jsonResult(result, isBackendFailure(result));
907
600
  }
908
601
  //#endregion
909
602
  //#region src/server.ts
910
603
  const require$1 = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
911
604
  function getInjectedPackageVersion() {
912
- const version = "0.3.1";
605
+ const version = "0.5.0-alpha.1";
913
606
  return typeof version === "string" && true ? version : null;
914
607
  }
915
608
  function readPackageVersion() {
@@ -928,17 +621,39 @@ function createOpenFairyGuiMcpServer(options = {}) {
928
621
  name: options.name ?? "openfairygui-mcp",
929
622
  version: options.version ?? PACKAGE_VERSION
930
623
  });
931
- for (const definition of OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS) server.registerTool(definition.name, {
932
- title: definition.title,
933
- description: definition.description,
934
- inputSchema: definition.inputSchema,
935
- outputSchema: definition.outputSchema,
936
- annotations: definition.annotations,
937
- _meta: {
938
- "openfairygui/backendMethod": definition.backendMethod,
939
- "openfairygui/adapter": "thin-backend-p2"
940
- }
941
- }, async (args) => callOpenFairyGuiBackendTool(runtime, definition.name, args));
624
+ const tools = [];
625
+ for (const definition of OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS) {
626
+ const metadata = {
627
+ name: definition.name,
628
+ title: definition.title,
629
+ description: definition.description,
630
+ annotations: definition.annotations,
631
+ _meta: {
632
+ "openfairygui/backendMethod": definition.backendMethod,
633
+ "openfairygui/adapter": "thin-backend-p2",
634
+ "openfairygui/contractDigest": CONTRACT_SNAPSHOT.digest
635
+ }
636
+ };
637
+ server.registerTool(definition.name, {
638
+ ...metadata,
639
+ inputSchema: definition.inputSchema,
640
+ outputSchema: definition.outputSchema
641
+ }, async (args) => callOpenFairyGuiBackendTool(runtime, definition.name, args));
642
+ tools.push(_modelcontextprotocol_sdk_types_js.ToolSchema.parse({
643
+ ...metadata,
644
+ inputSchema: zod.z.toJSONSchema(definition.inputSchema, {
645
+ target: "draft-07",
646
+ io: "input",
647
+ reused: "ref"
648
+ }),
649
+ outputSchema: zod.z.toJSONSchema(definition.outputSchema, {
650
+ target: "draft-07",
651
+ io: "output",
652
+ reused: "ref"
653
+ })
654
+ }));
655
+ }
656
+ server.server.setRequestHandler(_modelcontextprotocol_sdk_types_js.ListToolsRequestSchema, () => ({ tools: structuredClone(tools) }));
942
657
  registerOpenFairyGuiBackendResources(server, runtime);
943
658
  registerOpenFairyGuiBackendPrompts(server);
944
659
  return server;
@@ -990,16 +705,16 @@ Object.defineProperty(exports, "OPENFAIRYGUI_BACKEND_TOOL_NAMES", {
990
705
  return OPENFAIRYGUI_BACKEND_TOOL_NAMES;
991
706
  }
992
707
  });
993
- Object.defineProperty(exports, "OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA", {
708
+ Object.defineProperty(exports, "OPENFAIRYGUI_BACKEND_TOOL_PREFIX", {
994
709
  enumerable: true,
995
710
  get: function() {
996
- return OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA;
711
+ return OPENFAIRYGUI_BACKEND_TOOL_PREFIX;
997
712
  }
998
713
  });
999
- Object.defineProperty(exports, "OPENFAIRYGUI_BACKEND_TOOL_PREFIX", {
714
+ Object.defineProperty(exports, "__toESM", {
1000
715
  enumerable: true,
1001
716
  get: function() {
1002
- return OPENFAIRYGUI_BACKEND_TOOL_PREFIX;
717
+ return __toESM;
1003
718
  }
1004
719
  });
1005
720
  Object.defineProperty(exports, "callOpenFairyGuiBackendTool", {