@maplezzk/mcps 1.1.6 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -387
- package/README.zh.md +407 -0
- package/dist/commands/call.js +75 -18
- package/dist/commands/tools.js +32 -2
- package/dist/tests/unit/call.test.js +111 -0
- package/dist/tests/unit/tools.test.js +170 -0
- package/package.json +3 -2
package/README.zh.md
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# mcps - MCP CLI Manager
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | [简体中文](./README.zh.md)
|
|
4
|
+
|
|
5
|
+
一个用于管理和交互 [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) 服务的强大命令行工具。
|
|
6
|
+
|
|
7
|
+
## 功能特性
|
|
8
|
+
|
|
9
|
+
- 🔌 **服务管理**:轻松添加、移除、查看和更新 MCP 服务(支持 Stdio、SSE 和 HTTP 模式)
|
|
10
|
+
- 🛠️ **工具发现**:查看已配置服务中所有可用的工具
|
|
11
|
+
- 🚀 **工具执行**:直接在命令行调用工具,支持参数自动解析
|
|
12
|
+
- 🔄 **守护进程**:保持与 MCP 服务的长连接,显著提高性能
|
|
13
|
+
- 📊 **表格输出**:清晰的服务器状态和工具列表展示
|
|
14
|
+
- 🔍 **工具筛选**:按关键词筛选工具,支持简洁模式
|
|
15
|
+
- 🚨 **详细日志**:可选的详细日志模式,方便调试
|
|
16
|
+
- ✅ **自动化测试**:完整的测试套件,确保代码质量
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g @maplezzk/mcps
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 快速开始
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# 1. 添加一个服务
|
|
28
|
+
mcps add fetch --command uvx --args mcp-server-fetch
|
|
29
|
+
|
|
30
|
+
# 2. 启动守护进程
|
|
31
|
+
mcps start
|
|
32
|
+
|
|
33
|
+
# 3. 查看服务状态
|
|
34
|
+
mcps status
|
|
35
|
+
|
|
36
|
+
# 4. 查看可用工具
|
|
37
|
+
mcps tools fetch
|
|
38
|
+
|
|
39
|
+
# 5. 调用工具
|
|
40
|
+
mcps call fetch fetch url="https://example.com"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 使用指南
|
|
44
|
+
|
|
45
|
+
### 1. 守护进程 (Daemon Mode)
|
|
46
|
+
|
|
47
|
+
mcps 支持守护进程模式,可以保持与 MCP 服务的长连接,显著提高频繁调用的性能。
|
|
48
|
+
|
|
49
|
+
**启动守护进程:**
|
|
50
|
+
```bash
|
|
51
|
+
# 普通模式
|
|
52
|
+
mcps start
|
|
53
|
+
|
|
54
|
+
# 详细模式(显示每个服务器的连接过程和禁用的服务器)
|
|
55
|
+
mcps start --verbose
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
输出示例:
|
|
59
|
+
```
|
|
60
|
+
Starting daemon in background...
|
|
61
|
+
[Daemon] Connecting to 7 server(s)...
|
|
62
|
+
[Daemon] - chrome-devtools... Connected ✓
|
|
63
|
+
[Daemon] - fetch... Connected ✓
|
|
64
|
+
[Daemon] - gitlab-mr-creator... Connected ✓
|
|
65
|
+
[Daemon] Connected: 7/7
|
|
66
|
+
Daemon started successfully on port 4100.
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**重启连接:**
|
|
70
|
+
```bash
|
|
71
|
+
# 重置所有连接
|
|
72
|
+
mcps restart
|
|
73
|
+
|
|
74
|
+
# 仅重置特定服务的连接
|
|
75
|
+
mcps restart my-server
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**停止守护进程:**
|
|
79
|
+
```bash
|
|
80
|
+
mcps stop
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**查看守护进程状态:**
|
|
84
|
+
```bash
|
|
85
|
+
mcps status
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
输出示例:
|
|
89
|
+
```
|
|
90
|
+
Daemon is running (v1.0.29)
|
|
91
|
+
|
|
92
|
+
Active Connections:
|
|
93
|
+
NAME STATUS TOOLS
|
|
94
|
+
───────────────── ────────── ──────
|
|
95
|
+
chrome-devtools Connected 26
|
|
96
|
+
fetch Connected 1
|
|
97
|
+
gitlab-mr-creator Connected 30
|
|
98
|
+
Total: 3 connection(s)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 2. 服务管理 (Server Management)
|
|
102
|
+
|
|
103
|
+
**查看所有服务(配置信息):**
|
|
104
|
+
```bash
|
|
105
|
+
mcps ls
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
输出示例:
|
|
109
|
+
```
|
|
110
|
+
NAME TYPE ENABLED COMMAND/URL
|
|
111
|
+
───────────────── ────── ─────── ─────────────
|
|
112
|
+
chrome-devtools stdio ✓ npx -y chrome-devtools-mcp ...
|
|
113
|
+
fetch stdio ✓ uvx mcp-server-fetch
|
|
114
|
+
my-server stdio ✗ npx my-server
|
|
115
|
+
Total: 3 server(s)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**添加 Stdio 服务:**
|
|
119
|
+
```bash
|
|
120
|
+
# 添加本地 Node.js 服务
|
|
121
|
+
mcps add my-server --command node --args ./build/index.js
|
|
122
|
+
|
|
123
|
+
# 使用 npx/uvx 添加服务
|
|
124
|
+
mcps add fetch --command uvx --args mcp-server-fetch
|
|
125
|
+
|
|
126
|
+
# 添加带环境变量的服务
|
|
127
|
+
mcps add my-db --command npx --args @modelcontextprotocol/server-postgres --env POSTGRES_CONNECTION_STRING="${DATABASE_URL}"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**添加 SSE 服务:**
|
|
131
|
+
```bash
|
|
132
|
+
mcps add remote-server --type sse --url http://localhost:8000/sse
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**添加 Streamable HTTP 服务:**
|
|
136
|
+
```bash
|
|
137
|
+
mcps add my-http-server --type http --url http://localhost:8000/mcp
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**移除服务:**
|
|
141
|
+
```bash
|
|
142
|
+
mcps rm my-server
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**更新服务:**
|
|
146
|
+
```bash
|
|
147
|
+
# 刷新所有服务连接
|
|
148
|
+
mcps update
|
|
149
|
+
|
|
150
|
+
# 更新特定服务的命令
|
|
151
|
+
mcps update my-server --command new-command
|
|
152
|
+
|
|
153
|
+
# 更新特定服务的参数
|
|
154
|
+
mcps update my-server --args arg1 arg2
|
|
155
|
+
|
|
156
|
+
# 同时更新命令和参数
|
|
157
|
+
mcps update my-server --command node --args ./new-build/index.js
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 3. 工具交互 (Tool Interaction)
|
|
161
|
+
|
|
162
|
+
**查看服务下的可用工具:**
|
|
163
|
+
```bash
|
|
164
|
+
# 详细模式(显示所有信息,包括嵌套对象属性)
|
|
165
|
+
mcps tools chrome-devtools
|
|
166
|
+
|
|
167
|
+
# 简洁模式(只显示工具名称)
|
|
168
|
+
mcps tools chrome-devtools --simple
|
|
169
|
+
|
|
170
|
+
# JSON 输出(原始工具 schema)
|
|
171
|
+
mcps tools chrome-devtools --json
|
|
172
|
+
|
|
173
|
+
# 筛选工具(按关键词)
|
|
174
|
+
mcps tools chrome-devtools --tool screenshot
|
|
175
|
+
|
|
176
|
+
# 多个关键词 + 简洁模式
|
|
177
|
+
mcps tools gitlab-mr-creator --tool file --tool wiki --simple
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
详细模式输出示例(包含嵌套对象):
|
|
181
|
+
```
|
|
182
|
+
Available Tools for chrome-devtools:
|
|
183
|
+
|
|
184
|
+
- take_screenshot
|
|
185
|
+
Take a screenshot of the page or element.
|
|
186
|
+
Arguments:
|
|
187
|
+
format*: string ["jpeg", "png", "webp"] (Type of format to save the screenshot as...)
|
|
188
|
+
quality: number (Compression quality from 0-100)
|
|
189
|
+
uid: string (The uid of an element to screenshot...)
|
|
190
|
+
|
|
191
|
+
- click
|
|
192
|
+
Clicks on the provided element
|
|
193
|
+
Arguments:
|
|
194
|
+
uid*: string (The uid of an element...)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
简洁模式输出示例:
|
|
198
|
+
```
|
|
199
|
+
$ mcps tools chrome-devtools -s
|
|
200
|
+
click
|
|
201
|
+
close_page
|
|
202
|
+
drag
|
|
203
|
+
emulate
|
|
204
|
+
evaluate_script
|
|
205
|
+
fill
|
|
206
|
+
...
|
|
207
|
+
take_screenshot
|
|
208
|
+
take_snapshot
|
|
209
|
+
|
|
210
|
+
Total: 26 tool(s)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
**调用工具:**
|
|
214
|
+
|
|
215
|
+
语法:
|
|
216
|
+
```bash
|
|
217
|
+
mcps call <server_name> <tool_name> [options] [arguments...]
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
- `<server_name>`: 已配置的 MCP 服务名称
|
|
221
|
+
- `<tool_name>`: 要调用的工具名称
|
|
222
|
+
- `[options]`: 可选参数(`--raw`, `--json`)
|
|
223
|
+
- `[arguments...]`: 以 `key=value` 形式传递的参数
|
|
224
|
+
|
|
225
|
+
**选项:**
|
|
226
|
+
|
|
227
|
+
| 选项 | 说明 |
|
|
228
|
+
|------|------|
|
|
229
|
+
| `-r, --raw` | 将所有值作为原始字符串处理(禁用 JSON 解析) |
|
|
230
|
+
| `-j, --json <value>` | 从 JSON 字符串或文件加载参数 |
|
|
231
|
+
|
|
232
|
+
**默认模式(自动 JSON 解析):**
|
|
233
|
+
|
|
234
|
+
默认情况下,参数值会被自动解析为 JSON:
|
|
235
|
+
```bash
|
|
236
|
+
# 字符串
|
|
237
|
+
mcps call fetch fetch url="https://example.com"
|
|
238
|
+
|
|
239
|
+
# 数字和布尔值会被解析
|
|
240
|
+
mcps call fetch fetch max_length=5000 follow_redirects=true
|
|
241
|
+
# 实际发送: { "max_length": 5000, "follow_redirects": true }
|
|
242
|
+
|
|
243
|
+
# JSON 对象
|
|
244
|
+
mcps call my-server createUser user='{"name": "Alice", "age": 30}'
|
|
245
|
+
|
|
246
|
+
# 混合参数
|
|
247
|
+
mcps call my-server config debug=true timeout=5000 options='{"retries": 3}'
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**--raw 模式(仅字符串值):**
|
|
251
|
+
|
|
252
|
+
使用 `--raw` 禁用 JSON 解析,所有值保持为字符串:
|
|
253
|
+
```bash
|
|
254
|
+
# ID 和编码保持为字符串
|
|
255
|
+
mcps call my-db createOrder --raw order_id="12345" sku="ABC-001"
|
|
256
|
+
# 实际发送: { "order_id": "12345", "sku": "ABC-001" }
|
|
257
|
+
|
|
258
|
+
# 带特殊字符的 SQL 查询
|
|
259
|
+
mcps call alibaba-dms createDataChangeOrder --raw \
|
|
260
|
+
database_id="36005357" \
|
|
261
|
+
script="DELETE FROM table WHERE id = 'xxx';" \
|
|
262
|
+
logic=true
|
|
263
|
+
# 实际发送: { "database_id": "36005357", "script": "...", "logic": "true" }
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**--json 模式(复杂参数):**
|
|
267
|
+
|
|
268
|
+
对于复杂参数,使用 `--json` 从 JSON 字符串或文件加载:
|
|
269
|
+
```bash
|
|
270
|
+
# JSON 字符串
|
|
271
|
+
mcps call my-server createUser --json '{"name": "Alice", "age": 30}'
|
|
272
|
+
|
|
273
|
+
# 文件
|
|
274
|
+
mcps call my-server createUser --json params.json
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## 配置文件
|
|
278
|
+
|
|
279
|
+
默认情况下,配置文件存储在:
|
|
280
|
+
`~/.mcps/mcp.json`
|
|
281
|
+
|
|
282
|
+
您可以通过设置 `MCPS_CONFIG_DIR` 环境变量来更改存储位置。
|
|
283
|
+
|
|
284
|
+
配置文件示例:
|
|
285
|
+
```json
|
|
286
|
+
{
|
|
287
|
+
"servers": [
|
|
288
|
+
{
|
|
289
|
+
"name": "fetch",
|
|
290
|
+
"type": "stdio",
|
|
291
|
+
"command": "uvx",
|
|
292
|
+
"args": ["mcp-server-fetch"]
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"name": "my-server",
|
|
296
|
+
"type": "stdio",
|
|
297
|
+
"command": "node",
|
|
298
|
+
"args": ["./build/index.js"],
|
|
299
|
+
"env": {
|
|
300
|
+
"API_KEY": "${API_KEY}"
|
|
301
|
+
},
|
|
302
|
+
"disabled": false
|
|
303
|
+
}
|
|
304
|
+
]
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## 环境变量
|
|
309
|
+
|
|
310
|
+
- `MCPS_CONFIG_DIR`: 配置文件目录(默认:`~/.mcps`)
|
|
311
|
+
- `MCPS_PORT`: Daemon 端口(默认:`4100`)
|
|
312
|
+
- `MCPS_VERBOSE`: 详细日志模式(默认:`false`)
|
|
313
|
+
|
|
314
|
+
## 命令参考
|
|
315
|
+
|
|
316
|
+
### 服务管理
|
|
317
|
+
- `mcps ls` - 列出所有服务
|
|
318
|
+
- `mcps add <name>` - 添加新服务
|
|
319
|
+
- `mcps rm <name>` - 移除服务
|
|
320
|
+
- `mcps update [name]` - 更新服务配置
|
|
321
|
+
|
|
322
|
+
### 守护进程
|
|
323
|
+
- `mcps start [-v]` - 启动守护进程(`-v` 显示详细日志)
|
|
324
|
+
- `mcps stop` - 停止守护进程
|
|
325
|
+
- `mcps status` - 查看守护进程状态
|
|
326
|
+
- `mcps restart [server]` - 重启守护进程或特定服务
|
|
327
|
+
|
|
328
|
+
### 工具交互
|
|
329
|
+
- `mcps tools <server> [-s] [-j] [-t <name>...]` - 查看可用工具
|
|
330
|
+
- `-s, --simple`: 只显示工具名称
|
|
331
|
+
- `-j, --json`: 输出原始 JSON(用于调试)
|
|
332
|
+
- `-t, --tool`: 按名称筛选工具(可重复使用)
|
|
333
|
+
- `mcps call <server> <tool> [args...]` - 调用工具
|
|
334
|
+
|
|
335
|
+
## 性能优化
|
|
336
|
+
|
|
337
|
+
mcps 通过以下方式优化性能:
|
|
338
|
+
|
|
339
|
+
1. **守护进程模式**:保持长连接,避免重复启动开销
|
|
340
|
+
2. **工具缓存**:连接时缓存工具数量,避免重复查询
|
|
341
|
+
3. **异步连接**:并行初始化多个服务器连接
|
|
342
|
+
|
|
343
|
+
典型性能:
|
|
344
|
+
- 启动守护进程:10-15 秒(首次,取决于服务数量)
|
|
345
|
+
- 查看状态:~200ms
|
|
346
|
+
- 调用工具:~50-100ms
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
## 开发工作流
|
|
350
|
+
|
|
351
|
+
欢迎贡献代码!
|
|
352
|
+
|
|
353
|
+
**快速开始:**
|
|
354
|
+
```bash
|
|
355
|
+
# 克隆项目
|
|
356
|
+
git clone https://github.com/maplezzk/mcps.git
|
|
357
|
+
cd mcps
|
|
358
|
+
npm install
|
|
359
|
+
|
|
360
|
+
# 开发模式
|
|
361
|
+
npm run dev -- <command>
|
|
362
|
+
|
|
363
|
+
# 构建和测试
|
|
364
|
+
npm run build
|
|
365
|
+
npm test
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**重要规范:**
|
|
369
|
+
- 不要直接在 `main` 分支提交代码
|
|
370
|
+
- 使用 `npm version` 更新版本号(禁止手动修改)
|
|
371
|
+
- 新功能必须包含单元测试
|
|
372
|
+
|
|
373
|
+
📖 **完整开发文档**:[DEVELOPMENT.md](./DEVELOPMENT.md)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
## 常见问题
|
|
377
|
+
|
|
378
|
+
**Q: 如何查看所有服务器的运行状态?**
|
|
379
|
+
```bash
|
|
380
|
+
mcps status # 查看活跃连接
|
|
381
|
+
mcps ls # 查看所有配置(包括禁用的)
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Q: 某个服务连接失败了怎么办?**
|
|
385
|
+
```bash
|
|
386
|
+
# 查看详细日志
|
|
387
|
+
mcps start --verbose
|
|
388
|
+
|
|
389
|
+
# 重启该服务
|
|
390
|
+
mcps restart my-server
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Q: 如何临时禁用某个服务?**
|
|
394
|
+
在配置文件中设置 `"disabled": true`,或使用 `mcps update` 修改配置。
|
|
395
|
+
|
|
396
|
+
**Q: 工具太多怎么快速找到?**
|
|
397
|
+
```bash
|
|
398
|
+
# 筛选工具名称
|
|
399
|
+
mcps tools my-server --tool keyword
|
|
400
|
+
|
|
401
|
+
# 只显示名称
|
|
402
|
+
mcps tools my-server --simple
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## 许可证
|
|
406
|
+
|
|
407
|
+
ISC
|
package/dist/commands/call.js
CHANGED
|
@@ -1,6 +1,56 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
2
3
|
import { configManager } from '../core/config.js';
|
|
3
4
|
import { DaemonClient } from '../core/daemon-client.js';
|
|
5
|
+
/**
|
|
6
|
+
* Parse call command arguments
|
|
7
|
+
* @param args - Command line arguments in key=value format
|
|
8
|
+
* @param raw - Whether to treat all values as raw strings
|
|
9
|
+
* @returns Parsed parameters object
|
|
10
|
+
*/
|
|
11
|
+
export function parseCallArgs(args, raw) {
|
|
12
|
+
const params = {};
|
|
13
|
+
if (!args)
|
|
14
|
+
return params;
|
|
15
|
+
args.forEach((arg) => {
|
|
16
|
+
const eqIndex = arg.indexOf('=');
|
|
17
|
+
if (eqIndex > 0) {
|
|
18
|
+
const key = arg.slice(0, eqIndex);
|
|
19
|
+
const valStr = arg.slice(eqIndex + 1);
|
|
20
|
+
if (raw) {
|
|
21
|
+
// --raw mode: treat all values as strings
|
|
22
|
+
params[key] = valStr;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// Default mode: try JSON parsing
|
|
26
|
+
try {
|
|
27
|
+
params[key] = JSON.parse(valStr);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
params[key] = valStr;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return params;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Load parameters from JSON string or file
|
|
39
|
+
* @param jsonValue - JSON string (starts with { or [) or file path
|
|
40
|
+
* @returns Parsed parameters object
|
|
41
|
+
*/
|
|
42
|
+
export function loadJsonParams(jsonValue) {
|
|
43
|
+
const trimmed = jsonValue.trim();
|
|
44
|
+
// Check if it's a JSON string (starts with { or [)
|
|
45
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
46
|
+
return JSON.parse(jsonValue);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Treat as file path
|
|
50
|
+
const jsonContent = readFileSync(jsonValue, 'utf-8');
|
|
51
|
+
return JSON.parse(jsonContent);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
4
54
|
function printResult(result) {
|
|
5
55
|
if (result.content) {
|
|
6
56
|
result.content.forEach((item) => {
|
|
@@ -22,6 +72,8 @@ function printResult(result) {
|
|
|
22
72
|
export const registerCallCommand = (program) => {
|
|
23
73
|
program.command('call <server> <tool> [args...]')
|
|
24
74
|
.description('Call a tool on a server. Arguments format: key=value')
|
|
75
|
+
.option('-r, --raw', 'Treat all values as raw strings (no JSON parsing)')
|
|
76
|
+
.option('-j, --json <file>', 'Load parameters from a JSON file')
|
|
25
77
|
.addHelpText('after', `
|
|
26
78
|
Examples:
|
|
27
79
|
$ mcps call my-server echo message="Hello World"
|
|
@@ -29,28 +81,33 @@ Examples:
|
|
|
29
81
|
$ mcps call my-server config debug=true
|
|
30
82
|
$ mcps call my-server createUser user='{"name":"Alice","age":30}'
|
|
31
83
|
|
|
84
|
+
# Use --raw to treat all values as strings
|
|
85
|
+
$ mcps call my-server createUser --raw id="123" name="Alice"
|
|
86
|
+
|
|
87
|
+
# Use --json to load parameters from a file
|
|
88
|
+
$ mcps call my-server createUser --json params.json
|
|
89
|
+
|
|
32
90
|
Notes:
|
|
33
91
|
- Arguments are parsed as key=value pairs.
|
|
34
|
-
-
|
|
35
|
-
-
|
|
92
|
+
- By default, values are automatically parsed as JSON if possible (numbers, booleans, objects).
|
|
93
|
+
- Use --raw to disable JSON parsing and treat all values as strings.
|
|
94
|
+
- Use --json to load parameters from a JSON file or JSON string.
|
|
36
95
|
- For strings with spaces, wrap the value in quotes (e.g., msg="hello world").
|
|
37
96
|
`)
|
|
38
|
-
.action(async (serverName, toolName, args) => {
|
|
39
|
-
|
|
40
|
-
if
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
});
|
|
97
|
+
.action(async (serverName, toolName, args, options) => {
|
|
98
|
+
let params = {};
|
|
99
|
+
// Load from JSON file or string if specified
|
|
100
|
+
if (options.json) {
|
|
101
|
+
try {
|
|
102
|
+
params = loadJsonParams(options.json);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error(chalk.red(`Failed to parse JSON: ${error.message}`));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
params = parseCallArgs(args, options.raw);
|
|
54
111
|
}
|
|
55
112
|
// Check if server exists in config first
|
|
56
113
|
const serverConfig = configManager.getServer(serverName);
|
package/dist/commands/tools.js
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import { configManager } from '../core/config.js';
|
|
3
3
|
import { DaemonClient } from '../core/daemon-client.js';
|
|
4
|
+
function printProperty(key, value, required, indent) {
|
|
5
|
+
const indentStr = ' '.repeat(indent);
|
|
6
|
+
const requiredMark = required ? chalk.red('*') : '';
|
|
7
|
+
const desc = value.description ? ` (${value.description})` : '';
|
|
8
|
+
if (value.type === 'object' && value.properties) {
|
|
9
|
+
console.log(`${indentStr}${key}${requiredMark}: object${desc}`);
|
|
10
|
+
const nestedRequired = value.required || [];
|
|
11
|
+
Object.entries(value.properties).forEach(([nestedKey, nestedValue]) => {
|
|
12
|
+
printProperty(nestedKey, nestedValue, nestedRequired.includes(nestedKey), indent + 1);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
let typeInfo = value.type || 'any';
|
|
17
|
+
if (value.type === 'array' && value.items) {
|
|
18
|
+
const itemType = value.items.type || 'any';
|
|
19
|
+
typeInfo = `array of ${itemType}`;
|
|
20
|
+
}
|
|
21
|
+
if (value.enum) {
|
|
22
|
+
const enumValues = value.enum.map((v) => typeof v === 'string' ? `"${v}"` : String(v)).join(', ');
|
|
23
|
+
typeInfo += ` [${enumValues}]`;
|
|
24
|
+
}
|
|
25
|
+
console.log(`${indentStr}${key}${requiredMark}: ${typeInfo}${desc}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
4
28
|
function printTools(serverName, tools) {
|
|
5
29
|
console.log(chalk.bold(`\nAvailable Tools for ${serverName}:`));
|
|
6
30
|
if (!tools || tools.length === 0) {
|
|
@@ -15,9 +39,9 @@ function printTools(serverName, tools) {
|
|
|
15
39
|
console.log(chalk.gray(' Arguments:'));
|
|
16
40
|
const schema = tool.inputSchema;
|
|
17
41
|
if (schema.properties) {
|
|
42
|
+
const required = schema.required || [];
|
|
18
43
|
Object.entries(schema.properties).forEach(([key, value]) => {
|
|
19
|
-
|
|
20
|
-
console.log(` ${key}${required}: ${value.type || 'any'} ${value.description ? `(${value.description})` : ''}`);
|
|
44
|
+
printProperty(key, value, required.includes(key), 2);
|
|
21
45
|
});
|
|
22
46
|
}
|
|
23
47
|
else {
|
|
@@ -30,6 +54,7 @@ export const registerToolsCommand = (program) => {
|
|
|
30
54
|
program.command('tools <server>')
|
|
31
55
|
.description('List available tools on a server')
|
|
32
56
|
.option('-s, --simple', 'Show only tool names')
|
|
57
|
+
.option('-j, --json', 'Output raw JSON')
|
|
33
58
|
.option('-t, --tool <name...>', 'Filter tools by name(s)')
|
|
34
59
|
.action(async (serverName, options) => {
|
|
35
60
|
// Check if server exists in config first
|
|
@@ -52,6 +77,11 @@ export const registerToolsCommand = (program) => {
|
|
|
52
77
|
console.log(chalk.yellow('No tools found.'));
|
|
53
78
|
return;
|
|
54
79
|
}
|
|
80
|
+
if (options.json) {
|
|
81
|
+
// Raw JSON output
|
|
82
|
+
console.log(JSON.stringify(tools, null, 2));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
55
85
|
if (options.simple) {
|
|
56
86
|
// Simple mode: only show tool names
|
|
57
87
|
tools.forEach((tool) => console.log(tool.name));
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { parseCallArgs, loadJsonParams } from '../../commands/call.js';
|
|
6
|
+
describe('call command', () => {
|
|
7
|
+
describe('parseCallArgs', () => {
|
|
8
|
+
it('should parse string values in default mode', () => {
|
|
9
|
+
const args = ['name=Alice', 'url=https://example.com'];
|
|
10
|
+
const result = parseCallArgs(args, false);
|
|
11
|
+
expect(result).toEqual({
|
|
12
|
+
name: 'Alice',
|
|
13
|
+
url: 'https://example.com'
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
it('should parse numbers and booleans in default mode', () => {
|
|
17
|
+
const args = ['count=10', 'active=true', 'score=3.14'];
|
|
18
|
+
const result = parseCallArgs(args, false);
|
|
19
|
+
expect(result).toEqual({
|
|
20
|
+
count: 10,
|
|
21
|
+
active: true,
|
|
22
|
+
score: 3.14
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
it('should parse JSON objects in default mode', () => {
|
|
26
|
+
const args = ['user={"name":"Alice","age":30}'];
|
|
27
|
+
const result = parseCallArgs(args, false);
|
|
28
|
+
expect(result).toEqual({
|
|
29
|
+
user: { name: 'Alice', age: 30 }
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
it('should treat all values as strings in raw mode', () => {
|
|
33
|
+
const args = ['id=123', 'count=10', 'active=true', 'pi=3.14'];
|
|
34
|
+
const result = parseCallArgs(args, true);
|
|
35
|
+
expect(result).toEqual({
|
|
36
|
+
id: '123',
|
|
37
|
+
count: '10',
|
|
38
|
+
active: 'true',
|
|
39
|
+
pi: '3.14'
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
it('should handle empty args', () => {
|
|
43
|
+
expect(parseCallArgs(undefined, false)).toEqual({});
|
|
44
|
+
expect(parseCallArgs([], false)).toEqual({});
|
|
45
|
+
});
|
|
46
|
+
it('should handle args without equals sign', () => {
|
|
47
|
+
const args = ['invalid', 'name=Alice', 'noequals'];
|
|
48
|
+
const result = parseCallArgs(args, false);
|
|
49
|
+
expect(result).toEqual({
|
|
50
|
+
name: 'Alice'
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
it('should handle values with equals sign', () => {
|
|
54
|
+
const args = ['equation=1+1=2', 'url=https://example.com?a=1&b=2'];
|
|
55
|
+
const result = parseCallArgs(args, false);
|
|
56
|
+
expect(result).toEqual({
|
|
57
|
+
equation: '1+1=2',
|
|
58
|
+
url: 'https://example.com?a=1&b=2'
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe('loadJsonParams', () => {
|
|
63
|
+
let tempDir;
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
tempDir = path.join(os.tmpdir(), `mcps-test-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`);
|
|
66
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
|
67
|
+
});
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
if (fs.existsSync(tempDir)) {
|
|
70
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
it('should parse JSON string starting with {', () => {
|
|
74
|
+
const jsonStr = '{"key": "value", "number": 42}';
|
|
75
|
+
const result = loadJsonParams(jsonStr);
|
|
76
|
+
expect(result).toEqual({
|
|
77
|
+
key: 'value',
|
|
78
|
+
number: 42
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
it('should parse JSON string starting with [', () => {
|
|
82
|
+
const jsonStr = '[{"id": 1}, {"id": 2}]';
|
|
83
|
+
const result = loadJsonParams(jsonStr);
|
|
84
|
+
expect(result).toEqual([{ id: 1 }, { id: 2 }]);
|
|
85
|
+
});
|
|
86
|
+
it('should load and parse JSON from file', () => {
|
|
87
|
+
const testFile = path.join(tempDir, 'params.json');
|
|
88
|
+
const testData = { name: 'test', value: 123 };
|
|
89
|
+
fs.writeFileSync(testFile, JSON.stringify(testData));
|
|
90
|
+
const result = loadJsonParams(testFile);
|
|
91
|
+
expect(result).toEqual(testData);
|
|
92
|
+
});
|
|
93
|
+
it('should handle JSON string with leading/trailing whitespace', () => {
|
|
94
|
+
const jsonStr = ' {"key": "value"} ';
|
|
95
|
+
const result = loadJsonParams(jsonStr);
|
|
96
|
+
expect(result).toEqual({ key: 'value' });
|
|
97
|
+
});
|
|
98
|
+
it('should throw error for invalid JSON string', () => {
|
|
99
|
+
expect(() => loadJsonParams('not valid json')).toThrow();
|
|
100
|
+
});
|
|
101
|
+
it('should throw error for non-existent file', () => {
|
|
102
|
+
const nonExistentFile = path.join(tempDir, 'non-existent.json');
|
|
103
|
+
expect(() => loadJsonParams(nonExistentFile)).toThrow();
|
|
104
|
+
});
|
|
105
|
+
it('should throw error for invalid JSON in file', () => {
|
|
106
|
+
const testFile = path.join(tempDir, 'invalid.json');
|
|
107
|
+
fs.writeFileSync(testFile, 'not valid json');
|
|
108
|
+
expect(() => loadJsonParams(testFile)).toThrow();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|