@szc-ft/mcp-szcd-client 0.11.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/agents/szcd-component-expert.md +147 -0
- package/agents/szcd-component-expert.trae.md +145 -0
- package/commands/szcd-mcp-url.md +25 -0
- package/mcp-proxy.js +543 -0
- package/package.json +56 -0
- package/scripts/lib/claude-code.js +342 -0
- package/scripts/lib/common.js +161 -0
- package/scripts/lib/opencode.js +37 -0
- package/scripts/lib/qoder.js +426 -0
- package/scripts/lib/qwen-code.js +408 -0
- package/scripts/lib/trae-cli.js +337 -0
- package/scripts/lib/trae-ide.js +198 -0
- package/scripts/lib/trae.js +65 -0
- package/scripts/postinstall.js +203 -0
- package/scripts/update-mcp-url.js +146 -0
- package/skill/SKILL.md +897 -0
- package/standard-skill/SKILL.md +1509 -0
package/mcp-proxy.js
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Proxy - stdio-to-StreamableHTTP/SSE 桥接代理
|
|
4
|
+
*
|
|
5
|
+
* 用于向后兼容旧的客户端配置。
|
|
6
|
+
* 新客户端可以直接连接 Streamable HTTP 端点(POST /mcp)。
|
|
7
|
+
*
|
|
8
|
+
* 工作原理:
|
|
9
|
+
* 1. IDE 通过 stdio 发送 JSON-RPC 消息给本代理
|
|
10
|
+
* 2. 本代理通过 Streamable HTTP(POST /mcp)转发到远程 MCP 服务器
|
|
11
|
+
* 3. 服务器的响应通过 HTTP 响应返回,本代理再通过 stdout 转发给 IDE
|
|
12
|
+
*
|
|
13
|
+
* 传输模式自动检测:
|
|
14
|
+
* - 优先使用 Streamable HTTP(POST /mcp)
|
|
15
|
+
* - 回退到 SSE(GET /sse + POST /message)
|
|
16
|
+
* - 最终回退到旧版 REST API(/tools/list, /tools/call)
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import http from "node:http";
|
|
20
|
+
import https from "node:https";
|
|
21
|
+
import readline from "node:readline";
|
|
22
|
+
import fs from "node:fs";
|
|
23
|
+
import path from "node:path";
|
|
24
|
+
import os from "node:os";
|
|
25
|
+
|
|
26
|
+
// ==================== 配置加载 ====================
|
|
27
|
+
|
|
28
|
+
function getConfigFilePath() {
|
|
29
|
+
const platform = os.platform();
|
|
30
|
+
if (platform === "win32") {
|
|
31
|
+
return path.join(process.env.USERPROFILE || process.env.HOME, ".szcd-mcp-config.json");
|
|
32
|
+
} else {
|
|
33
|
+
return path.join(process.env.HOME || os.homedir(), ".szcd-mcp-config.json");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function loadConfigFromFile() {
|
|
38
|
+
const configPath = getConfigFilePath();
|
|
39
|
+
try {
|
|
40
|
+
if (fs.existsSync(configPath)) {
|
|
41
|
+
const content = fs.readFileSync(configPath, "utf8");
|
|
42
|
+
return JSON.parse(content);
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
// 配置文件读取失败,使用默认值
|
|
46
|
+
}
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const fileConfig = loadConfigFromFile();
|
|
51
|
+
|
|
52
|
+
// 配置优先级:环境变量 > 配置文件 > 默认值
|
|
53
|
+
const SERVER_URL = process.env.MCP_SERVER_URL || fileConfig.MCP_SERVER_URL || "http://localhost:3456";
|
|
54
|
+
const SERVER_TIMEOUT = parseInt(process.env.MCP_TIMEOUT || fileConfig.MCP_TIMEOUT || "120000", 10);
|
|
55
|
+
const SERVER_API_KEY = process.env.MCP_API_KEY || fileConfig.MCP_API_KEY || "";
|
|
56
|
+
|
|
57
|
+
// ==================== 日志 ====================
|
|
58
|
+
|
|
59
|
+
function log(msg) {
|
|
60
|
+
console.error(`[${new Date().toISOString()}] ${msg}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
log(`Server URL: ${SERVER_URL}`);
|
|
64
|
+
log(`Timeout: ${SERVER_TIMEOUT}`);
|
|
65
|
+
log(`API Key: ${SERVER_API_KEY ? "set" : "not set"}`);
|
|
66
|
+
|
|
67
|
+
// ==================== 测试模式 ====================
|
|
68
|
+
|
|
69
|
+
if (process.argv.includes("--test")) {
|
|
70
|
+
console.log("Testing MCP proxy connection...");
|
|
71
|
+
console.log(`Server URL: ${SERVER_URL}`);
|
|
72
|
+
|
|
73
|
+
const url = new URL("/health", SERVER_URL);
|
|
74
|
+
const requestImpl = url.protocol === "https:" ? https : http;
|
|
75
|
+
|
|
76
|
+
const req = requestImpl.get(url, { timeout: SERVER_TIMEOUT }, (res) => {
|
|
77
|
+
let data = "";
|
|
78
|
+
res.on("data", (chunk) => (data += chunk));
|
|
79
|
+
res.on("end", () => {
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(data);
|
|
82
|
+
if (parsed.status === "ok") {
|
|
83
|
+
console.log("Connection test successful!", parsed);
|
|
84
|
+
process.exit(0);
|
|
85
|
+
} else {
|
|
86
|
+
console.error("Connection test failed: unexpected response", parsed);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
} catch {
|
|
90
|
+
console.error("Connection test failed: invalid JSON response");
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
req.on("error", (e) => {
|
|
97
|
+
console.error("Connection test failed:", e.message);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
req.on("timeout", () => {
|
|
102
|
+
req.destroy();
|
|
103
|
+
console.error("Connection test failed: timeout");
|
|
104
|
+
process.exit(1);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ==================== 传输模式 ====================
|
|
111
|
+
|
|
112
|
+
const TRANSPORT_MODE = {
|
|
113
|
+
STREAMABLE_HTTP: "streamable_http",
|
|
114
|
+
SSE: "sse",
|
|
115
|
+
LEGACY_REST: "legacy_rest",
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
let currentMode = null;
|
|
119
|
+
let httpSessionId = null;
|
|
120
|
+
|
|
121
|
+
// ==================== Streamable HTTP 转发 ====================
|
|
122
|
+
|
|
123
|
+
function forwardToStreamableHttp(msg) {
|
|
124
|
+
return new Promise((resolve) => {
|
|
125
|
+
const url = new URL("/mcp", SERVER_URL);
|
|
126
|
+
const isHttps = url.protocol === "https:";
|
|
127
|
+
const requestImpl = isHttps ? https : http;
|
|
128
|
+
|
|
129
|
+
const headers = {
|
|
130
|
+
"Content-Type": "application/json",
|
|
131
|
+
"Accept": "application/json, text/event-stream",
|
|
132
|
+
"User-Agent": "Claude-Code-MCP-Proxy/0.4.3",
|
|
133
|
+
};
|
|
134
|
+
if (SERVER_API_KEY) {
|
|
135
|
+
headers["X-API-Key"] = SERVER_API_KEY;
|
|
136
|
+
headers["Authorization"] = `Bearer ${SERVER_API_KEY}`;
|
|
137
|
+
}
|
|
138
|
+
if (httpSessionId) {
|
|
139
|
+
headers["Mcp-Session-Id"] = httpSessionId;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const postData = JSON.stringify(msg);
|
|
143
|
+
|
|
144
|
+
const options = {
|
|
145
|
+
hostname: url.hostname,
|
|
146
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
147
|
+
path: url.pathname,
|
|
148
|
+
method: "POST",
|
|
149
|
+
headers,
|
|
150
|
+
timeout: SERVER_TIMEOUT,
|
|
151
|
+
rejectUnauthorized: false,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const req = requestImpl.request(options, (res) => {
|
|
155
|
+
log(`HTTP Response Status: ${res.statusCode}, Headers: ${JSON.stringify(res.headers)}`);
|
|
156
|
+
|
|
157
|
+
// 保存 session id
|
|
158
|
+
const sid = res.headers["mcp-session-id"];
|
|
159
|
+
if (sid) {
|
|
160
|
+
httpSessionId = sid;
|
|
161
|
+
log(`Got session ID: ${sid}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let data = "";
|
|
165
|
+
res.on("data", (chunk) => (data += chunk));
|
|
166
|
+
res.on("end", () => {
|
|
167
|
+
log(`HTTP Response Body: ${data.substring(0, 500)}${data.length > 500 ? '...' : ''}`);
|
|
168
|
+
|
|
169
|
+
const contentType = res.headers["content-type"] || "";
|
|
170
|
+
|
|
171
|
+
if (res.statusCode === 406) {
|
|
172
|
+
// 特殊处理 406 错误 - 客户端 Accept 头部不匹配
|
|
173
|
+
resolve({
|
|
174
|
+
jsonrpc: "2.0",
|
|
175
|
+
id: msg.id ?? null,
|
|
176
|
+
error: {
|
|
177
|
+
code: -32000,
|
|
178
|
+
message: `Not Acceptable: Server requires Accept header with both 'application/json' and 'text/event-stream'. Response: ${data}`
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
} else if (contentType.includes("text/event-stream")) {
|
|
182
|
+
// SSE 响应:解析 SSE 事件中的 JSON-RPC 消息
|
|
183
|
+
const lines = data.split("\n");
|
|
184
|
+
for (const line of lines) {
|
|
185
|
+
if (line.startsWith("data:")) {
|
|
186
|
+
const jsonStr = line.slice(5).trim();
|
|
187
|
+
try {
|
|
188
|
+
const parsed = JSON.parse(jsonStr);
|
|
189
|
+
resolve(parsed);
|
|
190
|
+
return;
|
|
191
|
+
} catch {
|
|
192
|
+
// 继续尝试下一行
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// 没有找到有效数据
|
|
197
|
+
resolve({
|
|
198
|
+
jsonrpc: "2.0",
|
|
199
|
+
id: msg.id ?? null,
|
|
200
|
+
error: { code: -32000, message: "No valid JSON-RPC response in SSE stream" },
|
|
201
|
+
});
|
|
202
|
+
} else {
|
|
203
|
+
// JSON 响应
|
|
204
|
+
try {
|
|
205
|
+
const parsed = JSON.parse(data);
|
|
206
|
+
resolve(parsed);
|
|
207
|
+
} catch {
|
|
208
|
+
resolve({
|
|
209
|
+
jsonrpc: "2.0",
|
|
210
|
+
id: msg.id ?? null,
|
|
211
|
+
error: {
|
|
212
|
+
code: -32000,
|
|
213
|
+
message: `Server returned invalid JSON. Status: ${res.statusCode}. Raw body: ${data.slice(0, 200)}`,
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
req.on("error", (e) => {
|
|
222
|
+
log(`HTTP Request Error: ${e.message}`);
|
|
223
|
+
resolve({
|
|
224
|
+
jsonrpc: "2.0",
|
|
225
|
+
id: msg.id ?? null,
|
|
226
|
+
error: { code: -32000, message: `HTTP request failed: ${e.message}` },
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
req.on("timeout", () => {
|
|
231
|
+
req.destroy();
|
|
232
|
+
log('HTTP Request Timeout');
|
|
233
|
+
resolve({
|
|
234
|
+
jsonrpc: "2.0",
|
|
235
|
+
id: msg.id ?? null,
|
|
236
|
+
error: { code: -32000, message: "Request timeout" },
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
log(`Sending HTTP Request: ${JSON.stringify({method: options.method, url: url.toString(), headers: headers})}`);
|
|
241
|
+
req.write(postData);
|
|
242
|
+
req.end();
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ==================== SSE 转发 ====================
|
|
247
|
+
|
|
248
|
+
let sseSessionId = null;
|
|
249
|
+
let sseMessageEndpoint = null;
|
|
250
|
+
|
|
251
|
+
function connectSSE() {
|
|
252
|
+
return new Promise((resolve, reject) => {
|
|
253
|
+
const url = new URL("/sse", SERVER_URL);
|
|
254
|
+
const requestImpl = url.protocol === "https:" ? https : http;
|
|
255
|
+
|
|
256
|
+
const headers = {
|
|
257
|
+
"Accept": "text/event-stream",
|
|
258
|
+
"Cache-Control": "no-cache",
|
|
259
|
+
"User-Agent": "Claude-Code-MCP-Proxy/0.4.3",
|
|
260
|
+
};
|
|
261
|
+
if (SERVER_API_KEY) {
|
|
262
|
+
headers["X-API-Key"] = SERVER_API_KEY;
|
|
263
|
+
headers["Authorization"] = `Bearer ${SERVER_API_KEY}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
log(`Connecting to SSE endpoint: ${url.toString()}, headers: ${JSON.stringify(headers)}`);
|
|
267
|
+
|
|
268
|
+
const req = requestImpl.get(url, { headers, timeout: SERVER_TIMEOUT }, (res) => {
|
|
269
|
+
log(`SSE Connection Response Status: ${res.statusCode}, Headers: ${JSON.stringify(res.headers)}`);
|
|
270
|
+
|
|
271
|
+
let buffer = "";
|
|
272
|
+
|
|
273
|
+
res.on("data", (chunk) => {
|
|
274
|
+
const chunkStr = chunk.toString();
|
|
275
|
+
log(`SSE Data Chunk: ${chunkStr.substring(0, 200)}...`);
|
|
276
|
+
|
|
277
|
+
buffer += chunkStr;
|
|
278
|
+
|
|
279
|
+
const lines = buffer.split("\n");
|
|
280
|
+
buffer = lines.pop() || "";
|
|
281
|
+
|
|
282
|
+
for (const line of lines) {
|
|
283
|
+
if (line.startsWith("event: endpoint")) {
|
|
284
|
+
// 下一个 data 行包含 endpoint URL
|
|
285
|
+
} else if (line.startsWith("data:") && !sseMessageEndpoint) {
|
|
286
|
+
const data = line.slice(5).trim();
|
|
287
|
+
sseMessageEndpoint = data;
|
|
288
|
+
sseSessionId = new URL(data, SERVER_URL).searchParams.get("sessionId");
|
|
289
|
+
log(`SSE endpoint: ${sseMessageEndpoint}, sessionId: ${sseSessionId}`);
|
|
290
|
+
resolve();
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
res.on("end", () => {
|
|
296
|
+
log("SSE connection closed");
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
req.on("error", (e) => {
|
|
301
|
+
log(`SSE connection error: ${e.message}`);
|
|
302
|
+
reject(e);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
req.on("timeout", () => {
|
|
306
|
+
req.destroy();
|
|
307
|
+
log("SSE connection timeout");
|
|
308
|
+
reject(new Error("SSE connection timeout"));
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function forwardToSSEMessageEndpoint(msg) {
|
|
314
|
+
return new Promise((resolve) => {
|
|
315
|
+
if (!sseMessageEndpoint) {
|
|
316
|
+
resolve({
|
|
317
|
+
jsonrpc: "2.0",
|
|
318
|
+
id: msg.id ?? null,
|
|
319
|
+
error: { code: -32000, message: "SSE not connected" },
|
|
320
|
+
});
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const url = new URL(sseMessageEndpoint, SERVER_URL);
|
|
325
|
+
const isHttps = url.protocol === "https:";
|
|
326
|
+
const requestImpl = isHttps ? https : http;
|
|
327
|
+
|
|
328
|
+
const headers = {
|
|
329
|
+
"Content-Type": "application/json",
|
|
330
|
+
"Accept": "application/json, text/event-stream",
|
|
331
|
+
"User-Agent": "Claude-Code-MCP-Proxy/0.4.3",
|
|
332
|
+
};
|
|
333
|
+
if (SERVER_API_KEY) {
|
|
334
|
+
headers["X-API-Key"] = SERVER_API_KEY;
|
|
335
|
+
headers["Authorization"] = `Bearer ${SERVER_API_KEY}`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const postData = JSON.stringify(msg);
|
|
339
|
+
|
|
340
|
+
const options = {
|
|
341
|
+
hostname: url.hostname,
|
|
342
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
343
|
+
path: url.pathname + url.search,
|
|
344
|
+
method: "POST",
|
|
345
|
+
headers,
|
|
346
|
+
timeout: SERVER_TIMEOUT,
|
|
347
|
+
rejectUnauthorized: false,
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const req = requestImpl.request(options, (res) => {
|
|
351
|
+
log(`SSE Message Response Status: ${res.statusCode}, Headers: ${JSON.stringify(res.headers)}`);
|
|
352
|
+
|
|
353
|
+
let data = "";
|
|
354
|
+
res.on("data", (chunk) => (data += chunk));
|
|
355
|
+
res.on("end", () => {
|
|
356
|
+
log(`SSE Message Response Body: ${data.substring(0, 500)}${data.length > 500 ? '...' : ''}`);
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
resolve(JSON.parse(data));
|
|
360
|
+
} catch {
|
|
361
|
+
resolve({
|
|
362
|
+
jsonrpc: "2.0",
|
|
363
|
+
id: msg.id ?? null,
|
|
364
|
+
error: { code: -32000, message: `Invalid JSON from SSE message endpoint: ${data.substring(0, 200)}` },
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
req.on("error", (e) => {
|
|
371
|
+
log(`SSE Message Request Error: ${e.message}`);
|
|
372
|
+
resolve({
|
|
373
|
+
jsonrpc: "2.0",
|
|
374
|
+
id: msg.id ?? null,
|
|
375
|
+
error: { code: -32000, message: `SSE message request failed: ${e.message}` },
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
req.on("timeout", () => {
|
|
380
|
+
req.destroy();
|
|
381
|
+
log(`SSE Message Request Timeout`);
|
|
382
|
+
resolve({
|
|
383
|
+
jsonrpc: "2.0",
|
|
384
|
+
id: msg.id ?? null,
|
|
385
|
+
error: { code: -32000, message: "SSE message request timeout" },
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
log(`Sending SSE Message Request: ${JSON.stringify({method: options.method, url: url.toString(), headers: headers})}`);
|
|
390
|
+
req.write(postData);
|
|
391
|
+
req.end();
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// ==================== 旧版 REST API 转发 ====================
|
|
396
|
+
|
|
397
|
+
function forwardToRestApi(msg) {
|
|
398
|
+
return new Promise((resolve) => {
|
|
399
|
+
if (msg.method === "initialize") {
|
|
400
|
+
resolve({
|
|
401
|
+
jsonrpc: "2.0",
|
|
402
|
+
id: msg.id ?? null,
|
|
403
|
+
result: {
|
|
404
|
+
protocolVersion: "2024-11-05",
|
|
405
|
+
serverInfo: { name: "mcp-szcd-component-helper-proxy", version: "0.4.2" },
|
|
406
|
+
capabilities: { tools: {} },
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (msg.method === "ping") {
|
|
412
|
+
resolve({ jsonrpc: "2.0", id: msg.id ?? null, result: {} });
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
resolve({
|
|
417
|
+
jsonrpc: "2.0",
|
|
418
|
+
id: msg.id ?? null,
|
|
419
|
+
error: { code: -32601, message: "Method not found (legacy REST API)" },
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ==================== 自动检测传输模式 ====================
|
|
425
|
+
|
|
426
|
+
async function detectTransportMode() {
|
|
427
|
+
// 1. 先尝试 Streamable HTTP
|
|
428
|
+
try {
|
|
429
|
+
const result = await forwardToStreamableHttp({
|
|
430
|
+
jsonrpc: "2.0",
|
|
431
|
+
id: "detect",
|
|
432
|
+
method: "initialize",
|
|
433
|
+
params: {
|
|
434
|
+
protocolVersion: "2024-11-05",
|
|
435
|
+
capabilities: {},
|
|
436
|
+
clientInfo: { name: "mcp-proxy-detect", version: "1.0.0" },
|
|
437
|
+
},
|
|
438
|
+
});
|
|
439
|
+
if (result.result || (result.id === "detect" && !result.error?.message?.includes("Not Acceptable"))) {
|
|
440
|
+
currentMode = TRANSPORT_MODE.STREAMABLE_HTTP;
|
|
441
|
+
log(`Transport mode: Streamable HTTP (POST /mcp)`);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
} catch (e) {
|
|
445
|
+
log(`Streamable HTTP detection failed: ${e.message}`);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// 2. 尝试 SSE
|
|
449
|
+
try {
|
|
450
|
+
await connectSSE();
|
|
451
|
+
if (sseMessageEndpoint) {
|
|
452
|
+
currentMode = TRANSPORT_MODE.SSE;
|
|
453
|
+
log(`Transport mode: SSE (GET /sse + POST /message)`);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
} catch (e) {
|
|
457
|
+
log(`SSE detection failed: ${e.message}`);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// 3. 回退到旧版 REST API
|
|
461
|
+
currentMode = TRANSPORT_MODE.LEGACY_REST;
|
|
462
|
+
log(`Transport mode: Legacy REST API`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ==================== 主逻辑 ====================
|
|
466
|
+
|
|
467
|
+
async function main() {
|
|
468
|
+
await detectTransportMode();
|
|
469
|
+
|
|
470
|
+
const rl = readline.createInterface({
|
|
471
|
+
input: process.stdin,
|
|
472
|
+
terminal: false,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
rl.on("line", async (line) => {
|
|
476
|
+
if (!line.trim()) return;
|
|
477
|
+
|
|
478
|
+
log(`Received stdin: ${line}`);
|
|
479
|
+
|
|
480
|
+
let msgId = null;
|
|
481
|
+
try {
|
|
482
|
+
const msg = JSON.parse(line);
|
|
483
|
+
msgId = msg.id ?? null;
|
|
484
|
+
|
|
485
|
+
let response;
|
|
486
|
+
|
|
487
|
+
switch (currentMode) {
|
|
488
|
+
case TRANSPORT_MODE.STREAMABLE_HTTP:
|
|
489
|
+
response = await forwardToStreamableHttp(msg);
|
|
490
|
+
break;
|
|
491
|
+
case TRANSPORT_MODE.SSE:
|
|
492
|
+
if (msg.method === "initialize") {
|
|
493
|
+
// SSE 模式下 initialize 已经在检测时完成
|
|
494
|
+
response = {
|
|
495
|
+
jsonrpc: "2.0",
|
|
496
|
+
id: msgId,
|
|
497
|
+
result: {
|
|
498
|
+
protocolVersion: "2024-11-05",
|
|
499
|
+
serverInfo: { name: "mcp-szcd-component-helper", version: "0.4.2" },
|
|
500
|
+
capabilities: { tools: {} },
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
} else {
|
|
504
|
+
response = await forwardToSSEMessageEndpoint(msg);
|
|
505
|
+
}
|
|
506
|
+
break;
|
|
507
|
+
default:
|
|
508
|
+
response = await forwardToRestApi(msg);
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// 构造纯净的 JSON-RPC 响应
|
|
513
|
+
const cleanResponse = {
|
|
514
|
+
jsonrpc: "2.0",
|
|
515
|
+
id: msgId,
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
if (response.error) {
|
|
519
|
+
cleanResponse.error = response.error;
|
|
520
|
+
} else {
|
|
521
|
+
cleanResponse.result = response.result !== undefined ? response.result : null;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const output = JSON.stringify(cleanResponse);
|
|
525
|
+
log(`Sending stdout: ${output}`);
|
|
526
|
+
process.stdout.write(output + "\n");
|
|
527
|
+
} catch (e) {
|
|
528
|
+
log(`Error: ${e.message}`);
|
|
529
|
+
process.stdout.write(
|
|
530
|
+
JSON.stringify({
|
|
531
|
+
jsonrpc: "2.0",
|
|
532
|
+
id: msgId,
|
|
533
|
+
error: { code: -32603, message: `Internal proxy error: ${e.message}` },
|
|
534
|
+
}) + "\n"
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
main().catch((e) => {
|
|
541
|
+
log(`Fatal error: ${e.message}`);
|
|
542
|
+
process.exit(1);
|
|
543
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@szc-ft/mcp-szcd-client",
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "MCP client for szcd component library - connects to remote MCP server via SSE or stdio proxy",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"szcd",
|
|
8
|
+
"component",
|
|
9
|
+
"ai",
|
|
10
|
+
"agent",
|
|
11
|
+
"claude",
|
|
12
|
+
"skill",
|
|
13
|
+
"proxy",
|
|
14
|
+
"sse",
|
|
15
|
+
"client"
|
|
16
|
+
],
|
|
17
|
+
"author": "szc-ft",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"type": "module",
|
|
20
|
+
"main": "mcp-proxy.js",
|
|
21
|
+
"bin": {
|
|
22
|
+
"szcd-mcp-proxy": "mcp-proxy.js",
|
|
23
|
+
"szcd-mcp-setup": "scripts/postinstall.js",
|
|
24
|
+
"szcd-mcp-update-url": "scripts/update-mcp-url.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"mcp-proxy.js",
|
|
28
|
+
"scripts/postinstall.js",
|
|
29
|
+
"scripts/update-mcp-url.js",
|
|
30
|
+
"scripts/lib/",
|
|
31
|
+
"standard-skill/",
|
|
32
|
+
"skill/",
|
|
33
|
+
"agents/",
|
|
34
|
+
"commands/",
|
|
35
|
+
"README.md"
|
|
36
|
+
],
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"postinstall": "node scripts/postinstall.js --quick",
|
|
42
|
+
"setup": "node scripts/postinstall.js"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
46
|
+
},
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/szc-ft/szcd.git",
|
|
50
|
+
"directory": "mcp/szcd-mcp-client"
|
|
51
|
+
},
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/szc-ft/szcd/issues"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/szc-ft/szcd#readme"
|
|
56
|
+
}
|