@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
@@ -24,6 +24,10 @@ export function createMcpStatusSnapshot(state: McpExtensionState): McpStatusSnap
24
24
  const activeFailure = !disabled && isServerInActiveFailureBackoff(state, name);
25
25
  const metadata = disabled || activeFailure ? undefined : state.toolMetadata.get(name);
26
26
  const toolCount = metadata?.length ?? (connection?.status === "connected" ? connection.tools.length : 0);
27
+ // This is the last active direct-tool sync result, rather than a fresh
28
+ // resolution. A frozen direct surface may intentionally retain its
29
+ // registrations while the server is in failure backoff.
30
+ const directToolCount = disabled ? 0 : state.directToolCounts?.get(name) ?? 0;
27
31
  const resourceCount = disabled || activeFailure
28
32
  ? undefined
29
33
  : state.resourceCounts?.get(name) ?? (connection?.status === "connected" ? connection.resources.length : undefined);
@@ -50,6 +54,7 @@ export function createMcpStatusSnapshot(state: McpExtensionState): McpStatusSnap
50
54
  status,
51
55
  listenState: connection?.status === "connected" ? connection.listenState : "disconnected",
52
56
  toolCount,
57
+ directToolCount,
53
58
  ...(connection?.status === "connected" && connection.listenCatalogStale ? { catalogStale: true } : {}),
54
59
  ...(resourceCount !== undefined ? { resourceCount } : {}),
55
60
  ...(status === "failed" && failedAgoSeconds !== null ? { failedAgoSeconds } : {}),
@@ -3,6 +3,7 @@ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from "
3
3
  import { dirname } from "node:path";
4
4
  import { getAgentPath } from "./agent-dir.ts";
5
5
  import { createHash } from "node:crypto";
6
+ import { isBuiltInAgentPlugin } from "./agent-plugin-provenance.ts";
6
7
  import { getToolUiResourceUri } from "./ui-app-bridge-helpers.ts";
7
8
  import type {
8
9
  CachedPrompt,
@@ -28,6 +29,7 @@ import {
28
29
  resolveBearerToken,
29
30
  resolveConfigPath,
30
31
  resolveServerUrl,
32
+ stableStringify,
31
33
  } from "./utils.ts";
32
34
  import { extractUiToolVisibility, isUiToolVisibleToModel } from "./ui-tool-visibility.ts";
33
35
 
@@ -87,10 +89,10 @@ export function computeServerHash(definition: ServerEntry, environment: NodeJS.P
87
89
  command: definition.command,
88
90
  args: definition.args,
89
91
  socket: resolveConfigPath(definition.socket, environment),
90
- env: interpolateEnvRecord(definition.env, environment),
91
- cwd: resolveConfigPath(definition.cwd, environment),
92
+ env: isBuiltInAgentPlugin(definition, "env") ? definition.env : interpolateEnvRecord(definition.env, environment),
93
+ cwd: isBuiltInAgentPlugin(definition, "cwd") ? definition.cwd : resolveConfigPath(definition.cwd, environment),
92
94
  url: resolveServerUrl(definition, environment),
93
- headers: interpolateEnvRecord(definition.headers, environment),
95
+ headers: isBuiltInAgentPlugin(definition, "headers") ? definition.headers : interpolateEnvRecord(definition.headers, environment),
94
96
  requestHeadersCommand: definition.requestHeadersCommand
95
97
  ? {
96
98
  command: interpolateEnvVars(definition.requestHeadersCommand.command, environment),
@@ -231,6 +233,7 @@ export function reconstructToolMetadata(
231
233
  originalName: tool.name,
232
234
  description: tool.description ?? "",
233
235
  ...(tool.inputSchema !== undefined ? { inputSchema: tool.inputSchema } : {}),
236
+ ...(tool.outputSchema !== undefined ? { outputSchema: tool.outputSchema } : {}),
234
237
  ...(tool.uiResourceUri !== undefined ? { uiResourceUri: tool.uiResourceUri } : {}),
235
238
  ...(tool.uiVisibility !== undefined ? { uiVisibility: tool.uiVisibility } : {}),
236
239
  ...(tool.uiStreamMode !== undefined ? { uiStreamMode: tool.uiStreamMode } : {}),
@@ -298,6 +301,7 @@ export function serializeTools(tools: McpTool[]): CachedTool[] {
298
301
  name: t.name,
299
302
  ...(t.description !== undefined ? { description: t.description } : {}),
300
303
  ...(t.inputSchema !== undefined ? { inputSchema: t.inputSchema } : {}),
304
+ ...(t.outputSchema !== undefined ? { outputSchema: t.outputSchema } : {}),
301
305
  ...(uiResourceUri !== undefined ? { uiResourceUri } : {}),
302
306
  ...(uiVisibility !== undefined ? { uiVisibility } : {}),
303
307
  ...(uiStreamMode !== undefined ? { uiStreamMode } : {}),
@@ -360,19 +364,6 @@ export function reconstructPromptMetadata(
360
364
  });
361
365
  }
362
366
 
363
- function stableStringify(value: unknown): string {
364
- if (value === null || value === undefined || typeof value !== "object") {
365
- const serialized = JSON.stringify(value);
366
- return serialized === undefined ? "undefined" : serialized;
367
- }
368
- if (Array.isArray(value)) {
369
- return `[${value.map(v => stableStringify(v)).join(",")}]`;
370
- }
371
- const obj = value as Record<string, unknown>;
372
- const keys = Object.keys(obj).sort();
373
- return `{${keys.map(k => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
374
- }
375
-
376
367
  function tryGetToolUiResourceUri(tool: McpTool): string | undefined {
377
368
  try {
378
369
  return getToolUiResourceUri({ _meta: tool._meta });
@@ -3,7 +3,6 @@ import { Type } from "typebox";
3
3
  import type { McpExtensionState } from "./state.ts";
4
4
  import { isServerDisabled, type McpConfig } from "./types.ts";
5
5
  import { isServerCacheValid, type MetadataCache } from "./metadata-cache.ts";
6
- import { executeCall } from "./proxy-modes.ts";
7
6
  import { createMcpProxyToolCallRenderer, createMcpToolResultRenderer, resolveMcpToolRenderOptions, type McpToolRenderOptions, type RenderTheme, type McpToolRenderContext } from "./tool-result-renderer.ts";
8
7
  export { namespaceProxyName } from "./mcp-references.ts";
9
8
  import { hasCallableCachedTargets, isMcpServerDirectlyRegistered, namespaceProxyName, type DirectToolSelectorOverride } from "./mcp-references.ts";
@@ -95,10 +94,22 @@ function resolveNamespaceProxyTools(
95
94
  export type GetState = () => McpExtensionState | null;
96
95
  export type GetInitPromise = () => Promise<McpExtensionState> | null;
97
96
  export type GetPiTools = () => ToolInfo[];
97
+ export type EnsureNamespaceRuntime = (ctx: unknown) => Promise<McpExtensionState | null>;
98
+ export type ExecuteNamespaceCall = (
99
+ state: McpExtensionState,
100
+ toolName: string,
101
+ args: Record<string, unknown>,
102
+ serverName: string,
103
+ getPiTools: GetPiTools,
104
+ signal: AbortSignal | undefined,
105
+ origin: "proxy",
106
+ ) => Promise<AgentToolResult<Record<string, unknown>>>;
98
107
 
99
108
  function namespaceExecute(
100
109
  getState: GetState,
101
110
  getInitPromise: GetInitPromise,
111
+ ensureRuntime: EnsureNamespaceRuntime | undefined,
112
+ executeCall: ExecuteNamespaceCall,
102
113
  serverName: string,
103
114
  getPiTools: GetPiTools,
104
115
  ) {
@@ -106,6 +117,8 @@ function namespaceExecute(
106
117
  _toolCallId: string,
107
118
  params: { tool?: string; args?: Record<string, unknown> },
108
119
  signal: AbortSignal | undefined,
120
+ _onUpdate: unknown,
121
+ ctx: unknown,
109
122
  ): Promise<AgentToolResult<Record<string, unknown>>> => {
110
123
  if (typeof params.tool !== "string" || params.tool.length === 0) {
111
124
  return {
@@ -114,6 +127,17 @@ function namespaceExecute(
114
127
  };
115
128
  }
116
129
  let state = getState();
130
+ if (!state && ensureRuntime) {
131
+ try {
132
+ state = await ensureRuntime(ctx);
133
+ } catch (error) {
134
+ const message = error instanceof Error ? error.message : String(error);
135
+ return {
136
+ content: [{ type: "text" as const, text: `MCP initialization failed for ${serverName}: ${message}` }],
137
+ details: { error: "init_failed", server: serverName, message },
138
+ };
139
+ }
140
+ }
117
141
  if (!state) {
118
142
  const initPromise = getInitPromise();
119
143
  if (initPromise) {
@@ -150,7 +174,7 @@ const parameters = Type.Object({
150
174
  tool: Type.String({ description: "Underlying MCP tool name to call on this server." }),
151
175
  args: Type.Optional(Type.Object({}, {
152
176
  additionalProperties: true,
153
- description: "Arguments for the underlying tool. The exact shape depends on the tool being called; use mcp({ describe: 'server/tool' }) to inspect.",
177
+ description: "Arguments for the underlying tool. When mcp is available, use mcp({ search: '...' }) to inspect schemas; for unique names use mcp({ describe: 'tool_name' }) with the exact tool name returned by search.",
154
178
  })),
155
179
  });
156
180
 
@@ -165,10 +189,14 @@ export interface SyncNamespaceProxyToolsInput {
165
189
  pi: ExtensionAPI;
166
190
  getState: GetState;
167
191
  getInitPromise: GetInitPromise;
192
+ ensureRuntime?: EnsureNamespaceRuntime;
193
+ executeCall?: ExecuteNamespaceCall;
168
194
  getPiTools: GetPiTools;
169
195
  renderOptions?: McpToolRenderOptions;
170
196
  renderShell?: "self" | "default";
171
197
  renderResult?: unknown;
198
+ guardReentrant?: () => void;
199
+ onToolRegistered?: (name: string) => void;
172
200
  }
173
201
 
174
202
  export interface SyncNamespaceProxyToolsResult {
@@ -187,6 +215,15 @@ function createNamespaceRenderCall(renderOptions: McpToolRenderOptions, serverNa
187
215
  }, theme, context);
188
216
  }
189
217
 
218
+ let defaultProxyModesPromise: Promise<typeof import("./proxy-modes.ts")> | null = null;
219
+ async function loadDefaultExecuteCall(): Promise<(typeof import("./proxy-modes.ts"))["executeCall"]> {
220
+ defaultProxyModesPromise ??= import("./proxy-modes.ts").catch((error) => {
221
+ defaultProxyModesPromise = null;
222
+ throw error;
223
+ });
224
+ return (await defaultProxyModesPromise).executeCall;
225
+ }
226
+
190
227
  function registerNamespaceProxyTool(
191
228
  input: SyncNamespaceProxyToolsInput,
192
229
  spec: NamespaceProxySpec,
@@ -194,6 +231,17 @@ function registerNamespaceProxyTool(
194
231
  renderShell: "self" | "default",
195
232
  renderResult: unknown,
196
233
  ): void {
234
+ const executeCall = input.executeCall ?? (async (...args: Parameters<ExecuteNamespaceCall>) => {
235
+ const expectedState = args[0];
236
+ const loadedExecuteCall = await loadDefaultExecuteCall();
237
+ if (input.getState() !== expectedState || expectedState.owner?.isActive() === false) {
238
+ throw expectedState.owner?.signal.reason ?? new Error("MCP namespace operation belongs to a stale session");
239
+ }
240
+ return loadedExecuteCall(...args);
241
+ });
242
+
243
+ input.onToolRegistered?.(spec.toolName);
244
+ input.guardReentrant?.();
197
245
  (input.pi.registerTool as (tool: unknown) => unknown)({
198
246
  name: spec.toolName,
199
247
  label: `MCP: ${spec.serverName}`,
@@ -206,10 +254,13 @@ function registerNamespaceProxyTool(
206
254
  execute: namespaceExecute(
207
255
  input.getState,
208
256
  input.getInitPromise,
257
+ input.ensureRuntime,
258
+ executeCall,
209
259
  spec.serverName,
210
260
  input.getPiTools,
211
261
  ),
212
262
  });
263
+ input.guardReentrant?.();
213
264
  }
214
265
 
215
266
  function getActiveToolsForStaleCleanup(pi: ExtensionAPI, staleNames: string[]): string[] | undefined {
@@ -232,16 +283,23 @@ function deactivateStaleNamespaceTools(input: SyncNamespaceProxyToolsInput, next
232
283
  unregisterTool?: (name: string) => boolean;
233
284
  }).unregisterTool;
234
285
  for (const stale of staleNames) {
235
- if (unregisterTool?.(stale)) deactivated.push(stale);
286
+ input.guardReentrant?.();
287
+ const removed = unregisterTool?.(stale);
288
+ input.guardReentrant?.();
289
+ if (removed) deactivated.push(stale);
236
290
  }
291
+ input.guardReentrant?.();
237
292
  const activeTools = getActiveToolsForStaleCleanup(input.pi, staleNames);
293
+ input.guardReentrant?.();
238
294
  if (!activeTools) return deactivated;
239
295
 
240
296
  const stale = new Set(staleNames);
241
297
  const nextActiveTools = activeTools.filter((name) => !stale.has(name));
242
298
  if (nextActiveTools.length === activeTools.length) return deactivated;
243
299
 
300
+ input.guardReentrant?.();
244
301
  input.pi.setActiveTools(nextActiveTools);
302
+ input.guardReentrant?.();
245
303
  for (const name of staleNames) {
246
304
  if (!deactivated.includes(name)) deactivated.push(name);
247
305
  }
@@ -6,12 +6,16 @@ import {
6
6
  type StoredTokens,
7
7
  } from "./mcp-auth.ts";
8
8
 
9
+ import type { ServerEntry } from "./types.ts";
10
+
9
11
  export type McpOAuthTokens = StoredTokens;
10
12
  export type McpOAuthStorageOptions = AuthStorageOptions;
11
13
  export interface McpOAuthTokenOptions {
12
14
  authStorageOptions?: McpOAuthStorageOptions;
13
15
  signal?: AbortSignal;
14
16
  skipIssuerMetadataValidation?: boolean;
17
+ /** Explicit refresh configuration; never loaded from ambient config or stored with tokens. */
18
+ definition?: Pick<ServerEntry, "headers" | "oauth">;
15
19
  }
16
20
  export type McpOAuthTokenStatus =
17
21
  | { status: "present"; tokens: McpOAuthTokens }
@@ -38,11 +42,11 @@ export function inspectMcpOAuthTokensForUrl(
38
42
  : { status: "absent" };
39
43
  }
40
44
 
41
- export function updateMcpOAuthTokensForUrl(
45
+ export async function updateMcpOAuthTokensForUrl(
42
46
  serverName: string,
43
47
  serverUrl: string,
44
48
  tokens: McpOAuthTokens,
45
49
  options?: McpOAuthStorageOptions,
46
- ): void {
50
+ ): Promise<void> {
47
51
  updateTokens(serverName, tokens, serverUrl, options);
48
52
  }
@@ -1,8 +1,8 @@
1
- import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
2
- import { isAbsolute, join, relative, resolve, sep } from "node:path";
3
- import stripJsonComments from "strip-json-comments";
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { isAbsolute, join, resolve } from "node:path";
4
3
  import { getAgentDir, getConfigDirName } from "./agent-dir.ts";
5
4
  import type { McpConfig, ServerEntry } from "./types.ts";
5
+ import { parseJsonWithComments, resolveContainedPath, resolveRealContainedPath } from "./utils.ts";
6
6
 
7
7
  interface PackageSetting {
8
8
  source: string;
@@ -130,7 +130,7 @@ function readMcpConfig(path: string, packageName: string): McpConfig | null {
130
130
 
131
131
  function readRequiredJson(path: string, description: string): unknown {
132
132
  try {
133
- return JSON.parse(stripJsonComments(readFileSync(path, "utf8"), { trailingCommas: true }));
133
+ return parseJsonWithComments(readFileSync(path, "utf8"));
134
134
  } catch (error) {
135
135
  throw new Error(`${description} ${path} contains invalid JSON: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
136
136
  }
@@ -141,22 +141,10 @@ function readOptionalJson(path: string, description: string): unknown | undefine
141
141
  return readRequiredJson(path, description);
142
142
  }
143
143
 
144
- function resolveContainedPath(root: string, path: string): string | null {
145
- const resolved = resolve(root, path);
146
- const rel = relative(root, resolved);
147
- return rel === "" || (!rel.startsWith("..") && !rel.startsWith(sep) && !isAbsolute(rel)) ? resolved : null;
148
- }
149
-
150
144
  function resolvePackageConfigPath(packageRoot: string, path: string): string | null {
151
145
  const lexicalPath = resolveContainedPath(packageRoot, path);
152
146
  if (!lexicalPath || !existsSync(lexicalPath) || !statSync(lexicalPath).isFile()) return null;
153
- try {
154
- const realPackageRoot = realpathSync(packageRoot);
155
- const realConfigPath = realpathSync(lexicalPath);
156
- return resolveContainedPath(realPackageRoot, realConfigPath);
157
- } catch {
158
- return null;
159
- }
147
+ return resolveRealContainedPath(packageRoot, lexicalPath);
160
148
  }
161
149
 
162
150
  function formatPackageName(name: string): string {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mcp-adapter",
3
- "version": "2.32.1",
3
+ "version": "2.34.0",
4
4
  "description": "MCP (Model Context Protocol) adapter extension for Pi coding agent",
5
5
  "type": "module",
6
6
  "types": "./index.ts",
@@ -48,7 +48,7 @@
48
48
  "test:public-exports": "npm run build:public && node --test public-exports.test.mjs",
49
49
  "test:watch": "vitest",
50
50
  "test:coverage": "vitest run --coverage",
51
- "test:oauth": "PI_MCP_ADAPTER_TEST_AUTH_STORE=memory PI_MCP_ADAPTER_DISABLE_AUTH_CACHE=1 node --import tsx --test --test-concurrency=1 oauth-public-api.test.ts mcp-auth.test.ts mcp-auth-flow.test.ts mcp-callback-server.test.ts mcp-oauth-provider.test.ts",
51
+ "test:oauth": "PI_MCP_ADAPTER_TEST_AUTH_STORE=memory PI_MCP_ADAPTER_DISABLE_AUTH_CACHE=1 node --import tsx --test --test-concurrency=1 oauth-public-api.test.ts mcp-auth.test.ts mcp-auth-flow.test.ts mcp-auth-flow-timeout.test.ts mcp-auth-fetch.test.ts mcp-callback-server.test.ts mcp-oauth-provider.test.ts mcp-local-oauth-flow.test.ts",
52
52
  "test:oauth-provider": "PI_MCP_ADAPTER_TEST_AUTH_STORE=memory PI_MCP_ADAPTER_DISABLE_AUTH_CACHE=1 node --import tsx --test mcp-oauth-provider.test.ts",
53
53
  "test:conformance": "bash conformance/run.sh"
54
54
  },
@@ -89,11 +89,14 @@
89
89
  "ts-shape.ts",
90
90
  "search-ranking.ts",
91
91
  "tool-approval.ts",
92
+ "session-approvals.ts",
92
93
  "init.ts",
93
94
  "failure-backoff.ts",
94
95
  "ui-session.ts",
95
96
  "proxy-modes.ts",
96
97
  "direct-tools.ts",
98
+ "direct-tool-surface.ts",
99
+ "lazy-loader.ts",
97
100
  "namespace-tools.ts",
98
101
  "mcp-references.ts",
99
102
  "commands.ts",
@@ -105,8 +108,10 @@
105
108
  "ui-tool-visibility.ts",
106
109
  "ui-app-bridge-helpers.ts",
107
110
  "config.ts",
111
+ "claude-plugin-loader.ts",
108
112
  "package-mcp-loader.ts",
109
113
  "server-manager.ts",
114
+ "http-ca.ts",
110
115
  "request-headers-command.ts",
111
116
  "unix-socket-transport.ts",
112
117
  "json-schema-validator.ts",
@@ -119,7 +124,9 @@
119
124
  "mcp-code.ts",
120
125
  "mcp-script-worker.mjs",
121
126
  "mcp-probe.ts",
127
+ "mcp-install.ts",
122
128
  "agent-plugin-loader.ts",
129
+ "agent-plugin-provenance.ts",
123
130
  "resource-tools.ts",
124
131
  "lifecycle.ts",
125
132
  "mcp-status.ts",
@@ -139,7 +146,9 @@
139
146
  "mcp-oauth-provider.ts",
140
147
  "mcp-callback-server.ts",
141
148
  "mcp-auth-flow.ts",
149
+ "mcp-auth-fetch.ts",
142
150
  "mcp-panel.ts",
151
+ "mcp-panel-theme.ts",
143
152
  "panel-keys.ts",
144
153
  "mcp-trace.ts",
145
154
  "logger.ts",
@@ -164,10 +173,11 @@
164
173
  "recheck": "^4.5.0",
165
174
  "smol-toml": "^1.6.1",
166
175
  "strip-json-comments": "^5.0.3",
176
+ "undici": "^6.28.1",
167
177
  "zod": "^3.25.0 || ^4.0.0"
168
178
  },
169
179
  "peerDependencies": {
170
- "@earendil-works/pi-ai": "^0.84.1",
180
+ "@earendil-works/pi-ai": "^0.84.1 || ^0.85.0",
171
181
  "@earendil-works/pi-tui": "*",
172
182
  "typebox": "*",
173
183
  "zod": "^3.25.0 || ^4.0.0"
@@ -6,7 +6,6 @@ import type {
6
6
  import type { McpExtensionState } from "./state.ts";
7
7
  import { isServerDisabled, type McpConfig, type PromptMetadata } from "./types.ts";
8
8
  import { formatPromptCommandName } from "./types.ts";
9
- import { lazyConnect } from "./init.ts";
10
9
  import { isServerCacheValid, loadMetadataCache, reconstructPromptMetadata } from "./metadata-cache.ts";
11
10
  import { logger } from "./logger.ts";
12
11
  import { truncateAtWord } from "./utils.ts";
@@ -226,17 +225,41 @@ function extractMessageText(message: PromptMessage): string {
226
225
  * per prompt at extension load time from the metadata cache; the same
227
226
  * factory is reused by tests to invoke the handler without a running pi.
228
227
  */
228
+ export interface PromptRuntimeDependencies {
229
+ ensureState?: (ctx: ExtensionCommandContext) => Promise<McpExtensionState | null>;
230
+ lazyConnect?: (state: McpExtensionState, serverName: string, signal?: AbortSignal) => Promise<boolean>;
231
+ isStateCurrent?: (state: McpExtensionState) => boolean;
232
+ }
233
+
234
+ let defaultRuntimePromise: Promise<typeof import("./init.ts")> | null = null;
235
+ function loadDefaultRuntime(): Promise<typeof import("./init.ts")> {
236
+ return defaultRuntimePromise ??= import("./init.ts").catch((error) => {
237
+ defaultRuntimePromise = null;
238
+ throw error;
239
+ });
240
+ }
241
+
229
242
  export function createPromptCommand(
230
243
  pi: ExtensionAPI,
231
244
  getState: () => McpExtensionState | null,
232
245
  metadata: PromptMetadata,
246
+ runtime: PromptRuntimeDependencies = {},
233
247
  ) {
234
248
  const description = buildCommandDescription(metadata);
235
249
 
236
250
  return {
237
251
  description,
238
252
  handler: async (args: string, ctx: ExtensionCommandContext) => {
239
- const state = getState();
253
+ let state = getState();
254
+ if (!state && runtime.ensureState) {
255
+ try {
256
+ state = await runtime.ensureState(ctx);
257
+ } catch (error) {
258
+ const message = error instanceof Error ? error.message : String(error);
259
+ if (ctx.hasUI) ctx.ui.notify(`MCP initialization failed: ${message}`, "error");
260
+ return;
261
+ }
262
+ }
240
263
  if (!state) {
241
264
  if (ctx.hasUI) ctx.ui.notify("MCP not initialized", "error");
242
265
  return;
@@ -271,7 +294,14 @@ export function createPromptCommand(
271
294
  return;
272
295
  }
273
296
 
274
- const connected = await lazyConnect(state, metadata.serverName, ctx.signal);
297
+ const expectedState = state;
298
+ const isStateCurrent = () => runtime.isStateCurrent
299
+ ? runtime.isStateCurrent(expectedState)
300
+ : getState() === expectedState;
301
+ const lazyConnect = runtime.lazyConnect ?? (await loadDefaultRuntime()).lazyConnect;
302
+ if (!isStateCurrent()) return;
303
+ const connected = await lazyConnect(expectedState, metadata.serverName, ctx.signal);
304
+ if (!isStateCurrent()) return;
275
305
  if (!connected) {
276
306
  if (ctx.hasUI) {
277
307
  const conn = state.manager.getConnection(metadata.serverName);
@@ -305,12 +335,14 @@ export function createPromptCommand(
305
335
  } catch (error) {
306
336
  const message = error instanceof Error ? error.message : String(error);
307
337
  logger.debug(`MCP prompt "${live.originalName}" on ${metadata.serverName} failed: ${message}`);
338
+ if (!isStateCurrent()) return;
308
339
  if (ctx.hasUI) {
309
340
  ctx.ui.notify(`MCP prompt "${live.originalName}" failed: ${message}`, "error");
310
341
  }
311
342
  return;
312
343
  }
313
344
 
345
+ if (!isStateCurrent()) return;
314
346
  const text = formatPromptResult(result);
315
347
  if (!text) {
316
348
  if (ctx.hasUI) {
@@ -3,7 +3,7 @@ import { UrlElicitationRequiredError, type Client, type Progress, type RequestOp
3
3
  import { createRequire } from "node:module";
4
4
  import type { McpExtensionState } from "./state.ts";
5
5
  import type { ToolMetadata, McpContent } from "./types.ts";
6
- import { getServerPrefix, isServerDisabled, parseUiPromptHandoff } from "./types.ts";
6
+ import { getServerPrefix, isServerDisabled, parseUiPromptHandoff, type ServerEntry } from "./types.ts";
7
7
  import { lazyConnect, markKeepAliveAfterConnect, notifyToolMetadataUpdated, updateServerMetadata, updateMetadataCache, getFailureAgeSeconds, updateStatusBar, clearFailure, recordFailure } from "./init.ts";
8
8
  import { abortable, throwIfAborted } from "./abort.ts";
9
9
  import { combineAbortSignals, isAbortError } from "./runtime-owner.ts";
@@ -14,7 +14,7 @@ import { resolveMcpResultContent, transformMcpContent, transformMcpResourceConte
14
14
  import { guardMcpOutput, guardedMcpDetails, resolveMcpOutputGuardOptions } from "./mcp-output-guard.ts";
15
15
  import { maybeStartUiSession, summarizeUiSessionResult, type UiSessionRuntime } from "./ui-session.ts";
16
16
  import { formatAuthRequiredMessage, formatMcpStatus, normalizeToolArguments, resolveServerUrl, truncateAtWord } from "./utils.ts";
17
- import { authenticate, completeAuthFromInput, startAuth, supportsOAuth } from "./mcp-auth-flow.ts";
17
+ import { authenticate, completeAuthFromInput, getAuthStatus, startAuth, supportsOAuth } from "./mcp-auth-flow.ts";
18
18
  import { SessionRecoveryAuthRequiredError, withSessionRecovery } from "./session-recovery.ts";
19
19
  import { paginate, rankSuggestions, rankToolMatches, resolveSearchKeywords } from "./search-ranking.ts";
20
20
  import { ensureToolCallApproved, isToolCallApprovalRequired } from "./tool-approval.ts";
@@ -34,6 +34,8 @@ const REGEX_SAFETY_CHECK_PARAMS = {
34
34
  timeout: 250,
35
35
  } as const;
36
36
 
37
+ const backgroundAuthWatchers = new WeakMap<McpExtensionState, Map<string, Promise<void>>>();
38
+
37
39
  type AutoAuthResult =
38
40
  | { status: "skipped" }
39
41
  | { status: "success" }
@@ -128,6 +130,76 @@ function disabledResult(mode: string, serverName: string): ProxyToolResult {
128
130
  };
129
131
  }
130
132
 
133
+ function emitAuthStatus(state: McpExtensionState, serverName: string, status: "authenticated" | "failed", message: string): void {
134
+ state.ui?.notify(message, status === "authenticated" ? "info" : "error");
135
+ state.sendMessage?.(
136
+ {
137
+ customType: "mcp-oauth-status",
138
+ content: [{ type: "text", text: message }],
139
+ display: message,
140
+ details: {
141
+ server: serverName,
142
+ status,
143
+ ...(status === "authenticated" ? { nextAction: { connect: serverName } } : {}),
144
+ },
145
+ },
146
+ { triggerTurn: true },
147
+ );
148
+ }
149
+
150
+ function ensureBackgroundAuthWatcher(
151
+ state: McpExtensionState,
152
+ serverName: string,
153
+ serverUrl: string,
154
+ definition: ServerEntry,
155
+ ): void {
156
+ let watchers = backgroundAuthWatchers.get(state);
157
+ if (!watchers) {
158
+ watchers = new Map();
159
+ backgroundAuthWatchers.set(state, watchers);
160
+ }
161
+ if (watchers.has(serverName)) return;
162
+
163
+ const authOptions = {
164
+ authStorageOptions: state.authStorageOptions,
165
+ runtime: state.oauthRuntime,
166
+ openAuthorizationUrl: state.openBrowser,
167
+ };
168
+ const watcher = authenticate(serverName, serverUrl, definition, authOptions).then(async (status) => {
169
+ throwIfAborted(state.owner.signal);
170
+ if (status !== "authenticated") throw new Error(`OAuth authentication ended with status: ${status}`);
171
+ await state.manager.close(serverName);
172
+ throwIfAborted(state.owner.signal);
173
+ clearFailure(state, serverName, "auth-background-complete");
174
+ updateStatusBar(state);
175
+ emitAuthStatus(
176
+ state,
177
+ serverName,
178
+ "authenticated",
179
+ `OAuth authentication completed for MCP server "${serverName}". Connect it now to load and verify its tools.`,
180
+ );
181
+ });
182
+ watchers.set(serverName, watcher);
183
+
184
+ void watcher.catch(async (error) => {
185
+ if (isAbortError(error, state.owner.signal)) return;
186
+ try {
187
+ if (await getAuthStatus(serverName, authOptions) === "authenticated") return;
188
+ } catch (statusError) {
189
+ if (isAbortError(statusError, state.owner.signal)) return;
190
+ }
191
+ if (state.owner.signal.aborted) return;
192
+ emitAuthStatus(
193
+ state,
194
+ serverName,
195
+ "failed",
196
+ `OAuth authentication failed for MCP server "${serverName}". Start authorization again or inspect trusted logs.`,
197
+ );
198
+ }).finally(() => {
199
+ if (watchers?.get(serverName) === watcher) watchers.delete(serverName);
200
+ });
201
+ }
202
+
131
203
  function getAuthRequiredMessage(
132
204
  state: McpExtensionState,
133
205
  serverName: string,
@@ -165,18 +237,20 @@ function formatManualAuthInstructions(serverName: string, authorizationUrl: stri
165
237
  const redirect = getRedirectDetails(authorizationUrl);
166
238
  const redirectNote = redirect.remote
167
239
  ? "The provider uses a pre-registered HTTPS callback. Copy its full URL from the browser address bar, even if the destination page reports an error."
168
- : redirect.port
169
- ? `The redirect URL will use local port ${redirect.port}. On a remote server it is expected for that localhost page to fail locally; copy the address bar URL anyway.`
170
- : "";
240
+ : `The adapter is watching${redirect.port ? ` local port ${redirect.port}` : " the local callback"} and will report when authentication completes.`;
171
241
 
172
242
  return [
173
243
  `MCP OAuth required for "${serverName}".`,
174
244
  "",
175
- "Open this URL in your local browser:",
245
+ redirect.remote
246
+ ? "The adapter is attempting to open this authorization URL in your local browser:"
247
+ : "The adapter is attempting to open this authorization URL and watching for its callback:",
176
248
  "",
177
249
  authorizationUrl,
178
250
  "",
179
- "After approving, copy the full callback URL from your browser address bar and send it back with:",
251
+ redirect.remote
252
+ ? "After approving, copy the full callback URL from your browser address bar and send it back with:"
253
+ : "If the browser does not open or the callback is not detected, open the URL above and complete manually with:",
180
254
  `mcp({ action: "auth-complete", server: "${serverName}", args: { redirectUrl: "PASTE_REDIRECT_URL_HERE" } })`,
181
255
  "",
182
256
  redirect.remote
@@ -466,6 +540,16 @@ export async function executeAuthStart(state: McpExtensionState, serverName: str
466
540
  };
467
541
  }
468
542
 
543
+ if (getRedirectDetails(authorizationUrl).remote) {
544
+ try {
545
+ await state.openBrowser(authorizationUrl);
546
+ } catch (error) {
547
+ if (isAbortError(error, ownedSignal)) throw error;
548
+ }
549
+ } else {
550
+ ensureBackgroundAuthWatcher(state, serverName, serverUrl, definition);
551
+ }
552
+
469
553
  return {
470
554
  content: [{ type: "text" as const, text: formatManualAuthInstructions(serverName, authorizationUrl) }],
471
555
  details: { mode: "auth-start", server: serverName, authorizationUrl },
@@ -926,6 +1010,8 @@ export async function executeCall(
926
1010
  getPiTools?: () => ToolInfo[],
927
1011
  signal?: AbortSignal,
928
1012
  origin?: "proxy" | "script",
1013
+ // Internal consumers own successful data delivery; origin remains approval metadata only.
1014
+ internalDelivery?: { onSuccess: (data: unknown) => void },
929
1015
  ): Promise<ProxyToolResult> {
930
1016
  const ownedSignal = combineAbortSignals(state.owner?.signal, signal);
931
1017
  throwIfAborted(ownedSignal);
@@ -1345,6 +1431,13 @@ export async function executeCall(
1345
1431
  },
1346
1432
  );
1347
1433
  const content = transformMcpResourceContents(result.contents ?? [], state.owner?.signal);
1434
+ if (internalDelivery) {
1435
+ throwIfAborted(ownedSignal);
1436
+ internalDelivery.onSuccess(content.length > 0
1437
+ ? content.filter(block => block.type === "text").map(block => block.text).join("\n")
1438
+ : "(empty resource)");
1439
+ return { content: [], details: { mode: "call", ...callIdentity } };
1440
+ }
1348
1441
  const guarded = await guardMcpOutput(content.length > 0 ? content : [{ type: "text" as const, text: "(empty resource)" }], outputGuardOptions);
1349
1442
  return {
1350
1443
  content: guarded.content,
@@ -1384,7 +1477,15 @@ export async function executeCall(
1384
1477
 
1385
1478
  if (toolMeta.uiResourceUri) {
1386
1479
  uiSession?.sendToolResult(result as unknown as import("@modelcontextprotocol/client").CallToolResult);
1480
+ }
1481
+
1482
+ if (!result.isError && internalDelivery) {
1483
+ throwIfAborted(ownedSignal);
1484
+ internalDelivery.onSuccess(result);
1485
+ return { content: [], details: { mode: "call", ...callIdentity } };
1486
+ }
1387
1487
 
1488
+ if (toolMeta.uiResourceUri) {
1388
1489
  if (result.isError) {
1389
1490
  const mcpContent = (result.content ?? []) as McpContent[];
1390
1491
  const content = transformMcpContent(mcpContent, state.owner?.signal);