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:
@@ -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.3–0.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());
|
||||
}
|
||||
Reference in New Issue
Block a user