@migor/agentia 0.7.1 → 0.7.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/CHANGELOG.md +63 -1
- package/README.md +1 -1
- package/dist/AGENTS.md +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/integrations/anthropic.d.ts +5 -0
- package/dist/integrations/anthropic.js +29 -2
- package/dist/integrations/anthropic.js.map +1 -1
- package/dist/integrations/mcp-http.d.ts +43 -0
- package/dist/integrations/mcp-http.js +250 -0
- package/dist/integrations/mcp-http.js.map +1 -0
- package/dist/integrations/mcp-stdio.d.ts +46 -0
- package/dist/integrations/mcp-stdio.js +230 -0
- package/dist/integrations/mcp-stdio.js.map +1 -0
- package/dist/integrations/mcp.d.ts +37 -72
- package/dist/integrations/mcp.js +9 -468
- package/dist/integrations/mcp.js.map +1 -1
- package/dist/integrations/metrics-otlp.d.ts +42 -0
- package/dist/integrations/metrics-otlp.js +178 -0
- package/dist/integrations/metrics-otlp.js.map +1 -0
- package/dist/integrations/metrics-render.d.ts +2 -0
- package/dist/integrations/metrics-render.js +145 -0
- package/dist/integrations/metrics-render.js.map +1 -0
- package/dist/integrations/metrics-state.d.ts +191 -0
- package/dist/integrations/metrics-state.js +363 -0
- package/dist/integrations/metrics-state.js.map +1 -0
- package/dist/integrations/metrics.d.ts +3 -86
- package/dist/integrations/metrics.js +28 -648
- package/dist/integrations/metrics.js.map +1 -1
- package/dist/integrations/openai.js +4 -3
- package/dist/integrations/openai.js.map +1 -1
- package/dist/store/fsStore.d.ts +1 -1
- package/dist/store/fsStore.js +5 -2
- package/dist/store/fsStore.js.map +1 -1
- package/dist/transport/async.d.ts +13 -1
- package/dist/transport/async.js +109 -12
- package/dist/transport/async.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { DEFAULT_CLIENT_INFO, DEFAULT_PROTOCOL_VERSION, MCP_DEFAULT_TIMEOUT_MS, brief, isJsonRpcResponse, unwrap, withDeadline, } from './mcp.js';
|
|
3
|
+
/**
|
|
4
|
+
* MCP **stdio 连接器**(内置默认件之一):spawn 子进程、换行分隔 JSON-RPC。
|
|
5
|
+
* 桥与共享 helper 在 `mcp.ts`;本文件只含 stdio 传输。module 级 export,
|
|
6
|
+
* 公共面仍由 `mcp.ts` re-export(`src/index.ts` 不变)。
|
|
7
|
+
*/
|
|
8
|
+
/** `close()` 里 SIGTERM → SIGKILL 的宽限期(毫秒) */
|
|
9
|
+
export const MCP_CLOSE_GRACE_MS = 2_000;
|
|
10
|
+
/**
|
|
11
|
+
* **stdio 连接器**:spawn 一个 MCP server 子进程,走换行分隔的 JSON-RPC 2.0
|
|
12
|
+
* (`initialize` → `notifications/initialized` → `tools/list` / `tools/call`)。
|
|
13
|
+
*
|
|
14
|
+
* 进程与握手都是**惰性**的 —— 构造不产生副作用,第一次 `listTools` / `callTool` 才 spawn;
|
|
15
|
+
* 握手只做一次(并发调用共享同一次握手)。
|
|
16
|
+
*
|
|
17
|
+
* 三处非显然的坑,这里都兜住了(此前 `scripts/e2e-mcp.ts` 内联的那 94 行踩过):
|
|
18
|
+
* 1. spawn **失败**(命令不存在 → `ENOENT`)是异步的 `'error'` 事件,不是抛出 ——
|
|
19
|
+
* 没有监听器就是未捕获异常、直接把宿主进程带崩,所以必须接住并拒绝在途请求;
|
|
20
|
+
* 2. 分帧:一条报文可能跨多个 chunk,必须自己攒 buffer 按 `\n` 切;
|
|
21
|
+
* 3. **协议层 `isError: true` 必须转成抛错** —— 否则模型收到一条「成功」的结果,
|
|
22
|
+
* trace 也把它记成成功的调用(`McpClientLike` 的约定,见其注释)。
|
|
23
|
+
*/
|
|
24
|
+
export function createStdioMcpConnector(cmd, opts = {}) {
|
|
25
|
+
const bin = cmd[0];
|
|
26
|
+
if (typeof bin !== 'string' || bin === '') {
|
|
27
|
+
throw new Error('createStdioMcpConnector:cmd 不能为空(需要可执行文件 + 参数)');
|
|
28
|
+
}
|
|
29
|
+
const rest = cmd.slice(1);
|
|
30
|
+
const timeoutMs = opts.timeoutMs ?? MCP_DEFAULT_TIMEOUT_MS;
|
|
31
|
+
const clientInfo = opts.clientInfo ?? DEFAULT_CLIENT_INFO;
|
|
32
|
+
const protocolVersion = opts.protocolVersion ?? DEFAULT_PROTOCOL_VERSION;
|
|
33
|
+
let proc = null;
|
|
34
|
+
let fatal = null;
|
|
35
|
+
let closed = false;
|
|
36
|
+
let exited = false;
|
|
37
|
+
let buf = '';
|
|
38
|
+
let nextId = 1;
|
|
39
|
+
let ready = null;
|
|
40
|
+
let resolveExit = null;
|
|
41
|
+
const exitPromise = new Promise((r) => {
|
|
42
|
+
resolveExit = r;
|
|
43
|
+
});
|
|
44
|
+
const pending = new Map();
|
|
45
|
+
const markExited = () => {
|
|
46
|
+
exited = true;
|
|
47
|
+
resolveExit?.();
|
|
48
|
+
};
|
|
49
|
+
/** 进程已死 / 已 close 时一次性拒绝全部在途请求 —— 不留永久挂起的 promise */
|
|
50
|
+
const fail = (err) => {
|
|
51
|
+
fatal = err;
|
|
52
|
+
for (const p of pending.values())
|
|
53
|
+
p.reject(err);
|
|
54
|
+
pending.clear();
|
|
55
|
+
};
|
|
56
|
+
const guard = (p, label) => withDeadline(p, timeoutMs, label);
|
|
57
|
+
const ensureProc = () => {
|
|
58
|
+
if (closed)
|
|
59
|
+
throw new Error('MCP 连接器已 close —— 请重新创建一个');
|
|
60
|
+
if (fatal)
|
|
61
|
+
throw fatal;
|
|
62
|
+
if (proc)
|
|
63
|
+
return proc;
|
|
64
|
+
const spawnOpts = {
|
|
65
|
+
stdio: ['pipe', 'pipe', opts.stderr ?? 'inherit'],
|
|
66
|
+
// exactOptionalPropertyTypes:可选字段不能赋 undefined,只能条件展开
|
|
67
|
+
...(opts.env ? { env: { ...process.env, ...opts.env } } : {}),
|
|
68
|
+
...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),
|
|
69
|
+
};
|
|
70
|
+
const p = spawn(bin, rest, spawnOpts);
|
|
71
|
+
proc = p;
|
|
72
|
+
p.on('error', (err) => {
|
|
73
|
+
// 见函数头第 1 条:这个监听器不是可选的
|
|
74
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
75
|
+
markExited();
|
|
76
|
+
});
|
|
77
|
+
p.on('exit', (code, signal) => {
|
|
78
|
+
if (!closed) {
|
|
79
|
+
fail(new Error(`MCP server 进程已退出(code=${code ?? 'null'} signal=${signal ?? 'null'})`));
|
|
80
|
+
}
|
|
81
|
+
markExited();
|
|
82
|
+
});
|
|
83
|
+
// 'exit' 在 spawn 失败时可能不触发,'close' 一定会 —— close() 靠它才不会白等
|
|
84
|
+
p.on('close', markExited);
|
|
85
|
+
// 进程已死时写 stdin 会异步报 EPIPE;真实原因由上面的 'error' / 'exit' 给出
|
|
86
|
+
p.stdin?.on('error', () => {
|
|
87
|
+
/* 吞掉:EPIPE 是「进程没了」的次生现象,不是根因 */
|
|
88
|
+
});
|
|
89
|
+
p.stdout?.setEncoding('utf8');
|
|
90
|
+
p.stdout?.on('data', (chunk) => {
|
|
91
|
+
buf += chunk;
|
|
92
|
+
for (;;) {
|
|
93
|
+
const nl = buf.indexOf('\n');
|
|
94
|
+
if (nl < 0)
|
|
95
|
+
break;
|
|
96
|
+
const line = buf.slice(0, nl);
|
|
97
|
+
buf = buf.slice(nl + 1);
|
|
98
|
+
if (line.trim() === '')
|
|
99
|
+
continue;
|
|
100
|
+
let msg;
|
|
101
|
+
try {
|
|
102
|
+
msg = JSON.parse(line);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
continue; // MCP 规定 stdout 只走协议,但 server 混日志进来是常事
|
|
106
|
+
}
|
|
107
|
+
if (!isJsonRpcResponse(msg))
|
|
108
|
+
continue; // 通知(无 id)不配对
|
|
109
|
+
const waiter = pending.get(msg.id);
|
|
110
|
+
if (!waiter)
|
|
111
|
+
continue;
|
|
112
|
+
pending.delete(msg.id);
|
|
113
|
+
try {
|
|
114
|
+
waiter.resolve(unwrap(msg, 'response'));
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
waiter.reject(e instanceof Error ? e : new Error(String(e)));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
return p;
|
|
122
|
+
};
|
|
123
|
+
const write = (payload) => {
|
|
124
|
+
const p = ensureProc();
|
|
125
|
+
p.stdin?.write(`${JSON.stringify(payload)}\n`);
|
|
126
|
+
};
|
|
127
|
+
const send = (method, params, abandoned) => {
|
|
128
|
+
if (closed)
|
|
129
|
+
return Promise.reject(new Error('MCP 连接器已 close —— 请重新创建一个'));
|
|
130
|
+
if (fatal)
|
|
131
|
+
return Promise.reject(fatal);
|
|
132
|
+
const id = nextId++;
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
try {
|
|
135
|
+
ensureProc();
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
pending.set(id, { resolve, reject });
|
|
142
|
+
// 裁判放弃等待 ≠ 条目自动回收:超时的调用(引擎 toolTimeoutMs 或桥兜底)只是
|
|
143
|
+
// 不再 await,pending 里的 {resolve,reject} 会留到「server 终于回包 / 进程死 /
|
|
144
|
+
// close」—— 对「活着但不回包」的 server 就是无界泄漏。裁判表过态就删条目:
|
|
145
|
+
// 之后回包到了也没人等(:441 的 pending.get 落空即忽略,语义安全)。
|
|
146
|
+
if (abandoned !== undefined) {
|
|
147
|
+
const drop = () => {
|
|
148
|
+
pending.delete(id);
|
|
149
|
+
};
|
|
150
|
+
if (abandoned.aborted)
|
|
151
|
+
drop();
|
|
152
|
+
else
|
|
153
|
+
abandoned.addEventListener('abort', drop, { once: true });
|
|
154
|
+
}
|
|
155
|
+
write({ jsonrpc: '2.0', id, method, params });
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
/** 握手只做一次;失败是**粘性**的(连接器已不可用 → 重建,而不是半初始化态) */
|
|
159
|
+
const ensureReady = () => {
|
|
160
|
+
if (ready)
|
|
161
|
+
return ready;
|
|
162
|
+
ready = (async () => {
|
|
163
|
+
const init = await guard(send('initialize', { protocolVersion, capabilities: {}, clientInfo }), 'initialize');
|
|
164
|
+
// 协商结果以 server 回的那个为准(协议允许 server 降级或选自己的版本)
|
|
165
|
+
void init;
|
|
166
|
+
write({ jsonrpc: '2.0', method: 'notifications/initialized', params: {} });
|
|
167
|
+
})();
|
|
168
|
+
return ready;
|
|
169
|
+
};
|
|
170
|
+
const request = async (method, params, abandoned) => {
|
|
171
|
+
await ensureReady();
|
|
172
|
+
return send(method, params, abandoned);
|
|
173
|
+
};
|
|
174
|
+
const listTools = async () => {
|
|
175
|
+
const r = await guard(request('tools/list', {}), 'tools/list');
|
|
176
|
+
if (typeof r !== 'object' || r === null) {
|
|
177
|
+
throw new Error(`MCP tools/list 返回了非对象:${brief(r)}`);
|
|
178
|
+
}
|
|
179
|
+
const listed = r.tools;
|
|
180
|
+
if (listed === undefined)
|
|
181
|
+
return [];
|
|
182
|
+
if (!Array.isArray(listed)) {
|
|
183
|
+
// 响亮失败:把协议不符吞成空菜单,用户看到的是「一个工具都没有」而不是「server 坏了」
|
|
184
|
+
throw new Error(`MCP tools/list 的 tools 不是数组:${brief(listed)}`);
|
|
185
|
+
}
|
|
186
|
+
return listed;
|
|
187
|
+
};
|
|
188
|
+
const callTool = async (name, args, callOpts) => {
|
|
189
|
+
const r = await request('tools/call', { name, arguments: args }, callOpts?.abandoned);
|
|
190
|
+
// 承重:协议层 isError 只有连接器看得见(见函数头第 3 条)
|
|
191
|
+
if (typeof r === 'object' && r !== null && r.isError) {
|
|
192
|
+
throw new Error(`MCP 工具 ${name} 返回 isError: ${brief(r.content)}`);
|
|
193
|
+
}
|
|
194
|
+
return r;
|
|
195
|
+
};
|
|
196
|
+
return {
|
|
197
|
+
listTools,
|
|
198
|
+
callTool,
|
|
199
|
+
async close() {
|
|
200
|
+
if (closed)
|
|
201
|
+
return;
|
|
202
|
+
closed = true;
|
|
203
|
+
fail(new Error('MCP 连接器已 close —— 请重新创建一个'));
|
|
204
|
+
const p = proc;
|
|
205
|
+
proc = null;
|
|
206
|
+
if (!p)
|
|
207
|
+
return;
|
|
208
|
+
p.kill('SIGTERM');
|
|
209
|
+
if (exited)
|
|
210
|
+
return;
|
|
211
|
+
const killTimer = setTimeout(() => {
|
|
212
|
+
try {
|
|
213
|
+
p.kill('SIGKILL');
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
/* 已经死了 */
|
|
217
|
+
}
|
|
218
|
+
// ⚠️ 这里**不** resolve:SIGKILL 不可被捕获,'exit'/'close' 必达 —— 继续等真的退出。
|
|
219
|
+
// 到点即返回是 2026-09-18 之前的行为:调用方以为进程没了,实际还留着一个孤儿。
|
|
220
|
+
}, MCP_CLOSE_GRACE_MS);
|
|
221
|
+
try {
|
|
222
|
+
await exitPromise;
|
|
223
|
+
}
|
|
224
|
+
finally {
|
|
225
|
+
clearTimeout(killTimer);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=mcp-stdio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-stdio.js","sourceRoot":"","sources":["../../src/integrations/mcp-stdio.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,9 +6,11 @@ import type { AgentTool } from '../core/tool.js';
|
|
|
6
6
|
* `McpClientLike` 的客户端都能接(同 `RedisLike` 的 duck-typing)。本文件只依赖 core,
|
|
7
7
|
* 符合分层约定(`integrations` → `core`)。
|
|
8
8
|
*
|
|
9
|
-
* **连接器**(2026-09-18
|
|
9
|
+
* **连接器**(2026-09-18 起**内置**,见文末「连接器」一节与 spec §10 同日条):
|
|
10
10
|
* `createStdioMcpConnector` / `createStreamableHttpMcpConnector`。二者只用标准库
|
|
11
11
|
* (`node:child_process` + 全局 `fetch`),**不新增任何第三方依赖**。
|
|
12
|
+
* 2026-09-20 起两个连接器各居其文件(`mcp-stdio.ts` / `mcp-http.ts`,纯结构拆分、
|
|
13
|
+
* 零行为变化),本文件保留桥 + 共享 helper 并 re-export —— 公共面不变。
|
|
12
14
|
*
|
|
13
15
|
* 用法(接入点是 `AppOptions.tools` 这条**裸工具缝**):
|
|
14
16
|
* ```ts
|
|
@@ -114,80 +116,43 @@ export interface McpConnector extends McpClientLike {
|
|
|
114
116
|
*/
|
|
115
117
|
close(): Promise<void>;
|
|
116
118
|
}
|
|
117
|
-
/** `close()` 里 SIGTERM → SIGKILL 的宽限期(毫秒) */
|
|
118
|
-
export declare const MCP_CLOSE_GRACE_MS = 2000;
|
|
119
|
-
export interface StdioMcpConnectorOptions {
|
|
120
|
-
/** 追加 / 覆盖的环境变量(缺省继承 `process.env`) */
|
|
121
|
-
env?: Record<string, string>;
|
|
122
|
-
/** 子进程工作目录 */
|
|
123
|
-
cwd?: string;
|
|
124
|
-
/** 子进程 stderr 去向:`'inherit'`(缺省,server 日志直通终端)| `'ignore'` */
|
|
125
|
-
stderr?: 'inherit' | 'ignore';
|
|
126
|
-
/** 见 {@link DEFAULT_CLIENT_INFO} */
|
|
127
|
-
clientInfo?: {
|
|
128
|
-
name: string;
|
|
129
|
-
version: string;
|
|
130
|
-
};
|
|
131
|
-
/** 请求的协议版本,缺省 `'2024-11-05'` */
|
|
132
|
-
protocolVersion?: string;
|
|
133
|
-
/**
|
|
134
|
-
* 装配期超时(毫秒)—— 只作用于**握手 + `tools/list`**,缺省
|
|
135
|
-
* {@link MCP_DEFAULT_TIMEOUT_MS};非正数 = 不限。语义见 {@link Guard}。
|
|
136
|
-
*
|
|
137
|
-
* 超时同样只是一类账(`code='timeout'`):**不终止子进程**,与仓库其余「等待的终点」
|
|
138
|
-
* 一致(超时 = 放弃等待)。
|
|
139
|
-
*/
|
|
140
|
-
timeoutMs?: number;
|
|
141
|
-
}
|
|
142
119
|
/**
|
|
143
|
-
*
|
|
144
|
-
* (`initialize` → `notifications/initialized` → `tools/list` / `tools/call`)。
|
|
145
|
-
*
|
|
146
|
-
* 进程与握手都是**惰性**的 —— 构造不产生副作用,第一次 `listTools` / `callTool` 才 spawn;
|
|
147
|
-
* 握手只做一次(并发调用共享同一次握手)。
|
|
120
|
+
* 缺省 `clientInfo`(`initialize` 握手要发,协议要求该字段存在)。
|
|
148
121
|
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* 2. 分帧:一条报文可能跨多个 chunk,必须自己攒 buffer 按 `\n` 切;
|
|
153
|
-
* 3. **协议层 `isError: true` 必须转成抛错** —— 否则模型收到一条「成功」的结果,
|
|
154
|
-
* trace 也把它记成成功的调用(`McpClientLike` 的约定,见其注释)。
|
|
122
|
+
* **刻意不写框架自身版本**:`integrations` 层不 import 公共面(`AGENTIA_VERSION` 在
|
|
123
|
+
* `src/index.ts`),且 `scripts/release-surface.mjs` 的发版面按**精确版本串**计数 ——
|
|
124
|
+
* 这里多一处版本字面量会让它的 count 断言失配。需要真实版本请自行传 `clientInfo`。
|
|
155
125
|
*/
|
|
156
|
-
export declare
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
name: string;
|
|
163
|
-
version: string;
|
|
164
|
-
};
|
|
165
|
-
/** 请求的协议版本,缺省 `'2024-11-05'`;协商结果以 server 回的为准 */
|
|
166
|
-
protocolVersion?: string;
|
|
167
|
-
/** 装配期超时(毫秒)—— 只作用于**握手 + `tools/list`**,语义同 {@link StdioMcpConnectorOptions.timeoutMs} */
|
|
168
|
-
timeoutMs?: number;
|
|
169
|
-
/**
|
|
170
|
-
* 会话过期时被调一次(见 `rpc` 的 404 自愈)。**给可观测用**:默认自愈是静默的,
|
|
171
|
-
* 而「静默恢复」和「静默失效」在监控上看不出区别 —— 要计数 / 告警 / 打日志就挂这个钩子。
|
|
172
|
-
*/
|
|
173
|
-
onSessionExpired?: () => void;
|
|
174
|
-
/** 注入 `fetch`(测试用;缺省全局 `fetch`,与 `createOpenAIClient` 同款) */
|
|
175
|
-
fetchImpl?: typeof fetch;
|
|
176
|
-
}
|
|
126
|
+
export declare const DEFAULT_CLIENT_INFO: {
|
|
127
|
+
name: string;
|
|
128
|
+
version: string;
|
|
129
|
+
};
|
|
130
|
+
/** `initialize` 缺省请求的协议版本(e2e 已验证的版本;要对齐新版 server 请显式传) */
|
|
131
|
+
export declare const DEFAULT_PROTOCOL_VERSION = "2024-11-05";
|
|
177
132
|
/**
|
|
178
|
-
*
|
|
179
|
-
* Streamable HTTP 传输)。
|
|
133
|
+
* 装配期超时的落点:**握手 + `tools/list`**。
|
|
180
134
|
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
* (后续响应不带该头时**不覆盖**);`close()` 尽力 `DELETE` 终止会话;
|
|
185
|
-
* **会话过期自愈**:带会话 id 收到 `404` ⇒ 视为「会话已终止、该请求未被 server 执行」
|
|
186
|
-
* ⇒ 丢会话 → 重新握手 → 把这一次**重试一次**(只一次,不再循环)。自愈是静默的,
|
|
187
|
-
* 所以给了 `onSessionExpired` 钩子给你计数 / 告警 —— 否则它和「静默失效」没区别。
|
|
188
|
-
* - 协议版本:请求头 `MCP-Protocol-Version` 带**协商到的**版本(2025-06-18 起要求,
|
|
189
|
-
* 老 server 忽略未知头);
|
|
190
|
-
* - HTTP 层失败**挂数值 `status`** ⇒ `engine/errors.ts::classifyError` 自动分流
|
|
191
|
-
* (429 → `rate_limit` 可重试 / ≥500 → `server` 可重试 / 其余 4xx → `api` 不可重试)。
|
|
135
|
+
* 为什么不给 `callTool` 也套一个:一次调用只有一个裁判(见 `McpToolsOptions.timeoutMs`)。
|
|
136
|
+
* 走 `mcpTools` 时 `callTool` 由引擎 / 桥判定;而握手与 `tools/list` 发生在**装配期**,
|
|
137
|
+
* 没有任何别的裁判 —— server 卡住会让 `createApp` 永久挂起,必须在这里兜住。
|
|
192
138
|
*/
|
|
193
|
-
export
|
|
139
|
+
export type Guard = <T>(p: Promise<T>, label: string) => Promise<T>;
|
|
140
|
+
/** 把可能很大的值截成可读片段(错误消息要能进日志,不能是一兆的 JSON) */
|
|
141
|
+
export declare function brief(value: unknown, max?: number): string;
|
|
142
|
+
/** JSON-RPC 错误对象 → `Error`(`code` 进文案:`classifyError` 只认数值 `status`,这里不冒充它) */
|
|
143
|
+
export declare function jsonRpcError(err: unknown): Error;
|
|
144
|
+
/** server 混进 stdout / SSE 的非 JSON 行 */
|
|
145
|
+
export declare function isJsonRpcResponse(msg: unknown): msg is {
|
|
146
|
+
id: number;
|
|
147
|
+
result?: unknown;
|
|
148
|
+
error?: unknown;
|
|
149
|
+
};
|
|
150
|
+
/** 从一条已解析的报文里取结果;带 `error` 则抛 */
|
|
151
|
+
export declare function unwrap(msg: {
|
|
152
|
+
result?: unknown;
|
|
153
|
+
error?: unknown;
|
|
154
|
+
}, method: string): unknown;
|
|
155
|
+
export { createStdioMcpConnector, MCP_CLOSE_GRACE_MS } from './mcp-stdio.js';
|
|
156
|
+
export type { StdioMcpConnectorOptions } from './mcp-stdio.js';
|
|
157
|
+
export { createStreamableHttpMcpConnector } from './mcp-http.js';
|
|
158
|
+
export type { StreamableHttpMcpConnectorOptions } from './mcp-http.js';
|