@dahawa/hawa-cli-analysis 1.0.4
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/.tools.json +7 -0
- package/.vscode/launch.json +27 -0
- package/LICENSE +21 -0
- package/README.md +196 -0
- package/_uclaude.js +165 -0
- package/anthropic-transformer.js +986 -0
- package/api-anthropic.js +279 -0
- package/api-openai.js +539 -0
- package/claude/claude-openai-proxy.js +305 -0
- package/claude/claude-proxy.js +341 -0
- package/clogger-openai.js +190 -0
- package/clogger.js +318 -0
- package/codex/mcp-client.js +556 -0
- package/codex/mcpclient.js +118 -0
- package/codex/mcpserver.js +374 -0
- package/codex/mcpserverproxy.js +144 -0
- package/codex/test.js +30 -0
- package/config.js +105 -0
- package/index.js +0 -0
- package/logger-manager.js +85 -0
- package/logger.js +112 -0
- package/mcp/claude-mcpproxy-launcher.js +5 -0
- package/mcp_oauth_tokens.js +40 -0
- package/package.json +36 -0
- package/port-manager.js +80 -0
- package/simple-transform-example.js +213 -0
- package/tests/test-lazy-load.js +36 -0
- package/tests/test.js +30 -0
- package/tests/test_mcp_proxy.js +51 -0
- package/tests/test_supabase_mcp.js +42 -0
- package/uclaude.js +221 -0
- package/ucodex-proxy.js +173 -0
- package/ucodex.js +129 -0
- package/untils.js +261 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 代理 MCP 服务是可以单独进行的,理论上一台机器只需要启动一个代理进程,便可以处理来自多个客户端的请求。
|
|
3
|
+
* 但是如果主进程关闭,代理进程也会随之关闭。
|
|
4
|
+
*/
|
|
5
|
+
import net from 'node:net';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import { pathToFileURL } from "url";
|
|
8
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
9
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
10
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
11
|
+
import {loadMCPConfig,initMCPConfig} from "../config.js"
|
|
12
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
13
|
+
import { Client as OauthClient, StreamableHTTPClientTransport as OauthStreamableHTTPClientTransport} from './mcp-client.js';
|
|
14
|
+
import {getMcpOauthTokensPath , getPipePath } from '../untils.js';
|
|
15
|
+
import LogManager from "../logger-manager.js";
|
|
16
|
+
const logger = LogManager.getSystemLogger();
|
|
17
|
+
|
|
18
|
+
const PIPE_PATH = await getPipePath();
|
|
19
|
+
|
|
20
|
+
logger.debug("MCPServer PIPE_PATH:" + PIPE_PATH);
|
|
21
|
+
|
|
22
|
+
initMCPConfig();
|
|
23
|
+
let mcpConfig = loadMCPConfig();
|
|
24
|
+
/**
|
|
25
|
+
* 启动 local mcp 服务
|
|
26
|
+
* {
|
|
27
|
+
* command:""
|
|
28
|
+
* args:[],
|
|
29
|
+
* evn:{}
|
|
30
|
+
* }
|
|
31
|
+
* @returns
|
|
32
|
+
*/
|
|
33
|
+
async function createLocalClient(config){
|
|
34
|
+
//让客户端以“子进程”方式拉起/连接本地 stdio server
|
|
35
|
+
const transport = new StdioClientTransport({
|
|
36
|
+
command:config.command,
|
|
37
|
+
args:config.args,
|
|
38
|
+
env:config.env?config.env:{}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const client = new Client({
|
|
42
|
+
name: "demo-node-client",
|
|
43
|
+
version: "1.0.0",
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
await client.connect(transport);
|
|
47
|
+
logger.debug("Client connected");
|
|
48
|
+
return client;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* url
|
|
52
|
+
* bearer_token
|
|
53
|
+
* @returns
|
|
54
|
+
*/
|
|
55
|
+
async function createRemoteClient(config){
|
|
56
|
+
// 先尝试 Streamable HTTP
|
|
57
|
+
try {
|
|
58
|
+
const client = new Client({ name: 'node-client', version: '1.0.0' });
|
|
59
|
+
const transport = new StreamableHTTPClientTransport(new URL(config.url), {
|
|
60
|
+
// 可选:传自定义 header(如 Authorization)
|
|
61
|
+
requestInit: config.bearer_token ? { headers: { Authorization: `Bearer ${config.bearer_token}` } } : undefined,
|
|
62
|
+
// 也可在这里传 Cookie / X-... 等企业网关要求的头
|
|
63
|
+
});
|
|
64
|
+
await client.connect(transport);
|
|
65
|
+
logger.debug('✅ Connected via Streamable HTTP');
|
|
66
|
+
//const tools = await client.listTools();
|
|
67
|
+
//console.log('Tools:', tools.tools.map(t => t.name));
|
|
68
|
+
return client;
|
|
69
|
+
} catch (err) {
|
|
70
|
+
logger.debug('Streamable HTTP failed, fallback to SSE...', err?.message);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 回退到 SSE(老服务或未升级的实现)
|
|
74
|
+
const client = new Client({ name: 'node-client-sse', version: '1.0.0' });
|
|
75
|
+
const sse = new SSEClientTransport(new URL(config.url), {
|
|
76
|
+
requestInit: config.bearer_token ? { headers: { Authorization: `Bearer ${config.bearer_token}` } } : undefined,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
await client.connect(sse);
|
|
81
|
+
logger.debug('✅ Connected via SSE');
|
|
82
|
+
//const tools = await client.listTools();
|
|
83
|
+
//console.log('Tools:', tools.tools.map(t => t.name));
|
|
84
|
+
return client;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 创建需要登录的远程客户端
|
|
88
|
+
* @param {} config
|
|
89
|
+
*/
|
|
90
|
+
async function createRemoteOauthClient(config){
|
|
91
|
+
//console.log("createRemoteOauthClient",config);
|
|
92
|
+
const issuer = config.issuer; //'https://radar.mcp.cloudflare.com';
|
|
93
|
+
const client = new OauthClient({ name: 'radar-demo', version: '1.0.0' });
|
|
94
|
+
const transport = new OauthStreamableHTTPClientTransport(config.url, {
|
|
95
|
+
oauth: {
|
|
96
|
+
issuer,
|
|
97
|
+
redirectUri: 'http://127.0.0.1:53175/callback',
|
|
98
|
+
tokenStorePath: getMcpOauthTokensPath(),
|
|
99
|
+
clientName: 'demo-radar-auto-reg',
|
|
100
|
+
debug: true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await client.connect(transport); // 需要登录时会自动拉起浏览器
|
|
106
|
+
//console.log('Radar tools:', await client.listTools());
|
|
107
|
+
return client;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 查询启用状态的的 MCP 配置
|
|
111
|
+
*/
|
|
112
|
+
function loadEnableMCPConfigs(){
|
|
113
|
+
let allMCPs = {
|
|
114
|
+
count: 0,
|
|
115
|
+
mcpServers: {}
|
|
116
|
+
};
|
|
117
|
+
for (const key in mcpConfig.mcpServers) {
|
|
118
|
+
let config = mcpConfig.mcpServers[key];
|
|
119
|
+
if(!config.disable){
|
|
120
|
+
allMCPs.mcpServers[key] = config;
|
|
121
|
+
allMCPs.count++;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
}
|
|
125
|
+
return allMCPs;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 过滤和改写 tools
|
|
130
|
+
* @param {string} mcpName - MCP 服务名称
|
|
131
|
+
* @param {Array} tools - 原始 tools 列表
|
|
132
|
+
* @returns {Array} - 过滤和改写后的 tools 列表
|
|
133
|
+
*/
|
|
134
|
+
function filterAndRewriteTools(mcpName, tools) {
|
|
135
|
+
const config = mcpConfig.mcpServers[mcpName];
|
|
136
|
+
if (!config || !config.tools) {
|
|
137
|
+
return tools;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let filteredTools = tools;
|
|
141
|
+
|
|
142
|
+
// 应用黑名单过滤
|
|
143
|
+
if (config.tools.blacklist && Array.isArray(config.tools.blacklist)) {
|
|
144
|
+
filteredTools = tools.filter(tool =>
|
|
145
|
+
!config.tools.blacklist.includes(tool.name)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 应用描述改写
|
|
150
|
+
if (config.tools.descriptions && typeof config.tools.descriptions === 'object') {
|
|
151
|
+
filteredTools = filteredTools.map(tool => {
|
|
152
|
+
if (config.tools.descriptions[tool.name]) {
|
|
153
|
+
return {
|
|
154
|
+
...tool,
|
|
155
|
+
description: config.tools.descriptions[tool.name]
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return tool;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return filteredTools;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let allMCPClients = {};
|
|
166
|
+
let allMCPConfigs = {};
|
|
167
|
+
let enabledMCPs = loadEnableMCPConfigs().mcpServers;
|
|
168
|
+
//console.log("Enabled MCPs:", enabledMCPs);
|
|
169
|
+
|
|
170
|
+
// 只保存配置,不立即连接
|
|
171
|
+
for (const key in enabledMCPs) {
|
|
172
|
+
allMCPConfigs[key] = enabledMCPs[key];
|
|
173
|
+
// 初始化客户端为null,表示未连接
|
|
174
|
+
allMCPClients[key] = null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
//创建本地连接
|
|
178
|
+
//let client = await createLocalClient();
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 懒加载MCP客户端
|
|
182
|
+
* 如果客户端未连接,则根据配置创建连接
|
|
183
|
+
* @param {string} name - MCP服务名称
|
|
184
|
+
* @returns {Promise<object>} - 返回连接的客户端
|
|
185
|
+
*/
|
|
186
|
+
async function getOrCreateMCPClient(name) {
|
|
187
|
+
// 如果客户端已存在且已连接,直接返回
|
|
188
|
+
if (allMCPClients[name]) {
|
|
189
|
+
return allMCPClients[name];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 获取配置
|
|
193
|
+
const config = allMCPConfigs[name];
|
|
194
|
+
if (!config) {
|
|
195
|
+
logger.error("MCP configuration not found for: " + name + " 检查配置文件是否缺失配置,或者 " + name + " 已经被禁用 " );
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
logger.debug(`懒加载创建MCP客户端: ${name}`);
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
// 根据配置类型创建客户端
|
|
203
|
+
if(config.issuer&&config.url){
|
|
204
|
+
allMCPClients[name] = await createRemoteOauthClient(config);
|
|
205
|
+
}else if (config.url) {
|
|
206
|
+
allMCPClients[name] = await createRemoteClient(config);
|
|
207
|
+
}else{
|
|
208
|
+
allMCPClients[name] = await createLocalClient(config);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
logger.debug(`MCP客户端创建成功: ${name}`);
|
|
212
|
+
return allMCPClients[name];
|
|
213
|
+
} catch (error) {
|
|
214
|
+
logger.error(`创建MCP客户端失败 ${name}:`, error);
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function getMCPClient(name) {
|
|
220
|
+
return allMCPClients[name];
|
|
221
|
+
}
|
|
222
|
+
function getMCPNameMethod(method){
|
|
223
|
+
let res = {
|
|
224
|
+
//MCP 服务名称
|
|
225
|
+
name:"",
|
|
226
|
+
//Tool 方法名称
|
|
227
|
+
method:"",
|
|
228
|
+
mcpClient:""
|
|
229
|
+
}
|
|
230
|
+
res.name = method.split("_")[0];
|
|
231
|
+
res.method = method.substring(res.name.length + 1);
|
|
232
|
+
//console.log(res.name);
|
|
233
|
+
res.mcpClient = getMCPClient(res.name);
|
|
234
|
+
//console.log(">>"+res.mcpClient);
|
|
235
|
+
return res;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
// 用户实现:统一请求处理器(返回值作为 result,抛错则作为 error)
|
|
240
|
+
export async function handle(methodfull, params, id, socket ) {
|
|
241
|
+
logger.debug(" mcpserver Handling request:" + JSON.stringify({ methodfull, params, id }));
|
|
242
|
+
let {name, mcpClient,method} = getMCPNameMethod(methodfull);
|
|
243
|
+
|
|
244
|
+
// 使用懒加载获取客户端
|
|
245
|
+
if(!mcpClient){
|
|
246
|
+
logger.debug(`MCP Client not found for: ${name}, attempting lazy load...`);
|
|
247
|
+
try {
|
|
248
|
+
mcpClient = await getOrCreateMCPClient(name);
|
|
249
|
+
if (!mcpClient) {
|
|
250
|
+
logger.error("MCP Client creation failed for: " + name);
|
|
251
|
+
throw new Error(`McpServer not found: ${name}` + " 检查 mcpserver 是否已经配置,或者被禁用 " );
|
|
252
|
+
}
|
|
253
|
+
} catch (error) {
|
|
254
|
+
logger.error(`Failed to create MCP client for ${name}:`, error);
|
|
255
|
+
throw new Error(`Failed to connect to MCP server: ${name} - ${error.message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (method === 'initialize'){
|
|
260
|
+
//新版本已经在 await client.connect(transport); 完成协商,不需要处理
|
|
261
|
+
//这里是可以通过
|
|
262
|
+
if(mcpClient._initializeInfo){
|
|
263
|
+
return mcpClient._initializeInfo;
|
|
264
|
+
}else if (mcpClient.initialize){
|
|
265
|
+
return await mcpClient.initialize({
|
|
266
|
+
clientInfo: {
|
|
267
|
+
name: 'my-client',
|
|
268
|
+
version: '0.1.0',
|
|
269
|
+
},
|
|
270
|
+
capabilities: {
|
|
271
|
+
tools: true,
|
|
272
|
+
resources: true,
|
|
273
|
+
logging: false,
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
}else{
|
|
277
|
+
let initialize = {
|
|
278
|
+
//不知道怎么获取
|
|
279
|
+
"protocolVersion":mcpClient.transport.protocolVersion?mcpClient.transport.protocolVersion:"2025-03-26",
|
|
280
|
+
"serverInfo": mcpClient.getServerVersion(),
|
|
281
|
+
"capabilities": mcpClient.getServerCapabilities(),
|
|
282
|
+
"instructions": mcpClient["_instructions"]? mcpClient["_instructions"]:""
|
|
283
|
+
};
|
|
284
|
+
//console.log("Initialize1:", JSON.stringify(initialize, null, 2));
|
|
285
|
+
return initialize;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (method === 'list') {
|
|
291
|
+
let tools = await mcpClient.listTools();
|
|
292
|
+
// 应用黑名单过滤和描述改写
|
|
293
|
+
if (tools && tools.tools) {
|
|
294
|
+
tools.tools = filterAndRewriteTools(name, tools.tools);
|
|
295
|
+
}
|
|
296
|
+
logger.debug("Tools:" + JSON.stringify(tools, null, 2));
|
|
297
|
+
return tools;
|
|
298
|
+
};
|
|
299
|
+
if (method === 'call') {
|
|
300
|
+
let result = await mcpClient.callTool({ name: params?.name, arguments: params?.arguments });
|
|
301
|
+
//console.log("Call result:", JSON.stringify(result, null, 2));
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
throw new Error(`McpServer Method not found: ${method}`);
|
|
305
|
+
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const respond = (sock, id, result, error) => {
|
|
309
|
+
const msg = { jsonrpc: '2.0', id: id ?? null };
|
|
310
|
+
error ? (msg.error = { code: -32000, message: String(error) }) : (msg.result = result);
|
|
311
|
+
sock.write(JSON.stringify(msg) + '\n');
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
export function startMCPServerProxy(){
|
|
315
|
+
let start = false
|
|
316
|
+
for(const key in mcpConfig.mcpServers){
|
|
317
|
+
if(key){
|
|
318
|
+
start =true;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if(!start){
|
|
324
|
+
logger.debug("当前没有配置 MCP Server ,无需启动代理服务。");
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const rpcserver = net.createServer((socket) => {
|
|
329
|
+
let buf = '';
|
|
330
|
+
socket.on('data', (chunk) => {
|
|
331
|
+
buf += chunk;
|
|
332
|
+
for (let i = buf.indexOf('\n'); i >= 0; i = buf.indexOf('\n')) {
|
|
333
|
+
const line = buf.slice(0, i).trim(); buf = buf.slice(i + 1);
|
|
334
|
+
//console.log("Received line:", line);
|
|
335
|
+
if (!line) continue;
|
|
336
|
+
|
|
337
|
+
let req;
|
|
338
|
+
try { req = JSON.parse(line); }
|
|
339
|
+
catch { return respond(socket, null, null, 'Parse error: invalid JSON'); }
|
|
340
|
+
|
|
341
|
+
const { jsonrpc, id, method, params } = req ?? {};
|
|
342
|
+
if (jsonrpc !== '2.0' || typeof method !== 'string' || !('id' in (req ?? {}))) {
|
|
343
|
+
return respond(socket, ('id' in (req ?? {})) ? id : null, null, 'Invalid JSON-RPC request');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
Promise.resolve(handle(req.method, req.params, req.id, socket))
|
|
347
|
+
.then((res) => respond(socket, id, res))
|
|
348
|
+
.catch((err) => respond(socket, id, null, err?.message || err));
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
socket.on('error', () => {});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
//如果已经存在删除
|
|
355
|
+
if (fs.existsSync(PIPE_PATH)){
|
|
356
|
+
try {
|
|
357
|
+
fs.unlinkSync(PIPE_PATH);
|
|
358
|
+
} catch (error) {
|
|
359
|
+
logger.debug('无法删除已存在的管道文件,可能正在被使用:', error.message);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
rpcserver.listen(PIPE_PATH, () => {
|
|
364
|
+
logger.debug('JSON-RPC server listening on', PIPE_PATH);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function main() {
|
|
370
|
+
logger.debug('Starting MCP Server Proxy...');
|
|
371
|
+
startMCPServerProxy();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
//main();
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import JsonRpcClient from "./mcpclient.js"
|
|
2
|
+
import { pathToFileURL } from "url";
|
|
3
|
+
import readline from 'node:readline';
|
|
4
|
+
import {getOptions} from "../untils.js"
|
|
5
|
+
import LogManager from "../logger-manager.js";
|
|
6
|
+
const logger = LogManager.getSystemLogger();
|
|
7
|
+
|
|
8
|
+
// ---- 2) JSON-RPC 帮助函数 ----
|
|
9
|
+
function send(resultOrError, id) {
|
|
10
|
+
const msg = { jsonrpc: '2.0', ...(resultOrError.error ? { error: resultOrError.error } : { result: resultOrError.result }), id };
|
|
11
|
+
process.stdout.write(JSON.stringify(msg) + '\n');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function error(code, message, data) {
|
|
15
|
+
return { error: { code, message, ...(data !== undefined ? { data } : {}) } };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function ok(result) {
|
|
19
|
+
return { result };
|
|
20
|
+
}
|
|
21
|
+
let mcpServerName = getOptions().mcpServerName;
|
|
22
|
+
if(!mcpServerName){
|
|
23
|
+
logger.error("MCP server name is required --mcpServerName=supabase");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
logger.debug("执行代理 MCPServer " + mcpServerName);
|
|
28
|
+
|
|
29
|
+
// 这里执行异步连接 mcpServer,不需要启动的时候全部连接
|
|
30
|
+
let mcpclient = new JsonRpcClient();
|
|
31
|
+
|
|
32
|
+
logger.debug("执行代理 MCPServer , 创建客户端成功 " );
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
// ---- 3) 处理 JSON-RPC 请求 ----
|
|
36
|
+
async function handleRequest({ id, method, params }) {
|
|
37
|
+
logger.debug(` handleRequest ${mcpServerName} ` + JSON.stringify({id,method,params}));
|
|
38
|
+
try {
|
|
39
|
+
// a) 初始化握手(极简实现)
|
|
40
|
+
if (method === 'initialize') {
|
|
41
|
+
/**
|
|
42
|
+
return send(
|
|
43
|
+
ok({
|
|
44
|
+
protocolVersion: '2025-03-26', // 示例:协议版本字符串
|
|
45
|
+
capabilities: { tools: {} }, // 表示支持工具子协议
|
|
46
|
+
serverInfo: { name: 'Supabase', version: '0.0.1' },
|
|
47
|
+
}),
|
|
48
|
+
id,
|
|
49
|
+
);
|
|
50
|
+
*/
|
|
51
|
+
let res = await mcpclient.call(`${mcpServerName}_initialize`);
|
|
52
|
+
logger.debug(" mcpclient result: " + JSON.stringify(res));
|
|
53
|
+
return send(ok(res), id);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// b) 列出工具
|
|
57
|
+
if (method === 'tools/list') {
|
|
58
|
+
let tools = await mcpclient.call(`${mcpServerName}_list`);
|
|
59
|
+
return send(ok(tools), id);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// c) 调用工具
|
|
63
|
+
if (method === 'tools/call') {
|
|
64
|
+
let result = await mcpclient.call(`${mcpServerName}_call`, params);
|
|
65
|
+
return send(ok(result ?? { content: [] }), id);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// d) 可选心跳
|
|
69
|
+
if (method === 'ping') return send(ok({}), id);
|
|
70
|
+
|
|
71
|
+
// e) 未实现的方法
|
|
72
|
+
return send(error(-32601, `Method not found: ${method}`), id);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
logger.error(` handleRequest ${mcpServerName} error ` + e?.message + " " + e + "\nStack trace: " + e.stack);
|
|
75
|
+
return send(error(-32603, 'Internal error ' + e?.message , { message: String(e?.message || e) }), id);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---- 4) 读 STDIN(每行一个 JSON-RPC 消息) ----
|
|
80
|
+
const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity });
|
|
81
|
+
|
|
82
|
+
rl.on('line', async (line) => {
|
|
83
|
+
if (!line.trim()) return;
|
|
84
|
+
let msg;
|
|
85
|
+
try {
|
|
86
|
+
msg = JSON.parse(line);
|
|
87
|
+
} catch {
|
|
88
|
+
// 非法 JSON:按 JSON-RPC 返回解析错误
|
|
89
|
+
return send(error(-32700, 'Parse error'), null);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 只处理 Request;Notification 没有 id
|
|
93
|
+
if (msg?.jsonrpc !== '2.0' || typeof msg?.method !== 'string') {
|
|
94
|
+
return send(error(-32600, 'Invalid Request'), msg?.id ?? null);
|
|
95
|
+
}
|
|
96
|
+
// 异步处理
|
|
97
|
+
handleRequest(msg);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// 可选:把错误打印到 stderr(不影响 JSON-RPC 数据流)
|
|
101
|
+
process.on('uncaughtException', (e) => console.error('uncaught:', e));
|
|
102
|
+
process.on('unhandledRejection', (e) => console.error('unhandled:', e));
|
|
103
|
+
|
|
104
|
+
//console.log(`mcpclient.call('${mcpServerName}_list')`);
|
|
105
|
+
//let tools = await mcpclient.call(`${mcpServerName}_list`);
|
|
106
|
+
//console.log(JSON.stringify(tools, null, 2));
|
|
107
|
+
async function main(){
|
|
108
|
+
logger.debug(`mcpclient.call('${mcpServerName}_initialize')`);
|
|
109
|
+
try {
|
|
110
|
+
let initialize = await mcpclient.call(`${mcpServerName}_initialize`);
|
|
111
|
+
logger.debug(JSON.stringify(initialize, null, 2));
|
|
112
|
+
} catch (error) {
|
|
113
|
+
logger.error("Error initializing MCP client:", error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 判断当前模块是否是主运行模块:
|
|
119
|
+
* ✅ node xxx.js 直接执行 → true
|
|
120
|
+
* 🚫 import 时 → false
|
|
121
|
+
* 🚫 子进程 (spawn/fork) 启动时 → false
|
|
122
|
+
*/
|
|
123
|
+
export function isMainModule() {
|
|
124
|
+
// 当前模块文件 URL
|
|
125
|
+
const currentFile = pathToFileURL(process.argv[1]).href;
|
|
126
|
+
|
|
127
|
+
// 是否为直接运行
|
|
128
|
+
const isDirectRun = import.meta.url === currentFile;
|
|
129
|
+
|
|
130
|
+
// 是否为子进程
|
|
131
|
+
const isChildProcess =
|
|
132
|
+
process.send !== undefined ||
|
|
133
|
+
process.env.__IS_SUBPROCESS__ === "1" ||
|
|
134
|
+
(process.ppid !== 1 && process.ppid !== process.pid);
|
|
135
|
+
logger.debug("isChildProcess:", isDirectRun && !isChildProcess);
|
|
136
|
+
return isDirectRun && !isChildProcess;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//if(isMainModule()){
|
|
140
|
+
// main();
|
|
141
|
+
//}
|
|
142
|
+
|
|
143
|
+
logger.debug("Environment Variables: " + JSON.stringify(process.env, null, 2));
|
|
144
|
+
logger.debug("Process Arguments: " + JSON.stringify(process.argv, null, 2));
|
package/codex/test.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Client, StreamableHTTPClientTransport } from './mcp-client.js';
|
|
2
|
+
import LogManager from '../logger-manager.js';
|
|
3
|
+
|
|
4
|
+
const logger = LogManager.getSystemLogger();
|
|
5
|
+
|
|
6
|
+
const issuer = 'https://radar.mcp.cloudflare.com';
|
|
7
|
+
const client = new Client({ name: 'radar-demo', version: '1.0.0' });
|
|
8
|
+
const transport = new StreamableHTTPClientTransport(`${issuer}/mcp`, {
|
|
9
|
+
|
|
10
|
+
oauth: {
|
|
11
|
+
issuer,
|
|
12
|
+
// 显式覆盖端点(根据你调试日志里看到的为准)
|
|
13
|
+
//authorizationUrl: `${issuer}/oauth/authorize`,
|
|
14
|
+
//tokenUrl: `${issuer}/oauth/token`,
|
|
15
|
+
//registrationUrl: `${issuer}/oauth/register`,
|
|
16
|
+
|
|
17
|
+
// 先尝试动态注册;如报“no registration_endpoint”,请改为手动 clientId
|
|
18
|
+
// clientId: '从门户或运维处获得',
|
|
19
|
+
scopes: ['openid','profile','email','offline_access'],
|
|
20
|
+
redirectUri: 'http://127.0.0.1:53175/callback',
|
|
21
|
+
tokenStorePath: '.mcp_oauth_tokens.json',
|
|
22
|
+
clientName: 'demo-radar-auto-reg',
|
|
23
|
+
debug: true
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
await client.connect(transport); // 需要登录时会自动拉起浏览器
|
|
30
|
+
logger.debug('Radar tools:', await client.listTools());
|
package/config.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
|
|
5
|
+
let defaultConfig = {
|
|
6
|
+
"kimi-k2":{
|
|
7
|
+
enable:false,
|
|
8
|
+
env:{
|
|
9
|
+
BASE_URL:"https://api.moonshot.cn/anthropic",
|
|
10
|
+
AUTH_TOKEN:"sk-{ 使用自己的 token }",
|
|
11
|
+
MODEL:"kimi-k2-0905-preview",
|
|
12
|
+
SMALL_FAST_MODEL:"kimi-k2-0905-preview"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"deepseek":{
|
|
16
|
+
enable:false,
|
|
17
|
+
env:{
|
|
18
|
+
BASE_URL:"https://api.deepseek.com/anthropic",
|
|
19
|
+
AUTH_TOKEN:"sk-{ 使用自己的 token }",
|
|
20
|
+
API_TIMEOUT_MS:"600000",
|
|
21
|
+
MODEL:"deepseek-chat",
|
|
22
|
+
SMALL_FAST_MODEL:"deepseek-chat",
|
|
23
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:"1"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
/**
|
|
27
|
+
* "anthropic/claude-sonnet-4",
|
|
28
|
+
"anthropic/claude-3.5-sonnet",
|
|
29
|
+
"anthropic/claude-3.7-sonnet:thinking"
|
|
30
|
+
*/
|
|
31
|
+
"openrouter":{
|
|
32
|
+
enable:false,
|
|
33
|
+
env:{
|
|
34
|
+
"BASE_URL": "http://127.0.0.1:3000",
|
|
35
|
+
"AUTH_TOKEN": "sk-or-v1-{ 使用自己的 token }",
|
|
36
|
+
"MODEL": "anthropic/claude-sonnet-4",
|
|
37
|
+
"SMALL_FAST_MODEL": "anthropic/claude-sonnet-4"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getConfigDir(){
|
|
43
|
+
let home = os.homedir();
|
|
44
|
+
return path.join(home, ".hawa-cli-analysis", "config.json");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* init config dir
|
|
49
|
+
*/
|
|
50
|
+
export function initConfig(){
|
|
51
|
+
//如果路径不存在,创建
|
|
52
|
+
let dir = getConfigDir();
|
|
53
|
+
if (!fs.existsSync(dir)){
|
|
54
|
+
//创建初始化文件
|
|
55
|
+
fs.mkdirSync(path.dirname(dir), { recursive: true });
|
|
56
|
+
fs.writeFileSync(dir, JSON.stringify(defaultConfig,null, 2));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function loadConfig(){
|
|
61
|
+
const data = fs.readFileSync(getConfigDir(), 'utf-8');
|
|
62
|
+
return JSON.parse(data);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
let defaultMCPConfig = {
|
|
68
|
+
"mcpServers": {
|
|
69
|
+
"supabase": {
|
|
70
|
+
"url": "https://mcp.supabase.com/mcp",
|
|
71
|
+
"bearer_token":"sbp_xxxxx",
|
|
72
|
+
"tools": {
|
|
73
|
+
"blacklist": ["tool_name_to_exclude"],
|
|
74
|
+
"descriptions": {
|
|
75
|
+
"tool_name": "Custom description for this tool"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
function getMCPConfigDir(){
|
|
85
|
+
let home = os.homedir();
|
|
86
|
+
return path.join(home, ".hawa-cli-analysis", "mcp.json");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* init config dir
|
|
91
|
+
*/
|
|
92
|
+
export function initMCPConfig(){
|
|
93
|
+
//如果路径不存在,创建
|
|
94
|
+
let dir = getMCPConfigDir();
|
|
95
|
+
if (!fs.existsSync(dir)){
|
|
96
|
+
//创建初始化文件
|
|
97
|
+
fs.mkdirSync(path.dirname(dir), { recursive: true });
|
|
98
|
+
fs.writeFileSync(dir, JSON.stringify(defaultMCPConfig,null, 2));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function loadMCPConfig(){
|
|
103
|
+
const data = fs.readFileSync(getMCPConfigDir(), 'utf-8');
|
|
104
|
+
return JSON.parse(data);
|
|
105
|
+
}
|
package/index.js
ADDED
|
File without changes
|