refactor: 拆分 claude-dev-stack 为 windows-dev-stack 和 wsl-dev-stack
将原 claude-dev-stack 目录拆分为独立的 Windows 和 WSL 部署栈,便于分别维护和使用。 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||
import { z } from "zod";
|
||||
import { GodotConnection } from "../godot-connection.js";
|
||||
import { formatErrorForMcp } from "../utils/errors.js";
|
||||
|
||||
export function registerScriptTools(
|
||||
server: McpServer,
|
||||
godot: GodotConnection
|
||||
): void {
|
||||
server.tool(
|
||||
"list_scripts",
|
||||
"List all GDScript/C#/shader files in the project with class info",
|
||||
{
|
||||
path: z.string().optional().describe("Root path to search (default: res://)"),
|
||||
recursive: z.boolean().optional().describe("Search recursively (default: true)"),
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("list_scripts", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"read_script",
|
||||
"Read the full content of a GDScript file",
|
||||
{
|
||||
path: z.string().describe("Path to the script (e.g. 'res://scripts/player.gd')"),
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("read_script", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"create_script",
|
||||
"Create a new GDScript file with optional content or auto-generated template. Restricted to .gd/.cs paths. Refuses to overwrite a script that is currently open in Godot's script editor unless force=true is set.",
|
||||
{
|
||||
path: z.string().describe("Path for the new script (e.g. 'res://scripts/enemy_ai.gd'). Must be a .gd or .cs file."),
|
||||
content: z.string().optional().describe("Full script content. If empty, generates a template."),
|
||||
extends: z.string().optional().describe("Base class (default: 'Node'). Only used for template generation."),
|
||||
class_name: z.string().optional().describe("Class name to add. Only used for template generation."),
|
||||
force: z.boolean().optional().describe("Override the open-script-editor guard and write anyway. Use only when no editor buffer holds unsaved changes for the target path."),
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("create_script", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"edit_script",
|
||||
"Edit a script using search-and-replace, full content replacement, line insertion, or 1-based inclusive line-range replacement. Restricted to .gd/.cs paths. Refuses to write a script that is currently open in Godot's script editor unless force=true is set.",
|
||||
{
|
||||
path: z.string().describe("Path to the script to edit. Must be a .gd or .cs file."),
|
||||
replacements: z
|
||||
.array(
|
||||
z.object({
|
||||
search: z.string().describe("Text to find"),
|
||||
replace: z.string().describe("Replacement text"),
|
||||
regex: z.boolean().optional().describe("Use regex for search (default: false)"),
|
||||
})
|
||||
)
|
||||
.optional()
|
||||
.describe("Array of search-and-replace operations"),
|
||||
content: z.string().optional().describe("Full replacement content (replaces entire file), or replacement lines when combined with start_line/end_line"),
|
||||
insert_at_line: z.number().optional().describe("Line number to insert text at (0-indexed)"),
|
||||
text: z.string().optional().describe("Text to insert (used with insert_at_line)"),
|
||||
start_line: z.number().optional().describe("1-based inclusive starting line for range replacement (used with content)"),
|
||||
end_line: z.number().optional().describe("1-based inclusive ending line for range replacement (defaults to start_line)"),
|
||||
force: z.boolean().optional().describe("Override the open-script-editor guard and write anyway."),
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("edit_script", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"attach_script",
|
||||
"Attach a GDScript to a node in the current scene",
|
||||
{
|
||||
node_path: z.string().describe("Path to the target node"),
|
||||
script_path: z.string().describe("Path to the script file (e.g. 'res://scripts/player.gd')"),
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("attach_script", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"validate_script",
|
||||
"Validate a GDScript file by attempting to compile it. Returns whether the script is valid. Use get_output_log or get_editor_errors for detailed error messages on failure.",
|
||||
{
|
||||
path: z.string().describe("Path to the script to validate (e.g. 'res://scripts/player.gd')"),
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await godot.sendCommand("validate_script", params);
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
server.tool(
|
||||
"get_open_scripts",
|
||||
"Get a list of scripts currently open in the Godot script editor",
|
||||
{},
|
||||
async () => {
|
||||
try {
|
||||
const result = await godot.sendCommand("get_open_scripts");
|
||||
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
||||
} catch (e) {
|
||||
return { content: [{ type: "text", text: formatErrorForMcp(e) }], isError: true };
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user