@openfairygui/mcp 0.3.0 → 0.4.0

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/stdio.cjs CHANGED
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_stdio = require("./stdio-9ewjcCag.cjs");
2
+ const require_stdio = require("./stdio-B0OU-oZC.cjs");
3
3
  exports.connectOpenFairyGuiMcpStdio = require_stdio.connectOpenFairyGuiMcpStdio;
package/dist/stdio.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { t as connectOpenFairyGuiMcpStdio } from "./stdio-48jCJzS3.mjs";
1
+ import { t as connectOpenFairyGuiMcpStdio } from "./stdio-9ka7bvOr.mjs";
2
2
  export { connectOpenFairyGuiMcpStdio };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfairygui/mcp",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "FairyGUI Headless Authoring SDK - MCP server adapter for the backend runtime.",
5
5
  "author": "OpenFairyGUI Contributors",
6
6
  "license": "MIT",
@@ -13,6 +13,9 @@
13
13
  "bugs": {
14
14
  "url": "https://github.com/OpenFairyGUI/OpenFairyGUI/issues"
15
15
  },
16
+ "engines": {
17
+ "node": ">=22"
18
+ },
16
19
  "type": "module",
17
20
  "sideEffects": false,
18
21
  "main": "./dist/index.cjs",
@@ -58,13 +61,13 @@
58
61
  "dependencies": {
59
62
  "@modelcontextprotocol/sdk": "^1.29.0",
60
63
  "zod": "^4.3.6",
61
- "@openfairygui/backend": "0.3.0"
64
+ "@openfairygui/backend": "0.4.0"
62
65
  },
63
66
  "devDependencies": {
64
67
  "ava": "^7.0.0",
65
68
  "tsx": "^4.0.0",
66
- "@openfairygui/test-utils": "0.2.0-alpha.10",
67
- "@openfairygui/core": "0.3.0"
69
+ "@openfairygui/core": "0.4.0",
70
+ "@openfairygui/test-utils": "0.3.0"
68
71
  },
69
72
  "ava": {
70
73
  "extensions": {
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ import { getInstalledContractSnapshot, type ContractSchema } from '@openfairygui/backend/docs';
3
+ export { getOpenFairyGuiOperationCatalog, getOpenFairyGuiOperationSchema, OPENFAIRYGUI_OPERATION_CATALOG_URI, OPENFAIRYGUI_OPERATION_SCHEMA_TEMPLATE } from '@openfairygui/backend/docs';
4
+
5
+ export const CONTRACT_SNAPSHOT = getInstalledContractSnapshot();
6
+
7
+ export function contractObjectSchema(schema: ContractSchema): z.ZodObject {
8
+ const result = z.fromJSONSchema({ ...schema, $defs: CONTRACT_SNAPSHOT.$defs });
9
+ if (!(result instanceof z.ZodObject)) throw new TypeError('Tool contract must be an object');
10
+ return result;
11
+ }
12
+
13
+ /** Decode only generated Uint8Array locations; arbitrary JSON metadata is not rewritten. */
14
+ export function decodeToolBytes(input: Record<string, unknown>, paths: string[][]): Record<string, unknown> {
15
+ if (!paths.length) return input;
16
+ const result = structuredClone(input);
17
+ function visit(value: unknown, parts: string[]): unknown {
18
+ if (!parts.length) return value === null ? value : Uint8Array.from(value as number[]);
19
+ if (!value || typeof value !== 'object') return value;
20
+ const [key, ...rest] = parts;
21
+ const record = value as Record<string, unknown>;
22
+ for (const name of key === '*' ? Object.keys(record) : [key]) {
23
+ if (Object.hasOwn(record, name)) record[name] = visit(record[name], rest);
24
+ }
25
+ return value;
26
+ }
27
+ for (const parts of paths) visit(result, parts);
28
+ return result;
29
+ }
package/src/index.ts CHANGED
@@ -21,9 +21,14 @@ export {
21
21
  export {
22
22
  OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS,
23
23
  OPENFAIRYGUI_BACKEND_TOOL_NAMES,
24
- OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
25
24
  OPENFAIRYGUI_BACKEND_TOOL_PREFIX,
26
25
  type BackendMethodName,
27
26
  type OpenFairyGuiBackendToolDefinition,
28
27
  type OpenFairyGuiBackendToolName,
29
28
  } from './tool-definitions.js';
29
+ export {
30
+ getOpenFairyGuiOperationCatalog,
31
+ getOpenFairyGuiOperationSchema,
32
+ OPENFAIRYGUI_OPERATION_CATALOG_URI,
33
+ OPENFAIRYGUI_OPERATION_SCHEMA_TEMPLATE,
34
+ } from './contract-schema.js';
@@ -56,6 +56,11 @@ export const OPENFAIRYGUI_BACKEND_PROMPT_DEFINITIONS = [
56
56
  description: 'Guide a client through backend-owned revision checks without inventing operation grammar.',
57
57
  text: [
58
58
  'Use openfairygui_backend_get_session to read the current revision before mutation.',
59
+ 'Use openfairygui_backend_get_project_outline for identities, then openfairygui_backend_query_entity for current properties at the returned revision.',
60
+ '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.',
61
+ 'Read openfairygui://contracts/operations and openfairygui://contracts/operations/{kind} for the current operation names and exact JSON parameters.',
62
+ 'Call openfairygui_backend_preflight_transaction with the queried revision and planned operations to execute and discard an isolated preview; inspect the backend diagnostics.',
63
+ '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.',
59
64
  'Call openfairygui_backend_apply_transaction with sessionId, expectedRevision, and backend/UAM-owned operations.',
60
65
  'If the backend returns a stale revision error, refresh the session snapshot and re-plan against the new revision.',
61
66
  'Do not invent selector grammar, transaction grammar, or operation payload semantics at the MCP layer.',
@@ -1,6 +1,14 @@
1
1
  import { ResourceTemplate, type McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { ReadResourceResult } from '@modelcontextprotocol/sdk/types.js';
3
+ import { BACKEND_DIAGNOSTICS_URI, BACKEND_DIAGNOSTIC_TEMPLATE, getBackendDiagnosticCatalog, getBackendDiagnosticGuide } from '@openfairygui/backend';
4
+ import { getInstalledDocumentationIndex, readInstalledDocumentation, OPENFAIRYGUI_DOCS_INDEX_URI } from '@openfairygui/backend/docs';
3
5
  import type { OpenFairyGuiBackendRuntime } from './tool-handler.js';
6
+ import {
7
+ getOpenFairyGuiOperationCatalog,
8
+ getOpenFairyGuiOperationSchema,
9
+ OPENFAIRYGUI_OPERATION_CATALOG_URI,
10
+ OPENFAIRYGUI_OPERATION_SCHEMA_TEMPLATE,
11
+ } from './contract-schema.js';
4
12
 
5
13
  const JSON_MIME_TYPE = 'application/json';
6
14
 
@@ -23,6 +31,10 @@ function jsonResource(uri: URL, backendResult: unknown): ReadResourceResult {
23
31
  export const OPENFAIRYGUI_BACKEND_CAPABILITIES_RESOURCE_URI = 'openfairygui://backend/capabilities';
24
32
 
25
33
  export const OPENFAIRYGUI_BACKEND_RESOURCE_TEMPLATES = [
34
+ 'openfairygui://docs/methods/{method}',
35
+ 'openfairygui://docs/cli/{command}',
36
+ BACKEND_DIAGNOSTIC_TEMPLATE,
37
+ OPENFAIRYGUI_OPERATION_SCHEMA_TEMPLATE,
26
38
  'openfairygui://backend/session/{sessionId}',
27
39
  'openfairygui://backend/session/{sessionId}/outline',
28
40
  'openfairygui://backend/cache/{sessionId}',
@@ -30,6 +42,53 @@ export const OPENFAIRYGUI_BACKEND_RESOURCE_TEMPLATES = [
30
42
  ] as const;
31
43
 
32
44
  export function registerOpenFairyGuiBackendResources(server: McpServer, runtime: OpenFairyGuiBackendRuntime): void {
45
+ server.registerResource(
46
+ 'openfairygui_docs_index', OPENFAIRYGUI_DOCS_INDEX_URI,
47
+ { title: 'Installed Documentation', description: 'Offline documentation IDs, URIs, installed package version and contract digest shared with the CLI.', mimeType: JSON_MIME_TYPE },
48
+ (uri) => jsonResource(uri, getInstalledDocumentationIndex()),
49
+ );
50
+ function installedDocument(uri: URL, id: string): ReadResourceResult {
51
+ const document = readInstalledDocumentation(id);
52
+ return { contents: [{ uri: uri.toString(), mimeType: document.mimeType, text: document.text }] };
53
+ }
54
+ for (const id of ['workflow', 'restore-limits', 'skill', 'contracts']) {
55
+ server.registerResource(
56
+ `openfairygui_docs_${id}`, `openfairygui://docs/${id}`,
57
+ { title: `Installed ${id}`, description: 'Read the installed-version corpus without repository or network access.', mimeType: id === 'contracts' ? JSON_MIME_TYPE : 'text/markdown' },
58
+ (uri) => installedDocument(uri, id),
59
+ );
60
+ }
61
+ server.registerResource(
62
+ 'openfairygui_docs_method', new ResourceTemplate('openfairygui://docs/methods/{method}', { list: undefined }),
63
+ { title: 'Installed Method Contract', description: 'Read self-contained Backend/MCP wire input/output schemas and metadata.', mimeType: JSON_MIME_TYPE },
64
+ (uri, variables) => installedDocument(uri, `methods/${firstVariable(variables.method)}`),
65
+ );
66
+ server.registerResource(
67
+ 'openfairygui_diagnostic_catalog', BACKEND_DIAGNOSTICS_URI,
68
+ { title: 'Diagnostic Recovery Catalog', description: 'Complete formal diagnostic ownership and recovery guidance; never automatic repair.', mimeType: JSON_MIME_TYPE },
69
+ (uri) => jsonResource(uri, getBackendDiagnosticCatalog()),
70
+ );
71
+ server.registerResource(
72
+ 'openfairygui_docs_cli', new ResourceTemplate('openfairygui://docs/cli/{command}', { list: undefined }),
73
+ { title: 'Installed CLI Output Contract', description: 'Read a generated, self-contained CLI JSON envelope schema.', mimeType: JSON_MIME_TYPE },
74
+ (uri, variables) => installedDocument(uri, `cli/${decodeURIComponent(firstVariable(variables.command))}`),
75
+ );
76
+ server.registerResource(
77
+ 'openfairygui_diagnostic_guide', new ResourceTemplate(BACKEND_DIAGNOSTIC_TEMPLATE, { list: undefined }),
78
+ { title: 'Diagnostic Recovery Guide', description: 'Read the recovery boundary for one stable diagnostic code.', mimeType: JSON_MIME_TYPE },
79
+ (uri, variables) => jsonResource(uri, getBackendDiagnosticGuide(firstVariable(variables.code))),
80
+ );
81
+ server.registerResource(
82
+ 'openfairygui_operation_catalog', OPENFAIRYGUI_OPERATION_CATALOG_URI,
83
+ { title: 'UAM Operation Catalog', description: 'Discover current operations and their generated JSON schemas.', mimeType: JSON_MIME_TYPE },
84
+ (uri) => jsonResource(uri, getOpenFairyGuiOperationCatalog()),
85
+ );
86
+ server.registerResource(
87
+ 'openfairygui_operation_schema',
88
+ new ResourceTemplate(OPENFAIRYGUI_OPERATION_SCHEMA_TEMPLATE, { list: undefined }),
89
+ { title: 'UAM Operation Schema', description: 'Read the precise Core-derived JSON wire schema for one operation. Structure is not semantic preflight.', mimeType: JSON_MIME_TYPE },
90
+ (uri, variables) => jsonResource(uri, getOpenFairyGuiOperationSchema(firstVariable(variables.kind))),
91
+ );
33
92
  server.registerResource(
34
93
  'openfairygui_backend_capabilities',
35
94
  OPENFAIRYGUI_BACKEND_CAPABILITIES_RESOURCE_URI,
package/src/server.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { ListToolsRequestSchema, ToolSchema, type Tool } from '@modelcontextprotocol/sdk/types.js';
2
3
  import { createNodeBackendRuntime } from '@openfairygui/backend/node';
3
4
  import { createRequire } from 'node:module';
5
+ import { z } from 'zod';
4
6
  import { registerOpenFairyGuiBackendPrompts } from './prompt-definitions.js';
5
7
  import { registerOpenFairyGuiBackendResources } from './resource-definitions.js';
8
+ import { CONTRACT_SNAPSHOT } from './contract-schema.js';
6
9
  import { callOpenFairyGuiBackendTool, type OpenFairyGuiBackendRuntime } from './tool-handler.js';
7
10
  import {
8
11
  OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS,
@@ -35,34 +38,50 @@ const PACKAGE_VERSION = readPackageVersion();
35
38
 
36
39
  export interface CreateOpenFairyGuiMcpServerOptions {
37
40
  runtime?: OpenFairyGuiBackendRuntime;
41
+ /** Filesystem roots exposed by the default Node backend runtime. Defaults to process.cwd(). */
42
+ allowedProjectRoots?: readonly string[];
38
43
  name?: string;
39
44
  version?: string;
40
45
  }
41
46
 
42
47
  export function createOpenFairyGuiMcpServer(options: CreateOpenFairyGuiMcpServerOptions = {}): McpServer {
43
- const runtime = options.runtime ?? createNodeBackendRuntime();
48
+ const runtime = options.runtime ?? createNodeBackendRuntime({
49
+ allowedProjectRoots: options.allowedProjectRoots ?? [process.cwd()],
50
+ });
44
51
  const server = new McpServer({
45
52
  name: options.name ?? 'openfairygui-mcp',
46
53
  version: options.version ?? PACKAGE_VERSION,
47
54
  });
48
55
 
56
+ const tools: Tool[] = [];
49
57
  for (const definition of OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS) {
58
+ const metadata = {
59
+ name: definition.name, title: definition.title, description: definition.description,
60
+ annotations: definition.annotations,
61
+ _meta: {
62
+ 'openfairygui/backendMethod': definition.backendMethod,
63
+ 'openfairygui/adapter': 'thin-backend-p2',
64
+ 'openfairygui/contractDigest': CONTRACT_SNAPSHOT.digest,
65
+ },
66
+ };
50
67
  server.registerTool(
51
68
  definition.name,
52
69
  {
53
- title: definition.title,
54
- description: definition.description,
70
+ ...metadata,
55
71
  inputSchema: definition.inputSchema,
56
72
  outputSchema: definition.outputSchema,
57
- annotations: definition.annotations,
58
- _meta: {
59
- 'openfairygui/backendMethod': definition.backendMethod,
60
- 'openfairygui/adapter': 'thin-backend-p2',
61
- },
62
73
  },
63
74
  async (args: Record<string, unknown>) => callOpenFairyGuiBackendTool(runtime, definition.name as OpenFairyGuiBackendToolName, args),
64
75
  );
76
+ tools.push(ToolSchema.parse({
77
+ ...metadata,
78
+ inputSchema: z.toJSONSchema(definition.inputSchema, { target: 'draft-07', io: 'input', reused: 'ref' }),
79
+ outputSchema: z.toJSONSchema(definition.outputSchema, { target: 'draft-07', io: 'output', reused: 'ref' }),
80
+ }));
65
81
  }
82
+ // The installed Backend catalog is fixed. Reuse local definitions in discovery only;
83
+ // registered Zod schemas and the handler's structural/budget validation remain unchanged.
84
+ server.server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: structuredClone(tools) }));
66
85
 
67
86
  registerOpenFairyGuiBackendResources(server, runtime);
68
87
  registerOpenFairyGuiBackendPrompts(server);
package/src/stdio.ts CHANGED
@@ -1,9 +1,14 @@
1
1
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
2
+ import path from 'node:path';
2
3
  import { pathToFileURL } from 'node:url';
3
4
  import { createOpenFairyGuiMcpServer } from './server.js';
4
5
 
5
6
  export async function connectOpenFairyGuiMcpStdio(): Promise<void> {
6
- const server = createOpenFairyGuiMcpServer();
7
+ const configuredRoots = process.env.OPENFAIRYGUI_ALLOWED_PROJECT_ROOTS
8
+ ?.split(path.delimiter)
9
+ .map((value) => value.trim())
10
+ .filter(Boolean);
11
+ const server = createOpenFairyGuiMcpServer({ allowedProjectRoots: configuredRoots });
7
12
  await server.connect(new StdioServerTransport());
8
13
  }
9
14
 
@@ -1,252 +1,58 @@
1
- import { z } from 'zod';
1
+ import type { z } from 'zod';
2
+ import type { BackendMethodName } from '@openfairygui/backend';
3
+ import { contractObjectSchema, CONTRACT_SNAPSHOT } from './contract-schema.js';
4
+ import { OPENFAIRYGUI_BACKEND_TOOL_METADATA, type BackendToolMetadata } from './tool-metadata.js';
2
5
 
3
6
  export const OPENFAIRYGUI_BACKEND_TOOL_PREFIX = 'openfairygui_backend_';
7
+ export type { BackendMethodName };
8
+ export type OpenFairyGuiBackendToolName = typeof OPENFAIRYGUI_BACKEND_TOOL_METADATA[number]['name'];
9
+ export const OPENFAIRYGUI_BACKEND_TOOL_NAMES = OPENFAIRYGUI_BACKEND_TOOL_METADATA.map((entry) => entry.name);
4
10
 
5
- export const OPENFAIRYGUI_BACKEND_TOOL_NAMES = [
6
- 'openfairygui_backend_get_capabilities',
7
- 'openfairygui_backend_open_session',
8
- 'openfairygui_backend_open_project_session',
9
- 'openfairygui_backend_get_session',
10
- 'openfairygui_backend_get_project_outline',
11
- 'openfairygui_backend_validate_session',
12
- 'openfairygui_backend_apply_transaction',
13
- 'openfairygui_backend_save_session',
14
- 'openfairygui_backend_materialize_session',
15
- 'openfairygui_backend_close_session',
16
- 'openfairygui_backend_get_events',
17
- 'openfairygui_backend_get_job',
18
- 'openfairygui_backend_list_jobs',
19
- 'openfairygui_backend_cancel_job',
20
- 'openfairygui_backend_get_cache_snapshot',
21
- 'openfairygui_backend_refresh_cache',
22
- ] as const;
23
-
24
- export type OpenFairyGuiBackendToolName = typeof OPENFAIRYGUI_BACKEND_TOOL_NAMES[number];
25
-
26
- export type BackendMethodName =
27
- | 'getCapabilities'
28
- | 'openSession'
29
- | 'openProjectSession'
30
- | 'getSession'
31
- | 'getProjectOutline'
32
- | 'validateSession'
33
- | 'applyTransaction'
34
- | 'saveSession'
35
- | 'materializeSession'
36
- | 'closeSession'
37
- | 'getEvents'
38
- | 'getJob'
39
- | 'listJobs'
40
- | 'cancelJob'
41
- | 'getCacheSnapshot'
42
- | 'refreshCache';
43
-
44
- export interface OpenFairyGuiBackendToolDefinition {
11
+ export interface OpenFairyGuiBackendToolDefinition extends BackendToolMetadata {
45
12
  name: OpenFairyGuiBackendToolName;
46
- backendMethod: BackendMethodName;
47
- title: string;
48
- description: string;
49
13
  inputSchema: z.ZodObject;
50
14
  outputSchema: z.ZodObject;
51
- annotations: {
52
- readOnlyHint?: boolean;
53
- destructiveHint?: boolean;
54
- idempotentHint?: boolean;
55
- openWorldHint?: boolean;
56
- };
57
15
  }
58
16
 
59
- const sessionId = z.string().min(1);
60
- const jobId = z.string().min(1);
61
- const expectedRevision = z.number().int().nonnegative();
62
- const limit = z.number().int().nonnegative().optional();
63
-
64
- export const OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA = z.object({
65
- backendResult: z.object({
66
- ok: z.boolean(),
67
- data: z.unknown().optional(),
68
- error: z.unknown().optional(),
69
- meta: z.unknown().optional(),
70
- }).passthrough(),
71
- });
17
+ export function isOpenFairyGuiMcpPayloadWithinBudget(root: unknown): boolean {
18
+ const pending: Array<{ value: unknown; depth: number }> = [{ value: root, depth: 0 }];
19
+ let nodes = 0;
20
+ while (pending.length > 0) {
21
+ const { value, depth } = pending.pop()!;
22
+ nodes += 1;
23
+ if (nodes > 100_000 || depth > 32) return false;
24
+ if (value === null || typeof value === 'boolean') continue;
25
+ if (typeof value === 'number') {
26
+ if (!Number.isFinite(value)) return false;
27
+ continue;
28
+ }
29
+ if (typeof value === 'string') {
30
+ if (value.length > 1_000_000) return false;
31
+ continue;
32
+ }
33
+ if (value instanceof Uint8Array) {
34
+ if (value.byteLength > 8 * 1024 * 1024) return false;
35
+ continue;
36
+ }
37
+ if (Array.isArray(value)) {
38
+ if (value.length > 10_000) return false;
39
+ for (const child of value) pending.push({ value: child, depth: depth + 1 });
40
+ continue;
41
+ }
42
+ if (typeof value !== 'object') return false;
43
+ const entries = Object.entries(value);
44
+ if (entries.length > 10_000 || entries.some(([key]) => key.length > 256)) return false;
45
+ for (const [, child] of entries) pending.push({ value: child, depth: depth + 1 });
46
+ }
47
+ return true;
48
+ }
72
49
 
73
- export const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS = [
74
- {
75
- name: 'openfairygui_backend_get_capabilities',
76
- backendMethod: 'getCapabilities',
77
- title: 'Get Backend Capabilities',
78
- description: 'Return the OpenFairyGUI backend capability, version, and service-plane snapshot.',
79
- inputSchema: z.object({}),
80
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
81
- annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
82
- },
83
- {
84
- name: 'openfairygui_backend_open_session',
85
- backendMethod: 'openSession',
86
- title: 'Open Backend Session',
87
- description: 'Open a FairyGUI project through BackendRuntime and acquire its backend-local session lock.',
88
- inputSchema: z.object({
89
- projectPath: z.string().min(1),
90
- }),
91
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
92
- annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
93
- },
94
- {
95
- name: 'openfairygui_backend_open_project_session',
96
- backendMethod: 'openProjectSession',
97
- title: 'Open Project Session',
98
- description: 'Open a browser-safe backend session from an already loaded UAM project without filesystem access.',
99
- inputSchema: z.object({
100
- project: z.unknown(),
101
- sessionId: z.string().min(1).optional(),
102
- canonicalProjectPath: z.string().min(1).optional(),
103
- canonicalPathKey: z.string().min(1).optional(),
104
- }),
105
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
106
- annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
107
- },
108
- {
109
- name: 'openfairygui_backend_get_session',
110
- backendMethod: 'getSession',
111
- title: 'Get Backend Session',
112
- description: 'Return a backend session snapshot by session id.',
113
- inputSchema: z.object({ sessionId }),
114
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
115
- annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
116
- },
117
- {
118
- name: 'openfairygui_backend_get_project_outline',
119
- backendMethod: 'getProjectOutline',
120
- title: 'Get Project Outline',
121
- description: 'Return a revision-bound project/package/resource/component identity outline without source bytes or full property payloads.',
122
- inputSchema: z.object({ sessionId }),
123
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
124
- annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
125
- },
126
- {
127
- name: 'openfairygui_backend_validate_session',
128
- backendMethod: 'validateSession',
129
- title: 'Validate Project Session',
130
- description: 'Validate the current session project structure, references, paths, and available source bytes without writing files.',
131
- inputSchema: z.object({ sessionId }),
132
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
133
- annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
134
- },
135
- {
136
- name: 'openfairygui_backend_apply_transaction',
137
- backendMethod: 'applyTransaction',
138
- title: 'Apply UAM Transaction',
139
- description: 'Apply a backend revision-checked UAM operation batch without redefining selector or operation grammar.',
140
- inputSchema: z.object({
141
- sessionId,
142
- expectedRevision,
143
- operations: z.array(z.unknown()),
144
- }),
145
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
146
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
147
- },
148
- {
149
- name: 'openfairygui_backend_save_session',
150
- backendMethod: 'saveSession',
151
- title: 'Save Backend Session',
152
- description: 'Write the current backend session back through the backend coordinated non-atomic save path.',
153
- inputSchema: z.object({
154
- sessionId,
155
- expectedRevision: expectedRevision.optional(),
156
- targetPath: z.string().min(1).optional(),
157
- force: z.boolean().optional(),
158
- mode: z.literal('materializeCleanSession').optional(),
159
- }),
160
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
161
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
162
- },
163
- {
164
- name: 'openfairygui_backend_materialize_session',
165
- backendMethod: 'materializeSession',
166
- title: 'Materialize Backend Session',
167
- description: 'Force materialize the current backend session project through the configured project storage without requiring a dirty edit revision.',
168
- inputSchema: z.object({
169
- sessionId,
170
- expectedRevision: expectedRevision.optional(),
171
- mode: z.literal('fullProject').optional(),
172
- reason: z.string().min(1).optional(),
173
- }),
174
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
175
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
176
- },
177
- {
178
- name: 'openfairygui_backend_close_session',
179
- backendMethod: 'closeSession',
180
- title: 'Close Backend Session',
181
- description: 'Close a backend session and release its backend-local session lock.',
182
- inputSchema: z.object({ sessionId }),
183
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
184
- annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
185
- },
186
- {
187
- name: 'openfairygui_backend_get_events',
188
- backendMethod: 'getEvents',
189
- title: 'Get Runtime Events',
190
- description: 'Poll backend runtime events for a session using the backend P2 event cursor contract.',
191
- inputSchema: z.object({
192
- sessionId,
193
- after: z.string().optional(),
194
- limit,
195
- }),
196
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
197
- annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
198
- },
199
- {
200
- name: 'openfairygui_backend_get_job',
201
- backendMethod: 'getJob',
202
- title: 'Get Runtime Job',
203
- description: 'Return a backend runtime job snapshot by session and backend-local job id.',
204
- inputSchema: z.object({ sessionId, jobId }),
205
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
206
- annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
207
- },
208
- {
209
- name: 'openfairygui_backend_list_jobs',
210
- backendMethod: 'listJobs',
211
- title: 'List Runtime Jobs',
212
- description: 'List backend runtime jobs for a session with backend P2 status/kind filters.',
213
- inputSchema: z.object({
214
- sessionId,
215
- status: z.enum(['queued', 'running', 'completed', 'failed', 'cancelled', 'active', 'terminal']).optional(),
216
- kind: z.literal('cache.refresh').optional(),
217
- limit,
218
- }),
219
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
220
- annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
221
- },
222
- {
223
- name: 'openfairygui_backend_cancel_job',
224
- backendMethod: 'cancelJob',
225
- title: 'Cancel Runtime Job',
226
- description: 'Request cooperative cancellation for a backend runtime job.',
227
- inputSchema: z.object({ sessionId, jobId }),
228
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
229
- annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
230
- },
231
- {
232
- name: 'openfairygui_backend_get_cache_snapshot',
233
- backendMethod: 'getCacheSnapshot',
234
- title: 'Get Cache Snapshot',
235
- description: 'Return the backend P2 derived read-only cache snapshot for a session.',
236
- inputSchema: z.object({ sessionId }),
237
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
238
- annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
239
- },
240
- {
241
- name: 'openfairygui_backend_refresh_cache',
242
- backendMethod: 'refreshCache',
243
- title: 'Refresh Cache',
244
- description: 'Create a backend P2 cache.refresh job for the session cache snapshot.',
245
- inputSchema: z.object({
246
- sessionId,
247
- reason: z.enum(['manual', 'session_open', 'after_save']).optional(),
248
- }),
249
- outputSchema: OPENFAIRYGUI_BACKEND_TOOL_OUTPUT_SCHEMA,
250
- annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
251
- },
252
- ] as const satisfies readonly OpenFairyGuiBackendToolDefinition[];
50
+ export const OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS: readonly OpenFairyGuiBackendToolDefinition[] =
51
+ OPENFAIRYGUI_BACKEND_TOOL_METADATA.map((metadata) => {
52
+ const contract = CONTRACT_SNAPSHOT.tools[metadata.backendMethod];
53
+ return {
54
+ ...metadata,
55
+ inputSchema: contractObjectSchema(contract.input),
56
+ outputSchema: contractObjectSchema(contract.output),
57
+ };
58
+ });