├── .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 | 
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 | 
156 |
157 | ## 工作原理
158 |
159 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------