@wrongstack/mcp 1.0.26 → 1.0.27
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 -1
- package/dist/index.js +84 -43
- package/dist/manage.d.ts +4 -0
- package/dist/transport-base.d.ts +5 -0
- package/package.json +2 -2
package/dist/client.d.ts
CHANGED
|
@@ -26,7 +26,15 @@ export declare class MCPClient {
|
|
|
26
26
|
* (stdio child exit, `close()`) so callers don't hang forever.
|
|
27
27
|
*/
|
|
28
28
|
private readonly pending;
|
|
29
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The unterminated tail of stdout, as the chunks it arrived in; joined only
|
|
31
|
+
* when its newline arrives. Kept as one string (`+=` then `indexOf`), every
|
|
32
|
+
* chunk flattened and re-scanned everything buffered so far — O(n²) for a
|
|
33
|
+
* multi-MiB tools/call result.
|
|
34
|
+
*/
|
|
35
|
+
private rxParts;
|
|
36
|
+
private get rxBuffer();
|
|
37
|
+
private set rxBuffer(value);
|
|
30
38
|
private rxBufferBytes;
|
|
31
39
|
/**
|
|
32
40
|
* Incremental UTF-8 decoder for the stdio rx path. A pipe read boundary can
|
package/dist/index.js
CHANGED
|
@@ -1543,27 +1543,8 @@ var SSEReader = class {
|
|
|
1543
1543
|
};
|
|
1544
1544
|
}
|
|
1545
1545
|
feed(chunk) {
|
|
1546
|
-
|
|
1547
|
-
throw new ToolError({
|
|
1548
|
-
message: `SSE: chunk size ${chunk.length} exceeds max buffer ${SSE_READER_MAX_BUFFER} \u2014 refusing to accumulate`,
|
|
1549
|
-
code: "TOOL_EXECUTION_FAILED",
|
|
1550
|
-
toolName: "mcp_transport_sse_reader",
|
|
1551
|
-
context: { phase: "feed", chunkLength: chunk.length, maxBuffer: SSE_READER_MAX_BUFFER }
|
|
1552
|
-
});
|
|
1553
|
-
}
|
|
1546
|
+
const carried = this.buffer.length;
|
|
1554
1547
|
this.buffer += chunk;
|
|
1555
|
-
if (this.buffer.length > SSE_READER_MAX_BUFFER) {
|
|
1556
|
-
throw new ToolError({
|
|
1557
|
-
message: `SSE: pending line exceeds ${SSE_READER_MAX_BUFFER} bytes \u2014 upstream is not framing events`,
|
|
1558
|
-
code: "TOOL_EXECUTION_FAILED",
|
|
1559
|
-
toolName: "mcp_transport_sse_reader",
|
|
1560
|
-
context: {
|
|
1561
|
-
phase: "feed",
|
|
1562
|
-
bufferLength: this.buffer.length,
|
|
1563
|
-
maxBuffer: SSE_READER_MAX_BUFFER
|
|
1564
|
-
}
|
|
1565
|
-
});
|
|
1566
|
-
}
|
|
1567
1548
|
let start = 0;
|
|
1568
1549
|
let idx = this.buffer.indexOf("\n", start);
|
|
1569
1550
|
while (idx !== -1) {
|
|
@@ -1574,6 +1555,25 @@ var SSEReader = class {
|
|
|
1574
1555
|
idx = this.buffer.indexOf("\n", start);
|
|
1575
1556
|
}
|
|
1576
1557
|
if (start > 0) this.buffer = this.buffer.slice(start);
|
|
1558
|
+
if (this.buffer.length <= SSE_READER_MAX_BUFFER) return;
|
|
1559
|
+
if (start >= carried) {
|
|
1560
|
+
throw new ToolError({
|
|
1561
|
+
message: `SSE: chunk size ${chunk.length} exceeds max buffer ${SSE_READER_MAX_BUFFER} \u2014 refusing to accumulate`,
|
|
1562
|
+
code: "TOOL_EXECUTION_FAILED",
|
|
1563
|
+
toolName: "mcp_transport_sse_reader",
|
|
1564
|
+
context: { phase: "feed", chunkLength: chunk.length, maxBuffer: SSE_READER_MAX_BUFFER }
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
throw new ToolError({
|
|
1568
|
+
message: `SSE: pending line exceeds ${SSE_READER_MAX_BUFFER} bytes \u2014 upstream is not framing events`,
|
|
1569
|
+
code: "TOOL_EXECUTION_FAILED",
|
|
1570
|
+
toolName: "mcp_transport_sse_reader",
|
|
1571
|
+
context: {
|
|
1572
|
+
phase: "feed",
|
|
1573
|
+
bufferLength: this.buffer.length,
|
|
1574
|
+
maxBuffer: SSE_READER_MAX_BUFFER
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
1577
|
}
|
|
1578
1578
|
processLine(line) {
|
|
1579
1579
|
if (line === "") {
|
|
@@ -1649,6 +1649,7 @@ import * as https2 from "node:https";
|
|
|
1649
1649
|
import { ConfigError as ConfigError2 } from "@wrongstack/core/types";
|
|
1650
1650
|
|
|
1651
1651
|
// src/elicitation.ts
|
|
1652
|
+
import { sanitizeTerminalText } from "@wrongstack/core/utils";
|
|
1652
1653
|
var ELICITATION_MAX_WAIT_MS = 10 * 6e4;
|
|
1653
1654
|
var MAX_FIELDS = 32;
|
|
1654
1655
|
var MAX_MESSAGE_CHARS = 4e3;
|
|
@@ -1659,6 +1660,13 @@ function isRecord(v) {
|
|
|
1659
1660
|
function optionalString3(v) {
|
|
1660
1661
|
return typeof v === "string" && v.trim() ? v : void 0;
|
|
1661
1662
|
}
|
|
1663
|
+
function displayText(v) {
|
|
1664
|
+
return sanitizeTerminalText(v);
|
|
1665
|
+
}
|
|
1666
|
+
function optionalDisplayString(v) {
|
|
1667
|
+
const text = optionalString3(v);
|
|
1668
|
+
return text === void 0 ? void 0 : optionalString3(displayText(text));
|
|
1669
|
+
}
|
|
1662
1670
|
function optionalNumber(v) {
|
|
1663
1671
|
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
1664
1672
|
}
|
|
@@ -1672,7 +1680,7 @@ function parseOptions(schema) {
|
|
|
1672
1680
|
const names = Array.isArray(schema["enumNames"]) ? schema["enumNames"] : [];
|
|
1673
1681
|
return values.map((value, i) => ({
|
|
1674
1682
|
value,
|
|
1675
|
-
label: typeof names[i] === "string" && names[i] ? names[i] : value
|
|
1683
|
+
label: displayText(typeof names[i] === "string" && names[i] ? names[i] : value)
|
|
1676
1684
|
}));
|
|
1677
1685
|
}
|
|
1678
1686
|
const titled = Array.isArray(schema["oneOf"]) ? schema["oneOf"] : Array.isArray(schema["anyOf"]) ? schema["anyOf"] : void 0;
|
|
@@ -1682,7 +1690,7 @@ function parseOptions(schema) {
|
|
|
1682
1690
|
if (!isRecord(entry) || typeof entry["const"] !== "string") return void 0;
|
|
1683
1691
|
options.push({
|
|
1684
1692
|
value: entry["const"],
|
|
1685
|
-
label:
|
|
1693
|
+
label: optionalDisplayString(entry["title"]) ?? displayText(entry["const"])
|
|
1686
1694
|
});
|
|
1687
1695
|
}
|
|
1688
1696
|
return options;
|
|
@@ -1691,8 +1699,8 @@ function parseField(name, schema, required) {
|
|
|
1691
1699
|
if (!isRecord(schema)) return { ok: false, error: `property "${name}" is not a schema object` };
|
|
1692
1700
|
const base = {
|
|
1693
1701
|
name,
|
|
1694
|
-
title:
|
|
1695
|
-
description:
|
|
1702
|
+
title: optionalDisplayString(schema["title"]),
|
|
1703
|
+
description: optionalDisplayString(schema["description"]),
|
|
1696
1704
|
required
|
|
1697
1705
|
};
|
|
1698
1706
|
const type = schema["type"];
|
|
@@ -1789,7 +1797,7 @@ function parseUrlElicitation(params) {
|
|
|
1789
1797
|
ok: true,
|
|
1790
1798
|
value: {
|
|
1791
1799
|
mode: "url",
|
|
1792
|
-
message: message.slice(0, MAX_MESSAGE_CHARS),
|
|
1800
|
+
message: displayText(message).slice(0, MAX_MESSAGE_CHARS),
|
|
1793
1801
|
url: url.href,
|
|
1794
1802
|
elicitationId
|
|
1795
1803
|
}
|
|
@@ -1822,10 +1830,10 @@ function parseElicitationParams(params) {
|
|
|
1822
1830
|
if (!parsed.ok) return parsed;
|
|
1823
1831
|
fields.push(parsed.value);
|
|
1824
1832
|
}
|
|
1825
|
-
return { ok: true, value: { message: message.slice(0, MAX_MESSAGE_CHARS), fields } };
|
|
1833
|
+
return { ok: true, value: { message: displayText(message).slice(0, MAX_MESSAGE_CHARS), fields } };
|
|
1826
1834
|
}
|
|
1827
1835
|
function label(field) {
|
|
1828
|
-
return field.title ?? field.name;
|
|
1836
|
+
return field.title ?? displayText(field.name);
|
|
1829
1837
|
}
|
|
1830
1838
|
function checkValue(field, raw) {
|
|
1831
1839
|
const name = label(field);
|
|
@@ -2191,6 +2199,14 @@ if (!pinnedAgentsCleanupRegistered) {
|
|
|
2191
2199
|
pinnedAgents.clear();
|
|
2192
2200
|
});
|
|
2193
2201
|
}
|
|
2202
|
+
function urlOriginForLog(raw) {
|
|
2203
|
+
try {
|
|
2204
|
+
const url = new URL(raw);
|
|
2205
|
+
return `${url.protocol}//${url.host}`;
|
|
2206
|
+
} catch {
|
|
2207
|
+
return "<invalid url>";
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2194
2210
|
function makeAbortError(method) {
|
|
2195
2211
|
const err = new Error(`MCP request "${method}" aborted by client`);
|
|
2196
2212
|
err.name = "AbortError";
|
|
@@ -2262,15 +2278,16 @@ var BaseHTTPTransport = class {
|
|
|
2262
2278
|
this.requestTimeout = opts.requestTimeoutMs ?? 6e4;
|
|
2263
2279
|
if (opts.tls) {
|
|
2264
2280
|
if (opts.tls.rejectUnauthorized === false) {
|
|
2281
|
+
const where = urlOriginForLog(this.url);
|
|
2265
2282
|
if (!isTlsUnsafeAllowed()) {
|
|
2266
2283
|
throw new ConfigError2({
|
|
2267
|
-
message: `[mcp:${transportName}] TLS verification disabled \u2014 set WRONGSTACK_UNSAFE_MCP_TLS=1 to allow. Rejecting insecure configuration for ${
|
|
2284
|
+
message: `[mcp:${transportName}] TLS verification disabled \u2014 set WRONGSTACK_UNSAFE_MCP_TLS=1 to allow. Rejecting insecure configuration for ${where}.`,
|
|
2268
2285
|
code: "CONFIG_INVALID",
|
|
2269
|
-
context: { field: "tls.rejectUnauthorized", transportName, url:
|
|
2286
|
+
context: { field: "tls.rejectUnauthorized", transportName, url: where }
|
|
2270
2287
|
});
|
|
2271
2288
|
}
|
|
2272
2289
|
console.error(
|
|
2273
|
-
`[mcp:${transportName}] \u26A0\uFE0F TLS verification DISABLED for ${
|
|
2290
|
+
`[mcp:${transportName}] \u26A0\uFE0F TLS verification DISABLED for ${where}. Network attacks are possible \u2014 only use on localhost.`
|
|
2274
2291
|
);
|
|
2275
2292
|
}
|
|
2276
2293
|
this.tlsAgent = new https2.Agent({
|
|
@@ -3581,7 +3598,19 @@ var MCPClient = class _MCPClient {
|
|
|
3581
3598
|
* (stdio child exit, `close()`) so callers don't hang forever.
|
|
3582
3599
|
*/
|
|
3583
3600
|
pending = /* @__PURE__ */ new Map();
|
|
3584
|
-
|
|
3601
|
+
/**
|
|
3602
|
+
* The unterminated tail of stdout, as the chunks it arrived in; joined only
|
|
3603
|
+
* when its newline arrives. Kept as one string (`+=` then `indexOf`), every
|
|
3604
|
+
* chunk flattened and re-scanned everything buffered so far — O(n²) for a
|
|
3605
|
+
* multi-MiB tools/call result.
|
|
3606
|
+
*/
|
|
3607
|
+
rxParts = [];
|
|
3608
|
+
get rxBuffer() {
|
|
3609
|
+
return this.rxParts.join("");
|
|
3610
|
+
}
|
|
3611
|
+
set rxBuffer(value) {
|
|
3612
|
+
this.rxParts = value ? [value] : [];
|
|
3613
|
+
}
|
|
3585
3614
|
rxBufferBytes = 0;
|
|
3586
3615
|
/**
|
|
3587
3616
|
* Incremental UTF-8 decoder for the stdio rx path. A pipe read boundary can
|
|
@@ -4063,11 +4092,10 @@ var MCPClient = class _MCPClient {
|
|
|
4063
4092
|
}
|
|
4064
4093
|
}
|
|
4065
4094
|
onData(s) {
|
|
4066
|
-
this.rxBuffer += s;
|
|
4067
4095
|
this.rxBufferBytes += Buffer.byteLength(s, "utf8");
|
|
4068
4096
|
if (this.rxBufferBytes > _MCPClient.MAX_RX_BUFFER_BYTES) {
|
|
4069
4097
|
const truncated = this.rxBufferBytes;
|
|
4070
|
-
this.
|
|
4098
|
+
this.rxParts = [];
|
|
4071
4099
|
this.rxBufferBytes = 0;
|
|
4072
4100
|
this.failPending(
|
|
4073
4101
|
`MCP "${this.opts.name}" rx buffer overflow (${truncated} bytes without a newline) \u2014 closing connection`
|
|
@@ -4076,17 +4104,22 @@ var MCPClient = class _MCPClient {
|
|
|
4076
4104
|
return;
|
|
4077
4105
|
}
|
|
4078
4106
|
let start = 0;
|
|
4079
|
-
let idx =
|
|
4107
|
+
let idx = s.indexOf("\n");
|
|
4108
|
+
if (idx === -1) {
|
|
4109
|
+
this.rxParts.push(s);
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4080
4112
|
while (idx !== -1) {
|
|
4081
|
-
const
|
|
4113
|
+
const head = this.rxParts.length > 0 ? this.rxParts.join("") : "";
|
|
4114
|
+
this.rxParts = [];
|
|
4115
|
+
const line = (head + s.slice(start, idx)).trim();
|
|
4082
4116
|
start = idx + 1;
|
|
4083
4117
|
if (line) this.onLine(line);
|
|
4084
|
-
idx =
|
|
4085
|
-
}
|
|
4086
|
-
if (start > 0) {
|
|
4087
|
-
this.rxBufferBytes -= Buffer.byteLength(this.rxBuffer.slice(0, start), "utf8");
|
|
4088
|
-
this.rxBuffer = this.rxBuffer.slice(start);
|
|
4118
|
+
idx = s.indexOf("\n", start);
|
|
4089
4119
|
}
|
|
4120
|
+
const tail = s.slice(start);
|
|
4121
|
+
this.rxBufferBytes = Buffer.byteLength(tail, "utf8");
|
|
4122
|
+
if (tail) this.rxParts.push(tail);
|
|
4090
4123
|
}
|
|
4091
4124
|
onLine(line) {
|
|
4092
4125
|
let msg;
|
|
@@ -4424,6 +4457,7 @@ function requireIdentity(value, label2) {
|
|
|
4424
4457
|
// src/elicitation-form.ts
|
|
4425
4458
|
import { randomUUID } from "node:crypto";
|
|
4426
4459
|
import { hostname } from "node:os";
|
|
4460
|
+
import { sanitizeTerminalText as sanitizeTerminalText2 } from "@wrongstack/core/utils";
|
|
4427
4461
|
var MAX_ATTEMPTS = 3;
|
|
4428
4462
|
var CONFIRM_QUESTION = "confirm";
|
|
4429
4463
|
var URL_QUESTION = "url";
|
|
@@ -4456,7 +4490,8 @@ function describe(field) {
|
|
|
4456
4490
|
function toQuestion(field, index) {
|
|
4457
4491
|
const base = {
|
|
4458
4492
|
id: questionId(index),
|
|
4459
|
-
|
|
4493
|
+
// Titles arrive sanitized from the parser; a bare field name does not.
|
|
4494
|
+
prompt: field.title ?? sanitizeTerminalText2(field.name),
|
|
4460
4495
|
description: describe(field),
|
|
4461
4496
|
required: field.required
|
|
4462
4497
|
};
|
|
@@ -4733,6 +4768,10 @@ function buildConfig(input, base) {
|
|
|
4733
4768
|
if (allowedTools !== void 0) cfg.allowedTools = allowedTools;
|
|
4734
4769
|
const permission = input.permission ?? base?.permission;
|
|
4735
4770
|
if (permission !== void 0) cfg.permission = permission;
|
|
4771
|
+
const startupTimeoutMs = input.startupTimeoutMs ?? base?.startupTimeoutMs;
|
|
4772
|
+
if (startupTimeoutMs !== void 0) cfg.startupTimeoutMs = startupTimeoutMs;
|
|
4773
|
+
const requestTimeoutMs = input.requestTimeoutMs ?? base?.requestTimeoutMs;
|
|
4774
|
+
if (requestTimeoutMs !== void 0) cfg.requestTimeoutMs = requestTimeoutMs;
|
|
4736
4775
|
const allowPrivateNetworks = input.allowPrivateNetworks ?? base?.allowPrivateNetworks;
|
|
4737
4776
|
if (allowPrivateNetworks !== void 0) cfg.allowPrivateNetworks = allowPrivateNetworks;
|
|
4738
4777
|
const enabled = input.enabled ?? base?.enabled;
|
|
@@ -5648,7 +5687,8 @@ async function attemptConnectSlot(ctx, slot) {
|
|
|
5648
5687
|
slot.reconnectPending = false;
|
|
5649
5688
|
ctx.events.emit("mcp.server.disconnected", {
|
|
5650
5689
|
name: slot.cfg.name,
|
|
5651
|
-
reason: err instanceof Error ? err.message : "unknown"
|
|
5690
|
+
reason: err instanceof Error ? err.message : "unknown",
|
|
5691
|
+
terminal: true
|
|
5652
5692
|
});
|
|
5653
5693
|
return;
|
|
5654
5694
|
}
|
|
@@ -5869,7 +5909,8 @@ function scheduleRegistryReconnect({
|
|
|
5869
5909
|
);
|
|
5870
5910
|
events.emit("mcp.server.disconnected", {
|
|
5871
5911
|
name: slot.cfg.name,
|
|
5872
|
-
reason: `reconnect-exhausted:${slot.reconnectCycles}
|
|
5912
|
+
reason: `reconnect-exhausted:${slot.reconnectCycles}`,
|
|
5913
|
+
terminal: true
|
|
5873
5914
|
});
|
|
5874
5915
|
return;
|
|
5875
5916
|
}
|
package/dist/manage.d.ts
CHANGED
|
@@ -17,6 +17,10 @@ export interface McpServerInput {
|
|
|
17
17
|
bearerTokenEnv?: string | undefined;
|
|
18
18
|
allowedTools?: string[] | undefined;
|
|
19
19
|
permission?: Permission | undefined;
|
|
20
|
+
/** Connect/startup timeout in ms. Defaults to 10s when omitted. */
|
|
21
|
+
startupTimeoutMs?: number | undefined;
|
|
22
|
+
/** Per-request JSON-RPC timeout in ms. Defaults to 60s when omitted. */
|
|
23
|
+
requestTimeoutMs?: number | undefined;
|
|
20
24
|
/** Lazy connect — spawn the process only on first tool call (see config). */
|
|
21
25
|
lazy?: boolean | undefined;
|
|
22
26
|
/** Env var names to forward from parent process at spawn time. */
|
package/dist/transport-base.d.ts
CHANGED
|
@@ -51,6 +51,11 @@ export interface HttpTransportOptions {
|
|
|
51
51
|
* Abort error whose `name` is `'AbortError'` so the core executor's
|
|
52
52
|
* classifyToolError maps it to FATAL / not-retryable (user cancellation).
|
|
53
53
|
*/
|
|
54
|
+
/**
|
|
55
|
+
* `scheme://host:port` of a server URL, for log lines. The path and query can
|
|
56
|
+
* hold an expanded `${SECRET}` (config-env), and userinfo is a credential.
|
|
57
|
+
*/
|
|
58
|
+
export declare function urlOriginForLog(raw: string): string;
|
|
54
59
|
export declare function makeAbortError(method: string): Error;
|
|
55
60
|
/** Advance a JSON-RPC number id without leaving JavaScript's safe-integer range. */
|
|
56
61
|
export declare function nextJsonRpcId(id: number): number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.27",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "WrongStack Model Context Protocol client and registry: stdio, SSE, and streamable HTTP transports.",
|
|
6
6
|
"repository": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"!dist/**/*.map"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@wrongstack/core": "1.0.
|
|
29
|
+
"@wrongstack/core": "1.0.27",
|
|
30
30
|
"undici": "^8.10.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|