@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
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
|
-
| 'getCapabilities'
|
|
15
|
-
| 'openSession'
|
|
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, McpResponseBudgetFailure } from './tool-metadata.js';
|
|
31
16
|
|
|
32
|
-
|
|
33
|
-
|
|
17
|
+
export type OpenFairyGuiBackendRuntime = Pick<BackendRuntime, BackendMethodName>;
|
|
18
|
+
|
|
19
|
+
function jsonResult(payload: unknown, isError = false, compact = false): CallToolResult {
|
|
20
|
+
const text = JSON.stringify(payload, (_key, value) => value instanceof Uint8Array ? [...value] : value, compact ? undefined : 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,23 @@ 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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
}
|
|
76
|
+
const result = await Reflect.apply(runtime[definition.backendMethod], runtime, definition.backendMethod === 'getCapabilities' ? [] : [decoded]);
|
|
77
|
+
let response = jsonResult(result, isBackendFailure(result), definition.maxResponseBytes !== undefined);
|
|
78
|
+
if (definition.maxResponseBytes !== undefined && new TextEncoder().encode(JSON.stringify(response)).byteLength > definition.maxResponseBytes) {
|
|
79
|
+
response = jsonResult({
|
|
80
|
+
...unhandledBackendFailure(startedAt),
|
|
81
|
+
error: { code: 'mcp_response_budget_exceeded', message: 'The complete MCP tool response exceeds its byte limit.', maxBytes: definition.maxResponseBytes },
|
|
82
|
+
} satisfies McpResponseBudgetFailure, true);
|
|
195
83
|
}
|
|
84
|
+
definition.outputSchema.parse(response.structuredContent);
|
|
85
|
+
return response;
|
|
196
86
|
} catch {
|
|
197
87
|
return jsonResult(unhandledBackendFailure(startedAt), true);
|
|
198
88
|
}
|
|
199
|
-
return jsonResult(result, isBackendFailure(result));
|
|
200
89
|
}
|
|
@@ -0,0 +1,181 @@
|
|
|
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
|
+
/** Bound the complete CallToolResult JSON; bounded reads use compact text JSON. */
|
|
9
|
+
maxResponseBytes?: number;
|
|
10
|
+
annotations: {
|
|
11
|
+
readOnlyHint?: boolean;
|
|
12
|
+
destructiveHint?: boolean;
|
|
13
|
+
idempotentHint?: boolean;
|
|
14
|
+
openWorldHint?: boolean;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** MCP transport failure, not a Backend domain error. */
|
|
19
|
+
export interface McpUnhandledFailure {
|
|
20
|
+
ok: false;
|
|
21
|
+
meta: BackendResponseMeta;
|
|
22
|
+
error: { code: 'backend_unhandled_error'; message: string };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface McpResponseBudgetFailure {
|
|
26
|
+
ok: false;
|
|
27
|
+
meta: BackendResponseMeta;
|
|
28
|
+
error: { code: 'mcp_response_budget_exceeded'; message: string; maxBytes: number };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Host objects cannot cross JSON; materialize keeps its existing MCP target boundary. */
|
|
32
|
+
export const MCP_OMITTED_INPUT_FIELDS = {
|
|
33
|
+
openProjectSession: ['storage'],
|
|
34
|
+
saveSession: ['fileSystem'],
|
|
35
|
+
materializeSession: ['storage', 'fileSystem', 'targetPath'],
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
export const OPENFAIRYGUI_BACKEND_TOOL_METADATA = [
|
|
39
|
+
{
|
|
40
|
+
name: 'openfairygui_backend_get_capabilities',
|
|
41
|
+
backendMethod: 'getCapabilities',
|
|
42
|
+
title: 'Get Backend Capabilities',
|
|
43
|
+
description: 'Return the OpenFairyGUI backend capability, version, and service-plane snapshot.',
|
|
44
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'openfairygui_backend_open_session',
|
|
48
|
+
backendMethod: 'openSession',
|
|
49
|
+
title: 'Open Backend Session',
|
|
50
|
+
description: 'Open a FairyGUI project through BackendRuntime and acquire its backend-local session lock.',
|
|
51
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'openfairygui_backend_open_project_session',
|
|
55
|
+
backendMethod: 'openProjectSession',
|
|
56
|
+
title: 'Open Project Session',
|
|
57
|
+
description: 'Open a browser-safe backend session from an already loaded UAM project without filesystem access.',
|
|
58
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'openfairygui_backend_get_session',
|
|
62
|
+
backendMethod: 'getSession',
|
|
63
|
+
title: 'Get Backend Session',
|
|
64
|
+
description: 'Return a backend session snapshot by session id.',
|
|
65
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'openfairygui_backend_get_project_outline',
|
|
69
|
+
backendMethod: 'getProjectOutline',
|
|
70
|
+
title: 'Get Project Outline',
|
|
71
|
+
description: 'Return a revision-bound project/package/resource/component identity outline without source bytes or full property payloads.',
|
|
72
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'openfairygui_backend_query_entity',
|
|
76
|
+
backendMethod: 'queryEntity',
|
|
77
|
+
title: 'Query Entity Properties',
|
|
78
|
+
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.',
|
|
79
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'openfairygui_backend_read_session_state',
|
|
83
|
+
backendMethod: 'readSessionState',
|
|
84
|
+
title: 'Read Session State',
|
|
85
|
+
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.',
|
|
86
|
+
maxResponseBytes: 16777216,
|
|
87
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'openfairygui_backend_read_resource_bytes',
|
|
91
|
+
backendMethod: 'readResourceBytes',
|
|
92
|
+
title: 'Read Resource Bytes',
|
|
93
|
+
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.',
|
|
94
|
+
maxResponseBytes: 16777216,
|
|
95
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'openfairygui_backend_validate_session',
|
|
99
|
+
backendMethod: 'validateSession',
|
|
100
|
+
title: 'Validate Project Session',
|
|
101
|
+
description: 'Validate the current session project structure, references, paths, and available source bytes without writing files.',
|
|
102
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'openfairygui_backend_preflight_transaction',
|
|
106
|
+
backendMethod: 'preflightTransaction',
|
|
107
|
+
title: 'Preview UAM Transaction',
|
|
108
|
+
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.',
|
|
109
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'openfairygui_backend_apply_transaction',
|
|
113
|
+
backendMethod: 'applyTransaction',
|
|
114
|
+
title: 'Apply UAM Transaction',
|
|
115
|
+
description: 'Apply a bounded, revision-checked UAM operation batch using the Core transaction discriminants.',
|
|
116
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'openfairygui_backend_save_session',
|
|
120
|
+
backendMethod: 'saveSession',
|
|
121
|
+
title: 'Save Backend Session',
|
|
122
|
+
description: 'Write the current backend session through its coordinated save path; Node uses an atomic staged directory swap.',
|
|
123
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'openfairygui_backend_materialize_session',
|
|
127
|
+
backendMethod: 'materializeSession',
|
|
128
|
+
title: 'Materialize Backend Session',
|
|
129
|
+
description: 'Force materialize the current backend session project through the configured project storage without requiring a dirty edit revision.',
|
|
130
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'openfairygui_backend_close_session',
|
|
134
|
+
backendMethod: 'closeSession',
|
|
135
|
+
title: 'Close Backend Session',
|
|
136
|
+
description: 'Close a backend session and release its backend-local session lock.',
|
|
137
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'openfairygui_backend_get_events',
|
|
141
|
+
backendMethod: 'getEvents',
|
|
142
|
+
title: 'Get Runtime Events',
|
|
143
|
+
description: 'Poll backend runtime events for a session using the backend P2 event cursor contract.',
|
|
144
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'openfairygui_backend_get_job',
|
|
148
|
+
backendMethod: 'getJob',
|
|
149
|
+
title: 'Get Runtime Job',
|
|
150
|
+
description: 'Return a backend runtime job snapshot by session and backend-local job id.',
|
|
151
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
name: 'openfairygui_backend_list_jobs',
|
|
155
|
+
backendMethod: 'listJobs',
|
|
156
|
+
title: 'List Runtime Jobs',
|
|
157
|
+
description: 'List backend runtime jobs for a session with backend P2 status/kind filters.',
|
|
158
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'openfairygui_backend_cancel_job',
|
|
162
|
+
backendMethod: 'cancelJob',
|
|
163
|
+
title: 'Cancel Runtime Job',
|
|
164
|
+
description: 'Request cooperative cancellation for a backend runtime job.',
|
|
165
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'openfairygui_backend_get_cache_snapshot',
|
|
169
|
+
backendMethod: 'getCacheSnapshot',
|
|
170
|
+
title: 'Get Cache Snapshot',
|
|
171
|
+
description: 'Return the backend P2 derived read-only cache snapshot for a session.',
|
|
172
|
+
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: false },
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'openfairygui_backend_refresh_cache',
|
|
176
|
+
backendMethod: 'refreshCache',
|
|
177
|
+
title: 'Refresh Cache',
|
|
178
|
+
description: 'Create a backend P2 cache.refresh job for the session cache snapshot.',
|
|
179
|
+
annotations: { readOnlyHint: false, idempotentHint: false, openWorldHint: false },
|
|
180
|
+
},
|
|
181
|
+
] as const satisfies readonly BackendToolMetadata[];
|