@fitchmultz/pi-mcp-adapter 6.0.0 → 7.1.0
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 +33 -0
- package/OAUTH.md +1 -1
- package/README.md +27 -18
- package/dist/app-bridge.bundle.js +14 -14
- package/dist/config.js +41 -16
- package/dist/direct-tools.js +1 -1
- package/dist/gateway-arguments.d.ts +1 -1
- package/dist/index.js +38 -59
- package/dist/lifecycle.d.ts +2 -2
- package/dist/lifecycle.js +2 -2
- package/dist/mcp-auth-flow.js +2 -2
- package/dist/mcp-auth.d.ts +1 -1
- package/dist/mcp-auth.js +3 -3
- package/dist/mcp-code.d.ts +6 -6
- package/dist/mcp-code.js +11 -10
- package/dist/mcp-oauth-provider.d.ts +5 -5
- package/dist/mcp-oauth-provider.js +5 -5
- package/dist/mcp-output-guard.d.ts +1 -1
- package/dist/mcp-output-guard.js +1 -1
- package/dist/mcp-panel.d.ts +2 -2
- package/dist/mcp-panel.js +2 -2
- package/dist/mcp-setup-panel.d.ts +5 -4
- package/dist/mcp-setup-panel.js +15 -7
- package/dist/mcp-trace.d.ts +1 -1
- package/dist/mcp-trace.js +1 -1
- package/dist/npx-resolver.js +8 -4
- package/dist/proxy-modes.d.ts +2 -2
- package/dist/proxy-modes.js +20 -19
- package/dist/sampling-handler.js +1 -0
- package/dist/search-ranking.js +6 -2
- package/dist/server-manager.d.ts +2 -2
- package/dist/server-manager.js +44 -27
- package/dist/session-recovery.d.ts +7 -4
- package/dist/session-recovery.js +76 -10
- package/dist/tool-loader.d.ts +7 -2
- package/dist/tool-loader.js +56 -5
- package/dist/tool-metadata.d.ts +43 -43
- package/dist/tool-result-renderer.d.ts +3 -3
- package/dist/tool-result-renderer.js +3 -3
- package/dist/ui-resource-handler.d.ts +2 -2
- package/dist/ui-resource-handler.js +2 -2
- package/dist/ui-server.js +5 -4
- package/dist/ui-stream-types.d.ts +8 -8
- package/dist/unix-socket-transport.d.ts +1 -1
- package/dist/unix-socket-transport.js +1 -1
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +14 -0
- package/package.json +34 -98
- package/skills/mcp-scripting/SKILL.md +2 -0
- package/abort.ts +0 -34
- package/agent-dir.ts +0 -24
- package/app-bridge.bundle.js +0 -109
- package/banner.png +0 -0
- package/checkpoint.ts +0 -55
- package/commands.ts +0 -622
- package/config.ts +0 -1204
- package/consent-manager.ts +0 -25
- package/direct-tools.ts +0 -75
- package/elicitation-handler.ts +0 -349
- package/error-signal.ts +0 -21
- package/gateway-arguments.ts +0 -73
- package/glimpse-ui.ts +0 -80
- package/host-html-template.ts +0 -456
- package/index.ts +0 -767
- package/init.ts +0 -613
- package/legacy-migration.ts +0 -47
- package/lifecycle.ts +0 -165
- package/logger.ts +0 -127
- package/mcp-auth-flow.ts +0 -975
- package/mcp-auth.ts +0 -793
- package/mcp-callback-server.ts +0 -450
- package/mcp-code.ts +0 -412
- package/mcp-keyring-helper.cjs +0 -89
- package/mcp-oauth-provider.ts +0 -774
- package/mcp-output-guard.ts +0 -530
- package/mcp-panel.ts +0 -907
- package/mcp-script-worker.mjs +0 -131
- package/mcp-setup-panel.ts +0 -666
- package/mcp-status.ts +0 -110
- package/mcp-trace.ts +0 -295
- package/metadata-cache.ts +0 -271
- package/npx-resolver.ts +0 -488
- package/onboarding-state.ts +0 -58
- package/panel-keys.ts +0 -37
- package/prompts.ts +0 -337
- package/proxy-modes.ts +0 -1109
- package/resource-tools.ts +0 -52
- package/runtime-owner.ts +0 -123
- package/sampling-handler.ts +0 -276
- package/scripts/build-app-bridge.mjs +0 -15
- package/scripts/build.mjs +0 -132
- package/scripts/prepare.mjs +0 -103
- package/search-ranking.ts +0 -132
- package/server-manager.ts +0 -1320
- package/session-recovery.ts +0 -316
- package/state.ts +0 -64
- package/tool-approval.ts +0 -78
- package/tool-loader.ts +0 -153
- package/tool-metadata.ts +0 -240
- package/tool-registrar.ts +0 -74
- package/tool-result-renderer.ts +0 -294
- package/tsconfig.build.json +0 -13
- package/tsconfig.json +0 -16
- package/types.ts +0 -785
- package/ui-resource-handler.ts +0 -249
- package/ui-server.ts +0 -808
- package/ui-session.ts +0 -568
- package/ui-stream-types.ts +0 -89
- package/ui-tool-visibility.ts +0 -25
- package/unix-socket-transport.ts +0 -85
- package/utils.ts +0 -307
package/dist/session-recovery.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
// - treat generic -32000/ConnectionClosed errors as session expiry
|
|
22
22
|
// - treat AbortError/cancellation as a session failure
|
|
23
23
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
24
|
-
import { SdkHttpError, SdkErrorCode, ProtocolError, InsufficientScopeError, UnauthorizedError } from "@modelcontextprotocol/client";
|
|
24
|
+
import { SdkError, SdkHttpError, SdkErrorCode, ProtocolError, InsufficientScopeError, UnauthorizedError } from "@modelcontextprotocol/client";
|
|
25
25
|
import { logger } from "./logger.js";
|
|
26
26
|
import { abortable, throwIfAborted } from "./abort.js";
|
|
27
27
|
import { isServerDisabled, isNonInteractiveOAuth } from "./types.js";
|
|
@@ -30,18 +30,62 @@ const toolTransportFailures = new WeakSet();
|
|
|
30
30
|
export function isToolTransportFailure(error) {
|
|
31
31
|
return error instanceof Error && toolTransportFailures.has(error);
|
|
32
32
|
}
|
|
33
|
+
const interruptedToolCalls = new WeakSet();
|
|
34
|
+
export function isInterruptedToolCall(error) {
|
|
35
|
+
return error instanceof Error && interruptedToolCalls.has(error);
|
|
36
|
+
}
|
|
37
|
+
const toolCallOutcome = new AsyncLocalStorage();
|
|
38
|
+
/** Interrupted outcome reporting is separate from the transport retry policy. */
|
|
39
|
+
export async function trackToolCallOutcome(fn, signal) {
|
|
40
|
+
const outcome = { pending: false, cleanup: new Set() };
|
|
41
|
+
return toolCallOutcome.run(outcome, async () => {
|
|
42
|
+
try {
|
|
43
|
+
return await fn();
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (outcome.pending && error instanceof Error
|
|
47
|
+
&& (signal?.aborted || (error instanceof SdkError
|
|
48
|
+
&& (error.code === SdkErrorCode.RequestTimeout || error.code === SdkErrorCode.ConnectionClosed)))) {
|
|
49
|
+
// Abort reasons can be shared by calls that never reached dispatch.
|
|
50
|
+
const interrupted = new Error(error.message, { cause: error });
|
|
51
|
+
interruptedToolCalls.add(interrupted);
|
|
52
|
+
throw interrupted;
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
for (const cleanup of outcome.cleanup)
|
|
58
|
+
cleanup();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
33
62
|
const toolSend = new AsyncLocalStorage();
|
|
34
|
-
/** Retain fetch/body-error origin without parsing or buffering the
|
|
63
|
+
/** Retain fetch/body-error origin without parsing or buffering the response. */
|
|
35
64
|
export const trackToolTransportFailure = async (input, init) => {
|
|
36
65
|
const pending = toolSend.getStore();
|
|
37
66
|
if (pending)
|
|
38
67
|
pending.bodyFailure = undefined;
|
|
68
|
+
let toolPost = false;
|
|
69
|
+
if (pending && init?.method === "POST" && typeof init.body === "string") {
|
|
70
|
+
try {
|
|
71
|
+
const request = JSON.parse(init.body);
|
|
72
|
+
toolPost = request.method === "tools/call" && request.id === pending.id;
|
|
73
|
+
}
|
|
74
|
+
catch { }
|
|
75
|
+
}
|
|
76
|
+
if (toolPost && pending?.outcome)
|
|
77
|
+
pending.outcome.pending = true;
|
|
39
78
|
try {
|
|
40
79
|
const response = await fetch(input, init);
|
|
41
|
-
if (
|
|
42
|
-
|
|
80
|
+
if (toolPost && !response.ok && response.status < 500 && pending?.outcome)
|
|
81
|
+
pending.outcome.pending = false;
|
|
82
|
+
const mediaType = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
|
|
83
|
+
const observeBody = response.ok
|
|
84
|
+
? mediaType === "text/event-stream" || (toolPost && mediaType === "application/json")
|
|
85
|
+
: toolPost && response.status >= 500;
|
|
86
|
+
if (!pending || !response.body || !observeBody)
|
|
43
87
|
return response;
|
|
44
|
-
pending.streaming =
|
|
88
|
+
pending.streaming = response.ok && mediaType === "text/event-stream";
|
|
45
89
|
// The native end hook has no error argument. Only a failed body read proves loss.
|
|
46
90
|
const reader = response.body.getReader();
|
|
47
91
|
return new Response(new ReadableStream({
|
|
@@ -59,8 +103,11 @@ export const trackToolTransportFailure = async (input, init) => {
|
|
|
59
103
|
// Native intentional closure suppresses onRequestStreamEnd.
|
|
60
104
|
if (init?.signal?.aborted)
|
|
61
105
|
pending.finishSend();
|
|
62
|
-
else if (error instanceof Error)
|
|
106
|
+
else if (error instanceof Error) {
|
|
63
107
|
pending.bodyFailure = error;
|
|
108
|
+
if (!pending.streaming)
|
|
109
|
+
interruptedToolCalls.add(error);
|
|
110
|
+
}
|
|
64
111
|
controller.error(error);
|
|
65
112
|
reader.releaseLock();
|
|
66
113
|
}
|
|
@@ -77,12 +124,16 @@ export const trackToolTransportFailure = async (input, init) => {
|
|
|
77
124
|
}
|
|
78
125
|
};
|
|
79
126
|
/** Observe only the originating tool send, not OAuth or catalog requests. */
|
|
80
|
-
export function
|
|
127
|
+
export function trackToolTransport(transport, http = false) {
|
|
128
|
+
const requests = new Map();
|
|
81
129
|
const onmessage = transport.onmessage;
|
|
82
130
|
transport.onmessage = (message, extra) => {
|
|
83
|
-
const pending = toolSend.getStore();
|
|
131
|
+
const pending = ("id" in message && message.id !== undefined ? requests.get(message.id) : undefined) ?? toolSend.getStore();
|
|
84
132
|
if (pending && "id" in message && message.id === pending.id && ("result" in message || "error" in message)) {
|
|
85
133
|
pending.responseReceived = true;
|
|
134
|
+
if (pending.outcome)
|
|
135
|
+
pending.outcome.pending = false;
|
|
136
|
+
requests.delete(pending.id);
|
|
86
137
|
pending.finishSend();
|
|
87
138
|
}
|
|
88
139
|
onmessage?.(message, extra);
|
|
@@ -98,15 +149,24 @@ export function trackToolHttpFailures(transport) {
|
|
|
98
149
|
const send = transport.send.bind(transport);
|
|
99
150
|
transport.send = async (message, options) => {
|
|
100
151
|
try {
|
|
101
|
-
if (!("method" in message && message.method === "tools/call" && "id" in message
|
|
152
|
+
if (!("method" in message && message.method === "tools/call" && "id" in message)) {
|
|
102
153
|
return await toolSend.run(undefined, () => send(message, options));
|
|
103
154
|
}
|
|
104
155
|
const pending = {
|
|
105
|
-
id: message.id, streaming: false, responseReceived: false,
|
|
156
|
+
id: message.id, outcome: toolCallOutcome.getStore(), streaming: false, responseReceived: false,
|
|
106
157
|
bodyFailure: undefined, otherFailure: undefined,
|
|
107
158
|
finishSend: () => { },
|
|
108
159
|
};
|
|
160
|
+
if (pending.outcome) {
|
|
161
|
+
requests.set(message.id, pending);
|
|
162
|
+
pending.outcome.cleanup.add(() => requests.delete(message.id));
|
|
163
|
+
if (!http)
|
|
164
|
+
pending.outcome.pending = true;
|
|
165
|
+
}
|
|
109
166
|
try {
|
|
167
|
+
if (!options?.requestSignal) {
|
|
168
|
+
return await toolSend.run(pending, () => send(message, options));
|
|
169
|
+
}
|
|
110
170
|
await abortable(new Promise((resolve, reject) => {
|
|
111
171
|
pending.finishSend = resolve;
|
|
112
172
|
void toolSend.run(pending, () => send(message, {
|
|
@@ -126,6 +186,12 @@ export function trackToolHttpFailures(transport) {
|
|
|
126
186
|
resolve(); }, reject);
|
|
127
187
|
}), options.requestSignal);
|
|
128
188
|
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
// Local send failures are distinct from a sent request waiting for its reply.
|
|
191
|
+
if (!http && pending.outcome)
|
|
192
|
+
pending.outcome.pending = false;
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
129
195
|
finally {
|
|
130
196
|
pending.finishSend = () => { };
|
|
131
197
|
}
|
package/dist/tool-loader.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ type Reference = {
|
|
|
7
7
|
name: string;
|
|
8
8
|
namespace?: string;
|
|
9
9
|
};
|
|
10
|
+
export type Feature = "gateway" | "script";
|
|
11
|
+
export type FeaturePolicy = Partial<Record<Feature, {
|
|
12
|
+
eager?: boolean;
|
|
13
|
+
}>>;
|
|
10
14
|
type SearchDefinition<TParams extends TSchema, TDetails> = Omit<ToolDefinition<TParams, TDetails>, "execute"> & {
|
|
11
15
|
execute: (...args: Parameters<ToolDefinition<TParams, TDetails>["execute"]>) => Promise<AgentToolResult<TDetails> & {
|
|
12
16
|
tools: Reference[];
|
|
@@ -20,12 +24,13 @@ export type SearchAPI = ExtensionAPI & {
|
|
|
20
24
|
export declare function supportsNativeAsync(ctx: ExtensionContext): boolean;
|
|
21
25
|
export declare function createToolLoader(pi: SearchAPI, getState: () => McpExtensionState | null, getInitPromise: () => Promise<McpExtensionState> | null, beforeExecute?: McpAdapterOptions["beforeExecute"]): {
|
|
22
26
|
native: boolean;
|
|
27
|
+
registerFeature: <TParams extends TSchema, TDetails>(feature: Feature, definition: ToolDefinition<TParams, TDetails>) => void;
|
|
23
28
|
restore: (ctx: ExtensionContext) => void;
|
|
24
|
-
sync: (config: McpConfig, cache: MetadataCache | null, env?: string[] | null, asyncSupported?: boolean, registerGateways?: (pins: DirectToolSpec[]) =>
|
|
29
|
+
sync: (config: McpConfig, cache: MetadataCache | null, env?: string[] | null, asyncSupported?: boolean, registerGateways?: (pins: DirectToolSpec[]) => FeaturePolicy) => DirectToolSpec[];
|
|
25
30
|
activate: (matches: Array<{
|
|
26
31
|
server: string;
|
|
27
32
|
tool: ToolMetadata;
|
|
28
|
-
}
|
|
33
|
+
}>, enable?: Feature[]) => Reference[];
|
|
29
34
|
persist: () => void;
|
|
30
35
|
selectedServers: () => string[];
|
|
31
36
|
};
|
package/dist/tool-loader.js
CHANGED
|
@@ -5,6 +5,7 @@ import { createMcpDirectToolCallRenderer, renderMcpToolResult } from "./tool-res
|
|
|
5
5
|
import { isServerDisabled } from "./types.js";
|
|
6
6
|
import { normalizeDirectToolInputSchema } from "./utils.js";
|
|
7
7
|
import { isToolCallApprovalRequired } from "./tool-approval.js";
|
|
8
|
+
const featureRefs = { gateway: { name: "mcp" }, script: { name: "mcp_script" } };
|
|
8
9
|
const ENTRY = "mcp-tool-selection";
|
|
9
10
|
const key = (value) => JSON.stringify([value.server, value.tool]);
|
|
10
11
|
const refKey = (value) => JSON.stringify([value.namespace ?? null, value.name]);
|
|
@@ -23,6 +24,10 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
|
|
|
23
24
|
let available = new Set();
|
|
24
25
|
let selected = new Map();
|
|
25
26
|
let inactive = new Map();
|
|
27
|
+
let features = {};
|
|
28
|
+
let selectedFeatures = new Set();
|
|
29
|
+
let inactiveFeatures = new Set();
|
|
30
|
+
const registeredFeatures = new Set();
|
|
26
31
|
let restoring = true;
|
|
27
32
|
const active = () => native ? pi.getActiveToolReferences() : pi.getActiveTools().map(name => ({ name }));
|
|
28
33
|
const setActive = (refs) => {
|
|
@@ -34,8 +39,24 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
|
|
|
34
39
|
else
|
|
35
40
|
pi.setActiveTools(unique.map(ref => ref.name));
|
|
36
41
|
};
|
|
42
|
+
function registerFeature(feature, definition) {
|
|
43
|
+
pi.registerTool(definition);
|
|
44
|
+
registeredFeatures.add(feature);
|
|
45
|
+
}
|
|
37
46
|
function capture() {
|
|
38
47
|
const current = new Set(active().map(refKey));
|
|
48
|
+
for (const feature of Object.keys(features)) {
|
|
49
|
+
if (current.has(refKey(featureRefs[feature]))) {
|
|
50
|
+
if (!features[feature]?.eager)
|
|
51
|
+
selectedFeatures.add(feature);
|
|
52
|
+
inactiveFeatures.delete(feature);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
selectedFeatures.delete(feature);
|
|
56
|
+
if (features[feature]?.eager)
|
|
57
|
+
inactiveFeatures.add(feature);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
39
60
|
for (const [id, { selection, ref, pinned }] of registered) {
|
|
40
61
|
if (!available.has(id))
|
|
41
62
|
continue;
|
|
@@ -53,15 +74,19 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
|
|
|
53
74
|
}
|
|
54
75
|
function persist() {
|
|
55
76
|
capture();
|
|
56
|
-
pi.appendEntry(ENTRY, { selected: [...selected.values()], inactive: [...inactive.values()] });
|
|
77
|
+
pi.appendEntry(ENTRY, { selected: [...selected.values()], inactive: [...inactive.values()], features: [...selectedFeatures], inactiveFeatures: [...inactiveFeatures] });
|
|
57
78
|
}
|
|
58
79
|
function restore(ctx) {
|
|
59
80
|
selected = new Map();
|
|
60
81
|
inactive = new Map();
|
|
82
|
+
selectedFeatures = new Set();
|
|
83
|
+
inactiveFeatures = new Set();
|
|
61
84
|
for (const entry of ctx.sessionManager.getBranch()) {
|
|
62
85
|
if (entry.type !== "custom" || entry.customType !== ENTRY || !entry.data || typeof entry.data !== "object")
|
|
63
86
|
continue;
|
|
64
87
|
const data = entry.data;
|
|
88
|
+
selectedFeatures = new Set(Array.isArray(data.features) ? data.features.filter(value => value === "gateway" || value === "script") : []);
|
|
89
|
+
inactiveFeatures = new Set(Array.isArray(data.inactiveFeatures) ? data.inactiveFeatures.filter(value => value === "gateway" || value === "script") : []);
|
|
65
90
|
for (const [field, target] of [[data.selected, selected], [data.inactive, inactive]]) {
|
|
66
91
|
target.clear();
|
|
67
92
|
for (const selection of Array.isArray(field) ? field : []) {
|
|
@@ -125,28 +150,54 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
|
|
|
125
150
|
}
|
|
126
151
|
const permitted = new Set(pi.getAllTools().map(refKey));
|
|
127
152
|
const pins = specs.filter(item => item.pinned && available.has(key(item.selection)) && permitted.has(refKey(item.ref))).map(item => item.spec);
|
|
128
|
-
registerGateways?.(pins);
|
|
153
|
+
const policy = registerGateways?.(pins);
|
|
154
|
+
if (policy) {
|
|
155
|
+
const permittedFeatures = new Set(pi.getAllTools().map(refKey));
|
|
156
|
+
const loaderMissing = !permittedFeatures.has(refKey({ name: "mcp_search" }));
|
|
157
|
+
features = {};
|
|
158
|
+
for (const feature of Object.keys(featureRefs)) {
|
|
159
|
+
if (policy[feature] && permittedFeatures.has(refKey(featureRefs[feature]))) {
|
|
160
|
+
features[feature] = { eager: policy[feature].eager || loaderMissing };
|
|
161
|
+
if (selectedFeatures.has(feature) || (features[feature]?.eager && !inactiveFeatures.has(feature)))
|
|
162
|
+
enabled.push(featureRefs[feature]);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
selectedFeatures.delete(feature);
|
|
166
|
+
inactiveFeatures.delete(feature);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
129
170
|
// Official 0.87 reactivates allowlisted definitions on registration. Apply our loadout last.
|
|
130
171
|
const ownedRefs = new Set([...previousRefs, ...[...registered.values()].map(item => refKey(item.ref))]);
|
|
172
|
+
for (const feature of registeredFeatures)
|
|
173
|
+
ownedRefs.add(refKey(featureRefs[feature]));
|
|
131
174
|
setActive([...active().filter(ref => !ownedRefs.has(refKey(ref))), ...enabled]);
|
|
132
175
|
restoring = false;
|
|
133
176
|
return pins;
|
|
134
177
|
}
|
|
135
|
-
function activate(matches) {
|
|
178
|
+
function activate(matches, enable = []) {
|
|
136
179
|
const requested = matches.flatMap(({ server, tool }) => {
|
|
137
180
|
const id = key({ server, tool: tool.originalName });
|
|
138
181
|
const registration = available.has(id) ? registered.get(id) : undefined;
|
|
139
182
|
return registration ? [registration] : [];
|
|
140
183
|
});
|
|
141
184
|
// No await between reading and extending the current loadout: parallel searches union their selections.
|
|
142
|
-
|
|
185
|
+
const permitted = new Set(pi.getAllTools().map(refKey));
|
|
186
|
+
const requestedFeatures = [...new Set(enable)].filter(feature => features[feature] && permitted.has(refKey(featureRefs[feature])));
|
|
187
|
+
setActive([...active(), ...requested.map(item => item.ref), ...requestedFeatures.map(feature => featureRefs[feature])]);
|
|
143
188
|
const current = new Set(active().map(refKey));
|
|
144
189
|
const tools = requested.filter(item => current.has(refKey(item.ref))).map(({ selection, ref }) => {
|
|
145
190
|
selected.set(key(selection), selection);
|
|
146
191
|
return ref;
|
|
147
192
|
});
|
|
193
|
+
for (const feature of requestedFeatures) {
|
|
194
|
+
if (!current.has(refKey(featureRefs[feature])))
|
|
195
|
+
continue;
|
|
196
|
+
selectedFeatures.add(feature);
|
|
197
|
+
tools.push(featureRefs[feature]);
|
|
198
|
+
}
|
|
148
199
|
persist();
|
|
149
200
|
return tools;
|
|
150
201
|
}
|
|
151
|
-
return { native, restore, sync, activate, persist, selectedServers: () => [...new Set([...selected.values()].map(item => item.server))] };
|
|
202
|
+
return { native, registerFeature, restore, sync, activate, persist, selectedServers: () => [...new Set([...selected.values()].map(item => item.server))] };
|
|
152
203
|
}
|
package/dist/tool-metadata.d.ts
CHANGED
|
@@ -6,10 +6,20 @@ export declare function buildToolMetadata(tools: McpTool[], resources: McpResour
|
|
|
6
6
|
};
|
|
7
7
|
export declare function totalToolCount(state: McpExtensionState): number;
|
|
8
8
|
export declare function toToolDescriptor(server: string, tool: ToolMetadata): {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
outputSchema?: {
|
|
10
|
+
[x: string]: unknown;
|
|
11
|
+
$schema?: string | undefined;
|
|
12
|
+
} | undefined;
|
|
13
|
+
annotations?: {
|
|
14
|
+
title?: string | undefined;
|
|
15
|
+
readOnlyHint?: boolean | undefined;
|
|
16
|
+
destructiveHint?: boolean | undefined;
|
|
17
|
+
idempotentHint?: boolean | undefined;
|
|
18
|
+
openWorldHint?: boolean | undefined;
|
|
19
|
+
} | undefined;
|
|
20
|
+
execution?: {
|
|
21
|
+
taskSupport?: "forbidden" | "optional" | "required" | undefined;
|
|
22
|
+
} | undefined;
|
|
13
23
|
_meta?: {
|
|
14
24
|
[x: string]: unknown;
|
|
15
25
|
} | undefined;
|
|
@@ -17,55 +27,45 @@ export declare function toToolDescriptor(server: string, tool: ToolMetadata): {
|
|
|
17
27
|
src: string;
|
|
18
28
|
mimeType?: string | undefined;
|
|
19
29
|
sizes?: string[] | undefined;
|
|
20
|
-
theme?: "
|
|
30
|
+
theme?: "dark" | "light" | undefined;
|
|
21
31
|
}[] | undefined;
|
|
22
32
|
title?: string | undefined;
|
|
23
33
|
inputSchema?: {
|
|
24
34
|
[x: string]: unknown;
|
|
25
35
|
type: "object";
|
|
26
36
|
properties?: {
|
|
27
|
-
[x: string]: string | number | boolean | {
|
|
28
|
-
[x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
|
|
29
|
-
} | (string | number | boolean | {
|
|
30
|
-
[x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
|
|
31
|
-
} | (string | number | boolean | {
|
|
32
|
-
[x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
|
|
33
|
-
} | (string | number | boolean | {
|
|
34
|
-
[x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
|
|
35
|
-
} | (string | number | boolean | {
|
|
36
|
-
[x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
|
|
37
|
-
} | (string | number | boolean | {
|
|
38
|
-
[x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null)[] | null;
|
|
39
|
-
} | (string | number | boolean | {
|
|
40
|
-
[x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null)[] | null;
|
|
41
|
-
} | (string | number | boolean | {
|
|
42
|
-
[x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null)[] | null;
|
|
43
|
-
} | (string | number | boolean | {
|
|
44
|
-
[x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null)[] | null;
|
|
45
|
-
} | (string | number | boolean | {
|
|
46
|
-
[x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null)[] | null;
|
|
47
|
-
} | (string | number | boolean | {
|
|
48
|
-
[x: string]: string | number | boolean | /*elided*/ any | (string | number | boolean | /*elided*/ any | /*elided*/ any | null)[] | null;
|
|
49
|
-
} | (string | number | boolean | {
|
|
37
|
+
[x: string]: string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | (string | number | boolean | /*elided*/ any | {
|
|
50
38
|
[x: string]: string | number | boolean | /*elided*/ any | /*elided*/ any | null;
|
|
51
|
-
} |
|
|
39
|
+
} | null)[] | {
|
|
40
|
+
[x: string]: string | number | boolean | any | any | null;
|
|
41
|
+
} | null)[] | {
|
|
42
|
+
[x: string]: string | number | boolean | any | any | null;
|
|
43
|
+
} | null)[] | {
|
|
44
|
+
[x: string]: string | number | boolean | any | any | null;
|
|
45
|
+
} | null)[] | {
|
|
46
|
+
[x: string]: string | number | boolean | any | any | null;
|
|
47
|
+
} | null)[] | {
|
|
48
|
+
[x: string]: string | number | boolean | any | any | null;
|
|
49
|
+
} | null)[] | {
|
|
50
|
+
[x: string]: string | number | boolean | any | any | null;
|
|
51
|
+
} | null)[] | {
|
|
52
|
+
[x: string]: string | number | boolean | any | any | null;
|
|
53
|
+
} | null)[] | {
|
|
54
|
+
[x: string]: string | number | boolean | any | any | null;
|
|
55
|
+
} | null)[] | {
|
|
56
|
+
[x: string]: string | number | boolean | any | any | null;
|
|
57
|
+
} | null)[] | {
|
|
58
|
+
[x: string]: string | number | boolean | any | any | null;
|
|
59
|
+
} | null)[] | {
|
|
60
|
+
[x: string]: string | number | boolean | any | any | null;
|
|
61
|
+
} | null;
|
|
52
62
|
} | undefined;
|
|
53
63
|
required?: string[] | undefined;
|
|
54
64
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
annotations?: {
|
|
60
|
-
title?: string | undefined;
|
|
61
|
-
readOnlyHint?: boolean | undefined;
|
|
62
|
-
destructiveHint?: boolean | undefined;
|
|
63
|
-
idempotentHint?: boolean | undefined;
|
|
64
|
-
openWorldHint?: boolean | undefined;
|
|
65
|
-
} | undefined;
|
|
66
|
-
execution?: {
|
|
67
|
-
taskSupport?: "optional" | "required" | "forbidden" | undefined;
|
|
68
|
-
} | undefined;
|
|
65
|
+
description: string;
|
|
66
|
+
server: string;
|
|
67
|
+
path: string;
|
|
68
|
+
name: string;
|
|
69
69
|
};
|
|
70
70
|
export declare function catalogCoverage(state: McpExtensionState, server?: string): {
|
|
71
71
|
complete: boolean;
|
|
@@ -25,15 +25,15 @@ export interface McpToolResultDisplay {
|
|
|
25
25
|
truncated: boolean;
|
|
26
26
|
}
|
|
27
27
|
declare class CollapsibleText implements Component {
|
|
28
|
+
private readonly fullText;
|
|
29
|
+
private readonly footerText;
|
|
30
|
+
private collapsedText;
|
|
28
31
|
private readonly text;
|
|
29
32
|
private readonly expanded;
|
|
30
33
|
private readonly maxCollapsedLines;
|
|
31
34
|
private readonly ellipsis;
|
|
32
35
|
private readonly expandHint;
|
|
33
36
|
private readonly preTruncated;
|
|
34
|
-
private readonly fullText;
|
|
35
|
-
private readonly footerText;
|
|
36
|
-
private collapsedText;
|
|
37
37
|
constructor(text: string, expanded: boolean, maxCollapsedLines: number, ellipsis: string, expandHint: string, preTruncated?: boolean);
|
|
38
38
|
render(width: number): string[];
|
|
39
39
|
invalidate(): void;
|
|
@@ -9,15 +9,15 @@ const DEFAULT_MAX_COLLAPSED_LINES = 3;
|
|
|
9
9
|
const DEFAULT_MAX_COLLAPSED_CHARS = 8000;
|
|
10
10
|
const COLLAPSED_RENDER_CHAR_SLACK = 8;
|
|
11
11
|
class CollapsibleText {
|
|
12
|
+
fullText;
|
|
13
|
+
footerText;
|
|
14
|
+
collapsedText = null;
|
|
12
15
|
text;
|
|
13
16
|
expanded;
|
|
14
17
|
maxCollapsedLines;
|
|
15
18
|
ellipsis;
|
|
16
19
|
expandHint;
|
|
17
20
|
preTruncated;
|
|
18
|
-
fullText;
|
|
19
|
-
footerText;
|
|
20
|
-
collapsedText = null;
|
|
21
21
|
constructor(text, expanded, maxCollapsedLines, ellipsis, expandHint, preTruncated = false) {
|
|
22
22
|
this.text = text;
|
|
23
23
|
this.expanded = expanded;
|
|
@@ -7,10 +7,10 @@ interface ReadUiResourceOptions {
|
|
|
7
7
|
onNeedsAuth?: SessionRecoveryDeps["onNeedsAuth"];
|
|
8
8
|
}
|
|
9
9
|
export declare class UiResourceHandler {
|
|
10
|
+
private log;
|
|
10
11
|
private manager;
|
|
11
12
|
private config;
|
|
12
|
-
|
|
13
|
-
constructor(manager: McpServerManager, config?: McpConfig | undefined);
|
|
13
|
+
constructor(manager: McpServerManager, config?: McpConfig);
|
|
14
14
|
readUiResource(serverName: string, uri: string, options?: ReadUiResourceOptions): Promise<UiResourceContent>;
|
|
15
15
|
private getListResourceMeta;
|
|
16
16
|
}
|
|
@@ -4,10 +4,10 @@ import { logger } from "./logger.js";
|
|
|
4
4
|
import { SessionRecoveryAuthRequiredError, withSessionRecovery } from "./session-recovery.js";
|
|
5
5
|
import { isServerDisabled } from "./types.js";
|
|
6
6
|
export class UiResourceHandler {
|
|
7
|
+
log = logger.child({ component: "UiResourceHandler" });
|
|
7
8
|
manager;
|
|
8
9
|
config;
|
|
9
|
-
|
|
10
|
-
constructor(manager, config = undefined) {
|
|
10
|
+
constructor(manager, config) {
|
|
11
11
|
this.manager = manager;
|
|
12
12
|
this.config = config;
|
|
13
13
|
}
|
package/dist/ui-server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import http from "node:http";
|
|
1
|
+
import http, {} from "node:http";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
@@ -10,7 +10,7 @@ import { logger } from "./logger.js";
|
|
|
10
10
|
import { SessionRecoveryAuthRequiredError, withSessionRecovery } from "./session-recovery.js";
|
|
11
11
|
import { ensureToolCallApproved, isToolCallApprovalRequired } from "./tool-approval.js";
|
|
12
12
|
import { extractUiToolVisibility, isUiToolCallableByApp } from "./ui-tool-visibility.js";
|
|
13
|
-
import { createUiModelContextUpdate, extractUiPromptText, getVisualizationStreamEnvelope, isServerDisabled, } from "./types.js";
|
|
13
|
+
import { createUiModelContextUpdate, extractUiPromptText, getVisualizationStreamEnvelope, isServerDisabled, isToolAllowed, resolveToolPrefix, } from "./types.js";
|
|
14
14
|
const MAX_BODY_SIZE = 2 * 1024 * 1024;
|
|
15
15
|
const ABANDONED_GRACE_MS = 60_000;
|
|
16
16
|
const WATCHDOG_INTERVAL_MS = 5_000;
|
|
@@ -299,9 +299,10 @@ export async function startUiServer(options) {
|
|
|
299
299
|
sendJson(res, 503, { ok: false, error: `Server "${options.serverName}" is disabled` });
|
|
300
300
|
return;
|
|
301
301
|
}
|
|
302
|
+
const definition = options.config?.mcpServers[options.serverName] ?? connection.definition;
|
|
302
303
|
const toolDefinitions = Array.isArray(connection.tools) ? connection.tools : [];
|
|
303
304
|
const toolDefinition = toolDefinitions.find((tool) => tool.name === callParams.name);
|
|
304
|
-
if (!toolDefinition) {
|
|
305
|
+
if (!toolDefinition || !isToolAllowed(callParams.name, options.serverName, resolveToolPrefix(definition, options.config?.settings?.toolPrefix), definition?.includeTools, definition?.excludeTools)) {
|
|
305
306
|
sendJson(res, 403, { ok: false, error: `MCP tool "${callParams.name}" is not callable by apps` });
|
|
306
307
|
return;
|
|
307
308
|
}
|
|
@@ -480,7 +481,7 @@ export async function startUiServer(options) {
|
|
|
480
481
|
}
|
|
481
482
|
catch (error) {
|
|
482
483
|
if (error instanceof SessionRecoveryAuthRequiredError) {
|
|
483
|
-
const fallback = `Server "${options.serverName}" requires OAuth authentication. Run mcp({ action: "auth-start", server: "${options.serverName}" }) to get a browser URL, or /mcp-auth ${options.serverName} in an interactive local session.`;
|
|
484
|
+
const fallback = `Server "${options.serverName}" requires OAuth authentication. If mcp is inactive, first mcp_search({ enable: ["gateway"] }). Run mcp({ action: "auth-start", server: "${options.serverName}" }) to get a browser URL, or /mcp-auth ${options.serverName} in an interactive local session.`;
|
|
484
485
|
const message = error.authMessage ?? (options.config
|
|
485
486
|
? formatAuthRequiredMessage(options.config, options.serverName, fallback)
|
|
486
487
|
: fallback);
|
|
@@ -10,17 +10,17 @@ export declare const uiStreamModeSchema: z.ZodEnum<{
|
|
|
10
10
|
}>;
|
|
11
11
|
export type UiStreamMode = z.infer<typeof uiStreamModeSchema>;
|
|
12
12
|
export declare const visualizationStreamPhaseSchema: z.ZodEnum<{
|
|
13
|
-
shell: "shell";
|
|
14
|
-
narrative: "narrative";
|
|
15
|
-
structure: "structure";
|
|
16
13
|
detail: "detail";
|
|
14
|
+
narrative: "narrative";
|
|
17
15
|
settled: "settled";
|
|
16
|
+
shell: "shell";
|
|
17
|
+
structure: "structure";
|
|
18
18
|
}>;
|
|
19
19
|
export type VisualizationStreamPhase = z.infer<typeof visualizationStreamPhaseSchema>;
|
|
20
20
|
export declare const visualizationStreamFrameTypeSchema: z.ZodEnum<{
|
|
21
|
-
patch: "patch";
|
|
22
21
|
checkpoint: "checkpoint";
|
|
23
22
|
final: "final";
|
|
23
|
+
patch: "patch";
|
|
24
24
|
}>;
|
|
25
25
|
export type VisualizationStreamFrameType = z.infer<typeof visualizationStreamFrameTypeSchema>;
|
|
26
26
|
export declare const visualizationStreamStatusSchema: z.ZodEnum<{
|
|
@@ -42,16 +42,16 @@ export declare const visualizationStreamEnvelopeSchema: z.ZodObject<{
|
|
|
42
42
|
streamId: z.ZodString;
|
|
43
43
|
sequence: z.ZodNumber;
|
|
44
44
|
frameType: z.ZodEnum<{
|
|
45
|
-
patch: "patch";
|
|
46
45
|
checkpoint: "checkpoint";
|
|
47
46
|
final: "final";
|
|
47
|
+
patch: "patch";
|
|
48
48
|
}>;
|
|
49
49
|
phase: z.ZodEnum<{
|
|
50
|
-
shell: "shell";
|
|
51
|
-
narrative: "narrative";
|
|
52
|
-
structure: "structure";
|
|
53
50
|
detail: "detail";
|
|
51
|
+
narrative: "narrative";
|
|
54
52
|
settled: "settled";
|
|
53
|
+
shell: "shell";
|
|
54
|
+
structure: "structure";
|
|
55
55
|
}>;
|
|
56
56
|
status: z.ZodEnum<{
|
|
57
57
|
error: "error";
|
|
@@ -2,12 +2,12 @@ import type { Transport } from "@modelcontextprotocol/client";
|
|
|
2
2
|
import type { JSONRPCMessage } from "@modelcontextprotocol/client";
|
|
3
3
|
/** MCP JSONL transport for an explicitly configured Unix-domain socket. */
|
|
4
4
|
export declare class UnixSocketClientTransport implements Transport {
|
|
5
|
-
private readonly socketPath;
|
|
6
5
|
private socket;
|
|
7
6
|
private readonly readBuffer;
|
|
8
7
|
onclose?: () => void;
|
|
9
8
|
onerror?: (error: Error) => void;
|
|
10
9
|
onmessage?: (message: JSONRPCMessage) => void;
|
|
10
|
+
private readonly socketPath;
|
|
11
11
|
constructor(socketPath: string);
|
|
12
12
|
start(): Promise<void>;
|
|
13
13
|
close(): Promise<void>;
|
|
@@ -2,12 +2,12 @@ import { createConnection } from "node:net";
|
|
|
2
2
|
import { ReadBuffer, serializeMessage } from "@modelcontextprotocol/client";
|
|
3
3
|
/** MCP JSONL transport for an explicitly configured Unix-domain socket. */
|
|
4
4
|
export class UnixSocketClientTransport {
|
|
5
|
-
socketPath;
|
|
6
5
|
socket;
|
|
7
6
|
readBuffer = new ReadBuffer();
|
|
8
7
|
onclose;
|
|
9
8
|
onerror;
|
|
10
9
|
onmessage;
|
|
10
|
+
socketPath;
|
|
11
11
|
constructor(socketPath) {
|
|
12
12
|
this.socketPath = socketPath;
|
|
13
13
|
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { McpConfig, ServerEntry } from "./types.ts";
|
|
3
|
+
export declare const ADAPTER_VERSION: string;
|
|
3
4
|
export declare function normalizeRequestTimeoutMs(timeoutMs: number | undefined): number | undefined;
|
|
4
5
|
export declare function openUrl(pi: ExtensionAPI, url: string, browser?: string, signal?: AbortSignal): Promise<void>;
|
|
5
6
|
export declare function openPath(pi: ExtensionAPI, targetPath: string): Promise<void>;
|
package/dist/utils.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
2
3
|
import { homedir, platform } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
5
|
+
function readAdapterVersion() {
|
|
6
|
+
// Source modules sit beside package.json; compiled modules sit one level down in dist/.
|
|
7
|
+
for (const path of ["./package.json", "../package.json"]) {
|
|
8
|
+
try {
|
|
9
|
+
const manifest = JSON.parse(readFileSync(new URL(path, import.meta.url), "utf8"));
|
|
10
|
+
if (manifest.name === "@fitchmultz/pi-mcp-adapter" && typeof manifest.version === "string")
|
|
11
|
+
return manifest.version;
|
|
12
|
+
}
|
|
13
|
+
catch { }
|
|
14
|
+
}
|
|
15
|
+
return "0.0.0";
|
|
16
|
+
}
|
|
17
|
+
export const ADAPTER_VERSION = readAdapterVersion();
|
|
4
18
|
export function normalizeRequestTimeoutMs(timeoutMs) {
|
|
5
19
|
return typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0
|
|
6
20
|
? timeoutMs
|