@fitchmultz/pi-mcp-adapter 8.0.6 → 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 +128 -8
- 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.js +58 -33
- 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 +26 -2
- package/skills/mcp-scripting/SKILL.md +6 -6
package/dist/tool-loader.js
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { createDirectToolExecutor, directToolSelection } from "./direct-tools.js";
|
|
3
|
-
import { isServerCacheValid, reconstructToolMetadata } from "./metadata-cache.js";
|
|
3
|
+
import { isServerCacheValid, reconstructToolMetadata, } from "./metadata-cache.js";
|
|
4
4
|
import { createMcpDirectToolCallRenderer, renderMcpToolResult } from "./tool-result-renderer.js";
|
|
5
|
-
import { isServerDisabled } from "./types.js";
|
|
5
|
+
import { isServerDisabled, } from "./types.js";
|
|
6
6
|
import { normalizeDirectToolInputSchema } from "./utils.js";
|
|
7
7
|
import { nativeMcpOutputSchema } from "./tool-registrar.js";
|
|
8
8
|
import { toolErrorOverride } from "./error-signal.js";
|
|
9
9
|
const featureNames = { gateway: "mcp", script: "mcp_script" };
|
|
10
10
|
const ENTRY = "mcp-tool-selection";
|
|
11
11
|
const key = (value) => JSON.stringify([value.server, value.tool]);
|
|
12
|
-
const reserved = new Set([
|
|
12
|
+
const reserved = new Set([
|
|
13
|
+
"read",
|
|
14
|
+
"bash",
|
|
15
|
+
"powershell",
|
|
16
|
+
"edit",
|
|
17
|
+
"write",
|
|
18
|
+
"grep",
|
|
19
|
+
"find",
|
|
20
|
+
"ls",
|
|
21
|
+
"mcp",
|
|
22
|
+
"mcp_search",
|
|
23
|
+
"mcp_script",
|
|
24
|
+
]);
|
|
13
25
|
export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
|
|
14
|
-
pi.registerEntryRenderer(ENTRY, () =>
|
|
26
|
+
pi.registerEntryRenderer(ENTRY, () => { });
|
|
15
27
|
const registered = new Map();
|
|
16
28
|
const fingerprints = new WeakMap();
|
|
17
29
|
let available = new Set();
|
|
@@ -25,49 +37,64 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
|
|
|
25
37
|
const active = () => pi.getActiveTools();
|
|
26
38
|
const setActive = (names) => {
|
|
27
39
|
const unique = [...new Set(names)];
|
|
28
|
-
if (JSON.stringify(active()) === JSON.stringify(unique))
|
|
40
|
+
if (JSON.stringify(active()) === JSON.stringify(unique)) {
|
|
29
41
|
return;
|
|
42
|
+
}
|
|
30
43
|
pi.setActiveTools(unique);
|
|
31
44
|
};
|
|
32
45
|
function registerFeature(feature, definition) {
|
|
33
|
-
pi.registerTool({
|
|
46
|
+
pi.registerTool({
|
|
47
|
+
defaultActive: false,
|
|
48
|
+
...definition,
|
|
49
|
+
async execute(...args) {
|
|
34
50
|
const result = await definition.execute(...args);
|
|
35
51
|
return { ...result, ...toolErrorOverride(result.details) };
|
|
36
|
-
}
|
|
52
|
+
},
|
|
53
|
+
});
|
|
37
54
|
registeredFeatures.add(feature);
|
|
38
55
|
}
|
|
39
56
|
function capture() {
|
|
40
57
|
const current = new Set(active());
|
|
41
58
|
for (const feature of Object.keys(features)) {
|
|
42
59
|
if (current.has(featureNames[feature])) {
|
|
43
|
-
if (!features[feature]?.eager)
|
|
60
|
+
if (!features[feature]?.eager) {
|
|
44
61
|
selectedFeatures.add(feature);
|
|
62
|
+
}
|
|
45
63
|
inactiveFeatures.delete(feature);
|
|
46
64
|
}
|
|
47
65
|
else {
|
|
48
66
|
selectedFeatures.delete(feature);
|
|
49
|
-
if (features[feature]?.eager)
|
|
67
|
+
if (features[feature]?.eager) {
|
|
50
68
|
inactiveFeatures.add(feature);
|
|
69
|
+
}
|
|
51
70
|
}
|
|
52
71
|
}
|
|
53
72
|
for (const [id, { selection, name, pinned }] of registered) {
|
|
54
|
-
if (!available.has(id))
|
|
73
|
+
if (!available.has(id)) {
|
|
55
74
|
continue;
|
|
75
|
+
}
|
|
56
76
|
if (current.has(name)) {
|
|
57
|
-
if (!pinned)
|
|
77
|
+
if (!pinned) {
|
|
58
78
|
selected.set(id, selection);
|
|
79
|
+
}
|
|
59
80
|
inactive.delete(id);
|
|
60
81
|
}
|
|
61
82
|
else {
|
|
62
83
|
selected.delete(id);
|
|
63
|
-
if (pinned)
|
|
84
|
+
if (pinned) {
|
|
64
85
|
inactive.set(id, selection);
|
|
86
|
+
}
|
|
65
87
|
}
|
|
66
88
|
}
|
|
67
89
|
}
|
|
68
90
|
function persist() {
|
|
69
91
|
capture();
|
|
70
|
-
pi.appendEntry(ENTRY, {
|
|
92
|
+
pi.appendEntry(ENTRY, {
|
|
93
|
+
selected: [...selected.values()],
|
|
94
|
+
inactive: [...inactive.values()],
|
|
95
|
+
features: [...selectedFeatures],
|
|
96
|
+
inactiveFeatures: [...inactiveFeatures],
|
|
97
|
+
});
|
|
71
98
|
}
|
|
72
99
|
function restore(ctx) {
|
|
73
100
|
selected = new Map();
|
|
@@ -79,27 +106,43 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
|
|
|
79
106
|
let id = manager.getLeafId();
|
|
80
107
|
while (id) {
|
|
81
108
|
const entry = manager.getEntry(id);
|
|
82
|
-
if (!entry)
|
|
109
|
+
if (!entry) {
|
|
83
110
|
break;
|
|
111
|
+
}
|
|
84
112
|
yield entry;
|
|
85
113
|
id = entry.parentId;
|
|
86
114
|
}
|
|
87
115
|
}
|
|
88
116
|
for (const entry of snapshots()) {
|
|
89
|
-
if (entry?.type !== "custom" ||
|
|
117
|
+
if (entry?.type !== "custom" ||
|
|
118
|
+
entry.customType !== ENTRY ||
|
|
119
|
+
!entry.data ||
|
|
120
|
+
typeof entry.data !== "object") {
|
|
90
121
|
continue;
|
|
122
|
+
}
|
|
91
123
|
const data = entry.data;
|
|
92
|
-
if (!Array.isArray(data.selected) ||
|
|
124
|
+
if (!Array.isArray(data.selected) ||
|
|
125
|
+
[data.inactive, data.features, data.inactiveFeatures].some((value) => value !== undefined && !Array.isArray(value))) {
|
|
93
126
|
continue;
|
|
94
|
-
|
|
127
|
+
}
|
|
128
|
+
if ([...data.selected, ...(data.inactive ?? [])].some((value) => !value || typeof value.server !== "string" || typeof value.tool !== "string")) {
|
|
95
129
|
continue;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
130
|
+
}
|
|
131
|
+
selectedFeatures = new Set(Array.isArray(data.features)
|
|
132
|
+
? data.features.filter((value) => value === "gateway" || value === "script")
|
|
133
|
+
: []);
|
|
134
|
+
inactiveFeatures = new Set(Array.isArray(data.inactiveFeatures)
|
|
135
|
+
? data.inactiveFeatures.filter((value) => value === "gateway" || value === "script")
|
|
136
|
+
: []);
|
|
137
|
+
for (const [field, target] of [
|
|
138
|
+
[data.selected, selected],
|
|
139
|
+
[data.inactive, inactive],
|
|
140
|
+
]) {
|
|
99
141
|
target.clear();
|
|
100
142
|
for (const selection of Array.isArray(field) ? field : []) {
|
|
101
|
-
if (typeof selection?.server === "string" && typeof selection.tool === "string")
|
|
143
|
+
if (typeof selection?.server === "string" && typeof selection.tool === "string") {
|
|
102
144
|
target.set(key(selection), selection);
|
|
145
|
+
}
|
|
103
146
|
}
|
|
104
147
|
}
|
|
105
148
|
break;
|
|
@@ -107,53 +150,86 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
|
|
|
107
150
|
restoring = true;
|
|
108
151
|
}
|
|
109
152
|
function sync(config, cache, env, registerGateways) {
|
|
110
|
-
if (!restoring)
|
|
153
|
+
if (!restoring) {
|
|
111
154
|
capture();
|
|
112
|
-
|
|
113
|
-
const
|
|
155
|
+
}
|
|
156
|
+
const previousNames = new Set([...registered.values()].map((item) => item.name));
|
|
157
|
+
const existing = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
114
158
|
const specs = [];
|
|
115
159
|
for (const [server, definition] of Object.entries(env === null ? {} : config.mcpServers)) {
|
|
116
|
-
if (isServerDisabled(definition))
|
|
160
|
+
if (isServerDisabled(definition)) {
|
|
117
161
|
continue;
|
|
162
|
+
}
|
|
118
163
|
const entry = cache?.servers[server];
|
|
119
|
-
const metadata = getState()?.toolMetadata.get(server) ??
|
|
120
|
-
|
|
164
|
+
const metadata = getState()?.toolMetadata.get(server) ??
|
|
165
|
+
(entry && isServerCacheValid(entry, definition)
|
|
166
|
+
? reconstructToolMetadata(server, entry, config.settings?.toolPrefix ?? "server", definition)
|
|
167
|
+
: []);
|
|
121
168
|
const selection = directToolSelection(config, server, env ?? undefined);
|
|
122
169
|
for (const source of metadata) {
|
|
123
170
|
const { name: prefixedName, ...tool } = source;
|
|
124
|
-
if (tool.resourceUri)
|
|
171
|
+
if (tool.resourceUri) {
|
|
125
172
|
continue;
|
|
173
|
+
}
|
|
126
174
|
const pinned = selection === true || (Array.isArray(selection) && selection.includes(tool.originalName));
|
|
127
|
-
specs.push({
|
|
175
|
+
specs.push({
|
|
176
|
+
spec: { ...tool, prefixedName, serverName: server },
|
|
177
|
+
source,
|
|
178
|
+
selection: { server, tool: tool.originalName },
|
|
179
|
+
name: prefixedName,
|
|
180
|
+
pinned,
|
|
181
|
+
});
|
|
128
182
|
}
|
|
129
183
|
}
|
|
130
184
|
const counts = new Map();
|
|
131
|
-
for (const { name } of specs)
|
|
185
|
+
for (const { name } of specs) {
|
|
132
186
|
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
187
|
+
}
|
|
133
188
|
available = new Set();
|
|
134
189
|
const enabled = [];
|
|
135
190
|
for (const { spec, source, selection, name, pinned } of specs) {
|
|
136
191
|
const id = key(selection);
|
|
137
|
-
if (reserved.has(name) ||
|
|
192
|
+
if (reserved.has(name) ||
|
|
193
|
+
counts.get(name) > 1 ||
|
|
194
|
+
(existing.has(name) && !previousNames.has(name))) {
|
|
138
195
|
continue;
|
|
196
|
+
}
|
|
139
197
|
available.add(id);
|
|
140
|
-
const instructions = getState()?.serverInstructions.get(spec.serverName) ??
|
|
198
|
+
const instructions = getState()?.serverInstructions.get(spec.serverName) ??
|
|
199
|
+
cache?.servers[spec.serverName]?.instructions;
|
|
141
200
|
const previous = Object.isFrozen(source) ? fingerprints.get(source) : undefined;
|
|
142
201
|
const fingerprint = previous?.server === spec.serverName && previous.instructions === instructions
|
|
143
|
-
? previous.value
|
|
144
|
-
|
|
202
|
+
? previous.value
|
|
203
|
+
: JSON.stringify({ spec, name, instructions });
|
|
204
|
+
if (Object.isFrozen(source)) {
|
|
145
205
|
fingerprints.set(source, { server: spec.serverName, instructions, value: fingerprint });
|
|
206
|
+
}
|
|
146
207
|
if (registered.get(id)?.fingerprint !== fingerprint) {
|
|
147
208
|
pi.registerTool({
|
|
148
209
|
name,
|
|
149
|
-
namespace: {
|
|
210
|
+
namespace: {
|
|
211
|
+
name: `mcp_${spec.serverName}`,
|
|
212
|
+
...(instructions !== undefined ? { instructions } : {}),
|
|
213
|
+
},
|
|
150
214
|
...(spec.outputSchema ? { outputSchema: nativeMcpOutputSchema(spec.outputSchema) } : {}),
|
|
151
|
-
...(spec.annotations
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
215
|
+
...(spec.annotations
|
|
216
|
+
? {
|
|
217
|
+
annotations: {
|
|
218
|
+
...(typeof spec.annotations.readOnlyHint === "boolean"
|
|
219
|
+
? { readOnlyHint: spec.annotations.readOnlyHint }
|
|
220
|
+
: {}),
|
|
221
|
+
...(typeof spec.annotations.destructiveHint === "boolean"
|
|
222
|
+
? { destructiveHint: spec.annotations.destructiveHint }
|
|
223
|
+
: {}),
|
|
224
|
+
...(typeof spec.annotations.idempotentHint === "boolean"
|
|
225
|
+
? { idempotentHint: spec.annotations.idempotentHint }
|
|
226
|
+
: {}),
|
|
227
|
+
...(typeof spec.annotations.openWorldHint === "boolean"
|
|
228
|
+
? { openWorldHint: spec.annotations.openWorldHint }
|
|
229
|
+
: {}),
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
: {}),
|
|
157
233
|
// Inactive direct tools are not callable through codemode or nested execution.
|
|
158
234
|
defaultActive: false,
|
|
159
235
|
...(beforeExecute ? { executionMode: "sequential" } : {}),
|
|
@@ -166,21 +242,26 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
|
|
|
166
242
|
});
|
|
167
243
|
}
|
|
168
244
|
registered.set(id, { selection, name, fingerprint, pinned });
|
|
169
|
-
if (selected.has(id) || (pinned && !inactive.has(id)))
|
|
245
|
+
if (selected.has(id) || (pinned && !inactive.has(id))) {
|
|
170
246
|
enabled.push(name);
|
|
247
|
+
}
|
|
171
248
|
}
|
|
172
|
-
const permitted = new Set(pi.getAllTools().map(tool => tool.name));
|
|
173
|
-
const pins = specs
|
|
249
|
+
const permitted = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
250
|
+
const pins = specs
|
|
251
|
+
.filter((item) => item.pinned && available.has(key(item.selection)) && permitted.has(item.name))
|
|
252
|
+
.map((item) => item.spec);
|
|
174
253
|
const policy = registerGateways?.(pins);
|
|
175
254
|
if (policy) {
|
|
176
|
-
const permittedFeatures = new Set(pi.getAllTools().map(tool => tool.name));
|
|
255
|
+
const permittedFeatures = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
177
256
|
const loaderMissing = !permittedFeatures.has("mcp_search");
|
|
178
257
|
features = {};
|
|
179
258
|
for (const feature of Object.keys(featureNames)) {
|
|
180
259
|
if (policy[feature] && permittedFeatures.has(featureNames[feature])) {
|
|
181
260
|
features[feature] = { eager: policy[feature].eager || loaderMissing };
|
|
182
|
-
if (selectedFeatures.has(feature) ||
|
|
261
|
+
if (selectedFeatures.has(feature) ||
|
|
262
|
+
(features[feature]?.eager && !inactiveFeatures.has(feature))) {
|
|
183
263
|
enabled.push(featureNames[feature]);
|
|
264
|
+
}
|
|
184
265
|
}
|
|
185
266
|
else {
|
|
186
267
|
selectedFeatures.delete(feature);
|
|
@@ -189,10 +270,14 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
|
|
|
189
270
|
}
|
|
190
271
|
}
|
|
191
272
|
// Retain the journal: official 1.0 initial SDK resume has a native loadout restoration gap.
|
|
192
|
-
const ownedNames = new Set([
|
|
193
|
-
|
|
273
|
+
const ownedNames = new Set([
|
|
274
|
+
...previousNames,
|
|
275
|
+
...[...registered.values()].map((item) => item.name),
|
|
276
|
+
]);
|
|
277
|
+
for (const feature of registeredFeatures) {
|
|
194
278
|
ownedNames.add(featureNames[feature]);
|
|
195
|
-
|
|
279
|
+
}
|
|
280
|
+
setActive([...active().filter((name) => !ownedNames.has(name)), ...enabled]);
|
|
196
281
|
restoring = false;
|
|
197
282
|
return pins;
|
|
198
283
|
}
|
|
@@ -203,22 +288,36 @@ export function createToolLoader(pi, getState, getInitPromise, beforeExecute) {
|
|
|
203
288
|
return registration ? [registration] : [];
|
|
204
289
|
});
|
|
205
290
|
// No await between reading and extending the current loadout: parallel searches union their selections.
|
|
206
|
-
const permitted = new Set(pi.getAllTools().map(tool => tool.name));
|
|
207
|
-
const requestedFeatures = [...new Set(enable)].filter(feature => features[feature] && permitted.has(featureNames[feature]));
|
|
208
|
-
setActive([
|
|
291
|
+
const permitted = new Set(pi.getAllTools().map((tool) => tool.name));
|
|
292
|
+
const requestedFeatures = [...new Set(enable)].filter((feature) => features[feature] && permitted.has(featureNames[feature]));
|
|
293
|
+
setActive([
|
|
294
|
+
...active(),
|
|
295
|
+
...requested.map((item) => item.name),
|
|
296
|
+
...requestedFeatures.map((feature) => featureNames[feature]),
|
|
297
|
+
]);
|
|
209
298
|
const current = new Set(active());
|
|
210
|
-
const tools = requested
|
|
299
|
+
const tools = requested
|
|
300
|
+
.filter((item) => current.has(item.name))
|
|
301
|
+
.map(({ selection, name }) => {
|
|
211
302
|
selected.set(key(selection), selection);
|
|
212
303
|
return { name };
|
|
213
304
|
});
|
|
214
305
|
for (const feature of requestedFeatures) {
|
|
215
|
-
if (!current.has(featureNames[feature]))
|
|
306
|
+
if (!current.has(featureNames[feature])) {
|
|
216
307
|
continue;
|
|
308
|
+
}
|
|
217
309
|
selectedFeatures.add(feature);
|
|
218
310
|
tools.push({ name: featureNames[feature] });
|
|
219
311
|
}
|
|
220
312
|
persist();
|
|
221
313
|
return tools;
|
|
222
314
|
}
|
|
223
|
-
return {
|
|
315
|
+
return {
|
|
316
|
+
registerFeature,
|
|
317
|
+
restore,
|
|
318
|
+
sync,
|
|
319
|
+
activate,
|
|
320
|
+
persist,
|
|
321
|
+
selectedServers: () => [...new Set([...selected.values()].map((item) => item.server))],
|
|
322
|
+
};
|
|
224
323
|
}
|
package/dist/tool-metadata.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { McpExtensionState } from "./state.ts";
|
|
2
|
-
import type
|
|
2
|
+
import { type ToolMetadata, type McpTool, type McpResource, type ServerEntry, type ToolPrefix } from "./types.ts";
|
|
3
3
|
export declare function buildToolMetadata(tools: McpTool[], resources: McpResource[], definition: ServerEntry, serverName: string, prefix: ToolPrefix): {
|
|
4
4
|
metadata: ToolMetadata[];
|
|
5
5
|
failedTools: string[];
|
package/dist/tool-metadata.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { formatToolName, isServerDisabled, isToolAllowed, resolveToolPrefix } from "./types.js";
|
|
1
|
+
import { formatToolName, isServerDisabled, isToolAllowed, resolveToolPrefix, } from "./types.js";
|
|
2
2
|
import { extractToolUiStreamMode } from "./utils.js";
|
|
3
3
|
import { extractUiToolVisibility, isUiToolVisibleToModel } from "./ui-tool-visibility.js";
|
|
4
4
|
export function buildToolMetadata(tools, resources, definition, serverName, prefix) {
|
|
5
5
|
const metadata = [];
|
|
6
6
|
const failedTools = [];
|
|
7
|
-
if (isServerDisabled(definition))
|
|
7
|
+
if (isServerDisabled(definition)) {
|
|
8
8
|
return { metadata, failedTools };
|
|
9
|
+
}
|
|
9
10
|
const effectivePrefix = resolveToolPrefix(definition, prefix);
|
|
10
11
|
for (const tool of tools) {
|
|
11
12
|
if (!tool?.name) {
|
|
@@ -25,10 +26,12 @@ export function buildToolMetadata(tools, resources, definition, serverName, pref
|
|
|
25
26
|
// MCP Apps metadata must also be readable from the cache before loading the SDK.
|
|
26
27
|
const uri = tool._meta?.ui?.resourceUri;
|
|
27
28
|
const resourceUri = uri === undefined ? tool._meta?.["ui/resourceUri"] : uri;
|
|
28
|
-
if (typeof resourceUri === "string" && resourceUri.startsWith("ui://"))
|
|
29
|
+
if (typeof resourceUri === "string" && resourceUri.startsWith("ui://")) {
|
|
29
30
|
uiResourceUri = resourceUri;
|
|
30
|
-
|
|
31
|
+
}
|
|
32
|
+
else if (resourceUri !== undefined) {
|
|
31
33
|
failedTools.push(tool.name);
|
|
34
|
+
}
|
|
32
35
|
}
|
|
33
36
|
catch {
|
|
34
37
|
failedTools.push(tool.name);
|
|
@@ -46,8 +49,9 @@ export function buildToolMetadata(tools, resources, definition, serverName, pref
|
|
|
46
49
|
}
|
|
47
50
|
if (definition.exposeResources !== false) {
|
|
48
51
|
for (const resource of resources) {
|
|
49
|
-
if (!resource?.name || !resource?.uri)
|
|
52
|
+
if (!resource?.name || !resource?.uri) {
|
|
50
53
|
continue;
|
|
54
|
+
}
|
|
51
55
|
const baseName = `read_${resourceNameToToolName(resource.name)}`;
|
|
52
56
|
if (!isToolAllowed(baseName, serverName, effectivePrefix, definition.includeTools, definition.excludeTools)) {
|
|
53
57
|
continue;
|
|
@@ -67,7 +71,7 @@ export function buildToolMetadata(tools, resources, definition, serverName, pref
|
|
|
67
71
|
export function totalToolCount(state) {
|
|
68
72
|
let count = 0;
|
|
69
73
|
for (const metadata of state.toolMetadata.values()) {
|
|
70
|
-
count += metadata.filter(tool => tool.resourceUri === undefined).length;
|
|
74
|
+
count += metadata.filter((tool) => tool.resourceUri === undefined).length;
|
|
71
75
|
}
|
|
72
76
|
return count;
|
|
73
77
|
}
|
|
@@ -76,19 +80,21 @@ export function toToolDescriptor(server, tool) {
|
|
|
76
80
|
return { ...descriptor, server, path, name };
|
|
77
81
|
}
|
|
78
82
|
export function catalogCoverage(state, server) {
|
|
79
|
-
const servers = Object.keys(state.config.mcpServers).filter(name => (!server || name === server) && !isServerDisabled(state.config.mcpServers[name]));
|
|
80
|
-
const knownServers = servers.filter(name => state.toolMetadata.has(name));
|
|
81
|
-
const unknownServers = servers.filter(name => !state.toolMetadata.has(name));
|
|
83
|
+
const servers = Object.keys(state.config.mcpServers).filter((name) => (!server || name === server) && !isServerDisabled(state.config.mcpServers[name]));
|
|
84
|
+
const knownServers = servers.filter((name) => state.toolMetadata.has(name));
|
|
85
|
+
const unknownServers = servers.filter((name) => !state.toolMetadata.has(name));
|
|
82
86
|
return { complete: unknownServers.length === 0, knownServers, unknownServers };
|
|
83
87
|
}
|
|
84
88
|
export function findToolByName(metadata, toolName) {
|
|
85
|
-
if (!metadata)
|
|
89
|
+
if (!metadata) {
|
|
86
90
|
return undefined;
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
}
|
|
92
|
+
const exact = metadata.filter((m) => m.name === toolName);
|
|
93
|
+
if (exact.length > 0) {
|
|
89
94
|
return exact.length === 1 ? exact[0] : undefined;
|
|
95
|
+
}
|
|
90
96
|
const normalized = toolName.replace(/-/g, "_");
|
|
91
|
-
const matches = metadata.filter(m => m.name.replace(/-/g, "_") === normalized);
|
|
97
|
+
const matches = metadata.filter((m) => m.name.replace(/-/g, "_") === normalized);
|
|
92
98
|
return matches.length === 1 ? matches[0] : undefined;
|
|
93
99
|
}
|
|
94
100
|
export function formatSchema(schema, indent = " ") {
|
|
@@ -96,9 +102,14 @@ export function formatSchema(schema, indent = " ") {
|
|
|
96
102
|
return `${indent}(no schema)`;
|
|
97
103
|
}
|
|
98
104
|
const s = schema;
|
|
99
|
-
if (s.type === "object" &&
|
|
105
|
+
if (s.type === "object" &&
|
|
106
|
+
s.properties &&
|
|
107
|
+
typeof s.properties === "object" &&
|
|
108
|
+
!Array.isArray(s.properties)) {
|
|
100
109
|
const props = s.properties;
|
|
101
|
-
const required = Array.isArray(s.required)
|
|
110
|
+
const required = Array.isArray(s.required)
|
|
111
|
+
? s.required.filter((name) => typeof name === "string")
|
|
112
|
+
: [];
|
|
102
113
|
if (Object.keys(props).length === 0) {
|
|
103
114
|
return `${indent}(no parameters)`;
|
|
104
115
|
}
|
|
@@ -125,10 +136,12 @@ function formatProperty(name, schema, required, indent) {
|
|
|
125
136
|
const s = schema;
|
|
126
137
|
const parts = [`${indent}${name}`];
|
|
127
138
|
const typeStr = formatType(s);
|
|
128
|
-
if (typeStr)
|
|
139
|
+
if (typeStr) {
|
|
129
140
|
parts.push(`(${typeStr})`);
|
|
130
|
-
|
|
141
|
+
}
|
|
142
|
+
if (required) {
|
|
131
143
|
parts.push("*required*");
|
|
144
|
+
}
|
|
132
145
|
appendSchemaAnnotations(parts, s);
|
|
133
146
|
return [parts.join(" "), ...formatNestedSchema(s, `${indent} `)];
|
|
134
147
|
}
|
|
@@ -143,8 +156,12 @@ function formatNestedSchema(schema, indent) {
|
|
|
143
156
|
if (schema.items !== undefined) {
|
|
144
157
|
lines.push(...formatProperty("items", schema.items, false, indent));
|
|
145
158
|
}
|
|
146
|
-
if (schema.properties &&
|
|
147
|
-
|
|
159
|
+
if (schema.properties &&
|
|
160
|
+
typeof schema.properties === "object" &&
|
|
161
|
+
!Array.isArray(schema.properties)) {
|
|
162
|
+
const required = Array.isArray(schema.required)
|
|
163
|
+
? schema.required.filter((name) => typeof name === "string")
|
|
164
|
+
: [];
|
|
148
165
|
for (const [name, propSchema] of Object.entries(schema.properties)) {
|
|
149
166
|
lines.push(...formatProperty(name, propSchema, required.includes(name), indent));
|
|
150
167
|
}
|
|
@@ -172,15 +189,17 @@ function formatType(schema) {
|
|
|
172
189
|
return `const ${JSON.stringify(schema.const)}`;
|
|
173
190
|
}
|
|
174
191
|
if (Array.isArray(schema.enum)) {
|
|
175
|
-
return `enum: ${schema.enum.map(v => JSON.stringify(v)).join(", ")}`;
|
|
192
|
+
return `enum: ${schema.enum.map((v) => JSON.stringify(v)).join(", ")}`;
|
|
176
193
|
}
|
|
177
194
|
if (Array.isArray(schema.type)) {
|
|
178
|
-
return schema.type.map(type => String(type)).join(" | ");
|
|
195
|
+
return schema.type.map((type) => String(type)).join(" | ");
|
|
179
196
|
}
|
|
180
197
|
if (schema.type) {
|
|
181
198
|
return String(schema.type);
|
|
182
199
|
}
|
|
183
|
-
if (schema.properties &&
|
|
200
|
+
if (schema.properties &&
|
|
201
|
+
typeof schema.properties === "object" &&
|
|
202
|
+
!Array.isArray(schema.properties)) {
|
|
184
203
|
return "object";
|
|
185
204
|
}
|
|
186
205
|
if (schema.items !== undefined) {
|
|
@@ -192,7 +211,16 @@ function appendSchemaAnnotations(parts, schema) {
|
|
|
192
211
|
if (schema.description && typeof schema.description === "string") {
|
|
193
212
|
parts.push(`- ${schema.description}`);
|
|
194
213
|
}
|
|
195
|
-
for (const key of [
|
|
214
|
+
for (const key of [
|
|
215
|
+
"minLength",
|
|
216
|
+
"maxLength",
|
|
217
|
+
"minimum",
|
|
218
|
+
"maximum",
|
|
219
|
+
"minItems",
|
|
220
|
+
"maxItems",
|
|
221
|
+
"format",
|
|
222
|
+
"pattern",
|
|
223
|
+
]) {
|
|
196
224
|
if (schema[key] !== undefined) {
|
|
197
225
|
parts.push(`[${key}: ${JSON.stringify(schema[key])}]`);
|
|
198
226
|
}
|
package/dist/tool-registrar.js
CHANGED
|
@@ -4,13 +4,16 @@ import { Type } from "typebox";
|
|
|
4
4
|
import { toolErrorOverride } from "./error-signal.js";
|
|
5
5
|
/** Overflow/not-run outcomes are receipts, never a fabricated server-schema result. */
|
|
6
6
|
export function nativeMcpOutputSchema(schema) {
|
|
7
|
-
return Type.Union([
|
|
7
|
+
return Type.Union([
|
|
8
|
+
Type.Unsafe(schema),
|
|
9
|
+
Type.Object({
|
|
8
10
|
mcpAdapter: Type.Object({
|
|
9
11
|
omitted: Type.Literal(true),
|
|
10
12
|
resultRef: Type.Optional(Type.String()),
|
|
11
13
|
error: Type.Optional(Type.String()),
|
|
12
14
|
}),
|
|
13
|
-
})
|
|
15
|
+
}),
|
|
16
|
+
]);
|
|
14
17
|
}
|
|
15
18
|
export function nativeMcpResult(result, structured = false) {
|
|
16
19
|
const { details } = result;
|
|
@@ -18,16 +21,30 @@ export function nativeMcpResult(result, structured = false) {
|
|
|
18
21
|
return {
|
|
19
22
|
...result,
|
|
20
23
|
...toolErrorOverride(details),
|
|
21
|
-
...(structured
|
|
22
|
-
|
|
23
|
-
:
|
|
24
|
+
...(structured
|
|
25
|
+
? {
|
|
26
|
+
structuredContent: !details.outputGuard &&
|
|
27
|
+
(!details.error || details.error === "tool_error") &&
|
|
28
|
+
raw?.structuredContent !== undefined
|
|
29
|
+
? raw.structuredContent
|
|
30
|
+
: {
|
|
31
|
+
mcpAdapter: {
|
|
32
|
+
omitted: true,
|
|
33
|
+
...(typeof details.resultRef === "string"
|
|
34
|
+
? { resultRef: details.resultRef }
|
|
35
|
+
: {}),
|
|
36
|
+
...(typeof details.error === "string" ? { error: details.error } : {}),
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
: {}),
|
|
24
41
|
};
|
|
25
42
|
}
|
|
26
43
|
/**
|
|
27
44
|
* Transform MCP content types to Pi content blocks.
|
|
28
45
|
*/
|
|
29
46
|
export function transformMcpContent(content) {
|
|
30
|
-
return content.map(c => {
|
|
47
|
+
return content.map((c) => {
|
|
31
48
|
if (c.type === "text") {
|
|
32
49
|
return { type: "text", text: c.text ?? "" };
|
|
33
50
|
}
|
|
@@ -40,7 +57,8 @@ export function transformMcpContent(content) {
|
|
|
40
57
|
}
|
|
41
58
|
if (c.type === "resource") {
|
|
42
59
|
const resourceUri = c.resource?.uri ?? "(no URI)";
|
|
43
|
-
const resourceContent = c.resource?.text ??
|
|
60
|
+
const resourceContent = c.resource?.text ??
|
|
61
|
+
(c.resource?.blob !== undefined ? "[Binary content; not rendered]" : "(no content)");
|
|
44
62
|
return {
|
|
45
63
|
type: "text",
|
|
46
64
|
text: `[Resource: ${resourceUri}]\n${resourceContent}`,
|
|
@@ -74,10 +92,14 @@ export function resolveMcpResultContent(result) {
|
|
|
74
92
|
/** Persist content the model cannot consume; never imply an audio/blob payload was rendered. */
|
|
75
93
|
export function renderMcpResultContent(result, payloadFiles = []) {
|
|
76
94
|
const content = Array.isArray(result.contents)
|
|
77
|
-
? result.contents.map(resource => typeof resource.text === "string"
|
|
78
|
-
|
|
95
|
+
? result.contents.map((resource) => typeof resource.text === "string"
|
|
96
|
+
? { type: "text", text: resource.text }
|
|
97
|
+
: { type: "resource", resource })
|
|
98
|
+
: Array.isArray(result.content)
|
|
99
|
+
? result.content
|
|
100
|
+
: [];
|
|
79
101
|
const rendered = content.map((block, index) => {
|
|
80
|
-
const file = payloadFiles.find(file => file.index === index);
|
|
102
|
+
const file = payloadFiles.find((file) => file.index === index);
|
|
81
103
|
return file ? { type: "text", text: formatMcpPayloadFile(file) } : block;
|
|
82
104
|
});
|
|
83
105
|
return resolveMcpResultContent({ ...result, content: rendered });
|