@humain/terminal 0.0.3-beta.2 → 0.0.4-beta.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/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +0 -1
- package/dist/bun/cli.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +7 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +4 -0
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +46 -22
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/tools/index.d.ts +2 -1
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +9 -1
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +82 -0
- package/dist/core/tools/subagent.d.ts.map +1 -0
- package/dist/core/tools/subagent.js +980 -0
- package/dist/core/tools/subagent.js.map +1 -0
- package/dist/humain/bedrock-provider/vendor/index.bundle.d.mts +3 -0
- package/dist/humain/bedrock-provider/vendor/index.bundle.mjs +1509 -0
- package/dist/humain/index.d.ts.map +1 -1
- package/dist/humain/index.js +3 -0
- package/dist/humain/index.js.map +1 -1
- package/dist/humain/mcp-adapter/vendor/CHANGELOG.md +36 -0
- package/dist/humain/mcp-adapter/vendor/README.md +47 -5
- package/dist/humain/mcp-adapter/vendor/cli.js +163 -1
- package/dist/humain/mcp-adapter/vendor/commands.ts +128 -22
- package/dist/humain/mcp-adapter/vendor/config.ts +25 -7
- package/dist/humain/mcp-adapter/vendor/direct-tools.ts +5 -4
- package/dist/humain/mcp-adapter/vendor/index.bundle.mjs +1663 -458
- package/dist/humain/mcp-adapter/vendor/index.ts +219 -32
- package/dist/humain/mcp-adapter/vendor/init.ts +36 -4
- package/dist/humain/mcp-adapter/vendor/lifecycle.ts +315 -24
- package/dist/humain/mcp-adapter/vendor/mcp-auth-flow.ts +39 -6
- package/dist/humain/mcp-adapter/vendor/mcp-bearer-store.ts +387 -0
- package/dist/humain/mcp-adapter/vendor/mcp-callback-server.ts +27 -3
- package/dist/humain/mcp-adapter/vendor/mcp-code.ts +4 -0
- package/dist/humain/mcp-adapter/vendor/mcp-panel.ts +42 -13
- package/dist/humain/mcp-adapter/vendor/metadata-cache.ts +32 -21
- package/dist/humain/mcp-adapter/vendor/package-mcp-loader.ts +168 -0
- package/dist/humain/mcp-adapter/vendor/package.json +179 -177
- package/dist/humain/mcp-adapter/vendor/proxy-modes.ts +5 -4
- package/dist/humain/mcp-adapter/vendor/request-headers-command.ts +31 -37
- package/dist/humain/mcp-adapter/vendor/search-ranking.ts +92 -22
- package/dist/humain/mcp-adapter/vendor/server-manager.ts +140 -2
- package/dist/humain/mcp-adapter/vendor/session-recovery.ts +7 -0
- package/dist/humain/mcp-adapter/vendor/tool-approval.ts +15 -2
- package/dist/humain/mcp-adapter/vendor/tool-result-renderer.ts +44 -4
- package/dist/humain/mcp-adapter/vendor/types.ts +2 -0
- package/dist/humain/mcp-adapter/vendor/ui-server.ts +18 -16
- package/dist/humain/mcp-adapter/vendor/utils.ts +70 -0
- package/dist/humain/web-access/vendor/anysearch.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/anysearch.js +3 -2
- package/dist/humain/web-access/vendor/anysearch.js.map +1 -1
- package/dist/humain/web-access/vendor/brave.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/brave.js +14 -4
- package/dist/humain/web-access/vendor/brave.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.d.ts +3 -0
- package/dist/humain/web-access/vendor/curator-page.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-page.js +4 -1
- package/dist/humain/web-access/vendor/curator-page.js.map +1 -1
- package/dist/humain/web-access/vendor/curator-server.d.ts +3 -0
- package/dist/humain/web-access/vendor/curator-server.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/curator-server.js +6 -0
- package/dist/humain/web-access/vendor/curator-server.js.map +1 -1
- package/dist/humain/web-access/vendor/exa.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/exa.js +16 -7
- package/dist/humain/web-access/vendor/exa.js.map +1 -1
- package/dist/humain/web-access/vendor/extract.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/extract.js +31 -3
- package/dist/humain/web-access/vendor/extract.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 +25 -4
- 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 +36 -49
- package/dist/humain/web-access/vendor/index.js.map +1 -1
- package/dist/humain/web-access/vendor/page-query.d.ts +1 -2
- package/dist/humain/web-access/vendor/page-query.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/page-query.js +5 -2
- package/dist/humain/web-access/vendor/page-query.js.map +1 -1
- package/dist/humain/web-access/vendor/parallel-mcp.d.ts +10 -0
- package/dist/humain/web-access/vendor/parallel-mcp.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/parallel-mcp.js +224 -0
- package/dist/humain/web-access/vendor/parallel-mcp.js.map +1 -0
- package/dist/humain/web-access/vendor/parallel.d.ts +1 -0
- package/dist/humain/web-access/vendor/parallel.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/parallel.js +2 -2
- package/dist/humain/web-access/vendor/parallel.js.map +1 -1
- package/dist/humain/web-access/vendor/query-rewrite.d.ts +3 -0
- package/dist/humain/web-access/vendor/query-rewrite.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/query-rewrite.js +42 -0
- package/dist/humain/web-access/vendor/query-rewrite.js.map +1 -0
- package/dist/humain/web-access/vendor/serper.d.ts +4 -0
- package/dist/humain/web-access/vendor/serper.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/serper.js +206 -0
- package/dist/humain/web-access/vendor/serper.js.map +1 -0
- package/dist/humain/web-access/vendor/summary-model-scope.d.ts +5 -0
- package/dist/humain/web-access/vendor/summary-model-scope.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/summary-model-scope.js +10 -5
- package/dist/humain/web-access/vendor/summary-model-scope.js.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/summary-review.js +40 -14
- package/dist/humain/web-access/vendor/summary-review.js.map +1 -1
- package/dist/humain/web-access/vendor/tavily.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/tavily.js +14 -4
- package/dist/humain/web-access/vendor/tavily.js.map +1 -1
- package/dist/humain/web-access/vendor/utils.d.ts +10 -0
- package/dist/humain/web-access/vendor/utils.d.ts.map +1 -1
- package/dist/humain/web-access/vendor/utils.js +70 -0
- package/dist/humain/web-access/vendor/utils.js.map +1 -1
- package/dist/humain/web-access/vendor/valyu.d.ts +6 -0
- package/dist/humain/web-access/vendor/valyu.d.ts.map +1 -0
- package/dist/humain/web-access/vendor/valyu.js +194 -0
- package/dist/humain/web-access/vendor/valyu.js.map +1 -0
- package/docs/extensions.md +1 -1
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/subagent/README.md +24 -147
- package/npm-shrinkwrap.json +7 -2
- package/package.json +7 -2
- package/src/humain/mcp-adapter/UPSTREAM.md +1 -1
- package/src/humain/mcp-adapter/upstream.json +3 -3
- package/src/humain/mcp-adapter/vendor/CHANGELOG.md +36 -0
- package/src/humain/web-access/UPSTREAM.md +1 -1
- package/src/humain/web-access/upstream.json +4 -4
- package/dist/bun/register-bedrock.d.ts +0 -2
- package/dist/bun/register-bedrock.d.ts.map +0 -1
- package/dist/bun/register-bedrock.js +0 -4
- package/dist/bun/register-bedrock.js.map +0 -1
- package/examples/extensions/subagent/agents.ts +0 -126
- package/examples/extensions/subagent/index.ts +0 -1015
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { AgentToolUpdateCallback, ExtensionAPI, ExtensionContext, ToolInfo } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { McpExtensionState } from "./state.ts";
|
|
3
|
-
import type { DirectToolSpec, McpAdapterOptions, McpConfig, PromptMetadata } from "./types.ts";
|
|
3
|
+
import type { DirectToolSpec, McpAdapterOptions, McpConfig, PromptMetadata, ServerEntry } from "./types.ts";
|
|
4
4
|
import type { McpOAuthRuntime } from "./mcp-auth-flow.ts";
|
|
5
5
|
import { Type } from "typebox";
|
|
6
6
|
import type { TSchema } from "typebox";
|
|
7
|
-
import { showStatus, showTools, showPrompts, reconnectServer, reconnectServers, authenticateServer, logoutServer, openMcpAuthPanel, openMcpPanel, openMcpSetup } from "./commands.ts";
|
|
7
|
+
import { showStatus, showTools, showPrompts, reconnectServer, reconnectServers, authenticateServer, logoutServer, manageBearerToken, openMcpAuthPanel, openMcpPanel, openMcpSetup } from "./commands.ts";
|
|
8
8
|
import { cloneMcpConfig, loadMcpConfig, writeProjectServerDisabledOverride } from "./config.ts";
|
|
9
9
|
import { buildProxyDescription, createDirectToolExecutor, getMissingConfiguredDirectToolServers, resolveDirectTools } from "./direct-tools.ts";
|
|
10
10
|
import { flushMetadataCache, initializeMcp, updateStatusBar } from "./init.ts";
|
|
@@ -22,6 +22,7 @@ import { runMcpScript } from "./mcp-code.ts";
|
|
|
22
22
|
import { cleanupMaterializedBinaryResources } from "./tool-registrar.ts";
|
|
23
23
|
|
|
24
24
|
export type { McpAdapterOptions } from "./types.ts";
|
|
25
|
+
export type { ServerEntry } from "./types.ts";
|
|
25
26
|
export {
|
|
26
27
|
MCP_STATUS_EVENT,
|
|
27
28
|
MCP_STATUS_SNAPSHOT_VERSION,
|
|
@@ -38,6 +39,14 @@ export {
|
|
|
38
39
|
const INIT_WAIT_TIMEOUT_MS = 30_000;
|
|
39
40
|
const INIT_WAIT_TIMED_OUT: unique symbol = Symbol("init-wait-timed-out");
|
|
40
41
|
|
|
42
|
+
export interface McpServerRegistration {
|
|
43
|
+
dispose(): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Routes runtime registrations to the adapter installed for a specific Pi
|
|
47
|
+
// instance, so a process with several adapters cannot cross-register.
|
|
48
|
+
const runtimeRegistrars = new WeakMap<ExtensionAPI, (name: string, definition: ServerEntry) => McpServerRegistration>();
|
|
49
|
+
|
|
41
50
|
async function awaitWithTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T | typeof INIT_WAIT_TIMED_OUT> {
|
|
42
51
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
43
52
|
try {
|
|
@@ -130,6 +139,19 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
130
139
|
let proxyToolRegistered = false;
|
|
131
140
|
let proxyToolDescription: string | null = null;
|
|
132
141
|
let directToolsFrozen = false;
|
|
142
|
+
// Session/runtime scoped server registrations from other extensions. They
|
|
143
|
+
// survive session restarts within this install and die with the process.
|
|
144
|
+
const runtimeServers = new Map<string, ServerEntry>();
|
|
145
|
+
|
|
146
|
+
// Mirrors init's per-server lifecycle registration so runtime servers get
|
|
147
|
+
// idle cleanup and keep-alive health recovery like configured servers.
|
|
148
|
+
function attachRuntimeServerLifecycle(targetState: McpExtensionState, name: string, definition: ServerEntry): void {
|
|
149
|
+
const lifecycleMode = definition.lifecycle ?? "lazy";
|
|
150
|
+
const persistsAfterFirstSpawn = lifecycleMode === "eager" || lifecycleMode === "lazy-keep-alive";
|
|
151
|
+
const idleOverride = definition.idleTimeout ?? (persistsAfterFirstSpawn ? 0 : undefined);
|
|
152
|
+
targetState.lifecycle.registerServer(name, definition, idleOverride !== undefined ? { idleTimeout: idleOverride } : undefined);
|
|
153
|
+
if (lifecycleMode === "keep-alive") targetState.lifecycle.markKeepAlive(name, definition);
|
|
154
|
+
}
|
|
133
155
|
|
|
134
156
|
// OMP remaps `typebox` to a host shim that historically lacked Type.Unsafe.
|
|
135
157
|
// Prefer Unsafe when present (real TypeBox / fixed OMP shim); otherwise pass
|
|
@@ -282,6 +304,45 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
282
304
|
|
|
283
305
|
registerPromptCommands(resolveCachedPrompts(earlyConfig));
|
|
284
306
|
|
|
307
|
+
runtimeRegistrars.set(pi, (name: string, definition: ServerEntry): McpServerRegistration => {
|
|
308
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
309
|
+
throw new Error("MCP server name must be a non-empty string");
|
|
310
|
+
}
|
|
311
|
+
if (typeof definition !== "object" || definition === null || Array.isArray(definition)) {
|
|
312
|
+
throw new Error(`MCP server definition for "${name}" must be an object`);
|
|
313
|
+
}
|
|
314
|
+
const effective = state?.config ?? earlyConfig;
|
|
315
|
+
if (runtimeServers.has(name) || Object.hasOwn(effective.mcpServers, name)) {
|
|
316
|
+
throw new Error(`MCP server "${name}" is already registered`);
|
|
317
|
+
}
|
|
318
|
+
// Runtime-registered servers are proxy-tool-only: direct tools are frozen
|
|
319
|
+
// at startup and must not be rebuilt for late registrations.
|
|
320
|
+
const entry: ServerEntry = { ...structuredClone(definition), directTools: false };
|
|
321
|
+
runtimeServers.set(name, entry);
|
|
322
|
+
const registeredState = state;
|
|
323
|
+
if (registeredState) {
|
|
324
|
+
registeredState.config.mcpServers[name] = entry;
|
|
325
|
+
attachRuntimeServerLifecycle(registeredState, name, entry);
|
|
326
|
+
syncToolSurface();
|
|
327
|
+
updateStatusBar(registeredState);
|
|
328
|
+
}
|
|
329
|
+
let disposed = false;
|
|
330
|
+
return {
|
|
331
|
+
dispose: async (): Promise<void> => {
|
|
332
|
+
if (disposed) return;
|
|
333
|
+
disposed = true;
|
|
334
|
+
runtimeServers.delete(name);
|
|
335
|
+
const currentState = state;
|
|
336
|
+
if (!currentState || currentState.config.mcpServers[name] !== entry) return;
|
|
337
|
+
delete currentState.config.mcpServers[name];
|
|
338
|
+
currentState.lifecycle.unregisterServer(name);
|
|
339
|
+
await currentState.manager.close(name);
|
|
340
|
+
syncToolSurface();
|
|
341
|
+
updateStatusBar(currentState);
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
});
|
|
345
|
+
|
|
285
346
|
const getPiTools = (): ToolInfo[] => pi.getAllTools();
|
|
286
347
|
|
|
287
348
|
pi.registerFlag("mcp-config", {
|
|
@@ -299,7 +360,6 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
299
360
|
}
|
|
300
361
|
: {}),
|
|
301
362
|
oauthRuntime,
|
|
302
|
-
statusEvents: pi.events,
|
|
303
363
|
});
|
|
304
364
|
initPromise = promise;
|
|
305
365
|
|
|
@@ -314,6 +374,14 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
314
374
|
}
|
|
315
375
|
|
|
316
376
|
state = nextState;
|
|
377
|
+
for (const [name, definition] of runtimeServers) {
|
|
378
|
+
if (Object.hasOwn(nextState.config.mcpServers, name)) {
|
|
379
|
+
console.error(`MCP: runtime-registered server "${name}" now collides with a configured server; keeping the configured server`);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
nextState.config.mcpServers[name] = definition;
|
|
383
|
+
attachRuntimeServerLifecycle(nextState, name, definition);
|
|
384
|
+
}
|
|
317
385
|
nextState.onToolMetadataUpdated = (_serverName, _reason) => {
|
|
318
386
|
if (state !== nextState || !owner.isActive()) return;
|
|
319
387
|
syncPromptCommands();
|
|
@@ -325,6 +393,9 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
325
393
|
};
|
|
326
394
|
syncPromptCommands();
|
|
327
395
|
syncToolSurface(ctx);
|
|
396
|
+
// A connected snapshot is readiness-like external state. Publish it only
|
|
397
|
+
// after Pi's model-facing direct-tool surface reflects live metadata.
|
|
398
|
+
nextState.statusEvents = pi.events;
|
|
328
399
|
updateStatusBar(nextState);
|
|
329
400
|
initPromise = null;
|
|
330
401
|
if (earlyConfig.settings?.freezeDirectTools === true) {
|
|
@@ -417,6 +488,29 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
417
488
|
}
|
|
418
489
|
});
|
|
419
490
|
|
|
491
|
+
pi.on("input", async () => {
|
|
492
|
+
const inputOwner = currentOwner;
|
|
493
|
+
if (!inputOwner?.isActive()) return;
|
|
494
|
+
|
|
495
|
+
if (!state && initPromise) {
|
|
496
|
+
try {
|
|
497
|
+
await awaitWithTimeout(initPromise, INIT_WAIT_TIMEOUT_MS);
|
|
498
|
+
} catch {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
const inputState = state;
|
|
504
|
+
if (!inputState || !inputOwner.isActive()) return;
|
|
505
|
+
try {
|
|
506
|
+
await inputState.lifecycle.ensureConverged(inputOwner.signal);
|
|
507
|
+
} catch (error) {
|
|
508
|
+
if (!isAbortError(error, inputOwner.signal)) {
|
|
509
|
+
logger.debug(`MCP: keep-alive convergence failed before input: ${formatTerminalError(error)}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
420
514
|
pi.on("session_shutdown", async () => {
|
|
421
515
|
++lifecycleGeneration;
|
|
422
516
|
const currentState = state;
|
|
@@ -444,7 +538,7 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
444
538
|
// Re-flag returned MCP tool failures so pi registers them as errors (see toolErrorOverride).
|
|
445
539
|
pi.on("tool_result", (event) => toolErrorOverride(event.details));
|
|
446
540
|
|
|
447
|
-
pi.registerCommand(
|
|
541
|
+
const registerMcpCommand = (commandName: string) => pi.registerCommand(commandName, {
|
|
448
542
|
description: "Show MCP server status",
|
|
449
543
|
getArgumentCompletions: (prefix: string) => {
|
|
450
544
|
const normalized = prefix.trimStart();
|
|
@@ -456,6 +550,7 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
456
550
|
{ value: "prompts", label: "prompts — List all MCP prompts" },
|
|
457
551
|
{ value: "setup", label: "setup — Configure MCP servers" },
|
|
458
552
|
{ value: "logout", label: "logout — Clear server credentials" },
|
|
553
|
+
{ value: "token", label: "token — Manage stored bearer tokens" },
|
|
459
554
|
{ value: "disable", label: "disable — Disable a server" },
|
|
460
555
|
{ value: "enable", label: "enable — Enable a server" },
|
|
461
556
|
{ value: "status", label: "status — Show server status" },
|
|
@@ -465,11 +560,27 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
465
560
|
|
|
466
561
|
const [, subcommand, argumentPrefix] = argumentMatch;
|
|
467
562
|
if (
|
|
468
|
-
(subcommand !== "reconnect" && subcommand !== "logout" && subcommand !== "disable" && subcommand !== "enable")
|
|
563
|
+
(subcommand !== "reconnect" && subcommand !== "logout" && subcommand !== "disable" && subcommand !== "enable" && subcommand !== "token")
|
|
469
564
|
|| argumentPrefix === undefined
|
|
470
565
|
|| !state
|
|
471
566
|
) return null;
|
|
472
567
|
|
|
568
|
+
if (subcommand === "token") {
|
|
569
|
+
const tokenMatch = argumentPrefix.trimStart().match(/^(set|remove|status)\s+(.*)$/);
|
|
570
|
+
if (!tokenMatch) {
|
|
571
|
+
const actions = ["set", "remove", "status"]
|
|
572
|
+
.filter(action => action.startsWith(argumentPrefix.trimStart()))
|
|
573
|
+
.map(action => ({ value: `token ${action} `, label: `${action} — Bearer token ${action}` }));
|
|
574
|
+
return actions.length > 0 ? actions : null;
|
|
575
|
+
}
|
|
576
|
+
const action = tokenMatch[1] ?? "";
|
|
577
|
+
const serverPrefix = tokenMatch[2] ?? "";
|
|
578
|
+
const servers = Object.keys(state.config.mcpServers)
|
|
579
|
+
.filter(serverName => serverName.startsWith(serverPrefix.trimStart()))
|
|
580
|
+
.map(serverName => ({ value: `token ${action} ${serverName}`, label: serverName }));
|
|
581
|
+
return servers.length > 0 ? servers : null;
|
|
582
|
+
}
|
|
583
|
+
|
|
473
584
|
const servers = Object.keys(state.config.mcpServers)
|
|
474
585
|
.filter((serverName) => serverName.startsWith(argumentPrefix.trimStart()))
|
|
475
586
|
.map((serverName) => ({ value: `${subcommand} ${serverName}`, label: serverName }));
|
|
@@ -545,6 +656,21 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
545
656
|
await logoutServer(serverName, state, commandCtx);
|
|
546
657
|
break;
|
|
547
658
|
}
|
|
659
|
+
case "token": {
|
|
660
|
+
const action = parts[1];
|
|
661
|
+
const serverName = parts.slice(2).join(" ");
|
|
662
|
+
if (action !== "set" && action !== "remove" && action !== "status") {
|
|
663
|
+
if (commandCtx.hasUI) commandCtx.ui?.notify("Usage: /mcp token set|remove|status <server>", "error");
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
if (!serverName) {
|
|
667
|
+
if (commandCtx.hasUI) commandCtx.ui?.notify("Usage: /mcp token set|remove|status <server>", "error");
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
commandOwner?.throwIfInactive();
|
|
671
|
+
await manageBearerToken(action, serverName, state, commandCtx);
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
548
674
|
case "disable":
|
|
549
675
|
case "enable": {
|
|
550
676
|
const serverName = rest;
|
|
@@ -595,6 +721,8 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
595
721
|
}
|
|
596
722
|
},
|
|
597
723
|
});
|
|
724
|
+
registerMcpCommand("mcp");
|
|
725
|
+
registerMcpCommand("pi-mcp");
|
|
598
726
|
|
|
599
727
|
pi.registerCommand("mcp-auth", {
|
|
600
728
|
description: "Authenticate with an MCP server (OAuth)",
|
|
@@ -737,12 +865,12 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
737
865
|
action?: string;
|
|
738
866
|
}, signal: AbortSignal | undefined, _onUpdate: AgentToolUpdateCallback<Record<string, unknown>> | undefined, _ctx: ExtensionContext) {
|
|
739
867
|
const executeOwner = currentOwner;
|
|
740
|
-
|
|
741
|
-
|
|
868
|
+
const parseArgs = (value: string | Record<string, unknown> | undefined): Record<string, unknown> | undefined => {
|
|
869
|
+
if (value === undefined || value === "") return undefined;
|
|
742
870
|
let args: unknown;
|
|
743
|
-
if (typeof
|
|
871
|
+
if (typeof value === "string") {
|
|
744
872
|
try {
|
|
745
|
-
args = JSON.parse(
|
|
873
|
+
args = JSON.parse(value);
|
|
746
874
|
} catch (error) {
|
|
747
875
|
if (error instanceof SyntaxError) {
|
|
748
876
|
throw new Error(`Invalid args JSON: ${error.message}`, { cause: error });
|
|
@@ -750,14 +878,57 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
750
878
|
throw error;
|
|
751
879
|
}
|
|
752
880
|
} else {
|
|
753
|
-
args =
|
|
881
|
+
args = value;
|
|
754
882
|
}
|
|
755
883
|
|
|
756
884
|
if (typeof args !== "object" || args === null || Array.isArray(args)) {
|
|
757
885
|
const gotType = Array.isArray(args) ? "array" : args === null ? "null" : typeof args;
|
|
758
886
|
throw new Error(`Invalid args: expected a JSON object, got ${gotType}`);
|
|
759
887
|
}
|
|
760
|
-
|
|
888
|
+
return args as Record<string, unknown>;
|
|
889
|
+
};
|
|
890
|
+
const validateNestedGatewayParams = (value: Record<string, unknown>): typeof params => {
|
|
891
|
+
for (const key of ["tool", "connect", "describe", "instructions", "search", "server", "action"] as const) {
|
|
892
|
+
if (value[key] !== undefined && typeof value[key] !== "string") {
|
|
893
|
+
throw new Error(`Invalid nested gateway param \`${key}\`: expected string, got ${Array.isArray(value[key]) ? "array" : typeof value[key]}`);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
for (const key of ["regex", "includeSchemas"] as const) {
|
|
897
|
+
if (value[key] !== undefined && typeof value[key] !== "boolean") {
|
|
898
|
+
throw new Error(`Invalid nested gateway param \`${key}\`: expected boolean, got ${Array.isArray(value[key]) ? "array" : typeof value[key]}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
for (const key of ["limit", "offset"] as const) {
|
|
902
|
+
if (value[key] !== undefined && typeof value[key] !== "number") {
|
|
903
|
+
throw new Error(`Invalid nested gateway param \`${key}\`: expected number, got ${Array.isArray(value[key]) ? "array" : typeof value[key]}`);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
const args = value.args;
|
|
907
|
+
if (args !== undefined && typeof args !== "string" && (typeof args !== "object" || args === null || Array.isArray(args))) {
|
|
908
|
+
throw new Error(`Invalid nested gateway param \`args\`: expected JSON object or JSON string, got ${Array.isArray(args) ? "array" : args === null ? "null" : typeof args}`);
|
|
909
|
+
}
|
|
910
|
+
return value as typeof params;
|
|
911
|
+
};
|
|
912
|
+
let parsedArgs = parseArgs(params.args);
|
|
913
|
+
let dispatchParams = params;
|
|
914
|
+
const hasGatewayMode = (value: typeof params): boolean =>
|
|
915
|
+
value.tool !== undefined
|
|
916
|
+
|| value.connect !== undefined
|
|
917
|
+
|| value.describe !== undefined
|
|
918
|
+
|| value.instructions !== undefined
|
|
919
|
+
|| value.search !== undefined
|
|
920
|
+
|| value.server !== undefined
|
|
921
|
+
|| value.action !== undefined;
|
|
922
|
+
if (!hasGatewayMode(params) && parsedArgs) {
|
|
923
|
+
const nestedParams = validateNestedGatewayParams(parsedArgs);
|
|
924
|
+
if (hasGatewayMode(nestedParams)) {
|
|
925
|
+
dispatchParams = nestedParams;
|
|
926
|
+
parsedArgs = parseArgs(nestedParams.args);
|
|
927
|
+
} else {
|
|
928
|
+
throw new Error("Gateway params were nested inside `args`; pass them top-level (for example, mcp({ search: \"...\" }) or mcp({ tool: \"...\", args: {} })).");
|
|
929
|
+
}
|
|
930
|
+
} else if (!hasGatewayMode(params) && params.args !== undefined) {
|
|
931
|
+
throw new Error("Gateway params were nested inside `args`; pass them top-level (for example, mcp({ search: \"...\" }) or mcp({ tool: \"...\", args: {} })).");
|
|
761
932
|
}
|
|
762
933
|
|
|
763
934
|
if (!state && initPromise) {
|
|
@@ -788,22 +959,22 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
788
959
|
}
|
|
789
960
|
executeOwner?.throwIfInactive();
|
|
790
961
|
|
|
791
|
-
if (
|
|
962
|
+
if (dispatchParams.action === "ui-messages") {
|
|
792
963
|
return executeUiMessages(state);
|
|
793
964
|
}
|
|
794
|
-
if (
|
|
795
|
-
if (!
|
|
965
|
+
if (dispatchParams.action === "auth-start") {
|
|
966
|
+
if (!dispatchParams.server) {
|
|
796
967
|
return {
|
|
797
968
|
content: [{ type: "text" as const, text: "auth-start requires `server`. Example: mcp({ action: \"auth-start\", server: \"linear-server\" })" }],
|
|
798
969
|
details: { mode: "auth-start", error: "missing_server" },
|
|
799
970
|
};
|
|
800
971
|
}
|
|
801
972
|
return signal
|
|
802
|
-
? executeAuthStart(state,
|
|
803
|
-
: executeAuthStart(state,
|
|
973
|
+
? executeAuthStart(state, dispatchParams.server, signal)
|
|
974
|
+
: executeAuthStart(state, dispatchParams.server);
|
|
804
975
|
}
|
|
805
|
-
if (
|
|
806
|
-
if (!
|
|
976
|
+
if (dispatchParams.action === "auth-complete") {
|
|
977
|
+
if (!dispatchParams.server) {
|
|
807
978
|
return {
|
|
808
979
|
content: [{ type: "text" as const, text: "auth-complete requires `server`." }],
|
|
809
980
|
details: { mode: "auth-complete", error: "missing_server" },
|
|
@@ -817,28 +988,28 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
817
988
|
};
|
|
818
989
|
}
|
|
819
990
|
return signal
|
|
820
|
-
? executeAuthComplete(state,
|
|
821
|
-
: executeAuthComplete(state,
|
|
991
|
+
? executeAuthComplete(state, dispatchParams.server, input, signal)
|
|
992
|
+
: executeAuthComplete(state, dispatchParams.server, input);
|
|
822
993
|
}
|
|
823
|
-
if (
|
|
824
|
-
return executeCall(state,
|
|
994
|
+
if (dispatchParams.tool) {
|
|
995
|
+
return executeCall(state, dispatchParams.tool, parsedArgs, dispatchParams.server, getPiTools, signal);
|
|
825
996
|
}
|
|
826
|
-
if (
|
|
827
|
-
const result = await executeConnect(state,
|
|
997
|
+
if (dispatchParams.connect) {
|
|
998
|
+
const result = await executeConnect(state, dispatchParams.connect, signal);
|
|
828
999
|
syncToolSurface(_ctx as ExtensionContext);
|
|
829
1000
|
return result;
|
|
830
1001
|
}
|
|
831
|
-
if (
|
|
832
|
-
return executeDescribe(state,
|
|
1002
|
+
if (dispatchParams.describe) {
|
|
1003
|
+
return executeDescribe(state, dispatchParams.describe);
|
|
833
1004
|
}
|
|
834
|
-
if (
|
|
835
|
-
return executeInstructions(state,
|
|
1005
|
+
if (dispatchParams.instructions) {
|
|
1006
|
+
return executeInstructions(state, dispatchParams.instructions);
|
|
836
1007
|
}
|
|
837
|
-
if (
|
|
838
|
-
return executeSearch(state,
|
|
1008
|
+
if (dispatchParams.search !== undefined) {
|
|
1009
|
+
return executeSearch(state, dispatchParams.search, dispatchParams.regex, dispatchParams.server, dispatchParams.includeSchemas, dispatchParams.limit, dispatchParams.offset);
|
|
839
1010
|
}
|
|
840
|
-
if (
|
|
841
|
-
return executeList(state,
|
|
1011
|
+
if (dispatchParams.server) {
|
|
1012
|
+
return executeList(state, dispatchParams.server);
|
|
842
1013
|
}
|
|
843
1014
|
return executeStatus(state);
|
|
844
1015
|
},
|
|
@@ -895,4 +1066,20 @@ export function createMcpAdapter(options: McpAdapterOptions = {}) {
|
|
|
895
1066
|
};
|
|
896
1067
|
}
|
|
897
1068
|
|
|
1069
|
+
/**
|
|
1070
|
+
* Register an MCP server with the adapter installed for this Pi instance.
|
|
1071
|
+
* Registrations are session/runtime scoped and never persisted. Duplicate
|
|
1072
|
+
* names fail closed. Registered servers are proxy-tool-only; their tools
|
|
1073
|
+
* become visible at the next tool sync. To change a definition, dispose the
|
|
1074
|
+
* registration and register again.
|
|
1075
|
+
*/
|
|
1076
|
+
export function registerMcpServer(options: { pi: ExtensionAPI; name: string; definition: ServerEntry }): McpServerRegistration {
|
|
1077
|
+
const { pi, name, definition } = options;
|
|
1078
|
+
const register = runtimeRegistrars.get(pi);
|
|
1079
|
+
if (!register) {
|
|
1080
|
+
throw new Error("pi-mcp-adapter is not installed for this Pi instance");
|
|
1081
|
+
}
|
|
1082
|
+
return register(name, definition);
|
|
1083
|
+
}
|
|
1084
|
+
|
|
898
1085
|
export default createMcpAdapter();
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { McpExtensionState } from "./state.ts";
|
|
3
|
-
import { formatToolName, isServerDisabled, resolveToolPrefix, type McpAdapterOptions, type PromptMetadata, type ToolMetadata } from "./types.ts";
|
|
3
|
+
import { formatToolName, isServerDisabled, resolveToolPrefix, type McpAdapterOptions, type PromptMetadata, type ToolMetadata, type ToolSelectorCandidateIndex } from "./types.ts";
|
|
4
4
|
import { existsSync } from "node:fs";
|
|
5
5
|
import { cloneMcpConfig, loadMcpConfig } from "./config.ts";
|
|
6
6
|
import { ConsentManager } from "./consent-manager.ts";
|
|
7
7
|
import { McpLifecycleManager } from "./lifecycle.ts";
|
|
8
8
|
import {
|
|
9
9
|
computeServerHash,
|
|
10
|
+
createCachedToolSelectorCandidateIndex,
|
|
10
11
|
getMetadataCachePath,
|
|
11
12
|
getMissingConfiguredDirectToolServers,
|
|
12
13
|
isServerCacheValid,
|
|
@@ -179,8 +180,20 @@ export async function initializeMcp(
|
|
|
179
180
|
},
|
|
180
181
|
...(ui !== undefined ? { ui } : {}),
|
|
181
182
|
sendMessage: (message, options) => {
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
const deliver = () => {
|
|
184
|
+
if (!owner.isActive()) return;
|
|
185
|
+
pi.sendMessage(message as unknown as Parameters<typeof pi.sendMessage>[0], options);
|
|
186
|
+
};
|
|
187
|
+
if (!options?.triggerTurn) {
|
|
188
|
+
deliver();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
void lifecycle.ensureConverged(owner.signal).then(deliver, error => {
|
|
192
|
+
if (!owner.isActive() || isAbortError(error, owner.signal)) return;
|
|
193
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
194
|
+
logger.debug(`MCP: pre-turn keep-alive convergence failed: ${sanitizeTerminalText(detail)}`);
|
|
195
|
+
deliver();
|
|
196
|
+
});
|
|
184
197
|
},
|
|
185
198
|
...(options.statusEvents !== undefined ? { statusEvents: options.statusEvents } : {}),
|
|
186
199
|
};
|
|
@@ -227,6 +240,7 @@ export async function initializeMcp(
|
|
|
227
240
|
}
|
|
228
241
|
|
|
229
242
|
const prefix = config.settings?.toolPrefix ?? "server";
|
|
243
|
+
let cachedSelectorCandidateIndex: ToolSelectorCandidateIndex | undefined;
|
|
230
244
|
|
|
231
245
|
for (const [name, definition] of serverEntries) {
|
|
232
246
|
const lifecycleMode = definition.lifecycle ?? "lazy";
|
|
@@ -243,7 +257,13 @@ export async function initializeMcp(
|
|
|
243
257
|
|
|
244
258
|
const cachedEntry = cache?.servers?.[name];
|
|
245
259
|
if (cachedEntry && isServerCacheValid(cachedEntry, definition)) {
|
|
246
|
-
const
|
|
260
|
+
const hasToolFilters =
|
|
261
|
+
(Array.isArray(definition.includeTools) && definition.includeTools.length > 0) ||
|
|
262
|
+
(Array.isArray(definition.excludeTools) && definition.excludeTools.length > 0);
|
|
263
|
+
if (hasToolFilters && !cachedSelectorCandidateIndex && cache) {
|
|
264
|
+
cachedSelectorCandidateIndex = createCachedToolSelectorCandidateIndex(config.mcpServers, cache, prefix);
|
|
265
|
+
}
|
|
266
|
+
const metadata = reconstructToolMetadata(name, cachedEntry, prefix, definition, config.mcpServers, cache ?? undefined, cachedSelectorCandidateIndex);
|
|
247
267
|
toolMetadata.set(name, metadata);
|
|
248
268
|
if (Array.isArray(cachedEntry.resources)) {
|
|
249
269
|
resourceCounts.set(name, cachedEntry.resources.length);
|
|
@@ -419,6 +439,18 @@ export async function initializeMcp(
|
|
|
419
439
|
updateStatusBar(state);
|
|
420
440
|
});
|
|
421
441
|
|
|
442
|
+
lifecycle.setHealthRestoredCallback((serverName) => {
|
|
443
|
+
if (!owner.isActive()) return;
|
|
444
|
+
clearFailure(state, serverName);
|
|
445
|
+
updateStatusBar(state);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
lifecycle.setAuthRequiredCallback((serverName) => {
|
|
449
|
+
if (!owner.isActive()) return;
|
|
450
|
+
clearFailure(state, serverName);
|
|
451
|
+
updateStatusBar(state);
|
|
452
|
+
});
|
|
453
|
+
|
|
422
454
|
lifecycle.setIdleShutdownCallback((serverName) => {
|
|
423
455
|
if (!owner.isActive()) return;
|
|
424
456
|
const idleMinutes = getEffectiveIdleTimeoutMinutes(state, serverName);
|