@leadfund/lead-mcp 202610.1.0 → 202610.2.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 +133 -37
- package/package.json +12 -3
- package/src/cli.js +14 -6
- package/src/config.js +162 -59
- package/src/http.js +224 -0
- package/test/config.test.mjs +84 -25
- package/test/e2e-published.mjs +118 -0
- package/test/http-curl-demo.sh +67 -0
- package/test/http.test.mjs +284 -0
package/README.md
CHANGED
|
@@ -9,6 +9,18 @@
|
|
|
9
9
|
- 只提供只读工具,不含任何写操作
|
|
10
10
|
- 日志一律走 stderr(stdio 传输下 stdout 是 JSON-RPC 通道)
|
|
11
11
|
|
|
12
|
+
## ⚠️ 使用前提
|
|
13
|
+
|
|
14
|
+
**必须在公司网络或已连接 VPN 的环境下使用。**
|
|
15
|
+
|
|
16
|
+
本包发布在公共 npm 上,任何人都能安装;但**后端 dmmp-system(275)部署在公司内网**,
|
|
17
|
+
`10.1.1.197` / `10.32.30.208` / `10.32.30.209` 公网不可路由。
|
|
18
|
+
|
|
19
|
+
- 包能正常下载安装 ✓
|
|
20
|
+
- 但脱离内网后查询会超时失败(生产双节点会依次尝试后报错)
|
|
21
|
+
|
|
22
|
+
包本身不含任何凭证,脱离内网也无法访问到客户数据。
|
|
23
|
+
|
|
12
24
|
## 工具
|
|
13
25
|
|
|
14
26
|
| 工具 | 说明 |
|
|
@@ -72,61 +84,133 @@ npm install
|
|
|
72
84
|
|
|
73
85
|
## 接入 Claude Code
|
|
74
86
|
|
|
87
|
+
### stdio 方式(本地 MCP Client)
|
|
88
|
+
|
|
75
89
|
```bash
|
|
76
90
|
claude mcp add-json lead-mcp '{
|
|
77
91
|
"command": "npx",
|
|
78
|
-
"args": ["-y", "@leadfund/lead-mcp", "--env", "sit",
|
|
92
|
+
"args": ["-y", "--registry=https://registry.npmjs.org/", "@leadfund/lead-mcp", "--env", "sit"],
|
|
93
|
+
"env": {"LEAD_MCP_ENV": "sit"}
|
|
79
94
|
}'
|
|
80
95
|
```
|
|
81
96
|
|
|
82
|
-
###
|
|
97
|
+
### 指定服务地址
|
|
83
98
|
|
|
84
|
-
|
|
85
|
-
|---|---|
|
|
86
|
-
| `local` | `http://localhost:8081` |
|
|
87
|
-
| `sit` | `http://10.1.1.197:8081` |
|
|
88
|
-
| `prd` | `http://10.32.30.208:8081`、`http://10.32.30.209:8081` |
|
|
99
|
+
服务地址**不在包里**,由使用方指定。两种方式:
|
|
89
100
|
|
|
90
|
-
|
|
91
|
-
- **未知环境名直接报错退出**,不会静默回落到默认环境。
|
|
92
|
-
- 生产是双节点:只有网络错误、超时、5xx 这类「节点不可用」才会自动切到下一个节点;
|
|
93
|
-
查不到、参数错误这类业务失败不切换,直接返回。
|
|
94
|
-
- 上次成功的节点会被记住并优先使用,避免每次都从第一个节点试探。
|
|
101
|
+
**方式一:命令行参数(支持多个,逗号分隔)**
|
|
95
102
|
|
|
96
|
-
|
|
103
|
+
```bash
|
|
104
|
+
# 单个
|
|
105
|
+
lead-mcp --base-url http://10.1.1.197:8081
|
|
97
106
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
107
|
+
# 多个(按顺序故障转移,前一个不通自动切下一个)
|
|
108
|
+
lead-mcp --base-url http://10.32.30.208:8081,http://10.32.30.209:8081
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
也可以用环境变量 `LEAD_MCP_BASE_URL`。**`--base-url` 优先级高于 `--env`。**
|
|
112
|
+
|
|
113
|
+
**方式二:用户配置文件**(推荐,环境多时更清晰)
|
|
114
|
+
|
|
115
|
+
默认路径 `~/.lead-mcp/config.json`,可用 `LEAD_MCP_CONFIG` 指定别处:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"defaultEnvironment": "sit",
|
|
120
|
+
"environments": {
|
|
121
|
+
"local": ["http://localhost:8081"],
|
|
122
|
+
"sit": ["http://10.1.1.197:8081"],
|
|
123
|
+
"prd": ["http://10.32.30.208:8081", "http://10.32.30.209:8081"]
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
之后用 `--env sit` / `--env prd` 引用;不写 `--env` 就用 `defaultEnvironment`。
|
|
129
|
+
配置文件缺失时只有内置的 `local` 可用。
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
lead-mcp --env prd
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 为什么指定官方源
|
|
105
136
|
|
|
106
|
-
|
|
107
|
-
|
|
137
|
+
```json
|
|
138
|
+
"args": ["-y", "--registry=https://registry.npmjs.org/", "@leadfund/lead-mcp"]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
npm 默认走淘宝源(`registry.npmmirror.com`),而**镜像源对新发布的包有同步延迟**——
|
|
142
|
+
本包发布后淘宝源一度返回 404,导致 `npx` 装上却没有 bin,报 `lead-mcp: command not found`。
|
|
143
|
+
显式指定官方源可以完全避开这个问题(官方源是源,镜像只是它的副本)。
|
|
144
|
+
|
|
145
|
+
### operator 要不要配
|
|
146
|
+
|
|
147
|
+
**默认不用配。** 没传 `--operator` 时,Server 会自动用操作系统账号(`os.userInfo().username`)作为操作人,
|
|
148
|
+
限流维度和审计记录都有值。
|
|
149
|
+
|
|
150
|
+
想要审计里直接是工号而不是机器账号,才需要显式配置:
|
|
108
151
|
|
|
109
|
-
|
|
152
|
+
```json
|
|
153
|
+
"args": ["-y", "--registry=https://registry.npmjs.org/", "@leadfund/lead-mcp", "--env", "sit", "--operator", "E10086"]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## HTTP(S) 模式(云端 MCP 服务)
|
|
157
|
+
|
|
158
|
+
stdio 只适用于本地 MCP Client。要接入云端服务(如阿里云百炼),用 HTTP 模式。
|
|
110
159
|
|
|
111
|
-
###
|
|
160
|
+
### 启动
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
# SSE 协议
|
|
164
|
+
lead-mcp --transport sse --http-port 3100 --http-host 0.0.0.0 \
|
|
165
|
+
--auth-token <你的token> --env sit
|
|
112
166
|
|
|
113
|
-
|
|
167
|
+
# Streamable HTTP 协议
|
|
168
|
+
lead-mcp --transport http --http-port 3100 --http-host 0.0.0.0 \
|
|
169
|
+
--auth-token <你的token> --env sit
|
|
170
|
+
```
|
|
114
171
|
|
|
115
|
-
|
|
|
172
|
+
| 传输 | 端点 | 说明 |
|
|
116
173
|
|---|---|---|
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
174
|
+
| `stdio` | — | 默认,本地 MCP Client |
|
|
175
|
+
| `sse` | `GET /sse` + `POST /messages?sessionId=` | 云端 MCP 常用 |
|
|
176
|
+
| `http` | `POST /mcp` | Streamable HTTP,一站式 |
|
|
177
|
+
|
|
178
|
+
**HTTP 模式下 `--auth-token` 必填**(没有令牌直接拒绝启动)—— 这套服务通常要暴露到公网,
|
|
179
|
+
不设令牌等于不设防。客户端通过 `Authorization: Bearer <token>` 认证。
|
|
180
|
+
`GET /health` 不鉴权,方便探活。
|
|
181
|
+
|
|
182
|
+
### 百炼云端配置
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"type": "mcp",
|
|
187
|
+
"server_protocol": "sse",
|
|
188
|
+
"server_label": "lead-mcp",
|
|
189
|
+
"server_url": "https://你的域名/sse",
|
|
190
|
+
"headers": { "Authorization": "Bearer 你的token" }
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`server_protocol` 换成 `http` 时,`server_url` 用 `https://你的域名/mcp`。
|
|
120
195
|
|
|
121
|
-
|
|
196
|
+
### 暴露到公网(ng 隧道示例)
|
|
122
197
|
|
|
123
198
|
```bash
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
199
|
+
lead-mcp --transport sse --http-host 0.0.0.0 --http-port 3100 \
|
|
200
|
+
--auth-token <你的token> --env sit
|
|
201
|
+
|
|
202
|
+
# 另一个终端
|
|
203
|
+
ng http 3100
|
|
204
|
+
# 拿到 https://xxxx.ngrok-free.app
|
|
128
205
|
```
|
|
129
206
|
|
|
207
|
+
然后百炼里填 `https://xxxx.ngrok-free.app/sse` 和 Bearer 令牌。
|
|
208
|
+
|
|
209
|
+
> ⚠️ 暴露到公网前请确认:
|
|
210
|
+
> - `--auth-token` 已配置且足够随机
|
|
211
|
+
> - 后端地址走 `--env` 指向内网,**不要**把 275 的直连地址暴露出去
|
|
212
|
+
> - ng 隧道是临时地址,重启会变
|
|
213
|
+
|
|
130
214
|
## 本地调试
|
|
131
215
|
|
|
132
216
|
```bash
|
|
@@ -139,9 +223,15 @@ npx @modelcontextprotocol/inspector node src/cli.js --base-url http://127.0.0.1:
|
|
|
139
223
|
### 测试
|
|
140
224
|
|
|
141
225
|
```bash
|
|
142
|
-
npm test # 配置解析单元测试 +
|
|
226
|
+
npm test # 配置解析单元测试 + HTTP 端到端 + 冒烟测试
|
|
227
|
+
node test/e2e-published.mjs [手机号] # 用公网已发布的包跑真实链路(默认 sit)
|
|
143
228
|
```
|
|
144
229
|
|
|
230
|
+
> ⚠️ **在本包源码目录下不要用 `npx` 启动**:npx 会把本地 `package.json` 当作「已安装」,
|
|
231
|
+
> 直接去找 `node_modules/.bin/lead-mcp`,而它不是自己的依赖、软链不存在,
|
|
232
|
+
> 结果是 `sh: lead-mcp: command not found`(exit 127)。
|
|
233
|
+
> 开发时用 `node src/cli.js`,或换到别的目录执行。
|
|
234
|
+
|
|
145
235
|
- `test/config.test.mjs` —— 环境选择、地址优先级、未知环境报错等纯逻辑
|
|
146
236
|
- `test/smoke.mjs` —— 拉起 mock 的 dmmp-system,再以子进程方式拉起真实的 MCP Server,
|
|
147
237
|
走完整协议握手,覆盖命中 / 未命中 / 多会员 / 参数非法 / 下游异常 / 操作人透传 /
|
|
@@ -171,13 +261,17 @@ MES MemberQueryFacade
|
|
|
171
261
|
lead-mcp/
|
|
172
262
|
├── package.json
|
|
173
263
|
├── src/
|
|
174
|
-
│ ├── cli.js #
|
|
264
|
+
│ ├── cli.js # 入口,按 transport 分派
|
|
265
|
+
│ ├── http.js # HTTP(S) 传输:SSE / Streamable HTTP
|
|
175
266
|
│ ├── server.js # MCP Server 与工具注册
|
|
176
267
|
│ ├── client.js # dmmp-system HTTP 客户端
|
|
177
268
|
│ ├── config.js # 配置解析
|
|
178
269
|
│ └── logger.js # stderr 日志
|
|
179
270
|
└── test/
|
|
180
|
-
|
|
271
|
+
├── config.test.mjs # 配置解析单元测试
|
|
272
|
+
├── http.test.mjs # HTTP(S) 传输端到端测试
|
|
273
|
+
├── smoke.mjs # 端到端冒烟测试
|
|
274
|
+
└── e2e-published.mjs # 用公网已发布的包跑真实链路
|
|
181
275
|
```
|
|
182
276
|
|
|
183
277
|
## 约束
|
|
@@ -186,3 +280,5 @@ lead-mcp/
|
|
|
186
280
|
- **日志走 stderr**:写 stdout 会破坏 JSON-RPC 协议。
|
|
187
281
|
- **脱敏在服务端完成**:本项目不做脱敏,直接透传后端已脱敏的结果。
|
|
188
282
|
- **限流在服务端做**:客户端侧计数可被绕过,服务端按「操作人」和「查询目标」两个维度限流。
|
|
283
|
+
- **包内不含内网地址**:服务地址由使用方通过 `--base-url` 或 `~/.lead-mcp/config.json` 指定。
|
|
284
|
+
- **HTTP 模式必须带令牌**:`--auth-token` 未配置时直接拒绝启动。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leadfund/lead-mcp",
|
|
3
|
-
"version": "202610.
|
|
3
|
+
"version": "202610.2.0",
|
|
4
4
|
"description": "利得零售系统 MCP Server —— 客户账户信息只读查询",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,9 +18,10 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"start": "node src/cli.js",
|
|
20
20
|
"inspector": "npx @modelcontextprotocol/inspector node src/cli.js",
|
|
21
|
-
"test": "node test/config.test.mjs && node test/smoke.mjs",
|
|
21
|
+
"test": "node test/config.test.mjs && node test/http.test.mjs && node test/smoke.mjs",
|
|
22
22
|
"smoke": "node test/smoke.mjs",
|
|
23
|
-
"release": "npm test && npm publish --registry=https://registry.npmjs.org/ --access public"
|
|
23
|
+
"release": "npm test && npm publish --registry=https://registry.npmjs.org/ --access public",
|
|
24
|
+
"test:http": "node test/http.test.mjs"
|
|
24
25
|
},
|
|
25
26
|
"publishConfig": {
|
|
26
27
|
"access": "public",
|
|
@@ -36,5 +37,13 @@
|
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
38
39
|
"zod": "^3.25.76 || ^4.0.0"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "http://10.1.1.90/root/lead-mcp.git"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "http://10.1.1.90/root/lead-mcp",
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "http://10.1.1.90/root/lead-mcp/-/issues"
|
|
39
48
|
}
|
|
40
49
|
}
|
package/src/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { USAGE, isHelpRequested, loadConfig } from "./config.js";
|
|
4
|
+
import { startHttpServer } from "./http.js";
|
|
4
5
|
import { logger } from "./logger.js";
|
|
5
6
|
import { createServer } from "./server.js";
|
|
6
7
|
|
|
@@ -12,12 +13,19 @@ async function main() {
|
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
const config = loadConfig();
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
|
|
17
|
+
if (config.transport === "stdio") {
|
|
18
|
+
const server = createServer(config);
|
|
19
|
+
const transport = new StdioServerTransport();
|
|
20
|
+
await server.connect(transport);
|
|
21
|
+
logger.info(
|
|
22
|
+
`已启动(stdio),环境=${config.env},服务地址=${config.baseUrls.join(", ")},超时=${config.timeoutMs}ms`,
|
|
23
|
+
);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// HTTP 模式常驻,直到进程被结束
|
|
28
|
+
await startHttpServer(config);
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
main().catch((error) => {
|
package/src/config.js
CHANGED
|
@@ -1,71 +1,128 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* 运行配置
|
|
5
7
|
*
|
|
6
|
-
* <p>配置来源优先级:命令行参数 > 环境变量 >
|
|
8
|
+
* <p>配置来源优先级:命令行参数 > 环境变量 > 用户配置文件 > 内置默认值。</p>
|
|
9
|
+
*
|
|
10
|
+
* <p><b>服务地址不在包里</b>:包内只保留 local 默认地址,
|
|
11
|
+
* sit / prd 等环境地址由使用方在自己的配置文件
|
|
12
|
+
* {@code ~/.lead-mcp/config.json} 或 {@code --base-url} 里指定。</p>
|
|
7
13
|
*/
|
|
8
14
|
|
|
9
15
|
/**
|
|
10
16
|
* @typedef {Object} LeadMcpConfig
|
|
11
|
-
* @property {string} env
|
|
17
|
+
* @property {string} env 环境标识,会随请求透传给后端用于审计
|
|
12
18
|
* @property {string[]} baseUrls dmmp-system(系统 id 275)服务地址列表,多个时按顺序故障转移
|
|
13
19
|
* @property {number} timeoutMs 单次请求超时(毫秒)
|
|
14
20
|
* @property {string|undefined} token 访问令牌,可选
|
|
15
21
|
* @property {string|undefined} operator 操作人标识(工号),用于服务端限流与审计
|
|
16
22
|
* @property {string|undefined} operatorOs 操作人操作系统账号
|
|
23
|
+
* @property {"stdio"|"sse"|"http"} transport 传输方式
|
|
24
|
+
* @property {number} httpPort HTTP 监听端口
|
|
25
|
+
* @property {string} httpHost HTTP 监听地址
|
|
26
|
+
* @property {string} httpPath HTTP 端点路径
|
|
27
|
+
* @property {string|undefined} authToken HTTP 访问令牌(Bearer),HTTP 模式下必填
|
|
17
28
|
*/
|
|
18
29
|
|
|
19
30
|
/**
|
|
20
|
-
*
|
|
31
|
+
* 内置环境
|
|
21
32
|
*
|
|
22
|
-
* <p
|
|
33
|
+
* <p>只保留本地开发地址。内网地址一律由使用方配置,避免随包公开。</p>
|
|
23
34
|
*/
|
|
24
|
-
export const
|
|
35
|
+
export const BUILTIN_ENVIRONMENTS = {
|
|
25
36
|
local: {
|
|
26
37
|
label: "本地开发",
|
|
27
38
|
baseUrls: ["http://localhost:8081"],
|
|
28
39
|
},
|
|
29
|
-
sit: {
|
|
30
|
-
label: "集成测试",
|
|
31
|
-
baseUrls: ["http://10.1.1.197:8081"],
|
|
32
|
-
},
|
|
33
|
-
prd: {
|
|
34
|
-
label: "生产",
|
|
35
|
-
baseUrls: ["http://10.32.30.208:8081", "http://10.32.30.209:8081"],
|
|
36
|
-
},
|
|
37
40
|
};
|
|
38
41
|
|
|
39
|
-
/**
|
|
40
|
-
* 默认环境
|
|
41
|
-
*
|
|
42
|
-
* <p>默认走 local:打错环境名不至于直接连到生产。</p>
|
|
43
|
-
*/
|
|
44
42
|
export const DEFAULT_ENV = "local";
|
|
45
|
-
|
|
46
43
|
export const DEFAULT_TIMEOUT_MS = 10000;
|
|
44
|
+
export const DEFAULT_HTTP_PORT = 3100;
|
|
45
|
+
export const DEFAULT_HTTP_HOST = "127.0.0.1";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 传输方式
|
|
49
|
+
*/
|
|
50
|
+
export const TRANSPORTS = ["stdio", "sse", "http"];
|
|
47
51
|
|
|
48
52
|
export const USAGE = `lead-mcp —— 利得零售系统 MCP Server
|
|
49
53
|
|
|
50
54
|
用法:
|
|
51
55
|
lead-mcp [选项]
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
--env <环境>
|
|
55
|
-
|
|
56
|
-
.map(([name, env]) => `${name}(${env.label}) ${env.baseUrls.join(",")}`)
|
|
57
|
-
.join("\n ")}
|
|
58
|
-
也可用环境变量 LEAD_MCP_ENV;未知环境名会直接报错退出
|
|
57
|
+
服务地址(dmmp-system 275)
|
|
58
|
+
--env <环境> 目标环境名,从用户配置文件的 environments 里取
|
|
59
|
+
也可用环境变量 LEAD_MCP_ENV
|
|
59
60
|
--base-url <url> 直接指定服务地址,逗号分隔多个时按顺序故障转移
|
|
60
61
|
优先级高于 --env,也可用环境变量 LEAD_MCP_BASE_URL
|
|
62
|
+
例:--base-url http://10.1.1.197:8081
|
|
63
|
+
--base-url http://a:8081,http://b:8081
|
|
64
|
+
|
|
65
|
+
传输方式
|
|
66
|
+
--transport <方式> stdio(默认,本地 MCP Client)/ sse / http
|
|
67
|
+
也可用环境变量 LEAD_MCP_TRANSPORT
|
|
68
|
+
--http-port <端口> HTTP 监听端口,默认 ${DEFAULT_HTTP_PORT}
|
|
69
|
+
--http-host <地址> HTTP 监听地址,默认 ${DEFAULT_HTTP_HOST}
|
|
70
|
+
需要对外暴露时传 0.0.0.0
|
|
71
|
+
--http-path <路径> HTTP 端点路径,默认 sse 用 /sse、http 用 /mcp
|
|
72
|
+
--auth-token <token> HTTP 访问令牌,HTTP 模式下必填(Bearer 认证)
|
|
73
|
+
也可用环境变量 LEAD_MCP_AUTH_TOKEN
|
|
74
|
+
|
|
75
|
+
通用
|
|
61
76
|
--timeout <ms> 单次请求超时毫秒数,默认 ${DEFAULT_TIMEOUT_MS}
|
|
62
|
-
|
|
63
|
-
--
|
|
64
|
-
--operator <工号> 操作人标识,用于服务端限流与审计
|
|
65
|
-
也可用环境变量 LEAD_MCP_OPERATOR;未配置时退化为操作系统账号
|
|
77
|
+
--token <token> dmmp-system 访问令牌,可选
|
|
78
|
+
--operator <工号> 操作人标识,用于服务端限流与审计;不配置则用操作系统账号
|
|
66
79
|
-h, --help 查看帮助
|
|
80
|
+
|
|
81
|
+
用户配置文件(默认 ~/.lead-mcp/config.json,可用 LEAD_MCP_CONFIG 指定):
|
|
82
|
+
{
|
|
83
|
+
"defaultEnvironment": "sit",
|
|
84
|
+
"environments": {
|
|
85
|
+
"sit": ["http://10.1.1.197:8081"],
|
|
86
|
+
"prd": ["http://10.32.30.208:8081", "http://10.32.30.209:8081"]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
67
89
|
`;
|
|
68
90
|
|
|
91
|
+
/**
|
|
92
|
+
* 配置文件路径
|
|
93
|
+
*
|
|
94
|
+
* @returns {string} 路径
|
|
95
|
+
*/
|
|
96
|
+
export function resolveConfigFile() {
|
|
97
|
+
return (
|
|
98
|
+
process.env.LEAD_MCP_CONFIG ??
|
|
99
|
+
path.join(os.homedir(), ".lead-mcp", "config.json")
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 读取用户配置文件
|
|
105
|
+
*
|
|
106
|
+
* @returns {{defaultEnvironment?: string, environments?: Record<string, string[]>}} 配置
|
|
107
|
+
* @throws {Error} 配置文件存在但格式非法
|
|
108
|
+
*/
|
|
109
|
+
export function loadUserConfig() {
|
|
110
|
+
const file = resolveConfigFile();
|
|
111
|
+
try {
|
|
112
|
+
const raw = fs.readFileSync(file, "utf8");
|
|
113
|
+
const parsed = JSON.parse(raw);
|
|
114
|
+
if (!parsed || typeof parsed !== "object") {
|
|
115
|
+
throw new Error("格式必须是 JSON 对象");
|
|
116
|
+
}
|
|
117
|
+
return parsed;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error.code === "ENOENT") {
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
throw new Error(`读取配置文件失败 ${file}:${error.message}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
69
126
|
/**
|
|
70
127
|
* 解析操作人的操作系统账号
|
|
71
128
|
*
|
|
@@ -108,23 +165,6 @@ export function parseArgs(argv) {
|
|
|
108
165
|
return result;
|
|
109
166
|
}
|
|
110
167
|
|
|
111
|
-
/**
|
|
112
|
-
* 归一化环境名并校验
|
|
113
|
-
*
|
|
114
|
-
* @param {string} name 环境名
|
|
115
|
-
* @returns {string} 环境名
|
|
116
|
-
* @throws {Error} 环境名非法
|
|
117
|
-
*/
|
|
118
|
-
export function normalizeEnv(name) {
|
|
119
|
-
const env = String(name).trim().toLowerCase();
|
|
120
|
-
if (!ENVIRONMENTS[env]) {
|
|
121
|
-
throw new Error(
|
|
122
|
-
`未知环境 "${name}",可选值:${Object.keys(ENVIRONMENTS).join(" / ")}`,
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
return env;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
168
|
/**
|
|
129
169
|
* 解析服务地址列表
|
|
130
170
|
*
|
|
@@ -138,32 +178,90 @@ export function splitBaseUrls(value) {
|
|
|
138
178
|
.filter((item) => item.length > 0);
|
|
139
179
|
}
|
|
140
180
|
|
|
181
|
+
/**
|
|
182
|
+
* 解析传输方式
|
|
183
|
+
*
|
|
184
|
+
* @param {string} name 传输方式
|
|
185
|
+
* @returns {"stdio"|"sse"|"http"} 传输方式
|
|
186
|
+
* @throws {Error} 非法
|
|
187
|
+
*/
|
|
188
|
+
export function normalizeTransport(name) {
|
|
189
|
+
const transport = String(name).trim().toLowerCase();
|
|
190
|
+
if (!TRANSPORTS.includes(transport)) {
|
|
191
|
+
throw new Error(`未知传输方式 "${name}",可选值:${TRANSPORTS.join(" / ")}`);
|
|
192
|
+
}
|
|
193
|
+
return transport;
|
|
194
|
+
}
|
|
195
|
+
|
|
141
196
|
/**
|
|
142
197
|
* 加载配置
|
|
143
198
|
*
|
|
144
199
|
* @param {string[]} [argv]
|
|
145
200
|
* @returns {LeadMcpConfig}
|
|
146
|
-
* @throws {Error}
|
|
201
|
+
* @throws {Error} 配置非法
|
|
147
202
|
*/
|
|
148
203
|
export function loadConfig(argv = process.argv.slice(2)) {
|
|
149
204
|
const args = parseArgs(argv);
|
|
205
|
+
const userConfig = loadUserConfig();
|
|
150
206
|
|
|
151
207
|
const rawEnv = args.get("env") ?? process.env.LEAD_MCP_ENV;
|
|
152
208
|
const rawBaseUrl = args.get("base-url") ?? process.env.LEAD_MCP_BASE_URL;
|
|
153
209
|
const hasBaseUrl = Boolean(rawBaseUrl) && rawBaseUrl !== "true";
|
|
154
210
|
|
|
155
|
-
//
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
211
|
+
// 解析服务地址:显式地址 > 用户配置的环境 > 内置 local
|
|
212
|
+
let env;
|
|
213
|
+
let baseUrls;
|
|
214
|
+
if (hasBaseUrl) {
|
|
215
|
+
env = rawEnv && rawEnv !== "true" ? String(rawEnv).trim() : "custom";
|
|
216
|
+
baseUrls = splitBaseUrls(rawBaseUrl);
|
|
217
|
+
} else {
|
|
218
|
+
const envName =
|
|
219
|
+
(rawEnv && rawEnv !== "true" ? rawEnv : undefined) ??
|
|
220
|
+
userConfig.defaultEnvironment ??
|
|
221
|
+
DEFAULT_ENV;
|
|
222
|
+
const configured = userConfig.environments?.[envName];
|
|
223
|
+
const builtin = BUILTIN_ENVIRONMENTS[envName];
|
|
224
|
+
if (!configured && !builtin) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`未知环境 "${envName}"。请在 ${resolveConfigFile()} 的 environments 里配置,` +
|
|
227
|
+
`或直接用 --base-url 指定地址`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
env = envName;
|
|
231
|
+
baseUrls = configured ?? builtin.baseUrls;
|
|
232
|
+
}
|
|
233
|
+
if (!Array.isArray(baseUrls) || baseUrls.length === 0) {
|
|
234
|
+
throw new Error(`环境 "${env}" 没有配置服务地址`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 传输方式
|
|
238
|
+
const rawTransport = args.get("transport") ?? process.env.LEAD_MCP_TRANSPORT;
|
|
239
|
+
const transport = normalizeTransport(
|
|
240
|
+
rawTransport && rawTransport !== "true" ? rawTransport : "stdio",
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const portRaw = args.get("http-port") ?? process.env.LEAD_MCP_HTTP_PORT;
|
|
244
|
+
const httpPort = portRaw ? Number.parseInt(portRaw, 10) : DEFAULT_HTTP_PORT;
|
|
245
|
+
const hostRaw = args.get("http-host") ?? process.env.LEAD_MCP_HTTP_HOST;
|
|
246
|
+
const httpHost = hostRaw && hostRaw !== "true" ? hostRaw : DEFAULT_HTTP_HOST;
|
|
247
|
+
const pathRaw = args.get("http-path");
|
|
248
|
+
const httpPath =
|
|
249
|
+
pathRaw && pathRaw !== "true"
|
|
250
|
+
? pathRaw
|
|
251
|
+
: transport === "sse"
|
|
252
|
+
? "/sse"
|
|
253
|
+
: transport === "http"
|
|
254
|
+
? "/mcp"
|
|
255
|
+
: "/mcp";
|
|
256
|
+
|
|
257
|
+
const authRaw = args.get("auth-token") ?? process.env.LEAD_MCP_AUTH_TOKEN;
|
|
258
|
+
const authToken = authRaw && authRaw !== "true" ? authRaw : undefined;
|
|
259
|
+
|
|
260
|
+
// HTTP 模式会被暴露到公网(典型是走 ng 隧道),没有令牌等于不设防
|
|
261
|
+
if (transport !== "stdio" && !authToken) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
"HTTP 模式必须配置访问令牌:请用 --auth-token 或环境变量 LEAD_MCP_AUTH_TOKEN",
|
|
264
|
+
);
|
|
167
265
|
}
|
|
168
266
|
|
|
169
267
|
const timeoutRaw = args.get("timeout") ?? process.env.LEAD_MCP_TIMEOUT_MS;
|
|
@@ -180,6 +278,11 @@ export function loadConfig(argv = process.argv.slice(2)) {
|
|
|
180
278
|
// 未显式配置工号时退化为操作系统账号,保证服务端审计至少有「谁」
|
|
181
279
|
operator: operator ?? resolveOperatorOs(),
|
|
182
280
|
operatorOs: resolveOperatorOs(),
|
|
281
|
+
transport,
|
|
282
|
+
httpPort: Number.isFinite(httpPort) && httpPort > 0 ? httpPort : DEFAULT_HTTP_PORT,
|
|
283
|
+
httpHost,
|
|
284
|
+
httpPath,
|
|
285
|
+
authToken,
|
|
183
286
|
};
|
|
184
287
|
}
|
|
185
288
|
|