@wrongstack/mcp 0.293.0 → 0.295.1
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/README.md +1 -1
- package/dist/client.d.ts +9 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/index.js +227 -168
- package/dist/index.js.map +4 -4
- package/dist/manage.d.ts +4 -2
- package/dist/manage.d.ts.map +1 -1
- package/dist/manifest-cache.d.ts.map +1 -1
- package/dist/operations.d.ts +1 -1
- package/dist/operations.d.ts.map +1 -1
- package/dist/read-body.d.ts +21 -0
- package/dist/read-body.d.ts.map +1 -0
- package/dist/registry.d.ts +3 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/sse-reader.d.ts.map +1 -1
- package/dist/token-store.d.ts +1 -1
- package/dist/token-store.d.ts.map +1 -1
- package/dist/tool-schema.d.ts.map +1 -1
- package/dist/transport-base.d.ts.map +1 -1
- package/dist/transport-jsonrpc.d.ts +2 -2
- package/dist/transport-jsonrpc.d.ts.map +1 -1
- package/dist/transport-sse.d.ts.map +1 -1
- package/dist/transport-streamable.d.ts.map +1 -1
- package/dist/transport.d.ts +2 -2
- package/dist/transport.d.ts.map +1 -1
- package/dist/wrap-tool.d.ts +1 -1
- package/dist/wrap-tool.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -47,7 +47,7 @@ function parseMcpBearerChallenge(header, resource) {
|
|
|
47
47
|
if (!header) return challenge;
|
|
48
48
|
const bearer = /(?:^|,)\s*Bearer(?:\s+|$)/i.exec(header);
|
|
49
49
|
if (!bearer) return challenge;
|
|
50
|
-
const parameters = header.slice(
|
|
50
|
+
const parameters = header.slice(bearer.index + bearer[0].length);
|
|
51
51
|
const resourceMetadata = challengeParameter(parameters, "resource_metadata");
|
|
52
52
|
if (resourceMetadata) {
|
|
53
53
|
const metadataUrl = validateMetadataUrl(resourceMetadata);
|
|
@@ -395,7 +395,7 @@ async function requestPinnedJson(rawUrl, options) {
|
|
|
395
395
|
};
|
|
396
396
|
const requestFn = url.protocol === "https:" ? https.request : http.request;
|
|
397
397
|
const request3 = requestFn(requestOptions, (response) => {
|
|
398
|
-
const status = response.statusCode
|
|
398
|
+
const status = response.statusCode;
|
|
399
399
|
if (status === 404 || status === 410) {
|
|
400
400
|
response.resume();
|
|
401
401
|
finish(void 0, void 0);
|
|
@@ -739,8 +739,7 @@ function boundedServerName(value) {
|
|
|
739
739
|
|
|
740
740
|
// src/client.ts
|
|
741
741
|
import { spawn } from "node:child_process";
|
|
742
|
-
import { buildChildEnv } from "@wrongstack/core";
|
|
743
|
-
import { toErrorMessage } from "@wrongstack/core/utils";
|
|
742
|
+
import { buildChildEnv, toErrorMessage } from "@wrongstack/core/utils";
|
|
744
743
|
|
|
745
744
|
// src/constants.ts
|
|
746
745
|
var MCP_CONSTANTS = Object.freeze({
|
|
@@ -997,13 +996,15 @@ function normalizeMCPTools(value) {
|
|
|
997
996
|
if (typeof t.name !== "string" || t.name.trim().length === 0) continue;
|
|
998
997
|
const inputSchema = t.inputSchema && typeof t.inputSchema === "object" && !Array.isArray(t.inputSchema) ? t.inputSchema : { type: "object", properties: {} };
|
|
999
998
|
if (!t.inputSchema || typeof t.inputSchema !== "object" || Array.isArray(t.inputSchema)) {
|
|
1000
|
-
console.warn(
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
999
|
+
console.warn(
|
|
1000
|
+
JSON.stringify({
|
|
1001
|
+
level: "warn",
|
|
1002
|
+
event: "mcp.tool_schema_invalid",
|
|
1003
|
+
tool: t.name,
|
|
1004
|
+
message: "no/invalid inputSchema \u2014 defaulting to empty object",
|
|
1005
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1006
|
+
})
|
|
1007
|
+
);
|
|
1007
1008
|
}
|
|
1008
1009
|
tools.push({
|
|
1009
1010
|
name: t.name,
|
|
@@ -1014,96 +1015,8 @@ function normalizeMCPTools(value) {
|
|
|
1014
1015
|
return tools;
|
|
1015
1016
|
}
|
|
1016
1017
|
|
|
1017
|
-
// src/transport-jsonrpc.ts
|
|
1018
|
-
import { ToolError } from "@wrongstack/core";
|
|
1019
|
-
function isJsonRpcResult(v) {
|
|
1020
|
-
if (typeof v !== "object" || v === null) return false;
|
|
1021
|
-
const r = v;
|
|
1022
|
-
if (r["jsonrpc"] !== "2.0" || typeof r["id"] !== "number") return false;
|
|
1023
|
-
if (Object.hasOwn(r, "method")) return false;
|
|
1024
|
-
const hasResult = Object.hasOwn(r, "result");
|
|
1025
|
-
const hasError = Object.hasOwn(r, "error");
|
|
1026
|
-
if (hasResult === hasError) return false;
|
|
1027
|
-
if (hasError) {
|
|
1028
|
-
const error = r["error"];
|
|
1029
|
-
return typeof error === "object" && error !== null && typeof error["code"] === "number" && typeof error["message"] === "string";
|
|
1030
|
-
}
|
|
1031
|
-
return true;
|
|
1032
|
-
}
|
|
1033
|
-
function isJsonRpcMethodEnvelope(v) {
|
|
1034
|
-
if (typeof v !== "object" || v === null) return false;
|
|
1035
|
-
const envelope = v;
|
|
1036
|
-
if (envelope["jsonrpc"] !== "2.0" || typeof envelope["method"] !== "string") return false;
|
|
1037
|
-
const id = envelope["id"];
|
|
1038
|
-
return id === void 0 || typeof id === "number" || typeof id === "string";
|
|
1039
|
-
}
|
|
1040
|
-
function extractJsonRpcEnvelopes(text) {
|
|
1041
|
-
const out = [];
|
|
1042
|
-
let dataBuf = [];
|
|
1043
|
-
const flush = () => {
|
|
1044
|
-
if (dataBuf.length === 0) return;
|
|
1045
|
-
const joined = dataBuf.join("\n").trim();
|
|
1046
|
-
dataBuf = [];
|
|
1047
|
-
if (!joined) return;
|
|
1048
|
-
try {
|
|
1049
|
-
const parsed = JSON.parse(joined);
|
|
1050
|
-
if (isJsonRpcResult(parsed) || isJsonRpcMethodEnvelope(parsed)) out.push(parsed);
|
|
1051
|
-
} catch {
|
|
1052
|
-
}
|
|
1053
|
-
};
|
|
1054
|
-
for (const raw of text.split("\n")) {
|
|
1055
|
-
const line = raw.replace(/\r$/, "");
|
|
1056
|
-
if (line === "") {
|
|
1057
|
-
flush();
|
|
1058
|
-
continue;
|
|
1059
|
-
}
|
|
1060
|
-
if (line.startsWith(":")) continue;
|
|
1061
|
-
if (line.startsWith("data:")) {
|
|
1062
|
-
let v = line.slice(5);
|
|
1063
|
-
if (v.startsWith(" ")) v = v.slice(1);
|
|
1064
|
-
dataBuf.push(v);
|
|
1065
|
-
continue;
|
|
1066
|
-
}
|
|
1067
|
-
if (line.startsWith("event:") || line.startsWith("id:") || line.startsWith("retry:")) {
|
|
1068
|
-
continue;
|
|
1069
|
-
}
|
|
1070
|
-
const trimmed = line.trim();
|
|
1071
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
1072
|
-
try {
|
|
1073
|
-
const parsed = JSON.parse(trimmed);
|
|
1074
|
-
if (isJsonRpcResult(parsed) || isJsonRpcMethodEnvelope(parsed)) out.push(parsed);
|
|
1075
|
-
} catch {
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
flush();
|
|
1080
|
-
return out;
|
|
1081
|
-
}
|
|
1082
|
-
function extractJsonRpcResults(text) {
|
|
1083
|
-
return extractJsonRpcEnvelopes(text).filter(isJsonRpcResult);
|
|
1084
|
-
}
|
|
1085
|
-
function assertMatchingJsonRpcResult(data, expectedId, method) {
|
|
1086
|
-
if (!isJsonRpcResult(data)) {
|
|
1087
|
-
throw new ToolError({
|
|
1088
|
-
message: "Invalid JSON-RPC response: not a JSON-RPC 2.0 envelope",
|
|
1089
|
-
code: "TOOL_EXECUTION_FAILED",
|
|
1090
|
-
toolName: "mcp_transport_jsonrpc",
|
|
1091
|
-
context: { method, expectedId, reason: "not-jsonrpc-envelope" }
|
|
1092
|
-
});
|
|
1093
|
-
}
|
|
1094
|
-
if (data.id !== expectedId) {
|
|
1095
|
-
throw new ToolError({
|
|
1096
|
-
message: `Invalid JSON-RPC response: id mismatch for ${method} (expected ${expectedId}, got ${data.id})`,
|
|
1097
|
-
code: "TOOL_EXECUTION_FAILED",
|
|
1098
|
-
toolName: "mcp_transport_jsonrpc",
|
|
1099
|
-
context: { method, expectedId, actualId: data.id, reason: "id-mismatch" }
|
|
1100
|
-
});
|
|
1101
|
-
}
|
|
1102
|
-
return data;
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
1018
|
// src/sse-reader.ts
|
|
1106
|
-
import { ToolError
|
|
1019
|
+
import { ToolError } from "@wrongstack/core/types";
|
|
1107
1020
|
var SSE_READER_MAX_BUFFER = 256 * 1024;
|
|
1108
1021
|
var SSE_READER_MAX_DATA_LINES = 1024;
|
|
1109
1022
|
var SSEReader = class {
|
|
@@ -1119,7 +1032,7 @@ var SSEReader = class {
|
|
|
1119
1032
|
}
|
|
1120
1033
|
feed(chunk) {
|
|
1121
1034
|
if (chunk.length > SSE_READER_MAX_BUFFER) {
|
|
1122
|
-
throw new
|
|
1035
|
+
throw new ToolError({
|
|
1123
1036
|
message: `SSE: chunk size ${chunk.length} exceeds max buffer ${SSE_READER_MAX_BUFFER} \u2014 refusing to accumulate`,
|
|
1124
1037
|
code: "TOOL_EXECUTION_FAILED",
|
|
1125
1038
|
toolName: "mcp_transport_sse_reader",
|
|
@@ -1128,7 +1041,7 @@ var SSEReader = class {
|
|
|
1128
1041
|
}
|
|
1129
1042
|
this.buffer += chunk;
|
|
1130
1043
|
if (this.buffer.length > SSE_READER_MAX_BUFFER) {
|
|
1131
|
-
throw new
|
|
1044
|
+
throw new ToolError({
|
|
1132
1045
|
message: `SSE: pending line exceeds ${SSE_READER_MAX_BUFFER} bytes \u2014 upstream is not framing events`,
|
|
1133
1046
|
code: "TOOL_EXECUTION_FAILED",
|
|
1134
1047
|
toolName: "mcp_transport_sse_reader",
|
|
@@ -1139,13 +1052,16 @@ var SSEReader = class {
|
|
|
1139
1052
|
}
|
|
1140
1053
|
});
|
|
1141
1054
|
}
|
|
1142
|
-
let
|
|
1055
|
+
let start = 0;
|
|
1056
|
+
let idx = this.buffer.indexOf("\n", start);
|
|
1143
1057
|
while (idx !== -1) {
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1058
|
+
let end = idx;
|
|
1059
|
+
if (end > start && this.buffer.charCodeAt(end - 1) === 13) end--;
|
|
1060
|
+
this.processLine(this.buffer.slice(start, end));
|
|
1061
|
+
start = idx + 1;
|
|
1062
|
+
idx = this.buffer.indexOf("\n", start);
|
|
1148
1063
|
}
|
|
1064
|
+
if (start > 0) this.buffer = this.buffer.slice(start);
|
|
1149
1065
|
}
|
|
1150
1066
|
processLine(line) {
|
|
1151
1067
|
if (line === "") {
|
|
@@ -1160,7 +1076,7 @@ var SSEReader = class {
|
|
|
1160
1076
|
if (field === "event") {
|
|
1161
1077
|
} else if (field === "data") {
|
|
1162
1078
|
if (this.dataLines.length >= SSE_READER_MAX_DATA_LINES) {
|
|
1163
|
-
throw new
|
|
1079
|
+
throw new ToolError({
|
|
1164
1080
|
message: `SSE: exceeded ${SSE_READER_MAX_DATA_LINES} data lines per event \u2014 upstream is not sending blank-line delimiters`,
|
|
1165
1081
|
code: "TOOL_EXECUTION_FAILED",
|
|
1166
1082
|
toolName: "mcp_transport_sse_reader",
|
|
@@ -1204,11 +1120,11 @@ var SSEReader = class {
|
|
|
1204
1120
|
|
|
1205
1121
|
// src/transport-base.ts
|
|
1206
1122
|
import * as https2 from "node:https";
|
|
1207
|
-
import { ConfigError as ConfigError2 } from "@wrongstack/core";
|
|
1123
|
+
import { ConfigError as ConfigError2 } from "@wrongstack/core/types";
|
|
1208
1124
|
|
|
1209
1125
|
// src/transport-security.ts
|
|
1210
1126
|
import * as net2 from "node:net";
|
|
1211
|
-
import { ConfigError } from "@wrongstack/core";
|
|
1127
|
+
import { ConfigError } from "@wrongstack/core/types";
|
|
1212
1128
|
function isTlsUnsafeAllowed() {
|
|
1213
1129
|
return process.env["WRONGSTACK_UNSAFE_MCP_TLS"] === "1";
|
|
1214
1130
|
}
|
|
@@ -1450,9 +1366,133 @@ var BaseHTTPTransport = class {
|
|
|
1450
1366
|
}
|
|
1451
1367
|
};
|
|
1452
1368
|
|
|
1369
|
+
// src/transport-jsonrpc.ts
|
|
1370
|
+
import { ToolError as ToolError2 } from "@wrongstack/core/types";
|
|
1371
|
+
function isJsonRpcResult(v) {
|
|
1372
|
+
if (typeof v !== "object" || v === null) return false;
|
|
1373
|
+
const r = v;
|
|
1374
|
+
if (r["jsonrpc"] !== "2.0" || typeof r["id"] !== "number") return false;
|
|
1375
|
+
if (Object.hasOwn(r, "method")) return false;
|
|
1376
|
+
const hasResult = Object.hasOwn(r, "result");
|
|
1377
|
+
const hasError = Object.hasOwn(r, "error");
|
|
1378
|
+
if (hasResult === hasError) return false;
|
|
1379
|
+
if (hasError) {
|
|
1380
|
+
const error = r["error"];
|
|
1381
|
+
return typeof error === "object" && error !== null && typeof error["code"] === "number" && typeof error["message"] === "string";
|
|
1382
|
+
}
|
|
1383
|
+
return true;
|
|
1384
|
+
}
|
|
1385
|
+
function isJsonRpcMethodEnvelope(v) {
|
|
1386
|
+
if (typeof v !== "object" || v === null) return false;
|
|
1387
|
+
const envelope = v;
|
|
1388
|
+
if (envelope["jsonrpc"] !== "2.0" || typeof envelope["method"] !== "string") return false;
|
|
1389
|
+
const id = envelope["id"];
|
|
1390
|
+
return id === void 0 || typeof id === "number" || typeof id === "string";
|
|
1391
|
+
}
|
|
1392
|
+
function extractJsonRpcEnvelopes(text) {
|
|
1393
|
+
const out = [];
|
|
1394
|
+
let dataBuf = [];
|
|
1395
|
+
const flush = () => {
|
|
1396
|
+
if (dataBuf.length === 0) return;
|
|
1397
|
+
const joined = dataBuf.join("\n").trim();
|
|
1398
|
+
dataBuf = [];
|
|
1399
|
+
if (!joined) return;
|
|
1400
|
+
try {
|
|
1401
|
+
const parsed = JSON.parse(joined);
|
|
1402
|
+
if (isJsonRpcResult(parsed) || isJsonRpcMethodEnvelope(parsed)) out.push(parsed);
|
|
1403
|
+
} catch {
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
for (const raw of text.split("\n")) {
|
|
1407
|
+
const line = raw.replace(/\r$/, "");
|
|
1408
|
+
if (line === "") {
|
|
1409
|
+
flush();
|
|
1410
|
+
continue;
|
|
1411
|
+
}
|
|
1412
|
+
if (line.startsWith(":")) continue;
|
|
1413
|
+
if (line.startsWith("data:")) {
|
|
1414
|
+
let v = line.slice(5);
|
|
1415
|
+
if (v.startsWith(" ")) v = v.slice(1);
|
|
1416
|
+
dataBuf.push(v);
|
|
1417
|
+
continue;
|
|
1418
|
+
}
|
|
1419
|
+
if (line.startsWith("event:") || line.startsWith("id:") || line.startsWith("retry:")) {
|
|
1420
|
+
continue;
|
|
1421
|
+
}
|
|
1422
|
+
const trimmed = line.trim();
|
|
1423
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
1424
|
+
try {
|
|
1425
|
+
const parsed = JSON.parse(trimmed);
|
|
1426
|
+
if (isJsonRpcResult(parsed) || isJsonRpcMethodEnvelope(parsed)) out.push(parsed);
|
|
1427
|
+
} catch {
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
flush();
|
|
1432
|
+
return out;
|
|
1433
|
+
}
|
|
1434
|
+
function extractJsonRpcResults(text) {
|
|
1435
|
+
return extractJsonRpcEnvelopes(text).filter(isJsonRpcResult);
|
|
1436
|
+
}
|
|
1437
|
+
function assertMatchingJsonRpcResult(data, expectedId, method) {
|
|
1438
|
+
if (!isJsonRpcResult(data)) {
|
|
1439
|
+
throw new ToolError2({
|
|
1440
|
+
message: "Invalid JSON-RPC response: not a JSON-RPC 2.0 envelope",
|
|
1441
|
+
code: "TOOL_EXECUTION_FAILED",
|
|
1442
|
+
toolName: "mcp_transport_jsonrpc",
|
|
1443
|
+
context: { method, expectedId, reason: "not-jsonrpc-envelope" }
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
if (data.id !== expectedId) {
|
|
1447
|
+
throw new ToolError2({
|
|
1448
|
+
message: `Invalid JSON-RPC response: id mismatch for ${method} (expected ${expectedId}, got ${data.id})`,
|
|
1449
|
+
code: "TOOL_EXECUTION_FAILED",
|
|
1450
|
+
toolName: "mcp_transport_jsonrpc",
|
|
1451
|
+
context: { method, expectedId, actualId: data.id, reason: "id-mismatch" }
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
return data;
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1453
1457
|
// src/transport-sse.ts
|
|
1454
1458
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
1455
|
-
import { ToolError as
|
|
1459
|
+
import { ToolError as ToolError4 } from "@wrongstack/core/types";
|
|
1460
|
+
|
|
1461
|
+
// src/read-body.ts
|
|
1462
|
+
import { ToolError as ToolError3 } from "@wrongstack/core/types";
|
|
1463
|
+
var MAX_MCP_HTTP_BODY_BYTES = 16 * 1024 * 1024;
|
|
1464
|
+
async function readBodyCapped(res, maxBytes = MAX_MCP_HTTP_BODY_BYTES) {
|
|
1465
|
+
const body = res.body;
|
|
1466
|
+
if (!body || typeof body.getReader !== "function") {
|
|
1467
|
+
return await res.text();
|
|
1468
|
+
}
|
|
1469
|
+
const reader = body.getReader();
|
|
1470
|
+
const chunks = [];
|
|
1471
|
+
let total = 0;
|
|
1472
|
+
try {
|
|
1473
|
+
for (; ; ) {
|
|
1474
|
+
const { done, value } = await reader.read();
|
|
1475
|
+
if (done) break;
|
|
1476
|
+
if (!value) continue;
|
|
1477
|
+
total += value.byteLength;
|
|
1478
|
+
if (total > maxBytes) {
|
|
1479
|
+
await reader.cancel().catch(() => void 0);
|
|
1480
|
+
throw new ToolError3({
|
|
1481
|
+
message: `MCP response body exceeded ${maxBytes} bytes \u2014 refusing to buffer`,
|
|
1482
|
+
code: "TOOL_EXECUTION_FAILED",
|
|
1483
|
+
toolName: "mcp_transport",
|
|
1484
|
+
context: { maxBytes, received: total }
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
chunks.push(value);
|
|
1488
|
+
}
|
|
1489
|
+
} finally {
|
|
1490
|
+
reader.releaseLock?.();
|
|
1491
|
+
}
|
|
1492
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// src/transport-sse.ts
|
|
1456
1496
|
var SSETransport = class extends BaseHTTPTransport {
|
|
1457
1497
|
_nextId = 1;
|
|
1458
1498
|
readerDone = false;
|
|
@@ -1499,7 +1539,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1499
1539
|
this.applyTlsAgent(fetchOpts);
|
|
1500
1540
|
const response = await this.fetchWithAuthorization(sseUrl, fetchOpts, signal);
|
|
1501
1541
|
if (!response.ok) {
|
|
1502
|
-
throw new
|
|
1542
|
+
throw new ToolError4({
|
|
1503
1543
|
message: `SSE connect HTTP ${response.status}: ${response.statusText}`,
|
|
1504
1544
|
code: "TOOL_EXECUTION_FAILED",
|
|
1505
1545
|
toolName: "mcp_transport_sse_connect",
|
|
@@ -1507,7 +1547,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1507
1547
|
});
|
|
1508
1548
|
}
|
|
1509
1549
|
if (!response.body) {
|
|
1510
|
-
throw new
|
|
1550
|
+
throw new ToolError4({
|
|
1511
1551
|
message: "SSE response has no body",
|
|
1512
1552
|
code: "TOOL_EXECUTION_FAILED",
|
|
1513
1553
|
toolName: "mcp_transport_sse_connect",
|
|
@@ -1540,7 +1580,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1540
1580
|
clientInfo: MCP_CONSTANTS.CLIENT_INFO
|
|
1541
1581
|
});
|
|
1542
1582
|
if (initRes.error) {
|
|
1543
|
-
throw new
|
|
1583
|
+
throw new ToolError4({
|
|
1544
1584
|
message: `initialize failed: ${initRes.error.message}`,
|
|
1545
1585
|
code: "TOOL_EXECUTION_FAILED",
|
|
1546
1586
|
toolName: "mcp_transport_initialize",
|
|
@@ -1615,7 +1655,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1615
1655
|
const body2 = await res.text();
|
|
1616
1656
|
const cap = MCP_CONSTANTS.REQUEST_LOG_CAP;
|
|
1617
1657
|
const snippet = body2.length > cap ? `${body2.slice(0, cap)}\u2026 [${body2.length} bytes total]` : body2;
|
|
1618
|
-
throw new
|
|
1658
|
+
throw new ToolError4({
|
|
1619
1659
|
message: `HTTP ${res.status}: ${snippet}`,
|
|
1620
1660
|
code: "TOOL_EXECUTION_FAILED",
|
|
1621
1661
|
toolName: method,
|
|
@@ -1624,9 +1664,9 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1624
1664
|
}
|
|
1625
1665
|
let data;
|
|
1626
1666
|
try {
|
|
1627
|
-
data = await res
|
|
1667
|
+
data = JSON.parse(await readBodyCapped(res));
|
|
1628
1668
|
} catch (err) {
|
|
1629
|
-
throw new
|
|
1669
|
+
throw new ToolError4({
|
|
1630
1670
|
message: `Invalid JSON-RPC response: ${err instanceof Error ? err.message : "parse failed"}`,
|
|
1631
1671
|
code: "TOOL_EXECUTION_FAILED",
|
|
1632
1672
|
toolName: method,
|
|
@@ -1651,7 +1691,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1651
1691
|
}
|
|
1652
1692
|
async callTool(name, input, opts) {
|
|
1653
1693
|
if (this.state !== "connected") {
|
|
1654
|
-
throw new
|
|
1694
|
+
throw new ToolError4({
|
|
1655
1695
|
message: `SSE transport not connected (state=${this.state})`,
|
|
1656
1696
|
code: "TOOL_EXECUTION_FAILED",
|
|
1657
1697
|
toolName: name,
|
|
@@ -1688,7 +1728,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1688
1728
|
try {
|
|
1689
1729
|
const res = await this.fetchWithAuthorization(this.url, fetchOpts, timeoutSignal.signal);
|
|
1690
1730
|
if (!res.ok) {
|
|
1691
|
-
throw new
|
|
1731
|
+
throw new ToolError4({
|
|
1692
1732
|
message: `HTTP ${res.status}: ${res.statusText}`,
|
|
1693
1733
|
code: "TOOL_EXECUTION_FAILED",
|
|
1694
1734
|
toolName: method,
|
|
@@ -1702,9 +1742,9 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1702
1742
|
}
|
|
1703
1743
|
let data;
|
|
1704
1744
|
try {
|
|
1705
|
-
data = await res
|
|
1745
|
+
data = JSON.parse(await readBodyCapped(res));
|
|
1706
1746
|
} catch (err) {
|
|
1707
|
-
throw new
|
|
1747
|
+
throw new ToolError4({
|
|
1708
1748
|
message: `Invalid JSON-RPC response: ${err instanceof Error ? err.message : "parse failed"}`,
|
|
1709
1749
|
code: "TOOL_EXECUTION_FAILED",
|
|
1710
1750
|
toolName: method,
|
|
@@ -1764,7 +1804,7 @@ var StreamableHTTPTransport = class extends BaseHTTPTransport {
|
|
|
1764
1804
|
}
|
|
1765
1805
|
}
|
|
1766
1806
|
const responses = envelopes.filter(isJsonRpcResult);
|
|
1767
|
-
return responses.find((envelope) => envelope.id === requestId) ?? responses
|
|
1807
|
+
return responses.find((envelope) => envelope.id === requestId) ?? responses[0];
|
|
1768
1808
|
}
|
|
1769
1809
|
handleNotification(method) {
|
|
1770
1810
|
if (method === "notifications/resources/list_changed") {
|
|
@@ -1883,9 +1923,9 @@ var StreamableHTTPTransport = class extends BaseHTTPTransport {
|
|
|
1883
1923
|
}
|
|
1884
1924
|
if (method.startsWith("notifications/")) {
|
|
1885
1925
|
await res.text().catch(() => void 0);
|
|
1886
|
-
return { jsonrpc: "2.0" };
|
|
1926
|
+
return { jsonrpc: "2.0", id };
|
|
1887
1927
|
}
|
|
1888
|
-
const match = this.consumeResponseText(await res
|
|
1928
|
+
const match = this.consumeResponseText(await readBodyCapped(res), id);
|
|
1889
1929
|
if (match) {
|
|
1890
1930
|
return assertMatchingJsonRpcResult(match, id, method);
|
|
1891
1931
|
}
|
|
@@ -1932,7 +1972,7 @@ var StreamableHTTPTransport = class extends BaseHTTPTransport {
|
|
|
1932
1972
|
await res.text().catch(() => void 0);
|
|
1933
1973
|
return { jsonrpc: "2.0", id };
|
|
1934
1974
|
}
|
|
1935
|
-
const parsed = this.consumeResponseText(await res
|
|
1975
|
+
const parsed = this.consumeResponseText(await readBodyCapped(res), id);
|
|
1936
1976
|
if (parsed) {
|
|
1937
1977
|
return {
|
|
1938
1978
|
jsonrpc: "2.0",
|
|
@@ -1979,17 +2019,32 @@ var StreamableHTTPTransport = class extends BaseHTTPTransport {
|
|
|
1979
2019
|
};
|
|
1980
2020
|
|
|
1981
2021
|
// src/client.ts
|
|
1982
|
-
function
|
|
1983
|
-
if (
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
if (
|
|
1991
|
-
|
|
1992
|
-
|
|
2022
|
+
function forceKillTree(child) {
|
|
2023
|
+
if (child.pid === void 0) {
|
|
2024
|
+
try {
|
|
2025
|
+
child.kill("SIGKILL");
|
|
2026
|
+
} catch {
|
|
2027
|
+
}
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
if (process.platform === "win32") {
|
|
2031
|
+
const killer = spawn("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
|
|
2032
|
+
stdio: "ignore",
|
|
2033
|
+
windowsHide: true
|
|
2034
|
+
});
|
|
2035
|
+
killer.once("error", () => {
|
|
2036
|
+
try {
|
|
2037
|
+
child.kill("SIGKILL");
|
|
2038
|
+
} catch {
|
|
2039
|
+
}
|
|
2040
|
+
});
|
|
2041
|
+
killer.unref();
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
try {
|
|
2045
|
+
child.kill("SIGKILL");
|
|
2046
|
+
} catch {
|
|
2047
|
+
}
|
|
1993
2048
|
}
|
|
1994
2049
|
var MCPClient = class _MCPClient {
|
|
1995
2050
|
constructor(opts) {
|
|
@@ -2383,10 +2438,7 @@ var MCPClient = class _MCPClient {
|
|
|
2383
2438
|
new Promise((resolve) => setTimeout(() => resolve("timeout"), GRACEFUL_MS))
|
|
2384
2439
|
]);
|
|
2385
2440
|
if (gracefulRace === "timeout") {
|
|
2386
|
-
|
|
2387
|
-
child.kill("SIGKILL");
|
|
2388
|
-
} catch {
|
|
2389
|
-
}
|
|
2441
|
+
forceKillTree(child);
|
|
2390
2442
|
await Promise.race([
|
|
2391
2443
|
exitPromise,
|
|
2392
2444
|
new Promise((resolve) => setTimeout(resolve, FORCE_TIMEOUT_MS))
|
|
@@ -2615,11 +2667,12 @@ var MCPClient = class _MCPClient {
|
|
|
2615
2667
|
}
|
|
2616
2668
|
return;
|
|
2617
2669
|
}
|
|
2618
|
-
if (!
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
this.pending.
|
|
2622
|
-
|
|
2670
|
+
if (!isJsonRpcResult(msg)) return;
|
|
2671
|
+
const response = msg;
|
|
2672
|
+
if (this.pending.has(response.id)) {
|
|
2673
|
+
const entry = this.pending.get(response.id);
|
|
2674
|
+
this.pending.delete(response.id);
|
|
2675
|
+
entry?.resolve(response);
|
|
2623
2676
|
}
|
|
2624
2677
|
}
|
|
2625
2678
|
handleServerRequest(request3) {
|
|
@@ -2853,7 +2906,7 @@ async function writeConfig(path2, cfg) {
|
|
|
2853
2906
|
try {
|
|
2854
2907
|
await fs.rename(tmp, path2);
|
|
2855
2908
|
} catch (err) {
|
|
2856
|
-
await fs.rm(tmp, { force: true })
|
|
2909
|
+
await fs.rm(tmp, { force: true });
|
|
2857
2910
|
throw err;
|
|
2858
2911
|
}
|
|
2859
2912
|
}
|
|
@@ -2917,6 +2970,8 @@ function projectServer(name, cfg, registry) {
|
|
|
2917
2970
|
if (cfg.description !== void 0) info.description = cfg.description;
|
|
2918
2971
|
if (cfg.url !== void 0) info.url = cfg.url;
|
|
2919
2972
|
if (cfg.command !== void 0) info.command = cfg.command;
|
|
2973
|
+
if (cfg.args !== void 0) info.args = cfg.args;
|
|
2974
|
+
if (cfg.env !== void 0) info.env = cfg.env;
|
|
2920
2975
|
if (cfg.lazy !== void 0) info.lazy = cfg.lazy;
|
|
2921
2976
|
return info;
|
|
2922
2977
|
}
|
|
@@ -3091,7 +3146,7 @@ function forgetRegistryState(registry, name) {
|
|
|
3091
3146
|
}
|
|
3092
3147
|
|
|
3093
3148
|
// src/manifest-cache.ts
|
|
3094
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
3149
|
+
import { createHash as createHash2, randomBytes as randomBytes4 } from "node:crypto";
|
|
3095
3150
|
import * as fs2 from "node:fs/promises";
|
|
3096
3151
|
import * as path from "node:path";
|
|
3097
3152
|
function manifestConfigHash(cfg) {
|
|
@@ -3139,9 +3194,14 @@ async function writeCapabilityManifest(cacheDir, name, configHash, manifest) {
|
|
|
3139
3194
|
const file = manifestFile(cacheDir, name);
|
|
3140
3195
|
await fs2.mkdir(path.dirname(file), { recursive: true });
|
|
3141
3196
|
const body = { version: 2, configHash, ...manifest };
|
|
3142
|
-
const tmp = `${file}.tmp`;
|
|
3143
|
-
|
|
3144
|
-
|
|
3197
|
+
const tmp = `${file}.${process.pid}.${randomBytes4(6).toString("hex")}.tmp`;
|
|
3198
|
+
try {
|
|
3199
|
+
await fs2.writeFile(tmp, JSON.stringify(body, null, 2), "utf8");
|
|
3200
|
+
await fs2.rename(tmp, file);
|
|
3201
|
+
} catch (err) {
|
|
3202
|
+
await fs2.rm(tmp, { force: true });
|
|
3203
|
+
throw err;
|
|
3204
|
+
}
|
|
3145
3205
|
} catch {
|
|
3146
3206
|
}
|
|
3147
3207
|
}
|
|
@@ -3202,7 +3262,10 @@ function evaluateHealthThresholds(operations, thresholds) {
|
|
|
3202
3262
|
if (!thresholds) return [];
|
|
3203
3263
|
const checks = [];
|
|
3204
3264
|
if (thresholds.connectionLatencyP95Ms !== void 0 && operations.connectionSamples.length > 0) {
|
|
3205
|
-
const value = percentile(
|
|
3265
|
+
const value = percentile(
|
|
3266
|
+
[...operations.connectionSamples].sort((a, b) => a - b),
|
|
3267
|
+
0.95
|
|
3268
|
+
);
|
|
3206
3269
|
checks.push({
|
|
3207
3270
|
name: "connection-latency-p95",
|
|
3208
3271
|
passed: value <= thresholds.connectionLatencyP95Ms,
|
|
@@ -3211,7 +3274,10 @@ function evaluateHealthThresholds(operations, thresholds) {
|
|
|
3211
3274
|
});
|
|
3212
3275
|
}
|
|
3213
3276
|
if (thresholds.discoveryLatencyP95Ms !== void 0 && operations.discoverySamples.length > 0) {
|
|
3214
|
-
const value = percentile(
|
|
3277
|
+
const value = percentile(
|
|
3278
|
+
[...operations.discoverySamples].sort((a, b) => a - b),
|
|
3279
|
+
0.95
|
|
3280
|
+
);
|
|
3215
3281
|
checks.push({
|
|
3216
3282
|
name: "discovery-latency-p95",
|
|
3217
3283
|
passed: value <= thresholds.discoveryLatencyP95Ms,
|
|
@@ -3220,7 +3286,10 @@ function evaluateHealthThresholds(operations, thresholds) {
|
|
|
3220
3286
|
});
|
|
3221
3287
|
}
|
|
3222
3288
|
if (thresholds.callLatencyP95Ms !== void 0 && operations.callSamples.length > 0) {
|
|
3223
|
-
const value = percentile(
|
|
3289
|
+
const value = percentile(
|
|
3290
|
+
[...operations.callSamples].sort((a, b) => a - b),
|
|
3291
|
+
0.95
|
|
3292
|
+
);
|
|
3224
3293
|
checks.push({
|
|
3225
3294
|
name: "call-latency-p95",
|
|
3226
3295
|
passed: value <= thresholds.callLatencyP95Ms,
|
|
@@ -3268,10 +3337,10 @@ function percentile(sorted, ratio) {
|
|
|
3268
3337
|
}
|
|
3269
3338
|
|
|
3270
3339
|
// src/registry.ts
|
|
3271
|
-
import { expectDefined } from "@wrongstack/core";
|
|
3340
|
+
import { expectDefined } from "@wrongstack/core/utils";
|
|
3272
3341
|
|
|
3273
3342
|
// src/wrap-tool.ts
|
|
3274
|
-
import { ToolCapabilities } from "@wrongstack/core";
|
|
3343
|
+
import { ToolCapabilities } from "@wrongstack/core/security";
|
|
3275
3344
|
var MUTATING_RE = /create|update|delete|write|send|set|put|post|patch|remove|rename|move/i;
|
|
3276
3345
|
function isMutatingTool(mcpTool) {
|
|
3277
3346
|
if (MUTATING_RE.test(mcpTool.name)) return true;
|
|
@@ -3456,11 +3525,7 @@ var MCPRegistry = class _MCPRegistry {
|
|
|
3456
3525
|
* cache yet, do a one-time cold discovery connect to learn + cache the tools.
|
|
3457
3526
|
*/
|
|
3458
3527
|
async startLazy(slot) {
|
|
3459
|
-
const cacheDir = this.cacheDir;
|
|
3460
|
-
if (!cacheDir) {
|
|
3461
|
-
await this.attemptConnect(slot);
|
|
3462
|
-
return;
|
|
3463
|
-
}
|
|
3528
|
+
const cacheDir = expectDefined(this.cacheDir);
|
|
3464
3529
|
const hash = manifestConfigHash(slot.cfg);
|
|
3465
3530
|
const cached = await readCapabilityManifest(cacheDir, slot.cfg.name, hash);
|
|
3466
3531
|
if (cached) {
|
|
@@ -4298,8 +4363,7 @@ function catalogSnapshot(slot) {
|
|
|
4298
4363
|
|
|
4299
4364
|
// src/server.ts
|
|
4300
4365
|
import { createServer } from "node:http";
|
|
4301
|
-
import { expectDefined as expectDefined2 } from "@wrongstack/core";
|
|
4302
|
-
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
4366
|
+
import { expectDefined as expectDefined2, toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
4303
4367
|
var PARSE_ERROR = -32700;
|
|
4304
4368
|
var INVALID_REQUEST = -32600;
|
|
4305
4369
|
var METHOD_NOT_FOUND = -32601;
|
|
@@ -4624,8 +4688,7 @@ function serveHttp(server, opts = {}) {
|
|
|
4624
4688
|
httpServer.once("error", reject);
|
|
4625
4689
|
httpServer.listen(port, host, () => {
|
|
4626
4690
|
httpServer.removeListener("error", reject);
|
|
4627
|
-
const
|
|
4628
|
-
const boundPort = typeof addr === "object" && addr ? addr.port : port;
|
|
4691
|
+
const boundPort = httpServer.address().port;
|
|
4629
4692
|
const displayHost = host === "::1" ? "[::1]" : host;
|
|
4630
4693
|
resolve({
|
|
4631
4694
|
port: boundPort,
|
|
@@ -4657,18 +4720,14 @@ async function handleHttpRequest(server, req, res, token, log) {
|
|
|
4657
4720
|
}
|
|
4658
4721
|
}
|
|
4659
4722
|
let body = "";
|
|
4660
|
-
let tooLarge = false;
|
|
4661
4723
|
req.on("data", (chunk) => {
|
|
4662
|
-
if (tooLarge) return;
|
|
4663
4724
|
body += chunk.toString("utf8");
|
|
4664
4725
|
if (body.length > HTTP_BODY_CAP) {
|
|
4665
|
-
tooLarge = true;
|
|
4666
4726
|
send(413, JSON.stringify({ error: "payload too large" }));
|
|
4667
4727
|
req.destroy();
|
|
4668
4728
|
}
|
|
4669
4729
|
});
|
|
4670
4730
|
req.on("end", () => {
|
|
4671
|
-
if (tooLarge) return;
|
|
4672
4731
|
void server.handleMessage(body).then((out) => {
|
|
4673
4732
|
if (out === null) return send(202, "");
|
|
4674
4733
|
return send(200, out);
|