@wrongstack/mcp 1.0.10 → 1.0.11
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/dist/authorization.d.ts +6 -0
- package/dist/client.d.ts +8 -0
- package/dist/index.js +430 -191
- package/dist/registry-connect-loop.d.ts +1 -0
- package/dist/registry-slots.d.ts +11 -1
- package/dist/registry-types.d.ts +6 -0
- package/dist/registry.d.ts +31 -1
- package/dist/sse-reader.d.ts +8 -0
- package/dist/token-store.d.ts +2 -0
- package/dist/tool-schema.d.ts +14 -0
- package/dist/transport-jsonrpc.d.ts +6 -0
- package/dist/transport-sse.d.ts +25 -3
- package/dist/transport-streamable.d.ts +1 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -5,6 +5,14 @@ import * as http from "node:http";
|
|
|
5
5
|
import * as https from "node:https";
|
|
6
6
|
import * as net from "node:net";
|
|
7
7
|
import { isPrivateIPv4, isPrivateIPv6 } from "@wrongstack/core/utils";
|
|
8
|
+
var MCPOAuthHttpError = class extends Error {
|
|
9
|
+
constructor(message, status) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.status = status;
|
|
12
|
+
}
|
|
13
|
+
status;
|
|
14
|
+
name = "MCPOAuthHttpError";
|
|
15
|
+
};
|
|
8
16
|
function canonicalMcpResource(rawUrl) {
|
|
9
17
|
let url;
|
|
10
18
|
try {
|
|
@@ -299,7 +307,8 @@ async function exchangeMcpAuthorizationCode(options) {
|
|
|
299
307
|
timeoutMs: options.timeoutMs,
|
|
300
308
|
maxResponseBytes: options.maxResponseBytes,
|
|
301
309
|
lookup: options.lookup,
|
|
302
|
-
allowedLoopbackHostname: loopbackHostnameForResource(resource)
|
|
310
|
+
allowedLoopbackHostname: loopbackHostnameForResource(resource),
|
|
311
|
+
label: "token endpoint"
|
|
303
312
|
});
|
|
304
313
|
if (response === void 0) throw new Error("MCP OAuth token endpoint returned no response");
|
|
305
314
|
return parseTokenResponse(response, resource);
|
|
@@ -321,7 +330,8 @@ async function refreshMcpAccessToken(options) {
|
|
|
321
330
|
timeoutMs: options.timeoutMs,
|
|
322
331
|
maxResponseBytes: options.maxResponseBytes,
|
|
323
332
|
lookup: options.lookup,
|
|
324
|
-
allowedLoopbackHostname: loopbackHostnameForResource(resource)
|
|
333
|
+
allowedLoopbackHostname: loopbackHostnameForResource(resource),
|
|
334
|
+
label: "token endpoint"
|
|
325
335
|
});
|
|
326
336
|
if (response === void 0) throw new Error("MCP OAuth token endpoint returned no response");
|
|
327
337
|
const parsed = parseTokenResponse(response, resource);
|
|
@@ -412,7 +422,8 @@ async function discoverFirst(candidates, fetchJson, signal, parse, label) {
|
|
|
412
422
|
throw new Error(`MCP ${label} discovery failed (${failures.join("; ")})`);
|
|
413
423
|
}
|
|
414
424
|
async function requestPinnedJson(rawUrl, options) {
|
|
415
|
-
const
|
|
425
|
+
const label = options.label ?? "discovery";
|
|
426
|
+
const url = secureOAuthUrl(rawUrl, `${label} URL`);
|
|
416
427
|
const target = await resolvePinnedAddress(url, options);
|
|
417
428
|
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
418
429
|
const maxBytes = options.maxResponseBytes ?? 64 * 1024;
|
|
@@ -456,12 +467,12 @@ async function requestPinnedJson(rawUrl, options) {
|
|
|
456
467
|
}
|
|
457
468
|
if (status >= 300 && status < 400) {
|
|
458
469
|
response.resume();
|
|
459
|
-
finish(new Error(
|
|
470
|
+
finish(new Error(`MCP OAuth ${label} redirects are not allowed`));
|
|
460
471
|
return;
|
|
461
472
|
}
|
|
462
473
|
if (status < 200 || status >= 300) {
|
|
463
474
|
response.resume();
|
|
464
|
-
finish(new
|
|
475
|
+
finish(new MCPOAuthHttpError(`MCP OAuth ${label} HTTP ${status}`, status));
|
|
465
476
|
return;
|
|
466
477
|
}
|
|
467
478
|
const contentType = response.headers["content-type"] ?? "";
|
|
@@ -1127,6 +1138,34 @@ function parseGetPromptResult(value) {
|
|
|
1127
1138
|
}
|
|
1128
1139
|
|
|
1129
1140
|
// src/tool-schema.ts
|
|
1141
|
+
var MAX_TOOL_PAGES = 100;
|
|
1142
|
+
var MAX_TOOLS = 1e4;
|
|
1143
|
+
async function listAllTools(requestPage) {
|
|
1144
|
+
const tools = [];
|
|
1145
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
1146
|
+
let cursor;
|
|
1147
|
+
for (let page = 0; page < MAX_TOOL_PAGES; page++) {
|
|
1148
|
+
let response;
|
|
1149
|
+
try {
|
|
1150
|
+
response = await requestPage(cursor ? { cursor } : {});
|
|
1151
|
+
} catch (err) {
|
|
1152
|
+
if (page === 0) throw err;
|
|
1153
|
+
break;
|
|
1154
|
+
}
|
|
1155
|
+
if (response.error) {
|
|
1156
|
+
if (page === 0) return null;
|
|
1157
|
+
break;
|
|
1158
|
+
}
|
|
1159
|
+
const result = response.result;
|
|
1160
|
+
tools.push(...normalizeMCPTools(result?.tools));
|
|
1161
|
+
const next = result?.nextCursor;
|
|
1162
|
+
if (typeof next !== "string" || next.length === 0) break;
|
|
1163
|
+
if (seenCursors.has(next) || tools.length >= MAX_TOOLS) break;
|
|
1164
|
+
seenCursors.add(next);
|
|
1165
|
+
cursor = next;
|
|
1166
|
+
}
|
|
1167
|
+
return tools.slice(0, MAX_TOOLS);
|
|
1168
|
+
}
|
|
1130
1169
|
function normalizeMCPTools(value) {
|
|
1131
1170
|
if (!Array.isArray(value)) return [];
|
|
1132
1171
|
const tools = [];
|
|
@@ -1164,6 +1203,20 @@ var SSE_READER_MAX_DATA_LINES = 1024;
|
|
|
1164
1203
|
var SSEReader = class {
|
|
1165
1204
|
buffer = "";
|
|
1166
1205
|
dataLines = [];
|
|
1206
|
+
eventName = "";
|
|
1207
|
+
endpointListeners = [];
|
|
1208
|
+
/**
|
|
1209
|
+
* Legacy HTTP+SSE transport: the server's first event is
|
|
1210
|
+
* `event: endpoint` whose data is the (relative) URL to POST requests to.
|
|
1211
|
+
* Its payload is a URL, not JSON, so it is dispatched separately.
|
|
1212
|
+
*/
|
|
1213
|
+
onEndpoint(cb) {
|
|
1214
|
+
this.endpointListeners.push(cb);
|
|
1215
|
+
return () => {
|
|
1216
|
+
const idx = this.endpointListeners.indexOf(cb);
|
|
1217
|
+
if (idx >= 0) this.endpointListeners.splice(idx, 1);
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1167
1220
|
listeners = [];
|
|
1168
1221
|
onMessage(cb) {
|
|
1169
1222
|
this.listeners.push(cb);
|
|
@@ -1216,6 +1269,7 @@ var SSEReader = class {
|
|
|
1216
1269
|
let value = colonIdx === -1 ? "" : line.slice(colonIdx + 1);
|
|
1217
1270
|
if (value.startsWith(" ")) value = value.slice(1);
|
|
1218
1271
|
if (field === "event") {
|
|
1272
|
+
this.eventName = value;
|
|
1219
1273
|
} else if (field === "data") {
|
|
1220
1274
|
if (this.dataLines.length >= SSE_READER_MAX_DATA_LINES) {
|
|
1221
1275
|
throw new ToolError({
|
|
@@ -1233,12 +1287,23 @@ var SSEReader = class {
|
|
|
1233
1287
|
}
|
|
1234
1288
|
}
|
|
1235
1289
|
flush() {
|
|
1290
|
+
const eventName = this.eventName;
|
|
1291
|
+
this.eventName = "";
|
|
1236
1292
|
if (this.dataLines.length === 0) {
|
|
1237
1293
|
return;
|
|
1238
1294
|
}
|
|
1239
1295
|
const data = this.dataLines.join("\n").trim();
|
|
1240
1296
|
this.dataLines = [];
|
|
1241
1297
|
if (!data) return;
|
|
1298
|
+
if (eventName === "endpoint") {
|
|
1299
|
+
for (const cb of this.endpointListeners) {
|
|
1300
|
+
try {
|
|
1301
|
+
cb(data);
|
|
1302
|
+
} catch {
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1242
1307
|
try {
|
|
1243
1308
|
const parsed = JSON.parse(data);
|
|
1244
1309
|
this.dispatch(parsed);
|
|
@@ -1256,7 +1321,9 @@ var SSEReader = class {
|
|
|
1256
1321
|
reset() {
|
|
1257
1322
|
this.buffer = "";
|
|
1258
1323
|
this.dataLines = [];
|
|
1324
|
+
this.eventName = "";
|
|
1259
1325
|
this.listeners = [];
|
|
1326
|
+
this.endpointListeners = [];
|
|
1260
1327
|
}
|
|
1261
1328
|
};
|
|
1262
1329
|
|
|
@@ -1688,6 +1755,11 @@ var BaseHTTPTransport = class {
|
|
|
1688
1755
|
|
|
1689
1756
|
// src/transport-jsonrpc.ts
|
|
1690
1757
|
import { ToolError as ToolError2 } from "@wrongstack/core/types";
|
|
1758
|
+
function encodeJsonRpcMessage(id, method, params) {
|
|
1759
|
+
return JSON.stringify(
|
|
1760
|
+
method.startsWith("notifications/") ? { jsonrpc: "2.0", method, params } : { jsonrpc: "2.0", id, method, params }
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1691
1763
|
function isJsonRpcResult(v) {
|
|
1692
1764
|
if (typeof v !== "object" || v === null) return false;
|
|
1693
1765
|
const r = v;
|
|
@@ -1813,11 +1885,20 @@ async function readBodyCapped(res, maxBytes = MAX_MCP_HTTP_BODY_BYTES) {
|
|
|
1813
1885
|
}
|
|
1814
1886
|
|
|
1815
1887
|
// src/transport-sse.ts
|
|
1888
|
+
var ENDPOINT_WAIT_MS = 1e3;
|
|
1889
|
+
var SESSION_FATAL_HTTP_STATUSES = /* @__PURE__ */ new Set([401, 403, 404, 410]);
|
|
1816
1890
|
var SSETransport = class extends BaseHTTPTransport {
|
|
1817
1891
|
_nextId = 1;
|
|
1818
1892
|
readerDone = false;
|
|
1893
|
+
closed = false;
|
|
1819
1894
|
readLoopAbort;
|
|
1820
1895
|
reader;
|
|
1896
|
+
/** POST target announced by the server's `endpoint` event. */
|
|
1897
|
+
endpointUrl;
|
|
1898
|
+
/** Wakes connect() once the endpoint is known (or will not arrive). */
|
|
1899
|
+
streamSignal;
|
|
1900
|
+
/** Requests whose response is expected over the event stream, keyed by id. */
|
|
1901
|
+
streamPending = /* @__PURE__ */ new Map();
|
|
1821
1902
|
constructor(opts) {
|
|
1822
1903
|
super(opts, "SSETransport");
|
|
1823
1904
|
}
|
|
@@ -1829,18 +1910,13 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1829
1910
|
/** Refresh tool list when server sends notifications/tools/list_changed. */
|
|
1830
1911
|
async handleToolsListChanged() {
|
|
1831
1912
|
try {
|
|
1832
|
-
const
|
|
1833
|
-
if (!
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
...
|
|
1838
|
-
|
|
1839
|
-
for (const cb of this.toolsChangedListeners) {
|
|
1840
|
-
try {
|
|
1841
|
-
cb([...this.tools]);
|
|
1842
|
-
} catch {
|
|
1843
|
-
}
|
|
1913
|
+
const tools = await listAllTools((params) => this.httpPost("tools/list", params));
|
|
1914
|
+
if (!tools) return;
|
|
1915
|
+
this.tools.splice(0, this.tools.length, ...tools);
|
|
1916
|
+
for (const cb of this.toolsChangedListeners) {
|
|
1917
|
+
try {
|
|
1918
|
+
cb([...this.tools]);
|
|
1919
|
+
} catch {
|
|
1844
1920
|
}
|
|
1845
1921
|
}
|
|
1846
1922
|
} catch {
|
|
@@ -1848,6 +1924,8 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1848
1924
|
}
|
|
1849
1925
|
async connect() {
|
|
1850
1926
|
this.readerDone = false;
|
|
1927
|
+
this.closed = false;
|
|
1928
|
+
this.endpointUrl = void 0;
|
|
1851
1929
|
this.state = "connecting";
|
|
1852
1930
|
this.serverMetadata = void 0;
|
|
1853
1931
|
this.abortController = new AbortController();
|
|
@@ -1856,7 +1934,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1856
1934
|
try {
|
|
1857
1935
|
const sseUrl = this.buildSSEUrl();
|
|
1858
1936
|
const fetchOpts = {
|
|
1859
|
-
headers: this.headers,
|
|
1937
|
+
headers: { Accept: "text/event-stream", ...this.headers },
|
|
1860
1938
|
signal
|
|
1861
1939
|
};
|
|
1862
1940
|
this.applyTlsAgent(fetchOpts);
|
|
@@ -1880,7 +1958,15 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1880
1958
|
const textDecoder = new TextDecoder();
|
|
1881
1959
|
const sseReader = new SSEReader();
|
|
1882
1960
|
this.readLoopAbort = new AbortController();
|
|
1961
|
+
const streamReady = new Promise((resolve) => {
|
|
1962
|
+
this.streamSignal = resolve;
|
|
1963
|
+
});
|
|
1964
|
+
sseReader.onEndpoint((endpoint) => {
|
|
1965
|
+
this.acceptEndpoint(endpoint);
|
|
1966
|
+
this.streamSignal?.();
|
|
1967
|
+
});
|
|
1883
1968
|
sseReader.onMessage((msg) => {
|
|
1969
|
+
this.streamSignal?.();
|
|
1884
1970
|
if (msg.method && !msg.id) {
|
|
1885
1971
|
if (msg.method === "notifications/tools/list_changed") {
|
|
1886
1972
|
void this.handleToolsListChanged();
|
|
@@ -1889,6 +1975,14 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1889
1975
|
} else if (msg.method === "notifications/prompts/list_changed") {
|
|
1890
1976
|
this.notifyPromptsChanged();
|
|
1891
1977
|
}
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
if (!msg.method && isJsonRpcResult(msg)) {
|
|
1981
|
+
const pending = this.streamPending.get(msg.id);
|
|
1982
|
+
if (pending) {
|
|
1983
|
+
this.streamPending.delete(msg.id);
|
|
1984
|
+
pending.resolve(msg);
|
|
1985
|
+
}
|
|
1892
1986
|
}
|
|
1893
1987
|
});
|
|
1894
1988
|
const reader = response.body.getReader();
|
|
@@ -1896,10 +1990,13 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1896
1990
|
cancel: () => reader.cancel(),
|
|
1897
1991
|
releaseLock: () => reader.releaseLock()
|
|
1898
1992
|
};
|
|
1899
|
-
this.readSSEBody(reader, textDecoder, sseReader);
|
|
1993
|
+
void this.readSSEBody(reader, textDecoder, sseReader);
|
|
1994
|
+
await this.waitForStream(streamReady, signal);
|
|
1900
1995
|
const initRes = await this.httpPost("initialize", {
|
|
1901
1996
|
protocolVersion: MCP_CONSTANTS.PROTOCOL_VERSION,
|
|
1902
|
-
capabilities
|
|
1997
|
+
// Client capabilities (roots/sampling/elicitation) — none are offered.
|
|
1998
|
+
// `tools` is a SERVER capability and never belonged here.
|
|
1999
|
+
capabilities: {},
|
|
1903
2000
|
clientInfo: MCP_CONSTANTS.CLIENT_INFO
|
|
1904
2001
|
});
|
|
1905
2002
|
if (initRes.error) {
|
|
@@ -1916,13 +2013,8 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1916
2013
|
await this.httpPost("notifications/initialized", {});
|
|
1917
2014
|
} catch {
|
|
1918
2015
|
}
|
|
1919
|
-
const
|
|
1920
|
-
|
|
1921
|
-
this.tools.splice(0, this.tools.length);
|
|
1922
|
-
} else {
|
|
1923
|
-
const result = toolsRes.result;
|
|
1924
|
-
this.tools.splice(0, this.tools.length, ...normalizeMCPTools(result?.tools));
|
|
1925
|
-
}
|
|
2016
|
+
const tools = await listAllTools((params) => this.httpPost("tools/list", params));
|
|
2017
|
+
this.tools.splice(0, this.tools.length, ...tools ?? []);
|
|
1926
2018
|
this.state = "connected";
|
|
1927
2019
|
clearTimeout(startupTimer);
|
|
1928
2020
|
} catch (err) {
|
|
@@ -1930,6 +2022,43 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1930
2022
|
this.state = "failed";
|
|
1931
2023
|
this.abortController.abort();
|
|
1932
2024
|
throw err;
|
|
2025
|
+
} finally {
|
|
2026
|
+
this.streamSignal = void 0;
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
/** Resolve once the stream announced its endpoint, emitted anything, ended, or the cap passed. */
|
|
2030
|
+
async waitForStream(ready, signal) {
|
|
2031
|
+
let timer;
|
|
2032
|
+
let onAbort;
|
|
2033
|
+
try {
|
|
2034
|
+
await Promise.race([
|
|
2035
|
+
ready,
|
|
2036
|
+
new Promise((resolve) => {
|
|
2037
|
+
timer = setTimeout(resolve, Math.min(ENDPOINT_WAIT_MS, this.timeout));
|
|
2038
|
+
timer.unref?.();
|
|
2039
|
+
}),
|
|
2040
|
+
new Promise((resolve) => {
|
|
2041
|
+
onAbort = resolve;
|
|
2042
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2043
|
+
})
|
|
2044
|
+
]);
|
|
2045
|
+
} finally {
|
|
2046
|
+
clearTimeout(timer);
|
|
2047
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
/**
|
|
2051
|
+
* Adopt the server's POST endpoint. It must stay on the configured origin:
|
|
2052
|
+
* a cross-origin endpoint would move every request — Authorization header
|
|
2053
|
+
* included — to a host that never passed configuration review.
|
|
2054
|
+
*/
|
|
2055
|
+
acceptEndpoint(endpoint) {
|
|
2056
|
+
try {
|
|
2057
|
+
const next = new URL(endpoint, this.url);
|
|
2058
|
+
if (next.origin !== new URL(this.url).origin) return;
|
|
2059
|
+
validateTransportUrl(next.toString());
|
|
2060
|
+
this.endpointUrl = next.toString();
|
|
2061
|
+
} catch {
|
|
1933
2062
|
}
|
|
1934
2063
|
}
|
|
1935
2064
|
async readSSEBody(reader, decoder, sseReader) {
|
|
@@ -1942,12 +2071,21 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1942
2071
|
}
|
|
1943
2072
|
} catch {
|
|
1944
2073
|
} finally {
|
|
2074
|
+
this.streamSignal?.();
|
|
2075
|
+
this.rejectStreamPending("SSE stream closed");
|
|
1945
2076
|
if (!this.readerDone && this.state !== "disconnected" && this.state !== "failed") {
|
|
1946
2077
|
this.state = "disconnected";
|
|
1947
2078
|
this.notifyDisconnect();
|
|
1948
2079
|
}
|
|
1949
2080
|
}
|
|
1950
2081
|
}
|
|
2082
|
+
rejectStreamPending(reason) {
|
|
2083
|
+
if (this.streamPending.size === 0) return;
|
|
2084
|
+
const err = new Error(`MCP "${this.name}": ${reason}`);
|
|
2085
|
+
const pending = [...this.streamPending.values()];
|
|
2086
|
+
this.streamPending.clear();
|
|
2087
|
+
for (const entry of pending) entry.reject(err);
|
|
2088
|
+
}
|
|
1951
2089
|
buildSSEUrl() {
|
|
1952
2090
|
try {
|
|
1953
2091
|
const url = new URL(this.url);
|
|
@@ -1957,12 +2095,37 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1957
2095
|
return this.url;
|
|
1958
2096
|
}
|
|
1959
2097
|
}
|
|
1960
|
-
|
|
2098
|
+
/** Register interest in a stream-delivered response before the POST goes out. */
|
|
2099
|
+
awaitStreamResponse(id, signal) {
|
|
2100
|
+
const promise = new Promise((resolve, reject) => {
|
|
2101
|
+
const onAbort = () => reject(signal.reason instanceof Error ? signal.reason : new Error("MCP request aborted"));
|
|
2102
|
+
this.streamPending.set(id, {
|
|
2103
|
+
resolve: (result) => {
|
|
2104
|
+
signal.removeEventListener("abort", onAbort);
|
|
2105
|
+
resolve(result);
|
|
2106
|
+
},
|
|
2107
|
+
reject: (err) => {
|
|
2108
|
+
signal.removeEventListener("abort", onAbort);
|
|
2109
|
+
reject(err);
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
if (signal.aborted) onAbort();
|
|
2113
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
2114
|
+
});
|
|
2115
|
+
promise.catch(() => void 0);
|
|
2116
|
+
return promise;
|
|
2117
|
+
}
|
|
2118
|
+
httpPost(method, params, opts) {
|
|
2119
|
+
return this.postJsonRpc(method, params, this.requestTimeout, opts);
|
|
2120
|
+
}
|
|
2121
|
+
async postJsonRpc(method, params, timeoutMs, opts) {
|
|
1961
2122
|
const id = this.genId();
|
|
1962
|
-
const
|
|
2123
|
+
const isNotification = method.startsWith("notifications/");
|
|
2124
|
+
const body = encodeJsonRpcMessage(id, method, params);
|
|
1963
2125
|
const external = opts?.signal;
|
|
1964
2126
|
const parent = external && this.abortController ? AbortSignal.any([this.abortController.signal, external]) : external ?? this.abortController?.signal;
|
|
1965
|
-
const timeoutSignal = createTimeoutSignal(parent,
|
|
2127
|
+
const timeoutSignal = createTimeoutSignal(parent, timeoutMs);
|
|
2128
|
+
const streamed = isNotification ? void 0 : this.awaitStreamResponse(id, timeoutSignal.signal);
|
|
1966
2129
|
const fetchOpts = {
|
|
1967
2130
|
method: "POST",
|
|
1968
2131
|
headers: {
|
|
@@ -1974,8 +2137,19 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1974
2137
|
};
|
|
1975
2138
|
this.applyTlsAgent(fetchOpts);
|
|
1976
2139
|
try {
|
|
1977
|
-
|
|
2140
|
+
let res;
|
|
2141
|
+
try {
|
|
2142
|
+
res = await this.fetchWithAuthorization(
|
|
2143
|
+
this.endpointUrl ?? this.url,
|
|
2144
|
+
fetchOpts,
|
|
2145
|
+
timeoutSignal.signal
|
|
2146
|
+
);
|
|
2147
|
+
} catch (err) {
|
|
2148
|
+
if (!timeoutSignal.signal.aborted) this.markDisconnected();
|
|
2149
|
+
throw err;
|
|
2150
|
+
}
|
|
1978
2151
|
if (!res.ok) {
|
|
2152
|
+
if (SESSION_FATAL_HTTP_STATUSES.has(res.status)) this.markDisconnected();
|
|
1979
2153
|
let snippet;
|
|
1980
2154
|
try {
|
|
1981
2155
|
snippet = await readBodyCapped(res, MCP_CONSTANTS.REQUEST_LOG_CAP);
|
|
@@ -1990,25 +2164,28 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1990
2164
|
context: { transport: "sse", url: this.url, status: res.status }
|
|
1991
2165
|
});
|
|
1992
2166
|
}
|
|
1993
|
-
if (
|
|
2167
|
+
if (isNotification || !streamed) {
|
|
1994
2168
|
await readBodyCapped(res).catch(() => void 0);
|
|
1995
2169
|
return { jsonrpc: "2.0", id };
|
|
1996
2170
|
}
|
|
2171
|
+
let text;
|
|
2172
|
+
try {
|
|
2173
|
+
text = await readBodyCapped(res);
|
|
2174
|
+
} catch (err) {
|
|
2175
|
+
throw invalidResponse(method, this.url, err);
|
|
2176
|
+
}
|
|
2177
|
+
if (res.status === 202 || text.trim() === "") {
|
|
2178
|
+
return await streamed;
|
|
2179
|
+
}
|
|
1997
2180
|
let data;
|
|
1998
2181
|
try {
|
|
1999
|
-
data = JSON.parse(
|
|
2182
|
+
data = JSON.parse(text);
|
|
2000
2183
|
} catch (err) {
|
|
2001
|
-
throw
|
|
2002
|
-
message: `Invalid JSON-RPC response: ${err instanceof Error ? err.message : "parse failed"}`,
|
|
2003
|
-
code: "TOOL_EXECUTION_FAILED",
|
|
2004
|
-
toolName: method,
|
|
2005
|
-
context: { transport: "sse", url: this.url, phase: "parse-json" },
|
|
2006
|
-
cause: err
|
|
2007
|
-
});
|
|
2184
|
+
throw invalidResponse(method, this.url, err);
|
|
2008
2185
|
}
|
|
2009
2186
|
return assertMatchingJsonRpcResult(data, id, method);
|
|
2010
2187
|
} catch (err) {
|
|
2011
|
-
if (external?.aborted && !
|
|
2188
|
+
if (external?.aborted && !isNotification) {
|
|
2012
2189
|
void this.httpPost("notifications/cancelled", {
|
|
2013
2190
|
requestId: id,
|
|
2014
2191
|
reason: "client aborted"
|
|
@@ -2016,9 +2193,13 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
2016
2193
|
});
|
|
2017
2194
|
throw makeAbortError(method);
|
|
2018
2195
|
}
|
|
2019
|
-
this.markDisconnected();
|
|
2020
2196
|
throw err;
|
|
2021
2197
|
} finally {
|
|
2198
|
+
const pending = this.streamPending.get(id);
|
|
2199
|
+
if (pending) {
|
|
2200
|
+
this.streamPending.delete(id);
|
|
2201
|
+
pending.reject(new Error("MCP request settled"));
|
|
2202
|
+
}
|
|
2022
2203
|
timeoutSignal.dispose();
|
|
2023
2204
|
}
|
|
2024
2205
|
}
|
|
@@ -2043,73 +2224,16 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
2043
2224
|
}
|
|
2044
2225
|
/** Generic JSON-RPC request — used by MCPClient.request() for SSE transports. */
|
|
2045
2226
|
async request(method, params, timeoutMs, opts) {
|
|
2046
|
-
const
|
|
2047
|
-
|
|
2048
|
-
const external = opts?.signal;
|
|
2049
|
-
const parent = external && this.abortController ? AbortSignal.any([this.abortController.signal, external]) : external ?? this.abortController?.signal;
|
|
2050
|
-
const timeoutSignal = createTimeoutSignal(parent, timeoutMs ?? this.requestTimeout);
|
|
2051
|
-
const fetchOpts = {
|
|
2052
|
-
method: "POST",
|
|
2053
|
-
headers: {
|
|
2054
|
-
"Content-Type": "application/json",
|
|
2055
|
-
...this.headers
|
|
2056
|
-
},
|
|
2057
|
-
body,
|
|
2058
|
-
signal: timeoutSignal.signal
|
|
2059
|
-
};
|
|
2060
|
-
this.applyTlsAgent(fetchOpts);
|
|
2061
|
-
try {
|
|
2062
|
-
const res = await this.fetchWithAuthorization(this.url, fetchOpts, timeoutSignal.signal);
|
|
2063
|
-
if (!res.ok) {
|
|
2064
|
-
throw new ToolError4({
|
|
2065
|
-
message: `HTTP ${res.status}: ${res.statusText}`,
|
|
2066
|
-
code: "TOOL_EXECUTION_FAILED",
|
|
2067
|
-
toolName: method,
|
|
2068
|
-
context: {
|
|
2069
|
-
transport: "sse",
|
|
2070
|
-
url: this.url,
|
|
2071
|
-
status: res.status,
|
|
2072
|
-
statusText: res.statusText
|
|
2073
|
-
}
|
|
2074
|
-
});
|
|
2075
|
-
}
|
|
2076
|
-
if (method.startsWith("notifications/")) {
|
|
2077
|
-
await readBodyCapped(res).catch(() => void 0);
|
|
2078
|
-
return { jsonrpc: "2.0", id };
|
|
2079
|
-
}
|
|
2080
|
-
let data;
|
|
2081
|
-
try {
|
|
2082
|
-
data = JSON.parse(await readBodyCapped(res));
|
|
2083
|
-
} catch (err) {
|
|
2084
|
-
throw new ToolError4({
|
|
2085
|
-
message: `Invalid JSON-RPC response: ${err instanceof Error ? err.message : "parse failed"}`,
|
|
2086
|
-
code: "TOOL_EXECUTION_FAILED",
|
|
2087
|
-
toolName: method,
|
|
2088
|
-
context: { transport: "sse", url: this.url, phase: "parse-json" },
|
|
2089
|
-
cause: err
|
|
2090
|
-
});
|
|
2091
|
-
}
|
|
2092
|
-
const result = assertMatchingJsonRpcResult(data, id, method);
|
|
2093
|
-
return { jsonrpc: "2.0", id, result: result.result, error: result.error };
|
|
2094
|
-
} catch (err) {
|
|
2095
|
-
if (external?.aborted && !method.startsWith("notifications/")) {
|
|
2096
|
-
void this.httpPost("notifications/cancelled", {
|
|
2097
|
-
requestId: id,
|
|
2098
|
-
reason: "client aborted"
|
|
2099
|
-
}).catch(() => {
|
|
2100
|
-
});
|
|
2101
|
-
throw makeAbortError(method);
|
|
2102
|
-
}
|
|
2103
|
-
this.markDisconnected();
|
|
2104
|
-
throw err;
|
|
2105
|
-
} finally {
|
|
2106
|
-
timeoutSignal.dispose();
|
|
2107
|
-
}
|
|
2227
|
+
const result = await this.postJsonRpc(method, params, timeoutMs ?? this.requestTimeout, opts);
|
|
2228
|
+
return { jsonrpc: "2.0", id: result.id, result: result.result, error: result.error };
|
|
2108
2229
|
}
|
|
2109
2230
|
async close() {
|
|
2110
2231
|
this.releasePinnedDispatcher();
|
|
2111
|
-
|
|
2232
|
+
const alreadyClosed = this.closed;
|
|
2233
|
+
this.closed = true;
|
|
2112
2234
|
this.readerDone = true;
|
|
2235
|
+
this.state = "disconnected";
|
|
2236
|
+
if (alreadyClosed) return;
|
|
2113
2237
|
this.readLoopAbort?.abort();
|
|
2114
2238
|
try {
|
|
2115
2239
|
this.reader?.cancel();
|
|
@@ -2120,8 +2244,8 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
2120
2244
|
} catch {
|
|
2121
2245
|
}
|
|
2122
2246
|
this.abortController?.abort();
|
|
2247
|
+
this.rejectStreamPending("transport closed");
|
|
2123
2248
|
this.disconnectHandlers.splice(0, this.disconnectHandlers.length);
|
|
2124
|
-
this.state = "disconnected";
|
|
2125
2249
|
}
|
|
2126
2250
|
markDisconnected() {
|
|
2127
2251
|
if (this.state === "connected") {
|
|
@@ -2130,10 +2254,20 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
2130
2254
|
}
|
|
2131
2255
|
}
|
|
2132
2256
|
};
|
|
2257
|
+
function invalidResponse(method, url, err) {
|
|
2258
|
+
return new ToolError4({
|
|
2259
|
+
message: `Invalid JSON-RPC response: ${err instanceof Error ? err.message : "parse failed"}`,
|
|
2260
|
+
code: "TOOL_EXECUTION_FAILED",
|
|
2261
|
+
toolName: method,
|
|
2262
|
+
context: { transport: "sse", url, phase: "parse-json" },
|
|
2263
|
+
cause: err
|
|
2264
|
+
});
|
|
2265
|
+
}
|
|
2133
2266
|
|
|
2134
2267
|
// src/transport-streamable.ts
|
|
2135
2268
|
var StreamableHTTPTransport = class _StreamableHTTPTransport extends BaseHTTPTransport {
|
|
2136
2269
|
_nextId = 1;
|
|
2270
|
+
closed = false;
|
|
2137
2271
|
sessionId;
|
|
2138
2272
|
constructor(opts) {
|
|
2139
2273
|
super(opts, "StreamableHTTP");
|
|
@@ -2164,11 +2298,8 @@ var StreamableHTTPTransport = class _StreamableHTTPTransport extends BaseHTTPTra
|
|
|
2164
2298
|
}
|
|
2165
2299
|
async refreshTools() {
|
|
2166
2300
|
try {
|
|
2167
|
-
const
|
|
2168
|
-
if (
|
|
2169
|
-
const tools = normalizeMCPTools(
|
|
2170
|
-
response.result?.tools
|
|
2171
|
-
);
|
|
2301
|
+
const tools = await listAllTools((params) => this.postRaw("tools/list", params));
|
|
2302
|
+
if (!tools) return;
|
|
2172
2303
|
this.tools.splice(0, this.tools.length, ...tools);
|
|
2173
2304
|
for (const listener of this.toolsChangedListeners) {
|
|
2174
2305
|
try {
|
|
@@ -2180,6 +2311,7 @@ var StreamableHTTPTransport = class _StreamableHTTPTransport extends BaseHTTPTra
|
|
|
2180
2311
|
}
|
|
2181
2312
|
}
|
|
2182
2313
|
async connect() {
|
|
2314
|
+
this.closed = false;
|
|
2183
2315
|
this.state = "connecting";
|
|
2184
2316
|
this.serverMetadata = void 0;
|
|
2185
2317
|
this.abortController = new AbortController();
|
|
@@ -2200,7 +2332,8 @@ var StreamableHTTPTransport = class _StreamableHTTPTransport extends BaseHTTPTra
|
|
|
2200
2332
|
method: "initialize",
|
|
2201
2333
|
params: {
|
|
2202
2334
|
protocolVersion: MCP_CONSTANTS.PROTOCOL_VERSION,
|
|
2203
|
-
capabilities
|
|
2335
|
+
// Client capabilities — none offered (`tools` is a server capability).
|
|
2336
|
+
capabilities: {},
|
|
2204
2337
|
clientInfo: MCP_CONSTANTS.CLIENT_INFO
|
|
2205
2338
|
}
|
|
2206
2339
|
}),
|
|
@@ -2230,13 +2363,8 @@ var StreamableHTTPTransport = class _StreamableHTTPTransport extends BaseHTTPTra
|
|
|
2230
2363
|
this.protocolVersion = this.serverMetadata.protocolVersion;
|
|
2231
2364
|
this.sessionId = initRes.headers.get("mcp-session-id") ?? void 0;
|
|
2232
2365
|
await this.postRaw("notifications/initialized", {});
|
|
2233
|
-
const
|
|
2234
|
-
|
|
2235
|
-
this.tools.splice(0, this.tools.length);
|
|
2236
|
-
} else {
|
|
2237
|
-
const result = toolsRes.result;
|
|
2238
|
-
this.tools.splice(0, this.tools.length, ...normalizeMCPTools(result?.tools));
|
|
2239
|
-
}
|
|
2366
|
+
const tools = await listAllTools((params) => this.postRaw("tools/list", params));
|
|
2367
|
+
this.tools.splice(0, this.tools.length, ...tools ?? []);
|
|
2240
2368
|
this.state = "connected";
|
|
2241
2369
|
clearTimeout(startupTimer);
|
|
2242
2370
|
} catch (err) {
|
|
@@ -2248,7 +2376,7 @@ var StreamableHTTPTransport = class _StreamableHTTPTransport extends BaseHTTPTra
|
|
|
2248
2376
|
}
|
|
2249
2377
|
async postRaw(method, params, opts) {
|
|
2250
2378
|
const id = this.genId();
|
|
2251
|
-
const body =
|
|
2379
|
+
const body = encodeJsonRpcMessage(id, method, params);
|
|
2252
2380
|
const external = opts?.signal;
|
|
2253
2381
|
const parent = external && this.abortController ? AbortSignal.any([this.abortController.signal, external]) : external ?? this.abortController?.signal;
|
|
2254
2382
|
const timeoutSignal = createTimeoutSignal(parent, this.requestTimeout);
|
|
@@ -2298,7 +2426,7 @@ var StreamableHTTPTransport = class _StreamableHTTPTransport extends BaseHTTPTra
|
|
|
2298
2426
|
/** Generic JSON-RPC request — used by MCPClient.request() for SSE/streamable-http transports. */
|
|
2299
2427
|
async request(method, params, timeoutMs, opts) {
|
|
2300
2428
|
const id = this.genId();
|
|
2301
|
-
const body =
|
|
2429
|
+
const body = encodeJsonRpcMessage(id, method, params);
|
|
2302
2430
|
const external = opts?.signal;
|
|
2303
2431
|
const parent = external && this.abortController ? AbortSignal.any([this.abortController.signal, external]) : external ?? this.abortController?.signal;
|
|
2304
2432
|
const timeoutSignal = createTimeoutSignal(parent, timeoutMs ?? this.requestTimeout);
|
|
@@ -2328,11 +2456,12 @@ var StreamableHTTPTransport = class _StreamableHTTPTransport extends BaseHTTPTra
|
|
|
2328
2456
|
}
|
|
2329
2457
|
const parsed = this.consumeResponseText(await readBodyCapped(res), id);
|
|
2330
2458
|
if (parsed) {
|
|
2459
|
+
const matched = assertMatchingJsonRpcResult(parsed, id, method);
|
|
2331
2460
|
return {
|
|
2332
2461
|
jsonrpc: "2.0",
|
|
2333
2462
|
id,
|
|
2334
|
-
result:
|
|
2335
|
-
error:
|
|
2463
|
+
result: matched.result,
|
|
2464
|
+
error: matched.error
|
|
2336
2465
|
};
|
|
2337
2466
|
}
|
|
2338
2467
|
throw new Error("Could not parse response as JSON-RPC");
|
|
@@ -2366,8 +2495,10 @@ var StreamableHTTPTransport = class _StreamableHTTPTransport extends BaseHTTPTra
|
|
|
2366
2495
|
}
|
|
2367
2496
|
async close() {
|
|
2368
2497
|
this.releasePinnedDispatcher();
|
|
2369
|
-
|
|
2498
|
+
const alreadyClosed = this.closed;
|
|
2499
|
+
this.closed = true;
|
|
2370
2500
|
this.state = "disconnected";
|
|
2501
|
+
if (alreadyClosed) return;
|
|
2371
2502
|
this.abortController?.abort();
|
|
2372
2503
|
this.disconnectHandlers.splice(0, this.disconnectHandlers.length);
|
|
2373
2504
|
}
|
|
@@ -2525,12 +2656,18 @@ var MCPClient = class _MCPClient {
|
|
|
2525
2656
|
return spawn2(shim.command, shim.args, {
|
|
2526
2657
|
env: spawnEnv,
|
|
2527
2658
|
stdio,
|
|
2659
|
+
...this.opts.cwd ? { cwd: this.opts.cwd } : {},
|
|
2528
2660
|
windowsVerbatimArguments: shim.windowsVerbatimArguments,
|
|
2529
2661
|
// Without this every MCP server spawned from a console-less host
|
|
2530
2662
|
// (WebUI server, scheduled runs) opens a visible console window.
|
|
2531
2663
|
windowsHide: true
|
|
2532
2664
|
});
|
|
2533
|
-
})() : spawn2(this.opts.command, rawArgs, {
|
|
2665
|
+
})() : spawn2(this.opts.command, rawArgs, {
|
|
2666
|
+
env: spawnEnv,
|
|
2667
|
+
stdio,
|
|
2668
|
+
windowsHide: true,
|
|
2669
|
+
...this.opts.cwd ? { cwd: this.opts.cwd } : {}
|
|
2670
|
+
});
|
|
2534
2671
|
this.child = child;
|
|
2535
2672
|
child.stdout?.on("data", (chunk) => this.onData(chunk.toString()));
|
|
2536
2673
|
child.stdout?.on("end", () => {
|
|
@@ -2565,7 +2702,9 @@ var MCPClient = class _MCPClient {
|
|
|
2565
2702
|
"initialize",
|
|
2566
2703
|
{
|
|
2567
2704
|
protocolVersion: MCP_CONSTANTS.PROTOCOL_VERSION,
|
|
2568
|
-
capabilities
|
|
2705
|
+
// Client capabilities (roots/sampling/elicitation) — none offered.
|
|
2706
|
+
// `tools` is a SERVER capability and never belonged here.
|
|
2707
|
+
capabilities: {},
|
|
2569
2708
|
clientInfo: MCP_CONSTANTS.CLIENT_INFO
|
|
2570
2709
|
},
|
|
2571
2710
|
this.opts.startupTimeoutMs ?? 1e4
|
|
@@ -2587,13 +2726,7 @@ var MCPClient = class _MCPClient {
|
|
|
2587
2726
|
'[MCP] notify("notifications/initialized") failed for "' + this.opts.name + '": ' + toErrorMessage(err)
|
|
2588
2727
|
);
|
|
2589
2728
|
}
|
|
2590
|
-
|
|
2591
|
-
if (toolsRes.error) {
|
|
2592
|
-
this._tools = [];
|
|
2593
|
-
} else {
|
|
2594
|
-
const result = toolsRes.result;
|
|
2595
|
-
this._tools = normalizeMCPTools(result?.tools);
|
|
2596
|
-
}
|
|
2729
|
+
this._tools = await listAllTools((params) => this.request("tools/list", params)) ?? [];
|
|
2597
2730
|
this._toolsCache = this._tools;
|
|
2598
2731
|
this.state = "connected";
|
|
2599
2732
|
}
|
|
@@ -2843,8 +2976,7 @@ var MCPClient = class _MCPClient {
|
|
|
2843
2976
|
this.child = void 0;
|
|
2844
2977
|
}
|
|
2845
2978
|
this.failPending(`MCP "${this.opts.name}" closed`);
|
|
2846
|
-
this.sseTransport?.close();
|
|
2847
|
-
this.httpTransport?.close();
|
|
2979
|
+
await Promise.allSettled([this.sseTransport?.close(), this.httpTransport?.close()]);
|
|
2848
2980
|
this.state = "disconnected";
|
|
2849
2981
|
}
|
|
2850
2982
|
request(method, params, timeoutMs = this.opts.requestTimeoutMs ?? 6e4, opts) {
|
|
@@ -3085,11 +3217,13 @@ var MCPClient = class _MCPClient {
|
|
|
3085
3217
|
}
|
|
3086
3218
|
}
|
|
3087
3219
|
handleServerRequest(request3) {
|
|
3088
|
-
const
|
|
3089
|
-
const response = {
|
|
3220
|
+
const response = request3.method === "ping" ? { jsonrpc: "2.0", id: request3.id, result: {} } : {
|
|
3090
3221
|
jsonrpc: "2.0",
|
|
3091
3222
|
id: request3.id,
|
|
3092
|
-
error: {
|
|
3223
|
+
error: {
|
|
3224
|
+
code: -32601,
|
|
3225
|
+
message: request3.method === "sampling/createMessage" ? "Client sampling is disabled by policy" : `Method not found: ${request3.method}`
|
|
3226
|
+
}
|
|
3093
3227
|
};
|
|
3094
3228
|
try {
|
|
3095
3229
|
this.child?.stdin?.write(`${JSON.stringify(response)}
|
|
@@ -3105,10 +3239,8 @@ var MCPClient = class _MCPClient {
|
|
|
3105
3239
|
*/
|
|
3106
3240
|
async handleToolsListChanged() {
|
|
3107
3241
|
try {
|
|
3108
|
-
const
|
|
3109
|
-
|
|
3110
|
-
toolsRes.result?.tools
|
|
3111
|
-
);
|
|
3242
|
+
const tools = await listAllTools((params) => this.request("tools/list", params));
|
|
3243
|
+
if (!tools) return;
|
|
3112
3244
|
this._tools = tools;
|
|
3113
3245
|
this._toolsCache = tools;
|
|
3114
3246
|
for (const listener of this.toolsChangedListeners) {
|
|
@@ -3446,7 +3578,7 @@ async function updateMcp(input, deps) {
|
|
|
3446
3578
|
servers[input.name] = cfg;
|
|
3447
3579
|
await persist(deps.configPath, full, servers);
|
|
3448
3580
|
if (cfg.enabled !== false) {
|
|
3449
|
-
return startServer(input.name, cfg, deps, `Server "${input.name}" updated
|
|
3581
|
+
return startServer(input.name, cfg, deps, `Server "${input.name}" updated`);
|
|
3450
3582
|
}
|
|
3451
3583
|
await safeStop(input.name, deps);
|
|
3452
3584
|
trackDisabled(deps.registry, cfg);
|
|
@@ -3483,7 +3615,7 @@ async function enableMcp(name, deps) {
|
|
|
3483
3615
|
cfg.enabled = true;
|
|
3484
3616
|
servers[name] = cfg;
|
|
3485
3617
|
await persist(deps.configPath, full, servers);
|
|
3486
|
-
return startServer(name, cfg, deps, `Server "${name}" enabled
|
|
3618
|
+
return startServer(name, cfg, deps, `Server "${name}" enabled`);
|
|
3487
3619
|
}
|
|
3488
3620
|
async function disableMcp(name, deps) {
|
|
3489
3621
|
if (!name) return { ok: false, message: "Server name is required" };
|
|
@@ -3526,12 +3658,19 @@ async function restartMcp(name, deps) {
|
|
|
3526
3658
|
}
|
|
3527
3659
|
const cfg = servers[name];
|
|
3528
3660
|
if (!cfg) return { ok: false, message: `Server "${name}" is not in config.` };
|
|
3529
|
-
return startServer(name, { ...cfg, name }, deps, `Server "${name}" started
|
|
3661
|
+
return startServer(name, { ...cfg, name }, deps, `Server "${name}" started`);
|
|
3530
3662
|
}
|
|
3531
3663
|
async function discoverMcp(name, deps) {
|
|
3532
3664
|
if (!name) return { ok: false, message: "Server name is required" };
|
|
3533
3665
|
const result = await restartMcp(name, deps);
|
|
3534
3666
|
if (!result.ok) return result;
|
|
3667
|
+
if (liveState(name, deps.registry).state === "dormant") {
|
|
3668
|
+
try {
|
|
3669
|
+
await deps.registry.ensureConnected(name);
|
|
3670
|
+
} catch (err) {
|
|
3671
|
+
return { ok: false, message: `Failed to discover "${name}": ${errMessage(err)}` };
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3535
3674
|
const { state, tools } = liveState(name, deps.registry);
|
|
3536
3675
|
return {
|
|
3537
3676
|
ok: true,
|
|
@@ -3540,15 +3679,13 @@ async function discoverMcp(name, deps) {
|
|
|
3540
3679
|
tools
|
|
3541
3680
|
};
|
|
3542
3681
|
}
|
|
3543
|
-
async function startServer(name, cfg, deps, okMessage
|
|
3682
|
+
async function startServer(name, cfg, deps, okMessage) {
|
|
3544
3683
|
try {
|
|
3545
3684
|
const alreadyRegistered = deps.registry.list().some((s) => s.name === name);
|
|
3546
|
-
if (alreadyRegistered
|
|
3547
|
-
await deps.registry.restart(name);
|
|
3548
|
-
} else if (alreadyRegistered) {
|
|
3549
|
-
await deps.registry.restart(name);
|
|
3685
|
+
if (alreadyRegistered) {
|
|
3686
|
+
await deps.registry.restart(name, { ...cfg, name, enabled: true });
|
|
3550
3687
|
} else {
|
|
3551
|
-
await deps.registry.start({ ...cfg, enabled: true });
|
|
3688
|
+
await deps.registry.start({ ...cfg, name, enabled: true });
|
|
3552
3689
|
}
|
|
3553
3690
|
const { state, tools } = liveState(name, deps.registry);
|
|
3554
3691
|
return {
|
|
@@ -3905,14 +4042,37 @@ function wrapMCPTool(serverName, mcpTool, client, permission = "confirm", observ
|
|
|
3905
4042
|
}
|
|
3906
4043
|
};
|
|
3907
4044
|
}
|
|
4045
|
+
function renderContentBlock(item) {
|
|
4046
|
+
const type = item["type"];
|
|
4047
|
+
if (type === "text") return typeof item["text"] === "string" ? item["text"] : "";
|
|
4048
|
+
if (type === "image" || type === "audio") {
|
|
4049
|
+
const mime = typeof item["mimeType"] === "string" ? item["mimeType"] : "unknown type";
|
|
4050
|
+
const data = typeof item["data"] === "string" ? item["data"] : "";
|
|
4051
|
+
const kb = Math.max(1, Math.round(data.length * 3 / 4 / 1024));
|
|
4052
|
+
return `[${type} content: ${mime}, ~${kb} KB \u2014 binary payload not inlined]`;
|
|
4053
|
+
}
|
|
4054
|
+
if (type === "resource" && item["resource"] && typeof item["resource"] === "object") {
|
|
4055
|
+
const resource = item["resource"];
|
|
4056
|
+
const uri = typeof resource["uri"] === "string" ? resource["uri"] : "unknown";
|
|
4057
|
+
if (typeof resource["text"] === "string") return `[resource ${uri}]
|
|
4058
|
+
${resource["text"]}`;
|
|
4059
|
+
if (typeof resource["blob"] === "string") {
|
|
4060
|
+
const mime = typeof resource["mimeType"] === "string" ? `${resource["mimeType"]}, ` : "";
|
|
4061
|
+
return `[resource ${uri}: ${mime}binary payload not inlined]`;
|
|
4062
|
+
}
|
|
4063
|
+
return JSON.stringify(item);
|
|
4064
|
+
}
|
|
4065
|
+
if (type === "resource_link" && typeof item["uri"] === "string") {
|
|
4066
|
+
return `[resource link: ${item["uri"]}]`;
|
|
4067
|
+
}
|
|
4068
|
+
return JSON.stringify(item);
|
|
4069
|
+
}
|
|
3908
4070
|
function stringify(c) {
|
|
3909
4071
|
if (typeof c === "string") return c;
|
|
3910
4072
|
if (Array.isArray(c)) {
|
|
3911
4073
|
return c.map((item) => {
|
|
3912
4074
|
if (item && typeof item === "object") {
|
|
3913
|
-
|
|
3914
|
-
if (t === "text") return item.text ?? "";
|
|
3915
|
-
return JSON.stringify(item);
|
|
4075
|
+
return renderContentBlock(item);
|
|
3916
4076
|
}
|
|
3917
4077
|
return String(item);
|
|
3918
4078
|
}).join("\n");
|
|
@@ -3928,9 +4088,14 @@ function stringify(c) {
|
|
|
3928
4088
|
|
|
3929
4089
|
// src/registry-connect-loop.ts
|
|
3930
4090
|
function applySlotTools(ctx, slot, tools, client) {
|
|
3931
|
-
|
|
4091
|
+
slot.discoveredTools = tools;
|
|
3932
4092
|
const allowed = slot.cfg.allowedTools;
|
|
3933
4093
|
const filtered = tools.filter((t) => !allowed || allowed.includes(t.name));
|
|
4094
|
+
const signature = JSON.stringify(
|
|
4095
|
+
filtered.map((t) => [t.name, t.description ?? null, t.inputSchema ?? null])
|
|
4096
|
+
);
|
|
4097
|
+
const lazyWrappersCurrent = slot.lazy && slot.toolSignature === signature && slot.lazyTools.length === filtered.length && (slot.registeredLazy || ctx.lazyMode);
|
|
4098
|
+
if (lazyWrappersCurrent) return;
|
|
3934
4099
|
const clientArg = slot.lazy ? () => ctx.ensureConnected(slot.cfg.name) : expectDefined(client);
|
|
3935
4100
|
const wrapped = filtered.map(
|
|
3936
4101
|
(t) => wrapMCPTool(slot.cfg.name, t, clientArg, slot.cfg.permission ?? "confirm", {
|
|
@@ -3956,9 +4121,16 @@ function applySlotTools(ctx, slot, tools, client) {
|
|
|
3956
4121
|
})
|
|
3957
4122
|
);
|
|
3958
4123
|
slot.lazyTools = wrapped;
|
|
3959
|
-
|
|
3960
|
-
|
|
4124
|
+
slot.toolSignature = signature;
|
|
4125
|
+
const wasActive = slot.toolNames.length > 0;
|
|
4126
|
+
if (ctx.lazyMode && !wasActive) return;
|
|
4127
|
+
for (const name of slot.toolNames) {
|
|
4128
|
+
try {
|
|
4129
|
+
ctx.toolRegistry.unregister(name);
|
|
4130
|
+
} catch {
|
|
4131
|
+
}
|
|
3961
4132
|
}
|
|
4133
|
+
slot.toolNames = [];
|
|
3962
4134
|
for (const tool of wrapped) {
|
|
3963
4135
|
try {
|
|
3964
4136
|
ctx.toolRegistry.register(tool, `mcp:${slot.cfg.name}`);
|
|
@@ -3967,7 +4139,7 @@ function applySlotTools(ctx, slot, tools, client) {
|
|
|
3967
4139
|
ctx.log.warn(`MCP tool "${tool.name}" not registered`, err);
|
|
3968
4140
|
}
|
|
3969
4141
|
}
|
|
3970
|
-
if (slot.lazy && wrapped.length > 0) slot.registeredLazy = true;
|
|
4142
|
+
if (slot.lazy && !ctx.lazyMode && wrapped.length > 0) slot.registeredLazy = true;
|
|
3971
4143
|
}
|
|
3972
4144
|
async function discoverSlotCapabilities(ctx, slot, client) {
|
|
3973
4145
|
const startedAt = Date.now();
|
|
@@ -4019,15 +4191,17 @@ async function discoverSlotCapabilities(ctx, slot, client) {
|
|
|
4019
4191
|
async function persistSlotCapabilityManifest(cacheDir, slot) {
|
|
4020
4192
|
if (!slot.lazy || !cacheDir) return;
|
|
4021
4193
|
const previous = slot.manifestWrite ?? Promise.resolve();
|
|
4022
|
-
const pending = previous.then(
|
|
4023
|
-
|
|
4024
|
-
|
|
4194
|
+
const pending = previous.then(() => {
|
|
4195
|
+
const tools = slot.discoveredTools ?? slot.client?.listTools();
|
|
4196
|
+
if (!tools) return;
|
|
4197
|
+
return writeCapabilityManifest(cacheDir, slot.cfg.name, manifestConfigHash(slot.cfg), {
|
|
4198
|
+
tools,
|
|
4025
4199
|
serverMetadata: slot.serverMetadata,
|
|
4026
4200
|
resources: slot.resources,
|
|
4027
4201
|
resourceTemplates: slot.resourceTemplates,
|
|
4028
4202
|
prompts: slot.prompts
|
|
4029
|
-
})
|
|
4030
|
-
);
|
|
4203
|
+
});
|
|
4204
|
+
});
|
|
4031
4205
|
slot.manifestWrite = pending;
|
|
4032
4206
|
await pending;
|
|
4033
4207
|
if (slot.manifestWrite === pending) slot.manifestWrite = void 0;
|
|
@@ -4036,7 +4210,7 @@ async function attemptConnectSlot(ctx, slot) {
|
|
|
4036
4210
|
const MAX_ATTEMPTS = MCP_CONSTANTS.RECONNECT.MAX_ATTEMPTS;
|
|
4037
4211
|
let attempt = 0;
|
|
4038
4212
|
while (attempt < MAX_ATTEMPTS) {
|
|
4039
|
-
if (ctx.servers.
|
|
4213
|
+
if (ctx.servers.get(slot.cfg.name) !== slot) {
|
|
4040
4214
|
return;
|
|
4041
4215
|
}
|
|
4042
4216
|
attempt++;
|
|
@@ -4056,6 +4230,7 @@ async function attemptConnectSlot(ctx, slot) {
|
|
|
4056
4230
|
headers: slot.cfg.headers,
|
|
4057
4231
|
startupTimeoutMs: slot.cfg.startupTimeoutMs,
|
|
4058
4232
|
requestTimeoutMs: slot.cfg.requestTimeoutMs,
|
|
4233
|
+
cwd: ctx.cwd,
|
|
4059
4234
|
allowPrivateNetworks: slot.cfg.allowPrivateNetworks,
|
|
4060
4235
|
passthroughEnv: slot.cfg.passthroughEnv,
|
|
4061
4236
|
authorizationProvider: ctx.authorizationProviderFactory?.(slot.cfg)
|
|
@@ -4095,6 +4270,7 @@ async function attemptConnectSlot(ctx, slot) {
|
|
|
4095
4270
|
slot.reconnectCycles = 0;
|
|
4096
4271
|
const mc = client;
|
|
4097
4272
|
const discovered = mc.listTools();
|
|
4273
|
+
slot.discoveredTools = discovered;
|
|
4098
4274
|
await discoverSlotCapabilities(ctx, slot, mc);
|
|
4099
4275
|
await persistSlotCapabilityManifest(ctx.cacheDir, slot);
|
|
4100
4276
|
applySlotTools(ctx, slot, discovered, mc);
|
|
@@ -4150,7 +4326,7 @@ async function attemptConnectSlot(ctx, slot) {
|
|
|
4150
4326
|
}
|
|
4151
4327
|
const delay = 500 * 2 ** attempt;
|
|
4152
4328
|
await new Promise((r) => setTimeout(r, delay));
|
|
4153
|
-
if (slot.state === "disconnected" || ctx.servers.
|
|
4329
|
+
if (slot.state === "disconnected" || ctx.servers.get(slot.cfg.name) !== slot) {
|
|
4154
4330
|
return;
|
|
4155
4331
|
}
|
|
4156
4332
|
}
|
|
@@ -4167,6 +4343,7 @@ function resetDisconnectedSlotTools(slot, toolRegistry) {
|
|
|
4167
4343
|
}
|
|
4168
4344
|
slot.toolNames = [];
|
|
4169
4345
|
slot.lazyTools = [];
|
|
4346
|
+
slot.toolSignature = void 0;
|
|
4170
4347
|
slot.serverMetadata = void 0;
|
|
4171
4348
|
slot.resources = void 0;
|
|
4172
4349
|
slot.resourceTemplates = void 0;
|
|
@@ -4387,6 +4564,7 @@ var MCPRegistry = class _MCPRegistry {
|
|
|
4387
4564
|
log;
|
|
4388
4565
|
lazyMode;
|
|
4389
4566
|
cacheDir;
|
|
4567
|
+
cwd;
|
|
4390
4568
|
idleTimeoutMs;
|
|
4391
4569
|
authorizationProviderFactory;
|
|
4392
4570
|
authorizationManager;
|
|
@@ -4399,6 +4577,7 @@ var MCPRegistry = class _MCPRegistry {
|
|
|
4399
4577
|
this.log = opts.log;
|
|
4400
4578
|
this.lazyMode = opts.lazyMode ?? false;
|
|
4401
4579
|
this.cacheDir = opts.cacheDir;
|
|
4580
|
+
this.cwd = opts.cwd;
|
|
4402
4581
|
this.idleTimeoutMs = opts.idleTimeoutMs ?? MCP_CONSTANTS.IDLE.DEFAULT_TIMEOUT_MS;
|
|
4403
4582
|
this.authorizationProviderFactory = opts.authorizationProviderFactory;
|
|
4404
4583
|
this.authorizationManager = opts.authorizationManager;
|
|
@@ -4558,7 +4737,6 @@ var MCPRegistry = class _MCPRegistry {
|
|
|
4558
4737
|
}
|
|
4559
4738
|
}
|
|
4560
4739
|
this.log.info(`MCP server "${name}" activated (${slot.toolNames.length} tools)`);
|
|
4561
|
-
this.events.emit("mcp.server.connected", { name, toolCount: slot.toolNames.length });
|
|
4562
4740
|
}
|
|
4563
4741
|
/**
|
|
4564
4742
|
* Unregister all tools for a given server from the tool registry.
|
|
@@ -4578,9 +4756,27 @@ var MCPRegistry = class _MCPRegistry {
|
|
|
4578
4756
|
}
|
|
4579
4757
|
slot.toolNames = [];
|
|
4580
4758
|
this.log.info(`MCP server "${name}" deactivated (${count} tools removed)`);
|
|
4581
|
-
this.events.emit("mcp.server.disconnected", { name, reason: "deactivate" });
|
|
4582
4759
|
return count;
|
|
4583
4760
|
}
|
|
4761
|
+
/**
|
|
4762
|
+
* The tools a server offers — bare names, descriptions and input schemas —
|
|
4763
|
+
* without activating, registering or waking it. In token-saving mode the
|
|
4764
|
+
* model reaches MCP only through `mcp_use`, which needs the bare tool name
|
|
4765
|
+
* and its input shape; before this there was no way to learn either short
|
|
4766
|
+
* of guessing and reading the error. Honors `allowedTools`. Returns
|
|
4767
|
+
* `undefined` for an unknown server, `[]` when nothing was discovered yet.
|
|
4768
|
+
*/
|
|
4769
|
+
describeTools(name) {
|
|
4770
|
+
const slot = this.servers.get(name);
|
|
4771
|
+
if (!slot) return void 0;
|
|
4772
|
+
const allowed = slot.cfg.allowedTools;
|
|
4773
|
+
const tools = slot.discoveredTools ?? slot.client?.listTools() ?? [];
|
|
4774
|
+
return tools.filter((tool) => !allowed || allowed.includes(tool.name)).map((tool) => ({
|
|
4775
|
+
name: tool.name,
|
|
4776
|
+
...tool.description !== void 0 ? { description: tool.description } : {},
|
|
4777
|
+
inputSchema: structuredClone(tool.inputSchema)
|
|
4778
|
+
}));
|
|
4779
|
+
}
|
|
4584
4780
|
/**
|
|
4585
4781
|
* Check whether a server's tools are currently registered.
|
|
4586
4782
|
*/
|
|
@@ -4617,12 +4813,52 @@ var MCPRegistry = class _MCPRegistry {
|
|
|
4617
4813
|
this.recordOperation(slot, "stop", "manual");
|
|
4618
4814
|
this.events.emit("mcp.server.disconnected", { name, reason: "stop" });
|
|
4619
4815
|
}
|
|
4620
|
-
|
|
4816
|
+
/**
|
|
4817
|
+
* Stop and start a registered server. Pass `nextCfg` to apply an edited
|
|
4818
|
+
* configuration: without it the slot reconnects with the config it was
|
|
4819
|
+
* started with, so an update/enable routed through restart() silently kept
|
|
4820
|
+
* the old command, url, env, permission and lazy flag until the next boot.
|
|
4821
|
+
*/
|
|
4822
|
+
/**
|
|
4823
|
+
* Put a running server to sleep while keeping its configuration enabled.
|
|
4824
|
+
*
|
|
4825
|
+
* A lazy server goes `dormant`: the process stops but its tools stay
|
|
4826
|
+
* registered and the next call wakes it. Surfaces previously used stop() for
|
|
4827
|
+
* this, which unregistered a lazy server's tools — "sleep" silently became
|
|
4828
|
+
* "unreachable until restarted". Eager servers have no dormant state, so
|
|
4829
|
+
* for them sleep is a stop.
|
|
4830
|
+
*/
|
|
4831
|
+
async sleep(name) {
|
|
4832
|
+
const slot = this.requireSlot(name);
|
|
4833
|
+
if (!slot.lazy) {
|
|
4834
|
+
await this.stop(name);
|
|
4835
|
+
return;
|
|
4836
|
+
}
|
|
4837
|
+
if (slot.state === "dormant") return;
|
|
4838
|
+
if (slot.operations.inFlightCalls > 0) {
|
|
4839
|
+
throw new Error(`MCP server "${name}" has tool calls in flight \u2014 try again when they finish`);
|
|
4840
|
+
}
|
|
4841
|
+
await sleepIdleSlot(this.idleContext(), slot);
|
|
4842
|
+
}
|
|
4843
|
+
async restart(name, nextCfg) {
|
|
4621
4844
|
const slot = this.servers.get(name);
|
|
4622
4845
|
if (!slot) throw new Error(`MCP server "${name}" not registered`);
|
|
4846
|
+
if (nextCfg && nextCfg.name !== name) {
|
|
4847
|
+
throw new Error(`MCP restart config names "${nextCfg.name}", expected "${name}"`);
|
|
4848
|
+
}
|
|
4849
|
+
if (nextCfg?.enabled === false) {
|
|
4850
|
+
await this.stop(name);
|
|
4851
|
+
this.markDisabled(nextCfg);
|
|
4852
|
+
return;
|
|
4853
|
+
}
|
|
4623
4854
|
slot.operations.restartCount++;
|
|
4624
4855
|
this.recordOperation(slot, "restart", "manual");
|
|
4625
4856
|
await this.stop(name);
|
|
4857
|
+
if (nextCfg) {
|
|
4858
|
+
slot.cfg = nextCfg;
|
|
4859
|
+
slot.lazy = !!nextCfg.lazy && !!this.cacheDir;
|
|
4860
|
+
slot.discoveredTools = void 0;
|
|
4861
|
+
}
|
|
4626
4862
|
slot.attempts = 0;
|
|
4627
4863
|
slot.reconnectCycles = 0;
|
|
4628
4864
|
if (slot.lazy) {
|
|
@@ -4744,6 +4980,7 @@ var MCPRegistry = class _MCPRegistry {
|
|
|
4744
4980
|
log: this.log,
|
|
4745
4981
|
lazyMode: this.lazyMode,
|
|
4746
4982
|
cacheDir: this.cacheDir,
|
|
4983
|
+
cwd: this.cwd,
|
|
4747
4984
|
authorizationProviderFactory: this.authorizationProviderFactory,
|
|
4748
4985
|
operationListeners: this.operationListeners,
|
|
4749
4986
|
ensureConnected: (name) => this.ensureConnected(name),
|
|
@@ -4855,21 +5092,9 @@ var MCPRegistry = class _MCPRegistry {
|
|
|
4855
5092
|
onToolsChanged = (name, _tools) => {
|
|
4856
5093
|
const slot = this.servers.get(name);
|
|
4857
5094
|
if (!slot?.client) return;
|
|
4858
|
-
for (const t of slot.toolNames) {
|
|
4859
|
-
try {
|
|
4860
|
-
this.toolRegistry.unregister(t);
|
|
4861
|
-
} catch {
|
|
4862
|
-
}
|
|
4863
|
-
}
|
|
4864
|
-
slot.toolNames = [];
|
|
4865
|
-
slot.registeredLazy = false;
|
|
4866
5095
|
const discovered = slot.client.listTools();
|
|
4867
5096
|
this.applyTools(slot, discovered, slot.client);
|
|
4868
5097
|
void this.persistCapabilityManifest(slot);
|
|
4869
|
-
this.events.emit("mcp.server.connected", {
|
|
4870
|
-
name: slot.cfg.name,
|
|
4871
|
-
toolCount: slot.toolNames.length
|
|
4872
|
-
});
|
|
4873
5098
|
this.log.info(
|
|
4874
5099
|
`MCP server "${slot.cfg.name}" tools refreshed (${this.toolNamesForSlot(slot).length} active)`
|
|
4875
5100
|
);
|
|
@@ -4899,7 +5124,7 @@ var MCPRegistry = class _MCPRegistry {
|
|
|
4899
5124
|
}
|
|
4900
5125
|
onChildExit = (name, code, _signal) => {
|
|
4901
5126
|
const slot = this.servers.get(name);
|
|
4902
|
-
if (
|
|
5127
|
+
if (slot?.state !== "connected") return;
|
|
4903
5128
|
if (slot.lazy) {
|
|
4904
5129
|
this.recordFailure(slot, "transport", "process-exit-lazy");
|
|
4905
5130
|
markLazySlotDormant(slot, this.events, `exit:${code ?? "unknown"}`, {
|
|
@@ -4918,7 +5143,7 @@ var MCPRegistry = class _MCPRegistry {
|
|
|
4918
5143
|
/** Handles SSE / streamable-http disconnect — same recovery as stdio child exit. */
|
|
4919
5144
|
onTransportDisconnect = (name) => {
|
|
4920
5145
|
const slot = this.servers.get(name);
|
|
4921
|
-
if (
|
|
5146
|
+
if (slot?.state !== "connected") return;
|
|
4922
5147
|
if (slot.lazy) {
|
|
4923
5148
|
this.recordFailure(slot, "transport", "http-disconnect-lazy");
|
|
4924
5149
|
markLazySlotDormant(slot, this.events, "http-disconnect", {
|
|
@@ -4954,7 +5179,7 @@ var MCPRegistry = class _MCPRegistry {
|
|
|
4954
5179
|
slot.reconnectCycles++;
|
|
4955
5180
|
slot.operations.reconnectCount++;
|
|
4956
5181
|
this.recordOperation(slot, "reconnect", "automatic");
|
|
4957
|
-
await this.
|
|
5182
|
+
await this.singleFlightConnect(slot);
|
|
4958
5183
|
}
|
|
4959
5184
|
recordSuccess(slot, resetFailures = true) {
|
|
4960
5185
|
recordRegistrySuccess(slot, resetFailures);
|
|
@@ -5639,6 +5864,8 @@ var MCPRefreshingAuthorizationProvider = class {
|
|
|
5639
5864
|
}
|
|
5640
5865
|
options;
|
|
5641
5866
|
refreshPromise;
|
|
5867
|
+
/** Refresh token the authorization server rejected; cleared by a new authorization. */
|
|
5868
|
+
rejectedRefreshToken;
|
|
5642
5869
|
resource;
|
|
5643
5870
|
refreshSkewMs;
|
|
5644
5871
|
async getAccessToken(context) {
|
|
@@ -5676,16 +5903,28 @@ var MCPRefreshingAuthorizationProvider = class {
|
|
|
5676
5903
|
}
|
|
5677
5904
|
async refreshInner(state) {
|
|
5678
5905
|
const refreshToken = state.tokenSet.refreshToken;
|
|
5679
|
-
if (!refreshToken) {
|
|
5680
|
-
this.emit("reauth_required", state);
|
|
5906
|
+
if (!refreshToken || refreshToken === this.rejectedRefreshToken) {
|
|
5907
|
+
if (!refreshToken) this.emit("reauth_required", state);
|
|
5681
5908
|
return void 0;
|
|
5682
5909
|
}
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5910
|
+
let tokenSet;
|
|
5911
|
+
try {
|
|
5912
|
+
tokenSet = await refreshMcpAccessToken({
|
|
5913
|
+
authorizationServer: state.authorizationServer,
|
|
5914
|
+
clientId: state.clientId,
|
|
5915
|
+
resource: state.resource,
|
|
5916
|
+
refreshToken
|
|
5917
|
+
});
|
|
5918
|
+
} catch (err) {
|
|
5919
|
+
const current = await this.options.store.load(this.options.serverName, this.resource).catch(() => void 0);
|
|
5920
|
+
if (current && current.tokenSet.refreshToken !== refreshToken) return current;
|
|
5921
|
+
if (err instanceof MCPOAuthHttpError && err.status >= 400 && err.status < 500) {
|
|
5922
|
+
this.rejectedRefreshToken = refreshToken;
|
|
5923
|
+
this.emit("reauth_required", state);
|
|
5924
|
+
return void 0;
|
|
5925
|
+
}
|
|
5926
|
+
throw err;
|
|
5927
|
+
}
|
|
5689
5928
|
const next = normalizeStoredAuthorization({
|
|
5690
5929
|
...state,
|
|
5691
5930
|
tokenSet,
|