unreal-engine-mcp-server 0.5.0 → 0.5.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/.env.example +1 -1
- package/.github/release-drafter-config.yml +51 -0
- package/.github/workflows/greetings.yml +5 -1
- package/.github/workflows/labeler.yml +2 -1
- package/.github/workflows/publish-mcp.yml +1 -0
- package/.github/workflows/release-drafter.yml +1 -1
- package/.github/workflows/release.yml +3 -3
- package/CHANGELOG.md +71 -0
- package/CONTRIBUTING.md +1 -1
- package/GEMINI.md +115 -0
- package/Public/Plugin_setup_guide.mp4 +0 -0
- package/README.md +166 -200
- package/dist/config.d.ts +0 -1
- package/dist/config.js +0 -1
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +4 -0
- package/dist/graphql/loaders.d.ts +64 -0
- package/dist/graphql/loaders.js +117 -0
- package/dist/graphql/resolvers.d.ts +3 -3
- package/dist/graphql/resolvers.js +33 -30
- package/dist/graphql/server.js +3 -1
- package/dist/graphql/types.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -2
- package/dist/server-setup.d.ts +0 -1
- package/dist/server-setup.js +0 -40
- package/dist/tools/actors.d.ts +40 -24
- package/dist/tools/actors.js +8 -2
- package/dist/tools/assets.d.ts +19 -71
- package/dist/tools/assets.js +28 -22
- package/dist/tools/base-tool.d.ts +4 -4
- package/dist/tools/base-tool.js +1 -1
- package/dist/tools/blueprint.d.ts +33 -61
- package/dist/tools/consolidated-tool-handlers.js +96 -110
- package/dist/tools/dynamic-handler-registry.d.ts +11 -9
- package/dist/tools/dynamic-handler-registry.js +17 -95
- package/dist/tools/editor.d.ts +19 -193
- package/dist/tools/editor.js +8 -0
- package/dist/tools/environment.d.ts +8 -14
- package/dist/tools/foliage.d.ts +18 -143
- package/dist/tools/foliage.js +4 -2
- package/dist/tools/handlers/actor-handlers.js +0 -5
- package/dist/tools/handlers/asset-handlers.js +454 -454
- package/dist/tools/landscape.d.ts +16 -116
- package/dist/tools/landscape.js +7 -3
- package/dist/tools/level.d.ts +22 -103
- package/dist/tools/level.js +24 -16
- package/dist/tools/lighting.js +5 -1
- package/dist/tools/materials.js +5 -1
- package/dist/tools/niagara.js +37 -2
- package/dist/tools/performance.d.ts +0 -1
- package/dist/tools/performance.js +0 -1
- package/dist/tools/physics.js +5 -1
- package/dist/tools/sequence.d.ts +24 -24
- package/dist/tools/sequence.js +13 -0
- package/dist/tools/ui.d.ts +0 -2
- package/dist/types/automation-responses.d.ts +115 -0
- package/dist/types/automation-responses.js +2 -0
- package/dist/types/responses.d.ts +249 -0
- package/dist/types/responses.js +2 -0
- package/dist/types/tool-interfaces.d.ts +135 -135
- package/dist/utils/command-validator.js +3 -2
- package/dist/utils/path-security.d.ts +2 -0
- package/dist/utils/path-security.js +24 -0
- package/dist/utils/response-factory.d.ts +4 -4
- package/dist/utils/response-factory.js +15 -21
- package/docs/Migration-Guide-v0.5.0.md +1 -9
- package/docs/testing-guide.md +2 -2
- package/package.json +12 -6
- package/scripts/run-all-tests.mjs +25 -20
- package/server.json +3 -2
- package/src/config.ts +1 -1
- package/src/constants.ts +7 -0
- package/src/graphql/loaders.ts +244 -0
- package/src/graphql/resolvers.ts +47 -49
- package/src/graphql/server.ts +3 -1
- package/src/graphql/types.ts +3 -0
- package/src/index.ts +15 -2
- package/src/resources/assets.ts +5 -4
- package/src/server-setup.ts +3 -37
- package/src/tools/actors.ts +36 -28
- package/src/tools/animation.ts +1 -0
- package/src/tools/assets.ts +74 -63
- package/src/tools/base-tool.ts +3 -3
- package/src/tools/blueprint.ts +59 -59
- package/src/tools/consolidated-tool-handlers.ts +129 -150
- package/src/tools/dynamic-handler-registry.ts +22 -140
- package/src/tools/editor.ts +39 -26
- package/src/tools/environment.ts +21 -27
- package/src/tools/foliage.ts +28 -25
- package/src/tools/handlers/actor-handlers.ts +2 -8
- package/src/tools/handlers/asset-handlers.ts +484 -484
- package/src/tools/handlers/sequence-handlers.ts +1 -1
- package/src/tools/landscape.ts +34 -28
- package/src/tools/level.ts +96 -76
- package/src/tools/lighting.ts +6 -1
- package/src/tools/materials.ts +8 -2
- package/src/tools/niagara.ts +44 -2
- package/src/tools/performance.ts +1 -2
- package/src/tools/physics.ts +7 -1
- package/src/tools/sequence.ts +41 -25
- package/src/tools/ui.ts +0 -2
- package/src/types/automation-responses.ts +119 -0
- package/src/types/responses.ts +355 -0
- package/src/types/tool-interfaces.ts +135 -135
- package/src/utils/command-validator.ts +3 -2
- package/src/utils/normalize.test.ts +162 -0
- package/src/utils/path-security.ts +43 -0
- package/src/utils/response-factory.ts +29 -24
- package/src/utils/safe-json.test.ts +90 -0
- package/src/utils/validation.test.ts +184 -0
- package/tests/test-animation.mjs +358 -33
- package/tests/test-asset-graph.mjs +311 -0
- package/tests/test-audio.mjs +314 -116
- package/tests/test-behavior-tree.mjs +327 -144
- package/tests/test-blueprint-graph.mjs +343 -12
- package/tests/test-control-editor.mjs +85 -53
- package/tests/test-graphql.mjs +58 -8
- package/tests/test-input.mjs +349 -0
- package/tests/test-inspect.mjs +291 -61
- package/tests/test-landscape.mjs +304 -48
- package/tests/test-lighting.mjs +428 -0
- package/tests/test-manage-level.mjs +70 -51
- package/tests/test-performance.mjs +539 -0
- package/tests/test-sequence.mjs +82 -46
- package/tests/test-system.mjs +72 -33
- package/tests/test-wasm.mjs +98 -8
- package/vitest.config.ts +35 -0
- package/.github/release-drafter.yml +0 -148
- package/dist/prompts/index.d.ts +0 -21
- package/dist/prompts/index.js +0 -217
- package/dist/tools/blueprint/helpers.d.ts +0 -29
- package/dist/tools/blueprint/helpers.js +0 -182
- package/src/prompts/index.ts +0 -249
- package/src/tools/blueprint/helpers.ts +0 -189
- package/tests/test-blueprint-events.mjs +0 -35
- package/tests/test-extra-tools.mjs +0 -38
- package/tests/test-render.mjs +0 -33
- package/tests/test-search-assets.mjs +0 -66
|
@@ -2,6 +2,7 @@ import { cleanObject } from '../utils/safe-json.js';
|
|
|
2
2
|
import { Logger } from '../utils/logger.js';
|
|
3
3
|
import { ResponseFactory } from '../utils/response-factory.js';
|
|
4
4
|
import { ITools } from '../types/tool-interfaces.js';
|
|
5
|
+
import { toolRegistry } from './dynamic-handler-registry.js';
|
|
5
6
|
import { executeAutomationRequest, requireAction } from './handlers/common-handlers.js';
|
|
6
7
|
import { handleAssetTools } from './handlers/asset-handlers.js';
|
|
7
8
|
import { handleActorTools } from './handlers/actor-handlers.js';
|
|
@@ -20,8 +21,8 @@ import { handleAudioTools } from './handlers/audio-handlers.js';
|
|
|
20
21
|
import { handleLightingTools } from './handlers/lighting-handlers.js';
|
|
21
22
|
import { handlePerformanceTools } from './handlers/performance-handlers.js';
|
|
22
23
|
import { handleInputTools } from './handlers/input-handlers.js';
|
|
23
|
-
import { getDynamicHandlerForTool } from './dynamic-handler-registry.js';
|
|
24
|
-
import { consolidatedToolDefinitions } from './consolidated-tool-definitions.js';
|
|
24
|
+
// import { getDynamicHandlerForTool } from './dynamic-handler-registry.js';
|
|
25
|
+
// import { consolidatedToolDefinitions } from './consolidated-tool-definitions.js';
|
|
25
26
|
|
|
26
27
|
type NormalizedToolCall = {
|
|
27
28
|
name: string;
|
|
@@ -116,157 +117,141 @@ function normalizeToolCall(
|
|
|
116
117
|
};
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
args
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// Reroute merged functionality
|
|
129
|
-
if (['create_render_target', 'nanite_rebuild_mesh'].includes(action)) {
|
|
130
|
-
const payload = { ...args, subAction: action };
|
|
131
|
-
return cleanObject(await executeAutomationRequest(tools, 'manage_render', payload, `Automation bridge not available for ${action}`));
|
|
132
|
-
}
|
|
133
|
-
if (isMaterialGraphAction(action)) {
|
|
134
|
-
// Map new action names to legacy subActions if needed, or just pass through if plugin supports them.
|
|
135
|
-
// The plugin expects 'manage_material_graph' for these.
|
|
136
|
-
// We need to map 'add_material_node' -> 'add_node' etc if the plugin is strict,
|
|
137
|
-
// or we assume we updated the plugin.
|
|
138
|
-
// For now, let's assume we map them to the old tool 'manage_material_graph' with the old action names.
|
|
139
|
-
const subAction = MATERIAL_GRAPH_ACTION_MAP[action] || action;
|
|
140
|
-
|
|
141
|
-
return await handleGraphTools('manage_material_graph', subAction, args, tools);
|
|
142
|
-
}
|
|
143
|
-
if (isBehaviorTreeGraphAction(action)) {
|
|
144
|
-
const subAction = BEHAVIOR_TREE_ACTION_MAP[action] || action;
|
|
145
|
-
return await handleGraphTools('manage_behavior_tree', subAction, args, tools);
|
|
146
|
-
}
|
|
147
|
-
return await handleAssetTools(action, args, tools);
|
|
120
|
+
// Registration of default handlers
|
|
121
|
+
function registerDefaultHandlers() {
|
|
122
|
+
// 1. ASSET MANAGER
|
|
123
|
+
toolRegistry.register('manage_asset', async (args, tools) => {
|
|
124
|
+
const action = args.subAction || args.action || requireAction(args); // Fallback assumption
|
|
125
|
+
// Reroute merged functionality
|
|
126
|
+
if (['create_render_target', 'nanite_rebuild_mesh'].includes(action)) {
|
|
127
|
+
const payload = { ...args, subAction: action };
|
|
128
|
+
return cleanObject(await executeAutomationRequest(tools, 'manage_render', payload, `Automation bridge not available for ${action}`));
|
|
148
129
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
case 'manage_blueprint': {
|
|
153
|
-
if (action === 'get_blueprint') {
|
|
154
|
-
return await handleBlueprintGet(args, tools);
|
|
155
|
-
}
|
|
156
|
-
// Graph actions
|
|
157
|
-
const graphActions = ['create_node', 'delete_node', 'connect_pins', 'break_pin_links', 'set_node_property', 'create_reroute_node', 'get_node_details', 'get_graph_details', 'get_pin_details'];
|
|
158
|
-
if (graphActions.includes(action)) {
|
|
159
|
-
return await handleGraphTools('manage_blueprint_graph', action, args, tools);
|
|
160
|
-
}
|
|
161
|
-
return await handleBlueprintTools(action, args, tools);
|
|
130
|
+
if (isMaterialGraphAction(action)) {
|
|
131
|
+
const subAction = MATERIAL_GRAPH_ACTION_MAP[action] || action;
|
|
132
|
+
return await handleGraphTools('manage_material_graph', subAction, args, tools);
|
|
162
133
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return await handleActorTools(action, args, tools);
|
|
167
|
-
|
|
168
|
-
// 4. EDITOR CONTROL
|
|
169
|
-
case 'control_editor': {
|
|
170
|
-
if (action === 'simulate_input') {
|
|
171
|
-
const payload = { ...args, subAction: action };
|
|
172
|
-
return cleanObject(await executeAutomationRequest(tools, 'manage_ui', payload, 'Automation bridge not available'));
|
|
173
|
-
}
|
|
174
|
-
return await handleEditorTools(action, args, tools);
|
|
134
|
+
if (isBehaviorTreeGraphAction(action)) {
|
|
135
|
+
const subAction = BEHAVIOR_TREE_ACTION_MAP[action] || action;
|
|
136
|
+
return await handleGraphTools('manage_behavior_tree', subAction, args, tools);
|
|
175
137
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return await
|
|
138
|
+
return await handleAssetTools(action, args, tools);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// 2. BLUEPRINT MANAGER
|
|
142
|
+
toolRegistry.register('manage_blueprint', async (args, tools) => {
|
|
143
|
+
const action = args.action || requireAction(args);
|
|
144
|
+
if (action === 'get_blueprint') {
|
|
145
|
+
return await handleBlueprintGet(args, tools);
|
|
184
146
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
147
|
+
const graphActions = ['create_node', 'delete_node', 'connect_pins', 'break_pin_links', 'set_node_property', 'create_reroute_node', 'get_node_details', 'get_graph_details', 'get_pin_details'];
|
|
148
|
+
if (graphActions.includes(action)) {
|
|
149
|
+
return await handleGraphTools('manage_blueprint_graph', action, args, tools);
|
|
150
|
+
}
|
|
151
|
+
return await handleBlueprintTools(action, args, tools);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// 3. ACTOR CONTROL
|
|
155
|
+
toolRegistry.register('control_actor', async (args, tools) => {
|
|
156
|
+
return await handleActorTools(args.action || requireAction(args), args, tools);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// 4. EDITOR CONTROL
|
|
160
|
+
toolRegistry.register('control_editor', async (args, tools) => {
|
|
161
|
+
const action = args.action || requireAction(args);
|
|
162
|
+
if (action === 'simulate_input') {
|
|
163
|
+
const payload = { ...args, subAction: action };
|
|
164
|
+
return cleanObject(await executeAutomationRequest(tools, 'manage_ui', payload, 'Automation bridge not available'));
|
|
165
|
+
}
|
|
166
|
+
return await handleEditorTools(action, args, tools);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// 5. LEVEL MANAGER
|
|
170
|
+
toolRegistry.register('manage_level', async (args, tools) => {
|
|
171
|
+
const action = args.action || requireAction(args);
|
|
172
|
+
if (['load_cells', 'set_datalayer'].includes(action)) {
|
|
173
|
+
const payload = { ...args, subAction: action };
|
|
174
|
+
return cleanObject(await executeAutomationRequest(tools, 'manage_world_partition', payload, 'Automation bridge not available'));
|
|
175
|
+
}
|
|
176
|
+
return await handleLevelTools(action, args, tools);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// 6. ANIMATION & PHYSICS
|
|
180
|
+
toolRegistry.register('animation_physics', async (args, tools) => await handleAnimationTools(args.action || requireAction(args), args, tools));
|
|
181
|
+
|
|
182
|
+
// 7. EFFECTS MANAGER
|
|
183
|
+
toolRegistry.register('manage_effect', async (args, tools) => {
|
|
184
|
+
const action = args.action || requireAction(args);
|
|
185
|
+
if (isNiagaraGraphAction(action)) {
|
|
186
|
+
// Instance check
|
|
187
|
+
const isInstanceOp = action === 'set_niagara_parameter' && (args.actorName || (args.systemName && !args.assetPath && !args.systemPath));
|
|
188
|
+
if (isInstanceOp) {
|
|
189
|
+
return await handleEffectTools(action, args, tools);
|
|
202
190
|
}
|
|
203
|
-
|
|
191
|
+
const subAction = NIAGARA_GRAPH_ACTION_MAP[action] || action;
|
|
192
|
+
return await handleGraphTools('manage_niagara_graph', subAction, args, tools);
|
|
204
193
|
}
|
|
194
|
+
return await handleEffectTools(action, args, tools);
|
|
195
|
+
});
|
|
205
196
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
return await handleEnvironmentTools(action, args, tools);
|
|
197
|
+
// 8. ENVIRONMENT BUILDER
|
|
198
|
+
toolRegistry.register('build_environment', async (args, tools) => await handleEnvironmentTools(args.action || requireAction(args), args, tools));
|
|
209
199
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
200
|
+
// 9. SYSTEM CONTROL
|
|
201
|
+
toolRegistry.register('system_control', async (args, tools) => {
|
|
202
|
+
const action = args.action || requireAction(args);
|
|
203
|
+
if (action === 'console_command') return await handleConsoleCommand(args, tools);
|
|
204
|
+
if (action === 'run_ubt') return await handlePipelineTools(action, args, tools);
|
|
214
205
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (action === 'lumen_update_scene') return cleanObject(await executeAutomationRequest(tools, 'manage_render', { ...args, subAction: action }, 'Bridge unavailable'));
|
|
206
|
+
if (action === 'run_tests') return cleanObject(await executeAutomationRequest(tools, 'manage_tests', { ...args, subAction: action }, 'Bridge unavailable'));
|
|
207
|
+
if (action === 'subscribe' || action === 'unsubscribe') return cleanObject(await executeAutomationRequest(tools, 'manage_logs', { ...args, subAction: action }, 'Bridge unavailable'));
|
|
208
|
+
if (action === 'spawn_category') return cleanObject(await executeAutomationRequest(tools, 'manage_debug', { ...args, subAction: action }, 'Bridge unavailable'));
|
|
209
|
+
if (action === 'start_session') return cleanObject(await executeAutomationRequest(tools, 'manage_insights', { ...args, subAction: action }, 'Bridge unavailable'));
|
|
210
|
+
if (action === 'lumen_update_scene') return cleanObject(await executeAutomationRequest(tools, 'manage_render', { ...args, subAction: action }, 'Bridge unavailable'));
|
|
221
211
|
|
|
222
|
-
|
|
223
|
-
|
|
212
|
+
return await handleSystemTools(action, args, tools);
|
|
213
|
+
});
|
|
224
214
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return await handleSequenceTools(action, args, tools);
|
|
215
|
+
// 10. SEQUENCER
|
|
216
|
+
toolRegistry.register('manage_sequence', async (args, tools) => await handleSequenceTools(args.action || requireAction(args), args, tools));
|
|
228
217
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return await handleInspectTools(action, args, tools);
|
|
218
|
+
// 11. INTROSPECTION
|
|
219
|
+
toolRegistry.register('inspect', async (args, tools) => await handleInspectTools(args.action || requireAction(args), args, tools));
|
|
232
220
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return await handleAudioTools(action, args, tools);
|
|
221
|
+
// 12. AUDIO
|
|
222
|
+
toolRegistry.register('manage_audio', async (args, tools) => await handleAudioTools(args.action || requireAction(args), args, tools));
|
|
236
223
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
return await handleGraphTools('manage_behavior_tree', action, args, tools);
|
|
224
|
+
// 13. BEHAVIOR TREE
|
|
225
|
+
toolRegistry.register('manage_behavior_tree', async (args, tools) => await handleGraphTools('manage_behavior_tree', args.action || requireAction(args), args, tools));
|
|
240
226
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
return await handleGraphTools('manage_blueprint_graph', action, args, tools);
|
|
227
|
+
// 14. BLUEPRINT GRAPH DIRECT
|
|
228
|
+
toolRegistry.register('manage_blueprint_graph', async (args, tools) => await handleGraphTools('manage_blueprint_graph', args.action || requireAction(args), args, tools));
|
|
244
229
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
230
|
+
// 15. RENDER TOOLS
|
|
231
|
+
toolRegistry.register('manage_render', async (args, tools) => {
|
|
232
|
+
const action = args.action || requireAction(args);
|
|
233
|
+
return cleanObject(await executeAutomationRequest(tools, 'manage_render', { ...args, subAction: action }, 'Bridge unavailable'));
|
|
234
|
+
});
|
|
248
235
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
236
|
+
// 16. WORLD PARTITION
|
|
237
|
+
toolRegistry.register('manage_world_partition', async (args, tools) => {
|
|
238
|
+
const action = args.action || requireAction(args);
|
|
239
|
+
return cleanObject(await executeAutomationRequest(tools, 'manage_world_partition', { ...args, subAction: action }, 'Bridge unavailable'));
|
|
240
|
+
});
|
|
252
241
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return await handleLightingTools(action, args, tools);
|
|
242
|
+
// 17. LIGHTING
|
|
243
|
+
toolRegistry.register('manage_lighting', async (args, tools) => await handleLightingTools(args.action || requireAction(args), args, tools));
|
|
256
244
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
return await handlePerformanceTools(action, args, tools);
|
|
245
|
+
// 18. PERFORMANCE
|
|
246
|
+
toolRegistry.register('manage_performance', async (args, tools) => await handlePerformanceTools(args.action || requireAction(args), args, tools));
|
|
260
247
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return await handleInputTools(action, args, tools);
|
|
264
|
-
|
|
265
|
-
default:
|
|
266
|
-
return cleanObject({ success: false, error: 'UNKNOWN_TOOL', message: `Unknown consolidated tool: ${name}` });
|
|
267
|
-
}
|
|
248
|
+
// 19. INPUT
|
|
249
|
+
toolRegistry.register('manage_input', async (args, tools) => await handleInputTools(args.action || requireAction(args), args, tools));
|
|
268
250
|
}
|
|
269
251
|
|
|
252
|
+
// Initialize default handlers immediately
|
|
253
|
+
registerDefaultHandlers();
|
|
254
|
+
|
|
270
255
|
// Export the main consolidated tool call handler
|
|
271
256
|
export async function handleConsolidatedToolCall(
|
|
272
257
|
name: string,
|
|
@@ -280,30 +265,24 @@ export async function handleConsolidatedToolCall(
|
|
|
280
265
|
try {
|
|
281
266
|
const normalized = normalizeToolCall(name, args);
|
|
282
267
|
const normalizedName = normalized.name;
|
|
283
|
-
const action = normalized.action;
|
|
284
268
|
const normalizedArgs = normalized.args;
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (
|
|
290
|
-
|
|
291
|
-
const allowed: string[] | undefined = Array.isArray(actionProp.enum) ? actionProp.enum as string[] : undefined;
|
|
292
|
-
if (allowed && !allowed.includes(action)) {
|
|
293
|
-
return cleanObject({
|
|
294
|
-
success: false,
|
|
295
|
-
error: 'UNKNOWN_ACTION',
|
|
296
|
-
message: `Unknown action '${action}' for consolidated tool '${normalizedName}'.`
|
|
297
|
-
});
|
|
298
|
-
}
|
|
269
|
+
// Note: action extracted inside handler usually, but here we might pass it if needed.
|
|
270
|
+
// The handlers above re-extract or use normalizedArgs.action.
|
|
271
|
+
// `normalizeToolCall` puts `action` into `normalized.action` but does NOT necessarily put it into `normalized.args.action`.
|
|
272
|
+
// Let's ensure args has action if we relied on it above.
|
|
273
|
+
if (normalized.action && !normalizedArgs.action) {
|
|
274
|
+
normalizedArgs.action = normalized.action;
|
|
299
275
|
}
|
|
300
|
-
const defaultHandler = async () => invokeNamedTool(normalizedName, action, normalizedArgs, tools);
|
|
301
276
|
|
|
302
|
-
|
|
303
|
-
|
|
277
|
+
const handler = toolRegistry.getHandler(normalizedName);
|
|
278
|
+
|
|
279
|
+
if (handler) {
|
|
280
|
+
return await handler(normalizedArgs, tools);
|
|
304
281
|
}
|
|
305
282
|
|
|
306
|
-
|
|
283
|
+
// Fallback or Unknown
|
|
284
|
+
return cleanObject({ success: false, error: 'UNKNOWN_TOOL', message: `Unknown consolidated tool: ${name}` });
|
|
285
|
+
|
|
307
286
|
} catch (err: any) {
|
|
308
287
|
const duration = Date.now() - startTime;
|
|
309
288
|
logger.error(`Failed execution of ${name} after ${duration}ms: ${err?.message || String(err)}`);
|
|
@@ -1,151 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { ITools } from '../types/tool-interfaces.js';
|
|
3
|
-
import fs from 'fs/promises';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { pathToFileURL } from 'node:url';
|
|
1
|
+
import { ITools } from '../types/tool-interfaces.js';
|
|
6
2
|
|
|
7
|
-
|
|
3
|
+
type ToolHandler = (args: any, tools: ITools) => Promise<any>;
|
|
8
4
|
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
action: string;
|
|
12
|
-
args: any;
|
|
13
|
-
tools: ITools;
|
|
14
|
-
defaultHandler: () => Promise<any>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type DynamicHandlerFn = (ctx: DynamicHandlerContext) => Promise<any>;
|
|
18
|
-
|
|
19
|
-
interface RawHandlerEntry {
|
|
20
|
-
tool: string;
|
|
21
|
-
module: string;
|
|
22
|
-
function?: string;
|
|
23
|
-
actions?: string[];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface RawRegistry {
|
|
27
|
-
handlers?: RawHandlerEntry[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface LoadedHandler {
|
|
31
|
-
tool: string;
|
|
32
|
-
actions?: string[];
|
|
33
|
-
fn: DynamicHandlerFn;
|
|
34
|
-
}
|
|
5
|
+
export class DynamicHandlerRegistry {
|
|
6
|
+
private handlers = new Map<string, ToolHandler>();
|
|
35
7
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
async function loadRegistryIfNeeded(): Promise<void> {
|
|
40
|
-
if (registryLoaded) return;
|
|
41
|
-
registryLoaded = true;
|
|
42
|
-
|
|
43
|
-
// Project root (works for both src/ and dist/ builds)
|
|
44
|
-
const rootUrl = new URL('../..', import.meta.url);
|
|
45
|
-
|
|
46
|
-
const envPath = (process.env.MCP_HANDLER_REGISTRY || '').trim();
|
|
47
|
-
let configUrl: URL | null = null;
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
if (envPath) {
|
|
51
|
-
// Allow absolute paths, file:// URLs, or paths relative to project root
|
|
52
|
-
if (envPath.startsWith('file:')) {
|
|
53
|
-
configUrl = new URL(envPath);
|
|
54
|
-
} else if (path.isAbsolute(envPath)) {
|
|
55
|
-
configUrl = pathToFileURL(envPath);
|
|
56
|
-
} else {
|
|
57
|
-
configUrl = new URL(envPath, rootUrl);
|
|
58
|
-
}
|
|
59
|
-
} else {
|
|
60
|
-
// Default: handler-registry.json at project root
|
|
61
|
-
configUrl = new URL('handler-registry.json', rootUrl);
|
|
8
|
+
register(toolName: string, handler: ToolHandler) {
|
|
9
|
+
if (this.handlers.has(toolName)) {
|
|
10
|
+
console.warn(`Handler for tool '${toolName}' is being overwritten.`);
|
|
62
11
|
}
|
|
63
|
-
|
|
64
|
-
let jsonText: string;
|
|
65
|
-
try {
|
|
66
|
-
jsonText = await fs.readFile(configUrl, 'utf8');
|
|
67
|
-
} catch (err: any) {
|
|
68
|
-
if (err && (err.code === 'ENOENT' || err.code === 'ERR_MODULE_NOT_FOUND')) {
|
|
69
|
-
log.debug(`Handler registry not found at ${configUrl.href}; dynamic handlers disabled.`);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
log.warn(`Failed to read handler registry from ${configUrl.href}:`, err);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
let raw: RawRegistry;
|
|
77
|
-
try {
|
|
78
|
-
raw = JSON.parse(jsonText) as RawRegistry;
|
|
79
|
-
} catch (err) {
|
|
80
|
-
log.warn(`Invalid JSON in handler registry at ${configUrl.href}:`, err);
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!raw.handlers || !Array.isArray(raw.handlers)) {
|
|
85
|
-
log.debug('Handler registry contains no handlers.');
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
for (const entry of raw.handlers) {
|
|
90
|
-
if (!entry || typeof entry.tool !== 'string' || typeof entry.module !== 'string') {
|
|
91
|
-
continue;
|
|
92
|
-
}
|
|
93
|
-
try {
|
|
94
|
-
// Resolve module relative to project root by default
|
|
95
|
-
const modUrl = new URL(entry.module, rootUrl);
|
|
96
|
-
const mod: any = await import(modUrl.href);
|
|
97
|
-
|
|
98
|
-
const exportName = entry.function && entry.function.trim().length > 0
|
|
99
|
-
? entry.function.trim()
|
|
100
|
-
: 'default';
|
|
101
|
-
|
|
102
|
-
const fnExport = mod[exportName];
|
|
103
|
-
if (typeof fnExport !== 'function') {
|
|
104
|
-
log.warn(
|
|
105
|
-
`Dynamic handler module ${modUrl.href} for tool '${entry.tool}' does not export a callable '${exportName}'.`
|
|
106
|
-
);
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const wrapped: DynamicHandlerFn = fnExport;
|
|
111
|
-
loadedHandlers.push({
|
|
112
|
-
tool: entry.tool,
|
|
113
|
-
actions: Array.isArray(entry.actions) ? entry.actions.slice() : undefined,
|
|
114
|
-
fn: wrapped
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
log.info(`Registered dynamic handler for tool '${entry.tool}' from ${modUrl.href}`);
|
|
118
|
-
} catch (err) {
|
|
119
|
-
log.warn(
|
|
120
|
-
`Failed to load dynamic handler for tool '${entry.tool}' from module spec '${entry.module}':`,
|
|
121
|
-
err as any
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
} catch (err) {
|
|
126
|
-
log.warn('Unexpected error while loading handler registry:', err as any);
|
|
12
|
+
this.handlers.set(toolName, handler);
|
|
127
13
|
}
|
|
128
|
-
}
|
|
129
14
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
*/
|
|
134
|
-
export async function getDynamicHandlerForTool(
|
|
135
|
-
toolName: string,
|
|
136
|
-
action?: string
|
|
137
|
-
): Promise<DynamicHandlerFn | null> {
|
|
138
|
-
await loadRegistryIfNeeded();
|
|
139
|
-
if (!loadedHandlers.length) return null;
|
|
15
|
+
getHandler(toolName: string): ToolHandler | undefined {
|
|
16
|
+
return this.handlers.get(toolName);
|
|
17
|
+
}
|
|
140
18
|
|
|
141
|
-
|
|
142
|
-
|
|
19
|
+
hasHandler(toolName: string): boolean {
|
|
20
|
+
return this.handlers.has(toolName);
|
|
21
|
+
}
|
|
143
22
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (handler.actions && act && !handler.actions.includes(act)) continue;
|
|
147
|
-
return handler.fn;
|
|
23
|
+
removeHandler(toolName: string): boolean {
|
|
24
|
+
return this.handlers.delete(toolName);
|
|
148
25
|
}
|
|
149
26
|
|
|
150
|
-
|
|
27
|
+
getAllRegisteredTools(): string[] {
|
|
28
|
+
return Array.from(this.handlers.keys());
|
|
29
|
+
}
|
|
151
30
|
}
|
|
31
|
+
|
|
32
|
+
// Global registry instance
|
|
33
|
+
export const toolRegistry = new DynamicHandlerRegistry();
|