@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
|
@@ -115,10 +115,12 @@ function handleRequest(req, res) {
|
|
|
115
115
|
}
|
|
116
116
|
try {
|
|
117
117
|
const validate = pending?.validate ?? reserved?.validate;
|
|
118
|
-
if (validate)
|
|
118
|
+
if (validate) {
|
|
119
119
|
validate({ ...(iss !== null ? { iss } : {}), ...(error ? { error } : {}) });
|
|
120
|
-
|
|
120
|
+
}
|
|
121
|
+
else if (error) {
|
|
121
122
|
throw new Error("Cannot verify the OAuth error callback issuer");
|
|
123
|
+
}
|
|
122
124
|
}
|
|
123
125
|
catch (failure) {
|
|
124
126
|
const message = failure instanceof Error ? failure.message : "Invalid OAuth callback";
|
|
@@ -147,7 +149,10 @@ function handleRequest(req, res) {
|
|
|
147
149
|
}
|
|
148
150
|
else if (reserved) {
|
|
149
151
|
// Keep provider-controlled callback payloads out of tool output.
|
|
150
|
-
reserved.result = {
|
|
152
|
+
reserved.result = {
|
|
153
|
+
error: "OAuth authorization was denied or failed. Finish authorization in the browser, then retry auth-complete.",
|
|
154
|
+
...(iss !== null ? { iss } : {}),
|
|
155
|
+
};
|
|
151
156
|
}
|
|
152
157
|
return;
|
|
153
158
|
}
|
|
@@ -158,8 +163,9 @@ function handleRequest(req, res) {
|
|
|
158
163
|
return;
|
|
159
164
|
}
|
|
160
165
|
if (!pending) {
|
|
161
|
-
if (reserved)
|
|
166
|
+
if (reserved) {
|
|
162
167
|
reserved.result = { code, ...(iss !== null ? { iss } : {}) };
|
|
168
|
+
}
|
|
163
169
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
164
170
|
res.end(HTML_MANUAL_SUCCESS);
|
|
165
171
|
return;
|
|
@@ -206,7 +212,9 @@ async function ensureCallbackServerLocked(options = {}) {
|
|
|
206
212
|
const strictPort = options.strictPort === true;
|
|
207
213
|
const requestedHost = options.callbackHost ?? DEFAULT_OAUTH_CALLBACK_HOST;
|
|
208
214
|
const rawRequestedPath = options.callbackPath ?? DEFAULT_OAUTH_CALLBACK_PATH;
|
|
209
|
-
const requestedPath = rawRequestedPath.startsWith("/")
|
|
215
|
+
const requestedPath = rawRequestedPath.startsWith("/")
|
|
216
|
+
? rawRequestedPath
|
|
217
|
+
: `/${rawRequestedPath}`;
|
|
210
218
|
if (options.reserveState && !options.oauthState) {
|
|
211
219
|
throw new Error("OAuth callback reservation requires an oauthState");
|
|
212
220
|
}
|
|
@@ -289,8 +297,9 @@ export function releaseCallbackServer(oauthState) {
|
|
|
289
297
|
export function takeCallbackResult(oauthState) {
|
|
290
298
|
const reserved = reservedAuthStates.get(oauthState);
|
|
291
299
|
const result = reserved?.result;
|
|
292
|
-
if (reserved)
|
|
300
|
+
if (reserved) {
|
|
293
301
|
delete reserved.result;
|
|
302
|
+
}
|
|
294
303
|
return result;
|
|
295
304
|
}
|
|
296
305
|
/**
|
|
@@ -333,8 +342,9 @@ export function cancelPendingCallback(oauthState) {
|
|
|
333
342
|
* Stop the callback server and reject all pending authorizations.
|
|
334
343
|
*/
|
|
335
344
|
export function stopCallbackServer() {
|
|
336
|
-
if (stoppingPromise)
|
|
345
|
+
if (stoppingPromise) {
|
|
337
346
|
return stoppingPromise;
|
|
347
|
+
}
|
|
338
348
|
callbackGeneration += 1;
|
|
339
349
|
const cleanup = (async () => {
|
|
340
350
|
while (bindingPromise) {
|
|
@@ -363,8 +373,9 @@ export function stopCallbackServer() {
|
|
|
363
373
|
}, 0);
|
|
364
374
|
})();
|
|
365
375
|
const operation = cleanup.finally(() => {
|
|
366
|
-
if (stoppingPromise === operation)
|
|
376
|
+
if (stoppingPromise === operation) {
|
|
367
377
|
stoppingPromise = undefined;
|
|
378
|
+
}
|
|
368
379
|
});
|
|
369
380
|
stoppingPromise = operation;
|
|
370
381
|
return operation;
|
package/dist/mcp-code.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { formatWithOptions } from "node:util";
|
|
2
2
|
import { Worker } from "node:worker_threads";
|
|
3
|
-
import { guardedMcpDetails, resolveMcpOutputGuardOptions, readMcpResult, formatMcpResultReference, formatMcpPayloadFile } from "./mcp-output-guard.js";
|
|
3
|
+
import { guardedMcpDetails, resolveMcpOutputGuardOptions, readMcpResult, formatMcpResultReference, formatMcpPayloadFile, } from "./mcp-output-guard.js";
|
|
4
4
|
import { createMcpOutputSpool } from "./mcp-output-preview.js";
|
|
5
5
|
import { executeCall, executeConnect } from "./proxy-modes.js";
|
|
6
6
|
import { executeResourceList, executeResourceRead } from "./resource-tools.js";
|
|
@@ -18,14 +18,24 @@ class McpScriptTimeoutError extends Error {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
function needsInspectableFormatting(value, stack = new WeakSet()) {
|
|
21
|
-
if (value === undefined ||
|
|
21
|
+
if (value === undefined ||
|
|
22
|
+
typeof value === "bigint" ||
|
|
23
|
+
typeof value === "function" ||
|
|
24
|
+
typeof value === "symbol") {
|
|
22
25
|
return true;
|
|
23
|
-
|
|
26
|
+
}
|
|
27
|
+
if (typeof value !== "object" || value === null) {
|
|
24
28
|
return false;
|
|
25
|
-
|
|
29
|
+
}
|
|
30
|
+
if (stack.has(value)) {
|
|
26
31
|
return true;
|
|
27
|
-
|
|
32
|
+
}
|
|
33
|
+
if (value instanceof Map ||
|
|
34
|
+
value instanceof Set ||
|
|
35
|
+
value instanceof WeakMap ||
|
|
36
|
+
value instanceof WeakSet) {
|
|
28
37
|
return true;
|
|
38
|
+
}
|
|
29
39
|
stack.add(value);
|
|
30
40
|
try {
|
|
31
41
|
return Object.values(value).some((entry) => needsInspectableFormatting(entry, stack));
|
|
@@ -35,13 +45,15 @@ function needsInspectableFormatting(value, stack = new WeakSet()) {
|
|
|
35
45
|
}
|
|
36
46
|
}
|
|
37
47
|
function formatValue(value) {
|
|
38
|
-
if (typeof value === "string")
|
|
48
|
+
if (typeof value === "string") {
|
|
39
49
|
return value;
|
|
50
|
+
}
|
|
40
51
|
try {
|
|
41
52
|
if (!needsInspectableFormatting(value)) {
|
|
42
53
|
const json = JSON.stringify(value, null, 2);
|
|
43
|
-
if (json !== undefined)
|
|
54
|
+
if (json !== undefined) {
|
|
44
55
|
return json;
|
|
56
|
+
}
|
|
45
57
|
}
|
|
46
58
|
return formatWithOptions({ colors: false, depth: 6 }, value);
|
|
47
59
|
}
|
|
@@ -55,7 +67,9 @@ function toContentBlock(value) {
|
|
|
55
67
|
if (block.type === "text" && typeof block.text === "string") {
|
|
56
68
|
return { type: "text", text: block.text };
|
|
57
69
|
}
|
|
58
|
-
if (block.type === "image" &&
|
|
70
|
+
if (block.type === "image" &&
|
|
71
|
+
typeof block.data === "string" &&
|
|
72
|
+
typeof block.mimeType === "string") {
|
|
59
73
|
return { type: "image", data: block.data, mimeType: block.mimeType };
|
|
60
74
|
}
|
|
61
75
|
}
|
|
@@ -71,22 +85,43 @@ function abortReasonError(reason) {
|
|
|
71
85
|
return reason instanceof Error ? reason : new Error(String(reason ?? "MCP request aborted"));
|
|
72
86
|
}
|
|
73
87
|
function parseWorkerMessage(value) {
|
|
74
|
-
if (typeof value !== "object" || value === null)
|
|
88
|
+
if (typeof value !== "object" || value === null) {
|
|
75
89
|
return null;
|
|
90
|
+
}
|
|
76
91
|
const message = value;
|
|
77
92
|
// An older worker file, such as one restored by a rollback under a running Pi, neither charges the window nor sends a size.
|
|
78
|
-
if (message.type === "emit" && "block" in message)
|
|
79
|
-
return {
|
|
80
|
-
|
|
81
|
-
|
|
93
|
+
if (message.type === "emit" && "block" in message) {
|
|
94
|
+
return {
|
|
95
|
+
type: "emit",
|
|
96
|
+
block: message.block,
|
|
97
|
+
size: typeof message.size === "number" ? message.size : 0,
|
|
98
|
+
};
|
|
82
99
|
}
|
|
83
|
-
if (
|
|
100
|
+
if (message.type === "call" &&
|
|
101
|
+
typeof message.id === "number" &&
|
|
102
|
+
typeof message.path === "string") {
|
|
103
|
+
return {
|
|
104
|
+
type: "call",
|
|
105
|
+
id: message.id,
|
|
106
|
+
path: message.path,
|
|
107
|
+
...(message.args !== undefined ? { args: message.args } : {}),
|
|
108
|
+
...(typeof message.server === "string" ? { server: message.server } : {}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if ((message.type === "search" ||
|
|
112
|
+
message.type === "describe" ||
|
|
113
|
+
message.type === "resources" ||
|
|
114
|
+
message.type === "readResource" ||
|
|
115
|
+
message.type === "readResult") &&
|
|
116
|
+
typeof message.id === "number") {
|
|
84
117
|
return "input" in message
|
|
85
118
|
? { type: message.type, id: message.id, input: message.input }
|
|
86
119
|
: { type: message.type, id: message.id };
|
|
87
120
|
}
|
|
88
121
|
if (message.type === "done") {
|
|
89
|
-
return "returnBlock" in message
|
|
122
|
+
return "returnBlock" in message
|
|
123
|
+
? { type: "done", returnBlock: message.returnBlock }
|
|
124
|
+
: { type: "done" };
|
|
90
125
|
}
|
|
91
126
|
if (message.type === "error" && typeof message.message === "string") {
|
|
92
127
|
return { type: "error", message: message.message };
|
|
@@ -111,9 +146,11 @@ export const runMcpScript = async (...args) => {
|
|
|
111
146
|
}
|
|
112
147
|
};
|
|
113
148
|
async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT_TIMEOUT_MS, getPiTools, signal, toolCallId, beforeDispatch) {
|
|
114
|
-
const resolvedTimeoutMs = timeoutMs === null
|
|
115
|
-
?
|
|
116
|
-
:
|
|
149
|
+
const resolvedTimeoutMs = timeoutMs === null
|
|
150
|
+
? null
|
|
151
|
+
: Number.isFinite(timeoutMs) && timeoutMs > 0
|
|
152
|
+
? Math.floor(timeoutMs)
|
|
153
|
+
: DEFAULT_MCP_SCRIPT_TIMEOUT_MS;
|
|
117
154
|
const output = createMcpOutputSpool(resolveMcpOutputGuardOptions(state.config.settings, state.outputDirectory));
|
|
118
155
|
const externalSignal = combineAbortSignals(state.owner?.signal, signal);
|
|
119
156
|
const timeoutController = new AbortController();
|
|
@@ -131,15 +168,25 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
131
168
|
const recordCall = async (path, dispatch) => {
|
|
132
169
|
// Record before dispatch so calls still in flight at timeout/abort appear in the trace.
|
|
133
170
|
const startedAt = Date.now();
|
|
134
|
-
const index = calls.push({
|
|
171
|
+
const index = calls.push({
|
|
172
|
+
operation: "call",
|
|
173
|
+
path,
|
|
174
|
+
ok: false,
|
|
175
|
+
error: "incomplete",
|
|
176
|
+
durationMs: 0,
|
|
177
|
+
startedAt,
|
|
178
|
+
}) - 1;
|
|
135
179
|
const result = await dispatch();
|
|
136
180
|
const details = result.details;
|
|
137
|
-
if (typeof details.resultRef === "string")
|
|
181
|
+
if (typeof details.resultRef === "string") {
|
|
138
182
|
resultRefs.add(details.resultRef);
|
|
139
|
-
|
|
183
|
+
}
|
|
184
|
+
if (typeof details.resultWriteError === "string") {
|
|
140
185
|
payloadNotices.add(`[MCP result could not be saved: ${details.resultWriteError}]`);
|
|
141
|
-
|
|
186
|
+
}
|
|
187
|
+
for (const file of (details.payloadFiles ?? [])) {
|
|
142
188
|
payloadNotices.add(formatMcpPayloadFile(file));
|
|
189
|
+
}
|
|
143
190
|
if (details.error !== undefined) {
|
|
144
191
|
const errorCode = String(details.error);
|
|
145
192
|
const suggestions = Array.isArray(details.suggestions)
|
|
@@ -151,38 +198,63 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
151
198
|
? details.message
|
|
152
199
|
: textFromContent(result.content);
|
|
153
200
|
calls[index] = {
|
|
154
|
-
operation: "call",
|
|
201
|
+
operation: "call",
|
|
202
|
+
path,
|
|
203
|
+
ok: false,
|
|
204
|
+
error: errorCode,
|
|
205
|
+
durationMs: Date.now() - startedAt,
|
|
206
|
+
startedAt,
|
|
155
207
|
...(details.recovery !== undefined ? { recovery: details.recovery } : {}),
|
|
156
208
|
};
|
|
157
|
-
if (errorCode === "call_capture_failed")
|
|
209
|
+
if (errorCode === "call_capture_failed") {
|
|
158
210
|
throw new McpScriptCaptureError(details.recovery, message);
|
|
159
|
-
|
|
211
|
+
}
|
|
212
|
+
if (details.recovery !== undefined) {
|
|
160
213
|
output.push({ type: "text", text: message });
|
|
214
|
+
}
|
|
161
215
|
return {
|
|
162
216
|
ok: false,
|
|
163
217
|
...(details.mcpResult !== undefined ? { data: details.mcpResult } : {}),
|
|
164
218
|
...(details.resultRef ? { resultRef: details.resultRef } : {}),
|
|
165
|
-
error: {
|
|
219
|
+
error: {
|
|
220
|
+
code: errorCode,
|
|
221
|
+
message,
|
|
222
|
+
...(details.recovery ? { recovery: details.recovery } : {}),
|
|
223
|
+
},
|
|
166
224
|
};
|
|
167
225
|
}
|
|
168
|
-
calls[index] = {
|
|
226
|
+
calls[index] = {
|
|
227
|
+
operation: "call",
|
|
228
|
+
path,
|
|
229
|
+
ok: true,
|
|
230
|
+
durationMs: Date.now() - startedAt,
|
|
231
|
+
startedAt,
|
|
232
|
+
};
|
|
169
233
|
return {
|
|
170
234
|
ok: true,
|
|
171
235
|
data: details.mcpResult !== undefined ? details.mcpResult : textFromContent(result.content),
|
|
172
236
|
...(details.resultRef ? { resultRef: details.resultRef } : {}),
|
|
173
237
|
};
|
|
174
238
|
};
|
|
175
|
-
const callIdentity = (innerCallId) => ({
|
|
239
|
+
const callIdentity = (innerCallId) => ({
|
|
240
|
+
...(toolCallId !== undefined ? { toolCallId } : {}),
|
|
241
|
+
innerCallId,
|
|
242
|
+
});
|
|
176
243
|
const callTool = (innerCallId, path, args, server) => recordCall(path, () => executeCall(state, path, args, server, getPiTools, callSignal, callIdentity(innerCallId), beforeDispatch, { raw: true }));
|
|
177
244
|
const discoverServer = async (server) => {
|
|
178
|
-
if (!server)
|
|
245
|
+
if (!server) {
|
|
179
246
|
return undefined;
|
|
180
|
-
|
|
247
|
+
}
|
|
248
|
+
if (isServerDisabled(state.config.mcpServers[server])) {
|
|
181
249
|
return { code: "server_disabled", message: `Server "${server}" is disabled.` };
|
|
182
|
-
|
|
250
|
+
}
|
|
251
|
+
if (state.config.mcpServers[server] && state.toolMetadata.has(server)) {
|
|
183
252
|
return undefined;
|
|
253
|
+
}
|
|
184
254
|
const result = await executeConnect(state, server, callSignal);
|
|
185
|
-
return result.details.error
|
|
255
|
+
return result.details.error
|
|
256
|
+
? { code: result.details.error, message: textFromContent(result.content) }
|
|
257
|
+
: undefined;
|
|
186
258
|
};
|
|
187
259
|
const searchTools = async (input) => {
|
|
188
260
|
const startedAt = Date.now();
|
|
@@ -196,13 +268,15 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
196
268
|
error = discoveryError.code;
|
|
197
269
|
return { error: discoveryError, coverage };
|
|
198
270
|
}
|
|
199
|
-
if (query.trim() === "")
|
|
271
|
+
if (query.trim() === "") {
|
|
200
272
|
return { items: [], total: 0, hasMore: false, nextOffset: null, coverage };
|
|
273
|
+
}
|
|
201
274
|
const limit = typeof input?.limit === "number" ? input.limit : 12;
|
|
202
275
|
const offset = typeof input?.offset === "number" ? input.offset : 0;
|
|
203
|
-
const page = paginate(rankToolMatches(state, query, server).filter(match => !match.tool.resourceUri), offset, limit);
|
|
276
|
+
const page = paginate(rankToolMatches(state, query, server).filter((match) => !match.tool.resourceUri), offset, limit);
|
|
204
277
|
return {
|
|
205
|
-
...page,
|
|
278
|
+
...page,
|
|
279
|
+
coverage,
|
|
206
280
|
items: page.items.map(({ server: matchServer, tool, score }) => ({
|
|
207
281
|
path: tool.name,
|
|
208
282
|
name: tool.originalName,
|
|
@@ -219,7 +293,14 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
219
293
|
finally {
|
|
220
294
|
calls.push(error === undefined
|
|
221
295
|
? { operation: "search", query, ok: true, durationMs: Date.now() - startedAt, startedAt }
|
|
222
|
-
: {
|
|
296
|
+
: {
|
|
297
|
+
operation: "search",
|
|
298
|
+
query,
|
|
299
|
+
ok: false,
|
|
300
|
+
error: error instanceof Error ? error.message : String(error),
|
|
301
|
+
durationMs: Date.now() - startedAt,
|
|
302
|
+
startedAt,
|
|
303
|
+
});
|
|
223
304
|
}
|
|
224
305
|
};
|
|
225
306
|
const describeTool = async (input) => {
|
|
@@ -234,17 +315,24 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
234
315
|
return { path, error: discoveryError };
|
|
235
316
|
}
|
|
236
317
|
const matches = [...state.toolMetadata].flatMap(([server, metadata]) => {
|
|
237
|
-
if ((selectedServer && server !== selectedServer) ||
|
|
318
|
+
if ((selectedServer && server !== selectedServer) ||
|
|
319
|
+
!state.config.mcpServers[server] ||
|
|
320
|
+
isServerDisabled(state.config.mcpServers[server])) {
|
|
238
321
|
return [];
|
|
239
|
-
|
|
240
|
-
const
|
|
241
|
-
|
|
322
|
+
}
|
|
323
|
+
const tools = metadata.filter((tool) => !tool.resourceUri);
|
|
324
|
+
const originals = selectedServer ? tools.filter((tool) => tool.originalName === path) : [];
|
|
325
|
+
return (originals.length ? originals : tools.filter((tool) => tool.name === path)).map((tool) => toToolDescriptor(server, tool));
|
|
242
326
|
});
|
|
243
|
-
if (matches.length === 1)
|
|
327
|
+
if (matches.length === 1) {
|
|
244
328
|
return matches[0];
|
|
329
|
+
}
|
|
245
330
|
if (matches.length > 1) {
|
|
246
331
|
error = "ambiguous_tool";
|
|
247
|
-
return {
|
|
332
|
+
return {
|
|
333
|
+
path,
|
|
334
|
+
error: { code: "ambiguous_tool", message: "Specify server and exact tool name." },
|
|
335
|
+
};
|
|
248
336
|
}
|
|
249
337
|
const suggestions = path ? rankSuggestions(state, path, 5, selectedServer) : [];
|
|
250
338
|
error = "tool_not_found";
|
|
@@ -264,7 +352,14 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
264
352
|
finally {
|
|
265
353
|
calls.push(error === undefined
|
|
266
354
|
? { operation: "describe", path, ok: true, durationMs: Date.now() - startedAt, startedAt }
|
|
267
|
-
: {
|
|
355
|
+
: {
|
|
356
|
+
operation: "describe",
|
|
357
|
+
path,
|
|
358
|
+
ok: false,
|
|
359
|
+
error: error instanceof Error ? error.message : String(error),
|
|
360
|
+
durationMs: Date.now() - startedAt,
|
|
361
|
+
startedAt,
|
|
362
|
+
});
|
|
268
363
|
}
|
|
269
364
|
};
|
|
270
365
|
let worker;
|
|
@@ -287,19 +382,22 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
287
382
|
let completed = false;
|
|
288
383
|
activeWorker.on("message", (value) => {
|
|
289
384
|
const message = parseWorkerMessage(value);
|
|
290
|
-
if (!message || completed)
|
|
385
|
+
if (!message || completed) {
|
|
291
386
|
return;
|
|
387
|
+
}
|
|
292
388
|
if (message.type === "emit") {
|
|
293
389
|
output.push(toContentBlock(message.block));
|
|
294
390
|
const queued = Atomics.sub(outputFlow, 0, message.size) - message.size;
|
|
295
|
-
if (queued <= SCRIPT_OUTPUT_WINDOW / 2)
|
|
391
|
+
if (queued <= SCRIPT_OUTPUT_WINDOW / 2) {
|
|
296
392
|
Atomics.notify(outputFlow, 0);
|
|
393
|
+
}
|
|
297
394
|
return;
|
|
298
395
|
}
|
|
299
396
|
if (message.type === "done") {
|
|
300
397
|
completed = true;
|
|
301
|
-
if ("returnBlock" in message)
|
|
398
|
+
if ("returnBlock" in message) {
|
|
302
399
|
output.push(toContentBlock(message.returnBlock));
|
|
400
|
+
}
|
|
303
401
|
resolve();
|
|
304
402
|
return;
|
|
305
403
|
}
|
|
@@ -324,12 +422,23 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
324
422
|
}
|
|
325
423
|
else {
|
|
326
424
|
const input = message.input;
|
|
327
|
-
if (typeof input?.server !== "string" ||
|
|
328
|
-
|
|
425
|
+
if (typeof input?.server !== "string" ||
|
|
426
|
+
!input.server ||
|
|
427
|
+
(message.type === "readResource" && (typeof input.uri !== "string" || !input.uri))) {
|
|
428
|
+
envelope = {
|
|
429
|
+
error: {
|
|
430
|
+
code: "invalid_arguments",
|
|
431
|
+
message: "Specify server and, for reads, resource uri.",
|
|
432
|
+
},
|
|
433
|
+
};
|
|
329
434
|
}
|
|
330
435
|
else if (message.type === "resources") {
|
|
331
436
|
const result = await executeResourceList(state, input.server, typeof input.limit === "number" ? input.limit : 12, typeof input.offset === "number" ? input.offset : 0, callSignal);
|
|
332
|
-
envelope = result.details.error
|
|
437
|
+
envelope = result.details.error
|
|
438
|
+
? {
|
|
439
|
+
error: { code: result.details.error, message: textFromContent(result.content) },
|
|
440
|
+
}
|
|
441
|
+
: result.details;
|
|
333
442
|
}
|
|
334
443
|
else {
|
|
335
444
|
const server = input.server;
|
|
@@ -345,13 +454,15 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
345
454
|
});
|
|
346
455
|
activeWorker.once("error", reject);
|
|
347
456
|
activeWorker.once("exit", (code) => {
|
|
348
|
-
if (!completed && code !== 0)
|
|
457
|
+
if (!completed && code !== 0) {
|
|
349
458
|
reject(new Error(`mcp_script worker exited with code ${code}`));
|
|
459
|
+
}
|
|
350
460
|
});
|
|
351
461
|
});
|
|
352
462
|
const timeout = new Promise((_resolve, reject) => {
|
|
353
|
-
if (resolvedTimeoutMs === null)
|
|
463
|
+
if (resolvedTimeoutMs === null) {
|
|
354
464
|
return;
|
|
465
|
+
}
|
|
355
466
|
const timeoutError = new McpScriptTimeoutError(resolvedTimeoutMs);
|
|
356
467
|
timer = setTimeout(() => {
|
|
357
468
|
timeoutController.abort(timeoutError);
|
|
@@ -403,12 +514,15 @@ async function runMcpScriptOperation(state, code, timeoutMs = DEFAULT_MCP_SCRIPT
|
|
|
403
514
|
await worker?.terminate();
|
|
404
515
|
}
|
|
405
516
|
const callsSnapshot = snapshotCalls();
|
|
406
|
-
if (output.empty)
|
|
517
|
+
if (output.empty) {
|
|
407
518
|
output.push({ type: "text", text: "(no output)" });
|
|
408
|
-
|
|
519
|
+
}
|
|
520
|
+
for (const text of payloadNotices) {
|
|
409
521
|
output.push({ type: "text", text });
|
|
410
|
-
|
|
522
|
+
}
|
|
523
|
+
for (const ref of resultRefs) {
|
|
411
524
|
output.push({ type: "text", text: formatMcpResultReference(ref) });
|
|
525
|
+
}
|
|
412
526
|
const guarded = output.finish();
|
|
413
527
|
return {
|
|
414
528
|
content: guarded.content,
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
2
|
+
"use strict";
|
|
3
3
|
|
|
4
|
-
const { createRequire } = require(
|
|
5
|
-
const { dirname, join } = require(
|
|
4
|
+
const { createRequire } = require("node:module");
|
|
5
|
+
const { dirname, join } = require("node:path");
|
|
6
6
|
|
|
7
7
|
const requireFromHere = createRequire(__filename);
|
|
8
8
|
|
|
9
|
+
/** @param {unknown} error @returns {unknown} */
|
|
10
|
+
function errorMessage(error) {
|
|
11
|
+
if (
|
|
12
|
+
error !== null &&
|
|
13
|
+
(typeof error === "object" || typeof error === "function") &&
|
|
14
|
+
"message" in error
|
|
15
|
+
) {
|
|
16
|
+
return error.message;
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
9
21
|
function loadKeyringEntryClass() {
|
|
10
22
|
try {
|
|
11
|
-
return requireFromHere(
|
|
23
|
+
return requireFromHere("@napi-rs/keyring").Entry;
|
|
12
24
|
} catch (loaderError) {
|
|
13
25
|
const suffixes = getNativeBindingSuffixes(process.platform, process.arch);
|
|
14
26
|
let lastError;
|
|
@@ -20,38 +32,50 @@ function loadKeyringEntryClass() {
|
|
|
20
32
|
lastError = error;
|
|
21
33
|
}
|
|
22
34
|
}
|
|
23
|
-
const error = new Error(
|
|
35
|
+
const error = new Error(
|
|
36
|
+
`Failed to load @napi-rs/keyring in recovery helper: ${errorMessage(lastError) ?? errorMessage(loaderError)}`,
|
|
37
|
+
);
|
|
24
38
|
error.cause = loaderError;
|
|
25
39
|
throw error;
|
|
26
40
|
}
|
|
27
41
|
}
|
|
28
42
|
|
|
43
|
+
/** @param {string} platform @param {string} arch */
|
|
29
44
|
function getNativeBindingSuffixes(platform, arch) {
|
|
30
|
-
if (platform ===
|
|
31
|
-
if (arch ===
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (arch ===
|
|
45
|
+
if (platform === "linux") {
|
|
46
|
+
if (arch === "arm64") {
|
|
47
|
+
return ["linux-arm64-gnu", "linux-arm64-musl"];
|
|
48
|
+
}
|
|
49
|
+
if (arch === "arm") {
|
|
50
|
+
return ["linux-arm-gnueabihf"];
|
|
51
|
+
}
|
|
52
|
+
if (arch === "riscv64") {
|
|
53
|
+
return ["linux-riscv64-gnu"];
|
|
54
|
+
}
|
|
55
|
+
if (arch === "x64") {
|
|
56
|
+
return ["linux-x64-gnu", "linux-x64-musl"];
|
|
57
|
+
}
|
|
35
58
|
}
|
|
36
59
|
return [];
|
|
37
60
|
}
|
|
38
61
|
|
|
39
62
|
function readStdin() {
|
|
40
63
|
return new Promise((resolve, reject) => {
|
|
41
|
-
let input =
|
|
42
|
-
process.stdin.setEncoding(
|
|
43
|
-
process.stdin.on(
|
|
64
|
+
let input = "";
|
|
65
|
+
process.stdin.setEncoding("utf8");
|
|
66
|
+
process.stdin.on("data", (chunk) => {
|
|
44
67
|
input += chunk;
|
|
45
68
|
if (input.length > 1024 * 1024) {
|
|
46
|
-
reject(new Error(
|
|
69
|
+
reject(new Error("request too large"));
|
|
47
70
|
process.stdin.destroy();
|
|
48
71
|
}
|
|
49
72
|
});
|
|
50
|
-
process.stdin.on(
|
|
51
|
-
process.stdin.on(
|
|
73
|
+
process.stdin.on("error", reject);
|
|
74
|
+
process.stdin.on("end", () => resolve(input));
|
|
52
75
|
});
|
|
53
76
|
}
|
|
54
77
|
|
|
78
|
+
/** @param {unknown} response */
|
|
55
79
|
function writeResponse(response) {
|
|
56
80
|
process.stdout.write(`${JSON.stringify(response)}\n`);
|
|
57
81
|
}
|
|
@@ -59,22 +83,32 @@ function writeResponse(response) {
|
|
|
59
83
|
(async () => {
|
|
60
84
|
try {
|
|
61
85
|
const request = JSON.parse(await readStdin());
|
|
62
|
-
if (!request || typeof request !==
|
|
86
|
+
if (!request || typeof request !== "object") {
|
|
87
|
+
throw new Error("invalid request");
|
|
88
|
+
}
|
|
63
89
|
const { operation, service, account, payload } = request;
|
|
64
|
-
if (![
|
|
65
|
-
|
|
66
|
-
|
|
90
|
+
if (!["read", "write", "remove"].includes(operation)) {
|
|
91
|
+
throw new Error("invalid operation");
|
|
92
|
+
}
|
|
93
|
+
if (typeof service !== "string" || !service) {
|
|
94
|
+
throw new Error("invalid service");
|
|
95
|
+
}
|
|
96
|
+
if (typeof account !== "string" || !account) {
|
|
97
|
+
throw new Error("invalid account");
|
|
98
|
+
}
|
|
67
99
|
|
|
68
100
|
const Entry = loadKeyringEntryClass();
|
|
69
101
|
const entry = new Entry(service, account);
|
|
70
102
|
|
|
71
|
-
if (operation ===
|
|
103
|
+
if (operation === "read") {
|
|
72
104
|
const value = entry.getPassword();
|
|
73
105
|
writeResponse(value === null ? { ok: true, found: false } : { ok: true, found: true, value });
|
|
74
106
|
return;
|
|
75
107
|
}
|
|
76
|
-
if (operation ===
|
|
77
|
-
if (typeof payload !==
|
|
108
|
+
if (operation === "write") {
|
|
109
|
+
if (typeof payload !== "string") {
|
|
110
|
+
throw new Error("invalid payload");
|
|
111
|
+
}
|
|
78
112
|
entry.setPassword(payload);
|
|
79
113
|
writeResponse({ ok: true });
|
|
80
114
|
return;
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* Implementation of the MCP SDK's OAuthClientProvider interface.
|
|
5
5
|
* Handles OAuth client registration, token storage, and authorization redirection.
|
|
6
6
|
*/
|
|
7
|
-
import { type FetchLike, type AddClientAuthentication, type OAuthClientProvider, type OAuthClientInformationContext, type StoredOAuthClientInformation, type StoredOAuthTokens, type OAuthDiscoveryState } from "@modelcontextprotocol/client";
|
|
8
|
-
import type { OAuthClientMetadata } from "@modelcontextprotocol/client";
|
|
7
|
+
import { type FetchLike, type AddClientAuthentication, type OAuthClientProvider, type OAuthClientInformationContext, type StoredOAuthClientInformation, type StoredOAuthTokens, type OAuthDiscoveryState, type OAuthClientMetadata } from "@modelcontextprotocol/client";
|
|
9
8
|
import { type AuthStorageOptions } from "./mcp-auth.ts";
|
|
10
9
|
import { type OAuthPrivateKeyJwtConfig, type OAuthCrossAppAccessConfig } from "./types.ts";
|
|
11
10
|
/** Validate explicit document URLs without echoing possible credentials in errors. */
|
|
@@ -54,11 +53,11 @@ export declare class McpOAuthProvider implements OAuthClientProvider {
|
|
|
54
53
|
private flowDiscoveryState;
|
|
55
54
|
private flowIssuerMismatch;
|
|
56
55
|
private flowState;
|
|
57
|
-
private serverName;
|
|
58
|
-
private serverUrl;
|
|
59
|
-
private config;
|
|
60
|
-
private callbacks;
|
|
61
|
-
private storageOptions;
|
|
56
|
+
private readonly serverName;
|
|
57
|
+
private readonly serverUrl;
|
|
58
|
+
private readonly config;
|
|
59
|
+
private readonly callbacks;
|
|
60
|
+
private readonly storageOptions;
|
|
62
61
|
constructor(serverName: string, serverUrl: string, config: McpOAuthConfig, callbacks: McpOAuthCallbacks, storageOptions?: AuthStorageOptions, runtimeSignal?: AbortSignal, initialState?: string);
|
|
63
62
|
private get isNonInteractive();
|
|
64
63
|
private get discoveredIssuer();
|