@xpufx/paseo-mcp-tools 0.1.3

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.
Files changed (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/client/__snapshots__/attribution.test.tsx.snap +170 -0
  4. package/client/commands.ts +113 -0
  5. package/client/mcp-query.tsx +39 -0
  6. package/client/pill.tsx +886 -0
  7. package/client/timeline.tsx +136 -0
  8. package/client/vendor/paseo-plugin-helper/command-center.ts +43 -0
  9. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +493 -0
  10. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +250 -0
  11. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +156 -0
  12. package/client/vendor/paseo-plugin-helper/components/Button.tsx +178 -0
  13. package/client/vendor/paseo-plugin-helper/components/Card.tsx +225 -0
  14. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +196 -0
  15. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +277 -0
  16. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +172 -0
  17. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +180 -0
  18. package/client/vendor/paseo-plugin-helper/components/DataTable.tsx +200 -0
  19. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +97 -0
  20. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +70 -0
  21. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +73 -0
  22. package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +446 -0
  23. package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +247 -0
  24. package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +117 -0
  25. package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +53 -0
  26. package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +118 -0
  27. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +80 -0
  28. package/client/vendor/paseo-plugin-helper/components/Select.tsx +215 -0
  29. package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +80 -0
  30. package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +319 -0
  31. package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +150 -0
  32. package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +163 -0
  33. package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +157 -0
  34. package/client/vendor/paseo-plugin-helper/components/index.ts +25 -0
  35. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +224 -0
  36. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +79 -0
  37. package/client/vendor/paseo-plugin-helper/host.ts +277 -0
  38. package/client/vendor/paseo-plugin-helper/icon.tsx +39 -0
  39. package/client/vendor/paseo-plugin-helper/index.ts +28 -0
  40. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +49 -0
  41. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +103 -0
  42. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +65 -0
  43. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +378 -0
  44. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +49 -0
  45. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +39 -0
  46. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +39 -0
  47. package/client/vendor/paseo-plugin-helper/layout/index.ts +7 -0
  48. package/client/vendor/paseo-plugin-helper/panel.tsx +81 -0
  49. package/client/vendor/paseo-plugin-helper/pill.tsx +884 -0
  50. package/client/vendor/paseo-plugin-helper/query-refresh.ts +79 -0
  51. package/client/vendor/paseo-plugin-helper/query.ts +66 -0
  52. package/client/vendor/paseo-plugin-helper/settings-screen.tsx +372 -0
  53. package/client/vendor/paseo-plugin-helper/settings.ts +181 -0
  54. package/client/vendor/paseo-plugin-helper/shared-settings.ts +46 -0
  55. package/client/vendor/paseo-plugin-helper/snapshot.ts +68 -0
  56. package/client/vendor/paseo-plugin-helper/surface.tsx +80 -0
  57. package/client/vendor/paseo-plugin-helper/theme/color-utils.ts +118 -0
  58. package/client/vendor/paseo-plugin-helper/theme/flair.ts +76 -0
  59. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +121 -0
  60. package/client/vendor/paseo-plugin-helper/theme/index.ts +7 -0
  61. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +214 -0
  62. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +213 -0
  63. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +161 -0
  64. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +57 -0
  65. package/client/vendor/paseo-plugin-helper/utils/clipboard.ts +149 -0
  66. package/client/vendor/paseo-plugin-helper/utils/haptics.ts +34 -0
  67. package/docs/TEST_METHODOLOGY.md +29 -0
  68. package/package.json +41 -0
  69. package/paseo-plugin.json +4 -0
  70. package/scripts/version.mjs +4 -0
  71. package/server/discovery/extract.ts +277 -0
  72. package/server/discovery/types.ts +23 -0
  73. package/server/health/health.ts +274 -0
  74. package/server/inject.ts +32 -0
  75. package/server/mcp.ts +365 -0
  76. package/server/providers/README.md +51 -0
  77. package/server/providers/antigravity.ts +22 -0
  78. package/server/providers/catalog.ts +6 -0
  79. package/server/providers/claude.ts +21 -0
  80. package/server/providers/codex.ts +85 -0
  81. package/server/providers/family.ts +24 -0
  82. package/server/providers/index.ts +11 -0
  83. package/server/providers/opencode.ts +95 -0
  84. package/server/providers/paseo.ts +112 -0
  85. package/server/providers/pi.ts +39 -0
  86. package/server/settings.ts +19 -0
  87. package/server/vendor/paseo-plugin-helper/agent.ts +85 -0
  88. package/server/vendor/paseo-plugin-helper/custom-pills.ts +344 -0
  89. package/server/vendor/paseo-plugin-helper/index.ts +18 -0
  90. package/server/vendor/paseo-plugin-helper/jsonc.ts +78 -0
  91. package/server/vendor/paseo-plugin-helper/logger.ts +210 -0
  92. package/server/vendor/paseo-plugin-helper/mcp/client.ts +349 -0
  93. package/server/vendor/paseo-plugin-helper/mcp/http-client.ts +399 -0
  94. package/server/vendor/paseo-plugin-helper/mcp/index.ts +5 -0
  95. package/server/vendor/paseo-plugin-helper/mcp/process-killer.ts +65 -0
  96. package/server/vendor/paseo-plugin-helper/mcp/ring-buffer.ts +29 -0
  97. package/server/vendor/paseo-plugin-helper/mcp/types.ts +41 -0
  98. package/server/vendor/paseo-plugin-helper/mcp-config.ts +367 -0
  99. package/server/vendor/paseo-plugin-helper/mcp-injection.ts +85 -0
  100. package/server/vendor/paseo-plugin-helper/network.ts +91 -0
  101. package/server/vendor/paseo-plugin-helper/plugins.ts +160 -0
  102. package/server/vendor/paseo-plugin-helper/process.ts +186 -0
  103. package/server/vendor/paseo-plugin-helper/redact.ts +86 -0
  104. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +77 -0
  105. package/server/vendor/paseo-plugin-helper/settings.ts +97 -0
  106. package/server/vendor/paseo-plugin-helper/shared-settings.ts +243 -0
  107. package/server/vendor/paseo-plugin-helper/storage.ts +244 -0
  108. package/server/vendor/paseo-plugin-helper/system.ts +128 -0
  109. package/server/vendor/paseo-plugin-helper/task.ts +116 -0
  110. package/server/vendor/paseo-plugin-helper/version.ts +153 -0
  111. package/server/vendor/paseo-plugin-helper/workspace-beacon.ts +418 -0
  112. package/shared/mcp.ts +207 -0
  113. package/shared/vendor/paseo-plugin-helper/README.md +11 -0
  114. package/shared/vendor/paseo-plugin-helper/async.ts +35 -0
  115. package/shared/vendor/paseo-plugin-helper/custom-pills.ts +169 -0
  116. package/shared/vendor/paseo-plugin-helper/forge.ts +110 -0
  117. package/shared/vendor/paseo-plugin-helper/formatters.ts +271 -0
  118. package/shared/vendor/paseo-plugin-helper/highlight.ts +184 -0
  119. package/shared/vendor/paseo-plugin-helper/index.ts +10 -0
  120. package/shared/vendor/paseo-plugin-helper/rpc.ts +72 -0
  121. package/shared/vendor/paseo-plugin-helper/settings.ts +138 -0
  122. package/shared/vendor/paseo-plugin-helper/suite-settings.ts +17 -0
  123. package/shared/vendor/paseo-plugin-helper/suppressed.ts +31 -0
  124. package/shared/vendor/paseo-plugin-helper/types.ts +36 -0
  125. package/shared/version.ts +2 -0
@@ -0,0 +1,399 @@
1
+ import crypto from "node:crypto";
2
+ import type {
3
+ McpClientOptions,
4
+ McpPingOptions,
5
+ McpPingResult,
6
+ McpToolCallResult,
7
+ McpToolInfo,
8
+ } from "./types";
9
+
10
+ export interface McpHttpOptions extends McpClientOptions {
11
+ headers?: Record<string, string>;
12
+ }
13
+
14
+ interface PendingRequest {
15
+ resolve: (res: any) => void;
16
+ reject: (err: Error) => void;
17
+ timer: NodeJS.Timeout;
18
+ }
19
+
20
+ /**
21
+ * Zero-dependency MCP client supporting direct HTTP POST (Streamable HTTP)
22
+ * and Server-Sent Events (SSE) transports.
23
+ */
24
+ export class McpHttpClient {
25
+ private pendingRequests = new Map<string | number, PendingRequest>();
26
+ private initialized = false;
27
+ private _serverInfo?: { name: string; version?: string };
28
+ private _instructions?: string;
29
+ private _capabilities?: Record<string, unknown>;
30
+ private _protocolVersion?: string;
31
+ private initPromise?: Promise<void>;
32
+ private abortController = new AbortController();
33
+ private postUrl: string;
34
+ private sessionId?: string;
35
+ private isSse = false;
36
+ private sseConnectPromise?: Promise<void>;
37
+
38
+ constructor(
39
+ private readonly url: string,
40
+ private readonly options: McpHttpOptions = {},
41
+ ) {
42
+ this.postUrl = url;
43
+ }
44
+
45
+ /**
46
+ * Server metadata returned during MCP initialize handshake.
47
+ */
48
+ get serverInfo(): { name: string; version?: string } | undefined {
49
+ return this._serverInfo;
50
+ }
51
+
52
+ /**
53
+ * Optional server instructions / prompt guidance returned during initialize handshake.
54
+ */
55
+ get instructions(): string | undefined {
56
+ return this._instructions;
57
+ }
58
+
59
+ /**
60
+ * Declared server capabilities returned during initialize handshake.
61
+ */
62
+ get capabilities(): Record<string, unknown> | undefined {
63
+ return this._capabilities;
64
+ }
65
+
66
+ /**
67
+ * Protocol version negotiated during initialize handshake.
68
+ */
69
+ get protocolVersion(): string | undefined {
70
+ return this._protocolVersion;
71
+ }
72
+
73
+ private async ensureSseConnected(): Promise<void> {
74
+ if (this.sseConnectPromise) return this.sseConnectPromise;
75
+
76
+ this.sseConnectPromise = (async () => {
77
+ try {
78
+ const res = await fetch(this.url, {
79
+ method: "GET",
80
+ headers: {
81
+ Accept: "text/event-stream",
82
+ ...this.options.headers,
83
+ },
84
+ signal: this.abortController.signal,
85
+ });
86
+
87
+ const contentType = res.headers.get("content-type") ?? "";
88
+ if (contentType.includes("text/event-stream") && res.body) {
89
+ this.isSse = true;
90
+ this.consumeSseStream(res.body);
91
+ }
92
+ } catch {
93
+ // Fall back to direct HTTP POST if SSE connection fails
94
+ }
95
+ })();
96
+
97
+ return this.sseConnectPromise;
98
+ }
99
+
100
+ private async consumeSseStream(stream: ReadableStream<Uint8Array>): Promise<void> {
101
+ const reader = stream.getReader();
102
+ const decoder = new TextDecoder();
103
+ let buffer = "";
104
+ let currentEvent = "message";
105
+ let currentData = "";
106
+
107
+ try {
108
+ while (true) {
109
+ const { done, value } = await reader.read();
110
+ if (done) break;
111
+
112
+ buffer += decoder.decode(value, { stream: true });
113
+ const lines = buffer.split(/\r?\n/);
114
+ buffer = lines.pop() ?? "";
115
+
116
+ for (const line of lines) {
117
+ if (line.startsWith("event:")) {
118
+ currentEvent = line.slice(6).trim();
119
+ } else if (line.startsWith("data:")) {
120
+ currentData += (currentData ? "\n" : "") + line.slice(5).trim();
121
+ } else if (line.trim() === "") {
122
+ if (currentEvent === "endpoint" && currentData) {
123
+ try {
124
+ this.postUrl = new URL(currentData, this.url).toString();
125
+ } catch {
126
+ this.postUrl = currentData;
127
+ }
128
+ } else if (currentData) {
129
+ try {
130
+ const msg = JSON.parse(currentData);
131
+ this.handleIncomingMessage(msg);
132
+ } catch {
133
+ // Ignore non-JSON lines
134
+ }
135
+ }
136
+ currentEvent = "message";
137
+ currentData = "";
138
+ }
139
+ }
140
+ }
141
+ } catch {
142
+ // Stream aborted or closed
143
+ }
144
+ }
145
+
146
+ private handleIncomingMessage(msg: any): void {
147
+ if (msg.id !== undefined && this.pendingRequests.has(msg.id)) {
148
+ const pending = this.pendingRequests.get(msg.id)!;
149
+ this.pendingRequests.delete(msg.id);
150
+ clearTimeout(pending.timer);
151
+
152
+ if (msg.error) {
153
+ const err = new Error(msg.error.message ?? "RPC Error");
154
+ (err as any).code = msg.error.code;
155
+ pending.reject(err);
156
+ } else {
157
+ pending.resolve(msg.result);
158
+ }
159
+ }
160
+ }
161
+
162
+ private async sendRequest<T = any>(
163
+ method: string,
164
+ params?: any,
165
+ timeoutMs?: number,
166
+ retryOnSessionExpired = true,
167
+ ): Promise<T> {
168
+ const timeout = timeoutMs ?? this.options.timeoutMs ?? 10000;
169
+ const id = crypto.randomUUID();
170
+
171
+ const payload = {
172
+ jsonrpc: "2.0",
173
+ id,
174
+ method,
175
+ ...(params !== undefined ? { params } : {}),
176
+ };
177
+
178
+ if (this.url.includes("/sse")) {
179
+ await this.ensureSseConnected();
180
+ }
181
+
182
+ return new Promise<T>((resolve, reject) => {
183
+ const timer = setTimeout(() => {
184
+ this.pendingRequests.delete(id);
185
+ reject(new Error(`HTTP MCP request "${method}" timed out after ${timeout}ms.`));
186
+ }, timeout);
187
+
188
+ this.pendingRequests.set(id, { resolve, reject, timer });
189
+
190
+ const reqHeaders: Record<string, string> = {
191
+ "Content-Type": "application/json",
192
+ Accept: "application/json, text/event-stream",
193
+ ...this.options.headers,
194
+ };
195
+ if (this.sessionId) {
196
+ reqHeaders["Mcp-Session-Id"] = this.sessionId;
197
+ }
198
+
199
+ fetch(this.postUrl, {
200
+ method: "POST",
201
+ headers: reqHeaders,
202
+ body: JSON.stringify(payload),
203
+ signal: this.abortController.signal,
204
+ })
205
+ .then(async (res) => {
206
+ const sid = res.headers.get("mcp-session-id");
207
+ if (sid) {
208
+ this.sessionId = sid;
209
+ }
210
+
211
+ if (!res.ok) {
212
+ clearTimeout(timer);
213
+ this.pendingRequests.delete(id);
214
+
215
+ // If a stateful session expired (404), invalidate and retry once with a fresh handshake
216
+ if (
217
+ res.status === 404 &&
218
+ this.sessionId &&
219
+ retryOnSessionExpired &&
220
+ method !== "initialize"
221
+ ) {
222
+ this.sessionId = undefined;
223
+ this.initialized = false;
224
+ this.initPromise = undefined;
225
+ try {
226
+ await this.initialize();
227
+ const retried = await this.sendRequest<T>(method, params, timeoutMs, false);
228
+ resolve(retried);
229
+ return;
230
+ } catch (retryErr) {
231
+ reject(retryErr);
232
+ return;
233
+ }
234
+ }
235
+
236
+ reject(new Error(`MCP HTTP POST returned HTTP ${res.status}: ${res.statusText}`));
237
+ return;
238
+ }
239
+
240
+ const contentType = res.headers.get("content-type") ?? "";
241
+ if (contentType.includes("application/json")) {
242
+ const body = await res.json();
243
+ this.handleIncomingMessage(body);
244
+ } else if (contentType.includes("text/event-stream") && res.body) {
245
+ this.consumeSseStream(res.body);
246
+ } else {
247
+ // Unhandled content type; ensure request does not hang
248
+ clearTimeout(timer);
249
+ this.pendingRequests.delete(id);
250
+ reject(new Error(`Unsupported MCP HTTP response content-type: ${contentType}`));
251
+ }
252
+ })
253
+ .catch((err) => {
254
+ clearTimeout(timer);
255
+ this.pendingRequests.delete(id);
256
+ reject(err);
257
+ });
258
+ });
259
+ }
260
+
261
+ private async sendNotification(method: string, params?: any): Promise<void> {
262
+ const payload = {
263
+ jsonrpc: "2.0",
264
+ method,
265
+ ...(params !== undefined ? { params } : {}),
266
+ };
267
+
268
+ const reqHeaders: Record<string, string> = {
269
+ "Content-Type": "application/json",
270
+ ...this.options.headers,
271
+ };
272
+ if (this.sessionId) {
273
+ reqHeaders["Mcp-Session-Id"] = this.sessionId;
274
+ }
275
+
276
+ try {
277
+ await fetch(this.postUrl, {
278
+ method: "POST",
279
+ headers: reqHeaders,
280
+ body: JSON.stringify(payload),
281
+ signal: this.abortController.signal,
282
+ });
283
+ } catch {
284
+ // Fire-and-forget notification
285
+ }
286
+ }
287
+
288
+ async initialize(): Promise<void> {
289
+ if (this.initialized) return;
290
+ if (this.initPromise) return this.initPromise;
291
+
292
+ this.initPromise = (async () => {
293
+ if (this.url.includes("/sse")) {
294
+ await this.ensureSseConnected();
295
+ }
296
+
297
+ const result = await this.sendRequest("initialize", {
298
+ protocolVersion: "2024-11-05",
299
+ capabilities: {
300
+ tools: {},
301
+ },
302
+ clientInfo: this.options.clientInfo ?? {
303
+ name: "paseo-plugin",
304
+ version: "1.0.0",
305
+ },
306
+ });
307
+
308
+ this._serverInfo = result?.serverInfo;
309
+ this._instructions = result?.instructions;
310
+ this._capabilities = result?.capabilities;
311
+ this._protocolVersion = result?.protocolVersion;
312
+ await this.sendNotification("notifications/initialized");
313
+ this.initialized = true;
314
+ })();
315
+
316
+ return this.initPromise;
317
+ }
318
+
319
+ async ping(options: McpPingOptions = {}): Promise<McpPingResult> {
320
+ const start = Date.now();
321
+ const timeout = options.timeoutMs ?? this.options.timeoutMs ?? 10000;
322
+
323
+ try {
324
+ await this.initialize();
325
+
326
+ if (options.mode === "tools") {
327
+ await this.listTools();
328
+ } else {
329
+ try {
330
+ await this.sendRequest("ping", {}, timeout);
331
+ } catch (err: any) {
332
+ if (err.code !== -32601) {
333
+ throw err;
334
+ }
335
+ }
336
+ }
337
+
338
+ return {
339
+ healthy: true,
340
+ latencyMs: Date.now() - start,
341
+ serverInfo: this._serverInfo,
342
+ instructions: this._instructions,
343
+ };
344
+ } catch (err: any) {
345
+ return {
346
+ healthy: false,
347
+ latencyMs: Date.now() - start,
348
+ error: err.message,
349
+ };
350
+ }
351
+ }
352
+
353
+ async listTools(): Promise<McpToolInfo[]> {
354
+ await this.initialize();
355
+ const result = await this.sendRequest("tools/list", {});
356
+ return (result?.tools as McpToolInfo[]) ?? [];
357
+ }
358
+
359
+ async callTool(name: string, args: Record<string, unknown> = {}): Promise<McpToolCallResult> {
360
+ await this.initialize();
361
+ const result = await this.sendRequest("tools/call", {
362
+ name,
363
+ arguments: args,
364
+ });
365
+ return result as McpToolCallResult;
366
+ }
367
+
368
+ async close(): Promise<void> {
369
+ if (this.sessionId) {
370
+ const sid = this.sessionId;
371
+ this.sessionId = undefined;
372
+ const teardownController = new AbortController();
373
+ const teardownTimer = setTimeout(() => teardownController.abort(), 2000);
374
+ try {
375
+ await fetch(this.postUrl, {
376
+ method: "DELETE",
377
+ headers: {
378
+ "Mcp-Session-Id": sid,
379
+ ...this.options.headers,
380
+ },
381
+ signal: teardownController.signal,
382
+ });
383
+ } catch {
384
+ // Ignore session teardown errors
385
+ } finally {
386
+ clearTimeout(teardownTimer);
387
+ }
388
+ }
389
+
390
+ this.abortController.abort();
391
+ for (const [, pending] of this.pendingRequests.entries()) {
392
+ clearTimeout(pending.timer);
393
+ pending.reject(new Error("HTTP MCP Client was closed."));
394
+ }
395
+ this.pendingRequests.clear();
396
+ this.initialized = false;
397
+ this.initPromise = undefined;
398
+ }
399
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./types";
2
+ export * from "./client";
3
+ export * from "./http-client";
4
+ export * from "./ring-buffer";
5
+ export * from "./process-killer";
@@ -0,0 +1,65 @@
1
+ import { spawn, type ChildProcess } from "node:child_process";
2
+
3
+ /**
4
+ * Cross-platform process tree killer.
5
+ * On POSIX, signals the entire process group (-pid).
6
+ * On Windows, executes taskkill /T /F to terminate parent and descendants.
7
+ */
8
+ export function killProcessTree(child: ChildProcess, timeoutMs = 1500): Promise<void> {
9
+ return new Promise((resolve) => {
10
+ if (!child || !child.pid || child.killed) {
11
+ resolve();
12
+ return;
13
+ }
14
+
15
+ const pid = child.pid;
16
+
17
+ if (process.platform === "win32") {
18
+ try {
19
+ const killer = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
20
+ stdio: "ignore",
21
+ windowsHide: true,
22
+ });
23
+ killer.on("close", () => resolve());
24
+ killer.on("error", () => {
25
+ try {
26
+ child.kill("SIGKILL");
27
+ } catch {}
28
+ resolve();
29
+ });
30
+ } catch {
31
+ try {
32
+ child.kill("SIGKILL");
33
+ } catch {}
34
+ resolve();
35
+ }
36
+ return;
37
+ }
38
+
39
+ // POSIX process group termination
40
+ try {
41
+ // Signal negative PID for process group
42
+ process.kill(-pid, "SIGTERM");
43
+ } catch {
44
+ try {
45
+ child.kill("SIGTERM");
46
+ } catch {}
47
+ }
48
+
49
+ const timer = setTimeout(() => {
50
+ try {
51
+ process.kill(-pid, "SIGKILL");
52
+ } catch {
53
+ try {
54
+ child.kill("SIGKILL");
55
+ } catch {}
56
+ }
57
+ resolve();
58
+ }, timeoutMs);
59
+
60
+ child.on("close", () => {
61
+ clearTimeout(timer);
62
+ resolve();
63
+ });
64
+ });
65
+ }
@@ -0,0 +1,29 @@
1
+ export class StderrRingBuffer {
2
+ private lines: string[] = [];
3
+
4
+ constructor(private readonly maxLines = 25) {}
5
+
6
+ push(chunk: string): void {
7
+ const newLines = chunk
8
+ .split(/\r?\n/)
9
+ .map((l) => l.trim())
10
+ .filter((l) => l.length > 0);
11
+
12
+ this.lines.push(...newLines);
13
+ if (this.lines.length > this.maxLines) {
14
+ this.lines = this.lines.slice(-this.maxLines);
15
+ }
16
+ }
17
+
18
+ getRecentLines(): string[] {
19
+ return [...this.lines];
20
+ }
21
+
22
+ getRecentText(): string {
23
+ return this.lines.join("\n");
24
+ }
25
+
26
+ clear(): void {
27
+ this.lines = [];
28
+ }
29
+ }
@@ -0,0 +1,41 @@
1
+ export interface McpClientOptions {
2
+ timeoutMs?: number;
3
+ clientInfo?: {
4
+ name: string;
5
+ version: string;
6
+ };
7
+ }
8
+
9
+ export interface McpToolInfo {
10
+ name: string;
11
+ description?: string;
12
+ inputSchema?: Record<string, unknown>;
13
+ }
14
+
15
+ export interface McpToolCallResult {
16
+ content: Array<{
17
+ type: string;
18
+ text?: string;
19
+ data?: string;
20
+ mimeType?: string;
21
+ [key: string]: unknown;
22
+ }>;
23
+ isError?: boolean;
24
+ }
25
+
26
+ export interface McpPingResult {
27
+ healthy: boolean;
28
+ latencyMs: number;
29
+ serverInfo?: {
30
+ name: string;
31
+ version?: string;
32
+ };
33
+ instructions?: string;
34
+ error?: string;
35
+ stderr?: string;
36
+ }
37
+
38
+ export interface McpPingOptions {
39
+ mode?: "protocol" | "tools";
40
+ timeoutMs?: number;
41
+ }