reasonix 0.32.0 → 0.33.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/dist/cli/chat-Q5ZCVIOO.js +39 -0
- package/dist/cli/chunk-2AWTGJ2C.js +110 -0
- package/dist/cli/chunk-2AWTGJ2C.js.map +1 -0
- package/dist/cli/chunk-3Q3C4W66.js +30 -0
- package/dist/cli/chunk-3Q3C4W66.js.map +1 -0
- package/dist/cli/chunk-4DCHFFEY.js +149 -0
- package/dist/cli/chunk-4DCHFFEY.js.map +1 -0
- package/dist/cli/chunk-5X7LZJDE.js +36 -0
- package/dist/cli/chunk-5X7LZJDE.js.map +1 -0
- package/dist/cli/chunk-63KAV5DX.js +106 -0
- package/dist/cli/chunk-63KAV5DX.js.map +1 -0
- package/dist/cli/chunk-6TMHAK5D.js +576 -0
- package/dist/cli/chunk-6TMHAK5D.js.map +1 -0
- package/dist/cli/chunk-APPB3ZPQ.js +43 -0
- package/dist/cli/chunk-APPB3ZPQ.js.map +1 -0
- package/dist/cli/chunk-BQNUJJN7.js +42 -0
- package/dist/cli/chunk-BQNUJJN7.js.map +1 -0
- package/dist/cli/chunk-CPOV2O73.js +39 -0
- package/dist/cli/chunk-CPOV2O73.js.map +1 -0
- package/dist/cli/chunk-D5DKXIP5.js +368 -0
- package/dist/cli/chunk-D5DKXIP5.js.map +1 -0
- package/dist/cli/chunk-DFP4YSVM.js +247 -0
- package/dist/cli/chunk-DFP4YSVM.js.map +1 -0
- package/dist/cli/chunk-DULSP7JH.js +410 -0
- package/dist/cli/chunk-DULSP7JH.js.map +1 -0
- package/dist/cli/chunk-FM57FNPJ.js +46 -0
- package/dist/cli/chunk-FM57FNPJ.js.map +1 -0
- package/dist/cli/chunk-FWGEHRB7.js +54 -0
- package/dist/cli/chunk-FWGEHRB7.js.map +1 -0
- package/dist/cli/chunk-FXGQ5NHE.js +513 -0
- package/dist/cli/chunk-FXGQ5NHE.js.map +1 -0
- package/dist/cli/chunk-G3XNWSFN.js +53 -0
- package/dist/cli/chunk-G3XNWSFN.js.map +1 -0
- package/dist/cli/chunk-I6YIAK6C.js +757 -0
- package/dist/cli/chunk-I6YIAK6C.js.map +1 -0
- package/dist/cli/chunk-J5VLP23S.js +94 -0
- package/dist/cli/chunk-J5VLP23S.js.map +1 -0
- package/dist/cli/chunk-KMWKGPFZ.js +303 -0
- package/dist/cli/chunk-KMWKGPFZ.js.map +1 -0
- package/dist/cli/chunk-MDHVWCJ4.js +14965 -0
- package/dist/cli/chunk-MDHVWCJ4.js.map +1 -0
- package/dist/cli/chunk-MHDNZXJJ.js +48 -0
- package/dist/cli/chunk-MHDNZXJJ.js.map +1 -0
- package/dist/cli/chunk-ORM6PK57.js +140 -0
- package/dist/cli/chunk-ORM6PK57.js.map +1 -0
- package/dist/cli/chunk-Q6YFXW7H.js +4986 -0
- package/dist/cli/chunk-Q6YFXW7H.js.map +1 -0
- package/dist/cli/chunk-QGE6AF76.js +1467 -0
- package/dist/cli/chunk-QGE6AF76.js.map +1 -0
- package/dist/cli/chunk-RFX7TYVV.js +28 -0
- package/dist/cli/chunk-RFX7TYVV.js.map +1 -0
- package/dist/cli/chunk-RZILUXUC.js +940 -0
- package/dist/cli/chunk-RZILUXUC.js.map +1 -0
- package/dist/cli/chunk-SDE5U32Z.js +535 -0
- package/dist/cli/chunk-SDE5U32Z.js.map +1 -0
- package/dist/cli/chunk-SOZE7V7V.js +340 -0
- package/dist/cli/chunk-SOZE7V7V.js.map +1 -0
- package/dist/cli/chunk-U3V2ZQ5J.js +479 -0
- package/dist/cli/chunk-U3V2ZQ5J.js.map +1 -0
- package/dist/cli/chunk-W4LDFAZ6.js +1544 -0
- package/dist/cli/chunk-W4LDFAZ6.js.map +1 -0
- package/dist/cli/chunk-WBDE4IRI.js +208 -0
- package/dist/cli/chunk-WBDE4IRI.js.map +1 -0
- package/dist/cli/chunk-XHQIK7B6.js +189 -0
- package/dist/cli/chunk-XHQIK7B6.js.map +1 -0
- package/dist/cli/chunk-XJLZ4HKU.js +307 -0
- package/dist/cli/chunk-XJLZ4HKU.js.map +1 -0
- package/dist/cli/chunk-ZPTSJGX5.js +88 -0
- package/dist/cli/chunk-ZPTSJGX5.js.map +1 -0
- package/dist/cli/chunk-ZTLZO42A.js +231 -0
- package/dist/cli/chunk-ZTLZO42A.js.map +1 -0
- package/dist/cli/code-DLR77NPZ.js +151 -0
- package/dist/cli/code-DLR77NPZ.js.map +1 -0
- package/dist/cli/commands-JWT2MWVH.js +352 -0
- package/dist/cli/commands-JWT2MWVH.js.map +1 -0
- package/dist/cli/commit-RPZBOZS2.js +288 -0
- package/dist/cli/commit-RPZBOZS2.js.map +1 -0
- package/dist/cli/diff-NTEHCSDW.js +145 -0
- package/dist/cli/diff-NTEHCSDW.js.map +1 -0
- package/dist/cli/doctor-3TGB2NZN.js +19 -0
- package/dist/cli/doctor-3TGB2NZN.js.map +1 -0
- package/dist/cli/events-P27CX7LN.js +338 -0
- package/dist/cli/events-P27CX7LN.js.map +1 -0
- package/dist/cli/index.js +80 -33693
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-ARTNQ24O.js +266 -0
- package/dist/cli/mcp-ARTNQ24O.js.map +1 -0
- package/dist/cli/mcp-browse-HLO2ENDL.js +163 -0
- package/dist/cli/mcp-browse-HLO2ENDL.js.map +1 -0
- package/dist/cli/mcp-inspect-T2HBR22P.js +103 -0
- package/dist/cli/mcp-inspect-T2HBR22P.js.map +1 -0
- package/dist/cli/{prompt-XHICFAYN.js → prompt-V47QKSAR.js} +3 -2
- package/dist/cli/prompt-V47QKSAR.js.map +1 -0
- package/dist/cli/prune-sessions-ERL6B4G5.js +42 -0
- package/dist/cli/prune-sessions-ERL6B4G5.js.map +1 -0
- package/dist/cli/replay-Q43DSMG6.js +273 -0
- package/dist/cli/replay-Q43DSMG6.js.map +1 -0
- package/dist/cli/run-JMEOTQCG.js +215 -0
- package/dist/cli/run-JMEOTQCG.js.map +1 -0
- package/dist/cli/server-SYC3OVOP.js +2967 -0
- package/dist/cli/server-SYC3OVOP.js.map +1 -0
- package/dist/cli/sessions-MOJAALJI.js +102 -0
- package/dist/cli/sessions-MOJAALJI.js.map +1 -0
- package/dist/cli/setup-CCJZAWTY.js +404 -0
- package/dist/cli/setup-CCJZAWTY.js.map +1 -0
- package/dist/cli/stats-5RJCATCE.js +12 -0
- package/dist/cli/stats-5RJCATCE.js.map +1 -0
- package/dist/cli/update-4TJWRUIN.js +90 -0
- package/dist/cli/update-4TJWRUIN.js.map +1 -0
- package/dist/cli/version-3MYFE4G6.js +29 -0
- package/dist/cli/version-3MYFE4G6.js.map +1 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.js +493 -89
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/cli/chunk-VWFJNLIK.js +0 -1031
- package/dist/cli/chunk-VWFJNLIK.js.map +0 -1
- /package/dist/cli/{prompt-XHICFAYN.js.map → chat-Q5ZCVIOO.js.map} +0 -0
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
VERSION
|
|
4
|
+
} from "./chunk-2AWTGJ2C.js";
|
|
5
|
+
|
|
6
|
+
// src/mcp/types.ts
|
|
7
|
+
var MCP_PROTOCOL_VERSION = "2024-11-05";
|
|
8
|
+
function isJsonRpcError(msg) {
|
|
9
|
+
return "error" in msg;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/mcp/client.ts
|
|
13
|
+
var McpClient = class {
|
|
14
|
+
transport;
|
|
15
|
+
clientInfo;
|
|
16
|
+
requestTimeoutMs;
|
|
17
|
+
pending = /* @__PURE__ */ new Map();
|
|
18
|
+
nextId = 1;
|
|
19
|
+
readerStarted = false;
|
|
20
|
+
initialized = false;
|
|
21
|
+
_serverCapabilities = {};
|
|
22
|
+
_serverInfo = { name: "", version: "" };
|
|
23
|
+
_protocolVersion = "";
|
|
24
|
+
_instructions;
|
|
25
|
+
// Progress-token → handler for notifications/progress routing. Tokens
|
|
26
|
+
// are minted per call when the caller supplies an onProgress
|
|
27
|
+
// callback; cleared when the final response lands (or the pending
|
|
28
|
+
// request rejects). No leaks — the `try/finally` in callTool
|
|
29
|
+
// guarantees cleanup even on timeout.
|
|
30
|
+
progressHandlers = /* @__PURE__ */ new Map();
|
|
31
|
+
nextProgressToken = 1;
|
|
32
|
+
constructor(opts) {
|
|
33
|
+
this.transport = opts.transport;
|
|
34
|
+
this.clientInfo = opts.clientInfo ?? { name: "reasonix", version: VERSION };
|
|
35
|
+
this.requestTimeoutMs = opts.requestTimeoutMs ?? 6e4;
|
|
36
|
+
}
|
|
37
|
+
/** Server's advertised capabilities, available after initialize(). */
|
|
38
|
+
get serverCapabilities() {
|
|
39
|
+
return this._serverCapabilities;
|
|
40
|
+
}
|
|
41
|
+
/** Server's self-reported name + version, available after initialize(). */
|
|
42
|
+
get serverInfo() {
|
|
43
|
+
return this._serverInfo;
|
|
44
|
+
}
|
|
45
|
+
/** Protocol version the server agreed to during the handshake. */
|
|
46
|
+
get protocolVersion() {
|
|
47
|
+
return this._protocolVersion;
|
|
48
|
+
}
|
|
49
|
+
/** Optional free-form instructions the server provides at handshake. */
|
|
50
|
+
get serverInstructions() {
|
|
51
|
+
return this._instructions;
|
|
52
|
+
}
|
|
53
|
+
/** Compliant servers reject other methods until this completes. */
|
|
54
|
+
async initialize() {
|
|
55
|
+
if (this.initialized) throw new Error("MCP client already initialized");
|
|
56
|
+
this.startReaderIfNeeded();
|
|
57
|
+
const result = await this.request("initialize", {
|
|
58
|
+
protocolVersion: MCP_PROTOCOL_VERSION,
|
|
59
|
+
// Advertise every method the client can consume so servers know
|
|
60
|
+
// they can send listChanged notifications etc. Sub-feature flags
|
|
61
|
+
// (e.g. `resources.subscribe`) are omitted — we don't implement
|
|
62
|
+
// those yet and the empty object means "method-level support, no
|
|
63
|
+
// sub-features."
|
|
64
|
+
capabilities: { tools: {}, resources: {}, prompts: {} },
|
|
65
|
+
clientInfo: this.clientInfo
|
|
66
|
+
});
|
|
67
|
+
this._serverCapabilities = result.capabilities ?? {};
|
|
68
|
+
this._serverInfo = result.serverInfo ?? { name: "", version: "" };
|
|
69
|
+
this._protocolVersion = result.protocolVersion ?? "";
|
|
70
|
+
this._instructions = result.instructions;
|
|
71
|
+
await this.transport.send({
|
|
72
|
+
jsonrpc: "2.0",
|
|
73
|
+
method: "notifications/initialized"
|
|
74
|
+
});
|
|
75
|
+
this.initialized = true;
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
/** List tools the server exposes. */
|
|
79
|
+
async listTools() {
|
|
80
|
+
this.assertInitialized();
|
|
81
|
+
return this.request("tools/list", {});
|
|
82
|
+
}
|
|
83
|
+
/** Abort sends `notifications/cancelled` and rejects immediately; late server responses are dropped. */
|
|
84
|
+
async callTool(name, args, opts = {}) {
|
|
85
|
+
this.assertInitialized();
|
|
86
|
+
const params = { name, arguments: args ?? {} };
|
|
87
|
+
let token;
|
|
88
|
+
if (opts.onProgress) {
|
|
89
|
+
token = this.nextProgressToken++;
|
|
90
|
+
this.progressHandlers.set(token, opts.onProgress);
|
|
91
|
+
params._meta = { progressToken: token };
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
return await this.request("tools/call", params, opts.signal);
|
|
95
|
+
} finally {
|
|
96
|
+
if (token !== void 0) this.progressHandlers.delete(token);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Throws on method-not-found; callers should gate on `serverCapabilities.resources` first. */
|
|
100
|
+
async listResources(cursor) {
|
|
101
|
+
this.assertInitialized();
|
|
102
|
+
return this.request("resources/list", {
|
|
103
|
+
...cursor ? { cursor } : {}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Read the contents of a resource by URI. */
|
|
107
|
+
async readResource(uri) {
|
|
108
|
+
this.assertInitialized();
|
|
109
|
+
return this.request("resources/read", {
|
|
110
|
+
uri
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/** List prompt templates the server exposes. */
|
|
114
|
+
async listPrompts(cursor) {
|
|
115
|
+
this.assertInitialized();
|
|
116
|
+
return this.request("prompts/list", {
|
|
117
|
+
...cursor ? { cursor } : {}
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
async getPrompt(name, args) {
|
|
121
|
+
this.assertInitialized();
|
|
122
|
+
return this.request("prompts/get", {
|
|
123
|
+
name,
|
|
124
|
+
...args ? { arguments: args } : {}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/** Close the transport and reject any outstanding requests. */
|
|
128
|
+
async close() {
|
|
129
|
+
for (const [, pending] of this.pending) {
|
|
130
|
+
clearTimeout(pending.timeout);
|
|
131
|
+
pending.reject(new Error("MCP client closed"));
|
|
132
|
+
}
|
|
133
|
+
this.pending.clear();
|
|
134
|
+
await this.transport.close();
|
|
135
|
+
}
|
|
136
|
+
assertInitialized() {
|
|
137
|
+
if (!this.initialized) throw new Error("MCP client not initialized \u2014 call initialize() first");
|
|
138
|
+
}
|
|
139
|
+
async request(method, params, signal) {
|
|
140
|
+
const id = this.nextId++;
|
|
141
|
+
const frame = { jsonrpc: "2.0", id, method, params };
|
|
142
|
+
let abortHandler = null;
|
|
143
|
+
const promise = new Promise((resolve, reject) => {
|
|
144
|
+
const timeout = setTimeout(() => {
|
|
145
|
+
this.pending.delete(id);
|
|
146
|
+
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
147
|
+
reject(
|
|
148
|
+
new Error(`MCP request ${method} (id=${id}) timed out after ${this.requestTimeoutMs}ms`)
|
|
149
|
+
);
|
|
150
|
+
}, this.requestTimeoutMs);
|
|
151
|
+
this.pending.set(id, {
|
|
152
|
+
resolve,
|
|
153
|
+
reject,
|
|
154
|
+
timeout
|
|
155
|
+
});
|
|
156
|
+
if (signal) {
|
|
157
|
+
if (signal.aborted) {
|
|
158
|
+
this.pending.delete(id);
|
|
159
|
+
clearTimeout(timeout);
|
|
160
|
+
reject(new Error(`MCP request ${method} (id=${id}) aborted before send`));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
abortHandler = () => {
|
|
164
|
+
this.pending.delete(id);
|
|
165
|
+
clearTimeout(timeout);
|
|
166
|
+
void this.transport.send({
|
|
167
|
+
jsonrpc: "2.0",
|
|
168
|
+
method: "notifications/cancelled",
|
|
169
|
+
params: { requestId: id, reason: "aborted by user" }
|
|
170
|
+
}).catch(() => {
|
|
171
|
+
});
|
|
172
|
+
reject(new Error(`MCP request ${method} (id=${id}) aborted by user`));
|
|
173
|
+
};
|
|
174
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
promise.catch(() => void 0);
|
|
178
|
+
try {
|
|
179
|
+
await Promise.race([this.transport.send(frame), promise.then(() => void 0)]);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
const pending = this.pending.get(id);
|
|
182
|
+
if (pending) clearTimeout(pending.timeout);
|
|
183
|
+
this.pending.delete(id);
|
|
184
|
+
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
return await promise;
|
|
189
|
+
} finally {
|
|
190
|
+
if (abortHandler && signal) signal.removeEventListener("abort", abortHandler);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
startReaderIfNeeded() {
|
|
194
|
+
if (this.readerStarted) return;
|
|
195
|
+
this.readerStarted = true;
|
|
196
|
+
void this.readLoop();
|
|
197
|
+
}
|
|
198
|
+
async readLoop() {
|
|
199
|
+
try {
|
|
200
|
+
for await (const msg of this.transport.messages()) {
|
|
201
|
+
this.dispatch(msg);
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
for (const [, pending] of this.pending) {
|
|
205
|
+
clearTimeout(pending.timeout);
|
|
206
|
+
pending.reject(err);
|
|
207
|
+
}
|
|
208
|
+
this.pending.clear();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
dispatch(msg) {
|
|
212
|
+
if (!("id" in msg) || msg.id === null || msg.id === void 0) {
|
|
213
|
+
if ("method" in msg && msg.method === "notifications/progress") {
|
|
214
|
+
const p = msg.params;
|
|
215
|
+
if (!p || p.progressToken === void 0) return;
|
|
216
|
+
const handler = this.progressHandlers.get(p.progressToken);
|
|
217
|
+
if (!handler) return;
|
|
218
|
+
handler({ progress: p.progress, total: p.total, message: p.message });
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (!("result" in msg) && !("error" in msg)) return;
|
|
223
|
+
const pending = this.pending.get(msg.id);
|
|
224
|
+
if (!pending) return;
|
|
225
|
+
this.pending.delete(msg.id);
|
|
226
|
+
clearTimeout(pending.timeout);
|
|
227
|
+
const resp = msg;
|
|
228
|
+
if (isJsonRpcError(resp)) {
|
|
229
|
+
pending.reject(new Error(`MCP ${resp.error.code}: ${resp.error.message}`));
|
|
230
|
+
} else {
|
|
231
|
+
pending.resolve(resp.result);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// src/mcp/stdio.ts
|
|
237
|
+
import { spawn } from "child_process";
|
|
238
|
+
var StdioTransport = class {
|
|
239
|
+
child;
|
|
240
|
+
queue = [];
|
|
241
|
+
waiters = [];
|
|
242
|
+
closed = false;
|
|
243
|
+
stdoutBuffer = "";
|
|
244
|
+
constructor(opts) {
|
|
245
|
+
const env = opts.replaceEnv ? { ...opts.env ?? {} } : { ...process.env, ...opts.env ?? {} };
|
|
246
|
+
const shell = opts.shell ?? process.platform === "win32";
|
|
247
|
+
if (shell) {
|
|
248
|
+
const line = [
|
|
249
|
+
opts.command,
|
|
250
|
+
...(opts.args ?? []).map((a) => quoteArg(a, process.platform === "win32"))
|
|
251
|
+
].join(" ");
|
|
252
|
+
this.child = spawn(line, [], {
|
|
253
|
+
env,
|
|
254
|
+
cwd: opts.cwd,
|
|
255
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
256
|
+
shell: true
|
|
257
|
+
});
|
|
258
|
+
} else {
|
|
259
|
+
this.child = spawn(opts.command, opts.args ?? [], {
|
|
260
|
+
env,
|
|
261
|
+
cwd: opts.cwd,
|
|
262
|
+
stdio: ["pipe", "pipe", "inherit"]
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
this.child.stdout.setEncoding("utf8");
|
|
266
|
+
this.child.stdout.on("data", (chunk) => this.onStdout(chunk));
|
|
267
|
+
this.child.on("close", () => this.onClose());
|
|
268
|
+
this.child.on("error", (err) => {
|
|
269
|
+
this.push({
|
|
270
|
+
jsonrpc: "2.0",
|
|
271
|
+
id: null,
|
|
272
|
+
error: { code: -32e3, message: `transport error: ${err.message}` }
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
async send(message) {
|
|
277
|
+
if (this.closed) throw new Error("MCP transport is closed");
|
|
278
|
+
return new Promise((resolve, reject) => {
|
|
279
|
+
const line = `${JSON.stringify(message)}
|
|
280
|
+
`;
|
|
281
|
+
this.child.stdin.write(line, "utf8", (err) => {
|
|
282
|
+
if (err) reject(err);
|
|
283
|
+
else resolve();
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
async *messages() {
|
|
288
|
+
while (true) {
|
|
289
|
+
if (this.queue.length > 0) {
|
|
290
|
+
yield this.queue.shift();
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (this.closed) return;
|
|
294
|
+
const next = await new Promise((resolve) => {
|
|
295
|
+
this.waiters.push(resolve);
|
|
296
|
+
});
|
|
297
|
+
if (next === null) return;
|
|
298
|
+
yield next;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async close() {
|
|
302
|
+
if (this.closed) return;
|
|
303
|
+
this.closed = true;
|
|
304
|
+
while (this.waiters.length > 0) this.waiters.shift()(null);
|
|
305
|
+
try {
|
|
306
|
+
this.child.stdin.end();
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
if (this.child.exitCode === null && !this.child.killed) {
|
|
310
|
+
try {
|
|
311
|
+
this.child.kill(process.platform === "win32" ? void 0 : "SIGTERM");
|
|
312
|
+
} catch {
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
/** Parse incoming stdout chunks into NDJSON messages. */
|
|
317
|
+
onStdout(chunk) {
|
|
318
|
+
this.stdoutBuffer += chunk;
|
|
319
|
+
let newlineIdx;
|
|
320
|
+
while ((newlineIdx = this.stdoutBuffer.indexOf("\n")) !== -1) {
|
|
321
|
+
const line = this.stdoutBuffer.slice(0, newlineIdx).trim();
|
|
322
|
+
this.stdoutBuffer = this.stdoutBuffer.slice(newlineIdx + 1);
|
|
323
|
+
if (!line) continue;
|
|
324
|
+
try {
|
|
325
|
+
const msg = JSON.parse(line);
|
|
326
|
+
this.push(msg);
|
|
327
|
+
} catch {
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
onClose() {
|
|
332
|
+
this.closed = true;
|
|
333
|
+
while (this.waiters.length > 0) this.waiters.shift()(null);
|
|
334
|
+
}
|
|
335
|
+
push(msg) {
|
|
336
|
+
const waiter = this.waiters.shift();
|
|
337
|
+
if (waiter) waiter(msg);
|
|
338
|
+
else this.queue.push(msg);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
function quoteArg(s, windows) {
|
|
342
|
+
if (!windows) {
|
|
343
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
344
|
+
}
|
|
345
|
+
return `"${s.replace(/"/g, '""')}"`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/mcp/sse.ts
|
|
349
|
+
import { createParser } from "eventsource-parser";
|
|
350
|
+
var SseTransport = class {
|
|
351
|
+
url;
|
|
352
|
+
headers;
|
|
353
|
+
queue = [];
|
|
354
|
+
waiters = [];
|
|
355
|
+
controller = new AbortController();
|
|
356
|
+
closed = false;
|
|
357
|
+
postUrl = null;
|
|
358
|
+
endpointReady;
|
|
359
|
+
resolveEndpoint;
|
|
360
|
+
rejectEndpoint;
|
|
361
|
+
constructor(opts) {
|
|
362
|
+
this.url = opts.url;
|
|
363
|
+
this.headers = opts.headers ?? {};
|
|
364
|
+
this.endpointReady = new Promise((resolve, reject) => {
|
|
365
|
+
this.resolveEndpoint = resolve;
|
|
366
|
+
this.rejectEndpoint = reject;
|
|
367
|
+
});
|
|
368
|
+
this.endpointReady.catch(() => void 0);
|
|
369
|
+
void this.runStream();
|
|
370
|
+
}
|
|
371
|
+
async send(message) {
|
|
372
|
+
if (this.closed) throw new Error("MCP SSE transport is closed");
|
|
373
|
+
const postUrl = await this.endpointReady;
|
|
374
|
+
const res = await fetch(postUrl, {
|
|
375
|
+
method: "POST",
|
|
376
|
+
headers: { "content-type": "application/json", ...this.headers },
|
|
377
|
+
body: JSON.stringify(message),
|
|
378
|
+
signal: this.controller.signal
|
|
379
|
+
});
|
|
380
|
+
await res.arrayBuffer().catch(() => void 0);
|
|
381
|
+
if (!res.ok) {
|
|
382
|
+
throw new Error(`MCP SSE POST ${postUrl} failed: ${res.status} ${res.statusText}`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
async *messages() {
|
|
386
|
+
while (true) {
|
|
387
|
+
if (this.queue.length > 0) {
|
|
388
|
+
yield this.queue.shift();
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (this.closed) return;
|
|
392
|
+
const next = await new Promise((resolve) => {
|
|
393
|
+
this.waiters.push(resolve);
|
|
394
|
+
});
|
|
395
|
+
if (next === null) return;
|
|
396
|
+
yield next;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
async close() {
|
|
400
|
+
if (this.closed) return;
|
|
401
|
+
this.closed = true;
|
|
402
|
+
while (this.waiters.length > 0) this.waiters.shift()(null);
|
|
403
|
+
this.rejectEndpoint(new Error("MCP SSE transport closed before endpoint was ready"));
|
|
404
|
+
try {
|
|
405
|
+
this.controller.abort();
|
|
406
|
+
} catch {
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async runStream() {
|
|
410
|
+
let res;
|
|
411
|
+
try {
|
|
412
|
+
res = await fetch(this.url, {
|
|
413
|
+
method: "GET",
|
|
414
|
+
headers: { accept: "text/event-stream", ...this.headers },
|
|
415
|
+
signal: this.controller.signal
|
|
416
|
+
});
|
|
417
|
+
} catch (err) {
|
|
418
|
+
this.failHandshake(`SSE connect to ${this.url} failed: ${err.message}`);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (!res.ok || !res.body) {
|
|
422
|
+
await res.body?.cancel().catch(() => void 0);
|
|
423
|
+
this.failHandshake(`SSE handshake ${this.url} \u2192 ${res.status} ${res.statusText}`);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const parser = createParser({
|
|
427
|
+
onEvent: (ev) => this.handleEvent(ev.event ?? "message", ev.data)
|
|
428
|
+
});
|
|
429
|
+
const decoder = new TextDecoder();
|
|
430
|
+
try {
|
|
431
|
+
for await (const chunk of res.body) {
|
|
432
|
+
parser.feed(decoder.decode(chunk, { stream: true }));
|
|
433
|
+
}
|
|
434
|
+
} catch (err) {
|
|
435
|
+
if (!this.closed) {
|
|
436
|
+
this.pushError(`SSE stream error: ${err.message}`);
|
|
437
|
+
}
|
|
438
|
+
} finally {
|
|
439
|
+
this.markClosed();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
handleEvent(type, data) {
|
|
443
|
+
if (type === "endpoint") {
|
|
444
|
+
if (this.postUrl) return;
|
|
445
|
+
try {
|
|
446
|
+
this.postUrl = new URL(data, this.url).toString();
|
|
447
|
+
this.resolveEndpoint(this.postUrl);
|
|
448
|
+
} catch (err) {
|
|
449
|
+
this.failHandshake(`SSE endpoint event had bad URL "${data}": ${err.message}`);
|
|
450
|
+
}
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
if (type === "message") {
|
|
454
|
+
try {
|
|
455
|
+
const parsed = JSON.parse(data);
|
|
456
|
+
this.pushMessage(parsed);
|
|
457
|
+
} catch {
|
|
458
|
+
}
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
failHandshake(reason) {
|
|
463
|
+
this.rejectEndpoint(new Error(reason));
|
|
464
|
+
this.pushError(reason);
|
|
465
|
+
this.markClosed();
|
|
466
|
+
}
|
|
467
|
+
pushMessage(msg) {
|
|
468
|
+
const waiter = this.waiters.shift();
|
|
469
|
+
if (waiter) waiter(msg);
|
|
470
|
+
else this.queue.push(msg);
|
|
471
|
+
}
|
|
472
|
+
pushError(message) {
|
|
473
|
+
this.pushMessage({
|
|
474
|
+
jsonrpc: "2.0",
|
|
475
|
+
id: null,
|
|
476
|
+
error: { code: -32e3, message }
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
markClosed() {
|
|
480
|
+
if (this.closed) return;
|
|
481
|
+
this.closed = true;
|
|
482
|
+
while (this.waiters.length > 0) this.waiters.shift()(null);
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
// src/mcp/streamable-http.ts
|
|
487
|
+
import { createParser as createParser2 } from "eventsource-parser";
|
|
488
|
+
var SESSION_HEADER = "mcp-session-id";
|
|
489
|
+
var StreamableHttpTransport = class {
|
|
490
|
+
url;
|
|
491
|
+
extraHeaders;
|
|
492
|
+
queue = [];
|
|
493
|
+
waiters = [];
|
|
494
|
+
controller = new AbortController();
|
|
495
|
+
/** Session id minted by server on (typically) the initialize response. */
|
|
496
|
+
sessionId = null;
|
|
497
|
+
closed = false;
|
|
498
|
+
/** Background SSE read-loops kicked off by send(); awaited on close(). */
|
|
499
|
+
streams = /* @__PURE__ */ new Set();
|
|
500
|
+
constructor(opts) {
|
|
501
|
+
this.url = opts.url;
|
|
502
|
+
this.extraHeaders = opts.headers ?? {};
|
|
503
|
+
}
|
|
504
|
+
async send(message) {
|
|
505
|
+
if (this.closed) throw new Error("MCP Streamable HTTP transport is closed");
|
|
506
|
+
const headers = {
|
|
507
|
+
"content-type": "application/json",
|
|
508
|
+
// Both accepted — server picks. application/json first signals a
|
|
509
|
+
// mild preference for the simpler shape when the response is a
|
|
510
|
+
// single message.
|
|
511
|
+
accept: "application/json, text/event-stream",
|
|
512
|
+
...this.extraHeaders
|
|
513
|
+
};
|
|
514
|
+
if (this.sessionId !== null) headers["mcp-session-id"] = this.sessionId;
|
|
515
|
+
let res;
|
|
516
|
+
try {
|
|
517
|
+
res = await fetch(this.url, {
|
|
518
|
+
method: "POST",
|
|
519
|
+
headers,
|
|
520
|
+
body: JSON.stringify(message),
|
|
521
|
+
signal: this.controller.signal
|
|
522
|
+
});
|
|
523
|
+
} catch (err) {
|
|
524
|
+
throw new Error(`MCP Streamable HTTP POST ${this.url} failed: ${err.message}`);
|
|
525
|
+
}
|
|
526
|
+
const serverSessionId = res.headers.get(SESSION_HEADER);
|
|
527
|
+
if (serverSessionId && this.sessionId === null) {
|
|
528
|
+
this.sessionId = serverSessionId;
|
|
529
|
+
}
|
|
530
|
+
if (res.status === 404 && this.sessionId !== null) {
|
|
531
|
+
await res.body?.cancel().catch(() => void 0);
|
|
532
|
+
throw new Error(
|
|
533
|
+
`MCP Streamable HTTP session expired (server returned 404 with Mcp-Session-Id "${this.sessionId}"). Reinitialize the client.`
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
if (!res.ok) {
|
|
537
|
+
const body = await res.text().catch(() => "");
|
|
538
|
+
throw new Error(
|
|
539
|
+
`MCP Streamable HTTP POST ${this.url} \u2192 ${res.status} ${res.statusText}${body ? `: ${body}` : ""}`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
if (res.status === 202) {
|
|
543
|
+
await res.body?.cancel().catch(() => void 0);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const ct = (res.headers.get("content-type") ?? "").toLowerCase();
|
|
547
|
+
if (ct.includes("application/json")) {
|
|
548
|
+
let parsed;
|
|
549
|
+
try {
|
|
550
|
+
parsed = await res.json();
|
|
551
|
+
} catch (err) {
|
|
552
|
+
throw new Error(`MCP Streamable HTTP body wasn't valid JSON: ${err.message}`);
|
|
553
|
+
}
|
|
554
|
+
if (Array.isArray(parsed)) {
|
|
555
|
+
for (const item of parsed) this.pushMessage(item);
|
|
556
|
+
} else {
|
|
557
|
+
this.pushMessage(parsed);
|
|
558
|
+
}
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (ct.includes("text/event-stream")) {
|
|
562
|
+
if (!res.body) {
|
|
563
|
+
throw new Error("MCP Streamable HTTP SSE response had no body");
|
|
564
|
+
}
|
|
565
|
+
const stream = this.consumeStream(res.body);
|
|
566
|
+
this.streams.add(stream);
|
|
567
|
+
stream.finally(() => this.streams.delete(stream));
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
await res.body?.cancel().catch(() => void 0);
|
|
571
|
+
}
|
|
572
|
+
async *messages() {
|
|
573
|
+
while (true) {
|
|
574
|
+
if (this.queue.length > 0) {
|
|
575
|
+
yield this.queue.shift();
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
if (this.closed) return;
|
|
579
|
+
const next = await new Promise((resolve) => {
|
|
580
|
+
this.waiters.push(resolve);
|
|
581
|
+
});
|
|
582
|
+
if (next === null) return;
|
|
583
|
+
yield next;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
async close() {
|
|
587
|
+
if (this.closed) return;
|
|
588
|
+
this.closed = true;
|
|
589
|
+
while (this.waiters.length > 0) this.waiters.shift()(null);
|
|
590
|
+
try {
|
|
591
|
+
this.controller.abort();
|
|
592
|
+
} catch {
|
|
593
|
+
}
|
|
594
|
+
await Promise.allSettled(Array.from(this.streams));
|
|
595
|
+
}
|
|
596
|
+
/** Visible for tests — confirm session header round-trip. */
|
|
597
|
+
getSessionId() {
|
|
598
|
+
return this.sessionId;
|
|
599
|
+
}
|
|
600
|
+
async consumeStream(body) {
|
|
601
|
+
const parser = createParser2({
|
|
602
|
+
onEvent: (ev) => {
|
|
603
|
+
const type = ev.event ?? "message";
|
|
604
|
+
if (type !== "message") return;
|
|
605
|
+
try {
|
|
606
|
+
const parsed = JSON.parse(ev.data);
|
|
607
|
+
this.pushMessage(parsed);
|
|
608
|
+
} catch {
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
const decoder = new TextDecoder();
|
|
613
|
+
try {
|
|
614
|
+
for await (const chunk of body) {
|
|
615
|
+
if (this.closed) break;
|
|
616
|
+
parser.feed(decoder.decode(chunk, { stream: true }));
|
|
617
|
+
}
|
|
618
|
+
} catch (err) {
|
|
619
|
+
if (!this.closed) {
|
|
620
|
+
this.pushMessage({
|
|
621
|
+
jsonrpc: "2.0",
|
|
622
|
+
id: null,
|
|
623
|
+
error: {
|
|
624
|
+
code: -32e3,
|
|
625
|
+
message: `Streamable HTTP stream error: ${err.message}`
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
pushMessage(msg) {
|
|
632
|
+
const waiter = this.waiters.shift();
|
|
633
|
+
if (waiter) waiter(msg);
|
|
634
|
+
else this.queue.push(msg);
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
// src/mcp/shell-split.ts
|
|
639
|
+
function shellSplit(input) {
|
|
640
|
+
const tokens = [];
|
|
641
|
+
let cur = "";
|
|
642
|
+
let quote = null;
|
|
643
|
+
let i = 0;
|
|
644
|
+
const s = input;
|
|
645
|
+
while (i < s.length) {
|
|
646
|
+
const ch = s[i];
|
|
647
|
+
if (quote) {
|
|
648
|
+
if (ch === quote) {
|
|
649
|
+
quote = null;
|
|
650
|
+
i++;
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
if (ch === "\\" && quote === '"' && i + 1 < s.length) {
|
|
654
|
+
cur += s[i + 1];
|
|
655
|
+
i += 2;
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
cur += ch;
|
|
659
|
+
i++;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (ch === '"' || ch === "'") {
|
|
663
|
+
quote = ch;
|
|
664
|
+
i++;
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (ch === " " || ch === " ") {
|
|
668
|
+
if (cur.length > 0) {
|
|
669
|
+
tokens.push(cur);
|
|
670
|
+
cur = "";
|
|
671
|
+
}
|
|
672
|
+
i++;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
cur += ch;
|
|
676
|
+
i++;
|
|
677
|
+
}
|
|
678
|
+
if (quote) {
|
|
679
|
+
throw new Error(
|
|
680
|
+
`shellSplit: unterminated ${quote === '"' ? "double" : "single"} quote in input`
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
if (cur.length > 0) tokens.push(cur);
|
|
684
|
+
return tokens;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/mcp/spec.ts
|
|
688
|
+
var NAME_PREFIX = /^([a-zA-Z_][a-zA-Z0-9_-]*)=(.*)$/;
|
|
689
|
+
var HTTP_URL = /^https?:\/\//i;
|
|
690
|
+
var STREAMABLE_PREFIX = /^streamable\+(https?:\/\/.+)$/i;
|
|
691
|
+
function parseMcpSpec(input) {
|
|
692
|
+
const trimmed = input.trim();
|
|
693
|
+
if (!trimmed) {
|
|
694
|
+
throw new Error("empty MCP spec");
|
|
695
|
+
}
|
|
696
|
+
const nameMatch = NAME_PREFIX.exec(trimmed);
|
|
697
|
+
const name = nameMatch ? nameMatch[1] : null;
|
|
698
|
+
const body = (nameMatch ? nameMatch[2] : trimmed).trim();
|
|
699
|
+
if (!body) {
|
|
700
|
+
throw new Error(`MCP spec has name but no command: ${input}`);
|
|
701
|
+
}
|
|
702
|
+
const streamMatch = STREAMABLE_PREFIX.exec(body);
|
|
703
|
+
if (streamMatch) {
|
|
704
|
+
return { transport: "streamable-http", name, url: streamMatch[1] };
|
|
705
|
+
}
|
|
706
|
+
if (HTTP_URL.test(body)) {
|
|
707
|
+
return { transport: "sse", name, url: body };
|
|
708
|
+
}
|
|
709
|
+
const argv = shellSplit(body);
|
|
710
|
+
if (argv.length === 0) {
|
|
711
|
+
throw new Error(`MCP spec has name but no command: ${input}`);
|
|
712
|
+
}
|
|
713
|
+
const [command, ...args] = argv;
|
|
714
|
+
return { transport: "stdio", name, command, args };
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/mcp/inspect.ts
|
|
718
|
+
async function inspectMcpServer(client) {
|
|
719
|
+
const t0 = Date.now();
|
|
720
|
+
const tools = await trySection(() => client.listTools().then((r) => r.tools));
|
|
721
|
+
const resources = await trySection(
|
|
722
|
+
() => client.listResources().then((r) => r.resources)
|
|
723
|
+
);
|
|
724
|
+
const prompts = await trySection(() => client.listPrompts().then((r) => r.prompts));
|
|
725
|
+
return {
|
|
726
|
+
protocolVersion: client.protocolVersion || "(unknown)",
|
|
727
|
+
serverInfo: client.serverInfo,
|
|
728
|
+
capabilities: client.serverCapabilities ?? {},
|
|
729
|
+
instructions: client.serverInstructions,
|
|
730
|
+
tools,
|
|
731
|
+
resources,
|
|
732
|
+
prompts,
|
|
733
|
+
elapsedMs: Date.now() - t0
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
async function trySection(load) {
|
|
737
|
+
try {
|
|
738
|
+
const items = await load();
|
|
739
|
+
return { supported: true, items };
|
|
740
|
+
} catch (err) {
|
|
741
|
+
const msg = err.message ?? String(err);
|
|
742
|
+
if (/-32601/.test(msg) || /method not found/i.test(msg)) {
|
|
743
|
+
return { supported: false, reason: "method not found (-32601)" };
|
|
744
|
+
}
|
|
745
|
+
return { supported: false, reason: msg };
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
export {
|
|
750
|
+
McpClient,
|
|
751
|
+
StdioTransport,
|
|
752
|
+
SseTransport,
|
|
753
|
+
StreamableHttpTransport,
|
|
754
|
+
parseMcpSpec,
|
|
755
|
+
inspectMcpServer
|
|
756
|
+
};
|
|
757
|
+
//# sourceMappingURL=chunk-I6YIAK6C.js.map
|