@x-otto/mcp 0.0.1-alpha.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 ADDED
@@ -0,0 +1,66 @@
1
+ # @x-otto/mcp
2
+
3
+ ## 模块定位
4
+
5
+ 实现 MCP(Model Context Protocol)客户端与服务端,支持工具/资源/提示的跨进程交互,提供 Stdio 和 SSE 双传输层。
6
+
7
+ ## 核心功能
8
+
9
+ | 模块 | 功能 |
10
+ | ---------------- | ------------------------------------------------- |
11
+ | McpClient | MCP 客户端(callTool / readResource / getPrompt) |
12
+ | McpManager | 多服务器生命周期管理 + 工具/资源聚合 |
13
+ | StdioTransport | 子进程 stdin/stdout 传输 |
14
+ | SseTransport | HTTP SSE 传输 + 断线重连 |
15
+ | Tool Adapter | MCP 工具 → AgentTool 适配(JSON Schema → Zod) |
16
+ | OttoMcpServer | 将本地 AgentTool 暴露为 MCP 服务端 |
17
+
18
+ ## 安装
19
+
20
+ ```bash
21
+ pnpm add @x-otto/mcp
22
+ ```
23
+
24
+ ## 快速开始
25
+
26
+ ```ts
27
+ import { McpManager, StdioTransport } from '@x-otto/mcp'
28
+
29
+ const manager = new McpManager()
30
+ manager.addServer({
31
+ name: 'my-server',
32
+ transport: new StdioTransport({ command: 'node', args: ['server.js'] }),
33
+ })
34
+
35
+ await manager.start()
36
+ const tools = manager.getTools() // MCP 工具 → AgentTool 适配
37
+ const result = await manager.callTool('my-server', 'tool-name', { key: 'value' })
38
+ const prompt = await manager.getPrompt('my-server', 'greeting', { name: 'World' })
39
+ ```
40
+
41
+ ## 目录概览
42
+
43
+ ```
44
+ src/
45
+ types.ts # 核心类型
46
+ mcp-client.ts # MCP 客户端
47
+ mcp-manager.ts # 多服务器管理
48
+ transports/
49
+ stdio-transport.ts # Stdio 传输
50
+ sse-transport.ts # SSE 传输
51
+ tool-adapter.ts # 工具适配
52
+ mcp-server.ts # MCP 服务端
53
+ index.ts
54
+ ```
55
+
56
+ ## 开发命令
57
+
58
+ ```bash
59
+ pnpm --filter @x-otto/mcp build
60
+ pnpm --filter @x-otto/mcp typecheck
61
+ pnpm --filter @x-otto/mcp clean
62
+ ```
63
+
64
+ ## 关联包
65
+
66
+ `@x-otto/agent`、`@x-otto/shared`、`@x-otto/env`
@@ -0,0 +1,799 @@
1
+ import { TypedEventEmitter } from "@x-otto/shared";
2
+ import { z } from "zod";
3
+ import { AgentTool, ToolResult } from "@x-otto/interchange";
4
+
5
+ //#region src/types.d.ts
6
+ /** JSON-RPC 2.0 协议版本字面量——单一真源,避免散落各处的裸字符串漂移。 */
7
+ declare const JSON_RPC_VERSION: "2.0";
8
+ interface JsonRpcRequest {
9
+ jsonrpc: typeof JSON_RPC_VERSION;
10
+ id: number | string;
11
+ method: string;
12
+ params?: Record<string, unknown>;
13
+ }
14
+ interface JsonRpcResponse {
15
+ jsonrpc: typeof JSON_RPC_VERSION;
16
+ id: number | string | null;
17
+ result?: unknown;
18
+ error?: JsonRpcError;
19
+ }
20
+ interface JsonRpcNotification {
21
+ jsonrpc: typeof JSON_RPC_VERSION;
22
+ method: string;
23
+ params?: Record<string, unknown>;
24
+ }
25
+ interface JsonRpcError {
26
+ code: number;
27
+ message: string;
28
+ data?: unknown;
29
+ }
30
+ interface McpServerCapabilities {
31
+ tools?: {
32
+ listChanged?: boolean;
33
+ };
34
+ resources?: {
35
+ subscribe?: boolean;
36
+ listChanged?: boolean;
37
+ };
38
+ prompts?: {
39
+ listChanged?: boolean;
40
+ };
41
+ }
42
+ interface McpClientCapabilities {
43
+ roots?: {
44
+ listChanged?: boolean;
45
+ };
46
+ sampling?: Record<string, unknown>;
47
+ }
48
+ interface McpInitializeParams {
49
+ protocolVersion: string;
50
+ capabilities: McpClientCapabilities;
51
+ clientInfo: {
52
+ name: string;
53
+ version: string;
54
+ };
55
+ }
56
+ interface McpInitializeResult {
57
+ protocolVersion: string;
58
+ capabilities: McpServerCapabilities;
59
+ serverInfo: {
60
+ name: string;
61
+ version?: string;
62
+ };
63
+ }
64
+ /**
65
+ * MCP 工具行为注解(MCP spec `Tool.annotations`)。仅作模型/调度提示,非安全保证。
66
+ * 服务端在 tools/list 携带;client 透传原始 JSON,故运行时本就存在,此处补类型以供消费。
67
+ */
68
+ interface McpToolAnnotations {
69
+ title?: string;
70
+ /** 只读工具:不改变环境,可与其他只读调用并发执行。 */
71
+ readOnlyHint?: boolean;
72
+ destructiveHint?: boolean;
73
+ idempotentHint?: boolean;
74
+ openWorldHint?: boolean;
75
+ }
76
+ interface McpToolDefinition {
77
+ name: string;
78
+ description?: string;
79
+ inputSchema: McpJsonSchema;
80
+ annotations?: McpToolAnnotations;
81
+ }
82
+ interface McpJsonSchema {
83
+ type: string;
84
+ properties?: Record<string, McpJsonSchemaProperty>;
85
+ required?: string[];
86
+ additionalProperties?: boolean;
87
+ [key: string]: unknown;
88
+ }
89
+ interface McpJsonSchemaProperty {
90
+ type: string;
91
+ description?: string;
92
+ enum?: unknown[];
93
+ default?: unknown;
94
+ items?: McpJsonSchemaProperty;
95
+ properties?: Record<string, McpJsonSchemaProperty>;
96
+ required?: string[];
97
+ [key: string]: unknown;
98
+ }
99
+ interface McpToolCallParams {
100
+ name: string;
101
+ arguments?: Record<string, unknown>;
102
+ }
103
+ interface McpToolCallResult {
104
+ content: McpContent[];
105
+ isError?: boolean;
106
+ }
107
+ type McpContent = McpTextContent | McpImageContent | McpResourceContent;
108
+ interface McpTextContent {
109
+ type: 'text';
110
+ text: string;
111
+ }
112
+ interface McpImageContent {
113
+ type: 'image';
114
+ data: string;
115
+ mimeType: string;
116
+ }
117
+ interface McpResourceContent {
118
+ type: 'resource';
119
+ resource: {
120
+ uri: string;
121
+ text?: string;
122
+ blob?: string;
123
+ mimeType?: string;
124
+ };
125
+ }
126
+ interface McpResource {
127
+ uri: string;
128
+ name: string;
129
+ description?: string;
130
+ mimeType?: string;
131
+ }
132
+ interface McpResourceTemplate {
133
+ uriTemplate: string;
134
+ name: string;
135
+ description?: string;
136
+ mimeType?: string;
137
+ }
138
+ interface McpResourceContents {
139
+ uri: string;
140
+ mimeType?: string;
141
+ text?: string;
142
+ blob?: string;
143
+ }
144
+ interface McpPrompt {
145
+ name: string;
146
+ description?: string;
147
+ arguments?: McpPromptArgument[];
148
+ }
149
+ interface McpPromptArgument {
150
+ name: string;
151
+ description?: string;
152
+ required?: boolean;
153
+ }
154
+ interface McpPromptMessage {
155
+ role: 'user' | 'assistant';
156
+ content: McpTextContent | McpImageContent | McpResourceContent;
157
+ }
158
+ interface McpServerConfig {
159
+ /**
160
+ * 传输类型(显式判别)。缺省按 url/command 推断(向后兼容):有 url→http,有 command→stdio。
161
+ * 'http'='sse'(同一 streamable HTTP transport,'http' 为首选名)。
162
+ */
163
+ type?: McpTransportType;
164
+ /** stdio 模式:命令 */
165
+ command?: string;
166
+ /** stdio 模式:命令参数 */
167
+ args?: string[];
168
+ /** stdio 模式:环境变量 */
169
+ env?: Record<string, string>;
170
+ /** streamable HTTP 模式:URL */
171
+ url?: string;
172
+ /**
173
+ * HTTP 模式静态请求头,随每次 GET(流)/POST(消息)发出。
174
+ * 用于鉴权(`Authorization: Bearer …` / `X-API-Key: …`)及自定义头。来自 `.otto/config.json`
175
+ * 的 `mcp_servers`(JSON 安全)。stdio 模式忽略。
176
+ */
177
+ headers?: Record<string, string>;
178
+ /** 请求超时(毫秒),覆盖 manager 级默认值 */
179
+ requestTimeoutMs?: number;
180
+ /** 自动重连(默认 true) */
181
+ autoReconnect?: boolean;
182
+ /** 最大重连次数(默认 3) */
183
+ maxReconnectAttempts?: number;
184
+ /**
185
+ * 是否由所在层管理(RFC-081)。
186
+ * 仅 plugin 层有效。标记后用户不可在 /extension 面板中单独 disable/delete,
187
+ * 只能通过卸载插件来移除该 MCP server。
188
+ */
189
+ managed?: boolean;
190
+ }
191
+ type McpTransportType = 'stdio' | 'sse' | 'http';
192
+ type McpClientStatus = 'disconnected' | 'connecting' | 'ready' | 'error' | 'reconnecting';
193
+ interface McpServerInfo {
194
+ name: string;
195
+ /** 禁用 server(无 client 实例)无从取真配置 → optional;活 client 经 getConfig() 填真值。 */
196
+ config?: McpServerConfig;
197
+ status: McpClientStatus;
198
+ tools: McpToolDefinition[];
199
+ resources?: McpResource[];
200
+ prompts?: McpPrompt[];
201
+ capabilities?: McpServerCapabilities;
202
+ error?: string;
203
+ }
204
+ interface McpEvents {
205
+ 'server.connected': (info: {
206
+ name: string;
207
+ tools: number;
208
+ }) => void;
209
+ /**
210
+ * RFC-165 D8/H2:`kind` 区分预期内的显式断开(`'manual'`,如用户经 /extension 禁用、
211
+ * McpController.stop() 主动断开)与传输层意外关闭(`'crash'`,子进程异常退出/重连耗尽)。
212
+ * 消费方(如 `usePanelBackend()` 的 `status` 派生逻辑)据此区分 `disabled` 语境与
213
+ * `crashed` 语境——`'manual'` 断开若同时命中"用户刚调用了 setServerEnabled(false)"这个
214
+ * 应用层上下文,上层(`McpController`)可进一步归类为 `disabled`;本事件本身只提供
215
+ * "是否预期内"这一层区分,不深入解析应用层意图。
216
+ */
217
+ 'server.disconnected': (info: {
218
+ name: string;
219
+ reason?: string;
220
+ kind: 'manual' | 'crash';
221
+ }) => void;
222
+ 'server.error': (info: {
223
+ name: string;
224
+ error: string;
225
+ code?: string;
226
+ }) => void;
227
+ 'server.reconnecting': (info: {
228
+ name: string;
229
+ attempt: number;
230
+ }) => void;
231
+ 'tools.changed': (info: {
232
+ serverName: string;
233
+ tools: McpToolDefinition[];
234
+ }) => void;
235
+ 'resources.changed': (info: {
236
+ serverName: string;
237
+ }) => void;
238
+ /**
239
+ * RFC-165 D9/H1:通用 notification 转发——未被 `tools.changed`/`resources.changed`
240
+ * 语义化事件覆盖的 server→client notification(如插件 backend facet 的 `backend/state`)
241
+ * 经此事件透传给上层消费方(`PanelBackendController` 等)。也会收到已被语义化事件覆盖的
242
+ * notification(如 `notifications/tools/list_changed`),消费方按 `method` 自行过滤。
243
+ */
244
+ 'server.notification': (info: {
245
+ name: string;
246
+ method: string;
247
+ params?: Record<string, unknown>;
248
+ }) => void;
249
+ }
250
+ //#endregion
251
+ //#region src/transport.d.ts
252
+ /** 轻量进程句柄(避免 MCP 包直接依赖 @x-otto/runtime) */
253
+ interface LightManagedProcess {
254
+ readonly pid: number | undefined;
255
+ readonly stdin: NodeJS.WritableStream | null;
256
+ readonly stdout: NodeJS.ReadableStream | null;
257
+ readonly stderr: NodeJS.ReadableStream | null;
258
+ kill(): Promise<void>;
259
+ onError(callback: (err: Error) => void): void;
260
+ onExit(callback: () => void): void;
261
+ }
262
+ /**
263
+ * 进程 spawn 工厂:由宿主(coding/McpController)注入,内部 wrap @x-otto/runtime 的 ProcessRuntime,
264
+ * 使 MCP 子进程纳入统一运行时(Layer 3 exit reaper 覆盖 + 可观测)。缺省时 StdioTransport 走
265
+ * 向后兼容的私有 spawn(仍 buildAllowedEnv + detached)。
266
+ */
267
+ type ProcessSpawnFactory = (command: string, args: string[], env: Record<string, string>) => LightManagedProcess;
268
+ type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
269
+ interface McpTransport {
270
+ send(message: JsonRpcRequest | JsonRpcNotification): void | Promise<void>;
271
+ onMessage(handler: (message: JsonRpcResponse | JsonRpcNotification) => void): void;
272
+ /** 传输非预期断开(进程退出 / SSE 流结束或出错)时回调;client 据此触发自动重连。 */
273
+ onClose(handler: (reason?: string) => void): void;
274
+ start(): Promise<void>;
275
+ close(): Promise<void>;
276
+ }
277
+ /**
278
+ * 校验 MCP server URL:协议白名单 http/https,拒绝 file:/ftp:/data:/javascript: 等。
279
+ * **不**拦本机/私网——本地 MCP server(localhost/127.0.0.1)是常态;DNS-rebinding 对长驻配置端点
280
+ * 风险低,且 MCP 端点由运维配置(非 LLM 输入)。仅挡协议层误用与畸形 URL。
281
+ */
282
+ declare function validateMcpUrl(url: string): URL;
283
+ declare class StdioTransport implements McpTransport {
284
+ private managedProcess;
285
+ private messageHandler;
286
+ private closeHandler;
287
+ private closing;
288
+ private buffer;
289
+ private readonly command;
290
+ private readonly args;
291
+ private readonly env;
292
+ private readonly processSpawnFactory;
293
+ constructor(command: string, args?: string[], env?: Record<string, string>, processSpawnFactory?: ProcessSpawnFactory);
294
+ start(): Promise<void>;
295
+ send(message: JsonRpcRequest | JsonRpcNotification): void;
296
+ onMessage(handler: (message: JsonRpcResponse | JsonRpcNotification) => void): void;
297
+ onClose(handler: (reason?: string) => void): void;
298
+ close(): Promise<void>;
299
+ private processBuffer;
300
+ }
301
+ interface SseTransportOptions {
302
+ /** 单次请求超时(ms),与传输级 abort 组合。缺省 30s。 */
303
+ requestTimeoutMs?: number;
304
+ /** 注入 fetch(测试用)。缺省全局 fetch。 */
305
+ fetchImpl?: FetchLike;
306
+ /**
307
+ * 静态请求头,随每次 GET(流)/POST(消息)发出。鉴权(`Authorization: Bearer …` /
308
+ * `X-API-Key`)及自定义头。与内置头(Accept/Content-Type/Mcp-Session-Id)合并——**内置头优先**,
309
+ * 防止误覆盖协议必需头。
310
+ */
311
+ headers?: Record<string, string>;
312
+ /**
313
+ * 动态 Bearer token provider(OAuth 2.1)。每次请求前调用,返回的 access token
314
+ * 注入 `Authorization: Bearer <token>` 头。**优先级最高**——覆盖静态 headers 中的 Authorization。
315
+ * 返回 undefined 时跳过(用静态 headers 中的 Authorization)。
316
+ */
317
+ getAuthToken?: () => Promise<string | undefined>;
318
+ }
319
+ declare class SseTransport implements McpTransport {
320
+ private messageHandler;
321
+ private closeHandler;
322
+ private abortController;
323
+ private sessionId;
324
+ private closing;
325
+ private readonly baseUrl;
326
+ private readonly fetchImpl;
327
+ private readonly requestTimeoutMs;
328
+ private readonly headers;
329
+ private readonly getAuthToken?;
330
+ constructor(url: string, options?: SseTransportOptions);
331
+ /** 解析本次请求的 headers:静态 headers + 动态 Bearer(最高优先)+ 内置协议头覆盖。 */
332
+ private resolveHeaders;
333
+ /**
334
+ * 认证中间层——401 时自动刷新 token 并重试一次(handle_401 → refresh → retry)。
335
+ */
336
+ private fetchWithAuthRetry;
337
+ start(): Promise<void>;
338
+ send(message: JsonRpcRequest | JsonRpcNotification): Promise<void>;
339
+ private failRequest;
340
+ onMessage(handler: (message: JsonRpcResponse | JsonRpcNotification) => void): void;
341
+ onClose(handler: (reason?: string) => void): void;
342
+ close(): Promise<void>;
343
+ private consumeStream;
344
+ }
345
+ //#endregion
346
+ //#region src/sse.d.ts
347
+ /**
348
+ * sse.ts — W3C/MCP 规范的 Server-Sent Events 数据解析(M6/RFC-016 一致性硬化).
349
+ *
350
+ * 旧 consumeStream 内联解析有三处偏离规范:
351
+ * 1. 仅按 `\n` 切,未剥 CRLF 的尾随 `\r`;
352
+ * 2. 硬要求 `data: `(带空格),漏掉 `data:{...}`(冒号后空格可选);
353
+ * 3. 每条 `data:` 行立即派发,未按规范累积多行 data 直到空行(事件边界)再 join。
354
+ *
355
+ * 本解析器对齐规范 + 官方 SDK(eventsource-parser 语义):累积 `data` 字段值,
356
+ * 遇空行派发一个事件(多行 data 以 `\n` 连接);忽略注释行(`:` 开头)与其它字段
357
+ * (event/id/retry);流结束时 flush 末尾未以空行收尾的事件。
358
+ */
359
+ interface SseDataParser {
360
+ /** 喂入一段原始文本块(可跨事件/跨行边界)。 */
361
+ feed(chunk: string): void;
362
+ /** 流结束时调用:派发任何未以空行收尾的累积事件。 */
363
+ flush(): void;
364
+ }
365
+ /** 创建一个 SSE data 解析器;每个完整事件的 data(多行已 join)经 onData 回调。 */
366
+ declare function createSseDataParser(onData: (data: string) => void): SseDataParser;
367
+ //#endregion
368
+ //#region src/mcp-client.d.ts
369
+ /**
370
+ * RFC-165 D8/H2:断连分类——区分"预期内的显式断开"(`kind: 'manual'`,如
371
+ * `McpClient.disconnect()` 被调用)与"传输层意外关闭"(`kind: 'crash'`,如子进程异常退出、
372
+ * SSE 流出错、重连耗尽)。这是最小可行区分(不深入解析子进程 exit code),供上层
373
+ * (`McpManager`/`McpController`)据此派生更细的语义(如"用户经 /extension 禁用" vs
374
+ * "server 真的崩了"——那一层区分需要 disable 调用方上下文,留给 `McpManager`/
375
+ * `McpController` 在 `kind: 'manual'` 基础上进一步标注,见 D8 备注)。
376
+ */
377
+ interface DisconnectInfo {
378
+ reason?: string;
379
+ kind: 'manual' | 'crash';
380
+ }
381
+ interface McpClientOptions {
382
+ /** 请求超时(毫秒),默认 30000 */
383
+ requestTimeoutMs?: number;
384
+ /** 自动重连(默认 true) */
385
+ autoReconnect?: boolean;
386
+ /** 最大重连次数(默认 3) */
387
+ maxReconnectAttempts?: number;
388
+ /** 重连退避基数(毫秒,默认 500;第 n 次=base·2^(n-1))。测试可设小值。 */
389
+ reconnectDelayMs?: number;
390
+ /** 注入 transport 工厂(测试用)。缺省按 config 构造 stdio/sse。 */
391
+ transportFactory?: () => McpTransport;
392
+ /**
393
+ * 注入进程 spawn 工厂(RFC-085 M2):stdio transport 用它把 MCP 子进程纳入 ProcessRuntime。
394
+ * 缺省时 StdioTransport 走向后兼容私有 spawn(仍 buildAllowedEnv + detached)。
395
+ */
396
+ processSpawnFactory?: ProcessSpawnFactory;
397
+ /**
398
+ * 动态 Bearer token provider(OAuth 2.1)。
399
+ * 每次 HTTP 请求前调用,返回的 access token 注入 Authorization 头。
400
+ * 由调用方(McpManager 或外部)注入 TokenProvider.getAccessToken。
401
+ */
402
+ getAuthToken?: () => Promise<string | undefined>;
403
+ }
404
+ declare class McpClient {
405
+ readonly serverName: string;
406
+ private transport;
407
+ private config;
408
+ private options;
409
+ private status;
410
+ private serverInfo;
411
+ private capabilities;
412
+ private tools;
413
+ private resources;
414
+ private prompts;
415
+ private nextRequestId;
416
+ private pending;
417
+ private closing;
418
+ private reconnectAttempts;
419
+ private reconnectTimer;
420
+ private toolsChangedCallback;
421
+ private resourcesChangedCallback;
422
+ private disconnectedCallback;
423
+ /** RFC-165 D9:通用 notification 订阅——server 发出的、未被上面专用处理的 method
424
+ * (如 `backend/state`)经此回调透传给上层,不再被 `logger.debug` 静默丢弃。
425
+ * 对全部 notification(含已被专用 switch-case 处理的)都会调用,让消费方可按需扩展。 */
426
+ private notificationCallback;
427
+ constructor(serverName: string, config: McpServerConfig, options?: McpClientOptions);
428
+ getStatus(): McpClientStatus;
429
+ getConfig(): McpServerConfig;
430
+ getTools(): McpToolDefinition[];
431
+ getResources(): McpResource[];
432
+ getPrompts(): McpPrompt[];
433
+ getCapabilities(): McpServerCapabilities | null;
434
+ getServerInfo(): McpInitializeResult | null;
435
+ onToolsChanged(callback: () => void): void;
436
+ onResourcesChanged(callback: () => void): void;
437
+ onDisconnected(callback: (info: DisconnectInfo) => void): void;
438
+ /**
439
+ * RFC-165 D9/H1:通用 notification 订阅——面向未被 `handleNotification` 专用 switch-case
440
+ * 处理的 method(如插件 backend facet 推送的 `backend/state`)。已处理的 method
441
+ * (`tools/list_changed`/`resources/list_changed`)也会触发本回调,消费方按 method 自行过滤。
442
+ */
443
+ onNotification(callback: (method: string, params?: Record<string, unknown>) => void): void;
444
+ connect(): Promise<void>;
445
+ private establish;
446
+ private handleTransportClose;
447
+ private scheduleReconnect;
448
+ private attemptReconnect;
449
+ private failPending;
450
+ refreshTools(): Promise<McpToolDefinition[]>;
451
+ refreshResources(): Promise<McpResource[]>;
452
+ readResource(uri: string): Promise<McpResourceContents[]>;
453
+ refreshPrompts(): Promise<McpPrompt[]>;
454
+ getPrompt(name: string, args?: Record<string, string>): Promise<{
455
+ description?: string;
456
+ messages: McpPromptMessage[];
457
+ }>;
458
+ callTool(params: McpToolCallParams, signal?: AbortSignal): Promise<McpToolCallResult>;
459
+ /**
460
+ * RFC-165 T6:通用请求出口——镜像底层私有 `request`,但方法名不含 "tool" 语义
461
+ * (避免与模型工具调用面混淆,对齐 D3 概念分离:即使是客户端内部 API 命名也不应让
462
+ * `backend/*` 调用看起来像"调用了一个 tool")。供 `PanelBackendController` 发送
463
+ * `backend/call`/`backend/cancel`/`backend/getState` 等面板专用请求。
464
+ *
465
+ * 刻意不要求 `status === 'ready'`(区别于 `callTool`)——D7 降级启动场景下 backend facet
466
+ * 恒可用,但 `mcp` facet 的业务工具可能未注册,`ready` 判断不应耦合 `mcp` facet 健康度;
467
+ * 断连场景由调用方(`PanelBackendController`)经 `McpClient.getStatus()` 感知并拒绝新请求。
468
+ */
469
+ callRaw(method: string, params?: Record<string, unknown>, signal?: AbortSignal): Promise<unknown>;
470
+ disconnect(): Promise<void>;
471
+ private createTransport;
472
+ private request;
473
+ private notify;
474
+ private handleMessage;
475
+ private handleNotification;
476
+ }
477
+ declare function createMcpClient(serverName: string, config: McpServerConfig, options?: McpClientOptions): McpClient;
478
+ //#endregion
479
+ //#region src/mcp-manager.d.ts
480
+ interface McpManagerOptions {
481
+ /** 每个 MCP 请求超时 */
482
+ requestTimeoutMs?: number;
483
+ /** 连接失败时是否静默跳过(默认 true) */
484
+ skipOnError?: boolean;
485
+ /** 自动重连(默认 true) */
486
+ autoReconnect?: boolean;
487
+ /** 最大重连次数(默认 3) */
488
+ maxReconnectAttempts?: number;
489
+ /**
490
+ * RFC-230:重连退避基准延迟(ms,指数退避,默认 500)。**只做全局配置**,不支持
491
+ * per-server 覆盖——`McpServerConfig` 没有对应字段(与 `requestTimeoutMs`/
492
+ * `maxReconnectAttempts` 不同,那两者有 per-server 覆盖能力)。若未来有真实需求,
493
+ * 可另立 TODO 补 `McpServerConfig.reconnectDelayMs` + settings schema 字段。
494
+ */
495
+ reconnectDelayMs?: number;
496
+ /** 进程 spawn 工厂(RFC-085 M2):注入后 stdio MCP 子进程纳入 ProcessRuntime 统一管理。 */
497
+ processSpawnFactory?: ProcessSpawnFactory;
498
+ }
499
+ declare class McpManager extends TypedEventEmitter<McpEvents> {
500
+ private clients;
501
+ private options;
502
+ private disabledServers;
503
+ constructor(options?: McpManagerOptions, disabledServers?: string[]);
504
+ /**
505
+ * 返回值(RFC-150 D1 追加,向后兼容——原调用点若不消费返回值零影响):本轮 connectAll 的
506
+ * 汇总统计,供宿主层(McpController.start())非阻塞路径完成后驱动 toast/日志通知,
507
+ * 不需要重新遍历/监听事件才能知道"这轮到底连上几个"。
508
+ */
509
+ connectAll(servers: Record<string, McpServerConfig>): Promise<{
510
+ connected: number;
511
+ failed: number;
512
+ }>;
513
+ connect(name: string, config: McpServerConfig): Promise<void>;
514
+ disconnect(name: string): Promise<void>;
515
+ disconnectAll(): Promise<void>;
516
+ getAllTools(): AgentTool[];
517
+ getAllResources(): Array<McpResource & {
518
+ serverName: string;
519
+ }>;
520
+ getAllPrompts(): Array<McpPrompt & {
521
+ serverName: string;
522
+ }>;
523
+ getClient(name: string): McpClient | undefined;
524
+ getServerInfos(): McpServerInfo[];
525
+ get connectedCount(): number;
526
+ get toolCount(): number;
527
+ }
528
+ //#endregion
529
+ //#region src/mcp-tool-adapter.d.ts
530
+ /**
531
+ * 将 MCP JSON Schema property → Zod Schema
532
+ */
533
+ declare function jsonSchemaPropertyToZod(prop: McpJsonSchemaProperty): z.ZodType;
534
+ /**
535
+ * 将 MCP tool inputSchema → Zod schema
536
+ */
537
+ declare function mcpSchemaToZod(tool: McpToolDefinition): z.ZodType;
538
+ /**
539
+ * 将 MCP content → ToolResult content
540
+ */
541
+ declare function mcpContentToToolContent(content: McpContent[]): ToolResult['content'];
542
+ /**
543
+ * 为单个 MCP tool 创建 AgentTool 适配器
544
+ * 工具名格式: mcp__{serverName}__{toolName}
545
+ */
546
+ declare function createMcpToolAdapter(client: McpClient, toolDef: McpToolDefinition, serverName: string): AgentTool;
547
+ /**
548
+ * 为一个 MCP Client 的所有工具创建适配器
549
+ */
550
+ declare function createMcpToolAdapters(client: McpClient, serverName: string): AgentTool[];
551
+ //#endregion
552
+ //#region src/oauth/discovery.d.ts
553
+ declare function parseWwwAuthenticate(header: string): string | undefined;
554
+ interface ProtectedResourceMetadata {
555
+ /** 资源标识符(即 MCP server 的 canonical URL)。 */
556
+ resource: string;
557
+ /** 授权服务器 issuer URL 列表。 */
558
+ authorization_servers?: string[];
559
+ /** 支持的 Bearer 方法。 */
560
+ bearer_methods_supported?: string[];
561
+ /** 可读的资源文档 URL。 */
562
+ resource_documentation?: string;
563
+ }
564
+ declare function fetchProtectedResourceMetadata(metadataUrl: string, fetchImpl?: typeof globalThis.fetch): Promise<ProtectedResourceMetadata>;
565
+ interface AuthorizationServerMetadata {
566
+ issuer: string;
567
+ authorization_endpoint: string;
568
+ token_endpoint: string;
569
+ /** DCR 注册端点(RFC 7591);若无则不支持动态注册。 */
570
+ registration_endpoint?: string;
571
+ /** 注册端点认证方式;'none'=无需 client_secret,PKCE only。 */
572
+ registration_endpoint_auth_methods_supported?: string[];
573
+ /** 支持的 PKCE challenge 方法(期望 ['S256'])。 */
574
+ code_challenge_methods_supported?: string[];
575
+ /** 授权端点支持的 response_type。 */
576
+ response_types_supported?: string[];
577
+ /** token 端点支持的 grant_type。 */
578
+ grant_types_supported?: string[];
579
+ /** 授权端点支持的 scope。 */
580
+ scopes_supported?: string[];
581
+ /** 令牌端点认证方式。 */
582
+ token_endpoint_auth_methods_supported?: string[];
583
+ /** RFC 8414 未定义的其他字段(passthrough)。 */
584
+ [key: string]: unknown;
585
+ }
586
+ /**
587
+ * 获取 authorization server metadata。
588
+ * 先试 RFC 8414(GET issuer/.well-known/oauth-authorization-server),
589
+ * 失败回退 OIDC discovery(GET issuer/.well-known/openid-configuration),
590
+ * 只取与 OAuth 授权码流相关的字段。
591
+ */
592
+ declare function fetchAuthorizationServerMetadata(issuer: string, fetchImpl?: typeof globalThis.fetch): Promise<AuthorizationServerMetadata>;
593
+ interface OAuthDiscoveryResult {
594
+ protectedResource: ProtectedResourceMetadata;
595
+ authorizationServer: AuthorizationServerMetadata;
596
+ /** 选定的授权服务器 issuer。 */
597
+ issuer: string;
598
+ }
599
+ /**
600
+ * 完整 OAuth 发现:
601
+ * 1. 构造 metadata URL(优先 401 WWW-Authenticate,否则 assume <server-url>/.well-known/oauth-protected-resource)
602
+ * 2. 获取 protected resource metadata
603
+ * 3. 选择 authorization server(取 metadata 列表第一个,或 issuer)
604
+ * 4. 获取 authorization server metadata
605
+ */
606
+ declare function discoverOAuth(serverUrl: string, options?: {
607
+ /** 401 响应的 WWW-Authenticate 头(可选,有一等公民优先)。 */wwwAuthenticate?: string; /** 可注入 fetch(测试)。 */
608
+ fetch?: typeof globalThis.fetch;
609
+ }): Promise<OAuthDiscoveryResult>;
610
+ //#endregion
611
+ //#region src/oauth/registration.d.ts
612
+ interface DCRRequest {
613
+ /** 可读的客户端名称。 */
614
+ client_name?: string;
615
+ /** 授权码回调 redirect URI 列表。 */
616
+ redirect_uris: string[];
617
+ /** 授权端点请求的 grant type。 */
618
+ grant_types?: string[];
619
+ /** token 端点认证方式;PKCE 用 'none'。 */
620
+ token_endpoint_auth_method?: string;
621
+ }
622
+ interface DCRResponse {
623
+ client_id: string;
624
+ client_secret?: string;
625
+ /** 注册完成后的 access token(供后续管理此客户端)。 */
626
+ registration_access_token?: string;
627
+ /** 管理此注册的 endpoint URL。 */
628
+ registration_client_uri?: string;
629
+ /** RFC 7591 未定义字段(passthrough)。 */
630
+ [key: string]: unknown;
631
+ }
632
+ interface RegisterClientOptions {
633
+ /** 可注入 fetch(测试)。 */
634
+ fetch?: typeof globalThis.fetch;
635
+ /** 注册端点的初始 access token(少数 AS 要求 bearer 认证)。 */
636
+ initialAccessToken?: string;
637
+ }
638
+ /**
639
+ * 向 authorization server 注册 OAuth 客户端(DCR)。
640
+ * POST <registration_endpoint>,请求体 JSON,响应 JSON。
641
+ */
642
+ declare function registerClient(registrationEndpoint: string, params: DCRRequest, options?: RegisterClientOptions): Promise<DCRResponse>;
643
+ //#endregion
644
+ //#region src/oauth/token.d.ts
645
+ interface AuthorizationCodeExchangeParams {
646
+ /** 授权码(来自回调)。 */
647
+ code: string;
648
+ /** 回调 redirect_uri(必须与 authorize 请求一致)。 */
649
+ redirectUri: string;
650
+ /** PKCE code_verifier。 */
651
+ codeVerifier: string;
652
+ /** RFC 8707 resource indicator(指向 MCP server)。 */
653
+ resource?: string;
654
+ /** DCR 获得的 client_id。 */
655
+ clientId: string;
656
+ }
657
+ interface RefreshTokenParams {
658
+ refreshToken: string;
659
+ /** RFC 8707 resource indicator。 */
660
+ resource?: string;
661
+ clientId?: string;
662
+ }
663
+ interface TokenResponse {
664
+ access_token: string;
665
+ token_type: string;
666
+ expires_in?: number;
667
+ refresh_token?: string;
668
+ /** token 签发时的时间戳(ms),用于计算过期。 */
669
+ issued_at?: number;
670
+ /** RFC 8707 审计。 */
671
+ resource?: string;
672
+ }
673
+ interface OAuthCredentials {
674
+ accessToken: string;
675
+ refreshToken?: string;
676
+ expiresAt?: number;
677
+ resource?: string;
678
+ /** DCR client_id(持久化供后续 refresh)。 */
679
+ clientId?: string;
680
+ }
681
+ interface TokenExchangeOptions {
682
+ /** 可注入 fetch(测试)。 */
683
+ fetch?: typeof globalThis.fetch;
684
+ }
685
+ /**
686
+ * 用授权码交换 token(PKCE)。
687
+ * POST <token_endpoint>(application/x-www-form-urlencoded)。
688
+ */
689
+ declare function exchangeCode(tokenEndpoint: string, params: AuthorizationCodeExchangeParams, options?: TokenExchangeOptions): Promise<OAuthCredentials>;
690
+ /**
691
+ * 用 refresh_token 刷新 access token。
692
+ * POST <token_endpoint>(application/x-www-form-urlencoded)。
693
+ */
694
+ declare function refreshAccessToken(tokenEndpoint: string, params: RefreshTokenParams, options?: TokenExchangeOptions): Promise<OAuthCredentials>;
695
+ /** 检查 access token 是否已过期(提前 60 秒视为过期,避免边界竞态)。 */
696
+ declare function isTokenExpired(credentials: OAuthCredentials, skewSeconds?: number): boolean;
697
+ //#endregion
698
+ //#region src/oauth/token-store.d.ts
699
+ interface TokenStore {
700
+ /** 加载某 server 的凭据;不存在返回 undefined。 */
701
+ load(serverName: string): Promise<OAuthCredentials | undefined>;
702
+ /** 保存某 server 的凭据。 */
703
+ save(serverName: string, credentials: OAuthCredentials): Promise<void>;
704
+ /** 删除某 server 的凭据(logout)。 */
705
+ delete(serverName: string): Promise<void>;
706
+ /** 列出所有有凭据的 server name。 */
707
+ list(): Promise<string[]>;
708
+ }
709
+ interface FileTokenStoreOptions {
710
+ /** 存储目录,默认 `~/.otto/mcp/credentials`。 */
711
+ baseDir?: string;
712
+ }
713
+ declare function createFileTokenStore(options?: FileTokenStoreOptions): TokenStore;
714
+ declare function createMemoryTokenStore(): TokenStore;
715
+ /**
716
+ * RFC-345 影子模式 overlay:读穿透到 base(继承磁盘既有凭据),写/删只进内存 overlay
717
+ * (对齐 Chromium OverlayUserPrefStore:读父、写内存)。进程退出后磁盘凭据逐字节不变。
718
+ *
719
+ * 语义细节:
720
+ * - load:overlay 命中优先(含"本会话内 delete 过"的墓碑,返回 undefined);否则回退 base;
721
+ * - save:只写 overlay,绝不落盘;
722
+ * - delete:只在 overlay 记墓碑,不删磁盘文件;
723
+ * - list:base 列表并入 overlay 新增、扣除 overlay 墓碑。
724
+ */
725
+ declare function createOverlayTokenStore(base: TokenStore): TokenStore;
726
+ /**
727
+ * MCP OAuth 凭据 store 的 §D1 白名单构造入口:影子态返回 overlay(读磁盘、写内存),
728
+ * 否则返回纯文件 store。业务层(mcp-manager / login)统一走此函数,不自行判断影子态。
729
+ */
730
+ declare function createDefaultTokenStore(options?: FileTokenStoreOptions): TokenStore;
731
+ //#endregion
732
+ //#region src/oauth/login.d.ts
733
+ interface McpOAuthLoginOptions {
734
+ /** MCP server URL。 */
735
+ serverUrl: string;
736
+ /** MCP server 名称(用于 token store 键控)。 */
737
+ serverName: string;
738
+ /** 401 响应的 WWW-Authenticate 头(可选,优先用于发现)。 */
739
+ wwwAuthenticate?: string;
740
+ /** 手动指定 client_id(跳过 DCR)。 */
741
+ clientId?: string;
742
+ /** 授权服务器元数据(跳过发现,适用于已知 AS)。 */
743
+ authorizationServer?: AuthorizationServerMetadata;
744
+ /** token store(默认文件存储 ~/.otto/mcp/credentials/)。 */
745
+ tokenStore?: TokenStore;
746
+ /** 打开浏览器的函数(默认 `open`命令)。注入用于测试。 */
747
+ openBrowser?: (url: string) => Promise<void>;
748
+ /** 可注入 fetch(测试)。 */
749
+ fetch?: typeof globalThis.fetch;
750
+ /** 取消信号。 */
751
+ signal?: AbortSignal;
752
+ }
753
+ interface McpOAuthLoginResult {
754
+ credentials: OAuthCredentials;
755
+ /** 是否经 DCR 注册了新的 client_id。 */
756
+ registered: boolean;
757
+ /** DCR 获得的 client_id(持久化供后续刷新)。 */
758
+ clientId: string;
759
+ }
760
+ /**
761
+ * 执行完整 OAuth 2.1 交互式登录(PKCE + DCR)。
762
+ *
763
+ * 流程(修正终局 review):
764
+ * 1. 发现 OAuth 端点(或使用传入的已知 metadata)
765
+ * 2. 生成 PKCE pair + state,**先**启动 loopback server(获取真实 redirect_uri)
766
+ * 3. DCR 注册客户端(使用真实 redirect_uri;或使用传入的 client_id)
767
+ * 4. 构造 authorize URL,打开浏览器
768
+ * 5. 等待浏览器回调送达授权码
769
+ * 6. 用 PKCE code_verifier 交换 token
770
+ * 7. 保存凭据到 token store(含 client_id)
771
+ */
772
+ declare function login(options: McpOAuthLoginOptions): Promise<McpOAuthLoginResult>;
773
+ interface TokenProvider {
774
+ /** 获取 access token;过期自动刷新。不可恢复的失败抛 McpUnauthorized 错误。 */
775
+ getAccessToken(): Promise<string | undefined>;
776
+ /** 强制重新登录(login → 保存 → 返回 token)。 */
777
+ login(options: McpOAuthLoginOptions): Promise<string>;
778
+ /** 登出(删除存储的凭据)。 */
779
+ logout(): Promise<void>;
780
+ }
781
+ interface TokenProviderOptions {
782
+ serverName: string;
783
+ serverUrl: string;
784
+ tokenStore?: TokenStore;
785
+ /** client_id(跳过 DCR)。 */
786
+ clientId?: string;
787
+ /** 已知的 AS metadata(跳过发现,也用于跳过刷新时的 rediscovery)。 */
788
+ authorizationServer?: AuthorizationServerMetadata;
789
+ fetch?: typeof globalThis.fetch;
790
+ }
791
+ /**
792
+ * 创建 MCP token provider:加载 → 检查刷新 → 提供 access token。
793
+ * token 过期时自动 refresh;refresh 失败 → getAccessToken 返回 undefined
794
+ * (提示用户运行 `otto mcp login`)。
795
+ */
796
+ declare function createTokenProvider(options: TokenProviderOptions): TokenProvider;
797
+ //#endregion
798
+ export { type AuthorizationCodeExchangeParams, type AuthorizationServerMetadata, type DCRRequest, type DCRResponse, type FileTokenStoreOptions, type JsonRpcError, type JsonRpcNotification, type JsonRpcRequest, type JsonRpcResponse, type LightManagedProcess, McpClient, type McpClientCapabilities, type McpClientOptions, type McpClientStatus, type McpContent, type McpEvents, type McpImageContent, type McpInitializeParams, type McpInitializeResult, type McpJsonSchema, type McpJsonSchemaProperty, McpManager, type McpManagerOptions, type McpOAuthLoginOptions, type McpOAuthLoginResult, type McpPrompt, type McpPromptArgument, type McpPromptMessage, type McpResource, type McpResourceContent, type McpResourceContents, type McpResourceTemplate, type McpServerCapabilities, type McpServerConfig, type McpServerInfo, type McpTextContent, type McpToolCallParams, type McpToolCallResult, type McpToolDefinition, type McpTransport, type McpTransportType, type OAuthCredentials, type OAuthDiscoveryResult, type ProcessSpawnFactory, type ProtectedResourceMetadata, type RefreshTokenParams, type RegisterClientOptions, type SseDataParser, SseTransport, type SseTransportOptions, StdioTransport, type TokenExchangeOptions, type TokenProvider, type TokenProviderOptions, type TokenResponse, type TokenStore, createDefaultTokenStore, createFileTokenStore, createMcpClient, createMcpToolAdapter, createMcpToolAdapters, createMemoryTokenStore, createOverlayTokenStore, createSseDataParser, createTokenProvider, discoverOAuth, exchangeCode, fetchAuthorizationServerMetadata, fetchProtectedResourceMetadata, isTokenExpired, jsonSchemaPropertyToZod, login, mcpContentToToolContent, mcpSchemaToZod, parseWwwAuthenticate, refreshAccessToken, registerClient, validateMcpUrl };
799
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/transport.ts","../src/sse.ts","../src/mcp-client.ts","../src/mcp-manager.ts","../src/mcp-tool-adapter.ts","../src/oauth/discovery.ts","../src/oauth/registration.ts","../src/oauth/token.ts","../src/oauth/token-store.ts","../src/oauth/login.ts"],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import{spawn as e}from"node:child_process";import{API_IMAGE_DIMENSION_LIMIT_PX as t,McpError as n,OAuthError as r,PROVIDER_IMAGE_INLINE_MAX_BYTES as i,TypedEventEmitter as a,buildAllowedEnv as o,createLogger as s,createPkcePair as c,generateState as l,probeImageDimensions as u,startLoopbackServer as d,traceStartupPhase as f}from"@x-otto/shared";import{z as p}from"zod";import{chmod as m,mkdir as h,readFile as g,readdir as _,rename as ee,unlink as v,writeFile as te}from"node:fs/promises";import{createHash as ne}from"node:crypto";import{homedir as re}from"node:os";import{join as y}from"node:path";import{isShadowModeEnabled as b}from"@x-otto/env";function x(e){let t=``,n=[],r=()=>{if(n.length===0)return;let t=n.join(`
2
+ `);n=[],e(t)},i=e=>{let t=e.endsWith(`\r`)?e.slice(0,-1):e;if(t===``){r();return}if(!t.startsWith(`:`)&&t.startsWith(`data:`)){let e=t.slice(5);e.startsWith(` `)&&(e=e.slice(1)),n.push(e)}};return{feed(e){t+=e;let n=t.split(`
3
+ `);t=n.pop()??``;for(let e of n)i(e)},flush(){t.length>0&&(i(t),t=``),r()}}}const S=s(`@x-otto/mcp:transport`);function C(e){let t=[],n=e;for(;;){let e=n.indexOf(10);if(e===-1){n.length>33554432&&(S.warn(`MCP newline frame exceeds limit without terminator; resetting buffer`),n=Buffer.alloc(0));break}let r=n.subarray(0,e);r.length>0&&r[r.length-1]===13&&(r=r.subarray(0,r.length-1)),n=n.subarray(e+1);let i=r.toString(`utf-8`).trim();i&&t.push(i)}return{bodies:t,rest:n}}function w(e){let t;try{t=new URL(e)}catch{throw Error(`Invalid MCP server URL: ${e}`)}if(t.protocol!==`http:`&&t.protocol!==`https:`)throw Error(`Unsupported MCP server URL protocol: ${t.protocol} (use http/https)`);return t}var T=class{managedProcess=null;messageHandler=null;closeHandler=null;closing=!1;buffer=Buffer.alloc(0);command;args;env;processSpawnFactory;constructor(e,t=[],n={},r){this.command=e,this.args=t,this.env=n,this.processSpawnFactory=r}async start(){if(this.processSpawnFactory)this.managedProcess=this.processSpawnFactory(this.command,this.args,this.env);else{let t=e(this.command,this.args,{stdio:[`pipe`,`pipe`,`pipe`],env:o(this.env),detached:!0});this.managedProcess={pid:t.pid,stdin:t.stdin,stdout:t.stdout,stderr:t.stderr,kill:async()=>(t.stdin?.end(),new Promise(e=>{let n=setTimeout(()=>{t.kill(`SIGKILL`),e()},2e3);t.on(`exit`,()=>{clearTimeout(n),e()}),t.kill(`SIGTERM`)})),onError:e=>{t.on(`error`,e)},onExit:e=>{t.on(`exit`,()=>e())}}}let t=this.managedProcess,n=t.stdout;n&&n.on(`data`,e=>{this.buffer=Buffer.concat([this.buffer,Buffer.isBuffer(e)?e:Buffer.from(e)]),this.processBuffer()});let r=t.stderr;r&&r.on(`data`,e=>{let t=Buffer.isBuffer(e)?e.toString(`utf-8`).trimEnd():String(e).trimEnd();S.debug(`MCP stderr: %s`,t)}),t.onError(e=>{S.error(`MCP process error: %s`,e.message)}),t.onExit(()=>{S.debug(`MCP process exited`),this.closing||this.closeHandler?.(`process exited`),this.managedProcess=null})}send(e){let t=this.managedProcess?.stdin;if(!t?.writable)throw Error(`MCP stdio transport is not connected`);let n=JSON.stringify(e);t.write(n+`
4
+ `)}onMessage(e){this.messageHandler=e}onClose(e){this.closeHandler=e}async close(){this.closing=!0,this.managedProcess&&=(await this.managedProcess.kill(),null)}processBuffer(){let{bodies:e,rest:t}=C(this.buffer);this.buffer=t;for(let t of e)try{let e=JSON.parse(t);this.messageHandler?.(e)}catch(e){S.warn(`Failed to parse MCP message: %s`,e.message)}}},E=class{messageHandler=null;closeHandler=null;abortController=null;sessionId=null;closing=!1;baseUrl;fetchImpl;requestTimeoutMs;headers;getAuthToken;constructor(e,t={}){this.baseUrl=w(e).href,this.fetchImpl=t.fetchImpl??globalThis.fetch,this.requestTimeoutMs=t.requestTimeoutMs??3e4,this.headers=t.headers??{},this.getAuthToken=t.getAuthToken}async resolveHeaders(...e){let t={...this.headers};if(this.getAuthToken){let e=await this.getAuthToken();e&&(t={...t,Authorization:`Bearer ${e}`})}for(let n of e)t={...t,...n};return t}async fetchWithAuthRetry(e,t,n){let r=e===`GET`?{Accept:`text/event-stream`}:{"Content-Type":`application/json`,...this.sessionId?{"Mcp-Session-Id":this.sessionId}:{}},i=await this.resolveHeaders(r),a=await this.fetchImpl(this.baseUrl,{method:e,headers:i,body:t,signal:n});if(a.status===401&&this.getAuthToken){if(S.debug(`MCP 401 on %s %s — attempting token refresh and retry`,e,this.baseUrl),await this.getAuthToken()){let i=await this.resolveHeaders(r);return this.fetchImpl(this.baseUrl,{method:e,headers:i,body:t,signal:n})}S.warn(`MCP token refresh returned no token for %s`,this.baseUrl)}return a}async start(){this.closing=!1,this.abortController=new AbortController;let e=await this.fetchWithAuthRetry(`GET`,void 0,this.abortController.signal);if(!e.ok)throw e.status===401?new n(`MCP server '${this.baseUrl}' requires authentication.`,{code:`MCP_UNAUTHORIZED`}):Error(`MCP SSE connection failed: ${e.status} ${e.statusText}`);let t=e.headers.get(`mcp-session-id`);t&&(this.sessionId=t),this.consumeStream(e,!0)}async send(e){let t=new AbortController,n=setTimeout(()=>{t.abort(new DOMException(`MCP request timeout`,`TimeoutError`))},this.requestTimeoutMs);n.unref?.();let r=this.abortController?AbortSignal.any([this.abortController.signal,t.signal]):t.signal;this.fetchWithAuthRetry(`POST`,JSON.stringify(e),r).then(async t=>{if(clearTimeout(n),!t.ok){if(t.status===401){S.warn(`MCP SSE POST 401: authentication required for %s`,this.baseUrl),this.failRequest(e,-32001,`MCP server requires authentication.`);return}S.warn(`MCP SSE POST failed: %s`,t.status),this.failRequest(e,-32e3,`MCP SSE POST failed: HTTP ${t.status}`);return}let r=t.headers.get(`mcp-session-id`);r&&(this.sessionId=r);let i=t.headers.get(`content-type`)??``;if(i.includes(`application/json`)){let e=await t.json();this.messageHandler?.(e)}else i.includes(`text/event-stream`)&&this.consumeStream(t,!1)}).catch(t=>{clearTimeout(n),t.name!==`AbortError`&&(S.warn(`MCP SSE send error: %s`,t.message),this.failRequest(e,-32e3,`MCP SSE send error: ${t.message}`))})}failRequest(e,t,n){`id`in e&&e.id!==void 0&&e.id!==null&&this.messageHandler?.({jsonrpc:`2.0`,id:e.id,error:{code:t,message:n}})}onMessage(e){this.messageHandler=e}onClose(e){this.closeHandler=e}async close(){this.closing=!0,this.abortController?.abort(),this.abortController=null}async consumeStream(e,t){let n=e.body?.getReader();if(!n){t&&!this.closing&&this.closeHandler?.(`SSE stream had no readable body`);return}let r=new TextDecoder,i=x(e=>{let t=e.trim();if(!(!t||t===`[DONE]`))try{let e=JSON.parse(t);this.messageHandler?.(e)}catch{S.debug(`Ignoring non-JSON SSE data`)}});try{for(;;){let{done:e,value:t}=await n.read();if(e)break;i.feed(r.decode(t,{stream:!0}))}i.flush(),t&&!this.closing&&this.closeHandler?.(`SSE stream ended`)}catch(e){e.name!==`AbortError`&&(S.warn(`MCP SSE stream error: %s`,e.message),t&&!this.closing&&this.closeHandler?.(`SSE stream error: ${e.message}`))}finally{n.releaseLock()}}};const D=s(`@x-otto/mcp:client`);var O=class{serverName;transport=null;config;options;status=`disconnected`;serverInfo=null;capabilities=null;tools=[];resources=[];prompts=[];nextRequestId=1;pending=new Map;closing=!1;reconnectAttempts=0;reconnectTimer=null;toolsChangedCallback=null;resourcesChangedCallback=null;disconnectedCallback=null;notificationCallback=null;constructor(e,t,n={}){this.serverName=e,this.config=t,this.options=n}getStatus(){return this.status}getConfig(){return this.config}getTools(){return this.tools}getResources(){return this.resources}getPrompts(){return this.prompts}getCapabilities(){return this.capabilities}getServerInfo(){return this.serverInfo}onToolsChanged(e){this.toolsChangedCallback=e}onResourcesChanged(e){this.resourcesChangedCallback=e}onDisconnected(e){this.disconnectedCallback=e}onNotification(e){this.notificationCallback=e}async connect(){if(this.status!==`ready`){this.closing=!1,this.status=`connecting`;try{await this.establish(),this.reconnectAttempts=0,this.status=`ready`}catch(e){this.status=`error`;let t=e instanceof Error?e.message:String(e);D.error(`MCP server "%s" connection failed: %s`,this.serverName,t);let r=e instanceof n&&e.code===`MCP_UNAUTHORIZED`?`MCP_UNAUTHORIZED`:`MCP_CONNECT_ERROR`;throw new n(`Failed to connect to MCP server "${this.serverName}": ${t}`,{code:r,cause:e instanceof Error?e:void 0})}}}async establish(){let e=this.createTransport();this.transport=e,e.onMessage(e=>this.handleMessage(e)),e.onClose(t=>{this.transport===e&&this.handleTransportClose(t)});try{await e.start(),this.serverInfo=await this.request(`initialize`,{protocolVersion:`2024-11-05`,capabilities:{roots:{listChanged:!1}},clientInfo:{name:`otto-coding`,version:`0.0.1`}}),this.capabilities=this.serverInfo.capabilities,this.notify(`notifications/initialized`),D.info(`MCP server "%s" connected: %s (protocol %s)`,this.serverName,this.serverInfo.serverInfo.name,this.serverInfo.protocolVersion),await this.refreshTools(),this.capabilities?.resources&&await this.refreshResources(),this.capabilities?.prompts&&await this.refreshPrompts()}catch(t){throw this.transport===e&&(this.transport=null),await e.close().catch(()=>void 0),t}}handleTransportClose(e){if(this.closing||(this.failPending(new n(`MCP transport closed: ${e??`unknown`}`,{code:`MCP_DISCONNECTED`})),this.status===`connecting`))return;let t=this.options.autoReconnect??!0,r=this.options.maxReconnectAttempts??3;if(!t||this.reconnectAttempts>=r){this.status=`disconnected`,this.transport=null,this.disconnectedCallback?.({reason:e,kind:`crash`});return}this.scheduleReconnect()}scheduleReconnect(){this.reconnectAttempts++,this.status=`reconnecting`;let e=(this.options.reconnectDelayMs??500)*2**(this.reconnectAttempts-1);D.info(`MCP server "%s" reconnecting (attempt %d) in %dms`,this.serverName,this.reconnectAttempts,e),this.reconnectTimer=setTimeout(()=>{this.attemptReconnect()},e)}async attemptReconnect(){if(!this.closing)try{this.transport&&await this.transport.close().catch(()=>void 0),this.transport=null,await this.establish(),this.reconnectAttempts=0,this.status=`ready`,D.info(`MCP server "%s" reconnected`,this.serverName)}catch(e){D.warn(`MCP server "%s" reconnect attempt %d failed: %s`,this.serverName,this.reconnectAttempts,e.message);let t=this.options.maxReconnectAttempts??3;this.reconnectAttempts>=t?(this.status=`error`,this.transport=null,this.disconnectedCallback?.({reason:`reconnect failed after ${t} attempts`,kind:`crash`})):this.scheduleReconnect()}}failPending(e){for(let[t,n]of this.pending)clearTimeout(n.timer),n.reject(e),this.pending.delete(t)}async refreshTools(){return this.tools=(await this.request(`tools/list`)).tools??[],D.debug(`MCP server "%s" provides %d tools: %s`,this.serverName,this.tools.length,this.tools.map(e=>e.name).join(`, `)),this.tools}async refreshResources(){try{this.resources=(await this.request(`resources/list`)).resources??[],D.debug(`MCP server "%s" provides %d resources`,this.serverName,this.resources.length)}catch(e){D.debug(`MCP server "%s" resources/list failed: %s`,this.serverName,e.message),this.resources=[]}return this.resources}async readResource(e){return(await this.request(`resources/read`,{uri:e})).contents??[]}async refreshPrompts(){try{this.prompts=(await this.request(`prompts/list`)).prompts??[],D.debug(`MCP server "%s" provides %d prompts`,this.serverName,this.prompts.length)}catch(e){D.debug(`MCP server "%s" prompts/list failed: %s`,this.serverName,e.message),this.prompts=[]}return this.prompts}async getPrompt(e,t){return await this.request(`prompts/get`,{name:e,arguments:t})}async callTool(e,t){if(this.status!==`ready`)throw new n(`MCP server "${this.serverName}" is not ready (status: ${this.status})`,{code:`MCP_NOT_READY`});return await this.request(`tools/call`,e,t)}async callRaw(e,t,n){return this.request(e,t,n)}async disconnect(){if(this.closing=!0,this.reconnectTimer&&=(clearTimeout(this.reconnectTimer),null),this.transport){try{await this.transport.close()}catch(e){D.debug(`Error closing MCP transport: %s`,e.message)}this.transport=null}this.failPending(new n(`MCP client disconnected`,{code:`MCP_DISCONNECTED`})),this.status=`disconnected`,this.tools=[],this.resources=[],this.prompts=[],this.serverInfo=null,this.capabilities=null,this.disconnectedCallback?.({reason:`client disconnect`,kind:`manual`})}createTransport(){if(this.options.transportFactory)return this.options.transportFactory();let{type:e,url:t,command:r}=this.config,i=e===`http`||e===`sse`||e===void 0&&t!==void 0,a=e===`stdio`||e===void 0&&r!==void 0;if(i){if(!t)throw new n(`MCP server "${this.serverName}" transport '${e}' requires a url`,{code:`MCP_CONFIG_ERROR`});return new E(t,{requestTimeoutMs:this.config.requestTimeoutMs??this.options.requestTimeoutMs,headers:this.config.headers,getAuthToken:this.options.getAuthToken})}if(a){if(!r)throw new n(`MCP server "${this.serverName}" transport 'stdio' requires a command`,{code:`MCP_CONFIG_ERROR`});return new T(r,this.config.args??[],this.config.env??{},this.options.processSpawnFactory)}throw new n(`MCP server "${this.serverName}" has no command or url configured`,{code:`MCP_CONFIG_ERROR`})}request(e,t,r){return new Promise((i,a)=>{if(r?.aborted){a(new n(`MCP request "${e}" aborted`,{code:`MCP_ABORTED`}));return}let o=this.nextRequestId++,s=this.config.requestTimeoutMs??this.options.requestTimeoutMs??3e4,c,l=()=>{c&&r&&r.removeEventListener(`abort`,c)},u=setTimeout(()=>{this.pending.delete(o),l(),a(new n(`MCP request "${e}" timed out after ${s}ms`,{code:`MCP_TIMEOUT`}))},s);this.pending.set(o,{resolve:e=>{l(),i(e)},reject:e=>{l(),a(e)},timer:u}),r&&(c=()=>{let t=this.pending.get(o);t&&(clearTimeout(t.timer),this.pending.delete(o),l(),a(new n(`MCP request "${e}" aborted`,{code:`MCP_ABORTED`})))},r.addEventListener(`abort`,c,{once:!0}));let d={jsonrpc:`2.0`,id:o,method:e,...t!==void 0&&{params:t}};try{this.transport.send(d)}catch(e){clearTimeout(u),this.pending.delete(o),l(),a(e)}})}notify(e,t){let n={jsonrpc:`2.0`,method:e,...t!==void 0&&{params:t}};try{this.transport.send(n)}catch(t){D.debug(`Failed to send MCP notification "%s": %s`,e,t.message)}}handleMessage(e){if(!(`id`in e)||e.id===void 0){this.handleNotification(e);return}let t=e,r=this.pending.get(t.id);if(!r){D.debug(`Received response for unknown request id: %s`,t.id);return}clearTimeout(r.timer),this.pending.delete(t.id),t.error?r.reject(new n(`MCP error: ${t.error.message} (code: ${t.error.code})`,{code:`MCP_SERVER_ERROR`})):r.resolve(t.result)}handleNotification(e){switch(e.method){case`notifications/tools/list_changed`:D.info(`MCP server "%s" notified tools list changed`,this.serverName),this.refreshTools().then(()=>{this.toolsChangedCallback?.()});break;case`notifications/resources/list_changed`:D.info(`MCP server "%s" notified resources list changed`,this.serverName),this.refreshResources().then(()=>{this.resourcesChangedCallback?.()});break;default:D.debug(`MCP notification: %s`,e.method)}this.notificationCallback?.(e.method,e.params)}};function k(e,t,n){return new O(e,t,n)}const ie=s(`@x-otto/mcp:adapter`);function ae(e){if(e.length===0)return null;if(e.every(e=>typeof e==`string`))return p.enum(e);let t=e.filter(e=>{let t=typeof e;return t===`string`||t===`number`||t===`boolean`}).map(e=>p.literal(e));return t.length===0?null:t.length===1?t[0]:p.union(t)}function A(e){if(Array.isArray(e.enum)){let t=ae(e.enum);if(t)return e.description?t.describe(e.description):t}switch(e.type){case`string`:return e.description?p.string().describe(e.description):p.string();case`number`:case`integer`:{let t=e.type===`integer`?p.number().int():p.number(),n=p.preprocess(e=>typeof e==`string`?Number(e):e,t);return e.description?n.describe(e.description):n}case`boolean`:return e.description?p.boolean().describe(e.description):p.boolean();case`array`:{let t=e.items?A(e.items):p.unknown();return e.description?p.array(t).describe(e.description):p.array(t)}case`object`:if(e.properties){let t={},n=new Set(e.required??[]);for(let[r,i]of Object.entries(e.properties)){let e=A(i);t[r]=n.has(r)?e:e.optional()}return e.description?p.object(t).describe(e.description):p.object(t)}return e.description?p.record(p.string(),p.unknown()).describe(e.description):p.record(p.string(),p.unknown());default:return p.unknown()}}function j(e){let t=e.inputSchema.properties;if(!t||Object.keys(t).length===0)return p.object({});let n={},r=new Set(e.inputSchema.required??[]);for(let[e,i]of Object.entries(t)){let t=A(i);n[e]=r.has(e)?t:t.optional()}return p.object(n)}const M=i;function N(e){let t=e.endsWith(`==`)?2:e.endsWith(`=`)?1:0;return Math.max(0,Math.floor(e.length*3/4)-t)}function oe(e){if(e.text!==void 0)return{type:`text`,text:e.text};if(e.blob!==void 0){if(e.mimeType?.startsWith(`image/`)&&e.mimeType!==`image/svg+xml`){let n=N(e.blob);if(n>M)return{type:`text`,text:`[image omitted: ${n} bytes exceeds ${M} byte limit — ask the tool to capture a smaller region/resolution]`};let r=u(e.blob,e.mimeType);return r&&(r.width>t||r.height>t)?{type:`text`,text:`[image omitted: ${r.width}x${r.height}px exceeds Anthropic per-image dimension hard limit of ${t}px — ask the tool to capture a smaller region or lower resolution]`}:{type:`image`,mime:e.mimeType,source:`data:${e.mimeType};base64,${e.blob}`}}let n=e.mimeType?` (${e.mimeType})`:``;return{type:`text`,text:`[Resource: ${e.uri}${n}, ${N(e.blob)} bytes binary]`}}return{type:`text`,text:`[Resource: ${e.uri}]`}}function P(e){return e.map(e=>{switch(e.type){case`text`:return{type:`text`,text:e.text};case`image`:if(!e.data)return{type:`text`,text:`[image unavailable: no data]`};{let n=N(e.data);if(n>M)return{type:`text`,text:`[image omitted: ${n} bytes exceeds ${M} byte limit — ask the tool to capture a smaller region/resolution]`};let r=u(e.data,e.mimeType??``);if(r&&(r.width>t||r.height>t))return{type:`text`,text:`[image omitted: ${r.width}x${r.height}px exceeds Anthropic per-image dimension hard limit of ${t}px — ask the tool to capture a smaller region or lower resolution]`}}return{type:`image`,mime:e.mimeType,source:`data:${e.mimeType};base64,${e.data}`};case`resource`:return oe(e.resource);default:return{type:`text`,text:JSON.stringify(e)}}})}function F(e){let t=e.toLowerCase().replace(/[_\s-]/g,``);return[`source`,`destination`,`dest`,`src`,`cwd`,`location`].includes(t)?!0:t.includes(`path`)||t.includes(`file`)||t.includes(`dir`)||t.includes(`folder`)}function I(e){let t=e.inputSchema.properties;if(!t)return[];let n=[];for(let[e,r]of Object.entries(t)){let t=r.type;(t===`string`||t===void 0)&&F(e)&&n.push(e)}return n}function L(e,t,n){let r=`mcp__${n}__${t.name}`,i=t.description?`[MCP: ${n}] ${t.description}`:`[MCP: ${n}] ${t.name}`,a=j(t),o=I(t),s=t.annotations?.readOnlyHint===!0;return{name:r,description:i,parameters:a,...o.length>0?{pathParams:o}:{},...s?{readonly:!0}:{},exposure:`deferred`,async execute({params:r,signal:i}){ie.debug(`Calling MCP tool %s on server %s`,t.name,n);let a=await e.callTool({name:t.name,arguments:r},i),o=P(a.content);return{content:o.length>0?o:[{type:`text`,text:`(empty result)`}],isError:a.isError??!1,details:{mcpServer:n,mcpTool:t.name}}}}}function R(e,t){return e.getTools().map(n=>L(e,n,t))}function z(e){let t=e.split(`,`).map(e=>e.trim()).find(e=>e.toLowerCase().startsWith(`bearer `)||e.toLowerCase()===`bearer`);if(t)return t.match(/metadata_url\s*=\s*"([^"]+)"/i)?.[1]}async function B(e,t=fetch){let n=await t(e,{headers:{Accept:`application/json`}});if(!n.ok)throw Error(`Failed to fetch protected resource metadata from ${e}: ${n.status}`);return await n.json()}async function V(e,t=fetch){let n=`${e.replace(/\/$/,``)}/.well-known/oauth-authorization-server`,r=`${e.replace(/\/$/,``)}/.well-known/openid-configuration`,i=async e=>{let n=await t(e,{headers:{Accept:`application/json`}});if(!n.ok)throw Error(`Metadata fetch failed: ${e} → ${n.status}`);return await n.json()};try{return await i(n)}catch{return i(r)}}async function H(e,t={}){let n=t.fetch??fetch,r=z(t.wwwAuthenticate??``)??`${e.replace(/\/$/,``)}/.well-known/oauth-protected-resource`,i=await B(r,n),a=i.authorization_servers?.[0]??new URL(r).origin;return{protectedResource:i,authorizationServer:await V(a,n),issuer:a}}async function U(e,t,n={}){let r=n.fetch??fetch,i={"Content-Type":`application/json`,Accept:`application/json`};n.initialAccessToken&&(i.Authorization=`Bearer ${n.initialAccessToken}`);let a=await r(e,{method:`POST`,headers:i,body:JSON.stringify({client_name:t.client_name??`otto MCP Client`,redirect_uris:t.redirect_uris,grant_types:t.grant_types??[`authorization_code`,`refresh_token`],token_endpoint_auth_method:t.token_endpoint_auth_method??`none`})});if(!a.ok){let t=await a.text().catch(()=>``);throw Error(`DCR registration failed at ${e}: ${a.status}${t?` — ${t}`:``}`)}return await a.json()}async function W(e,t,n={}){let i=n.fetch??fetch,a=new URLSearchParams({grant_type:`authorization_code`,code:t.code,redirect_uri:t.redirectUri,code_verifier:t.codeVerifier,client_id:t.clientId});t.resource&&a.set(`resource`,t.resource);let o=await i(e,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`,Accept:`application/json`},body:a.toString()});if(!o.ok){let e=await o.text().catch(()=>``);throw new r(`Token exchange failed: ${o.status}${e?` — ${e}`:``}`,{code:`OAUTH_TOKEN_EXCHANGE_FAILED`})}return K(await o.json())}async function G(e,t,n={}){let i=n.fetch??fetch,a=new URLSearchParams({grant_type:`refresh_token`,refresh_token:t.refreshToken});t.clientId&&a.set(`client_id`,t.clientId),t.resource&&a.set(`resource`,t.resource);let o=await i(e,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`,Accept:`application/json`},body:a.toString()});if(!o.ok){let e=await o.text().catch(()=>``);throw new r(`Token refresh failed: ${o.status}${e?` — ${e}`:``}`,{code:`OAUTH_REFRESH_FAILED`})}return K(await o.json(),t.refreshToken)}function K(e,t){return{accessToken:e.access_token,refreshToken:e.refresh_token??t,expiresAt:e.expires_in?(e.issued_at??Date.now())+e.expires_in*1e3:void 0,resource:e.resource}}function q(e,t=60){return e.expiresAt==null?!1:Date.now()+t*1e3>=e.expiresAt}function J(e={}){let t=e.baseDir??y(re(),`.otto`,`mcp`,`credentials`),n=e=>y(t,`${ce(e)}.json`),r=async()=>{await h(t,{recursive:!0,mode:448});try{await m(t,448)}catch{}};return{async load(e){try{let t=await g(n(e),`utf-8`);return JSON.parse(t)}catch(e){if(e?.code===`ENOENT`)return;throw e}},async save(e,t){await r();let i=n(e),a=`${i}.tmp-${process.pid}`;try{await te(a,JSON.stringify({...t,serverName:e},null,2),{encoding:`utf-8`,mode:384});try{await m(a,384)}catch{}await ee(a,i)}catch(e){throw await v(a).catch(()=>{}),e}},async delete(e){try{await v(n(e))}catch(e){if(e?.code===`ENOENT`)return;throw e}},async list(){try{let e=await _(t),n=[];for(let r of e)if(r.endsWith(`.json`))try{let e=le(await g(y(t,r),`utf-8`));n.push(e??r.slice(0,-5))}catch{n.push(r.slice(0,-5))}return n}catch(e){if(e?.code===`ENOENT`)return[];throw e}}}}function se(){let e=new Map;return{async load(t){return e.get(t)},async save(t,n){e.set(t,n)},async delete(t){e.delete(t)},async list(){return[...e.keys()]}}}function Y(e){let t=new Map,n=new Set;return{async load(r){if(t.has(r))return t.get(r);if(!n.has(r))return e.load(r)},async save(e,r){n.delete(e),t.set(e,r)},async delete(e){t.delete(e),n.add(e)},async list(){let r=new Set(await e.list());for(let e of n)r.delete(e);for(let e of t.keys())r.add(e);return[...r]}}}function X(e={}){let t=J(e);return b()?Y(t):t}function ce(e){return`${e.replace(/[^a-zA-Z0-9._-]/g,`_`)}_${ne(`sha256`).update(e).digest(`hex`).slice(0,8)}`}function le(e){try{let t=JSON.parse(e);return typeof t.serverName==`string`?t.serverName:void 0}catch{return}}async function Z(e){let{serverUrl:t,serverName:n,wwwAuthenticate:r,clientId:i,authorizationServer:a,tokenStore:o=X(),openBrowser:s=ue,fetch:u=fetch,signal:f}=e;if(f?.aborted)throw Error(`Login cancelled`);let p=a??(await H(t,{wwwAuthenticate:r,fetch:u})).authorizationServer;if(!p.token_endpoint)throw Error(`Authorization server has no token_endpoint`);let m=c(),h=l(),g=await d({ports:[1455,1457,1459],expectedState:h,signal:f}),_=g.redirectUri;try{let e=i,r=!1;if(!e&&p.registration_endpoint&&(e=(await U(p.registration_endpoint,{redirect_uris:[_]},{fetch:u})).client_id,r=!0),!e)throw Error(`No client_id available. Configure client_id in MCP server settings or ensure the authorization server supports Dynamic Client Registration (registration_endpoint).`);let a=new URLSearchParams({response_type:`code`,client_id:e,redirect_uri:_,code_challenge:m.challenge,code_challenge_method:m.method,state:h});a.set(`resource`,t),await s(`${p.authorization_endpoint}?${a.toString()}`);let c=await g.waitForCallback(),l=await W(p.token_endpoint,{code:c.code,redirectUri:_,codeVerifier:m.verifier,resource:t,clientId:e},{fetch:u});return await o.save(n,{...l,clientId:e}),{credentials:l,registered:r,clientId:e}}finally{await g.close()}}function Q(e){let{serverName:t,serverUrl:n,tokenStore:r=X(),clientId:i,authorizationServer:a,fetch:o}=e,s,c=a??void 0;return{async getAccessToken(){if(s||=await r.load(t),s){if(q(s)&&s.refreshToken)try{c||=(await H(n,{fetch:o})).authorizationServer,s=await G(c.token_endpoint,{refreshToken:s.refreshToken,resource:n,clientId:s.clientId??i},{fetch:o}),i&&(s={...s,clientId:i}),await r.save(t,s)}catch{return}return s.accessToken}},async login(e){return s=(await Z({...e,tokenStore:r,clientId:i,authorizationServer:c})).credentials,c??=e.authorizationServer,s.accessToken},async logout(){s=void 0,c=void 0,await r.delete(t)}}}async function ue(t){let n=process.platform===`darwin`?`open`:process.platform===`win32`?`start`:`xdg-open`,r=process.platform===`win32`?[``,t]:[t];return new Promise((t,i)=>{let a=e(n,r,{stdio:`ignore`,detached:!0});a.on(`error`,e=>{i(Error(`Failed to open browser: ${e.message}`))}),a.on(`close`,e=>{e===0||e===null?t():i(Error(`Browser command exited with code ${e}`))}),a.unref()})}const $=s(`@x-otto/mcp:manager`);var de=class extends a{clients=new Map;options;disabledServers;constructor(e={},t=[]){super(),this.options=e,this.disabledServers=new Set(t)}async connectAll(e){let t=Object.entries(e);if(t.length===0)return{connected:0,failed:0};$.info(`Connecting to %d MCP servers...`,t.length);let n=await Promise.allSettled(t.map(([e,t])=>f(`mcp:${e} connect`,()=>this.connect(e,t)))),r=0,i=0;for(let e of n)e.status===`fulfilled`?r++:i++;return $.info(`MCP servers: %d connected, %d failed`,r,i),{connected:r,failed:i}}async connect(e,t){if(this.disabledServers.has(e)){$.info(`MCP server "%s" is disabled, skipping`,e);return}this.clients.has(e)&&await this.disconnect(e);let r;if(t.url&&t.type!==`stdio`){let n=X(),i=await n.load(e);if(i){let a=Q({serverName:e,serverUrl:t.url,tokenStore:n,clientId:i.clientId});r=()=>a.getAccessToken()}}let i=k(e,t,{requestTimeoutMs:t.requestTimeoutMs??this.options.requestTimeoutMs,autoReconnect:t.autoReconnect??this.options.autoReconnect,maxReconnectAttempts:t.maxReconnectAttempts??this.options.maxReconnectAttempts,reconnectDelayMs:this.options.reconnectDelayMs,getAuthToken:r,processSpawnFactory:this.options.processSpawnFactory});i.onToolsChanged(()=>{$.info(`MCP server "%s" tools changed`,e),this.emit(`tools.changed`,{serverName:e,tools:i.getTools()})}),i.onResourcesChanged(()=>{$.info(`MCP server "%s" resources changed`,e),this.emit(`resources.changed`,{serverName:e})}),i.onDisconnected(t=>{$.info(`MCP server "%s" disconnected (%s): %s`,e,t.kind,t.reason??`unknown`),this.emit(`server.disconnected`,{name:e,reason:t.reason,kind:t.kind})}),i.onNotification((t,n)=>{this.emit(`server.notification`,{name:e,method:t,params:n})});try{await i.connect(),this.clients.set(e,i),this.emit(`server.connected`,{name:e,tools:i.getTools().length})}catch(t){let r=t.message,i=t instanceof n?t.code:void 0;if(this.emit(`server.error`,{name:e,error:r,code:i}),this.options.skipOnError!==!1)$.warn(`MCP server "%s" connection failed, skipping: %s`,e,r);else throw t}}async disconnect(e){let t=this.clients.get(e);t&&(await t.disconnect(),this.clients.delete(e),this.emit(`server.disconnected`,{name:e,reason:`manual disconnect`,kind:`manual`}))}async disconnectAll(){let e=[...this.clients.keys()];await Promise.allSettled(e.map(e=>this.disconnect(e)))}getAllTools(){let e=[];for(let[t,n]of this.clients)n.getStatus()===`ready`&&e.push(...R(n,t));return e}getAllResources(){let e=[];for(let[t,n]of this.clients)if(n.getStatus()===`ready`)for(let r of n.getResources())e.push({...r,serverName:t});return e}getAllPrompts(){let e=[];for(let[t,n]of this.clients)if(n.getStatus()===`ready`)for(let r of n.getPrompts())e.push({...r,serverName:t});return e}getClient(e){return this.clients.get(e)}getServerInfos(){let e=[];for(let[t,n]of this.clients)e.push({name:t,config:n.getConfig(),status:n.getStatus(),tools:n.getTools(),resources:n.getResources(),prompts:n.getPrompts(),capabilities:n.getCapabilities()??void 0,error:n.getStatus()===`error`?`Connection failed`:void 0});for(let t of this.disabledServers)this.clients.has(t)||e.push({name:t,status:`disconnected`,tools:[]});return e}get connectedCount(){let e=0;for(let t of this.clients.values())t.getStatus()===`ready`&&e++;return e}get toolCount(){let e=0;for(let t of this.clients.values())t.getStatus()===`ready`&&(e+=t.getTools().length);return e}};export{O as McpClient,de as McpManager,E as SseTransport,T as StdioTransport,X as createDefaultTokenStore,J as createFileTokenStore,k as createMcpClient,L as createMcpToolAdapter,R as createMcpToolAdapters,se as createMemoryTokenStore,Y as createOverlayTokenStore,x as createSseDataParser,Q as createTokenProvider,H as discoverOAuth,W as exchangeCode,V as fetchAuthorizationServerMetadata,B as fetchProtectedResourceMetadata,q as isTokenExpired,A as jsonSchemaPropertyToZod,Z as login,P as mcpContentToToolContent,j as mcpSchemaToZod,z as parseWwwAuthenticate,G as refreshAccessToken,U as registerClient,w as validateMcpUrl};
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["logger","logger","logger"],"sources":["../src/sse.ts","../src/transport.ts","../src/mcp-client.ts","../src/mcp-tool-adapter.ts","../src/oauth/discovery.ts","../src/oauth/registration.ts","../src/oauth/token.ts","../src/oauth/token-store.ts","../src/oauth/login.ts","../src/mcp-manager.ts"],"sourcesContent":["/**\n * sse.ts — W3C/MCP 规范的 Server-Sent Events 数据解析(M6/RFC-016 一致性硬化).\n *\n * 旧 consumeStream 内联解析有三处偏离规范:\n * 1. 仅按 `\\n` 切,未剥 CRLF 的尾随 `\\r`;\n * 2. 硬要求 `data: `(带空格),漏掉 `data:{...}`(冒号后空格可选);\n * 3. 每条 `data:` 行立即派发,未按规范累积多行 data 直到空行(事件边界)再 join。\n *\n * 本解析器对齐规范 + 官方 SDK(eventsource-parser 语义):累积 `data` 字段值,\n * 遇空行派发一个事件(多行 data 以 `\\n` 连接);忽略注释行(`:` 开头)与其它字段\n * (event/id/retry);流结束时 flush 末尾未以空行收尾的事件。\n */\nexport interface SseDataParser {\n /** 喂入一段原始文本块(可跨事件/跨行边界)。 */\n feed(chunk: string): void\n /** 流结束时调用:派发任何未以空行收尾的累积事件。 */\n flush(): void\n}\n\n/** 创建一个 SSE data 解析器;每个完整事件的 data(多行已 join)经 onData 回调。 */\nexport function createSseDataParser(onData: (data: string) => void): SseDataParser {\n let buffer = ''\n let dataLines: string[] = []\n\n const dispatch = (): void => {\n if (dataLines.length === 0) {\n return\n }\n const data = dataLines.join('\\n')\n dataLines = []\n onData(data)\n }\n\n const consumeLine = (rawLine: string): void => {\n const line = rawLine.endsWith('\\r') ? rawLine.slice(0, -1) : rawLine\n if (line === '') {\n dispatch()\n return\n }\n if (line.startsWith(':')) {\n return\n }\n if (line.startsWith('data:')) {\n let value = line.slice(5)\n if (value.startsWith(' ')) {\n value = value.slice(1)\n }\n dataLines.push(value)\n }\n }\n\n return {\n feed(chunk: string): void {\n buffer += chunk\n const lines = buffer.split('\\n')\n buffer = lines.pop() ?? ''\n for (const line of lines) {\n consumeLine(line)\n }\n },\n flush(): void {\n if (buffer.length > 0) {\n consumeLine(buffer)\n buffer = ''\n }\n dispatch()\n },\n }\n}\n","import { spawn } from 'node:child_process'\nimport { createLogger, McpError, buildAllowedEnv } from '@x-otto/shared'\nimport { JSON_RPC_VERSION } from './types'\nimport type { JsonRpcRequest, JsonRpcResponse, JsonRpcNotification } from './types'\nimport { createSseDataParser } from './sse'\n\n/** 轻量进程句柄(避免 MCP 包直接依赖 @x-otto/runtime) */\nexport interface LightManagedProcess {\n readonly pid: number | undefined\n readonly stdin: NodeJS.WritableStream | null\n readonly stdout: NodeJS.ReadableStream | null\n readonly stderr: NodeJS.ReadableStream | null\n kill(): Promise<void>\n onError(callback: (err: Error) => void): void\n onExit(callback: () => void): void\n}\n\n/**\n * 进程 spawn 工厂:由宿主(coding/McpController)注入,内部 wrap @x-otto/runtime 的 ProcessRuntime,\n * 使 MCP 子进程纳入统一运行时(Layer 3 exit reaper 覆盖 + 可观测)。缺省时 StdioTransport 走\n * 向后兼容的私有 spawn(仍 buildAllowedEnv + detached)。\n */\nexport type ProcessSpawnFactory = (command: string, args: string[], env: Record<string, string>) => LightManagedProcess\n\nconst logger = createLogger('@x-otto/mcp:transport')\n\nconst SSE_DEFAULT_TIMEOUT_MS = 30_000\n\nconst MAX_MESSAGE_BYTES = 32 * 1024 * 1024\n\n/**\n * 官方 MCP stdio 分帧:每条 JSON-RPC 消息单行、`\\n` 分隔、正文不含内嵌换行\n * (JSON.stringify 会把字符串内换行转义为 `\\n` 字面量,故按字节 0x0A 切是安全的;\n * 多字节 UTF-8 续字节永不为 0x0A)。容忍 CRLF。返回完整行文本 + 剩余未成行字节。\n *\n * 健壮性:无换行且缓冲超 MAX_MESSAGE_BYTES → 复位(防 DoS)。\n */\nexport function decodeNewlineFrames(buffer: Buffer): { bodies: string[]; rest: Buffer } {\n const bodies: string[] = []\n let buf = buffer\n\n for (;;) {\n const nl = buf.indexOf(0x0a)\n if (nl === -1) {\n if (buf.length > MAX_MESSAGE_BYTES) {\n logger.warn('MCP newline frame exceeds limit without terminator; resetting buffer')\n buf = Buffer.alloc(0)\n }\n break\n }\n\n let line = buf.subarray(0, nl)\n if (line.length > 0 && line[line.length - 1] === 0x0d) {\n line = line.subarray(0, line.length - 1)\n }\n buf = buf.subarray(nl + 1)\n\n const text = line.toString('utf-8').trim()\n if (text) bodies.push(text)\n }\n\n return { bodies, rest: buf }\n}\n\ntype FetchLike = (input: string, init?: RequestInit) => Promise<Response>\n\nexport interface McpTransport {\n send(message: JsonRpcRequest | JsonRpcNotification): void | Promise<void>\n onMessage(handler: (message: JsonRpcResponse | JsonRpcNotification) => void): void\n /** 传输非预期断开(进程退出 / SSE 流结束或出错)时回调;client 据此触发自动重连。 */\n onClose(handler: (reason?: string) => void): void\n start(): Promise<void>\n close(): Promise<void>\n}\n\n/**\n * 校验 MCP server URL:协议白名单 http/https,拒绝 file:/ftp:/data:/javascript: 等。\n * **不**拦本机/私网——本地 MCP server(localhost/127.0.0.1)是常态;DNS-rebinding 对长驻配置端点\n * 风险低,且 MCP 端点由运维配置(非 LLM 输入)。仅挡协议层误用与畸形 URL。\n */\nexport function validateMcpUrl(url: string): URL {\n let parsed: URL\n try {\n parsed = new URL(url)\n } catch {\n throw new Error(`Invalid MCP server URL: ${url}`)\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error(`Unsupported MCP server URL protocol: ${parsed.protocol} (use http/https)`)\n }\n return parsed\n}\n\nexport class StdioTransport implements McpTransport {\n private managedProcess: LightManagedProcess | null = null\n private messageHandler: ((msg: JsonRpcResponse | JsonRpcNotification) => void) | null = null\n private closeHandler: ((reason?: string) => void) | null = null\n private closing = false\n private buffer: Buffer = Buffer.alloc(0)\n private readonly command: string\n private readonly args: string[]\n private readonly env: Record<string, string>\n private readonly processSpawnFactory: ProcessSpawnFactory | undefined\n\n constructor(\n command: string,\n args: string[] = [],\n env: Record<string, string> = {},\n processSpawnFactory?: ProcessSpawnFactory,\n ) {\n this.command = command\n this.args = args\n this.env = env\n this.processSpawnFactory = processSpawnFactory\n }\n\n async start(): Promise<void> {\n if (this.processSpawnFactory) {\n // RFC-085 M2:通过注入的 spawn factory 管理进程(将接入 ProcessRuntime)\n this.managedProcess = this.processSpawnFactory(this.command, this.args, this.env)\n } else {\n // 向后兼容:直接 spawn\n const child = spawn(this.command, this.args, {\n stdio: ['pipe', 'pipe', 'pipe'],\n env: buildAllowedEnv(this.env),\n detached: true, // C1\n })\n this.managedProcess = {\n pid: child.pid,\n stdin: child.stdin,\n stdout: child.stdout,\n stderr: child.stderr,\n kill: async (): Promise<void> => {\n child.stdin?.end()\n return new Promise<void>((resolve) => {\n const t = setTimeout(() => { child.kill('SIGKILL'); resolve() }, 2000)\n child.on('exit', () => { clearTimeout(t); resolve() })\n child.kill('SIGTERM')\n })\n },\n onError: (callback: (err: Error) => void): void => { child.on('error', callback) },\n onExit: (callback: () => void): void => { child.on('exit', () => callback()) },\n }\n }\n\n const proc = this.managedProcess\n const stdout = proc.stdout\n if (stdout) {\n stdout.on('data', (chunk: unknown) => {\n this.buffer = Buffer.concat([this.buffer, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string)])\n this.processBuffer()\n })\n }\n\n const stderr = proc.stderr\n if (stderr) {\n stderr.on('data', (chunk: unknown) => {\n const s = Buffer.isBuffer(chunk) ? chunk.toString('utf-8').trimEnd() : String(chunk).trimEnd()\n logger.debug('MCP stderr: %s', s)\n })\n }\n\n proc.onError((err) => {\n logger.error('MCP process error: %s', err.message)\n })\n\n proc.onExit(() => {\n logger.debug('MCP process exited')\n if (!this.closing) {\n this.closeHandler?.('process exited')\n }\n this.managedProcess = null\n })\n }\n\n send(message: JsonRpcRequest | JsonRpcNotification): void {\n const stdin = this.managedProcess?.stdin\n if (!stdin?.writable) {\n throw new Error('MCP stdio transport is not connected')\n }\n\n const json = JSON.stringify(message)\n ;(stdin as NodeJS.WritableStream).write(json + '\\n')\n }\n\n onMessage(handler: (message: JsonRpcResponse | JsonRpcNotification) => void): void {\n this.messageHandler = handler\n }\n\n onClose(handler: (reason?: string) => void): void {\n this.closeHandler = handler\n }\n\n async close(): Promise<void> {\n this.closing = true\n if (this.managedProcess) {\n await this.managedProcess.kill()\n this.managedProcess = null\n }\n }\n\n private processBuffer(): void {\n const { bodies, rest } = decodeNewlineFrames(this.buffer)\n this.buffer = rest\n for (const body of bodies) {\n try {\n const message = JSON.parse(body) as JsonRpcResponse | JsonRpcNotification\n this.messageHandler?.(message)\n } catch (err) {\n logger.warn('Failed to parse MCP message: %s', (err as Error).message)\n }\n }\n }\n}\n\nexport interface SseTransportOptions {\n /** 单次请求超时(ms),与传输级 abort 组合。缺省 30s。 */\n requestTimeoutMs?: number\n /** 注入 fetch(测试用)。缺省全局 fetch。 */\n fetchImpl?: FetchLike\n /**\n * 静态请求头,随每次 GET(流)/POST(消息)发出。鉴权(`Authorization: Bearer …` /\n * `X-API-Key`)及自定义头。与内置头(Accept/Content-Type/Mcp-Session-Id)合并——**内置头优先**,\n * 防止误覆盖协议必需头。\n */\n headers?: Record<string, string>\n /**\n * 动态 Bearer token provider(OAuth 2.1)。每次请求前调用,返回的 access token\n * 注入 `Authorization: Bearer <token>` 头。**优先级最高**——覆盖静态 headers 中的 Authorization。\n * 返回 undefined 时跳过(用静态 headers 中的 Authorization)。\n */\n getAuthToken?: () => Promise<string | undefined>\n}\n\nexport class SseTransport implements McpTransport {\n private messageHandler: ((msg: JsonRpcResponse | JsonRpcNotification) => void) | null = null\n private closeHandler: ((reason?: string) => void) | null = null\n private abortController: AbortController | null = null\n private sessionId: string | null = null\n private closing = false\n private readonly baseUrl: string\n private readonly fetchImpl: FetchLike\n private readonly requestTimeoutMs: number\n private readonly headers: Record<string, string>\n private readonly getAuthToken?: () => Promise<string | undefined>\n\n constructor(url: string, options: SseTransportOptions = {}) {\n this.baseUrl = validateMcpUrl(url).href\n this.fetchImpl = options.fetchImpl ?? (globalThis.fetch as FetchLike)\n this.requestTimeoutMs = options.requestTimeoutMs ?? SSE_DEFAULT_TIMEOUT_MS\n this.headers = options.headers ?? {}\n this.getAuthToken = options.getAuthToken\n }\n\n /** 解析本次请求的 headers:静态 headers + 动态 Bearer(最高优先)+ 内置协议头覆盖。 */\n private async resolveHeaders(\n ...builtin: Record<string, string>[]\n ): Promise<Record<string, string>> {\n let merged = { ...this.headers }\n\n if (this.getAuthToken) {\n const token = await this.getAuthToken()\n if (token) {\n merged = { ...merged, Authorization: `Bearer ${token}` }\n }\n }\n\n for (const override of builtin) {\n merged = { ...merged, ...override }\n }\n return merged\n }\n\n /**\n * 认证中间层——401 时自动刷新 token 并重试一次(handle_401 → refresh → retry)。\n */\n private async fetchWithAuthRetry(\n method: 'GET' | 'POST',\n body?: string,\n signal?: AbortSignal,\n ): Promise<Response> {\n const builtin: Record<string, string> =\n method === 'GET'\n ? { Accept: 'text/event-stream' }\n : {\n 'Content-Type': 'application/json',\n ...(this.sessionId ? { 'Mcp-Session-Id': this.sessionId } : {}),\n }\n\n const headers = await this.resolveHeaders(builtin)\n const response = await this.fetchImpl(this.baseUrl, {\n method,\n headers,\n body,\n signal,\n })\n\n if (response.status === 401 && this.getAuthToken) {\n logger.debug('MCP 401 on %s %s — attempting token refresh and retry', method, this.baseUrl)\n\n const newToken = await this.getAuthToken()\n if (newToken) {\n const retryHeaders = await this.resolveHeaders(builtin)\n return this.fetchImpl(this.baseUrl, {\n method,\n headers: retryHeaders,\n body,\n signal,\n })\n }\n\n logger.warn('MCP token refresh returned no token for %s', this.baseUrl)\n }\n\n return response\n }\n\n async start(): Promise<void> {\n this.closing = false\n this.abortController = new AbortController()\n\n const response = await this.fetchWithAuthRetry('GET', undefined, this.abortController.signal)\n\n if (!response.ok) {\n if (response.status === 401) {\n // 协议层只报中性事实 + 错误码,**不**嵌宿主 CLI 文案\n // ('otto mcp login' 是 otto CLI 词汇,宿主知识)。actionable 指引由宿主据 code 渲染。\n throw new McpError(`MCP server '${this.baseUrl}' requires authentication.`, {\n code: 'MCP_UNAUTHORIZED',\n })\n }\n throw new Error(`MCP SSE connection failed: ${response.status} ${response.statusText}`)\n }\n\n const sessionId = response.headers.get('mcp-session-id')\n if (sessionId) {\n this.sessionId = sessionId\n }\n\n void this.consumeStream(response, true)\n }\n\n async send(message: JsonRpcRequest | JsonRpcNotification): Promise<void> {\n const perRequest = new AbortController()\n const timer = setTimeout(() => {\n perRequest.abort(new DOMException('MCP request timeout', 'TimeoutError'))\n }, this.requestTimeoutMs)\n timer.unref?.()\n const signal = this.abortController\n ? AbortSignal.any([this.abortController.signal, perRequest.signal])\n : perRequest.signal\n\n void this.fetchWithAuthRetry('POST', JSON.stringify(message), signal)\n .then(async (resp) => {\n clearTimeout(timer)\n if (!resp.ok) {\n if (resp.status === 401) {\n logger.warn('MCP SSE POST 401: authentication required for %s', this.baseUrl)\n // 中性 reason + 鉴权错误码(-32001);宿主据码渲染 CLI 指引。\n this.failRequest(message, -32001, `MCP server requires authentication.`)\n return\n }\n logger.warn('MCP SSE POST failed: %s', resp.status)\n this.failRequest(message, -32000, `MCP SSE POST failed: HTTP ${resp.status}`)\n return\n }\n\n const sid = resp.headers.get('mcp-session-id')\n if (sid) {\n this.sessionId = sid\n }\n\n const contentType = resp.headers.get('content-type') ?? ''\n if (contentType.includes('application/json')) {\n const body = (await resp.json()) as JsonRpcResponse | JsonRpcNotification\n this.messageHandler?.(body)\n } else if (contentType.includes('text/event-stream')) {\n void this.consumeStream(resp, false)\n }\n })\n .catch((err: Error) => {\n clearTimeout(timer)\n if (err.name !== 'AbortError') {\n logger.warn('MCP SSE send error: %s', err.message)\n this.failRequest(message, -32000, `MCP SSE send error: ${err.message}`)\n }\n })\n }\n\n private failRequest(\n message: JsonRpcRequest | JsonRpcNotification,\n code: number,\n reason: string,\n ): void {\n if ('id' in message && message.id !== undefined && message.id !== null) {\n this.messageHandler?.({ jsonrpc: JSON_RPC_VERSION, id: message.id, error: { code, message: reason } })\n }\n }\n\n onMessage(handler: (message: JsonRpcResponse | JsonRpcNotification) => void): void {\n this.messageHandler = handler\n }\n\n onClose(handler: (reason?: string) => void): void {\n this.closeHandler = handler\n }\n\n async close(): Promise<void> {\n this.closing = true\n this.abortController?.abort()\n this.abortController = null\n }\n\n private async consumeStream(response: Response, isPrimary: boolean): Promise<void> {\n const reader = response.body?.getReader()\n if (!reader) {\n if (isPrimary && !this.closing) {\n this.closeHandler?.('SSE stream had no readable body')\n }\n return\n }\n\n const decoder = new TextDecoder()\n const parser = createSseDataParser((data) => {\n const trimmed = data.trim()\n if (!trimmed || trimmed === '[DONE]') {\n return\n }\n try {\n const message = JSON.parse(trimmed) as JsonRpcResponse | JsonRpcNotification\n this.messageHandler?.(message)\n } catch {\n logger.debug('Ignoring non-JSON SSE data')\n }\n })\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) {\n break\n }\n parser.feed(decoder.decode(value, { stream: true }))\n }\n parser.flush()\n if (isPrimary && !this.closing) {\n this.closeHandler?.('SSE stream ended')\n }\n } catch (err) {\n if ((err as Error).name !== 'AbortError') {\n logger.warn('MCP SSE stream error: %s', (err as Error).message)\n if (isPrimary && !this.closing) {\n this.closeHandler?.(`SSE stream error: ${(err as Error).message}`)\n }\n }\n } finally {\n reader.releaseLock()\n }\n }\n}\n\n// transport 工厂族(createStdioTransport/createSseTransport/createHttpTransport)已删——\n// 零生产消费,mcp-client 直接 new StdioTransport/SseTransport,仅测试曾用工厂便捷构造(RFC-074 M10)。\n","import { McpError, createLogger } from '@x-otto/shared'\nimport { StdioTransport, SseTransport } from './transport'\n\nimport type { McpTransport, ProcessSpawnFactory } from './transport'\nimport { JSON_RPC_VERSION } from './types'\nimport type {\n JsonRpcRequest,\n JsonRpcResponse,\n JsonRpcNotification,\n McpInitializeResult,\n McpToolDefinition,\n McpToolCallParams,\n McpToolCallResult,\n McpServerConfig,\n McpClientStatus,\n McpServerCapabilities,\n McpResource,\n McpResourceContents,\n McpPrompt,\n McpPromptMessage,\n} from './types'\n\nconst logger = createLogger('@x-otto/mcp:client')\n\nconst MCP_PROTOCOL_VERSION = '2024-11-05'\n\n/**\n * RFC-165 D8/H2:断连分类——区分\"预期内的显式断开\"(`kind: 'manual'`,如\n * `McpClient.disconnect()` 被调用)与\"传输层意外关闭\"(`kind: 'crash'`,如子进程异常退出、\n * SSE 流出错、重连耗尽)。这是最小可行区分(不深入解析子进程 exit code),供上层\n * (`McpManager`/`McpController`)据此派生更细的语义(如\"用户经 /extension 禁用\" vs\n * \"server 真的崩了\"——那一层区分需要 disable 调用方上下文,留给 `McpManager`/\n * `McpController` 在 `kind: 'manual'` 基础上进一步标注,见 D8 备注)。\n */\nexport interface DisconnectInfo {\n reason?: string\n kind: 'manual' | 'crash'\n}\nconst MCP_CLIENT_NAME = 'otto-coding'\nconst MCP_CLIENT_VERSION = '0.0.1'\n\ninterface PendingRequest {\n resolve: (value: unknown) => void\n reject: (reason: Error) => void\n timer: ReturnType<typeof setTimeout>\n}\n\nexport interface McpClientOptions {\n /** 请求超时(毫秒),默认 30000 */\n requestTimeoutMs?: number\n /** 自动重连(默认 true) */\n autoReconnect?: boolean\n /** 最大重连次数(默认 3) */\n maxReconnectAttempts?: number\n /** 重连退避基数(毫秒,默认 500;第 n 次=base·2^(n-1))。测试可设小值。 */\n reconnectDelayMs?: number\n /** 注入 transport 工厂(测试用)。缺省按 config 构造 stdio/sse。 */\n transportFactory?: () => McpTransport\n /**\n * 注入进程 spawn 工厂(RFC-085 M2):stdio transport 用它把 MCP 子进程纳入 ProcessRuntime。\n * 缺省时 StdioTransport 走向后兼容私有 spawn(仍 buildAllowedEnv + detached)。\n */\n processSpawnFactory?: ProcessSpawnFactory\n /**\n * 动态 Bearer token provider(OAuth 2.1)。\n * 每次 HTTP 请求前调用,返回的 access token 注入 Authorization 头。\n * 由调用方(McpManager 或外部)注入 TokenProvider.getAccessToken。\n */\n getAuthToken?: () => Promise<string | undefined>\n}\n\nexport class McpClient {\n readonly serverName: string\n private transport: McpTransport | null = null\n private config: McpServerConfig\n private options: McpClientOptions\n\n private status: McpClientStatus = 'disconnected'\n private serverInfo: McpInitializeResult | null = null\n private capabilities: McpServerCapabilities | null = null\n private tools: McpToolDefinition[] = []\n private resources: McpResource[] = []\n private prompts: McpPrompt[] = []\n private nextRequestId = 1\n private pending = new Map<number | string, PendingRequest>()\n private closing = false\n private reconnectAttempts = 0\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null\n private toolsChangedCallback: (() => void) | null = null\n private resourcesChangedCallback: (() => void) | null = null\n private disconnectedCallback: ((info: DisconnectInfo) => void) | null = null\n /** RFC-165 D9:通用 notification 订阅——server 发出的、未被上面专用处理的 method\n * (如 `backend/state`)经此回调透传给上层,不再被 `logger.debug` 静默丢弃。\n * 对全部 notification(含已被专用 switch-case 处理的)都会调用,让消费方可按需扩展。 */\n private notificationCallback: ((method: string, params?: Record<string, unknown>) => void) | null = null\n\n constructor(serverName: string, config: McpServerConfig, options: McpClientOptions = {}) {\n this.serverName = serverName\n this.config = config\n this.options = options\n }\n\n getStatus(): McpClientStatus {\n return this.status\n }\n\n getConfig(): McpServerConfig {\n return this.config\n }\n\n getTools(): McpToolDefinition[] {\n return this.tools\n }\n\n getResources(): McpResource[] {\n return this.resources\n }\n\n getPrompts(): McpPrompt[] {\n return this.prompts\n }\n\n getCapabilities(): McpServerCapabilities | null {\n return this.capabilities\n }\n\n getServerInfo(): McpInitializeResult | null {\n return this.serverInfo\n }\n\n onToolsChanged(callback: () => void): void {\n this.toolsChangedCallback = callback\n }\n\n onResourcesChanged(callback: () => void): void {\n this.resourcesChangedCallback = callback\n }\n\n onDisconnected(callback: (info: DisconnectInfo) => void): void {\n this.disconnectedCallback = callback\n }\n\n /**\n * RFC-165 D9/H1:通用 notification 订阅——面向未被 `handleNotification` 专用 switch-case\n * 处理的 method(如插件 backend facet 推送的 `backend/state`)。已处理的 method\n * (`tools/list_changed`/`resources/list_changed`)也会触发本回调,消费方按 method 自行过滤。\n */\n onNotification(callback: (method: string, params?: Record<string, unknown>) => void): void {\n this.notificationCallback = callback\n }\n\n async connect(): Promise<void> {\n if (this.status === 'ready') {\n return\n }\n\n this.closing = false\n this.status = 'connecting'\n\n try {\n await this.establish()\n this.reconnectAttempts = 0\n this.status = 'ready'\n } catch (err) {\n this.status = 'error'\n const message = err instanceof Error ? err.message : String(err)\n logger.error('MCP server \"%s\" connection failed: %s', this.serverName, message)\n // 保留底层鉴权错误码(MCP_UNAUTHORIZED),不一律压成 MCP_CONNECT_ERROR——\n // 否则宿主无从据码渲染 actionable 指引('otto mcp login')。\n const code =\n err instanceof McpError && err.code === 'MCP_UNAUTHORIZED'\n ? 'MCP_UNAUTHORIZED'\n : 'MCP_CONNECT_ERROR'\n throw new McpError(`Failed to connect to MCP server \"${this.serverName}\": ${message}`, {\n code,\n cause: err instanceof Error ? err : undefined,\n })\n }\n }\n\n private async establish(): Promise<void> {\n const transport = this.createTransport()\n this.transport = transport\n transport.onMessage((msg) => this.handleMessage(msg))\n transport.onClose((reason) => {\n if (this.transport !== transport) {\n return\n }\n this.handleTransportClose(reason)\n })\n\n try {\n await transport.start()\n\n this.serverInfo = await this.request<McpInitializeResult>('initialize', {\n protocolVersion: MCP_PROTOCOL_VERSION,\n capabilities: { roots: { listChanged: false } },\n clientInfo: { name: MCP_CLIENT_NAME, version: MCP_CLIENT_VERSION },\n })\n\n this.capabilities = this.serverInfo.capabilities\n this.notify('notifications/initialized')\n\n logger.info(\n 'MCP server \"%s\" connected: %s (protocol %s)',\n this.serverName,\n this.serverInfo.serverInfo.name,\n this.serverInfo.protocolVersion,\n )\n\n await this.refreshTools()\n if (this.capabilities?.resources) {\n await this.refreshResources()\n }\n if (this.capabilities?.prompts) {\n await this.refreshPrompts()\n }\n } catch (err) {\n if (this.transport === transport) {\n this.transport = null\n }\n await transport.close().catch(() => undefined)\n throw err\n }\n }\n\n private handleTransportClose(reason?: string): void {\n if (this.closing) {\n return\n }\n this.failPending(\n new McpError(`MCP transport closed: ${reason ?? 'unknown'}`, { code: 'MCP_DISCONNECTED' }),\n )\n\n // B10:初次连接期(status==='connecting')transport 关闭,交给 establish 的 catch + connect() 的\n // throw 上报,此处不重连、不改状态、不回调——否则被丢弃的 client(从未入池)仍武装重连定时器,\n // 反复 spawn 子进程/重开流(孤儿重连循环)。只有已建立(ready)或重连中(reconnecting)的掉线才自愈。\n if (this.status === 'connecting') {\n return\n }\n\n const autoReconnect = this.options.autoReconnect ?? true\n const maxAttempts = this.options.maxReconnectAttempts ?? 3\n if (!autoReconnect || this.reconnectAttempts >= maxAttempts) {\n this.status = 'disconnected'\n this.transport = null\n // transport 非预期关闭(非 disconnect() 主动调用)——分类为 crash(D8/H2)。\n this.disconnectedCallback?.({ reason, kind: 'crash' })\n return\n }\n this.scheduleReconnect()\n }\n\n private scheduleReconnect(): void {\n this.reconnectAttempts++\n this.status = 'reconnecting'\n const base = this.options.reconnectDelayMs ?? 500\n const delay = base * 2 ** (this.reconnectAttempts - 1)\n logger.info(\n 'MCP server \"%s\" reconnecting (attempt %d) in %dms',\n this.serverName,\n this.reconnectAttempts,\n delay,\n )\n this.reconnectTimer = setTimeout(() => {\n void this.attemptReconnect()\n }, delay)\n }\n\n private async attemptReconnect(): Promise<void> {\n if (this.closing) {\n return\n }\n try {\n // 防御性:先 close 旧 transport(stdio 下 child 通常已 exit,但仍该做)\n if (this.transport) {\n await this.transport.close().catch(() => undefined)\n }\n this.transport = null\n await this.establish()\n this.reconnectAttempts = 0\n this.status = 'ready'\n logger.info('MCP server \"%s\" reconnected', this.serverName)\n } catch (err) {\n logger.warn(\n 'MCP server \"%s\" reconnect attempt %d failed: %s',\n this.serverName,\n this.reconnectAttempts,\n (err as Error).message,\n )\n const maxAttempts = this.options.maxReconnectAttempts ?? 3\n if (this.reconnectAttempts >= maxAttempts) {\n this.status = 'error'\n this.transport = null\n // 重连耗尽——仍是非预期关闭的延续,分类为 crash(D8/H2)。\n this.disconnectedCallback?.({\n reason: `reconnect failed after ${maxAttempts} attempts`,\n kind: 'crash',\n })\n } else {\n this.scheduleReconnect()\n }\n }\n }\n\n private failPending(error: Error): void {\n for (const [id, pending] of this.pending) {\n clearTimeout(pending.timer)\n pending.reject(error)\n this.pending.delete(id)\n }\n }\n\n async refreshTools(): Promise<McpToolDefinition[]> {\n const result = await this.request<{ tools: McpToolDefinition[] }>('tools/list')\n this.tools = result.tools ?? []\n\n logger.debug(\n 'MCP server \"%s\" provides %d tools: %s',\n this.serverName,\n this.tools.length,\n this.tools.map((t) => t.name).join(', '),\n )\n\n return this.tools\n }\n\n async refreshResources(): Promise<McpResource[]> {\n try {\n const result = await this.request<{ resources: McpResource[] }>('resources/list')\n this.resources = result.resources ?? []\n\n logger.debug('MCP server \"%s\" provides %d resources', this.serverName, this.resources.length)\n } catch (err) {\n logger.debug(\n 'MCP server \"%s\" resources/list failed: %s',\n this.serverName,\n (err as Error).message,\n )\n this.resources = []\n }\n\n return this.resources\n }\n\n async readResource(uri: string): Promise<McpResourceContents[]> {\n const result = await this.request<{ contents: McpResourceContents[] }>('resources/read', {\n uri,\n })\n return result.contents ?? []\n }\n\n async refreshPrompts(): Promise<McpPrompt[]> {\n try {\n const result = await this.request<{ prompts: McpPrompt[] }>('prompts/list')\n this.prompts = result.prompts ?? []\n\n logger.debug('MCP server \"%s\" provides %d prompts', this.serverName, this.prompts.length)\n } catch (err) {\n logger.debug(\n 'MCP server \"%s\" prompts/list failed: %s',\n this.serverName,\n (err as Error).message,\n )\n this.prompts = []\n }\n\n return this.prompts\n }\n\n async getPrompt(\n name: string,\n args?: Record<string, string>,\n ): Promise<{ description?: string; messages: McpPromptMessage[] }> {\n const result = await this.request<{ description?: string; messages: McpPromptMessage[] }>(\n 'prompts/get',\n {\n name,\n arguments: args,\n },\n )\n return result\n }\n\n async callTool(params: McpToolCallParams, signal?: AbortSignal): Promise<McpToolCallResult> {\n if (this.status !== 'ready') {\n throw new McpError(`MCP server \"${this.serverName}\" is not ready (status: ${this.status})`, {\n code: 'MCP_NOT_READY',\n })\n }\n\n const result = await this.request<McpToolCallResult>(\n 'tools/call',\n params as unknown as Record<string, unknown>,\n signal,\n )\n return result\n }\n\n /**\n * RFC-165 T6:通用请求出口——镜像底层私有 `request`,但方法名不含 \"tool\" 语义\n * (避免与模型工具调用面混淆,对齐 D3 概念分离:即使是客户端内部 API 命名也不应让\n * `backend/*` 调用看起来像\"调用了一个 tool\")。供 `PanelBackendController` 发送\n * `backend/call`/`backend/cancel`/`backend/getState` 等面板专用请求。\n *\n * 刻意不要求 `status === 'ready'`(区别于 `callTool`)——D7 降级启动场景下 backend facet\n * 恒可用,但 `mcp` facet 的业务工具可能未注册,`ready` 判断不应耦合 `mcp` facet 健康度;\n * 断连场景由调用方(`PanelBackendController`)经 `McpClient.getStatus()` 感知并拒绝新请求。\n */\n async callRaw(method: string, params?: Record<string, unknown>, signal?: AbortSignal): Promise<unknown> {\n return this.request<unknown>(method, params, signal)\n }\n\n async disconnect(): Promise<void> {\n this.closing = true\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n\n if (this.transport) {\n try {\n await this.transport.close()\n } catch (err) {\n logger.debug('Error closing MCP transport: %s', (err as Error).message)\n }\n this.transport = null\n }\n\n this.failPending(new McpError('MCP client disconnected', { code: 'MCP_DISCONNECTED' }))\n\n this.status = 'disconnected'\n this.tools = []\n this.resources = []\n this.prompts = []\n this.serverInfo = null\n this.capabilities = null\n // 调用方主动发起(如 McpManager.disconnect()/setServerEnabled(false))——分类为 manual(D8/H2)。\n this.disconnectedCallback?.({ reason: 'client disconnect', kind: 'manual' })\n }\n\n private createTransport(): McpTransport {\n if (this.options.transportFactory) {\n return this.options.transportFactory()\n }\n\n const { type, url, command } = this.config\n const wantsHttp = type === 'http' || type === 'sse' || (type === undefined && url !== undefined)\n const wantsStdio = type === 'stdio' || (type === undefined && command !== undefined)\n\n if (wantsHttp) {\n if (!url) {\n throw new McpError(`MCP server \"${this.serverName}\" transport '${type}' requires a url`, {\n code: 'MCP_CONFIG_ERROR',\n })\n }\n return new SseTransport(url, {\n requestTimeoutMs: this.config.requestTimeoutMs ?? this.options.requestTimeoutMs,\n headers: this.config.headers,\n getAuthToken: this.options.getAuthToken,\n })\n }\n\n if (wantsStdio) {\n if (!command) {\n throw new McpError(`MCP server \"${this.serverName}\" transport 'stdio' requires a command`, {\n code: 'MCP_CONFIG_ERROR',\n })\n }\n return new StdioTransport(\n command,\n this.config.args ?? [],\n this.config.env ?? {},\n this.options.processSpawnFactory,\n )\n }\n\n throw new McpError(`MCP server \"${this.serverName}\" has no command or url configured`, {\n code: 'MCP_CONFIG_ERROR',\n })\n }\n\n private request<T>(\n method: string,\n params?: Record<string, unknown>,\n signal?: AbortSignal,\n ): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new McpError(`MCP request \"${method}\" aborted`, { code: 'MCP_ABORTED' }))\n return\n }\n const id = this.nextRequestId++\n const timeoutMs = this.config.requestTimeoutMs ?? this.options.requestTimeoutMs ?? 30_000\n\n let abortHandler: (() => void) | undefined\n const detach = (): void => {\n if (abortHandler && signal) signal.removeEventListener('abort', abortHandler)\n }\n\n const timer = setTimeout(() => {\n this.pending.delete(id)\n detach()\n reject(\n new McpError(`MCP request \"${method}\" timed out after ${timeoutMs}ms`, {\n code: 'MCP_TIMEOUT',\n }),\n )\n }, timeoutMs)\n\n this.pending.set(id, {\n resolve: (v: unknown) => {\n detach()\n ;(resolve as (value: unknown) => void)(v)\n },\n reject: (e: unknown) => {\n detach()\n reject(e)\n },\n timer,\n })\n\n if (signal) {\n abortHandler = () => {\n const p = this.pending.get(id)\n if (!p) return\n clearTimeout(p.timer)\n this.pending.delete(id)\n detach()\n reject(new McpError(`MCP request \"${method}\" aborted`, { code: 'MCP_ABORTED' }))\n }\n signal.addEventListener('abort', abortHandler, { once: true })\n }\n\n const message: JsonRpcRequest = {\n jsonrpc: JSON_RPC_VERSION,\n id,\n method,\n ...(params !== undefined && { params }),\n }\n\n try {\n this.transport!.send(message)\n } catch (err) {\n clearTimeout(timer)\n this.pending.delete(id)\n detach()\n reject(err)\n }\n })\n }\n\n private notify(method: string, params?: Record<string, unknown>): void {\n const message: JsonRpcNotification = {\n jsonrpc: JSON_RPC_VERSION,\n method,\n ...(params !== undefined && { params }),\n }\n\n try {\n this.transport!.send(message)\n } catch (err) {\n logger.debug('Failed to send MCP notification \"%s\": %s', method, (err as Error).message)\n }\n }\n\n private handleMessage(message: JsonRpcResponse | JsonRpcNotification): void {\n if (!('id' in message) || message.id === undefined) {\n this.handleNotification(message as JsonRpcNotification)\n return\n }\n\n const response = message as JsonRpcResponse\n const pending = this.pending.get(response.id!)\n if (!pending) {\n logger.debug('Received response for unknown request id: %s', response.id)\n return\n }\n\n clearTimeout(pending.timer)\n this.pending.delete(response.id!)\n\n if (response.error) {\n pending.reject(\n new McpError(`MCP error: ${response.error.message} (code: ${response.error.code})`, {\n code: 'MCP_SERVER_ERROR',\n }),\n )\n } else {\n pending.resolve(response.result)\n }\n }\n\n private handleNotification(notification: JsonRpcNotification): void {\n switch (notification.method) {\n case 'notifications/tools/list_changed':\n logger.info('MCP server \"%s\" notified tools list changed', this.serverName)\n void this.refreshTools().then(() => {\n this.toolsChangedCallback?.()\n })\n break\n\n case 'notifications/resources/list_changed':\n logger.info('MCP server \"%s\" notified resources list changed', this.serverName)\n void this.refreshResources().then(() => {\n this.resourcesChangedCallback?.()\n })\n break\n\n default:\n logger.debug('MCP notification: %s', notification.method)\n }\n\n // RFC-165 D9/H1:通用订阅——所有 notification(含上面已被专用 case 处理的)都转发,\n // 不再对未知 method 静默丢弃。让 backend/state 这类插件自定义 notification 能被\n // 上层(McpManager → PanelBackendController)消费。\n this.notificationCallback?.(notification.method, notification.params)\n }\n}\n\nexport function createMcpClient(\n serverName: string,\n config: McpServerConfig,\n options?: McpClientOptions,\n): McpClient {\n return new McpClient(serverName, config, options)\n}\n","import { z } from 'zod'\nimport { createLogger, probeImageDimensions, API_IMAGE_DIMENSION_LIMIT_PX, PROVIDER_IMAGE_INLINE_MAX_BYTES } from '@x-otto/shared'\nimport type { AgentTool, ToolResult } from '@x-otto/interchange'\nimport type { McpClient } from './mcp-client'\nimport type { McpToolDefinition, McpJsonSchemaProperty, McpContent } from './types'\n\nconst logger = createLogger('@x-otto/mcp:adapter')\n\nfunction enumToZod(values: unknown[]): z.ZodType | null {\n if (values.length === 0) {\n return null\n }\n if (values.every((v) => typeof v === 'string')) {\n return z.enum(values as [string, ...string[]])\n }\n const literals = values\n .filter((v): v is string | number | boolean => {\n const t = typeof v\n return t === 'string' || t === 'number' || t === 'boolean'\n })\n .map((v) => z.literal(v))\n if (literals.length === 0) {\n return null\n }\n if (literals.length === 1) {\n return literals[0]!\n }\n return z.union(literals as unknown as [z.ZodType, z.ZodType, ...z.ZodType[]])\n}\n\n/**\n * 将 MCP JSON Schema property → Zod Schema\n */\nexport function jsonSchemaPropertyToZod(prop: McpJsonSchemaProperty): z.ZodType {\n if (Array.isArray(prop.enum)) {\n const enumSchema = enumToZod(prop.enum)\n if (enumSchema) {\n return prop.description ? enumSchema.describe(prop.description) : enumSchema\n }\n }\n\n switch (prop.type) {\n case 'string':\n return prop.description ? z.string().describe(prop.description) : z.string()\n\n case 'number':\n case 'integer': {\n // LLM 常把快照提取的数字参数写成字符串(如 index:\"3856\")。\n // z.preprocess 在 safeParse 前强制转换,避免 expected number, received string。\n const base = prop.type === 'integer' ? z.number().int() : z.number()\n const coerced = z.preprocess((v) => (typeof v === 'string' ? Number(v) : v), base)\n return prop.description ? coerced.describe(prop.description) : coerced\n }\n\n case 'boolean':\n return prop.description ? z.boolean().describe(prop.description) : z.boolean()\n\n case 'array': {\n const itemSchema = prop.items ? jsonSchemaPropertyToZod(prop.items) : z.unknown()\n return prop.description ? z.array(itemSchema).describe(prop.description) : z.array(itemSchema)\n }\n\n case 'object': {\n if (prop.properties) {\n const shape: Record<string, z.ZodType> = {}\n const requiredSet = new Set(prop.required ?? [])\n\n for (const [key, value] of Object.entries(prop.properties)) {\n const fieldSchema = jsonSchemaPropertyToZod(value)\n shape[key] = requiredSet.has(key) ? fieldSchema : fieldSchema.optional()\n }\n\n return prop.description ? z.object(shape).describe(prop.description) : z.object(shape)\n }\n return prop.description\n ? z.record(z.string(), z.unknown()).describe(prop.description)\n : z.record(z.string(), z.unknown())\n }\n\n default:\n return z.unknown()\n }\n}\n\n/**\n * 将 MCP tool inputSchema → Zod schema\n */\nexport function mcpSchemaToZod(tool: McpToolDefinition): z.ZodType {\n const props = tool.inputSchema.properties\n if (!props || Object.keys(props).length === 0) {\n return z.object({})\n }\n\n const shape: Record<string, z.ZodType> = {}\n const requiredSet = new Set(tool.inputSchema.required ?? [])\n\n for (const [key, value] of Object.entries(props)) {\n const fieldSchema = jsonSchemaPropertyToZod(value)\n shape[key] = requiredSet.has(key) ? fieldSchema : fieldSchema.optional()\n }\n\n return z.object(shape)\n}\n\n/** MCP 工具产出图片体积硬上限——跨包单源 @x-otto/shared PROVIDER_IMAGE_INLINE_MAX_BYTES(C 组收敛)。\n * 语义:字节长度 > 上限才拒绝,恰好等于上限放行。 */\nconst MAX_MCP_IMAGE_BYTES = PROVIDER_IMAGE_INLINE_MAX_BYTES\n\nfunction base64ByteLength(b64: string): number {\n const padding = b64.endsWith('==') ? 2 : b64.endsWith('=') ? 1 : 0\n return Math.max(0, Math.floor((b64.length * 3) / 4) - padding)\n}\n\nfunction resourceToContent(resource: {\n uri: string\n text?: string\n blob?: string\n mimeType?: string\n}): ToolResult['content'][number] {\n if (resource.text !== undefined) {\n return { type: 'text', text: resource.text }\n }\n if (resource.blob !== undefined) {\n if (resource.mimeType?.startsWith('image/') && resource.mimeType !== 'image/svg+xml') {\n const bytes = base64ByteLength(resource.blob)\n if (bytes > MAX_MCP_IMAGE_BYTES) {\n return {\n type: 'text' as const,\n text: `[image omitted: ${bytes} bytes exceeds ${MAX_MCP_IMAGE_BYTES} byte limit — ask the tool to capture a smaller region/resolution]`,\n }\n }\n const dims = probeImageDimensions(resource.blob, resource.mimeType)\n // 2000px many-image 规则由 work-loop D6 cap(≤20 张/请求)保证不适用,\n // 此处只拦 API 必拒的 >8000px。原 2000px 会把 Retina 截图(2560px+)拒成占位文本。\n if (dims && (dims.width > API_IMAGE_DIMENSION_LIMIT_PX || dims.height > API_IMAGE_DIMENSION_LIMIT_PX)) {\n return {\n type: 'text' as const,\n text: `[image omitted: ${dims.width}x${dims.height}px exceeds Anthropic per-image dimension hard limit of ${API_IMAGE_DIMENSION_LIMIT_PX}px — ask the tool to capture a smaller region or lower resolution]`,\n }\n }\n return {\n type: 'image',\n mime: resource.mimeType,\n source: `data:${resource.mimeType};base64,${resource.blob}`,\n }\n }\n const mimeNote = resource.mimeType ? ` (${resource.mimeType})` : ''\n return {\n type: 'text',\n text: `[Resource: ${resource.uri}${mimeNote}, ${base64ByteLength(resource.blob)} bytes binary]`,\n }\n }\n return { type: 'text', text: `[Resource: ${resource.uri}]` }\n}\n\n/**\n * 将 MCP content → ToolResult content\n */\nexport function mcpContentToToolContent(content: McpContent[]): ToolResult['content'] {\n return content.map((item) => {\n switch (item.type) {\n case 'text':\n return { type: 'text' as const, text: item.text }\n case 'image':\n if (!item.data) {\n return { type: 'text' as const, text: '[image unavailable: no data]' }\n }\n {\n const bytes = base64ByteLength(item.data)\n if (bytes > MAX_MCP_IMAGE_BYTES) {\n return {\n type: 'text' as const,\n text: `[image omitted: ${bytes} bytes exceeds ${MAX_MCP_IMAGE_BYTES} byte limit — ask the tool to capture a smaller region/resolution]`,\n }\n }\n const dims = probeImageDimensions(item.data, item.mimeType ?? '')\n // 2000px many-image 规则由 work-loop D6 cap(≤20 张/请求)保证不适用,\n // 此处只拦 API 必拒的 >8000px。原 2000px 会把 Retina 截图(2560px+)拒成占位文本。\n if (dims && (dims.width > API_IMAGE_DIMENSION_LIMIT_PX || dims.height > API_IMAGE_DIMENSION_LIMIT_PX)) {\n return {\n type: 'text' as const,\n text: `[image omitted: ${dims.width}x${dims.height}px exceeds Anthropic per-image dimension hard limit of ${API_IMAGE_DIMENSION_LIMIT_PX}px — ask the tool to capture a smaller region or lower resolution]`,\n }\n }\n }\n return {\n type: 'image' as const,\n mime: item.mimeType,\n source: `data:${item.mimeType};base64,${item.data}`,\n }\n case 'resource':\n return resourceToContent(item.resource)\n default:\n return { type: 'text' as const, text: JSON.stringify(item) }\n }\n })\n}\n\n/**\n * 从 MCP 工具 inputSchema 推断\"文件系统路径\"参数名(endgame review:MCP 工具此前无 pathParams →\n * permission-guard 的 path 规则(file-guard 写保护 / 凭证读侧 deny / sandbox)全不命中 →\n * `mcp__fs__read_file{path:~/.ssh/id_rsa}` 零守卫读出密钥)。声明 pathParams 后,执行器经\n * resolveToolPaths 把这些 string 实参作为路径喂给所有路径守卫。\n *\n * 名字启发式刻意**宽**(宁可多声明几个无害的——非路径串过守卫不命中即放行):含\n * path/file/dir/folder 子串,或精确 source/destination/dest/src/cwd/location。刻意**排除** uri/url\n * (多为网络地址,按 fs 路径解析会误导 sandbox 写评估)。仅取 string 类型属性。\n */\nfunction looksLikePathParam(name: string): boolean {\n const n = name.toLowerCase().replace(/[_\\s-]/g, '')\n if (['source', 'destination', 'dest', 'src', 'cwd', 'location'].includes(n)) return true\n return n.includes('path') || n.includes('file') || n.includes('dir') || n.includes('folder')\n}\n\nexport function deriveMcpPathParams(toolDef: McpToolDefinition): string[] {\n const props = toolDef.inputSchema.properties\n if (!props) return []\n const out: string[] = []\n for (const [key, prop] of Object.entries(props)) {\n const t = prop.type\n if ((t === 'string' || t === undefined) && looksLikePathParam(key)) {\n out.push(key)\n }\n }\n return out\n}\n\n/**\n * 为单个 MCP tool 创建 AgentTool 适配器\n * 工具名格式: mcp__{serverName}__{toolName}\n */\nexport function createMcpToolAdapter(\n client: McpClient,\n toolDef: McpToolDefinition,\n serverName: string,\n): AgentTool {\n const qualifiedName = `mcp__${serverName}__${toolDef.name}`\n const description = toolDef.description\n ? `[MCP: ${serverName}] ${toolDef.description}`\n : `[MCP: ${serverName}] ${toolDef.name}`\n\n const parameters = mcpSchemaToZod(toolDef)\n const pathParams = deriveMcpPathParams(toolDef)\n // MCP `annotations.readOnlyHint` → AgentTool.readonly:work-loop 据 readonly 把只读调用分入并行桶\n // 此前适配器不传 readonly → 所有外部 MCP 工具恒串行。仅 true 才置位(保守)。\n const readonly = toolDef.annotations?.readOnlyHint === true\n\n return {\n name: qualifiedName,\n description,\n parameters,\n ...(pathParams.length > 0 ? { pathParams } : {}),\n ...(readonly ? { readonly: true } : {}),\n exposure: 'deferred',\n\n async execute({ params, signal }): Promise<ToolResult> {\n logger.debug('Calling MCP tool %s on server %s', toolDef.name, serverName)\n\n const result = await client.callTool(\n {\n name: toolDef.name,\n arguments: params as Record<string, unknown>,\n },\n signal,\n )\n\n const content = mcpContentToToolContent(result.content)\n\n return {\n content: content.length > 0 ? content : [{ type: 'text', text: '(empty result)' }],\n isError: result.isError ?? false,\n details: {\n mcpServer: serverName,\n mcpTool: toolDef.name,\n },\n }\n },\n }\n}\n\n/**\n * 为一个 MCP Client 的所有工具创建适配器\n */\nexport function createMcpToolAdapters(client: McpClient, serverName: string): AgentTool[] {\n const tools = client.getTools()\n return tools.map((toolDef) => createMcpToolAdapter(client, toolDef, serverName))\n}\n","export function parseWwwAuthenticate(header: string): string | undefined {\n const parts = header.split(',').map((p) => p.trim())\n const bearer = parts.find(\n (p) => p.toLowerCase().startsWith('bearer ') || p.toLowerCase() === 'bearer',\n )\n if (!bearer) return undefined\n\n const match = bearer.match(/metadata_url\\s*=\\s*\"([^\"]+)\"/i)\n return match?.[1]\n}\n\nexport interface ProtectedResourceMetadata {\n /** 资源标识符(即 MCP server 的 canonical URL)。 */\n resource: string\n /** 授权服务器 issuer URL 列表。 */\n authorization_servers?: string[]\n /** 支持的 Bearer 方法。 */\n bearer_methods_supported?: string[]\n /** 可读的资源文档 URL。 */\n resource_documentation?: string\n}\n\nexport async function fetchProtectedResourceMetadata(\n metadataUrl: string,\n fetchImpl: typeof globalThis.fetch = fetch,\n): Promise<ProtectedResourceMetadata> {\n const res = await fetchImpl(metadataUrl, {\n headers: { Accept: 'application/json' },\n })\n if (!res.ok) {\n throw new Error(\n `Failed to fetch protected resource metadata from ${metadataUrl}: ${res.status}`,\n )\n }\n return (await res.json()) as ProtectedResourceMetadata\n}\n\nexport interface AuthorizationServerMetadata {\n issuer: string\n authorization_endpoint: string\n token_endpoint: string\n /** DCR 注册端点(RFC 7591);若无则不支持动态注册。 */\n registration_endpoint?: string\n /** 注册端点认证方式;'none'=无需 client_secret,PKCE only。 */\n registration_endpoint_auth_methods_supported?: string[]\n /** 支持的 PKCE challenge 方法(期望 ['S256'])。 */\n code_challenge_methods_supported?: string[]\n /** 授权端点支持的 response_type。 */\n response_types_supported?: string[]\n /** token 端点支持的 grant_type。 */\n grant_types_supported?: string[]\n /** 授权端点支持的 scope。 */\n scopes_supported?: string[]\n /** 令牌端点认证方式。 */\n token_endpoint_auth_methods_supported?: string[]\n /** RFC 8414 未定义的其他字段(passthrough)。 */\n [key: string]: unknown\n}\n\n/**\n * 获取 authorization server metadata。\n * 先试 RFC 8414(GET issuer/.well-known/oauth-authorization-server),\n * 失败回退 OIDC discovery(GET issuer/.well-known/openid-configuration),\n * 只取与 OAuth 授权码流相关的字段。\n */\nexport async function fetchAuthorizationServerMetadata(\n issuer: string,\n fetchImpl: typeof globalThis.fetch = fetch,\n): Promise<AuthorizationServerMetadata> {\n const rfc8414Url = `${issuer.replace(/\\/$/, '')}/.well-known/oauth-authorization-server`\n const oidcUrl = `${issuer.replace(/\\/$/, '')}/.well-known/openid-configuration`\n\n const tryFetch = async (url: string): Promise<AuthorizationServerMetadata> => {\n const res = await fetchImpl(url, {\n headers: { Accept: 'application/json' },\n })\n if (!res.ok) {\n throw new Error(`Metadata fetch failed: ${url} → ${res.status}`)\n }\n return (await res.json()) as AuthorizationServerMetadata\n }\n\n try {\n return await tryFetch(rfc8414Url)\n } catch {\n return tryFetch(oidcUrl)\n }\n}\n\nexport interface OAuthDiscoveryResult {\n protectedResource: ProtectedResourceMetadata\n authorizationServer: AuthorizationServerMetadata\n /** 选定的授权服务器 issuer。 */\n issuer: string\n}\n\n/**\n * 完整 OAuth 发现:\n * 1. 构造 metadata URL(优先 401 WWW-Authenticate,否则 assume <server-url>/.well-known/oauth-protected-resource)\n * 2. 获取 protected resource metadata\n * 3. 选择 authorization server(取 metadata 列表第一个,或 issuer)\n * 4. 获取 authorization server metadata\n */\nexport async function discoverOAuth(\n serverUrl: string,\n options: {\n /** 401 响应的 WWW-Authenticate 头(可选,有一等公民优先)。 */\n wwwAuthenticate?: string\n /** 可注入 fetch(测试)。 */\n fetch?: typeof globalThis.fetch\n } = {},\n): Promise<OAuthDiscoveryResult> {\n const fetchImpl = options.fetch ?? fetch\n const metadataUrl =\n parseWwwAuthenticate(options.wwwAuthenticate ?? '') ??\n `${serverUrl.replace(/\\/$/, '')}/.well-known/oauth-protected-resource`\n\n const protectedResource = await fetchProtectedResourceMetadata(metadataUrl, fetchImpl)\n\n const issuer = protectedResource.authorization_servers?.[0] ?? new URL(metadataUrl).origin\n\n const authorizationServer = await fetchAuthorizationServerMetadata(issuer, fetchImpl)\n\n return { protectedResource, authorizationServer, issuer }\n}\n","export interface DCRRequest {\n /** 可读的客户端名称。 */\n client_name?: string\n /** 授权码回调 redirect URI 列表。 */\n redirect_uris: string[]\n /** 授权端点请求的 grant type。 */\n grant_types?: string[]\n /** token 端点认证方式;PKCE 用 'none'。 */\n token_endpoint_auth_method?: string\n}\n\nexport interface DCRResponse {\n client_id: string\n client_secret?: string\n /** 注册完成后的 access token(供后续管理此客户端)。 */\n registration_access_token?: string\n /** 管理此注册的 endpoint URL。 */\n registration_client_uri?: string\n /** RFC 7591 未定义字段(passthrough)。 */\n [key: string]: unknown\n}\n\nexport interface RegisterClientOptions {\n /** 可注入 fetch(测试)。 */\n fetch?: typeof globalThis.fetch\n /** 注册端点的初始 access token(少数 AS 要求 bearer 认证)。 */\n initialAccessToken?: string\n}\n\n/**\n * 向 authorization server 注册 OAuth 客户端(DCR)。\n * POST <registration_endpoint>,请求体 JSON,响应 JSON。\n */\nexport async function registerClient(\n registrationEndpoint: string,\n params: DCRRequest,\n options: RegisterClientOptions = {},\n): Promise<DCRResponse> {\n const fetchImpl = options.fetch ?? fetch\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n }\n if (options.initialAccessToken) {\n headers['Authorization'] = `Bearer ${options.initialAccessToken}`\n }\n\n const res = await fetchImpl(registrationEndpoint, {\n method: 'POST',\n headers,\n body: JSON.stringify({\n client_name: params.client_name ?? 'otto MCP Client',\n redirect_uris: params.redirect_uris,\n grant_types: params.grant_types ?? ['authorization_code', 'refresh_token'],\n token_endpoint_auth_method: params.token_endpoint_auth_method ?? 'none',\n }),\n })\n\n if (!res.ok) {\n const body = await res.text().catch(() => '')\n throw new Error(\n `DCR registration failed at ${registrationEndpoint}: ${res.status}${body ? ` — ${body}` : ''}`,\n )\n }\n\n return (await res.json()) as DCRResponse\n}\n","import { OAuthError } from '@x-otto/shared'\n\nexport interface AuthorizationCodeExchangeParams {\n /** 授权码(来自回调)。 */\n code: string\n /** 回调 redirect_uri(必须与 authorize 请求一致)。 */\n redirectUri: string\n /** PKCE code_verifier。 */\n codeVerifier: string\n /** RFC 8707 resource indicator(指向 MCP server)。 */\n resource?: string\n /** DCR 获得的 client_id。 */\n clientId: string\n}\n\nexport interface RefreshTokenParams {\n refreshToken: string\n /** RFC 8707 resource indicator。 */\n resource?: string\n clientId?: string\n}\n\nexport interface TokenResponse {\n access_token: string\n token_type: string\n expires_in?: number\n refresh_token?: string\n /** token 签发时的时间戳(ms),用于计算过期。 */\n issued_at?: number\n /** RFC 8707 审计。 */\n resource?: string\n}\n\nexport interface OAuthCredentials {\n accessToken: string\n refreshToken?: string\n expiresAt?: number\n resource?: string\n /** DCR client_id(持久化供后续 refresh)。 */\n clientId?: string\n}\n\nexport interface TokenExchangeOptions {\n /** 可注入 fetch(测试)。 */\n fetch?: typeof globalThis.fetch\n}\n\n/**\n * 用授权码交换 token(PKCE)。\n * POST <token_endpoint>(application/x-www-form-urlencoded)。\n */\nexport async function exchangeCode(\n tokenEndpoint: string,\n params: AuthorizationCodeExchangeParams,\n options: TokenExchangeOptions = {},\n): Promise<OAuthCredentials> {\n const fetchImpl = options.fetch ?? fetch\n const body = new URLSearchParams({\n grant_type: 'authorization_code',\n code: params.code,\n redirect_uri: params.redirectUri,\n code_verifier: params.codeVerifier,\n client_id: params.clientId,\n })\n if (params.resource) {\n body.set('resource', params.resource)\n }\n\n const res = await fetchImpl(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },\n body: body.toString(),\n })\n\n if (!res.ok) {\n const errBody = await res.text().catch(() => '')\n throw new OAuthError(`Token exchange failed: ${res.status}${errBody ? ` — ${errBody}` : ''}`, {\n code: 'OAUTH_TOKEN_EXCHANGE_FAILED',\n })\n }\n\n const data: TokenResponse = (await res.json()) as TokenResponse\n return toCredentials(data)\n}\n\n/**\n * 用 refresh_token 刷新 access token。\n * POST <token_endpoint>(application/x-www-form-urlencoded)。\n */\nexport async function refreshAccessToken(\n tokenEndpoint: string,\n params: RefreshTokenParams,\n options: TokenExchangeOptions = {},\n): Promise<OAuthCredentials> {\n const fetchImpl = options.fetch ?? fetch\n const body = new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: params.refreshToken,\n })\n if (params.clientId) {\n body.set('client_id', params.clientId)\n }\n if (params.resource) {\n body.set('resource', params.resource)\n }\n\n const res = await fetchImpl(tokenEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },\n body: body.toString(),\n })\n\n if (!res.ok) {\n const errBody = await res.text().catch(() => '')\n throw new OAuthError(`Token refresh failed: ${res.status}${errBody ? ` — ${errBody}` : ''}`, {\n code: 'OAUTH_REFRESH_FAILED',\n })\n }\n\n const data: TokenResponse = (await res.json()) as TokenResponse\n return toCredentials(data, params.refreshToken)\n}\n\nfunction toCredentials(data: TokenResponse, fallbackRefreshToken?: string): OAuthCredentials {\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? fallbackRefreshToken,\n expiresAt: data.expires_in\n ? (data.issued_at ?? Date.now()) + data.expires_in * 1000\n : undefined,\n resource: data.resource,\n }\n}\n\n/** 检查 access token 是否已过期(提前 60 秒视为过期,避免边界竞态)。 */\nexport function isTokenExpired(credentials: OAuthCredentials, skewSeconds = 60): boolean {\n if (credentials.expiresAt == null) return false\n return Date.now() + skewSeconds * 1000 >= credentials.expiresAt\n}\n","import { chmod, mkdir, readFile, rename, unlink, readdir, writeFile } from 'node:fs/promises'\nimport { createHash } from 'node:crypto'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nimport { isShadowModeEnabled } from '@x-otto/env'\n\nimport type { OAuthCredentials } from './token'\n\nexport interface TokenStore {\n /** 加载某 server 的凭据;不存在返回 undefined。 */\n load(serverName: string): Promise<OAuthCredentials | undefined>\n /** 保存某 server 的凭据。 */\n save(serverName: string, credentials: OAuthCredentials): Promise<void>\n /** 删除某 server 的凭据(logout)。 */\n delete(serverName: string): Promise<void>\n /** 列出所有有凭据的 server name。 */\n list(): Promise<string[]>\n}\n\nexport interface FileTokenStoreOptions {\n /** 存储目录,默认 `~/.otto/mcp/credentials`。 */\n baseDir?: string\n}\n\nexport function createFileTokenStore(options: FileTokenStoreOptions = {}): TokenStore {\n const baseDir = options.baseDir ?? join(homedir(), '.otto', 'mcp', 'credentials')\n\n const filePath = (serverName: string): string => join(baseDir, `${sanitize(serverName)}.json`)\n\n const ensureDir = async (): Promise<void> => {\n // mode 仅对新建目录生效(受 umask 影响),已存在目录需显式 chmod 收紧——\n // 与 @x-otto/ai auth-store 的凭据文件权限纪律对齐(文件 0o600 / 目录 0o700)。\n await mkdir(baseDir, { recursive: true, mode: 0o700 })\n try {\n await chmod(baseDir, 0o700)\n } catch {\n // 权限收紧失败不阻断主流程(如非 POSIX 文件系统),仅降级安全性。\n }\n }\n\n return {\n async load(serverName) {\n try {\n const raw = await readFile(filePath(serverName), 'utf-8')\n return JSON.parse(raw) as OAuthCredentials\n } catch (err) {\n const code = (err as NodeJS.ErrnoException)?.code\n if (code === 'ENOENT') return undefined\n throw err\n }\n },\n\n async save(serverName, credentials) {\n await ensureDir()\n // 临时文件 + chmod 0o600 + 原子 rename:凭据从落盘第一刻起就不可被其他本地用户读取,\n // 且读者永远不会观测到半写状态(对齐 @x-otto/ai auth-store saveSerial 的写盘纹理)。\n const target = filePath(serverName)\n const tmpPath = `${target}.tmp-${process.pid}`\n try {\n await writeFile(tmpPath, JSON.stringify({ ...credentials, serverName }, null, 2), {\n encoding: 'utf-8',\n mode: 0o600,\n })\n try {\n await chmod(tmpPath, 0o600)\n } catch {\n // 同 ensureDir:权限收紧失败仅降级安全性,不阻断保存。\n }\n await rename(tmpPath, target)\n } catch (err) {\n await unlink(tmpPath).catch(() => {})\n throw err\n }\n },\n\n async delete(serverName) {\n try {\n await unlink(filePath(serverName))\n } catch (err) {\n const code = (err as NodeJS.ErrnoException)?.code\n if (code === 'ENOENT') return\n throw err\n }\n },\n\n async list() {\n try {\n const entries = await readdir(baseDir)\n const names: string[] = []\n for (const f of entries) {\n if (!f.endsWith('.json')) continue\n try {\n const raw = await readFile(join(baseDir, f), 'utf-8')\n const original = readServerName(raw)\n names.push(original ?? f.slice(0, -5))\n } catch {\n names.push(f.slice(0, -5))\n }\n }\n return names\n } catch (err) {\n const code = (err as NodeJS.ErrnoException)?.code\n if (code === 'ENOENT') return []\n throw err\n }\n },\n }\n}\n\nexport function createMemoryTokenStore(): TokenStore {\n const store = new Map<string, OAuthCredentials>()\n return {\n async load(serverName) {\n return store.get(serverName)\n },\n async save(serverName, credentials) {\n store.set(serverName, credentials)\n },\n async delete(serverName) {\n store.delete(serverName)\n },\n async list() {\n return [...store.keys()]\n },\n }\n}\n\n/**\n * RFC-345 影子模式 overlay:读穿透到 base(继承磁盘既有凭据),写/删只进内存 overlay\n * (对齐 Chromium OverlayUserPrefStore:读父、写内存)。进程退出后磁盘凭据逐字节不变。\n *\n * 语义细节:\n * - load:overlay 命中优先(含\"本会话内 delete 过\"的墓碑,返回 undefined);否则回退 base;\n * - save:只写 overlay,绝不落盘;\n * - delete:只在 overlay 记墓碑,不删磁盘文件;\n * - list:base 列表并入 overlay 新增、扣除 overlay 墓碑。\n */\nexport function createOverlayTokenStore(base: TokenStore): TokenStore {\n const writes = new Map<string, OAuthCredentials>()\n const tombstones = new Set<string>()\n return {\n async load(serverName) {\n if (writes.has(serverName)) return writes.get(serverName)\n if (tombstones.has(serverName)) return undefined\n return base.load(serverName)\n },\n async save(serverName, credentials) {\n tombstones.delete(serverName)\n writes.set(serverName, credentials)\n },\n async delete(serverName) {\n writes.delete(serverName)\n tombstones.add(serverName)\n },\n async list() {\n const names = new Set<string>(await base.list())\n for (const t of tombstones) names.delete(t)\n for (const w of writes.keys()) names.add(w)\n return [...names]\n },\n }\n}\n\n/**\n * MCP OAuth 凭据 store 的 §D1 白名单构造入口:影子态返回 overlay(读磁盘、写内存),\n * 否则返回纯文件 store。业务层(mcp-manager / login)统一走此函数,不自行判断影子态。\n */\nexport function createDefaultTokenStore(options: FileTokenStoreOptions = {}): TokenStore {\n const fileStore = createFileTokenStore(options)\n return isShadowModeEnabled() ? createOverlayTokenStore(fileStore) : fileStore\n}\n\n/** 防止路径穿越 + 冲突:sanitize + 前 8 位 SHA256 哈希避免 sanitize 碰撞。 */\nfunction sanitize(name: string): string {\n const safe = name.replace(/[^a-zA-Z0-9._-]/g, '_')\n const hash = createHash('sha256').update(name).digest('hex').slice(0, 8)\n return `${safe}_${hash}`\n}\n\nfunction readServerName(raw: string): string | undefined {\n try {\n const obj = JSON.parse(raw) as Record<string, unknown>\n return typeof obj.serverName === 'string' ? obj.serverName : undefined\n } catch {\n return undefined\n }\n}\n","import { spawn } from 'node:child_process'\nimport { createPkcePair, generateState, startLoopbackServer } from '@x-otto/shared'\n\nimport { discoverOAuth } from './discovery'\nimport { registerClient } from './registration'\nimport { exchangeCode, refreshAccessToken, isTokenExpired } from './token'\nimport { createDefaultTokenStore } from './token-store'\n\nimport type { AuthorizationServerMetadata } from './discovery'\nimport type { OAuthCredentials } from './token'\nimport type { TokenStore } from './token-store'\n\nexport interface McpOAuthLoginOptions {\n /** MCP server URL。 */\n serverUrl: string\n /** MCP server 名称(用于 token store 键控)。 */\n serverName: string\n /** 401 响应的 WWW-Authenticate 头(可选,优先用于发现)。 */\n wwwAuthenticate?: string\n /** 手动指定 client_id(跳过 DCR)。 */\n clientId?: string\n /** 授权服务器元数据(跳过发现,适用于已知 AS)。 */\n authorizationServer?: AuthorizationServerMetadata\n /** token store(默认文件存储 ~/.otto/mcp/credentials/)。 */\n tokenStore?: TokenStore\n /** 打开浏览器的函数(默认 `open`命令)。注入用于测试。 */\n openBrowser?: (url: string) => Promise<void>\n /** 可注入 fetch(测试)。 */\n fetch?: typeof globalThis.fetch\n /** 取消信号。 */\n signal?: AbortSignal\n}\n\nexport interface McpOAuthLoginResult {\n credentials: OAuthCredentials\n /** 是否经 DCR 注册了新的 client_id。 */\n registered: boolean\n /** DCR 获得的 client_id(持久化供后续刷新)。 */\n clientId: string\n}\n\n/**\n * 执行完整 OAuth 2.1 交互式登录(PKCE + DCR)。\n *\n * 流程(修正终局 review):\n * 1. 发现 OAuth 端点(或使用传入的已知 metadata)\n * 2. 生成 PKCE pair + state,**先**启动 loopback server(获取真实 redirect_uri)\n * 3. DCR 注册客户端(使用真实 redirect_uri;或使用传入的 client_id)\n * 4. 构造 authorize URL,打开浏览器\n * 5. 等待浏览器回调送达授权码\n * 6. 用 PKCE code_verifier 交换 token\n * 7. 保存凭据到 token store(含 client_id)\n */\nexport async function login(options: McpOAuthLoginOptions): Promise<McpOAuthLoginResult> {\n const {\n serverUrl,\n serverName,\n wwwAuthenticate,\n clientId: explicitClientId,\n authorizationServer: knownAs,\n tokenStore = createDefaultTokenStore(),\n openBrowser = defaultOpenBrowser,\n fetch: fetchImpl = fetch,\n signal,\n } = options\n\n if (signal?.aborted) {\n throw new Error('Login cancelled')\n }\n\n const as =\n knownAs ??\n (await discoverOAuth(serverUrl, { wwwAuthenticate, fetch: fetchImpl })).authorizationServer\n\n if (!as.token_endpoint) {\n throw new Error('Authorization server has no token_endpoint')\n }\n\n const pkce = createPkcePair()\n const state = generateState()\n\n const loopback = await startLoopbackServer({\n ports: [1455, 1457, 1459],\n expectedState: state,\n signal,\n })\n const redirectUri = loopback.redirectUri\n\n try {\n let clientId = explicitClientId\n let registered = false\n if (!clientId && as.registration_endpoint) {\n const reg = await registerClient(\n as.registration_endpoint,\n { redirect_uris: [redirectUri] },\n { fetch: fetchImpl },\n )\n clientId = reg.client_id\n registered = true\n }\n if (!clientId) {\n throw new Error(\n 'No client_id available. Configure client_id in MCP server settings or ensure the ' +\n 'authorization server supports Dynamic Client Registration (registration_endpoint).',\n )\n }\n\n const authParams = new URLSearchParams({\n response_type: 'code',\n client_id: clientId,\n redirect_uri: redirectUri,\n code_challenge: pkce.challenge,\n code_challenge_method: pkce.method,\n state,\n })\n // RFC 8707 resource 指示器:authorize 与 token 交换必须一致(token 交换无条件携带\n // resource=serverUrl),否则受众限定不对称——统一无条件携带 canonical serverUrl。\n authParams.set('resource', serverUrl)\n const authUrl = `${as.authorization_endpoint}?${authParams.toString()}`\n\n await openBrowser(authUrl)\n\n const callback = await loopback.waitForCallback()\n\n const credentials = await exchangeCode(\n as.token_endpoint,\n {\n code: callback.code,\n redirectUri,\n codeVerifier: pkce.verifier,\n resource: serverUrl,\n clientId,\n },\n { fetch: fetchImpl },\n )\n\n await tokenStore.save(serverName, { ...credentials, clientId })\n\n return { credentials, registered, clientId }\n } finally {\n await loopback.close()\n }\n}\n\nexport interface TokenProvider {\n /** 获取 access token;过期自动刷新。不可恢复的失败抛 McpUnauthorized 错误。 */\n getAccessToken(): Promise<string | undefined>\n /** 强制重新登录(login → 保存 → 返回 token)。 */\n login(options: McpOAuthLoginOptions): Promise<string>\n /** 登出(删除存储的凭据)。 */\n logout(): Promise<void>\n}\n\nexport interface TokenProviderOptions {\n serverName: string\n serverUrl: string\n tokenStore?: TokenStore\n /** client_id(跳过 DCR)。 */\n clientId?: string\n /** 已知的 AS metadata(跳过发现,也用于跳过刷新时的 rediscovery)。 */\n authorizationServer?: AuthorizationServerMetadata\n fetch?: typeof globalThis.fetch\n}\n\n/**\n * 创建 MCP token provider:加载 → 检查刷新 → 提供 access token。\n * token 过期时自动 refresh;refresh 失败 → getAccessToken 返回 undefined\n * (提示用户运行 `otto mcp login`)。\n */\nexport function createTokenProvider(options: TokenProviderOptions): TokenProvider {\n const {\n serverName,\n serverUrl,\n tokenStore = createDefaultTokenStore(),\n clientId,\n authorizationServer,\n fetch: fetchImpl,\n } = options\n\n let credentials: OAuthCredentials | undefined\n let cachedAs = authorizationServer ?? undefined\n\n const provider: TokenProvider = {\n async getAccessToken() {\n if (!credentials) {\n credentials = await tokenStore.load(serverName)\n }\n if (!credentials) return undefined\n\n if (isTokenExpired(credentials) && credentials.refreshToken) {\n try {\n if (!cachedAs) {\n cachedAs = (await discoverOAuth(serverUrl, { fetch: fetchImpl })).authorizationServer\n }\n credentials = await refreshAccessToken(\n cachedAs.token_endpoint,\n {\n refreshToken: credentials.refreshToken,\n resource: serverUrl,\n clientId: credentials.clientId ?? clientId,\n },\n { fetch: fetchImpl },\n )\n if (clientId) credentials = { ...credentials, clientId }\n await tokenStore.save(serverName, credentials)\n } catch {\n return undefined\n }\n }\n\n return credentials.accessToken\n },\n\n async login(options) {\n const result = await login({\n ...options,\n tokenStore,\n clientId,\n authorizationServer: cachedAs,\n })\n credentials = result.credentials\n cachedAs ??= options.authorizationServer\n return credentials.accessToken\n },\n\n async logout() {\n credentials = undefined\n cachedAs = undefined\n await tokenStore.delete(serverName)\n },\n }\n\n return provider\n}\nasync function defaultOpenBrowser(url: string): Promise<void> {\n const cmd =\n process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open'\n const args = process.platform === 'win32' ? ['', url] : [url]\n\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { stdio: 'ignore', detached: true })\n child.on('error', (err) => {\n reject(new Error(`Failed to open browser: ${err.message}`))\n })\n child.on('close', (code) => {\n if (code === 0 || code === null) {\n resolve()\n } else {\n reject(new Error(`Browser command exited with code ${code}`))\n }\n })\n child.unref()\n })\n}\n","import { createLogger, McpError, TypedEventEmitter, traceStartupPhase } from '@x-otto/shared'\nimport { McpClient, createMcpClient } from './mcp-client'\nimport type { ProcessSpawnFactory } from './transport'\nimport { createMcpToolAdapters } from './mcp-tool-adapter'\nimport { createTokenProvider, createDefaultTokenStore } from './oauth'\nimport type {\n McpServerConfig,\n McpServerInfo,\n McpClientStatus,\n McpResource,\n McpPrompt,\n McpEvents,\n} from './types'\nimport type { AgentTool } from '@x-otto/interchange'\n\nconst logger = createLogger('@x-otto/mcp:manager')\n\nexport interface McpManagerOptions {\n /** 每个 MCP 请求超时 */\n requestTimeoutMs?: number\n /** 连接失败时是否静默跳过(默认 true) */\n skipOnError?: boolean\n /** 自动重连(默认 true) */\n autoReconnect?: boolean\n /** 最大重连次数(默认 3) */\n maxReconnectAttempts?: number\n /**\n * RFC-230:重连退避基准延迟(ms,指数退避,默认 500)。**只做全局配置**,不支持\n * per-server 覆盖——`McpServerConfig` 没有对应字段(与 `requestTimeoutMs`/\n * `maxReconnectAttempts` 不同,那两者有 per-server 覆盖能力)。若未来有真实需求,\n * 可另立 TODO 补 `McpServerConfig.reconnectDelayMs` + settings schema 字段。\n */\n reconnectDelayMs?: number\n /** 进程 spawn 工厂(RFC-085 M2):注入后 stdio MCP 子进程纳入 ProcessRuntime 统一管理。 */\n processSpawnFactory?: ProcessSpawnFactory\n}\n\nexport class McpManager extends TypedEventEmitter<McpEvents> {\n private clients = new Map<string, McpClient>()\n private options: McpManagerOptions\n private disabledServers: Set<string>\n constructor(options: McpManagerOptions = {}, disabledServers: string[] = []) {\n super()\n this.options = options\n this.disabledServers = new Set(disabledServers)\n }\n\n /**\n * 返回值(RFC-150 D1 追加,向后兼容——原调用点若不消费返回值零影响):本轮 connectAll 的\n * 汇总统计,供宿主层(McpController.start())非阻塞路径完成后驱动 toast/日志通知,\n * 不需要重新遍历/监听事件才能知道\"这轮到底连上几个\"。\n */\n async connectAll(\n servers: Record<string, McpServerConfig>,\n ): Promise<{ connected: number; failed: number }> {\n const entries = Object.entries(servers)\n if (entries.length === 0) {\n return { connected: 0, failed: 0 }\n }\n\n logger.info('Connecting to %d MCP servers...', entries.length)\n\n // RFC-150 D7/M0:per-server 打点——connectAll 内部并行,但整体常被上层 await(RFC-150 D1\n // 拟改为非阻塞);per-server 耗时是定位\"哪个 server 拖累整体\"的直接证据(§1.1 时间线)。\n const results = await Promise.allSettled(\n entries.map(([name, config]) =>\n traceStartupPhase(`mcp:${name} connect`, () => this.connect(name, config)),\n ),\n )\n\n let connected = 0\n let failed = 0\n for (const result of results) {\n if (result.status === 'fulfilled') {\n connected++\n } else {\n failed++\n }\n }\n\n logger.info('MCP servers: %d connected, %d failed', connected, failed)\n return { connected, failed }\n }\n\n async connect(name: string, config: McpServerConfig): Promise<void> {\n if (this.disabledServers.has(name)) {\n logger.info('MCP server \"%s\" is disabled, skipping', name)\n return\n }\n\n if (this.clients.has(name)) {\n await this.disconnect(name)\n }\n\n let getAuthToken: (() => Promise<string | undefined>) | undefined\n if (config.url && config.type !== 'stdio') {\n const tokenStore = createDefaultTokenStore()\n const existing = await tokenStore.load(name)\n if (existing) {\n const provider = createTokenProvider({\n serverName: name,\n serverUrl: config.url,\n tokenStore,\n clientId: existing.clientId,\n })\n getAuthToken = () => provider.getAccessToken()\n }\n }\n\n const client = createMcpClient(name, config, {\n requestTimeoutMs: config.requestTimeoutMs ?? this.options.requestTimeoutMs,\n autoReconnect: config.autoReconnect ?? this.options.autoReconnect,\n maxReconnectAttempts: config.maxReconnectAttempts ?? this.options.maxReconnectAttempts,\n // RFC-230:只读全局 this.options.reconnectDelayMs——不读 config.reconnectDelayMs,\n // 该 per-server 字段不存在也不新增(见 McpManagerOptions.reconnectDelayMs 注释)。\n reconnectDelayMs: this.options.reconnectDelayMs,\n getAuthToken,\n processSpawnFactory: this.options.processSpawnFactory,\n })\n\n client.onToolsChanged(() => {\n logger.info('MCP server \"%s\" tools changed', name)\n this.emit('tools.changed', { serverName: name, tools: client.getTools() })\n })\n\n client.onResourcesChanged(() => {\n logger.info('MCP server \"%s\" resources changed', name)\n this.emit('resources.changed', { serverName: name })\n })\n\n client.onDisconnected((info) => {\n logger.info('MCP server \"%s\" disconnected (%s): %s', name, info.kind, info.reason ?? 'unknown')\n this.emit('server.disconnected', { name, reason: info.reason, kind: info.kind })\n })\n\n // RFC-165 D9/H1:转发通用 notification 为 server.notification 事件,供\n // PanelBackendController(M2)等上层消费方订阅(如 backend/state 推送)。\n client.onNotification((method, params) => {\n this.emit('server.notification', { name, method, params })\n })\n\n try {\n await client.connect()\n this.clients.set(name, client)\n this.emit('server.connected', { name, tools: client.getTools().length })\n } catch (err) {\n const message = (err as Error).message\n // 透传错误码,供宿主据码渲染 actionable 指引(如 MCP_UNAUTHORIZED → 'otto mcp login')。\n const code = err instanceof McpError ? err.code : undefined\n this.emit('server.error', { name, error: message, code })\n\n if (this.options.skipOnError !== false) {\n logger.warn('MCP server \"%s\" connection failed, skipping: %s', name, message)\n } else {\n throw err\n }\n }\n }\n\n async disconnect(name: string): Promise<void> {\n const client = this.clients.get(name)\n if (client) {\n await client.disconnect()\n this.clients.delete(name)\n this.emit('server.disconnected', { name, reason: 'manual disconnect', kind: 'manual' })\n }\n }\n\n async disconnectAll(): Promise<void> {\n const names = [...this.clients.keys()]\n await Promise.allSettled(names.map((name) => this.disconnect(name)))\n }\n\n getAllTools(): AgentTool[] {\n const tools: AgentTool[] = []\n for (const [name, client] of this.clients) {\n if (client.getStatus() === 'ready') {\n tools.push(...createMcpToolAdapters(client, name))\n }\n }\n return tools\n }\n\n getAllResources(): Array<McpResource & { serverName: string }> {\n const resources: Array<McpResource & { serverName: string }> = []\n for (const [name, client] of this.clients) {\n if (client.getStatus() === 'ready') {\n for (const resource of client.getResources()) {\n resources.push({ ...resource, serverName: name })\n }\n }\n }\n return resources\n }\n\n getAllPrompts(): Array<McpPrompt & { serverName: string }> {\n const prompts: Array<McpPrompt & { serverName: string }> = []\n for (const [name, client] of this.clients) {\n if (client.getStatus() === 'ready') {\n for (const prompt of client.getPrompts()) {\n prompts.push({ ...prompt, serverName: name })\n }\n }\n }\n return prompts\n }\n\n getClient(name: string): McpClient | undefined {\n return this.clients.get(name)\n }\n\n getServerInfos(): McpServerInfo[] {\n const infos: McpServerInfo[] = []\n\n for (const [name, client] of this.clients) {\n infos.push({\n name,\n config: client.getConfig(),\n status: client.getStatus(),\n tools: client.getTools(),\n resources: client.getResources(),\n prompts: client.getPrompts(),\n capabilities: client.getCapabilities() ?? undefined,\n error: client.getStatus() === 'error' ? 'Connection failed' : undefined,\n })\n }\n\n for (const name of this.disabledServers) {\n if (!this.clients.has(name)) {\n infos.push({\n name,\n status: 'disconnected' as McpClientStatus,\n tools: [],\n })\n }\n }\n\n return infos\n }\n\n get connectedCount(): number {\n let count = 0\n for (const client of this.clients.values()) {\n if (client.getStatus() === 'ready') {\n count++\n }\n }\n return count\n }\n\n get toolCount(): number {\n let count = 0\n for (const client of this.clients.values()) {\n if (client.getStatus() === 'ready') {\n count += client.getTools().length\n }\n }\n return count\n }\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@x-otto/mcp",
3
+ "version": "0.0.1-alpha.0",
4
+ "files": [
5
+ "dist"
6
+ ],
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "dependencies": {
17
+ "@x-otto/env": "0.1.0-alpha.1",
18
+ "@x-otto/interchange": "0.1.0-alpha.1",
19
+ "@x-otto/shared": "0.1.0-alpha.1",
20
+ "zod": "4.3.6"
21
+ },
22
+ "devDependencies": {
23
+ "@x-otto/runtime": "0.0.1-alpha.0"
24
+ },
25
+ "private": false,
26
+ "publishConfig": {
27
+ "access": "public",
28
+ "registry": "https://registry.npmjs.org",
29
+ "tag": "alpha"
30
+ },
31
+ "scripts": {
32
+ "build": "tsdown",
33
+ "dev": "tsdown --watch",
34
+ "typecheck": "tsc --noEmit"
35
+ }
36
+ }