@herbertgao/pi-extensions 2026.9.11 → 2026.9.13
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 +4 -4
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +1 -1
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
- package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +5 -4
- package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +39 -16
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +21 -4
- package/node_modules/@narumitw/pi-btw/dist/index.ts +1668 -958
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
- package/node_modules/@narumitw/pi-btw/docs/workflows.md +10 -2
- package/node_modules/@narumitw/pi-btw/package.json +1 -1
- package/node_modules/@narumitw/pi-btw/src/btw.ts +17 -79
- package/node_modules/@narumitw/pi-btw/src/conversation-context.ts +74 -0
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +196 -7
- package/node_modules/@narumitw/pi-btw/src/main-thread-updates.ts +40 -0
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
- package/node_modules/@narumitw/pi-btw/src/settings.ts +49 -0
- package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +9 -1
- package/node_modules/@narumitw/pi-btw/src/workspace-layout.ts +559 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +62 -0
- package/node_modules/pi-mcp-adapter/README.md +75 -7
- package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +193 -0
- package/node_modules/pi-mcp-adapter/cli.js +56 -5
- package/node_modules/pi-mcp-adapter/commands.ts +96 -5
- package/node_modules/pi-mcp-adapter/config.ts +73 -6
- package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +22 -28
- package/node_modules/pi-mcp-adapter/direct-tools.ts +13 -5
- package/node_modules/pi-mcp-adapter/dist/abort.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js +36 -0
- package/node_modules/pi-mcp-adapter/dist/abort.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +194 -0
- package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.d.ts +5 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +75 -6
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +18 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +316 -0
- package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/errors.d.ts +131 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js +278 -0
- package/node_modules/pi-mcp-adapter/dist/errors.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +8 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js +88 -0
- package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js +340 -0
- package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +77 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +22 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +2 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +55 -0
- package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +56 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js +410 -0
- package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/logger.d.ts +51 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js +131 -0
- package/node_modules/pi-mcp-adapter/dist/logger.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +103 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +118 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +1098 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +168 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +1117 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +7 -140
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +53 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +441 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +148 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +689 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +166 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +102 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +369 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +95 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +242 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +2 -11
- package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +6 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +505 -0
- package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +10 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +312 -0
- package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +13 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +97 -0
- package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +203 -0
- package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +11 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +172 -0
- package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +157 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js +1743 -0
- package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +34 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js +140 -0
- package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +26 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js +142 -0
- package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/state.d.ts +73 -0
- package/node_modules/pi-mcp-adapter/dist/state.js +2 -0
- package/node_modules/pi-mcp-adapter/dist/state.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +36 -0
- package/node_modules/pi-mcp-adapter/dist/types.js +18 -0
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +17 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +219 -0
- package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +15 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +85 -0
- package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.d.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js +13 -0
- package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
- package/node_modules/pi-mcp-adapter/elicitation-handler.ts +29 -19
- package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +85 -0
- package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +34 -0
- package/node_modules/pi-mcp-adapter/index.ts +129 -6
- package/node_modules/pi-mcp-adapter/init.ts +6 -19
- package/node_modules/pi-mcp-adapter/jev-client.ts +273 -0
- package/node_modules/pi-mcp-adapter/jev-contracts.ts +53 -0
- package/node_modules/pi-mcp-adapter/jev-key-store.ts +79 -0
- package/node_modules/pi-mcp-adapter/lifecycle.ts +14 -3
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +7 -142
- package/node_modules/pi-mcp-adapter/mcp-code.ts +119 -11
- package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +4 -0
- package/node_modules/pi-mcp-adapter/mcp-references.ts +5 -3
- package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +5 -0
- package/node_modules/pi-mcp-adapter/mcp-tasks.ts +467 -0
- package/node_modules/pi-mcp-adapter/metadata-cache.ts +2 -13
- package/node_modules/pi-mcp-adapter/namespace-tools.ts +1 -1
- package/node_modules/pi-mcp-adapter/package.json +17 -9
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +319 -166
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +6 -3
- package/node_modules/pi-mcp-adapter/search-ranking.ts +38 -7
- package/node_modules/pi-mcp-adapter/secure-keyring.ts +170 -0
- package/node_modules/pi-mcp-adapter/semantic-search.ts +186 -0
- package/node_modules/pi-mcp-adapter/server-manager.ts +293 -50
- package/node_modules/pi-mcp-adapter/session-approvals.ts +14 -0
- package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +2 -0
- package/node_modules/pi-mcp-adapter/state.ts +3 -1
- package/node_modules/pi-mcp-adapter/tool-approval.ts +26 -4
- package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -14
- package/node_modules/pi-mcp-adapter/tool-registrar.ts +8 -6
- package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +4 -1
- package/node_modules/pi-mcp-adapter/types.ts +53 -0
- package/node_modules/pi-mcp-adapter/ui-server.ts +17 -9
- package/node_modules/pi-mcp-adapter/utils.ts +16 -0
- package/node_modules/pi-multi-account/CHANGELOG.md +23 -0
- package/node_modules/pi-multi-account/README.md +38 -11
- package/node_modules/pi-multi-account/index.ts +371 -96
- package/node_modules/pi-multi-account/package.json +5 -4
- package/node_modules/pi-multi-account/provider-payload-stream.ts +36 -28
- package/node_modules/pi-multi-account/usage.ts +31 -2
- package/node_modules/pi-typesafe/README.md +3 -1
- package/node_modules/pi-typesafe/dist/auth.d.ts +10 -3
- package/node_modules/pi-typesafe/dist/auth.js +13 -7
- package/node_modules/pi-typesafe/dist/backends.d.ts +31 -0
- package/node_modules/pi-typesafe/dist/backends.js +33 -0
- package/node_modules/pi-typesafe/dist/client.d.ts +3 -9
- package/node_modules/pi-typesafe/dist/client.js +63 -39
- package/node_modules/pi-typesafe/dist/credentials.d.ts +11 -5
- package/node_modules/pi-typesafe/dist/credentials.js +15 -8
- package/node_modules/pi-typesafe/dist/extension.js +4 -1
- package/node_modules/pi-typesafe/dist/index.d.ts +1 -1
- package/node_modules/pi-typesafe/dist/index.js +1 -1
- package/node_modules/pi-typesafe/dist/login.d.ts +13 -7
- package/node_modules/pi-typesafe/dist/login.js +17 -8
- package/node_modules/pi-typesafe/dist/schema.js +19 -13
- package/node_modules/pi-typesafe/package.json +1 -1
- package/package.json +11 -10
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { AjvJsonSchemaValidator } from "@modelcontextprotocol/client/validators/ajv";
|
|
12
12
|
import type { JsonSchemaType } from "@modelcontextprotocol/client";
|
|
13
13
|
import open from "open";
|
|
14
|
+
import { abortable, throwIfAborted } from "./abort.ts";
|
|
14
15
|
|
|
15
16
|
export type ElicitationValue = string | number | boolean | string[] | undefined;
|
|
16
17
|
type FormProperty = ElicitRequestFormParams["requestedSchema"]["properties"][string];
|
|
@@ -35,49 +36,52 @@ export function registerElicitationHandler(client: Client, options: ElicitationH
|
|
|
35
36
|
export async function handleElicitationRequest(
|
|
36
37
|
options: ElicitationHandlerOptions,
|
|
37
38
|
request: ElicitRequest,
|
|
39
|
+
signal?: AbortSignal,
|
|
38
40
|
): Promise<ElicitResult> {
|
|
41
|
+
throwIfAborted(signal);
|
|
39
42
|
return request.params.mode === "url"
|
|
40
|
-
? handleUrlElicitation(options, request.params)
|
|
41
|
-
: handleFormElicitation(options, request.params);
|
|
43
|
+
? handleUrlElicitation(options, request.params, signal)
|
|
44
|
+
: handleFormElicitation(options, request.params, signal);
|
|
42
45
|
}
|
|
43
46
|
|
|
44
47
|
export async function handleFormElicitation(
|
|
45
48
|
options: ElicitationHandlerOptions,
|
|
46
49
|
params: ElicitRequestFormParams,
|
|
50
|
+
signal?: AbortSignal,
|
|
47
51
|
): Promise<ElicitResult> {
|
|
48
52
|
const properties = Object.entries(params.requestedSchema.properties);
|
|
49
|
-
const decision = await options.ui.select(
|
|
53
|
+
const decision = await abortable(options.ui.select(
|
|
50
54
|
`MCP Input Request\nServer: ${options.serverName}\n\n${params.message}`,
|
|
51
55
|
["Continue", "Decline"],
|
|
52
|
-
);
|
|
56
|
+
), signal);
|
|
53
57
|
if (decision === undefined) return { action: "cancel" };
|
|
54
58
|
if (decision === "Decline") return { action: "decline" };
|
|
55
59
|
if (properties.length === 0) return { action: "accept", content: {} };
|
|
56
60
|
|
|
57
61
|
const values: Record<string, ElicitationValue> = {};
|
|
58
62
|
for (const [name, schema] of properties) {
|
|
59
|
-
const value = await collectValidField(options.ui, params, name, schema);
|
|
63
|
+
const value = await collectValidField(options.ui, params, name, schema, undefined, signal);
|
|
60
64
|
if (value.status === "cancelled") return { action: "cancel" };
|
|
61
65
|
values[name] = value.value;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
while (true) {
|
|
65
69
|
const content = coerceAndValidateFormValues(params, values);
|
|
66
|
-
const action = await options.ui.select(
|
|
70
|
+
const action = await abortable(options.ui.select(
|
|
67
71
|
formatReview(options.serverName, properties, content),
|
|
68
72
|
["Submit", "Edit", "Decline"],
|
|
69
|
-
);
|
|
73
|
+
), signal);
|
|
70
74
|
if (action === undefined) return { action: "cancel" };
|
|
71
75
|
if (action === "Decline") return { action: "decline" };
|
|
72
76
|
if (action === "Submit") return { action: "accept", content };
|
|
73
77
|
|
|
74
78
|
const labels = properties.map(([name, schema]) => `${schema.title ?? humanizeName(name)} (${name})`);
|
|
75
|
-
const selected = await options.ui.select("Choose a field to edit", labels);
|
|
79
|
+
const selected = await abortable(options.ui.select("Choose a field to edit", labels), signal);
|
|
76
80
|
if (selected === undefined) return { action: "cancel" };
|
|
77
81
|
const property = properties[labels.indexOf(selected)];
|
|
78
82
|
if (!property) continue;
|
|
79
83
|
const [name, schema] = property;
|
|
80
|
-
const value = await collectValidField(options.ui, params, name, schema, values[name]);
|
|
84
|
+
const value = await collectValidField(options.ui, params, name, schema, values[name], signal);
|
|
81
85
|
if (value.status === "cancelled") return { action: "cancel" };
|
|
82
86
|
values[name] = value.value;
|
|
83
87
|
}
|
|
@@ -89,10 +93,11 @@ async function collectValidField(
|
|
|
89
93
|
name: string,
|
|
90
94
|
schema: FormProperty,
|
|
91
95
|
current?: ElicitationValue,
|
|
96
|
+
signal?: AbortSignal,
|
|
92
97
|
): Promise<FieldCollectionResult> {
|
|
93
98
|
const required = params.requestedSchema.required?.includes(name) === true;
|
|
94
99
|
while (true) {
|
|
95
|
-
const result = await collectField(ui, params, name, schema, current);
|
|
100
|
+
const result = await collectField(ui, params, name, schema, current, signal);
|
|
96
101
|
if (result.status === "cancelled") return result;
|
|
97
102
|
try {
|
|
98
103
|
coerceAndValidateFormValues({
|
|
@@ -117,6 +122,7 @@ async function collectField(
|
|
|
117
122
|
name: string,
|
|
118
123
|
schema: FormProperty,
|
|
119
124
|
current?: ElicitationValue,
|
|
125
|
+
signal?: AbortSignal,
|
|
120
126
|
): Promise<FieldCollectionResult> {
|
|
121
127
|
const required = params.requestedSchema.required?.includes(name) === true;
|
|
122
128
|
const title = [schema.title ?? humanizeName(name), required ? "(required)" : "", schema.description]
|
|
@@ -136,7 +142,7 @@ async function collectField(
|
|
|
136
142
|
if (useDefault) actions.push(useDefault);
|
|
137
143
|
const omit = required ? undefined : uniqueAction("Omit", actions);
|
|
138
144
|
if (omit) actions.push(omit);
|
|
139
|
-
const action = await ui.select(title, actions);
|
|
145
|
+
const action = await abortable(ui.select(title, actions), signal);
|
|
140
146
|
if (action === undefined) return { status: "cancelled" };
|
|
141
147
|
if (action === useDefault) return { status: "collected", value: schema.default };
|
|
142
148
|
if (action === omit) return { status: "collected", value: undefined };
|
|
@@ -147,7 +153,7 @@ async function collectField(
|
|
|
147
153
|
const actions = ["Yes", "No"];
|
|
148
154
|
if (schema.default !== undefined) actions.push("Use default");
|
|
149
155
|
if (!required) actions.push("Omit");
|
|
150
|
-
const action = await ui.select(title, actions);
|
|
156
|
+
const action = await abortable(ui.select(title, actions), signal);
|
|
151
157
|
if (action === undefined) return { status: "cancelled" };
|
|
152
158
|
if (action === "Use default") return { status: "collected", value: schema.default };
|
|
153
159
|
if (action === "Omit") return { status: "collected", value: undefined };
|
|
@@ -158,7 +164,7 @@ async function collectField(
|
|
|
158
164
|
const actions = ["Choose values"];
|
|
159
165
|
if (schema.default !== undefined) actions.push("Use default");
|
|
160
166
|
if (!required) actions.push("Omit");
|
|
161
|
-
const action = await ui.select(title, actions);
|
|
167
|
+
const action = await abortable(ui.select(title, actions), signal);
|
|
162
168
|
if (action === undefined) return { status: "cancelled" };
|
|
163
169
|
if (action === "Use default") return { status: "collected", value: schema.default };
|
|
164
170
|
if (action === "Omit") return { status: "collected", value: undefined };
|
|
@@ -168,7 +174,7 @@ async function collectField(
|
|
|
168
174
|
while (true) {
|
|
169
175
|
const displays = uniqueLabels(choices.map(choice => selected.has(choice.value) ? `✓ ${choice.display}` : choice.display));
|
|
170
176
|
const done = uniqueAction("Done", displays);
|
|
171
|
-
const picked = await ui.select(title, [...displays, done]);
|
|
177
|
+
const picked = await abortable(ui.select(title, [...displays, done]), signal);
|
|
172
178
|
if (picked === undefined) return { status: "cancelled" };
|
|
173
179
|
if (picked === done) return { status: "collected", value: [...selected] };
|
|
174
180
|
const choice = choices[displays.indexOf(picked)];
|
|
@@ -181,11 +187,11 @@ async function collectField(
|
|
|
181
187
|
const actions = ["Enter value"];
|
|
182
188
|
if (schema.default !== undefined) actions.push("Use default");
|
|
183
189
|
if (!required) actions.push("Omit");
|
|
184
|
-
const action = await ui.select(title, actions);
|
|
190
|
+
const action = await abortable(ui.select(title, actions), signal);
|
|
185
191
|
if (action === undefined) return { status: "cancelled" };
|
|
186
192
|
if (action === "Use default") return { status: "collected", value: schema.default };
|
|
187
193
|
if (action === "Omit") return { status: "collected", value: undefined };
|
|
188
|
-
const entered = await ui.input(title, current === undefined ? undefined : String(current));
|
|
194
|
+
const entered = await abortable(ui.input(title, current === undefined ? undefined : String(current)), signal);
|
|
189
195
|
return entered === undefined ? { status: "cancelled" } : { status: "collected", value: entered };
|
|
190
196
|
}
|
|
191
197
|
|
|
@@ -305,7 +311,9 @@ function formatReview(
|
|
|
305
311
|
export async function handleUrlElicitation(
|
|
306
312
|
options: ElicitationHandlerOptions,
|
|
307
313
|
params: ElicitRequestURLParams,
|
|
314
|
+
signal?: AbortSignal,
|
|
308
315
|
): Promise<ElicitResult> {
|
|
316
|
+
throwIfAborted(signal);
|
|
309
317
|
if (!options.allowUrl) throw new ProtocolError(ProtocolErrorCode.InvalidParams, "URL elicitation is not supported");
|
|
310
318
|
|
|
311
319
|
let parsed: URL;
|
|
@@ -318,7 +326,7 @@ export async function handleUrlElicitation(
|
|
|
318
326
|
throw new ProtocolError(ProtocolErrorCode.InvalidParams, "URL elicitation only supports HTTP and HTTPS URLs");
|
|
319
327
|
}
|
|
320
328
|
|
|
321
|
-
const decision = await options.ui.select([
|
|
329
|
+
const decision = await abortable(options.ui.select([
|
|
322
330
|
"MCP Browser Request",
|
|
323
331
|
`Server: ${options.serverName}`,
|
|
324
332
|
"",
|
|
@@ -328,16 +336,18 @@ export async function handleUrlElicitation(
|
|
|
328
336
|
`Full URL: ${params.url}`,
|
|
329
337
|
"",
|
|
330
338
|
"Open this URL in your browser?",
|
|
331
|
-
].join("\n"), ["Open", "Decline"]);
|
|
339
|
+
].join("\n"), ["Open", "Decline"]), signal);
|
|
332
340
|
if (decision === undefined) return { action: "cancel" };
|
|
333
341
|
if (decision === "Decline") return { action: "decline" };
|
|
334
342
|
|
|
335
343
|
try {
|
|
336
|
-
await open(params.url);
|
|
344
|
+
await abortable(open(params.url), signal);
|
|
337
345
|
} catch (error) {
|
|
346
|
+
throwIfAborted(signal);
|
|
338
347
|
options.ui.notify(`Could not open MCP elicitation URL: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
339
348
|
return { action: "cancel" };
|
|
340
349
|
}
|
|
350
|
+
throwIfAborted(signal);
|
|
341
351
|
options.onUrlAccepted?.(params.elicitationId);
|
|
342
352
|
options.ui.notify("Opened browser for MCP elicitation.", "info");
|
|
343
353
|
return { action: "accept" };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const SAFE_OPERATIONS = new Set(["focus", "scroll"]);
|
|
2
|
+
|
|
3
|
+
export async function runAccessibilityLoop(tools, jev, options) {
|
|
4
|
+
const { observePath, actionPath, goal, allowedOperations, sources, maxSteps, maxMs, maxEvaluations } = options;
|
|
5
|
+
if (typeof observePath !== "string" || !observePath || typeof actionPath !== "string" || !actionPath || typeof goal !== "string" || !goal
|
|
6
|
+
|| !Array.isArray(allowedOperations) || allowedOperations.length === 0 || allowedOperations.some(operation => !SAFE_OPERATIONS.has(operation))) {
|
|
7
|
+
throw new Error("Exact tool paths, a goal, and non-destructive focus/scroll operations are required");
|
|
8
|
+
}
|
|
9
|
+
if (![maxSteps, maxMs, maxEvaluations].every(value => Number.isInteger(value) && value > 0)) {
|
|
10
|
+
throw new Error("Positive step, time, and evaluation budgets are required");
|
|
11
|
+
}
|
|
12
|
+
if (!Array.isArray(sources) || sources.length === 0 || sources.some(source => typeof source !== "string" || !source)) {
|
|
13
|
+
throw new Error("Explicit MCP sources are required for accessibility data");
|
|
14
|
+
}
|
|
15
|
+
const startedAt = Date.now();
|
|
16
|
+
let evaluations = 0;
|
|
17
|
+
let previousFingerprint;
|
|
18
|
+
|
|
19
|
+
const observe = async () => {
|
|
20
|
+
const result = await tools.call(observePath, {});
|
|
21
|
+
if (!result.ok) return { error: "observation-failed" };
|
|
22
|
+
const tree = result.data?.structuredContent;
|
|
23
|
+
if (!tree || typeof tree.observationId !== "string" || !tree.observationId || !Array.isArray(tree.nodes)) {
|
|
24
|
+
return { error: "needs-information" };
|
|
25
|
+
}
|
|
26
|
+
return { tree };
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
for (let step = 0; step < maxSteps; step += 1) {
|
|
30
|
+
if (Date.now() - startedAt >= maxMs) return { status: "stop", reason: "time-budget" };
|
|
31
|
+
const snapshot = await observe();
|
|
32
|
+
if (snapshot.error) return { status: snapshot.error === "needs-information" ? "needs-information" : "stop", reason: snapshot.error };
|
|
33
|
+
const fingerprint = JSON.stringify(snapshot.tree.nodes);
|
|
34
|
+
if (fingerprint === previousFingerprint) return { status: "stop", reason: "no-progress" };
|
|
35
|
+
previousFingerprint = fingerprint;
|
|
36
|
+
|
|
37
|
+
const actions = snapshot.tree.nodes.flatMap(node =>
|
|
38
|
+
(node && typeof node.id === "string" && node.id && Array.isArray(node.actions) ? node.actions : [])
|
|
39
|
+
.filter(operation => typeof operation === "string" && allowedOperations.includes(operation))
|
|
40
|
+
.map(operation => ({ target: node.id, operation })),
|
|
41
|
+
).slice(0, 120).map((action, index) => ({ ...action, label: `a${index}` }));
|
|
42
|
+
if (actions.length === 0) return { status: "no-match" };
|
|
43
|
+
if (evaluations >= maxEvaluations) return { status: "stop", reason: "evaluation-budget" };
|
|
44
|
+
evaluations += 1;
|
|
45
|
+
const evaluation = await jev.evaluate({
|
|
46
|
+
state: { goal, observationId: snapshot.tree.observationId, nodes: snapshot.tree.nodes },
|
|
47
|
+
sources,
|
|
48
|
+
questions: {
|
|
49
|
+
next: {
|
|
50
|
+
type: "choice",
|
|
51
|
+
instructions: "Choose complete only when the observation proves the goal. Otherwise choose one safe action, none, or needsInformation.",
|
|
52
|
+
criteria: Object.fromEntries([
|
|
53
|
+
...actions.map(action => [action.label, { target: action.target, operation: action.operation }]),
|
|
54
|
+
["complete", "Goal is observably complete"],
|
|
55
|
+
["none", "No matching safe action"],
|
|
56
|
+
["needsInformation", "User information is required"],
|
|
57
|
+
]),
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
if (!evaluation.ok) return { status: "stop", reason: evaluation.error.code };
|
|
62
|
+
const answer = evaluation.data.answers.next;
|
|
63
|
+
if (answer.type !== "choice" || answer.confidence < 0.85) return { status: "stop", reason: "uncertain" };
|
|
64
|
+
if (answer.choice === "complete") return { status: "complete", observationId: snapshot.tree.observationId };
|
|
65
|
+
if (answer.choice === "none") return { status: "no-match" };
|
|
66
|
+
if (answer.choice === "needsInformation") return { status: "needs-information" };
|
|
67
|
+
const selected = actions.find(action => action.label === answer.choice);
|
|
68
|
+
if (!selected) return { status: "stop", reason: "invalid-action" };
|
|
69
|
+
|
|
70
|
+
if (Date.now() - startedAt >= maxMs) return { status: "stop", reason: "time-budget" };
|
|
71
|
+
const fresh = await observe();
|
|
72
|
+
if (fresh.error || fresh.tree.observationId === snapshot.tree.observationId) return { status: "stop", reason: "stale-candidate" };
|
|
73
|
+
const target = fresh.tree.nodes.find(node => node.id === selected.target);
|
|
74
|
+
if (!target || !Array.isArray(target.actions) || !target.actions.includes(selected.operation)) {
|
|
75
|
+
return { status: "stop", reason: "stale-candidate" };
|
|
76
|
+
}
|
|
77
|
+
const acted = await tools.call(actionPath, {
|
|
78
|
+
operation: selected.operation,
|
|
79
|
+
target: selected.target,
|
|
80
|
+
observationId: fresh.tree.observationId,
|
|
81
|
+
});
|
|
82
|
+
if (!acted.ok) return { status: "stop", reason: "action-failed-or-uncertain" };
|
|
83
|
+
}
|
|
84
|
+
return { status: "stop", reason: "step-budget" };
|
|
85
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export async function filterIssuesForTriage(tools, jev) {
|
|
2
|
+
const found = await tools.search({
|
|
3
|
+
query: "list open customer issue reports",
|
|
4
|
+
server: "github",
|
|
5
|
+
searchMode: "semantic",
|
|
6
|
+
limit: 5,
|
|
7
|
+
});
|
|
8
|
+
const tool = found.items?.[0];
|
|
9
|
+
if (!tool) return { status: "no-match" };
|
|
10
|
+
|
|
11
|
+
const listed = await tools.call(tool.path, { state: "open", limit: 20 });
|
|
12
|
+
if (!listed.ok) return { status: "stop", error: listed.error };
|
|
13
|
+
const issues = listed.data?.structuredContent?.issues;
|
|
14
|
+
if (!Array.isArray(issues) || issues.length === 0) return { status: "no-match" };
|
|
15
|
+
|
|
16
|
+
const candidates = issues.slice(0, 20).map((issue, index) => ({
|
|
17
|
+
id: `issue${index}`,
|
|
18
|
+
title: String(issue.title ?? ""),
|
|
19
|
+
body: String(issue.body ?? "").slice(0, 2000),
|
|
20
|
+
}));
|
|
21
|
+
const questions = Object.fromEntries(candidates.flatMap(issue => [
|
|
22
|
+
[`relevant_${issue.id}`, { type: "noul", instructions: "Is this issue actionable and relevant?", criteria: { true: "Relevant", false: "Not relevant" } }],
|
|
23
|
+
[`regression_${issue.id}`, { type: "noul", instructions: "Does this issue contain evidence of a regression?", criteria: { true: "Regression", false: "No regression evidence" } }],
|
|
24
|
+
]));
|
|
25
|
+
const evaluation = await jev.evaluate({ state: { candidates }, questions, sources: ["github"] });
|
|
26
|
+
if (!evaluation.ok) return { status: "stop", error: evaluation.error };
|
|
27
|
+
|
|
28
|
+
const useful = candidates.filter(issue => {
|
|
29
|
+
const relevant = evaluation.data.answers[`relevant_${issue.id}`];
|
|
30
|
+
const regression = evaluation.data.answers[`regression_${issue.id}`];
|
|
31
|
+
return relevant?.type === "noul" && regression?.type === "noul" && relevant.noul >= 0.8 && regression.noul >= 0.7;
|
|
32
|
+
});
|
|
33
|
+
return useful.length > 0 ? { status: "matched", issues: useful } : { status: "no-match" };
|
|
34
|
+
}
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { withFileMutationQueue, type AgentToolUpdateCallback, type ExtensionAPI, type ExtensionContext, type ToolInfo } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
3
5
|
import type { McpExtensionState } from "./state.ts";
|
|
4
6
|
import { isServerDisabled, type DirectToolSpec, type McpAdapterOptions, type McpConfig, type PromptMetadata, type ServerEntry } from "./types.ts";
|
|
5
7
|
import type { McpOAuthRuntime } from "./mcp-auth-flow.ts";
|
|
6
8
|
import { Type } from "typebox";
|
|
7
9
|
import type { TSchema } from "typebox";
|
|
8
10
|
import { cloneMcpConfig, discoverConfiguredClaudePluginSkills, getPiGlobalConfigPath, getProjectConfigPath, loadMcpConfig, resolveConfiguredClaudePluginMcp, writeProjectServerDisabledOverride, writeSharedServerEntry } from "./config.ts";
|
|
9
|
-
import { buildProxyDescription, getMissingConfiguredDirectToolServers, prepareDirectToolArguments, resolveDirectTools } from "./direct-tool-surface.ts";
|
|
11
|
+
import { buildProxyDescription, getLargeDirectToolsAdvisory, getMissingConfiguredDirectToolServers, prepareDirectToolArguments, resolveDirectTools } from "./direct-tool-surface.ts";
|
|
10
12
|
import { isServerInActiveFailureBackoff } from "./failure-backoff.ts";
|
|
11
13
|
import { computeServerHash, isServerCacheValid, loadMetadataCache, parseDirectToolSelectors, type MetadataCache } from "./metadata-cache.ts";
|
|
12
14
|
import { createPromptCommand, resolveCachedPrompts } from "./prompts.ts";
|
|
13
15
|
import { logger } from "./logger.ts";
|
|
14
|
-
import { formatTerminalError, getConfigPathFromArgv, normalizeDirectToolInputSchema, truncateAtWord } from "./utils.ts";
|
|
16
|
+
import { formatMcpFooterStatus, formatTerminalError, getConfigPathFromArgv, normalizeDirectToolInputSchema, truncateAtWord } from "./utils.ts";
|
|
15
17
|
import { createMcpDirectToolCallRenderer, createMcpProxyToolCallRenderer, createMcpScriptToolCallRenderer, createMcpToolResultRenderer, resolveMcpToolRenderOptions } from "./tool-result-renderer.ts";
|
|
16
18
|
import { toolErrorOverride } from "./error-signal.ts";
|
|
17
19
|
import { createMcpRuntimeOwner, createOwnedUi, isAbortError, type McpRuntimeOwner } from "./runtime-owner.ts";
|
|
@@ -348,6 +350,7 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
348
350
|
let proxyToolRegistered = false;
|
|
349
351
|
let proxyToolDescription: string | null = null;
|
|
350
352
|
let directToolsFrozen = false;
|
|
353
|
+
let largeDirectToolsAdvisoryDelivered = false;
|
|
351
354
|
// Session/runtime scoped server registrations from other extensions. They
|
|
352
355
|
// survive session restarts within this install and die with the process.
|
|
353
356
|
const runtimeServers = new Map<string, { definition: ServerEntry; entry: ServerEntry }>();
|
|
@@ -605,6 +608,8 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
605
608
|
}
|
|
606
609
|
|
|
607
610
|
function syncToolSurface(ctx?: ExtensionContext): void {
|
|
611
|
+
const notificationGeneration = lifecycleGeneration;
|
|
612
|
+
const notificationOwner = currentOwner;
|
|
608
613
|
const config = state?.config ?? earlyConfig;
|
|
609
614
|
const cache = loadToolSurfaceCache(config);
|
|
610
615
|
const result = syncDirectTools(config, cache);
|
|
@@ -618,6 +623,13 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
618
623
|
}
|
|
619
624
|
syncProxyTool(config, cache, result.specs);
|
|
620
625
|
syncNamespaceTools(config, cache, result.reservedDirectNames, result.activeDirectNames);
|
|
626
|
+
deliverLargeDirectToolsAdvisory(ctx, config, result.specs);
|
|
627
|
+
if (ctx && (notificationGeneration !== lifecycleGeneration
|
|
628
|
+
|| notificationOwner !== currentOwner
|
|
629
|
+
|| (notificationOwner && !notificationOwner.isActive()))) {
|
|
630
|
+
throw notificationOwner?.signal.reason ?? new Error("Stale MCP session after direct-tools advisory");
|
|
631
|
+
}
|
|
632
|
+
finalizationGuard?.();
|
|
621
633
|
const changed = result.added.length + result.updated.length + result.deactivated.length;
|
|
622
634
|
if (changed > 0 && ctx?.hasUI) {
|
|
623
635
|
callReentrant(() => ctx.ui.notify(
|
|
@@ -627,6 +639,49 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
627
639
|
}
|
|
628
640
|
}
|
|
629
641
|
|
|
642
|
+
function deliverLargeDirectToolsAdvisory(
|
|
643
|
+
ctx: ExtensionContext | undefined,
|
|
644
|
+
config: McpConfig,
|
|
645
|
+
specs: readonly DirectToolSpec[],
|
|
646
|
+
): void {
|
|
647
|
+
if (!ctx || largeDirectToolsAdvisoryDelivered) return;
|
|
648
|
+
const message = getLargeDirectToolsAdvisory(config, specs);
|
|
649
|
+
if (!message) return;
|
|
650
|
+
largeDirectToolsAdvisoryDelivered = true;
|
|
651
|
+
if (ctx.hasUI) {
|
|
652
|
+
callReentrant(() => ctx.ui.notify(message, "warning"));
|
|
653
|
+
} else {
|
|
654
|
+
console.warn(message);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function getDeferredSessionSnapshot(cwd: string | undefined): {
|
|
659
|
+
config: McpConfig;
|
|
660
|
+
specs: DirectToolSpec[];
|
|
661
|
+
enabledServerCount: number;
|
|
662
|
+
} | undefined {
|
|
663
|
+
const config = programmaticConfig
|
|
664
|
+
? resolveConfiguredClaudePluginMcp(cloneMcpConfig(sessionConfig), cwd ?? process.cwd())
|
|
665
|
+
: loadMcpConfig(earlyConfigPath, cwd);
|
|
666
|
+
const cache = loadMetadataCache();
|
|
667
|
+
const enabledServers = Object.entries(config.mcpServers)
|
|
668
|
+
.filter(([, definition]) => !isServerDisabled(definition));
|
|
669
|
+
if (enabledServers.some(([, definition]) => definition.lifecycle === "eager" || definition.lifecycle === "keep-alive")) {
|
|
670
|
+
return undefined;
|
|
671
|
+
}
|
|
672
|
+
if (!cache || !enabledServers.every(([serverName, definition]) => {
|
|
673
|
+
const entry = cache.servers[serverName];
|
|
674
|
+
return entry !== undefined && isServerCacheValid(entry, definition);
|
|
675
|
+
})) return undefined;
|
|
676
|
+
if (envRaw !== undefined && envRaw !== "__none__"
|
|
677
|
+
&& getMissingConfiguredDirectToolServers(config, cache, envDirectToolOverride).length > 0) return undefined;
|
|
678
|
+
return {
|
|
679
|
+
config,
|
|
680
|
+
specs: resolveCurrentDirectTools(config, cache),
|
|
681
|
+
enabledServerCount: enabledServers.length,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
630
685
|
function syncNamespaceTools(
|
|
631
686
|
config: McpConfig,
|
|
632
687
|
cache: MetadataCache | null,
|
|
@@ -1016,11 +1071,18 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1016
1071
|
? cloneMcpConfig(sessionConfig)
|
|
1017
1072
|
: loadMcpConfig(earlyConfigPath, event.cwd);
|
|
1018
1073
|
const skillPaths = discoverConfiguredClaudePluginSkills(resourceConfig, event.cwd);
|
|
1074
|
+
if (earlyConfig.settings?.scriptMode !== false) {
|
|
1075
|
+
const scriptingSkillPath = fileURLToPath(new URL("./skills/mcp-scripting/SKILL.md", import.meta.url));
|
|
1076
|
+
if (existsSync(scriptingSkillPath) && !skillPaths.includes(scriptingSkillPath)) {
|
|
1077
|
+
skillPaths.push(scriptingSkillPath);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1019
1080
|
return skillPaths.length > 0 ? { skillPaths } : undefined;
|
|
1020
1081
|
});
|
|
1021
1082
|
|
|
1022
1083
|
pi.on("session_start", async (_event, ctx) => {
|
|
1023
1084
|
const generation = ++lifecycleGeneration;
|
|
1085
|
+
largeDirectToolsAdvisoryDelivered = false;
|
|
1024
1086
|
const previousState = state;
|
|
1025
1087
|
const previousOwner = currentOwner;
|
|
1026
1088
|
const previousOAuthRuntime = currentOAuthRuntime;
|
|
@@ -1049,7 +1111,24 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1049
1111
|
if (state) return;
|
|
1050
1112
|
|
|
1051
1113
|
if (!initPromise) {
|
|
1052
|
-
|
|
1114
|
+
const deferredSnapshot = deferSessionRuntime ? getDeferredSessionSnapshot(ctx.cwd) : undefined;
|
|
1115
|
+
if (deferredSnapshot) {
|
|
1116
|
+
deliverLargeDirectToolsAdvisory(ctx, deferredSnapshot.config, deferredSnapshot.specs);
|
|
1117
|
+
if (generation !== lifecycleGeneration || !owner.isActive() || currentOwner !== owner) return;
|
|
1118
|
+
const serverCount = Object.keys(deferredSnapshot.config.mcpServers).length;
|
|
1119
|
+
const formattedStatus = formatMcpFooterStatus(
|
|
1120
|
+
deferredSnapshot.config,
|
|
1121
|
+
deferredSnapshot.enabledServerCount,
|
|
1122
|
+
serverCount - deferredSnapshot.enabledServerCount,
|
|
1123
|
+
0,
|
|
1124
|
+
);
|
|
1125
|
+
const theme = ctx.ui?.theme;
|
|
1126
|
+
const styledStatus = formattedStatus !== undefined && typeof theme?.fg === "function"
|
|
1127
|
+
? theme.fg("accent", formattedStatus)
|
|
1128
|
+
: formattedStatus;
|
|
1129
|
+
ctx.ui?.setStatus("mcp", styledStatus);
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1053
1132
|
startInitialization(ctx, owner, generation, "stale_session_start");
|
|
1054
1133
|
}
|
|
1055
1134
|
|
|
@@ -1148,6 +1227,8 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1148
1227
|
{ value: "tools", label: "tools — List all tools" },
|
|
1149
1228
|
{ value: "prompts", label: "prompts — List all MCP prompts" },
|
|
1150
1229
|
{ value: "setup", label: "setup — Configure MCP servers" },
|
|
1230
|
+
{ value: "jev", label: "jev setup — Configure Jev semantic search" },
|
|
1231
|
+
{ value: "edit", label: "edit — Edit .mcp.json or the global config" },
|
|
1151
1232
|
{ value: "logout", label: "logout — Clear server credentials" },
|
|
1152
1233
|
{ value: "token", label: "token — Manage stored bearer tokens" },
|
|
1153
1234
|
{ value: "disable", label: "disable — Disable a server" },
|
|
@@ -1158,6 +1239,11 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1158
1239
|
}
|
|
1159
1240
|
|
|
1160
1241
|
const [, subcommand, argumentPrefix] = argumentMatch;
|
|
1242
|
+
if (subcommand === "jev") {
|
|
1243
|
+
return "setup".startsWith((argumentPrefix ?? "").trimStart())
|
|
1244
|
+
? [{ value: "jev setup", label: "setup — Configure Jev semantic search" }]
|
|
1245
|
+
: null;
|
|
1246
|
+
}
|
|
1161
1247
|
if (
|
|
1162
1248
|
(subcommand !== "reconnect" && subcommand !== "logout" && subcommand !== "disable" && subcommand !== "enable" && subcommand !== "token")
|
|
1163
1249
|
|| argumentPrefix === undefined
|
|
@@ -1246,6 +1332,41 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1246
1332
|
}
|
|
1247
1333
|
break;
|
|
1248
1334
|
}
|
|
1335
|
+
case "jev": {
|
|
1336
|
+
if (parts[1] !== "setup" || parts.length !== 2) {
|
|
1337
|
+
commandCtx.ui?.notify("Usage: /mcp jev setup", "error");
|
|
1338
|
+
break;
|
|
1339
|
+
}
|
|
1340
|
+
if (programmaticConfig) {
|
|
1341
|
+
commandCtx.ui?.notify("Jev setup is unavailable when config is supplied by createMcpAdapter().", "info");
|
|
1342
|
+
break;
|
|
1343
|
+
}
|
|
1344
|
+
commandOwner?.throwIfInactive();
|
|
1345
|
+
if (await commands.setupJevSemanticSearch(state, commandCtx, earlyConfigPath)) {
|
|
1346
|
+
commandOwner?.throwIfInactive();
|
|
1347
|
+
await commandReload();
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
break;
|
|
1351
|
+
}
|
|
1352
|
+
case "edit": {
|
|
1353
|
+
if (programmaticConfig) {
|
|
1354
|
+
commandCtx.ui?.notify("MCP edit is unavailable when config is supplied by createMcpAdapter().", "info");
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
const target = parts[1] ?? "project";
|
|
1358
|
+
if (target !== "project" && target !== "global") {
|
|
1359
|
+
commandCtx.ui?.notify("Usage: /mcp edit [project|global]", "error");
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
commandOwner?.throwIfInactive();
|
|
1363
|
+
if (await commands.editSharedConfig(commandCtx, target)) {
|
|
1364
|
+
commandOwner?.throwIfInactive();
|
|
1365
|
+
await commandReload();
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
break;
|
|
1369
|
+
}
|
|
1249
1370
|
case "logout": {
|
|
1250
1371
|
const serverName = rest;
|
|
1251
1372
|
if (!serverName) {
|
|
@@ -1666,11 +1787,12 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1666
1787
|
describe: Type.Optional(Type.String({ description: "Tool name to describe (shows parameters)" })),
|
|
1667
1788
|
instructions: Type.Optional(Type.String({ description: "Server name to show that server's usage instructions" })),
|
|
1668
1789
|
search: Type.Optional(Type.String({ description: "Search tools by name/description" })),
|
|
1790
|
+
searchMode: Type.Optional(Type.String({ enum: ["lexical", "semantic"], description: "Search backend (default: lexical; semantic is available when a TypeSafe key is configured)" })),
|
|
1669
1791
|
regex: Type.Optional(Type.Boolean({ description: "Treat search as regex (default: substring match)" })),
|
|
1670
1792
|
includeSchemas: Type.Optional(Type.Boolean({ description: "Include parameter schemas in search results (default: true)" })),
|
|
1671
1793
|
limit: optionalNumber({ minimum: 1, description: "Maximum search results to return (default: 12)" }),
|
|
1672
1794
|
offset: optionalNumber({ minimum: 0, description: "Search result offset (default: 0)" }),
|
|
1673
|
-
server: Type.Optional(Type.String({ description: "Server name
|
|
1795
|
+
server: Type.Optional(Type.String({ description: "Server name: filters searches, disambiguates calls and describe operations, and optionally names an install" })),
|
|
1674
1796
|
action: Type.Optional(Type.String({ description: "Action: 'install', 'ui-messages', 'auth-start', or 'auth-complete'" })),
|
|
1675
1797
|
url: Type.Optional(Type.String({ description: "MCP endpoint URL for action: 'install'" })),
|
|
1676
1798
|
target: Type.Optional(Type.String({ description: "Install target: 'global' (default) or 'project'" })),
|
|
@@ -1683,6 +1805,7 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1683
1805
|
describe?: string;
|
|
1684
1806
|
instructions?: string;
|
|
1685
1807
|
search?: string;
|
|
1808
|
+
searchMode?: "lexical" | "semantic";
|
|
1686
1809
|
regex?: boolean;
|
|
1687
1810
|
includeSchemas?: boolean;
|
|
1688
1811
|
limit?: number;
|
|
@@ -1811,13 +1934,13 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1811
1934
|
return connectAndReport(proxyState, params.connect, signal, _ctx as ExtensionContext);
|
|
1812
1935
|
}
|
|
1813
1936
|
if (params.describe) {
|
|
1814
|
-
return proxyModes.executeDescribe(proxyState, params.describe);
|
|
1937
|
+
return proxyModes.executeDescribe(proxyState, params.describe, params.server);
|
|
1815
1938
|
}
|
|
1816
1939
|
if (params.instructions) {
|
|
1817
1940
|
return proxyModes.executeInstructions(proxyState, params.instructions);
|
|
1818
1941
|
}
|
|
1819
1942
|
if (params.search !== undefined) {
|
|
1820
|
-
const result = proxyModes.executeSearch(proxyState, params.search, params.regex, params.server, params.includeSchemas, params.limit, params.offset);
|
|
1943
|
+
const result = await proxyModes.executeSearch(proxyState, params.search, params.regex, params.server, params.includeSchemas, params.limit, params.offset, params.searchMode, signal);
|
|
1821
1944
|
if (lazyDirectTools.size === 0) return result;
|
|
1822
1945
|
holdLazyToolsInactive();
|
|
1823
1946
|
const matches = (result.details as { matches?: Array<{ server: string; tool: string }> } | undefined)?.matches ?? [];
|
|
@@ -24,7 +24,7 @@ import { McpServerManager, isTransientHttpConnectError } from "./server-manager.
|
|
|
24
24
|
import { buildToolMetadata, totalToolCount } from "./tool-metadata.ts";
|
|
25
25
|
import { resourceNameToToolName } from "./resource-tools.ts";
|
|
26
26
|
import { UiResourceHandler } from "./ui-resource-handler.ts";
|
|
27
|
-
import { formatMcpStatus, openUrl, parallelLimit, sanitizeTerminalText } from "./utils.ts";
|
|
27
|
+
import { formatMcpFooterStatus, formatMcpStatus, openUrl, parallelLimit, sanitizeTerminalText } from "./utils.ts";
|
|
28
28
|
import { logger } from "./logger.ts";
|
|
29
29
|
import { throwIfAborted } from "./abort.ts";
|
|
30
30
|
import { getAuthStorageOptions } from "./mcp-auth.ts";
|
|
@@ -209,6 +209,7 @@ export async function initializeMcp(
|
|
|
209
209
|
failureTracker,
|
|
210
210
|
failureMessages,
|
|
211
211
|
approvedToolCalls,
|
|
212
|
+
approvedServers: new Map(),
|
|
212
213
|
...(persistSessionApproval !== undefined ? { persistSessionApproval } : {}),
|
|
213
214
|
...(sessionManager !== undefined ? { sessionManager } : {}),
|
|
214
215
|
approvalEvents: pi.events,
|
|
@@ -253,6 +254,9 @@ export async function initializeMcp(
|
|
|
253
254
|
if (!owner.isActive()) return;
|
|
254
255
|
updateStatusBar(state);
|
|
255
256
|
});
|
|
257
|
+
owner.addCleanup(() => {
|
|
258
|
+
state.approvedServers = new Map();
|
|
259
|
+
});
|
|
256
260
|
owner.addCleanup(() => lifecycle.gracefulShutdown());
|
|
257
261
|
owner.addCleanup(() => {
|
|
258
262
|
if (state.uiServer) {
|
|
@@ -642,28 +646,11 @@ export function updateStatusBar(state: McpExtensionState): void {
|
|
|
642
646
|
const entries = Object.entries(state.config.mcpServers);
|
|
643
647
|
const disabledCount = entries.filter(([, definition]) => isServerDisabled(definition)).length;
|
|
644
648
|
const enabledCount = entries.length - disabledCount;
|
|
645
|
-
if (entries.length === 0) {
|
|
646
|
-
ui.setStatus("mcp", undefined);
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
649
|
const connectedCount = [...state.manager.getAllConnections()].filter(([name, connection]) => {
|
|
650
650
|
const definition = state.config.mcpServers[name];
|
|
651
651
|
return connection.status === "connected" && definition !== undefined && !isServerDisabled(definition);
|
|
652
652
|
}).length;
|
|
653
|
-
const
|
|
654
|
-
if (footerStatus === "off") {
|
|
655
|
-
ui.setStatus("mcp", undefined);
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
let status = footerStatus === "compact"
|
|
660
|
-
? `MCP ${connectedCount}/${enabledCount}`
|
|
661
|
-
: `${enabledCount} ${enabledCount === 1 ? "server" : "servers"} enabled`;
|
|
662
|
-
if (footerStatus === "full") {
|
|
663
|
-
if (connectedCount > 0) status += ` (${connectedCount} connected)`;
|
|
664
|
-
if (disabledCount > 0) status += ` (${disabledCount} disabled)`;
|
|
665
|
-
}
|
|
666
|
-
const formattedStatus = footerStatus === "compact" ? status : formatMcpStatus(state.config, status);
|
|
653
|
+
const formattedStatus = formatMcpFooterStatus(state.config, enabledCount, disabledCount, connectedCount);
|
|
667
654
|
if (formattedStatus === undefined) {
|
|
668
655
|
ui.setStatus("mcp", undefined);
|
|
669
656
|
return;
|