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/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
- tools: [
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
- switch (request.params.name) {
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.handleCreateScene(request.params.arguments);
3117
+ return await this.handleViaBridge('create_scene', request.params.arguments);
2942
3118
  case 'add_node':
2943
- return await this.handleAddNode(request.params.arguments);
3119
+ return await this.handleViaBridge('add_node', request.params.arguments);
2944
3120
  case 'load_sprite':
2945
- return await this.handleLoadSprite(request.params.arguments);
3121
+ return await this.handleViaBridge('load_sprite', request.params.arguments);
2946
3122
  case 'save_scene':
2947
- return await this.handleSaveScene(request.params.arguments);
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.handleListSceneNodes(request.params.arguments);
3130
+ return await this.handleViaBridge('list_scene_nodes', request.params.arguments);
2955
3131
  case 'get_node_properties':
2956
- return await this.handleGetNodeProperties(request.params.arguments);
3132
+ return await this.handleViaBridge('get_node_properties', request.params.arguments);
2957
3133
  case 'set_node_properties':
2958
- return await this.handleSetNodeProperties(request.params.arguments);
3134
+ return await this.handleViaBridge('set_node_properties', request.params.arguments);
2959
3135
  case 'delete_node':
2960
- return await this.handleDeleteNode(request.params.arguments);
3136
+ return await this.handleViaBridge('delete_node', request.params.arguments);
2961
3137
  case 'duplicate_node':
2962
- return await this.handleDuplicateNode(request.params.arguments);
3138
+ return await this.handleViaBridge('duplicate_node', request.params.arguments);
2963
3139
  case 'reparent_node':
2964
- return await this.handleReparentNode(request.params.arguments);
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.handleConnectSignal(request.params.arguments);
3180
+ return await this.handleViaBridge('connect_signal', request.params.arguments);
3005
3181
  case 'disconnect_signal':
3006
- return await this.handleDisconnectSignal(request.params.arguments);
3182
+ return await this.handleViaBridge('disconnect_signal', request.params.arguments);
3007
3183
  case 'list_connections':
3008
- return await this.handleListConnections(request.params.arguments);
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.handleCreateResource(request.params.arguments);
3198
+ return await this.handleViaBridge('create_resource', request.params.arguments);
3023
3199
  case 'create_material':
3024
- return await this.handleCreateMaterial(request.params.arguments);
3200
+ return await this.handleViaBridge('create_material', request.params.arguments);
3025
3201
  case 'create_shader':
3026
- return await this.handleCreateShader(request.params.arguments);
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.handleCreateAnimation(request.params.arguments);
3212
+ return await this.handleViaBridge('create_animation', request.params.arguments);
3037
3213
  case 'add_animation_track':
3038
- return await this.handleAddAnimationTrack(request.params.arguments);
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.handleCreateTileset(request.params.arguments);
3230
+ return await this.handleViaBridge('create_tileset', request.params.arguments);
3055
3231
  case 'set_tilemap_cells':
3056
- return await this.handleSetTilemapCells(request.params.arguments);
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.handleCreateNavigationRegion(request.params.arguments);
3246
+ return await this.handleViaBridge('create_navigation_region', request.params.arguments);
3071
3247
  case 'create_navigation_agent':
3072
- return await this.handleCreateNavigationAgent(request.params.arguments);
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.handleCreateAnimationTree(request.params.arguments);
3252
+ return await this.handleViaBridge('create_animation_tree', request.params.arguments);
3077
3253
  case 'add_animation_state':
3078
- return await this.handleAddAnimationState(request.params.arguments);
3254
+ return await this.handleViaBridge('add_animation_state', request.params.arguments);
3079
3255
  case 'connect_animation_states':
3080
- return await this.handleConnectAnimationStates(request.params.arguments);
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.handleSetThemeColor(request.params.arguments);
3259
+ return await this.handleViaBridge('set_theme_color', request.params.arguments);
3084
3260
  case 'set_theme_font_size':
3085
- return await this.handleSetThemeFontSize(request.params.arguments);
3261
+ return await this.handleViaBridge('set_theme_font_size', request.params.arguments);
3086
3262
  case 'apply_theme_shader':
3087
- return await this.handleApplyThemeShader(request.params.arguments);
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.handleModifyResource(request.params.arguments);
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(request.params.name, request.params.arguments);
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(request.params.name, request.params.arguments);
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
- this.startHealthServer();
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();