@fitchmultz/pi-mcp-adapter 8.0.5 → 8.0.7
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 +140 -7
- package/OAUTH.md +13 -16
- package/README.md +8 -8
- package/cli.js +67 -28
- package/dist/abort.js +18 -9
- package/dist/client-operations.js +4 -2
- package/dist/commands.js +77 -43
- package/dist/config.js +194 -105
- package/dist/connection-runtime.d.ts +2 -2
- package/dist/connection-runtime.js +29 -18
- package/dist/consent-manager.d.ts +2 -2
- package/dist/consent-manager.js +5 -2
- package/dist/direct-tools.d.ts +1 -1
- package/dist/direct-tools.js +13 -7
- package/dist/elicitation-handler.d.ts +2 -3
- package/dist/elicitation-handler.js +113 -58
- package/dist/gateway-arguments.d.ts +1 -1
- package/dist/gateway-arguments.js +90 -23
- package/dist/glimpse-ui.js +10 -5
- package/dist/host-html-template.d.ts +3 -1
- package/dist/host-html-template.js +9 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.js +264 -119
- package/dist/init.d.ts +1 -3
- package/dist/init.js +91 -51
- package/dist/legacy-migration.d.ts +1 -1
- package/dist/legacy-migration.js +17 -12
- package/dist/lifecycle.d.ts +3 -3
- package/dist/lifecycle.js +33 -17
- package/dist/logger.d.ts +2 -2
- package/dist/logger.js +4 -2
- package/dist/mcp-auth-flow.js +150 -58
- package/dist/mcp-auth.d.ts +7 -7
- package/dist/mcp-auth.js +189 -138
- package/dist/mcp-callback-server.js +19 -8
- package/dist/mcp-code.js +168 -54
- package/dist/mcp-keyring-helper.cjs +57 -23
- package/dist/mcp-oauth-provider.d.ts +6 -7
- package/dist/mcp-oauth-provider.js +146 -72
- package/dist/mcp-oauth-runtime.js +29 -16
- package/dist/mcp-output-guard.js +193 -65
- package/dist/mcp-output-preview.js +41 -20
- package/dist/mcp-panel.d.ts +9 -9
- package/dist/mcp-panel.js +141 -70
- package/dist/mcp-script-worker.mjs +135 -43
- package/dist/mcp-setup-panel.d.ts +8 -8
- package/dist/mcp-setup-panel.js +130 -44
- package/dist/mcp-status.js +14 -7
- package/dist/mcp-trace.d.ts +10 -5
- package/dist/mcp-trace.js +127 -61
- package/dist/metadata-cache.d.ts +3 -4
- package/dist/metadata-cache.js +83 -43
- package/dist/npx-resolver.js +101 -60
- package/dist/onboarding-state.js +4 -2
- package/dist/plain-result-reader.js +78 -31
- package/dist/prompts.js +36 -20
- package/dist/proxy-modes.js +670 -193
- package/dist/resource-tools.js +30 -9
- package/dist/runtime-owner.js +36 -18
- package/dist/sampling-handler.d.ts +1 -2
- package/dist/sampling-handler.js +41 -21
- package/dist/search-ranking.d.ts +1 -1
- package/dist/search-ranking.js +67 -34
- package/dist/server-manager.d.ts +11 -11
- package/dist/server-manager.js +283 -166
- package/dist/session-recovery.js +113 -61
- package/dist/tool-approval.js +9 -3
- package/dist/tool-loader.js +154 -55
- package/dist/tool-metadata.d.ts +1 -1
- package/dist/tool-metadata.js +51 -23
- package/dist/tool-registrar.js +32 -10
- package/dist/tool-result-renderer.d.ts +3 -3
- package/dist/tool-result-renderer.js +65 -44
- package/dist/types.d.ts +2 -2
- package/dist/types.js +39 -18
- package/dist/ui-message-history.js +33 -13
- package/dist/ui-resource-handler.d.ts +3 -3
- package/dist/ui-resource-handler.js +37 -21
- package/dist/ui-server.js +101 -43
- package/dist/ui-session.js +66 -29
- package/dist/ui-stream-types.js +14 -4
- package/dist/ui-tool-visibility.js +12 -6
- package/dist/unix-socket-transport.d.ts +1 -2
- package/dist/unix-socket-transport.js +19 -12
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +60 -35
- package/package.json +29 -5
- package/skills/mcp-scripting/SKILL.md +6 -6
package/dist/resource-tools.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isServerDisabled, isToolAllowed, resolveToolPrefix } from "./types.js";
|
|
1
|
+
import { isServerDisabled, isToolAllowed, resolveToolPrefix, } from "./types.js";
|
|
2
2
|
import { executeCall, prepareMcpConnection } from "./proxy-modes.js";
|
|
3
3
|
import { paginate } from "./search-ranking.js";
|
|
4
4
|
import { throwIfAborted } from "./abort.js";
|
|
@@ -7,20 +7,41 @@ export async function executeResourceList(state, server, limit = 12, offset = 0,
|
|
|
7
7
|
throwIfAborted(combineAbortSignals(state.owner?.signal, signal));
|
|
8
8
|
const definition = state.config.mcpServers[server];
|
|
9
9
|
if (!definition || isServerDisabled(definition)) {
|
|
10
|
-
return {
|
|
10
|
+
return {
|
|
11
|
+
content: [
|
|
12
|
+
{
|
|
13
|
+
type: "text",
|
|
14
|
+
text: `Server "${server}" is ${definition ? "disabled" : "not configured"}.`,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
details: {
|
|
18
|
+
mode: "resources",
|
|
19
|
+
server,
|
|
20
|
+
error: definition ? "server_disabled" : "server_not_found",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
11
23
|
}
|
|
12
|
-
if (!state.toolMetadata.has(server) ||
|
|
24
|
+
if (!state.toolMetadata.has(server) ||
|
|
25
|
+
state.manager.getConnection(server)?.status === "connected") {
|
|
13
26
|
const prepared = await prepareMcpConnection(state, server, signal);
|
|
14
|
-
if (prepared.error)
|
|
27
|
+
if (prepared.error) {
|
|
15
28
|
return prepared.error;
|
|
29
|
+
}
|
|
16
30
|
}
|
|
17
31
|
const prefix = resolveToolPrefix(definition, state.config.settings?.toolPrefix);
|
|
18
32
|
const page = paginate((state.toolMetadata.get(server) ?? [])
|
|
19
|
-
.filter(tool => tool.resourceUri !== undefined &&
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
.filter((tool) => tool.resourceUri !== undefined &&
|
|
34
|
+
definition.exposeResources !== false &&
|
|
35
|
+
isToolAllowed(tool.originalName, server, prefix, definition.includeTools, definition.excludeTools))
|
|
36
|
+
.map((tool) => tool.resourceDescriptor ?? { uri: tool.resourceUri, description: tool.description }), offset, limit);
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: "text", text: JSON.stringify({ server, ...page }, null, 2) }],
|
|
39
|
+
details: { mode: "resources", server, ...page },
|
|
40
|
+
};
|
|
23
41
|
}
|
|
24
42
|
export async function executeResourceRead(state, server, uri, signal, identity = {}, beforeDispatch, raw = false) {
|
|
25
|
-
return executeCall(state, uri, {}, server, undefined, signal, identity, beforeDispatch, {
|
|
43
|
+
return executeCall(state, uri, {}, server, undefined, signal, identity, beforeDispatch, {
|
|
44
|
+
resourceUri: uri,
|
|
45
|
+
raw,
|
|
46
|
+
});
|
|
26
47
|
}
|
package/dist/runtime-owner.js
CHANGED
|
@@ -7,11 +7,13 @@ export function createMcpRuntimeOwner() {
|
|
|
7
7
|
// promises through settlement, without cancelling, serializing, or replaying callback work.
|
|
8
8
|
const callbacks = { beforeExecute: 0, onToolCall: 0 };
|
|
9
9
|
const pendingCallback = () => {
|
|
10
|
-
if (callbacks.beforeExecute)
|
|
10
|
+
if (callbacks.beforeExecute) {
|
|
11
11
|
return "MCP beforeExecute callback is pending";
|
|
12
|
-
|
|
12
|
+
}
|
|
13
|
+
if (callbacks.onToolCall) {
|
|
13
14
|
return "MCP onToolCall callback is pending";
|
|
14
|
-
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
15
17
|
};
|
|
16
18
|
const reportCleanupFailure = (error, late) => {
|
|
17
19
|
console.error(`MCP: ${late ? "late " : ""}runtime cleanup failed: ${formatTerminalError(error)}`);
|
|
@@ -30,23 +32,30 @@ export function createMcpRuntimeOwner() {
|
|
|
30
32
|
}
|
|
31
33
|
},
|
|
32
34
|
isActive: () => !controller.signal.aborted,
|
|
33
|
-
addCleanup: cleanup => {
|
|
35
|
+
addCleanup: (cleanup) => {
|
|
34
36
|
if (controller.signal.aborted) {
|
|
35
|
-
void Promise.resolve()
|
|
37
|
+
void Promise.resolve()
|
|
38
|
+
.then(cleanup)
|
|
39
|
+
.catch((error) => reportCleanupFailure(error, true));
|
|
36
40
|
return;
|
|
37
41
|
}
|
|
38
42
|
cleanups.push(cleanup);
|
|
39
43
|
},
|
|
40
44
|
stop: (reason = "MCP extension runtime stopped") => {
|
|
41
|
-
if (stopPromise)
|
|
45
|
+
if (stopPromise) {
|
|
42
46
|
return stopPromise;
|
|
47
|
+
}
|
|
43
48
|
controller.abort(new Error(reason));
|
|
44
|
-
const pendingCleanups = cleanups
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
const pendingCleanups = cleanups
|
|
50
|
+
.splice(0)
|
|
51
|
+
.reverse()
|
|
52
|
+
.map((cleanup) => Promise.resolve().then(cleanup));
|
|
53
|
+
stopPromise = Promise.allSettled(pendingCleanups).then((results) => {
|
|
54
|
+
const failures = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
47
55
|
const callback = pendingCallback();
|
|
48
|
-
if (callback)
|
|
56
|
+
if (callback) {
|
|
49
57
|
failures.push(new Error(callback));
|
|
58
|
+
}
|
|
50
59
|
if (failures.length > 0) {
|
|
51
60
|
const aggregate = new AggregateError(failures, "MCP runtime cleanup failed");
|
|
52
61
|
console.error(`MCP: runtime cleanup failed: ${formatTerminalError(aggregate)}`);
|
|
@@ -55,15 +64,19 @@ export function createMcpRuntimeOwner() {
|
|
|
55
64
|
});
|
|
56
65
|
return stopPromise;
|
|
57
66
|
},
|
|
58
|
-
throwIfInactive: () => {
|
|
67
|
+
throwIfInactive: () => {
|
|
68
|
+
controller.signal.throwIfAborted();
|
|
69
|
+
},
|
|
59
70
|
};
|
|
60
71
|
}
|
|
61
72
|
export function combineAbortSignals(...signals) {
|
|
62
73
|
const active = signals.filter((signal) => signal !== undefined);
|
|
63
|
-
if (active.length === 0)
|
|
74
|
+
if (active.length === 0) {
|
|
64
75
|
return undefined;
|
|
65
|
-
|
|
76
|
+
}
|
|
77
|
+
if (active.length === 1) {
|
|
66
78
|
return active[0];
|
|
79
|
+
}
|
|
67
80
|
return AbortSignal.any(active);
|
|
68
81
|
}
|
|
69
82
|
/** Fence session-bound UI calls after the owning extension runtime stops. */
|
|
@@ -75,17 +88,20 @@ export function createOwnedUi(ui, owner) {
|
|
|
75
88
|
}
|
|
76
89
|
const object = value;
|
|
77
90
|
const existing = proxies.get(object);
|
|
78
|
-
if (existing)
|
|
91
|
+
if (existing) {
|
|
79
92
|
return existing;
|
|
93
|
+
}
|
|
80
94
|
const proxy = new Proxy(object, {
|
|
81
95
|
get(target, property, receiver) {
|
|
82
|
-
if (!owner.isActive())
|
|
96
|
+
if (!owner.isActive()) {
|
|
83
97
|
return undefined;
|
|
98
|
+
}
|
|
84
99
|
const member = Reflect.get(target, property, receiver);
|
|
85
100
|
if (typeof member === "function") {
|
|
86
101
|
return (...args) => {
|
|
87
|
-
if (!owner.isActive())
|
|
102
|
+
if (!owner.isActive()) {
|
|
88
103
|
return undefined;
|
|
104
|
+
}
|
|
89
105
|
return Reflect.apply(member, target, args);
|
|
90
106
|
};
|
|
91
107
|
}
|
|
@@ -98,7 +114,9 @@ export function createOwnedUi(ui, owner) {
|
|
|
98
114
|
return wrap(ui);
|
|
99
115
|
}
|
|
100
116
|
export function isAbortError(error, signal) {
|
|
101
|
-
if (signal?.aborted)
|
|
117
|
+
if (signal?.aborted) {
|
|
102
118
|
return true;
|
|
103
|
-
|
|
119
|
+
}
|
|
120
|
+
return (error instanceof Error &&
|
|
121
|
+
(error.name === "AbortError" || error.message === "MCP extension runtime stopped"));
|
|
104
122
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { ExtensionUIContext, ModelRegistry } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import type
|
|
4
|
-
import { type CreateMessageRequest, type CreateMessageResult } from "@modelcontextprotocol/client";
|
|
3
|
+
import { type Client, type CreateMessageRequest, type CreateMessageResult } from "@modelcontextprotocol/client";
|
|
5
4
|
export interface SamplingHandlerOptions {
|
|
6
5
|
serverName: string;
|
|
7
6
|
autoApprove: boolean;
|
package/dist/sampling-handler.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { truncateAtWord } from "./utils.js";
|
|
2
2
|
import { throwIfAborted } from "./abort.js";
|
|
3
3
|
import {} from "@modelcontextprotocol/client";
|
|
4
|
-
export function registerSamplingHandler(client, options, own = run => run()) {
|
|
4
|
+
export function registerSamplingHandler(client, options, own = (run) => run()) {
|
|
5
5
|
// Own the handler's real promise, not the SDK caller's cancellable response facade.
|
|
6
|
-
client.setRequestHandler("sampling/createMessage", request => own(() => handleSamplingRequest(options, request)));
|
|
6
|
+
client.setRequestHandler("sampling/createMessage", (request) => own(() => handleSamplingRequest(options, request)));
|
|
7
7
|
}
|
|
8
8
|
export async function handleSamplingRequest(options, request) {
|
|
9
9
|
const params = request.params;
|
|
@@ -31,13 +31,16 @@ export async function handleSamplingRequest(options, request) {
|
|
|
31
31
|
throwIfAborted(signal);
|
|
32
32
|
const supportsTemperature = model.compat && "supportsTemperature" in model.compat
|
|
33
33
|
? model.compat.supportsTemperature !== false
|
|
34
|
-
: !(model.id === "gpt-6-astra" &&
|
|
34
|
+
: !(model.id === "gpt-6-astra" &&
|
|
35
|
+
(model.api === "openai-responses" || model.api === "openai-codex-responses"));
|
|
35
36
|
const result = await options.modelRegistry.complete(model, {
|
|
36
37
|
...(params.systemPrompt !== undefined ? { systemPrompt: params.systemPrompt } : {}),
|
|
37
38
|
messages,
|
|
38
39
|
}, {
|
|
39
40
|
maxTokens: params.maxTokens,
|
|
40
|
-
...(supportsTemperature && params.temperature !== undefined
|
|
41
|
+
...(supportsTemperature && params.temperature !== undefined
|
|
42
|
+
? { temperature: params.temperature }
|
|
43
|
+
: {}),
|
|
41
44
|
...(params.metadata !== undefined ? { metadata: params.metadata } : {}),
|
|
42
45
|
...(signal ? { signal } : {}),
|
|
43
46
|
});
|
|
@@ -47,7 +50,9 @@ export async function handleSamplingRequest(options, request) {
|
|
|
47
50
|
return converted;
|
|
48
51
|
}
|
|
49
52
|
function formatRequestApproval(serverName, modelName, systemPrompt, messages) {
|
|
50
|
-
const lines = [
|
|
53
|
+
const lines = [
|
|
54
|
+
`${serverName} wants to sample ${messages.length} message${messages.length === 1 ? "" : "s"} with ${modelName}.`,
|
|
55
|
+
];
|
|
51
56
|
if (systemPrompt) {
|
|
52
57
|
lines.push(`System: ${truncateAtWord(systemPrompt, 400)}`);
|
|
53
58
|
}
|
|
@@ -61,27 +66,35 @@ function formatResponseApproval(serverName, response) {
|
|
|
61
66
|
return `${serverName} will receive this response from ${response.model}:\n\n${truncateAtWord(text, 1000)}`;
|
|
62
67
|
}
|
|
63
68
|
function messageText(message) {
|
|
64
|
-
if (typeof message.content === "string")
|
|
69
|
+
if (typeof message.content === "string") {
|
|
65
70
|
return message.content;
|
|
66
|
-
|
|
67
|
-
|
|
71
|
+
}
|
|
72
|
+
return message.content
|
|
73
|
+
.map((block) => {
|
|
74
|
+
if (block.type === "text") {
|
|
68
75
|
return block.text;
|
|
69
|
-
|
|
76
|
+
}
|
|
77
|
+
if (block.type === "image") {
|
|
70
78
|
return `[image: ${block.mimeType}]`;
|
|
71
|
-
|
|
79
|
+
}
|
|
80
|
+
if (block.type === "thinking") {
|
|
72
81
|
return "[thinking]";
|
|
73
|
-
|
|
82
|
+
}
|
|
83
|
+
if (block.type === "toolCall") {
|
|
74
84
|
return `[tool call: ${block.name}]`;
|
|
85
|
+
}
|
|
75
86
|
return "[content]";
|
|
76
|
-
})
|
|
87
|
+
})
|
|
88
|
+
.join("\n");
|
|
77
89
|
}
|
|
78
90
|
async function resolveSamplingModel(options, modelPreferences) {
|
|
79
91
|
const candidates = [];
|
|
80
92
|
const availableModels = options.modelRegistry.getAvailable();
|
|
81
93
|
for (const hint of modelPreferences?.hints ?? []) {
|
|
82
94
|
const normalizedHint = hint.name?.trim().toLowerCase();
|
|
83
|
-
if (!normalizedHint)
|
|
95
|
+
if (!normalizedHint) {
|
|
84
96
|
continue;
|
|
97
|
+
}
|
|
85
98
|
for (const model of availableModels) {
|
|
86
99
|
const searchableNames = [`${model.provider}/${model.id}`, model.id, model.name];
|
|
87
100
|
if (searchableNames.some((name) => name.toLowerCase().includes(normalizedHint))) {
|
|
@@ -90,8 +103,9 @@ async function resolveSamplingModel(options, modelPreferences) {
|
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
const currentModel = options.getCurrentModel();
|
|
93
|
-
if (currentModel)
|
|
106
|
+
if (currentModel) {
|
|
94
107
|
addSamplingCandidate(candidates, currentModel);
|
|
108
|
+
}
|
|
95
109
|
for (const model of availableModels) {
|
|
96
110
|
addSamplingCandidate(candidates, model);
|
|
97
111
|
}
|
|
@@ -101,7 +115,7 @@ async function resolveSamplingModel(options, modelPreferences) {
|
|
|
101
115
|
throwIfAborted(signal);
|
|
102
116
|
const auth = await options.modelRegistry.getApiKeyAndHeaders(model);
|
|
103
117
|
throwIfAborted(signal);
|
|
104
|
-
if (auth.ok
|
|
118
|
+
if (!auth.ok) {
|
|
105
119
|
errors.push(`${model.provider}/${model.id}: ${auth.error}`);
|
|
106
120
|
continue;
|
|
107
121
|
}
|
|
@@ -118,8 +132,9 @@ function addSamplingCandidate(candidates, model) {
|
|
|
118
132
|
}
|
|
119
133
|
}
|
|
120
134
|
async function confirmSampling(options, title, message) {
|
|
121
|
-
if (options.autoApprove)
|
|
135
|
+
if (options.autoApprove) {
|
|
122
136
|
return;
|
|
137
|
+
}
|
|
123
138
|
if (!options.ui) {
|
|
124
139
|
throw new Error("MCP sampling requires interactive approval. Set settings.samplingAutoApprove to true to allow it without UI.");
|
|
125
140
|
}
|
|
@@ -169,10 +184,12 @@ function convertAssistantResult(message) {
|
|
|
169
184
|
}
|
|
170
185
|
const text = message.content
|
|
171
186
|
.map((block) => {
|
|
172
|
-
if (block.type === "text")
|
|
187
|
+
if (block.type === "text") {
|
|
173
188
|
return block.text;
|
|
174
|
-
|
|
189
|
+
}
|
|
190
|
+
if (block.type === "thinking") {
|
|
175
191
|
return undefined;
|
|
192
|
+
}
|
|
176
193
|
throw new Error(`MCP sampling result ${block.type} content is not supported`);
|
|
177
194
|
})
|
|
178
195
|
.filter((value) => value !== undefined)
|
|
@@ -189,12 +206,15 @@ function convertAssistantResult(message) {
|
|
|
189
206
|
};
|
|
190
207
|
}
|
|
191
208
|
function mapStopReason(reason) {
|
|
192
|
-
if (reason === "stop")
|
|
209
|
+
if (reason === "stop") {
|
|
193
210
|
return "endTurn";
|
|
194
|
-
|
|
211
|
+
}
|
|
212
|
+
if (reason === "length") {
|
|
195
213
|
return "maxTokens";
|
|
196
|
-
|
|
214
|
+
}
|
|
215
|
+
if (reason === "toolUse") {
|
|
197
216
|
return "toolUse";
|
|
217
|
+
}
|
|
198
218
|
return reason;
|
|
199
219
|
}
|
|
200
220
|
function zeroUsage() {
|
package/dist/search-ranking.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { McpExtensionState } from "./state.ts";
|
|
2
|
-
import type
|
|
2
|
+
import { type ToolMetadata } from "./types.ts";
|
|
3
3
|
export declare const MAX_PAGE_SIZE = 100;
|
|
4
4
|
export declare const MAX_TOOL_NAME_LENGTH = 512;
|
|
5
5
|
export interface RankedToolMatch {
|
package/dist/search-ranking.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import MiniSearch, {} from "minisearch";
|
|
2
|
-
import { getServerPrefix, isServerDisabled, isToolAllowed, resolveToolPrefix } from "./types.js";
|
|
2
|
+
import { getServerPrefix, isServerDisabled, isToolAllowed, resolveToolPrefix, } from "./types.js";
|
|
3
3
|
export const MAX_PAGE_SIZE = 100;
|
|
4
4
|
export const MAX_TOOL_NAME_LENGTH = 512;
|
|
5
5
|
export function normalizeSearchText(value) {
|
|
@@ -9,40 +9,56 @@ export function normalizeSearchText(value) {
|
|
|
9
9
|
.toLowerCase();
|
|
10
10
|
}
|
|
11
11
|
function schemaSearchText(schema) {
|
|
12
|
-
if (!schema || typeof schema !== "object")
|
|
12
|
+
if (!schema || typeof schema !== "object") {
|
|
13
13
|
return "";
|
|
14
|
-
|
|
14
|
+
}
|
|
15
|
+
if (Array.isArray(schema)) {
|
|
15
16
|
return schema.map(schemaSearchText).join(" ");
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
}
|
|
18
|
+
return Object.entries(schema)
|
|
19
|
+
.flatMap(([key, value]) => {
|
|
20
|
+
if ((key === "description" || key === "title" || key === "const") &&
|
|
21
|
+
typeof value === "string") {
|
|
18
22
|
return value;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
}
|
|
24
|
+
if (key === "enum" && Array.isArray(value)) {
|
|
25
|
+
return value.filter((item) => typeof item === "string");
|
|
26
|
+
}
|
|
27
|
+
if (key === "properties" && value && typeof value === "object") {
|
|
22
28
|
return [Object.keys(value).join(" "), schemaSearchText(value)];
|
|
29
|
+
}
|
|
23
30
|
return schemaSearchText(value);
|
|
24
|
-
})
|
|
31
|
+
})
|
|
32
|
+
.join(" ");
|
|
25
33
|
}
|
|
26
34
|
const indexes = new WeakMap();
|
|
27
35
|
function getIndex(state) {
|
|
28
36
|
const cached = indexes.get(state);
|
|
29
37
|
// Catalog publishers replace each server's metadata array on discovery/reconnect.
|
|
30
|
-
if (cached &&
|
|
31
|
-
|
|
38
|
+
if (cached &&
|
|
39
|
+
cached.metadata.size === state.toolMetadata.size &&
|
|
40
|
+
[...state.toolMetadata].every(([server, metadata]) => cached.metadata.get(server) === metadata)) {
|
|
32
41
|
return cached.index;
|
|
33
42
|
}
|
|
34
43
|
const index = new MiniSearch({
|
|
35
44
|
fields: ["name", "originalName", "description", "schema"],
|
|
36
45
|
storeFields: ["server", "tool"],
|
|
37
|
-
searchOptions: {
|
|
46
|
+
searchOptions: {
|
|
47
|
+
fields: ["name", "description", "schema"],
|
|
48
|
+
boost: { name: 2, description: 1, schema: 1 },
|
|
49
|
+
prefix: true,
|
|
50
|
+
},
|
|
38
51
|
});
|
|
39
52
|
let id = 0;
|
|
40
53
|
for (const [server, tools] of state.toolMetadata) {
|
|
41
54
|
for (const tool of tools) {
|
|
42
|
-
if (tool.resourceUri !== undefined)
|
|
55
|
+
if (tool.resourceUri !== undefined) {
|
|
43
56
|
continue;
|
|
57
|
+
}
|
|
44
58
|
index.add({
|
|
45
|
-
id: id++,
|
|
59
|
+
id: id++,
|
|
60
|
+
server,
|
|
61
|
+
tool,
|
|
46
62
|
name: normalizeSearchText(tool.name),
|
|
47
63
|
originalName: normalizeSearchText(tool.originalName),
|
|
48
64
|
description: normalizeSearchText([tool.title, tool.description].filter(Boolean).join(" ")),
|
|
@@ -54,18 +70,22 @@ function getIndex(state) {
|
|
|
54
70
|
return index;
|
|
55
71
|
}
|
|
56
72
|
function searchCatalog(state, query, server, options) {
|
|
57
|
-
return getIndex(state)
|
|
73
|
+
return getIndex(state)
|
|
74
|
+
.search(normalizeSearchText(query), {
|
|
58
75
|
...options,
|
|
59
|
-
filter: result => {
|
|
60
|
-
if (server && result.server !== server)
|
|
76
|
+
filter: (result) => {
|
|
77
|
+
if (server && result.server !== server) {
|
|
61
78
|
return false;
|
|
79
|
+
}
|
|
62
80
|
const definition = state.config.mcpServers[result.server];
|
|
63
|
-
if (!definition || isServerDisabled(definition))
|
|
81
|
+
if (!definition || isServerDisabled(definition)) {
|
|
64
82
|
return false;
|
|
83
|
+
}
|
|
65
84
|
const prefix = resolveToolPrefix(definition, state.config.settings?.toolPrefix);
|
|
66
85
|
return isToolAllowed(result.tool.originalName, result.server, prefix, definition.includeTools, definition.excludeTools);
|
|
67
86
|
},
|
|
68
|
-
})
|
|
87
|
+
})
|
|
88
|
+
.map(({ server, tool, score }) => ({ server, tool, score }))
|
|
69
89
|
.sort((a, b) => b.score - a.score || a.tool.name.localeCompare(b.tool.name));
|
|
70
90
|
}
|
|
71
91
|
export function rankToolMatches(state, query, server) {
|
|
@@ -73,11 +93,13 @@ export function rankToolMatches(state, query, server) {
|
|
|
73
93
|
const identity = query.trim();
|
|
74
94
|
const exact = ({ tool }) => tool.name === identity || (Boolean(server) && tool.originalName === identity);
|
|
75
95
|
// Stable partition retains MiniSearch order and scores within both groups.
|
|
76
|
-
return [...matches.filter(exact), ...matches.filter(match => !exact(match))];
|
|
96
|
+
return [...matches.filter(exact), ...matches.filter((match) => !exact(match))];
|
|
77
97
|
}
|
|
78
98
|
export function paginate(items, offset, limit) {
|
|
79
99
|
const safeOffset = Number.isFinite(offset) ? Math.max(0, Math.trunc(offset)) : 0;
|
|
80
|
-
const safeLimit = Number.isFinite(limit)
|
|
100
|
+
const safeLimit = Number.isFinite(limit)
|
|
101
|
+
? Math.min(MAX_PAGE_SIZE, Math.max(1, Math.trunc(limit)))
|
|
102
|
+
: 1;
|
|
81
103
|
const total = items.length;
|
|
82
104
|
const page = items.slice(safeOffset, safeOffset + safeLimit);
|
|
83
105
|
const nextOffset = safeOffset + page.length;
|
|
@@ -89,25 +111,36 @@ export function paginate(items, offset, limit) {
|
|
|
89
111
|
};
|
|
90
112
|
}
|
|
91
113
|
export function rankSuggestions(state, name, limit, server) {
|
|
92
|
-
if (name.length > MAX_TOOL_NAME_LENGTH)
|
|
114
|
+
if (name.length > MAX_TOOL_NAME_LENGTH) {
|
|
93
115
|
return [];
|
|
116
|
+
}
|
|
94
117
|
const globalPrefix = state.config.settings?.toolPrefix ?? "server";
|
|
95
|
-
const inferred = server
|
|
96
|
-
|
|
97
|
-
.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
118
|
+
const inferred = server
|
|
119
|
+
? undefined
|
|
120
|
+
: Object.entries(state.config.mcpServers)
|
|
121
|
+
.filter(([, definition]) => !isServerDisabled(definition))
|
|
122
|
+
.map(([candidateServer, definition]) => ({
|
|
123
|
+
server: candidateServer,
|
|
124
|
+
prefix: getServerPrefix(candidateServer, resolveToolPrefix(definition, globalPrefix)),
|
|
125
|
+
}))
|
|
126
|
+
.filter((candidate) => Boolean(candidate.prefix) && name.startsWith(`${candidate.prefix}_`))
|
|
127
|
+
.sort((a, b) => b.prefix.length - a.prefix.length)[0];
|
|
103
128
|
const matchedServer = server ?? inferred?.server;
|
|
104
129
|
const definition = matchedServer ? state.config.mcpServers[matchedServer] : undefined;
|
|
105
|
-
if (matchedServer && (!definition || isServerDisabled(definition)))
|
|
130
|
+
if (matchedServer && (!definition || isServerDisabled(definition))) {
|
|
106
131
|
return [];
|
|
107
|
-
|
|
132
|
+
}
|
|
133
|
+
const prefix = inferred?.prefix ??
|
|
134
|
+
(matchedServer
|
|
135
|
+
? getServerPrefix(matchedServer, resolveToolPrefix(definition, globalPrefix))
|
|
136
|
+
: "");
|
|
108
137
|
const query = prefix && name.startsWith(`${prefix}_`) ? name.slice(prefix.length + 1) : name;
|
|
109
138
|
const suggestions = searchCatalog(state, query, matchedServer, {
|
|
110
|
-
fields: [matchedServer ? "originalName" : "name"],
|
|
111
|
-
|
|
139
|
+
fields: [matchedServer ? "originalName" : "name"],
|
|
140
|
+
combineWith: "AND",
|
|
141
|
+
fuzzy: 2,
|
|
142
|
+
})
|
|
143
|
+
.slice(0, limit)
|
|
144
|
+
.map((match) => match.tool.name);
|
|
112
145
|
return suggestions.length > 0 || !server ? suggestions : rankSuggestions(state, name, limit);
|
|
113
146
|
}
|
package/dist/server-manager.d.ts
CHANGED
|
@@ -23,24 +23,24 @@ export interface ServerConnection {
|
|
|
23
23
|
type UiStreamListener = (serverName: string, notification: ServerStreamResultPatchNotification["params"]) => void;
|
|
24
24
|
type MetadataListChangedListener = (serverName: string, reason: string) => void;
|
|
25
25
|
export declare class McpServerManager {
|
|
26
|
-
private ownedClients;
|
|
27
|
-
private connections;
|
|
28
|
-
private connectPromises;
|
|
29
|
-
private reconnectPromises;
|
|
30
|
-
private retiredConnections;
|
|
31
|
-
private disposePromises;
|
|
32
|
-
private uiStreamListeners;
|
|
26
|
+
private readonly ownedClients;
|
|
27
|
+
private readonly connections;
|
|
28
|
+
private readonly connectPromises;
|
|
29
|
+
private readonly reconnectPromises;
|
|
30
|
+
private readonly retiredConnections;
|
|
31
|
+
private readonly disposePromises;
|
|
32
|
+
private readonly uiStreamListeners;
|
|
33
33
|
private samplingConfig;
|
|
34
34
|
private metadataListChangedListener;
|
|
35
35
|
private elicitationConfig;
|
|
36
36
|
private authStorageOptions;
|
|
37
37
|
private oauthRuntime;
|
|
38
|
-
private acceptedUrlElicitations;
|
|
38
|
+
private readonly acceptedUrlElicitations;
|
|
39
39
|
private defaultRequestTimeoutMs;
|
|
40
40
|
private runtimeSignal;
|
|
41
|
-
private closePromises;
|
|
42
|
-
private closeGenerations;
|
|
43
|
-
private connectAttempts;
|
|
41
|
+
private readonly closePromises;
|
|
42
|
+
private readonly closeGenerations;
|
|
43
|
+
private readonly connectAttempts;
|
|
44
44
|
private traceSettings;
|
|
45
45
|
private traceWriter;
|
|
46
46
|
private stopped;
|