@wrongstack/mcp 0.286.0 → 0.289.0
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/client.d.ts +9 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/index.js +183 -146
- package/dist/index.js.map +4 -4
- package/dist/sse-reader.d.ts +17 -0
- package/dist/sse-reader.d.ts.map +1 -0
- package/dist/transport-base.d.ts +90 -0
- package/dist/transport-base.d.ts.map +1 -0
- package/dist/transport-jsonrpc.d.ts +32 -0
- package/dist/transport-jsonrpc.d.ts.map +1 -0
- package/dist/transport-security.d.ts +13 -0
- package/dist/transport-security.d.ts.map +1 -0
- package/dist/transport-sse.d.ts +31 -0
- package/dist/transport-sse.d.ts.map +1 -0
- package/dist/transport-streamable.d.ts +27 -0
- package/dist/transport-streamable.d.ts.map +1 -0
- package/dist/transport.d.ts +7 -160
- package/dist/transport.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/client.d.ts
CHANGED
|
@@ -66,6 +66,15 @@ export type MCPListChangedListener = (name: string) => void;
|
|
|
66
66
|
*/
|
|
67
67
|
export declare class MCPClient {
|
|
68
68
|
readonly opts: MCPClientOptions;
|
|
69
|
+
/**
|
|
70
|
+
* Maximum bytes the rx buffer may accumulate before the connection is
|
|
71
|
+
* forcefully closed. A well-behaved JSON-RPC server emits newline-delimited
|
|
72
|
+
* messages that are individually much smaller than this; a server that never
|
|
73
|
+
* sends a newline would grow the buffer without limit and OOM the process.
|
|
74
|
+
* 16 MiB is generous for any legitimate single message while bounding the
|
|
75
|
+
* worst-case memory to a predictable cap.
|
|
76
|
+
*/
|
|
77
|
+
private static readonly MAX_RX_BUFFER_BYTES;
|
|
69
78
|
private state;
|
|
70
79
|
private child?;
|
|
71
80
|
private nextId;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAEnE,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,8BAA8B,EACnC,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EAOvB,MAAM,eAAe,CAAC;AAIvB,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,KAAK,GAAG,iBAAiB,CAAC;AAE5D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACzC,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IAC7C,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,kEAAkE;IAClE,qBAAqB,CAAC,EAAE,wBAAwB,GAAG,SAAS,CAAC;IAC7D;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,YAAY,GACZ,WAAW,GACX,cAAc,GACd,cAAc,GACd,QAAQ;AACV,2EAA2E;GACzE,SAAS,CAAC;AAEd,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;CAClC;AAED,MAAM,WAAW,cAAe,SAAQ,iBAAiB;IACvD,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AASD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC7B,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;KAAE,GAAG,SAAS,CAAC;CAC/F;AA6BD,KAAK,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;AACvF;;;;GAIG;AACH,KAAK,oBAAoB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;AACrE,MAAM,MAAM,sBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAE5D;;;;;GAKG;AACH,qBAAa,SAAS;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1014,66 +1014,96 @@ function normalizeMCPTools(value) {
|
|
|
1014
1014
|
return tools;
|
|
1015
1015
|
}
|
|
1016
1016
|
|
|
1017
|
-
// src/transport.ts
|
|
1018
|
-
import {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
throw new ConfigError({
|
|
1031
|
-
message: `MCP transport: invalid URL "${rawUrl}"`,
|
|
1032
|
-
code: "CONFIG_INVALID",
|
|
1033
|
-
context: { field: "url", rawUrl }
|
|
1034
|
-
});
|
|
1035
|
-
}
|
|
1036
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
1037
|
-
throw new ConfigError({
|
|
1038
|
-
message: `MCP transport: unsupported protocol "${url.protocol}" \u2014 only http/https allowed`,
|
|
1039
|
-
code: "CONFIG_INVALID",
|
|
1040
|
-
context: { field: "url", rawUrl, protocol: url.protocol }
|
|
1041
|
-
});
|
|
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";
|
|
1042
1030
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
if (
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
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 {
|
|
1054
1052
|
}
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
const
|
|
1058
|
-
if (
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
code: "CONFIG_INVALID",
|
|
1062
|
-
context: { field: "url", rawUrl, hostname }
|
|
1063
|
-
});
|
|
1053
|
+
};
|
|
1054
|
+
for (const raw of text.split("\n")) {
|
|
1055
|
+
const line = raw.replace(/\r$/, "");
|
|
1056
|
+
if (line === "") {
|
|
1057
|
+
flush();
|
|
1058
|
+
continue;
|
|
1064
1059
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
code: "CONFIG_INVALID",
|
|
1072
|
-
context: { field: "url", rawUrl, hostname, protocol: url.protocol }
|
|
1073
|
-
});
|
|
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;
|
|
1074
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
|
+
});
|
|
1075
1101
|
}
|
|
1102
|
+
return data;
|
|
1076
1103
|
}
|
|
1104
|
+
|
|
1105
|
+
// src/sse-reader.ts
|
|
1106
|
+
import { ToolError as ToolError2 } from "@wrongstack/core";
|
|
1077
1107
|
var SSE_READER_MAX_BUFFER = 256 * 1024;
|
|
1078
1108
|
var SSE_READER_MAX_DATA_LINES = 1024;
|
|
1079
1109
|
var SSEReader = class {
|
|
@@ -1089,7 +1119,7 @@ var SSEReader = class {
|
|
|
1089
1119
|
}
|
|
1090
1120
|
feed(chunk) {
|
|
1091
1121
|
if (chunk.length > SSE_READER_MAX_BUFFER) {
|
|
1092
|
-
throw new
|
|
1122
|
+
throw new ToolError2({
|
|
1093
1123
|
message: `SSE: chunk size ${chunk.length} exceeds max buffer ${SSE_READER_MAX_BUFFER} \u2014 refusing to accumulate`,
|
|
1094
1124
|
code: "TOOL_EXECUTION_FAILED",
|
|
1095
1125
|
toolName: "mcp_transport_sse_reader",
|
|
@@ -1098,7 +1128,7 @@ var SSEReader = class {
|
|
|
1098
1128
|
}
|
|
1099
1129
|
this.buffer += chunk;
|
|
1100
1130
|
if (this.buffer.length > SSE_READER_MAX_BUFFER) {
|
|
1101
|
-
throw new
|
|
1131
|
+
throw new ToolError2({
|
|
1102
1132
|
message: `SSE: pending line exceeds ${SSE_READER_MAX_BUFFER} bytes \u2014 upstream is not framing events`,
|
|
1103
1133
|
code: "TOOL_EXECUTION_FAILED",
|
|
1104
1134
|
toolName: "mcp_transport_sse_reader",
|
|
@@ -1130,7 +1160,7 @@ var SSEReader = class {
|
|
|
1130
1160
|
if (field === "event") {
|
|
1131
1161
|
} else if (field === "data") {
|
|
1132
1162
|
if (this.dataLines.length >= SSE_READER_MAX_DATA_LINES) {
|
|
1133
|
-
throw new
|
|
1163
|
+
throw new ToolError2({
|
|
1134
1164
|
message: `SSE: exceeded ${SSE_READER_MAX_DATA_LINES} data lines per event \u2014 upstream is not sending blank-line delimiters`,
|
|
1135
1165
|
code: "TOOL_EXECUTION_FAILED",
|
|
1136
1166
|
toolName: "mcp_transport_sse_reader",
|
|
@@ -1171,91 +1201,71 @@ var SSEReader = class {
|
|
|
1171
1201
|
this.listeners = [];
|
|
1172
1202
|
}
|
|
1173
1203
|
};
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
return typeof error === "object" && error !== null && typeof error["code"] === "number" && typeof error["message"] === "string";
|
|
1185
|
-
}
|
|
1186
|
-
return true;
|
|
1187
|
-
}
|
|
1188
|
-
function isJsonRpcMethodEnvelope(v) {
|
|
1189
|
-
if (typeof v !== "object" || v === null) return false;
|
|
1190
|
-
const envelope = v;
|
|
1191
|
-
if (envelope["jsonrpc"] !== "2.0" || typeof envelope["method"] !== "string") return false;
|
|
1192
|
-
const id = envelope["id"];
|
|
1193
|
-
return id === void 0 || typeof id === "number" || typeof id === "string";
|
|
1194
|
-
}
|
|
1195
|
-
function extractJsonRpcEnvelopes(text) {
|
|
1196
|
-
const out = [];
|
|
1197
|
-
let dataBuf = [];
|
|
1198
|
-
const flush = () => {
|
|
1199
|
-
if (dataBuf.length === 0) return;
|
|
1200
|
-
const joined = dataBuf.join("\n").trim();
|
|
1201
|
-
dataBuf = [];
|
|
1202
|
-
if (!joined) return;
|
|
1203
|
-
try {
|
|
1204
|
-
const parsed = JSON.parse(joined);
|
|
1205
|
-
if (isJsonRpcResult(parsed) || isJsonRpcMethodEnvelope(parsed)) out.push(parsed);
|
|
1206
|
-
} catch {
|
|
1207
|
-
}
|
|
1208
|
-
};
|
|
1209
|
-
for (const raw of text.split("\n")) {
|
|
1210
|
-
const line = raw.replace(/\r$/, "");
|
|
1211
|
-
if (line === "") {
|
|
1212
|
-
flush();
|
|
1213
|
-
continue;
|
|
1214
|
-
}
|
|
1215
|
-
if (line.startsWith(":")) continue;
|
|
1216
|
-
if (line.startsWith("data:")) {
|
|
1217
|
-
let v = line.slice(5);
|
|
1218
|
-
if (v.startsWith(" ")) v = v.slice(1);
|
|
1219
|
-
dataBuf.push(v);
|
|
1220
|
-
continue;
|
|
1221
|
-
}
|
|
1222
|
-
if (line.startsWith("event:") || line.startsWith("id:") || line.startsWith("retry:")) {
|
|
1223
|
-
continue;
|
|
1224
|
-
}
|
|
1225
|
-
const trimmed = line.trim();
|
|
1226
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
1227
|
-
try {
|
|
1228
|
-
const parsed = JSON.parse(trimmed);
|
|
1229
|
-
if (isJsonRpcResult(parsed) || isJsonRpcMethodEnvelope(parsed)) out.push(parsed);
|
|
1230
|
-
} catch {
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
flush();
|
|
1235
|
-
return out;
|
|
1236
|
-
}
|
|
1237
|
-
function extractJsonRpcResults(text) {
|
|
1238
|
-
return extractJsonRpcEnvelopes(text).filter(isJsonRpcResult);
|
|
1204
|
+
|
|
1205
|
+
// src/transport-base.ts
|
|
1206
|
+
import * as https2 from "node:https";
|
|
1207
|
+
import { ConfigError as ConfigError2 } from "@wrongstack/core";
|
|
1208
|
+
|
|
1209
|
+
// src/transport-security.ts
|
|
1210
|
+
import * as net2 from "node:net";
|
|
1211
|
+
import { ConfigError } from "@wrongstack/core";
|
|
1212
|
+
function isTlsUnsafeAllowed() {
|
|
1213
|
+
return process.env["WRONGSTACK_UNSAFE_MCP_TLS"] === "1";
|
|
1239
1214
|
}
|
|
1240
|
-
function
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1215
|
+
function validateTransportUrl(rawUrl) {
|
|
1216
|
+
let url;
|
|
1217
|
+
try {
|
|
1218
|
+
url = new URL(rawUrl);
|
|
1219
|
+
} catch {
|
|
1220
|
+
throw new ConfigError({
|
|
1221
|
+
message: `MCP transport: invalid URL "${rawUrl}"`,
|
|
1222
|
+
code: "CONFIG_INVALID",
|
|
1223
|
+
context: { field: "url", rawUrl }
|
|
1247
1224
|
});
|
|
1248
1225
|
}
|
|
1249
|
-
if (
|
|
1250
|
-
throw new
|
|
1251
|
-
message: `
|
|
1252
|
-
code: "
|
|
1253
|
-
|
|
1254
|
-
context: { method, expectedId, actualId: data.id, reason: "id-mismatch" }
|
|
1226
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
1227
|
+
throw new ConfigError({
|
|
1228
|
+
message: `MCP transport: unsupported protocol "${url.protocol}" \u2014 only http/https allowed`,
|
|
1229
|
+
code: "CONFIG_INVALID",
|
|
1230
|
+
context: { field: "url", rawUrl, protocol: url.protocol }
|
|
1255
1231
|
});
|
|
1256
1232
|
}
|
|
1257
|
-
|
|
1233
|
+
const hostname = url.hostname;
|
|
1234
|
+
const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
1235
|
+
const ipVersion = net2.isIP(host);
|
|
1236
|
+
if (ipVersion === 4) {
|
|
1237
|
+
const parts = host.split(".").map(Number);
|
|
1238
|
+
if (parts[0] === 169 && parts[1] === 254) {
|
|
1239
|
+
throw new ConfigError({
|
|
1240
|
+
message: `MCP transport: blocked link-local/IMDS address "${hostname}" \u2014 likely not a valid MCP server`,
|
|
1241
|
+
code: "CONFIG_INVALID",
|
|
1242
|
+
context: { field: "url", rawUrl, hostname }
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
} else if (ipVersion === 6) {
|
|
1246
|
+
const lower = host.toLowerCase();
|
|
1247
|
+
const linkLocal = /^fe[89ab]/.test(lower);
|
|
1248
|
+
if (linkLocal || lower === "fd00:ec2::254") {
|
|
1249
|
+
throw new ConfigError({
|
|
1250
|
+
message: `MCP transport: blocked link-local/IMDS address "${hostname}" \u2014 likely not a valid MCP server`,
|
|
1251
|
+
code: "CONFIG_INVALID",
|
|
1252
|
+
context: { field: "url", rawUrl, hostname }
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
if (url.protocol === "http:") {
|
|
1257
|
+
const isLoopback = hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
1258
|
+
if (!isLoopback) {
|
|
1259
|
+
throw new ConfigError({
|
|
1260
|
+
message: `MCP transport: http:// is only allowed for loopback addresses; use https:// for "${hostname}"`,
|
|
1261
|
+
code: "CONFIG_INVALID",
|
|
1262
|
+
context: { field: "url", rawUrl, hostname, protocol: url.protocol }
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1258
1266
|
}
|
|
1267
|
+
|
|
1268
|
+
// src/transport-base.ts
|
|
1259
1269
|
function makeAbortError(method) {
|
|
1260
1270
|
const err = new Error(`MCP request "${method}" aborted by client`);
|
|
1261
1271
|
err.name = "AbortError";
|
|
@@ -1312,7 +1322,7 @@ var BaseHTTPTransport = class {
|
|
|
1312
1322
|
if (opts.tls) {
|
|
1313
1323
|
if (opts.tls.rejectUnauthorized === false) {
|
|
1314
1324
|
if (!isTlsUnsafeAllowed()) {
|
|
1315
|
-
throw new
|
|
1325
|
+
throw new ConfigError2({
|
|
1316
1326
|
message: `[mcp:${transportName}] TLS verification disabled \u2014 set WRONGSTACK_UNSAFE_MCP_TLS=1 to allow. Rejecting insecure configuration for ${this.url}.`,
|
|
1317
1327
|
code: "CONFIG_INVALID",
|
|
1318
1328
|
context: { field: "tls.rejectUnauthorized", transportName, url: this.url }
|
|
@@ -1439,6 +1449,10 @@ var BaseHTTPTransport = class {
|
|
|
1439
1449
|
}
|
|
1440
1450
|
}
|
|
1441
1451
|
};
|
|
1452
|
+
|
|
1453
|
+
// src/transport-sse.ts
|
|
1454
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
1455
|
+
import { ToolError as ToolError3 } from "@wrongstack/core";
|
|
1442
1456
|
var SSETransport = class extends BaseHTTPTransport {
|
|
1443
1457
|
_nextId = 1;
|
|
1444
1458
|
readerDone = false;
|
|
@@ -1485,7 +1499,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1485
1499
|
this.applyTlsAgent(fetchOpts);
|
|
1486
1500
|
const response = await this.fetchWithAuthorization(sseUrl, fetchOpts, signal);
|
|
1487
1501
|
if (!response.ok) {
|
|
1488
|
-
throw new
|
|
1502
|
+
throw new ToolError3({
|
|
1489
1503
|
message: `SSE connect HTTP ${response.status}: ${response.statusText}`,
|
|
1490
1504
|
code: "TOOL_EXECUTION_FAILED",
|
|
1491
1505
|
toolName: "mcp_transport_sse_connect",
|
|
@@ -1493,7 +1507,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1493
1507
|
});
|
|
1494
1508
|
}
|
|
1495
1509
|
if (!response.body) {
|
|
1496
|
-
throw new
|
|
1510
|
+
throw new ToolError3({
|
|
1497
1511
|
message: "SSE response has no body",
|
|
1498
1512
|
code: "TOOL_EXECUTION_FAILED",
|
|
1499
1513
|
toolName: "mcp_transport_sse_connect",
|
|
@@ -1526,7 +1540,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1526
1540
|
clientInfo: MCP_CONSTANTS.CLIENT_INFO
|
|
1527
1541
|
});
|
|
1528
1542
|
if (initRes.error) {
|
|
1529
|
-
throw new
|
|
1543
|
+
throw new ToolError3({
|
|
1530
1544
|
message: `initialize failed: ${initRes.error.message}`,
|
|
1531
1545
|
code: "TOOL_EXECUTION_FAILED",
|
|
1532
1546
|
toolName: "mcp_transport_initialize",
|
|
@@ -1601,7 +1615,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1601
1615
|
const body2 = await res.text();
|
|
1602
1616
|
const cap = MCP_CONSTANTS.REQUEST_LOG_CAP;
|
|
1603
1617
|
const snippet = body2.length > cap ? `${body2.slice(0, cap)}\u2026 [${body2.length} bytes total]` : body2;
|
|
1604
|
-
throw new
|
|
1618
|
+
throw new ToolError3({
|
|
1605
1619
|
message: `HTTP ${res.status}: ${snippet}`,
|
|
1606
1620
|
code: "TOOL_EXECUTION_FAILED",
|
|
1607
1621
|
toolName: method,
|
|
@@ -1612,7 +1626,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1612
1626
|
try {
|
|
1613
1627
|
data = await res.json();
|
|
1614
1628
|
} catch (err) {
|
|
1615
|
-
throw new
|
|
1629
|
+
throw new ToolError3({
|
|
1616
1630
|
message: `Invalid JSON-RPC response: ${err instanceof Error ? err.message : "parse failed"}`,
|
|
1617
1631
|
code: "TOOL_EXECUTION_FAILED",
|
|
1618
1632
|
toolName: method,
|
|
@@ -1637,7 +1651,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1637
1651
|
}
|
|
1638
1652
|
async callTool(name, input, opts) {
|
|
1639
1653
|
if (this.state !== "connected") {
|
|
1640
|
-
throw new
|
|
1654
|
+
throw new ToolError3({
|
|
1641
1655
|
message: `SSE transport not connected (state=${this.state})`,
|
|
1642
1656
|
code: "TOOL_EXECUTION_FAILED",
|
|
1643
1657
|
toolName: name,
|
|
@@ -1674,7 +1688,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1674
1688
|
try {
|
|
1675
1689
|
const res = await this.fetchWithAuthorization(this.url, fetchOpts, timeoutSignal.signal);
|
|
1676
1690
|
if (!res.ok) {
|
|
1677
|
-
throw new
|
|
1691
|
+
throw new ToolError3({
|
|
1678
1692
|
message: `HTTP ${res.status}: ${res.statusText}`,
|
|
1679
1693
|
code: "TOOL_EXECUTION_FAILED",
|
|
1680
1694
|
toolName: method,
|
|
@@ -1690,7 +1704,7 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1690
1704
|
try {
|
|
1691
1705
|
data = await res.json();
|
|
1692
1706
|
} catch (err) {
|
|
1693
|
-
throw new
|
|
1707
|
+
throw new ToolError3({
|
|
1694
1708
|
message: `Invalid JSON-RPC response: ${err instanceof Error ? err.message : "parse failed"}`,
|
|
1695
1709
|
code: "TOOL_EXECUTION_FAILED",
|
|
1696
1710
|
toolName: method,
|
|
@@ -1731,6 +1745,8 @@ var SSETransport = class extends BaseHTTPTransport {
|
|
|
1731
1745
|
this.state = "disconnected";
|
|
1732
1746
|
}
|
|
1733
1747
|
};
|
|
1748
|
+
|
|
1749
|
+
// src/transport-streamable.ts
|
|
1734
1750
|
var StreamableHTTPTransport = class extends BaseHTTPTransport {
|
|
1735
1751
|
_nextId = 1;
|
|
1736
1752
|
sessionId;
|
|
@@ -1975,11 +1991,20 @@ function isJsonRpcResponse(value) {
|
|
|
1975
1991
|
const error = response["error"];
|
|
1976
1992
|
return typeof error === "object" && error !== null && typeof error["code"] === "number" && typeof error["message"] === "string";
|
|
1977
1993
|
}
|
|
1978
|
-
var MCPClient = class {
|
|
1994
|
+
var MCPClient = class _MCPClient {
|
|
1979
1995
|
constructor(opts) {
|
|
1980
1996
|
this.opts = opts;
|
|
1981
1997
|
}
|
|
1982
1998
|
opts;
|
|
1999
|
+
/**
|
|
2000
|
+
* Maximum bytes the rx buffer may accumulate before the connection is
|
|
2001
|
+
* forcefully closed. A well-behaved JSON-RPC server emits newline-delimited
|
|
2002
|
+
* messages that are individually much smaller than this; a server that never
|
|
2003
|
+
* sends a newline would grow the buffer without limit and OOM the process.
|
|
2004
|
+
* 16 MiB is generous for any legitimate single message while bounding the
|
|
2005
|
+
* worst-case memory to a predictable cap.
|
|
2006
|
+
*/
|
|
2007
|
+
static MAX_RX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
1983
2008
|
state = "idle";
|
|
1984
2009
|
child;
|
|
1985
2010
|
nextId = 1;
|
|
@@ -2092,6 +2117,9 @@ var MCPClient = class {
|
|
|
2092
2117
|
child.stdout?.on("data", (chunk) => this.onData(chunk.toString()));
|
|
2093
2118
|
child.stderr?.on("data", () => {
|
|
2094
2119
|
});
|
|
2120
|
+
child.stdin?.on("error", (err) => {
|
|
2121
|
+
this.failPending(`MCP "${this.opts.name}" stdin error: ${toErrorMessage(err)}`);
|
|
2122
|
+
});
|
|
2095
2123
|
child.on("exit", (code, signal) => {
|
|
2096
2124
|
this.state = "disconnected";
|
|
2097
2125
|
this.failPending(
|
|
@@ -2539,6 +2567,15 @@ var MCPClient = class {
|
|
|
2539
2567
|
}
|
|
2540
2568
|
onData(s) {
|
|
2541
2569
|
this.rxBuffer += s;
|
|
2570
|
+
if (this.rxBuffer.length > _MCPClient.MAX_RX_BUFFER_BYTES) {
|
|
2571
|
+
const truncated = this.rxBuffer.length;
|
|
2572
|
+
this.rxBuffer = "";
|
|
2573
|
+
this.failPending(
|
|
2574
|
+
`MCP "${this.opts.name}" rx buffer overflow (${truncated} bytes without a newline) \u2014 closing connection`
|
|
2575
|
+
);
|
|
2576
|
+
void this.close();
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2542
2579
|
let idx = this.rxBuffer.indexOf("\n");
|
|
2543
2580
|
while (idx !== -1) {
|
|
2544
2581
|
const line = this.rxBuffer.slice(0, idx).trim();
|