@herbertgao/pi-extensions 2026.8.1 → 2026.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/node_modules/@czottmann/pi-automode/README.md +16 -2
- package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +35 -0
- package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +18 -9
- package/node_modules/@czottmann/pi-automode/docs/defaults.md +6 -2
- package/node_modules/@czottmann/pi-automode/examples/automode.local.json +6 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +11 -7
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +100 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +32 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +78 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +16 -4
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +23 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +22 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +18 -2
- package/node_modules/@czottmann/pi-automode/package.json +4 -4
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +5 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail-frontmatter.cjs +23 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs +99 -0
- package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +30 -0
- package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +16 -0
- package/node_modules/@dietrichgebert/ponytail/AGENTS.md +32 -0
- package/node_modules/@dietrichgebert/ponytail/LICENSE +21 -0
- package/node_modules/@dietrichgebert/ponytail/README.es.md +298 -0
- package/node_modules/@dietrichgebert/ponytail/README.ko.md +315 -0
- package/node_modules/@dietrichgebert/ponytail/README.md +351 -0
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-3model.svg +21 -0
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-agentic.svg +62 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.svg +115 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt-dark.svg +27 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt.svg +27 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/social-preview.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-es.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-ko.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +44 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/copilot-hooks.json +21 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +96 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-config.js +169 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +98 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +130 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +85 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.ps1 +24 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.sh +18 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-subagent.js +77 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/qoder-hooks.json +26 -0
- package/node_modules/@dietrichgebert/ponytail/package.json +47 -0
- package/node_modules/@dietrichgebert/ponytail/pi-extension/index.js +211 -0
- package/node_modules/@dietrichgebert/ponytail/pi-extension/package.json +8 -0
- package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +60 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +120 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +41 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +44 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +50 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +71 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +57 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -3
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -3
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +10 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary.ts +6 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -83
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/markdown-enhance.ts +119 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +73 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-hover.ts +130 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-interaction.ts +65 -542
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-layout.ts +160 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-packets.ts +143 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-scroll.ts +184 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-diff/diff-renderer.ts +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-grouping.ts +9 -2
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +91 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/agent-summary.ts +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-dark.json +1 -0
- package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-light.json +2 -2
- package/node_modules/@herbertgao/pi-stash/README.md +9 -9
- package/node_modules/@herbertgao/pi-stash/package.json +1 -1
- package/node_modules/@herbertgao/pi-stash/src/index.ts +2 -1
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +14 -0
- package/node_modules/@herbertgao/pi-subagents/README.md +6 -2
- package/node_modules/@herbertgao/pi-subagents/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +85 -11
- package/node_modules/@herbertgao/pi-subagents/src/index.ts +28 -4
- package/node_modules/@herbertgao/pi-subagents/src/settings.ts +12 -0
- package/node_modules/@herbertgao/pi-subagents/src/types.ts +2 -0
- package/node_modules/@narumitw/pi-btw/LICENSE +21 -0
- package/node_modules/@narumitw/pi-btw/README.md +210 -0
- package/node_modules/@narumitw/pi-btw/package.json +53 -0
- package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +632 -0
- package/node_modules/@narumitw/pi-btw/src/btw.ts +996 -0
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +339 -0
- package/node_modules/@narumitw/pi-btw/src/index.ts +1 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +296 -0
- package/node_modules/@narumitw/pi-btw/src/settings.ts +237 -0
- package/node_modules/@narumitw/pi-btw/src/side-thread.ts +238 -0
- package/node_modules/@narumitw/pi-btw/src/text.ts +10 -0
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +620 -0
- package/node_modules/@pi-plugins/fast-mode/LICENSE +21 -0
- package/node_modules/@pi-plugins/fast-mode/README.md +92 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts +6 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +2026 -0
- package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -0
- package/node_modules/@pi-plugins/fast-mode/package.json +73 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +59 -0
- package/node_modules/pi-mcp-adapter/README.md +62 -13
- package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +377 -0
- package/node_modules/pi-mcp-adapter/commands.ts +9 -7
- package/node_modules/pi-mcp-adapter/config.ts +110 -49
- package/node_modules/pi-mcp-adapter/direct-tools.ts +34 -19
- package/node_modules/pi-mcp-adapter/elicitation-handler.ts +37 -36
- package/node_modules/pi-mcp-adapter/errors.ts +21 -17
- package/node_modules/pi-mcp-adapter/host-html-template.ts +13 -7
- package/node_modules/pi-mcp-adapter/index.ts +37 -14
- package/node_modules/pi-mcp-adapter/init.ts +9 -7
- package/node_modules/pi-mcp-adapter/json-schema-validator.ts +2 -2
- package/node_modules/pi-mcp-adapter/lifecycle.ts +8 -8
- package/node_modules/pi-mcp-adapter/logger.ts +1 -1
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +36 -22
- package/node_modules/pi-mcp-adapter/mcp-auth.ts +120 -2
- package/node_modules/pi-mcp-adapter/mcp-code.ts +13 -11
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +19 -13
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +15 -11
- package/node_modules/pi-mcp-adapter/mcp-panel.ts +34 -15
- package/node_modules/pi-mcp-adapter/mcp-probe.ts +131 -34
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +2 -2
- package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +7 -4
- package/node_modules/pi-mcp-adapter/mcp-trace.ts +59 -64
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +34 -26
- package/node_modules/pi-mcp-adapter/npx-resolver.ts +6 -4
- package/node_modules/pi-mcp-adapter/oauth-handler.ts +1 -1
- package/node_modules/pi-mcp-adapter/onboarding-state.ts +19 -11
- package/node_modules/pi-mcp-adapter/package.json +13 -8
- package/node_modules/pi-mcp-adapter/panel-keys.ts +20 -4
- package/node_modules/pi-mcp-adapter/prompts.ts +2 -2
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +48 -11
- package/node_modules/pi-mcp-adapter/sampling-handler.ts +22 -15
- package/node_modules/pi-mcp-adapter/search-ranking.ts +2 -1
- package/node_modules/pi-mcp-adapter/server-manager.ts +165 -113
- package/node_modules/pi-mcp-adapter/session-recovery.ts +11 -12
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +3 -3
- package/node_modules/pi-mcp-adapter/state.ts +3 -1
- package/node_modules/pi-mcp-adapter/tool-approval.ts +67 -4
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -5
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +14 -2
- package/node_modules/pi-mcp-adapter/ts-shape.ts +9 -2
- package/node_modules/pi-mcp-adapter/types.ts +109 -22
- package/node_modules/pi-mcp-adapter/ui-app-bridge-helpers.ts +40 -0
- package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +28 -19
- package/node_modules/pi-mcp-adapter/ui-server.ts +90 -58
- package/node_modules/pi-mcp-adapter/ui-session.ts +16 -14
- package/node_modules/pi-mcp-adapter/unix-socket-transport.ts +4 -4
- package/node_modules/pi-mcp-adapter/utils.ts +23 -9
- package/package.json +22 -7
|
@@ -22,7 +22,16 @@ import { resolveConfiguredOAuthDir } from './config.ts';
|
|
|
22
22
|
const require = createRequire(import.meta.url);
|
|
23
23
|
const AUTH_SECRET_SERVICE = 'pi-mcp-adapter.oauth';
|
|
24
24
|
const TEST_AUTH_STORE_ENV = 'PI_MCP_ADAPTER_TEST_AUTH_STORE';
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Windows Credential Manager caps one value at CRED_MAX_CREDENTIAL_BLOB_SIZE
|
|
27
|
+
* (2560 bytes) and stores it as UTF-16, so the real ceiling is
|
|
28
|
+
* AUTH_SECRET_VALUE_LIMIT characters. Chunks must stay below that, and so must
|
|
29
|
+
* the threshold that decides whether to chunk at all, or oversized records still
|
|
30
|
+
* fail to persist on Windows.
|
|
31
|
+
*/
|
|
32
|
+
const AUTH_SECRET_CHUNK_SIZE = 1000;
|
|
33
|
+
/** Largest single value the strictest supported credential store accepts. */
|
|
34
|
+
const AUTH_SECRET_VALUE_LIMIT = 1280;
|
|
26
35
|
const KEYRING_RECOVERY_DISABLED_ENV = 'PI_MCP_ADAPTER_DISABLE_KEYRING_RECOVERY';
|
|
27
36
|
const KEYRING_RECOVERY_KEYCTL_ENV = 'PI_MCP_ADAPTER_KEYRING_RECOVERY_KEYCTL';
|
|
28
37
|
const KEYRING_RECOVERY_NODE_ENV = 'PI_MCP_ADAPTER_KEYRING_RECOVERY_NODE';
|
|
@@ -163,6 +172,22 @@ const keyringAuthSecretStore: AuthSecretStore = {
|
|
|
163
172
|
},
|
|
164
173
|
};
|
|
165
174
|
|
|
175
|
+
/** Mimics the Windows Credential Manager per-value ceiling for tests. */
|
|
176
|
+
const sizeLimitedAuthSecretStore: AuthSecretStore = {
|
|
177
|
+
read(account) {
|
|
178
|
+
return memoryAuthEntries.get(account);
|
|
179
|
+
},
|
|
180
|
+
write(account, payload) {
|
|
181
|
+
if (payload.length > AUTH_SECRET_VALUE_LIMIT) {
|
|
182
|
+
throw new Error(`Value of 'password encoded as UTF-16' is longer than the platform limit of ${AUTH_SECRET_VALUE_LIMIT * 2} chars`);
|
|
183
|
+
}
|
|
184
|
+
memoryAuthEntries.set(account, payload);
|
|
185
|
+
},
|
|
186
|
+
remove(account) {
|
|
187
|
+
memoryAuthEntries.delete(account);
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
|
|
166
191
|
const unavailableAuthSecretStore: AuthSecretStore = {
|
|
167
192
|
read() {
|
|
168
193
|
throw new Error('simulated secure credential store unavailable');
|
|
@@ -205,6 +230,7 @@ export function removeTestAuthSecretStoreEntry(account: string): void {
|
|
|
205
230
|
|
|
206
231
|
function getAuthSecretStore(): AuthSecretStore {
|
|
207
232
|
if (process.env[TEST_AUTH_STORE_ENV] === 'memory') return memoryAuthSecretStore;
|
|
233
|
+
if (process.env[TEST_AUTH_STORE_ENV] === 'sizelimited') return sizeLimitedAuthSecretStore;
|
|
208
234
|
if (process.env[TEST_AUTH_STORE_ENV] === 'unavailable') return unavailableAuthSecretStore;
|
|
209
235
|
if (process.env[TEST_AUTH_STORE_ENV] === 'keyrevoked') return keyRevokedAuthSecretStore;
|
|
210
236
|
return keyringAuthSecretStore;
|
|
@@ -401,7 +427,99 @@ function parseJsonPayload(serverName: string, payload: string, source: string):
|
|
|
401
427
|
}
|
|
402
428
|
|
|
403
429
|
function parseAuthEntryPayload(serverName: string, payload: string, source: string): AuthEntry {
|
|
404
|
-
|
|
430
|
+
const parsed = parseJsonPayload(serverName, payload, source);
|
|
431
|
+
const entry = toAuthEntry(parsed);
|
|
432
|
+
if (!entry) {
|
|
433
|
+
throw new Error(`Failed to parse OAuth credentials for ${serverName} from ${source}: invalid credential shape`);
|
|
434
|
+
}
|
|
435
|
+
return entry;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function toAuthEntry(value: unknown): AuthEntry | undefined {
|
|
439
|
+
const entry = toRecord(value);
|
|
440
|
+
if (!entry) return undefined;
|
|
441
|
+
|
|
442
|
+
const codeVerifier = optionalString(entry.codeVerifier);
|
|
443
|
+
const oauthState = optionalString(entry.oauthState);
|
|
444
|
+
const serverUrl = optionalString(entry.serverUrl);
|
|
445
|
+
if (codeVerifier === null || oauthState === null || serverUrl === null) return undefined;
|
|
446
|
+
|
|
447
|
+
const tokens = entry.tokens === undefined ? undefined : toStoredTokens(entry.tokens);
|
|
448
|
+
const clientInfo = entry.clientInfo === undefined ? undefined : toStoredClientInfo(entry.clientInfo);
|
|
449
|
+
if ((entry.tokens !== undefined && !tokens) || (entry.clientInfo !== undefined && !clientInfo)) return undefined;
|
|
450
|
+
|
|
451
|
+
const authEntry: AuthEntry = {};
|
|
452
|
+
if (tokens) authEntry.tokens = tokens;
|
|
453
|
+
if (clientInfo) authEntry.clientInfo = clientInfo;
|
|
454
|
+
if (codeVerifier !== undefined) authEntry.codeVerifier = codeVerifier;
|
|
455
|
+
if (oauthState !== undefined) authEntry.oauthState = oauthState;
|
|
456
|
+
if (serverUrl !== undefined) authEntry.serverUrl = serverUrl;
|
|
457
|
+
return authEntry;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function toStoredTokens(value: unknown): StoredTokens | undefined {
|
|
461
|
+
const tokens = toRecord(value);
|
|
462
|
+
if (!tokens || typeof tokens.accessToken !== 'string') return undefined;
|
|
463
|
+
|
|
464
|
+
const refreshToken = optionalString(tokens.refreshToken);
|
|
465
|
+
const scope = optionalString(tokens.scope);
|
|
466
|
+
const issuer = optionalString(tokens.issuer);
|
|
467
|
+
const expiresAt = optionalNumber(tokens.expiresAt);
|
|
468
|
+
if (refreshToken === null || scope === null || issuer === null || expiresAt === null) return undefined;
|
|
469
|
+
|
|
470
|
+
const storedTokens: StoredTokens = { accessToken: tokens.accessToken };
|
|
471
|
+
if (refreshToken !== undefined) storedTokens.refreshToken = refreshToken;
|
|
472
|
+
if (expiresAt !== undefined) storedTokens.expiresAt = expiresAt;
|
|
473
|
+
if (scope !== undefined) storedTokens.scope = scope;
|
|
474
|
+
if (issuer !== undefined) storedTokens.issuer = issuer;
|
|
475
|
+
return storedTokens;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function toStoredClientInfo(value: unknown): StoredClientInfo | undefined {
|
|
479
|
+
const clientInfo = toRecord(value);
|
|
480
|
+
if (!clientInfo || typeof clientInfo.clientId !== 'string') return undefined;
|
|
481
|
+
|
|
482
|
+
const clientSecret = optionalString(clientInfo.clientSecret);
|
|
483
|
+
const issuer = optionalString(clientInfo.issuer);
|
|
484
|
+
const clientIdIssuedAt = optionalNumber(clientInfo.clientIdIssuedAt);
|
|
485
|
+
const clientSecretExpiresAt = optionalNumber(clientInfo.clientSecretExpiresAt);
|
|
486
|
+
const configPreRegistered = optionalBoolean(clientInfo.configPreRegistered);
|
|
487
|
+
if (clientSecret === null || issuer === null || clientIdIssuedAt === null || clientSecretExpiresAt === null || configPreRegistered === null) return undefined;
|
|
488
|
+
|
|
489
|
+
const storedClient: StoredClientInfo = { clientId: clientInfo.clientId };
|
|
490
|
+
const redirectUris = stringArray(clientInfo.redirectUris);
|
|
491
|
+
if (clientSecret !== undefined) storedClient.clientSecret = clientSecret;
|
|
492
|
+
if (clientIdIssuedAt !== undefined) storedClient.clientIdIssuedAt = clientIdIssuedAt;
|
|
493
|
+
if (clientSecretExpiresAt !== undefined) storedClient.clientSecretExpiresAt = clientSecretExpiresAt;
|
|
494
|
+
if (redirectUris !== undefined) storedClient.redirectUris = redirectUris;
|
|
495
|
+
if (issuer !== undefined) storedClient.issuer = issuer;
|
|
496
|
+
if (configPreRegistered !== undefined) storedClient.configPreRegistered = configPreRegistered;
|
|
497
|
+
return storedClient;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function toRecord(value: unknown): Record<string, unknown> | undefined {
|
|
501
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
502
|
+
? value as Record<string, unknown>
|
|
503
|
+
: undefined;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function optionalString(value: unknown): string | null | undefined {
|
|
507
|
+
if (value === undefined) return undefined;
|
|
508
|
+
return typeof value === 'string' ? value : null;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function optionalNumber(value: unknown): number | null | undefined {
|
|
512
|
+
if (value === undefined) return undefined;
|
|
513
|
+
return typeof value === 'number' ? value : null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function optionalBoolean(value: unknown): boolean | null | undefined {
|
|
517
|
+
if (value === undefined) return undefined;
|
|
518
|
+
return typeof value === 'boolean' ? value : null;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function stringArray(value: unknown): string[] | undefined {
|
|
522
|
+
return Array.isArray(value) && value.every(uri => typeof uri === 'string') ? value : undefined;
|
|
405
523
|
}
|
|
406
524
|
|
|
407
525
|
function isAuthEntryChunkManifest(value: unknown): value is AuthEntryChunkManifest {
|
|
@@ -6,7 +6,7 @@ import { executeCall } from "./proxy-modes.ts";
|
|
|
6
6
|
import { combineAbortSignals } from "./runtime-owner.ts";
|
|
7
7
|
import { paginate, rankSuggestions, rankToolMatches } from "./search-ranking.ts";
|
|
8
8
|
import type { McpExtensionState } from "./state.ts";
|
|
9
|
-
import { findToolByName } from "./tool-metadata.ts";
|
|
9
|
+
import { findToolByName, formatSchema } from "./tool-metadata.ts";
|
|
10
10
|
import { renderTsShape } from "./ts-shape.ts";
|
|
11
11
|
import type { ContentBlock } from "./types.ts";
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@ export const DEFAULT_MCP_SCRIPT_TIMEOUT_MS = 30_000;
|
|
|
14
14
|
|
|
15
15
|
class McpScriptTimeoutError extends Error {
|
|
16
16
|
constructor(timeoutMs: number) {
|
|
17
|
-
super(`
|
|
17
|
+
super(`mcpScript timed out after ${timeoutMs}ms`);
|
|
18
18
|
this.name = "McpScriptTimeoutError";
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -139,7 +139,7 @@ export async function runMcpScript(
|
|
|
139
139
|
// Record before dispatch so calls still in flight at timeout/abort appear in the trace.
|
|
140
140
|
const startedAt = Date.now();
|
|
141
141
|
const index = calls.push({ operation: "call", path, ok: false, error: "incomplete", durationMs: 0, startedAt }) - 1;
|
|
142
|
-
const result = await executeCall(state, path, args, undefined, getPiTools, callSignal);
|
|
142
|
+
const result = await executeCall(state, path, args, undefined, getPiTools, callSignal, "script");
|
|
143
143
|
const details = result.details;
|
|
144
144
|
if (details.error !== undefined) {
|
|
145
145
|
const errorCode = String(details.error);
|
|
@@ -147,7 +147,7 @@ export async function runMcpScript(
|
|
|
147
147
|
? details.suggestions.filter((suggestion): suggestion is string => typeof suggestion === "string")
|
|
148
148
|
: [];
|
|
149
149
|
const message = errorCode === "tool_not_found"
|
|
150
|
-
? `Tool "${path}" not found. Use await tools.search({ query: "..." }) inside
|
|
150
|
+
? `Tool "${path}" not found. Use await tools.search({ query: "..." }) inside mcpScript.${suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}` : ""}`
|
|
151
151
|
: typeof details.message === "string"
|
|
152
152
|
? details.message
|
|
153
153
|
: textFromContent(result.content);
|
|
@@ -172,9 +172,9 @@ export async function runMcpScript(
|
|
|
172
172
|
if (query.trim() === "") {
|
|
173
173
|
return { items: [], total: 0, hasMore: false, nextOffset: null };
|
|
174
174
|
}
|
|
175
|
-
const server = typeof input
|
|
176
|
-
const limit = typeof input
|
|
177
|
-
const offset = typeof input
|
|
175
|
+
const server = typeof input?.server === "string" ? input.server : undefined;
|
|
176
|
+
const limit = typeof input?.limit === "number" ? input.limit : 12;
|
|
177
|
+
const offset = typeof input?.offset === "number" ? input.offset : 0;
|
|
178
178
|
const page = paginate(rankToolMatches(state, query, server), offset, limit);
|
|
179
179
|
return {
|
|
180
180
|
...page,
|
|
@@ -204,7 +204,9 @@ export async function runMcpScript(
|
|
|
204
204
|
for (const [server, metadata] of state.toolMetadata) {
|
|
205
205
|
const tool = findToolByName(metadata, path);
|
|
206
206
|
if (!tool) continue;
|
|
207
|
-
const inputTypeScript = tool.inputSchema
|
|
207
|
+
const inputTypeScript = tool.inputSchema
|
|
208
|
+
? renderTsShape(tool.inputSchema) ?? formatSchema(tool.inputSchema)
|
|
209
|
+
: null;
|
|
208
210
|
return {
|
|
209
211
|
path: tool.name,
|
|
210
212
|
name: tool.originalName,
|
|
@@ -285,7 +287,7 @@ export async function runMcpScript(
|
|
|
285
287
|
});
|
|
286
288
|
activeWorker.once("error", reject);
|
|
287
289
|
activeWorker.once("exit", (code) => {
|
|
288
|
-
if (!completed && code !== 0) reject(new Error(`
|
|
290
|
+
if (!completed && code !== 0) reject(new Error(`mcpScript worker exited with code ${code}`));
|
|
289
291
|
});
|
|
290
292
|
});
|
|
291
293
|
const timeoutError = new McpScriptTimeoutError(resolvedTimeoutMs);
|
|
@@ -313,7 +315,7 @@ export async function runMcpScript(
|
|
|
313
315
|
} catch (error) {
|
|
314
316
|
if (error instanceof McpScriptTimeoutError) {
|
|
315
317
|
errorCode = "timeout";
|
|
316
|
-
errorMessage = `
|
|
318
|
+
errorMessage = `mcpScript timed out after ${resolvedTimeoutMs}ms`;
|
|
317
319
|
} else if (externalSignal?.aborted) {
|
|
318
320
|
errorCode = "aborted";
|
|
319
321
|
errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -330,7 +332,7 @@ export async function runMcpScript(
|
|
|
330
332
|
callsSnapshot ??= snapshotCalls();
|
|
331
333
|
// A script may finish without awaiting every call; abort leftovers so
|
|
332
334
|
// parent-side dispatches do not outlive the script.
|
|
333
|
-
timeoutController.abort(new Error("
|
|
335
|
+
timeoutController.abort(new Error("mcpScript finished"));
|
|
334
336
|
await worker?.terminate();
|
|
335
337
|
}
|
|
336
338
|
|
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
type AddClientAuthentication,
|
|
11
11
|
type OAuthClientProvider,
|
|
12
12
|
type OAuthDiscoveryState,
|
|
13
|
-
} from "@modelcontextprotocol/
|
|
13
|
+
} from "@modelcontextprotocol/client"
|
|
14
14
|
import type {
|
|
15
15
|
OAuthClientInformationMixed,
|
|
16
16
|
OAuthClientMetadata,
|
|
17
17
|
OAuthTokens,
|
|
18
|
-
} from "@modelcontextprotocol/
|
|
18
|
+
} from "@modelcontextprotocol/client"
|
|
19
19
|
import {
|
|
20
20
|
getAuthForUrl,
|
|
21
21
|
updateTokens,
|
|
@@ -86,6 +86,7 @@ export interface McpOAuthConfig {
|
|
|
86
86
|
redirectUri?: string
|
|
87
87
|
clientName?: string
|
|
88
88
|
clientUri?: string
|
|
89
|
+
skipIssuerMetadataValidation?: boolean
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
const reservedAuthorizationParams = new Set([
|
|
@@ -288,7 +289,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
288
289
|
updateClientInfo(this.serverName, clientInfo, this.serverUrl, this.storageOptions)
|
|
289
290
|
}
|
|
290
291
|
// Return all registration metadata and the local issuer extension.
|
|
291
|
-
// This keeps the SDK
|
|
292
|
+
// This keeps the SDK OAuth view and the stored issuer binding consistent.
|
|
292
293
|
return {
|
|
293
294
|
client_id: clientInfo.clientId,
|
|
294
295
|
client_secret: clientInfo.clientSecret,
|
|
@@ -318,7 +319,11 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
318
319
|
if (this.config.clientId && info.client_id === this.config.clientId) {
|
|
319
320
|
updateClientInfo(
|
|
320
321
|
this.serverName,
|
|
321
|
-
{
|
|
322
|
+
{
|
|
323
|
+
clientId: info.client_id,
|
|
324
|
+
...(issuer !== undefined ? { issuer } : {}),
|
|
325
|
+
configPreRegistered: true,
|
|
326
|
+
},
|
|
322
327
|
this.serverUrl,
|
|
323
328
|
this.storageOptions,
|
|
324
329
|
)
|
|
@@ -329,11 +334,11 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
329
334
|
?? (this.redirectUrl ? [this.redirectUrl] : undefined)
|
|
330
335
|
const clientInfo: StoredClientInfo = {
|
|
331
336
|
clientId: info.client_id,
|
|
332
|
-
clientSecret: info.client_secret,
|
|
333
|
-
clientIdIssuedAt: info.client_id_issued_at,
|
|
334
|
-
clientSecretExpiresAt: info.client_secret_expires_at,
|
|
335
|
-
redirectUris,
|
|
336
|
-
issuer,
|
|
337
|
+
...(info.client_secret !== undefined ? { clientSecret: info.client_secret } : {}),
|
|
338
|
+
...(info.client_id_issued_at !== undefined ? { clientIdIssuedAt: info.client_id_issued_at } : {}),
|
|
339
|
+
...(info.client_secret_expires_at !== undefined ? { clientSecretExpiresAt: info.client_secret_expires_at } : {}),
|
|
340
|
+
...(redirectUris !== undefined ? { redirectUris } : {}),
|
|
341
|
+
...(issuer !== undefined ? { issuer } : {}),
|
|
337
342
|
}
|
|
338
343
|
this.flowClientInfo = clientInfo
|
|
339
344
|
updateClientInfo(this.serverName, clientInfo, this.serverUrl, this.storageOptions)
|
|
@@ -370,15 +375,16 @@ export class McpOAuthProvider implements OAuthClientProvider {
|
|
|
370
375
|
* Save OAuth tokens.
|
|
371
376
|
*/
|
|
372
377
|
async saveTokens(tokens: OAuthTokens): Promise<void> {
|
|
378
|
+
const issuer = this.discoveredIssuer ?? (tokens as IssuerBoundTokens).issuer
|
|
373
379
|
const storedTokens: StoredTokens = {
|
|
374
380
|
accessToken: tokens.access_token,
|
|
375
|
-
refreshToken: tokens.refresh_token,
|
|
381
|
+
...(tokens.refresh_token !== undefined ? { refreshToken: tokens.refresh_token } : {}),
|
|
376
382
|
// Preserve expiry even when expires_in is 0 (e.g. the SDK re-saving an
|
|
377
383
|
// already-expired token) so expired tokens stay expired instead of
|
|
378
384
|
// being persisted as never-expiring.
|
|
379
|
-
|
|
380
|
-
scope: tokens.scope,
|
|
381
|
-
issuer
|
|
385
|
+
...(tokens.expires_in !== undefined ? { expiresAt: Date.now() / 1000 + tokens.expires_in } : {}),
|
|
386
|
+
...(tokens.scope !== undefined ? { scope: tokens.scope } : {}),
|
|
387
|
+
...(issuer !== undefined ? { issuer } : {}),
|
|
382
388
|
}
|
|
383
389
|
this.throwIfInactive()
|
|
384
390
|
updateTokens(this.serverName, storedTokens, this.serverUrl, this.storageOptions)
|
|
@@ -107,7 +107,7 @@ export async function guardMcpOutput(
|
|
|
107
107
|
if (options.enabled === false) {
|
|
108
108
|
return {
|
|
109
109
|
content: addAffixes(normalizedContent, prefix, suffix),
|
|
110
|
-
mcpResult: options.rawMcpResult,
|
|
110
|
+
...(options.rawMcpResult !== undefined ? { mcpResult: options.rawMcpResult } : {}),
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -138,8 +138,8 @@ export async function guardMcpOutput(
|
|
|
138
138
|
originalLines: stats.lines,
|
|
139
139
|
returnedLines: finalStats.lines,
|
|
140
140
|
...(imageBlocks.length > 0 ? { imageBlocksPassedThrough: imageBlocks.length } : {}),
|
|
141
|
-
fullOutputPath,
|
|
142
|
-
writeError,
|
|
141
|
+
...(fullOutputPath !== undefined ? { fullOutputPath } : {}),
|
|
142
|
+
...(writeError !== undefined ? { writeError } : {}),
|
|
143
143
|
};
|
|
144
144
|
}
|
|
145
145
|
|
|
@@ -147,7 +147,11 @@ export async function guardMcpOutput(
|
|
|
147
147
|
? undefined
|
|
148
148
|
: await boundMcpResult(options.rawMcpResult, detailsMaxBytes);
|
|
149
149
|
|
|
150
|
-
return {
|
|
150
|
+
return {
|
|
151
|
+
content: guardedContent,
|
|
152
|
+
...(outputGuard ? { outputGuard } : {}),
|
|
153
|
+
...(mcpResult !== undefined ? { mcpResult } : {}),
|
|
154
|
+
};
|
|
151
155
|
}
|
|
152
156
|
|
|
153
157
|
function sanitizeContent(content: ContentBlock[]): ContentBlock[] {
|
|
@@ -177,7 +181,7 @@ function addAffixes(content: ContentBlock[], prefix: string, suffix: string): Co
|
|
|
177
181
|
if (prefix) {
|
|
178
182
|
const index = next.findIndex((block) => block.type === "text");
|
|
179
183
|
const block = next[index];
|
|
180
|
-
if (
|
|
184
|
+
if (block?.type === "text") {
|
|
181
185
|
next[index] = { ...block, text: `${prefix}${block.text}` };
|
|
182
186
|
} else {
|
|
183
187
|
next.unshift({ type: "text", text: prefix });
|
|
@@ -187,13 +191,13 @@ function addAffixes(content: ContentBlock[], prefix: string, suffix: string): Co
|
|
|
187
191
|
if (suffix) {
|
|
188
192
|
let index = -1;
|
|
189
193
|
for (let i = next.length - 1; i >= 0; i--) {
|
|
190
|
-
if (next[i]
|
|
194
|
+
if (next[i]?.type === "text") {
|
|
191
195
|
index = i;
|
|
192
196
|
break;
|
|
193
197
|
}
|
|
194
198
|
}
|
|
195
199
|
const block = next[index];
|
|
196
|
-
if (
|
|
200
|
+
if (block?.type === "text") {
|
|
197
201
|
next[index] = { ...block, text: `${block.text}${suffix}` };
|
|
198
202
|
} else {
|
|
199
203
|
next.push({ type: "text", text: suffix });
|
|
@@ -239,8 +243,8 @@ function truncateHead(text: string, maxBytes: number, maxLines: number): { conte
|
|
|
239
243
|
function truncateStringToBytes(value: string, maxBytes: number): string {
|
|
240
244
|
if (byteLength(value) <= maxBytes) return value;
|
|
241
245
|
const buffer = Buffer.from(value, "utf8");
|
|
242
|
-
let end = Math.max(0, maxBytes);
|
|
243
|
-
while (end > 0 && (buffer
|
|
246
|
+
let end = Number.isFinite(maxBytes) ? Math.max(0, Math.floor(maxBytes)) : 0;
|
|
247
|
+
while (end > 0 && (buffer.readUInt8(end) & 0xc0) === 0x80) end--;
|
|
244
248
|
return buffer.subarray(0, end).toString("utf8");
|
|
245
249
|
}
|
|
246
250
|
|
|
@@ -280,8 +284,8 @@ async function summarizeMcpResult(result: unknown, raw: string, rawBytes: number
|
|
|
280
284
|
contentBlocks: content.length,
|
|
281
285
|
contentSummary: summarizeContent(content),
|
|
282
286
|
rawResultBytes: rawBytes,
|
|
283
|
-
fullResultPath,
|
|
284
|
-
resultWriteError,
|
|
287
|
+
...(fullResultPath !== undefined ? { fullResultPath } : {}),
|
|
288
|
+
...(resultWriteError !== undefined ? { resultWriteError } : {}),
|
|
285
289
|
};
|
|
286
290
|
|
|
287
291
|
if (record && "structuredContent" in record) {
|
|
@@ -52,6 +52,7 @@ function rainbowProgress(filled: number, total: number): string {
|
|
|
52
52
|
const dots: string[] = [];
|
|
53
53
|
for (let i = 0; i < total; i++) {
|
|
54
54
|
const color = RAINBOW_COLORS[i % RAINBOW_COLORS.length];
|
|
55
|
+
if (!color) continue;
|
|
55
56
|
dots.push(fg(color, i < filled ? "●" : "○"));
|
|
56
57
|
}
|
|
57
58
|
return dots.join(" ");
|
|
@@ -220,7 +221,10 @@ class McpPanel {
|
|
|
220
221
|
}
|
|
221
222
|
|
|
222
223
|
const isDirect = toolFilter === true || (Array.isArray(toolFilter) && toolFilter.includes(baseName));
|
|
223
|
-
const ct: CachedTool = {
|
|
224
|
+
const ct: CachedTool = {
|
|
225
|
+
name: baseName,
|
|
226
|
+
...(resource.description !== undefined ? { description: resource.description } : {}),
|
|
227
|
+
};
|
|
224
228
|
tools.push({
|
|
225
229
|
name: baseName,
|
|
226
230
|
description: resource.description ?? `Read resource: ${resource.uri}`,
|
|
@@ -239,9 +243,9 @@ class McpPanel {
|
|
|
239
243
|
name: serverName,
|
|
240
244
|
expanded: false,
|
|
241
245
|
source: prov?.kind ?? "user",
|
|
242
|
-
importKind: prov
|
|
243
|
-
includeTools: definition.includeTools,
|
|
244
|
-
excludeTools: definition.excludeTools,
|
|
246
|
+
...(prov?.importKind !== undefined ? { importKind: prov.importKind } : {}),
|
|
247
|
+
...(definition.includeTools !== undefined ? { includeTools: definition.includeTools } : {}),
|
|
248
|
+
...(definition.excludeTools !== undefined ? { excludeTools: definition.excludeTools } : {}),
|
|
245
249
|
exposeResources: definition.exposeResources !== false,
|
|
246
250
|
connectionStatus: status,
|
|
247
251
|
failureMessage,
|
|
@@ -276,6 +280,7 @@ class McpPanel {
|
|
|
276
280
|
this.visibleItems = [];
|
|
277
281
|
for (let si = 0; si < this.servers.length; si++) {
|
|
278
282
|
const server = this.servers[si];
|
|
283
|
+
if (!server) continue;
|
|
279
284
|
if (query && this.authOnly) {
|
|
280
285
|
const score = mode === "name" ? fuzzyScore(query, server.name) : 0;
|
|
281
286
|
if (score > 0) {
|
|
@@ -288,6 +293,7 @@ class McpPanel {
|
|
|
288
293
|
if (server.expanded || query) {
|
|
289
294
|
for (let ti = 0; ti < server.tools.length; ti++) {
|
|
290
295
|
const tool = server.tools[ti];
|
|
296
|
+
if (!tool) continue;
|
|
291
297
|
if (query) {
|
|
292
298
|
const score = mode === "name"
|
|
293
299
|
? Math.max(
|
|
@@ -352,7 +358,7 @@ class McpPanel {
|
|
|
352
358
|
return;
|
|
353
359
|
}
|
|
354
360
|
|
|
355
|
-
if (
|
|
361
|
+
if (this.keys.save(data)) {
|
|
356
362
|
this.cleanup();
|
|
357
363
|
this.done(this.buildResult());
|
|
358
364
|
return;
|
|
@@ -422,6 +428,7 @@ class McpPanel {
|
|
|
422
428
|
const item = this.visibleItems[this.cursorIndex];
|
|
423
429
|
if (!item) return;
|
|
424
430
|
const server = this.servers[item.serverIndex];
|
|
431
|
+
if (!server) return;
|
|
425
432
|
if (item.type === "server") {
|
|
426
433
|
if (server.connectionStatus === "disabled") return;
|
|
427
434
|
if (this.authOnly || server.connectionStatus === "needs-auth") {
|
|
@@ -433,6 +440,7 @@ class McpPanel {
|
|
|
433
440
|
this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, this.visibleItems.length - 1));
|
|
434
441
|
} else if (item.toolIndex !== undefined) {
|
|
435
442
|
const tool = server.tools[item.toolIndex];
|
|
443
|
+
if (!tool) return;
|
|
436
444
|
tool.isDirect = !tool.isDirect;
|
|
437
445
|
if (tool.isDirect && server.source === "import") {
|
|
438
446
|
this.importNotice = `Imported from ${sanitizeDisplayText(server.importKind ?? "external")} — will copy to user config on save`;
|
|
@@ -451,7 +459,8 @@ class McpPanel {
|
|
|
451
459
|
if (matchesKey(data, "ctrl+r")) {
|
|
452
460
|
const item = this.visibleItems[this.cursorIndex];
|
|
453
461
|
if (!item) return;
|
|
454
|
-
this.
|
|
462
|
+
const server = this.servers[item.serverIndex];
|
|
463
|
+
if (server) this.reconnectServer(server);
|
|
455
464
|
return;
|
|
456
465
|
}
|
|
457
466
|
|
|
@@ -459,7 +468,7 @@ class McpPanel {
|
|
|
459
468
|
const item = this.visibleItems[this.cursorIndex];
|
|
460
469
|
if (!item) return;
|
|
461
470
|
const server = this.servers[item.serverIndex];
|
|
462
|
-
if (server.connectionStatus !== "failed" || !server.failureMessage) return;
|
|
471
|
+
if (!server || server.connectionStatus !== "failed" || !server.failureMessage) return;
|
|
463
472
|
const serverName = sanitizeDisplayText(server.name);
|
|
464
473
|
const failureMessage = sanitizeDisplayText(server.failureMessage);
|
|
465
474
|
copyToClipboard(failureMessage).then(() => {
|
|
@@ -502,7 +511,8 @@ class McpPanel {
|
|
|
502
511
|
}
|
|
503
512
|
|
|
504
513
|
private authenticateSelectedServer(item: VisibleItem): void {
|
|
505
|
-
this.
|
|
514
|
+
const server = this.servers[item.serverIndex];
|
|
515
|
+
if (server) this.authenticateServer(server);
|
|
506
516
|
}
|
|
507
517
|
|
|
508
518
|
private authenticateServer(server: ServerState): void {
|
|
@@ -574,6 +584,7 @@ class McpPanel {
|
|
|
574
584
|
private toggleItem(item: VisibleItem): void {
|
|
575
585
|
if (this.authOnly) return;
|
|
576
586
|
const server = this.servers[item.serverIndex];
|
|
587
|
+
if (!server) return;
|
|
577
588
|
if (item.type === "server") {
|
|
578
589
|
const newState = !server.tools.every((t) => t.isDirect);
|
|
579
590
|
if (server.source === "import" && newState) {
|
|
@@ -582,6 +593,7 @@ class McpPanel {
|
|
|
582
593
|
for (const t of server.tools) t.isDirect = newState;
|
|
583
594
|
} else if (item.toolIndex !== undefined) {
|
|
584
595
|
const tool = server.tools[item.toolIndex];
|
|
596
|
+
if (!tool) return;
|
|
585
597
|
tool.isDirect = !tool.isDirect;
|
|
586
598
|
if (tool.isDirect && server.source === "import") {
|
|
587
599
|
this.importNotice = `Imported from ${sanitizeDisplayText(server.importKind ?? "external")} — will copy to user config on save`;
|
|
@@ -654,7 +666,10 @@ class McpPanel {
|
|
|
654
666
|
|
|
655
667
|
const prev = existingState.get(baseName);
|
|
656
668
|
const isDirect = prev !== undefined ? prev : false;
|
|
657
|
-
const ct: CachedTool = {
|
|
669
|
+
const ct: CachedTool = {
|
|
670
|
+
name: baseName,
|
|
671
|
+
...(resource.description !== undefined ? { description: resource.description } : {}),
|
|
672
|
+
};
|
|
658
673
|
newTools.push({
|
|
659
674
|
name: baseName,
|
|
660
675
|
description: resource.description ?? `Read resource: ${resource.uri}`,
|
|
@@ -724,8 +739,10 @@ class McpPanel {
|
|
|
724
739
|
|
|
725
740
|
for (let i = startIdx; i < endIdx; i++) {
|
|
726
741
|
const item = this.visibleItems[i];
|
|
742
|
+
if (!item) continue;
|
|
727
743
|
const isCursor = i === this.cursorIndex;
|
|
728
744
|
const server = this.servers[item.serverIndex];
|
|
745
|
+
if (!server) continue;
|
|
729
746
|
|
|
730
747
|
if (item.type === "server") {
|
|
731
748
|
lines.push(row(this.renderServerRow(server, isCursor)));
|
|
@@ -735,7 +752,8 @@ class McpPanel {
|
|
|
735
752
|
}
|
|
736
753
|
}
|
|
737
754
|
} else if (item.toolIndex !== undefined) {
|
|
738
|
-
|
|
755
|
+
const tool = server.tools[item.toolIndex];
|
|
756
|
+
if (tool) lines.push(row(this.renderToolRow(tool, isCursor, innerW)));
|
|
739
757
|
}
|
|
740
758
|
}
|
|
741
759
|
|
|
@@ -784,6 +802,7 @@ class McpPanel {
|
|
|
784
802
|
}
|
|
785
803
|
|
|
786
804
|
lines.push(emptyRow());
|
|
805
|
+
const saveLabel = this.keys.saveLabel();
|
|
787
806
|
const hints = this.authOnly
|
|
788
807
|
? [
|
|
789
808
|
italic("↑↓") + " navigate",
|
|
@@ -800,7 +819,7 @@ class McpPanel {
|
|
|
800
819
|
italic("ctrl+r") + " reconnect",
|
|
801
820
|
...(this.selectedServerHasFailureMessage() ? [italic("ctrl+y") + " copy error"] : []),
|
|
802
821
|
italic("?") + " desc search",
|
|
803
|
-
italic(
|
|
822
|
+
...(saveLabel ? [italic(saveLabel) + " save"] : []),
|
|
804
823
|
italic("esc") + " clear/close",
|
|
805
824
|
italic("ctrl+c") + " quit",
|
|
806
825
|
];
|
|
@@ -871,7 +890,7 @@ class McpPanel {
|
|
|
871
890
|
const item = this.visibleItems[this.cursorIndex];
|
|
872
891
|
if (!item) return false;
|
|
873
892
|
const server = this.servers[item.serverIndex];
|
|
874
|
-
return server
|
|
893
|
+
return server?.connectionStatus === "failed" && !!server.failureMessage;
|
|
875
894
|
}
|
|
876
895
|
|
|
877
896
|
private wrapText(text: string, width: number): string[] {
|
|
@@ -884,11 +903,11 @@ class McpPanel {
|
|
|
884
903
|
while (visibleWidth(rest) > max) {
|
|
885
904
|
let take = "";
|
|
886
905
|
let index = 0;
|
|
887
|
-
while (index < rest.length && visibleWidth(take + rest
|
|
888
|
-
take += rest
|
|
906
|
+
while (index < rest.length && visibleWidth(take + rest.charAt(index)) <= max) {
|
|
907
|
+
take += rest.charAt(index);
|
|
889
908
|
index++;
|
|
890
909
|
}
|
|
891
|
-
if (!take) take = rest
|
|
910
|
+
if (!take) take = rest.charAt(0);
|
|
892
911
|
lines.push(take);
|
|
893
912
|
rest = rest.slice(take.length);
|
|
894
913
|
}
|