gopeak 2.3.2 → 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
@@ -5,15 +5,17 @@
5
5
  [![](https://img.shields.io/badge/Node.js-339933?style=flat&logo=nodedotjs&logoColor=white 'Node.js')](https://nodejs.org/en/download/)
6
6
  [![](https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=white 'TypeScript')](https://www.typescriptlang.org/)
7
7
  [![npm](https://img.shields.io/npm/v/gopeak?style=flat&logo=npm&logoColor=white 'npm')](https://www.npmjs.com/package/gopeak)
8
- [![](https://img.shields.io/github/last-commit/HaD0Yun/godot-mcp 'Last Commit')](https://github.com/HaD0Yun/godot-mcp/commits/main)
9
- [![](https://img.shields.io/github/stars/HaD0Yun/godot-mcp 'Stars')](https://github.com/HaD0Yun/godot-mcp/stargazers)
10
- [![](https://img.shields.io/github/forks/HaD0Yun/godot-mcp 'Forks')](https://github.com/HaD0Yun/godot-mcp/network/members)
8
+ [![](https://img.shields.io/github/last-commit/HaD0Yun/Gopeak-godot-mcp 'Last Commit')](https://github.com/HaD0Yun/Gopeak-godot-mcp/commits/main)
9
+ [![](https://img.shields.io/github/stars/HaD0Yun/Gopeak-godot-mcp 'Stars')](https://github.com/HaD0Yun/Gopeak-godot-mcp/stargazers)
10
+ [![](https://img.shields.io/github/forks/HaD0Yun/Gopeak-godot-mcp 'Forks')](https://github.com/HaD0Yun/Gopeak-godot-mcp/network/members)
11
11
  [![](https://img.shields.io/badge/License-MIT-red.svg 'MIT License')](https://opensource.org/licenses/MIT)
12
12
 
13
13
  ![GoPeak Hero](assets/gopeak-hero-v2.png)
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)
@@ -169,7 +171,7 @@ gopeak
169
171
  ### C) From source
170
172
 
171
173
  ```bash
172
- git clone https://github.com/HaD0Yun/godot-mcp.git
174
+ git clone https://github.com/HaD0Yun/Gopeak-godot-mcp.git
173
175
  cd godot-mcp
174
176
  npm install
175
177
  npm run build
@@ -183,13 +185,21 @@ GoPeak also exposes two CLI bin names:
183
185
 
184
186
  ---
185
187
 
188
+ ## Documentation
189
+
190
+ - [Documentation Map](docs/README.md)
191
+ - [Architecture](docs/architecture.md)
192
+ - [Platform Roadmap](docs/platform-roadmap.md)
193
+ - [Unity-MCP Benchmark Plan](docs/unity-mcp-benchmark-plan.md)
194
+ - [Release Process](docs/release-process.md)
195
+
186
196
  ## CI
187
197
 
188
198
  GitHub Actions runs on push/PR and executes:
189
199
 
190
200
  1. `npm run build`
191
201
  2. `npx tsc --noEmit`
192
- 3. `npm run test:ci`
202
+ 3. `npm run smoke`
193
203
 
194
204
  Run the same checks locally:
195
205
 
@@ -219,13 +229,13 @@ Full release checklist: [`docs/release-process.md`](docs/release-process.md).
219
229
  Install in your Godot project folder:
220
230
 
221
231
  ```bash
222
- curl -sL https://raw.githubusercontent.com/HaD0Yun/godot-mcp/main/install-addon.sh | bash
232
+ curl -sL https://raw.githubusercontent.com/HaD0Yun/Gopeak-godot-mcp/main/install-addon.sh | bash
223
233
  ```
224
234
 
225
235
  PowerShell:
226
236
 
227
237
  ```powershell
228
- iwr https://raw.githubusercontent.com/HaD0Yun/godot-mcp/main/install-addon.ps1 -UseBasicParsing | iex
238
+ iwr https://raw.githubusercontent.com/HaD0Yun/Gopeak-godot-mcp/main/install-addon.ps1 -UseBasicParsing | iex
229
239
  ```
230
240
 
231
241
  Then enable plugins in **Project Settings → Plugins** (especially `godot_mcp_editor` for bridge-backed scene/resource tools).
@@ -63,7 +63,7 @@ async function backgroundCheck() {
63
63
  function printUpdateBox(current, latest) {
64
64
  const line1 = ` 🚀 GoPeak v${latest} available! (current: v${current})`;
65
65
  const line2 = ` npm update -g gopeak`;
66
- const line3 = ` https://github.com/HaD0Yun/godot-mcp/releases`;
66
+ const line3 = ` https://github.com/HaD0Yun/Gopeak-godot-mcp/releases`;
67
67
  const maxLen = Math.max(line1.length, line2.length, line3.length) + 2;
68
68
  const pad = (s) => s + ' '.repeat(Math.max(0, maxLen - s.length));
69
69
  console.log('');
@@ -7,7 +7,7 @@
7
7
  import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
8
8
  import { createInterface } from 'readline';
9
9
  import { NOTIFY_FILE, STAR_PROMPTED_FILE, ensureGopeakDir, commandExists, runCommand, } from './utils.js';
10
- const REPO_URL = 'https://github.com/HaD0Yun/godot-mcp';
10
+ const REPO_URL = 'https://github.com/HaD0Yun/Gopeak-godot-mcp';
11
11
  export async function showNotification() {
12
12
  ensureGopeakDir();
13
13
  const hasUpdate = existsSync(NOTIFY_FILE);
@@ -61,12 +61,12 @@ async function handleStar() {
61
61
  return;
62
62
  }
63
63
  // Check if already starred
64
- const checkResult = await runCommand('gh api user/starred/HaD0Yun/godot-mcp');
64
+ const checkResult = await runCommand('gh api user/starred/HaD0Yun/Gopeak-godot-mcp');
65
65
  if (checkResult.code === 0) {
66
66
  console.log(' ⭐ Already starred! Thank you!');
67
67
  return;
68
68
  }
69
- const starResult = await runCommand('gh api -X PUT user/starred/HaD0Yun/godot-mcp');
69
+ const starResult = await runCommand('gh api -X PUT user/starred/HaD0Yun/Gopeak-godot-mcp');
70
70
  if (starResult.code === 0) {
71
71
  console.log(' ⭐ Starred! Thank you for your support!');
72
72
  }
@@ -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);
@@ -106,7 +113,7 @@ function printOnboarding(log = console.log) {
106
113
  log('║ ║');
107
114
  log('║ 110+ tools for Godot Engine via MCP ║');
108
115
  log('║ ║');
109
- log('║ 📖 Docs: https://github.com/HaD0Yun/godot-mcp ║');
116
+ log('║ 📖 Docs: https://github.com/HaD0Yun/Gopeak-godot-mcp ║');
110
117
  log('║ ⭐ Star: gopeak star ║');
111
118
  log('║ 🔄 Update: npm update -g gopeak ║');
112
119
  log('╚══════════════════════════════════════════════════════╝');
package/build/cli/star.js CHANGED
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { commandExists, runCommand, STAR_PROMPTED_FILE, ensureGopeakDir } from './utils.js';
9
9
  import { existsSync, writeFileSync } from 'fs';
10
- const REPO = 'HaD0Yun/godot-mcp';
10
+ const REPO = 'HaD0Yun/Gopeak-godot-mcp';
11
11
  const REPO_URL = `https://github.com/${REPO}`;
12
12
  export async function starGoPeak() {
13
13
  // 1. Check if gh CLI exists
@@ -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/cli.js CHANGED
@@ -82,7 +82,7 @@ Usage:
82
82
  Shell hooks wrap these commands with update notifications:
83
83
  claude, codex, gemini, opencode, omc, omx
84
84
 
85
- More info: https://github.com/HaD0Yun/godot-mcp
85
+ More info: https://github.com/HaD0Yun/Gopeak-godot-mcp
86
86
  `.trim());
87
87
  }
88
88
  main().catch((err) => {
package/build/index.js CHANGED
@@ -671,42 +671,109 @@ class GodotServer {
671
671
  const payload = JSON.stringify({ command, params, id: Date.now() });
672
672
  socket.write(payload + '\n');
673
673
  });
674
- let responseData = '';
674
+ let responseBuffer = Buffer.alloc(0);
675
+ let resolved = false;
675
676
  const timer = setTimeout(() => {
677
+ if (resolved) {
678
+ return;
679
+ }
680
+ resolved = true;
676
681
  socket.destroy();
677
682
  resolve({
678
683
  content: [{ type: 'text', text: `Runtime command '${command}' timed out after ${TIMEOUT_MS}ms. Ensure the Godot game is running with the MCP runtime addon enabled.` }],
679
684
  });
680
685
  }, TIMEOUT_MS);
681
- socket.setEncoding('utf8');
686
+ const resolveRuntimePayload = (parsed) => {
687
+ if (resolved) {
688
+ return;
689
+ }
690
+ resolved = true;
691
+ clearTimeout(timer);
692
+ socket.destroy();
693
+ if (parsed.type === 'screenshot' && parsed.data) {
694
+ resolve({
695
+ content: [
696
+ { type: 'text', text: `Screenshot captured: ${parsed.width}x${parsed.height} ${parsed.format}` },
697
+ { type: 'image', data: parsed.data, mimeType: 'image/png' },
698
+ ],
699
+ });
700
+ return;
701
+ }
702
+ resolve({
703
+ content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }],
704
+ });
705
+ };
682
706
  socket.on('data', (chunk) => {
683
- responseData += chunk;
684
- if (responseData.includes('\n') || responseData.includes('}')) {
685
- clearTimeout(timer);
686
- socket.destroy();
707
+ responseBuffer = Buffer.concat([responseBuffer, Buffer.from(chunk)]);
708
+ const parsedMessages = [];
709
+ const parseCandidate = (candidate) => {
710
+ const trimmed = candidate.trim();
711
+ if (!trimmed) {
712
+ return;
713
+ }
687
714
  try {
688
- const parsed = JSON.parse(responseData.trim());
689
- if (parsed.type === 'screenshot' && parsed.data) {
690
- resolve({
691
- content: [
692
- { type: 'text', text: `Screenshot captured: ${parsed.width}x${parsed.height} ${parsed.format}` },
693
- { type: 'image', text: parsed.data },
694
- ],
695
- });
696
- return;
697
- }
698
- resolve({
699
- content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }],
700
- });
715
+ parsedMessages.push(JSON.parse(trimmed));
701
716
  }
702
717
  catch {
703
- resolve({
704
- content: [{ type: 'text', text: responseData.trim() || 'Command sent successfully (no structured response).' }],
705
- });
718
+ // Ignore malformed frame/line and keep scanning.
719
+ }
720
+ };
721
+ // First, parse the framed payload format emitted by Godot's StreamPeerTCP.put_utf8_string().
722
+ let offset = 0;
723
+ while (offset + 4 <= responseBuffer.length) {
724
+ const frameLength = responseBuffer.readUInt32LE(offset);
725
+ if (frameLength <= 0 || offset + 4 + frameLength > responseBuffer.length) {
726
+ break;
706
727
  }
728
+ const frame = responseBuffer.subarray(offset + 4, offset + 4 + frameLength).toString('utf8');
729
+ parseCandidate(frame);
730
+ offset += 4 + frameLength;
731
+ }
732
+ if (offset > 0) {
733
+ responseBuffer = responseBuffer.subarray(offset);
734
+ }
735
+ // Fallback for plain newline-delimited JSON payloads.
736
+ let newlineIndex = responseBuffer.indexOf(0x0a);
737
+ while (newlineIndex !== -1) {
738
+ const line = responseBuffer.subarray(0, newlineIndex).toString('utf8');
739
+ responseBuffer = responseBuffer.subarray(newlineIndex + 1);
740
+ parseCandidate(line);
741
+ newlineIndex = responseBuffer.indexOf(0x0a);
742
+ }
743
+ if (parsedMessages.length > 0) {
744
+ const candidate = parsedMessages.find((message) => message?.type === 'screenshot' && message?.data)
745
+ ?? parsedMessages.find((message) => message?.type === 'pong')
746
+ ?? parsedMessages.find((message) => message?.type && message.type !== 'welcome')
747
+ ?? null;
748
+ if (candidate) {
749
+ resolveRuntimePayload(candidate);
750
+ }
751
+ }
752
+ });
753
+ socket.on('end', () => {
754
+ if (resolved) {
755
+ return;
756
+ }
757
+ clearTimeout(timer);
758
+ const responseData = responseBuffer.toString('utf8').trim();
759
+ resolved = true;
760
+ try {
761
+ const parsed = JSON.parse(responseData);
762
+ resolve({
763
+ content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }],
764
+ });
765
+ }
766
+ catch {
767
+ resolve({
768
+ content: [{ type: 'text', text: responseData || 'Command sent successfully (no structured response).' }],
769
+ });
707
770
  }
708
771
  });
709
772
  socket.on('error', (error) => {
773
+ if (resolved) {
774
+ return;
775
+ }
776
+ resolved = true;
710
777
  clearTimeout(timer);
711
778
  resolve({
712
779
  content: [{ type: 'text', text: `Failed to connect to Godot runtime addon at ${RUNTIME_HOST}:${RUNTIME_PORT}: ${error.message}. Ensure the game is running with the MCP runtime autoload enabled.` }],
@@ -726,8 +793,41 @@ class GodotServer {
726
793
  }
727
794
  return handleDAPTool(this.dapClient, toolName, args);
728
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
+ }
729
825
  resolveToolAlias(requestedToolName) {
730
- 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;
731
831
  }
732
832
  buildCompactTools(allTools) {
733
833
  const compactTools = [];
@@ -744,6 +844,26 @@ class GodotServer {
744
844
  }
745
845
  return compactTools;
746
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
+ }
747
867
  getExposedTools(allTools) {
748
868
  if (this.toolExposureProfile === 'full' || this.toolExposureProfile === 'legacy') {
749
869
  return allTools;
@@ -3559,7 +3679,7 @@ class GodotServer {
3559
3679
  this.server.setRequestHandler(ListToolsRequestSchema, async (request) => {
3560
3680
  const allTools = buildToolDefinitions();
3561
3681
  this.cachedToolDefinitions = allTools;
3562
- const exposedTools = this.getExposedTools(allTools);
3682
+ const exposedTools = this.sanitizeToolsForList(this.getExposedTools(allTools));
3563
3683
  return this.paginateToolsForList(exposedTools, request.params?.cursor);
3564
3684
  });
3565
3685
  // Handle tool calls
@@ -4586,6 +4706,26 @@ class GodotServer {
4586
4706
  })
4587
4707
  .filter((v) => v !== null);
4588
4708
  }
4709
+ extractLastJsonLine(stdout) {
4710
+ const lines = stdout
4711
+ .split(/\r?\n/)
4712
+ .map((line) => line.trim())
4713
+ .filter((line) => line.length > 0);
4714
+ for (let i = lines.length - 1; i >= 0; i -= 1) {
4715
+ const line = lines[i];
4716
+ if (!(line.startsWith('{') || line.startsWith('['))) {
4717
+ continue;
4718
+ }
4719
+ try {
4720
+ JSON.parse(line);
4721
+ return line;
4722
+ }
4723
+ catch {
4724
+ continue;
4725
+ }
4726
+ }
4727
+ return null;
4728
+ }
4589
4729
  /**
4590
4730
  * Capture/update current intent snapshot
4591
4731
  */
@@ -5377,7 +5517,7 @@ class GodotServer {
5377
5517
  return this.createErrorResponse(`Failed to list scene nodes: ${stderr}`, ['Verify the scene file is valid']);
5378
5518
  }
5379
5519
  return {
5380
- content: [{ type: 'text', text: stdout.trim() }],
5520
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
5381
5521
  };
5382
5522
  }
5383
5523
  catch (error) {
@@ -5414,7 +5554,7 @@ class GodotServer {
5414
5554
  return this.createErrorResponse(`Failed to get node properties: ${stderr}`, ['Verify the node path is correct', 'Check if the node exists in the scene']);
5415
5555
  }
5416
5556
  return {
5417
- content: [{ type: 'text', text: stdout.trim() }],
5557
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
5418
5558
  };
5419
5559
  }
5420
5560
  catch (error) {
@@ -5603,7 +5743,7 @@ class GodotServer {
5603
5743
  return this.createErrorResponse(`Failed to get import status: ${stderr}`, ['Verify the resource path if specified']);
5604
5744
  }
5605
5745
  return {
5606
- content: [{ type: 'text', text: stdout.trim() }],
5746
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
5607
5747
  };
5608
5748
  }
5609
5749
  catch (error) {
@@ -5638,7 +5778,7 @@ class GodotServer {
5638
5778
  return this.createErrorResponse(`Failed to get import options: ${stderr}`, ['Verify the resource is an importable file type']);
5639
5779
  }
5640
5780
  return {
5641
- content: [{ type: 'text', text: stdout.trim() }],
5781
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
5642
5782
  };
5643
5783
  }
5644
5784
  catch (error) {
@@ -5744,7 +5884,7 @@ class GodotServer {
5744
5884
  return this.createErrorResponse(`Failed to list export presets: ${stderr}`, ['Check if export_presets.cfg exists in the project']);
5745
5885
  }
5746
5886
  return {
5747
- content: [{ type: 'text', text: stdout.trim() }],
5887
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
5748
5888
  };
5749
5889
  }
5750
5890
  catch (error) {
@@ -5814,7 +5954,7 @@ class GodotServer {
5814
5954
  return this.createErrorResponse(`Failed to validate project: ${stderr}`, ['Verify the project structure is valid']);
5815
5955
  }
5816
5956
  return {
5817
- content: [{ type: 'text', text: stdout.trim() }],
5957
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
5818
5958
  };
5819
5959
  }
5820
5960
  catch (error) {
@@ -5850,7 +5990,7 @@ class GodotServer {
5850
5990
  return this.createErrorResponse(`Failed to get dependencies: ${stderr}`, ['Verify the resource path is correct']);
5851
5991
  }
5852
5992
  return {
5853
- content: [{ type: 'text', text: stdout.trim() }],
5993
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
5854
5994
  };
5855
5995
  }
5856
5996
  catch (error) {
@@ -5882,7 +6022,7 @@ class GodotServer {
5882
6022
  return this.createErrorResponse(`Failed to find resource usages: ${stderr}`, ['Verify the resource path is correct']);
5883
6023
  }
5884
6024
  return {
5885
- content: [{ type: 'text', text: stdout.trim() }],
6025
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
5886
6026
  };
5887
6027
  }
5888
6028
  catch (error) {
@@ -5914,7 +6054,7 @@ class GodotServer {
5914
6054
  return this.createErrorResponse(`Failed to parse error log: ${stderr}`, ['Verify the log content or ensure godot.log exists']);
5915
6055
  }
5916
6056
  return {
5917
- content: [{ type: 'text', text: stdout.trim() }],
6057
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
5918
6058
  };
5919
6059
  }
5920
6060
  catch (error) {
@@ -5945,7 +6085,7 @@ class GodotServer {
5945
6085
  return this.createErrorResponse(`Failed to get project health: ${stderr}`, ['Verify the project structure']);
5946
6086
  }
5947
6087
  return {
5948
- content: [{ type: 'text', text: stdout.trim() }],
6088
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
5949
6089
  };
5950
6090
  }
5951
6091
  catch (error) {
@@ -5979,7 +6119,7 @@ class GodotServer {
5979
6119
  return this.createErrorResponse(`Failed to get project setting: ${stderr}`, ['Verify the setting path is correct']);
5980
6120
  }
5981
6121
  return {
5982
- content: [{ type: 'text', text: stdout.trim() }],
6122
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
5983
6123
  };
5984
6124
  }
5985
6125
  catch (error) {
@@ -6103,7 +6243,7 @@ class GodotServer {
6103
6243
  return this.createErrorResponse(`Failed to list autoloads: ${stderr}`, ['Verify the project structure']);
6104
6244
  }
6105
6245
  return {
6106
- content: [{ type: 'text', text: stdout.trim() }],
6246
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
6107
6247
  };
6108
6248
  }
6109
6249
  catch (error) {
@@ -6260,7 +6400,7 @@ class GodotServer {
6260
6400
  return this.createErrorResponse(`Failed to list connections: ${stderr}`, ['Verify the scene path is correct']);
6261
6401
  }
6262
6402
  return {
6263
- content: [{ type: 'text', text: stdout.trim() }],
6403
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
6264
6404
  };
6265
6405
  }
6266
6406
  catch (error) {
@@ -6279,32 +6419,59 @@ class GodotServer {
6279
6419
  return this.createErrorResponse('Project path is required', ['Provide a valid path to a Godot project directory']);
6280
6420
  }
6281
6421
  try {
6282
- // Runtime connection requires a running Godot instance with the addon
6283
- // For now, return status based on active process
6284
- if (this.activeProcess) {
6422
+ const runtime = await this.handleRuntimeCommand('ping', {});
6423
+ const runtimeText = runtime?.content?.[0]?.text || '';
6424
+ let runtimePayload = null;
6425
+ try {
6426
+ runtimePayload = JSON.parse(runtimeText);
6427
+ }
6428
+ catch {
6429
+ runtimePayload = null;
6430
+ }
6431
+ const runtimeConnected = runtimePayload?.type === 'pong';
6432
+ if (runtimeConnected) {
6285
6433
  return {
6286
6434
  content: [{
6287
6435
  type: 'text',
6288
6436
  text: JSON.stringify({
6289
6437
  connected: true,
6290
6438
  status: 'running',
6291
- note: 'A Godot process is active. Use inspect_runtime_tree to explore.',
6439
+ processActive: Boolean(this.activeProcess),
6440
+ runtimeAddon: 'connected',
6441
+ note: 'Godot runtime addon responded to ping. Use inspect_runtime_tree to explore.',
6442
+ runtimeResponse: runtimePayload,
6292
6443
  }, null, 2),
6293
6444
  }],
6294
6445
  };
6295
6446
  }
6296
- else {
6447
+ if (this.activeProcess) {
6297
6448
  return {
6298
6449
  content: [{
6299
6450
  type: 'text',
6300
6451
  text: JSON.stringify({
6301
6452
  connected: false,
6302
- status: 'not_running',
6303
- note: 'No active Godot process. Use run_project to start one.',
6453
+ status: 'process_running_runtime_disconnected',
6454
+ processActive: true,
6455
+ runtimeAddon: 'unreachable',
6456
+ note: 'A Godot process is active, but the runtime addon did not respond on port 7777.',
6457
+ runtimeResponse: runtimeText,
6304
6458
  }, null, 2),
6305
6459
  }],
6306
6460
  };
6307
6461
  }
6462
+ return {
6463
+ content: [{
6464
+ type: 'text',
6465
+ text: JSON.stringify({
6466
+ connected: false,
6467
+ status: 'not_running',
6468
+ processActive: false,
6469
+ runtimeAddon: 'unreachable',
6470
+ note: 'No active Godot process or runtime addon detected. Use run_project to start one.',
6471
+ runtimeResponse: runtimeText,
6472
+ }, null, 2),
6473
+ }],
6474
+ };
6308
6475
  }
6309
6476
  catch (error) {
6310
6477
  return this.createErrorResponse(`Failed to get runtime status: ${error?.message || 'Unknown error'}`, ['Ensure Godot is installed correctly']);
@@ -6319,27 +6486,14 @@ class GodotServer {
6319
6486
  return this.createErrorResponse('Project path is required', ['Provide a valid path to a Godot project directory']);
6320
6487
  }
6321
6488
  try {
6322
- // Runtime inspection requires a running Godot instance
6323
- if (!this.activeProcess) {
6324
- return this.createErrorResponse('No active Godot process', ['Use run_project to start a Godot instance first']);
6325
- }
6326
- // Return information about the running process
6327
- return {
6328
- content: [{
6329
- type: 'text',
6330
- text: JSON.stringify({
6331
- status: 'running',
6332
- nodePath: args.nodePath || '/',
6333
- depth: args.depth || 3,
6334
- note: 'Runtime tree inspection requires the godot_mcp_runtime addon. Current output shows debug logs.',
6335
- recentOutput: this.activeProcess.output.slice(-20),
6336
- recentErrors: this.activeProcess.errors.slice(-10),
6337
- }, null, 2),
6338
- }],
6339
- };
6489
+ return await this.handleRuntimeCommand('get_tree', {
6490
+ root: args.nodePath || '/root',
6491
+ depth: args.depth || 3,
6492
+ include_properties: Boolean(args.includeProperties),
6493
+ });
6340
6494
  }
6341
6495
  catch (error) {
6342
- 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']);
6343
6497
  }
6344
6498
  }
6345
6499
  /**
@@ -6351,24 +6505,11 @@ class GodotServer {
6351
6505
  return this.createErrorResponse('Missing required parameters', ['Provide projectPath, nodePath, property, and value']);
6352
6506
  }
6353
6507
  try {
6354
- if (!this.activeProcess) {
6355
- return this.createErrorResponse('No active Godot process', ['Use run_project to start a Godot instance first']);
6356
- }
6357
- // Runtime property modification requires the addon
6358
- return {
6359
- content: [{
6360
- type: 'text',
6361
- text: JSON.stringify({
6362
- status: 'not_implemented',
6363
- note: 'Runtime property modification requires the godot_mcp_runtime addon installed in the running project.',
6364
- requested: {
6365
- nodePath: args.nodePath,
6366
- property: args.property,
6367
- value: args.value,
6368
- },
6369
- }, null, 2),
6370
- }],
6371
- };
6508
+ return await this.handleRuntimeCommand('set_property', {
6509
+ path: args.nodePath,
6510
+ property: args.property,
6511
+ value: args.value,
6512
+ });
6372
6513
  }
6373
6514
  catch (error) {
6374
6515
  return this.createErrorResponse(`Failed to set runtime property: ${error?.message || 'Unknown error'}`, ['Ensure a Godot process is running with the runtime addon']);
@@ -6383,24 +6524,11 @@ class GodotServer {
6383
6524
  return this.createErrorResponse('Missing required parameters', ['Provide projectPath, nodePath, and method']);
6384
6525
  }
6385
6526
  try {
6386
- if (!this.activeProcess) {
6387
- return this.createErrorResponse('No active Godot process', ['Use run_project to start a Godot instance first']);
6388
- }
6389
- // Runtime method calling requires the addon
6390
- return {
6391
- content: [{
6392
- type: 'text',
6393
- text: JSON.stringify({
6394
- status: 'not_implemented',
6395
- note: 'Runtime method calling requires the godot_mcp_runtime addon installed in the running project.',
6396
- requested: {
6397
- nodePath: args.nodePath,
6398
- method: args.method,
6399
- args: args.args || [],
6400
- },
6401
- }, null, 2),
6402
- }],
6403
- };
6527
+ return await this.handleRuntimeCommand('call_method', {
6528
+ path: args.nodePath,
6529
+ method: args.method,
6530
+ args: Array.isArray(args.args) ? args.args : [],
6531
+ });
6404
6532
  }
6405
6533
  catch (error) {
6406
6534
  return this.createErrorResponse(`Failed to call runtime method: ${error?.message || 'Unknown error'}`, ['Ensure a Godot process is running with the runtime addon']);
@@ -6415,24 +6543,9 @@ class GodotServer {
6415
6543
  return this.createErrorResponse('Project path is required', ['Provide a valid path to a Godot project directory']);
6416
6544
  }
6417
6545
  try {
6418
- if (!this.activeProcess) {
6419
- return this.createErrorResponse('No active Godot process', ['Use run_project to start a Godot instance first']);
6420
- }
6421
- // Basic metrics from process output
6422
- return {
6423
- content: [{
6424
- type: 'text',
6425
- text: JSON.stringify({
6426
- status: 'running',
6427
- metrics: {
6428
- outputLines: this.activeProcess.output.length,
6429
- errorLines: this.activeProcess.errors.length,
6430
- note: 'Detailed metrics require the godot_mcp_runtime addon.',
6431
- },
6432
- requestedMetrics: args.metrics || 'all',
6433
- }, null, 2),
6434
- }],
6435
- };
6546
+ return await this.handleRuntimeCommand('get_metrics', {
6547
+ metrics: Array.isArray(args.metrics) ? args.metrics : [],
6548
+ });
6436
6549
  }
6437
6550
  catch (error) {
6438
6551
  return this.createErrorResponse(`Failed to get runtime metrics: ${error?.message || 'Unknown error'}`, ['Ensure a Godot process is running']);
@@ -6861,7 +6974,7 @@ class GodotServer {
6861
6974
  return this.createErrorResponse(`Failed to list plugins: ${stderr}`, ['Verify the project structure']);
6862
6975
  }
6863
6976
  return {
6864
- content: [{ type: 'text', text: stdout.trim() }],
6977
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
6865
6978
  };
6866
6979
  }
6867
6980
  catch (error) {
@@ -7000,7 +7113,7 @@ class GodotServer {
7000
7113
  return this.createErrorResponse(`Failed to search project: ${stderr}`, ['Check if the query/regex pattern is valid']);
7001
7114
  }
7002
7115
  return {
7003
- content: [{ type: 'text', text: stdout.trim() }],
7116
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
7004
7117
  };
7005
7118
  }
7006
7119
  catch (error) {
@@ -7787,7 +7900,16 @@ uniform float dissolve_amount : hint_range(0.0, 1.0) = 0.0;
7787
7900
  params.instantiable_only = args.instantiableOnly;
7788
7901
  if (args?.instantiable_only !== undefined)
7789
7902
  params.instantiable_only = args.instantiable_only;
7790
- return await this.executeOperation('query_classes', params, projectPath);
7903
+ const { stdout, stderr } = await this.executeOperation('query_classes', params, projectPath);
7904
+ if (stderr && stderr.trim()) {
7905
+ return this.createErrorResponse(`Failed to query classes: ${stderr.trim()}`, [
7906
+ 'Check the project path and ensure project.godot exists',
7907
+ 'Verify the category/filter arguments are valid',
7908
+ ]);
7909
+ }
7910
+ return {
7911
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
7912
+ };
7791
7913
  }
7792
7914
  /**
7793
7915
  * Handle the query_class_info tool — ClassDB introspection
@@ -7808,7 +7930,16 @@ uniform float dissolve_amount : hint_range(0.0, 1.0) = 0.0;
7808
7930
  params.include_inherited = args.includeInherited;
7809
7931
  if (args?.include_inherited !== undefined)
7810
7932
  params.include_inherited = args.include_inherited;
7811
- return await this.executeOperation('query_class_info', params, projectPath);
7933
+ const { stdout, stderr } = await this.executeOperation('query_class_info', params, projectPath);
7934
+ if (stderr && stderr.trim()) {
7935
+ return this.createErrorResponse(`Failed to query class info: ${stderr.trim()}`, [
7936
+ 'Check that the class name exists in the current Godot version',
7937
+ 'Verify the project path and ClassDB availability',
7938
+ ]);
7939
+ }
7940
+ return {
7941
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
7942
+ };
7812
7943
  }
7813
7944
  /**
7814
7945
  * Handle the inspect_inheritance tool — ClassDB introspection
@@ -7822,9 +7953,17 @@ uniform float dissolve_amount : hint_range(0.0, 1.0) = 0.0;
7822
7953
  if (!className) {
7823
7954
  throw new McpError(ErrorCode.InvalidParams, 'className is required');
7824
7955
  }
7825
- return await this.executeOperation('inspect_inheritance', {
7956
+ const { stdout, stderr } = await this.executeOperation('inspect_inheritance', {
7826
7957
  class_name: className,
7827
7958
  }, projectPath);
7959
+ if (stderr && stderr.trim()) {
7960
+ return this.createErrorResponse(`Failed to inspect inheritance: ${stderr.trim()}`, [
7961
+ 'Check that the class name exists in the current Godot version',
7962
+ ]);
7963
+ }
7964
+ return {
7965
+ content: [{ type: 'text', text: this.extractLastJsonLine(stdout) || stdout.trim() }],
7966
+ };
7828
7967
  }
7829
7968
  /**
7830
7969
  * Handle the modify_resource tool
@@ -1,6 +1,6 @@
1
- import { readFile } from 'node:fs/promises';
1
+ import { readFile, realpath } from 'node:fs/promises';
2
2
  import { createConnection } from 'node:net';
3
- import { dirname, resolve } from 'node:path';
3
+ import { dirname, resolve, sep } from 'node:path';
4
4
  import { pathToFileURL } from 'node:url';
5
5
  export class GodotLSPClient {
6
6
  socket = null;
@@ -464,6 +464,38 @@ function normalizeLSPError(error) {
464
464
  }
465
465
  return String(error);
466
466
  }
467
+ function normalizePathForComparison(pathValue) {
468
+ const resolved = resolve(pathValue);
469
+ return process.platform === 'win32' ? resolved.toLowerCase() : resolved;
470
+ }
471
+ function isPathWithinRoot(rootPath, candidatePath) {
472
+ const normalizedRoot = normalizePathForComparison(rootPath);
473
+ const normalizedCandidate = normalizePathForComparison(candidatePath);
474
+ const rootPrefix = normalizedRoot.endsWith(sep) ? normalizedRoot : `${normalizedRoot}${sep}`;
475
+ return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(rootPrefix);
476
+ }
477
+ async function resolveLSPPaths(projectPathValue, scriptPathValue) {
478
+ const requestedProjectPath = resolve(projectPathValue);
479
+ let projectPath;
480
+ try {
481
+ projectPath = await realpath(requestedProjectPath);
482
+ }
483
+ catch {
484
+ throw new Error(`Project path does not exist: ${requestedProjectPath}`);
485
+ }
486
+ const requestedScriptPath = resolve(projectPath, scriptPathValue);
487
+ let scriptPath;
488
+ try {
489
+ scriptPath = await realpath(requestedScriptPath);
490
+ }
491
+ catch {
492
+ throw new Error(`Script file does not exist: ${requestedScriptPath}`);
493
+ }
494
+ if (!isPathWithinRoot(projectPath, scriptPath)) {
495
+ throw new Error('scriptPath resolves outside the project root boundary.');
496
+ }
497
+ return { projectPath, scriptPath };
498
+ }
467
499
  export async function handleLSPTool(client, toolName, args) {
468
500
  try {
469
501
  if (!args || typeof args !== 'object') {
@@ -478,8 +510,7 @@ export async function handleLSPTool(client, toolName, args) {
478
510
  if (typeof scriptPathValue !== 'string' || scriptPathValue.length === 0) {
479
511
  throw new Error('Missing required argument: scriptPath');
480
512
  }
481
- const projectPath = resolve(projectPathValue);
482
- const scriptPath = resolve(projectPath, scriptPathValue);
513
+ const { projectPath, scriptPath } = await resolveLSPPaths(projectPathValue, scriptPathValue);
483
514
  const content = await readFile(scriptPath, 'utf8');
484
515
  await client.initialize(projectPath);
485
516
  switch (toolName) {
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync, createWriteStream, appendFileSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import * as https from 'https';
4
+ import { providerLog } from './logging.js';
4
5
  const AMBIENTCG_CONFIG = {
5
6
  name: 'ambientcg',
6
7
  displayName: 'AmbientCG',
@@ -111,7 +112,7 @@ export class AmbientCGProvider {
111
112
  });
112
113
  }
113
114
  catch (error) {
114
- console.error(`[AmbientCG] Search failed: ${error}`);
115
+ providerLog('error', this.config.name, 'Search failed', error);
115
116
  return [];
116
117
  }
117
118
  }
@@ -159,6 +160,7 @@ export class AmbientCGProvider {
159
160
  };
160
161
  }
161
162
  catch (error) {
163
+ providerLog('error', this.config.name, 'Download failed', error);
162
164
  return {
163
165
  success: false,
164
166
  assetId,
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync, createWriteStream, appendFileSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import * as https from 'https';
4
+ import { providerLog } from './logging.js';
4
5
  const KENNEY_CONFIG = {
5
6
  name: 'kenney',
6
7
  displayName: 'Kenney',
@@ -105,6 +106,7 @@ export class KenneyProvider {
105
106
  const { assetId, projectPath, targetFolder = 'downloaded_assets/kenney' } = options;
106
107
  const pack = KENNEY_ASSET_PACKS.find(p => p.id === assetId);
107
108
  if (!pack) {
109
+ providerLog('warn', this.config.name, `Asset pack not found: ${assetId}`);
108
110
  return {
109
111
  success: false,
110
112
  assetId,
@@ -144,6 +146,7 @@ export class KenneyProvider {
144
146
  };
145
147
  }
146
148
  catch (error) {
149
+ providerLog('error', this.config.name, 'Download failed', error);
147
150
  return {
148
151
  success: false,
149
152
  assetId,
@@ -0,0 +1,19 @@
1
+ export function formatProviderError(error) {
2
+ if (error instanceof Error) {
3
+ return error.stack ?? error.message;
4
+ }
5
+ if (typeof error === 'string') {
6
+ return error;
7
+ }
8
+ try {
9
+ return JSON.stringify(error);
10
+ }
11
+ catch {
12
+ return String(error);
13
+ }
14
+ }
15
+ export function providerLog(level, provider, message, error) {
16
+ const errorSuffix = error === undefined ? '' : ` | error=${formatProviderError(error)}`;
17
+ const timestamp = new Date().toISOString();
18
+ console.error(`[${timestamp}] [providers:${provider}] [${level.toUpperCase()}] ${message}${errorSuffix}`);
19
+ }
@@ -2,6 +2,7 @@ import { PROVIDER_PRIORITY, } from './types.js';
2
2
  import { PolyHavenProvider } from './polyhaven.js';
3
3
  import { AmbientCGProvider } from './ambientcg.js';
4
4
  import { KenneyProvider } from './kenney.js';
5
+ import { providerLog } from './logging.js';
5
6
  export class AssetManager {
6
7
  providers = new Map();
7
8
  sortedProviders = [];
@@ -40,7 +41,7 @@ export class AssetManager {
40
41
  allResults.push(...results);
41
42
  }
42
43
  catch (error) {
43
- console.error(`[AssetManager] Search failed for ${provider.config.name}: ${error}`);
44
+ providerLog('error', 'asset-manager', `Search failed for ${provider.config.name}`, error);
44
45
  }
45
46
  }
46
47
  return allResults
@@ -66,7 +67,7 @@ export class AssetManager {
66
67
  }
67
68
  }
68
69
  catch (error) {
69
- console.error(`[AssetManager] Search failed for ${provider.config.name}: ${error}`);
70
+ providerLog('error', 'asset-manager', `Search failed for ${provider.config.name}`, error);
70
71
  }
71
72
  }
72
73
  return [];
@@ -104,7 +105,7 @@ export class AssetManager {
104
105
  }
105
106
  }
106
107
  catch (error) {
107
- console.error(`[AssetManager] Download failed for ${provider.config.name}: ${error}`);
108
+ providerLog('error', 'asset-manager', `Download failed for ${provider.config.name}`, error);
108
109
  }
109
110
  }
110
111
  return {
@@ -1,6 +1,7 @@
1
1
  import { existsSync, mkdirSync, createWriteStream, appendFileSync, writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import * as https from 'https';
4
+ import { providerLog } from './logging.js';
4
5
  const POLYHAVEN_CONFIG = {
5
6
  name: 'polyhaven',
6
7
  displayName: 'Poly Haven',
@@ -115,7 +116,7 @@ export class PolyHavenProvider {
115
116
  .slice(0, maxResults);
116
117
  }
117
118
  catch (error) {
118
- console.error(`[PolyHaven] Search failed: ${error}`);
119
+ providerLog('error', this.config.name, 'Search failed', error);
119
120
  return [];
120
121
  }
121
122
  }
@@ -176,6 +177,7 @@ export class PolyHavenProvider {
176
177
  };
177
178
  }
178
179
  catch (error) {
180
+ providerLog('error', this.config.name, 'Download failed', error);
179
181
  return {
180
182
  success: false,
181
183
  assetId,
@@ -2835,6 +2835,10 @@ func get_node_by_path_v2(scene_root: Node, node_path: String) -> Node:
2835
2835
 
2836
2836
  return scene_root.get_node_or_null(clean_path)
2837
2837
 
2838
+ # Backward-compatible alias for newer helpers that still call the old name
2839
+ func get_node_from_path(scene_root: Node, node_path: String) -> Node:
2840
+ return get_node_by_path_v2(scene_root, node_path)
2841
+
2838
2842
  # Helper function to build node tree structure recursively
2839
2843
  func build_node_tree(node: Node, current_depth: int, max_depth: int, include_properties: bool) -> Dictionary:
2840
2844
  var result = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gopeak",
3
- "version": "2.3.2",
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",
@@ -17,23 +17,27 @@
17
17
  "scripts": {
18
18
  "build": "tsc && node scripts/build-visualizer.js && node scripts/build.js",
19
19
  "typecheck": "tsc --noEmit",
20
+ "test": "npm run test:ci",
21
+ "smoke": "npm run test:ci",
20
22
  "test:smoke": "node scripts/smoke-test.mjs",
21
23
  "test:integration": "node test-bridge.mjs",
24
+ "test:dynamic-groups": "node test-dynamic-groups.mjs",
22
25
  "test:ci": "npm run test:smoke",
23
26
  "ci": "npm run build && npm run typecheck && npm run test:ci",
24
27
  "prepare": "npm run build",
25
- "postinstall": "node build/cli.js setup --silent 2>/dev/null || true",
28
+ "postinstall": "node -e \"try{require('child_process').execFileSync(process.execPath,['build/cli.js','setup','--silent'],{stdio:'ignore'})}catch{}\"",
26
29
  "watch": "tsc --watch",
27
30
  "inspector": "npx @modelcontextprotocol/inspector build/index.js",
28
31
  "pack": "npm pack --dry-run",
29
- "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"
30
34
  },
31
35
  "engines": {
32
36
  "node": ">=18"
33
37
  },
34
38
  "dependencies": {
35
- "@modelcontextprotocol/sdk": "^1.27.0",
36
- "axios": "^1.7.9",
39
+ "@modelcontextprotocol/sdk": "^1.27.1",
40
+ "axios": "^1.13.6",
37
41
  "fs-extra": "^11.2.0",
38
42
  "ws": "^8.18.0"
39
43
  },
@@ -43,14 +47,18 @@
43
47
  "esbuild": "^0.24.2",
44
48
  "typescript": "^5.3.3"
45
49
  },
50
+ "overrides": {
51
+ "@hono/node-server": "^1.19.11",
52
+ "hono": "4.12.7"
53
+ },
46
54
  "license": "MIT",
47
55
  "repository": {
48
56
  "type": "git",
49
- "url": "https://github.com/HaD0Yun/godot-mcp.git"
57
+ "url": "git+https://github.com/HaD0Yun/Gopeak-godot-mcp.git"
50
58
  },
51
- "homepage": "https://github.com/HaD0Yun/godot-mcp#readme",
59
+ "homepage": "https://github.com/HaD0Yun/Gopeak-godot-mcp#readme",
52
60
  "bugs": {
53
- "url": "https://github.com/HaD0Yun/godot-mcp/issues"
61
+ "url": "https://github.com/HaD0Yun/Gopeak-godot-mcp/issues"
54
62
  },
55
63
  "author": {
56
64
  "name": "HaD0Yun",