@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.
- package/CHANGELOG.md +16 -0
- package/dist/bundle/chunks/{anthropic-messages-XULXUBIG.js → anthropic-messages-QFCEKGI3.js} +1 -1
- package/dist/bundle/chunks/{azure-openai-responses-JEG2XOYC.js → azure-openai-responses-G4ZMHV37.js} +1 -1
- package/dist/bundle/chunks/chunk-7T6XCLJF.js +22 -0
- package/dist/bundle/chunks/chunk-D6HHRSB5.js +103 -0
- package/dist/bundle/chunks/chunk-EY4DWHQU.js +2 -0
- package/dist/bundle/chunks/{chunk-LYAMSPR2.js → chunk-I47ZWR55.js} +483 -506
- package/dist/bundle/chunks/{chunk-JUPSJTFQ.js → chunk-K4JAZYQH.js} +1 -1
- package/dist/bundle/chunks/{chunk-HPPQHK3J.js → chunk-KKIB2TVR.js} +1 -1
- package/dist/bundle/chunks/chunk-LI2YVPWP.js +2 -0
- package/dist/bundle/chunks/{chunk-BZQ53VGE.js → chunk-XSR434CO.js} +1 -1
- package/dist/bundle/chunks/compat-QCV26HNS.js +2 -0
- package/dist/bundle/chunks/dist-PRPIUY3U.js +2 -0
- package/dist/bundle/chunks/esm-E7UVEYTO.js +4 -0
- package/dist/bundle/chunks/{extract-TRW5FBY6.js → extract-CTIFQZDJ.js} +15 -14
- package/dist/bundle/chunks/{google-generative-ai-OX6VSCA6.js → google-generative-ai-RSUU4MWO.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-GW76GSS2.js → google-vertex-CKQPOITO.js} +1 -1
- package/dist/bundle/chunks/host-WP3MVKS2.js +2 -0
- package/dist/bundle/chunks/{node-KOIP55YZ.js → node-NF43OPII.js} +1 -1
- package/dist/bundle/chunks/{node-XY3CBY3C.js → node-UMFM7CDZ.js} +1 -1
- package/dist/bundle/chunks/{openai-codex-responses-NPLWP6ZC.js → openai-codex-responses-S34V5ZSD.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-OT2GTJKC.js → openai-completions-T2IGVUEI.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-5A6CR7GI.js → openai-responses-GYZODCT3.js} +1 -1
- package/dist/bundle/chunks/readability-LAGF34IB.js +3 -0
- package/dist/bundle/chunks/turndown.cjs-HRMZVRUL.js +2 -0
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +21 -8
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/subagent-env.d.ts +18 -0
- package/dist/core/tools/subagent-env.d.ts.map +1 -0
- package/dist/core/tools/subagent-env.js +43 -0
- package/dist/core/tools/subagent-env.js.map +1 -0
- package/dist/core/tools/subagent-scheduler.d.ts +31 -0
- package/dist/core/tools/subagent-scheduler.d.ts.map +1 -0
- package/dist/core/tools/subagent-scheduler.js +203 -0
- package/dist/core/tools/subagent-scheduler.js.map +1 -0
- package/dist/core/tools/subagent.d.ts +83 -3
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +214 -76
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
- package/dist/humain/mcp-adapter/vendor/README.md +125 -19
- package/dist/humain/mcp-adapter/vendor/agent-plugin-loader.ts +69 -30
- package/dist/humain/mcp-adapter/vendor/agent-plugin-provenance.ts +36 -0
- package/dist/humain/mcp-adapter/vendor/claude-plugin-loader.ts +365 -0
- package/dist/humain/mcp-adapter/vendor/commands.ts +13 -15
- package/dist/humain/mcp-adapter/vendor/config.ts +198 -30
- package/dist/humain/mcp-adapter/vendor/consent-manager.ts +40 -9
- package/dist/humain/mcp-adapter/vendor/direct-tool-surface.ts +250 -0
- package/dist/humain/mcp-adapter/vendor/direct-tools.ts +9 -228
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js +74 -32
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-loader.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.d.ts +1 -0
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js +31 -0
- package/dist/humain/mcp-adapter/vendor/dist/agent-plugin-provenance.js.map +1 -0
- package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.d.ts +9 -0
- package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js +362 -0
- package/dist/humain/mcp-adapter/vendor/dist/claude-plugin-loader.js.map +1 -0
- package/dist/humain/mcp-adapter/vendor/dist/config.d.ts +3 -1
- package/dist/humain/mcp-adapter/vendor/dist/config.js +189 -27
- package/dist/humain/mcp-adapter/vendor/dist/config.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js +7 -16
- package/dist/humain/mcp-adapter/vendor/dist/metadata-cache.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js +5 -17
- package/dist/humain/mcp-adapter/vendor/dist/package-mcp-loader.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/types.d.ts +30 -4
- package/dist/humain/mcp-adapter/vendor/dist/types.js +26 -0
- package/dist/humain/mcp-adapter/vendor/dist/types.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/dist/utils.d.ts +6 -0
- package/dist/humain/mcp-adapter/vendor/dist/utils.js +65 -6
- package/dist/humain/mcp-adapter/vendor/dist/utils.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/host-html-template.ts +20 -19
- package/dist/humain/mcp-adapter/vendor/http-ca.ts +84 -0
- package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +37102 -15103
- package/dist/humain/mcp-adapter/vendor/index.ts +828 -164
- package/dist/humain/mcp-adapter/vendor/init.ts +48 -10
- package/dist/humain/mcp-adapter/vendor/lazy-loader.ts +7 -0
- package/dist/humain/mcp-adapter/vendor/mcp-auth-fetch.ts +113 -0
- package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +256 -77
- package/dist/humain/mcp-adapter/vendor/mcp-auth.ts +316 -54
- package/dist/humain/mcp-adapter/vendor/mcp-code.ts +45 -18
- package/dist/humain/mcp-adapter/vendor/mcp-install.ts +60 -0
- package/dist/humain/mcp-adapter/vendor/mcp-oauth-provider.ts +70 -6
- package/dist/humain/mcp-adapter/vendor/mcp-output-guard.ts +53 -42
- package/dist/humain/mcp-adapter/vendor/mcp-panel-theme.ts +105 -0
- package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +366 -331
- package/dist/humain/mcp-adapter/vendor/mcp-references.ts +9 -14
- package/dist/humain/mcp-adapter/vendor/mcp-script-worker.mjs +3 -1
- package/dist/humain/mcp-adapter/vendor/mcp-setup-panel.ts +383 -341
- package/dist/humain/mcp-adapter/vendor/mcp-status.ts +5 -0
- package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +7 -16
- package/dist/humain/mcp-adapter/vendor/namespace-tools.ts +61 -3
- package/dist/humain/mcp-adapter/vendor/oauth.ts +6 -2
- package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +5 -17
- package/dist/humain/mcp-adapter/vendor/package.json +13 -3
- package/dist/humain/mcp-adapter/vendor/prompts.ts +35 -3
- package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +108 -7
- package/dist/humain/mcp-adapter/vendor/runtime-owner.ts +16 -1
- package/dist/humain/mcp-adapter/vendor/sampling-handler.ts +9 -32
- package/dist/humain/mcp-adapter/vendor/sandbox-proxy-template.ts +20 -93
- package/dist/humain/mcp-adapter/vendor/server-manager.ts +227 -40
- package/dist/humain/mcp-adapter/vendor/session-approvals.ts +187 -0
- package/dist/humain/mcp-adapter/vendor/skills/mcp-scripting/SKILL.md +9 -1
- package/dist/humain/mcp-adapter/vendor/state.ts +10 -1
- package/dist/humain/mcp-adapter/vendor/tool-approval.ts +10 -22
- package/dist/humain/mcp-adapter/vendor/tool-metadata.ts +14 -0
- package/dist/humain/mcp-adapter/vendor/types.ts +57 -4
- package/dist/humain/mcp-adapter/vendor/ui-server.ts +36 -28
- package/dist/humain/mcp-adapter/vendor/utils.ts +64 -6
- package/dist/humain/web-access/vendor/abortable.d.ts +3 -0
- package/dist/humain/web-access/vendor/abortable.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/abortable.js +22 -0
- package/dist/humain/web-access/vendor/abortable.js.map +1 -0
- package/dist/humain/web-access/vendor/crawl4ai.d.ts +9 -0
- package/dist/humain/web-access/vendor/crawl4ai.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/crawl4ai.js +187 -0
- package/dist/humain/web-access/vendor/crawl4ai.js.map +1 -0
- package/dist/humain/web-access/vendor/credential-source.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/credential-source.js +1 -0
- package/dist/humain/web-access/vendor/credential-source.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.js +20 -2
- package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-run.d.ts +11 -0
- package/dist/humain/web-access/vendor/curator-run.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/curator-run.js +38 -0
- package/dist/humain/web-access/vendor/curator-run.js.map +1 -0
- package/dist/humain/web-access/vendor/curator-server.d.ts +1 -0
- package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-server.js +2 -0
- package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
- package/dist/humain/web-access/vendor/duckduckgo.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/duckduckgo.js +1 -1
- package/dist/humain/web-access/vendor/duckduckgo.js.map +1 -1
- package/dist/humain/web-access/vendor/extract.d.ts +1 -1
- package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/extract.js +58 -11
- package/dist/humain/web-access/vendor/extract.js.map +1 -1
- package/dist/humain/web-access/vendor/fetch-params.js +1 -1
- package/dist/humain/web-access/vendor/fetch-params.js.map +1 -1
- package/dist/humain/web-access/vendor/gemini-search.d.ts +2 -2
- package/dist/humain/web-access/vendor/gemini-search.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/gemini-search.js +12 -5
- package/dist/humain/web-access/vendor/gemini-search.js.map +1 -1
- package/dist/humain/web-access/vendor/index.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/index.js +23 -19
- package/dist/humain/web-access/vendor/index.js.map +1 -1
- package/dist/humain/web-access/vendor/openai-search.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/openai-search.js +40 -14
- package/dist/humain/web-access/vendor/openai-search.js.map +1 -1
- package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/page-query.js +6 -2
- package/dist/humain/web-access/vendor/page-query.js.map +1 -1
- package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/query-rewrite.js +6 -2
- package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -1
- package/dist/humain/web-access/vendor/serpapi.d.ts +4 -0
- package/dist/humain/web-access/vendor/serpapi.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/serpapi.js +215 -0
- package/dist/humain/web-access/vendor/serpapi.js.map +1 -0
- package/dist/humain/web-access/vendor/ssrf-protection.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/ssrf-protection.js +5 -1
- package/dist/humain/web-access/vendor/ssrf-protection.js.map +1 -1
- package/dist/humain/web-access/vendor/storage.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/storage.js +9 -0
- package/dist/humain/web-access/vendor/storage.js.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.d.ts +1 -1
- package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.js +35 -24
- package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
- package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/utils.js +5 -3
- package/dist/humain/web-access/vendor/utils.js.map +1 -1
- package/dist/modes/json-event.d.ts +8 -0
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +17 -0
- package/dist/modes/json-event.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +3 -2
- package/dist/modes/print-mode.js.map +1 -1
- package/docs/environment-variables.md +8 -0
- package/docs/json.md +16 -0
- package/npm-shrinkwrap.json +6 -6
- package/package.json +2 -2
- package/src/humain/mcp-adapter/UPSTREAM.md +56 -7
- package/src/humain/mcp-adapter/upstream.json +6 -4
- package/src/humain/mcp-adapter/vendor/CHANGELOG.md +78 -0
- package/src/humain/web-access/UPSTREAM.md +18 -8
- package/src/humain/web-access/upstream.json +6 -5
- 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.
|
|
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
|
-
|
|
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,
|
|
2
|
-
import { isAbsolute, join,
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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);
|