├── .DS_Store ├── .env.example ├── .gitignore ├── README.md ├── mcp-servers.example.json ├── package-lock.json ├── package.json ├── public ├── arc.png ├── code-arc.png ├── demo.png ├── logs.png ├── time-arc.png └── time-arc2.png ├── src ├── index.ts ├── mcpClient.ts ├── services │ ├── LLMService.ts │ └── ToolService.ts ├── types │ ├── config.ts │ ├── global.d.ts │ └── index.ts └── utils │ ├── config.ts │ ├── log.ts │ └── schema.ts └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConardLi/mcp-client-nodejs/e363b03f3dcc9e66e0c2217adb96149885166f95/.DS_Store -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY=your_api_key_here 2 | MODEL_NAME=xxx 3 | BASE_URL=xxx -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | .env 4 | *.log 5 | mcp-servers.json 6 | logs/ 7 | logs2/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 项目介绍 4 | 5 | MCP Client 是一个基于 Model Context Protocol 的 Node.js 客户端实现(使用 Function Calling),它允许您的应用连接到各种 MCP 服务器,并通过大语言模型(LLM)与这些服务器交互。MCP(模型上下文协议)是一个开放协议,用于标准化应用程序向 LLM 提供上下文的方式。 6 | 7 | ![](./public/arc.png) 8 | 9 | 10 | ## 核心特性 11 | 12 | - 支持连接任何符合 MCP 标准的服务器 13 | - 支持兼容 OpenAI API 格式的 LLM 能力 14 | - 自动发现和使用服务器提供的工具 15 | - 完善的日志记录系统,包括 API 请求和工具调用 16 | - 交互式命令行界面 17 | - 支持工具调用和结果处理 18 | 19 | ## 系统要求 20 | 21 | - Node.js 17 或更高版本 22 | - LLM API 密钥(暂不支持 Ollama) 23 | - 磁盘空间用于存储日志文件(位于 `logs/` 目录) 24 | 25 | ## 安装 26 | 27 | ### 1. 克隆仓库 28 | 29 | ```bash 30 | git clone https://github.com/ConardLi/mcp-client-nodejs.git 31 | cd mcp-client-nodejs 32 | ``` 33 | 34 | ### 2. 安装依赖 35 | 36 | ```bash 37 | npm install 38 | ``` 39 | 40 | 所需依赖项: 41 | - @modelcontextprotocol/sdk 42 | - openai 43 | - dotenv 44 | - typescript(开发依赖) 45 | - @types/node(开发依赖) 46 | 47 | ### 3. 配置环境变量 48 | 49 | 复制示例环境变量文件并设置您的 LLM API 密钥: 50 | 51 | ```bash 52 | cp .env.example .env 53 | ``` 54 | 55 | 然后编辑 `.env` 文件,填入您的 LLM API 密钥、模型提供商 API 地址、以及模型名称: 56 | 57 | ``` 58 | OPENAI_API_KEY=your_api_key_here 59 | MODEL_NAME=xxx 60 | BASE_URL=xxx 61 | ``` 62 | 63 | ### 4. 编译项目 64 | 65 | ```bash 66 | npm run build 67 | ``` 68 | 69 | ## 使用方法 70 | 71 | 要启动 MCP 客户端,您可以使用以下几种方式: 72 | 73 | ### 1. 直接指定服务器脚本路径 74 | 75 | ```bash 76 | node build/index.js <服务器脚本路径> 77 | ``` 78 | 79 | 其中 `<服务器脚本路径>` 是指向 MCP 服务器脚本的路径,可以是 JavaScript (.js) 或 Python (.py) 文件。 80 | 81 | ### 2. 使用配置文件 82 | 83 | ```bash 84 | node build/index.js <服务器标识符> <配置文件路径> 85 | ``` 86 | 87 | 其中 `<服务器标识符>` 是配置文件中定义的服务器名称,`<配置文件路径>` 是包含服务器定义的 JSON 文件的路径。 88 | 89 | ```json 90 | { 91 | "mcpServers": { 92 | "time": { 93 | "command": "node", 94 | "args": [ 95 | "/Users/xxx/mcp/dist/index.js" 96 | ], 97 | "description": "自定义 Node.js MCP服务器" 98 | }, 99 | "mongodb": { 100 | "command": "npx", 101 | "args": [ 102 | "mcp-mongo-server", 103 | "mongodb://localhost:27017/studentManagement?authSource=admin" 104 | ] 105 | } 106 | }, 107 | "defaultServer": "mongodb", 108 | "system": "自定义系统提示词" 109 | } 110 | ``` 111 | 112 | 113 | 114 | ### 3. 使用 npm 包(npx) 115 | 116 | 您可以直接通过 npx 运行这个包,无需本地克隆和构建: 117 | 118 | ```bash 119 | # 直接连接脚本 120 | $ npx mcp-client-nodejs /path/to/mcp-server.js 121 | 122 | # 通过配置文件连接 123 | $ npx mcp-client-nodejs mongodb ./mcp-servers.json 124 | ``` 125 | 126 | > 注意:需要在当前运行目录的 .env 配置模型相关信息 127 | 128 | 129 | ### 示例 130 | 131 | 直接连接 JavaScript MCP 服务器: 132 | ```bash 133 | node build/index.js /path/to/weather-server/build/index.js 134 | ``` 135 | 136 | 直接连接 Python MCP 服务器: 137 | ```bash 138 | node build/index.js /path/to/mcp-server.py 139 | ``` 140 | 141 | 使用配置文件连接服务器: 142 | ```bash 143 | node build/index.js mongodb ./mcp-servers.json 144 | ``` 145 | 146 | 使用 npx 运行: 147 | 148 | ```bash 149 | # 直接连接脚本 150 | $ npx mcp-client-nodejs /path/to/mcp-server.js 151 | # 通过配置文件连接 152 | $ npx mcp-client-nodejs mongodb./mcp-servers.json 153 | ``` 154 | 155 | ![](./public/demo.png) 156 | 157 | ## 工作原理 158 | 159 | ![](./public/time-arc2.png) 160 | 161 | 1. **服务器连接**:客户端连接到指定的 MCP 服务器 162 | 2. **工具发现**:自动获取服务器提供的可用工具列表 163 | 3. **查询处理**: 164 | - 将用户查询发送给 LLM 165 | - LLM 决定是否需要使用工具 166 | - 如果需要,客户端通过服务器执行工具调用 167 | - 将工具结果返回给 LLM 168 | - LLM 提供最终回复 169 | 4. **交互式循环**:用户可以不断输入查询,直到输入"quit"退出 170 | 171 | ## 日志系统 172 | 173 | MCP Client 包含一个全面的日志系统,详细记录所有关键操作和通信。日志文件保存在 `logs/` 目录中,以 JSON 格式存储,方便查询和分析。 174 | 175 | ![](./public/logs.png) 176 | 177 | ### 日志类型 178 | 179 | - **LLM 的请求和响应** - 记录与 LLM API 的所有通信 180 | - **工具调用和结果** - 记录所有工具调用参数和返回结果 181 | - **错误信息** - 记录系统运行期间的任何错误 182 | 183 | ### 日志命名和格式 184 | 185 | 日志文件连统命名为 `[index] [log_type] YYYY-MM-DD HH:MM:SS.json`,包含序号、日志类型和时间戳,方便按时间顺序查看整个会话。 186 | 187 | ## 架构设计 188 | 189 | ![](./public/code-arc.png) 190 | 191 | 192 | MCP Client 基于模块化的客户端-服务器架构: 193 | - **传输层**:使用 Stdio 传输机制与服务器通信 194 | - **协议层**:使用 MCP 协议处理请求/响应和工具调用 195 | - **模型层**:通过 OpenAI SDK 提供 LLM 能力 196 | 197 | ### 核心组件 198 | 199 | - **MCPClient 类**:管理服务器连接、处理查询和工具调用 200 | - **Client 对象**:MCP SDK 提供的客户端实现 201 | - **StdioClientTransport**:基于标准输入/输出的传输实现 202 | 203 | ## 最佳实践 204 | 205 | - **错误处理**:使用 TypeScript 类型系统进行更好的错误检测 206 | - **安全性**:在 .env 文件中安全存储 API 密钥 207 | - **工具权限**:注意工具的权限和安全性 208 | 209 | ## 故障排除 210 | 211 | ### 服务器路径问题 212 | 213 | - 确保服务器脚本路径正确 214 | - 如果相对路径不起作用,请使用绝对路径 215 | - Windows 用户请确保在路径中使用正斜杠(/)或转义的反斜杠(\\) 216 | - 验证服务器文件具有正确的扩展名(.js 或 .py) 217 | 218 | ### 响应时间 219 | 220 | - 第一次响应可能需要最多 30 秒才能返回 221 | - 这是正常现象,发生在服务器初始化、查询处理和工具执行期间 222 | - 后续响应通常会更快 223 | 224 | ### 常见错误消息 225 | 226 | - `Error: Cannot find module`:检查构建文件夹并确保 TypeScript 编译成功 227 | - `Connection refused`:确保服务器正在运行且路径正确 228 | - `Tool execution failed`:验证工具所需的环境变量是否已设置 229 | - `OPENAI_API_KEY is not set`:检查您的 .env 文件和环境变量 230 | - `TypeError`:确保为工具参数使用正确的类型 231 | 232 | ## 许可证 233 | 234 | Apache License 2.0 235 | 236 | -------------------------------------------------------------------------------- /mcp-servers.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "mongodb": { 4 | "command": "npx", 5 | "args": [ 6 | "mcp-mongo-server", 7 | "mongodb://localhost:27017/studentManagement?authSource=admin" 8 | ] 9 | } 10 | }, 11 | "defaultServer": "mongodb", 12 | "system": "这里填写系统提示词" 13 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mcp-client", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "mcp-client", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@modelcontextprotocol/sdk": "^1.9.0", 13 | "dotenv": "^16.5.0", 14 | "openai": "^4.93.0" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^22.14.0", 18 | "typescript": "^5.8.3" 19 | } 20 | }, 21 | "node_modules/@modelcontextprotocol/sdk": { 22 | "version": "1.9.0", 23 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.9.0.tgz", 24 | "integrity": "sha512-Jq2EUCQpe0iyO5FGpzVYDNFR6oR53AIrwph9yWl7uSc7IWUMsrmpmSaTGra5hQNunXpM+9oit85p924jWuHzUA==", 25 | "dependencies": { 26 | "content-type": "^1.0.5", 27 | "cors": "^2.8.5", 28 | "cross-spawn": "^7.0.3", 29 | "eventsource": "^3.0.2", 30 | "express": "^5.0.1", 31 | "express-rate-limit": "^7.5.0", 32 | "pkce-challenge": "^5.0.0", 33 | "raw-body": "^3.0.0", 34 | "zod": "^3.23.8", 35 | "zod-to-json-schema": "^3.24.1" 36 | }, 37 | "engines": { 38 | "node": ">=18" 39 | } 40 | }, 41 | "node_modules/@types/node": { 42 | "version": "22.14.0", 43 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", 44 | "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", 45 | "dependencies": { 46 | "undici-types": "~6.21.0" 47 | } 48 | }, 49 | "node_modules/@types/node-fetch": { 50 | "version": "2.6.12", 51 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", 52 | "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", 53 | "dependencies": { 54 | "@types/node": "*", 55 | "form-data": "^4.0.0" 56 | } 57 | }, 58 | "node_modules/abort-controller": { 59 | "version": "3.0.0", 60 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 61 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 62 | "dependencies": { 63 | "event-target-shim": "^5.0.0" 64 | }, 65 | "engines": { 66 | "node": ">=6.5" 67 | } 68 | }, 69 | "node_modules/accepts": { 70 | "version": "2.0.0", 71 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", 72 | "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", 73 | "dependencies": { 74 | "mime-types": "^3.0.0", 75 | "negotiator": "^1.0.0" 76 | }, 77 | "engines": { 78 | "node": ">= 0.6" 79 | } 80 | }, 81 | "node_modules/agentkeepalive": { 82 | "version": "4.6.0", 83 | "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", 84 | "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", 85 | "dependencies": { 86 | "humanize-ms": "^1.2.1" 87 | }, 88 | "engines": { 89 | "node": ">= 8.0.0" 90 | } 91 | }, 92 | "node_modules/asynckit": { 93 | "version": "0.4.0", 94 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 95 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 96 | }, 97 | "node_modules/body-parser": { 98 | "version": "2.2.0", 99 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", 100 | "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", 101 | "dependencies": { 102 | "bytes": "^3.1.2", 103 | "content-type": "^1.0.5", 104 | "debug": "^4.4.0", 105 | "http-errors": "^2.0.0", 106 | "iconv-lite": "^0.6.3", 107 | "on-finished": "^2.4.1", 108 | "qs": "^6.14.0", 109 | "raw-body": "^3.0.0", 110 | "type-is": "^2.0.0" 111 | }, 112 | "engines": { 113 | "node": ">=18" 114 | } 115 | }, 116 | "node_modules/bytes": { 117 | "version": "3.1.2", 118 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 119 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 120 | "engines": { 121 | "node": ">= 0.8" 122 | } 123 | }, 124 | "node_modules/call-bind-apply-helpers": { 125 | "version": "1.0.2", 126 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 127 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 128 | "dependencies": { 129 | "es-errors": "^1.3.0", 130 | "function-bind": "^1.1.2" 131 | }, 132 | "engines": { 133 | "node": ">= 0.4" 134 | } 135 | }, 136 | "node_modules/call-bound": { 137 | "version": "1.0.4", 138 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 139 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 140 | "dependencies": { 141 | "call-bind-apply-helpers": "^1.0.2", 142 | "get-intrinsic": "^1.3.0" 143 | }, 144 | "engines": { 145 | "node": ">= 0.4" 146 | }, 147 | "funding": { 148 | "url": "https://github.com/sponsors/ljharb" 149 | } 150 | }, 151 | "node_modules/combined-stream": { 152 | "version": "1.0.8", 153 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 154 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 155 | "dependencies": { 156 | "delayed-stream": "~1.0.0" 157 | }, 158 | "engines": { 159 | "node": ">= 0.8" 160 | } 161 | }, 162 | "node_modules/content-disposition": { 163 | "version": "1.0.0", 164 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", 165 | "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", 166 | "dependencies": { 167 | "safe-buffer": "5.2.1" 168 | }, 169 | "engines": { 170 | "node": ">= 0.6" 171 | } 172 | }, 173 | "node_modules/content-type": { 174 | "version": "1.0.5", 175 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 176 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 177 | "engines": { 178 | "node": ">= 0.6" 179 | } 180 | }, 181 | "node_modules/cookie": { 182 | "version": "0.7.2", 183 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", 184 | "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", 185 | "engines": { 186 | "node": ">= 0.6" 187 | } 188 | }, 189 | "node_modules/cookie-signature": { 190 | "version": "1.2.2", 191 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", 192 | "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", 193 | "engines": { 194 | "node": ">=6.6.0" 195 | } 196 | }, 197 | "node_modules/cors": { 198 | "version": "2.8.5", 199 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 200 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 201 | "dependencies": { 202 | "object-assign": "^4", 203 | "vary": "^1" 204 | }, 205 | "engines": { 206 | "node": ">= 0.10" 207 | } 208 | }, 209 | "node_modules/cross-spawn": { 210 | "version": "7.0.6", 211 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 212 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 213 | "dependencies": { 214 | "path-key": "^3.1.0", 215 | "shebang-command": "^2.0.0", 216 | "which": "^2.0.1" 217 | }, 218 | "engines": { 219 | "node": ">= 8" 220 | } 221 | }, 222 | "node_modules/debug": { 223 | "version": "4.4.0", 224 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 225 | "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 226 | "dependencies": { 227 | "ms": "^2.1.3" 228 | }, 229 | "engines": { 230 | "node": ">=6.0" 231 | }, 232 | "peerDependenciesMeta": { 233 | "supports-color": { 234 | "optional": true 235 | } 236 | } 237 | }, 238 | "node_modules/delayed-stream": { 239 | "version": "1.0.0", 240 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 241 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 242 | "engines": { 243 | "node": ">=0.4.0" 244 | } 245 | }, 246 | "node_modules/depd": { 247 | "version": "2.0.0", 248 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 249 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 250 | "engines": { 251 | "node": ">= 0.8" 252 | } 253 | }, 254 | "node_modules/dotenv": { 255 | "version": "16.5.0", 256 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", 257 | "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", 258 | "engines": { 259 | "node": ">=12" 260 | }, 261 | "funding": { 262 | "url": "https://dotenvx.com" 263 | } 264 | }, 265 | "node_modules/dunder-proto": { 266 | "version": "1.0.1", 267 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 268 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 269 | "dependencies": { 270 | "call-bind-apply-helpers": "^1.0.1", 271 | "es-errors": "^1.3.0", 272 | "gopd": "^1.2.0" 273 | }, 274 | "engines": { 275 | "node": ">= 0.4" 276 | } 277 | }, 278 | "node_modules/ee-first": { 279 | "version": "1.1.1", 280 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 281 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 282 | }, 283 | "node_modules/encodeurl": { 284 | "version": "2.0.0", 285 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 286 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 287 | "engines": { 288 | "node": ">= 0.8" 289 | } 290 | }, 291 | "node_modules/es-define-property": { 292 | "version": "1.0.1", 293 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 294 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 295 | "engines": { 296 | "node": ">= 0.4" 297 | } 298 | }, 299 | "node_modules/es-errors": { 300 | "version": "1.3.0", 301 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 302 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 303 | "engines": { 304 | "node": ">= 0.4" 305 | } 306 | }, 307 | "node_modules/es-object-atoms": { 308 | "version": "1.1.1", 309 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 310 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 311 | "dependencies": { 312 | "es-errors": "^1.3.0" 313 | }, 314 | "engines": { 315 | "node": ">= 0.4" 316 | } 317 | }, 318 | "node_modules/es-set-tostringtag": { 319 | "version": "2.1.0", 320 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", 321 | "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", 322 | "dependencies": { 323 | "es-errors": "^1.3.0", 324 | "get-intrinsic": "^1.2.6", 325 | "has-tostringtag": "^1.0.2", 326 | "hasown": "^2.0.2" 327 | }, 328 | "engines": { 329 | "node": ">= 0.4" 330 | } 331 | }, 332 | "node_modules/escape-html": { 333 | "version": "1.0.3", 334 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 335 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 336 | }, 337 | "node_modules/etag": { 338 | "version": "1.8.1", 339 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 340 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 341 | "engines": { 342 | "node": ">= 0.6" 343 | } 344 | }, 345 | "node_modules/event-target-shim": { 346 | "version": "5.0.1", 347 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 348 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", 349 | "engines": { 350 | "node": ">=6" 351 | } 352 | }, 353 | "node_modules/eventsource": { 354 | "version": "3.0.6", 355 | "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.6.tgz", 356 | "integrity": "sha512-l19WpE2m9hSuyP06+FbuUUf1G+R0SFLrtQfbRb9PRr+oimOfxQhgGCbVaXg5IvZyyTThJsxh6L/srkMiCeBPDA==", 357 | "dependencies": { 358 | "eventsource-parser": "^3.0.1" 359 | }, 360 | "engines": { 361 | "node": ">=18.0.0" 362 | } 363 | }, 364 | "node_modules/eventsource-parser": { 365 | "version": "3.0.1", 366 | "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz", 367 | "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", 368 | "engines": { 369 | "node": ">=18.0.0" 370 | } 371 | }, 372 | "node_modules/express": { 373 | "version": "5.1.0", 374 | "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", 375 | "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", 376 | "dependencies": { 377 | "accepts": "^2.0.0", 378 | "body-parser": "^2.2.0", 379 | "content-disposition": "^1.0.0", 380 | "content-type": "^1.0.5", 381 | "cookie": "^0.7.1", 382 | "cookie-signature": "^1.2.1", 383 | "debug": "^4.4.0", 384 | "encodeurl": "^2.0.0", 385 | "escape-html": "^1.0.3", 386 | "etag": "^1.8.1", 387 | "finalhandler": "^2.1.0", 388 | "fresh": "^2.0.0", 389 | "http-errors": "^2.0.0", 390 | "merge-descriptors": "^2.0.0", 391 | "mime-types": "^3.0.0", 392 | "on-finished": "^2.4.1", 393 | "once": "^1.4.0", 394 | "parseurl": "^1.3.3", 395 | "proxy-addr": "^2.0.7", 396 | "qs": "^6.14.0", 397 | "range-parser": "^1.2.1", 398 | "router": "^2.2.0", 399 | "send": "^1.1.0", 400 | "serve-static": "^2.2.0", 401 | "statuses": "^2.0.1", 402 | "type-is": "^2.0.1", 403 | "vary": "^1.1.2" 404 | }, 405 | "engines": { 406 | "node": ">= 18" 407 | }, 408 | "funding": { 409 | "type": "opencollective", 410 | "url": "https://opencollective.com/express" 411 | } 412 | }, 413 | "node_modules/express-rate-limit": { 414 | "version": "7.5.0", 415 | "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", 416 | "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", 417 | "engines": { 418 | "node": ">= 16" 419 | }, 420 | "funding": { 421 | "url": "https://github.com/sponsors/express-rate-limit" 422 | }, 423 | "peerDependencies": { 424 | "express": "^4.11 || 5 || ^5.0.0-beta.1" 425 | } 426 | }, 427 | "node_modules/finalhandler": { 428 | "version": "2.1.0", 429 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", 430 | "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", 431 | "dependencies": { 432 | "debug": "^4.4.0", 433 | "encodeurl": "^2.0.0", 434 | "escape-html": "^1.0.3", 435 | "on-finished": "^2.4.1", 436 | "parseurl": "^1.3.3", 437 | "statuses": "^2.0.1" 438 | }, 439 | "engines": { 440 | "node": ">= 0.8" 441 | } 442 | }, 443 | "node_modules/form-data": { 444 | "version": "4.0.2", 445 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", 446 | "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", 447 | "dependencies": { 448 | "asynckit": "^0.4.0", 449 | "combined-stream": "^1.0.8", 450 | "es-set-tostringtag": "^2.1.0", 451 | "mime-types": "^2.1.12" 452 | }, 453 | "engines": { 454 | "node": ">= 6" 455 | } 456 | }, 457 | "node_modules/form-data-encoder": { 458 | "version": "1.7.2", 459 | "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", 460 | "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" 461 | }, 462 | "node_modules/form-data/node_modules/mime-db": { 463 | "version": "1.52.0", 464 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 465 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 466 | "engines": { 467 | "node": ">= 0.6" 468 | } 469 | }, 470 | "node_modules/form-data/node_modules/mime-types": { 471 | "version": "2.1.35", 472 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 473 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 474 | "dependencies": { 475 | "mime-db": "1.52.0" 476 | }, 477 | "engines": { 478 | "node": ">= 0.6" 479 | } 480 | }, 481 | "node_modules/formdata-node": { 482 | "version": "4.4.1", 483 | "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", 484 | "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", 485 | "dependencies": { 486 | "node-domexception": "1.0.0", 487 | "web-streams-polyfill": "4.0.0-beta.3" 488 | }, 489 | "engines": { 490 | "node": ">= 12.20" 491 | } 492 | }, 493 | "node_modules/forwarded": { 494 | "version": "0.2.0", 495 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 496 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 497 | "engines": { 498 | "node": ">= 0.6" 499 | } 500 | }, 501 | "node_modules/fresh": { 502 | "version": "2.0.0", 503 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", 504 | "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", 505 | "engines": { 506 | "node": ">= 0.8" 507 | } 508 | }, 509 | "node_modules/function-bind": { 510 | "version": "1.1.2", 511 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 512 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 513 | "funding": { 514 | "url": "https://github.com/sponsors/ljharb" 515 | } 516 | }, 517 | "node_modules/get-intrinsic": { 518 | "version": "1.3.0", 519 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 520 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 521 | "dependencies": { 522 | "call-bind-apply-helpers": "^1.0.2", 523 | "es-define-property": "^1.0.1", 524 | "es-errors": "^1.3.0", 525 | "es-object-atoms": "^1.1.1", 526 | "function-bind": "^1.1.2", 527 | "get-proto": "^1.0.1", 528 | "gopd": "^1.2.0", 529 | "has-symbols": "^1.1.0", 530 | "hasown": "^2.0.2", 531 | "math-intrinsics": "^1.1.0" 532 | }, 533 | "engines": { 534 | "node": ">= 0.4" 535 | }, 536 | "funding": { 537 | "url": "https://github.com/sponsors/ljharb" 538 | } 539 | }, 540 | "node_modules/get-proto": { 541 | "version": "1.0.1", 542 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 543 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 544 | "dependencies": { 545 | "dunder-proto": "^1.0.1", 546 | "es-object-atoms": "^1.0.0" 547 | }, 548 | "engines": { 549 | "node": ">= 0.4" 550 | } 551 | }, 552 | "node_modules/gopd": { 553 | "version": "1.2.0", 554 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 555 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 556 | "engines": { 557 | "node": ">= 0.4" 558 | }, 559 | "funding": { 560 | "url": "https://github.com/sponsors/ljharb" 561 | } 562 | }, 563 | "node_modules/has-symbols": { 564 | "version": "1.1.0", 565 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 566 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 567 | "engines": { 568 | "node": ">= 0.4" 569 | }, 570 | "funding": { 571 | "url": "https://github.com/sponsors/ljharb" 572 | } 573 | }, 574 | "node_modules/has-tostringtag": { 575 | "version": "1.0.2", 576 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", 577 | "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", 578 | "dependencies": { 579 | "has-symbols": "^1.0.3" 580 | }, 581 | "engines": { 582 | "node": ">= 0.4" 583 | }, 584 | "funding": { 585 | "url": "https://github.com/sponsors/ljharb" 586 | } 587 | }, 588 | "node_modules/hasown": { 589 | "version": "2.0.2", 590 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 591 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 592 | "dependencies": { 593 | "function-bind": "^1.1.2" 594 | }, 595 | "engines": { 596 | "node": ">= 0.4" 597 | } 598 | }, 599 | "node_modules/http-errors": { 600 | "version": "2.0.0", 601 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 602 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 603 | "dependencies": { 604 | "depd": "2.0.0", 605 | "inherits": "2.0.4", 606 | "setprototypeof": "1.2.0", 607 | "statuses": "2.0.1", 608 | "toidentifier": "1.0.1" 609 | }, 610 | "engines": { 611 | "node": ">= 0.8" 612 | } 613 | }, 614 | "node_modules/humanize-ms": { 615 | "version": "1.2.1", 616 | "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", 617 | "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", 618 | "dependencies": { 619 | "ms": "^2.0.0" 620 | } 621 | }, 622 | "node_modules/iconv-lite": { 623 | "version": "0.6.3", 624 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 625 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 626 | "dependencies": { 627 | "safer-buffer": ">= 2.1.2 < 3.0.0" 628 | }, 629 | "engines": { 630 | "node": ">=0.10.0" 631 | } 632 | }, 633 | "node_modules/inherits": { 634 | "version": "2.0.4", 635 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 636 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 637 | }, 638 | "node_modules/ipaddr.js": { 639 | "version": "1.9.1", 640 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 641 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 642 | "engines": { 643 | "node": ">= 0.10" 644 | } 645 | }, 646 | "node_modules/is-promise": { 647 | "version": "4.0.0", 648 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 649 | "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" 650 | }, 651 | "node_modules/isexe": { 652 | "version": "2.0.0", 653 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 654 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" 655 | }, 656 | "node_modules/math-intrinsics": { 657 | "version": "1.1.0", 658 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 659 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 660 | "engines": { 661 | "node": ">= 0.4" 662 | } 663 | }, 664 | "node_modules/media-typer": { 665 | "version": "1.1.0", 666 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", 667 | "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", 668 | "engines": { 669 | "node": ">= 0.8" 670 | } 671 | }, 672 | "node_modules/merge-descriptors": { 673 | "version": "2.0.0", 674 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", 675 | "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", 676 | "engines": { 677 | "node": ">=18" 678 | }, 679 | "funding": { 680 | "url": "https://github.com/sponsors/sindresorhus" 681 | } 682 | }, 683 | "node_modules/mime-db": { 684 | "version": "1.54.0", 685 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 686 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 687 | "engines": { 688 | "node": ">= 0.6" 689 | } 690 | }, 691 | "node_modules/mime-types": { 692 | "version": "3.0.1", 693 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", 694 | "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", 695 | "dependencies": { 696 | "mime-db": "^1.54.0" 697 | }, 698 | "engines": { 699 | "node": ">= 0.6" 700 | } 701 | }, 702 | "node_modules/ms": { 703 | "version": "2.1.3", 704 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 705 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 706 | }, 707 | "node_modules/negotiator": { 708 | "version": "1.0.0", 709 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", 710 | "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", 711 | "engines": { 712 | "node": ">= 0.6" 713 | } 714 | }, 715 | "node_modules/node-domexception": { 716 | "version": "1.0.0", 717 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 718 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 719 | "funding": [ 720 | { 721 | "type": "github", 722 | "url": "https://github.com/sponsors/jimmywarting" 723 | }, 724 | { 725 | "type": "github", 726 | "url": "https://paypal.me/jimmywarting" 727 | } 728 | ], 729 | "engines": { 730 | "node": ">=10.5.0" 731 | } 732 | }, 733 | "node_modules/node-fetch": { 734 | "version": "2.7.0", 735 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 736 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 737 | "dependencies": { 738 | "whatwg-url": "^5.0.0" 739 | }, 740 | "engines": { 741 | "node": "4.x || >=6.0.0" 742 | }, 743 | "peerDependencies": { 744 | "encoding": "^0.1.0" 745 | }, 746 | "peerDependenciesMeta": { 747 | "encoding": { 748 | "optional": true 749 | } 750 | } 751 | }, 752 | "node_modules/object-assign": { 753 | "version": "4.1.1", 754 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 755 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 756 | "engines": { 757 | "node": ">=0.10.0" 758 | } 759 | }, 760 | "node_modules/object-inspect": { 761 | "version": "1.13.4", 762 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 763 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 764 | "engines": { 765 | "node": ">= 0.4" 766 | }, 767 | "funding": { 768 | "url": "https://github.com/sponsors/ljharb" 769 | } 770 | }, 771 | "node_modules/on-finished": { 772 | "version": "2.4.1", 773 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 774 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 775 | "dependencies": { 776 | "ee-first": "1.1.1" 777 | }, 778 | "engines": { 779 | "node": ">= 0.8" 780 | } 781 | }, 782 | "node_modules/once": { 783 | "version": "1.4.0", 784 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 785 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 786 | "dependencies": { 787 | "wrappy": "1" 788 | } 789 | }, 790 | "node_modules/openai": { 791 | "version": "4.93.0", 792 | "resolved": "https://registry.npmjs.org/openai/-/openai-4.93.0.tgz", 793 | "integrity": "sha512-2kONcISbThKLfm7T9paVzg+QCE1FOZtNMMUfXyXckUAoXRRS/mTP89JSDHPMp8uM5s0bz28RISbvQjArD6mgUQ==", 794 | "dependencies": { 795 | "@types/node": "^18.11.18", 796 | "@types/node-fetch": "^2.6.4", 797 | "abort-controller": "^3.0.0", 798 | "agentkeepalive": "^4.2.1", 799 | "form-data-encoder": "1.7.2", 800 | "formdata-node": "^4.3.2", 801 | "node-fetch": "^2.6.7" 802 | }, 803 | "bin": { 804 | "openai": "bin/cli" 805 | }, 806 | "peerDependencies": { 807 | "ws": "^8.18.0", 808 | "zod": "^3.23.8" 809 | }, 810 | "peerDependenciesMeta": { 811 | "ws": { 812 | "optional": true 813 | }, 814 | "zod": { 815 | "optional": true 816 | } 817 | } 818 | }, 819 | "node_modules/openai/node_modules/@types/node": { 820 | "version": "18.19.86", 821 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.86.tgz", 822 | "integrity": "sha512-fifKayi175wLyKyc5qUfyENhQ1dCNI1UNjp653d8kuYcPQN5JhX3dGuP/XmvPTg/xRBn1VTLpbmi+H/Mr7tLfQ==", 823 | "dependencies": { 824 | "undici-types": "~5.26.4" 825 | } 826 | }, 827 | "node_modules/openai/node_modules/undici-types": { 828 | "version": "5.26.5", 829 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 830 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" 831 | }, 832 | "node_modules/parseurl": { 833 | "version": "1.3.3", 834 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 835 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 836 | "engines": { 837 | "node": ">= 0.8" 838 | } 839 | }, 840 | "node_modules/path-key": { 841 | "version": "3.1.1", 842 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 843 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 844 | "engines": { 845 | "node": ">=8" 846 | } 847 | }, 848 | "node_modules/path-to-regexp": { 849 | "version": "8.2.0", 850 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", 851 | "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", 852 | "engines": { 853 | "node": ">=16" 854 | } 855 | }, 856 | "node_modules/pkce-challenge": { 857 | "version": "5.0.0", 858 | "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", 859 | "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", 860 | "engines": { 861 | "node": ">=16.20.0" 862 | } 863 | }, 864 | "node_modules/proxy-addr": { 865 | "version": "2.0.7", 866 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 867 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 868 | "dependencies": { 869 | "forwarded": "0.2.0", 870 | "ipaddr.js": "1.9.1" 871 | }, 872 | "engines": { 873 | "node": ">= 0.10" 874 | } 875 | }, 876 | "node_modules/qs": { 877 | "version": "6.14.0", 878 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", 879 | "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", 880 | "dependencies": { 881 | "side-channel": "^1.1.0" 882 | }, 883 | "engines": { 884 | "node": ">=0.6" 885 | }, 886 | "funding": { 887 | "url": "https://github.com/sponsors/ljharb" 888 | } 889 | }, 890 | "node_modules/range-parser": { 891 | "version": "1.2.1", 892 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 893 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 894 | "engines": { 895 | "node": ">= 0.6" 896 | } 897 | }, 898 | "node_modules/raw-body": { 899 | "version": "3.0.0", 900 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 901 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 902 | "dependencies": { 903 | "bytes": "3.1.2", 904 | "http-errors": "2.0.0", 905 | "iconv-lite": "0.6.3", 906 | "unpipe": "1.0.0" 907 | }, 908 | "engines": { 909 | "node": ">= 0.8" 910 | } 911 | }, 912 | "node_modules/router": { 913 | "version": "2.2.0", 914 | "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", 915 | "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", 916 | "dependencies": { 917 | "debug": "^4.4.0", 918 | "depd": "^2.0.0", 919 | "is-promise": "^4.0.0", 920 | "parseurl": "^1.3.3", 921 | "path-to-regexp": "^8.0.0" 922 | }, 923 | "engines": { 924 | "node": ">= 18" 925 | } 926 | }, 927 | "node_modules/safe-buffer": { 928 | "version": "5.2.1", 929 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 930 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 931 | "funding": [ 932 | { 933 | "type": "github", 934 | "url": "https://github.com/sponsors/feross" 935 | }, 936 | { 937 | "type": "patreon", 938 | "url": "https://www.patreon.com/feross" 939 | }, 940 | { 941 | "type": "consulting", 942 | "url": "https://feross.org/support" 943 | } 944 | ] 945 | }, 946 | "node_modules/safer-buffer": { 947 | "version": "2.1.2", 948 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 949 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 950 | }, 951 | "node_modules/send": { 952 | "version": "1.2.0", 953 | "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", 954 | "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", 955 | "dependencies": { 956 | "debug": "^4.3.5", 957 | "encodeurl": "^2.0.0", 958 | "escape-html": "^1.0.3", 959 | "etag": "^1.8.1", 960 | "fresh": "^2.0.0", 961 | "http-errors": "^2.0.0", 962 | "mime-types": "^3.0.1", 963 | "ms": "^2.1.3", 964 | "on-finished": "^2.4.1", 965 | "range-parser": "^1.2.1", 966 | "statuses": "^2.0.1" 967 | }, 968 | "engines": { 969 | "node": ">= 18" 970 | } 971 | }, 972 | "node_modules/serve-static": { 973 | "version": "2.2.0", 974 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", 975 | "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", 976 | "dependencies": { 977 | "encodeurl": "^2.0.0", 978 | "escape-html": "^1.0.3", 979 | "parseurl": "^1.3.3", 980 | "send": "^1.2.0" 981 | }, 982 | "engines": { 983 | "node": ">= 18" 984 | } 985 | }, 986 | "node_modules/setprototypeof": { 987 | "version": "1.2.0", 988 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 989 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 990 | }, 991 | "node_modules/shebang-command": { 992 | "version": "2.0.0", 993 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 994 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 995 | "dependencies": { 996 | "shebang-regex": "^3.0.0" 997 | }, 998 | "engines": { 999 | "node": ">=8" 1000 | } 1001 | }, 1002 | "node_modules/shebang-regex": { 1003 | "version": "3.0.0", 1004 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1005 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1006 | "engines": { 1007 | "node": ">=8" 1008 | } 1009 | }, 1010 | "node_modules/side-channel": { 1011 | "version": "1.1.0", 1012 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 1013 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 1014 | "dependencies": { 1015 | "es-errors": "^1.3.0", 1016 | "object-inspect": "^1.13.3", 1017 | "side-channel-list": "^1.0.0", 1018 | "side-channel-map": "^1.0.1", 1019 | "side-channel-weakmap": "^1.0.2" 1020 | }, 1021 | "engines": { 1022 | "node": ">= 0.4" 1023 | }, 1024 | "funding": { 1025 | "url": "https://github.com/sponsors/ljharb" 1026 | } 1027 | }, 1028 | "node_modules/side-channel-list": { 1029 | "version": "1.0.0", 1030 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 1031 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1032 | "dependencies": { 1033 | "es-errors": "^1.3.0", 1034 | "object-inspect": "^1.13.3" 1035 | }, 1036 | "engines": { 1037 | "node": ">= 0.4" 1038 | }, 1039 | "funding": { 1040 | "url": "https://github.com/sponsors/ljharb" 1041 | } 1042 | }, 1043 | "node_modules/side-channel-map": { 1044 | "version": "1.0.1", 1045 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 1046 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 1047 | "dependencies": { 1048 | "call-bound": "^1.0.2", 1049 | "es-errors": "^1.3.0", 1050 | "get-intrinsic": "^1.2.5", 1051 | "object-inspect": "^1.13.3" 1052 | }, 1053 | "engines": { 1054 | "node": ">= 0.4" 1055 | }, 1056 | "funding": { 1057 | "url": "https://github.com/sponsors/ljharb" 1058 | } 1059 | }, 1060 | "node_modules/side-channel-weakmap": { 1061 | "version": "1.0.2", 1062 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 1063 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1064 | "dependencies": { 1065 | "call-bound": "^1.0.2", 1066 | "es-errors": "^1.3.0", 1067 | "get-intrinsic": "^1.2.5", 1068 | "object-inspect": "^1.13.3", 1069 | "side-channel-map": "^1.0.1" 1070 | }, 1071 | "engines": { 1072 | "node": ">= 0.4" 1073 | }, 1074 | "funding": { 1075 | "url": "https://github.com/sponsors/ljharb" 1076 | } 1077 | }, 1078 | "node_modules/statuses": { 1079 | "version": "2.0.1", 1080 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1081 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1082 | "engines": { 1083 | "node": ">= 0.8" 1084 | } 1085 | }, 1086 | "node_modules/toidentifier": { 1087 | "version": "1.0.1", 1088 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1089 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1090 | "engines": { 1091 | "node": ">=0.6" 1092 | } 1093 | }, 1094 | "node_modules/tr46": { 1095 | "version": "0.0.3", 1096 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1097 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 1098 | }, 1099 | "node_modules/type-is": { 1100 | "version": "2.0.1", 1101 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", 1102 | "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", 1103 | "dependencies": { 1104 | "content-type": "^1.0.5", 1105 | "media-typer": "^1.1.0", 1106 | "mime-types": "^3.0.0" 1107 | }, 1108 | "engines": { 1109 | "node": ">= 0.6" 1110 | } 1111 | }, 1112 | "node_modules/typescript": { 1113 | "version": "5.8.3", 1114 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", 1115 | "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", 1116 | "dev": true, 1117 | "bin": { 1118 | "tsc": "bin/tsc", 1119 | "tsserver": "bin/tsserver" 1120 | }, 1121 | "engines": { 1122 | "node": ">=14.17" 1123 | } 1124 | }, 1125 | "node_modules/undici-types": { 1126 | "version": "6.21.0", 1127 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", 1128 | "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" 1129 | }, 1130 | "node_modules/unpipe": { 1131 | "version": "1.0.0", 1132 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1133 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1134 | "engines": { 1135 | "node": ">= 0.8" 1136 | } 1137 | }, 1138 | "node_modules/vary": { 1139 | "version": "1.1.2", 1140 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1141 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1142 | "engines": { 1143 | "node": ">= 0.8" 1144 | } 1145 | }, 1146 | "node_modules/web-streams-polyfill": { 1147 | "version": "4.0.0-beta.3", 1148 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", 1149 | "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", 1150 | "engines": { 1151 | "node": ">= 14" 1152 | } 1153 | }, 1154 | "node_modules/webidl-conversions": { 1155 | "version": "3.0.1", 1156 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 1157 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 1158 | }, 1159 | "node_modules/whatwg-url": { 1160 | "version": "5.0.0", 1161 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 1162 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 1163 | "dependencies": { 1164 | "tr46": "~0.0.3", 1165 | "webidl-conversions": "^3.0.0" 1166 | } 1167 | }, 1168 | "node_modules/which": { 1169 | "version": "2.0.2", 1170 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1171 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1172 | "dependencies": { 1173 | "isexe": "^2.0.0" 1174 | }, 1175 | "bin": { 1176 | "node-which": "bin/node-which" 1177 | }, 1178 | "engines": { 1179 | "node": ">= 8" 1180 | } 1181 | }, 1182 | "node_modules/wrappy": { 1183 | "version": "1.0.2", 1184 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1185 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 1186 | }, 1187 | "node_modules/zod": { 1188 | "version": "3.24.2", 1189 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", 1190 | "integrity": "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==", 1191 | "funding": { 1192 | "url": "https://github.com/sponsors/colinhacks" 1193 | } 1194 | }, 1195 | "node_modules/zod-to-json-schema": { 1196 | "version": "3.24.5", 1197 | "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", 1198 | "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", 1199 | "peerDependencies": { 1200 | "zod": "^3.24.1" 1201 | } 1202 | } 1203 | } 1204 | } 1205 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mcp-client-nodejs", 3 | "version": "1.0.2", 4 | "description": "Node.js MCP Client 实现,基于 Model Context Protocol", 5 | "type": "module", 6 | "main": "build/index.js", 7 | "bin": { 8 | "mcp-client-nodejs": "./build/index.js" 9 | }, 10 | "scripts": { 11 | "build": "tsc && chmod 755 build/index.js", 12 | "start": "node build/index.js" 13 | }, 14 | "keywords": [ 15 | "mcp", 16 | "model-context-protocol", 17 | "llm", 18 | "client" 19 | ], 20 | "homepage": "https://github.com/ConardLi/mcp-client-nodejs", 21 | "author": "ConardLi", 22 | "license": "Apache License 2.0", 23 | "dependencies": { 24 | "@modelcontextprotocol/sdk": "^1.9.0", 25 | "dotenv": "^16.5.0", 26 | "openai": "^4.93.0" 27 | }, 28 | "devDependencies": { 29 | "@types/node": "^22.14.0", 30 | "typescript": "^5.8.3" 31 | } 32 | } -------------------------------------------------------------------------------- /public/arc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConardLi/mcp-client-nodejs/e363b03f3dcc9e66e0c2217adb96149885166f95/public/arc.png -------------------------------------------------------------------------------- /public/code-arc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConardLi/mcp-client-nodejs/e363b03f3dcc9e66e0c2217adb96149885166f95/public/code-arc.png -------------------------------------------------------------------------------- /public/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConardLi/mcp-client-nodejs/e363b03f3dcc9e66e0c2217adb96149885166f95/public/demo.png -------------------------------------------------------------------------------- /public/logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConardLi/mcp-client-nodejs/e363b03f3dcc9e66e0c2217adb96149885166f95/public/logs.png -------------------------------------------------------------------------------- /public/time-arc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConardLi/mcp-client-nodejs/e363b03f3dcc9e66e0c2217adb96149885166f95/public/time-arc.png -------------------------------------------------------------------------------- /public/time-arc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConardLi/mcp-client-nodejs/e363b03f3dcc9e66e0c2217adb96149885166f95/public/time-arc2.png -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * MCP Client 主入口文件 5 | * 6 | * 这是一个基于Model Context Protocol的客户端实现,使用OpenAI API 7 | * 用于连接MCP服务器并提供LLM对话能力 8 | */ 9 | 10 | // 显式引用Node.js类型以解决process相关问题 11 | /// 12 | 13 | import { MCPClient } from "./mcpClient.js"; 14 | import { validateEnv } from "./utils/config.js"; 15 | import { clearLogs } from "./utils/log.js"; 16 | 17 | /** 18 | * 显示使用说明 19 | */ 20 | function showUsage(): void { 21 | console.log("====================================================="); 22 | console.log("MCP Client - 模型上下文协议客户端"); 23 | console.log("====================================================="); 24 | console.log("基本用法:"); 25 | console.log(" node build/index.js <服务器脚本路径>"); 26 | console.log("使用配置文件:"); 27 | console.log(" node build/index.js <服务器名称> <配置文件路径>"); 28 | console.log("示例:"); 29 | console.log(" node build/index.js ../mcp-server/build/index.js"); 30 | console.log(" node build/index.js memory ./mcp-servers.json"); 31 | console.log(" node build/index.js default ./mcp-servers.json"); 32 | console.log("====================================================="); 33 | } 34 | 35 | /** 36 | * 主函数 37 | * 处理命令行参数并启动MCP客户端 38 | */ 39 | async function main() { 40 | // 清空日志目录 41 | clearLogs(); 42 | 43 | try { 44 | // 验证环境变量 45 | validateEnv(); 46 | 47 | // 检查命令行参数 48 | if (process.argv.length < 3) { 49 | showUsage(); 50 | return; 51 | } 52 | 53 | const serverIdentifier = process.argv[2]; 54 | const configPath = process.argv.length >= 4 ? process.argv[3] : undefined; 55 | 56 | // 创建MCP客户端实例 57 | const mcpClient = new MCPClient(); 58 | 59 | try { 60 | // 连接到MCP服务器 61 | if (configPath) { 62 | console.log( 63 | `正在连接到服务器: ${serverIdentifier} (使用配置文件: ${configPath})` 64 | ); 65 | } else { 66 | console.log(`正在连接到服务器: ${serverIdentifier}`); 67 | } 68 | await mcpClient.connectToServer(serverIdentifier, configPath); 69 | 70 | // 启动交互式聊天循环 71 | await mcpClient.chatLoop(); 72 | } catch (error) { 73 | console.error("\n运行MCP客户端时出错:", error); 74 | } finally { 75 | // 确保资源被清理 76 | await mcpClient.cleanup(); 77 | } 78 | } catch (error) { 79 | console.error("初始化MCP客户端失败:", error); 80 | } finally { 81 | process.exit(0); 82 | } 83 | } 84 | 85 | // 执行主函数 86 | main(); 87 | -------------------------------------------------------------------------------- /src/mcpClient.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MCP客户端实现类 3 | * 4 | * 该类负责管理MCP服务器连接、处理工具调用和实现聊天逻辑 5 | * 基于Model Context Protocol (MCP),使用OpenAI API实现对话功能 6 | */ 7 | 8 | import { Client } from "@modelcontextprotocol/sdk/client/index.js"; 9 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; 10 | import readline from "readline/promises"; 11 | import OpenAI from "openai"; 12 | 13 | // 导入服务和工具 14 | import { LLMService } from "./services/LLMService.js"; 15 | import { ToolService } from "./services/ToolService.js"; 16 | import { 17 | defaultConfig, 18 | getApiKey, 19 | getModelName, 20 | getBaseURL, 21 | } from "./utils/config.js"; 22 | import { OpenAITool } from "./types/index.js"; 23 | 24 | /** 25 | * MCP客户端类 26 | * 负责连接服务器、处理查询和工具调用,是应用程序的核心组件 27 | */ 28 | export class MCPClient { 29 | private mcp: Client; // MCP客户端 30 | private transport: StdioClientTransport | null = null; // 传输层 31 | private tools: OpenAITool[] = []; // 可用工具列表 32 | private llmService: LLMService; // LLM服务 33 | private toolService: ToolService; // 工具服务 34 | private systemPrompt?: string; // 系统提示词,从配置文件中读取 35 | private messages: Array = []; // 对话消息历史 36 | 37 | /** 38 | * 构造函数 39 | * 初始化服务和客户端 40 | */ 41 | constructor() { 42 | // 初始化MCP客户端 43 | this.mcp = new Client({ 44 | name: defaultConfig.clientName, 45 | version: defaultConfig.clientVersion, 46 | }); 47 | 48 | // 初始化LLM服务 49 | this.llmService = new LLMService(getApiKey(), getModelName(), getBaseURL()); 50 | 51 | // 初始化工具服务 52 | this.toolService = new ToolService(this.mcp); 53 | } 54 | 55 | /** 56 | * 连接到MCP服务器 57 | * @param serverIdentifier 服务器标识符(脚本路径或配置中的服务器名称) 58 | * @param configPath 可选,MCP服务器配置文件路径 59 | */ 60 | /** 61 | * 从脚本路径获取传输层选项 62 | * @param scriptPath 脚本路径 63 | * @returns 传输层选项 64 | * @private 65 | */ 66 | private getTransportOptionsForScript(scriptPath: string): { 67 | command: string; 68 | args: string[]; 69 | env?: Record; 70 | } { 71 | // 检查脚本类型 72 | const isJs = scriptPath.endsWith(".js"); 73 | const isPy = scriptPath.endsWith(".py"); 74 | 75 | if (!isJs && !isPy) { 76 | console.warn("警告: 服务器脚本没有.js或.py扩展名,将尝试使用Node.js运行"); 77 | } 78 | 79 | // 根据脚本类型确定命令 80 | const command = isPy 81 | ? process.platform === "win32" 82 | ? "python" 83 | : "python3" 84 | : process.execPath; 85 | 86 | return { 87 | command, 88 | args: [scriptPath], 89 | }; 90 | } 91 | 92 | async connectToServer( 93 | serverIdentifier: string, 94 | configPath?: string 95 | ): Promise { 96 | try { 97 | // 创建传输层参数 98 | let transportOptions: { 99 | command: string; 100 | args: string[]; 101 | env?: Record; 102 | }; 103 | 104 | // 如果提供了配置文件路径,从配置文件加载服务器设置 105 | if (configPath) { 106 | try { 107 | // 使用fs的promise API代替require 108 | const fs = await import("fs/promises"); 109 | const configContent = await fs.readFile(configPath, "utf8"); 110 | const config = JSON.parse(configContent); 111 | 112 | // 读取系统提示词(如果有) 113 | this.systemPrompt = config.system; 114 | this.messages.push({ 115 | role: "system", 116 | content: this.systemPrompt || "", 117 | }); 118 | 119 | // 检查服务器标识符是否存在于配置中 120 | if (config.mcpServers && config.mcpServers[serverIdentifier]) { 121 | const serverConfig = config.mcpServers[serverIdentifier]; 122 | transportOptions = { 123 | command: serverConfig.command, 124 | args: serverConfig.args || [], 125 | env: serverConfig.env, 126 | }; 127 | console.log(`使用配置文件启动服务器: ${serverIdentifier}`); 128 | } else if ( 129 | serverIdentifier === "default" && 130 | config.defaultServer && 131 | config.mcpServers[config.defaultServer] 132 | ) { 133 | // 使用默认服务器 134 | const defaultServerName = config.defaultServer; 135 | const serverConfig = config.mcpServers[defaultServerName]; 136 | transportOptions = { 137 | command: serverConfig.command, 138 | args: serverConfig.args || [], 139 | env: serverConfig.env, 140 | }; 141 | console.log(`使用默认服务器: ${defaultServerName}`); 142 | } else { 143 | // 如果指定的服务器不在配置中,打印错误消息 144 | throw new Error(`在配置文件中未找到服务器 ${serverIdentifier}`); 145 | } 146 | } catch (error) { 147 | console.error( 148 | `读取配置文件错误: ${ 149 | error instanceof Error ? error.message : String(error) 150 | }` 151 | ); 152 | // 如果指定了配置文件但未找到查找未指定的服务器,应直接抛出错误 153 | throw new Error( 154 | `未能从配置文件 '${configPath}' 中加载服务器 '${serverIdentifier}'` 155 | ); 156 | } 157 | } else { 158 | // 没有提供配置文件,使用传统的脚本路径模式 159 | transportOptions = this.getTransportOptionsForScript(serverIdentifier); 160 | } 161 | 162 | // 创建传输层 163 | this.transport = new StdioClientTransport(transportOptions); 164 | 165 | // 连接到服务器 166 | this.mcp.connect(this.transport); 167 | 168 | // 获取可用工具列表 169 | this.tools = await this.toolService.getTools(); 170 | } catch (error) { 171 | console.error("连接到MCP服务器失败: ", error); 172 | throw new Error( 173 | `连接失败: ${error instanceof Error ? error.message : String(error)}` 174 | ); 175 | } 176 | } 177 | 178 | /** 179 | * 处理用户查询 180 | * @param query 用户查询文本 181 | * @returns 回复文本 182 | */ 183 | async processQuery(query: string): Promise { 184 | try { 185 | // 添加新的用户查询 186 | this.messages.push({ role: "user", content: query }); 187 | 188 | // 获取初始响应 189 | const response = await this.llmService.sendMessage( 190 | this.messages, 191 | this.tools 192 | ); 193 | 194 | // 提取回复内容 195 | const finalText: string[] = []; 196 | 197 | if (!response.choices || !response.choices[0]) { 198 | console.log("error", response); 199 | } 200 | 201 | // 获取响应消息 202 | const responseMessage = response.choices[0].message; 203 | 204 | // 添加模型回复文本 205 | if (responseMessage.content) { 206 | finalText.push(responseMessage.content); 207 | 208 | // 将简单文本回复也添加到对话历史中 209 | this.messages.push({ 210 | role: "assistant", 211 | content: responseMessage.content, 212 | }); 213 | } 214 | 215 | // 处理工具调用 216 | if (responseMessage.tool_calls && responseMessage.tool_calls.length > 0) { 217 | // 添加工具调用到消息历史 218 | this.messages.push(responseMessage); 219 | 220 | // 处理每个工具调用 221 | for (const toolCall of responseMessage.tool_calls) { 222 | if (toolCall.type === "function") { 223 | const toolName = toolCall.function.name; 224 | const toolArgs = JSON.parse(toolCall.function.arguments || "{}"); 225 | 226 | // 添加工具调用说明 227 | finalText.push( 228 | `\n[调用工具 ${toolName},参数 ${JSON.stringify( 229 | toolArgs, 230 | null, 231 | 2 232 | )}]\n` 233 | ); 234 | 235 | try { 236 | // 执行工具调用 237 | const result = await this.toolService.callTool( 238 | toolName, 239 | toolArgs 240 | ); 241 | 242 | // 将工具结果添加到消息历史 243 | // 确保 content 是字符串类型 244 | const content = 245 | typeof result.content === "string" 246 | ? result.content 247 | : JSON.stringify(result.content); 248 | 249 | this.messages.push({ 250 | role: "tool", 251 | tool_call_id: toolCall.id, 252 | content: content, 253 | }); 254 | } catch (toolError) { 255 | // 工具调用失败,将错误信息添加到消息历史 256 | this.messages.push({ 257 | role: "tool", 258 | tool_call_id: toolCall.id, 259 | content: `错误: ${ 260 | toolError instanceof Error 261 | ? toolError.message 262 | : String(toolError) 263 | }`, 264 | }); 265 | 266 | finalText.push( 267 | `[工具调用失败: ${ 268 | toolError instanceof Error 269 | ? toolError.message 270 | : String(toolError) 271 | }]` 272 | ); 273 | } 274 | } 275 | } 276 | 277 | // 获取模型对工具结果的解释 278 | try { 279 | const followupResponse = await this.llmService.sendMessage( 280 | this.messages 281 | ); 282 | 283 | const followupContent = followupResponse.choices[0].message.content; 284 | 285 | if (followupContent) { 286 | finalText.push(followupContent); 287 | 288 | // 将工具调用后的最终回复也添加到对话历史中 289 | this.messages.push({ 290 | role: "assistant", 291 | content: followupContent, 292 | }); 293 | } 294 | } catch (followupError) { 295 | finalText.push( 296 | `[获取后续响应失败: ${ 297 | followupError instanceof Error 298 | ? followupError.message 299 | : String(followupError) 300 | }]` 301 | ); 302 | } 303 | } 304 | 305 | return finalText.join("\n"); 306 | } catch (error) { 307 | console.error("处理查询失败:", error); 308 | return `处理查询时出错: ${ 309 | error instanceof Error ? error.message : String(error) 310 | }`; 311 | } 312 | } 313 | 314 | /** 315 | * 交互式聊天循环 316 | * 提供命令行交互界面 317 | */ 318 | async chatLoop(): Promise { 319 | // 创建命令行交互界面 320 | const rl = readline.createInterface({ 321 | input: process.stdin, 322 | output: process.stdout, 323 | }); 324 | 325 | try { 326 | // 显示欢迎信息 327 | console.log("\n==============================="); 328 | console.log(" MCP客户端已启动!"); 329 | console.log(" 使用模型: " + this.llmService.getModel()); 330 | console.log(" 输入您的问题或输入'quit'退出"); 331 | console.log("===============================\n"); 332 | 333 | // 主聊天循环 334 | while (true) { 335 | const message = await rl.question("\n问题: "); 336 | 337 | // 检查退出命令 338 | if (message.toLowerCase() === "quit") { 339 | console.log("感谢使用MCP客户端,再见!"); 340 | break; 341 | } 342 | 343 | // 处理空输入 344 | if (!message.trim()) { 345 | console.log("请输入有效的问题"); 346 | continue; 347 | } 348 | 349 | try { 350 | // 处理查询并显示回复 351 | console.log("\n正在思考..."); 352 | const response = await this.processQuery(message); 353 | console.log("\n回答:"); 354 | console.log(response); 355 | } catch (error) { 356 | console.error("\n处理查询失败:", error); 357 | } 358 | } 359 | } finally { 360 | // 关闭命令行界面 361 | rl.close(); 362 | } 363 | } 364 | 365 | /** 366 | * 清理资源 367 | * 在程序退出前调用,确保资源被正确释放 368 | */ 369 | async cleanup(): Promise { 370 | // 清空消息历史 371 | this.messages = []; 372 | this.messages.push({ 373 | role: "system", 374 | content: this.systemPrompt || "", 375 | }); 376 | 377 | if (this.mcp) { 378 | try { 379 | await this.mcp.close(); 380 | console.log("已断开与MCP服务器的连接"); 381 | } catch (error) { 382 | console.error("关闭MCP客户端时出错:", error); 383 | } 384 | } 385 | } 386 | } 387 | -------------------------------------------------------------------------------- /src/services/LLMService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * LLM服务模块 3 | * 负责与OpenAI API的交互 4 | */ 5 | 6 | import OpenAI from "openai"; 7 | import { OpenAITool } from "../types/index.js"; 8 | import { addLogs, logType } from "../utils/log.js"; 9 | 10 | /** 11 | * LLM服务类 12 | * 提供发送消息和处理回复的功能 13 | */ 14 | export class LLMService { 15 | private openai: OpenAI; 16 | private model: string; 17 | 18 | /** 19 | * 构造函数 20 | * @param apiKey OpenAI API密钥 21 | * @param model 使用的模型名称,默认为gpt-3.5-turbo 22 | */ 23 | constructor( 24 | apiKey: string, 25 | model: string = "gpt-3.5-turbo", 26 | baseURL: string = "" 27 | ) { 28 | // 初始化OpenAI客户端 29 | this.openai = new OpenAI({ 30 | baseURL, 31 | apiKey, 32 | }); 33 | this.model = model; 34 | } 35 | 36 | /** 37 | * 发送消息到OpenAI并获取回复 38 | * @param messages 消息历史记录 39 | * @param tools 可用的工具列表 40 | * @returns 模型回复 41 | */ 42 | async sendMessage( 43 | messages: Array, 44 | tools?: OpenAITool[] 45 | ) { 46 | try { 47 | addLogs( 48 | { 49 | model: this.model, 50 | messages, 51 | tools: tools && tools.length > 0 ? tools : undefined, 52 | tool_choice: tools && tools.length > 0 ? "auto" : undefined, 53 | }, 54 | logType.LLMRequest 55 | ); 56 | 57 | // 调用OpenAI API创建聊天回复 58 | const result = await this.openai.chat.completions.create({ 59 | model: this.model, 60 | messages, 61 | tools: tools && tools.length > 0 ? tools : undefined, 62 | tool_choice: tools && tools.length > 0 ? "auto" : undefined, 63 | }); 64 | 65 | // 将请求和响应保存到日志文件 66 | 67 | addLogs(result, logType.LLMResponse); 68 | 69 | return result; 70 | } catch (error) { 71 | addLogs(error, logType.LLMError); 72 | throw new Error( 73 | `发送消息到LLM失败: ${ 74 | error instanceof Error ? error.message : String(error) 75 | }` 76 | ); 77 | } 78 | } 79 | 80 | /** 81 | * 获取当前使用的模型名称 82 | * @returns 模型名称 83 | */ 84 | getModel(): string { 85 | return this.model; 86 | } 87 | 88 | /** 89 | * 设置使用的模型 90 | * @param model 模型名称 91 | */ 92 | setModel(model: string): void { 93 | this.model = model; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/services/ToolService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 工具服务模块 3 | * 负责处理与MCP工具相关的逻辑 4 | */ 5 | 6 | import { Client } from "@modelcontextprotocol/sdk/client/index.js"; 7 | import { MCPTool, OpenAITool, ToolCallResult } from "../types/index.js"; 8 | import { patchSchemaArrays } from "../utils/schema.js"; 9 | import { addLogs, logType } from "../utils/log.js"; 10 | 11 | /** 12 | * 工具服务类 13 | * 提供工具列表获取和工具调用功能 14 | */ 15 | export class ToolService { 16 | private client: Client; 17 | 18 | /** 19 | * 构造函数 20 | * @param client MCP客户端实例 21 | */ 22 | constructor(client: Client) { 23 | this.client = client; 24 | } 25 | 26 | /** 27 | * 获取服务器提供的工具列表 28 | * @returns 转换后的OpenAI工具格式数组 29 | */ 30 | async getTools(): Promise { 31 | try { 32 | // 从MCP服务器获取工具列表 33 | const toolsResult = await this.client.listTools(); 34 | 35 | const logInfo = toolsResult.tools.map((tool) => { 36 | return { name: tool.name, description: tool.description }; 37 | }); 38 | 39 | addLogs(logInfo, logType.GetTools); 40 | 41 | // 将MCP工具转换为OpenAI工具格式 42 | return toolsResult.tools.map((tool: MCPTool) => ({ 43 | type: "function", 44 | function: { 45 | name: tool.name, 46 | description: tool.description, 47 | parameters: patchSchemaArrays(tool.inputSchema) || {}, 48 | }, 49 | })); 50 | } catch (error) { 51 | addLogs(error, logType.GetToolsError); 52 | throw new Error( 53 | `获取工具列表失败: ${ 54 | error instanceof Error ? error.message : String(error) 55 | }` 56 | ); 57 | } 58 | } 59 | 60 | /** 61 | * 调用MCP工具 62 | * @param toolName 工具名称 63 | * @param toolArgs 工具参数 64 | * @returns 工具调用结果 65 | */ 66 | async callTool( 67 | toolName: string, 68 | toolArgs: Record 69 | ): Promise { 70 | try { 71 | addLogs( 72 | { 73 | name: toolName, 74 | arguments: toolArgs, 75 | }, 76 | logType.ToolCall 77 | ); 78 | // 执行工具调用 79 | const result = await this.client.callTool({ 80 | name: toolName, 81 | arguments: toolArgs, 82 | }); 83 | addLogs(result, logType.ToolCallResponse); 84 | return result; 85 | } catch (error) { 86 | addLogs(error, logType.ToolCallError); 87 | 88 | throw new Error( 89 | `调用工具 ${toolName} 失败: ${ 90 | error instanceof Error ? error.message : String(error) 91 | }` 92 | ); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/types/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MCP 服务器配置类型定义 3 | */ 4 | 5 | /** 6 | * 单个 MCP 服务器配置 7 | */ 8 | export interface MCPServerConfig { 9 | command: string; // 启动命令 10 | args?: string[]; // 命令参数 11 | env?: Record; // 环境变量 12 | description?: string; // 服务描述 13 | } 14 | 15 | /** 16 | * MCP 服务器配置映射 17 | */ 18 | export interface MCPServersConfig { 19 | mcpServers: { 20 | [key: string]: MCPServerConfig; 21 | }; 22 | defaultServer?: string; // 默认服务器名称 23 | } 24 | -------------------------------------------------------------------------------- /src/types/global.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 全局类型声明文件 3 | * 为缺少类型定义的模块提供声明 4 | */ 5 | 6 | // 为 @modelcontextprotocol/sdk 提供类型声明 7 | declare module "@modelcontextprotocol/sdk/client/index.js" { 8 | export class Client { 9 | constructor(options: { name: string; version: string }); 10 | connect(transport: any): void; 11 | listTools(): Promise<{ tools: Array<{ name: string; description: string; inputSchema: any }> }>; 12 | callTool(params: { name: string; arguments: any }): Promise<{ content: string; [key: string]: any }>; 13 | close(): Promise; 14 | } 15 | } 16 | 17 | declare module "@modelcontextprotocol/sdk/client/stdio.js" { 18 | export class StdioClientTransport { 19 | constructor(options: { command: string; args: string[] }); 20 | } 21 | } 22 | 23 | // 为 readline/promises 提供类型声明 24 | declare module "readline/promises" { 25 | import { ReadStream, WriteStream } from "fs"; 26 | 27 | interface Interface { 28 | question(query: string): Promise; 29 | close(): void; 30 | } 31 | 32 | export function createInterface(options: { 33 | input: ReadStream; 34 | output: WriteStream; 35 | }): Interface; 36 | } 37 | 38 | // 为 dotenv 提供类型声明 39 | declare module "dotenv" { 40 | export function config(): void; 41 | } 42 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * MCP Client 类型定义 3 | * 4 | * 定义项目中使用的各种接口和类型 5 | */ 6 | 7 | /** 8 | * OpenAI 工具定义接口 9 | * 用于将 MCP 工具转换为 OpenAI API 可识别的格式 10 | */ 11 | export interface OpenAITool { 12 | type: "function"; 13 | function: { 14 | name: string; 15 | description: string; 16 | parameters: Record; 17 | }; 18 | } 19 | 20 | /** 21 | * MCP 工具定义接口 22 | * 从 MCP 服务器获取的工具定义 23 | */ 24 | export interface MCPTool { 25 | name: string; 26 | description: string; 27 | inputSchema: Record; 28 | } 29 | 30 | /** 31 | * 工具调用结果接口 32 | * 表示工具调用后返回的结果 33 | */ 34 | export interface ToolCallResult { 35 | content: string; 36 | [key: string]: unknown; 37 | } 38 | -------------------------------------------------------------------------------- /src/utils/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 配置工具模块 3 | * 负责处理环境变量和配置信息 4 | */ 5 | 6 | import dotenv from "dotenv"; 7 | 8 | // 加载环境变量 9 | dotenv.config(); 10 | 11 | /** 12 | * 检查必要的环境变量是否已配置 13 | * @throws 如果必需的环境变量未设置,则抛出错误 14 | */ 15 | export const validateEnv = (): void => { 16 | const OPENAI_API_KEY = process.env.OPENAI_API_KEY; 17 | 18 | if (!OPENAI_API_KEY) { 19 | throw new Error("OPENAI_API_KEY 未设置,请在.env文件中配置您的API密钥"); 20 | } 21 | }; 22 | 23 | /** 24 | * 获取OpenAI API密钥 25 | * @returns OpenAI API密钥 26 | */ 27 | export const getApiKey = (): string => { 28 | return process.env.OPENAI_API_KEY || ""; 29 | }; 30 | 31 | /** 32 | * 获取配置的LLM模型名称 33 | * 如果环境变量中未指定,则使用默认值 34 | * @returns 模型名称 35 | */ 36 | export const getModelName = (): string => { 37 | return process.env.MODEL_NAME || "gpt-3.5-turbo"; 38 | }; 39 | 40 | export const getBaseURL = (): string => { 41 | return process.env.BASE_URL || ""; 42 | }; 43 | 44 | /** 45 | * 默认配置 46 | */ 47 | export const defaultConfig = { 48 | clientName: "mcp-client-cli", 49 | clientVersion: "1.0.0", 50 | defaultModel: getModelName(), 51 | }; 52 | -------------------------------------------------------------------------------- /src/utils/log.ts: -------------------------------------------------------------------------------- 1 | import path, { dirname } from "path"; 2 | import fs from "fs"; 3 | import { fileURLToPath } from "url"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | const logsDir = path.join(__dirname, "../../logs"); 8 | let index = 0; 9 | 10 | export enum logType { 11 | GetTools = "[GET Tools]", 12 | GetToolsError = "[GET Tools Error]", 13 | ConnectToServer = "[Connect To Server]", 14 | LLMRequest = "[LLM Request]", 15 | LLMResponse = "[LLM Response]", 16 | LLMError = "[LLM Error]", 17 | LLMStream = "[LLM Stream]", 18 | ToolCall = "[Tool Call]", 19 | ToolCallResponse = "[Tool Call Response]", 20 | ToolCallError = "[Tool Call Error]", 21 | } 22 | 23 | /** 24 | * 清空日志目录 25 | */ 26 | export function clearLogs(): void { 27 | if (!fs.existsSync(logsDir)) { 28 | fs.mkdirSync(logsDir); 29 | } 30 | fs.readdir(logsDir, (err, files) => { 31 | if (err) { 32 | console.error("清空日志目录失败:", err); 33 | return; 34 | } 35 | files.forEach((file) => { 36 | const filePath = path.join(logsDir, file); 37 | fs.unlink(filePath, (err) => { 38 | if (err) { 39 | console.error(`删除文件 ${filePath} 失败:`, err); 40 | } else { 41 | console.log(`已删除历史日志文件 ${filePath}`); 42 | } 43 | }); 44 | }); 45 | }); 46 | } 47 | 48 | /** 49 | * 添加日志 50 | * @param logData 51 | */ 52 | export function addLogs(logData: any, logType: logType) { 53 | const now = new Date(); 54 | const year = now.getFullYear(); 55 | const month = String(now.getMonth() + 1).padStart(2, "0"); 56 | const day = String(now.getDate()).padStart(2, "0"); 57 | const hours = String(now.getHours()).padStart(2, "0"); 58 | const minutes = String(now.getMinutes()).padStart(2, "0"); 59 | const seconds = String(now.getSeconds()).padStart(2, "0"); 60 | const logFileName = `[${index++}] ${logType} ${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; 61 | 62 | // console.log(logFileName, JSON.stringify(logData, null, 2)); 63 | 64 | console.log(logFileName); 65 | 66 | if (logData) { 67 | fs.writeFileSync( 68 | path.join(__dirname, `../../logs/${logFileName}.json`), 69 | JSON.stringify(logData, null, 2) 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/utils/schema.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 极简Schema修补函数:为缺少items的数组类型添加最小合法定义 3 | * @param schema 原始JSON Schema 4 | * @param options 配置项(可选) 5 | * @returns 修补后的Schema(原Schema保持不变) 6 | */ 7 | export function patchSchemaArrays( 8 | schema: any, 9 | options: { 10 | log?: boolean; // 打印修补日志 11 | defaultItems?: any; // 自定义默认items(默认:{ type: "object" }) 12 | } = {} 13 | ): any { 14 | const { log, defaultItems = { type: "object" } } = options; 15 | const newSchema = JSON.parse(JSON.stringify(schema)); // 避免修改原始对象 16 | 17 | function processObject(node: any, path: string[]) { 18 | // 处理对象的所有属性 19 | if (node?.properties) { 20 | Object.entries(node.properties).forEach(([key, prop]: [string, any]) => { 21 | if (Array.isArray(prop.type) && prop.type.length > 1) { 22 | // 兼容豆包,type 不能为数组 23 | prop.type = prop.type[0]; 24 | } 25 | if (prop.type === "array" && !prop.items) { 26 | // 发现缺少items的数组属性 27 | prop.items = defaultItems; 28 | if (log) { 29 | console.log( 30 | `[SimplePatcher] 修补属性: ${path.join(".")}.${key}`, 31 | prop 32 | ); 33 | } 34 | } 35 | // 递归处理子对象(如果属性是对象) 36 | if (prop.type === "object") { 37 | processObject(prop, [...path, key]); 38 | } 39 | }); 40 | } 41 | 42 | // 处理数组的items(如果当前node是数组的items) 43 | if (node?.items && node.items.type === "array" && !node.items.items) { 44 | node.items.items = defaultItems; 45 | if (log) { 46 | console.log( 47 | `[SimplePatcher] 修补嵌套数组: ${path.join(".")}.items`, 48 | node.items 49 | ); 50 | } 51 | } 52 | } 53 | 54 | // 入口:从根对象开始处理 55 | if (newSchema.type === "object") { 56 | processObject(newSchema, []); 57 | } 58 | 59 | return newSchema; 60 | } 61 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "resolveJsonModule": true 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": ["node_modules"] 16 | } 17 | --------------------------------------------------------------------------------