@openfairygui/mcp 0.3.1 → 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/README.md +5 -1
- package/dist/index.cjs +26 -2
- package/dist/index.d.cts +41 -640
- package/dist/index.d.mts +42 -641
- package/dist/index.mjs +2 -2
- package/dist/{stdio-HOHWH0x4.mjs → stdio-9ka7bvOr.mjs} +211 -528
- package/dist/{stdio-N6Yxacus.cjs → stdio-B0OU-oZC.cjs} +214 -531
- package/dist/stdio.cjs +1 -1
- package/dist/stdio.mjs +1 -1
- package/package.json +4 -4
- 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 +16 -133
- package/src/tool-metadata.ts +157 -0
package/src/tool-handler.ts
CHANGED
|
@@ -3,34 +3,21 @@ import {
|
|
|
3
3
|
BACKEND_CAPABILITY_SCHEMA_VERSION,
|
|
4
4
|
BACKEND_CONTRACT_VERSION,
|
|
5
5
|
type BackendRuntime,
|
|
6
|
+
type BackendMethodName,
|
|
6
7
|
} from '@openfairygui/backend';
|
|
7
8
|
import {
|
|
8
9
|
isOpenFairyGuiMcpPayloadWithinBudget,
|
|
10
|
+
OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS,
|
|
9
11
|
type OpenFairyGuiBackendToolName,
|
|
10
12
|
} from './tool-definitions.js';
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
| 'openProjectSession'
|
|
17
|
-
| 'getSession'
|
|
18
|
-
| 'getProjectOutline'
|
|
19
|
-
| 'validateSession'
|
|
20
|
-
| 'applyTransaction'
|
|
21
|
-
| 'saveSession'
|
|
22
|
-
| 'materializeSession'
|
|
23
|
-
| 'closeSession'
|
|
24
|
-
| 'getEvents'
|
|
25
|
-
| 'getJob'
|
|
26
|
-
| 'listJobs'
|
|
27
|
-
| 'cancelJob'
|
|
28
|
-
| 'getCacheSnapshot'
|
|
29
|
-
| 'refreshCache'
|
|
30
|
-
>;
|
|
14
|
+
import { decodeToolBytes, CONTRACT_SNAPSHOT } from './contract-schema.js';
|
|
15
|
+
import type { McpUnhandledFailure } from './tool-metadata.js';
|
|
16
|
+
|
|
17
|
+
export type OpenFairyGuiBackendRuntime = Pick<BackendRuntime, BackendMethodName>;
|
|
31
18
|
|
|
32
19
|
function jsonResult(payload: unknown, isError = false): CallToolResult {
|
|
33
|
-
const text = JSON.stringify(payload,
|
|
20
|
+
const text = JSON.stringify(payload, (_key, value) => value instanceof Uint8Array ? [...value] : value, 2);
|
|
34
21
|
const wirePayload = JSON.parse(text) as unknown;
|
|
35
22
|
return {
|
|
36
23
|
content: [
|
|
@@ -53,7 +40,7 @@ function isBackendFailure(value: unknown): boolean {
|
|
|
53
40
|
&& (value as { ok?: unknown }).ok === false;
|
|
54
41
|
}
|
|
55
42
|
|
|
56
|
-
function unhandledBackendFailure(startedAt: number):
|
|
43
|
+
function unhandledBackendFailure(startedAt: number): McpUnhandledFailure {
|
|
57
44
|
return {
|
|
58
45
|
ok: false,
|
|
59
46
|
meta: {
|
|
@@ -80,121 +67,17 @@ export async function callOpenFairyGuiBackendTool(
|
|
|
80
67
|
if (!isOpenFairyGuiMcpPayloadWithinBudget(input)) {
|
|
81
68
|
throw new RangeError('MCP input exceeds the depth, node, key, string, or byte budget.');
|
|
82
69
|
}
|
|
70
|
+
const definition = OPENFAIRYGUI_BACKEND_TOOL_DEFINITIONS.find((entry) => entry.name === name);
|
|
71
|
+
if (!definition) throw new RangeError(`Unknown OpenFairyGUI backend MCP tool: ${name}`);
|
|
72
|
+
const parsed = definition.inputSchema.parse(input) as Record<string, unknown>;
|
|
73
|
+
const decoded = decodeToolBytes(parsed, CONTRACT_SNAPSHOT.tools[definition.backendMethod].bytePaths);
|
|
83
74
|
const startedAt = Date.now();
|
|
84
|
-
let result: unknown;
|
|
85
75
|
try {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
case 'openfairygui_backend_open_session':
|
|
91
|
-
result = await runtime.openSession({
|
|
92
|
-
projectPath: String(input.projectPath),
|
|
93
|
-
});
|
|
94
|
-
break;
|
|
95
|
-
case 'openfairygui_backend_open_project_session':
|
|
96
|
-
result = runtime.openProjectSession({
|
|
97
|
-
project: input.project as Parameters<BackendRuntime['openProjectSession']>[0]['project'],
|
|
98
|
-
sessionId: input.sessionId === undefined ? undefined : String(input.sessionId),
|
|
99
|
-
canonicalProjectPath: input.canonicalProjectPath === undefined ? undefined : String(input.canonicalProjectPath),
|
|
100
|
-
canonicalPathKey: input.canonicalPathKey === undefined ? undefined : String(input.canonicalPathKey),
|
|
101
|
-
});
|
|
102
|
-
break;
|
|
103
|
-
case 'openfairygui_backend_get_session':
|
|
104
|
-
result = runtime.getSession({
|
|
105
|
-
sessionId: String(input.sessionId),
|
|
106
|
-
});
|
|
107
|
-
break;
|
|
108
|
-
case 'openfairygui_backend_get_project_outline':
|
|
109
|
-
result = runtime.getProjectOutline({
|
|
110
|
-
sessionId: String(input.sessionId),
|
|
111
|
-
});
|
|
112
|
-
break;
|
|
113
|
-
case 'openfairygui_backend_validate_session':
|
|
114
|
-
result = runtime.validateSession({
|
|
115
|
-
sessionId: String(input.sessionId),
|
|
116
|
-
});
|
|
117
|
-
break;
|
|
118
|
-
case 'openfairygui_backend_apply_transaction': {
|
|
119
|
-
const operations = (input.operations as Parameters<BackendRuntime['applyTransaction']>[0]['operations']).map(
|
|
120
|
-
(operation) => operation.kind === 'replaceResourceBytes'
|
|
121
|
-
? { ...operation, sourceBytes: new Uint8Array(operation.sourceBytes) }
|
|
122
|
-
: operation,
|
|
123
|
-
);
|
|
124
|
-
result = await runtime.applyTransaction({
|
|
125
|
-
sessionId: String(input.sessionId),
|
|
126
|
-
expectedRevision: Number(input.expectedRevision),
|
|
127
|
-
operations,
|
|
128
|
-
});
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
case 'openfairygui_backend_save_session':
|
|
132
|
-
result = await runtime.saveSession({
|
|
133
|
-
sessionId: String(input.sessionId),
|
|
134
|
-
expectedRevision: input.expectedRevision === undefined ? undefined : Number(input.expectedRevision),
|
|
135
|
-
targetPath: input.targetPath === undefined ? undefined : String(input.targetPath),
|
|
136
|
-
force: input.force === undefined ? undefined : Boolean(input.force),
|
|
137
|
-
mode: input.mode as Parameters<BackendRuntime['saveSession']>[0]['mode'],
|
|
138
|
-
});
|
|
139
|
-
break;
|
|
140
|
-
case 'openfairygui_backend_materialize_session':
|
|
141
|
-
result = await runtime.materializeSession({
|
|
142
|
-
sessionId: String(input.sessionId),
|
|
143
|
-
expectedRevision: input.expectedRevision === undefined ? undefined : Number(input.expectedRevision),
|
|
144
|
-
mode: input.mode as Parameters<BackendRuntime['materializeSession']>[0]['mode'],
|
|
145
|
-
reason: input.reason === undefined ? undefined : String(input.reason),
|
|
146
|
-
});
|
|
147
|
-
break;
|
|
148
|
-
case 'openfairygui_backend_close_session':
|
|
149
|
-
result = await runtime.closeSession({
|
|
150
|
-
sessionId: String(input.sessionId),
|
|
151
|
-
});
|
|
152
|
-
break;
|
|
153
|
-
case 'openfairygui_backend_get_events':
|
|
154
|
-
result = runtime.getEvents({
|
|
155
|
-
sessionId: String(input.sessionId),
|
|
156
|
-
after: input.after === undefined ? undefined : String(input.after),
|
|
157
|
-
limit: input.limit === undefined ? undefined : Number(input.limit),
|
|
158
|
-
});
|
|
159
|
-
break;
|
|
160
|
-
case 'openfairygui_backend_get_job':
|
|
161
|
-
result = runtime.getJob({
|
|
162
|
-
sessionId: String(input.sessionId),
|
|
163
|
-
jobId: String(input.jobId),
|
|
164
|
-
});
|
|
165
|
-
break;
|
|
166
|
-
case 'openfairygui_backend_list_jobs':
|
|
167
|
-
result = runtime.listJobs({
|
|
168
|
-
sessionId: String(input.sessionId),
|
|
169
|
-
status: input.status as Parameters<BackendRuntime['listJobs']>[0]['status'],
|
|
170
|
-
kind: input.kind as Parameters<BackendRuntime['listJobs']>[0]['kind'],
|
|
171
|
-
limit: input.limit === undefined ? undefined : Number(input.limit),
|
|
172
|
-
});
|
|
173
|
-
break;
|
|
174
|
-
case 'openfairygui_backend_cancel_job':
|
|
175
|
-
result = runtime.cancelJob({
|
|
176
|
-
sessionId: String(input.sessionId),
|
|
177
|
-
jobId: String(input.jobId),
|
|
178
|
-
});
|
|
179
|
-
break;
|
|
180
|
-
case 'openfairygui_backend_get_cache_snapshot':
|
|
181
|
-
result = runtime.getCacheSnapshot({
|
|
182
|
-
sessionId: String(input.sessionId),
|
|
183
|
-
});
|
|
184
|
-
break;
|
|
185
|
-
case 'openfairygui_backend_refresh_cache':
|
|
186
|
-
result = runtime.refreshCache({
|
|
187
|
-
sessionId: String(input.sessionId),
|
|
188
|
-
reason: input.reason as Parameters<BackendRuntime['refreshCache']>[0]['reason'],
|
|
189
|
-
});
|
|
190
|
-
break;
|
|
191
|
-
default: {
|
|
192
|
-
const exhaustive: never = name;
|
|
193
|
-
throw new Error(`Unknown OpenFairyGUI backend MCP tool: ${exhaustive}`);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
76
|
+
const result = await Reflect.apply(runtime[definition.backendMethod], runtime, definition.backendMethod === 'getCapabilities' ? [] : [decoded]);
|
|
77
|
+
const response = jsonResult(result, isBackendFailure(result));
|
|
78
|
+
definition.outputSchema.parse(response.structuredContent);
|
|
79
|
+
return response;
|
|
196
80
|
} catch {
|
|
197
81
|
return jsonResult(unhandledBackendFailure(startedAt), true);
|
|
198
82
|
}
|
|
199
|
-
return jsonResult(result, isBackendFailure(result));
|
|
200
83
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { BackendMethodName, BackendResponseMeta } from '@openfairygui/backend';
|
|
2
|
+
|
|
3
|
+
export interface BackendToolMetadata {
|
|
4
|
+
name: `openfairygui_backend_${string}`;
|
|
5
|
+
backendMethod: BackendMethodName;
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
annotations: {
|
|
9
|
+
readOnlyHint?: boolean;
|
|
10
|
+
destructiveHint?: boolean;
|
|
11
|
+
idempotentHint?: boolean;
|
|
12
|
+
openWorldHint?: boolean;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** MCP transport failure, not a Backend domain error. */
|
|
17
|
+
export interface McpUnhandledFailure {
|
|
18
|
+
ok: false;
|
|
19
|
+
meta: BackendResponseMeta;
|
|
20
|
+
error: { code: 'backend_unhandled_error'; message: string };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Host objects cannot cross JSON; materialize keeps its existing MCP target boundary. */
|
|
24
|
+
export const MCP_OMITTED_INPUT_FIELDS = {
|
|
25
|
+
openProjectSession: ['storage'],
|
|
26
|
+
saveSession: ['fileSystem'],
|
|
27
|
+
materializeSession: ['storage', 'fileSystem', 'targetPath'],
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
export const OPENFAIRYGUI_BACKEND_TOOL_METADATA = [
|
|
31
|
+
{
|
|
32
|
+
name: 'openfairygui_backend_get_capabilities',
|
|
33
|
+
backendMethod: 'getCapabilities',
|
|
34
|
+
title: 'Get Backend Capabilities',
|
|
35
|
+
description: 'Return the OpenFairyGUI backend capability, version, and service-plane snapshot.',
|
|
36
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'openfairygui_backend_open_session',
|
|
40
|
+
backendMethod: 'openSession',
|
|
41
|
+
title: 'Open Backend Session',
|
|
42
|
+
description: 'Open a FairyGUI project through BackendRuntime and acquire its backend-local session lock.',
|
|
43
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'openfairygui_backend_open_project_session',
|
|
47
|
+
backendMethod: 'openProjectSession',
|
|
48
|
+
title: 'Open Project Session',
|
|
49
|
+
description: 'Open a browser-safe backend session from an already loaded UAM project without filesystem access.',
|
|
50
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'openfairygui_backend_get_session',
|
|
54
|
+
backendMethod: 'getSession',
|
|
55
|
+
title: 'Get Backend Session',
|
|
56
|
+
description: 'Return a backend session snapshot by session id.',
|
|
57
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'openfairygui_backend_get_project_outline',
|
|
61
|
+
backendMethod: 'getProjectOutline',
|
|
62
|
+
title: 'Get Project Outline',
|
|
63
|
+
description: 'Return a revision-bound project/package/resource/component identity outline without source bytes or full property payloads.',
|
|
64
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'openfairygui_backend_query_entity',
|
|
68
|
+
backendMethod: 'queryEntity',
|
|
69
|
+
title: 'Query Entity Properties',
|
|
70
|
+
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.',
|
|
71
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'openfairygui_backend_validate_session',
|
|
75
|
+
backendMethod: 'validateSession',
|
|
76
|
+
title: 'Validate Project Session',
|
|
77
|
+
description: 'Validate the current session project structure, references, paths, and available source bytes without writing files.',
|
|
78
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'openfairygui_backend_preflight_transaction',
|
|
82
|
+
backendMethod: 'preflightTransaction',
|
|
83
|
+
title: 'Preview UAM Transaction',
|
|
84
|
+
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.',
|
|
85
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'openfairygui_backend_apply_transaction',
|
|
89
|
+
backendMethod: 'applyTransaction',
|
|
90
|
+
title: 'Apply UAM Transaction',
|
|
91
|
+
description: 'Apply a bounded, revision-checked UAM operation batch using the Core transaction discriminants.',
|
|
92
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'openfairygui_backend_save_session',
|
|
96
|
+
backendMethod: 'saveSession',
|
|
97
|
+
title: 'Save Backend Session',
|
|
98
|
+
description: 'Write the current backend session through its coordinated save path; Node uses an atomic staged directory swap.',
|
|
99
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'openfairygui_backend_materialize_session',
|
|
103
|
+
backendMethod: 'materializeSession',
|
|
104
|
+
title: 'Materialize Backend Session',
|
|
105
|
+
description: 'Force materialize the current backend session project through the configured project storage without requiring a dirty edit revision.',
|
|
106
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'openfairygui_backend_close_session',
|
|
110
|
+
backendMethod: 'closeSession',
|
|
111
|
+
title: 'Close Backend Session',
|
|
112
|
+
description: 'Close a backend session and release its backend-local session lock.',
|
|
113
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'openfairygui_backend_get_events',
|
|
117
|
+
backendMethod: 'getEvents',
|
|
118
|
+
title: 'Get Runtime Events',
|
|
119
|
+
description: 'Poll backend runtime events for a session using the backend P2 event cursor contract.',
|
|
120
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'openfairygui_backend_get_job',
|
|
124
|
+
backendMethod: 'getJob',
|
|
125
|
+
title: 'Get Runtime Job',
|
|
126
|
+
description: 'Return a backend runtime job snapshot by session and backend-local job id.',
|
|
127
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: 'openfairygui_backend_list_jobs',
|
|
131
|
+
backendMethod: 'listJobs',
|
|
132
|
+
title: 'List Runtime Jobs',
|
|
133
|
+
description: 'List backend runtime jobs for a session with backend P2 status/kind filters.',
|
|
134
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'openfairygui_backend_cancel_job',
|
|
138
|
+
backendMethod: 'cancelJob',
|
|
139
|
+
title: 'Cancel Runtime Job',
|
|
140
|
+
description: 'Request cooperative cancellation for a backend runtime job.',
|
|
141
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'openfairygui_backend_get_cache_snapshot',
|
|
145
|
+
backendMethod: 'getCacheSnapshot',
|
|
146
|
+
title: 'Get Cache Snapshot',
|
|
147
|
+
description: 'Return the backend P2 derived read-only cache snapshot for a session.',
|
|
148
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: 'openfairygui_backend_refresh_cache',
|
|
152
|
+
backendMethod: 'refreshCache',
|
|
153
|
+
title: 'Refresh Cache',
|
|
154
|
+
description: 'Create a backend P2 cache.refresh job for the session cache snapshot.',
|
|
155
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
|
|
156
|
+
},
|
|
157
|
+
] as const satisfies readonly BackendToolMetadata[];
|