@herbertgao/pi-extensions 2026.9.15 → 2026.9.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/THIRD_PARTY_NOTICES.md +26 -0
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/dist/index.ts +12 -6
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
- package/node_modules/@narumitw/pi-btw/package.json +4 -4
- package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +2 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +16 -4
- package/node_modules/pi-lens/CHANGELOG.md +498 -0
- package/node_modules/pi-lens/README.md +45 -2
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +23 -7
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +187 -51
- package/node_modules/pi-lens/dist/clients/cache-manager.js +64 -5
- package/node_modules/pi-lens/dist/clients/child-unref.js +17 -8
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +2 -0
- package/node_modules/pi-lens/dist/clients/config-locations.js +237 -9
- package/node_modules/pi-lens/dist/clients/config-resolve.js +7 -1
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +33 -3
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +87 -64
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +39 -3
- package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +118 -1
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +8 -2
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +23 -4
- package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +14 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +26 -28
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +38 -25
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +32 -49
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +16 -14
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +25 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +28 -47
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +11 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +27 -22
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +31 -46
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +29 -46
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +91 -11
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +16 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +38 -23
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +27 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +17 -8
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +31 -21
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +34 -60
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +6 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +12 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +22 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +39 -16
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +27 -18
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +18 -19
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +6 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +6 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +10 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +11 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +10 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +11 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +12 -6
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +30 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +23 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +8 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +11 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +15 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +12 -2
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +27 -39
- package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +13 -3
- package/node_modules/pi-lens/dist/clients/effective-config.js +3 -1
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +42 -11
- package/node_modules/pi-lens/dist/clients/formatters.js +49 -11
- package/node_modules/pi-lens/dist/clients/git-guard.js +247 -34
- package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +121 -25
- package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +186 -0
- package/node_modules/pi-lens/dist/clients/install-diagnostics.js +19 -6
- package/node_modules/pi-lens/dist/clients/installer/index.js +281 -32
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +8 -0
- package/node_modules/pi-lens/dist/clients/latency-logger.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-config.js +66 -8
- package/node_modules/pi-lens/dist/clients/lsp/client.js +139 -11
- package/node_modules/pi-lens/dist/clients/lsp/config.js +9 -7
- package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +41 -0
- package/node_modules/pi-lens/dist/clients/lsp/index.js +1 -1
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +23 -2
- package/node_modules/pi-lens/dist/clients/lsp/server.js +106 -25
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +14 -0
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +105 -16
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +232 -44
- package/node_modules/pi-lens/dist/clients/mcp/review.js +53 -7
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +11 -6
- package/node_modules/pi-lens/dist/clients/pipeline.js +121 -66
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +21 -3
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +11 -3
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +34 -26
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +16 -10
- package/node_modules/pi-lens/dist/clients/ruff-client.js +31 -4
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +38 -4
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +16 -5
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +27 -12
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +447 -280
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +256 -65
- package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +84 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +119 -58
- package/node_modules/pi-lens/dist/clients/turn-end/lane.js +51 -0
- package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +112 -0
- package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +204 -0
- package/node_modules/pi-lens/dist/clients/warm-attach.js +59 -0
- package/node_modules/pi-lens/dist/clients/widget-state.js +145 -14
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +57 -3
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -13
- package/node_modules/pi-lens/dist/index.js +5695 -3733
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +4 -0
- package/node_modules/pi-lens/dist/mcp/server.js +33 -21
- package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +123 -0
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +43 -16
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +118 -7
- package/node_modules/pi-lens/docs/adr/0001-stale-advisory-live-arm.md +29 -0
- package/node_modules/pi-lens/docs/adr/0002-workspace-hash-rendezvous.md +30 -0
- package/node_modules/pi-lens/docs/adr/0003-git-guard-latch-writer.md +203 -0
- package/node_modules/pi-lens/docs/adr/0004-disposition-policy-seam.md +38 -0
- package/node_modules/pi-lens/docs/adr/0005-tool-availability-enforcement-seam.md +29 -0
- package/node_modules/pi-lens/docs/adr/0006-derived-state-benchmark-first.md +31 -0
- package/node_modules/pi-lens/docs/adr/0007-end-to-end-witness-per-seam-slice.md +38 -0
- package/node_modules/pi-lens/docs/adr/0008-turn-end-lane-interface.md +165 -0
- package/node_modules/pi-lens/docs/adr/0009-reported-path-attribution.md +114 -0
- package/node_modules/pi-lens/docs/adr/README.md +26 -0
- package/node_modules/pi-lens/docs/agent-tools.md +18 -1
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +4 -3
- package/node_modules/pi-lens/docs/configuration.md +55 -1
- package/node_modules/pi-lens/docs/engineering-principles.md +202 -0
- package/node_modules/pi-lens/docs/environment-variables.md +61 -4
- package/node_modules/pi-lens/docs/features.md +2 -2
- package/node_modules/pi-lens/docs/globalconfig.md +6 -1
- package/node_modules/pi-lens/docs/language-coverage.md +17 -0
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +98 -31
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +57 -3
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +9 -0
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +16 -0
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +9 -0
- package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +2 -0
- package/node_modules/pi-lens/docs/servercapabilities.md +21 -11
- package/node_modules/pi-lens/docs/settings.md +12 -3
- package/node_modules/pi-lens/docs/subagent-compat.md +1 -1
- package/node_modules/pi-lens/package.json +5 -3
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-assert-tuple-test.yml +13 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-boolean-in-except-test.yml +26 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-http-headers-bracket-access-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/unchecked-numeric-parse-python-test.yml +18 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/unchecked-throwing-call-python-test.yml +4 -3
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +2 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-assert-tuple.yml +23 -13
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-boolean-in-except.yml +7 -3
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-console-except-error-js.yml +2 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-console-except-error.yml +2 -2
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-http-headers-bracket-access.yml +12 -7
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/unchecked-numeric-parse-python.yml +44 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/unchecked-throwing-call-python.yml +5 -3
- package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/console-statement.yml +2 -2
- package/node_modules/pi-lens/scripts/install-selftest.mjs +21 -17
- package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +123 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +29 -0
- package/node_modules/pi-mcp-adapter/OAUTH.md +369 -0
- package/node_modules/pi-mcp-adapter/README.md +12 -5
- package/node_modules/pi-mcp-adapter/config.ts +31 -8
- package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +1 -1
- package/node_modules/pi-mcp-adapter/dist/config.js +33 -8
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +15 -2
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +27 -2
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +1 -1
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/server-manager.js +6 -4
- package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +2 -2
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +10 -3
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
- package/node_modules/pi-mcp-adapter/index.ts +37 -20
- package/node_modules/pi-mcp-adapter/init.ts +14 -15
- package/node_modules/pi-mcp-adapter/json-schema-validator.ts +16 -2
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +27 -2
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +1 -1
- package/node_modules/pi-mcp-adapter/package.json +3 -2
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +1 -1
- package/node_modules/pi-mcp-adapter/server-manager.ts +6 -3
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +99 -6
- package/node_modules/pi-mcp-adapter/types.ts +2 -2
- package/node_modules/pi-mcp-adapter/ui-session.ts +60 -27
- package/node_modules/pi-mcp-adapter/utils.ts +11 -3
- package/node_modules/pi-next-cue/.github/workflows/publish.yml +20 -0
- package/node_modules/pi-next-cue/LICENSE +21 -0
- package/node_modules/pi-next-cue/README.md +81 -0
- package/node_modules/pi-next-cue/index.ts +358 -0
- package/node_modules/pi-next-cue/package.json +28 -0
- package/package.json +8 -5
|
@@ -342,7 +342,7 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
342
342
|
const reportedDirectToolNamesByServer = new Map<string, Set<string>>();
|
|
343
343
|
const registeredNamespaceProxyTools = new Set<string>();
|
|
344
344
|
const fallbackDeactivatedTools = new Set<string>();
|
|
345
|
-
// directTools: "search" — registered inactive, activated by mcp({ search }).
|
|
345
|
+
// directTools: "search" — registered inactive, activated by mcp({ search }) or a successful mcp({ tool }) call.
|
|
346
346
|
const lazyDirectTools = new Set<string>();
|
|
347
347
|
const searchActivatedTools = new Set<string>();
|
|
348
348
|
const toolRenderOptions = resolveMcpToolRenderOptions(earlyConfig.settings);
|
|
@@ -450,10 +450,10 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
/**
|
|
453
|
-
* Activate
|
|
454
|
-
* one
|
|
455
|
-
*
|
|
456
|
-
* report only real additions.
|
|
453
|
+
* Activate lazy direct tools additively — the ones a search matched, or the
|
|
454
|
+
* one a successful proxy call named. This is the one place a search-mode tool
|
|
455
|
+
* becomes active; nothing is ever deactivated here. Returns the names that
|
|
456
|
+
* actually changed state so the result can report only real additions.
|
|
457
457
|
*/
|
|
458
458
|
function activateSearchMatches(matches: ReadonlyArray<{ server: string; tool: string }>): string[] {
|
|
459
459
|
const activeTools = getActiveToolsIfReady();
|
|
@@ -502,17 +502,14 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
502
502
|
const unregisterTool = (pi as ExtensionAPI & { unregisterTool?: (name: string) => boolean }).unregisterTool;
|
|
503
503
|
const unregistered = toolNames.filter((toolName) => callReentrant(() => unregisterTool?.(toolName)) === true);
|
|
504
504
|
const fallbackNames = toolNames.filter((toolName) => !unregistered.includes(toolName));
|
|
505
|
-
const remove = new Set(toolNames);
|
|
506
505
|
const activeTools = getActiveToolsIfReady();
|
|
507
|
-
if (!activeTools
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
506
|
+
if (!activeTools) return unregistered;
|
|
507
|
+
const removedFallbackNames = fallbackNames.filter((name) => activeTools.includes(name));
|
|
508
|
+
if (removedFallbackNames.length === 0) return unregistered;
|
|
509
|
+
const remove = new Set(removedFallbackNames);
|
|
511
510
|
const nextActiveTools = activeTools.filter((name) => !remove.has(name));
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
callReentrant(() => pi.setActiveTools(nextActiveTools));
|
|
515
|
-
}
|
|
511
|
+
callReentrant(() => pi.setActiveTools(nextActiveTools));
|
|
512
|
+
for (const toolName of removedFallbackNames) fallbackDeactivatedTools.add(toolName);
|
|
516
513
|
return unregistered;
|
|
517
514
|
}
|
|
518
515
|
|
|
@@ -1508,7 +1505,7 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1508
1505
|
(pi.registerTool as (tool: unknown) => unknown)({
|
|
1509
1506
|
name: "mcpScript",
|
|
1510
1507
|
label: "MCP Script",
|
|
1511
|
-
description: "Run trusted JavaScript that makes multiple MCP tool calls in one request — loop, filter, chain, or fan out between calls. For a single MCP call, search, describe, status check, or auth action, use the mcp tool instead. Discover with await tools.search({ query }) — resolves to { items: [{ path, name, server, description? }], total, hasMore, nextOffset }, not an { ok, data } envelope. Inspect with await tools.describe({ path }) — resolves to the tool descriptor with inputTypeScript, or { path, error: { code, message, suggestions } }. Then call tools.call(path, args) — resolves to { ok: true, data } or { ok: false, error: { code, message } } — or use direct flat calls when the name is already known; use emit(value) for user-visible output.
|
|
1508
|
+
description: "Run trusted JavaScript that makes multiple MCP tool calls in one request — loop, filter, chain, or fan out between calls. For a single MCP call, search, describe, status check, or auth action, use the mcp tool instead. Discover with await tools.search({ query }) — resolves to { items: [{ path, name, server, description? }], total, hasMore, nextOffset }, not an { ok, data } envelope. Inspect with await tools.describe({ path }) — resolves to the tool descriptor with inputTypeScript, or { path, error: { code, message, suggestions } }. Then call tools.call(path, args) — resolves to { ok: true, data } or { ok: false, error: { code, message } } — or use direct flat calls when the name is already known; use emit(value) for user-visible output.",
|
|
1512
1509
|
promptSnippet: "Batch multiple MCP tool calls in one JavaScript request (loop, filter, chain)",
|
|
1513
1510
|
parameters: Type.Object({
|
|
1514
1511
|
code: Type.String({ description: "Trusted JavaScript MCP script. Use tools.<prefixedToolName>(args) and emit(value)." }),
|
|
@@ -1939,7 +1936,23 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1939
1936
|
: proxyModes.executeAuthComplete(proxyState, params.server, input);
|
|
1940
1937
|
}
|
|
1941
1938
|
if (params.tool) {
|
|
1942
|
-
|
|
1939
|
+
const result = await proxyModes.executeCall(proxyState, params.tool, parsedArgs, params.server, getPiTools, signal);
|
|
1940
|
+
assertRuntimeGuard(proxyGuard);
|
|
1941
|
+
if (lazyDirectTools.size === 0) return result;
|
|
1942
|
+
// A successful proxy call is as clear a signal as a search hit: the
|
|
1943
|
+
// model wants this tool. Activate it the same additive way, keyed on
|
|
1944
|
+
// the registered (prefixed) name the call reports as canonicalTool.
|
|
1945
|
+
// Failed calls (lookup, approval, tool errors) carry details.error
|
|
1946
|
+
// and activate nothing.
|
|
1947
|
+
const identity = result.details as { error?: unknown; server?: unknown; canonicalTool?: unknown } | undefined;
|
|
1948
|
+
if (!identity || identity.error !== undefined || typeof identity.server !== "string" || typeof identity.canonicalTool !== "string") return result;
|
|
1949
|
+
holdLazyToolsInactive();
|
|
1950
|
+
const added = activateSearchMatches([{ server: identity.server, tool: identity.canonicalTool }]);
|
|
1951
|
+
if (added.length === 0) return result;
|
|
1952
|
+
// Content is returned untouched: the call's output is already sized to
|
|
1953
|
+
// the output limits and is what a compact view previews. The tool
|
|
1954
|
+
// description tells the model a called tool is direct from then on.
|
|
1955
|
+
return { ...result, details: { ...(result.details ?? {}), activated: added }, addedToolNames: added };
|
|
1943
1956
|
}
|
|
1944
1957
|
if (params.connect) {
|
|
1945
1958
|
return connectAndReport(proxyState, params.connect, signal, _ctx as ExtensionContext);
|
|
@@ -1982,8 +1995,8 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1982
1995
|
cache,
|
|
1983
1996
|
envRaw === undefined || envRaw === "__none__" ? undefined : envDirectToolOverride,
|
|
1984
1997
|
);
|
|
1985
|
-
// Search-mode tools are registered INACTIVE and `mcp(
|
|
1986
|
-
// activation entry point, so dropping
|
|
1998
|
+
// Search-mode tools are registered INACTIVE and the `mcp` gateway (search, or a
|
|
1999
|
+
// proxy call) is their only activation entry point, so dropping it strands them: every tool held, nothing
|
|
1987
2000
|
// able to activate one. Keep it whenever any spec depends on it.
|
|
1988
2001
|
const hasSearchModeSpecs = directSpecs.some((spec) => spec.lazy === true);
|
|
1989
2002
|
const shouldRegisterProxyTool =
|
|
@@ -1999,10 +2012,14 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1999
2012
|
finalizationRegistrations?.add("mcp");
|
|
2000
2013
|
registerProxyTool(description);
|
|
2001
2014
|
finalizationGuard?.();
|
|
2002
|
-
return;
|
|
2003
2015
|
}
|
|
2004
2016
|
const activeTools = getActiveToolsIfReady();
|
|
2005
|
-
if (activeTools
|
|
2017
|
+
if (activeTools?.includes("mcp")) {
|
|
2018
|
+
// Observed host reactivation ends our fallback ownership. A later
|
|
2019
|
+
// host removal must not be mistaken for our own deactivation.
|
|
2020
|
+
fallbackDeactivatedTools.delete("mcp");
|
|
2021
|
+
} else if (activeTools && fallbackDeactivatedTools.delete("mcp")) {
|
|
2022
|
+
// Only undo a fallback deactivation that the adapter still owns.
|
|
2006
2023
|
callReentrant(() => pi.setActiveTools([...activeTools, "mcp"]));
|
|
2007
2024
|
}
|
|
2008
2025
|
return;
|
|
@@ -267,28 +267,27 @@ export async function initializeMcp(
|
|
|
267
267
|
|
|
268
268
|
const allServerEntries = Object.entries(config.mcpServers);
|
|
269
269
|
const serverEntries = allServerEntries.filter(([, definition]) => !isServerDisabled(definition));
|
|
270
|
-
if (serverEntries.length === 0) {
|
|
271
|
-
|
|
272
|
-
ui?.notify(`MCP: All ${allServerEntries.length} server(s) are disabled`, "info");
|
|
273
|
-
}
|
|
274
|
-
publishMcpStatusSnapshot(state);
|
|
275
|
-
return state;
|
|
270
|
+
if (serverEntries.length === 0 && allServerEntries.length > 0 && hasUI) {
|
|
271
|
+
ui?.notify(`MCP: All ${allServerEntries.length} server(s) are disabled`, "info");
|
|
276
272
|
}
|
|
277
273
|
|
|
278
274
|
const idleSetting = typeof config.settings?.idleTimeout === "number" ? config.settings.idleTimeout : 10;
|
|
279
275
|
lifecycle.setGlobalIdleTimeout(idleSetting);
|
|
280
276
|
|
|
281
|
-
|
|
282
|
-
const cacheFileExists = existsSync(cachePath);
|
|
283
|
-
let cache = loadMetadataCache();
|
|
277
|
+
let cache: ReturnType<typeof loadMetadataCache> = null;
|
|
284
278
|
let bootstrapAll = false;
|
|
285
279
|
|
|
286
|
-
if (
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
280
|
+
if (serverEntries.length > 0) {
|
|
281
|
+
const cachePath = getMetadataCachePath();
|
|
282
|
+
const cacheFileExists = existsSync(cachePath);
|
|
283
|
+
cache = loadMetadataCache();
|
|
284
|
+
if (!cacheFileExists) {
|
|
285
|
+
bootstrapAll = true;
|
|
286
|
+
saveMetadataCache({ version: 1, servers: {} });
|
|
287
|
+
} else if (!cache) {
|
|
288
|
+
cache = { version: 1, servers: {} };
|
|
289
|
+
saveMetadataCache(cache);
|
|
290
|
+
}
|
|
292
291
|
}
|
|
293
292
|
|
|
294
293
|
const prefix = config.settings?.toolPrefix ?? "server";
|
|
@@ -11,6 +11,20 @@ import type {
|
|
|
11
11
|
// ajv-formats types target its bundled ajv; the runtime accepts both instances.
|
|
12
12
|
const addFormats = addFormatsImport as unknown as (instance: Ajv) => void;
|
|
13
13
|
|
|
14
|
+
// Rust servers built with schemars annotate numbers with these formats. The
|
|
15
|
+
// `type`, `minimum`, and `maximum` keywords already carry the constraint, so
|
|
16
|
+
// registering them as always-valid only stops Ajv's "unknown format" warnings.
|
|
17
|
+
// int32, int64, float, and double are omitted: ajv-formats validates them.
|
|
18
|
+
const SCHEMARS_NUMERIC_FORMATS = [
|
|
19
|
+
"int", "int8", "int16", "int128",
|
|
20
|
+
"uint", "uint8", "uint16", "uint32", "uint64", "uint128",
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
function addKnownFormats(ajv: Ajv): void {
|
|
24
|
+
addFormats(ajv);
|
|
25
|
+
for (const format of SCHEMARS_NUMERIC_FORMATS) ajv.addFormat(format, true);
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
type SchemaDialect =
|
|
15
29
|
| { status: "unstamped" }
|
|
16
30
|
| { status: "stamped"; uri: string };
|
|
@@ -44,7 +58,7 @@ export function createJsonSchemaValidator(): JsonSchemaValidatorProvider {
|
|
|
44
58
|
draft2020Validator ??= (() => {
|
|
45
59
|
const Ajv2020 = Ajv2020Import as unknown as typeof Ajv;
|
|
46
60
|
const ajv = new Ajv2020({ strict: false, allErrors: true });
|
|
47
|
-
|
|
61
|
+
addKnownFormats(ajv);
|
|
48
62
|
return new AjvJsonSchemaValidator(ajv);
|
|
49
63
|
})();
|
|
50
64
|
return draft2020Validator.getValidator<T>(schema);
|
|
@@ -60,7 +74,7 @@ export function createJsonSchemaValidator(): JsonSchemaValidatorProvider {
|
|
|
60
74
|
validateSchema: false,
|
|
61
75
|
allErrors: true,
|
|
62
76
|
});
|
|
63
|
-
|
|
77
|
+
addKnownFormats(ajv);
|
|
64
78
|
return new AjvJsonSchemaValidator(ajv);
|
|
65
79
|
})();
|
|
66
80
|
return draft07Validator.getValidator<T>(schema);
|
|
@@ -210,6 +210,7 @@ interface AuthEntryChunkManifest {
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
let KeyringEntryClass: KeyringEntryConstructor | undefined;
|
|
213
|
+
const keyringEntries = new Map<string, KeyringEntry>();
|
|
213
214
|
const memoryAuthEntries = new Map<string, string>();
|
|
214
215
|
|
|
215
216
|
let testAuthSecretStoreReadCount = 0;
|
|
@@ -238,12 +239,29 @@ const memoryAuthSecretStore: AuthSecretStore = {
|
|
|
238
239
|
|
|
239
240
|
const keyringAuthSecretStore: AuthSecretStore = {
|
|
240
241
|
read(account) {
|
|
241
|
-
|
|
242
|
+
const cached = keyringEntries.get(account);
|
|
243
|
+
if (cached) {
|
|
244
|
+
try {
|
|
245
|
+
// keyring v2 throws for provider/session failures; null means the credential is absent.
|
|
246
|
+
return cached.getPassword() ?? undefined;
|
|
247
|
+
} catch {
|
|
248
|
+
// A stale native Entry may survive a keyring daemon restart. Retry once.
|
|
249
|
+
}
|
|
250
|
+
keyringEntries.delete(account);
|
|
251
|
+
}
|
|
252
|
+
const fresh = getKeyringEntry(account);
|
|
253
|
+
const value = fresh.getPassword();
|
|
254
|
+
keyringEntries.set(account, fresh);
|
|
255
|
+
return value ?? undefined;
|
|
242
256
|
},
|
|
243
257
|
write(account, payload) {
|
|
244
|
-
|
|
258
|
+
keyringEntries.delete(account);
|
|
259
|
+
const fresh = getKeyringEntry(account);
|
|
260
|
+
fresh.setPassword(payload);
|
|
261
|
+
keyringEntries.set(account, fresh);
|
|
245
262
|
},
|
|
246
263
|
remove(account) {
|
|
264
|
+
keyringEntries.delete(account);
|
|
247
265
|
getKeyringEntry(account).deleteCredential();
|
|
248
266
|
},
|
|
249
267
|
};
|
|
@@ -305,9 +323,16 @@ const keyRevokedAuthSecretStore: AuthSecretStore = {
|
|
|
305
323
|
export function resetTestAuthSecretStore(): void {
|
|
306
324
|
memoryAuthEntries.clear();
|
|
307
325
|
authEntryCache.clear();
|
|
326
|
+
keyringEntries.clear();
|
|
308
327
|
testAuthSecretStoreReadCount = 0;
|
|
309
328
|
}
|
|
310
329
|
|
|
330
|
+
/** Install a fake native Entry for OAuth storage tests without opening a real keyring. */
|
|
331
|
+
export function setTestKeyringEntryClass(entryClass: KeyringEntryConstructor | undefined): void {
|
|
332
|
+
keyringEntries.clear();
|
|
333
|
+
KeyringEntryClass = entryClass;
|
|
334
|
+
}
|
|
335
|
+
|
|
311
336
|
export function resetAuthEntryCache(): void {
|
|
312
337
|
authEntryCache.clear();
|
|
313
338
|
}
|
|
@@ -86,7 +86,7 @@ export function computeServerHash(definition: ServerEntry, environment: NodeJS.P
|
|
|
86
86
|
// Exclude lifecycle, idleTimeout, requestTimeoutMs, debug — those are runtime behavior settings
|
|
87
87
|
// that don't change which tools a server exposes.
|
|
88
88
|
const identity: Record<string, unknown> = {
|
|
89
|
-
command: definition.command,
|
|
89
|
+
command: resolveConfigPath(definition.command, environment),
|
|
90
90
|
args: definition.args,
|
|
91
91
|
socket: resolveConfigPath(definition.socket, environment),
|
|
92
92
|
env: isBuiltInAgentPlugin(definition, "env") ? definition.env : interpolateEnvRecord(definition.env, environment),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mcp-adapter",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.38.0",
|
|
4
4
|
"description": "MCP (Model Context Protocol) adapter extension for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./index.ts",
|
|
@@ -163,6 +163,7 @@
|
|
|
163
163
|
"banner.png",
|
|
164
164
|
"skills",
|
|
165
165
|
"README.md",
|
|
166
|
+
"OAUTH.md",
|
|
166
167
|
"CHANGELOG.md",
|
|
167
168
|
"LICENSE",
|
|
168
169
|
"dist"
|
|
@@ -172,7 +173,7 @@
|
|
|
172
173
|
"@modelcontextprotocol/core": "2.0.0",
|
|
173
174
|
"@modelcontextprotocol/ext-apps": "^1.2.2",
|
|
174
175
|
"@modelcontextprotocol/ext-tasks": "^0.1.0",
|
|
175
|
-
"@napi-rs/keyring": "^1.
|
|
176
|
+
"@napi-rs/keyring": "^2.1.0",
|
|
176
177
|
"@typesafe-ai/sdk": "0.6.0",
|
|
177
178
|
"ajv": "^8.17.1",
|
|
178
179
|
"ajv-formats": "^3.0.1",
|
|
@@ -1374,7 +1374,7 @@ export async function executeCall(
|
|
|
1374
1374
|
}
|
|
1375
1375
|
|
|
1376
1376
|
const callIdentity = toolMeta.resourceUri
|
|
1377
|
-
? { server: serverName, resourceUri: toolMeta.resourceUri }
|
|
1377
|
+
? { server: serverName, resourceUri: toolMeta.resourceUri, canonicalTool: toolMeta.name }
|
|
1378
1378
|
: { server: serverName, tool: toolMeta.originalName, canonicalTool: toolMeta.name };
|
|
1379
1379
|
|
|
1380
1380
|
let connection = state.manager.getConnection(serverName);
|
|
@@ -63,6 +63,7 @@ import { attachTaskSession, type RawRequestChannel } from "./mcp-tasks.ts";
|
|
|
63
63
|
import type { TaskEnabledSession } from "@modelcontextprotocol/ext-tasks/client";
|
|
64
64
|
import {
|
|
65
65
|
interpolateEnvVars,
|
|
66
|
+
expandHomePath,
|
|
66
67
|
resolveBearerToken,
|
|
67
68
|
resolveCommandSecret,
|
|
68
69
|
resolveCommandSecretsRecord,
|
|
@@ -1044,11 +1045,13 @@ export class McpServerManager {
|
|
|
1044
1045
|
|
|
1045
1046
|
if (definition.command) {
|
|
1046
1047
|
client = this.createClient(name, definition);
|
|
1047
|
-
let command = definition.command;
|
|
1048
|
+
let command = resolveConfigPath(definition.command) ?? definition.command;
|
|
1048
1049
|
const literalArgs = isBuiltInAgentPlugin(definition, "args");
|
|
1049
1050
|
const literalCwd = isBuiltInAgentPlugin(definition, "cwd");
|
|
1050
|
-
let args = literalArgs
|
|
1051
|
-
|
|
1051
|
+
let args = literalArgs
|
|
1052
|
+
? [...(definition.args ?? [])]
|
|
1053
|
+
: (definition.args ?? []).map((argument) => expandHomePath(interpolateEnvVars(argument)) ?? argument);
|
|
1054
|
+
const cwd = (literalCwd ? expandHomePath(definition.cwd) : resolveConfigPath(definition.cwd)) ?? this.defaultCwd;
|
|
1052
1055
|
if (definition.pluginDataDir) mkdirSync(definition.pluginDataDir, { recursive: true });
|
|
1053
1056
|
if (cwd !== undefined) {
|
|
1054
1057
|
const cwdStats = statSync(cwd, { throwIfNoEntry: false });
|
|
@@ -75,6 +75,8 @@ class CompactMcpToolResult implements Component {
|
|
|
75
75
|
private readonly inputPreview: string,
|
|
76
76
|
private readonly display: McpToolResultDisplay,
|
|
77
77
|
private readonly theme: RenderTheme,
|
|
78
|
+
/** Shown after the title and never truncated; the title shrinks instead (e.g. `✗3`). */
|
|
79
|
+
private readonly status = "",
|
|
78
80
|
) {}
|
|
79
81
|
|
|
80
82
|
render(width: number): string[] {
|
|
@@ -113,17 +115,23 @@ class CompactMcpToolResult implements Component {
|
|
|
113
115
|
private renderPrefix(width: number): string {
|
|
114
116
|
if (!this.title) return "";
|
|
115
117
|
const arrow = " → ";
|
|
116
|
-
|
|
118
|
+
const statusWidth = this.status ? visibleWidth(this.status) + 1 : 0;
|
|
119
|
+
const head = (maxWidth?: number) => {
|
|
120
|
+
const title = maxWidth === undefined
|
|
121
|
+
? this.title
|
|
122
|
+
: truncateToWidth(this.title, Math.max(1, maxWidth - statusWidth), "…");
|
|
123
|
+
const status = this.status ? ` ${this.theme.fg("error", this.status)}` : "";
|
|
124
|
+
return `${this.theme.fg("toolTitle", title)}${status}`;
|
|
125
|
+
};
|
|
126
|
+
if (!this.inputPreview) return `${head()}${arrow}`;
|
|
117
127
|
|
|
118
128
|
const maxPrefixWidth = Math.max(12, Math.floor(width * 0.55));
|
|
119
|
-
const titleWidth = visibleWidth(this.title);
|
|
129
|
+
const titleWidth = visibleWidth(this.title) + statusWidth;
|
|
120
130
|
const inputWidth = Math.max(0, maxPrefixWidth - titleWidth - 1);
|
|
121
|
-
if (inputWidth <= 3) {
|
|
122
|
-
return `${this.theme.fg("toolTitle", truncateToWidth(this.title, maxPrefixWidth, "…"))}${arrow}`;
|
|
123
|
-
}
|
|
131
|
+
if (inputWidth <= 3) return `${head(maxPrefixWidth)}${arrow}`;
|
|
124
132
|
|
|
125
133
|
const input = truncateToWidth(this.inputPreview, inputWidth, "…");
|
|
126
|
-
return `${
|
|
134
|
+
return `${head()} ${this.theme.fg("muted", input)}${arrow}`;
|
|
127
135
|
}
|
|
128
136
|
}
|
|
129
137
|
|
|
@@ -326,11 +334,91 @@ export function createMcpDirectToolCallRenderer(displayName: string, options = r
|
|
|
326
334
|
|
|
327
335
|
export function createMcpScriptToolCallRenderer(options = resolveMcpToolRenderOptions()) {
|
|
328
336
|
return (args: { code: string }, theme?: RenderTheme, context?: McpToolRenderContext) => {
|
|
337
|
+
// Compact mode hides this call row once the result lands, so leave a title for the result row.
|
|
338
|
+
// The tool list comes from the result's `details.calls` trace (see formatMcpScriptCallSummary);
|
|
339
|
+
// the script code itself is never copied into the compact row.
|
|
340
|
+
if (context?.state) context.state.compactTitle = MCP_SCRIPT_TITLE;
|
|
329
341
|
if (shouldUseCompactFinalRender(options, context)) return new EmptyComponent();
|
|
330
342
|
return renderToolCallLines(formatMcpScriptToolCallLines(args), theme);
|
|
331
343
|
};
|
|
332
344
|
}
|
|
333
345
|
|
|
346
|
+
const MCP_SCRIPT_TITLE = "mcpScript";
|
|
347
|
+
const MCP_SCRIPT_SUMMARY_MAX_TOOLS = 4;
|
|
348
|
+
|
|
349
|
+
export interface McpScriptCallSummary {
|
|
350
|
+
/** Traced operations that failed; the compact row shows it as `mcpScript ✗N`. */
|
|
351
|
+
failed: number;
|
|
352
|
+
/** What the script touched; "" when it made no MCP operations. */
|
|
353
|
+
preview: string;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const PLAIN_TOOL_PATH = /^[A-Za-z0-9_.\/:@-]+$/;
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Traced paths are script-supplied. Only identifier-like paths (what MCP tool names look like)
|
|
360
|
+
* are shown bare. Anything else is shown as a quoted, ASCII-escaped JSON string, so it cannot
|
|
361
|
+
* break the row, cannot pass for summary syntax (`foo×2`, `a, b`, ` · `, `+1 more`), and stays
|
|
362
|
+
* distinct from the clean path it may resemble (`demo_red` vs `"\u001b[31mdemo_red"`).
|
|
363
|
+
*/
|
|
364
|
+
function formatTracedPath(path: string): string {
|
|
365
|
+
if (PLAIN_TOOL_PATH.test(path)) return path;
|
|
366
|
+
// JSON.stringify quotes and escapes C0 controls, quotes and backslashes; escaping all other
|
|
367
|
+
// non-ASCII too rules out DEL, C1 controls, bidi overrides and invisible characters.
|
|
368
|
+
return JSON.stringify(path).replace(/[^\x20-\x7e]/g, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Failure count and compact-row preview for an mcpScript run, from the `details.calls` trace.
|
|
373
|
+
* The preview lists distinct tool paths in first-call order with repeat counts, then other
|
|
374
|
+
* operation counts, e.g. `github_search_issues×6, slack_post_message · search`.
|
|
375
|
+
* Scripts that only describe tools list the described paths instead (`describe a, b`).
|
|
376
|
+
* The failure count is rendered as a title status, not in the preview: narrow rows drop the
|
|
377
|
+
* preview first, and the status is never truncated (the title shrinks instead).
|
|
378
|
+
* Returns null for non-script results.
|
|
379
|
+
*/
|
|
380
|
+
export function formatMcpScriptCallSummary(
|
|
381
|
+
details: McpToolResultDetails | undefined,
|
|
382
|
+
maxTools = MCP_SCRIPT_SUMMARY_MAX_TOOLS,
|
|
383
|
+
): McpScriptCallSummary | null {
|
|
384
|
+
if (details?.mode !== "script") return null;
|
|
385
|
+
const operations = Array.isArray(details.calls) ? details.calls : [];
|
|
386
|
+
const callCounts = new Map<string, number>();
|
|
387
|
+
const describeCounts = new Map<string, number>();
|
|
388
|
+
const metaCounts = new Map<string, number>();
|
|
389
|
+
let failed = 0;
|
|
390
|
+
for (const operation of operations) {
|
|
391
|
+
if (typeof operation !== "object" || operation === null) continue;
|
|
392
|
+
const { operation: kind, path, ok } = operation as { operation?: unknown; path?: unknown; ok?: unknown };
|
|
393
|
+
if (typeof kind !== "string") continue;
|
|
394
|
+
if (ok === false) failed += 1;
|
|
395
|
+
const target = kind === "call" ? callCounts : kind === "describe" ? describeCounts : null;
|
|
396
|
+
if (target && typeof path === "string") {
|
|
397
|
+
target.set(path, (target.get(path) ?? 0) + 1);
|
|
398
|
+
} else {
|
|
399
|
+
metaCounts.set(kind, (metaCounts.get(kind) ?? 0) + 1);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const withCount = (name: string, count: number) => (count > 1 ? `${name}×${count}` : name);
|
|
404
|
+
const listPaths = (counts: Map<string, number>) => {
|
|
405
|
+
const names = [...counts].map(([path, count]) => withCount(formatTracedPath(path), count));
|
|
406
|
+
return names.length > maxTools
|
|
407
|
+
? [...names.slice(0, maxTools), `+${names.length - maxTools} more`].join(", ")
|
|
408
|
+
: names.join(", ");
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
let tools = listPaths(callCounts);
|
|
412
|
+
if (!tools && describeCounts.size > 0) {
|
|
413
|
+
tools = `describe ${listPaths(describeCounts)}`;
|
|
414
|
+
} else {
|
|
415
|
+
const describes = [...describeCounts.values()].reduce((sum, count) => sum + count, 0);
|
|
416
|
+
if (describes > 0) metaCounts.set("describe", describes);
|
|
417
|
+
}
|
|
418
|
+
const meta = [...metaCounts].map(([kind, count]) => withCount(kind, count)).join(", ");
|
|
419
|
+
return { failed, preview: [tools, meta].filter(Boolean).join(" · ") };
|
|
420
|
+
}
|
|
421
|
+
|
|
334
422
|
function blockToLines(block: McpToolContentBlock): string[] {
|
|
335
423
|
if (block.type === "text") {
|
|
336
424
|
return block.text.split("\n");
|
|
@@ -448,6 +536,11 @@ export function renderMcpToolResult(
|
|
|
448
536
|
const expanded = options.expanded || context?.isError === true || hasErrorDetails;
|
|
449
537
|
if (!expanded && renderOptions.resultRendering === "compact") {
|
|
450
538
|
const display = formatMcpToolResultLines(result, false, renderOptions.collapsedResultLines);
|
|
539
|
+
const scriptSummary = formatMcpScriptCallSummary(result.details);
|
|
540
|
+
if (scriptSummary) {
|
|
541
|
+
const status = scriptSummary.failed > 0 ? `✗${scriptSummary.failed}` : "";
|
|
542
|
+
return new CompactMcpToolResult(MCP_SCRIPT_TITLE, scriptSummary.preview, display, activeTheme, status);
|
|
543
|
+
}
|
|
451
544
|
const title = context?.state?.compactTitle ?? formatMcpToolResultIdentity(result.details) ?? "";
|
|
452
545
|
const inputPreview = context?.state?.compactInputPreview ?? "";
|
|
453
546
|
return new CompactMcpToolResult(title, inputPreview, display, activeTheme);
|
|
@@ -192,7 +192,7 @@ export interface UiServerHandle {
|
|
|
192
192
|
sessionToken: string;
|
|
193
193
|
serverName: string;
|
|
194
194
|
toolName: string;
|
|
195
|
-
viewer?: "browser" | "glimpse" | "suppressed";
|
|
195
|
+
viewer?: "browser" | "glimpse" | "orca" | "suppressed";
|
|
196
196
|
windowOpen?: boolean;
|
|
197
197
|
close: (reason?: string) => void;
|
|
198
198
|
sendToolInput: (args: Record<string, unknown>) => void;
|
|
@@ -740,7 +740,7 @@ export interface PromptMetadata {
|
|
|
740
740
|
}
|
|
741
741
|
|
|
742
742
|
export interface DirectToolSpec {
|
|
743
|
-
/** Registered inactive; `mcp({ search })` activates it (directTools: "search"). */
|
|
743
|
+
/** Registered inactive; `mcp({ search })` or a successful `mcp({ tool })` call activates it (directTools: "search"). */
|
|
744
744
|
lazy?: boolean;
|
|
745
745
|
serverName: string;
|
|
746
746
|
originalName: string;
|
|
@@ -35,7 +35,7 @@ export interface UiSessionRequest {
|
|
|
35
35
|
onNeedsAuth?: SessionRecoveryDeps["onNeedsAuth"];
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export type UiSessionViewer = "browser" | "glimpse" | "suppressed";
|
|
38
|
+
export type UiSessionViewer = "browser" | "glimpse" | "orca" | "suppressed";
|
|
39
39
|
|
|
40
40
|
export interface UiSessionRuntime {
|
|
41
41
|
serverName: string;
|
|
@@ -143,6 +143,19 @@ async function openInBrowser(state: McpExtensionState, url: string, signal: Abor
|
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
async function openInOrcaBrowser(url: string, signal: AbortSignal): Promise<void> {
|
|
147
|
+
throwIfAborted(signal);
|
|
148
|
+
await new Promise<void>((resolve, reject) => {
|
|
149
|
+
execFile("orca", ["goto", "--url", url], { timeout: 10000, signal }, (error) => {
|
|
150
|
+
if (error) {
|
|
151
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
152
|
+
} else {
|
|
153
|
+
resolve();
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
146
159
|
function isRemoteSession(): boolean {
|
|
147
160
|
return Boolean(process.env.SSH_CONNECTION || process.env.SSH_TTY);
|
|
148
161
|
}
|
|
@@ -486,7 +499,6 @@ export async function maybeStartUiSession(
|
|
|
486
499
|
`If this session is remote, run ssh -L ${handle.port}:127.0.0.1:${handle.port} -L ${handle.proxyPort}:127.0.0.1:${handle.proxyPort} <this-host> first.`,
|
|
487
500
|
"info",
|
|
488
501
|
);
|
|
489
|
-
log.info("Suppressing MCP UI window (MCP_UI_VIEWER=" + viewerPref + ")", { url: handle.url });
|
|
490
502
|
} else {
|
|
491
503
|
const remoteLikely = remoteByEnv || await hasActiveRemoteLogin();
|
|
492
504
|
const emitRemoteHint = async (openError: string | null, openedOnHost = false) => {
|
|
@@ -499,33 +511,17 @@ export async function maybeStartUiSession(
|
|
|
499
511
|
openedOnHost,
|
|
500
512
|
}), openError === null ? "info" : "warning");
|
|
501
513
|
};
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
(viewerPref !== "browser" && glimpseDetected));
|
|
505
|
-
|
|
506
|
-
if (useGlimpse) {
|
|
514
|
+
// Handle Orca browser viewer
|
|
515
|
+
if (viewerPref === "orca") {
|
|
507
516
|
try {
|
|
508
|
-
|
|
509
|
-
const glimpseWindow = await openGlimpseWindow(glimpseHtml, {
|
|
510
|
-
title: `MCP · ${request.serverName} · ${request.toolName}`,
|
|
511
|
-
width: 1000,
|
|
512
|
-
height: 800,
|
|
513
|
-
onClosed: () => {
|
|
514
|
-
if (active) handle.close("glimpse-closed");
|
|
515
|
-
},
|
|
516
|
-
});
|
|
517
|
-
if (state.owner?.isActive() === false || runtimeSignal.aborted) {
|
|
518
|
-
glimpseWindow.close();
|
|
519
|
-
throwIfAborted(runtimeSignal);
|
|
520
|
-
throw new Error("MCP Glimpse window became stale before registration");
|
|
521
|
-
}
|
|
522
|
-
activeGlimpseWindow = glimpseWindow;
|
|
523
|
-
viewer = "glimpse";
|
|
517
|
+
await openInOrcaBrowser(handle.url, runtimeSignal);
|
|
524
518
|
if (remoteLikely) {
|
|
525
519
|
await emitRemoteHint(null, true);
|
|
526
520
|
}
|
|
521
|
+
viewer = "orca";
|
|
527
522
|
} catch (error) {
|
|
528
|
-
|
|
523
|
+
if (isAbortError(error, runtimeSignal)) throw error;
|
|
524
|
+
log.debug("Orca browser failed, using fallback", {
|
|
529
525
|
error: error instanceof Error ? error.message : String(error),
|
|
530
526
|
});
|
|
531
527
|
const openError = await openInBrowser(state, handle.url, runtimeSignal);
|
|
@@ -535,9 +531,46 @@ export async function maybeStartUiSession(
|
|
|
535
531
|
viewer = "browser";
|
|
536
532
|
}
|
|
537
533
|
} else {
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
534
|
+
const glimpseDetected = !remoteByEnv && isGlimpseAvailable();
|
|
535
|
+
const useGlimpse = !remoteByEnv && (viewerPref === "glimpse" ||
|
|
536
|
+
(viewerPref !== "browser" && glimpseDetected));
|
|
537
|
+
|
|
538
|
+
if (useGlimpse) {
|
|
539
|
+
try {
|
|
540
|
+
const glimpseHtml = `<!DOCTYPE html><html><head><meta charset="utf-8"><style>body{margin:0;padding:0;width:100vw;height:100vh;overflow:hidden}iframe{width:100%;height:100%;border:none}</style></head><body><iframe src="${handle.url}"></iframe></body></html>`;
|
|
541
|
+
const glimpseWindow = await openGlimpseWindow(glimpseHtml, {
|
|
542
|
+
title: `MCP · ${request.serverName} · ${request.toolName}`,
|
|
543
|
+
width: 1000,
|
|
544
|
+
height: 800,
|
|
545
|
+
onClosed: () => {
|
|
546
|
+
if (active) handle.close("glimpse-closed");
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
if (state.owner?.isActive() === false || runtimeSignal.aborted) {
|
|
550
|
+
glimpseWindow.close();
|
|
551
|
+
throwIfAborted(runtimeSignal);
|
|
552
|
+
throw new Error("MCP Glimpse window became stale before registration");
|
|
553
|
+
}
|
|
554
|
+
activeGlimpseWindow = glimpseWindow;
|
|
555
|
+
viewer = "glimpse";
|
|
556
|
+
if (remoteLikely) {
|
|
557
|
+
await emitRemoteHint(null, true);
|
|
558
|
+
}
|
|
559
|
+
} catch (error) {
|
|
560
|
+
log.debug("Glimpse unavailable, using browser", {
|
|
561
|
+
error: error instanceof Error ? error.message : String(error),
|
|
562
|
+
});
|
|
563
|
+
const openError = await openInBrowser(state, handle.url, runtimeSignal);
|
|
564
|
+
if (openError !== null || remoteLikely) {
|
|
565
|
+
await emitRemoteHint(openError);
|
|
566
|
+
}
|
|
567
|
+
viewer = "browser";
|
|
568
|
+
}
|
|
569
|
+
} else {
|
|
570
|
+
const openError = await openInBrowser(state, handle.url, runtimeSignal);
|
|
571
|
+
if (openError !== null || remoteLikely) {
|
|
572
|
+
await emitRemoteHint(openError);
|
|
573
|
+
}
|
|
541
574
|
}
|
|
542
575
|
}
|
|
543
576
|
}
|
|
@@ -247,10 +247,18 @@ export function resolveServerUrl(definition: Pick<ServerEntry, "url">, environme
|
|
|
247
247
|
export function resolveConfigPath(value: string | undefined, environment: NodeJS.ProcessEnv = process.env): string | undefined {
|
|
248
248
|
if (value === undefined) return undefined;
|
|
249
249
|
|
|
250
|
-
|
|
250
|
+
return expandHomePath(interpolateEnvVars(value, environment));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Expand a leading home-directory marker without interpolating environment variables. */
|
|
254
|
+
export function expandHomePath(value: string | undefined): string | undefined {
|
|
255
|
+
if (value === undefined) return undefined;
|
|
256
|
+
|
|
257
|
+
const resolved = value;
|
|
251
258
|
if (resolved === "~") return homedir();
|
|
252
|
-
if (resolved.startsWith("~/") || resolved.startsWith("~\\")) {
|
|
253
|
-
|
|
259
|
+
if (resolved.startsWith("~/") || (platform() === "win32" && resolved.startsWith("~\\"))) {
|
|
260
|
+
const suffix = platform() === "win32" ? resolved.slice(2).replace(/[\\/]/g, sep) : resolved.slice(2);
|
|
261
|
+
return join(homedir(), suffix);
|
|
254
262
|
}
|
|
255
263
|
return resolved;
|
|
256
264
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Publish to npm
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
id-token: write
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v6
|
|
15
|
+
- uses: actions/setup-node@v6
|
|
16
|
+
with:
|
|
17
|
+
node-version: 24
|
|
18
|
+
registry-url: https://registry.npmjs.org
|
|
19
|
+
package-manager-cache: false
|
|
20
|
+
- run: npm publish --access public
|