gopeak 2.1.0 → 2.2.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 +144 -75
- package/build/addon/godot_mcp_editor/mcp_client.gd +161 -0
- package/build/addon/godot_mcp_editor/plugin.cfg +6 -0
- package/build/addon/godot_mcp_editor/plugin.gd +84 -0
- package/build/addon/godot_mcp_editor/tool_executor.gd +114 -0
- package/build/addon/godot_mcp_editor/tools/animation_tools.gd +502 -0
- package/build/addon/godot_mcp_editor/tools/resource_tools.gd +425 -0
- package/build/addon/godot_mcp_editor/tools/scene_tools.gd +710 -0
- package/build/gdscript_parser.js +828 -0
- package/build/godot-bridge.js +470 -0
- package/build/index.js +284 -111
- package/build/visualizer/canvas.js +832 -0
- package/build/visualizer/events.js +814 -0
- package/build/visualizer/layout.js +304 -0
- package/build/visualizer/main.js +245 -0
- package/build/visualizer/modals.js +239 -0
- package/build/visualizer/panel.js +1091 -0
- package/build/visualizer/state.js +210 -0
- package/build/visualizer/syntax.js +106 -0
- package/build/visualizer/usages.js +352 -0
- package/build/visualizer/websocket.js +85 -0
- package/build/visualizer-server.js +375 -0
- package/build/visualizer.html +6395 -0
- package/package.json +6 -3
package/build/index.js
CHANGED
|
@@ -12,17 +12,18 @@ import { existsSync, readdirSync, mkdirSync, readFileSync, appendFileSync, write
|
|
|
12
12
|
import { spawn } from 'child_process';
|
|
13
13
|
import { promisify } from 'util';
|
|
14
14
|
import { exec } from 'child_process';
|
|
15
|
-
import { createServer } from 'http';
|
|
16
15
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
17
16
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
18
17
|
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
19
18
|
import { setupResourceHandlers } from './resources.js';
|
|
20
19
|
import { GodotLSPClient, createLSPTools, handleLSPTool } from './lsp_client.js';
|
|
21
20
|
import { GodotDAPClient, createDAPTools, handleDAPTool } from './dap_client.js';
|
|
21
|
+
import { mapProject } from './gdscript_parser.js';
|
|
22
|
+
import { serveVisualization, setProjectPath } from './visualizer-server.js';
|
|
23
|
+
import { getDefaultBridge } from './godot-bridge.js';
|
|
22
24
|
// Check if debug mode is enabled
|
|
23
25
|
const DEBUG_MODE = process.env.DEBUG === 'true';
|
|
24
26
|
const GODOT_DEBUG_MODE = true; // Always use GODOT DEBUG MODE
|
|
25
|
-
const HEALTH_PORT = parseInt(process.env.MCP_HEALTH_PORT || '8080', 10);
|
|
26
27
|
const execAsync = promisify(exec);
|
|
27
28
|
// Derive __filename and __dirname in ESM
|
|
28
29
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -44,6 +45,33 @@ class GodotServer {
|
|
|
44
45
|
logQueue = [];
|
|
45
46
|
logFlushTimer = null;
|
|
46
47
|
logFlushIntervalMs = 1500;
|
|
48
|
+
godotBridge;
|
|
49
|
+
cachedToolDefinitions = [];
|
|
50
|
+
toolExposureProfile;
|
|
51
|
+
toolsListPageSize;
|
|
52
|
+
compactAliasToLegacy = {
|
|
53
|
+
'tool.catalog': 'tool_catalog',
|
|
54
|
+
'project.list': 'list_projects',
|
|
55
|
+
'project.info': 'get_project_info',
|
|
56
|
+
'project.search': 'search_project',
|
|
57
|
+
'editor.launch': 'launch_editor',
|
|
58
|
+
'editor.run': 'run_project',
|
|
59
|
+
'editor.stop': 'stop_project',
|
|
60
|
+
'editor.debug_output': 'get_debug_output',
|
|
61
|
+
'editor.status': 'get_editor_status',
|
|
62
|
+
'scene.create': 'create_scene',
|
|
63
|
+
'scene.node.add': 'add_node',
|
|
64
|
+
'scene.save': 'save_scene',
|
|
65
|
+
'script.create': 'create_script',
|
|
66
|
+
'script.modify': 'modify_script',
|
|
67
|
+
'script.info': 'get_script_info',
|
|
68
|
+
'class.query': 'query_classes',
|
|
69
|
+
'class.info': 'query_class_info',
|
|
70
|
+
'runtime.status': 'get_runtime_status',
|
|
71
|
+
'visualizer.map': 'map_project',
|
|
72
|
+
'lsp.diagnostics': 'lsp_get_diagnostics',
|
|
73
|
+
'dap.output': 'dap_get_output',
|
|
74
|
+
};
|
|
47
75
|
/**
|
|
48
76
|
* Parameter name mappings between snake_case and camelCase
|
|
49
77
|
* This allows the server to accept both formats
|
|
@@ -91,6 +119,17 @@ class GodotServer {
|
|
|
91
119
|
*/
|
|
92
120
|
reverseParameterMappings = {};
|
|
93
121
|
constructor(config) {
|
|
122
|
+
const rawProfile = (process.env.GOPEAK_TOOL_PROFILE || process.env.MCP_TOOL_PROFILE || 'compact').toLowerCase();
|
|
123
|
+
if (rawProfile === 'full' || rawProfile === 'legacy' || rawProfile === 'compact') {
|
|
124
|
+
this.toolExposureProfile = rawProfile;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
this.toolExposureProfile = 'compact';
|
|
128
|
+
}
|
|
129
|
+
const rawToolsPageSize = parseInt(process.env.GOPEAK_TOOLS_PAGE_SIZE || '20', 10);
|
|
130
|
+
this.toolsListPageSize = Number.isFinite(rawToolsPageSize) && rawToolsPageSize > 0
|
|
131
|
+
? rawToolsPageSize
|
|
132
|
+
: 20;
|
|
94
133
|
// Initialize reverse parameter mappings
|
|
95
134
|
for (const [snakeCase, camelCase] of Object.entries(this.parameterMappings)) {
|
|
96
135
|
this.reverseParameterMappings[camelCase] = snakeCase;
|
|
@@ -122,6 +161,8 @@ class GodotServer {
|
|
|
122
161
|
}
|
|
123
162
|
// Set the path to the operations script
|
|
124
163
|
this.operationsScriptPath = join(__dirname, 'scripts', 'godot_operations.gd');
|
|
164
|
+
// Initialize the Godot Editor Bridge (WebSocket server for editor plugin)
|
|
165
|
+
this.godotBridge = getDefaultBridge();
|
|
125
166
|
if (debugMode)
|
|
126
167
|
console.error(`[DEBUG] Operations script path: ${this.operationsScriptPath}`);
|
|
127
168
|
// Initialize the MCP server
|
|
@@ -372,6 +413,12 @@ class GodotServer {
|
|
|
372
413
|
catch { }
|
|
373
414
|
this.dapClient = null;
|
|
374
415
|
}
|
|
416
|
+
if (this.godotBridge) {
|
|
417
|
+
try {
|
|
418
|
+
await this.godotBridge.stop();
|
|
419
|
+
}
|
|
420
|
+
catch { }
|
|
421
|
+
}
|
|
375
422
|
await this.server.close();
|
|
376
423
|
}
|
|
377
424
|
async handleRuntimeCommand(command, args) {
|
|
@@ -440,6 +487,86 @@ class GodotServer {
|
|
|
440
487
|
}
|
|
441
488
|
return handleDAPTool(this.dapClient, toolName, args);
|
|
442
489
|
}
|
|
490
|
+
resolveToolAlias(requestedToolName) {
|
|
491
|
+
return this.compactAliasToLegacy[requestedToolName] || requestedToolName;
|
|
492
|
+
}
|
|
493
|
+
buildCompactTools(allTools) {
|
|
494
|
+
const compactTools = [];
|
|
495
|
+
for (const [compactName, legacyName] of Object.entries(this.compactAliasToLegacy)) {
|
|
496
|
+
const source = allTools.find((tool) => tool.name === legacyName);
|
|
497
|
+
if (!source) {
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
compactTools.push({
|
|
501
|
+
...source,
|
|
502
|
+
name: compactName,
|
|
503
|
+
description: `[compact alias of ${legacyName}] ${source.description}`,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
return compactTools;
|
|
507
|
+
}
|
|
508
|
+
getExposedTools(allTools) {
|
|
509
|
+
if (this.toolExposureProfile === 'full' || this.toolExposureProfile === 'legacy') {
|
|
510
|
+
return allTools;
|
|
511
|
+
}
|
|
512
|
+
return this.buildCompactTools(allTools);
|
|
513
|
+
}
|
|
514
|
+
parseToolsListCursor(cursor, total) {
|
|
515
|
+
if (typeof cursor !== 'string' || cursor.length === 0) {
|
|
516
|
+
return 0;
|
|
517
|
+
}
|
|
518
|
+
const offset = Number.parseInt(cursor, 10);
|
|
519
|
+
if (!Number.isInteger(offset) || offset < 0 || offset > total) {
|
|
520
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid tools/list cursor: ${cursor}`);
|
|
521
|
+
}
|
|
522
|
+
return offset;
|
|
523
|
+
}
|
|
524
|
+
paginateToolsForList(tools, cursor) {
|
|
525
|
+
const start = this.parseToolsListCursor(cursor, tools.length);
|
|
526
|
+
const end = Math.min(start + this.toolsListPageSize, tools.length);
|
|
527
|
+
const page = tools.slice(start, end);
|
|
528
|
+
if (end < tools.length) {
|
|
529
|
+
return {
|
|
530
|
+
tools: page,
|
|
531
|
+
nextCursor: String(end),
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
return { tools: page };
|
|
535
|
+
}
|
|
536
|
+
async handleToolCatalog(args) {
|
|
537
|
+
const normalizedArgs = this.normalizeParameters(args || {});
|
|
538
|
+
const query = typeof normalizedArgs.query === 'string' ? normalizedArgs.query.trim().toLowerCase() : '';
|
|
539
|
+
const rawLimit = typeof normalizedArgs.limit === 'number' ? normalizedArgs.limit : 30;
|
|
540
|
+
const limit = Math.max(1, Math.min(100, rawLimit));
|
|
541
|
+
const tools = this.cachedToolDefinitions;
|
|
542
|
+
const reverseAlias = new Map();
|
|
543
|
+
for (const [compactName, legacyName] of Object.entries(this.compactAliasToLegacy)) {
|
|
544
|
+
reverseAlias.set(legacyName, compactName);
|
|
545
|
+
}
|
|
546
|
+
const filtered = tools.filter((tool) => {
|
|
547
|
+
if (!query)
|
|
548
|
+
return true;
|
|
549
|
+
const haystack = `${tool.name} ${tool.description}`.toLowerCase();
|
|
550
|
+
return haystack.includes(query);
|
|
551
|
+
});
|
|
552
|
+
const items = filtered.slice(0, limit).map((tool) => ({
|
|
553
|
+
tool: tool.name,
|
|
554
|
+
compactAlias: reverseAlias.get(tool.name) || null,
|
|
555
|
+
description: tool.description,
|
|
556
|
+
}));
|
|
557
|
+
return {
|
|
558
|
+
content: [{
|
|
559
|
+
type: 'text',
|
|
560
|
+
text: JSON.stringify({
|
|
561
|
+
profile: this.toolExposureProfile,
|
|
562
|
+
totalTools: tools.length,
|
|
563
|
+
query: query || null,
|
|
564
|
+
returned: items.length,
|
|
565
|
+
tools: items,
|
|
566
|
+
}, null, 2),
|
|
567
|
+
}],
|
|
568
|
+
};
|
|
569
|
+
}
|
|
443
570
|
/**
|
|
444
571
|
* Check if the Godot version is 4.4 or later
|
|
445
572
|
* @param version The Godot version string
|
|
@@ -684,8 +811,8 @@ class GodotServer {
|
|
|
684
811
|
*/
|
|
685
812
|
setupToolHandlers() {
|
|
686
813
|
// Define available tools
|
|
687
|
-
this.server.setRequestHandler(ListToolsRequestSchema, async () =>
|
|
688
|
-
|
|
814
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async (request) => {
|
|
815
|
+
const allTools = [
|
|
689
816
|
{
|
|
690
817
|
name: 'launch_editor',
|
|
691
818
|
description: 'Opens the Godot editor GUI for a project. Use when visual inspection or manual editing of scenes/scripts is needed. Opens a new window on the host system. Requires: project directory with project.godot file.',
|
|
@@ -975,6 +1102,18 @@ class GodotServer {
|
|
|
975
1102
|
required: [],
|
|
976
1103
|
},
|
|
977
1104
|
},
|
|
1105
|
+
{
|
|
1106
|
+
name: 'tool_catalog',
|
|
1107
|
+
description: 'Discover available tools including hidden legacy tools. Use query to search by capability keywords (e.g., animation, import, tilemap, audio).',
|
|
1108
|
+
inputSchema: {
|
|
1109
|
+
type: 'object',
|
|
1110
|
+
properties: {
|
|
1111
|
+
query: { type: 'string', description: 'Optional keyword search over tool names and descriptions.' },
|
|
1112
|
+
limit: { type: 'number', description: 'Maximum results to return. Default: 30, max: 100.' },
|
|
1113
|
+
},
|
|
1114
|
+
required: [],
|
|
1115
|
+
},
|
|
1116
|
+
},
|
|
978
1117
|
{
|
|
979
1118
|
name: 'create_scene',
|
|
980
1119
|
description: 'Creates a new Godot scene file (.tscn) with a specified root node type. Use to start building new game levels, UI screens, or reusable components. The scene is saved automatically after creation.',
|
|
@@ -1990,6 +2129,10 @@ class GodotServer {
|
|
|
1990
2129
|
type: 'string',
|
|
1991
2130
|
description: 'Optional: template name - "singleton", "state_machine", "component", "resource"',
|
|
1992
2131
|
},
|
|
2132
|
+
reason: {
|
|
2133
|
+
type: 'string',
|
|
2134
|
+
description: 'Optional reason/context for this change. Displayed in visualizer audit timeline.',
|
|
2135
|
+
},
|
|
1993
2136
|
},
|
|
1994
2137
|
required: ['projectPath', 'scriptPath'],
|
|
1995
2138
|
},
|
|
@@ -2062,6 +2205,10 @@ class GodotServer {
|
|
|
2062
2205
|
required: ['type', 'name'],
|
|
2063
2206
|
},
|
|
2064
2207
|
},
|
|
2208
|
+
reason: {
|
|
2209
|
+
type: 'string',
|
|
2210
|
+
description: 'Optional reason/context for this change. Displayed in visualizer audit timeline.',
|
|
2211
|
+
},
|
|
2065
2212
|
},
|
|
2066
2213
|
required: ['projectPath', 'scriptPath', 'modifications'],
|
|
2067
2214
|
},
|
|
@@ -2885,12 +3032,38 @@ class GodotServer {
|
|
|
2885
3032
|
required: ['x', 'y'],
|
|
2886
3033
|
},
|
|
2887
3034
|
},
|
|
3035
|
+
// Editor Plugin Bridge Status
|
|
3036
|
+
{
|
|
3037
|
+
name: 'get_editor_status',
|
|
3038
|
+
description: 'Returns the connection status of the Godot Editor Plugin bridge. Use to check if the editor is connected before using scene/resource tools that require the editor plugin.',
|
|
3039
|
+
inputSchema: {
|
|
3040
|
+
type: 'object',
|
|
3041
|
+
properties: {},
|
|
3042
|
+
},
|
|
3043
|
+
},
|
|
3044
|
+
// Project Visualizer Tool
|
|
3045
|
+
{
|
|
3046
|
+
name: 'map_project',
|
|
3047
|
+
description: 'Crawl the entire Godot project and build an interactive visual map of all scripts showing their structure (variables, functions, signals), connections (extends, preloads, signal connections), and descriptions. Opens an interactive browser-based visualization at localhost:6505.',
|
|
3048
|
+
inputSchema: {
|
|
3049
|
+
type: 'object',
|
|
3050
|
+
properties: {
|
|
3051
|
+
projectPath: { type: 'string', description: 'Absolute path to the Godot project directory' },
|
|
3052
|
+
root: { type: 'string', description: 'Root path to start crawling from (default: res://)' },
|
|
3053
|
+
include_addons: { type: 'boolean', description: 'Whether to include scripts in addons/ folder (default: false)' },
|
|
3054
|
+
},
|
|
3055
|
+
required: ['projectPath'],
|
|
3056
|
+
},
|
|
3057
|
+
},
|
|
2888
3058
|
// Godot LSP Tools (GDScript diagnostics via Godot editor LSP on port 6005)
|
|
2889
3059
|
...createLSPTools(),
|
|
2890
3060
|
// Godot DAP Tools (Debug Adapter Protocol via Godot editor DAP on port 6006)
|
|
2891
3061
|
...createDAPTools(),
|
|
2892
|
-
]
|
|
2893
|
-
|
|
3062
|
+
];
|
|
3063
|
+
this.cachedToolDefinitions = allTools;
|
|
3064
|
+
const exposedTools = this.getExposedTools(allTools);
|
|
3065
|
+
return this.paginateToolsForList(exposedTools, request.params?.cursor);
|
|
3066
|
+
});
|
|
2894
3067
|
// Handle tool calls
|
|
2895
3068
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2896
3069
|
this.logDebug(`Handling tool request: ${request.params.name}`);
|
|
@@ -2898,7 +3071,8 @@ class GodotServer {
|
|
|
2898
3071
|
if (rawArgs?.projectPath && typeof rawArgs.projectPath === 'string') {
|
|
2899
3072
|
this.lastProjectPath = rawArgs.projectPath;
|
|
2900
3073
|
}
|
|
2901
|
-
|
|
3074
|
+
const resolvedToolName = this.resolveToolAlias(request.params.name);
|
|
3075
|
+
switch (resolvedToolName) {
|
|
2902
3076
|
case 'launch_editor':
|
|
2903
3077
|
return await this.handleLaunchEditor(request.params.arguments);
|
|
2904
3078
|
case 'run_project':
|
|
@@ -2937,31 +3111,33 @@ class GodotServer {
|
|
|
2937
3111
|
return await this.handleSetRecordingMode(request.params.arguments);
|
|
2938
3112
|
case 'get_recording_mode':
|
|
2939
3113
|
return await this.handleGetRecordingMode();
|
|
3114
|
+
case 'tool_catalog':
|
|
3115
|
+
return await this.handleToolCatalog(request.params.arguments);
|
|
2940
3116
|
case 'create_scene':
|
|
2941
|
-
return await this.
|
|
3117
|
+
return await this.handleViaBridge('create_scene', request.params.arguments);
|
|
2942
3118
|
case 'add_node':
|
|
2943
|
-
return await this.
|
|
3119
|
+
return await this.handleViaBridge('add_node', request.params.arguments);
|
|
2944
3120
|
case 'load_sprite':
|
|
2945
|
-
return await this.
|
|
3121
|
+
return await this.handleViaBridge('load_sprite', request.params.arguments);
|
|
2946
3122
|
case 'save_scene':
|
|
2947
|
-
return await this.
|
|
3123
|
+
return await this.handleViaBridge('save_scene', request.params.arguments);
|
|
2948
3124
|
case 'get_uid':
|
|
2949
3125
|
return await this.handleGetUid(request.params.arguments);
|
|
2950
3126
|
case 'update_project_uids':
|
|
2951
3127
|
return await this.handleUpdateProjectUids(request.params.arguments);
|
|
2952
3128
|
// Phase 1: Scene Operations handlers
|
|
2953
3129
|
case 'list_scene_nodes':
|
|
2954
|
-
return await this.
|
|
3130
|
+
return await this.handleViaBridge('list_scene_nodes', request.params.arguments);
|
|
2955
3131
|
case 'get_node_properties':
|
|
2956
|
-
return await this.
|
|
3132
|
+
return await this.handleViaBridge('get_node_properties', request.params.arguments);
|
|
2957
3133
|
case 'set_node_properties':
|
|
2958
|
-
return await this.
|
|
3134
|
+
return await this.handleViaBridge('set_node_properties', request.params.arguments);
|
|
2959
3135
|
case 'delete_node':
|
|
2960
|
-
return await this.
|
|
3136
|
+
return await this.handleViaBridge('delete_node', request.params.arguments);
|
|
2961
3137
|
case 'duplicate_node':
|
|
2962
|
-
return await this.
|
|
3138
|
+
return await this.handleViaBridge('duplicate_node', request.params.arguments);
|
|
2963
3139
|
case 'reparent_node':
|
|
2964
|
-
return await this.
|
|
3140
|
+
return await this.handleViaBridge('reparent_node', request.params.arguments);
|
|
2965
3141
|
// Phase 2: Import/Export Pipeline handlers
|
|
2966
3142
|
case 'get_import_status':
|
|
2967
3143
|
return await this.handleGetImportStatus(request.params.arguments);
|
|
@@ -3001,11 +3177,11 @@ class GodotServer {
|
|
|
3001
3177
|
return await this.handleSetMainScene(request.params.arguments);
|
|
3002
3178
|
// Signal Management handlers
|
|
3003
3179
|
case 'connect_signal':
|
|
3004
|
-
return await this.
|
|
3180
|
+
return await this.handleViaBridge('connect_signal', request.params.arguments);
|
|
3005
3181
|
case 'disconnect_signal':
|
|
3006
|
-
return await this.
|
|
3182
|
+
return await this.handleViaBridge('disconnect_signal', request.params.arguments);
|
|
3007
3183
|
case 'list_connections':
|
|
3008
|
-
return await this.
|
|
3184
|
+
return await this.handleViaBridge('list_connections', request.params.arguments);
|
|
3009
3185
|
// Phase 4: Runtime Tools handlers
|
|
3010
3186
|
case 'get_runtime_status':
|
|
3011
3187
|
return await this.handleGetRuntimeStatus(request.params.arguments);
|
|
@@ -3019,11 +3195,11 @@ class GodotServer {
|
|
|
3019
3195
|
return await this.handleGetRuntimeMetrics(request.params.arguments);
|
|
3020
3196
|
// Resource Creation Tools handlers
|
|
3021
3197
|
case 'create_resource':
|
|
3022
|
-
return await this.
|
|
3198
|
+
return await this.handleViaBridge('create_resource', request.params.arguments);
|
|
3023
3199
|
case 'create_material':
|
|
3024
|
-
return await this.
|
|
3200
|
+
return await this.handleViaBridge('create_material', request.params.arguments);
|
|
3025
3201
|
case 'create_shader':
|
|
3026
|
-
return await this.
|
|
3202
|
+
return await this.handleViaBridge('create_shader', request.params.arguments);
|
|
3027
3203
|
// GDScript File Operations handlers
|
|
3028
3204
|
case 'create_script':
|
|
3029
3205
|
return await this.handleCreateScript(request.params.arguments);
|
|
@@ -3033,9 +3209,9 @@ class GodotServer {
|
|
|
3033
3209
|
return await this.handleGetScriptInfo(request.params.arguments);
|
|
3034
3210
|
// Animation Tools handlers
|
|
3035
3211
|
case 'create_animation':
|
|
3036
|
-
return await this.
|
|
3212
|
+
return await this.handleViaBridge('create_animation', request.params.arguments);
|
|
3037
3213
|
case 'add_animation_track':
|
|
3038
|
-
return await this.
|
|
3214
|
+
return await this.handleViaBridge('add_animation_track', request.params.arguments);
|
|
3039
3215
|
// Plugin Management handlers
|
|
3040
3216
|
case 'list_plugins':
|
|
3041
3217
|
return await this.handleListPlugins(request.params.arguments);
|
|
@@ -3051,9 +3227,9 @@ class GodotServer {
|
|
|
3051
3227
|
return await this.handleSearchProject(request.params.arguments);
|
|
3052
3228
|
// 2D Tile Tools handlers
|
|
3053
3229
|
case 'create_tileset':
|
|
3054
|
-
return await this.
|
|
3230
|
+
return await this.handleViaBridge('create_tileset', request.params.arguments);
|
|
3055
3231
|
case 'set_tilemap_cells':
|
|
3056
|
-
return await this.
|
|
3232
|
+
return await this.handleViaBridge('set_tilemap_cells', request.params.arguments);
|
|
3057
3233
|
// Audio System Tools handlers
|
|
3058
3234
|
case 'create_audio_bus':
|
|
3059
3235
|
return await this.handleCreateAudioBus(request.params.arguments);
|
|
@@ -3067,24 +3243,24 @@ class GodotServer {
|
|
|
3067
3243
|
// Physics Tools handlers
|
|
3068
3244
|
// Navigation Tools handlers
|
|
3069
3245
|
case 'create_navigation_region':
|
|
3070
|
-
return await this.
|
|
3246
|
+
return await this.handleViaBridge('create_navigation_region', request.params.arguments);
|
|
3071
3247
|
case 'create_navigation_agent':
|
|
3072
|
-
return await this.
|
|
3248
|
+
return await this.handleViaBridge('create_navigation_agent', request.params.arguments);
|
|
3073
3249
|
// Rendering Tools handlers
|
|
3074
3250
|
// Animation Tree Tools handlers
|
|
3075
3251
|
case 'create_animation_tree':
|
|
3076
|
-
return await this.
|
|
3252
|
+
return await this.handleViaBridge('create_animation_tree', request.params.arguments);
|
|
3077
3253
|
case 'add_animation_state':
|
|
3078
|
-
return await this.
|
|
3254
|
+
return await this.handleViaBridge('add_animation_state', request.params.arguments);
|
|
3079
3255
|
case 'connect_animation_states':
|
|
3080
|
-
return await this.
|
|
3256
|
+
return await this.handleViaBridge('connect_animation_states', request.params.arguments);
|
|
3081
3257
|
// UI/Theme Tools handlers
|
|
3082
3258
|
case 'set_theme_color':
|
|
3083
|
-
return await this.
|
|
3259
|
+
return await this.handleViaBridge('set_theme_color', request.params.arguments);
|
|
3084
3260
|
case 'set_theme_font_size':
|
|
3085
|
-
return await this.
|
|
3261
|
+
return await this.handleViaBridge('set_theme_font_size', request.params.arguments);
|
|
3086
3262
|
case 'apply_theme_shader':
|
|
3087
|
-
return await this.
|
|
3263
|
+
return await this.handleViaBridge('apply_theme_shader', request.params.arguments);
|
|
3088
3264
|
case 'search_assets':
|
|
3089
3265
|
return await this.handleSearchAssets(request.params.arguments);
|
|
3090
3266
|
case 'fetch_asset':
|
|
@@ -3100,7 +3276,13 @@ class GodotServer {
|
|
|
3100
3276
|
return await this.handleInspectInheritance(request.params.arguments);
|
|
3101
3277
|
// Resource Modification Tool
|
|
3102
3278
|
case 'modify_resource':
|
|
3103
|
-
return await this.
|
|
3279
|
+
return await this.handleViaBridge('modify_resource', request.params.arguments);
|
|
3280
|
+
// Editor Plugin Bridge Status
|
|
3281
|
+
case 'get_editor_status':
|
|
3282
|
+
return { content: [{ type: 'text', text: JSON.stringify(this.godotBridge.getStatus(), null, 2) }] };
|
|
3283
|
+
// Project Visualizer Tool
|
|
3284
|
+
case 'map_project':
|
|
3285
|
+
return await this.handleMapProject(request.params.arguments);
|
|
3104
3286
|
case 'capture_screenshot':
|
|
3105
3287
|
return await this.handleRuntimeCommand('capture_screenshot', request.params.arguments);
|
|
3106
3288
|
case 'capture_viewport':
|
|
@@ -3117,7 +3299,7 @@ class GodotServer {
|
|
|
3117
3299
|
case 'lsp_get_completions':
|
|
3118
3300
|
case 'lsp_get_hover':
|
|
3119
3301
|
case 'lsp_get_symbols':
|
|
3120
|
-
return await this.handleLSP(
|
|
3302
|
+
return await this.handleLSP(resolvedToolName, request.params.arguments);
|
|
3121
3303
|
case 'dap_get_output':
|
|
3122
3304
|
case 'dap_set_breakpoint':
|
|
3123
3305
|
case 'dap_remove_breakpoint':
|
|
@@ -3125,7 +3307,7 @@ class GodotServer {
|
|
|
3125
3307
|
case 'dap_pause':
|
|
3126
3308
|
case 'dap_step_over':
|
|
3127
3309
|
case 'dap_get_stack_trace':
|
|
3128
|
-
return await this.handleDAP(
|
|
3310
|
+
return await this.handleDAP(resolvedToolName, request.params.arguments);
|
|
3129
3311
|
default:
|
|
3130
3312
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
3131
3313
|
}
|
|
@@ -3271,6 +3453,33 @@ class GodotServer {
|
|
|
3271
3453
|
]);
|
|
3272
3454
|
}
|
|
3273
3455
|
}
|
|
3456
|
+
/**
|
|
3457
|
+
* Route a tool call through the Godot Editor Plugin bridge (WebSocket).
|
|
3458
|
+
* Returns an error response if the editor is not connected.
|
|
3459
|
+
*/
|
|
3460
|
+
async handleViaBridge(toolName, args) {
|
|
3461
|
+
if (!this.godotBridge.isConnected()) {
|
|
3462
|
+
return {
|
|
3463
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
3464
|
+
error: 'Godot Editor not connected. Launch Godot Editor and enable the "Godot MCP Editor" plugin to use this tool.',
|
|
3465
|
+
suggestion: 'Use the launch_editor tool to open the Godot Editor, then enable the plugin in Project > Project Settings > Plugins.',
|
|
3466
|
+
}, null, 2) }],
|
|
3467
|
+
isError: true,
|
|
3468
|
+
};
|
|
3469
|
+
}
|
|
3470
|
+
try {
|
|
3471
|
+
const result = await this.godotBridge.invokeTool(toolName, args);
|
|
3472
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
3473
|
+
}
|
|
3474
|
+
catch (error) {
|
|
3475
|
+
return {
|
|
3476
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
3477
|
+
error: error instanceof Error ? error.message : String(error),
|
|
3478
|
+
}, null, 2) }],
|
|
3479
|
+
isError: true,
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3274
3483
|
/**
|
|
3275
3484
|
* Handle the get_debug_output tool
|
|
3276
3485
|
*/
|
|
@@ -6315,7 +6524,9 @@ class GodotServer {
|
|
|
6315
6524
|
const transport = new StdioServerTransport();
|
|
6316
6525
|
await this.server.connect(transport);
|
|
6317
6526
|
console.error('Godot MCP server running on stdio');
|
|
6318
|
-
|
|
6527
|
+
// Start the Godot Editor Bridge (WebSocket server for editor plugin)
|
|
6528
|
+
await this.godotBridge.start();
|
|
6529
|
+
console.error('[SERVER] Godot Editor Bridge started on port 6505');
|
|
6319
6530
|
}
|
|
6320
6531
|
catch (error) {
|
|
6321
6532
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
@@ -6323,78 +6534,6 @@ class GodotServer {
|
|
|
6323
6534
|
process.exit(1);
|
|
6324
6535
|
}
|
|
6325
6536
|
}
|
|
6326
|
-
/**
|
|
6327
|
-
* Start a lightweight HTTP server for health checks from Godot editor plugin.
|
|
6328
|
-
* Supports GET /health and POST / (MCP initialize handshake).
|
|
6329
|
-
*/
|
|
6330
|
-
startHealthServer() {
|
|
6331
|
-
const httpServer = createServer((req, res) => {
|
|
6332
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
6333
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
6334
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept');
|
|
6335
|
-
if (req.method === 'OPTIONS') {
|
|
6336
|
-
res.writeHead(204);
|
|
6337
|
-
res.end();
|
|
6338
|
-
return;
|
|
6339
|
-
}
|
|
6340
|
-
if (req.method === 'GET' && (req.url === '/health' || req.url === '/')) {
|
|
6341
|
-
const payload = {
|
|
6342
|
-
status: 'ok',
|
|
6343
|
-
serverName: 'godot-mcp',
|
|
6344
|
-
version: '1.1.0',
|
|
6345
|
-
godotPath: this.godotPath,
|
|
6346
|
-
uptime: process.uptime(),
|
|
6347
|
-
timestamp: new Date().toISOString(),
|
|
6348
|
-
};
|
|
6349
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
6350
|
-
res.end(JSON.stringify(payload));
|
|
6351
|
-
return;
|
|
6352
|
-
}
|
|
6353
|
-
if (req.method === 'POST' && (req.url === '/' || req.url === '/mcp')) {
|
|
6354
|
-
let body = '';
|
|
6355
|
-
req.on('data', (chunk) => { body += chunk.toString(); });
|
|
6356
|
-
req.on('end', () => {
|
|
6357
|
-
try {
|
|
6358
|
-
const parsed = JSON.parse(body);
|
|
6359
|
-
if (parsed.method === 'initialize') {
|
|
6360
|
-
const response = {
|
|
6361
|
-
jsonrpc: '2.0',
|
|
6362
|
-
id: parsed.id ?? 1,
|
|
6363
|
-
result: {
|
|
6364
|
-
protocolVersion: parsed.params?.protocolVersion || '2025-06-18',
|
|
6365
|
-
capabilities: {},
|
|
6366
|
-
serverInfo: { name: 'godot-mcp', version: '1.1.0' },
|
|
6367
|
-
},
|
|
6368
|
-
};
|
|
6369
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
6370
|
-
res.end(JSON.stringify(response));
|
|
6371
|
-
return;
|
|
6372
|
-
}
|
|
6373
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
6374
|
-
res.end(JSON.stringify({ error: 'Unsupported method' }));
|
|
6375
|
-
}
|
|
6376
|
-
catch {
|
|
6377
|
-
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
6378
|
-
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
6379
|
-
}
|
|
6380
|
-
});
|
|
6381
|
-
return;
|
|
6382
|
-
}
|
|
6383
|
-
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
6384
|
-
res.end(JSON.stringify({ error: 'Not found' }));
|
|
6385
|
-
});
|
|
6386
|
-
httpServer.listen(HEALTH_PORT, () => {
|
|
6387
|
-
console.error(`[SERVER] Health endpoint: http://localhost:${HEALTH_PORT}/health`);
|
|
6388
|
-
});
|
|
6389
|
-
httpServer.on('error', (err) => {
|
|
6390
|
-
if (err.code === 'EADDRINUSE') {
|
|
6391
|
-
console.error(`[SERVER] Port ${HEALTH_PORT} in use — health endpoint disabled`);
|
|
6392
|
-
}
|
|
6393
|
-
else {
|
|
6394
|
-
console.error(`[SERVER] Health endpoint error: ${err.message}`);
|
|
6395
|
-
}
|
|
6396
|
-
});
|
|
6397
|
-
}
|
|
6398
6537
|
// ============================================
|
|
6399
6538
|
// 2D Tile Tools Handlers
|
|
6400
6539
|
// ============================================
|
|
@@ -7183,6 +7322,40 @@ uniform float dissolve_amount : hint_range(0.0, 1.0) = 0.0;
|
|
|
7183
7322
|
}
|
|
7184
7323
|
return await this.executeOperation('modify_resource', params, projectPath);
|
|
7185
7324
|
}
|
|
7325
|
+
async handleMapProject(args) {
|
|
7326
|
+
const projectPath = args?.projectPath || args?.project_path;
|
|
7327
|
+
if (!projectPath) {
|
|
7328
|
+
throw new McpError(ErrorCode.InvalidParams, 'projectPath is required');
|
|
7329
|
+
}
|
|
7330
|
+
const root = args?.root || 'res://';
|
|
7331
|
+
const includeAddons = args?.include_addons || false;
|
|
7332
|
+
const result = mapProject(projectPath, root, includeAddons);
|
|
7333
|
+
if (!result.ok || !result.project_map) {
|
|
7334
|
+
return {
|
|
7335
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: false, error: result.error || 'Failed to map project' }) }],
|
|
7336
|
+
isError: true,
|
|
7337
|
+
};
|
|
7338
|
+
}
|
|
7339
|
+
setProjectPath(projectPath);
|
|
7340
|
+
try {
|
|
7341
|
+
const url = await serveVisualization(result.project_map, this.godotBridge);
|
|
7342
|
+
return {
|
|
7343
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
7344
|
+
ok: true,
|
|
7345
|
+
url,
|
|
7346
|
+
total_scripts: result.project_map.total_scripts,
|
|
7347
|
+
total_connections: result.project_map.total_connections,
|
|
7348
|
+
message: `Interactive project map opened at ${url} — ${result.project_map.total_scripts} scripts, ${result.project_map.total_connections} connections`,
|
|
7349
|
+
}, null, 2) }],
|
|
7350
|
+
};
|
|
7351
|
+
}
|
|
7352
|
+
catch (error) {
|
|
7353
|
+
const errMsg = error instanceof Error ? error.message : 'Unknown error';
|
|
7354
|
+
return {
|
|
7355
|
+
content: [{ type: 'text', text: JSON.stringify({ ok: false, error: `Failed to start visualizer: ${errMsg}`, project_map: result.project_map }) }],
|
|
7356
|
+
};
|
|
7357
|
+
}
|
|
7358
|
+
}
|
|
7186
7359
|
}
|
|
7187
7360
|
// Create and run the server
|
|
7188
7361
|
const server = new GodotServer();
|