@humain/terminal 0.1.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (197) hide show
  1. package/CHANGELOG.md +22 -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-LYAMSPR2.js → chunk-ADQYVZ6A.js} +483 -506
  6. package/dist/bundle/chunks/chunk-D6HHRSB5.js +103 -0
  7. package/dist/bundle/chunks/{chunk-JUPSJTFQ.js → chunk-K4JAZYQH.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-HPPQHK3J.js → chunk-KKIB2TVR.js} +1 -1
  9. package/dist/bundle/chunks/chunk-LI2YVPWP.js +2 -0
  10. package/dist/bundle/chunks/chunk-NMPIM7MN.js +2 -0
  11. package/dist/bundle/chunks/{chunk-BZQ53VGE.js → chunk-XSR434CO.js} +1 -1
  12. package/dist/bundle/chunks/compat-CN7WN6DA.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/model-runtime.d.ts +21 -0
  30. package/dist/core/model-runtime.d.ts.map +1 -1
  31. package/dist/core/model-runtime.js +34 -4
  32. package/dist/core/model-runtime.js.map +1 -1
  33. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  34. package/dist/core/tools/file-mutation-queue.js +21 -8
  35. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  36. package/dist/core/tools/subagent-env.d.ts +20 -0
  37. package/dist/core/tools/subagent-env.d.ts.map +1 -0
  38. package/dist/core/tools/subagent-env.js +47 -0
  39. package/dist/core/tools/subagent-env.js.map +1 -0
  40. package/dist/core/tools/subagent-scheduler.d.ts +36 -0
  41. package/dist/core/tools/subagent-scheduler.d.ts.map +1 -0
  42. package/dist/core/tools/subagent-scheduler.js +213 -0
  43. package/dist/core/tools/subagent-scheduler.js.map +1 -0
  44. package/dist/core/tools/subagent.d.ts +83 -3
  45. package/dist/core/tools/subagent.d.ts.map +1 -1
  46. package/dist/core/tools/subagent.js +215 -76
  47. package/dist/core/tools/subagent.js.map +1 -1
  48. package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
  49. package/dist/humain/mcp-adapter/vendor/README.md +125 -19
  50. package/dist/humain/mcp-adapter/vendor/agent-plugin-loader.ts +69 -30
  51. package/dist/humain/mcp-adapter/vendor/agent-plugin-provenance.ts +36 -0
  52. package/dist/humain/mcp-adapter/vendor/claude-plugin-loader.ts +365 -0
  53. package/dist/humain/mcp-adapter/vendor/commands.ts +13 -15
  54. package/dist/humain/mcp-adapter/vendor/config.ts +198 -30
  55. package/dist/humain/mcp-adapter/vendor/consent-manager.ts +40 -9
  56. package/dist/humain/mcp-adapter/vendor/direct-tool-surface.ts +250 -0
  57. package/dist/humain/mcp-adapter/vendor/direct-tools.ts +9 -228
  58. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js +74 -32
  59. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js.map +1 -1
  60. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.d.ts +1 -0
  61. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js +31 -0
  62. package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js.map +1 -0
  63. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.d.ts +9 -0
  64. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js +362 -0
  65. package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js.map +1 -0
  66. package/dist/humain/mcp-adapter/vendor/dist/config.d.ts +3 -1
  67. package/dist/humain/mcp-adapter/vendor/dist/config.js +189 -27
  68. package/dist/humain/mcp-adapter/vendor/dist/config.js.map +1 -1
  69. package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js +7 -16
  70. package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js.map +1 -1
  71. package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js +5 -17
  72. package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js.map +1 -1
  73. package/dist/humain/mcp-adapter/vendor/dist/types.d.ts +30 -4
  74. package/dist/humain/mcp-adapter/vendor/dist/types.js +26 -0
  75. package/dist/humain/mcp-adapter/vendor/dist/types.js.map +1 -1
  76. package/dist/humain/mcp-adapter/vendor/dist/utils.d.ts +6 -0
  77. package/dist/humain/mcp-adapter/vendor/dist/utils.js +65 -6
  78. package/dist/humain/mcp-adapter/vendor/dist/utils.js.map +1 -1
  79. package/dist/humain/mcp-adapter/vendor/host-html-template.ts +20 -19
  80. package/dist/humain/mcp-adapter/vendor/http-ca.ts +84 -0
  81. package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +37102 -15103
  82. package/dist/humain/mcp-adapter/vendor/index.ts +828 -164
  83. package/dist/humain/mcp-adapter/vendor/init.ts +48 -10
  84. package/dist/humain/mcp-adapter/vendor/lazy-loader.ts +7 -0
  85. package/dist/humain/mcp-adapter/vendor/mcp-auth-fetch.ts +113 -0
  86. package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +256 -77
  87. package/dist/humain/mcp-adapter/vendor/mcp-auth.ts +316 -54
  88. package/dist/humain/mcp-adapter/vendor/mcp-code.ts +45 -18
  89. package/dist/humain/mcp-adapter/vendor/mcp-install.ts +60 -0
  90. package/dist/humain/mcp-adapter/vendor/mcp-oauth-provider.ts +70 -6
  91. package/dist/humain/mcp-adapter/vendor/mcp-output-guard.ts +53 -42
  92. package/dist/humain/mcp-adapter/vendor/mcp-panel-theme.ts +105 -0
  93. package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +366 -331
  94. package/dist/humain/mcp-adapter/vendor/mcp-references.ts +9 -14
  95. package/dist/humain/mcp-adapter/vendor/mcp-script-worker.mjs +3 -1
  96. package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +383 -341
  97. package/dist/humain/mcp-adapter/vendor/mcp-status.ts +5 -0
  98. package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +7 -16
  99. package/dist/humain/mcp-adapter/vendor/namespace-tools.ts +61 -3
  100. package/dist/humain/mcp-adapter/vendor/oauth.ts +6 -2
  101. package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +5 -17
  102. package/dist/humain/mcp-adapter/vendor/package.json +13 -3
  103. package/dist/humain/mcp-adapter/vendor/prompts.ts +35 -3
  104. package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +108 -7
  105. package/dist/humain/mcp-adapter/vendor/runtime-owner.ts +16 -1
  106. package/dist/humain/mcp-adapter/vendor/sampling-handler.ts +9 -32
  107. package/dist/humain/mcp-adapter/vendor/sandbox-proxy-template.ts +20 -93
  108. package/dist/humain/mcp-adapter/vendor/server-manager.ts +227 -40
  109. package/dist/humain/mcp-adapter/vendor/session-approvals.ts +187 -0
  110. package/dist/humain/mcp-adapter/vendor/skills/mcp-scripting/SKILL.md +9 -1
  111. package/dist/humain/mcp-adapter/vendor/state.ts +10 -1
  112. package/dist/humain/mcp-adapter/vendor/tool-approval.ts +10 -22
  113. package/dist/humain/mcp-adapter/vendor/tool-metadata.ts +14 -0
  114. package/dist/humain/mcp-adapter/vendor/types.ts +57 -4
  115. package/dist/humain/mcp-adapter/vendor/ui-server.ts +36 -28
  116. package/dist/humain/mcp-adapter/vendor/utils.ts +64 -6
  117. package/dist/humain/web-access/vendor/abortable.d.ts +3 -0
  118. package/dist/humain/web-access/vendor/abortable.d.ts.map +1 -0
  119. package/dist/humain/web-access/vendor/abortable.js +22 -0
  120. package/dist/humain/web-access/vendor/abortable.js.map +1 -0
  121. package/dist/humain/web-access/vendor/crawl4ai.d.ts +9 -0
  122. package/dist/humain/web-access/vendor/crawl4ai.d.ts.map +1 -0
  123. package/dist/humain/web-access/vendor/crawl4ai.js +187 -0
  124. package/dist/humain/web-access/vendor/crawl4ai.js.map +1 -0
  125. package/dist/humain/web-access/vendor/credential-source.d.ts.map +1 -1
  126. package/dist/humain/web-access/vendor/credential-source.js +1 -0
  127. package/dist/humain/web-access/vendor/credential-source.js.map +1 -1
  128. package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
  129. package/dist/humain/web-access/vendor/curator-page.js +20 -2
  130. package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
  131. package/dist/humain/web-access/vendor/curator-run.d.ts +11 -0
  132. package/dist/humain/web-access/vendor/curator-run.d.ts.map +1 -0
  133. package/dist/humain/web-access/vendor/curator-run.js +38 -0
  134. package/dist/humain/web-access/vendor/curator-run.js.map +1 -0
  135. package/dist/humain/web-access/vendor/curator-server.d.ts +1 -0
  136. package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
  137. package/dist/humain/web-access/vendor/curator-server.js +2 -0
  138. package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
  139. package/dist/humain/web-access/vendor/duckduckgo.d.ts.map +1 -1
  140. package/dist/humain/web-access/vendor/duckduckgo.js +1 -1
  141. package/dist/humain/web-access/vendor/duckduckgo.js.map +1 -1
  142. package/dist/humain/web-access/vendor/extract.d.ts +1 -1
  143. package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
  144. package/dist/humain/web-access/vendor/extract.js +58 -11
  145. package/dist/humain/web-access/vendor/extract.js.map +1 -1
  146. package/dist/humain/web-access/vendor/fetch-params.js +1 -1
  147. package/dist/humain/web-access/vendor/fetch-params.js.map +1 -1
  148. package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
  149. package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
  150. package/dist/humain/web-access/vendor/gemini-search.js +12 -5
  151. package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
  152. package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
  153. package/dist/humain/web-access/vendor/index.js +23 -19
  154. package/dist/humain/web-access/vendor/index.js.map +1 -1
  155. package/dist/humain/web-access/vendor/openai-search.d.ts.map +1 -1
  156. package/dist/humain/web-access/vendor/openai-search.js +40 -14
  157. package/dist/humain/web-access/vendor/openai-search.js.map +1 -1
  158. package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
  159. package/dist/humain/web-access/vendor/page-query.js +6 -2
  160. package/dist/humain/web-access/vendor/page-query.js.map +1 -1
  161. package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -1
  162. package/dist/humain/web-access/vendor/query-rewrite.js +6 -2
  163. package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -1
  164. package/dist/humain/web-access/vendor/serpapi.d.ts +4 -0
  165. package/dist/humain/web-access/vendor/serpapi.d.ts.map +1 -0
  166. package/dist/humain/web-access/vendor/serpapi.js +215 -0
  167. package/dist/humain/web-access/vendor/serpapi.js.map +1 -0
  168. package/dist/humain/web-access/vendor/ssrf-protection.d.ts.map +1 -1
  169. package/dist/humain/web-access/vendor/ssrf-protection.js +5 -1
  170. package/dist/humain/web-access/vendor/ssrf-protection.js.map +1 -1
  171. package/dist/humain/web-access/vendor/storage.d.ts.map +1 -1
  172. package/dist/humain/web-access/vendor/storage.js +9 -0
  173. package/dist/humain/web-access/vendor/storage.js.map +1 -1
  174. package/dist/humain/web-access/vendor/summary-review.d.ts +1 -1
  175. package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
  176. package/dist/humain/web-access/vendor/summary-review.js +35 -24
  177. package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
  178. package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
  179. package/dist/humain/web-access/vendor/utils.js +5 -3
  180. package/dist/humain/web-access/vendor/utils.js.map +1 -1
  181. package/dist/modes/json-event.d.ts +8 -0
  182. package/dist/modes/json-event.d.ts.map +1 -1
  183. package/dist/modes/json-event.js +17 -0
  184. package/dist/modes/json-event.js.map +1 -1
  185. package/dist/modes/print-mode.d.ts.map +1 -1
  186. package/dist/modes/print-mode.js +3 -2
  187. package/dist/modes/print-mode.js.map +1 -1
  188. package/docs/environment-variables.md +9 -0
  189. package/docs/json.md +16 -0
  190. package/npm-shrinkwrap.json +6 -6
  191. package/package.json +2 -2
  192. package/src/humain/mcp-adapter/UPSTREAM.md +56 -7
  193. package/src/humain/mcp-adapter/upstream.json +6 -4
  194. package/src/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
  195. package/src/humain/web-access/UPSTREAM.md +18 -8
  196. package/src/humain/web-access/upstream.json +6 -5
  197. package/dist/bundle/chunks/chunk-2LC3S3VV.js +0 -105
@@ -2,7 +2,7 @@ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-a
2
2
  import type { McpExtensionState } from "./state.ts";
3
3
  import { formatToolName, isServerDisabled, resolveToolPrefix, type McpAdapterOptions, type PromptMetadata, type ToolMetadata, type ToolSelectorCandidateIndex } from "./types.ts";
4
4
  import { existsSync } from "node:fs";
5
- import { cloneMcpConfig, loadMcpConfig } from "./config.ts";
5
+ import { cloneMcpConfig, loadMcpConfig, resolveConfiguredClaudePluginMcp } from "./config.ts";
6
6
  import { ConsentManager } from "./consent-manager.ts";
7
7
  import { McpLifecycleManager } from "./lifecycle.ts";
8
8
  import {
@@ -38,6 +38,10 @@ import {
38
38
  } from "./runtime-owner.ts";
39
39
  import { publishMcpStatusSnapshot } from "./mcp-status.ts";
40
40
  import { FAILURE_BACKOFF_MS, getFailureAgeSeconds } from "./failure-backoff.ts";
41
+ import {
42
+ createSessionApprovalWriter,
43
+ restoreSessionApprovalState,
44
+ } from "./session-approvals.ts";
41
45
  export { getFailureAgeSeconds, getFailureMessage, isServerInActiveFailureBackoff } from "./failure-backoff.ts";
42
46
 
43
47
  const MAX_FAILURE_MESSAGE_CHARS = 8 * 1024;
@@ -117,12 +121,27 @@ export async function initializeMcp(
117
121
  const rawUi = hasUI ? ctx.ui : undefined;
118
122
  const modelRegistry = ctx.modelRegistry;
119
123
  const initialSignal = ctx.signal;
124
+ let sessionManager: ExtensionContext["sessionManager"] | undefined;
125
+ try {
126
+ sessionManager = ctx.sessionManager;
127
+ } catch {
128
+ // Synthetic/load-time contexts may not expose a session manager.
129
+ }
130
+ let sessionBranch: readonly unknown[] = [];
131
+ if (sessionManager) {
132
+ try {
133
+ sessionBranch = sessionManager.getBranch();
134
+ } catch (error) {
135
+ const detail = error instanceof Error ? error.message : String(error);
136
+ logger.debug(`MCP: could not read the active session branch for approval restore: ${detail}`);
137
+ }
138
+ }
120
139
  const ui = rawUi ? createOwnedUi(rawUi, owner) : undefined;
121
140
  const runtimeSignal = combineAbortSignals(owner.signal, initialSignal);
122
141
  const config = options.config !== undefined
123
- ? cloneMcpConfig(options.config)
142
+ ? resolveConfiguredClaudePluginMcp(cloneMcpConfig(options.config), cwd)
124
143
  : loadMcpConfig(configPath, cwd);
125
- const authStorageOptions = getAuthStorageOptions(config.settings?.oauthDir, cwd);
144
+ const authStorageOptions = getAuthStorageOptions(config.settings?.oauthDir, cwd, config.settings?.oauthCredentialStore);
126
145
 
127
146
  const ownsOAuthRuntime = options.oauthRuntime === undefined;
128
147
  const oauthRuntime = options.oauthRuntime ?? createOAuthRuntime(owner.signal);
@@ -153,6 +172,7 @@ export async function initializeMcp(
153
172
  }
154
173
  const lifecycle = new McpLifecycleManager(manager, (serverName) => hasPendingAuth(serverName, undefined, oauthRuntime));
155
174
  const toolMetadata = new Map<string, ToolMetadata[]>();
175
+ const directToolCounts = new Map<string, number>();
156
176
  const resourceCounts = new Map<string, number>();
157
177
  const promptMetadata = new Map<string, PromptMetadata[]>();
158
178
  const promptMetadataLive = new Set<string>();
@@ -161,12 +181,23 @@ export async function initializeMcp(
161
181
  const failureMessages = new Map<string, string>();
162
182
  const approvedToolCalls = new Map<string, true>();
163
183
  const uiResourceHandler = new UiResourceHandler(manager, config);
164
- const consentManager = new ConsentManager("once-per-server");
184
+ let appendEntry: ((customType: string, data?: unknown) => void) | undefined;
185
+ try {
186
+ const candidate = pi.appendEntry;
187
+ appendEntry = typeof candidate === "function" ? candidate.bind(pi) : undefined;
188
+ } catch {
189
+ // Load-time or synthetic APIs may not expose appendEntry yet.
190
+ }
191
+ const persistSessionApproval = sessionManager && appendEntry
192
+ ? createSessionApprovalWriter(appendEntry, () => owner.isActive())
193
+ : undefined;
194
+ const consentManager = new ConsentManager("once-per-server", persistSessionApproval);
165
195
  const state: McpExtensionState = {
166
196
  owner,
167
197
  manager,
168
198
  lifecycle,
169
199
  toolMetadata,
200
+ directToolCounts,
170
201
  resourceCounts,
171
202
  promptMetadata,
172
203
  promptMetadataLive,
@@ -178,6 +209,8 @@ export async function initializeMcp(
178
209
  failureTracker,
179
210
  failureMessages,
180
211
  approvedToolCalls,
212
+ ...(persistSessionApproval !== undefined ? { persistSessionApproval } : {}),
213
+ ...(sessionManager !== undefined ? { sessionManager } : {}),
181
214
  approvalEvents: pi.events,
182
215
  uiResourceHandler,
183
216
  consentManager,
@@ -207,11 +240,12 @@ export async function initializeMcp(
207
240
  },
208
241
  ...(options.statusEvents !== undefined ? { statusEvents: options.statusEvents } : {}),
209
242
  };
243
+ if (sessionManager) restoreSessionApprovalState(state, sessionBranch);
210
244
  if (ownsOAuthRuntime) owner.addCleanup(() => shutdownOAuth(oauthRuntime));
211
245
  manager.setMetadataListChangedListener?.((serverName, reason) => {
212
246
  if (!owner.isActive()) return;
213
247
  updateServerMetadata(state, serverName);
214
- updateMetadataCache(state, serverName, { preserveEmptyResources: false });
248
+ updateMetadataCache(state, serverName);
215
249
  notifyToolMetadataUpdated(state, serverName, reason);
216
250
  updateStatusBar(state);
217
251
  });
@@ -500,7 +534,12 @@ export function markKeepAliveAfterConnect(state: McpExtensionState, serverName:
500
534
 
501
535
  export function updateServerMetadata(state: McpExtensionState, serverName: string): void {
502
536
  const connection = state.manager.getConnection(serverName);
503
- if (!connection || connection.status !== "connected") return;
537
+ if (!connection || connection.status !== "connected") {
538
+ state.toolMetadata.delete(serverName);
539
+ state.resourceCounts?.delete(serverName);
540
+ state.directToolCounts?.delete(serverName);
541
+ return;
542
+ }
504
543
 
505
544
  const definition = state.config.mcpServers[serverName];
506
545
  if (!definition) return;
@@ -532,8 +571,8 @@ export function updateServerMetadata(state: McpExtensionState, serverName: strin
532
571
  export function updateMetadataCache(
533
572
  state: McpExtensionState,
534
573
  serverName: string,
535
- options: { preserveEmptyResources?: boolean } = {},
536
574
  ): void {
575
+ if (state.provisionalInstalls?.has(serverName)) return;
537
576
  const connection = state.manager.getConnection(serverName);
538
577
  if (!connection || connection.status !== "connected") return;
539
578
 
@@ -552,10 +591,9 @@ export function updateMetadataCache(
552
591
 
553
592
  if (
554
593
  definition.exposeResources !== false &&
555
- resources.length === 0 &&
594
+ connection.resourceDiscoveryFailed === true &&
556
595
  existingEntry?.resources?.length &&
557
- existingEntry.configHash === configHash &&
558
- options.preserveEmptyResources !== false
596
+ isServerCacheValid(existingEntry, definition)
559
597
  ) {
560
598
  resources = existingEntry.resources;
561
599
  }
@@ -0,0 +1,7 @@
1
+ export function createRetryableLoader<T>(load: () => Promise<T>): () => Promise<T> {
2
+ let pending: Promise<T> | null = null;
3
+ return () => pending ??= load().catch((error) => {
4
+ pending = null;
5
+ throw error;
6
+ });
7
+ }
@@ -0,0 +1,113 @@
1
+ import type { FetchLike } from "@modelcontextprotocol/client"
2
+ import { combineAbortSignals } from "./runtime-owner.ts"
3
+ import { interpolateEnvRecord, resolveCommandSecretsRecord } from "./utils.ts"
4
+
5
+ /**
6
+ * Resolve only at a connection/authentication boundary, never during config discovery.
7
+ * TypeError is fatal to the native SDK's fresh and cached discovery paths; ordinary
8
+ * errors can be swallowed and allow authentication to continue without credentials.
9
+ */
10
+ type OAuthHeaderOptions = { commands?: boolean; literal?: boolean }
11
+
12
+ export function resolveOAuthHeaders(values: Record<string, string> | undefined, options: OAuthHeaderOptions = {}): Headers {
13
+ const { commands = true, literal = false } = options
14
+ if (literal) return new Headers(values)
15
+ const selected = Object.fromEntries(Object.entries(values ?? {}).filter(([, value]) =>
16
+ commands || !value.startsWith("!") || value.startsWith("!!")))
17
+ for (const value of Object.values(selected)) {
18
+ if (value.startsWith("!") && !value.startsWith("!!")) continue
19
+ for (const match of value.matchAll(/\$\{(\w+)\}|\$env:(\w+)|\{env:(\w+)\}/g)) {
20
+ const name = (match[1] ?? match[2] ?? match[3])!
21
+ if (!process.env[name]) throw new TypeError("Missing environment credential in OAuth HTTP headers")
22
+ }
23
+ }
24
+ try {
25
+ const resolved = commands
26
+ ? resolveCommandSecretsRecord(selected, () => "OAuth HTTP header")
27
+ : interpolateEnvRecord(selected)
28
+ if (Object.values(resolved ?? {}).some(value => !value.trim())) {
29
+ throw new Error("empty header")
30
+ }
31
+ return new Headers(resolved)
32
+ } catch {
33
+ // Header constructors and command errors can otherwise include secret values.
34
+ throw new TypeError("Failed to resolve OAuth HTTP headers")
35
+ }
36
+ }
37
+
38
+ const DEFAULT_OAUTH_REQUEST_TIMEOUT_MS = 30_000
39
+ function resolveOAuthRequestTimeoutMs(): number {
40
+ const parsed = Number(process.env.PI_MCP_OAUTH_REQUEST_TIMEOUT_MS)
41
+ return Number.isSafeInteger(parsed) && parsed > 0 && parsed <= 2_147_483_647
42
+ ? parsed : DEFAULT_OAUTH_REQUEST_TIMEOUT_MS
43
+ }
44
+
45
+ export type OAuthFetch = FetchLike & { throwIfHeaderResolutionFailed(): void }
46
+
47
+ /**
48
+ * Configured service headers belong only to the configured MCP origin, not to
49
+ * discovered issuers. SDK request headers win (notably Authorization and MIME
50
+ * types). Credential-bearing redirects fail closed, including same-origin ones.
51
+ */
52
+ export function createOAuthFetch(
53
+ serverUrl: string,
54
+ getHeaders: () => Headers = () => new Headers(),
55
+ signal?: AbortSignal,
56
+ options: { timeout?: boolean; delegate?: FetchLike } = {},
57
+ ): OAuthFetch {
58
+ const origin = new URL(serverUrl).origin
59
+ let headerResolutionError: TypeError | undefined
60
+ const throwIfHeaderResolutionFailed = () => {
61
+ if (headerResolutionError) throw headerResolutionError
62
+ }
63
+ const fetchFn: FetchLike = async (input, init) => {
64
+ const request = input instanceof Request ? input : undefined
65
+ const url = new URL(request ? request.url : String(input))
66
+ const combined = combineAbortSignals(signal, init?.signal ?? request?.signal,
67
+ options.timeout === false ? undefined : AbortSignal.timeout(resolveOAuthRequestTimeoutMs()))
68
+ combined?.throwIfAborted()
69
+ throwIfHeaderResolutionFailed()
70
+ let serviceHeaders: Headers
71
+ try {
72
+ serviceHeaders = url.origin === origin ? getHeaders() : new Headers()
73
+ } catch {
74
+ headerResolutionError = new TypeError("Failed to resolve OAuth HTTP headers")
75
+ throw headerResolutionError
76
+ }
77
+ const headers = new Headers(serviceHeaders)
78
+ new Headers(init?.headers ?? request?.headers).forEach((value, key) => headers.set(key, value))
79
+ const protectedRequest = [...serviceHeaders].length > 0
80
+ try {
81
+ return await (options.delegate ?? globalThis.fetch)(input, {
82
+ ...init,
83
+ headers,
84
+ ...(combined ? { signal: combined } : {}),
85
+ ...(protectedRequest ? { redirect: "error" as const } : {}),
86
+ })
87
+ } catch (error) {
88
+ combined?.throwIfAborted()
89
+ // Fetch failures also include DNS, TLS, and connection errors. Do not
90
+ // misdiagnose them as redirects or expose a potentially secret-bearing cause.
91
+ if (protectedRequest) throw new TypeError("OAuth HTTP request failed")
92
+ throw error
93
+ }
94
+ }
95
+ return Object.assign(fetchFn, { throwIfHeaderResolutionFailed })
96
+ }
97
+
98
+ /** Cache success or failure only in the owning auth leg/connection, never in storage. */
99
+ export function oauthHeaderResolver(values: Record<string, string> | undefined, options: Pick<OAuthHeaderOptions, "literal"> = {}): () => Headers {
100
+ const snapshot = values ? { ...values } : undefined
101
+ let result: { headers: Headers } | { error: unknown } | undefined
102
+ return () => {
103
+ if (!result) {
104
+ try {
105
+ result = { headers: resolveOAuthHeaders(snapshot, options) }
106
+ } catch (error) {
107
+ result = { error }
108
+ }
109
+ }
110
+ if ("error" in result) throw result.error
111
+ return result.headers
112
+ }
113
+ }