@next-open-ai/openbot 0.2.8 → 0.3.2
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 +12 -1
- package/apps/desktop/renderer/dist/assets/index-DKtaRFW4.js +89 -0
- package/apps/desktop/renderer/dist/assets/index-QHuqXpWQ.css +10 -0
- package/apps/desktop/renderer/dist/index.html +2 -2
- package/dist/core/agent/agent-manager.d.ts +3 -0
- package/dist/core/agent/agent-manager.js +12 -6
- package/dist/core/config/desktop-config.d.ts +4 -0
- package/dist/core/config/desktop-config.js +5 -1
- package/dist/core/installer/index.d.ts +1 -1
- package/dist/core/installer/index.js +1 -1
- package/dist/core/installer/skill-installer.d.ts +9 -0
- package/dist/core/installer/skill-installer.js +94 -0
- package/dist/core/mcp/adapter.d.ts +17 -0
- package/dist/core/mcp/adapter.js +49 -0
- package/dist/core/mcp/client.d.ts +24 -0
- package/dist/core/mcp/client.js +70 -0
- package/dist/core/mcp/config.d.ts +22 -0
- package/dist/core/mcp/config.js +69 -0
- package/dist/core/mcp/index.d.ts +18 -0
- package/dist/core/mcp/index.js +20 -0
- package/dist/core/mcp/operator.d.ts +15 -0
- package/dist/core/mcp/operator.js +72 -0
- package/dist/core/mcp/transport/index.d.ts +11 -0
- package/dist/core/mcp/transport/index.js +16 -0
- package/dist/core/mcp/transport/sse.d.ts +20 -0
- package/dist/core/mcp/transport/sse.js +82 -0
- package/dist/core/mcp/transport/stdio.d.ts +32 -0
- package/dist/core/mcp/transport/stdio.js +132 -0
- package/dist/core/mcp/types.d.ts +72 -0
- package/dist/core/mcp/types.js +5 -0
- package/dist/core/tools/bookmark-tool.d.ts +9 -0
- package/dist/core/tools/bookmark-tool.js +118 -0
- package/dist/core/tools/index.d.ts +1 -0
- package/dist/core/tools/index.js +1 -0
- package/dist/gateway/methods/agent-chat.js +1 -0
- package/dist/gateway/methods/install-skill-from-upload.d.ts +14 -0
- package/dist/gateway/methods/install-skill-from-upload.js +13 -0
- package/dist/gateway/methods/run-scheduled-task.js +1 -0
- package/dist/gateway/server.js +24 -0
- package/dist/server/agent-config/agent-config.controller.d.ts +1 -1
- package/dist/server/agent-config/agent-config.service.d.ts +4 -1
- package/dist/server/agent-config/agent-config.service.js +2 -0
- package/dist/server/agents/agents.controller.js +3 -5
- package/dist/server/agents/agents.service.d.ts +1 -1
- package/dist/server/agents/agents.service.js +4 -2
- package/dist/server/app.module.js +2 -0
- package/dist/server/database/database.service.d.ts +7 -0
- package/dist/server/database/database.service.js +54 -5
- package/dist/server/saved-items/saved-items.controller.d.ts +26 -0
- package/dist/server/saved-items/saved-items.controller.js +78 -0
- package/dist/server/saved-items/saved-items.module.d.ts +2 -0
- package/dist/server/saved-items/saved-items.module.js +23 -0
- package/dist/server/saved-items/saved-items.service.d.ts +31 -0
- package/dist/server/saved-items/saved-items.service.js +105 -0
- package/dist/server/saved-items/tags.controller.d.ts +30 -0
- package/dist/server/saved-items/tags.controller.js +85 -0
- package/dist/server/saved-items/tags.service.d.ts +24 -0
- package/dist/server/saved-items/tags.service.js +84 -0
- package/dist/server/skills/skills.service.d.ts +2 -0
- package/dist/server/skills/skills.service.js +80 -16
- package/package.json +5 -1
- package/skills/url-bookmark/SKILL.md +36 -0
- package/apps/desktop/renderer/dist/assets/index-BOS-F8a4.js +0 -89
- package/apps/desktop/renderer/dist/assets/index-DxqxayUL.css +0 -10
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 客户端:连接、list_tools、call_tool、断开。
|
|
3
|
+
* 支持 stdio(本地进程)与 sse(远程 HTTP)两种传输。
|
|
4
|
+
*/
|
|
5
|
+
import type { McpTool, McpToolCallResult, IMcpTransport } from "./types.js";
|
|
6
|
+
import type { McpServerConfig } from "./types.js";
|
|
7
|
+
export interface McpClientOptions {
|
|
8
|
+
initTimeoutMs?: number;
|
|
9
|
+
requestTimeoutMs?: number;
|
|
10
|
+
}
|
|
11
|
+
export declare class McpClient {
|
|
12
|
+
private transport;
|
|
13
|
+
private _tools;
|
|
14
|
+
private _requestId;
|
|
15
|
+
constructor(configOrTransport: McpServerConfig | IMcpTransport, options?: McpClientOptions);
|
|
16
|
+
/** 建立连接并完成握手;成功后可使用 listTools / callTool */
|
|
17
|
+
connect(): Promise<void>;
|
|
18
|
+
/** 获取工具列表(会缓存;断开重连后需重新 list) */
|
|
19
|
+
listTools(): Promise<McpTool[]>;
|
|
20
|
+
/** 调用指定工具 */
|
|
21
|
+
callTool(name: string, args: Record<string, unknown>): Promise<McpToolCallResult>;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
get isConnected(): boolean;
|
|
24
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 客户端:连接、list_tools、call_tool、断开。
|
|
3
|
+
* 支持 stdio(本地进程)与 sse(远程 HTTP)两种传输。
|
|
4
|
+
*/
|
|
5
|
+
import { createTransport } from "./transport/index.js";
|
|
6
|
+
export class McpClient {
|
|
7
|
+
transport;
|
|
8
|
+
_tools = null;
|
|
9
|
+
_requestId = 0;
|
|
10
|
+
constructor(configOrTransport, options = {}) {
|
|
11
|
+
if (typeof configOrTransport.request === "function" &&
|
|
12
|
+
typeof configOrTransport.start === "function") {
|
|
13
|
+
this.transport = configOrTransport;
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
this.transport = createTransport(configOrTransport, {
|
|
17
|
+
initTimeoutMs: options.initTimeoutMs,
|
|
18
|
+
requestTimeoutMs: options.requestTimeoutMs,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** 建立连接并完成握手;成功后可使用 listTools / callTool */
|
|
23
|
+
async connect() {
|
|
24
|
+
await this.transport.start();
|
|
25
|
+
}
|
|
26
|
+
/** 获取工具列表(会缓存;断开重连后需重新 list) */
|
|
27
|
+
async listTools() {
|
|
28
|
+
if (this._tools !== null) {
|
|
29
|
+
return this._tools;
|
|
30
|
+
}
|
|
31
|
+
const res = await this.transport.request({
|
|
32
|
+
jsonrpc: "2.0",
|
|
33
|
+
id: ++this._requestId,
|
|
34
|
+
method: "tools/list",
|
|
35
|
+
});
|
|
36
|
+
if (res.error) {
|
|
37
|
+
throw new Error(`MCP tools/list failed: ${res.error.message}`);
|
|
38
|
+
}
|
|
39
|
+
const list = res.result?.tools;
|
|
40
|
+
this._tools = Array.isArray(list) ? list : [];
|
|
41
|
+
return this._tools;
|
|
42
|
+
}
|
|
43
|
+
/** 调用指定工具 */
|
|
44
|
+
async callTool(name, args) {
|
|
45
|
+
const res = await this.transport.request({
|
|
46
|
+
jsonrpc: "2.0",
|
|
47
|
+
id: ++this._requestId,
|
|
48
|
+
method: "tools/call",
|
|
49
|
+
params: { name, arguments: args ?? {} },
|
|
50
|
+
});
|
|
51
|
+
if (res.error) {
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: `MCP call_tool error: ${res.error.message}` }],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const result = res.result;
|
|
58
|
+
if (!result || !Array.isArray(result.content)) {
|
|
59
|
+
return { content: [{ type: "text", text: "Invalid MCP tools/call result" }], isError: true };
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
async close() {
|
|
64
|
+
this._tools = null;
|
|
65
|
+
await this.transport.close();
|
|
66
|
+
}
|
|
67
|
+
get isConnected() {
|
|
68
|
+
return this.transport.isConnected;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 配置解析与校验。
|
|
3
|
+
* 从 getOrCreateSession 的 options.mcpServers 中取出并规范化,支持 stdio 与 sse。
|
|
4
|
+
*/
|
|
5
|
+
import type { McpServerConfig, McpServerConfigStdio, McpServerConfigSse } from "./types.js";
|
|
6
|
+
/**
|
|
7
|
+
* 从会话选项里解析出本会话启用的 MCP 服务器配置列表。
|
|
8
|
+
* 支持 stdio(本地进程)与 sse(远程 HTTP)。
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveMcpServersForSession(mcpServers: McpServerConfig[] | undefined): McpServerConfig[];
|
|
11
|
+
/**
|
|
12
|
+
* 为 stdio 配置生成缓存键(用于 Operator 复用同一进程连接)
|
|
13
|
+
*/
|
|
14
|
+
export declare function stdioConfigKey(config: McpServerConfigStdio): string;
|
|
15
|
+
/**
|
|
16
|
+
* 为 sse 配置生成缓存键
|
|
17
|
+
*/
|
|
18
|
+
export declare function sseConfigKey(config: McpServerConfigSse): string;
|
|
19
|
+
/**
|
|
20
|
+
* 任意 MCP 配置的缓存键
|
|
21
|
+
*/
|
|
22
|
+
export declare function mcpConfigKey(config: McpServerConfig): string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP 配置解析与校验。
|
|
3
|
+
* 从 getOrCreateSession 的 options.mcpServers 中取出并规范化,支持 stdio 与 sse。
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* 从会话选项里解析出本会话启用的 MCP 服务器配置列表。
|
|
7
|
+
* 支持 stdio(本地进程)与 sse(远程 HTTP)。
|
|
8
|
+
*/
|
|
9
|
+
export function resolveMcpServersForSession(mcpServers) {
|
|
10
|
+
if (!Array.isArray(mcpServers) || mcpServers.length === 0) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
const result = [];
|
|
14
|
+
for (const s of mcpServers) {
|
|
15
|
+
if (!s || typeof s !== "object")
|
|
16
|
+
continue;
|
|
17
|
+
if (s.transport === "stdio") {
|
|
18
|
+
if (typeof s.command !== "string" || !s.command.trim()) {
|
|
19
|
+
console.warn("[mcp] 跳过无效 stdio 配置:缺少或无效 command");
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
result.push({
|
|
23
|
+
transport: "stdio",
|
|
24
|
+
command: s.command.trim(),
|
|
25
|
+
args: Array.isArray(s.args) ? s.args : undefined,
|
|
26
|
+
env: s.env && typeof s.env === "object" ? s.env : undefined,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
else if (s.transport === "sse") {
|
|
30
|
+
const url = s.url?.trim();
|
|
31
|
+
if (!url) {
|
|
32
|
+
console.warn("[mcp] 跳过无效 sse 配置:缺少或无效 url");
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
result.push({
|
|
36
|
+
transport: "sse",
|
|
37
|
+
url,
|
|
38
|
+
headers: s.headers && typeof s.headers === "object" ? s.headers : undefined,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.warn("[mcp] 未知 transport:", s.transport);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 为 stdio 配置生成缓存键(用于 Operator 复用同一进程连接)
|
|
49
|
+
*/
|
|
50
|
+
export function stdioConfigKey(config) {
|
|
51
|
+
const args = (config.args ?? []).join(" ");
|
|
52
|
+
const envStr = config.env ? JSON.stringify(config.env) : "";
|
|
53
|
+
return `stdio:${config.command}\0${args}\0${envStr}`;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 为 sse 配置生成缓存键
|
|
57
|
+
*/
|
|
58
|
+
export function sseConfigKey(config) {
|
|
59
|
+
const headersStr = config.headers ? JSON.stringify(config.headers) : "";
|
|
60
|
+
return `sse:${config.url}\0${headersStr}`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 任意 MCP 配置的缓存键
|
|
64
|
+
*/
|
|
65
|
+
export function mcpConfigKey(config) {
|
|
66
|
+
if (config.transport === "stdio")
|
|
67
|
+
return stdioConfigKey(config);
|
|
68
|
+
return sseConfigKey(config);
|
|
69
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core MCP 模块:为 SessionAgent 提供 MCP Tools 能力(Client 端)。
|
|
3
|
+
* 配置在创建 Session 时通过 options.mcpServers 传入,与 Skill 类似。
|
|
4
|
+
*/
|
|
5
|
+
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
6
|
+
import type { McpServerConfig } from "./types.js";
|
|
7
|
+
export type { McpServerConfig, McpServerConfigStdio, McpServerConfigSse, McpTool } from "./types.js";
|
|
8
|
+
export { resolveMcpServersForSession, stdioConfigKey, sseConfigKey, mcpConfigKey } from "./config.js";
|
|
9
|
+
export { McpClient } from "./client.js";
|
|
10
|
+
export { getMcpToolDefinitions, shutdownMcpClients } from "./operator.js";
|
|
11
|
+
export { mcpToolToToolDefinition, mcpToolsToToolDefinitions } from "./adapter.js";
|
|
12
|
+
/**
|
|
13
|
+
* 根据会话选项中的 mcpServers 配置,返回该会话可用的 MCP 工具(ToolDefinition 数组)。
|
|
14
|
+
* 在 AgentManager.getOrCreateSession 中调用,并入 customTools。
|
|
15
|
+
*/
|
|
16
|
+
export declare function createMcpToolsForSession(options: {
|
|
17
|
+
mcpServers?: McpServerConfig[];
|
|
18
|
+
}): Promise<ToolDefinition[]>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core MCP 模块:为 SessionAgent 提供 MCP Tools 能力(Client 端)。
|
|
3
|
+
* 配置在创建 Session 时通过 options.mcpServers 传入,与 Skill 类似。
|
|
4
|
+
*/
|
|
5
|
+
import { resolveMcpServersForSession } from "./config.js";
|
|
6
|
+
import { getMcpToolDefinitions } from "./operator.js";
|
|
7
|
+
export { resolveMcpServersForSession, stdioConfigKey, sseConfigKey, mcpConfigKey } from "./config.js";
|
|
8
|
+
export { McpClient } from "./client.js";
|
|
9
|
+
export { getMcpToolDefinitions, shutdownMcpClients } from "./operator.js";
|
|
10
|
+
export { mcpToolToToolDefinition, mcpToolsToToolDefinitions } from "./adapter.js";
|
|
11
|
+
/**
|
|
12
|
+
* 根据会话选项中的 mcpServers 配置,返回该会话可用的 MCP 工具(ToolDefinition 数组)。
|
|
13
|
+
* 在 AgentManager.getOrCreateSession 中调用,并入 customTools。
|
|
14
|
+
*/
|
|
15
|
+
export async function createMcpToolsForSession(options) {
|
|
16
|
+
const configs = resolveMcpServersForSession(options.mcpServers);
|
|
17
|
+
if (configs.length === 0)
|
|
18
|
+
return [];
|
|
19
|
+
return getMcpToolDefinitions(configs);
|
|
20
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Operator:管理 MCP 客户端生命周期,按配置为会话提供 ToolDefinition 列表。
|
|
3
|
+
* 支持 stdio(本地)与 sse(远程)两种传输。
|
|
4
|
+
*/
|
|
5
|
+
import type { McpServerConfig } from "./types.js";
|
|
6
|
+
import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
7
|
+
/**
|
|
8
|
+
* 为给定 MCP 服务器配置列表获取或创建客户端,并返回其工具对应的 ToolDefinition 数组。
|
|
9
|
+
* 连接失败或 list_tools 失败的 server 会被跳过并打日志,不阻塞整体。
|
|
10
|
+
*/
|
|
11
|
+
export declare function getMcpToolDefinitions(serverConfigs: McpServerConfig[]): Promise<ToolDefinition[]>;
|
|
12
|
+
/**
|
|
13
|
+
* 关闭并移除所有缓存的 MCP 客户端(进程退出或显式清理时调用)。
|
|
14
|
+
*/
|
|
15
|
+
export declare function shutdownMcpClients(): Promise<void>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Operator:管理 MCP 客户端生命周期,按配置为会话提供 ToolDefinition 列表。
|
|
3
|
+
* 支持 stdio(本地)与 sse(远程)两种传输。
|
|
4
|
+
*/
|
|
5
|
+
import { mcpConfigKey } from "./config.js";
|
|
6
|
+
import { McpClient } from "./client.js";
|
|
7
|
+
import { mcpToolsToToolDefinitions } from "./adapter.js";
|
|
8
|
+
/** 按配置键缓存的客户端 */
|
|
9
|
+
const clientCache = new Map();
|
|
10
|
+
function configLabel(config) {
|
|
11
|
+
if (config.transport === "stdio")
|
|
12
|
+
return config.command;
|
|
13
|
+
return config.url;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 为给定 MCP 服务器配置列表获取或创建客户端,并返回其工具对应的 ToolDefinition 数组。
|
|
17
|
+
* 连接失败或 list_tools 失败的 server 会被跳过并打日志,不阻塞整体。
|
|
18
|
+
*/
|
|
19
|
+
export async function getMcpToolDefinitions(serverConfigs) {
|
|
20
|
+
const allTools = [];
|
|
21
|
+
for (let i = 0; i < serverConfigs.length; i++) {
|
|
22
|
+
const config = serverConfigs[i];
|
|
23
|
+
const key = mcpConfigKey(config);
|
|
24
|
+
let client = clientCache.get(key);
|
|
25
|
+
if (!client) {
|
|
26
|
+
client = new McpClient(config);
|
|
27
|
+
try {
|
|
28
|
+
await client.connect();
|
|
29
|
+
clientCache.set(key, client);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
console.warn(`[mcp] 连接失败 (${configLabel(config)}):`, err instanceof Error ? err.message : err);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (!client.isConnected) {
|
|
37
|
+
clientCache.delete(key);
|
|
38
|
+
try {
|
|
39
|
+
await client.close();
|
|
40
|
+
}
|
|
41
|
+
catch { }
|
|
42
|
+
const newClient = new McpClient(config);
|
|
43
|
+
try {
|
|
44
|
+
await newClient.connect();
|
|
45
|
+
clientCache.set(key, newClient);
|
|
46
|
+
client = newClient;
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
console.warn(`[mcp] 重连失败 (${configLabel(config)}):`, err instanceof Error ? err.message : err);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const tools = await client.listTools();
|
|
55
|
+
const serverId = `mcp${i}`;
|
|
56
|
+
const definitions = mcpToolsToToolDefinitions(tools, client, serverId);
|
|
57
|
+
allTools.push(...definitions);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
console.warn(`[mcp] list_tools 失败 (${configLabel(config)}):`, err instanceof Error ? err.message : err);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return allTools;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 关闭并移除所有缓存的 MCP 客户端(进程退出或显式清理时调用)。
|
|
67
|
+
*/
|
|
68
|
+
export async function shutdownMcpClients() {
|
|
69
|
+
const closeAll = Array.from(clientCache.values()).map((c) => c.close().catch(() => { }));
|
|
70
|
+
clientCache.clear();
|
|
71
|
+
await Promise.all(closeAll);
|
|
72
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { McpServerConfig, IMcpTransport } from "../types.js";
|
|
2
|
+
export interface TransportOptions {
|
|
3
|
+
initTimeoutMs?: number;
|
|
4
|
+
requestTimeoutMs?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* 根据配置创建对应的传输层实例。
|
|
8
|
+
*/
|
|
9
|
+
export declare function createTransport(config: McpServerConfig, options?: TransportOptions): IMcpTransport;
|
|
10
|
+
export { StdioTransport } from "./stdio.js";
|
|
11
|
+
export { SseTransport } from "./sse.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { StdioTransport } from "./stdio.js";
|
|
2
|
+
import { SseTransport } from "./sse.js";
|
|
3
|
+
/**
|
|
4
|
+
* 根据配置创建对应的传输层实例。
|
|
5
|
+
*/
|
|
6
|
+
export function createTransport(config, options) {
|
|
7
|
+
if (config.transport === "stdio") {
|
|
8
|
+
return new StdioTransport(config, options);
|
|
9
|
+
}
|
|
10
|
+
if (config.transport === "sse") {
|
|
11
|
+
return new SseTransport(config, options);
|
|
12
|
+
}
|
|
13
|
+
throw new Error(`Unsupported MCP transport: ${config.transport}`);
|
|
14
|
+
}
|
|
15
|
+
export { StdioTransport } from "./stdio.js";
|
|
16
|
+
export { SseTransport } from "./sse.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP SSE/HTTP 传输:通过 HTTP POST 向远程 URL 发送 JSON-RPC 请求,响应在 response body。
|
|
3
|
+
*/
|
|
4
|
+
import type { McpServerConfigSse } from "../types.js";
|
|
5
|
+
import type { JsonRpcRequest, JsonRpcResponse } from "../types.js";
|
|
6
|
+
export interface SseTransportOptions {
|
|
7
|
+
initTimeoutMs?: number;
|
|
8
|
+
requestTimeoutMs?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare class SseTransport {
|
|
11
|
+
private config;
|
|
12
|
+
private initTimeoutMs;
|
|
13
|
+
private requestTimeoutMs;
|
|
14
|
+
private _connected;
|
|
15
|
+
constructor(config: McpServerConfigSse, options?: SseTransportOptions);
|
|
16
|
+
start(): Promise<void>;
|
|
17
|
+
request(req: JsonRpcRequest, timeoutMs?: number): Promise<JsonRpcResponse>;
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
get isConnected(): boolean;
|
|
20
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP SSE/HTTP 传输:通过 HTTP POST 向远程 URL 发送 JSON-RPC 请求,响应在 response body。
|
|
3
|
+
*/
|
|
4
|
+
const MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
5
|
+
export class SseTransport {
|
|
6
|
+
config;
|
|
7
|
+
initTimeoutMs;
|
|
8
|
+
requestTimeoutMs;
|
|
9
|
+
_connected = false;
|
|
10
|
+
constructor(config, options = {}) {
|
|
11
|
+
this.config = config;
|
|
12
|
+
this.initTimeoutMs = options.initTimeoutMs ?? 15_000;
|
|
13
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 30_000;
|
|
14
|
+
}
|
|
15
|
+
async start() {
|
|
16
|
+
if (this._connected)
|
|
17
|
+
return;
|
|
18
|
+
const url = (this.config.url ?? "").trim();
|
|
19
|
+
if (!url)
|
|
20
|
+
throw new Error("MCP SSE url is required");
|
|
21
|
+
const initResult = await this.request({
|
|
22
|
+
jsonrpc: "2.0",
|
|
23
|
+
id: 1,
|
|
24
|
+
method: "initialize",
|
|
25
|
+
params: {
|
|
26
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
27
|
+
capabilities: {},
|
|
28
|
+
clientInfo: { name: "openbot", version: "0.1.0" },
|
|
29
|
+
},
|
|
30
|
+
}, this.initTimeoutMs);
|
|
31
|
+
if (initResult.error) {
|
|
32
|
+
throw new Error(`MCP initialize failed: ${initResult.error.message}`);
|
|
33
|
+
}
|
|
34
|
+
await this.request({ jsonrpc: "2.0", id: 2, method: "notifications/initialized" }, this.requestTimeoutMs).catch(() => ({}));
|
|
35
|
+
this._connected = true;
|
|
36
|
+
}
|
|
37
|
+
request(req, timeoutMs) {
|
|
38
|
+
const url = (this.config.url ?? "").trim();
|
|
39
|
+
if (!url) {
|
|
40
|
+
return Promise.reject(new Error("MCP SSE url is required"));
|
|
41
|
+
}
|
|
42
|
+
const t = timeoutMs ?? this.requestTimeoutMs;
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
const timeoutId = setTimeout(() => controller.abort(), t);
|
|
45
|
+
const headers = {
|
|
46
|
+
"Content-Type": "application/json",
|
|
47
|
+
Accept: "application/json",
|
|
48
|
+
...this.config.headers,
|
|
49
|
+
};
|
|
50
|
+
return fetch(url, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers,
|
|
53
|
+
body: JSON.stringify(req),
|
|
54
|
+
signal: controller.signal,
|
|
55
|
+
})
|
|
56
|
+
.then(async (res) => {
|
|
57
|
+
clearTimeout(timeoutId);
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
const text = await res.text();
|
|
60
|
+
throw new Error(`MCP HTTP ${res.status}: ${text.slice(0, 200)}`);
|
|
61
|
+
}
|
|
62
|
+
const json = (await res.json());
|
|
63
|
+
if (!json || json.jsonrpc !== "2.0") {
|
|
64
|
+
throw new Error("Invalid MCP response");
|
|
65
|
+
}
|
|
66
|
+
return json;
|
|
67
|
+
})
|
|
68
|
+
.catch((err) => {
|
|
69
|
+
clearTimeout(timeoutId);
|
|
70
|
+
if (err.name === "AbortError") {
|
|
71
|
+
throw new Error(`MCP request timeout (${t}ms)`);
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async close() {
|
|
77
|
+
this._connected = false;
|
|
78
|
+
}
|
|
79
|
+
get isConnected() {
|
|
80
|
+
return this._connected;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP stdio 传输:子进程 + 标准输入/输出上的 Newline-delimited JSON-RPC。
|
|
3
|
+
*/
|
|
4
|
+
import type { McpServerConfigStdio } from "../types.js";
|
|
5
|
+
import type { JsonRpcRequest, JsonRpcResponse } from "../types.js";
|
|
6
|
+
export interface StdioTransportOptions {
|
|
7
|
+
/** 初始化超时(毫秒) */
|
|
8
|
+
initTimeoutMs?: number;
|
|
9
|
+
/** 单次请求超时(毫秒) */
|
|
10
|
+
requestTimeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class StdioTransport {
|
|
13
|
+
private process;
|
|
14
|
+
private config;
|
|
15
|
+
private initTimeoutMs;
|
|
16
|
+
private requestTimeoutMs;
|
|
17
|
+
private nextId;
|
|
18
|
+
private pending;
|
|
19
|
+
private buffer;
|
|
20
|
+
constructor(config: McpServerConfigStdio, options?: StdioTransportOptions);
|
|
21
|
+
/** 启动子进程并完成 MCP initialize 握手 */
|
|
22
|
+
start(): Promise<void>;
|
|
23
|
+
private flushLines;
|
|
24
|
+
private rejectAll;
|
|
25
|
+
private initialize;
|
|
26
|
+
private sendNotification;
|
|
27
|
+
/** 发送 JSON-RPC 请求并等待响应 */
|
|
28
|
+
request(req: JsonRpcRequest, timeoutMs?: number): Promise<JsonRpcResponse>;
|
|
29
|
+
/** 关闭子进程 */
|
|
30
|
+
close(): Promise<void>;
|
|
31
|
+
get isConnected(): boolean;
|
|
32
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP stdio 传输:子进程 + 标准输入/输出上的 Newline-delimited JSON-RPC。
|
|
3
|
+
*/
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
const MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
6
|
+
export class StdioTransport {
|
|
7
|
+
process = null;
|
|
8
|
+
config;
|
|
9
|
+
initTimeoutMs;
|
|
10
|
+
requestTimeoutMs;
|
|
11
|
+
nextId = 1;
|
|
12
|
+
pending = new Map();
|
|
13
|
+
buffer = "";
|
|
14
|
+
constructor(config, options = {}) {
|
|
15
|
+
this.config = config;
|
|
16
|
+
this.initTimeoutMs = options.initTimeoutMs ?? 10_000;
|
|
17
|
+
this.requestTimeoutMs = options.requestTimeoutMs ?? 30_000;
|
|
18
|
+
}
|
|
19
|
+
/** 启动子进程并完成 MCP initialize 握手 */
|
|
20
|
+
async start() {
|
|
21
|
+
if (this.process) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const env = { ...process.env, ...this.config.env };
|
|
25
|
+
this.process = spawn(this.config.command, this.config.args ?? [], {
|
|
26
|
+
env,
|
|
27
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
28
|
+
});
|
|
29
|
+
const child = this.process;
|
|
30
|
+
child.stdout?.on("data", (chunk) => {
|
|
31
|
+
this.buffer += chunk.toString("utf-8");
|
|
32
|
+
this.flushLines();
|
|
33
|
+
});
|
|
34
|
+
child.stderr?.on("data", (data) => {
|
|
35
|
+
const msg = data.toString("utf-8").trim();
|
|
36
|
+
if (msg)
|
|
37
|
+
console.warn("[mcp stdio stderr]", msg);
|
|
38
|
+
});
|
|
39
|
+
child.on("error", (err) => {
|
|
40
|
+
this.rejectAll(new Error(`MCP process error: ${err.message}`));
|
|
41
|
+
});
|
|
42
|
+
child.on("exit", (code, signal) => {
|
|
43
|
+
this.rejectAll(new Error(`MCP process exited: code=${code} signal=${signal}`));
|
|
44
|
+
this.process = null;
|
|
45
|
+
});
|
|
46
|
+
await this.initialize();
|
|
47
|
+
}
|
|
48
|
+
flushLines() {
|
|
49
|
+
const lines = this.buffer.split("\n");
|
|
50
|
+
this.buffer = lines.pop() ?? "";
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
const trimmed = line.trim();
|
|
53
|
+
if (!trimmed)
|
|
54
|
+
continue;
|
|
55
|
+
try {
|
|
56
|
+
const msg = JSON.parse(trimmed);
|
|
57
|
+
if ("id" in msg && msg.id !== undefined) {
|
|
58
|
+
const pending = this.pending.get(msg.id);
|
|
59
|
+
if (pending) {
|
|
60
|
+
clearTimeout(pending.timer);
|
|
61
|
+
this.pending.delete(msg.id);
|
|
62
|
+
if (msg.error) {
|
|
63
|
+
pending.reject(new Error(msg.error.message));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
pending.resolve(msg);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// 忽略非 JSON 行
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
rejectAll(err) {
|
|
77
|
+
for (const [, { reject, timer }] of this.pending) {
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
reject(err);
|
|
80
|
+
}
|
|
81
|
+
this.pending.clear();
|
|
82
|
+
}
|
|
83
|
+
async initialize() {
|
|
84
|
+
const initResult = await this.request({
|
|
85
|
+
jsonrpc: "2.0",
|
|
86
|
+
id: this.nextId++,
|
|
87
|
+
method: "initialize",
|
|
88
|
+
params: {
|
|
89
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
90
|
+
capabilities: {},
|
|
91
|
+
clientInfo: { name: "openbot", version: "0.1.0" },
|
|
92
|
+
},
|
|
93
|
+
}, this.initTimeoutMs);
|
|
94
|
+
if (initResult.error) {
|
|
95
|
+
throw new Error(`MCP initialize failed: ${initResult.error.message}`);
|
|
96
|
+
}
|
|
97
|
+
this.sendNotification({ jsonrpc: "2.0", method: "notifications/initialized" });
|
|
98
|
+
}
|
|
99
|
+
sendNotification(msg) {
|
|
100
|
+
if (!this.process?.stdin?.writable)
|
|
101
|
+
return;
|
|
102
|
+
this.process.stdin.write(JSON.stringify(msg) + "\n", "utf-8");
|
|
103
|
+
}
|
|
104
|
+
/** 发送 JSON-RPC 请求并等待响应 */
|
|
105
|
+
request(req, timeoutMs) {
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
if (!this.process?.stdin?.writable) {
|
|
108
|
+
reject(new Error("MCP transport not connected"));
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const t = timeoutMs ?? this.requestTimeoutMs;
|
|
112
|
+
const timer = setTimeout(() => {
|
|
113
|
+
if (this.pending.delete(req.id)) {
|
|
114
|
+
reject(new Error(`MCP request timeout (${t}ms)`));
|
|
115
|
+
}
|
|
116
|
+
}, t);
|
|
117
|
+
this.pending.set(req.id, { resolve, reject, timer });
|
|
118
|
+
this.process.stdin.write(JSON.stringify(req) + "\n", "utf-8");
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/** 关闭子进程 */
|
|
122
|
+
async close() {
|
|
123
|
+
if (this.process) {
|
|
124
|
+
this.rejectAll(new Error("MCP transport closed"));
|
|
125
|
+
this.process.kill("SIGTERM");
|
|
126
|
+
this.process = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
get isConnected() {
|
|
130
|
+
return this.process != null && this.process.stdin?.writable === true;
|
|
131
|
+
}
|
|
132
|
+
}
|