@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.
- package/README.md +9 -1
- package/dist/index.cjs +26 -2
- package/dist/index.d.cts +65 -640
- package/dist/index.d.mts +66 -641
- package/dist/index.mjs +2 -2
- package/dist/{stdio-HOHWH0x4.mjs → stdio-BNobuRxx.mjs} +243 -528
- package/dist/{stdio-N6Yxacus.cjs → stdio-CB98zdOf.cjs} +246 -531
- package/dist/stdio.cjs +1 -1
- package/dist/stdio.mjs +1 -1
- package/package.json +5 -5
- package/src/contract-schema.ts +29 -0
- package/src/index.ts +6 -1
- package/src/prompt-definitions.ts +5 -0
- package/src/resource-definitions.ts +59 -0
- package/src/server.ts +22 -7
- package/src/tool-definitions.ts +17 -306
- package/src/tool-handler.ts +22 -133
- package/src/tool-metadata.ts +181 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { ListToolsRequestSchema, ToolSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
3
4
|
import { createNodeBackendRuntime } from "@openfairygui/backend/node";
|
|
4
|
-
import { BACKEND_CAPABILITY_SCHEMA_VERSION, BACKEND_CONTRACT_VERSION } from "@openfairygui/backend";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
+
import { BACKEND_CAPABILITY_SCHEMA_VERSION, BACKEND_CONTRACT_VERSION, BACKEND_DIAGNOSTICS_URI, BACKEND_DIAGNOSTIC_TEMPLATE, getBackendDiagnosticCatalog, getBackendDiagnosticGuide } from "@openfairygui/backend";
|
|
7
|
+
import { OPENFAIRYGUI_DOCS_INDEX_URI, OPENFAIRYGUI_OPERATION_CATALOG_URI, OPENFAIRYGUI_OPERATION_SCHEMA_TEMPLATE, getInstalledContractSnapshot, getInstalledDocumentationIndex, getOpenFairyGuiOperationCatalog, getOpenFairyGuiOperationSchema, readInstalledDocumentation } from "@openfairygui/backend/docs";
|
|
6
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
9
|
import path from "node:path";
|
|
8
10
|
import { pathToFileURL } from "node:url";
|
|
@@ -53,6 +55,11 @@ const OPENFAIRYGUI_BACKEND_PROMPT_DEFINITIONS = [
|
|
|
53
55
|
description: "Guide a client through backend-owned revision checks without inventing operation grammar.",
|
|
54
56
|
text: [
|
|
55
57
|
"Use openfairygui_backend_get_session to read the current revision before mutation.",
|
|
58
|
+
"Use openfairygui_backend_get_project_outline for identities, then openfairygui_backend_query_entity for current properties at the returned revision.",
|
|
59
|
+
"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.",
|
|
60
|
+
"Read openfairygui://contracts/operations and openfairygui://contracts/operations/{kind} for the current operation names and exact JSON parameters.",
|
|
61
|
+
"Call openfairygui_backend_preflight_transaction with the queried revision and planned operations to execute and discard an isolated preview; inspect the backend diagnostics.",
|
|
62
|
+
"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.",
|
|
56
63
|
"Call openfairygui_backend_apply_transaction with sessionId, expectedRevision, and backend/UAM-owned operations.",
|
|
57
64
|
"If the backend returns a stale revision error, refresh the session snapshot and re-plan against the new revision.",
|
|
58
65
|
"Do not invent selector grammar, transaction grammar, or operation payload semantics at the MCP layer."
|
|
@@ -96,6 +103,32 @@ function registerOpenFairyGuiBackendPrompts(server) {
|
|
|
96
103
|
}, () => promptResult(definition.text));
|
|
97
104
|
}
|
|
98
105
|
//#endregion
|
|
106
|
+
//#region src/contract-schema.ts
|
|
107
|
+
const CONTRACT_SNAPSHOT = getInstalledContractSnapshot();
|
|
108
|
+
function contractObjectSchema(schema) {
|
|
109
|
+
const result = z.fromJSONSchema({
|
|
110
|
+
...schema,
|
|
111
|
+
$defs: CONTRACT_SNAPSHOT.$defs
|
|
112
|
+
});
|
|
113
|
+
if (!(result instanceof z.ZodObject)) throw new TypeError("Tool contract must be an object");
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
/** Decode only generated Uint8Array locations; arbitrary JSON metadata is not rewritten. */
|
|
117
|
+
function decodeToolBytes(input, paths) {
|
|
118
|
+
if (!paths.length) return input;
|
|
119
|
+
const result = structuredClone(input);
|
|
120
|
+
function visit(value, parts) {
|
|
121
|
+
if (!parts.length) return value === null ? value : Uint8Array.from(value);
|
|
122
|
+
if (!value || typeof value !== "object") return value;
|
|
123
|
+
const [key, ...rest] = parts;
|
|
124
|
+
const record = value;
|
|
125
|
+
for (const name of key === "*" ? Object.keys(record) : [key]) if (Object.hasOwn(record, name)) record[name] = visit(record[name], rest);
|
|
126
|
+
return value;
|
|
127
|
+
}
|
|
128
|
+
for (const parts of paths) visit(result, parts);
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
99
132
|
//#region src/resource-definitions.ts
|
|
100
133
|
const JSON_MIME_TYPE = "application/json";
|
|
101
134
|
function firstVariable(value) {
|
|
@@ -110,12 +143,69 @@ function jsonResource(uri, backendResult) {
|
|
|
110
143
|
}
|
|
111
144
|
const OPENFAIRYGUI_BACKEND_CAPABILITIES_RESOURCE_URI = "openfairygui://backend/capabilities";
|
|
112
145
|
const OPENFAIRYGUI_BACKEND_RESOURCE_TEMPLATES = [
|
|
146
|
+
"openfairygui://docs/methods/{method}",
|
|
147
|
+
"openfairygui://docs/cli/{command}",
|
|
148
|
+
BACKEND_DIAGNOSTIC_TEMPLATE,
|
|
149
|
+
OPENFAIRYGUI_OPERATION_SCHEMA_TEMPLATE,
|
|
113
150
|
"openfairygui://backend/session/{sessionId}",
|
|
114
151
|
"openfairygui://backend/session/{sessionId}/outline",
|
|
115
152
|
"openfairygui://backend/cache/{sessionId}",
|
|
116
153
|
"openfairygui://backend/job/{sessionId}/{jobId}"
|
|
117
154
|
];
|
|
118
155
|
function registerOpenFairyGuiBackendResources(server, runtime) {
|
|
156
|
+
server.registerResource("openfairygui_docs_index", OPENFAIRYGUI_DOCS_INDEX_URI, {
|
|
157
|
+
title: "Installed Documentation",
|
|
158
|
+
description: "Offline documentation IDs, URIs, installed package version and contract digest shared with the CLI.",
|
|
159
|
+
mimeType: JSON_MIME_TYPE
|
|
160
|
+
}, (uri) => jsonResource(uri, getInstalledDocumentationIndex()));
|
|
161
|
+
function installedDocument(uri, id) {
|
|
162
|
+
const document = readInstalledDocumentation(id);
|
|
163
|
+
return { contents: [{
|
|
164
|
+
uri: uri.toString(),
|
|
165
|
+
mimeType: document.mimeType,
|
|
166
|
+
text: document.text
|
|
167
|
+
}] };
|
|
168
|
+
}
|
|
169
|
+
for (const id of [
|
|
170
|
+
"workflow",
|
|
171
|
+
"restore-limits",
|
|
172
|
+
"skill",
|
|
173
|
+
"contracts"
|
|
174
|
+
]) server.registerResource(`openfairygui_docs_${id}`, `openfairygui://docs/${id}`, {
|
|
175
|
+
title: `Installed ${id}`,
|
|
176
|
+
description: "Read the installed-version corpus without repository or network access.",
|
|
177
|
+
mimeType: id === "contracts" ? JSON_MIME_TYPE : "text/markdown"
|
|
178
|
+
}, (uri) => installedDocument(uri, id));
|
|
179
|
+
server.registerResource("openfairygui_docs_method", new ResourceTemplate("openfairygui://docs/methods/{method}", { list: void 0 }), {
|
|
180
|
+
title: "Installed Method Contract",
|
|
181
|
+
description: "Read self-contained Backend/MCP wire input/output schemas and metadata.",
|
|
182
|
+
mimeType: JSON_MIME_TYPE
|
|
183
|
+
}, (uri, variables) => installedDocument(uri, `methods/${firstVariable(variables.method)}`));
|
|
184
|
+
server.registerResource("openfairygui_diagnostic_catalog", BACKEND_DIAGNOSTICS_URI, {
|
|
185
|
+
title: "Diagnostic Recovery Catalog",
|
|
186
|
+
description: "Complete formal diagnostic ownership and recovery guidance; never automatic repair.",
|
|
187
|
+
mimeType: JSON_MIME_TYPE
|
|
188
|
+
}, (uri) => jsonResource(uri, getBackendDiagnosticCatalog()));
|
|
189
|
+
server.registerResource("openfairygui_docs_cli", new ResourceTemplate("openfairygui://docs/cli/{command}", { list: void 0 }), {
|
|
190
|
+
title: "Installed CLI Output Contract",
|
|
191
|
+
description: "Read a generated, self-contained CLI JSON envelope schema.",
|
|
192
|
+
mimeType: JSON_MIME_TYPE
|
|
193
|
+
}, (uri, variables) => installedDocument(uri, `cli/${decodeURIComponent(firstVariable(variables.command))}`));
|
|
194
|
+
server.registerResource("openfairygui_diagnostic_guide", new ResourceTemplate(BACKEND_DIAGNOSTIC_TEMPLATE, { list: void 0 }), {
|
|
195
|
+
title: "Diagnostic Recovery Guide",
|
|
196
|
+
description: "Read the recovery boundary for one stable diagnostic code.",
|
|
197
|
+
mimeType: JSON_MIME_TYPE
|
|
198
|
+
}, (uri, variables) => jsonResource(uri, getBackendDiagnosticGuide(firstVariable(variables.code))));
|
|
199
|
+
server.registerResource("openfairygui_operation_catalog", OPENFAIRYGUI_OPERATION_CATALOG_URI, {
|
|
200
|
+
title: "UAM Operation Catalog",
|
|
201
|
+
description: "Discover current operations and their generated JSON schemas.",
|
|
202
|
+
mimeType: JSON_MIME_TYPE
|
|
203
|
+
}, (uri) => jsonResource(uri, getOpenFairyGuiOperationCatalog()));
|
|
204
|
+
server.registerResource("openfairygui_operation_schema", new ResourceTemplate(OPENFAIRYGUI_OPERATION_SCHEMA_TEMPLATE, { list: void 0 }), {
|
|
205
|
+
title: "UAM Operation Schema",
|
|
206
|
+
description: "Read the precise Core-derived JSON wire schema for one operation. Structure is not semantic preflight.",
|
|
207
|
+
mimeType: JSON_MIME_TYPE
|
|
208
|
+
}, (uri, variables) => jsonResource(uri, getOpenFairyGuiOperationSchema(firstVariable(variables.kind))));
|
|
119
209
|
server.registerResource("openfairygui_backend_capabilities", OPENFAIRYGUI_BACKEND_CAPABILITIES_RESOURCE_URI, {
|
|
120
210
|
title: "OpenFairyGUI Backend Capabilities",
|
|
121
211
|
description: "Read the backend capability and version envelope as JSON.",
|
|
@@ -146,348 +236,13 @@ function registerOpenFairyGuiBackendResources(server, runtime) {
|
|
|
146
236
|
})));
|
|
147
237
|
}
|
|
148
238
|
//#endregion
|
|
149
|
-
//#region src/tool-
|
|
150
|
-
const
|
|
151
|
-
const OPENFAIRYGUI_BACKEND_TOOL_NAMES = [
|
|
152
|
-
"openfairygui_backend_get_capabilities",
|
|
153
|
-
"openfairygui_backend_open_session",
|
|
154
|
-
"openfairygui_backend_open_project_session",
|
|
155
|
-
"openfairygui_backend_get_session",
|
|
156
|
-
"openfairygui_backend_get_project_outline",
|
|
157
|
-
"openfairygui_backend_validate_session",
|
|
158
|
-
"openfairygui_backend_apply_transaction",
|
|
159
|
-
"openfairygui_backend_save_session",
|
|
160
|
-
"openfairygui_backend_materialize_session",
|
|
161
|
-
"openfairygui_backend_close_session",
|
|
162
|
-
"openfairygui_backend_get_events",
|
|
163
|
-
"openfairygui_backend_get_job",
|
|
164
|
-
"openfairygui_backend_list_jobs",
|
|
165
|
-
"openfairygui_backend_cancel_job",
|
|
166
|
-
"openfairygui_backend_get_cache_snapshot",
|
|
167
|
-
"openfairygui_backend_refresh_cache"
|
|
168
|
-
];
|
|
169
|
-
const sessionId = z.string().min(1);
|
|
170
|
-
const jobId = z.string().min(1);
|
|
171
|
-
const expectedRevision = z.number().int().nonnegative();
|
|
172
|
-
const limit = z.number().int().nonnegative().optional();
|
|
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
|
-
})]) });
|
|
483
|
-
const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
239
|
+
//#region src/tool-metadata.ts
|
|
240
|
+
const OPENFAIRYGUI_BACKEND_TOOL_METADATA = [
|
|
484
241
|
{
|
|
485
242
|
name: "openfairygui_backend_get_capabilities",
|
|
486
243
|
backendMethod: "getCapabilities",
|
|
487
244
|
title: "Get Backend Capabilities",
|
|
488
245
|
description: "Return the OpenFairyGUI backend capability, version, and service-plane snapshot.",
|
|
489
|
-
inputSchema: z.object({}),
|
|
490
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
491
246
|
annotations: {
|
|
492
247
|
readOnlyHint: true,
|
|
493
248
|
idempotentHint: true,
|
|
@@ -499,8 +254,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
499
254
|
backendMethod: "openSession",
|
|
500
255
|
title: "Open Backend Session",
|
|
501
256
|
description: "Open a FairyGUI project through BackendRuntime and acquire its backend-local session lock.",
|
|
502
|
-
inputSchema: z.object({ projectPath: z.string().min(1) }),
|
|
503
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
504
257
|
annotations: {
|
|
505
258
|
readOnlyHint: false,
|
|
506
259
|
idempotentHint: false,
|
|
@@ -512,13 +265,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
512
265
|
backendMethod: "openProjectSession",
|
|
513
266
|
title: "Open Project Session",
|
|
514
267
|
description: "Open a browser-safe backend session from an already loaded UAM project without filesystem access.",
|
|
515
|
-
inputSchema: z.object({
|
|
516
|
-
project,
|
|
517
|
-
sessionId: z.string().min(1).optional(),
|
|
518
|
-
canonicalProjectPath: z.string().min(1).optional(),
|
|
519
|
-
canonicalPathKey: z.string().min(1).optional()
|
|
520
|
-
}),
|
|
521
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
522
268
|
annotations: {
|
|
523
269
|
readOnlyHint: false,
|
|
524
270
|
idempotentHint: false,
|
|
@@ -530,8 +276,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
530
276
|
backendMethod: "getSession",
|
|
531
277
|
title: "Get Backend Session",
|
|
532
278
|
description: "Return a backend session snapshot by session id.",
|
|
533
|
-
inputSchema: z.object({ sessionId }),
|
|
534
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
535
279
|
annotations: {
|
|
536
280
|
readOnlyHint: true,
|
|
537
281
|
idempotentHint: true,
|
|
@@ -543,8 +287,41 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
543
287
|
backendMethod: "getProjectOutline",
|
|
544
288
|
title: "Get Project Outline",
|
|
545
289
|
description: "Return a revision-bound project/package/resource/component identity outline without source bytes or full property payloads.",
|
|
546
|
-
|
|
547
|
-
|
|
290
|
+
annotations: {
|
|
291
|
+
readOnlyHint: true,
|
|
292
|
+
idempotentHint: true,
|
|
293
|
+
openWorldHint: false
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: "openfairygui_backend_query_entity",
|
|
298
|
+
backendMethod: "queryEntity",
|
|
299
|
+
title: "Query Entity Properties",
|
|
300
|
+
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.",
|
|
301
|
+
annotations: {
|
|
302
|
+
readOnlyHint: true,
|
|
303
|
+
idempotentHint: true,
|
|
304
|
+
openWorldHint: false
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: "openfairygui_backend_read_session_state",
|
|
309
|
+
backendMethod: "readSessionState",
|
|
310
|
+
title: "Read Session State",
|
|
311
|
+
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.",
|
|
312
|
+
maxResponseBytes: 16777216,
|
|
313
|
+
annotations: {
|
|
314
|
+
readOnlyHint: true,
|
|
315
|
+
idempotentHint: true,
|
|
316
|
+
openWorldHint: false
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: "openfairygui_backend_read_resource_bytes",
|
|
321
|
+
backendMethod: "readResourceBytes",
|
|
322
|
+
title: "Read Resource Bytes",
|
|
323
|
+
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.",
|
|
324
|
+
maxResponseBytes: 16777216,
|
|
548
325
|
annotations: {
|
|
549
326
|
readOnlyHint: true,
|
|
550
327
|
idempotentHint: true,
|
|
@@ -556,8 +333,17 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
556
333
|
backendMethod: "validateSession",
|
|
557
334
|
title: "Validate Project Session",
|
|
558
335
|
description: "Validate the current session project structure, references, paths, and available source bytes without writing files.",
|
|
559
|
-
|
|
560
|
-
|
|
336
|
+
annotations: {
|
|
337
|
+
readOnlyHint: true,
|
|
338
|
+
idempotentHint: true,
|
|
339
|
+
openWorldHint: false
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: "openfairygui_backend_preflight_transaction",
|
|
344
|
+
backendMethod: "preflightTransaction",
|
|
345
|
+
title: "Preview UAM Transaction",
|
|
346
|
+
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.",
|
|
561
347
|
annotations: {
|
|
562
348
|
readOnlyHint: true,
|
|
563
349
|
idempotentHint: true,
|
|
@@ -569,12 +355,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
569
355
|
backendMethod: "applyTransaction",
|
|
570
356
|
title: "Apply UAM Transaction",
|
|
571
357
|
description: "Apply a bounded, revision-checked UAM operation batch using the Core transaction discriminants.",
|
|
572
|
-
inputSchema: z.object({
|
|
573
|
-
sessionId,
|
|
574
|
-
expectedRevision,
|
|
575
|
-
operations: z.array(operation).min(1).max(1e3)
|
|
576
|
-
}),
|
|
577
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
578
358
|
annotations: {
|
|
579
359
|
readOnlyHint: false,
|
|
580
360
|
destructiveHint: true,
|
|
@@ -587,14 +367,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
587
367
|
backendMethod: "saveSession",
|
|
588
368
|
title: "Save Backend Session",
|
|
589
369
|
description: "Write the current backend session through its coordinated save path; Node uses an atomic staged directory swap.",
|
|
590
|
-
inputSchema: z.object({
|
|
591
|
-
sessionId,
|
|
592
|
-
expectedRevision: expectedRevision.optional(),
|
|
593
|
-
targetPath: z.string().min(1).optional(),
|
|
594
|
-
force: z.boolean().optional(),
|
|
595
|
-
mode: z.literal("materializeCleanSession").optional()
|
|
596
|
-
}),
|
|
597
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
598
370
|
annotations: {
|
|
599
371
|
readOnlyHint: false,
|
|
600
372
|
destructiveHint: true,
|
|
@@ -607,13 +379,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
607
379
|
backendMethod: "materializeSession",
|
|
608
380
|
title: "Materialize Backend Session",
|
|
609
381
|
description: "Force materialize the current backend session project through the configured project storage without requiring a dirty edit revision.",
|
|
610
|
-
inputSchema: z.object({
|
|
611
|
-
sessionId,
|
|
612
|
-
expectedRevision: expectedRevision.optional(),
|
|
613
|
-
mode: z.literal("fullProject").optional(),
|
|
614
|
-
reason: z.string().min(1).optional()
|
|
615
|
-
}),
|
|
616
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
617
382
|
annotations: {
|
|
618
383
|
readOnlyHint: false,
|
|
619
384
|
destructiveHint: true,
|
|
@@ -626,8 +391,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
626
391
|
backendMethod: "closeSession",
|
|
627
392
|
title: "Close Backend Session",
|
|
628
393
|
description: "Close a backend session and release its backend-local session lock.",
|
|
629
|
-
inputSchema: z.object({ sessionId }),
|
|
630
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
631
394
|
annotations: {
|
|
632
395
|
readOnlyHint: false,
|
|
633
396
|
idempotentHint: false,
|
|
@@ -639,12 +402,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
639
402
|
backendMethod: "getEvents",
|
|
640
403
|
title: "Get Runtime Events",
|
|
641
404
|
description: "Poll backend runtime events for a session using the backend P2 event cursor contract.",
|
|
642
|
-
inputSchema: z.object({
|
|
643
|
-
sessionId,
|
|
644
|
-
after: z.string().optional(),
|
|
645
|
-
limit
|
|
646
|
-
}),
|
|
647
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
648
405
|
annotations: {
|
|
649
406
|
readOnlyHint: true,
|
|
650
407
|
idempotentHint: true,
|
|
@@ -656,11 +413,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
656
413
|
backendMethod: "getJob",
|
|
657
414
|
title: "Get Runtime Job",
|
|
658
415
|
description: "Return a backend runtime job snapshot by session and backend-local job id.",
|
|
659
|
-
inputSchema: z.object({
|
|
660
|
-
sessionId,
|
|
661
|
-
jobId
|
|
662
|
-
}),
|
|
663
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
664
416
|
annotations: {
|
|
665
417
|
readOnlyHint: true,
|
|
666
418
|
idempotentHint: true,
|
|
@@ -672,21 +424,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
672
424
|
backendMethod: "listJobs",
|
|
673
425
|
title: "List Runtime Jobs",
|
|
674
426
|
description: "List backend runtime jobs for a session with backend P2 status/kind filters.",
|
|
675
|
-
inputSchema: z.object({
|
|
676
|
-
sessionId,
|
|
677
|
-
status: z.enum([
|
|
678
|
-
"queued",
|
|
679
|
-
"running",
|
|
680
|
-
"completed",
|
|
681
|
-
"failed",
|
|
682
|
-
"cancelled",
|
|
683
|
-
"active",
|
|
684
|
-
"terminal"
|
|
685
|
-
]).optional(),
|
|
686
|
-
kind: z.literal("cache.refresh").optional(),
|
|
687
|
-
limit
|
|
688
|
-
}),
|
|
689
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
690
427
|
annotations: {
|
|
691
428
|
readOnlyHint: true,
|
|
692
429
|
idempotentHint: true,
|
|
@@ -698,11 +435,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
698
435
|
backendMethod: "cancelJob",
|
|
699
436
|
title: "Cancel Runtime Job",
|
|
700
437
|
description: "Request cooperative cancellation for a backend runtime job.",
|
|
701
|
-
inputSchema: z.object({
|
|
702
|
-
sessionId,
|
|
703
|
-
jobId
|
|
704
|
-
}),
|
|
705
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
706
438
|
annotations: {
|
|
707
439
|
readOnlyHint: false,
|
|
708
440
|
idempotentHint: false,
|
|
@@ -714,8 +446,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
714
446
|
backendMethod: "getCacheSnapshot",
|
|
715
447
|
title: "Get Cache Snapshot",
|
|
716
448
|
description: "Return the backend P2 derived read-only cache snapshot for a session.",
|
|
717
|
-
inputSchema: z.object({ sessionId }),
|
|
718
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
719
449
|
annotations: {
|
|
720
450
|
readOnlyHint: true,
|
|
721
451
|
idempotentHint: true,
|
|
@@ -727,15 +457,6 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
727
457
|
backendMethod: "refreshCache",
|
|
728
458
|
title: "Refresh Cache",
|
|
729
459
|
description: "Create a backend P2 cache.refresh job for the session cache snapshot.",
|
|
730
|
-
inputSchema: z.object({
|
|
731
|
-
sessionId,
|
|
732
|
-
reason: z.enum([
|
|
733
|
-
"manual",
|
|
734
|
-
"session_open",
|
|
735
|
-
"after_save"
|
|
736
|
-
]).optional()
|
|
737
|
-
}),
|
|
738
|
-
outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
|
|
739
460
|
annotations: {
|
|
740
461
|
readOnlyHint: false,
|
|
741
462
|
idempotentHint: false,
|
|
@@ -744,9 +465,62 @@ const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
|
|
|
744
465
|
}
|
|
745
466
|
];
|
|
746
467
|
//#endregion
|
|
468
|
+
//#region src/tool-definitions.ts
|
|
469
|
+
const OPENFAIRYGUI_BACKEND_TOOL_PREFIX = "openfairygui_backend_";
|
|
470
|
+
const OPENFAIRYGUI_BACKEND_TOOL_NAMES = OPENFAIRYGUI_BACKEND_TOOL_METADATA.map((entry) => entry.name);
|
|
471
|
+
function isOpenFairyGuiMcpPayloadWithinBudget(root) {
|
|
472
|
+
const pending = [{
|
|
473
|
+
value: root,
|
|
474
|
+
depth: 0
|
|
475
|
+
}];
|
|
476
|
+
let nodes = 0;
|
|
477
|
+
while (pending.length > 0) {
|
|
478
|
+
const { value, depth } = pending.pop();
|
|
479
|
+
nodes += 1;
|
|
480
|
+
if (nodes > 1e5 || depth > 32) return false;
|
|
481
|
+
if (value === null || typeof value === "boolean") continue;
|
|
482
|
+
if (typeof value === "number") {
|
|
483
|
+
if (!Number.isFinite(value)) return false;
|
|
484
|
+
continue;
|
|
485
|
+
}
|
|
486
|
+
if (typeof value === "string") {
|
|
487
|
+
if (value.length > 1e6) return false;
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
if (value instanceof Uint8Array) {
|
|
491
|
+
if (value.byteLength > 8 * 1024 * 1024) return false;
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
if (Array.isArray(value)) {
|
|
495
|
+
if (value.length > 1e4) return false;
|
|
496
|
+
for (const child of value) pending.push({
|
|
497
|
+
value: child,
|
|
498
|
+
depth: depth + 1
|
|
499
|
+
});
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (typeof value !== "object") return false;
|
|
503
|
+
const entries = Object.entries(value);
|
|
504
|
+
if (entries.length > 1e4 || entries.some(([key]) => key.length > 256)) return false;
|
|
505
|
+
for (const [, child] of entries) pending.push({
|
|
506
|
+
value: child,
|
|
507
|
+
depth: depth + 1
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = OPENFAIRYGUI_BACKEND_TOOL_METADATA.map((metadata) => {
|
|
513
|
+
const contract = CONTRACT_SNAPSHOT.tools[metadata.backendMethod];
|
|
514
|
+
return {
|
|
515
|
+
...metadata,
|
|
516
|
+
inputSchema: contractObjectSchema(contract.input),
|
|
517
|
+
outputSchema: contractObjectSchema(contract.output)
|
|
518
|
+
};
|
|
519
|
+
});
|
|
520
|
+
//#endregion
|
|
747
521
|
//#region src/tool-handler.ts
|
|
748
|
-
function jsonResult(payload, isError = false) {
|
|
749
|
-
const text = JSON.stringify(payload,
|
|
522
|
+
function jsonResult(payload, isError = false, compact = false) {
|
|
523
|
+
const text = JSON.stringify(payload, (_key, value) => value instanceof Uint8Array ? [...value] : value, compact ? void 0 : 2);
|
|
750
524
|
const wirePayload = JSON.parse(text);
|
|
751
525
|
return {
|
|
752
526
|
content: [{
|
|
@@ -780,113 +554,32 @@ function unhandledBackendFailure(startedAt) {
|
|
|
780
554
|
}
|
|
781
555
|
async function callOpenFairyGuiBackendTool(runtime, name, input) {
|
|
782
556
|
if (!isOpenFairyGuiMcpPayloadWithinBudget(input)) throw new RangeError("MCP input exceeds the depth, node, key, string, or byte budget.");
|
|
557
|
+
const definition = OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS.find((entry) => entry.name === name);
|
|
558
|
+
if (!definition) throw new RangeError(`Unknown OpenFairyGUI backend MCP tool: ${name}`);
|
|
559
|
+
const decoded = decodeToolBytes(definition.inputSchema.parse(input), CONTRACT_SNAPSHOT.tools[definition.backendMethod].bytePaths);
|
|
783
560
|
const startedAt = Date.now();
|
|
784
|
-
let result;
|
|
785
561
|
try {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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;
|
|
562
|
+
const result = await Reflect.apply(runtime[definition.backendMethod], runtime, definition.backendMethod === "getCapabilities" ? [] : [decoded]);
|
|
563
|
+
let response = jsonResult(result, isBackendFailure(result), definition.maxResponseBytes !== void 0);
|
|
564
|
+
if (definition.maxResponseBytes !== void 0 && new TextEncoder().encode(JSON.stringify(response)).byteLength > definition.maxResponseBytes) response = jsonResult({
|
|
565
|
+
...unhandledBackendFailure(startedAt),
|
|
566
|
+
error: {
|
|
567
|
+
code: "mcp_response_budget_exceeded",
|
|
568
|
+
message: "The complete MCP tool response exceeds its byte limit.",
|
|
569
|
+
maxBytes: definition.maxResponseBytes
|
|
821
570
|
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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
|
-
}
|
|
571
|
+
}, true);
|
|
572
|
+
definition.outputSchema.parse(response.structuredContent);
|
|
573
|
+
return response;
|
|
880
574
|
} catch {
|
|
881
575
|
return jsonResult(unhandledBackendFailure(startedAt), true);
|
|
882
576
|
}
|
|
883
|
-
return jsonResult(result, isBackendFailure(result));
|
|
884
577
|
}
|
|
885
578
|
//#endregion
|
|
886
579
|
//#region src/server.ts
|
|
887
580
|
const require = createRequire(import.meta.url);
|
|
888
581
|
function getInjectedPackageVersion() {
|
|
889
|
-
const version = "0.
|
|
582
|
+
const version = "0.5.0-alpha.1";
|
|
890
583
|
return typeof version === "string" && true ? version : null;
|
|
891
584
|
}
|
|
892
585
|
function readPackageVersion() {
|
|
@@ -905,17 +598,39 @@ function createOpenFairyGuiMcpServer(options = {}) {
|
|
|
905
598
|
name: options.name ?? "openfairygui-mcp",
|
|
906
599
|
version: options.version ?? PACKAGE_VERSION
|
|
907
600
|
});
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
601
|
+
const tools = [];
|
|
602
|
+
for (const definition of OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS) {
|
|
603
|
+
const metadata = {
|
|
604
|
+
name: definition.name,
|
|
605
|
+
title: definition.title,
|
|
606
|
+
description: definition.description,
|
|
607
|
+
annotations: definition.annotations,
|
|
608
|
+
_meta: {
|
|
609
|
+
"openfairygui/backendMethod": definition.backendMethod,
|
|
610
|
+
"openfairygui/adapter": "thin-backend-p2",
|
|
611
|
+
"openfairygui/contractDigest": CONTRACT_SNAPSHOT.digest
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
server.registerTool(definition.name, {
|
|
615
|
+
...metadata,
|
|
616
|
+
inputSchema: definition.inputSchema,
|
|
617
|
+
outputSchema: definition.outputSchema
|
|
618
|
+
}, async (args) => callOpenFairyGuiBackendTool(runtime, definition.name, args));
|
|
619
|
+
tools.push(ToolSchema.parse({
|
|
620
|
+
...metadata,
|
|
621
|
+
inputSchema: z.toJSONSchema(definition.inputSchema, {
|
|
622
|
+
target: "draft-07",
|
|
623
|
+
io: "input",
|
|
624
|
+
reused: "ref"
|
|
625
|
+
}),
|
|
626
|
+
outputSchema: z.toJSONSchema(definition.outputSchema, {
|
|
627
|
+
target: "draft-07",
|
|
628
|
+
io: "output",
|
|
629
|
+
reused: "ref"
|
|
630
|
+
})
|
|
631
|
+
}));
|
|
632
|
+
}
|
|
633
|
+
server.server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: structuredClone(tools) }));
|
|
919
634
|
registerOpenFairyGuiBackendResources(server, runtime);
|
|
920
635
|
registerOpenFairyGuiBackendPrompts(server);
|
|
921
636
|
return server;
|
|
@@ -931,4 +646,4 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
931
646
|
process.exitCode = 1;
|
|
932
647
|
});
|
|
933
648
|
//#endregion
|
|
934
|
-
export { OPENFAIRYGUI_BACKEND_TOOL_NAMES as a,
|
|
649
|
+
export { OPENFAIRYGUI_BACKEND_TOOL_NAMES as a, OPENFAIRYGUI_BACKEND_RESOURCE_TEMPLATES as c, getOpenFairyGuiOperationCatalog as d, getOpenFairyGuiOperationSchema as f, OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS as i, OPENFAIRYGUI_OPERATION_CATALOG_URI as l, OPENFAIRYGUI_BACKEND_PROMPT_NAMES as m, createOpenFairyGuiMcpServer as n, OPENFAIRYGUI_BACKEND_TOOL_PREFIX as o, OPENFAIRYGUI_BACKEND_PROMPT_DEFINITIONS as p, callOpenFairyGuiBackendTool as r, OPENFAIRYGUI_BACKEND_CAPABILITIES_RESOURCE_URI as s, connectOpenFairyGuiMcpStdio as t, OPENFAIRYGUI_OPERATION_SCHEMA_TEMPLATE as u };
|