@freesyntax/notch-cli 0.5.17 → 0.5.19
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/apply-patch-EBZ5VLO7.js +15 -0
- package/dist/chunk-3QUV4JEX.js +162 -0
- package/dist/chunk-6CZCFY6H.js +98 -0
- package/dist/chunk-6U3ZAGYA.js +38 -0
- package/dist/chunk-75K7DQVI.js +630 -0
- package/dist/chunk-C4CPDDMN.js +246 -0
- package/dist/chunk-CQMAVWLJ.js +134 -0
- package/dist/chunk-FAULT7VE.js +139 -0
- package/dist/chunk-FFB7GK3Y.js +72 -0
- package/dist/chunk-GBZGR6ID.js +174 -0
- package/dist/chunk-KZAS754V.js +118 -0
- package/dist/chunk-O3WZW7GS.js +35 -0
- package/dist/chunk-TH6GKC7E.js +315 -0
- package/dist/chunk-UR4XL6OM.js +104 -0
- package/dist/chunk-W4FAGQFL.js +171 -0
- package/dist/chunk-YAYPQTOU.js +53 -0
- package/dist/edit-FXWXOFAF.js +7 -0
- package/dist/git-XVWI2BT7.js +7 -0
- package/dist/github-DOZ2MVQE.js +7 -0
- package/dist/glob-XSBN4MDB.js +7 -0
- package/dist/grep-2A42QPWM.js +7 -0
- package/dist/index.js +1691 -3152
- package/dist/lsp-WUEGBQ3F.js +7 -0
- package/dist/notebook-5U6PAF6M.js +7 -0
- package/dist/plugins-GJIUZCJ5.js +7 -0
- package/dist/read-LY2VGCZY.js +7 -0
- package/dist/server-4JRQH3DT.js +1479 -0
- package/dist/shell-RGXMLRLH.js +7 -0
- package/dist/task-VIJ3N5EB.js +11 -0
- package/dist/tools-XKVTMNR5.js +31 -0
- package/dist/web-fetch-XOH5PUCP.js +7 -0
- package/dist/write-DOLDW7HM.js +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
import {
|
|
2
|
+
grepTool
|
|
3
|
+
} from "./chunk-6CZCFY6H.js";
|
|
4
|
+
import {
|
|
5
|
+
globTool
|
|
6
|
+
} from "./chunk-6U3ZAGYA.js";
|
|
7
|
+
import {
|
|
8
|
+
webFetchTool
|
|
9
|
+
} from "./chunk-FFB7GK3Y.js";
|
|
10
|
+
import {
|
|
11
|
+
githubTool
|
|
12
|
+
} from "./chunk-GBZGR6ID.js";
|
|
13
|
+
import {
|
|
14
|
+
lspTool
|
|
15
|
+
} from "./chunk-TH6GKC7E.js";
|
|
16
|
+
import {
|
|
17
|
+
notebookTool
|
|
18
|
+
} from "./chunk-KZAS754V.js";
|
|
19
|
+
import {
|
|
20
|
+
taskTool
|
|
21
|
+
} from "./chunk-UR4XL6OM.js";
|
|
22
|
+
import {
|
|
23
|
+
pluginManager
|
|
24
|
+
} from "./chunk-3QUV4JEX.js";
|
|
25
|
+
import {
|
|
26
|
+
readTool
|
|
27
|
+
} from "./chunk-CQMAVWLJ.js";
|
|
28
|
+
import {
|
|
29
|
+
writeTool
|
|
30
|
+
} from "./chunk-O3WZW7GS.js";
|
|
31
|
+
import {
|
|
32
|
+
editTool
|
|
33
|
+
} from "./chunk-YAYPQTOU.js";
|
|
34
|
+
import {
|
|
35
|
+
applyPatchTool
|
|
36
|
+
} from "./chunk-C4CPDDMN.js";
|
|
37
|
+
import {
|
|
38
|
+
shellTool
|
|
39
|
+
} from "./chunk-W4FAGQFL.js";
|
|
40
|
+
import {
|
|
41
|
+
gitTool
|
|
42
|
+
} from "./chunk-FAULT7VE.js";
|
|
43
|
+
|
|
44
|
+
// src/tools/index.ts
|
|
45
|
+
import { tool } from "ai";
|
|
46
|
+
|
|
47
|
+
// src/mcp/client.ts
|
|
48
|
+
import { z } from "zod";
|
|
49
|
+
|
|
50
|
+
// src/mcp/transport.ts
|
|
51
|
+
function detectTransport(config) {
|
|
52
|
+
if (config.transport) return config.transport;
|
|
53
|
+
if (config.url) return "http";
|
|
54
|
+
return "stdio";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// src/mcp/stdio-transport.ts
|
|
58
|
+
import { spawn } from "child_process";
|
|
59
|
+
var StdioTransport = class {
|
|
60
|
+
constructor(config, name) {
|
|
61
|
+
this.config = config;
|
|
62
|
+
this.name = name;
|
|
63
|
+
}
|
|
64
|
+
config;
|
|
65
|
+
process = null;
|
|
66
|
+
requestId = 0;
|
|
67
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
68
|
+
buffer = "";
|
|
69
|
+
name;
|
|
70
|
+
async connect() {
|
|
71
|
+
if (!this.config.command) {
|
|
72
|
+
throw new Error(`Stdio transport requires 'command' in config for server ${this.name}`);
|
|
73
|
+
}
|
|
74
|
+
this.process = spawn(this.config.command, this.config.args ?? [], {
|
|
75
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
76
|
+
env: { ...process.env, ...this.config.env },
|
|
77
|
+
cwd: this.config.cwd
|
|
78
|
+
});
|
|
79
|
+
this.process.stdout?.setEncoding("utf-8");
|
|
80
|
+
this.process.stdout?.on("data", (data) => {
|
|
81
|
+
this.buffer += data;
|
|
82
|
+
this.processBuffer();
|
|
83
|
+
});
|
|
84
|
+
this.process.on("error", (err) => {
|
|
85
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
86
|
+
pending.reject(new Error(`MCP server ${this.name} error: ${err.message}`));
|
|
87
|
+
this.pendingRequests.delete(id);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
this.process.on("exit", (code) => {
|
|
91
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
92
|
+
pending.reject(new Error(`MCP server ${this.name} exited with code ${code}`));
|
|
93
|
+
this.pendingRequests.delete(id);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
disconnect() {
|
|
98
|
+
if (this.process) {
|
|
99
|
+
this.process.stdin?.end();
|
|
100
|
+
this.process.kill();
|
|
101
|
+
this.process = null;
|
|
102
|
+
}
|
|
103
|
+
this.pendingRequests.clear();
|
|
104
|
+
}
|
|
105
|
+
get isAlive() {
|
|
106
|
+
return this.process !== null && this.process.exitCode === null && !this.process.killed;
|
|
107
|
+
}
|
|
108
|
+
sendRequest(method, params) {
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
const id = ++this.requestId;
|
|
111
|
+
const msg = { jsonrpc: "2.0", id, method, params };
|
|
112
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
113
|
+
const data = JSON.stringify(msg);
|
|
114
|
+
const header = `Content-Length: ${Buffer.byteLength(data)}\r
|
|
115
|
+
\r
|
|
116
|
+
`;
|
|
117
|
+
this.process?.stdin?.write(header + data);
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
if (this.pendingRequests.has(id)) {
|
|
120
|
+
this.pendingRequests.delete(id);
|
|
121
|
+
reject(new Error(`MCP request ${method} timed out`));
|
|
122
|
+
}
|
|
123
|
+
}, 3e4);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
sendNotification(method, params) {
|
|
127
|
+
const msg = { jsonrpc: "2.0", method, params };
|
|
128
|
+
const data = JSON.stringify(msg);
|
|
129
|
+
const header = `Content-Length: ${Buffer.byteLength(data)}\r
|
|
130
|
+
\r
|
|
131
|
+
`;
|
|
132
|
+
this.process?.stdin?.write(header + data);
|
|
133
|
+
}
|
|
134
|
+
processBuffer() {
|
|
135
|
+
while (this.buffer.length > 0) {
|
|
136
|
+
const headerEnd = this.buffer.indexOf("\r\n\r\n");
|
|
137
|
+
if (headerEnd === -1) break;
|
|
138
|
+
const header = this.buffer.slice(0, headerEnd);
|
|
139
|
+
const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
|
|
140
|
+
if (!lengthMatch) {
|
|
141
|
+
const nlIdx = this.buffer.indexOf("\n");
|
|
142
|
+
if (nlIdx === -1) break;
|
|
143
|
+
const line = this.buffer.slice(0, nlIdx).trim();
|
|
144
|
+
this.buffer = this.buffer.slice(nlIdx + 1);
|
|
145
|
+
if (line) this.handleMessage(line);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const contentLength = parseInt(lengthMatch[1], 10);
|
|
149
|
+
const messageStart = headerEnd + 4;
|
|
150
|
+
if (this.buffer.length < messageStart + contentLength) break;
|
|
151
|
+
const body = this.buffer.slice(messageStart, messageStart + contentLength);
|
|
152
|
+
this.buffer = this.buffer.slice(messageStart + contentLength);
|
|
153
|
+
this.handleMessage(body);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
handleMessage(raw) {
|
|
157
|
+
try {
|
|
158
|
+
const msg = JSON.parse(raw);
|
|
159
|
+
if (msg.id !== void 0 && this.pendingRequests.has(msg.id)) {
|
|
160
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
161
|
+
this.pendingRequests.delete(msg.id);
|
|
162
|
+
if (msg.error) {
|
|
163
|
+
pending.reject(new Error(`MCP error: ${msg.error.message}`));
|
|
164
|
+
} else {
|
|
165
|
+
pending.resolve(msg.result);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// src/mcp/http-transport.ts
|
|
174
|
+
var HttpTransport = class {
|
|
175
|
+
requestId = 0;
|
|
176
|
+
connected = false;
|
|
177
|
+
name;
|
|
178
|
+
baseUrl;
|
|
179
|
+
headers;
|
|
180
|
+
constructor(config, name) {
|
|
181
|
+
this.name = name;
|
|
182
|
+
if (!config.url) {
|
|
183
|
+
throw new Error(`HTTP transport requires 'url' in config for server ${name}`);
|
|
184
|
+
}
|
|
185
|
+
this.baseUrl = config.url.replace(/\/$/, "");
|
|
186
|
+
this.headers = {
|
|
187
|
+
"Content-Type": "application/json",
|
|
188
|
+
...config.headers
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
async connect() {
|
|
192
|
+
try {
|
|
193
|
+
const response = await fetch(`${this.baseUrl}`, {
|
|
194
|
+
method: "POST",
|
|
195
|
+
headers: this.headers,
|
|
196
|
+
body: JSON.stringify({
|
|
197
|
+
jsonrpc: "2.0",
|
|
198
|
+
id: ++this.requestId,
|
|
199
|
+
method: "initialize",
|
|
200
|
+
params: {
|
|
201
|
+
protocolVersion: "2024-11-05",
|
|
202
|
+
capabilities: {},
|
|
203
|
+
clientInfo: { name: "notch-cli", version: "0.4.8" }
|
|
204
|
+
}
|
|
205
|
+
}),
|
|
206
|
+
signal: AbortSignal.timeout(15e3)
|
|
207
|
+
});
|
|
208
|
+
if (!response.ok) {
|
|
209
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
210
|
+
}
|
|
211
|
+
this.sendNotification("notifications/initialized", {});
|
|
212
|
+
this.connected = true;
|
|
213
|
+
} catch (err) {
|
|
214
|
+
throw new Error(`MCP HTTP server ${this.name} unreachable: ${err.message}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
disconnect() {
|
|
218
|
+
this.connected = false;
|
|
219
|
+
}
|
|
220
|
+
get isAlive() {
|
|
221
|
+
return this.connected;
|
|
222
|
+
}
|
|
223
|
+
async sendRequest(method, params) {
|
|
224
|
+
const id = ++this.requestId;
|
|
225
|
+
const body = { jsonrpc: "2.0", id, method, params };
|
|
226
|
+
const response = await fetch(this.baseUrl, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
headers: this.headers,
|
|
229
|
+
body: JSON.stringify(body),
|
|
230
|
+
signal: AbortSignal.timeout(3e4)
|
|
231
|
+
});
|
|
232
|
+
if (!response.ok) {
|
|
233
|
+
throw new Error(`MCP HTTP error (${this.name}): ${response.status} ${response.statusText}`);
|
|
234
|
+
}
|
|
235
|
+
const json = await response.json();
|
|
236
|
+
if (json.error) {
|
|
237
|
+
throw new Error(`MCP error (${this.name}): ${json.error.message}`);
|
|
238
|
+
}
|
|
239
|
+
return json.result;
|
|
240
|
+
}
|
|
241
|
+
sendNotification(method, params) {
|
|
242
|
+
void fetch(this.baseUrl, {
|
|
243
|
+
method: "POST",
|
|
244
|
+
headers: this.headers,
|
|
245
|
+
body: JSON.stringify({ jsonrpc: "2.0", method, params }),
|
|
246
|
+
signal: AbortSignal.timeout(1e4)
|
|
247
|
+
}).catch(() => {
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// src/mcp/sse-transport.ts
|
|
253
|
+
var SSETransport = class {
|
|
254
|
+
requestId = 0;
|
|
255
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
256
|
+
abortController = null;
|
|
257
|
+
connected = false;
|
|
258
|
+
name;
|
|
259
|
+
baseUrl;
|
|
260
|
+
messageEndpoint;
|
|
261
|
+
sseEndpoint;
|
|
262
|
+
headers;
|
|
263
|
+
constructor(config, name) {
|
|
264
|
+
this.name = name;
|
|
265
|
+
if (!config.url) {
|
|
266
|
+
throw new Error(`SSE transport requires 'url' in config for server ${name}`);
|
|
267
|
+
}
|
|
268
|
+
this.baseUrl = config.url.replace(/\/$/, "");
|
|
269
|
+
this.sseEndpoint = `${this.baseUrl}/sse`;
|
|
270
|
+
this.messageEndpoint = `${this.baseUrl}/message`;
|
|
271
|
+
this.headers = {
|
|
272
|
+
"Content-Type": "application/json",
|
|
273
|
+
...config.headers
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
async connect() {
|
|
277
|
+
this.abortController = new AbortController();
|
|
278
|
+
const ssePromise = this.startSSEListener();
|
|
279
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
280
|
+
const initResult = await this.sendRequest("initialize", {
|
|
281
|
+
protocolVersion: "2024-11-05",
|
|
282
|
+
capabilities: {},
|
|
283
|
+
clientInfo: { name: "notch-cli", version: "0.4.8" }
|
|
284
|
+
});
|
|
285
|
+
if (!initResult) {
|
|
286
|
+
throw new Error(`MCP SSE server ${this.name} failed to initialize`);
|
|
287
|
+
}
|
|
288
|
+
this.sendNotification("notifications/initialized", {});
|
|
289
|
+
this.connected = true;
|
|
290
|
+
ssePromise.catch(() => {
|
|
291
|
+
this.connected = false;
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
disconnect() {
|
|
295
|
+
this.abortController?.abort();
|
|
296
|
+
this.abortController = null;
|
|
297
|
+
this.connected = false;
|
|
298
|
+
for (const [id, pending] of this.pendingRequests) {
|
|
299
|
+
pending.reject(new Error(`MCP SSE transport disconnected`));
|
|
300
|
+
this.pendingRequests.delete(id);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
get isAlive() {
|
|
304
|
+
return this.connected;
|
|
305
|
+
}
|
|
306
|
+
async sendRequest(method, params) {
|
|
307
|
+
const id = ++this.requestId;
|
|
308
|
+
const body = { jsonrpc: "2.0", id, method, params };
|
|
309
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
310
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
311
|
+
setTimeout(() => {
|
|
312
|
+
if (this.pendingRequests.has(id)) {
|
|
313
|
+
this.pendingRequests.delete(id);
|
|
314
|
+
reject(new Error(`MCP SSE request ${method} timed out`));
|
|
315
|
+
}
|
|
316
|
+
}, 3e4);
|
|
317
|
+
});
|
|
318
|
+
const response = await fetch(this.messageEndpoint, {
|
|
319
|
+
method: "POST",
|
|
320
|
+
headers: this.headers,
|
|
321
|
+
body: JSON.stringify(body),
|
|
322
|
+
signal: AbortSignal.timeout(1e4)
|
|
323
|
+
});
|
|
324
|
+
if (!response.ok) {
|
|
325
|
+
this.pendingRequests.delete(id);
|
|
326
|
+
throw new Error(`MCP SSE POST error (${this.name}): ${response.status} ${response.statusText}`);
|
|
327
|
+
}
|
|
328
|
+
return resultPromise;
|
|
329
|
+
}
|
|
330
|
+
sendNotification(method, params) {
|
|
331
|
+
const body = { jsonrpc: "2.0", method, params };
|
|
332
|
+
void fetch(this.messageEndpoint, {
|
|
333
|
+
method: "POST",
|
|
334
|
+
headers: this.headers,
|
|
335
|
+
body: JSON.stringify(body),
|
|
336
|
+
signal: AbortSignal.timeout(1e4)
|
|
337
|
+
}).catch(() => {
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Start listening for Server-Sent Events.
|
|
342
|
+
* Parses the SSE stream and resolves pending requests.
|
|
343
|
+
*/
|
|
344
|
+
async startSSEListener() {
|
|
345
|
+
const response = await fetch(this.sseEndpoint, {
|
|
346
|
+
headers: {
|
|
347
|
+
Accept: "text/event-stream",
|
|
348
|
+
...this.headers
|
|
349
|
+
},
|
|
350
|
+
signal: this.abortController?.signal
|
|
351
|
+
});
|
|
352
|
+
if (!response.ok || !response.body) {
|
|
353
|
+
throw new Error(`SSE connection failed: ${response.status}`);
|
|
354
|
+
}
|
|
355
|
+
const reader = response.body.getReader();
|
|
356
|
+
const decoder = new TextDecoder();
|
|
357
|
+
let buffer = "";
|
|
358
|
+
try {
|
|
359
|
+
while (true) {
|
|
360
|
+
const { done, value } = await reader.read();
|
|
361
|
+
if (done) break;
|
|
362
|
+
buffer += decoder.decode(value, { stream: true });
|
|
363
|
+
const events = buffer.split("\n\n");
|
|
364
|
+
buffer = events.pop() ?? "";
|
|
365
|
+
for (const event of events) {
|
|
366
|
+
const lines = event.split("\n");
|
|
367
|
+
let data = "";
|
|
368
|
+
for (const line of lines) {
|
|
369
|
+
if (line.startsWith("data: ")) {
|
|
370
|
+
data += line.slice(6);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (data) {
|
|
374
|
+
this.handleSSEMessage(data);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
} catch (err) {
|
|
379
|
+
if (err.name !== "AbortError") {
|
|
380
|
+
this.connected = false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
handleSSEMessage(raw) {
|
|
385
|
+
try {
|
|
386
|
+
const msg = JSON.parse(raw);
|
|
387
|
+
if (msg.id !== void 0 && this.pendingRequests.has(msg.id)) {
|
|
388
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
389
|
+
this.pendingRequests.delete(msg.id);
|
|
390
|
+
if (msg.error) {
|
|
391
|
+
pending.reject(new Error(`MCP SSE error: ${msg.error.message}`));
|
|
392
|
+
} else {
|
|
393
|
+
pending.resolve(msg.result);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
} catch {
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// src/mcp/client.ts
|
|
402
|
+
function createTransport(config, name) {
|
|
403
|
+
const type = detectTransport(config);
|
|
404
|
+
switch (type) {
|
|
405
|
+
case "http":
|
|
406
|
+
return new HttpTransport(config, name);
|
|
407
|
+
case "sse":
|
|
408
|
+
return new SSETransport(config, name);
|
|
409
|
+
case "stdio":
|
|
410
|
+
default:
|
|
411
|
+
return new StdioTransport(config, name);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
var MCPClient = class {
|
|
415
|
+
transport;
|
|
416
|
+
serverName;
|
|
417
|
+
_tools = [];
|
|
418
|
+
constructor(config, serverName) {
|
|
419
|
+
this.serverName = serverName;
|
|
420
|
+
this.transport = createTransport(config, serverName);
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Start the MCP server and initialize the connection.
|
|
424
|
+
*/
|
|
425
|
+
async connect() {
|
|
426
|
+
await this.transport.connect();
|
|
427
|
+
await this.transport.sendRequest("initialize", {
|
|
428
|
+
protocolVersion: "2024-11-05",
|
|
429
|
+
capabilities: {},
|
|
430
|
+
clientInfo: { name: "notch-cli", version: "0.4.8" }
|
|
431
|
+
});
|
|
432
|
+
this.transport.sendNotification("notifications/initialized", {});
|
|
433
|
+
const result = await this.transport.sendRequest("tools/list", {});
|
|
434
|
+
this._tools = result.tools ?? [];
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Get discovered tools from this server.
|
|
438
|
+
*/
|
|
439
|
+
get tools() {
|
|
440
|
+
return this._tools;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Check if the MCP server is still alive.
|
|
444
|
+
*/
|
|
445
|
+
get isAlive() {
|
|
446
|
+
return this.transport.isAlive;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Call a tool on the MCP server. Auto-reconnects if the server has crashed.
|
|
450
|
+
*/
|
|
451
|
+
async callTool(name, args) {
|
|
452
|
+
if (!this.isAlive) {
|
|
453
|
+
try {
|
|
454
|
+
await this.transport.connect();
|
|
455
|
+
} catch (err) {
|
|
456
|
+
throw new Error(`MCP server ${this.serverName} is down and could not reconnect: ${err.message}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return this.transport.sendRequest("tools/call", { name, arguments: args });
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Disconnect from the MCP server.
|
|
463
|
+
*/
|
|
464
|
+
disconnect() {
|
|
465
|
+
this.transport.disconnect();
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
function mcpToolsToNotch(client, serverName) {
|
|
469
|
+
return client.tools.map((toolDef) => {
|
|
470
|
+
const params = z.record(z.unknown()).describe(
|
|
471
|
+
toolDef.description || `MCP tool from ${serverName}`
|
|
472
|
+
);
|
|
473
|
+
const notchTool = {
|
|
474
|
+
name: `mcp_${serverName}_${toolDef.name}`,
|
|
475
|
+
description: `[MCP/${serverName}] ${toolDef.description}`,
|
|
476
|
+
parameters: params,
|
|
477
|
+
async execute(args, _ctx) {
|
|
478
|
+
try {
|
|
479
|
+
const result = await client.callTool(toolDef.name, args);
|
|
480
|
+
const mcpResult = result;
|
|
481
|
+
if (mcpResult?.content && Array.isArray(mcpResult.content)) {
|
|
482
|
+
const text = mcpResult.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
|
|
483
|
+
return { content: text || JSON.stringify(result) };
|
|
484
|
+
}
|
|
485
|
+
return { content: typeof result === "string" ? result : JSON.stringify(result, null, 2) };
|
|
486
|
+
} catch (err) {
|
|
487
|
+
return { content: `MCP error (${serverName}/${toolDef.name}): ${err.message}`, isError: true };
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
return notchTool;
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
function parseMCPConfig(config) {
|
|
495
|
+
const servers = config?.mcpServers;
|
|
496
|
+
if (!servers || typeof servers !== "object") return {};
|
|
497
|
+
const result = {};
|
|
498
|
+
for (const [name, cfg] of Object.entries(servers)) {
|
|
499
|
+
const c = cfg;
|
|
500
|
+
if (c?.command || c?.url) {
|
|
501
|
+
result[name] = {
|
|
502
|
+
command: c.command,
|
|
503
|
+
args: c.args,
|
|
504
|
+
env: c.env,
|
|
505
|
+
cwd: c.cwd,
|
|
506
|
+
transport: c.transport,
|
|
507
|
+
url: c.url,
|
|
508
|
+
headers: c.headers
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return result;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// src/tools/index.ts
|
|
516
|
+
var BUILTIN_TOOLS = [
|
|
517
|
+
readTool,
|
|
518
|
+
writeTool,
|
|
519
|
+
editTool,
|
|
520
|
+
applyPatchTool,
|
|
521
|
+
shellTool,
|
|
522
|
+
gitTool,
|
|
523
|
+
githubTool,
|
|
524
|
+
grepTool,
|
|
525
|
+
globTool,
|
|
526
|
+
webFetchTool,
|
|
527
|
+
lspTool,
|
|
528
|
+
notebookTool,
|
|
529
|
+
taskTool
|
|
530
|
+
];
|
|
531
|
+
var mcpClients = /* @__PURE__ */ new Map();
|
|
532
|
+
var mcpTools = [];
|
|
533
|
+
async function initMCPServers(config) {
|
|
534
|
+
const servers = parseMCPConfig(config);
|
|
535
|
+
let toolCount = 0;
|
|
536
|
+
for (const [name, serverConfig] of Object.entries(servers)) {
|
|
537
|
+
try {
|
|
538
|
+
const client = new MCPClient(serverConfig, name);
|
|
539
|
+
await client.connect();
|
|
540
|
+
mcpClients.set(name, client);
|
|
541
|
+
const tools = mcpToolsToNotch(client, name);
|
|
542
|
+
mcpTools.push(...tools);
|
|
543
|
+
toolCount += tools.length;
|
|
544
|
+
} catch (err) {
|
|
545
|
+
console.error(` MCP server '${name}' failed to connect: ${err.message}`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
return toolCount;
|
|
549
|
+
}
|
|
550
|
+
function disconnectMCPServers() {
|
|
551
|
+
for (const [, client] of mcpClients) {
|
|
552
|
+
client.disconnect();
|
|
553
|
+
}
|
|
554
|
+
mcpClients.clear();
|
|
555
|
+
mcpTools = [];
|
|
556
|
+
}
|
|
557
|
+
function getAllTools() {
|
|
558
|
+
return [...BUILTIN_TOOLS, ...mcpTools, ...pluginManager.getTools()];
|
|
559
|
+
}
|
|
560
|
+
function buildToolMap(ctx) {
|
|
561
|
+
const map = {};
|
|
562
|
+
for (const t of getAllTools()) {
|
|
563
|
+
map[t.name] = tool({
|
|
564
|
+
description: t.description,
|
|
565
|
+
parameters: t.parameters,
|
|
566
|
+
execute: async (params) => {
|
|
567
|
+
if (ctx.checkPermission) {
|
|
568
|
+
await ctx.runHook?.("permission-request", { tool: t.name, args: params });
|
|
569
|
+
const level = ctx.checkPermission(t.name, params);
|
|
570
|
+
if (level === "deny") {
|
|
571
|
+
await ctx.runHook?.("permission-denied", { tool: t.name, args: params, reason: "denied-by-config" });
|
|
572
|
+
return {
|
|
573
|
+
content: `Permission denied: ${t.name} is not allowed by your permission config.`,
|
|
574
|
+
isError: true
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
if (level === "prompt" && ctx.requireConfirm) {
|
|
578
|
+
const paramSummary = Object.entries(params).map(([k, v]) => `${k}=${String(v).slice(0, 80)}`).join(", ");
|
|
579
|
+
const confirmed = await ctx.confirm(
|
|
580
|
+
`Tool ${t.name}(${paramSummary}) requires approval. Proceed?`
|
|
581
|
+
);
|
|
582
|
+
if (!confirmed) {
|
|
583
|
+
await ctx.runHook?.("permission-denied", { tool: t.name, args: params, reason: "cancelled-by-user" });
|
|
584
|
+
return { content: "Cancelled by user.", isError: true };
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (ctx.dryRun && ["write", "edit", "apply_patch", "shell", "git"].includes(t.name)) {
|
|
589
|
+
const paramSummary = JSON.stringify(params, null, 2).slice(0, 500);
|
|
590
|
+
return {
|
|
591
|
+
content: `[DRY RUN] Would execute ${t.name}:
|
|
592
|
+
${paramSummary}`
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
await ctx.runHook?.("pre-tool", { tool: t.name, args: params });
|
|
596
|
+
const result = await t.execute(params, ctx);
|
|
597
|
+
await ctx.runHook?.("post-tool", {
|
|
598
|
+
tool: t.name,
|
|
599
|
+
args: params,
|
|
600
|
+
result: result.content.slice(0, 500),
|
|
601
|
+
isError: result.isError ?? false
|
|
602
|
+
});
|
|
603
|
+
return result;
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
return map;
|
|
608
|
+
}
|
|
609
|
+
function listToolNames() {
|
|
610
|
+
return getAllTools().map((t) => t.name);
|
|
611
|
+
}
|
|
612
|
+
function describeTools() {
|
|
613
|
+
return getAllTools().map(
|
|
614
|
+
(t) => `- **${t.name}**: ${t.description}`
|
|
615
|
+
).join("\n");
|
|
616
|
+
}
|
|
617
|
+
function mcpToolCount() {
|
|
618
|
+
return mcpTools.length;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export {
|
|
622
|
+
MCPClient,
|
|
623
|
+
parseMCPConfig,
|
|
624
|
+
initMCPServers,
|
|
625
|
+
disconnectMCPServers,
|
|
626
|
+
buildToolMap,
|
|
627
|
+
listToolNames,
|
|
628
|
+
describeTools,
|
|
629
|
+
mcpToolCount
|
|
630
|
+
};
|