chore: 重命名 Tools/godot-mcp-pro-v1.15.1 → Tools/godot-mcp-pro,更新 .mcp.json

- 去掉版本号后缀,路径更稳定(后续更新工具不必再改 .mcp.json)
- .mcp.json 指向新路径 server/build/index.js
- server 依赖已 npm install 就绪;node_modules 由 .gitignore 忽略,未入库

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-20 15:37:18 +08:00
co-authored by Claude Opus 4.8
parent ab6e3ce359
commit e65066a8fb
196 changed files with 3 additions and 3 deletions
@@ -0,0 +1,54 @@
export class GodotConnectionError extends Error {
constructor(message: string) {
super(message);
this.name = "GodotConnectionError";
}
}
export class GodotCommandError extends Error {
public code: number;
public data?: Record<string, unknown>;
constructor(code: number, message: string, data?: Record<string, unknown>) {
super(message);
this.name = "GodotCommandError";
this.code = code;
this.data = data;
}
}
export class TimeoutError extends Error {
constructor(method: string, timeoutMs: number) {
super(`Command '${method}' timed out after ${timeoutMs}ms`);
this.name = "TimeoutError";
}
}
export function formatErrorForMcp(error: unknown): string {
if (error instanceof GodotCommandError) {
let msg = `Godot error (${error.code}): ${error.message}`;
// Surface runtime errors captured at timeout so the agent sees the actual
// cause (a script error that paused the scene) instead of assuming the
// game/connection is dead.
const runtimeErrors = error.data?.runtime_errors;
if (Array.isArray(runtimeErrors) && runtimeErrors.length > 0) {
msg += `\nRuntime errors:\n${runtimeErrors
.map((e) => ` - ${String(e)}`)
.join("\n")}`;
}
if (error.data?.suggestion) {
msg += `\nSuggestion: ${error.data.suggestion}`;
}
return msg;
}
if (error instanceof GodotConnectionError) {
return `Connection error: ${error.message}. Make sure the Godot MCP Pro plugin is enabled in your Godot editor.`;
}
if (error instanceof TimeoutError) {
return error.message;
}
if (error instanceof Error) {
return error.message;
}
return String(error);
}
@@ -0,0 +1,45 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
const FALLBACK_INSTRUCTIONS = `# Godot MCP Pro
You have access to the Godot MCP Pro toolset for building and testing Godot games through the editor.
## Critical rules
- Tools are split into **editor** (always available) and **runtime** (require \`play_scene\` first).
- Never edit \`project.godot\` directly — use \`set_project_setting\`.
- For input simulation, use short \`simulate_key\` durations (0.30.5s), not integer seconds.
- \`execute_editor_script\` / \`execute_game_script\` must be valid GDScript; use \`_mcp_print(value)\` to return output.
## Getting oriented
- \`get_project_info\` — project overview
- \`get_scene_tree\` — current scene structure
- For a full usage guide, see \`instructions/CLAUDE.md\` in the installed package.
`;
/**
* Loads CLAUDE.md from the shipped `instructions/` directory so Claude has
* the essential usage guide from message #1 of every session. Falls back to
* a terse built-in string if the file can't be located (e.g. custom layouts).
*/
export function loadInstructions(): string {
const here = dirname(fileURLToPath(import.meta.url));
const candidates = [
resolve(here, "../../instructions/CLAUDE.md"),
resolve(here, "../../../instructions/CLAUDE.md"),
resolve(here, "../../../../instructions/CLAUDE.md"),
];
for (const path of candidates) {
try {
const content = readFileSync(path, "utf8");
if (content.trim().length > 0) return content;
} catch {
// try next
}
}
return FALLBACK_INSTRUCTIONS;
}
@@ -0,0 +1,73 @@
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
/**
* Minimal mode: only these tools are registered (~35 tools).
* Designed for clients with tight tool limits (Cursor: 40, local LLMs with small context).
*/
export const MINIMAL_TOOLS = new Set([
// project (5)
"get_project_info",
"get_filesystem_tree",
"search_files",
"search_in_files",
"set_project_setting",
// scene (6)
"get_scene_tree",
"create_scene",
"open_scene",
"play_scene",
"stop_scene",
"save_scene",
// node (5)
"add_node",
"delete_node",
"update_property",
"get_node_properties",
"connect_signal",
// script (5)
"read_script",
"create_script",
"edit_script",
"attach_script",
"validate_script",
// editor (5)
"get_editor_errors",
"get_output_log",
"get_game_screenshot",
"execute_editor_script",
"reload_project",
// input (3)
"simulate_key",
"simulate_mouse_click",
"simulate_action",
// runtime (5)
"get_game_scene_tree",
"get_game_node_properties",
"set_game_node_property",
"execute_game_script",
"find_ui_elements",
// input-map (1)
"get_input_actions",
]);
/**
* Creates a proxy around McpServer that filters tool registrations.
* Only tools in the allowSet will be registered; others are silently skipped.
*/
export function createFilteredServer(
server: McpServer,
allowSet: Set<string>
): McpServer {
return new Proxy(server, {
get(target, prop, receiver) {
if (prop === "tool") {
const originalTool = target.tool.bind(target);
return function filteredTool(name: string, ...args: unknown[]) {
if (!allowSet.has(name)) return;
return (originalTool as Function)(name, ...args);
};
}
return Reflect.get(target, prop, receiver);
},
});
}
@@ -0,0 +1,35 @@
export interface JsonRpcRequest {
jsonrpc: "2.0";
method: string;
params: Record<string, unknown>;
id: string;
}
export interface JsonRpcResponse {
jsonrpc: "2.0";
result?: unknown;
error?: JsonRpcError;
id: string | null;
}
export interface JsonRpcError {
code: number;
message: string;
data?: Record<string, unknown>;
}
export interface ToolDefinition {
name: string;
description: string;
inputSchema: {
type: "object";
properties: Record<string, unknown>;
required?: string[];
};
}
export interface PendingRequest {
resolve: (value: JsonRpcResponse) => void;
reject: (reason: Error) => void;
timer: ReturnType<typeof setTimeout>;
}
@@ -0,0 +1,33 @@
import { z } from "zod";
/**
* Coerce a value that might be a JSON string into a string array.
* LLMs sometimes pass arrays as stringified JSON (e.g. '["a","b"]' instead of ["a","b"]).
*/
export function coerceStringArray() {
return z.preprocess((val) => {
if (typeof val === "string") {
try {
const parsed = JSON.parse(val);
if (Array.isArray(parsed)) return parsed;
} catch {
// not JSON, return as-is for zod to validate
}
}
return val;
}, z.array(z.string()));
}
/**
* Coerce a value that might be a numeric string into a number.
* LLMs sometimes pass numbers as strings (e.g. "30" instead of 30).
*/
export function coerceNumber() {
return z.preprocess((val) => {
if (typeof val === "string") {
const n = Number(val);
if (!isNaN(n)) return n;
}
return val;
}, z.number());
}