diff --git a/.gitignore b/.gitignore index bd7fd82c1..69aaf3f74 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ node_modules/ # Build output build/ dist/ +mcpb-staging/ # Logs *.log diff --git a/CONTRIBUTING_CN.md b/CONTRIBUTING_CN.md new file mode 100644 index 000000000..b356d3a0e --- /dev/null +++ b/CONTRIBUTING_CN.md @@ -0,0 +1,187 @@ +# 为 Godot MCP 做贡献 + +感谢您考虑为 Godot MCP 做贡献!本文档概述了为项目做贡献的流程。 + +## 行为准则 + +通过参与本项目,您同意为每个人保持尊重和包容的环境。 + +## 我可以如何贡献? + +### 报告错误 + +- 检查错误是否已在问题部分报告 +- 如果可用,使用错误报告模板 +- 包含重现错误的详细步骤 +- 包含任何相关的日志或截图 +- 指定您的环境(操作系统、Godot 版本等) + +### 建议增强功能 + +- 检查增强功能是否已在问题部分建议 +- 如果可用,使用功能请求模板 +- 清楚地描述增强功能及其好处 +- 考虑增强功能如何适应项目范围 + +### 拉取请求 + +1. Fork 仓库 +2. 为您的功能或错误修复创建一个新分支(`git checkout -b feature/amazing-feature`) +3. 进行更改 +4. 如果有可用测试,请运行测试 +5. 使用清晰的提交消息提交您的更改 +6. 推送到您的分支(`git push origin feature/amazing-feature`) +7. 打开一个拉取请求 + +## 开发流程 + +### 设置开发环境 + +1. 克隆仓库 +2. 使用 `npm install` 安装依赖项 +3. 使用 `npm run build` 构建项目 +4. 对于自动重新构建的开发,使用 `npm run watch` + +### 项目结构 + +``` +godot-mcp/ +├── src/ # 源代码 +│ └── index.ts # 主服务器实现 +├── build/ # 编译的 JavaScript(生成的) +├── tests/ # 测试文件(未来) +├── examples/ # 示例 Godot 项目(未来) +├── LICENSE # MIT 许可证 +├── README.md # 文档 +├── CONTRIBUTING.md # 贡献指南 +├── package.json # 项目配置 +└── tsconfig.json # TypeScript 配置 +``` + +### 代码风格 + +- 遵循项目中现有的代码风格 +- 使用 TypeScript 以确保类型安全 +- 为所有函数和类包含 JSDoc 注释 +- 编写清晰和描述性的变量和函数名称 +- 为复杂对象使用有意义的接口 +- 使用详细的错误消息优雅地处理错误 + +### 调试 + +要调试 MCP 服务器: + +1. 将 `DEBUG` 环境变量设置为 `true` +2. 使用 MCP Inspector 进行交互式调试: + ```bash + npm run inspector + ``` +3. 检查日志以获取有关正在发生的事情的详细信息 + +### 添加新工具 + +向 MCP 服务器添加新工具时: + +1. 在 `setupToolHandlers` 方法定义工具 +2. 为工具创建处理程序方法 +3. 添加适当的输入验证和错误处理 +4. 使用新工具的文档更新 README.md +5. 更新 README.md 中的功能部分 +6. 更新配置示例中的 autoApprove 部分 +7. 为新功能添加测试 + +#### 最近添加的工具 + +最近添加了以下工具: + +- **get_project_info**:检索有关 Godot 项目的元数据 + - 分析项目结构 + - 返回有关场景、脚本和资源的信息 + - 帮助 LLM 理解 Godot 项目的组织 + +- **capture_screenshot**:截取正在运行的 Godot 项目的屏幕截图 + - 需要活动的 Godot 进程 + - 将屏幕截图保存到指定路径 + - 对于视觉调试和反馈很有用 + +示例: + +```typescript +// 在 setupToolHandlers 中 +{ + name: 'your_new_tool', + description: '描述您的工具做什么', + inputSchema: { + type: 'object', + properties: { + param1: { + type: 'string', + description: '参数 1 的描述', + }, + }, + required: ['param1'], + }, +} + +// 添加处理程序方法 +private async handleYourNewTool(args: any) { + // 验证输入 + if (!args.param1) { + return this.createErrorResponse( + '参数 1 是必需的', + ['为参数 1 提供有效值'] + ); + } + + try { + // 实现工具功能 + // ... + + return { + content: [ + { + type: 'text', + text: '您的工具的结果', + }, + ], + }; + } catch (error: any) { + return this.createErrorResponse( + `执行工具失败:${error?.message || '未知错误'}`, + [ + '可能的解决方案 1', + '可能的解决方案 2' + ] + ); + } +} +``` + +### 跨平台兼容性 + +进行更改时,确保它们在不同平台上工作: + +- 使用 Node.js 的路径工具(`path.join` 等)而不是硬编码的路径分隔符 +- 如果可能,在不同操作系统上测试 +- 考虑不同的 Godot 安装位置 +- 使用环境变量进行配置 + +## 测试 + +- 尽可能为新功能添加测试 +- 在提交拉取请求之前确保所有测试通过 +- 如果可能,在不同平台上测试 +- 使用不同版本的 Godot 进行测试 + +## 文档 + +- 使用新功能保持 README.md 更新 +- 记录所有工具及其参数 +- 为新功能包含示例 +- 使用常见问题更新故障排除部分 + +## 有问题? + +如果您对贡献有任何疑问,请随时打开一个问题进行讨论。 + +感谢您的贡献! diff --git a/README.md b/README.md index fcc253968..9b39f5bd1 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,17 @@ This direct feedback loop helps AI assistants like Claude understand what works - **Launch Godot Editor**: Open the Godot editor for a specific project - **Run Godot Projects**: Execute Godot projects in debug mode -- **Capture Debug Output**: Retrieve console output and error messages + - **Multi-Instance Support**: Run multiple Godot instances simultaneously (e.g., dedicated server + multiple clients) + - **Batch Launch**: Launch multiple instances in a single call with `run_multiple_projects` + - **Staggered Startup**: Use `delayMs` to stagger instance launches (e.g., start server before clients) + - **Custom Command-Line Arguments**: Pass additional arguments to Godot (e.g., `--server`, `--headless`, `profile=X`, `port=Y`) + - **Instance Management**: Track and manage multiple running instances with unique IDs + - **Bounded Output Buffers**: Automatic memory management for long-running instances (keeps last 1000 lines) + - **Auto-Cleanup**: Stale exited processes are automatically cleaned up after 10 minutes +- **Capture Debug Output**: Retrieve console output and error messages for specific instances or all instances - **Control Execution**: Start and stop Godot projects programmatically + - Stop individual instances, multiple specific instances, or all at once +- **List Running Processes**: View all currently running Godot instances and their status - **Get Godot Version**: Retrieve the installed Godot version - **List Godot Projects**: Find Godot projects in a specified directory - **Project Analysis**: Get detailed information about project structure @@ -90,15 +99,34 @@ This direct feedback loop helps AI assistants like Claude understand what works ### Step 1: Install and Build -First, clone the repository and build the MCP server: + +First, clone the repository and install dependencies: ```bash git clone https://github.com/Coding-Solo/godot-mcp.git cd godot-mcp npm install +``` + +#### Node + +To build the MCP server for CLI use: + +```bash npm run build ``` +#### Claude Desktop (MCPB) + +To build the MCPB extension for Claude Desktop: + +```bash +npm run build:mcpb +``` + +After building, double-click the generated `.mcpb` file in the build directory to install it in Claude Desktop. In the Config menu, set the path to your Godot executable. You can skip the rest of the configuration steps below. + + ### Step 2: Configure with Your AI Assistant #### Option A: Configure with Cline @@ -120,6 +148,8 @@ Add to your Cline MCP settings file (`~/Library/Application Support/Code/User/gl "run_project", "get_debug_output", "stop_project", + "list_processes", + "run_multiple_projects", "get_godot_version", "list_projects", "get_project_info", @@ -183,6 +213,20 @@ Once configured, your AI assistant will automatically run the MCP server when ne "Run my Godot project and show me any errors" +"Run my Godot project as a dedicated server on port 8080 in headless mode" + +"Launch a client instance with custom profile connecting to localhost:8080" + +"Launch a server and two clients for my multiplayer game, with the server starting first" + +"List all currently running Godot instances" + +"Stop all client instances but keep the server running" + +"Get debug output for the server instance" + +"Stop the client instance with ID 'client1'" + "Get information about my Godot project structure" "Analyze my Godot project structure and suggest improvements" @@ -223,6 +267,62 @@ This architecture provides several benefits: The bundled script accepts operation type and parameters as JSON, allowing for flexible and dynamic operation execution without generating temporary files for each operation. +### Multi-Instance Support + +The MCP server supports running multiple Godot instances simultaneously, which is essential for multiplayer game development and testing: + +- **Instance IDs**: Each running instance can be assigned a unique identifier (e.g., "server", "client1", "client2") or use auto-generated IDs +- **Command-Line Arguments**: Pass custom arguments to each instance (e.g., `--server`, `--headless`, `profile=X`, `port=Y`) +- **Batch Launch**: Use `run_multiple_projects` to launch multiple instances in a single tool call +- **Staggered Startup**: Use `delayMs` parameter to delay instance launches (e.g., start server 2 seconds before clients) +- **Instance Management**: + - Use `list_processes` to see all running instances and their status + - Use `get_debug_output` with an `instanceId` to get output for a specific instance + - Use `stop_project` with `instanceId` (single) or `instanceIds` (array) to stop specific instances, or without parameters to stop all + +**Example Workflow for Multiplayer Development:** + +**Option 1: Single Tool Call (Recommended)** + +Use `run_multiple_projects` to launch everything at once with staggered delays: + +```json +{ + "instances": [ + { + "projectPath": "/path/to/project", + "instanceId": "server", + "args": ["--", "--server", "port=8080", "--headless"], + "delayMs": 0 + }, + { + "projectPath": "/path/to/project", + "instanceId": "client1", + "args": ["--", "profile=player1", "ip=127.0.0.1", "port=8080"], + "delayMs": 2000 + }, + { + "projectPath": "/path/to/project", + "instanceId": "client2", + "args": ["--", "profile=player2", "ip=127.0.0.1", "port=8080"], + "delayMs": 2000 + } + ] +} +``` + +**Option 2: Individual Tool Calls** + +1. Launch dedicated server: `run_project` with `instanceId: "server"` and `args: ["--", "--server", "port=8080", "--headless"]` +2. Launch client instances: `run_project` with `instanceId: "client1"` and `args: ["--", "profile=player1", "ip=127.0.0.1", "port=8080"]` + +**Managing Instances:** + +- Monitor instances: Use `list_processes` to see all running instances +- Get specific output: Use `get_debug_output` with `instanceId: "server"` to see server logs +- Stop specific instances: Use `stop_project` with `instanceIds: ["client1", "client2"]` to stop multiple clients +- Stop all instances: Use `stop_project` without parameters + ## Troubleshooting - **Godot Not Found**: Set the GODOT_PATH environment variable to your Godot executable diff --git a/README_CN.md b/README_CN.md new file mode 100644 index 000000000..113d2ddfe --- /dev/null +++ b/README_CN.md @@ -0,0 +1,321 @@ +# Godot MCP + +[![Github-sponsors](https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=GitHub-Sponsors&logoColor=#EA4AAA)](https://github.com/sponsors/Coding-Solo) + +[![](https://badge.mcpx.dev?type=server 'MCP Server')](https://modelcontextprotocol.io/introduction) +[![Made with Godot](https://img.shields.io/badge/Made%20with-Godot-478CBF?style=flat&logo=godot%20engine&logoColor=white)](https://godotengine.org) +[![](https://img.shields.io/badge/Node.js-339933?style=flat&logo=nodedotjs&logoColor=white 'Node.js')](https://nodejs.org/en/download/) +[![](https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=white 'TypeScript')](https://www.typescriptlang.org/) + +[![](https://img.shields.io/github/last-commit/Coding-Solo/godot-mcp 'Last Commit')](https://github.com/Coding-Solo/godot-mcp/commits/main) +[![](https://img.shields.io/github/stars/Coding-Solo/godot-mcp 'Stars')](https://github.com/Coding-Solo/godot-mcp/stargazers) +[![](https://img.shields.io/github/forks/Coding-Solo/godot-mcp 'Forks')](https://github.com/Coding-Solo/godot-mcp/network/members) +[![](https://img.shields.io/badge/License-MIT-red.svg 'MIT License')](https://opensource.org/licenses/MIT) + +```text + ((((((( ((((((( + ((((((((((( ((((((((((( + ((((((((((((( ((((((((((((( + ((((((((((((((((((((((((((((((((( + ((((((((((((((((((((((((((((((((( + ((((( ((((((((((((((((((((((((((((((((((((((((( ((((( + ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( + (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( + (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( + ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( + ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( + (((((((((((@@@@@@@(((((((((((((((((((((((((((@@@@@@@((((((((((( + (((((((((@@@@,,,,,@@@(((((((((((((((((((((@@@,,,,,@@@@((((((((( + ((((((((@@@,,,,,,,,,@@(((((((@@@@@(((((((@@,,,,,,,,,@@@(((((((( + ((((((((@@@,,,,,,,,,@@(((((((@@@@@(((((((@@,,,,,,,,,@@@(((((((( + (((((((((@@@,,,,,,,@@((((((((@@@@@((((((((@@,,,,,,,@@@((((((((( + ((((((((((((@@@@@@(((((((((((@@@@@(((((((((((@@@@@@(((((((((((( + ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( + ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( + @@@@@@@@@@@@@((((((((((((@@@@@@@@@@@@@((((((((((((@@@@@@@@@@@@@ + ((((((((( @@@(((((((((((@@(((((((((((@@(((((((((((@@@ ((((((((( + (((((((((( @@((((((((((@@@(((((((((((@@@((((((((((@@ (((((((((( + (((((((((((@@@@@@@@@@@@@@(((((((((((@@@@@@@@@@@@@@((((((((((( + ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( + ((((((((((((((((((((((((((((((((((((((((((((((((((((( + ((((((((((((((((((((((((((((((((((((((((((((((( + ((((((((((((((((((((((((((((((((( + + + /$$ /$$ /$$$$$$ /$$$$$$$ + | $$$ /$$$ /$$__ $$| $$__ $$ + | $$$$ /$$$$| $$ \__/| $$ \ $$ + | $$ $$/$$ $$| $$ | $$$$$$$/ + | $$ $$$| $$| $$ | $$____/ + | $$\ $ | $$| $$ $$| $$ + | $$ \/ | $$| $$$$$$/| $$ + |__/ |__/ \______/ |__/ +``` + +一个用于与 Godot 游戏引擎交互的模型上下文协议(MCP)服务器。 + +## 简介 + +Godot MCP 使 AI 助手能够启动 Godot 编辑器、运行项目、捕获调试输出和控制项目执行——所有这些都通过标准化的接口实现。 + +这种直接反馈循环帮助像 Claude 这样的 AI 助手理解在真实的 Godot 项目中什么有效、什么无效,从而提供更好的代码生成和调试协助。 + +## 功能特性 + +- **启动 Godot 编辑器**:为特定项目打开 Godot 编辑器 +- **运行 Godot 项目**:在调试模式下执行 Godot 项目 + - **多实例支持**:同时运行多个 Godot 实例(例如:专用服务器 + 多个客户端) + - **批量启动**:使用 `run_multiple_projects` 在一次调用中启动多个实例 + - **交错启动**:使用 `delayMs` 交错启动实例(例如:先启动服务器再启动客户端) + - **自定义命令行参数**:向 Godot 传递额外参数(例如:`--server`、`--headless`、`profile=X`、`port=Y`) + - **实例管理**:使用唯一 ID 跟踪和管理多个运行中的实例 + - **有界输出缓冲区**:自动内存管理,用于长时间运行的实例(保留最后 1000 行) + - **自动清理**:已退出的进程在 10 分钟后自动清理 +- **捕获调试输出**:检索特定实例或所有实例的控制台输出和错误消息 +- **控制执行**:以编程方式启动和停止 Godot 项目 + - 停止单个实例、多个特定实例或一次性停止所有实例 +- **列出运行中的进程**:查看所有当前运行的 Godot 实例及其状态 +- **获取 Godot 版本**:检索已安装的 Godot 版本 +- **列出 Godot 项目**:在指定目录中查找 Godot 项目 +- **项目分析**:获取项目结构的详细信息 +- **场景管理**: + - 创建具有指定根节点类型的新场景 + - 向现有场景添加节点,并具有可自定义的属性 + - 将精灵和纹理加载到 Sprite2D 节点中 + - 将 3D 场景导出为 GridMap 的 MeshLibrary 资源 + - 保存场景,并提供创建变体的选项 +- **UID 管理**(适用于 Godot 4.4+): + - 获取特定文件的 UID + - 通过重新保存资源来更新 UID 引用 + +## 系统要求 + +- 系统上已安装 [Godot Engine](https://godotengine.org/download) +- Node.js 和 npm +- 支持 MCP 的 AI 助手(Cline、Cursor 等) + +## 安装和配置 + +### 步骤 1:安装和构建 + +首先,克隆仓库并构建 MCP 服务器: + +```bash +git clone https://github.com/Coding-Solo/godot-mcp.git +cd godot-mcp +npm install +npm run build +``` + +### 步骤 2:配置 AI 助手 + +#### 选项 A:配置 Cline + +将以下内容添加到您的 Cline MCP 设置文件(`~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`): + +```json +{ + "mcpServers": { + "godot": { + "command": "node", + "args": ["/absolute/path/to/godot-mcp/build/index.js"], + "env": { + "DEBUG": "true" // 可选:启用详细日志记录 + }, + "disabled": false, + "autoApprove": [ + "launch_editor", + "run_project", + "get_debug_output", + "stop_project", + "list_processes", + "run_multiple_projects", + "get_godot_version", + "list_projects", + "get_project_info", + "create_scene", + "add_node", + "load_sprite", + "export_mesh_library", + "save_scene", + "get_uid", + "update_project_uids" + ] + } + } +} +``` + +#### 选项 B:配置 Cursor + +**使用 Cursor UI:** + +1. 转到 **Cursor 设置** > **功能** > **MCP** +2. 点击 **+ 添加新的 MCP 服务器** 按钮 +3. 填写表单: + - 名称:`godot`(或您喜欢的任何名称) + - 类型:`command` + - 命令:`node /absolute/path/to/godot-mcp/build/index.js` +4. 点击"添加" +5. 您可能需要点击 MCP 服务器卡片右上角的刷新按钮来填充工具列表 + +**使用项目特定配置:** + +在项目目录中创建 `.cursor/mcp.json` 文件,内容如下: + +```json +{ + "mcpServers": { + "godot": { + "command": "node", + "args": ["/absolute/path/to/godot-mcp/build/index.js"], + "env": { + "DEBUG": "true" // 启用详细日志记录 + } + } + } +} +``` + +### 步骤 3:可选的环境变量 + +您可以使用以下环境变量自定义服务器行为: + +- `GODOT_PATH`:Godot 可执行文件的路径(覆盖自动检测) +- `DEBUG`:设置为 "true" 以启用详细的服务器端调试日志 + +## 示例提示词 + +配置完成后,您的 AI 助手将在需要时自动运行 MCP 服务器。您可以使用如下提示词: + +```text +"为 /path/to/project 中的我的项目启动 Godot 编辑器" + +"运行我的 Godot 项目并显示任何错误" + +"以无头模式在端口 8080 上运行我的 Godot 项目作为专用服务器" + +"启动一个连接到 localhost:8080 的自定义配置文件客户端实例" + +"为我的多人游戏启动一个服务器和两个客户端,服务器先启动" + +"列出所有当前运行的 Godot 实例" + +"停止所有客户端实例但保持服务器运行" + +"获取服务器实例的调试输出" + +"停止 ID 为 'client1' 的客户端实例" + +"获取有关我的 Godot 项目结构的信息" + +"分析我的 Godot 项目结构并提出改进建议" + +"帮助我调试 Godot 项目中的这个错误:[粘贴错误]" + +"为具有二段跳和墙壁滑动的角色控制器编写 GDScript" + +"在我的 Godot 项目中创建一个带有 Player 节点的新场景" + +"向我的玩家场景添加 Sprite2D 节点并加载角色纹理" + +"将我的 3D 模型导出为 MeshLibrary 以便与 GridMap 一起使用" + +"创建一个带有按钮和标签的 UI 场景,用于游戏的主菜单" + +"获取我的 Godot 4.4 项目中特定脚本文件的 UID" + +"升级到 4.4 后更新我的 Godot 项目中的 UID 引用" +``` + +## 实现细节 + +### 架构 + +Godot MCP 服务器使用捆绑的 GDScript 方法来处理复杂操作: + +1. **直接命令**:简单的操作(如启动编辑器或获取项目信息)直接使用 Godot 的内置 CLI 命令。 +2. **捆绑操作脚本**:复杂的操作(如创建场景或添加节点)使用单个全面的 GDScript 文件(`godot_operations.gd`)来处理所有操作。 + +这种架构提供了几个好处: + +- **无临时文件**:消除了对临时脚本文件的需求,保持系统整洁 +- **简化的代码库**:将所有 Godot 操作集中在一个(某种程度上)有组织的文件中 +- **更好的可维护性**:更容易添加新操作或修改现有操作 +- **改进的错误处理**:在所有操作中提供一致的错误报告 +- **减少开销**:最小化文件 I/O 操作以提高性能 + +捆绑脚本接受操作类型和参数作为 JSON,允许灵活和动态的操作执行,而无需为每个操作生成临时文件。 + +### 多实例支持 + +MCP 服务器支持同时运行多个 Godot 实例,这对于多人游戏开发和测试至关重要: + +- **实例 ID**:每个运行中的实例都可以分配一个唯一标识符(例如:"server"、"client1"、"client2")或使用自动生成的 ID +- **命令行参数**:向每个实例传递自定义参数(例如:`--server`、`--headless`、`profile=X`、`port=Y`) +- **批量启动**:使用 `run_multiple_projects` 在单个工具调用中启动多个实例 +- **交错启动**:使用 `delayMs` 参数延迟实例启动(例如:在客户端之前 2 秒启动服务器) +- **实例管理**: + - 使用 `list_processes` 查看所有运行中的实例及其状态 + - 使用带有 `instanceId` 的 `get_debug_output` 获取特定实例的输出 + - 使用带有 `instanceId`(单个)或 `instanceIds`(数组)的 `stop_project` 停止特定实例,或不带参数停止所有实例 + +**多人游戏开发示例工作流程:** + +**选项 1:单个工具调用(推荐)** + +使用 `run_multiple_projects` 一次启动所有内容,并带有交错延迟: + +```json +{ + "instances": [ + { + "projectPath": "/path/to/project", + "instanceId": "server", + "args": ["--", "--server", "port=8080", "--headless"], + "delayMs": 0 + }, + { + "projectPath": "/path/to/project", + "instanceId": "client1", + "args": ["--", "profile=player1", "ip=127.0.0.1", "port=8080"], + "delayMs": 2000 + }, + { + "projectPath": "/path/to/project", + "instanceId": "client2", + "args": ["--", "profile=player2", "ip=127.0.0.1", "port=8080"], + "delayMs": 2000 + } + ] +} +``` + +**选项 2:单独的工具调用** + +1. 启动专用服务器:`run_project` 带有 `instanceId: "server"` 和 `args: ["--", "--server", "port=8080", "--headless"]` +2. 启动客户端实例:`run_project` 带有 `instanceId: "client1"` 和 `args: ["--", "profile=player1", "ip=127.0.0.1", "port=8080"]` + +**管理实例:** + +- 监控实例:使用 `list_processes` 查看所有运行中的实例 +- 获取特定输出:使用带有 `instanceId: "server"` 的 `get_debug_output` 查看服务器日志 +- 停止特定实例:使用带有 `instanceIds: ["client1", "client2"]` 的 `stop_project` 停止多个客户端 +- 停止所有实例:使用不带参数的 `stop_project` + +## 故障排除 + +- **未找到 Godot**:将 GODOT_PATH 环境变量设置为您的 Godot 可执行文件 +- **连接问题**:确保服务器正在运行并重新启动您的 AI 助手 +- **无效的项目路径**:确保路径指向包含 project.godot 文件的目录 +- **构建问题**:通过运行 `npm install` 确保已安装所有依赖项 +- **针对 Cursor 特别说明**: + - 确保 MCP 服务器在 Cursor 设置中显示并已启用(设置 > MCP) + - MCP 工具只能使用 Agent 聊天配置文件运行(需要 Cursor Pro 或 Business 订阅) + - 使用"Yolo 模式"自动运行 MCP 工具请求 + +## 许可证 + +本项目采用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件。 + +[![MseeP.ai Security Assessment Badge](https://mseep.net/pr/coding-solo-godot-mcp-badge.png)](https://mseep.ai/app/coding-solo-godot-mcp) diff --git a/icon_color.png b/icon_color.png new file mode 100644 index 000000000..0e204bc49 Binary files /dev/null and b/icon_color.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 000000000..d4c716646 --- /dev/null +++ b/manifest.json @@ -0,0 +1,59 @@ +{ + "manifest_version": "0.3", + "name": "Godot MCP", + "version": "0.0.0", + "description": "MCP server for interfacing with Godot game engine. Launch editor, run projects, manage scenes, and capture debug output.", + "author": { + "name": "Solomon Elias", + "email": "solomon@sippy.ai" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/Coding-Solo/godot-mcp" + }, + "homepage": "https://github.com/Coding-Solo/godot-mcp", + "icon": "icon_color.png", + "server": { + "type": "node", + "entry_point": "server/index.js", + "mcp_config": { + "command": "node", + "args": ["${__dirname}/server/index.js"], + "env": { + "GODOT_PATH": "${user_config.godot_path}" + } + } + }, + "user_config": { + "godot_path": { + "type": "file", + "title": "Godot Executable Path", + "description": "Path to the Godot engine executable. Leave empty for auto-detection from system PATH.", + "required": false + } + }, + "compatibility": { + "platforms": ["darwin", "win32", "linux"], + "runtimes": { + "node": ">=18.0.0" + } + }, + "tools": [ + { "name": "launch_editor", "description": "Open Godot editor for a project" }, + { "name": "run_project", "description": "Execute Godot project in debug mode" }, + { "name": "get_debug_output", "description": "Retrieve captured console output" }, + { "name": "stop_project", "description": "Terminate running Godot process" }, + { "name": "get_godot_version", "description": "Get installed Godot version" }, + { "name": "list_projects", "description": "Find Godot projects in a directory" }, + { "name": "get_project_info", "description": "Get project metadata and structure" }, + { "name": "create_scene", "description": "Create new scene files" }, + { "name": "add_node", "description": "Add nodes to existing scenes" }, + { "name": "load_sprite", "description": "Load textures into Sprite2D nodes" }, + { "name": "export_mesh_library", "description": "Export 3D scenes as MeshLibrary" }, + { "name": "save_scene", "description": "Save scene changes" }, + { "name": "get_uid", "description": "Get UID for a specific file (Godot 4.4+)" }, + { "name": "update_project_uids", "description": "Update UID references across project resources" } + ], + "keywords": ["godot", "game-engine", "game-development"] +} diff --git a/package-lock.json b/package-lock.json index 3334689b4..5a9225f12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "godot-mcp", - "version": "0.1.0", + "version": "0.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "godot-mcp", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "0.6.0", + "@modelcontextprotocol/sdk": "^1.26.0", "axios": "^1.7.9", "fs-extra": "^11.2.0" }, @@ -17,243 +17,2224 @@ "godot-mcp": "build/index.js" }, "devDependencies": { + "@anthropic-ai/mcpb": "^2.1.2", "@types/node": "^20.11.24", + "esbuild": "^0.27.3", "typescript": "^5.3.3" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@anthropic-ai/mcpb": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@anthropic-ai/mcpb/-/mcpb-2.1.2.tgz", + "integrity": "sha512-goRbBC8ySo7SWb7tRzr+tL6FxDc4JPTRCdgfD2omba7freofvjq5rom1lBnYHZHo6Mizs1jAHJeN53aZbDoy8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/prompts": "^6.0.1", + "commander": "^13.1.0", + "fflate": "^0.8.2", + "galactus": "^1.0.0", + "ignore": "^7.0.5", + "node-forge": "^1.3.2", + "pretty-bytes": "^5.6.0", + "zod": "^3.25.67", + "zod-to-json-schema": "^3.24.6" + }, + "bin": { + "mcpb": "dist/cli/cli.js" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.11", + "resolved": "https://registry.npmmirror.com/@hono/node-server/-/node-server-1.19.11.tgz", + "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-3.0.1.tgz", + "integrity": "sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-4.0.1.tgz", + "integrity": "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", + "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.5.5", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/@types/node": { + "version": "22.19.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.13.tgz", + "integrity": "sha512-akNQMv0wW5uyRpD2v2IEyRSZiR+BeGuoB6L310EgGObO44HSMNT8z1xzio28V8qOrgYaopIDNA18YgdXd+qTiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@inquirer/core/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@inquirer/editor": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-3.0.1.tgz", + "integrity": "sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-3.0.1.tgz", + "integrity": "sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-3.0.1.tgz", + "integrity": "sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-2.0.1.tgz", + "integrity": "sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-3.0.1.tgz", + "integrity": "sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-6.0.1.tgz", + "integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^3.0.1", + "@inquirer/confirm": "^4.0.1", + "@inquirer/editor": "^3.0.1", + "@inquirer/expand": "^3.0.1", + "@inquirer/input": "^3.0.1", + "@inquirer/number": "^2.0.1", + "@inquirer/password": "^3.0.1", + "@inquirer/rawlist": "^3.0.1", + "@inquirer/search": "^2.0.1", + "@inquirer/select": "^3.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-3.0.1.tgz", + "integrity": "sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-2.0.1.tgz", + "integrity": "sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-3.0.1.tgz", + "integrity": "sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", + "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", + "dev": true, + "license": "MIT", + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.27.1", + "resolved": "https://registry.npmmirror.com/@modelcontextprotocol/sdk/-/sdk-1.27.1.tgz", + "integrity": "sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.17.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz", + "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmmirror.com/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmmirror.com/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmmirror.com/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.3.1", + "resolved": "https://registry.npmmirror.com/express-rate-limit/-/express-rate-limit-8.3.1.tgz", + "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/external-editor/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/flora-colossus": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-2.0.0.tgz", + "integrity": "sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "fs-extra": "^10.1.0" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/flora-colossus/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/galactus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/galactus/-/galactus-1.0.0.tgz", + "integrity": "sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "flora-colossus": "^2.0.0", + "fs-extra": "^10.1.0" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/galactus/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/@modelcontextprotocol/sdk": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.6.0.tgz", - "integrity": "sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==", + "node_modules/hono": { + "version": "4.12.6", + "resolved": "https://registry.npmmirror.com/hono/-/hono-4.12.6.tgz", + "integrity": "sha512-KljEp+MeEEEIOT75qBo1UjqqB29fRMtlDEwCxcexOzdkUq6LR/vRvHk5pdROcxyOYyW1niq7Gb5pFVGy5R1eBw==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "content-type": "^1.0.5", - "raw-body": "^3.0.0", - "zod": "^3.23.8" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/@types/node": { - "version": "20.17.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.24.tgz", - "integrity": "sha512-d7fGCyB96w9BnWQrOsJtpyiSaBcAYYr75bnK6ZRjDbql2cGLj/3GsL5OYmLPNq76l7Gf2q4Rv9J2o6h5CrD9sA==", - "dev": true, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, - "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.1", + "resolved": "https://registry.npmmirror.com/jose/-/jose-6.2.1.tgz", + "integrity": "sha512-jUaKr1yrbfaImV7R2TN/b3IcZzsw38/chqMpo2XJ7i2F8AfM/lA4G1goC3JVEwg0H7UldTmSt3P68nt31W7/mw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmmirror.com/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "mime-db": "1.52.0" }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-forge": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", + "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { - "delayed-stream": "~1.0.0" + "ee-first": "1.1.1" }, "engines": { "node": ">= 0.8" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { - "node": ">=0.4.0" + "node": ">= 0.8" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.10" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmmirror.com/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">= 0.6" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, "engines": { - "node": ">= 0.4" + "node": ">= 0.10" } }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": ">= 0.4" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, "engines": { - "node": ">= 0.4" + "node": ">= 0.6" } }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "node_modules/send/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, "engines": { - "node": ">=4.0" + "node": ">=18" }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmmirror.com/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "shebang-regex": "^3.0.0" }, "engines": { - "node": ">=14.14" + "node": ">=8" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=8" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -262,24 +2243,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -287,17 +2259,17 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, "engines": { "node": ">= 0.4" }, @@ -305,13 +2277,17 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { + "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { - "has-symbols": "^1.0.3" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -320,143 +2296,128 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", "engines": { - "node": ">= 0.4" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, "engines": { "node": ">= 0.8" } }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { - "universalify": "^2.0.0" + "ansi-regex": "^5.0.1" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "engines": { + "node": ">=8" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, "engines": { - "node": ">= 0.4" + "node": ">=0.6.0" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=0.6" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">= 0.6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "node_modules/type-is/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, "engines": { - "node": ">=0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/typescript": { @@ -498,14 +2459,81 @@ "node": ">= 0.8" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { - "version": "3.24.2", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", - "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } } } } diff --git a/package.json b/package.json index fe431bef8..ebd37eef2 100644 --- a/package.json +++ b/package.json @@ -28,18 +28,21 @@ ], "scripts": { "build": "tsc && node scripts/build.js", + "build:mcpb": "npm run build && node scripts/build-mcpb.js", "inspector": "npx @modelcontextprotocol/inspector build/index.js", "prepare": "npm run build", "watch": "tsc --watch" }, "dependencies": { - "@modelcontextprotocol/sdk": "0.6.0", + "@modelcontextprotocol/sdk": "^1.26.0", "axios": "^1.7.9", "fs-extra": "^11.2.0" }, "devDependencies": { + "@anthropic-ai/mcpb": "^2.1.2", "@types/node": "^20.11.24", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "esbuild": "^0.27.3" }, "engines": { "node": ">=18.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 000000000..212b128a5 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,362 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@modelcontextprotocol/sdk': + specifier: 0.6.0 + version: 0.6.0 + axios: + specifier: ^1.7.9 + version: 1.13.2 + fs-extra: + specifier: ^11.2.0 + version: 11.3.3 + devDependencies: + '@types/node': + specifier: ^20.11.24 + version: 20.19.27 + typescript: + specifier: ^5.3.3 + version: 5.9.3 + +packages: + + '@modelcontextprotocol/sdk@0.6.0': + resolution: {integrity: sha512-9rsDudGhDtMbvxohPoMMyAUOmEzQsOK+XFchh6gZGqo8sx9sBuZQs+CUttXqa8RZXKDaJRCN2tUtgGof7jRkkw==} + + '@types/node@20.19.27': + resolution: {integrity: sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fs-extra@11.3.3: + resolution: {integrity: sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==} + engines: {node: '>=14.14'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.7.1: + resolution: {integrity: sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==} + engines: {node: '>=0.10.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@modelcontextprotocol/sdk@0.6.0': + dependencies: + content-type: 1.0.5 + raw-body: 3.0.2 + zod: 3.25.76 + + '@types/node@20.19.27': + dependencies: + undici-types: 6.21.0 + + asynckit@0.4.0: {} + + axios@1.13.2: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + content-type@1.0.5: {} + + delayed-stream@1.0.0: {} + + depd@2.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + follow-redirects@1.15.11: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + fs-extra@11.3.3: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.1: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + proxy-from-env@1.1.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.1 + unpipe: 1.0.0 + + safer-buffer@2.1.2: {} + + setprototypeof@1.2.0: {} + + statuses@2.0.2: {} + + toidentifier@1.0.1: {} + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + zod@3.25.76: {} diff --git a/scripts/build-mcpb.js b/scripts/build-mcpb.js new file mode 100644 index 000000000..73670a84e --- /dev/null +++ b/scripts/build-mcpb.js @@ -0,0 +1,61 @@ +import * as esbuild from 'esbuild'; +import { packExtension } from '@anthropic-ai/mcpb'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.join(__dirname, '..'); +const stagingDir = path.join(root, 'mcpb-staging'); + +const buildIndex = path.join(root, 'build', 'index.js'); +const buildGd = path.join(root, 'build', 'scripts', 'godot_operations.gd'); + +if (!fs.existsSync(buildIndex)) { + console.error('Error: build/index.js not found. Run "npm run build" first.'); + process.exit(1); +} +if (!fs.existsSync(buildGd)) { + console.error('Error: build/scripts/godot_operations.gd not found. Run "npm run build" first.'); + process.exit(1); +} + +fs.rmSync(stagingDir, { recursive: true, force: true }); +fs.mkdirSync(path.join(stagingDir, 'server', 'scripts'), { recursive: true }); + +try { + const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); + const manifest = JSON.parse(fs.readFileSync(path.join(root, 'manifest.json'), 'utf8')); + manifest.version = pkg.version; + fs.writeFileSync(path.join(stagingDir, 'manifest.json'), JSON.stringify(manifest, null, 2)); + console.log(`Manifest version: ${pkg.version}`); + + console.log('Bundling with esbuild...'); + await esbuild.build({ + entryPoints: [buildIndex], + bundle: true, + platform: 'node', + target: 'node18', + format: 'esm', + outfile: path.join(stagingDir, 'server', 'index.js'), + }); + + fs.copyFileSync(buildGd, path.join(stagingDir, 'server', 'scripts', 'godot_operations.gd')); + + const iconSrc = path.join(root, 'icon_color.png'); + if (fs.existsSync(iconSrc)) { + fs.copyFileSync(iconSrc, path.join(stagingDir, 'icon_color.png')); + console.log('Copied icon_color.png'); + } else { + console.warn('Warning: icon_color.png not found'); + } + + fs.mkdirSync(path.join(root, 'build'), { recursive: true }); + const outputFile = path.join(root, 'build', 'godot-mcp.mcpb'); + console.log('Packing MCPB...'); + const success = await packExtension({ extensionPath: stagingDir, outputPath: outputFile }); + if (!success) process.exit(1); + console.log('\nMCPB bundle created: build/godot-mcp.mcpb'); +} finally { + fs.rmSync(stagingDir, { recursive: true, force: true }); +} diff --git a/scripts/configure.js b/scripts/configure.js new file mode 100644 index 000000000..95a5f7252 --- /dev/null +++ b/scripts/configure.js @@ -0,0 +1,305 @@ +#!/usr/bin/env node +/** + * MCP Configuration Wizard + * Automatically generates MCP configuration files for various AI clients + */ + +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +/** + * Supported AI clients configuration + */ +const CLIENTS = { + // IDE clients + 'trae-cn': { + name: 'Trae CN', + type: 'ide', + configPaths: { + win32: path.join(process.env.APPDATA || '', 'Trae CN', 'User', 'mcp.json'), + darwin: path.join(os.homedir(), 'Library', 'Application Support', 'Trae CN', 'User', 'mcp.json'), + linux: path.join(os.homedir(), '.config', 'Trae CN', 'User', 'mcp.json'), + }, + }, + 'cursor': { + name: 'Cursor', + type: 'ide', + configPaths: { + win32: path.join(process.env.APPDATA || '', 'Cursor', 'User', 'mcp.json'), + darwin: path.join(os.homedir(), '.cursor', 'mcp.json'), + linux: path.join(os.homedir(), '.cursor', 'mcp.json'), + }, + }, + 'windsurf': { + name: 'Windsurf', + type: 'ide', + configPaths: { + win32: path.join(process.env.APPDATA || '', 'Codeium', 'windsurf', 'mcp_config.json'), + darwin: path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json'), + linux: path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json'), + }, + }, + 'claude-desktop': { + name: 'Claude Desktop', + type: 'ide', + configPaths: { + win32: path.join(process.env.APPDATA || '', 'Claude', 'claude_desktop_config.json'), + darwin: path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'), + linux: path.join(os.homedir(), '.config', 'Claude', 'claude_desktop_config.json'), + }, + }, + 'cline': { + name: 'Cline (VS Code)', + type: 'ide', + configPaths: { + win32: path.join(process.env.APPDATA || '', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'), + darwin: path.join(os.homedir(), 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'), + linux: path.join(os.homedir(), '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'), + }, + }, +}; + +/** + * CLI commands for different CLI tools + */ +const CLI_COMMANDS = { + 'claude-cli': { + name: 'Claude CLI (Claude Code)', + command: (serverPath, scope = 'user') => + `claude mcp add --scope ${scope} --transport stdio godot-mcp node "${serverPath}"`, + }, + 'codex-cli': { + name: 'Codex CLI', + command: (serverPath, scope = 'user') => + `codex mcp add --scope ${scope} --transport stdio godot-mcp node "${serverPath}"`, + }, + 'gemini-cli': { + name: 'Gemini CLI', + command: (serverPath, scope = 'user') => + `gemini mcp add --scope ${scope} --transport stdio godot-mcp node "${serverPath}"`, + }, +}; + +/** + * Get the build output path + */ +function getServerPath() { + const scriptDir = __dirname; + return path.join(scriptDir, '..', 'build', 'index.js'); +} + +/** + * Generate MCP configuration for a client + */ +function generateConfig(serverPath, httpPort = null) { + const config = { + mcpServers: { + 'godot-mcp': { + command: 'node', + args: [serverPath], + env: { + DEBUG: 'false', + }, + }, + }, + }; + + if (httpPort) { + config.mcpServers['godot-mcp'].env.MCP_TRANSPORT = 'http'; + config.mcpServers['godot-mcp'].env.MCP_HTTP_PORT = httpPort.toString(); + } + + return config; +} + +/** + * Merge with existing configuration + */ +function mergeConfig(existingConfig, newConfig) { + if (!existingConfig) return newConfig; + + const merged = { ...existingConfig }; + merged.mcpServers = { + ...(existingConfig.mcpServers || {}), + ...newConfig.mcpServers, + }; + return merged; +} + +/** + * Configure a specific IDE client + */ +function configureClient(clientId, serverPath, httpPort = null) { + const client = CLIENTS[clientId]; + if (!client) { + console.error(`Unknown client: ${clientId}`); + console.log(`Available clients: ${Object.keys(CLIENTS).join(', ')}`); + return false; + } + + const configPath = client.configPaths[process.platform]; + if (!configPath) { + console.error(`Platform ${process.platform} is not supported for ${client.name}`); + return false; + } + + const newConfig = generateConfig(serverPath, httpPort); + + // Read existing config if it exists + let existingConfig = null; + if (fs.existsSync(configPath)) { + try { + const content = fs.readFileSync(configPath, 'utf8'); + existingConfig = JSON.parse(content); + console.log(`Found existing configuration at: ${configPath}`); + } catch (error) { + console.log(`Could not parse existing config, will create new one`); + } + } + + // Merge configurations + const finalConfig = mergeConfig(existingConfig, newConfig); + + // Ensure directory exists + const configDir = path.dirname(configPath); + if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir, { recursive: true }); + console.log(`Created directory: ${configDir}`); + } + + // Write configuration + fs.writeFileSync(configPath, JSON.stringify(finalConfig, null, 2), 'utf8'); + console.log(`✅ Configuration written to: ${configPath}`); + console.log(` Client: ${client.name}`); + console.log(` Server: ${serverPath}`); + + if (httpPort) { + console.log(` Transport: HTTP (port ${httpPort})`); + } else { + console.log(` Transport: stdio`); + } + + return true; +} + +/** + * Print CLI commands + */ +function printCliCommands(serverPath) { + console.log('\n📋 CLI Configuration Commands:\n'); + console.log('Copy and run these commands in your terminal:\n'); + + for (const [id, cli] of Object.entries(CLI_COMMANDS)) { + console.log(`# ${cli.name}`); + console.log(cli.command(serverPath, 'user')); + console.log(''); + } +} + +/** + * Print all available clients + */ +function printAvailableClients() { + console.log('\n📦 Available IDE Clients:\n'); + for (const [id, client] of Object.entries(CLIENTS)) { + const configPath = client.configPaths[process.platform]; + const exists = configPath && fs.existsSync(configPath); + const status = exists ? '✅ (config exists)' : '❌ (not configured)'; + console.log(` ${id.padEnd(15)} - ${client.name} ${status}`); + } + + console.log('\n📡 Available CLI Tools:\n'); + for (const [id, cli] of Object.entries(CLI_COMMANDS)) { + console.log(` ${id.padEnd(15)} - ${cli.name}`); + } +} + +/** + * Main function + */ +function main() { + const args = process.argv.slice(2); + const serverPath = getServerPath(); + + // Check if server build exists + if (!fs.existsSync(serverPath)) { + console.error('❌ Server build not found. Please run "npm run build" first.'); + process.exit(1); + } + + console.log('🔧 Godot MCP Configuration Wizard\n'); + console.log(`Server path: ${serverPath}`); + console.log(`Platform: ${process.platform}\n`); + + // Parse arguments + let targetClient = null; + let httpPort = null; + let showCli = false; + let listMode = false; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg === '--client' || arg === '-c') { + targetClient = args[++i]; + } else if (arg === '--http' || arg === '-h') { + httpPort = parseInt(args[++i]) || 3000; + } else if (arg === '--cli') { + showCli = true; + } else if (arg === '--list' || arg === '-l') { + listMode = true; + } else if (arg === '--help') { + console.log(` +Usage: node configure.js [options] + +Options: + --client, -c Configure a specific IDE client + --http, -h Use HTTP transport (default port: 3000) + --cli Show CLI configuration commands + --list, -l List all available clients + --help Show this help message + +Examples: + node configure.js --client cursor + node configure.js --client trae-cn --http 3000 + node configure.js --cli + node configure.js --list +`); + process.exit(0); + } + } + + // List mode + if (listMode) { + printAvailableClients(); + process.exit(0); + } + + // Show CLI commands + if (showCli) { + printCliCommands(serverPath); + process.exit(0); + } + + // Configure specific client + if (targetClient) { + configureClient(targetClient, serverPath, httpPort); + process.exit(0); + } + + // Interactive mode - configure all common clients + console.log('Configuring common IDE clients...\n'); + + const commonClients = ['trae-cn', 'cursor', 'claude-desktop']; + for (const clientId of commonClients) { + console.log(`\n--- Configuring ${CLIENTS[clientId]?.name || clientId} ---`); + configureClient(clientId, serverPath, httpPort); + } + + // Also show CLI commands + printCliCommands(serverPath); + + console.log('\n✨ Configuration complete! Restart your IDE to apply changes.\n'); +} + +main(); diff --git a/src/i18n.ts b/src/i18n.ts new file mode 100644 index 000000000..838b8d793 --- /dev/null +++ b/src/i18n.ts @@ -0,0 +1,485 @@ +/** + * Internationalization (i18n) module for Godot MCP Server + * Supports multiple languages for error messages and UI text + */ + +/** + * Supported languages + */ +export type Language = 'en' | 'zh-CN' | 'zh-TW' | 'ja' | 'ko' | 'ru' | 'fr' | 'de' | 'es' | 'pt'; + +/** + * Message categories + */ +export type MessageCategory = 'errors' | 'warnings' | 'info' | 'tools' | 'descriptions'; + +/** + * Translation messages structure + */ +interface Messages { + errors: Record; + warnings: Record; + info: Record; + tools: Record; + descriptions: Record; +} + +/** + * English messages (default) + */ +const enMessages: Messages = { + errors: { + project_path_required: 'Project path is required', + invalid_project_path: 'Invalid project path', + not_godot_project: 'Not a valid Godot project: {path}', + godot_not_found: 'Could not find a valid Godot executable path', + no_active_processes: 'No active Godot processes', + instance_not_found: 'No process found with instanceId: {id}', + instance_not_running: 'Instance {id} is not running', + instance_already_running: 'Instance ID "{id}" is already in use by a running process', + screenshot_not_enabled: 'Instance {id} does not have screenshot support enabled', + screenshot_timeout: 'Screenshot capture timed out', + scene_not_found: 'Scene not found: {path}', + node_not_found: 'Node not found: {path}', + script_not_found: 'Script not found: {path}', + resource_not_found: 'Resource not found: {path}', + invalid_path: 'Invalid path: {path}', + file_write_error: 'Failed to write file: {path}', + file_read_error: 'Failed to read file: {path}', + operation_failed: 'Operation failed: {operation}', + signal_not_found: 'Signal not found: {signal}', + group_not_found: 'Group not found: {group}', + }, + warnings: { + process_cleanup: 'Cleaning up exited process {id} to allow ID reuse', + screenshot_disabled: 'Could not determine main scene, screenshot disabled for instance {id}', + deprecated_tool: 'Tool {tool} is deprecated, use {replacement} instead', + }, + info: { + server_started: 'Godot MCP Server started', + process_started: 'Godot project started with instanceId: {id}', + process_stopped: 'Godot project instance "{id}" stopped', + screenshot_captured: 'Screenshot captured for instance {id}', + scene_created: 'Scene created: {path}', + node_added: 'Node added: {name}', + signal_connected: 'Signal {signal} connected', + group_added: 'Node added to group: {group}', + }, + tools: { + launch_editor: 'Launch Godot editor for a specific project', + run_project: 'Run the Godot project and capture output', + get_debug_output: 'Get the current debug output and errors', + stop_project: 'Stop running Godot project instance(s)', + capture_screenshot: 'Capture a screenshot from a running Godot project instance', + get_godot_version: 'Get the installed Godot version', + list_projects: 'List Godot projects in a directory', + get_project_info: 'Retrieve metadata about a Godot project', + create_scene: 'Create a new scene', + add_node: 'Add a new node to a scene', + load_sprite: 'Load a sprite into a Sprite2D node', + save_scene: 'Save a scene', + signal_connect: 'Connect a signal from one node to another', + signal_disconnect: 'Disconnect a signal connection', + group_add: 'Add a node to a group', + group_remove: 'Remove a node from a group', + list_groups: 'List all groups in a scene', + }, + descriptions: { + projectPath: 'Path to the Godot project directory', + scene: 'Optional: Specific scene to run', + instanceId: 'Optional: Unique identifier for this instance', + args: 'Optional: Additional command-line arguments', + enableScreenshot: 'Optional: Enable screenshot capture for this instance', + outputPath: 'Optional: Path to save the output', + nodeName: 'Name of the node', + nodeType: 'Type of the node to create', + parentNodePath: 'Path to the parent node', + signalName: 'Name of the signal to connect', + sourceNode: 'Path to the source node (emitter)', + targetNode: 'Path to the target node (receiver)', + methodName: 'Name of the method to call when signal is emitted', + groupName: 'Name of the group', + }, +}; + +/** + * Simplified Chinese messages + */ +const zhCNMessages: Messages = { + errors: { + project_path_required: '项目路径是必需的', + invalid_project_path: '无效的项目路径', + not_godot_project: '不是有效的 Godot 项目: {path}', + godot_not_found: '找不到有效的 Godot 可执行文件路径', + no_active_processes: '没有活动的 Godot 进程', + instance_not_found: '找不到实例 ID: {id}', + instance_not_running: '实例 {id} 未运行', + instance_already_running: '实例 ID "{id}" 已被运行中的进程使用', + screenshot_not_enabled: '实例 {id} 未启用截图支持', + screenshot_timeout: '截图捕获超时', + scene_not_found: '找不到场景: {path}', + node_not_found: '找不到节点: {path}', + script_not_found: '找不到脚本: {path}', + resource_not_found: '找不到资源: {path}', + invalid_path: '无效路径: {path}', + file_write_error: '写入文件失败: {path}', + file_read_error: '读取文件失败: {path}', + operation_failed: '操作失败: {operation}', + signal_not_found: '找不到信号: {signal}', + group_not_found: '找不到分组: {group}', + }, + warnings: { + process_cleanup: '正在清理已退出的进程 {id} 以允许 ID 重用', + screenshot_disabled: '无法确定主场景,实例 {id} 的截图功能已禁用', + deprecated_tool: '工具 {tool} 已弃用,请使用 {replacement}', + }, + info: { + server_started: 'Godot MCP 服务器已启动', + process_started: 'Godot 项目已启动,实例 ID: {id}', + process_stopped: 'Godot 项目实例 "{id}" 已停止', + screenshot_captured: '实例 {id} 截图已捕获', + scene_created: '场景已创建: {path}', + node_added: '节点已添加: {name}', + signal_connected: '信号 {signal} 已连接', + group_added: '节点已添加到分组: {group}', + }, + tools: { + launch_editor: '启动指定项目的 Godot 编辑器', + run_project: '运行 Godot 项目并捕获输出', + get_debug_output: '获取当前的调试输出和错误', + stop_project: '停止运行中的 Godot 项目实例', + capture_screenshot: '从运行中的 Godot 项目实例捕获截图', + get_godot_version: '获取已安装的 Godot 版本', + list_projects: '列出目录中的 Godot 项目', + get_project_info: '获取 Godot 项目的元数据', + create_scene: '创建新场景', + add_node: '向场景添加新节点', + load_sprite: '加载精灵到 Sprite2D 节点', + save_scene: '保存场景', + signal_connect: '将信号从一个节点连接到另一个节点', + signal_disconnect: '断开信号连接', + group_add: '将节点添加到分组', + group_remove: '从分组中移除节点', + list_groups: '列出场景中的所有分组', + }, + descriptions: { + projectPath: 'Godot 项目目录的路径', + scene: '可选:要运行的特定场景', + instanceId: '可选:此实例的唯一标识符', + args: '可选:额外的命令行参数', + enableScreenshot: '可选:为此实例启用截图捕获', + outputPath: '可选:保存输出的路径', + nodeName: '节点名称', + nodeType: '要创建的节点类型', + parentNodePath: '父节点的路径', + signalName: '要连接的信号名称', + sourceNode: '源节点(发射器)的路径', + targetNode: '目标节点(接收器)的路径', + methodName: '信号发射时要调用的方法名称', + groupName: '分组名称', + }, +}; + +/** + * Traditional Chinese messages + */ +const zhTWMessages: Messages = { + errors: { + project_path_required: '專案路徑是必需的', + invalid_project_path: '無效的專案路徑', + not_godot_project: '不是有效的 Godot 專案: {path}', + godot_not_found: '找不到有效的 Godot 可執行檔路徑', + no_active_processes: '沒有活動的 Godot 进程', + instance_not_found: '找不到實例 ID: {id}', + instance_not_running: '實例 {id} 未運行', + instance_already_running: '實例 ID "{id}" 已被運行中的进程使用', + screenshot_not_enabled: '實例 {id} 未啟用截圖支援', + screenshot_timeout: '截圖捕獲超時', + scene_not_found: '找不到場景: {path}', + node_not_found: '找不到節點: {path}', + script_not_found: '找不到腳本: {path}', + resource_not_found: '找不到資源: {path}', + invalid_path: '無效路徑: {path}', + file_write_error: '寫入檔案失敗: {path}', + file_read_error: '讀取檔案失敗: {path}', + operation_failed: '操作失敗: {operation}', + signal_not_found: '找不到訊號: {signal}', + group_not_found: '找不到群組: {group}', + }, + warnings: { + process_cleanup: '正在清理已退出的进程 {id} 以允許 ID 重用', + screenshot_disabled: '無法確定主場景,實例 {id} 的截圖功能已禁用', + deprecated_tool: '工具 {tool} 已棄用,請使用 {replacement}', + }, + info: { + server_started: 'Godot MCP 伺服器已啟動', + process_started: 'Godot 專案已啟動,實例 ID: {id}', + process_stopped: 'Godot 專案實例 "{id}" 已停止', + screenshot_captured: '實例 {id} 截圖已捕獲', + scene_created: '場景已創建: {path}', + node_added: '節點已添加: {name}', + signal_connected: '訊號 {signal} 已連接', + group_added: '節點已添加到群組: {group}', + }, + tools: { + launch_editor: '啟動指定專案的 Godot 編輯器', + run_project: '運行 Godot 專案並捕獲輸出', + get_debug_output: '獲取當前的除錯輸出和錯誤', + stop_project: '停止運行中的 Godot 專案實例', + capture_screenshot: '從運行中的 Godot 專案實例捕獲截圖', + get_godot_version: '獲取已安裝的 Godot 版本', + list_projects: '列出目錄中的 Godot 專案', + get_project_info: '獲取 Godot 專案的元數據', + create_scene: '創建新場景', + add_node: '向場景添加新節點', + load_sprite: '載入精靈到 Sprite2D 節點', + save_scene: '儲存場景', + signal_connect: '將訊號從一個節點連接到另一個節點', + signal_disconnect: '斷開訊號連接', + group_add: '將節點添加到群組', + group_remove: '從群組中移除節點', + list_groups: '列出場景中的所有群組', + }, + descriptions: { + projectPath: 'Godot 專案目錄的路徑', + scene: '可選:要運行的特定場景', + instanceId: '可選:此實例的唯一識別碼', + args: '可選:額外的命令列參數', + enableScreenshot: '可選:為此實例啟用截圖捕獲', + outputPath: '可選:儲存輸出的路徑', + nodeName: '節點名稱', + nodeType: '要創建的節點類型', + parentNodePath: '父節點的路徑', + signalName: '要連接的訊號名稱', + sourceNode: '源節點(發射器)的路徑', + targetNode: '目標節點(接收器)的路徑', + methodName: '訊號發射時要調用的方法名稱', + groupName: '群組名稱', + }, +}; + +/** + * Japanese messages + */ +const jaMessages: Messages = { + errors: { + project_path_required: 'プロジェクトパスが必要です', + invalid_project_path: '無効なプロジェクトパス', + not_godot_project: '有効なGodotプロジェクトではありません: {path}', + godot_not_found: '有効なGodot実行ファイルパスが見つかりません', + no_active_processes: 'アクティブなGodotプロセスがありません', + instance_not_found: 'インスタンスIDが見つかりません: {id}', + instance_not_running: 'インスタンス {id} は実行されていません', + instance_already_running: 'インスタンスID "{id}" は既に実行中のプロセスで使用されています', + screenshot_not_enabled: 'インスタンス {id} はスクリーンショットサポートが有効になっていません', + screenshot_timeout: 'スクリーンショットのキャプチャがタイムアウトしました', + scene_not_found: 'シーンが見つかりません: {path}', + node_not_found: 'ノードが見つかりません: {path}', + script_not_found: 'スクリプトが見つかりません: {path}', + resource_not_found: 'リソースが見つかりません: {path}', + invalid_path: '無効なパス: {path}', + file_write_error: 'ファイルの書き込みに失敗しました: {path}', + file_read_error: 'ファイルの読み込みに失敗しました: {path}', + operation_failed: '操作に失敗しました: {operation}', + signal_not_found: 'シグナルが見つかりません: {signal}', + group_not_found: 'グループが見つかりません: {group}', + }, + warnings: { + process_cleanup: '終了したプロセス {id} をクリーンアップしてIDの再利用を許可しています', + screenshot_disabled: 'メインシーンを特定できません。インスタンス {id} のスクリーンショットは無効です', + deprecated_tool: 'ツール {tool} は非推奨です。{replacement} を使用してください', + }, + info: { + server_started: 'Godot MCPサーバーが起動しました', + process_started: 'Godotプロジェクトが起動しました。インスタンスID: {id}', + process_stopped: 'Godotプロジェクトインスタンス "{id}" が停止しました', + screenshot_captured: 'インスタンス {id} のスクリーンショットをキャプチャしました', + scene_created: 'シーンを作成しました: {path}', + node_added: 'ノードを追加しました: {name}', + signal_connected: 'シグナル {signal} を接続しました', + group_added: 'ノードをグループに追加しました: {group}', + }, + tools: { + launch_editor: '特定のプロジェクトのGodotエディタを起動', + run_project: 'Godotプロジェクトを実行して出力をキャプチャ', + get_debug_output: '現在のデバッグ出力とエラーを取得', + stop_project: '実行中のGodotプロジェクトインスタンスを停止', + capture_screenshot: '実行中のGodotプロジェクトインスタンスからスクリーンショットをキャプチャ', + get_godot_version: 'インストールされているGodotバージョンを取得', + list_projects: 'ディレクトリ内のGodotプロジェクトを一覧表示', + get_project_info: 'Godotプロジェクトのメタデータを取得', + create_scene: '新しいシーンを作成', + add_node: 'シーンに新しいノードを追加', + load_sprite: 'Sprite2Dノードにスプライトをロード', + save_scene: 'シーンを保存', + signal_connect: 'シグナルをあるノードから別のノードに接続', + signal_disconnect: 'シグナル接続を切断', + group_add: 'ノードをグループに追加', + group_remove: 'グループからノードを削除', + list_groups: 'シーン内のすべてのグループを一覧表示', + }, + descriptions: { + projectPath: 'Godotプロジェクトディレクトリへのパス', + scene: 'オプション:実行する特定のシーン', + instanceId: 'オプション:このインスタンスの一意の識別子', + args: 'オプション:追加のコマンドライン引数', + enableScreenshot: 'オプション:このインスタンスのスクリーンショットキャプチャを有効にする', + outputPath: 'オプション:出力を保存するパス', + nodeName: 'ノード名', + nodeType: '作成するノードのタイプ', + parentNodePath: '親ノードへのパス', + signalName: '接続するシグナル名', + sourceNode: 'ソースノード(エミッター)へのパス', + targetNode: 'ターゲットノード(レシーバー)へのパス', + methodName: 'シグナル発信時に呼び出すメソッド名', + groupName: 'グループ名', + }, +}; + +/** + * All translations + */ +const translations: Record = { + 'en': enMessages, + 'zh-CN': zhCNMessages, + 'zh-TW': zhTWMessages, + 'ja': jaMessages, + 'ko': enMessages, // Fallback to English + 'ru': enMessages, // Fallback to English + 'fr': enMessages, // Fallback to English + 'de': enMessages, // Fallback to English + 'es': enMessages, // Fallback to English + 'pt': enMessages, // Fallback to English +}; + +/** + * I18n class for handling translations + */ +export class I18n { + private language: Language; + private messages: Messages; + + constructor(language: Language = 'en') { + this.language = language; + this.messages = translations[language] || translations['en']; + } + + /** + * Set the current language + */ + setLanguage(language: Language): void { + this.language = language; + this.messages = translations[language] || translations['en']; + } + + /** + * Get the current language + */ + getLanguage(): Language { + return this.language; + } + + /** + * Get a message from a category with parameter substitution + */ + private getMessage(category: MessageCategory, key: string, params?: Record): string { + const messages = this.messages[category] as Record; + let message = messages[key] || translations['en'][category][key] || key; + + if (params) { + for (const [param, value] of Object.entries(params)) { + message = message.replace(new RegExp(`\\{${param}\\}`, 'g'), value); + } + } + + return message; + } + + /** + * Get an error message + */ + error(key: string, params?: Record): string { + return this.getMessage('errors', key, params); + } + + /** + * Get a warning message + */ + warning(key: string, params?: Record): string { + return this.getMessage('warnings', key, params); + } + + /** + * Get an info message + */ + info(key: string, params?: Record): string { + return this.getMessage('info', key, params); + } + + /** + * Get a tool name/description + */ + tool(key: string): string { + return this.getMessage('tools', key); + } + + /** + * Get a parameter description + */ + description(key: string): string { + return this.getMessage('descriptions', key); + } + + /** + * Detect system language + */ + static detectSystemLanguage(): Language { + const envLang = process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES || ''; + const locale = envLang.split('.')[0].replace('_', '-'); + + // Map common locale codes + const localeMap: Record = { + 'en': 'en', + 'en-US': 'en', + 'en-GB': 'en', + 'zh': 'zh-CN', + 'zh-CN': 'zh-CN', + 'zh-Hans': 'zh-CN', + 'zh-Hans-CN': 'zh-CN', + 'zh-TW': 'zh-TW', + 'zh-Hant': 'zh-TW', + 'zh-Hant-TW': 'zh-TW', + 'ja': 'ja', + 'ja-JP': 'ja', + 'ko': 'ko', + 'ko-KR': 'ko', + 'ru': 'ru', + 'ru-RU': 'ru', + 'fr': 'fr', + 'fr-FR': 'fr', + 'de': 'de', + 'de-DE': 'de', + 'es': 'es', + 'es-ES': 'es', + 'pt': 'pt', + 'pt-BR': 'pt', + }; + + return localeMap[locale] || 'en'; + } +} + +/** + * Global i18n instance + */ +export const i18n = new I18n(I18n.detectSystemLanguage()); + +/** + * Initialize i18n with environment variable + */ +export function initI18n(): void { + const envLanguage = process.env.MCP_LANGUAGE as Language; + if (envLanguage && translations[envLanguage]) { + i18n.setLanguage(envLanguage); + } +} diff --git a/src/index.ts b/src/index.ts index 2ab5a6bac..f6077e45e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -36,11 +36,77 @@ const __dirname = dirname(__filename); * Interface representing a running Godot process */ interface GodotProcess { + id: string; process: any; output: string[]; errors: string[]; + projectPath: string; + scene?: string; + args?: string[]; + startedAt: Date; + isRunning: boolean; + exitedAt?: Date; + screenshotEnabled?: boolean; } +/** + * Interface for instance configuration in batch operations + */ +interface InstanceConfig { + projectPath: string; + instanceId?: string; + scene?: string; + args?: string[]; + delayMs?: number; +} + +/** + * Configuration for output buffer limits + */ +const OUTPUT_BUFFER_LIMIT = 1000; // Max lines to keep per stream +const STALE_PROCESS_CLEANUP_MS = 10 * 60 * 1000; // 10 minutes + +/** + * Screenshot wrapper script for capturing viewport + */ +const SCREENSHOT_WRAPPER_SCRIPT = `extends Node + +const REAL_MAIN_SCENE = "REPLACE_WITH_MAIN_SCENE_PATH" +const TRIGGER_FILE = "res://.mcp_screenshot_req" +const OUTPUT_FILE = "res://.mcp_screenshot.png" + +func _ready(): + print("[MCP] Wrapper loaded. Starting main scene: ", REAL_MAIN_SCENE) + if ResourceLoader.exists(REAL_MAIN_SCENE): + var main_packed = load(REAL_MAIN_SCENE) + var main_inst = main_packed.instantiate() + add_child.call_deferred(main_inst) + else: + printerr("[MCP] Error: Could not find main scene at ", REAL_MAIN_SCENE) + +func _process(_delta): + if FileAccess.file_exists(TRIGGER_FILE): + _take_screenshot() + DirAccess.remove_absolute(TRIGGER_FILE) + +func _take_screenshot(): + print("[MCP] Capturing screenshot...") + var image = get_viewport().get_texture().get_image() + var error = image.save_png(OUTPUT_FILE) + if error == OK: + print("[MCP] Screenshot saved to ", OUTPUT_FILE) + else: + printerr("[MCP] Failed to save screenshot. Error code: ", error) +`; + +const SCREENSHOT_WRAPPER_SCENE = `[gd_scene load_steps=2 format=3] + +[ext_resource type="Script" path="res://.mcp_wrapper.gd" id="1_mcp"] + +[node name="MCPWrapper" type="Node"] +script = ExtResource("1_mcp") +`; + /** * Interface for server configuration */ @@ -63,11 +129,13 @@ interface OperationParams { */ class GodotServer { private server: Server; - private activeProcess: GodotProcess | null = null; + private activeProcesses: Map = new Map(); private godotPath: string | null = null; private operationsScriptPath: string; private validatedPaths: Map = new Map(); private strictPathValidation: boolean = false; + private nextInstanceId: number = 1; + private cleanupInterval: NodeJS.Timeout | null = null; /** * Parameter name mappings between snake_case and camelCase @@ -154,6 +222,9 @@ class GodotServer { // Error handling this.server.onerror = (error) => console.error('[MCP Error]', error); + // Start periodic cleanup of stale exited processes + this.startStaleProcessCleanup(); + // Cleanup on exit process.on('SIGINT', async () => { await this.cleanup(); @@ -161,9 +232,52 @@ class GodotServer { }); } + /** + * Start periodic cleanup of stale exited processes + */ + private startStaleProcessCleanup(): void { + // Run cleanup every minute + this.cleanupInterval = setInterval(() => { + this.cleanupStaleProcesses(); + }, 60 * 1000); + } + + /** + * Clean up processes that have exited and been stale for too long + */ + private cleanupStaleProcesses(): void { + const now = Date.now(); + const toDelete: string[] = []; + + for (const [id, process] of this.activeProcesses.entries()) { + if (!process.isRunning && process.exitedAt) { + const staleDuration = now - process.exitedAt.getTime(); + if (staleDuration > STALE_PROCESS_CLEANUP_MS) { + this.logDebug(`Cleaning up stale process: ${id} (exited ${Math.round(staleDuration / 1000)}s ago)`); + toDelete.push(id); + } + } + } + + for (const id of toDelete) { + this.activeProcesses.delete(id); + } + } + + /** + * Add a line to a bounded buffer, removing oldest entries if limit exceeded + */ + private addToBoundedBuffer(buffer: string[], line: string): void { + buffer.push(line); + // Remove oldest entries if we exceed the limit + while (buffer.length > OUTPUT_BUFFER_LIMIT) { + buffer.shift(); + } + } + /** * Log debug messages if debug mode is enabled - * Using stderr instead of stdout to avoid interfering with JSON-RPC communication + * Note: Uses console.error to ensure it goes to stderr, not stdout (which is used for MCP protocol) */ private logDebug(message: string): void { if (DEBUG_MODE) { @@ -383,14 +497,96 @@ class GodotServer { */ private async cleanup() { this.logDebug('Cleaning up resources'); - if (this.activeProcess) { - this.logDebug('Killing active Godot process'); - this.activeProcess.process.kill(); - this.activeProcess = null; + + // Clear the cleanup interval + if (this.cleanupInterval) { + clearInterval(this.cleanupInterval); + this.cleanupInterval = null; + } + + for (const [id, godotProcess] of this.activeProcesses.entries()) { + this.logDebug(`Killing Godot process: ${id}`); + try { + this.cleanupScreenshotArtifacts(godotProcess.projectPath); + godotProcess.process.kill(); + } catch (error) { + this.logDebug(`Error killing process ${id}: ${error}`); + } } + this.activeProcesses.clear(); await this.server.close(); } + /** + * Setup screenshot wrapper files for a project + * @param projectPath Path to the Godot project + * @param mainScenePath Path to the main scene (relative to project) + */ + private setupScreenshotWrapper(projectPath: string, mainScenePath: string): void { + const fs = require('fs'); + const wrapperScriptPath = join(projectPath, '.mcp_wrapper.gd'); + const wrapperScenePath = join(projectPath, '.mcp_wrapper.tscn'); + + // Create wrapper script with the main scene path + const scriptContent = SCREENSHOT_WRAPPER_SCRIPT.replace( + 'REPLACE_WITH_MAIN_SCENE_PATH', + mainScenePath + ); + + fs.writeFileSync(wrapperScriptPath, scriptContent, 'utf8'); + fs.writeFileSync(wrapperScenePath, SCREENSHOT_WRAPPER_SCENE, 'utf8'); + + this.logDebug(`Created screenshot wrapper files in ${projectPath}`); + } + + /** + * Clean up screenshot wrapper files from a project + * @param projectPath Path to the Godot project + */ + private cleanupScreenshotArtifacts(projectPath: string): void { + const fs = require('fs'); + const filesToRemove = [ + '.mcp_wrapper.gd', + '.mcp_wrapper.tscn', + '.mcp_screenshot_req', + '.mcp_screenshot.png' + ]; + + for (const file of filesToRemove) { + const filePath = join(projectPath, file); + try { + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + this.logDebug(`Removed screenshot artifact: ${file}`); + } + } catch (error) { + this.logDebug(`Failed to remove ${file}: ${error}`); + } + } + } + + /** + * Get the main scene path from project.godot + * @param projectPath Path to the Godot project + * @returns The main scene path or null if not found + */ + private getMainScenePath(projectPath: string): string | null { + const fs = require('fs'); + const projectFile = join(projectPath, 'project.godot'); + + try { + const content = fs.readFileSync(projectFile, 'utf8'); + const match = content.match(/run\/main_scene="([^"]+)"/); + if (match && match[1]) { + return match[1]; + } + } catch (error) { + this.logDebug(`Failed to read project.godot: ${error}`); + } + + return null; + } + /** * Check if the Godot version is 4.4 or later * @param version The Godot version string @@ -685,6 +881,21 @@ class GodotServer { type: 'string', description: 'Optional: Specific scene to run', }, + instanceId: { + type: 'string', + description: 'Optional: Unique identifier for this instance (e.g., "server", "client1", "client2"). If not provided, an auto-generated ID will be used.', + }, + args: { + type: 'array', + items: { + type: 'string', + }, + description: 'Optional: Additional command-line arguments to pass to Godot (e.g., ["--server"] or ["--", "profile=player1"])', + }, + enableScreenshot: { + type: 'boolean', + description: 'Optional: Enable screenshot capture for this instance. When true, a wrapper scene will be used that allows capturing screenshots via capture_screenshot tool.', + }, }, required: ['projectPath'], }, @@ -694,19 +905,70 @@ class GodotServer { description: 'Get the current debug output and errors', inputSchema: { type: 'object', - properties: {}, + properties: { + instanceId: { + type: 'string', + description: 'Optional: Instance ID to get output for. If not provided, returns output for all instances.', + }, + }, required: [], }, }, { name: 'stop_project', - description: 'Stop the currently running Godot project', + description: 'Stop running Godot project instance(s)', inputSchema: { type: 'object', - properties: {}, + properties: { + instanceId: { + type: 'string', + description: 'Optional: Single instance ID to stop.', + }, + instanceIds: { + type: 'array', + items: { type: 'string' }, + description: 'Optional: Array of instance IDs to stop. Use this to stop multiple specific instances in one call.', + }, + }, required: [], }, }, + { + name: 'capture_screenshot', + description: 'Capture a screenshot from a running Godot project instance. The instance must have been started with enableScreenshot=true.', + inputSchema: { + type: 'object', + properties: { + instanceId: { + type: 'string', + description: 'Instance ID of the running Godot project to capture screenshot from.', + }, + outputPath: { + type: 'string', + description: 'Optional: Path to save the screenshot. If not provided, returns base64 encoded image.', + }, + }, + required: ['instanceId'], + }, + }, + { + name: 'capture_screenshot', + description: 'Capture a screenshot from a running Godot project instance. The instance must have been started with enableScreenshot=true.', + inputSchema: { + type: 'object', + properties: { + instanceId: { + type: 'string', + description: 'Instance ID of the running Godot project to capture screenshot from.', + }, + outputPath: { + type: 'string', + description: 'Optional: Path to save the screenshot. If not provided, returns base64 encoded image.', + }, + }, + required: ['instanceId'], + }, + }, { name: 'get_godot_version', description: 'Get the installed Godot version', @@ -913,284 +1175,1466 @@ class GodotServer { required: ['projectPath'], }, }, - ], - })); - - // Handle tool calls - this.server.setRequestHandler(CallToolRequestSchema, async (request) => { - this.logDebug(`Handling tool request: ${request.params.name}`); - switch (request.params.name) { - case 'launch_editor': - return await this.handleLaunchEditor(request.params.arguments); - case 'run_project': - return await this.handleRunProject(request.params.arguments); - case 'get_debug_output': - return await this.handleGetDebugOutput(); - case 'stop_project': - return await this.handleStopProject(); - case 'get_godot_version': - return await this.handleGetGodotVersion(); - case 'list_projects': - return await this.handleListProjects(request.params.arguments); - case 'get_project_info': - return await this.handleGetProjectInfo(request.params.arguments); - case 'create_scene': - return await this.handleCreateScene(request.params.arguments); - case 'add_node': - return await this.handleAddNode(request.params.arguments); - case 'load_sprite': - return await this.handleLoadSprite(request.params.arguments); - case 'export_mesh_library': - return await this.handleExportMeshLibrary(request.params.arguments); - case 'save_scene': - return await this.handleSaveScene(request.params.arguments); - case 'get_uid': - return await this.handleGetUid(request.params.arguments); - case 'update_project_uids': - return await this.handleUpdateProjectUids(request.params.arguments); - default: - throw new McpError( - ErrorCode.MethodNotFound, - `Unknown tool: ${request.params.name}` - ); - } - }); - } - - /** - * Handle the launch_editor tool - * @param args Tool arguments - */ - private async handleLaunchEditor(args: any) { - // Normalize parameters to camelCase - args = this.normalizeParameters(args); - - if (!args.projectPath) { - return this.createErrorResponse( - 'Project path is required', - ['Provide a valid path to a Godot project directory'] - ); + { + name: 'list_processes', + description: 'List all tracked Godot project instances (both running and recently exited)', + inputSchema: { + type: 'object', + properties: {}, + required: [], + }, + }, + { + name: 'signal_connect', + description: 'Connect a signal from one node to another node\'s method in a Godot scene', + inputSchema: { + type: 'object', + properties: { + projectPath: { + type: 'string', + description: 'Path to the Godot project directory', + }, + scenePath: { + type: 'string', + description: 'Path to the scene file (relative to project)', + }, + sourceNodePath: { + type: 'string', + description: 'Path to the source node that emits the signal (e.g., "root/Player/Button")', + }, + signalName: { + type: 'string', + description: 'Name of the signal to connect (e.g., "pressed", "body_entered")', + }, + targetNodePath: { + type: 'string', + description: 'Path to the target node that will receive the signal (e.g., "root/Player")', + }, + methodName: { + type: 'string', + description: 'Name of the method to call when the signal is emitted', + }, + flags: { + type: 'number', + description: 'Optional: Connection flags (default: 0). Common values: 0=DEFERRED, 1=PERSIST, 2=ONE_SHOT', + }, + }, + required: ['projectPath', 'scenePath', 'sourceNodePath', 'signalName', 'targetNodePath', 'methodName'], + }, + }, + { + name: 'signal_disconnect', + description: 'Disconnect a signal connection in a Godot scene', + inputSchema: { + type: 'object', + properties: { + projectPath: { + type: 'string', + description: 'Path to the Godot project directory', + }, + scenePath: { + type: 'string', + description: 'Path to the scene file (relative to project)', + }, + sourceNodePath: { + type: 'string', + description: 'Path to the source node that emits the signal', + }, + signalName: { + type: 'string', + description: 'Name of the signal to disconnect', + }, + targetNodePath: { + type: 'string', + description: 'Path to the target node that receives the signal', + }, + methodName: { + type: 'string', + description: 'Name of the method that was connected to the signal', + }, + }, + required: ['projectPath', 'scenePath', 'sourceNodePath', 'signalName', 'targetNodePath', 'methodName'], + }, + }, + { + name: 'group_add', + description: 'Add a node to a group in a Godot scene', + inputSchema: { + type: 'object', + properties: { + projectPath: { + type: 'string', + description: 'Path to the Godot project directory', + }, + scenePath: { + type: 'string', + description: 'Path to the scene file (relative to project)', + }, + nodePath: { + type: 'string', + description: 'Path to the node to add to the group (e.g., "root/Player")', + }, + groupName: { + type: 'string', + description: 'Name of the group to add the node to', + }, + }, + required: ['projectPath', 'scenePath', 'nodePath', 'groupName'], + }, + }, + { + name: 'group_remove', + description: 'Remove a node from a group in a Godot scene', + inputSchema: { + type: 'object', + properties: { + projectPath: { + type: 'string', + description: 'Path to the Godot project directory', + }, + scenePath: { + type: 'string', + description: 'Path to the scene file (relative to project)', + }, + nodePath: { + type: 'string', + description: 'Path to the node to remove from the group', + }, + groupName: { + type: 'string', + description: 'Name of the group to remove the node from', + }, + }, + required: ['projectPath', 'scenePath', 'nodePath', 'groupName'], + }, + }, + { + name: 'list_groups', + description: 'List all groups in a Godot scene', + inputSchema: { + type: 'object', + properties: { + projectPath: { + type: 'string', + description: 'Path to the Godot project directory', + }, + scenePath: { + type: 'string', + description: 'Path to the scene file (relative to project)', + }, + }, + required: ['projectPath', 'scenePath'], + }, + }, + { + name: 'ui_create_button', + description: 'Create a Button node in a Godot scene with customizable properties', + inputSchema: { + type: 'object', + properties: { + projectPath: { + type: 'string', + description: 'Path to the Godot project directory', + }, + scenePath: { + type: 'string', + description: 'Path to the scene file (relative to project)', + }, + parentNodePath: { + type: 'string', + description: 'Path to the parent node (e.g., "root/MainMenu")', + }, + nodeName: { + type: 'string', + description: 'Name for the new button node', + }, + text: { + type: 'string', + description: 'Text to display on the button', + }, + position: { + type: 'object', + properties: { + x: { type: 'number', description: 'X position' }, + y: { type: 'number', description: 'Y position' }, + }, + description: 'Position of the button (x, y)', + }, + size: { + type: 'object', + properties: { + width: { type: 'number', description: 'Width' }, + height: { type: 'number', description: 'Height' }, + }, + description: 'Size of the button (width, height)', + }, + }, + required: ['projectPath', 'scenePath', 'parentNodePath', 'nodeName', 'text'], + }, + }, + { + name: 'ui_create_label', + description: 'Create a Label node in a Godot scene with customizable properties', + inputSchema: { + type: 'object', + properties: { + projectPath: { + type: 'string', + description: 'Path to the Godot project directory', + }, + scenePath: { + type: 'string', + description: 'Path to the scene file (relative to project)', + }, + parentNodePath: { + type: 'string', + description: 'Path to the parent node (e.g., "root/MainMenu")', + }, + nodeName: { + type: 'string', + description: 'Name for the new label node', + }, + text: { + type: 'string', + description: 'Text to display in the label', + }, + position: { + type: 'object', + properties: { + x: { type: 'number', description: 'X position' }, + y: { type: 'number', description: 'Y position' }, + }, + description: 'Position of the label (x, y)', + }, + fontSize: { + type: 'number', + description: 'Font size for the label text', + }, + }, + required: ['projectPath', 'scenePath', 'parentNodePath', 'nodeName', 'text'], + }, + }, + { + name: 'run_multiple_projects', + description: 'Launch multiple Godot project instances in a single call. Useful for multiplayer testing (server + clients).', + inputSchema: { + type: 'object', + properties: { + instances: { + type: 'array', + items: { + type: 'object', + properties: { + projectPath: { + type: 'string', + description: 'Path to the Godot project directory', + }, + instanceId: { + type: 'string', + description: 'Optional: Unique identifier for this instance (e.g., "server", "client1")', + }, + scene: { + type: 'string', + description: 'Optional: Specific scene to run', + }, + args: { + type: 'array', + items: { type: 'string' }, + description: 'Optional: Command-line arguments for this instance', + }, + delayMs: { + type: 'number', + description: 'Optional: Delay in milliseconds before launching this instance (useful for staggering server/client startup)', + }, + }, + required: ['projectPath'], + }, + description: 'Array of instance configurations to launch', + }, + }, + required: ['instances'], + }, + }, + ], + })); + + // Handle tool calls + this.server.setRequestHandler(CallToolRequestSchema, async (request) => { + this.logDebug(`Handling tool request: ${request.params.name}`); + switch (request.params.name) { + case 'launch_editor': + return await this.handleLaunchEditor(request.params.arguments); + case 'run_project': + return await this.handleRunProject(request.params.arguments); + case 'get_debug_output': + return await this.handleGetDebugOutput(request.params.arguments); + case 'stop_project': + return await this.handleStopProject(request.params.arguments); + case 'capture_screenshot': + return await this.handleCaptureScreenshot(request.params.arguments); + case 'get_godot_version': + return await this.handleGetGodotVersion(); + case 'list_projects': + return await this.handleListProjects(request.params.arguments); + case 'get_project_info': + return await this.handleGetProjectInfo(request.params.arguments); + case 'create_scene': + return await this.handleCreateScene(request.params.arguments); + case 'add_node': + return await this.handleAddNode(request.params.arguments); + case 'load_sprite': + return await this.handleLoadSprite(request.params.arguments); + case 'export_mesh_library': + return await this.handleExportMeshLibrary(request.params.arguments); + case 'save_scene': + return await this.handleSaveScene(request.params.arguments); + case 'get_uid': + return await this.handleGetUid(request.params.arguments); + case 'update_project_uids': + return await this.handleUpdateProjectUids(request.params.arguments); + case 'list_processes': + return await this.handleListProcesses(); + case 'run_multiple_projects': + return await this.handleRunMultipleProjects(request.params.arguments); + case 'signal_connect': + return await this.handleSignalConnect(request.params.arguments); + case 'signal_disconnect': + return await this.handleSignalDisconnect(request.params.arguments); + case 'group_add': + return await this.handleGroupAdd(request.params.arguments); + case 'group_remove': + return await this.handleGroupRemove(request.params.arguments); + case 'list_groups': + return await this.handleListGroups(request.params.arguments); + case 'ui_create_button': + return await this.handleUiCreateButton(request.params.arguments); + case 'ui_create_label': + return await this.handleUiCreateLabel(request.params.arguments); + default: + throw new McpError( + ErrorCode.MethodNotFound, + `Unknown tool: ${request.params.name}` + ); + } + }); + } + + /** + * Handle the launch_editor tool + * @param args Tool arguments + */ + private async handleLaunchEditor(args: any) { + // Normalize parameters to camelCase + args = this.normalizeParameters(args); + + if (!args.projectPath) { + return this.createErrorResponse( + 'Project path is required', + ['Provide a valid path to a Godot project directory'] + ); + } + + if (!this.validatePath(args.projectPath)) { + return this.createErrorResponse( + 'Invalid project path', + ['Provide a valid path without ".." or other potentially unsafe characters'] + ); + } + + try { + // Ensure godotPath is set + if (!this.godotPath) { + await this.detectGodotPath(); + if (!this.godotPath) { + return this.createErrorResponse( + 'Could not find a valid Godot executable path', + [ + 'Ensure Godot is installed correctly', + 'Set GODOT_PATH environment variable to specify the correct path', + ] + ); + } + } + + // Check if the project directory exists and contains a project.godot file + const projectFile = join(args.projectPath, 'project.godot'); + if (!existsSync(projectFile)) { + return this.createErrorResponse( + `Not a valid Godot project: ${args.projectPath}`, + [ + 'Ensure the path points to a directory containing a project.godot file', + 'Use list_projects to find valid Godot projects', + ] + ); + } + + this.logDebug(`Launching Godot editor for project: ${args.projectPath}`); + const process = spawn(this.godotPath, ['-e', '--path', args.projectPath], { + stdio: 'pipe', + }); + + process.on('error', (err: Error) => { + console.error('Failed to start Godot editor:', err); + }); + + return { + content: [ + { + type: 'text', + text: `Godot editor launched successfully for project at ${args.projectPath}.`, + }, + ], + }; + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return this.createErrorResponse( + `Failed to launch Godot editor: ${errorMessage}`, + [ + 'Ensure Godot is installed correctly', + 'Check if the GODOT_PATH environment variable is set correctly', + 'Verify the project path is accessible', + ] + ); + } + } + + /** + * Handle the run_project tool + * @param args Tool arguments + */ + private async handleRunProject(args: any) { + // Normalize parameters to camelCase + args = this.normalizeParameters(args); + + if (!args.projectPath) { + return this.createErrorResponse( + 'Project path is required', + ['Provide a valid path to a Godot project directory'] + ); + } + + if (!this.validatePath(args.projectPath)) { + return this.createErrorResponse( + 'Invalid project path', + ['Provide a valid path without ".." or other potentially unsafe characters'] + ); + } + + try { + // Check if the project directory exists and contains a project.godot file + const projectFile = join(args.projectPath, 'project.godot'); + if (!existsSync(projectFile)) { + return this.createErrorResponse( + `Not a valid Godot project: ${args.projectPath}`, + [ + 'Ensure the path points to a directory containing a project.godot file', + 'Use list_projects to find valid Godot projects', + ] + ); + } + + // Generate or use provided instance ID + let instanceId = args.instanceId; + if (!instanceId) { + instanceId = `instance_${this.nextInstanceId++}`; + } + + // Check if instance ID already exists and is running + const existingProcess = this.activeProcesses.get(instanceId); + if (existingProcess) { + if (existingProcess.isRunning) { + return this.createErrorResponse( + `Instance ID "${instanceId}" is already in use by a running process`, + [ + 'Use a different instanceId', + 'Stop the existing instance first using stop_project', + 'Use list_processes to see all running instances', + ] + ); + } else { + // Clean up exited process to allow reuse of the ID + this.logDebug(`Cleaning up exited process ${instanceId} to allow ID reuse`); + this.activeProcesses.delete(instanceId); + } + } + + const cmdArgs = ['-d', '--path', args.projectPath]; + + // Handle screenshot wrapper setup + let screenshotEnabled = false; + if (args.enableScreenshot === true) { + const mainScene = this.getMainScenePath(args.projectPath); + if (mainScene) { + this.setupScreenshotWrapper(args.projectPath, mainScene); + cmdArgs.push('.mcp_wrapper.tscn'); + screenshotEnabled = true; + this.logDebug(`Screenshot wrapper enabled for instance ${instanceId}`); + } else { + this.logDebug(`Could not determine main scene, screenshot disabled for instance ${instanceId}`); + } + } + + if (args.scene && this.validatePath(args.scene) && !screenshotEnabled) { + this.logDebug(`Adding scene parameter: ${args.scene}`); + cmdArgs.push(args.scene); + } + // Add additional command-line arguments if provided + if (args.args && Array.isArray(args.args)) { + for (const arg of args.args) { + if (typeof arg === 'string' && arg.trim() !== '') { + this.logDebug(`Adding command-line argument: ${arg}`); + cmdArgs.push(arg); + } + } + } + + this.logDebug(`Running Godot project: ${args.projectPath} with instanceId: ${instanceId}`); + const godotProcess = spawn(this.godotPath!, cmdArgs, { stdio: 'pipe' }); + const output: string[] = []; + const errors: string[] = []; + + const processInfo: GodotProcess = { + id: instanceId, + process: godotProcess, + output, + errors, + projectPath: args.projectPath, + scene: args.scene, + args: args.args, + startedAt: new Date(), + isRunning: true, + screenshotEnabled, + }; + + godotProcess.stdout?.on('data', (data: Buffer) => { + const rawLines = data.toString().split('\n'); + for (const line of rawLines) { + // Strip carriage returns and trim whitespace + const cleaned = line.replace(/\r/g, '').trimEnd(); + // Only add non-empty lines to output + if (cleaned.length > 0) { + this.addToBoundedBuffer(processInfo.output, cleaned); + this.logDebug(`[Godot ${instanceId} stdout] ${cleaned}`); + } + } + }); + + godotProcess.stderr?.on('data', (data: Buffer) => { + const rawLines = data.toString().split('\n'); + for (const line of rawLines) { + // Strip carriage returns and trim whitespace + const cleaned = line.replace(/\r/g, '').trimEnd(); + // Only add non-empty lines to errors + if (cleaned.length > 0) { + this.addToBoundedBuffer(processInfo.errors, cleaned); + this.logDebug(`[Godot ${instanceId} stderr] ${cleaned}`); + } + } + }); + + godotProcess.on('exit', (code: number | null) => { + this.logDebug(`Godot process ${instanceId} exited with code ${code}`); + processInfo.isRunning = false; + processInfo.exitedAt = new Date(); + // Keep the process info after exit so we can retrieve output + // Stale processes will be cleaned up by the periodic cleanup task + }); + + godotProcess.on('error', (err: Error) => { + console.error(`Failed to start Godot process ${instanceId}:`, err); + processInfo.errors.push(`Process error: ${err.message}`); + processInfo.isRunning = false; + processInfo.exitedAt = new Date(); + }); + + this.activeProcesses.set(instanceId, processInfo); + + return { + content: [ + { + type: 'text', + text: `Godot project started in debug mode with instanceId: ${instanceId}. Use get_debug_output to see output.`, + }, + ], + }; + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return this.createErrorResponse( + `Failed to run Godot project: ${errorMessage}`, + [ + 'Ensure Godot is installed correctly', + 'Check if the GODOT_PATH environment variable is set correctly', + 'Verify the project path is accessible', + ] + ); + } + } + + /** + * Handle the get_debug_output tool + */ + private async handleGetDebugOutput(args: any) { + // Normalize parameters to camelCase + args = this.normalizeParameters(args || {}); + + if (this.activeProcesses.size === 0) { + return this.createErrorResponse( + 'No active Godot processes.', + [ + 'Use run_project to start a Godot project first', + 'Check if the Godot processes crashed unexpectedly', + ] + ); + } + + // If instanceId is provided, return output for that specific instance + if (args.instanceId) { + const process = this.activeProcesses.get(args.instanceId); + if (!process) { + return this.createErrorResponse( + `No process found with instanceId: ${args.instanceId}`, + [ + 'Use list_processes to see all tracked instances', + 'The instance may have been cleaned up after being exited for too long', + ] + ); + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + instanceId: process.id, + projectPath: process.projectPath, + scene: process.scene, + startedAt: process.startedAt.toISOString(), + isRunning: process.isRunning, + output: process.output, + errors: process.errors, + }, + null, + 2 + ), + }, + ], + }; + } + + // Return output for all instances + const allOutputs: any[] = []; + for (const [id, process] of this.activeProcesses.entries()) { + allOutputs.push({ + instanceId: process.id, + projectPath: process.projectPath, + scene: process.scene, + startedAt: process.startedAt.toISOString(), + isRunning: process.isRunning, + output: process.output, + errors: process.errors, + }); + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify(allOutputs, null, 2), + }, + ], + }; + } + + /** + * Handle the stop_project tool + */ + private async handleStopProject(args: any) { + // Normalize parameters to camelCase + args = this.normalizeParameters(args || {}); + + if (this.activeProcesses.size === 0) { + return this.createErrorResponse( + 'No active Godot processes to stop.', + [ + 'Use run_project to start a Godot project first', + 'The processes may have already terminated', + ] + ); + } + + // Helper function to stop a single instance + const stopInstance = (instanceId: string): { success: boolean; result?: any; error?: string } => { + const process = this.activeProcesses.get(instanceId); + if (!process) { + return { success: false, error: `No process found with instanceId: ${instanceId}` }; + } + + this.logDebug(`Stopping Godot process: ${instanceId}`); + try { + // Only attempt to kill if the process is still running + if (process.isRunning) { + process.process.kill(); + } + const result = { + instanceId: process.id, + projectPath: process.projectPath, + wasRunning: process.isRunning, + finalOutput: process.output, + finalErrors: process.errors, + }; + this.activeProcesses.delete(instanceId); + return { success: true, result }; + } catch (error) { + this.logDebug(`Error stopping process ${instanceId}: ${error}`); + return { success: false, error: `Failed to stop ${instanceId}: ${error}` }; + } + }; + + // If instanceIds array is provided, stop those specific instances + if (args.instanceIds && Array.isArray(args.instanceIds) && args.instanceIds.length > 0) { + this.logDebug(`Stopping multiple Godot processes: ${args.instanceIds.join(', ')}`); + const stoppedInstances: any[] = []; + const errors: string[] = []; + + for (const instanceId of args.instanceIds) { + const result = stopInstance(instanceId); + if (result.success && result.result) { + stoppedInstances.push(result.result); + } else if (result.error) { + errors.push(result.error); + } + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + message: `Stopped ${stoppedInstances.length} of ${args.instanceIds.length} requested instances`, + stoppedInstances, + errors: errors.length > 0 ? errors : undefined, + }, + null, + 2 + ), + }, + ], + }; + } + + // If single instanceId is provided, stop that specific instance + if (args.instanceId) { + const result = stopInstance(args.instanceId); + if (!result.success) { + return this.createErrorResponse( + result.error || `Failed to stop instance: ${args.instanceId}`, + [ + 'Use list_processes to see all running instances', + 'Check if the instance has already exited', + ] + ); + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + message: `Godot project instance "${args.instanceId}" stopped`, + ...result.result, + }, + null, + 2 + ), + }, + ], + }; + } + + // Stop all instances (only kill running ones, but remove all from tracking) + this.logDebug('Stopping all Godot processes'); + const stoppedInstances: any[] = []; + + for (const [id, process] of this.activeProcesses.entries()) { + try { + // Only attempt to kill if the process is still running + if (process.isRunning) { + process.process.kill(); + } + stoppedInstances.push({ + instanceId: process.id, + projectPath: process.projectPath, + wasRunning: process.isRunning, + finalOutput: process.output, + finalErrors: process.errors, + }); + } catch (error) { + this.logDebug(`Error stopping process ${id}: ${error}`); + } + } + + this.activeProcesses.clear(); + + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + message: 'All Godot project instances stopped', + stoppedInstances, + }, + null, + 2 + ), + }, + ], + }; + } + + /** + * Handle the list_processes tool + */ + private async handleListProcesses() { + if (this.activeProcesses.size === 0) { + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + message: 'No running Godot processes', + processes: [], + }, + null, + 2 + ), + }, + ], + }; + } + + const processes: any[] = []; + for (const [id, process] of this.activeProcesses.entries()) { + processes.push({ + instanceId: process.id, + projectPath: process.projectPath, + scene: process.scene, + args: process.args, + startedAt: process.startedAt.toISOString(), + exitedAt: process.exitedAt?.toISOString(), + isRunning: process.isRunning, + outputLines: process.output.length, + errorLines: process.errors.length, + }); + } + + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + count: processes.length, + processes, + }, + null, + 2 + ), + }, + ], + }; + } + + /** + * Handle the capture_screenshot tool + * Captures a screenshot from a running Godot project instance + */ + private async handleCaptureScreenshot(args: any) { + const fs = require('fs'); + args = this.normalizeParameters(args || {}); + + if (!args.instanceId) { + return this.createErrorResponse( + 'instanceId is required', + ['Provide the instance ID of the running Godot project'] + ); + } + + const process = this.activeProcesses.get(args.instanceId); + if (!process) { + return this.createErrorResponse( + `No process found with instanceId: ${args.instanceId}`, + [ + 'Use list_processes to see all tracked instances', + 'The instance may have been cleaned up after being exited for too long', + ] + ); + } + + if (!process.isRunning) { + return this.createErrorResponse( + `Instance ${args.instanceId} is not running`, + ['The Godot project must be running to capture a screenshot'] + ); + } + + if (!process.screenshotEnabled) { + return this.createErrorResponse( + `Instance ${args.instanceId} does not have screenshot support enabled`, + [ + 'Restart the instance with enableScreenshot=true', + 'Use run_project with enableScreenshot parameter to enable screenshot capture', + ] + ); + } + + try { + // Create trigger file to request screenshot + const triggerPath = join(process.projectPath, '.mcp_screenshot_req'); + const outputPath = join(process.projectPath, '.mcp_screenshot.png'); + + // Remove any existing screenshot + if (fs.existsSync(outputPath)) { + fs.unlinkSync(outputPath); + } + + // Create trigger file + fs.writeFileSync(triggerPath, '', 'utf8'); + this.logDebug(`Created screenshot trigger file for instance ${args.instanceId}`); + + // Wait for screenshot to be captured (with timeout) + const maxWaitMs = 5000; + const checkIntervalMs = 100; + let elapsed = 0; + + while (elapsed < maxWaitMs) { + await new Promise(resolve => setTimeout(resolve, checkIntervalMs)); + elapsed += checkIntervalMs; + + if (fs.existsSync(outputPath)) { + this.logDebug(`Screenshot captured for instance ${args.instanceId}`); + + // Read the screenshot file + const imageBuffer = fs.readFileSync(outputPath); + const base64Image = imageBuffer.toString('base64'); + + // If outputPath is provided, save to that location + if (args.outputPath && this.validatePath(args.outputPath)) { + fs.copyFileSync(outputPath, args.outputPath); + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + success: true, + message: `Screenshot saved to ${args.outputPath}`, + instanceId: args.instanceId, + savedPath: args.outputPath, + }, + null, + 2 + ), + }, + ], + }; + } + + // Return base64 encoded image + return { + content: [ + { + type: 'image', + data: base64Image, + mimeType: 'image/png', + }, + ], + }; + } + } + + return this.createErrorResponse( + 'Screenshot capture timed out', + [ + 'The Godot project may not be responding', + 'Ensure the game loop is running (not paused or blocked)', + 'Try again after the game has fully loaded', + ] + ); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return this.createErrorResponse( + `Failed to capture screenshot: ${errorMessage}`, + [ + 'Ensure the Godot project is running correctly', + 'Check if the project has write permissions', + ] + ); + } + } + + /** + * Handle the signal_connect tool + * Connect a signal from one node to another node's method + */ + private async handleSignalConnect(args: any) { + args = this.normalizeParameters(args || {}); + + if (!args.projectPath || !args.scenePath || !args.sourceNodePath || + !args.signalName || !args.targetNodePath || !args.methodName) { + return this.createErrorResponse( + 'Missing required parameters for signal_connect', + ['Required: projectPath, scenePath, sourceNodePath, signalName, targetNodePath, methodName'] + ); + } + + const params = { + scene_path: args.scenePath, + source_node_path: args.sourceNodePath, + signal_name: args.signalName, + target_node_path: args.targetNodePath, + method_name: args.methodName, + flags: args.flags || 0, + }; + + return await this.executeGodotOperation(args.projectPath, 'signal_connect', params); + } + + /** + * Handle the signal_disconnect tool + * Disconnect a signal connection + */ + private async handleSignalDisconnect(args: any) { + args = this.normalizeParameters(args || {}); + + if (!args.projectPath || !args.scenePath || !args.sourceNodePath || + !args.signalName || !args.targetNodePath || !args.methodName) { + return this.createErrorResponse( + 'Missing required parameters for signal_disconnect', + ['Required: projectPath, scenePath, sourceNodePath, signalName, targetNodePath, methodName'] + ); + } + + const params = { + scene_path: args.scenePath, + source_node_path: args.sourceNodePath, + signal_name: args.signalName, + target_node_path: args.targetNodePath, + method_name: args.methodName, + }; + + return await this.executeGodotOperation(args.projectPath, 'signal_disconnect', params); + } + + /** + * Handle the group_add tool + * Add a node to a group + */ + private async handleGroupAdd(args: any) { + args = this.normalizeParameters(args || {}); + + if (!args.projectPath || !args.scenePath || !args.nodePath || !args.groupName) { + return this.createErrorResponse( + 'Missing required parameters for group_add', + ['Required: projectPath, scenePath, nodePath, groupName'] + ); + } + + const params = { + scene_path: args.scenePath, + node_path: args.nodePath, + group_name: args.groupName, + }; + + return await this.executeGodotOperation(args.projectPath, 'group_add', params); + } + + /** + * Handle the group_remove tool + * Remove a node from a group + */ + private async handleGroupRemove(args: any) { + args = this.normalizeParameters(args || {}); + + if (!args.projectPath || !args.scenePath || !args.nodePath || !args.groupName) { + return this.createErrorResponse( + 'Missing required parameters for group_remove', + ['Required: projectPath, scenePath, nodePath, groupName'] + ); + } + + const params = { + scene_path: args.scenePath, + node_path: args.nodePath, + group_name: args.groupName, + }; + + return await this.executeGodotOperation(args.projectPath, 'group_remove', params); + } + + /** + * Handle the list_groups tool + * List all groups in a scene + */ + private async handleListGroups(args: any) { + args = this.normalizeParameters(args || {}); + + if (!args.projectPath || !args.scenePath) { + return this.createErrorResponse( + 'Missing required parameters for list_groups', + ['Required: projectPath, scenePath'] + ); + } + + const params = { + scene_path: args.scenePath, + }; + + return await this.executeGodotOperation(args.projectPath, 'list_groups', params); + } + + /** + * Handle the ui_create_button tool + * Create a Button node in a scene + */ + private async handleUiCreateButton(args: any) { + args = this.normalizeParameters(args || {}); + + if (!args.projectPath || !args.scenePath || !args.parentNodePath || + !args.nodeName || !args.text) { + return this.createErrorResponse( + 'Missing required parameters for ui_create_button', + ['Required: projectPath, scenePath, parentNodePath, nodeName, text'] + ); + } + + const params: any = { + scene_path: args.scenePath, + parent_node_path: args.parentNodePath, + node_name: args.nodeName, + text: args.text, + }; + + if (args.position) { + params.position = args.position; + } + if (args.size) { + params.size = args.size; + } + + return await this.executeGodotOperation(args.projectPath, 'ui_create_button', params); + } + + /** + * Handle the ui_create_label tool + * Create a Label node in a scene + */ + private async handleUiCreateLabel(args: any) { + args = this.normalizeParameters(args || {}); + + if (!args.projectPath || !args.scenePath || !args.parentNodePath || + !args.nodeName || !args.text) { + return this.createErrorResponse( + 'Missing required parameters for ui_create_label', + ['Required: projectPath, scenePath, parentNodePath, nodeName, text'] + ); + } + + const params: any = { + scene_path: args.scenePath, + parent_node_path: args.parentNodePath, + node_name: args.nodeName, + text: args.text, + }; + + if (args.position) { + params.position = args.position; + } + if (args.fontSize) { + params.font_size = args.fontSize; + } + + return await this.executeGodotOperation(args.projectPath, 'ui_create_label', params); + } + + /** + * Execute a Godot operation using the operations script + */ + private async executeGodotOperation(projectPath: string, operation: string, params: any) { + if (!this.godotPath) { + await this.detectGodotPath(); + if (!this.godotPath) { + return this.createErrorResponse( + 'Could not find a valid Godot executable path', + ['Set GODOT_PATH environment variable'] + ); + } } - if (!this.validatePath(args.projectPath)) { + const operationScript = join(__dirname, 'scripts', 'godot_operations.gd'); + if (!existsSync(operationScript)) { return this.createErrorResponse( - 'Invalid project path', - ['Provide a valid path without ".." or other potentially unsafe characters'] + 'Godot operations script not found', + ['Ensure the MCP server is built correctly'] ); } - try { - // Ensure godotPath is set - if (!this.godotPath) { - await this.detectGodotPath(); - if (!this.godotPath) { - return this.createErrorResponse( - 'Could not find a valid Godot executable path', - [ - 'Ensure Godot is installed correctly', - 'Set GODOT_PATH environment variable to specify the correct path', - ] - ); - } - } + const args = [ + '--headless', + '--path', projectPath, + '-s', operationScript, + '--', + JSON.stringify({ operation, params }), + ]; - // Check if the project directory exists and contains a project.godot file - const projectFile = join(args.projectPath, 'project.godot'); - if (!existsSync(projectFile)) { - return this.createErrorResponse( - `Not a valid Godot project: ${args.projectPath}`, - [ - 'Ensure the path points to a directory containing a project.godot file', - 'Use list_projects to find valid Godot projects', - ] - ); - } + return new Promise((resolve) => { + const process = spawn(this.godotPath!, args, { stdio: 'pipe' }); + let output = ''; + let errors = ''; - this.logDebug(`Launching Godot editor for project: ${args.projectPath}`); - const process = spawn(this.godotPath, ['-e', '--path', args.projectPath], { - stdio: 'pipe', + process.stdout.on('data', (data: Buffer) => { + output += data.toString(); }); - process.on('error', (err: Error) => { - console.error('Failed to start Godot editor:', err); + process.stderr.on('data', (data: Buffer) => { + errors += data.toString(); }); - return { - content: [ - { - type: 'text', - text: `Godot editor launched successfully for project at ${args.projectPath}.`, - }, - ], - }; - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - return this.createErrorResponse( - `Failed to launch Godot editor: ${errorMessage}`, - [ - 'Ensure Godot is installed correctly', - 'Check if the GODOT_PATH environment variable is set correctly', - 'Verify the project path is accessible', - ] - ); - } + process.on('close', (code: number) => { + if (code === 0) { + resolve({ + content: [ + { + type: 'text', + text: output || `Operation ${operation} completed successfully`, + }, + ], + }); + } else { + resolve(this.createErrorResponse( + `Operation ${operation} failed with code ${code}`, + [errors || 'Unknown error'] + )); + } + }); + + process.on('error', (error: Error) => { + resolve(this.createErrorResponse( + `Failed to execute operation: ${error.message}`, + ['Ensure Godot is installed correctly'] + )); + }); + }); } /** - * Handle the run_project tool - * @param args Tool arguments + * Handle the run_multiple_projects tool + * Launches multiple Godot instances with optional staggered delays */ - private async handleRunProject(args: any) { + private async handleRunMultipleProjects(args: any) { // Normalize parameters to camelCase - args = this.normalizeParameters(args); - - if (!args.projectPath) { - return this.createErrorResponse( - 'Project path is required', - ['Provide a valid path to a Godot project directory'] - ); - } + args = this.normalizeParameters(args || {}); - if (!this.validatePath(args.projectPath)) { + if (!args.instances || !Array.isArray(args.instances) || args.instances.length === 0) { return this.createErrorResponse( - 'Invalid project path', - ['Provide a valid path without ".." or other potentially unsafe characters'] + 'instances array is required and must not be empty', + ['Provide an array of instance configurations with at least one entry'] ); } - try { - // Check if the project directory exists and contains a project.godot file - const projectFile = join(args.projectPath, 'project.godot'); - if (!existsSync(projectFile)) { + // Ensure godotPath is set + if (!this.godotPath) { + await this.detectGodotPath(); + if (!this.godotPath) { return this.createErrorResponse( - `Not a valid Godot project: ${args.projectPath}`, + 'Could not find a valid Godot executable path', [ - 'Ensure the path points to a directory containing a project.godot file', - 'Use list_projects to find valid Godot projects', + 'Ensure Godot is installed correctly', + 'Set GODOT_PATH environment variable to specify the correct path', ] ); } + } - // Kill any existing process - if (this.activeProcess) { - this.logDebug('Killing existing Godot process before starting a new one'); - this.activeProcess.process.kill(); - } + // Validate all instances first before launching any + const validatedInstances: InstanceConfig[] = []; + const validationErrors: string[] = []; - const cmdArgs = ['-d', '--path', args.projectPath]; - if (args.scene && this.validatePath(args.scene)) { - this.logDebug(`Adding scene parameter: ${args.scene}`); - cmdArgs.push(args.scene); + for (let i = 0; i < args.instances.length; i++) { + const instance = this.normalizeParameters(args.instances[i]) as InstanceConfig; + + if (!instance.projectPath) { + validationErrors.push(`Instance ${i}: projectPath is required`); + continue; } - this.logDebug(`Running Godot project: ${args.projectPath}`); - const process = spawn(this.godotPath!, cmdArgs, { stdio: 'pipe' }); - const output: string[] = []; - const errors: string[] = []; - - process.stdout?.on('data', (data: Buffer) => { - const lines = data.toString().split('\n'); - output.push(...lines); - lines.forEach((line: string) => { - if (line.trim()) this.logDebug(`[Godot stdout] ${line}`); - }); - }); + if (!this.validatePath(instance.projectPath)) { + validationErrors.push(`Instance ${i}: Invalid project path`); + continue; + } - process.stderr?.on('data', (data: Buffer) => { - const lines = data.toString().split('\n'); - errors.push(...lines); - lines.forEach((line: string) => { - if (line.trim()) this.logDebug(`[Godot stderr] ${line}`); - }); - }); + const projectFile = join(instance.projectPath, 'project.godot'); + if (!existsSync(projectFile)) { + validationErrors.push(`Instance ${i}: Not a valid Godot project: ${instance.projectPath}`); + continue; + } - process.on('exit', (code: number | null) => { - this.logDebug(`Godot process exited with code ${code}`); - if (this.activeProcess && this.activeProcess.process === process) { - this.activeProcess = null; - } - }); + // Generate instanceId if not provided + if (!instance.instanceId) { + instance.instanceId = `instance_${this.nextInstanceId++}`; + } - process.on('error', (err: Error) => { - console.error('Failed to start Godot process:', err); - if (this.activeProcess && this.activeProcess.process === process) { - this.activeProcess = null; + // Check for duplicate instanceId - only block if process is still running + const existingProcess = this.activeProcesses.get(instance.instanceId); + if (existingProcess) { + if (existingProcess.isRunning) { + validationErrors.push(`Instance ${i}: instanceId "${instance.instanceId}" is already in use by a running process`); + continue; + } else { + // Clean up exited process to allow reuse of the ID + this.logDebug(`Cleaning up exited process ${instance.instanceId} to allow ID reuse`); + this.activeProcesses.delete(instance.instanceId); } - }); + } - this.activeProcess = { process, output, errors }; + // Check for duplicates within this batch + if (validatedInstances.some(v => v.instanceId === instance.instanceId)) { + validationErrors.push(`Instance ${i}: duplicate instanceId "${instance.instanceId}" in batch`); + continue; + } - return { - content: [ - { - type: 'text', - text: `Godot project started in debug mode. Use get_debug_output to see output.`, - }, - ], - }; - } catch (error: unknown) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - return this.createErrorResponse( - `Failed to run Godot project: ${errorMessage}`, - [ - 'Ensure Godot is installed correctly', - 'Check if the GODOT_PATH environment variable is set correctly', - 'Verify the project path is accessible', - ] - ); + validatedInstances.push(instance); } - } - /** - * Handle the get_debug_output tool - */ - private async handleGetDebugOutput() { - if (!this.activeProcess) { + if (validationErrors.length > 0) { return this.createErrorResponse( - 'No active Godot process.', + `Validation failed for ${validationErrors.length} instance(s):\n${validationErrors.join('\n')}`, [ - 'Use run_project to start a Godot project first', - 'Check if the Godot process crashed unexpectedly', + 'Fix the validation errors and try again', + 'Use list_processes to see currently running instances', ] ); } - return { - content: [ - { - type: 'text', - text: JSON.stringify( - { - output: this.activeProcess.output, - errors: this.activeProcess.errors, - }, - null, - 2 - ), - }, - ], + // Sort instances by delayMs (ascending) for proper staggering + validatedInstances.sort((a, b) => (a.delayMs || 0) - (b.delayMs || 0)); + + // Launch instances with delays + const launchedInstances: any[] = []; + const launchErrors: string[] = []; + + const launchInstance = (instance: InstanceConfig): { success: boolean; result?: any; error?: string } => { + try { + const cmdArgs = ['-d', '--path', instance.projectPath]; + + if (instance.scene && this.validatePath(instance.scene)) { + cmdArgs.push(instance.scene); + } + + if (instance.args && Array.isArray(instance.args)) { + for (const arg of instance.args) { + if (typeof arg === 'string' && arg.trim() !== '') { + cmdArgs.push(arg); + } + } + } + + this.logDebug(`Launching Godot instance: ${instance.instanceId} with args: ${cmdArgs.join(' ')}`); + const godotProcess = spawn(this.godotPath!, cmdArgs, { stdio: 'pipe' }); + const output: string[] = []; + const errors: string[] = []; + + const processInfo: GodotProcess = { + id: instance.instanceId!, + process: godotProcess, + output, + errors, + projectPath: instance.projectPath, + scene: instance.scene, + args: instance.args, + startedAt: new Date(), + isRunning: true, + }; + + godotProcess.stdout?.on('data', (data: Buffer) => { + const rawLines = data.toString().split('\n'); + for (const line of rawLines) { + const cleaned = line.replace(/\r/g, '').trimEnd(); + if (cleaned.length > 0) { + this.addToBoundedBuffer(processInfo.output, cleaned); + this.logDebug(`[Godot ${instance.instanceId} stdout] ${cleaned}`); + } + } + }); + + godotProcess.stderr?.on('data', (data: Buffer) => { + const rawLines = data.toString().split('\n'); + for (const line of rawLines) { + const cleaned = line.replace(/\r/g, '').trimEnd(); + if (cleaned.length > 0) { + this.addToBoundedBuffer(processInfo.errors, cleaned); + this.logDebug(`[Godot ${instance.instanceId} stderr] ${cleaned}`); + } + } + }); + + godotProcess.on('exit', (code: number | null) => { + this.logDebug(`Godot process ${instance.instanceId} exited with code ${code}`); + processInfo.isRunning = false; + processInfo.exitedAt = new Date(); + }); + + godotProcess.on('error', (err: Error) => { + console.error(`Failed to start Godot process ${instance.instanceId}:`, err); + processInfo.errors.push(`Process error: ${err.message}`); + processInfo.isRunning = false; + processInfo.exitedAt = new Date(); + }); + + this.activeProcesses.set(instance.instanceId!, processInfo); + + return { + success: true, + result: { + instanceId: instance.instanceId, + projectPath: instance.projectPath, + scene: instance.scene, + args: instance.args, + delayMs: instance.delayMs || 0, + }, + }; + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + return { success: false, error: `Failed to launch ${instance.instanceId}: ${errorMessage}` }; + } }; - } - /** - * Handle the stop_project tool - */ - private async handleStopProject() { - if (!this.activeProcess) { - return this.createErrorResponse( - 'No active Godot process to stop.', - [ - 'Use run_project to start a Godot project first', - 'The process may have already terminated', - ] - ); - } + // Launch with delays using async/await + const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + let lastDelay = 0; + + for (const instance of validatedInstances) { + const targetDelay = instance.delayMs || 0; + const waitTime = targetDelay - lastDelay; + + if (waitTime > 0) { + this.logDebug(`Waiting ${waitTime}ms before launching ${instance.instanceId}`); + await sleep(waitTime); + } + + lastDelay = targetDelay; - this.logDebug('Stopping active Godot process'); - this.activeProcess.process.kill(); - const output = this.activeProcess.output; - const errors = this.activeProcess.errors; - this.activeProcess = null; + const result = launchInstance(instance); + if (result.success && result.result) { + launchedInstances.push(result.result); + } else if (result.error) { + launchErrors.push(result.error); + } + } return { content: [ @@ -1198,9 +2642,9 @@ class GodotServer { type: 'text', text: JSON.stringify( { - message: 'Godot project stopped', - finalOutput: output, - finalErrors: errors, + message: `Launched ${launchedInstances.length} of ${validatedInstances.length} instances`, + launchedInstances, + errors: launchErrors.length > 0 ? launchErrors : undefined, }, null, 2 @@ -2176,15 +3620,174 @@ class GodotServer { console.error(`[SERVER] Using Godot at: ${this.godotPath}`); - const transport = new StdioServerTransport(); - await this.server.connect(transport); - console.error('Godot MCP server running on stdio'); + // Check transport mode + const transport = process.env.MCP_TRANSPORT || 'stdio'; + + if (transport === 'http') { + await this.runHttpServer(); + } else { + await this.runStdioServer(); + } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; console.error('[SERVER] Failed to start:', errorMessage); process.exit(1); } } + + /** + * Run the MCP server with stdio transport + */ + private async runStdioServer() { + const transport = new StdioServerTransport(); + await this.server.connect(transport); + console.error('Godot MCP server running on stdio'); + } + + /** + * Run the MCP server with HTTP transport + */ + private async runHttpServer() { + const http = require('http'); + const port = parseInt(process.env.MCP_HTTP_PORT || '3000', 10); + const host = process.env.MCP_HTTP_HOST || '127.0.0.1'; + const endpoint = process.env.MCP_HTTP_ENDPOINT || '/mcp'; + + const server = http.createServer(async (req: any, res: any) => { + // Set CORS headers + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + // Handle preflight requests + if (req.method === 'OPTIONS') { + res.writeHead(204); + res.end(); + return; + } + + // Only accept POST requests to the endpoint + if (req.method !== 'POST' || req.url !== endpoint) { + res.writeHead(404); + res.end(JSON.stringify({ error: 'Not found' })); + return; + } + + // Read request body + let body = ''; + req.on('data', (chunk: Buffer) => { + body += chunk.toString(); + }); + + req.on('end', async () => { + try { + const request = JSON.parse(body); + const response = await this.handleHttpRequest(request); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify(response)); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + res.writeHead(400, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: errorMessage })); + } + }); + }); + + server.listen(port, host, () => { + console.error(`Godot MCP server running on HTTP: http://${host}:${port}${endpoint}`); + }); + + // Handle server errors + server.on('error', (error: Error) => { + console.error('[SERVER] HTTP server error:', error.message); + process.exit(1); + }); + + // Keep the process alive + await new Promise(() => {}); // Never resolve + } + + /** + * Handle HTTP requests + */ + private async handleHttpRequest(request: any): Promise { + // Handle different request types + if (request.method === 'tools/list') { + return await this.handleToolsList(); + } else if (request.method === 'tools/call') { + return await this.handleToolCall(request.params); + } else if (request.method === 'initialize') { + return await this.handleInitialize(request.params); + } + + return { error: `Unknown method: ${request.method}` }; + } + + /** + * Handle tools/list request + */ + private async handleToolsList(): Promise { + // Return the list of tools + return { + tools: [ + { name: 'launch_editor', description: 'Launch Godot editor for a specific project' }, + { name: 'run_project', description: 'Run the Godot project and capture output' }, + { name: 'get_debug_output', description: 'Get the current debug output and errors' }, + { name: 'stop_project', description: 'Stop running Godot project instance(s)' }, + { name: 'capture_screenshot', description: 'Capture a screenshot from a running Godot project instance' }, + { name: 'get_godot_version', description: 'Get the installed Godot version' }, + { name: 'list_projects', description: 'List Godot projects in a directory' }, + { name: 'get_project_info', description: 'Retrieve metadata about a Godot project' }, + { name: 'list_processes', description: 'List all running Godot processes' }, + ], + }; + } + + /** + * Handle tools/call request + */ + private async handleToolCall(params: any): Promise { + const { name, arguments: args } = params; + + switch (name) { + case 'launch_editor': + return await this.handleLaunchEditor(args); + case 'run_project': + return await this.handleRunProject(args); + case 'get_debug_output': + return await this.handleGetDebugOutput(args); + case 'stop_project': + return await this.handleStopProject(args); + case 'capture_screenshot': + return await this.handleCaptureScreenshot(args); + case 'get_godot_version': + return await this.handleGetGodotVersion(); + case 'list_projects': + return await this.handleListProjects(args); + case 'get_project_info': + return await this.handleGetProjectInfo(args); + case 'list_processes': + return await this.handleListProcesses(); + default: + return { error: `Unknown tool: ${name}` }; + } + } + + /** + * Handle initialize request + */ + private async handleInitialize(params: any): Promise { + return { + protocolVersion: '2024-11-05', + capabilities: { + tools: {}, + }, + serverInfo: { + name: 'godot-mcp', + version: '0.1.1', + }, + }; + } } // Create and run the server diff --git a/src/scripts/godot_operations.gd b/src/scripts/godot_operations.gd index 65ea869b8..4839525f4 100644 --- a/src/scripts/godot_operations.gd +++ b/src/scripts/godot_operations.gd @@ -71,6 +71,20 @@ func _init(): get_uid(params) "resave_resources": resave_resources(params) + "signal_connect": + signal_connect(params) + "signal_disconnect": + signal_disconnect(params) + "group_add": + group_add(params) + "group_remove": + group_remove(params) + "list_groups": + list_groups(params) + "ui_create_button": + ui_create_button(params) + "ui_create_label": + ui_create_label(params) _: log_error("Unknown operation: " + operation) quit(1) @@ -461,6 +475,362 @@ func create_scene(params): quit(1) else: printerr("Failed to pack scene: " + str(result)) + +## Signal Operations + +func signal_connect(params: Dictionary) -> void: + """ + Connect a signal from one node to another node's method. + params: + - scene_path: Path to the scene file + - source_node_path: Path to the source node (emitter) + - signal_name: Name of the signal to connect + - target_node_path: Path to the target node (receiver) + - method_name: Name of the method to call when signal is emitted + - flags: Optional connection flags (default: 0) + """ + var scene_path = params.get("scene_path", "") + var source_node_path = params.get("source_node_path", "") + var signal_name = params.get("signal_name", "") + var target_node_path = params.get("target_node_path", "") + var method_name = params.get("method_name", "") + var flags = params.get("flags", 0) + + if scene_path.is_empty() or source_node_path.is_empty() or signal_name.is_empty(): + printerr("Missing required parameters for signal_connect") + quit(1) + return + + var scene = load_scene(scene_path) + if scene == null: + return + + var source_node = get_node_by_path(scene, source_node_path) + var target_node = get_node_by_path(scene, target_node_path) + + if source_node == null: + printerr("Source node not found: " + source_node_path) + quit(1) + return + + if target_node == null: + printerr("Target node not found: " + target_node_path) + quit(1) + return + + # Connect the signal + var error = source_node.connect(signal_name, Callable(target_node, method_name), flags) + + if error == OK: + print("Signal '" + signal_name + "' connected from '" + source_node_path + "' to '" + target_node_path + "." + method_name + "'") + + # Save the scene to persist the connection + var packed_scene = PackedScene.new() + var result = packed_scene.pack(scene) + if result == OK: + ResourceSaver.save(packed_scene, scene_path) + print("Scene saved with new signal connection") + else: + printerr("Failed to connect signal: " + str(error)) + quit(1) + +func signal_disconnect(params: Dictionary) -> void: + """ + Disconnect a signal connection. + params: + - scene_path: Path to the scene file + - source_node_path: Path to the source node (emitter) + - signal_name: Name of the signal to disconnect + - target_node_path: Path to the target node (receiver) + - method_name: Name of the method that was connected + """ + var scene_path = params.get("scene_path", "") + var source_node_path = params.get("source_node_path", "") + var signal_name = params.get("signal_name", "") + var target_node_path = params.get("target_node_path", "") + var method_name = params.get("method_name", "") + + if scene_path.is_empty() or source_node_path.is_empty() or signal_name.is_empty(): + printerr("Missing required parameters for signal_disconnect") + quit(1) + return + + var scene = load_scene(scene_path) + if scene == null: + return + + var source_node = get_node_by_path(scene, source_node_path) + var target_node = get_node_by_path(scene, target_node_path) + + if source_node == null: + printerr("Source node not found: " + source_node_path) + quit(1) + return + + if target_node == null: + printerr("Target node not found: " + target_node_path) + quit(1) + return + + # Disconnect the signal + if source_node.is_connected(signal_name, Callable(target_node, method_name)): + source_node.disconnect(signal_name, Callable(target_node, method_name)) + print("Signal '" + signal_name + "' disconnected from '" + target_node_path + "." + method_name + "'") + + # Save the scene + var packed_scene = PackedScene.new() + var result = packed_scene.pack(scene) + if result == OK: + ResourceSaver.save(packed_scene, scene_path) + print("Scene saved with signal disconnection") + else: + printerr("Signal connection not found") + quit(1) + +## Group Operations + +func group_add(params: Dictionary) -> void: + """ + Add a node to a group. + params: + - scene_path: Path to the scene file + - node_path: Path to the node + - group_name: Name of the group + """ + var scene_path = params.get("scene_path", "") + var node_path = params.get("node_path", "") + var group_name = params.get("group_name", "") + + if scene_path.is_empty() or node_path.is_empty() or group_name.is_empty(): + printerr("Missing required parameters for group_add") + quit(1) + return + + var scene = load_scene(scene_path) + if scene == null: + return + + var node = get_node_by_path(scene, node_path) + + if node == null: + printerr("Node not found: " + node_path) + quit(1) + return + + # Add to group + node.add_to_group(group_name) + print("Node '" + node_path + "' added to group '" + group_name + "'") + + # Save the scene + var packed_scene = PackedScene.new() + var result = packed_scene.pack(scene) + if result == OK: + ResourceSaver.save(packed_scene, scene_path) + print("Scene saved with group addition") + +func group_remove(params: Dictionary) -> void: + """ + Remove a node from a group. + params: + - scene_path: Path to the scene file + - node_path: Path to the node + - group_name: Name of the group + """ + var scene_path = params.get("scene_path", "") + var node_path = params.get("node_path", "") + var group_name = params.get("group_name", "") + + if scene_path.is_empty() or node_path.is_empty() or group_name.is_empty(): + printerr("Missing required parameters for group_remove") + quit(1) + return + + var scene = load_scene(scene_path) + if scene == null: + return + + var node = get_node_by_path(scene, node_path) + + if node == null: + printerr("Node not found: " + node_path) + quit(1) + return + + # Remove from group + if node.is_in_group(group_name): + node.remove_from_group(group_name) + print("Node '" + node_path + "' removed from group '" + group_name + "'") + + # Save the scene + var packed_scene = PackedScene.new() + var result = packed_scene.pack(scene) + if result == OK: + ResourceSaver.save(packed_scene, scene_path) + print("Scene saved with group removal") + else: + printerr("Node is not in group '" + group_name + "'") + quit(1) + +func list_groups(params: Dictionary) -> void: + """ + List all groups in a scene. + params: + - scene_path: Path to the scene file + """ + var scene_path = params.get("scene_path", "") + + if scene_path.is_empty(): + printerr("Missing required parameter: scene_path") + quit(1) + return + + var scene = load_scene(scene_path) + if scene == null: + return + + # Collect all groups + var groups = {} + var nodes_to_check = [scene] + + while nodes_to_check.size() > 0: + var node = nodes_to_check.pop_back() + var node_groups = node.get_groups() + + for group in node_groups: + if not groups.has(group): + groups[group] = [] + groups[group].append(node.get_path()) + + # Add children to check + for child in node.get_children(): + nodes_to_check.append(child) + + # Output result + var result = {"groups": groups, "count": groups.size()} + print(JSON.stringify(result)) + +## UI Operations + +func ui_create_button(params: Dictionary) -> void: + """ + Create a Button node in a scene. + params: + - scene_path: Path to the scene file + - parent_node_path: Path to the parent node + - node_name: Name for the new button + - text: Text to display on the button + - position: Optional {x, y} position + - size: Optional {width, height} size + """ + var scene_path = params.get("scene_path", "") + var parent_node_path = params.get("parent_node_path", "") + var node_name = params.get("node_name", "") + var text = params.get("text", "") + var position = params.get("position", {}) + var size = params.get("size", {}) + + if scene_path.is_empty() or parent_node_path.is_empty() or node_name.is_empty(): + printerr("Missing required parameters for ui_create_button") + quit(1) + return + + var scene = load_scene(scene_path) + if scene == null: + return + + var parent_node = get_node_by_path(scene, parent_node_path) + + if parent_node == null: + printerr("Parent node not found: " + parent_node_path) + quit(1) + return + + # Create Button node + var button = Button.new() + button.name = node_name + button.text = text + + # Set position if provided + if position.has("x") and position.has("y"): + button.position = Vector2(position.x, position.y) + + # Set size if provided + if size.has("width") and size.has("height"): + button.size = Vector2(size.width, size.height) + + # Add to parent + parent_node.add_child(button) + button.owner = scene + + print("Button '" + node_name + "' created at '" + parent_node_path + "'") + + # Save the scene + var packed_scene = PackedScene.new() + var result = packed_scene.pack(scene) + if result == OK: + ResourceSaver.save(packed_scene, scene_path) + print("Scene saved with new button") + +func ui_create_label(params: Dictionary) -> void: + """ + Create a Label node in a scene. + params: + - scene_path: Path to the scene file + - parent_node_path: Path to the parent node + - node_name: Name for the new label + - text: Text to display in the label + - position: Optional {x, y} position + - font_size: Optional font size + """ + var scene_path = params.get("scene_path", "") + var parent_node_path = params.get("parent_node_path", "") + var node_name = params.get("node_name", "") + var text = params.get("text", "") + var position = params.get("position", {}) + var font_size = params.get("font_size", 16) + + if scene_path.is_empty() or parent_node_path.is_empty() or node_name.is_empty(): + printerr("Missing required parameters for ui_create_label") + quit(1) + return + + var scene = load_scene(scene_path) + if scene == null: + return + + var parent_node = get_node_by_path(scene, parent_node_path) + + if parent_node == null: + printerr("Parent node not found: " + parent_node_path) + quit(1) + return + + # Create Label node + var label = Label.new() + label.name = node_name + label.text = text + + # Set position if provided + if position.has("x") and position.has("y"): + label.position = Vector2(position.x, position.y) + + # Set font size + var theme = Theme.new() + theme.set_font_size("font_size", "Label", font_size) + label.theme = theme + + # Add to parent + parent_node.add_child(label) + label.owner = scene + + print("Label '" + node_name + "' created at '" + parent_node_path + "'") + + # Save the scene + var packed_scene = PackedScene.new() + var result = packed_scene.pack(scene) + if result == OK: + ResourceSaver.save(packed_scene, scene_path) + print("Scene saved with new label") printerr("Error code: " + str(result)) quit(1)