@humain/terminal 0.1.1 → 0.2.1

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.
Files changed (193) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/bundle/chunks/{anthropic-messages-XULXUBIG.js → anthropic-messages-QFCEKGI3.js} +1 -1
  3. package/dist/bundle/chunks/{azure-openai-responses-JEG2XOYC.js → azure-openai-responses-G4ZMHV37.js} +1 -1
  4. package/dist/bundle/chunks/chunk-7T6XCLJF.js +22 -0
  5. package/dist/bundle/chunks/chunk-D6HHRSB5.js +103 -0
  6. package/dist/bundle/chunks/chunk-EY4DWHQU.js +2 -0
  7. package/dist/bundle/chunks/{chunk-LYAMSPR2.js → chunk-I47ZWR55.js} +483 -506
  8. package/dist/bundle/chunks/{chunk-JUPSJTFQ.js → chunk-K4JAZYQH.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-HPPQHK3J.js → chunk-KKIB2TVR.js} +1 -1
  10. package/dist/bundle/chunks/chunk-LI2YVPWP.js +2 -0
  11. package/dist/bundle/chunks/{chunk-BZQ53VGE.js → chunk-XSR434CO.js} +1 -1
  12. package/dist/bundle/chunks/compat-QCV26HNS.js +2 -0
  13. package/dist/bundle/chunks/dist-PRPIUY3U.js +2 -0
  14. package/dist/bundle/chunks/esm-E7UVEYTO.js +4 -0
  15. package/dist/bundle/chunks/{extract-TRW5FBY6.js → extract-CTIFQZDJ.js} +15 -14
  16. package/dist/bundle/chunks/{google-generative-ai-OX6VSCA6.js → google-generative-ai-RSUU4MWO.js} +1 -1
  17. package/dist/bundle/chunks/{google-vertex-GW76GSS2.js → google-vertex-CKQPOITO.js} +1 -1
  18. package/dist/bundle/chunks/host-WP3MVKS2.js +2 -0
  19. package/dist/bundle/chunks/{node-KOIP55YZ.js → node-NF43OPII.js} +1 -1
  20. package/dist/bundle/chunks/{node-XY3CBY3C.js → node-UMFM7CDZ.js} +1 -1
  21. package/dist/bundle/chunks/{openai-codex-responses-NPLWP6ZC.js → openai-codex-responses-S34V5ZSD.js} +1 -1
  22. package/dist/bundle/chunks/{openai-completions-OT2GTJKC.js → openai-completions-T2IGVUEI.js} +1 -1
  23. package/dist/bundle/chunks/{openai-responses-5A6CR7GI.js → openai-responses-GYZODCT3.js} +1 -1
  24. package/dist/bundle/chunks/readability-LAGF34IB.js +3 -0
  25. package/dist/bundle/chunks/turndown.cjs-HRMZVRUL.js +2 -0
  26. package/dist/bundle/cli.js +1 -1
  27. package/dist/bundle/index.js +1 -1
  28. package/dist/bundle/rpc-entry.js +1 -1
  29. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  30. package/dist/core/tools/file-mutation-queue.js +21 -8
  31. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  32. package/dist/core/tools/subagent-env.d.ts +18 -0
  33. package/dist/core/tools/subagent-env.d.ts.map +1 -0
  34. package/dist/core/tools/subagent-env.js +43 -0
  35. package/dist/core/tools/subagent-env.js.map +1 -0
  36. package/dist/core/tools/subagent-scheduler.d.ts +31 -0
  37. package/dist/core/tools/subagent-scheduler.d.ts.map +1 -0
  38. package/dist/core/tools/subagent-scheduler.js +203 -0
  39. package/dist/core/tools/subagent-scheduler.js.map +1 -0
  40. package/dist/core/tools/subagent.d.ts +83 -3
  41. package/dist/core/tools/subagent.d.ts.map +1 -1
  42. package/dist/core/tools/subagent.js +214 -76
  43. package/dist/core/tools/subagent.js.map +1 -1
  44. package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
  45. package/dist/humain/mcp-adapter/vendor/README.md +125 -19
  46. package/dist/humain/mcp-adapter/vendor/agent-plugin-loader.ts +69 -30
  47. package/dist/humain/mcp-adapter/vendor/agent-plugin-provenance.ts +36 -0
  48. package/dist/humain/mcp-adapter/vendor/claude-plugin-loader.ts +365 -0
  49. package/dist/humain/mcp-adapter/vendor/commands.ts +13 -15
  50. package/dist/humain/mcp-adapter/vendor/config.ts +198 -30
  51. package/dist/humain/mcp-adapter/vendor/consent-manager.ts +40 -9
  52. package/dist/humain/mcp-adapter/vendor/direct-tool-surface.ts +250 -0
  53. package/dist/humain/mcp-adapter/vendor/direct-tools.ts +9 -228
  54. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js +74 -32
  55. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js.map +1 -1
  56. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.d.ts +1 -0
  57. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js +31 -0
  58. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js.map +1 -0
  59. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.d.ts +9 -0
  60. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js +362 -0
  61. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js.map +1 -0
  62. package/dist/humain/mcp-adapter/vendor/dist/config.d.ts +3 -1
  63. package/dist/humain/mcp-adapter/vendor/dist/config.js +189 -27
  64. package/dist/humain/mcp-adapter/vendor/dist/config.js.map +1 -1
  65. package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js +7 -16
  66. package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js.map +1 -1
  67. package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js +5 -17
  68. package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js.map +1 -1
  69. package/dist/humain/mcp-adapter/vendor/dist/types.d.ts +30 -4
  70. package/dist/humain/mcp-adapter/vendor/dist/types.js +26 -0
  71. package/dist/humain/mcp-adapter/vendor/dist/types.js.map +1 -1
  72. package/dist/humain/mcp-adapter/vendor/dist/utils.d.ts +6 -0
  73. package/dist/humain/mcp-adapter/vendor/dist/utils.js +65 -6
  74. package/dist/humain/mcp-adapter/vendor/dist/utils.js.map +1 -1
  75. package/dist/humain/mcp-adapter/vendor/host-html-template.ts +20 -19
  76. package/dist/humain/mcp-adapter/vendor/http-ca.ts +84 -0
  77. package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +37102 -15103
  78. package/dist/humain/mcp-adapter/vendor/index.ts +828 -164
  79. package/dist/humain/mcp-adapter/vendor/init.ts +48 -10
  80. package/dist/humain/mcp-adapter/vendor/lazy-loader.ts +7 -0
  81. package/dist/humain/mcp-adapter/vendor/mcp-auth-fetch.ts +113 -0
  82. package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +256 -77
  83. package/dist/humain/mcp-adapter/vendor/mcp-auth.ts +316 -54
  84. package/dist/humain/mcp-adapter/vendor/mcp-code.ts +45 -18
  85. package/dist/humain/mcp-adapter/vendor/mcp-install.ts +60 -0
  86. package/dist/humain/mcp-adapter/vendor/mcp-oauth-provider.ts +70 -6
  87. package/dist/humain/mcp-adapter/vendor/mcp-output-guard.ts +53 -42
  88. package/dist/humain/mcp-adapter/vendor/mcp-panel-theme.ts +105 -0
  89. package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +366 -331
  90. package/dist/humain/mcp-adapter/vendor/mcp-references.ts +9 -14
  91. package/dist/humain/mcp-adapter/vendor/mcp-script-worker.mjs +3 -1
  92. package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +383 -341
  93. package/dist/humain/mcp-adapter/vendor/mcp-status.ts +5 -0
  94. package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +7 -16
  95. package/dist/humain/mcp-adapter/vendor/namespace-tools.ts +61 -3
  96. package/dist/humain/mcp-adapter/vendor/oauth.ts +6 -2
  97. package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +5 -17
  98. package/dist/humain/mcp-adapter/vendor/package.json +13 -3
  99. package/dist/humain/mcp-adapter/vendor/prompts.ts +35 -3
  100. package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +108 -7
  101. package/dist/humain/mcp-adapter/vendor/runtime-owner.ts +16 -1
  102. package/dist/humain/mcp-adapter/vendor/sampling-handler.ts +9 -32
  103. package/dist/humain/mcp-adapter/vendor/sandbox-proxy-template.ts +20 -93
  104. package/dist/humain/mcp-adapter/vendor/server-manager.ts +227 -40
  105. package/dist/humain/mcp-adapter/vendor/session-approvals.ts +187 -0
  106. package/dist/humain/mcp-adapter/vendor/skills/mcp-scripting/SKILL.md +9 -1
  107. package/dist/humain/mcp-adapter/vendor/state.ts +10 -1
  108. package/dist/humain/mcp-adapter/vendor/tool-approval.ts +10 -22
  109. package/dist/humain/mcp-adapter/vendor/tool-metadata.ts +14 -0
  110. package/dist/humain/mcp-adapter/vendor/types.ts +57 -4
  111. package/dist/humain/mcp-adapter/vendor/ui-server.ts +36 -28
  112. package/dist/humain/mcp-adapter/vendor/utils.ts +64 -6
  113. package/dist/humain/web-access/vendor/abortable.d.ts +3 -0
  114. package/dist/humain/web-access/vendor/abortable.d.ts.map +1 -0
  115. package/dist/humain/web-access/vendor/abortable.js +22 -0
  116. package/dist/humain/web-access/vendor/abortable.js.map +1 -0
  117. package/dist/humain/web-access/vendor/crawl4ai.d.ts +9 -0
  118. package/dist/humain/web-access/vendor/crawl4ai.d.ts.map +1 -0
  119. package/dist/humain/web-access/vendor/crawl4ai.js +187 -0
  120. package/dist/humain/web-access/vendor/crawl4ai.js.map +1 -0
  121. package/dist/humain/web-access/vendor/credential-source.d.ts.map +1 -1
  122. package/dist/humain/web-access/vendor/credential-source.js +1 -0
  123. package/dist/humain/web-access/vendor/credential-source.js.map +1 -1
  124. package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
  125. package/dist/humain/web-access/vendor/curator-page.js +20 -2
  126. package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
  127. package/dist/humain/web-access/vendor/curator-run.d.ts +11 -0
  128. package/dist/humain/web-access/vendor/curator-run.d.ts.map +1 -0
  129. package/dist/humain/web-access/vendor/curator-run.js +38 -0
  130. package/dist/humain/web-access/vendor/curator-run.js.map +1 -0
  131. package/dist/humain/web-access/vendor/curator-server.d.ts +1 -0
  132. package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
  133. package/dist/humain/web-access/vendor/curator-server.js +2 -0
  134. package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
  135. package/dist/humain/web-access/vendor/duckduckgo.d.ts.map +1 -1
  136. package/dist/humain/web-access/vendor/duckduckgo.js +1 -1
  137. package/dist/humain/web-access/vendor/duckduckgo.js.map +1 -1
  138. package/dist/humain/web-access/vendor/extract.d.ts +1 -1
  139. package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
  140. package/dist/humain/web-access/vendor/extract.js +58 -11
  141. package/dist/humain/web-access/vendor/extract.js.map +1 -1
  142. package/dist/humain/web-access/vendor/fetch-params.js +1 -1
  143. package/dist/humain/web-access/vendor/fetch-params.js.map +1 -1
  144. package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
  145. package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
  146. package/dist/humain/web-access/vendor/gemini-search.js +12 -5
  147. package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
  148. package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
  149. package/dist/humain/web-access/vendor/index.js +23 -19
  150. package/dist/humain/web-access/vendor/index.js.map +1 -1
  151. package/dist/humain/web-access/vendor/openai-search.d.ts.map +1 -1
  152. package/dist/humain/web-access/vendor/openai-search.js +40 -14
  153. package/dist/humain/web-access/vendor/openai-search.js.map +1 -1
  154. package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
  155. package/dist/humain/web-access/vendor/page-query.js +6 -2
  156. package/dist/humain/web-access/vendor/page-query.js.map +1 -1
  157. package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -1
  158. package/dist/humain/web-access/vendor/query-rewrite.js +6 -2
  159. package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -1
  160. package/dist/humain/web-access/vendor/serpapi.d.ts +4 -0
  161. package/dist/humain/web-access/vendor/serpapi.d.ts.map +1 -0
  162. package/dist/humain/web-access/vendor/serpapi.js +215 -0
  163. package/dist/humain/web-access/vendor/serpapi.js.map +1 -0
  164. package/dist/humain/web-access/vendor/ssrf-protection.d.ts.map +1 -1
  165. package/dist/humain/web-access/vendor/ssrf-protection.js +5 -1
  166. package/dist/humain/web-access/vendor/ssrf-protection.js.map +1 -1
  167. package/dist/humain/web-access/vendor/storage.d.ts.map +1 -1
  168. package/dist/humain/web-access/vendor/storage.js +9 -0
  169. package/dist/humain/web-access/vendor/storage.js.map +1 -1
  170. package/dist/humain/web-access/vendor/summary-review.d.ts +1 -1
  171. package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
  172. package/dist/humain/web-access/vendor/summary-review.js +35 -24
  173. package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
  174. package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
  175. package/dist/humain/web-access/vendor/utils.js +5 -3
  176. package/dist/humain/web-access/vendor/utils.js.map +1 -1
  177. package/dist/modes/json-event.d.ts +8 -0
  178. package/dist/modes/json-event.d.ts.map +1 -1
  179. package/dist/modes/json-event.js +17 -0
  180. package/dist/modes/json-event.js.map +1 -1
  181. package/dist/modes/print-mode.d.ts.map +1 -1
  182. package/dist/modes/print-mode.js +3 -2
  183. package/dist/modes/print-mode.js.map +1 -1
  184. package/docs/environment-variables.md +8 -0
  185. package/docs/json.md +16 -0
  186. package/npm-shrinkwrap.json +6 -6
  187. package/package.json +2 -2
  188. package/src/humain/mcp-adapter/UPSTREAM.md +56 -7
  189. package/src/humain/mcp-adapter/upstream.json +6 -4
  190. package/src/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
  191. package/src/humain/web-access/UPSTREAM.md +18 -8
  192. package/src/humain/web-access/upstream.json +6 -5
  193. package/dist/bundle/chunks/chunk-2LC3S3VV.js +0 -105
@@ -1,13 +1,16 @@
1
1
  import { mkdirSync, statSync } from "node:fs";
2
+ import { BlockList, isIP } from "node:net";
2
3
  import { isDeepStrictEqual } from "node:util";
3
4
  import {
4
5
  Client,
5
6
  SdkError,
6
7
  SdkErrorCode,
7
8
  SdkHttpError,
9
+ SseError,
8
10
  SSEClientTransport,
9
11
  StreamableHTTPClientTransport,
10
12
  UnauthorizedError,
13
+ type FetchLike,
11
14
  type GetPromptResult,
12
15
  type ListToolsResult,
13
16
  type ReadResourceResult,
@@ -38,9 +41,16 @@ import { resolveNpxBinary } from "./npx-resolver.ts";
38
41
  import { createJsonSchemaValidator } from "./json-schema-validator.ts";
39
42
  import { logger } from "./logger.ts";
40
43
  import { RESOURCE_MIME_TYPE } from "./ui-app-bridge-helpers.ts";
44
+ import { isBuiltInAgentPlugin } from "./agent-plugin-provenance.ts";
41
45
  import { McpOAuthProvider } from "./mcp-oauth-provider.ts";
42
46
  import { extractOAuthConfig, supportsOAuth, type McpOAuthRuntime } from "./mcp-auth-flow.ts";
43
- import { inspectAuthForUrl, invalidateAuthEntryCache, type AuthStorageOptions } from "./mcp-auth.ts";
47
+ import {
48
+ captureOAuthAuthority,
49
+ inspectAuthForUrl,
50
+ invalidateAuthEntryCache,
51
+ type AuthStorageOptions,
52
+ type OAuthAuthority,
53
+ } from "./mcp-auth.ts";
44
54
  import { getBearerTokenForUrl } from "./mcp-bearer-store.ts";
45
55
  import { registerSamplingHandler, type ServerSamplingConfig } from "./sampling-handler.ts";
46
56
  import {
@@ -66,7 +76,9 @@ import {
66
76
  traceTransportKind,
67
77
  wrapTransportWithMcpTrace,
68
78
  } from "./mcp-trace.ts";
79
+ import { createOAuthFetch, resolveOAuthHeaders } from "./mcp-auth-fetch.ts";
69
80
  import { createRequestHeadersCommandFetch } from "./request-headers-command.ts";
81
+ import { createCaFetch, validateCaFile } from "./http-ca.ts";
70
82
 
71
83
  const MAX_CAPTURED_STDERR_BYTES = 8 * 1024;
72
84
  const MAX_CAPTURED_STDERR_LINES = 3;
@@ -79,6 +91,35 @@ type HttpAuthProviderState =
79
91
  | { status: "explicit"; provider: McpOAuthProvider }
80
92
  | { status: "implicit-challenged"; provider: McpOAuthProvider };
81
93
 
94
+ function isLiteralLocalAddress(url: string): boolean {
95
+ const hostname = new URL(url).hostname.replace(/^\[|\]$/g, "");
96
+ const family = isIP(hostname);
97
+ if (!family) return false;
98
+ const local = new BlockList();
99
+ local.addSubnet("10.0.0.0", 8);
100
+ local.addSubnet("172.16.0.0", 12);
101
+ local.addSubnet("192.168.0.0", 16);
102
+ local.addSubnet("169.254.0.0", 16);
103
+ local.addSubnet("fc00::", 7, "ipv6");
104
+ local.addSubnet("fe80::", 10, "ipv6");
105
+ return local.check(hostname, family === 6 ? "ipv6" : "ipv4");
106
+ }
107
+
108
+ function localNetworkFailureCodes(error: unknown, seen = new Set<object>()): string[] {
109
+ if (typeof error !== "object" || error === null || seen.has(error)) return [];
110
+ seen.add(error);
111
+ const codes: string[] = [];
112
+ if ("code" in error && typeof error.code === "string"
113
+ && ["EHOSTUNREACH", "ENETUNREACH", "EACCES"].includes(error.code)) {
114
+ codes.push(error.code);
115
+ }
116
+ if ("cause" in error) codes.push(...localNetworkFailureCodes(error.cause, seen));
117
+ if (error instanceof AggregateError) {
118
+ for (const nested of error.errors) codes.push(...localNetworkFailureCodes(nested, seen));
119
+ }
120
+ return [...new Set(codes)];
121
+ }
122
+
82
123
  function isUnauthorizedHttpError(error: unknown): boolean {
83
124
  return error instanceof UnauthorizedError || (error instanceof SdkHttpError && error.status === 401);
84
125
  }
@@ -139,6 +180,8 @@ export interface ServerConnection {
139
180
  /** Monotonic guard against older refresh responses replacing newer notifications. */
140
181
  toolsRevision?: number;
141
182
  resources: McpResource[];
183
+ /** True when resources were advertised but resources/list failed. */
184
+ resourceDiscoveryFailed?: boolean;
142
185
  prompts: McpPrompt[];
143
186
  /** True when prompts were advertised but prompts/list failed. */
144
187
  promptDiscoveryFailed?: boolean;
@@ -172,6 +215,7 @@ export type ToolRefreshResult = "updated" | "unchanged" | "superseded" | "refres
172
215
 
173
216
  type ToolListCacheHints = Partial<Pick<ListToolsResult, "ttlMs" | "cacheScope">>;
174
217
  type ToolListResult = { tools: McpTool[]; hints?: ToolListCacheHints };
218
+ type ResourceListResult = { resources: McpResource[]; failed: boolean };
175
219
 
176
220
  const KEEP_ALIVE_REFRESH_TIMEOUT_MS = 5_000;
177
221
  const LISTEN_RETRY_DELAY_MS = 5_000;
@@ -190,6 +234,7 @@ export function isTransientHttpConnectError(error: unknown): boolean {
190
234
  export class McpServerManager {
191
235
  private connections = new Map<string, ServerConnection>();
192
236
  private connectPromises = new Map<string, Promise<ServerConnection>>();
237
+ private connectOAuthAuthorities = new Map<string, OAuthAuthority>();
193
238
  private reconnectPromises = new Map<string, Promise<ServerConnection>>();
194
239
  private uiStreamListeners = new Map<string, UiStreamListener>();
195
240
  private samplingConfig: ServerSamplingConfig | undefined;
@@ -305,17 +350,39 @@ export class McpServerManager {
305
350
  }
306
351
 
307
352
  async connect(name: string, definition: ServerDefinition, signal?: AbortSignal): Promise<ServerConnection> {
353
+ validateCaFile(definition);
308
354
  if (isServerDisabled(definition)) throw new Error(`MCP server "${name}" is disabled`);
309
355
  if (this.stopped) throw new Error("MCP server manager is closed");
310
356
  const ownedSignal = combineAbortSignals(this.runtimeSignal, signal);
311
357
  throwIfAborted(ownedSignal);
358
+ const oauthAuthority = definition.url && supportsOAuth(definition)
359
+ ? captureOAuthAuthority(name, false)
360
+ : undefined;
312
361
  const closing = this.closePromises.get(name);
313
362
  if (closing) await abortable(closing, ownedSignal);
314
363
  throwIfAborted(ownedSignal);
315
364
 
316
365
  // Dedupe concurrent connection attempts.
317
- if (this.connectPromises.has(name)) {
318
- return abortable(this.connectPromises.get(name)!, ownedSignal);
366
+ const existingConnect = this.connectPromises.get(name);
367
+ if (existingConnect) {
368
+ const existingAuthority = this.connectOAuthAuthorities.get(name);
369
+ let reusable = oauthAuthority === undefined && existingAuthority === undefined;
370
+ if (oauthAuthority && existingAuthority) {
371
+ try {
372
+ existingAuthority();
373
+ reusable = true;
374
+ } catch {
375
+ reusable = false;
376
+ }
377
+ }
378
+ if (reusable) return abortable(existingConnect, ownedSignal);
379
+ const replacedAttempt = this.connectAttempts.get(name);
380
+ replacedAttempt?.abort(new Error(`MCP connection ${name} was replaced`));
381
+ if (this.connectAttempts.get(name) === replacedAttempt) this.connectAttempts.delete(name);
382
+ if (this.connectPromises.get(name) === existingConnect) this.connectPromises.delete(name);
383
+ if (this.connectOAuthAuthorities.get(name) === existingAuthority) {
384
+ this.connectOAuthAuthorities.delete(name);
385
+ }
319
386
  }
320
387
 
321
388
  const existing = this.connections.get(name);
@@ -329,11 +396,20 @@ export class McpServerManager {
329
396
  const generation = this.closeGenerations.get(name) ?? 0;
330
397
  const attemptController = new AbortController();
331
398
  const attemptSignal = combineAbortSignals(ownedSignal, attemptController.signal);
332
- const connectionAttempt = this.createConnection(name, definition, attemptSignal, ownedSignal, credentialsInvalidated);
399
+ const connectionAttempt = this.createConnection(
400
+ name,
401
+ definition,
402
+ attemptSignal,
403
+ ownedSignal,
404
+ credentialsInvalidated,
405
+ oauthAuthority,
406
+ attemptController,
407
+ );
333
408
  const promise = definition.url
334
409
  ? connectionAttempt.catch(async error => { throw await this.enrichHttpConnectionError(definition, error); })
335
410
  : connectionAttempt;
336
411
  this.connectPromises.set(name, promise);
412
+ if (oauthAuthority) this.connectOAuthAuthorities.set(name, oauthAuthority);
337
413
  this.connectAttempts.set(name, attemptController);
338
414
 
339
415
  try {
@@ -351,6 +427,9 @@ export class McpServerManager {
351
427
  return connection;
352
428
  } finally {
353
429
  if (this.connectPromises.get(name) === promise) this.connectPromises.delete(name);
430
+ if (this.connectOAuthAuthorities.get(name) === oauthAuthority) {
431
+ this.connectOAuthAuthorities.delete(name);
432
+ }
354
433
  if (this.connectAttempts.get(name) === attemptController) this.connectAttempts.delete(name);
355
434
  }
356
435
  }
@@ -673,7 +752,10 @@ export class McpServerManager {
673
752
  !isDeepStrictEqual(expectedConnection.tools, nextTools.tools) ||
674
753
  !isDeepStrictEqual(expectedConnection.toolListHints, nextTools.hints)
675
754
  )) ||
676
- (nextResources !== undefined && !isDeepStrictEqual(expectedConnection.resources, nextResources)) ||
755
+ (nextResources !== undefined && (
756
+ !isDeepStrictEqual(expectedConnection.resources, nextResources.resources) ||
757
+ expectedConnection.resourceDiscoveryFailed !== nextResources.failed
758
+ )) ||
677
759
  (nextPrompts !== undefined && (
678
760
  !isDeepStrictEqual(expectedConnection.prompts, nextPrompts.prompts) ||
679
761
  expectedConnection.promptDiscoveryFailed !== false
@@ -690,7 +772,10 @@ export class McpServerManager {
690
772
  expectedConnection.toolListHints = nextTools.hints;
691
773
  expectedConnection.toolsRevision = (expectedConnection.toolsRevision ?? 0) + 1;
692
774
  }
693
- if (nextResources !== undefined) expectedConnection.resources = nextResources;
775
+ if (nextResources !== undefined) {
776
+ expectedConnection.resources = nextResources.resources;
777
+ expectedConnection.resourceDiscoveryFailed = nextResources.failed;
778
+ }
694
779
  if (nextPrompts !== undefined) {
695
780
  expectedConnection.prompts = nextPrompts.prompts;
696
781
  expectedConnection.promptDiscoveryFailed = false;
@@ -767,6 +852,8 @@ export class McpServerManager {
767
852
  signal?: AbortSignal,
768
853
  requestSignal?: AbortSignal,
769
854
  credentialsInvalidated = false,
855
+ oauthAuthority?: OAuthAuthority,
856
+ attemptOwner?: AbortController,
770
857
  ): Promise<ServerConnection> {
771
858
  throwIfAborted(signal);
772
859
 
@@ -795,8 +882,11 @@ export class McpServerManager {
795
882
  if (definition.command) {
796
883
  client = this.createClient(name, definition);
797
884
  let command = definition.command;
798
- let args = (definition.args ?? []).map((argument) => interpolateEnvVars(argument));
799
- const cwd = resolveConfigPath(definition.cwd) ?? this.defaultCwd;
885
+ const literalArgs = isBuiltInAgentPlugin(definition, "args");
886
+ const literalCwd = isBuiltInAgentPlugin(definition, "cwd");
887
+ let args = literalArgs ? [...(definition.args ?? [])] : (definition.args ?? []).map((argument) => interpolateEnvVars(argument));
888
+ const cwd = (literalCwd ? definition.cwd : resolveConfigPath(definition.cwd)) ?? this.defaultCwd;
889
+ if (definition.pluginDataDir) mkdirSync(definition.pluginDataDir, { recursive: true });
800
890
  if (cwd !== undefined) {
801
891
  const cwdStats = statSync(cwd, { throwIfNoEntry: false });
802
892
  if (!cwdStats) throw new Error(`MCP server "${name}" configured cwd does not exist: "${cwd}"`);
@@ -813,11 +903,15 @@ export class McpServerManager {
813
903
  }
814
904
  throwIfAborted(signal);
815
905
 
816
- if (definition.pluginDataDir) mkdirSync(definition.pluginDataDir, { recursive: true });
817
906
  const stdioTransport = new StdioClientTransport({
818
907
  command,
819
908
  args,
820
- env: resolveEnv(definition.env, name, definition.literalEnv === true),
909
+ env: resolveEnv(
910
+ definition.env,
911
+ name,
912
+ isBuiltInAgentPlugin(definition, "env") || definition.literalEnv === true,
913
+ definition.inheritEnv !== false,
914
+ ),
821
915
  ...(cwd !== undefined ? { cwd } : {}),
822
916
  stderr: definition.debug ? "inherit" : "pipe",
823
917
  });
@@ -837,6 +931,8 @@ export class McpServerManager {
837
931
  signal,
838
932
  traceObserver,
839
933
  invalidated,
934
+ oauthAuthority,
935
+ attemptOwner,
840
936
  );
841
937
  client = httpConnection.client;
842
938
  transport = httpConnection.transport;
@@ -905,9 +1001,10 @@ export class McpServerManager {
905
1001
  // identity so a stale connection's late close can never clobber a fresh
906
1002
  // connection. The SDK client owns the transport callbacks.
907
1003
  client.onclose = () => {
908
- if (this.connections.get(name) === connection) {
909
- connection.status = "closed";
910
- }
1004
+ if (connection.status === "closed") return;
1005
+ connection.status = "closed";
1006
+ if (this.connections.get(name) !== connection) return;
1007
+ this.metadataListChangedListener?.(name, "remote-close");
911
1008
  };
912
1009
 
913
1010
  // Discover tools, resources, and prompts. Resource and prompt listing is
@@ -917,9 +1014,13 @@ export class McpServerManager {
917
1014
  this.fetchAllResources(client, requestOptions),
918
1015
  this.fetchAllPrompts(client, requestOptions),
919
1016
  ]);
1017
+ if (connection.status !== "connected") {
1018
+ throw new Error(`MCP connection for ${name} closed during metadata discovery`);
1019
+ }
920
1020
  connection.tools = toolResult.tools;
921
1021
  connection.toolListHints = toolResult.hints;
922
- connection.resources = resources;
1022
+ connection.resources = resources.resources;
1023
+ connection.resourceDiscoveryFailed = resources.failed;
923
1024
  connection.prompts = promptResult.prompts;
924
1025
  connection.promptDiscoveryFailed = promptResult.failed;
925
1026
 
@@ -977,6 +1078,12 @@ export class McpServerManager {
977
1078
 
978
1079
  private async enrichHttpConnectionError(definition: ServerDefinition, error: unknown): Promise<Error> {
979
1080
  const originalMessage = error instanceof Error ? error.message : String(error);
1081
+ if (process.platform === "darwin") {
1082
+ const codes = localNetworkFailureCodes(error);
1083
+ if (codes.length > 0 && isLiteralLocalAddress(resolveServerUrl(definition)!)) {
1084
+ return new Error(`${originalMessage} — ${codes.join(", ")} — macOS Local Network Privacy may be blocking access. Check System Settings > Privacy & Security > Local Network for the app hosting Pi; enable access if listed and restart it. Try launching Pi from Terminal.app or SSH. Routing or firewall problems can also cause this error.`, { cause: error });
1085
+ }
1086
+ }
980
1087
  if (isTransientHttpConnectError(error)) {
981
1088
  return new Error(`${originalMessage} — endpoint is temporarily unavailable (HTTP 503)`, { cause: error });
982
1089
  }
@@ -1142,6 +1249,7 @@ export class McpServerManager {
1142
1249
  const connection = this.connections.get(serverName);
1143
1250
  if (!connection || connection.client !== client || connection.status !== "connected") return;
1144
1251
  connection.resources = resources;
1252
+ connection.resourceDiscoveryFailed = false;
1145
1253
  this.metadataListChangedListener?.(serverName, "resources-list-changed");
1146
1254
  this.pendingMetadataPublications.delete(serverName);
1147
1255
  }
@@ -1179,6 +1287,8 @@ export class McpServerManager {
1179
1287
  signal?: AbortSignal,
1180
1288
  traceObserver?: McpTraceObserver,
1181
1289
  credentialsInvalidated = false,
1290
+ oauthAuthority?: OAuthAuthority,
1291
+ attemptOwner?: AbortController,
1182
1292
  ): Promise<{ client: Client; transport: Transport; status: "connected" | "needs-auth"; credentialsInvalidated: boolean }> {
1183
1293
  throwIfAborted(signal);
1184
1294
  const serverUrl = resolveServerUrl(definition)!;
@@ -1186,12 +1296,21 @@ export class McpServerManager {
1186
1296
 
1187
1297
  // Resolve secret commands only for this connection attempt, without
1188
1298
  // mutating the persisted configuration.
1189
- const hasCommandHeader = Object.values(definition.headers ?? {})
1299
+ const literalHeaders = isBuiltInAgentPlugin(definition, "headers");
1300
+ const hasCommandHeader = !literalHeaders && Object.values(definition.headers ?? {})
1190
1301
  .some(value => value.startsWith("!") && !value.startsWith("!!"));
1191
- const headers = resolveCommandSecretsRecord(
1192
- definition.headers,
1193
- key => `MCP server "${serverName}" HTTP header "${key}"`,
1194
- ) ?? {};
1302
+ const oauthEnabled = supportsOAuth(definition);
1303
+ let headers: Record<string, string>;
1304
+ if (literalHeaders) {
1305
+ headers = { ...definition.headers };
1306
+ } else if (oauthEnabled) {
1307
+ headers = Object.fromEntries(resolveOAuthHeaders(definition.headers));
1308
+ } else {
1309
+ headers = resolveCommandSecretsRecord(
1310
+ definition.headers,
1311
+ key => `MCP server "${serverName}" HTTP header "${key}"`,
1312
+ ) ?? {};
1313
+ }
1195
1314
 
1196
1315
  // Resolve bearer auth before creating requestInit so every attempted
1197
1316
  // transport receives the same headers.
@@ -1216,18 +1335,34 @@ export class McpServerManager {
1216
1335
  }
1217
1336
  }
1218
1337
 
1219
- const requestInit = Object.keys(headers).length > 0 ? { headers } : undefined;
1220
- const requestFetch = definition.requestHeadersCommand
1221
- ? createRequestHeadersCommandFetch(definition.requestHeadersCommand)
1222
- : undefined;
1223
- const createAuthProvider = (): McpOAuthProvider => new McpOAuthProvider(
1224
- serverName,
1225
- serverUrl,
1226
- extractOAuthConfig(definition),
1227
- { onRedirect: async () => {} },
1228
- this.authStorageOptions,
1229
- this.oauthRuntime?.signal,
1230
- );
1338
+ // Do not give origin-bound headers to SDK requestInit: it reuses those
1339
+ // defaults for discovered OAuth endpoints, including other origins.
1340
+ const requestInit = !oauthEnabled && Object.keys(headers).length > 0 ? { headers } : undefined;
1341
+ const serviceHeaders = oauthEnabled ? new Headers(headers) : new Headers();
1342
+ let caFetch = createCaFetch(definition);
1343
+ try {
1344
+ const createAuthProvider = (): McpOAuthProvider => {
1345
+ const currentAttempt = this.connectAttempts.get(serverName);
1346
+ if (currentAttempt && currentAttempt !== attemptOwner) {
1347
+ throw new Error(`MCP connection for ${serverName} was replaced while connecting`);
1348
+ }
1349
+ if (!oauthAuthority) throw new Error(`Missing OAuth authority for ${serverName}`);
1350
+ const provider = new McpOAuthProvider(
1351
+ serverName,
1352
+ serverUrl,
1353
+ extractOAuthConfig(definition),
1354
+ { onRedirect: async () => {} },
1355
+ this.authStorageOptions,
1356
+ this.oauthRuntime?.signal,
1357
+ undefined,
1358
+ oauthAuthority,
1359
+ );
1360
+ provider.setAuthFetch(createOAuthFetch(serverUrl, () => serviceHeaders,
1361
+ combineAbortSignals(this.oauthRuntime?.signal, signal), {
1362
+ ...(caFetch ? { delegate: caFetch.fetch } : {}),
1363
+ }));
1364
+ return provider;
1365
+ };
1231
1366
 
1232
1367
  // Explicit OAuth checks secure storage immediately. Implicit OAuth keeps
1233
1368
  // anonymous servers provider-free unless URL-bound credentials are already
@@ -1252,6 +1387,16 @@ export class McpServerManager {
1252
1387
  : { status: "explicit", provider: createAuthProvider() }
1253
1388
  : { status: "disabled" };
1254
1389
 
1390
+ const commandFetch = definition.requestHeadersCommand
1391
+ ? createRequestHeadersCommandFetch(definition.requestHeadersCommand, caFetch?.fetch)
1392
+ : caFetch?.fetch;
1393
+ const requestFetch = oauthEnabled
1394
+ ? createOAuthFetch(serverUrl, () => serviceHeaders, this.oauthRuntime?.signal, {
1395
+ // MCP streams outlive individual auth requests; retain SDK request deadlines.
1396
+ timeout: false,
1397
+ ...(commandFetch ? { delegate: commandFetch } : {}),
1398
+ })
1399
+ : commandFetch;
1255
1400
  const attempt = async (
1256
1401
  kind: "streamable-http" | "sse",
1257
1402
  ): Promise<
@@ -1259,9 +1404,21 @@ export class McpServerManager {
1259
1404
  | { status: "failed"; client: Client; transport: Transport; error: unknown }
1260
1405
  > => {
1261
1406
  const authProvider = "provider" in authState ? authState.provider : undefined;
1407
+ let sseFetchFailure: unknown;
1408
+ const transportFetch: FetchLike | undefined = kind === "sse" && process.platform === "darwin" && isLiteralLocalAddress(serverUrl)
1409
+ ? async (input, init) => {
1410
+ try {
1411
+ return await (requestFetch ?? globalThis.fetch)(input, init);
1412
+ } catch (error) {
1413
+ // EventSource discards the fetch cause before the SDK creates SseError.
1414
+ if (localNetworkFailureCodes(error).length > 0) sseFetchFailure = error;
1415
+ throw error;
1416
+ }
1417
+ }
1418
+ : requestFetch;
1262
1419
  const transportOptions = {
1263
1420
  ...(requestInit !== undefined ? { requestInit } : {}),
1264
- ...(requestFetch !== undefined ? { fetch: requestFetch } : {}),
1421
+ ...(transportFetch !== undefined ? { fetch: transportFetch } : {}),
1265
1422
  ...(authProvider !== undefined ? { authProvider } : {}),
1266
1423
  ...(authProvider !== undefined
1267
1424
  && definition.oauth !== false
@@ -1281,6 +1438,9 @@ export class McpServerManager {
1281
1438
  await this.connectClientWithAbort(client, transport, requestOptions, signal);
1282
1439
  return { status: "connected", client, transport };
1283
1440
  } catch (error) {
1441
+ if (error instanceof SseError && sseFetchFailure !== undefined) {
1442
+ error = new AggregateError([error, sseFetchFailure], error.message);
1443
+ }
1284
1444
  const abortCleanupFailed = error instanceof AggregateError
1285
1445
  && error.message === "MCP connection abort cleanup failed";
1286
1446
  if (!abortCleanupFailed) {
@@ -1301,7 +1461,24 @@ export class McpServerManager {
1301
1461
  let invalidated = credentialsInvalidated;
1302
1462
  for (;;) {
1303
1463
  const result = await attempt(kind);
1304
- if (result.status === "connected") return { ...result, credentialsInvalidated: invalidated };
1464
+ if (result.status === "connected") {
1465
+ const ownedCa = caFetch;
1466
+ if (ownedCa) {
1467
+ const close = result.transport.close.bind(result.transport);
1468
+ const onclose = result.transport.onclose;
1469
+ result.transport.onclose = () => {
1470
+ void ownedCa.close().catch(error => {
1471
+ logger.debug(`MCP: CA dispatcher cleanup failed for ${serverName}: ${String(error)}`);
1472
+ });
1473
+ onclose?.();
1474
+ };
1475
+ result.transport.close = async () => {
1476
+ try { await close(); } finally { await ownedCa.close(); }
1477
+ };
1478
+ caFetch = undefined;
1479
+ }
1480
+ return { ...result, credentialsInvalidated: invalidated };
1481
+ }
1305
1482
  if (result.error instanceof AggregateError
1306
1483
  && result.error.message === "MCP connection abort cleanup failed") {
1307
1484
  throw result.error;
@@ -1334,6 +1511,9 @@ export class McpServerManager {
1334
1511
  }
1335
1512
  throw result.error;
1336
1513
  }
1514
+ } finally {
1515
+ await caFetch?.close();
1516
+ }
1337
1517
  }
1338
1518
 
1339
1519
  private async fetchAllTools(client: Client, requestOptions?: CacheableRequestOptions): Promise<ToolListResult> {
@@ -1391,9 +1571,9 @@ export class McpServerManager {
1391
1571
  }
1392
1572
  }
1393
1573
 
1394
- private async fetchAllResources(client: Client, requestOptions?: RequestOptions, strict = false): Promise<McpResource[]> {
1574
+ private async fetchAllResources(client: Client, requestOptions?: RequestOptions, strict = false): Promise<ResourceListResult> {
1395
1575
  const capabilities = client.getServerCapabilities?.();
1396
- if (!capabilities?.resources) return [];
1576
+ if (!capabilities?.resources) return { resources: [], failed: false };
1397
1577
 
1398
1578
  try {
1399
1579
  const allResources: McpResource[] = [];
@@ -1405,7 +1585,7 @@ export class McpServerManager {
1405
1585
  cursor = result.nextCursor;
1406
1586
  } while (cursor);
1407
1587
 
1408
- return allResources;
1588
+ return { resources: allResources, failed: false };
1409
1589
  } catch (error) {
1410
1590
  if (requestOptions?.signal?.aborted) {
1411
1591
  throwIfAborted(requestOptions.signal);
@@ -1413,7 +1593,7 @@ export class McpServerManager {
1413
1593
  if (isUnauthorizedHttpError(error)) throw error;
1414
1594
  if (strict) throw error;
1415
1595
  // The server advertises resources but the listing failed
1416
- return [];
1596
+ return { resources: [], failed: true };
1417
1597
  }
1418
1598
  }
1419
1599
 
@@ -1634,10 +1814,17 @@ export class McpServerManager {
1634
1814
  /**
1635
1815
  * Resolve environment variables with interpolation.
1636
1816
  */
1637
- function resolveEnv(env: Record<string, string> | undefined, serverName: string, literalEnv = false): Record<string, string> {
1817
+ function resolveEnv(
1818
+ env: Record<string, string> | undefined,
1819
+ serverName: string,
1820
+ literalEnv = false,
1821
+ inheritEnv = true,
1822
+ ): Record<string, string> {
1638
1823
  const resolved: Record<string, string> = {};
1639
- for (const [key, value] of Object.entries(process.env)) {
1640
- if (value !== undefined) resolved[key] = value;
1824
+ if (inheritEnv) {
1825
+ for (const [key, value] of Object.entries(process.env)) {
1826
+ if (value !== undefined) resolved[key] = value;
1827
+ }
1641
1828
  }
1642
1829
  if (literalEnv) return env ? { ...resolved, ...env } : resolved;
1643
1830