@x-otto/mcp 0.0.1-alpha.2 → 0.0.1-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -16
- package/dist/index.d.ts +65 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -12,8 +12,7 @@ Implements the MCP (Model Context Protocol) client and server, supporting cross-
|
|
|
12
12
|
| McpManager | Multi-server lifecycle management + tool/resource aggregation |
|
|
13
13
|
| StdioTransport | Child-process stdin/stdout transport |
|
|
14
14
|
| SseTransport | HTTP SSE transport + reconnection |
|
|
15
|
-
|
|
|
16
|
-
| OttoMcpServer | Exposes local AgentTools as an MCP server |
|
|
15
|
+
| McpToolAdapter | MCP tool → AgentTool adaptation (JSON Schema → Zod) |
|
|
17
16
|
|
|
18
17
|
## Installation
|
|
19
18
|
|
|
@@ -24,18 +23,17 @@ pnpm add @x-otto/mcp
|
|
|
24
23
|
## Quick Start
|
|
25
24
|
|
|
26
25
|
```ts
|
|
27
|
-
import { McpManager
|
|
26
|
+
import { McpManager } from '@x-otto/mcp'
|
|
28
27
|
|
|
29
28
|
const manager = new McpManager()
|
|
30
|
-
manager.
|
|
31
|
-
|
|
32
|
-
transport: new StdioTransport({ command: 'node', args: ['server.js'] }),
|
|
29
|
+
await manager.connectAll({
|
|
30
|
+
'my-server': { type: 'stdio', command: 'node', args: ['server.js'] },
|
|
33
31
|
})
|
|
34
32
|
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const result = await
|
|
38
|
-
const prompt = await
|
|
33
|
+
const tools = manager.getAllTools() // MCP tool → AgentTool adaptation
|
|
34
|
+
const client = manager.getClient('my-server')
|
|
35
|
+
const result = await client?.callTool({ name: 'tool-name', arguments: { key: 'value' } })
|
|
36
|
+
const prompt = await client?.getPrompt('greeting', { name: 'World' })
|
|
39
37
|
```
|
|
40
38
|
|
|
41
39
|
## Directory Overview
|
|
@@ -45,11 +43,10 @@ src/
|
|
|
45
43
|
types.ts # core types
|
|
46
44
|
mcp-client.ts # MCP client
|
|
47
45
|
mcp-manager.ts # multi-server management
|
|
48
|
-
transports
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
mcp-server.ts # MCP server
|
|
46
|
+
transport.ts # Stdio + streamable HTTP transports
|
|
47
|
+
sse.ts # streamable HTTP SSE framing/parsing
|
|
48
|
+
mcp-tool-adapter.ts # MCP → AgentTool adaptation
|
|
49
|
+
oauth/ # OAuth 2.1 (discovery / DCR / token / store / login)
|
|
53
50
|
index.ts
|
|
54
51
|
```
|
|
55
52
|
|
|
@@ -63,4 +60,4 @@ pnpm --filter @x-otto/mcp clean
|
|
|
63
60
|
|
|
64
61
|
## Related Packages
|
|
65
62
|
|
|
66
|
-
`@x-otto/
|
|
63
|
+
`@x-otto/credentials`, `@x-otto/env`, `@x-otto/interchange`, `@x-otto/shared`
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TypedEventEmitter } from "@x-otto/shared";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
3
|
+
import { RefreshStrategy } from "@x-otto/credentials";
|
|
4
|
+
import { AgentToolDefinition, ToolResult } from "@x-otto/interchange";
|
|
4
5
|
|
|
5
6
|
//#region src/types.d.ts
|
|
6
7
|
/** JSON-RPC 2.0 协议版本字面量——单一真源,避免散落各处的裸字符串漂移。 */
|
|
@@ -296,7 +297,19 @@ declare class StdioTransport implements McpTransport {
|
|
|
296
297
|
onMessage(handler: (message: JsonRpcResponse | JsonRpcNotification) => void): void;
|
|
297
298
|
onClose(handler: (reason?: string) => void): void;
|
|
298
299
|
close(): Promise<void>;
|
|
299
|
-
|
|
300
|
+
/**
|
|
301
|
+
* RFC review D6a:增量分帧——仅把「残留 buffer + chunk 中最后一个换行之前」的部分
|
|
302
|
+
* 交给 decodeNewlineFrames(一次性、线性),换行之后的未完成尾部作为新残留持有。
|
|
303
|
+
*
|
|
304
|
+
* 语义与旧实现全等:
|
|
305
|
+
* - 帧边界 = 换行(官方 MCP stdio 分帧);
|
|
306
|
+
* - 无换行且残留超 MAX_MESSAGE_BYTES → 复位(防 DoS,同 decodeNewlineFrames 语义);
|
|
307
|
+
* - 解析失败只告警不中断(同一消息级容错)。
|
|
308
|
+
*
|
|
309
|
+
* 复杂度:旧实现每 chunk 复制「全部残留 + chunk」(残留可含已收帧前缀 → O(n²));
|
|
310
|
+
* 新实现每 chunk 只复制「残留 + 完整帧段」,残留段通常为空或单帧尾部 → O(总字节)。
|
|
311
|
+
*/
|
|
312
|
+
private pushChunk;
|
|
300
313
|
}
|
|
301
314
|
interface SseTransportOptions {
|
|
302
315
|
/** 单次请求超时(ms),与传输级 abort 组合。缺省 30s。 */
|
|
@@ -523,7 +536,7 @@ declare class McpManager extends TypedEventEmitter<McpEvents> {
|
|
|
523
536
|
*/
|
|
524
537
|
disconnect(name: string): Promise<void>;
|
|
525
538
|
disconnectAll(): Promise<void>;
|
|
526
|
-
getAllTools():
|
|
539
|
+
getAllTools(): AgentToolDefinition[];
|
|
527
540
|
getAllResources(): Array<McpResource & {
|
|
528
541
|
serverName: string;
|
|
529
542
|
}>;
|
|
@@ -553,11 +566,11 @@ declare function mcpContentToToolContent(content: McpContent[]): ToolResult['con
|
|
|
553
566
|
* 为单个 MCP tool 创建 AgentTool 适配器
|
|
554
567
|
* 工具名格式: mcp__{serverName}__{toolName}
|
|
555
568
|
*/
|
|
556
|
-
declare function createMcpToolAdapter(client: McpClient, toolDef: McpToolDefinition, serverName: string):
|
|
569
|
+
declare function createMcpToolAdapter(client: McpClient, toolDef: McpToolDefinition, serverName: string): AgentToolDefinition;
|
|
557
570
|
/**
|
|
558
571
|
* 为一个 MCP Client 的所有工具创建适配器
|
|
559
572
|
*/
|
|
560
|
-
declare function createMcpToolAdapters(client: McpClient, serverName: string):
|
|
573
|
+
declare function createMcpToolAdapters(client: McpClient, serverName: string): AgentToolDefinition[];
|
|
561
574
|
//#endregion
|
|
562
575
|
//#region src/oauth/discovery.d.ts
|
|
563
576
|
declare function parseWwwAuthenticate(header: string): string | undefined;
|
|
@@ -725,6 +738,13 @@ interface TokenStore {
|
|
|
725
738
|
* undefined(后者影子态禁止写锁文件)。业务层据此决定 refresh 是否走跨进程锁。
|
|
726
739
|
*/
|
|
727
740
|
readonly refreshLockDir?: string;
|
|
741
|
+
/**
|
|
742
|
+
* 是否为影子(overlay)态 store。影子态 store 读穿透磁盘、写只进内存——
|
|
743
|
+
* 过期 token 不可刷新(轮换型 refresh_token 会作废磁盘旧 token,致全部普通会话
|
|
744
|
+
* 掉登录)。业务层据此在 refresh 入口拦截(RFC-345 §D2b,对齐 ai auth-store 的
|
|
745
|
+
* overlay 拒刷语义)。内存 store 非 shadow(无磁盘可破坏,刷新安全)。
|
|
746
|
+
*/
|
|
747
|
+
readonly shadow?: boolean;
|
|
728
748
|
}
|
|
729
749
|
interface FileTokenStoreOptions {
|
|
730
750
|
/** 存储目录,默认 `~/.otto/mcp/credentials`。 */
|
|
@@ -736,7 +756,11 @@ declare function createMemoryTokenStore(): TokenStore;
|
|
|
736
756
|
* RFC-345 影子模式 overlay(RFC-374 M1:实现委托 @x-otto/credentials 通用 overlay):
|
|
737
757
|
* 读穿透到 base(继承磁盘既有凭据),写/删只进内存 overlay。进程退出后磁盘凭据
|
|
738
758
|
* 逐字节不变。语义细节(load 墓碑优先/save 只进内存/delete 记墓碑/list 合并)
|
|
739
|
-
* 由叶包 createOverlayStore 保证,本函数只做 TokenStore
|
|
759
|
+
* 由叶包 createOverlayStore 保证,本函数只做 TokenStore 形状适配 + shadow 标记。
|
|
760
|
+
*
|
|
761
|
+
* shadow: true + refreshLockDir: undefined —— 显式声明影子态语义(此前靠 overlay
|
|
762
|
+
* 丢失 base 的 refreshLockDir 属性实现,是隐性开关,未来给 overlay 透传 refreshLockDir
|
|
763
|
+
* 会静默改变影子态锁语义)。
|
|
740
764
|
*/
|
|
741
765
|
declare function createOverlayTokenStore(base: TokenStore): TokenStore;
|
|
742
766
|
/**
|
|
@@ -763,8 +787,11 @@ interface RefreshWithLockPorts {
|
|
|
763
787
|
* 跨进程互斥刷新:同一时刻同一 server 全机器只有一个进程真正发起 `refreshAccessToken`
|
|
764
788
|
* 网络请求,其余进程等待后直接读盘复用结果。语义(拿锁重读/等待读盘/抛错/finally 释放)
|
|
765
789
|
* 由叶包 refreshUnderCrossProcessLock 保证,本函数做 MCP 域参数装配。
|
|
790
|
+
*
|
|
791
|
+
* P1 扩展:可选 `strategy` 透传到叶包骨架,启用永久失败缓存 / 磁盘恢复 / 可观测性。
|
|
792
|
+
* 缺省(未提供 strategy)时与原有行为完全一致。
|
|
766
793
|
*/
|
|
767
|
-
declare function refreshWithCrossProcessLock(credentialsDir: string, serverName: string, tokenEndpoint: string, params: RefreshTokenParams, options: TokenExchangeOptions | undefined, ports: RefreshWithLockPorts): Promise<OAuthCredentials>;
|
|
794
|
+
declare function refreshWithCrossProcessLock(credentialsDir: string, serverName: string, tokenEndpoint: string, params: RefreshTokenParams, options: TokenExchangeOptions | undefined, ports: RefreshWithLockPorts, strategy?: RefreshStrategy<OAuthCredentials>): Promise<OAuthCredentials>;
|
|
768
795
|
//#endregion
|
|
769
796
|
//#region src/oauth/login.d.ts
|
|
770
797
|
interface McpOAuthLoginOptions {
|
|
@@ -807,6 +834,25 @@ interface McpOAuthLoginResult {
|
|
|
807
834
|
* 7. 保存凭据到 token store(含 client_id)
|
|
808
835
|
*/
|
|
809
836
|
declare function login(options: McpOAuthLoginOptions): Promise<McpOAuthLoginResult>;
|
|
837
|
+
/** 凭据状态描述(对齐 ai AuthStore.getAuthSource/hasCredential,S1 补齐)。 */
|
|
838
|
+
interface CredentialStatus {
|
|
839
|
+
/** 是否有凭据(store 中存在)。 */
|
|
840
|
+
configured: boolean;
|
|
841
|
+
/** 凭据来源类型:'oauth' = 有 access/refresh token。缺省 = 无凭据。 */
|
|
842
|
+
source?: 'oauth';
|
|
843
|
+
/** 凭据是否即将过期(isTokenExpired 判定)。 */
|
|
844
|
+
expiringSoon?: boolean;
|
|
845
|
+
/** 是否影子态(影子态不可刷新)。 */
|
|
846
|
+
shadow?: boolean;
|
|
847
|
+
}
|
|
848
|
+
/** 凭据变更事件(对齐 ai AuthStore 'credential.changed')。 */
|
|
849
|
+
interface TokenProviderEvents {
|
|
850
|
+
/** 凭据发生变化(login/logout/refresh 成功,或外部进程写入被感知)。 */
|
|
851
|
+
'credential.changed': (payload: {
|
|
852
|
+
serverName: string;
|
|
853
|
+
source: 'self' | 'external' | 'refresh';
|
|
854
|
+
}) => void;
|
|
855
|
+
}
|
|
810
856
|
interface TokenProvider {
|
|
811
857
|
/** 获取 access token;过期自动刷新。不可恢复的失败抛 McpUnauthorized 错误。 */
|
|
812
858
|
getAccessToken(): Promise<string | undefined>;
|
|
@@ -814,6 +860,10 @@ interface TokenProvider {
|
|
|
814
860
|
login(options: McpOAuthLoginOptions): Promise<string>;
|
|
815
861
|
/** 登出(删除存储的凭据)。 */
|
|
816
862
|
logout(): Promise<void>;
|
|
863
|
+
/** S1 补齐:只读探查凭据状态(不触发刷新/网络)。对齐 ai getAuthSource/hasCredential。 */
|
|
864
|
+
describe(): Promise<CredentialStatus>;
|
|
865
|
+
/** S1 补齐:凭据变更事件(对齐 ai 'credential.changed')。 */
|
|
866
|
+
on(event: 'credential.changed', listener: TokenProviderEvents['credential.changed']): this;
|
|
817
867
|
}
|
|
818
868
|
interface TokenProviderOptions {
|
|
819
869
|
serverName: string;
|
|
@@ -832,5 +882,12 @@ interface TokenProviderOptions {
|
|
|
832
882
|
*/
|
|
833
883
|
declare function createTokenProvider(options: TokenProviderOptions): TokenProvider;
|
|
834
884
|
//#endregion
|
|
835
|
-
|
|
885
|
+
//#region src/dot-mcp-json.d.ts
|
|
886
|
+
/**
|
|
887
|
+
* 解析给定目录的 `.mcp.json`(`mcp_servers` 优先,回退 `mcpServers`)。
|
|
888
|
+
* 不做信任门判断;缺失/损坏 → {}。信任门由消费方叠加。
|
|
889
|
+
*/
|
|
890
|
+
declare function parseDotMcpJson(dir: string): Record<string, McpServerConfig>;
|
|
891
|
+
//#endregion
|
|
892
|
+
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 RefreshWithLockPorts, type RegisterClientOptions, type SseDataParser, SseTransport, type SseTransportOptions, StdioTransport, type TokenExchangeOptions, type TokenProvider, type TokenProviderOptions, type TokenResponse, type TokenStore, acquireRefreshLock, createDefaultTokenStore, createFileTokenStore, createMcpClient, createMcpToolAdapter, createMcpToolAdapters, createMemoryTokenStore, createOverlayTokenStore, createSseDataParser, createTokenProvider, discoverOAuth, exchangeCode, fetchAuthorizationServerMetadata, fetchProtectedResourceMetadata, isTokenExpired, jsonSchemaPropertyToZod, login, mcpContentToToolContent, mcpSchemaToZod, parseDotMcpJson, parseWwwAuthenticate, refreshAccessToken, refreshWithCrossProcessLock, registerClient, releaseRefreshLock, validateMcpUrl, waitForRefreshLockRelease };
|
|
836
893
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +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/refresh-lock.ts","../src/oauth/login.ts"],"mappings":"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"}
|
|
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/refresh-lock.ts","../src/oauth/login.ts","../src/dot-mcp-json.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +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{REFRESH_LOCK_TIMEOUT_MS as m,createOverlayStore as h,createRefreshLock as g,deleteFileIfExists as _,ensurePrivateDir as ee,isExpiredAt as te,readJsonFile as ne,refreshUnderCrossProcessLock as re,writeJsonAtomic as ie}from"@x-otto/credentials";import{createHash as ae}from"node:crypto";import{readFile as oe,readdir as
|
|
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{REFRESH_LOCK_TIMEOUT_MS as m,createOverlayStore as h,createRefreshLock as g,deleteFileIfExists as _,ensurePrivateDir as ee,isExpiredAt as te,readJsonFile as ne,refreshUnderCrossProcessLock as re,writeJsonAtomic as ie}from"@x-otto/credentials";import{createHash as ae}from"node:crypto";import{readFile as oe,readdir as se}from"node:fs/promises";import{homedir as ce}from"node:os";import{join as v,resolve as le}from"node:path";import{isShadowModeEnabled as ue}from"@x-otto/env";import{existsSync as de,readFileSync as fe}from"node:fs";function y(e){let t=``,n=[],r=()=>{if(n.length===0)return;let t=n.join(`
|
|
2
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
|
|
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}=le(this.buffer);this.buffer=t;for(let t of e)try{let e=JSON.parse(t);this.messageHandler?.(e)}catch(e){x.warn(`Failed to parse MCP message: %s`,e.message)}}},w=class{messageHandler=null;closeHandler=null;abortController=null;sessionId=null;closing=!1;baseUrl;fetchImpl;requestTimeoutMs;headers;getAuthToken;constructor(e,t={}){this.baseUrl=S(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(x.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})}x.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){x.warn(`MCP SSE POST 401: authentication required for %s`,this.baseUrl),this.failRequest(e,-32001,`MCP server requires authentication.`);return}x.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`&&(x.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=b(e=>{let t=e.trim();if(!(!t||t===`[DONE]`))try{let e=JSON.parse(t);this.messageHandler?.(e)}catch{x.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`&&(x.warn(`MCP SSE stream error: %s`,e.message),t&&!this.closing&&this.closeHandler?.(`SSE stream error: ${e.message}`))}finally{n.releaseLock()}}};const T=s(`@x-otto/mcp:client`);var E=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);T.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`),T.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);T.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`,T.info(`MCP server "%s" reconnected`,this.serverName)}catch(e){T.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??[],T.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??[],T.debug(`MCP server "%s" provides %d resources`,this.serverName,this.resources.length)}catch(e){T.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??[],T.debug(`MCP server "%s" provides %d prompts`,this.serverName,this.prompts.length)}catch(e){T.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){T.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 w(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 C(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){T.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){T.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`:T.info(`MCP server "%s" notified tools list changed`,this.serverName),this.refreshTools().then(()=>{this.toolsChangedCallback?.()});break;case`notifications/resources/list_changed`:T.info(`MCP server "%s" notified resources list changed`,this.serverName),this.refreshResources().then(()=>{this.resourcesChangedCallback?.()});break;default:T.debug(`MCP notification: %s`,e.method)}this.notificationCallback?.(e.method,e.params)}};function D(e,t,n){return new E(e,t,n)}const ue=s(`@x-otto/mcp:adapter`);function de(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 O(e){if(Array.isArray(e.enum)){let t=de(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?O(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=O(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 k(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=O(i);n[e]=r.has(e)?t:t.optional()}return p.object(n)}const A=i;function j(e){let t=e.endsWith(`==`)?2:e.endsWith(`=`)?1:0;return Math.max(0,Math.floor(e.length*3/4)-t)}function fe(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=j(e.blob);if(n>A)return{type:`text`,text:`[image omitted: ${n} bytes exceeds ${A} 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}, ${j(e.blob)} bytes binary]`}}return{type:`text`,text:`[Resource: ${e.uri}]`}}function M(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=j(e.data);if(n>A)return{type:`text`,text:`[image omitted: ${n} bytes exceeds ${A} 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 fe(e.resource);default:return{type:`text`,text:JSON.stringify(e)}}})}function pe(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 me(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)&&pe(e)&&n.push(e)}return n}function N(e,t,n){let r=`mcp__${n}__${t.name}`,i=t.description?`[MCP: ${n}] ${t.description}`:`[MCP: ${n}] ${t.name}`,a=k(t),o=me(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}){ue.debug(`Calling MCP tool %s on server %s`,t.name,n);let a=await e.callTool({name:t.name,arguments:r},i),o=M(a.content);return{content:o.length>0?o:[{type:`text`,text:`(empty result)`}],isError:a.isError??!1,details:{mcpServer:n,mcpTool:t.name}}}}}function P(e,t){return e.getTools().map(n=>N(e,n,t))}function F(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 I(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 L(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 R(e,t={}){let n=t.fetch??fetch,r=F(t.wwwAuthenticate??``)??`${e.replace(/\/$/,``)}/.well-known/oauth-protected-resource`,i=await I(r,n),a=i.authorization_servers?.[0]??new URL(r).origin;return{protectedResource:i,authorizationServer:await L(a,n),issuer:a}}async function z(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 B(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 H(await o.json())}async function V(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 H(await o.json(),t.refreshToken)}function H(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 U(e,t=60){return te(e.expiresAt,t*1e3)}function W(e={}){let t=e.baseDir??y(se(),`.otto`,`mcp`,`credentials`),n=e=>y(t,`${q(e)}.json`);return{refreshLockDir:t,async load(e){return ne(n(e),void 0)},async save(e,r){await ee(t),await ie(n(e),{...r,serverName:e})},async delete(e){await _(n(e))},async list(){try{let e=await v(t),n=[];for(let r of e)if(r.endsWith(`.json`))try{let e=ge(await oe(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 he(){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 G(e){return h(e)}function K(e={}){let t=W(e);return ce()?G(t):t}function q(e){return`${e.replace(/[^a-zA-Z0-9._-]/g,`_`)}_${ae(`sha256`).update(e).digest(`hex`).slice(0,8)}`}function ge(e){try{let t=JSON.parse(e);return typeof t.serverName==`string`?t.serverName:void 0}catch{return}}const J=`mcp-refresh`;async function Y(e,t,n=m){return g(e,J).acquire(q(t),n)}async function _e(e,t){return g(e,J).release(q(t))}async function ve(e,t,n=m){return g(e,J).waitForRelease(q(t),n)}async function X(e,t,n,r,i={},a){return re({lock:g(e,J),key:q(t),load:a.load,isFresh:e=>!U(e),refresh:()=>V(n,r,i),save:a.save})}async function Z(e){let{serverUrl:t,serverName:n,wwwAuthenticate:r,clientId:i,authorizationServer:a,tokenStore:o=K(),openBrowser:s=ye,fetch:u=fetch,signal:f}=e;if(f?.aborted)throw Error(`Login cancelled`);let p=a??(await R(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 z(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 B(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=K(),clientId:i,authorizationServer:a,fetch:o}=e,s,c=a??void 0,l=e=>i?{...e,clientId:i}:e;return{async getAccessToken(){if(s||=await r.load(t),s){if(U(s)&&s.refreshToken)try{c||=(await R(n,{fetch:o})).authorizationServer;let e={refreshToken:s.refreshToken,resource:n,clientId:s.clientId??i},a=r.refreshLockDir;a?(s=await X(a,t,c.token_endpoint,e,{fetch:o},{load:()=>r.load(t),save:e=>r.save(t,l(e))}),s=l(s)):(s=await V(c.token_endpoint,e,{fetch:o}),s=l(s),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 ye(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 be=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=K(),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=D(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,transportFactory:this.options.transportFactory});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))}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(...P(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{E as McpClient,be as McpManager,w as SseTransport,C as StdioTransport,Y as acquireRefreshLock,K as createDefaultTokenStore,W as createFileTokenStore,D as createMcpClient,N as createMcpToolAdapter,P as createMcpToolAdapters,he as createMemoryTokenStore,G as createOverlayTokenStore,b as createSseDataParser,Q as createTokenProvider,R as discoverOAuth,B as exchangeCode,L as fetchAuthorizationServerMetadata,I as fetchProtectedResourceMetadata,U as isTokenExpired,O as jsonSchemaPropertyToZod,Z as login,M as mcpContentToToolContent,k as mcpSchemaToZod,F as parseWwwAuthenticate,V as refreshAccessToken,X as refreshWithCrossProcessLock,z as registerClient,_e as releaseRefreshLock,S as validateMcpUrl,ve as waitForRefreshLockRelease};
|
|
3
|
+
`);t=n.pop()??``;for(let e of n)i(e)},flush(){t.length>0&&(i(t),t=``),r()}}}const b=s(`@x-otto/mcp:transport`),x=32*1024*1024;function pe(e){let t=[],n=e;for(;;){let e=n.indexOf(10);if(e===-1){n.length>x&&(b.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 S(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 C=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.pushChunk(Buffer.isBuffer(e)?e:Buffer.from(e))});let r=t.stderr;r&&r.on(`data`,e=>{let t=Buffer.isBuffer(e)?e.toString(`utf-8`).trimEnd():String(e).trimEnd();b.debug(`MCP stderr: %s`,t)}),t.onError(e=>{b.error(`MCP process error: %s`,e.message)}),t.onExit(()=>{b.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)}pushChunk(e){if(e.length===0)return;let t=e.lastIndexOf(10);if(t===-1){if(this.buffer.length+e.length>x){b.warn(`MCP newline frame exceeds limit without terminator; resetting buffer`),this.buffer=Buffer.alloc(0);return}this.buffer=this.buffer.length===0?e:Buffer.concat([this.buffer,e]);return}let{bodies:n,rest:r}=pe(this.buffer.length===0?e.subarray(0,t+1):Buffer.concat([this.buffer,e.subarray(0,t+1)])),i=e.subarray(t+1);r.length>0&&i.length>0?this.buffer=Buffer.concat([r,i]):r.length>0?this.buffer=r:this.buffer=i;for(let e of n)try{let t=JSON.parse(e);this.messageHandler?.(t)}catch(e){b.warn(`Failed to parse MCP message: %s`,e.message)}}},w=class{messageHandler=null;closeHandler=null;abortController=null;sessionId=null;closing=!1;baseUrl;fetchImpl;requestTimeoutMs;headers;getAuthToken;constructor(e,t={}){this.baseUrl=S(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(b.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})}b.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){b.warn(`MCP SSE POST 401: authentication required for %s`,this.baseUrl),this.failRequest(e,-32001,`MCP server requires authentication.`);return}b.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`&&(b.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=y(e=>{let t=e.trim();if(!(!t||t===`[DONE]`))try{let e=JSON.parse(t);this.messageHandler?.(e)}catch{b.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`&&(b.warn(`MCP SSE stream error: %s`,e.message),t&&!this.closing&&this.closeHandler?.(`SSE stream error: ${e.message}`))}finally{n.releaseLock()}}};const T=s(`@x-otto/mcp:client`);var E=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);T.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`),T.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);T.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`,T.info(`MCP server "%s" reconnected`,this.serverName)}catch(e){T.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??[],T.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??[],T.debug(`MCP server "%s" provides %d resources`,this.serverName,this.resources.length)}catch(e){T.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??[],T.debug(`MCP server "%s" provides %d prompts`,this.serverName,this.prompts.length)}catch(e){T.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){T.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 w(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 C(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){T.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){T.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`:T.info(`MCP server "%s" notified tools list changed`,this.serverName),this.refreshTools().then(()=>{this.toolsChangedCallback?.()});break;case`notifications/resources/list_changed`:T.info(`MCP server "%s" notified resources list changed`,this.serverName),this.refreshResources().then(()=>{this.resourcesChangedCallback?.()});break;default:T.debug(`MCP notification: %s`,e.method)}this.notificationCallback?.(e.method,e.params)}};function D(e,t,n){return new E(e,t,n)}const me=s(`@x-otto/mcp:adapter`);function he(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 O(e){if(Array.isArray(e.enum)){let t=he(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?O(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=O(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 k(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=O(i);n[e]=r.has(e)?t:t.optional()}return p.object(n)}const A=i;function j(e){let t=e.endsWith(`==`)?2:e.endsWith(`=`)?1:0;return Math.max(0,Math.floor(e.length*3/4)-t)}function ge(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=j(e.blob);if(n>A)return{type:`text`,text:`[image omitted: ${n} bytes exceeds ${A} 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}, ${j(e.blob)} bytes binary]`}}return{type:`text`,text:`[Resource: ${e.uri}]`}}function M(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=j(e.data);if(n>A)return{type:`text`,text:`[image omitted: ${n} bytes exceeds ${A} 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 ge(e.resource);default:return{type:`text`,text:JSON.stringify(e)}}})}function _e(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 ve(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)&&_e(e)&&n.push(e)}return n}function N(e,t,n){let r=`mcp__${n}__${t.name}`,i=t.description?`[MCP: ${n}] ${t.description}`:`[MCP: ${n}] ${t.name}`,a=k(t),o=ve(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}){me.debug(`Calling MCP tool %s on server %s`,t.name,n);let a=await e.callTool({name:t.name,arguments:r},i),o=M(a.content);return{content:o.length>0?o:[{type:`text`,text:`(empty result)`}],isError:a.isError??!1,details:{mcpServer:n,mcpTool:t.name}}}}}function P(e,t){return e.getTools().map(n=>N(e,n,t))}function F(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 I(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 L(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 R(e,t={}){let n=t.fetch??fetch,r=F(t.wwwAuthenticate??``)??`${e.replace(/\/$/,``)}/.well-known/oauth-protected-resource`,i=await I(r,n),a=i.authorization_servers?.[0]??new URL(r).origin;return{protectedResource:i,authorizationServer:await L(a,n),issuer:a}}async function z(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 B(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 H(await o.json())}async function V(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(()=>``),t=ye(e);throw new r(`Token refresh failed: ${o.status}${e?` — ${e}`:``}`,{code:`OAUTH_REFRESH_FAILED`,...t?{errorType:t}:{}})}return H(await o.json(),t.refreshToken)}function H(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 ye(e){try{let t=JSON.parse(e);return typeof t.error==`string`?t.error:void 0}catch{return}}function U(e,t=60){return te(e.expiresAt,t*1e3)}function W(e={}){let t=e.baseDir??v(ce(),`.otto`,`mcp`,`credentials`),n=e=>v(t,`${q(e)}.json`);return{refreshLockDir:t,async load(e){return ne(n(e),void 0)},async save(e,r){await ee(t),await ie(n(e),{...r,serverName:e})},async delete(e){await _(n(e))},async list(){try{let e=await se(t),n=[];for(let r of e)if(r.endsWith(`.json`))try{let e=xe(await oe(v(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 be(){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 G(e){let t=h(e);return{load:t.load,save:t.save,delete:t.delete,list:t.list,shadow:!0,refreshLockDir:void 0}}function K(e={}){let t=W(e);return ue()?G(t):t}function q(e){return`${e.replace(/[^a-zA-Z0-9._-]/g,`_`)}_${ae(`sha256`).update(e).digest(`hex`).slice(0,8)}`}function xe(e){try{let t=JSON.parse(e);return typeof t.serverName==`string`?t.serverName:void 0}catch{return}}const J=`mcp-refresh`;async function Se(e,t,n=m){return g(e,J).acquire(q(t),n)}async function Ce(e,t){return g(e,J).release(q(t))}async function we(e,t,n=m){return g(e,J).waitForRelease(q(t),n)}async function Y(e,t,n,r,i={},a,o){return re({lock:g(e,J),key:q(t),load:a.load,isFresh:e=>!U(e),refresh:()=>V(n,r,i),save:a.save,strategy:o})}async function X(e){let{serverUrl:t,serverName:n,wwwAuthenticate:r,clientId:i,authorizationServer:a,tokenStore:o=K(),openBrowser:s=De,fetch:u=fetch,signal:f}=e;if(f?.aborted)throw Error(`Login cancelled`);let p=a??(await R(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 z(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 B(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 Te(e){let{serverName:t,serverUrl:n,tokenStore:r=K(),clientId:i,authorizationServer:o,fetch:s}=e,c,l=o??void 0,u=e=>i?{...e,clientId:i}:e,d=new a;return{async getAccessToken(){if(c||=await r.load(t),c){if(U(c)&&c.refreshToken){if(r.shadow)return;try{l||=(await R(n,{fetch:s})).authorizationServer;let e={refreshToken:c.refreshToken,resource:n,clientId:c.clientId??i},a=r.refreshLockDir;a?(c=await Y(a,t,l.token_endpoint,e,{fetch:s},{load:()=>r.load(t),save:e=>r.save(t,u(e))},Ee(t,r,u)),c=u(c),d.emit(`credential.changed`,{serverName:t,source:`refresh`})):(c=await V(l.token_endpoint,e,{fetch:s}),c=u(c),await r.save(t,c),d.emit(`credential.changed`,{serverName:t,source:`refresh`}))}catch{return}}return c.accessToken}},async login(e){return c=(await X({...e,tokenStore:r,clientId:i,authorizationServer:l})).credentials,l??=e.authorizationServer,d.emit(`credential.changed`,{serverName:t,source:`self`}),c.accessToken},async logout(){c=void 0,l=void 0,await r.delete(t),d.emit(`credential.changed`,{serverName:t,source:`self`})},async describe(){let e=c??await r.load(t);return e?{configured:!0,source:`oauth`,expiringSoon:U(e),shadow:r.shadow}:{configured:!1,shadow:r.shadow}},on(e,t){return d.on(e,t),this}}}function Ee(e,t,n){let r=new Map;return{shouldFailFast(t){if(!t.refreshToken)return!1;let n=r.get(e);return!!n&&n.refreshToken===t.refreshToken},async recoverFromFailure(i){let a=await t.load(e);if(!(!a||!a.refreshToken)&&a.refreshToken!==i.refreshToken)return r.delete(e),n(a)},cacheFailure(t,n){t?.errorType===`invalid_grant`&&n.refreshToken&&r.set(e,{refreshToken:n.refreshToken,error:t})},onStep(t,n){process.env.OTTO_DEBUG&&t&&console.debug(`[mcp:refresh] ${e} ${t}${n?`: ${n}`:``}`)}}}async function De(t){let n=process.platform===`win32`,r=n?`cmd`:process.platform===`darwin`?`open`:`xdg-open`,i=n?[`/c`,`start`,``,t]:[t];return new Promise((t,n)=>{let a=e(r,i,{stdio:`ignore`,detached:!0});a.on(`error`,e=>{n(Error(`Failed to open browser: ${e.message}`))}),a.on(`close`,e=>{e===0||e===null?t():n(Error(`Browser command exited with code ${e}`))}),a.unref()})}const Z=s(`@x-otto/mcp:manager`);var Oe=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};Z.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 Z.info(`MCP servers: %d connected, %d failed`,r,i),{connected:r,failed:i}}async connect(e,t){if(this.disabledServers.has(e)){Z.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=K(),i=await n.load(e);if(i){let a=Te({serverName:e,serverUrl:t.url,tokenStore:n,clientId:i.clientId});r=()=>a.getAccessToken()}}let i=D(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,transportFactory:this.options.transportFactory});i.onToolsChanged(()=>{Z.info(`MCP server "%s" tools changed`,e),this.emit(`tools.changed`,{serverName:e,tools:i.getTools()})}),i.onResourcesChanged(()=>{Z.info(`MCP server "%s" resources changed`,e),this.emit(`resources.changed`,{serverName:e})}),i.onDisconnected(t=>{Z.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)Z.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))}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(...P(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}};function Q(e){return e.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g,(e,t,n)=>{let r=process.env[t];return r!==void 0&&r!==``?r:n??``})}function ke(e){if(Array.isArray(e))return e.filter(e=>typeof e==`string`).map(Q)}function $(e){if(!e||typeof e!=`object`)return;let t={};for(let[n,r]of Object.entries(e))typeof r==`string`&&(t[n]=Q(r));return Object.keys(t).length>0?t:void 0}function Ae(e){if(!e||typeof e!=`object`)return null;let t=e,n={};typeof t.type==`string`&&(n.type=t.type),typeof t.command==`string`&&(n.command=Q(t.command));let r=ke(t.args);r&&(n.args=r);let i=$(t.env);i&&(n.env=i),typeof t.url==`string`&&(n.url=Q(t.url));let a=$(t.headers);return a&&(n.headers=a),typeof t.managed==`boolean`&&(n.managed=t.managed),!n.command&&!n.url?null:n}function je(e){let t=v(le(e),`.mcp.json`);if(!de(t))return{};let n;try{n=JSON.parse(fe(t,`utf-8`))}catch{return{}}let r=n,i=r?.mcp_servers??r?.mcpServers;if(!i||typeof i!=`object`)return{};let a={};for(let[e,t]of Object.entries(i)){let n=Ae(t);n&&(a[e]=n)}return a}export{E as McpClient,Oe as McpManager,w as SseTransport,C as StdioTransport,Se as acquireRefreshLock,K as createDefaultTokenStore,W as createFileTokenStore,D as createMcpClient,N as createMcpToolAdapter,P as createMcpToolAdapters,be as createMemoryTokenStore,G as createOverlayTokenStore,y as createSseDataParser,Te as createTokenProvider,R as discoverOAuth,B as exchangeCode,L as fetchAuthorizationServerMetadata,I as fetchProtectedResourceMetadata,U as isTokenExpired,O as jsonSchemaPropertyToZod,X as login,M as mcpContentToToolContent,k as mcpSchemaToZod,je as parseDotMcpJson,F as parseWwwAuthenticate,V as refreshAccessToken,Y as refreshWithCrossProcessLock,z as registerClient,Ce as releaseRefreshLock,S as validateMcpUrl,we as waitForRefreshLockRelease};
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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/refresh-lock.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 { isExpiredAt } from '@x-otto/credentials'\nimport { 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 秒视为过期,避免边界竞态)。 */\n/**\n * RFC-374 M1:过期判断委托 @x-otto/credentials 叶包(skew 单源)。\n * 公开 API 形状不变(`isTokenExpired(credentials, skewSeconds=60)`),\n * 实现改为叶包 `isExpiredAt`(ms 口径)——mcp 域只负责取 `expiresAt` 字段。\n */\nexport function isTokenExpired(credentials: OAuthCredentials, skewSeconds = 60): boolean {\n return isExpiredAt(credentials.expiresAt, skewSeconds * 1000)\n}\n","import { createHash } from 'node:crypto'\nimport { readdir, readFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nimport { deleteFileIfExists, ensurePrivateDir, readJsonFile, writeJsonAtomic } from '@x-otto/credentials'\nimport { createOverlayStore } from '@x-otto/credentials'\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 * 跨进程刷新锁目录(与凭据文件同目录)。仅文件 store 提供;内存/overlay store 为\n * undefined(后者影子态禁止写锁文件)。业务层据此决定 refresh 是否走跨进程锁。\n */\n readonly refreshLockDir?: 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, `${sanitizeServerName(serverName)}.json`)\n\n return {\n refreshLockDir: baseDir,\n\n async load(serverName) {\n // RFC-374 M1:读盘纹理委托叶包 readJsonFile(ENOENT/损坏 → fallback,EACCES rethrow)。\n return readJsonFile<OAuthCredentials | undefined>(filePath(serverName), undefined)\n },\n\n async save(serverName, credentials) {\n // RFC-374 M1:原子写盘纹理委托叶包 writeJsonAtomic——临时文件 + chmod 0o600 +\n // rename 原子替换,凭据从落盘第一刻起就不可被其他本地用户读取,读者永远\n // 不会观测到半写状态(对齐 @x-otto/ai auth-store saveSerial 的同一语义)。\n await ensurePrivateDir(baseDir)\n await writeJsonAtomic(filePath(serverName), { ...credentials, serverName })\n },\n\n async delete(serverName) {\n await deleteFileIfExists(filePath(serverName))\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(RFC-374 M1:实现委托 @x-otto/credentials 通用 overlay):\n * 读穿透到 base(继承磁盘既有凭据),写/删只进内存 overlay。进程退出后磁盘凭据\n * 逐字节不变。语义细节(load 墓碑优先/save 只进内存/delete 记墓碑/list 合并)\n * 由叶包 createOverlayStore 保证,本函数只做 TokenStore 形状适配。\n */\nexport function createOverlayTokenStore(base: TokenStore): TokenStore {\n return createOverlayStore<OAuthCredentials>(base)\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/**\n * 防止路径穿越 + 冲突:sanitize + 前 8 位 SHA256 哈希避免 sanitize 碰撞。\n * 同时作为跨进程刷新锁的 key(与凭据文件同源命名,避免 serverName 中的路径分隔符\n * 破坏锁文件路径)。\n */\nexport function sanitizeServerName(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","/**\n * refresh-lock.ts —— MCP OAuth 跨进程刷新互斥锁(RFC-374 M1 迁移后:域配置薄层)。\n *\n * 背景与 ai 的 `oauth-refresh-coordinator.ts` 同源:本机可能同时运行多个 otto 进程\n * (CLI、feishu-use 桥接等),共享同一份 MCP 凭据目录 `~/.otto/mcp/credentials`。\n * 轮换型 refresh_token 在多进程几乎同时用同一个旧 token 刷新时会双花——服务端只认\n * 先到者,其余进程刷新失败并非凭据过期而是被抢先轮换。\n *\n * RFC-374 M1:通用锁原语与刷新协调骨架已下沉 `@x-otto/credentials` 叶包\n * (createRefreshLock / refreshUnderCrossProcessLock),本文件只保留 MCP 域的\n * 三样配置:锁目录(凭据文件同目录)、key 命名(sanitizeServerName)、锁文件前缀\n * `mcp-refresh`(**上线后不可变**——换前缀 = 新旧进程各锁各的文件 = 等于没锁,\n * 与 ai 的 `auth-refresh` 前缀同一条滚动升级硬约束)。\n *\n * 公开 API 形状不变(acquireRefreshLock/releaseRefreshLock/waitForRefreshLockRelease/\n * refreshWithCrossProcessLock/REFRESH_LOCK_TIMEOUT_MS/RefreshWithLockPorts),\n * 既有消费方(login.ts)与测试零改动。\n */\nimport {\n REFRESH_LOCK_TIMEOUT_MS,\n createRefreshLock,\n refreshUnderCrossProcessLock,\n} from '@x-otto/credentials'\n\nimport { isTokenExpired, refreshAccessToken } from './token'\nimport { sanitizeServerName } from './token-store'\n\nimport type { OAuthCredentials, RefreshTokenParams, TokenExchangeOptions } from './token'\n\n/** 锁文件名前缀(`.mcp-refresh-<key>.lock`)。MCP 是全新锁域,无滚动升级历史约束。 */\nconst LOCK_PREFIX = 'mcp-refresh'\n\n/** 陈旧锁清理阈值 = 等待释放超时(与 ai 的规则9一致,两者必须相等)。 */\nexport { REFRESH_LOCK_TIMEOUT_MS }\n\n/** 尝试获取某 server 的跨进程刷新锁。成功 true;被占用/异常 false。 */\nexport async function acquireRefreshLock(\n credentialsDir: string,\n serverName: string,\n timeoutMs: number = REFRESH_LOCK_TIMEOUT_MS,\n): Promise<boolean> {\n return createRefreshLock(credentialsDir, LOCK_PREFIX).acquire(sanitizeServerName(serverName), timeoutMs)\n}\n\n/** 释放某 server 的跨进程刷新锁(幂等,并停止心跳)。 */\nexport async function releaseRefreshLock(credentialsDir: string, serverName: string): Promise<void> {\n return createRefreshLock(credentialsDir, LOCK_PREFIX).release(sanitizeServerName(serverName))\n}\n\n/** 等待另一进程持有的刷新锁释放,超时返回 false(调用方走读盘兜底)。 */\nexport async function waitForRefreshLockRelease(\n credentialsDir: string,\n serverName: string,\n timeoutMs: number = REFRESH_LOCK_TIMEOUT_MS,\n): Promise<boolean> {\n return createRefreshLock(credentialsDir, LOCK_PREFIX).waitForRelease(\n sanitizeServerName(serverName),\n timeoutMs,\n )\n}\n\n/** 刷新协调器读写端口:锁内需要重读/落盘,但不应直接持有 TokenStore 实例。 */\nexport interface RefreshWithLockPorts {\n /** 读取当前磁盘/存储上的凭据(可能已被另一进程刷新)。 */\n load(): Promise<OAuthCredentials | undefined>\n /** 保存真正刷新成功的新凭据。 */\n save(credentials: OAuthCredentials): Promise<void>\n}\n\n/**\n * 跨进程互斥刷新:同一时刻同一 server 全机器只有一个进程真正发起 `refreshAccessToken`\n * 网络请求,其余进程等待后直接读盘复用结果。语义(拿锁重读/等待读盘/抛错/finally 释放)\n * 由叶包 refreshUnderCrossProcessLock 保证,本函数做 MCP 域参数装配。\n */\nexport async function refreshWithCrossProcessLock(\n credentialsDir: string,\n serverName: string,\n tokenEndpoint: string,\n params: RefreshTokenParams,\n options: TokenExchangeOptions = {},\n ports: RefreshWithLockPorts,\n): Promise<OAuthCredentials> {\n const lock = createRefreshLock(credentialsDir, LOCK_PREFIX)\n return refreshUnderCrossProcessLock<OAuthCredentials>({\n lock,\n key: sanitizeServerName(serverName),\n load: ports.load,\n isFresh: (c) => !isTokenExpired(c),\n refresh: () => refreshAccessToken(tokenEndpoint, params, options),\n save: ports.save,\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'\nimport { refreshWithCrossProcessLock } from './refresh-lock'\n\nimport type { AuthorizationServerMetadata } from './discovery'\nimport type { OAuthCredentials, RefreshTokenParams } 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 // clientId 合并保持与原有 refresh 路径语义一致:仅当 options.clientId 存在时才回填。\n const withClientId = (creds: OAuthCredentials): OAuthCredentials =>\n clientId ? { ...creds, clientId } : creds\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 const params: RefreshTokenParams = {\n refreshToken: credentials.refreshToken,\n resource: serverUrl,\n clientId: credentials.clientId ?? clientId,\n }\n const lockDir = tokenStore.refreshLockDir\n if (lockDir) {\n credentials = await refreshWithCrossProcessLock(\n lockDir,\n serverName,\n cachedAs.token_endpoint,\n params,\n { fetch: fetchImpl },\n {\n load: () => tokenStore.load(serverName),\n save: (creds) => tokenStore.save(serverName, withClientId(creds)),\n },\n )\n credentials = withClientId(credentials)\n } else {\n credentials = await refreshAccessToken(cachedAs.token_endpoint, params, {\n fetch: fetchImpl,\n })\n credentials = withClientId(credentials)\n await tokenStore.save(serverName, credentials)\n }\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 { McpTransport, 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 * 注入 transport 工厂(RFC-368 M2 测试用)。缺省时按 config 构造 stdio/sse。\n * 仅用于测试注入 FakeTransport;不可用于生产环境。\n */\n transportFactory?: () => McpTransport\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 transportFactory: this.options.transportFactory,\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 /**\n * RFC-368 M2:断开连接。`server.disconnected` 事件由 McpClient 的生命周期边界作为唯一\n * 发布者——manager 的 `onDisconnected` 回调已在 `connect()` 中注册转发,此处不再重复 emit,\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 // server.disconnected 已由 client 的 disconnectedCallback → manager 回调转发,\n // 此处不再直接 emit,避免同一次断开触发两次事件(RFC-368 M2/D3)。\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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"}
|
|
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/refresh-lock.ts","../src/oauth/login.ts","../src/mcp-manager.ts","../src/dot-mcp-json.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 // RFC review D6a:增量分帧——不再每次 data 事件 Buffer.concat 整个未完成缓冲\n //(O(n²) 复制);只把「残留 + chunk 中最后一个换行之前」一次性交给帧解码,\n // 尾部残留原样持有。大响应下每字节至多参与一次拼接(线性)。\n this.pushChunk(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string))\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 /**\n * RFC review D6a:增量分帧——仅把「残留 buffer + chunk 中最后一个换行之前」的部分\n * 交给 decodeNewlineFrames(一次性、线性),换行之后的未完成尾部作为新残留持有。\n *\n * 语义与旧实现全等:\n * - 帧边界 = 换行(官方 MCP stdio 分帧);\n * - 无换行且残留超 MAX_MESSAGE_BYTES → 复位(防 DoS,同 decodeNewlineFrames 语义);\n * - 解析失败只告警不中断(同一消息级容错)。\n *\n * 复杂度:旧实现每 chunk 复制「全部残留 + chunk」(残留可含已收帧前缀 → O(n²));\n * 新实现每 chunk 只复制「残留 + 完整帧段」,残留段通常为空或单帧尾部 → O(总字节)。\n */\n private pushChunk(chunk: Buffer): void {\n if (chunk.length === 0) return\n const lastNl = chunk.lastIndexOf(0x0a)\n if (lastNl === -1) {\n // 无换行:追加到残留缓冲(超限复位防护,同 decodeNewlineFrames 的 rest 语义)\n if (this.buffer.length + chunk.length > MAX_MESSAGE_BYTES) {\n logger.warn('MCP newline frame exceeds limit without terminator; resetting buffer')\n this.buffer = Buffer.alloc(0)\n return\n }\n this.buffer = this.buffer.length === 0 ? chunk : Buffer.concat([this.buffer, chunk])\n return\n }\n\n // 完整帧段 = 残留 + chunk[0..lastNl];decode 只消费一次\n const complete =\n this.buffer.length === 0\n ? chunk.subarray(0, lastNl + 1)\n : Buffer.concat([this.buffer, chunk.subarray(0, lastNl + 1)])\n const { bodies, rest } = decodeNewlineFrames(complete)\n\n // 残留 = decode 的 rest(恒为空,防御)+ chunk 尾部(lastNl 之后,通常为空)\n const tail = chunk.subarray(lastNl + 1)\n if (rest.length > 0 && tail.length > 0) {\n this.buffer = Buffer.concat([rest, tail])\n } else if (rest.length > 0) {\n this.buffer = rest\n } else {\n this.buffer = tail\n }\n\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 { AgentToolDefinition, 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): AgentToolDefinition {\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): AgentToolDefinition[] {\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 { isExpiredAt } from '@x-otto/credentials'\nimport { 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 // P1 策略层:解析 OAuth2 标准错误响应体({\"error\":\"invalid_grant\",...}),\n // 附加 errorType 到抛出的错误上——供 RefreshStrategy.shouldFailFast/cacheFailure\n // 判定是否永久失败(invalid_grant = refresh_token 已过期/被撤销/已轮换)。\n const oauthErrorType = parseOAuthErrorType(errBody)\n throw new OAuthError(\n `Token refresh failed: ${res.status}${errBody ? ` — ${errBody}` : ''}`,\n { code: 'OAUTH_REFRESH_FAILED', ...(oauthErrorType ? { errorType: oauthErrorType } : {}) },\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/**\n * 解析 OAuth2 标准错误响应体中的 error 字段(RFC 6749 §5.2)。\n * 服务端返回 `{\"error\":\"invalid_grant\"}` 时,refresh_token 已被永久拒绝——\n * 同一凭据快照后续刷新必失败,可 fail-fast 不再竞锁/打网络。\n */\nfunction parseOAuthErrorType(body: string): string | undefined {\n try {\n const obj = JSON.parse(body) as Record<string, unknown>\n return typeof obj.error === 'string' ? obj.error : undefined\n } catch {\n return undefined\n }\n}\n\n/** 检查 access token 是否已过期(提前 60 秒视为过期,避免边界竞态)。 */\n/**\n * RFC-374 M1:过期判断委托 @x-otto/credentials 叶包(skew 单源)。\n * 公开 API 形状不变(`isTokenExpired(credentials, skewSeconds=60)`),\n * 实现改为叶包 `isExpiredAt`(ms 口径)——mcp 域只负责取 `expiresAt` 字段。\n */\nexport function isTokenExpired(credentials: OAuthCredentials, skewSeconds = 60): boolean {\n return isExpiredAt(credentials.expiresAt, skewSeconds * 1000)\n}\n","import { createHash } from 'node:crypto'\nimport { readdir, readFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nimport { deleteFileIfExists, ensurePrivateDir, readJsonFile, writeJsonAtomic } from '@x-otto/credentials'\nimport { createOverlayStore } from '@x-otto/credentials'\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 * 跨进程刷新锁目录(与凭据文件同目录)。仅文件 store 提供;内存/overlay store 为\n * undefined(后者影子态禁止写锁文件)。业务层据此决定 refresh 是否走跨进程锁。\n */\n readonly refreshLockDir?: string\n /**\n * 是否为影子(overlay)态 store。影子态 store 读穿透磁盘、写只进内存——\n * 过期 token 不可刷新(轮换型 refresh_token 会作废磁盘旧 token,致全部普通会话\n * 掉登录)。业务层据此在 refresh 入口拦截(RFC-345 §D2b,对齐 ai auth-store 的\n * overlay 拒刷语义)。内存 store 非 shadow(无磁盘可破坏,刷新安全)。\n */\n readonly shadow?: boolean\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, `${sanitizeServerName(serverName)}.json`)\n\n return {\n refreshLockDir: baseDir,\n\n async load(serverName) {\n // RFC-374 M1:读盘纹理委托叶包 readJsonFile(ENOENT/损坏 → fallback,EACCES rethrow)。\n return readJsonFile<OAuthCredentials | undefined>(filePath(serverName), undefined)\n },\n\n async save(serverName, credentials) {\n // RFC-374 M1:原子写盘纹理委托叶包 writeJsonAtomic——临时文件 + chmod 0o600 +\n // rename 原子替换,凭据从落盘第一刻起就不可被其他本地用户读取,读者永远\n // 不会观测到半写状态(对齐 @x-otto/ai auth-store saveSerial 的同一语义)。\n await ensurePrivateDir(baseDir)\n await writeJsonAtomic(filePath(serverName), { ...credentials, serverName })\n },\n\n async delete(serverName) {\n await deleteFileIfExists(filePath(serverName))\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(RFC-374 M1:实现委托 @x-otto/credentials 通用 overlay):\n * 读穿透到 base(继承磁盘既有凭据),写/删只进内存 overlay。进程退出后磁盘凭据\n * 逐字节不变。语义细节(load 墓碑优先/save 只进内存/delete 记墓碑/list 合并)\n * 由叶包 createOverlayStore 保证,本函数只做 TokenStore 形状适配 + shadow 标记。\n *\n * shadow: true + refreshLockDir: undefined —— 显式声明影子态语义(此前靠 overlay\n * 丢失 base 的 refreshLockDir 属性实现,是隐性开关,未来给 overlay 透传 refreshLockDir\n * 会静默改变影子态锁语义)。\n */\nexport function createOverlayTokenStore(base: TokenStore): TokenStore {\n const overlay = createOverlayStore<OAuthCredentials>(base)\n return {\n load: overlay.load,\n save: overlay.save,\n delete: overlay.delete,\n list: overlay.list,\n shadow: true,\n refreshLockDir: undefined,\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/**\n * 防止路径穿越 + 冲突:sanitize + 前 8 位 SHA256 哈希避免 sanitize 碰撞。\n * 同时作为跨进程刷新锁的 key(与凭据文件同源命名,避免 serverName 中的路径分隔符\n * 破坏锁文件路径)。\n */\nexport function sanitizeServerName(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","/**\n * refresh-lock.ts —— MCP OAuth 跨进程刷新互斥锁(RFC-374 M1 迁移后:域配置薄层)。\n *\n * 背景与 ai 的 `oauth-refresh-coordinator.ts` 同源:本机可能同时运行多个 otto 进程\n * (CLI、feishu-use 桥接等),共享同一份 MCP 凭据目录 `~/.otto/mcp/credentials`。\n * 轮换型 refresh_token 在多进程几乎同时用同一个旧 token 刷新时会双花——服务端只认\n * 先到者,其余进程刷新失败并非凭据过期而是被抢先轮换。\n *\n * RFC-374 M1:通用锁原语与刷新协调骨架已下沉 `@x-otto/credentials` 叶包\n * (createRefreshLock / refreshUnderCrossProcessLock),本文件只保留 MCP 域的\n * 三样配置:锁目录(凭据文件同目录)、key 命名(sanitizeServerName)、锁文件前缀\n * `mcp-refresh`(**上线后不可变**——换前缀 = 新旧进程各锁各的文件 = 等于没锁,\n * 与 ai 的 `auth-refresh` 前缀同一条滚动升级硬约束)。\n *\n * 公开 API 形状不变(acquireRefreshLock/releaseRefreshLock/waitForRefreshLockRelease/\n * refreshWithCrossProcessLock/REFRESH_LOCK_TIMEOUT_MS/RefreshWithLockPorts),\n * 既有消费方(login.ts)与测试零改动。\n */\nimport {\n REFRESH_LOCK_TIMEOUT_MS,\n createRefreshLock,\n refreshUnderCrossProcessLock,\n type RefreshStrategy,\n} from '@x-otto/credentials'\n\nimport { isTokenExpired, refreshAccessToken } from './token'\nimport { sanitizeServerName } from './token-store'\n\nimport type { OAuthCredentials, RefreshTokenParams, TokenExchangeOptions } from './token'\n\nexport type { RefreshStrategy }\n\n/** 锁文件名前缀(`.mcp-refresh-<key>.lock`)。MCP 是全新锁域,无滚动升级历史约束。 */\nconst LOCK_PREFIX = 'mcp-refresh'\n\n/** 陈旧锁清理阈值 = 等待释放超时(与 ai 的规则9一致,两者必须相等)。 */\nexport { REFRESH_LOCK_TIMEOUT_MS }\n\n/** 尝试获取某 server 的跨进程刷新锁。成功 true;被占用/异常 false。 */\nexport async function acquireRefreshLock(\n credentialsDir: string,\n serverName: string,\n timeoutMs: number = REFRESH_LOCK_TIMEOUT_MS,\n): Promise<boolean> {\n return createRefreshLock(credentialsDir, LOCK_PREFIX).acquire(sanitizeServerName(serverName), timeoutMs)\n}\n\n/** 释放某 server 的跨进程刷新锁(幂等,并停止心跳)。 */\nexport async function releaseRefreshLock(credentialsDir: string, serverName: string): Promise<void> {\n return createRefreshLock(credentialsDir, LOCK_PREFIX).release(sanitizeServerName(serverName))\n}\n\n/** 等待另一进程持有的刷新锁释放,超时返回 false(调用方走读盘兜底)。 */\nexport async function waitForRefreshLockRelease(\n credentialsDir: string,\n serverName: string,\n timeoutMs: number = REFRESH_LOCK_TIMEOUT_MS,\n): Promise<boolean> {\n return createRefreshLock(credentialsDir, LOCK_PREFIX).waitForRelease(\n sanitizeServerName(serverName),\n timeoutMs,\n )\n}\n\n/** 刷新协调器读写端口:锁内需要重读/落盘,但不应直接持有 TokenStore 实例。 */\nexport interface RefreshWithLockPorts {\n /** 读取当前磁盘/存储上的凭据(可能已被另一进程刷新)。 */\n load(): Promise<OAuthCredentials | undefined>\n /** 保存真正刷新成功的新凭据。 */\n save(credentials: OAuthCredentials): Promise<void>\n}\n\n/**\n * 跨进程互斥刷新:同一时刻同一 server 全机器只有一个进程真正发起 `refreshAccessToken`\n * 网络请求,其余进程等待后直接读盘复用结果。语义(拿锁重读/等待读盘/抛错/finally 释放)\n * 由叶包 refreshUnderCrossProcessLock 保证,本函数做 MCP 域参数装配。\n *\n * P1 扩展:可选 `strategy` 透传到叶包骨架,启用永久失败缓存 / 磁盘恢复 / 可观测性。\n * 缺省(未提供 strategy)时与原有行为完全一致。\n */\nexport async function refreshWithCrossProcessLock(\n credentialsDir: string,\n serverName: string,\n tokenEndpoint: string,\n params: RefreshTokenParams,\n options: TokenExchangeOptions = {},\n ports: RefreshWithLockPorts,\n strategy?: RefreshStrategy<OAuthCredentials>,\n): Promise<OAuthCredentials> {\n const lock = createRefreshLock(credentialsDir, LOCK_PREFIX)\n return refreshUnderCrossProcessLock<OAuthCredentials>({\n lock,\n key: sanitizeServerName(serverName),\n load: ports.load,\n isFresh: (c) => !isTokenExpired(c),\n refresh: () => refreshAccessToken(tokenEndpoint, params, options),\n save: ports.save,\n strategy,\n })\n}\n","import { spawn } from 'node:child_process'\nimport { createPkcePair, generateState, startLoopbackServer, TypedEventEmitter } from '@x-otto/shared'\n\nimport { discoverOAuth } from './discovery'\nimport { registerClient } from './registration'\nimport { exchangeCode, refreshAccessToken, isTokenExpired } from './token'\nimport { createDefaultTokenStore } from './token-store'\nimport { refreshWithCrossProcessLock } from './refresh-lock'\nimport type { RefreshStrategy } from './refresh-lock'\n\nimport type { AuthorizationServerMetadata } from './discovery'\nimport type { OAuthCredentials, RefreshTokenParams } 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\n/** 凭据状态描述(对齐 ai AuthStore.getAuthSource/hasCredential,S1 补齐)。 */\nexport interface CredentialStatus {\n /** 是否有凭据(store 中存在)。 */\n configured: boolean\n /** 凭据来源类型:'oauth' = 有 access/refresh token。缺省 = 无凭据。 */\n source?: 'oauth'\n /** 凭据是否即将过期(isTokenExpired 判定)。 */\n expiringSoon?: boolean\n /** 是否影子态(影子态不可刷新)。 */\n shadow?: boolean\n}\n\n/** 凭据变更事件(对齐 ai AuthStore 'credential.changed')。 */\nexport interface TokenProviderEvents {\n /** 凭据发生变化(login/logout/refresh 成功,或外部进程写入被感知)。 */\n 'credential.changed': (payload: { serverName: string; source: 'self' | 'external' | 'refresh' }) => void\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 /** S1 补齐:只读探查凭据状态(不触发刷新/网络)。对齐 ai getAuthSource/hasCredential。 */\n describe(): Promise<CredentialStatus>\n /** S1 补齐:凭据变更事件(对齐 ai 'credential.changed')。 */\n on(event: 'credential.changed', listener: TokenProviderEvents['credential.changed']): this\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 // clientId 合并保持与原有 refresh 路径语义一致:仅当 options.clientId 存在时才回填。\n const withClientId = (creds: OAuthCredentials): OAuthCredentials =>\n clientId ? { ...creds, clientId } : creds\n\n // S1 补齐:事件面——TypedEventEmitter 提供 .on/.emit,与 TokenProvider 接口合并。\n const emitter = new TypedEventEmitter<TokenProviderEvents>()\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 // RFC-345 §D2b(终局 review P2 修复):影子态 store 不可刷新——\n // refresh_token 是轮换型的 provider(如 Anthropic),影子进程刷新会作废\n // 磁盘上的旧 token,且新 token 只进内存 overlay → 进程退出后蒸发 →\n // 全部普通会话掉登录(本仓已有同类真实事故,见 ai auth-store overlay 防御)。\n // 返回 undefined 提示用户重新登录,不发起网络刷新。\n if (tokenStore.shadow) {\n return undefined\n }\n try {\n if (!cachedAs) {\n cachedAs = (await discoverOAuth(serverUrl, { fetch: fetchImpl })).authorizationServer\n }\n const params: RefreshTokenParams = {\n refreshToken: credentials.refreshToken,\n resource: serverUrl,\n clientId: credentials.clientId ?? clientId,\n }\n const lockDir = tokenStore.refreshLockDir\n if (lockDir) {\n credentials = await refreshWithCrossProcessLock(\n lockDir,\n serverName,\n cachedAs.token_endpoint,\n params,\n { fetch: fetchImpl },\n {\n load: () => tokenStore.load(serverName),\n save: (creds) => tokenStore.save(serverName, withClientId(creds)),\n },\n // P1 策略层:永久失败缓存 + 磁盘恢复 + 可观测性\n // (对齐 ai OAuthRefreshCoordinator 的三层防御,mcp 此前完全缺失)\n createRefreshStrategy(serverName, tokenStore, withClientId),\n )\n credentials = withClientId(credentials)\n emitter.emit('credential.changed', { serverName, source: 'refresh' })\n } else {\n credentials = await refreshAccessToken(cachedAs.token_endpoint, params, {\n fetch: fetchImpl,\n })\n credentials = withClientId(credentials)\n await tokenStore.save(serverName, credentials)\n emitter.emit('credential.changed', { serverName, source: 'refresh' })\n }\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 emitter.emit('credential.changed', { serverName, source: 'self' })\n return credentials.accessToken\n },\n\n async logout() {\n credentials = undefined\n cachedAs = undefined\n await tokenStore.delete(serverName)\n emitter.emit('credential.changed', { serverName, source: 'self' })\n },\n\n async describe() {\n const creds = credentials ?? await tokenStore.load(serverName)\n if (!creds) {\n return { configured: false, shadow: tokenStore.shadow }\n }\n return {\n configured: true,\n source: 'oauth' as const,\n expiringSoon: isTokenExpired(creds),\n shadow: tokenStore.shadow,\n }\n },\n\n on(event, listener) {\n emitter.on(event, listener)\n return this\n },\n }\n\n return provider\n}\n\n/**\n * P1 策略层:为 mcp 的跨进程刷新构建永久失败缓存 + 磁盘恢复策略(对齐 ai\n * OAuthRefreshCoordinator 的三层防御)。\n *\n * - shouldFailFast:同一 refreshToken 快照此前被 invalid_grant 永久拒绝 → 不竞锁/不打网络。\n * - recoverFromFailure:重读 tokenStore(绕过内存 cache)——另一进程可能已抢先刷新。\n * - cacheFailure:服务端返回 invalid_grant 时缓存(key=refreshToken 快照)。\n * - onStep:恢复步骤可观测性(旁路日志,不影响控制流)。\n *\n * 策略状态(permanentFailures Map)随 createTokenProvider 闭包生命周期——provider\n * 被 GC 时策略状态随之释放。与 ai 的差异:ai 的协调器是长驻类(permanentFailures\n * 是实例字段),mcp 的 TokenProvider 是闭包——但 mcp 的 provider 生命周期与\n * MCP 连接绑定,不需要跨连接持久化失败缓存(重新连接 = 重新 login = 新凭据)。\n */\nfunction createRefreshStrategy(\n serverName: string,\n tokenStore: TokenStore,\n withClientId: (creds: OAuthCredentials) => OAuthCredentials,\n): RefreshStrategy<OAuthCredentials> {\n const permanentFailures = new Map<string, { refreshToken: string; error: unknown }>()\n\n return {\n shouldFailFast(latest) {\n if (!latest.refreshToken) return false\n const cached = permanentFailures.get(serverName)\n return !!cached && cached.refreshToken === latest.refreshToken\n },\n\n async recoverFromFailure(latest) {\n // 重读 store(绕过内存 cache)——另一进程可能已抢先刷新并落盘\n const disk = await tokenStore.load(serverName)\n if (!disk || !disk.refreshToken) return undefined\n // 磁盘凭据的 refreshToken 与当前旧值相同 → 不是另一进程刷新的结果\n if (disk.refreshToken === latest.refreshToken) return undefined\n // 磁盘有新凭据 → 清永久失败缓存(旧快照已失效)\n permanentFailures.delete(serverName)\n return withClientId(disk)\n },\n\n cacheFailure(error, latest) {\n const errorType = (error as { errorType?: string })?.errorType\n if (errorType === 'invalid_grant' && latest.refreshToken) {\n permanentFailures.set(serverName, {\n refreshToken: latest.refreshToken,\n error,\n })\n }\n },\n\n onStep(step, detail) {\n // 旁路可观测性——当前只 console.debug,未来可接线到日志体系\n if (process.env['OTTO_DEBUG'] && step) {\n console.debug(`[mcp:refresh] ${serverName} ${step}${detail ? `: ${detail}` : ''}`)\n }\n },\n }\n}\nasync function defaultOpenBrowser(url: string): Promise<void> {\n // Windows:`start` 是 cmd.exe 内建命令、非可执行文件,`spawn('start', ...)` 会 ENOENT。\n // 必须经 `cmd /c start`;空字符串是 start 的 title 占位(防 url 被当标题)。\n // darwin=open / linux=xdg-open 是真实可执行文件,直接 spawn。\n const isWin = process.platform === 'win32'\n const cmd = isWin ? 'cmd' : process.platform === 'darwin' ? 'open' : 'xdg-open'\n const args = isWin ? ['/c', 'start', '', 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 { McpTransport, 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 { AgentToolDefinition } 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 * 注入 transport 工厂(RFC-368 M2 测试用)。缺省时按 config 构造 stdio/sse。\n * 仅用于测试注入 FakeTransport;不可用于生产环境。\n */\n transportFactory?: () => McpTransport\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 transportFactory: this.options.transportFactory,\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 /**\n * RFC-368 M2:断开连接。`server.disconnected` 事件由 McpClient 的生命周期边界作为唯一\n * 发布者——manager 的 `onDisconnected` 回调已在 `connect()` 中注册转发,此处不再重复 emit,\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 // server.disconnected 已由 client 的 disconnectedCallback → manager 回调转发,\n // 此处不再直接 emit,避免同一次断开触发两次事件(RFC-368 M2/D3)。\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(): AgentToolDefinition[] {\n const tools: AgentToolDefinition[] = []\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","/**\n * dot-mcp-json.ts — `.mcp.json` 文件格式解析器(RFC-421 D2,原 claude-compat).\n *\n * `.mcp.json` 形如 `{ \"mcp_servers\": { \"<name>\": { command/args/env/url/type/headers } } }`,\n * 字段与 `McpServerConfig` 基本同构。支持双键:otto 统一键 `mcp_servers`(优先)+\n * Claude Code 原生驼峰键 `mcpServers`(回退,读第三方文件)。\n * 支持 `${VAR}` / `${VAR:-default}` 环境变量展开。\n *\n * 归属本包(`@x-otto/mcp`):`.mcp.json` 是 `McpServerConfig` 的文件格式,解析器与类型同域。\n * 原在 `@x-otto/claude-compat`,造成 `coding/plugin/mcp.ts` 反向依赖 compat(RFC-421 P0-1)。\n * 信任门逻辑不在此处——由各消费方自行叠加(compat 的 `resolveClaudeMcpServers` 叠加\n * Claude 信任门;plugin 的 `resolvePluginMcpServers` 叠加插件信任门)。\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\nimport type { McpServerConfig } from './types'\n\n/** 展开 `${VAR}` 与 `${VAR:-default}`(未设或空 → default/空串),遵循 shell `:-` 语义。 */\nfunction expandEnv(value: string): string {\n return value.replace(\n /\\$\\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\\}/g,\n (_m, name: string, def: string | undefined) => {\n const v = process.env[name]\n return v !== undefined && v !== '' ? v : (def ?? '')\n },\n )\n}\n\nfunction expandStringArray(arr: unknown): string[] | undefined {\n if (!Array.isArray(arr)) return undefined\n return arr.filter((x): x is string => typeof x === 'string').map(expandEnv)\n}\n\nfunction expandStringRecord(rec: unknown): Record<string, string> | undefined {\n if (!rec || typeof rec !== 'object') return undefined\n const out: Record<string, string> = {}\n for (const [k, v] of Object.entries(rec as Record<string, unknown>)) {\n if (typeof v === 'string') out[k] = expandEnv(v)\n }\n return Object.keys(out).length > 0 ? out : undefined\n}\n\n/** 把一条 `.mcp.json` server 原始项映射成 McpServerConfig(仅已知字段,含 env 展开)。 */\nfunction mapServerEntry(raw: unknown): McpServerConfig | null {\n if (!raw || typeof raw !== 'object') return null\n const r = raw as Record<string, unknown>\n const config: McpServerConfig = {}\n if (typeof r['type'] === 'string') config.type = r['type'] as McpServerConfig['type']\n if (typeof r['command'] === 'string') config.command = expandEnv(r['command'])\n const args = expandStringArray(r['args'])\n if (args) config.args = args\n const env = expandStringRecord(r['env'])\n if (env) config.env = env\n if (typeof r['url'] === 'string') config.url = expandEnv(r['url'])\n const headers = expandStringRecord(r['headers'])\n if (headers) config.headers = headers\n if (typeof r['managed'] === 'boolean') config.managed = r['managed'] as boolean\n if (!config.command && !config.url) return null\n return config\n}\n\n/**\n * 解析给定目录的 `.mcp.json`(`mcp_servers` 优先,回退 `mcpServers`)。\n * 不做信任门判断;缺失/损坏 → {}。信任门由消费方叠加。\n */\nexport function parseDotMcpJson(dir: string): Record<string, McpServerConfig> {\n const path = join(resolve(dir), '.mcp.json')\n if (!existsSync(path)) return {}\n let raw: unknown\n try {\n raw = JSON.parse(readFileSync(path, 'utf-8'))\n } catch {\n return {}\n }\n const rawObj = raw as { mcp_servers?: unknown; mcpServers?: unknown }\n const servers = rawObj?.mcp_servers ?? rawObj?.mcpServers\n if (!servers || typeof servers !== 'object') return {}\n\n const out: Record<string, McpServerConfig> = {}\n for (const [name, entry] of Object.entries(servers as Record<string, unknown>)) {\n const mapped = mapServerEntry(entry)\n if (mapped) out[name] = mapped\n }\n return out\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x-otto/mcp",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.4",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist"
|
|
6
6
|
],
|
|
@@ -15,13 +15,13 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"zod": "4.3.6",
|
|
18
|
-
"@x-otto/credentials": "0.0.1-alpha.
|
|
19
|
-
"@x-otto/
|
|
20
|
-
"@x-otto/interchange": "0.1.0-alpha.
|
|
21
|
-
"@x-otto/
|
|
18
|
+
"@x-otto/credentials": "0.0.1-alpha.2",
|
|
19
|
+
"@x-otto/shared": "0.1.0-alpha.7",
|
|
20
|
+
"@x-otto/interchange": "0.1.0-alpha.7",
|
|
21
|
+
"@x-otto/env": "0.1.0-alpha.7"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@x-otto/runtime": "0.0.1-alpha.
|
|
24
|
+
"@x-otto/runtime": "0.0.1-alpha.11"
|
|
25
25
|
},
|
|
26
26
|
"private": false,
|
|
27
27
|
"publishConfig": {
|