gopeak 2.3.3 → 2.3.4

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 CHANGED
@@ -14,6 +14,8 @@
14
14
 
15
15
  **GoPeak is an MCP server for Godot that lets AI assistants run, inspect, modify, and debug real projects end-to-end.**
16
16
 
17
+ > Discord community chat is temporarily unavailable while the invite link is refreshed. Please use GitHub Discussions in the meantime: https://github.com/HaD0Yun/Gopeak-godot-mcp/discussions
18
+
17
19
  ---
18
20
 
19
21
  ## Quick Start (3 Minutes)
@@ -5,7 +5,7 @@
5
5
  * with a precheck function that displays cached GoPeak update notifications.
6
6
  */
7
7
  import { existsSync, readFileSync, writeFileSync, appendFileSync } from 'fs';
8
- import { getShellRcFile, getShellName, getLocalVersion, ensureGopeakDir, ONBOARDING_SHOWN_FILE, STAR_PROMPTED_FILE, } from './utils.js';
8
+ import { getShellRcFile, getShellName, getLocalVersion, ensureGopeakDir, ONBOARDING_SHOWN_FILE, STAR_PROMPTED_FILE, supportsShellHooks, } from './utils.js';
9
9
  const MARKER_START = '# >>> GoPeak shell hooks >>>';
10
10
  const MARKER_END = '# <<< GoPeak shell hooks <<<';
11
11
  /** The shell hook block that gets appended to the RC file. */
@@ -55,6 +55,13 @@ function generateHookBlock() {
55
55
  }
56
56
  export async function setupShellHooks(args = []) {
57
57
  const silent = args.includes('--silent');
58
+ if (!supportsShellHooks()) {
59
+ if (!silent) {
60
+ console.log('ℹ️ GoPeak shell hooks are only installed for bash/zsh on Unix-like systems.');
61
+ console.log(' Skipping shell hook setup on this platform/shell.');
62
+ }
63
+ return;
64
+ }
58
65
  const rcFile = getShellRcFile();
59
66
  const shellName = getShellName();
60
67
  const log = silent ? (..._args) => { } : console.log.bind(console);
@@ -122,6 +122,11 @@ export function getShellName() {
122
122
  return 'zsh';
123
123
  return 'bash';
124
124
  }
125
+ export function supportsShellHooks(platform = process.platform, shell = process.env.SHELL ?? '') {
126
+ if (platform === 'win32')
127
+ return false;
128
+ return shell.includes('bash') || shell.includes('zsh');
129
+ }
125
130
  /* ------------------------------------------------------------------ */
126
131
  /* Command helpers */
127
132
  /* ------------------------------------------------------------------ */
package/build/index.js CHANGED
@@ -694,7 +694,7 @@ class GodotServer {
694
694
  resolve({
695
695
  content: [
696
696
  { type: 'text', text: `Screenshot captured: ${parsed.width}x${parsed.height} ${parsed.format}` },
697
- { type: 'image', text: parsed.data },
697
+ { type: 'image', data: parsed.data, mimeType: 'image/png' },
698
698
  ],
699
699
  });
700
700
  return;
@@ -793,8 +793,41 @@ class GodotServer {
793
793
  }
794
794
  return handleDAPTool(this.dapClient, toolName, args);
795
795
  }
796
+ sanitizeExportedToolName(toolName) {
797
+ const sanitized = toolName
798
+ .normalize('NFKD')
799
+ .replace(/[^\x00-\x7F]/g, '')
800
+ .replace(/[^a-zA-Z0-9-]+/g, '-')
801
+ .replace(/-+/g, '-')
802
+ .replace(/^-+|-+$/g, '')
803
+ .slice(0, 128);
804
+ return sanitized.length > 0 ? sanitized : 'tool';
805
+ }
806
+ buildToolNameResolutionMap(allTools) {
807
+ const resolutionMap = new Map();
808
+ const register = (candidateName, resolvedName) => {
809
+ const existing = resolutionMap.get(candidateName);
810
+ if (existing && existing !== resolvedName) {
811
+ throw new Error(`Sanitized tool name collision: "${candidateName}" maps to both "${existing}" and "${resolvedName}"`);
812
+ }
813
+ resolutionMap.set(candidateName, resolvedName);
814
+ };
815
+ for (const tool of allTools) {
816
+ register(tool.name, tool.name);
817
+ register(this.sanitizeExportedToolName(tool.name), tool.name);
818
+ }
819
+ for (const [compactName, legacyName] of Object.entries(this.compactAliasToLegacy)) {
820
+ register(compactName, legacyName);
821
+ register(this.sanitizeExportedToolName(compactName), legacyName);
822
+ }
823
+ return resolutionMap;
824
+ }
796
825
  resolveToolAlias(requestedToolName) {
797
- return this.compactAliasToLegacy[requestedToolName] || requestedToolName;
826
+ const allTools = this.getAllToolDefinitions();
827
+ const resolutionMap = this.buildToolNameResolutionMap(allTools);
828
+ return resolutionMap.get(requestedToolName)
829
+ || resolutionMap.get(this.sanitizeExportedToolName(requestedToolName))
830
+ || requestedToolName;
798
831
  }
799
832
  buildCompactTools(allTools) {
800
833
  const compactTools = [];
@@ -811,6 +844,26 @@ class GodotServer {
811
844
  }
812
845
  return compactTools;
813
846
  }
847
+ sanitizeToolsForList(tools) {
848
+ const seenNames = new Map();
849
+ return tools.map((tool) => {
850
+ const sanitizedName = this.sanitizeExportedToolName(tool.name);
851
+ const existing = seenNames.get(sanitizedName);
852
+ if (existing && existing !== tool.name) {
853
+ throw new Error(`Sanitized tool name collision in tools/list: "${sanitizedName}" from "${existing}" and "${tool.name}"`);
854
+ }
855
+ seenNames.set(sanitizedName, tool.name);
856
+ if (sanitizedName !== tool.name) {
857
+ this.logDebug(`Exporting tool "${tool.name}" as "${sanitizedName}" for OpenAI-compatible clients`);
858
+ }
859
+ return sanitizedName === tool.name
860
+ ? tool
861
+ : {
862
+ ...tool,
863
+ name: sanitizedName,
864
+ };
865
+ });
866
+ }
814
867
  getExposedTools(allTools) {
815
868
  if (this.toolExposureProfile === 'full' || this.toolExposureProfile === 'legacy') {
816
869
  return allTools;
@@ -3626,7 +3679,7 @@ class GodotServer {
3626
3679
  this.server.setRequestHandler(ListToolsRequestSchema, async (request) => {
3627
3680
  const allTools = buildToolDefinitions();
3628
3681
  this.cachedToolDefinitions = allTools;
3629
- const exposedTools = this.getExposedTools(allTools);
3682
+ const exposedTools = this.sanitizeToolsForList(this.getExposedTools(allTools));
3630
3683
  return this.paginateToolsForList(exposedTools, request.params?.cursor);
3631
3684
  });
3632
3685
  // Handle tool calls
@@ -6433,27 +6486,14 @@ class GodotServer {
6433
6486
  return this.createErrorResponse('Project path is required', ['Provide a valid path to a Godot project directory']);
6434
6487
  }
6435
6488
  try {
6436
- // Runtime inspection requires a running Godot instance
6437
- if (!this.activeProcess) {
6438
- return this.createErrorResponse('No active Godot process', ['Use run_project to start a Godot instance first']);
6439
- }
6440
- // Return information about the running process
6441
- return {
6442
- content: [{
6443
- type: 'text',
6444
- text: JSON.stringify({
6445
- status: 'running',
6446
- nodePath: args.nodePath || '/',
6447
- depth: args.depth || 3,
6448
- note: 'Runtime tree inspection requires the godot_mcp_runtime addon. Current output shows debug logs.',
6449
- recentOutput: this.activeProcess.output.slice(-20),
6450
- recentErrors: this.activeProcess.errors.slice(-10),
6451
- }, null, 2),
6452
- }],
6453
- };
6489
+ return await this.handleRuntimeCommand('get_tree', {
6490
+ root: args.nodePath || '/root',
6491
+ depth: args.depth || 3,
6492
+ include_properties: Boolean(args.includeProperties),
6493
+ });
6454
6494
  }
6455
6495
  catch (error) {
6456
- return this.createErrorResponse(`Failed to inspect runtime tree: ${error?.message || 'Unknown error'}`, ['Ensure a Godot process is running']);
6496
+ return this.createErrorResponse(`Failed to inspect runtime tree: ${error?.message || 'Unknown error'}`, ['Ensure a Godot process is running with the runtime addon enabled']);
6457
6497
  }
6458
6498
  }
6459
6499
  /**
@@ -6465,24 +6505,11 @@ class GodotServer {
6465
6505
  return this.createErrorResponse('Missing required parameters', ['Provide projectPath, nodePath, property, and value']);
6466
6506
  }
6467
6507
  try {
6468
- if (!this.activeProcess) {
6469
- return this.createErrorResponse('No active Godot process', ['Use run_project to start a Godot instance first']);
6470
- }
6471
- // Runtime property modification requires the addon
6472
- return {
6473
- content: [{
6474
- type: 'text',
6475
- text: JSON.stringify({
6476
- status: 'not_implemented',
6477
- note: 'Runtime property modification requires the godot_mcp_runtime addon installed in the running project.',
6478
- requested: {
6479
- nodePath: args.nodePath,
6480
- property: args.property,
6481
- value: args.value,
6482
- },
6483
- }, null, 2),
6484
- }],
6485
- };
6508
+ return await this.handleRuntimeCommand('set_property', {
6509
+ path: args.nodePath,
6510
+ property: args.property,
6511
+ value: args.value,
6512
+ });
6486
6513
  }
6487
6514
  catch (error) {
6488
6515
  return this.createErrorResponse(`Failed to set runtime property: ${error?.message || 'Unknown error'}`, ['Ensure a Godot process is running with the runtime addon']);
@@ -6497,24 +6524,11 @@ class GodotServer {
6497
6524
  return this.createErrorResponse('Missing required parameters', ['Provide projectPath, nodePath, and method']);
6498
6525
  }
6499
6526
  try {
6500
- if (!this.activeProcess) {
6501
- return this.createErrorResponse('No active Godot process', ['Use run_project to start a Godot instance first']);
6502
- }
6503
- // Runtime method calling requires the addon
6504
- return {
6505
- content: [{
6506
- type: 'text',
6507
- text: JSON.stringify({
6508
- status: 'not_implemented',
6509
- note: 'Runtime method calling requires the godot_mcp_runtime addon installed in the running project.',
6510
- requested: {
6511
- nodePath: args.nodePath,
6512
- method: args.method,
6513
- args: args.args || [],
6514
- },
6515
- }, null, 2),
6516
- }],
6517
- };
6527
+ return await this.handleRuntimeCommand('call_method', {
6528
+ path: args.nodePath,
6529
+ method: args.method,
6530
+ args: Array.isArray(args.args) ? args.args : [],
6531
+ });
6518
6532
  }
6519
6533
  catch (error) {
6520
6534
  return this.createErrorResponse(`Failed to call runtime method: ${error?.message || 'Unknown error'}`, ['Ensure a Godot process is running with the runtime addon']);
@@ -6529,24 +6543,9 @@ class GodotServer {
6529
6543
  return this.createErrorResponse('Project path is required', ['Provide a valid path to a Godot project directory']);
6530
6544
  }
6531
6545
  try {
6532
- if (!this.activeProcess) {
6533
- return this.createErrorResponse('No active Godot process', ['Use run_project to start a Godot instance first']);
6534
- }
6535
- // Basic metrics from process output
6536
- return {
6537
- content: [{
6538
- type: 'text',
6539
- text: JSON.stringify({
6540
- status: 'running',
6541
- metrics: {
6542
- outputLines: this.activeProcess.output.length,
6543
- errorLines: this.activeProcess.errors.length,
6544
- note: 'Detailed metrics require the godot_mcp_runtime addon.',
6545
- },
6546
- requestedMetrics: args.metrics || 'all',
6547
- }, null, 2),
6548
- }],
6549
- };
6546
+ return await this.handleRuntimeCommand('get_metrics', {
6547
+ metrics: Array.isArray(args.metrics) ? args.metrics : [],
6548
+ });
6550
6549
  }
6551
6550
  catch (error) {
6552
6551
  return this.createErrorResponse(`Failed to get runtime metrics: ${error?.message || 'Unknown error'}`, ['Ensure a Godot process is running']);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gopeak",
3
- "version": "2.3.3",
3
+ "version": "2.3.4",
4
4
  "mcpName": "io.github.HaD0Yun/gopeak",
5
5
  "description": "GoPeak — The most comprehensive MCP server for Godot Engine. 95+ tools: scene management, GDScript LSP diagnostics, DAP debugger, screenshot capture, input injection, ClassDB introspection, CC0 asset library. AI-assisted game development with Claude, Cursor, Cline, OpenCode.",
6
6
  "type": "module",
@@ -29,7 +29,8 @@
29
29
  "watch": "tsc --watch",
30
30
  "inspector": "npx @modelcontextprotocol/inspector build/index.js",
31
31
  "pack": "npm pack --dry-run",
32
- "version:bump": "node scripts/bump-version.mjs"
32
+ "version:bump": "node scripts/bump-version.mjs",
33
+ "test:setup": "npm run build && node test-setup-hooks.mjs"
33
34
  },
34
35
  "engines": {
35
36
  "node": ">=18"
@@ -48,7 +49,7 @@
48
49
  },
49
50
  "overrides": {
50
51
  "@hono/node-server": "^1.19.11",
51
- "hono": "^4.12.5"
52
+ "hono": "4.12.7"
52
53
  },
53
54
  "license": "MIT",
54
55
  "repository": {