内容
# MCPico
**MCP 代理, 将扁平的工具列表捆绑到分层子命令组。**
MCPico(MCP + "ico" = tiny)封装了上游 MCP 服务器,将其工具分组为可发现的子命令工具。每个组一个工具,而不是每个工具一个工具。`help` 子命令从上游模式中自动生成丰富的文档。
## 问题
MCP 服务器以扁平列表公开工具。每个工具都会消耗上下文令牌。文件系统服务器公开了 14+ 个独立工具 - 模型看到了所有这些工具,即使它只需要一个。
一些客户端添加了“工具搜索”作为变通方法。但搜索需要模型主动查找它不知道存在的工具。没有结构化信号表明哪些工具相互关联。
## MCPico 的解决方案
在单个入口点下组织相关工具。模型看到 9 个组而不是 14 个工具。通过 `help` 内置发现:
```
→ 文件系统工具:
14 个工具 → 9 个组:read, write, edit, create, list, directory, move, search, get
```
## 功能
- **工具捆绑** — 按前缀(可配置分隔符)分组工具,折叠扁平工具列表
- **自动生成帮助** — 每个组的 `help` 子命令从上游工具模式中生成
- **多服务器聚合** — 通过一个接口代理多个上游 MCP 服务器
- **双重上游传输** — 支持 stdio 和 Streamable HTTP(SSE)上游服务器
- **双重监听传输** — MCPico 自身通过 stdio 或 HTTP/SSE 监听(可配置端口)
- **可配置超时** — 每个服务器连接超时,默认 30 秒
- **资源和提示传递** — 命名空间以避免跨服务器冲突
- **身份验证** — 带有自动令牌刷新的 Bearer、自定义标头和 OAuth2 client_credentials
- **监听端点身份验证** — 使用 bearer 令牌验证保护 SSE 端点
## 用法
### 安装
```bash
npm install -g mcpico
```
### 配置
创建 `mcpico.json`:
```json
{
"servers": [
{
"name": "filesystem",
"transport": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/dir"]
}
}
]
}
```
### 运行
```bash
mcpico
```
### 连接您的 MCP 客户端
在您的 MCP 客户端配置中添加 MCPico 作为服务器:
```json
{
"mcpServers": {
"mcpico": {
"command": "mcpico",
"args": ["--config", "/path/to/mcpico.json"]
}
}
}
```
## 工作原理
1. **连接** 到上游 MCP 服务器
2. **发现** 它们的工具 (`tools/list`)
3. **分组** 工具按前缀(可配置分隔符,默认 `_`)
- `filesystem_read_file`, `filesystem_write_file` → 组 `filesystem`
4. **注册** 每个组为一个单独的 MCP 工具,带有 `command` 字符串参数
5. **转发** 工具调用通过解析 `<subcommand> {"key":"value"}` 并代理到上游
6. **生成帮助** 动态从原始工具模式
### 命令格式
```
<subcommand> {"arg1":"val1","arg2":"val2"}
```
示例:
- `help` — 查看所有子命令及其参数
- `read_file {"path":"/tmp/hello.txt"}` — 调用特定工具
- `write_file {"path":"/tmp/out.txt","content":"hello"}` — 带有参数
### 多服务器聚合
MCPico 可以同时代理多个上游服务器:
```json
{
"servers": [
{
"name": "filesystem",
"transport": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
}
},
{
"name": "github",
"transport": {
"type": "sse",
"url": "https://mcp-github.example.com/mcp"
}
}
]
}
```
如果不同服务器的组共享前缀,则合并。否则,每个服务器的工具作为单独的组出现。
## 配置
| 字段 | 类型 | 默认值 | 描述 |
|-------|------|---------|-------------|
| `servers` | `ServerConfig[]` | **必填** | 上游 MCP 服务器代理 |
| `separator` | `string` | `"_"` | 基于前缀的工具分组分隔符 |
| `groups` | `object` | `{}` | 显式组覆盖 (`{ "group": ["tool1","tool2"] }`) |
| `listen` | `ListenConfig` | `{"type":"stdio"}` | MCPico 向 MCP 客户端公开自身的方式 |
### ListenConfig
| 字段 | 类型 | 必填 | 描述 |
|-------|------|----------|-------------|
| `type` | `"stdio"` | 是 | 标准 stdio 传输 |
| `type` | `"sse"` | 是 | HTTP/SSE — 指定 `port` 和可选 `host` |
```json
// SSE 监听模式 — MCPico 作为 HTTP 端点
{
"servers": [...],
"listen": {
"type": "sse",
"port": 3000
}
}
```
### ServerConfig
| 字段 | 类型 | 必填 | 描述 |
|-------|------|----------|-------------|
| `name` | `string` | 是 | 友好名称 / 组命名空间 |
| `transport` | `TransportConfig` | 是 | 如何连接到上游服务器 |
| `connectTimeoutMs` | `number` | 否 | 连接超时(毫秒)(默认:30000) |
### TransportConfig(stdio)
| 字段 | 类型 | 必填 | 描述 |
|-------|------|----------|-------------|
| `type` | `"stdio"` | 是 | 传输类型 |
| `command` | `string` | 是 | 要执行的命令 |
| `args` | `string[]` | 否 | 命令行参数 |
| `env` | `object` | 否 | 环境变量 |
| `cwd` | `string` | 否 | 工作目录 |
### TransportConfig(SSE / Streamable HTTP)
| 字段 | 类型 | 必填 | 描述 |
|-------|------|----------|-------------|
| `type` | `"sse"` | 是 | 传输类型 |
| `url` | `string` | 是 | 到 MCP Streamable HTTP 端点的完整 URL |
## 身份验证
MCPico 支持两层身份验证:
### 层 1:保护监听端点
当 MCPico 公开 SSE 端点时,您可以要求客户端提供 bearer 令牌:
```json
{
"servers": [...],
"listen": {
"type": "sse",
"port": 3000,
"auth": {
"type": "bearer",
"token": "${MCPICO_API_KEY}"
}
}
}
```
客户端必须在请求中包含 `Authorization: Bearer <token>`。无效或缺失的令牌将收到 401 响应。
### 层 2:向 upstream 服务器身份验证
上游服务器可能需要身份验证。MCPico 支持三种方法:
**Bearer 令牌** — 标准 `Authorization: Bearer <token>` 头部:
```json
{
"servers": [
{
"name": "internal-api",
"transport": {
"type": "sse",
"url": "https://api.internal/mcp"
},
"auth": {
"type": "bearer",
"token": "${INTERNAL_KEY}"
}
}
]
}
```
**自定义头部** — 任意头部(例如 `X-API-Key`):
```json
{
"auth": {
"type": "header",
"name": "X-API-Key",
"value": "${WIDGET_KEY}"
}
}
```
**OAuth 2.0 客户端凭证** — 机器到机器身份验证,自动令牌刷新:
```json
{
"auth": {
"type": "oauth",
"grant_type": "client_credentials",
"client_id": "${PROVIDER_CLIENT_ID}",
"client_secret": "${PROVIDER_CLIENT_SECRET}",
"token_url": "https://auth.example.com/oauth/token",
"scopes": ["read", "write"]
}
}
```
MCPico 处理完整的 OAuth 流:
- 启动时获取初始访问令牌
- 在 `~/.mcplico/credentials.json` 中缓存令牌
- 在过期前自动刷新
- 在 401 时重试并使用新令牌
所有身份验证字段都支持 `${ENV_VAR}` 插值 — 永远不要硬编码秘密。
### 身份验证配置参考
| 字段 | 类型 | 必填 | 描述 |
|-------|------|----------|-------------|
| `auth.type` | `"bearer"` \| `"header"` \| `"oauth"` | 是 | 身份验证方法 |
| `auth.token` | `string` | 对于 `bearer` | Bearer 令牌值 |
| `auth.name` | `string` | 对于 `header` | 头部名称 |
| `auth.value` | `string` | 对于 `header` | 头部值 |
| `auth.grant_type` | `"client_credentials"` | 对于 `oauth` | OAuth 授予类型 |
| `auth.client_id` | `string` | 对于 `oauth` | OAuth 客户端 ID |
| `auth.client_secret` | `string` | 对于 `oauth` | OAuth 客户端秘密 |
| `auth.token_url` | `string` | 对于 `oauth` | 令牌端点 URL |
| `auth.scopes` | `string[]` | 否 | OAuth 请求范围 |
| `auth.authorization_server_url` | `string` | 否 | 身份验证服务器 URL(如果与 token_url 签发者不同) |
## 开发
## 开发
```bash
npm install
npm run build # TypeScript 编译
npm test # 运行测试(138 个测试,vitest)
npm run dev # 直接使用 tsx 运行
```
## 许可证
MIT