mcp-wiretap 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +19 -0
- package/.github/workflows/ci.yml +25 -0
- package/.github/workflows/publish.yml +28 -0
- package/CHANGELOG.md +15 -0
- package/CONTRIBUTING.md +22 -0
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/dist/index.js +1426 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
- package/src/display.ts +232 -0
- package/src/export.ts +93 -0
- package/src/index.ts +255 -0
- package/src/logger.ts +178 -0
- package/src/parser.ts +179 -0
- package/src/proxy.ts +239 -0
- package/src/sessions.ts +122 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +17 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1426 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/proxy.ts
|
|
27
|
+
var import_node_child_process = require("child_process");
|
|
28
|
+
|
|
29
|
+
// src/parser.ts
|
|
30
|
+
function isRequest(msg) {
|
|
31
|
+
return "method" in msg && msg.method !== void 0;
|
|
32
|
+
}
|
|
33
|
+
function isResponse(msg) {
|
|
34
|
+
return !("method" in msg) && ("result" in msg || "error" in msg);
|
|
35
|
+
}
|
|
36
|
+
function extractToolName(msg) {
|
|
37
|
+
if (isRequest(msg) && msg.method === "tools/call" && msg.params) {
|
|
38
|
+
return msg.params.name || null;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
function tryParseJsonRpc(raw) {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(raw);
|
|
45
|
+
if (parsed && typeof parsed === "object" && parsed.jsonrpc === "2.0") {
|
|
46
|
+
return parsed;
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
var RequestCorrelator = class {
|
|
54
|
+
pending = /* @__PURE__ */ new Map();
|
|
55
|
+
/**
|
|
56
|
+
* Record an outgoing request so we can match its response later.
|
|
57
|
+
*/
|
|
58
|
+
trackRequest(id, method, toolName) {
|
|
59
|
+
this.pending.set(id, {
|
|
60
|
+
method,
|
|
61
|
+
toolName,
|
|
62
|
+
timestamp: performance.now()
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Match a response to a tracked request.
|
|
67
|
+
* Returns { method, toolName, latencyMs } or null if no match.
|
|
68
|
+
*/
|
|
69
|
+
matchResponse(id) {
|
|
70
|
+
const req = this.pending.get(id);
|
|
71
|
+
if (!req) return null;
|
|
72
|
+
this.pending.delete(id);
|
|
73
|
+
return {
|
|
74
|
+
method: req.method,
|
|
75
|
+
toolName: req.toolName,
|
|
76
|
+
latencyMs: Math.round(performance.now() - req.timestamp)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Number of in-flight requests
|
|
81
|
+
*/
|
|
82
|
+
get size() {
|
|
83
|
+
return this.pending.size;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
var MessageBuffer = class {
|
|
87
|
+
buffer = "";
|
|
88
|
+
/**
|
|
89
|
+
* Feed raw data into the buffer. Returns an array of complete
|
|
90
|
+
* raw JSON strings ready for parsing.
|
|
91
|
+
*/
|
|
92
|
+
feed(chunk) {
|
|
93
|
+
this.buffer += chunk;
|
|
94
|
+
const messages = [];
|
|
95
|
+
let newlineIdx;
|
|
96
|
+
while ((newlineIdx = this.buffer.indexOf("\n")) !== -1) {
|
|
97
|
+
const line = this.buffer.slice(0, newlineIdx).trim();
|
|
98
|
+
this.buffer = this.buffer.slice(newlineIdx + 1);
|
|
99
|
+
if (line.length > 0) {
|
|
100
|
+
messages.push(line);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return messages;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Flush any remaining content in the buffer (for shutdown).
|
|
107
|
+
*/
|
|
108
|
+
flush() {
|
|
109
|
+
const remaining = this.buffer.trim();
|
|
110
|
+
this.buffer = "";
|
|
111
|
+
return remaining.length > 0 ? remaining : null;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// src/logger.ts
|
|
116
|
+
var import_better_sqlite3 = __toESM(require("better-sqlite3"));
|
|
117
|
+
var import_node_path = __toESM(require("path"));
|
|
118
|
+
var import_node_fs = __toESM(require("fs"));
|
|
119
|
+
var import_node_os = __toESM(require("os"));
|
|
120
|
+
var import_node_crypto = __toESM(require("crypto"));
|
|
121
|
+
var DEFAULT_DB_DIR = import_node_path.default.join(import_node_os.default.homedir(), ".mcp-wiretap");
|
|
122
|
+
var DEFAULT_DB_PATH = import_node_path.default.join(DEFAULT_DB_DIR, "wiretap.db");
|
|
123
|
+
var WiretapLogger = class {
|
|
124
|
+
db;
|
|
125
|
+
insertStmt;
|
|
126
|
+
sessionId;
|
|
127
|
+
constructor(dbPath) {
|
|
128
|
+
const resolvedPath = dbPath || DEFAULT_DB_PATH;
|
|
129
|
+
const dir = import_node_path.default.dirname(resolvedPath);
|
|
130
|
+
if (!import_node_fs.default.existsSync(dir)) {
|
|
131
|
+
import_node_fs.default.mkdirSync(dir, { recursive: true });
|
|
132
|
+
}
|
|
133
|
+
this.db = new import_better_sqlite3.default(resolvedPath);
|
|
134
|
+
this.sessionId = import_node_crypto.default.randomUUID();
|
|
135
|
+
this.db.pragma("journal_mode = WAL");
|
|
136
|
+
this.db.exec(`
|
|
137
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
138
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
139
|
+
timestamp TEXT NOT NULL,
|
|
140
|
+
direction TEXT NOT NULL,
|
|
141
|
+
jsonrpc_id TEXT,
|
|
142
|
+
method TEXT,
|
|
143
|
+
tool_name TEXT,
|
|
144
|
+
params TEXT,
|
|
145
|
+
latency_ms INTEGER,
|
|
146
|
+
payload_bytes INTEGER NOT NULL,
|
|
147
|
+
session_id TEXT NOT NULL
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
151
|
+
session_id TEXT PRIMARY KEY,
|
|
152
|
+
started_at TEXT NOT NULL,
|
|
153
|
+
server_command TEXT NOT NULL
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
CREATE INDEX IF NOT EXISTS idx_events_session ON events(session_id);
|
|
157
|
+
CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
|
|
158
|
+
CREATE INDEX IF NOT EXISTS idx_events_method ON events(method);
|
|
159
|
+
CREATE INDEX IF NOT EXISTS idx_events_tool_name ON events(tool_name);
|
|
160
|
+
`);
|
|
161
|
+
this.insertStmt = this.db.prepare(`
|
|
162
|
+
INSERT INTO events (timestamp, direction, jsonrpc_id, method, tool_name, params, latency_ms, payload_bytes, session_id)
|
|
163
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
164
|
+
`);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Record this session in the sessions table
|
|
168
|
+
*/
|
|
169
|
+
startSession(serverCommand) {
|
|
170
|
+
this.db.prepare("INSERT INTO sessions (session_id, started_at, server_command) VALUES (?, ?, ?)").run(this.sessionId, (/* @__PURE__ */ new Date()).toISOString(), serverCommand);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Log a correlated event to SQLite
|
|
174
|
+
*/
|
|
175
|
+
logEvent(event) {
|
|
176
|
+
this.insertStmt.run(
|
|
177
|
+
event.timestamp,
|
|
178
|
+
event.direction,
|
|
179
|
+
event.jsonrpcId != null ? String(event.jsonrpcId) : null,
|
|
180
|
+
event.method,
|
|
181
|
+
event.toolName,
|
|
182
|
+
event.params,
|
|
183
|
+
event.latencyMs,
|
|
184
|
+
event.payloadBytes,
|
|
185
|
+
event.sessionId
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get all events for a session
|
|
190
|
+
*/
|
|
191
|
+
getSessionEvents(sessionId) {
|
|
192
|
+
const sid = sessionId || this.sessionId;
|
|
193
|
+
return this.db.prepare("SELECT * FROM events WHERE session_id = ? ORDER BY timestamp ASC").all(sid);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get all events across all sessions
|
|
197
|
+
*/
|
|
198
|
+
getAllEvents() {
|
|
199
|
+
return this.db.prepare("SELECT * FROM events ORDER BY timestamp ASC").all();
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* List all recorded sessions
|
|
203
|
+
*/
|
|
204
|
+
listSessions() {
|
|
205
|
+
return this.db.prepare(
|
|
206
|
+
`SELECT s.session_id as sessionId, s.started_at as startedAt, s.server_command as serverCommand,
|
|
207
|
+
COUNT(e.id) as eventCount
|
|
208
|
+
FROM sessions s
|
|
209
|
+
LEFT JOIN events e ON s.session_id = e.session_id
|
|
210
|
+
GROUP BY s.session_id
|
|
211
|
+
ORDER BY s.started_at DESC`
|
|
212
|
+
).all();
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Search events by method or tool name across all sessions
|
|
216
|
+
*/
|
|
217
|
+
searchEvents(query) {
|
|
218
|
+
return this.db.prepare(
|
|
219
|
+
`SELECT * FROM events
|
|
220
|
+
WHERE method LIKE ? OR tool_name LIKE ? OR params LIKE ?
|
|
221
|
+
ORDER BY timestamp ASC`
|
|
222
|
+
).all(`%${query}%`, `%${query}%`, `%${query}%`);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Close the database connection
|
|
226
|
+
*/
|
|
227
|
+
close() {
|
|
228
|
+
this.db.close();
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Get the path to the database
|
|
232
|
+
*/
|
|
233
|
+
static get defaultDbPath() {
|
|
234
|
+
return DEFAULT_DB_PATH;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
239
|
+
var ANSI_BACKGROUND_OFFSET = 10;
|
|
240
|
+
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
241
|
+
var wrapAnsi256 = (offset = 0) => (code) => `\x1B[${38 + offset};5;${code}m`;
|
|
242
|
+
var wrapAnsi16m = (offset = 0) => (red, green, blue) => `\x1B[${38 + offset};2;${red};${green};${blue}m`;
|
|
243
|
+
var styles = {
|
|
244
|
+
modifier: {
|
|
245
|
+
reset: [0, 0],
|
|
246
|
+
// 21 isn't widely supported and 22 does the same thing
|
|
247
|
+
bold: [1, 22],
|
|
248
|
+
dim: [2, 22],
|
|
249
|
+
italic: [3, 23],
|
|
250
|
+
underline: [4, 24],
|
|
251
|
+
overline: [53, 55],
|
|
252
|
+
inverse: [7, 27],
|
|
253
|
+
hidden: [8, 28],
|
|
254
|
+
strikethrough: [9, 29]
|
|
255
|
+
},
|
|
256
|
+
color: {
|
|
257
|
+
black: [30, 39],
|
|
258
|
+
red: [31, 39],
|
|
259
|
+
green: [32, 39],
|
|
260
|
+
yellow: [33, 39],
|
|
261
|
+
blue: [34, 39],
|
|
262
|
+
magenta: [35, 39],
|
|
263
|
+
cyan: [36, 39],
|
|
264
|
+
white: [37, 39],
|
|
265
|
+
// Bright color
|
|
266
|
+
blackBright: [90, 39],
|
|
267
|
+
gray: [90, 39],
|
|
268
|
+
// Alias of `blackBright`
|
|
269
|
+
grey: [90, 39],
|
|
270
|
+
// Alias of `blackBright`
|
|
271
|
+
redBright: [91, 39],
|
|
272
|
+
greenBright: [92, 39],
|
|
273
|
+
yellowBright: [93, 39],
|
|
274
|
+
blueBright: [94, 39],
|
|
275
|
+
magentaBright: [95, 39],
|
|
276
|
+
cyanBright: [96, 39],
|
|
277
|
+
whiteBright: [97, 39]
|
|
278
|
+
},
|
|
279
|
+
bgColor: {
|
|
280
|
+
bgBlack: [40, 49],
|
|
281
|
+
bgRed: [41, 49],
|
|
282
|
+
bgGreen: [42, 49],
|
|
283
|
+
bgYellow: [43, 49],
|
|
284
|
+
bgBlue: [44, 49],
|
|
285
|
+
bgMagenta: [45, 49],
|
|
286
|
+
bgCyan: [46, 49],
|
|
287
|
+
bgWhite: [47, 49],
|
|
288
|
+
// Bright color
|
|
289
|
+
bgBlackBright: [100, 49],
|
|
290
|
+
bgGray: [100, 49],
|
|
291
|
+
// Alias of `bgBlackBright`
|
|
292
|
+
bgGrey: [100, 49],
|
|
293
|
+
// Alias of `bgBlackBright`
|
|
294
|
+
bgRedBright: [101, 49],
|
|
295
|
+
bgGreenBright: [102, 49],
|
|
296
|
+
bgYellowBright: [103, 49],
|
|
297
|
+
bgBlueBright: [104, 49],
|
|
298
|
+
bgMagentaBright: [105, 49],
|
|
299
|
+
bgCyanBright: [106, 49],
|
|
300
|
+
bgWhiteBright: [107, 49]
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
var modifierNames = Object.keys(styles.modifier);
|
|
304
|
+
var foregroundColorNames = Object.keys(styles.color);
|
|
305
|
+
var backgroundColorNames = Object.keys(styles.bgColor);
|
|
306
|
+
var colorNames = [...foregroundColorNames, ...backgroundColorNames];
|
|
307
|
+
function assembleStyles() {
|
|
308
|
+
const codes = /* @__PURE__ */ new Map();
|
|
309
|
+
for (const [groupName, group] of Object.entries(styles)) {
|
|
310
|
+
for (const [styleName, style] of Object.entries(group)) {
|
|
311
|
+
styles[styleName] = {
|
|
312
|
+
open: `\x1B[${style[0]}m`,
|
|
313
|
+
close: `\x1B[${style[1]}m`
|
|
314
|
+
};
|
|
315
|
+
group[styleName] = styles[styleName];
|
|
316
|
+
codes.set(style[0], style[1]);
|
|
317
|
+
}
|
|
318
|
+
Object.defineProperty(styles, groupName, {
|
|
319
|
+
value: group,
|
|
320
|
+
enumerable: false
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
Object.defineProperty(styles, "codes", {
|
|
324
|
+
value: codes,
|
|
325
|
+
enumerable: false
|
|
326
|
+
});
|
|
327
|
+
styles.color.close = "\x1B[39m";
|
|
328
|
+
styles.bgColor.close = "\x1B[49m";
|
|
329
|
+
styles.color.ansi = wrapAnsi16();
|
|
330
|
+
styles.color.ansi256 = wrapAnsi256();
|
|
331
|
+
styles.color.ansi16m = wrapAnsi16m();
|
|
332
|
+
styles.bgColor.ansi = wrapAnsi16(ANSI_BACKGROUND_OFFSET);
|
|
333
|
+
styles.bgColor.ansi256 = wrapAnsi256(ANSI_BACKGROUND_OFFSET);
|
|
334
|
+
styles.bgColor.ansi16m = wrapAnsi16m(ANSI_BACKGROUND_OFFSET);
|
|
335
|
+
Object.defineProperties(styles, {
|
|
336
|
+
rgbToAnsi256: {
|
|
337
|
+
value(red, green, blue) {
|
|
338
|
+
if (red === green && green === blue) {
|
|
339
|
+
if (red < 8) {
|
|
340
|
+
return 16;
|
|
341
|
+
}
|
|
342
|
+
if (red > 248) {
|
|
343
|
+
return 231;
|
|
344
|
+
}
|
|
345
|
+
return Math.round((red - 8) / 247 * 24) + 232;
|
|
346
|
+
}
|
|
347
|
+
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
|
|
348
|
+
},
|
|
349
|
+
enumerable: false
|
|
350
|
+
},
|
|
351
|
+
hexToRgb: {
|
|
352
|
+
value(hex) {
|
|
353
|
+
const matches = /[a-f\d]{6}|[a-f\d]{3}/i.exec(hex.toString(16));
|
|
354
|
+
if (!matches) {
|
|
355
|
+
return [0, 0, 0];
|
|
356
|
+
}
|
|
357
|
+
let [colorString] = matches;
|
|
358
|
+
if (colorString.length === 3) {
|
|
359
|
+
colorString = [...colorString].map((character) => character + character).join("");
|
|
360
|
+
}
|
|
361
|
+
const integer = Number.parseInt(colorString, 16);
|
|
362
|
+
return [
|
|
363
|
+
/* eslint-disable no-bitwise */
|
|
364
|
+
integer >> 16 & 255,
|
|
365
|
+
integer >> 8 & 255,
|
|
366
|
+
integer & 255
|
|
367
|
+
/* eslint-enable no-bitwise */
|
|
368
|
+
];
|
|
369
|
+
},
|
|
370
|
+
enumerable: false
|
|
371
|
+
},
|
|
372
|
+
hexToAnsi256: {
|
|
373
|
+
value: (hex) => styles.rgbToAnsi256(...styles.hexToRgb(hex)),
|
|
374
|
+
enumerable: false
|
|
375
|
+
},
|
|
376
|
+
ansi256ToAnsi: {
|
|
377
|
+
value(code) {
|
|
378
|
+
if (code < 8) {
|
|
379
|
+
return 30 + code;
|
|
380
|
+
}
|
|
381
|
+
if (code < 16) {
|
|
382
|
+
return 90 + (code - 8);
|
|
383
|
+
}
|
|
384
|
+
let red;
|
|
385
|
+
let green;
|
|
386
|
+
let blue;
|
|
387
|
+
if (code >= 232) {
|
|
388
|
+
red = ((code - 232) * 10 + 8) / 255;
|
|
389
|
+
green = red;
|
|
390
|
+
blue = red;
|
|
391
|
+
} else {
|
|
392
|
+
code -= 16;
|
|
393
|
+
const remainder = code % 36;
|
|
394
|
+
red = Math.floor(code / 36) / 5;
|
|
395
|
+
green = Math.floor(remainder / 6) / 5;
|
|
396
|
+
blue = remainder % 6 / 5;
|
|
397
|
+
}
|
|
398
|
+
const value = Math.max(red, green, blue) * 2;
|
|
399
|
+
if (value === 0) {
|
|
400
|
+
return 30;
|
|
401
|
+
}
|
|
402
|
+
let result = 30 + (Math.round(blue) << 2 | Math.round(green) << 1 | Math.round(red));
|
|
403
|
+
if (value === 2) {
|
|
404
|
+
result += 60;
|
|
405
|
+
}
|
|
406
|
+
return result;
|
|
407
|
+
},
|
|
408
|
+
enumerable: false
|
|
409
|
+
},
|
|
410
|
+
rgbToAnsi: {
|
|
411
|
+
value: (red, green, blue) => styles.ansi256ToAnsi(styles.rgbToAnsi256(red, green, blue)),
|
|
412
|
+
enumerable: false
|
|
413
|
+
},
|
|
414
|
+
hexToAnsi: {
|
|
415
|
+
value: (hex) => styles.ansi256ToAnsi(styles.hexToAnsi256(hex)),
|
|
416
|
+
enumerable: false
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
return styles;
|
|
420
|
+
}
|
|
421
|
+
var ansiStyles = assembleStyles();
|
|
422
|
+
var ansi_styles_default = ansiStyles;
|
|
423
|
+
|
|
424
|
+
// node_modules/chalk/source/vendor/supports-color/index.js
|
|
425
|
+
var import_node_process = __toESM(require("process"), 1);
|
|
426
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
427
|
+
var import_node_tty = __toESM(require("tty"), 1);
|
|
428
|
+
function hasFlag(flag, argv = globalThis.Deno ? globalThis.Deno.args : import_node_process.default.argv) {
|
|
429
|
+
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
430
|
+
const position = argv.indexOf(prefix + flag);
|
|
431
|
+
const terminatorPosition = argv.indexOf("--");
|
|
432
|
+
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
433
|
+
}
|
|
434
|
+
var { env } = import_node_process.default;
|
|
435
|
+
var flagForceColor;
|
|
436
|
+
if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) {
|
|
437
|
+
flagForceColor = 0;
|
|
438
|
+
} else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) {
|
|
439
|
+
flagForceColor = 1;
|
|
440
|
+
}
|
|
441
|
+
function envForceColor() {
|
|
442
|
+
if ("FORCE_COLOR" in env) {
|
|
443
|
+
if (env.FORCE_COLOR === "true") {
|
|
444
|
+
return 1;
|
|
445
|
+
}
|
|
446
|
+
if (env.FORCE_COLOR === "false") {
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
449
|
+
return env.FORCE_COLOR.length === 0 ? 1 : Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
function translateLevel(level) {
|
|
453
|
+
if (level === 0) {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
return {
|
|
457
|
+
level,
|
|
458
|
+
hasBasic: true,
|
|
459
|
+
has256: level >= 2,
|
|
460
|
+
has16m: level >= 3
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
|
|
464
|
+
const noFlagForceColor = envForceColor();
|
|
465
|
+
if (noFlagForceColor !== void 0) {
|
|
466
|
+
flagForceColor = noFlagForceColor;
|
|
467
|
+
}
|
|
468
|
+
const forceColor = sniffFlags ? flagForceColor : noFlagForceColor;
|
|
469
|
+
if (forceColor === 0) {
|
|
470
|
+
return 0;
|
|
471
|
+
}
|
|
472
|
+
if (sniffFlags) {
|
|
473
|
+
if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) {
|
|
474
|
+
return 3;
|
|
475
|
+
}
|
|
476
|
+
if (hasFlag("color=256")) {
|
|
477
|
+
return 2;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if ("TF_BUILD" in env && "AGENT_NAME" in env) {
|
|
481
|
+
return 1;
|
|
482
|
+
}
|
|
483
|
+
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
484
|
+
return 0;
|
|
485
|
+
}
|
|
486
|
+
const min = forceColor || 0;
|
|
487
|
+
if (env.TERM === "dumb") {
|
|
488
|
+
return min;
|
|
489
|
+
}
|
|
490
|
+
if (import_node_process.default.platform === "win32") {
|
|
491
|
+
const osRelease = import_node_os2.default.release().split(".");
|
|
492
|
+
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
493
|
+
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
494
|
+
}
|
|
495
|
+
return 1;
|
|
496
|
+
}
|
|
497
|
+
if ("CI" in env) {
|
|
498
|
+
if (["GITHUB_ACTIONS", "GITEA_ACTIONS", "CIRCLECI"].some((key) => key in env)) {
|
|
499
|
+
return 3;
|
|
500
|
+
}
|
|
501
|
+
if (["TRAVIS", "APPVEYOR", "GITLAB_CI", "BUILDKITE", "DRONE"].some((sign) => sign in env) || env.CI_NAME === "codeship") {
|
|
502
|
+
return 1;
|
|
503
|
+
}
|
|
504
|
+
return min;
|
|
505
|
+
}
|
|
506
|
+
if ("TEAMCITY_VERSION" in env) {
|
|
507
|
+
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
|
|
508
|
+
}
|
|
509
|
+
if (env.COLORTERM === "truecolor") {
|
|
510
|
+
return 3;
|
|
511
|
+
}
|
|
512
|
+
if (env.TERM === "xterm-kitty") {
|
|
513
|
+
return 3;
|
|
514
|
+
}
|
|
515
|
+
if (env.TERM === "xterm-ghostty") {
|
|
516
|
+
return 3;
|
|
517
|
+
}
|
|
518
|
+
if (env.TERM === "wezterm") {
|
|
519
|
+
return 3;
|
|
520
|
+
}
|
|
521
|
+
if ("TERM_PROGRAM" in env) {
|
|
522
|
+
const version = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
523
|
+
switch (env.TERM_PROGRAM) {
|
|
524
|
+
case "iTerm.app": {
|
|
525
|
+
return version >= 3 ? 3 : 2;
|
|
526
|
+
}
|
|
527
|
+
case "Apple_Terminal": {
|
|
528
|
+
return 2;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
if (/-256(color)?$/i.test(env.TERM)) {
|
|
533
|
+
return 2;
|
|
534
|
+
}
|
|
535
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
|
|
536
|
+
return 1;
|
|
537
|
+
}
|
|
538
|
+
if ("COLORTERM" in env) {
|
|
539
|
+
return 1;
|
|
540
|
+
}
|
|
541
|
+
return min;
|
|
542
|
+
}
|
|
543
|
+
function createSupportsColor(stream, options = {}) {
|
|
544
|
+
const level = _supportsColor(stream, {
|
|
545
|
+
streamIsTTY: stream && stream.isTTY,
|
|
546
|
+
...options
|
|
547
|
+
});
|
|
548
|
+
return translateLevel(level);
|
|
549
|
+
}
|
|
550
|
+
var supportsColor = {
|
|
551
|
+
stdout: createSupportsColor({ isTTY: import_node_tty.default.isatty(1) }),
|
|
552
|
+
stderr: createSupportsColor({ isTTY: import_node_tty.default.isatty(2) })
|
|
553
|
+
};
|
|
554
|
+
var supports_color_default = supportsColor;
|
|
555
|
+
|
|
556
|
+
// node_modules/chalk/source/utilities.js
|
|
557
|
+
function stringReplaceAll(string, substring, replacer) {
|
|
558
|
+
let index = string.indexOf(substring);
|
|
559
|
+
if (index === -1) {
|
|
560
|
+
return string;
|
|
561
|
+
}
|
|
562
|
+
const substringLength = substring.length;
|
|
563
|
+
let endIndex = 0;
|
|
564
|
+
let returnValue = "";
|
|
565
|
+
do {
|
|
566
|
+
returnValue += string.slice(endIndex, index) + substring + replacer;
|
|
567
|
+
endIndex = index + substringLength;
|
|
568
|
+
index = string.indexOf(substring, endIndex);
|
|
569
|
+
} while (index !== -1);
|
|
570
|
+
returnValue += string.slice(endIndex);
|
|
571
|
+
return returnValue;
|
|
572
|
+
}
|
|
573
|
+
function stringEncaseCRLFWithFirstIndex(string, prefix, postfix, index) {
|
|
574
|
+
let endIndex = 0;
|
|
575
|
+
let returnValue = "";
|
|
576
|
+
do {
|
|
577
|
+
const gotCR = string[index - 1] === "\r";
|
|
578
|
+
returnValue += string.slice(endIndex, gotCR ? index - 1 : index) + prefix + (gotCR ? "\r\n" : "\n") + postfix;
|
|
579
|
+
endIndex = index + 1;
|
|
580
|
+
index = string.indexOf("\n", endIndex);
|
|
581
|
+
} while (index !== -1);
|
|
582
|
+
returnValue += string.slice(endIndex);
|
|
583
|
+
return returnValue;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// node_modules/chalk/source/index.js
|
|
587
|
+
var { stdout: stdoutColor, stderr: stderrColor } = supports_color_default;
|
|
588
|
+
var GENERATOR = /* @__PURE__ */ Symbol("GENERATOR");
|
|
589
|
+
var STYLER = /* @__PURE__ */ Symbol("STYLER");
|
|
590
|
+
var IS_EMPTY = /* @__PURE__ */ Symbol("IS_EMPTY");
|
|
591
|
+
var levelMapping = [
|
|
592
|
+
"ansi",
|
|
593
|
+
"ansi",
|
|
594
|
+
"ansi256",
|
|
595
|
+
"ansi16m"
|
|
596
|
+
];
|
|
597
|
+
var styles2 = /* @__PURE__ */ Object.create(null);
|
|
598
|
+
var applyOptions = (object, options = {}) => {
|
|
599
|
+
if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) {
|
|
600
|
+
throw new Error("The `level` option should be an integer from 0 to 3");
|
|
601
|
+
}
|
|
602
|
+
const colorLevel = stdoutColor ? stdoutColor.level : 0;
|
|
603
|
+
object.level = options.level === void 0 ? colorLevel : options.level;
|
|
604
|
+
};
|
|
605
|
+
var chalkFactory = (options) => {
|
|
606
|
+
const chalk2 = (...strings) => strings.join(" ");
|
|
607
|
+
applyOptions(chalk2, options);
|
|
608
|
+
Object.setPrototypeOf(chalk2, createChalk.prototype);
|
|
609
|
+
return chalk2;
|
|
610
|
+
};
|
|
611
|
+
function createChalk(options) {
|
|
612
|
+
return chalkFactory(options);
|
|
613
|
+
}
|
|
614
|
+
Object.setPrototypeOf(createChalk.prototype, Function.prototype);
|
|
615
|
+
for (const [styleName, style] of Object.entries(ansi_styles_default)) {
|
|
616
|
+
styles2[styleName] = {
|
|
617
|
+
get() {
|
|
618
|
+
const builder = createBuilder(this, createStyler(style.open, style.close, this[STYLER]), this[IS_EMPTY]);
|
|
619
|
+
Object.defineProperty(this, styleName, { value: builder });
|
|
620
|
+
return builder;
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
styles2.visible = {
|
|
625
|
+
get() {
|
|
626
|
+
const builder = createBuilder(this, this[STYLER], true);
|
|
627
|
+
Object.defineProperty(this, "visible", { value: builder });
|
|
628
|
+
return builder;
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
var getModelAnsi = (model, level, type, ...arguments_) => {
|
|
632
|
+
if (model === "rgb") {
|
|
633
|
+
if (level === "ansi16m") {
|
|
634
|
+
return ansi_styles_default[type].ansi16m(...arguments_);
|
|
635
|
+
}
|
|
636
|
+
if (level === "ansi256") {
|
|
637
|
+
return ansi_styles_default[type].ansi256(ansi_styles_default.rgbToAnsi256(...arguments_));
|
|
638
|
+
}
|
|
639
|
+
return ansi_styles_default[type].ansi(ansi_styles_default.rgbToAnsi(...arguments_));
|
|
640
|
+
}
|
|
641
|
+
if (model === "hex") {
|
|
642
|
+
return getModelAnsi("rgb", level, type, ...ansi_styles_default.hexToRgb(...arguments_));
|
|
643
|
+
}
|
|
644
|
+
return ansi_styles_default[type][model](...arguments_);
|
|
645
|
+
};
|
|
646
|
+
var usedModels = ["rgb", "hex", "ansi256"];
|
|
647
|
+
for (const model of usedModels) {
|
|
648
|
+
styles2[model] = {
|
|
649
|
+
get() {
|
|
650
|
+
const { level } = this;
|
|
651
|
+
return function(...arguments_) {
|
|
652
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "color", ...arguments_), ansi_styles_default.color.close, this[STYLER]);
|
|
653
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
const bgModel = "bg" + model[0].toUpperCase() + model.slice(1);
|
|
658
|
+
styles2[bgModel] = {
|
|
659
|
+
get() {
|
|
660
|
+
const { level } = this;
|
|
661
|
+
return function(...arguments_) {
|
|
662
|
+
const styler = createStyler(getModelAnsi(model, levelMapping[level], "bgColor", ...arguments_), ansi_styles_default.bgColor.close, this[STYLER]);
|
|
663
|
+
return createBuilder(this, styler, this[IS_EMPTY]);
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
var proto = Object.defineProperties(() => {
|
|
669
|
+
}, {
|
|
670
|
+
...styles2,
|
|
671
|
+
level: {
|
|
672
|
+
enumerable: true,
|
|
673
|
+
get() {
|
|
674
|
+
return this[GENERATOR].level;
|
|
675
|
+
},
|
|
676
|
+
set(level) {
|
|
677
|
+
this[GENERATOR].level = level;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
var createStyler = (open, close, parent) => {
|
|
682
|
+
let openAll;
|
|
683
|
+
let closeAll;
|
|
684
|
+
if (parent === void 0) {
|
|
685
|
+
openAll = open;
|
|
686
|
+
closeAll = close;
|
|
687
|
+
} else {
|
|
688
|
+
openAll = parent.openAll + open;
|
|
689
|
+
closeAll = close + parent.closeAll;
|
|
690
|
+
}
|
|
691
|
+
return {
|
|
692
|
+
open,
|
|
693
|
+
close,
|
|
694
|
+
openAll,
|
|
695
|
+
closeAll,
|
|
696
|
+
parent
|
|
697
|
+
};
|
|
698
|
+
};
|
|
699
|
+
var createBuilder = (self, _styler, _isEmpty) => {
|
|
700
|
+
const builder = (...arguments_) => applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" "));
|
|
701
|
+
Object.setPrototypeOf(builder, proto);
|
|
702
|
+
builder[GENERATOR] = self;
|
|
703
|
+
builder[STYLER] = _styler;
|
|
704
|
+
builder[IS_EMPTY] = _isEmpty;
|
|
705
|
+
return builder;
|
|
706
|
+
};
|
|
707
|
+
var applyStyle = (self, string) => {
|
|
708
|
+
if (self.level <= 0 || !string) {
|
|
709
|
+
return self[IS_EMPTY] ? "" : string;
|
|
710
|
+
}
|
|
711
|
+
let styler = self[STYLER];
|
|
712
|
+
if (styler === void 0) {
|
|
713
|
+
return string;
|
|
714
|
+
}
|
|
715
|
+
const { openAll, closeAll } = styler;
|
|
716
|
+
if (string.includes("\x1B")) {
|
|
717
|
+
while (styler !== void 0) {
|
|
718
|
+
string = stringReplaceAll(string, styler.close, styler.open);
|
|
719
|
+
styler = styler.parent;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
const lfIndex = string.indexOf("\n");
|
|
723
|
+
if (lfIndex !== -1) {
|
|
724
|
+
string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex);
|
|
725
|
+
}
|
|
726
|
+
return openAll + string + closeAll;
|
|
727
|
+
};
|
|
728
|
+
Object.defineProperties(createChalk.prototype, styles2);
|
|
729
|
+
var chalk = createChalk();
|
|
730
|
+
var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
731
|
+
var source_default = chalk;
|
|
732
|
+
|
|
733
|
+
// src/display.ts
|
|
734
|
+
var DEFAULT_OPTIONS = {
|
|
735
|
+
verbose: false,
|
|
736
|
+
quiet: false,
|
|
737
|
+
noColor: false,
|
|
738
|
+
maxPayload: null,
|
|
739
|
+
filter: null
|
|
740
|
+
};
|
|
741
|
+
function formatTime(isoTimestamp) {
|
|
742
|
+
const d = new Date(isoTimestamp);
|
|
743
|
+
const h = String(d.getHours()).padStart(2, "0");
|
|
744
|
+
const m = String(d.getMinutes()).padStart(2, "0");
|
|
745
|
+
const s = String(d.getSeconds()).padStart(2, "0");
|
|
746
|
+
const ms = String(d.getMilliseconds()).padStart(3, "0");
|
|
747
|
+
return `${h}:${m}:${s}.${ms}`;
|
|
748
|
+
}
|
|
749
|
+
function truncate(str, maxLen) {
|
|
750
|
+
if (str.length <= maxLen) return str;
|
|
751
|
+
return str.slice(0, maxLen - 3) + "...";
|
|
752
|
+
}
|
|
753
|
+
function formatBytes(bytes) {
|
|
754
|
+
if (bytes < 1024) return `${bytes} bytes`;
|
|
755
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
756
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
757
|
+
}
|
|
758
|
+
function displayCompact(event, opts) {
|
|
759
|
+
const time = formatTime(event.timestamp);
|
|
760
|
+
const isRequest2 = event.direction === "client\u2192server";
|
|
761
|
+
let arrow;
|
|
762
|
+
let dirLabel;
|
|
763
|
+
if (isRequest2) {
|
|
764
|
+
arrow = opts.noColor ? "\u2500\u2500\u25B6" : source_default.green("\u2500\u2500\u25B6");
|
|
765
|
+
dirLabel = "";
|
|
766
|
+
} else {
|
|
767
|
+
arrow = opts.noColor ? "\u25C0\u2500\u2500" : source_default.blue("\u25C0\u2500\u2500");
|
|
768
|
+
dirLabel = "";
|
|
769
|
+
}
|
|
770
|
+
const method = event.method || "";
|
|
771
|
+
const tool = event.toolName ? ` ${event.toolName}` : "";
|
|
772
|
+
const latency = event.latencyMs != null ? ` ${event.latencyMs}ms` : "";
|
|
773
|
+
let payload = "";
|
|
774
|
+
if (!isRequest2 && event.params) {
|
|
775
|
+
try {
|
|
776
|
+
const parsed = JSON.parse(event.params);
|
|
777
|
+
if (parsed.result) {
|
|
778
|
+
if (typeof parsed.result === "object" && parsed.result.content) {
|
|
779
|
+
payload = ` (${formatBytes(event.payloadBytes)})`;
|
|
780
|
+
} else if (typeof parsed.result === "object") {
|
|
781
|
+
const keys = Object.keys(parsed.result);
|
|
782
|
+
if (parsed.result.tools) {
|
|
783
|
+
payload = ` (${parsed.result.tools.length} tools)`;
|
|
784
|
+
} else {
|
|
785
|
+
payload = ` (${formatBytes(event.payloadBytes)})`;
|
|
786
|
+
}
|
|
787
|
+
} else {
|
|
788
|
+
payload = ` (ok)`;
|
|
789
|
+
}
|
|
790
|
+
} else if (parsed.error) {
|
|
791
|
+
const errMsg = parsed.error.message || "error";
|
|
792
|
+
payload = opts.noColor ? ` ERROR: ${errMsg}` : ` ${source_default.red("ERROR:")} ${errMsg}`;
|
|
793
|
+
}
|
|
794
|
+
} catch {
|
|
795
|
+
payload = ` (${formatBytes(event.payloadBytes)})`;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (isRequest2 && event.params && event.method === "tools/call") {
|
|
799
|
+
try {
|
|
800
|
+
const parsed = JSON.parse(event.params);
|
|
801
|
+
if (parsed.params?.arguments) {
|
|
802
|
+
const argsStr = JSON.stringify(parsed.params.arguments);
|
|
803
|
+
payload = ` ${truncate(argsStr, 60)}`;
|
|
804
|
+
}
|
|
805
|
+
} catch {
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
const methodDisplay = opts.noColor ? method : isRequest2 ? source_default.green(method) : source_default.blue(method);
|
|
809
|
+
const toolDisplay = opts.noColor ? tool : source_default.yellow(tool);
|
|
810
|
+
const latencyDisplay = opts.noColor ? latency : source_default.gray(latency);
|
|
811
|
+
const timeDisplay = opts.noColor ? time : source_default.gray(time);
|
|
812
|
+
process.stderr.write(`${timeDisplay} ${arrow} ${methodDisplay}${toolDisplay}${payload}${latencyDisplay}
|
|
813
|
+
`);
|
|
814
|
+
}
|
|
815
|
+
function displayVerbose(event, opts) {
|
|
816
|
+
const time = formatTime(event.timestamp);
|
|
817
|
+
const isRequest2 = event.direction === "client\u2192server";
|
|
818
|
+
let arrow;
|
|
819
|
+
if (isRequest2) {
|
|
820
|
+
arrow = opts.noColor ? "\u2500\u2500\u25B6" : source_default.green("\u2500\u2500\u25B6");
|
|
821
|
+
} else {
|
|
822
|
+
arrow = opts.noColor ? "\u25C0\u2500\u2500" : source_default.blue("\u25C0\u2500\u2500");
|
|
823
|
+
}
|
|
824
|
+
const method = event.method || "(response)";
|
|
825
|
+
const methodDisplay = opts.noColor ? method : isRequest2 ? source_default.green(method) : source_default.blue(method);
|
|
826
|
+
const timeDisplay = opts.noColor ? time : source_default.gray(time);
|
|
827
|
+
process.stderr.write(`${timeDisplay} ${arrow} ${methodDisplay}
|
|
828
|
+
`);
|
|
829
|
+
if (event.toolName) {
|
|
830
|
+
process.stderr.write(` tool: ${event.toolName}
|
|
831
|
+
`);
|
|
832
|
+
}
|
|
833
|
+
if (event.latencyMs != null) {
|
|
834
|
+
process.stderr.write(` latency: ${event.latencyMs}ms
|
|
835
|
+
`);
|
|
836
|
+
}
|
|
837
|
+
if (event.params) {
|
|
838
|
+
try {
|
|
839
|
+
const parsed = JSON.parse(event.params);
|
|
840
|
+
let displayPayload;
|
|
841
|
+
if (isRequest2 && parsed.params) {
|
|
842
|
+
displayPayload = JSON.stringify(parsed.params, null, 2);
|
|
843
|
+
} else if (!isRequest2 && (parsed.result || parsed.error)) {
|
|
844
|
+
displayPayload = JSON.stringify(parsed.result || parsed.error, null, 2);
|
|
845
|
+
} else {
|
|
846
|
+
displayPayload = JSON.stringify(parsed, null, 2);
|
|
847
|
+
}
|
|
848
|
+
if (opts.maxPayload && displayPayload.length > opts.maxPayload) {
|
|
849
|
+
displayPayload = truncate(displayPayload, opts.maxPayload);
|
|
850
|
+
}
|
|
851
|
+
const indented = displayPayload.split("\n").map((line) => ` ${line}`).join("\n");
|
|
852
|
+
process.stderr.write(`${indented}
|
|
853
|
+
`);
|
|
854
|
+
} catch {
|
|
855
|
+
process.stderr.write(` ${event.params}
|
|
856
|
+
`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
process.stderr.write("\n");
|
|
860
|
+
}
|
|
861
|
+
function displayEvent(event, opts = {}) {
|
|
862
|
+
const options = { ...DEFAULT_OPTIONS, ...opts };
|
|
863
|
+
if (options.quiet) return;
|
|
864
|
+
if (options.filter && event.method && !event.method.includes(options.filter)) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
if (options.verbose) {
|
|
868
|
+
displayVerbose(event, options);
|
|
869
|
+
} else {
|
|
870
|
+
displayCompact(event, options);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
function displayBanner(serverCommand, sessionId, dbPath) {
|
|
874
|
+
const header = source_default.bold("mcp-wiretap");
|
|
875
|
+
const dim = source_default.gray;
|
|
876
|
+
process.stderr.write(`
|
|
877
|
+
${header} ${dim("v0.1.0")}
|
|
878
|
+
`);
|
|
879
|
+
process.stderr.write(` ${dim("session:")} ${sessionId.slice(0, 8)}
|
|
880
|
+
`);
|
|
881
|
+
process.stderr.write(` ${dim("server:")} ${serverCommand}
|
|
882
|
+
`);
|
|
883
|
+
process.stderr.write(` ${dim("db:")} ${dbPath}
|
|
884
|
+
`);
|
|
885
|
+
process.stderr.write(` ${dim("\u2500".repeat(50))}
|
|
886
|
+
|
|
887
|
+
`);
|
|
888
|
+
}
|
|
889
|
+
function displayShutdown(eventCount, sessionId) {
|
|
890
|
+
const dim = source_default.gray;
|
|
891
|
+
process.stderr.write(`
|
|
892
|
+
${dim("\u2500".repeat(50))}
|
|
893
|
+
`);
|
|
894
|
+
process.stderr.write(` ${dim("session")} ${sessionId.slice(0, 8)} ${dim("ended \u2014")} ${eventCount} ${dim("events logged")}
|
|
895
|
+
|
|
896
|
+
`);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// src/proxy.ts
|
|
900
|
+
var WiretapProxy = class {
|
|
901
|
+
child = null;
|
|
902
|
+
logger;
|
|
903
|
+
correlator = new RequestCorrelator();
|
|
904
|
+
clientBuffer = new MessageBuffer();
|
|
905
|
+
serverBuffer = new MessageBuffer();
|
|
906
|
+
eventCount = 0;
|
|
907
|
+
displayOptions;
|
|
908
|
+
serverCommand;
|
|
909
|
+
constructor(options) {
|
|
910
|
+
this.logger = new WiretapLogger(options.dbPath);
|
|
911
|
+
this.displayOptions = options.displayOptions || {};
|
|
912
|
+
this.serverCommand = [options.command, ...options.args].join(" ");
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Start the proxy. Spawns the child MCP server and begins intercepting.
|
|
916
|
+
*/
|
|
917
|
+
start(command, args) {
|
|
918
|
+
this.logger.startSession(this.serverCommand);
|
|
919
|
+
displayBanner(
|
|
920
|
+
this.serverCommand,
|
|
921
|
+
this.logger.sessionId,
|
|
922
|
+
WiretapLogger.defaultDbPath
|
|
923
|
+
);
|
|
924
|
+
this.child = (0, import_node_child_process.spawn)(command, args, {
|
|
925
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
926
|
+
env: process.env
|
|
927
|
+
});
|
|
928
|
+
process.stdin.on("data", (chunk) => {
|
|
929
|
+
const data = chunk.toString("utf-8");
|
|
930
|
+
if (this.child?.stdin?.writable) {
|
|
931
|
+
this.child.stdin.write(chunk);
|
|
932
|
+
}
|
|
933
|
+
const messages = this.clientBuffer.feed(data);
|
|
934
|
+
for (const raw of messages) {
|
|
935
|
+
this.processMessage(raw, "client\u2192server");
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
process.stdin.on("end", () => {
|
|
939
|
+
this.child?.stdin?.end();
|
|
940
|
+
});
|
|
941
|
+
this.child.stdout?.on("data", (chunk) => {
|
|
942
|
+
const data = chunk.toString("utf-8");
|
|
943
|
+
process.stdout.write(chunk);
|
|
944
|
+
const messages = this.serverBuffer.feed(data);
|
|
945
|
+
for (const raw of messages) {
|
|
946
|
+
this.processMessage(raw, "server\u2192client");
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
this.child.stderr?.on("data", (chunk) => {
|
|
950
|
+
process.stderr.write(chunk);
|
|
951
|
+
});
|
|
952
|
+
this.child.on("error", (err) => {
|
|
953
|
+
process.stderr.write(`
|
|
954
|
+
mcp-wiretap: failed to start server: ${err.message}
|
|
955
|
+
`);
|
|
956
|
+
this.shutdown(1);
|
|
957
|
+
});
|
|
958
|
+
this.child.on("exit", (code, signal) => {
|
|
959
|
+
const exitReason = signal ? `signal ${signal}` : `code ${code}`;
|
|
960
|
+
process.stderr.write(`
|
|
961
|
+
mcp-wiretap: server exited (${exitReason})
|
|
962
|
+
`);
|
|
963
|
+
this.shutdown(code ?? 1);
|
|
964
|
+
});
|
|
965
|
+
process.on("SIGINT", () => this.shutdown(0));
|
|
966
|
+
process.on("SIGTERM", () => this.shutdown(0));
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Process a single parsed message line
|
|
970
|
+
*/
|
|
971
|
+
processMessage(raw, direction) {
|
|
972
|
+
const msg = tryParseJsonRpc(raw);
|
|
973
|
+
if (!msg) return;
|
|
974
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
975
|
+
const payloadBytes = Buffer.byteLength(raw, "utf-8");
|
|
976
|
+
if (isRequest(msg)) {
|
|
977
|
+
const toolName = extractToolName(msg);
|
|
978
|
+
if (msg.id !== void 0) {
|
|
979
|
+
this.correlator.trackRequest(msg.id, msg.method, toolName);
|
|
980
|
+
}
|
|
981
|
+
const event = {
|
|
982
|
+
direction,
|
|
983
|
+
timestamp: now,
|
|
984
|
+
jsonrpcId: msg.id ?? null,
|
|
985
|
+
method: msg.method,
|
|
986
|
+
toolName,
|
|
987
|
+
params: raw,
|
|
988
|
+
payloadBytes,
|
|
989
|
+
latencyMs: null,
|
|
990
|
+
sessionId: this.logger.sessionId
|
|
991
|
+
};
|
|
992
|
+
this.logger.logEvent(event);
|
|
993
|
+
displayEvent(event, this.displayOptions);
|
|
994
|
+
this.eventCount++;
|
|
995
|
+
} else if (isResponse(msg)) {
|
|
996
|
+
let method = null;
|
|
997
|
+
let toolName = null;
|
|
998
|
+
let latencyMs = null;
|
|
999
|
+
if (msg.id !== void 0) {
|
|
1000
|
+
const match = this.correlator.matchResponse(msg.id);
|
|
1001
|
+
if (match) {
|
|
1002
|
+
method = match.method;
|
|
1003
|
+
toolName = match.toolName;
|
|
1004
|
+
latencyMs = match.latencyMs;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
const event = {
|
|
1008
|
+
direction,
|
|
1009
|
+
timestamp: now,
|
|
1010
|
+
jsonrpcId: msg.id ?? null,
|
|
1011
|
+
method,
|
|
1012
|
+
toolName,
|
|
1013
|
+
params: raw,
|
|
1014
|
+
payloadBytes,
|
|
1015
|
+
latencyMs,
|
|
1016
|
+
sessionId: this.logger.sessionId
|
|
1017
|
+
};
|
|
1018
|
+
this.logger.logEvent(event);
|
|
1019
|
+
displayEvent(event, this.displayOptions);
|
|
1020
|
+
this.eventCount++;
|
|
1021
|
+
} else {
|
|
1022
|
+
const event = {
|
|
1023
|
+
direction,
|
|
1024
|
+
timestamp: now,
|
|
1025
|
+
jsonrpcId: null,
|
|
1026
|
+
method: msg.method || null,
|
|
1027
|
+
toolName: null,
|
|
1028
|
+
params: raw,
|
|
1029
|
+
payloadBytes,
|
|
1030
|
+
latencyMs: null,
|
|
1031
|
+
sessionId: this.logger.sessionId
|
|
1032
|
+
};
|
|
1033
|
+
this.logger.logEvent(event);
|
|
1034
|
+
displayEvent(event, this.displayOptions);
|
|
1035
|
+
this.eventCount++;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Clean shutdown
|
|
1040
|
+
*/
|
|
1041
|
+
shutdown(exitCode) {
|
|
1042
|
+
const clientRemaining = this.clientBuffer.flush();
|
|
1043
|
+
if (clientRemaining) this.processMessage(clientRemaining, "client\u2192server");
|
|
1044
|
+
const serverRemaining = this.serverBuffer.flush();
|
|
1045
|
+
if (serverRemaining) this.processMessage(serverRemaining, "server\u2192client");
|
|
1046
|
+
displayShutdown(this.eventCount, this.logger.sessionId);
|
|
1047
|
+
if (this.child && !this.child.killed) {
|
|
1048
|
+
this.child.kill("SIGTERM");
|
|
1049
|
+
}
|
|
1050
|
+
this.logger.close();
|
|
1051
|
+
process.exit(exitCode);
|
|
1052
|
+
}
|
|
1053
|
+
};
|
|
1054
|
+
|
|
1055
|
+
// src/export.ts
|
|
1056
|
+
var import_node_fs2 = __toESM(require("fs"));
|
|
1057
|
+
function eventsToCSV(events) {
|
|
1058
|
+
const headers = [
|
|
1059
|
+
"id",
|
|
1060
|
+
"timestamp",
|
|
1061
|
+
"direction",
|
|
1062
|
+
"jsonrpc_id",
|
|
1063
|
+
"method",
|
|
1064
|
+
"tool_name",
|
|
1065
|
+
"params",
|
|
1066
|
+
"latency_ms",
|
|
1067
|
+
"payload_bytes",
|
|
1068
|
+
"session_id"
|
|
1069
|
+
];
|
|
1070
|
+
const rows = events.map((e) => {
|
|
1071
|
+
return [
|
|
1072
|
+
e.id,
|
|
1073
|
+
e.timestamp,
|
|
1074
|
+
`"${e.direction}"`,
|
|
1075
|
+
e.jsonrpc_id ?? "",
|
|
1076
|
+
e.method ?? "",
|
|
1077
|
+
e.tool_name ?? "",
|
|
1078
|
+
`"${(e.params ?? "").replace(/"/g, '""')}"`,
|
|
1079
|
+
e.latency_ms ?? "",
|
|
1080
|
+
e.payload_bytes,
|
|
1081
|
+
e.session_id
|
|
1082
|
+
].join(",");
|
|
1083
|
+
});
|
|
1084
|
+
return [headers.join(","), ...rows].join("\n") + "\n";
|
|
1085
|
+
}
|
|
1086
|
+
function exportEvents(options) {
|
|
1087
|
+
const logger = new WiretapLogger(options.dbPath);
|
|
1088
|
+
let events;
|
|
1089
|
+
if (options.all) {
|
|
1090
|
+
events = logger.getAllEvents();
|
|
1091
|
+
} else if (options.sessionId) {
|
|
1092
|
+
events = logger.getSessionEvents(options.sessionId);
|
|
1093
|
+
} else {
|
|
1094
|
+
const sessions = logger.listSessions();
|
|
1095
|
+
if (sessions.length === 0) {
|
|
1096
|
+
process.stderr.write("No sessions found.\n");
|
|
1097
|
+
logger.close();
|
|
1098
|
+
process.exit(1);
|
|
1099
|
+
}
|
|
1100
|
+
events = logger.getSessionEvents(sessions[0].sessionId);
|
|
1101
|
+
}
|
|
1102
|
+
let output;
|
|
1103
|
+
if (options.format === "csv") {
|
|
1104
|
+
output = eventsToCSV(events);
|
|
1105
|
+
} else {
|
|
1106
|
+
output = JSON.stringify(events, null, 2) + "\n";
|
|
1107
|
+
}
|
|
1108
|
+
if (options.output) {
|
|
1109
|
+
import_node_fs2.default.writeFileSync(options.output, output, "utf-8");
|
|
1110
|
+
process.stderr.write(`Exported ${events.length} events to ${options.output}
|
|
1111
|
+
`);
|
|
1112
|
+
} else {
|
|
1113
|
+
process.stdout.write(output);
|
|
1114
|
+
}
|
|
1115
|
+
logger.close();
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// src/sessions.ts
|
|
1119
|
+
function listSessions(dbPath) {
|
|
1120
|
+
const logger = new WiretapLogger(dbPath);
|
|
1121
|
+
const sessions = logger.listSessions();
|
|
1122
|
+
if (sessions.length === 0) {
|
|
1123
|
+
process.stderr.write("No sessions recorded yet.\n");
|
|
1124
|
+
logger.close();
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
process.stderr.write(`
|
|
1128
|
+
${source_default.bold("Recorded Sessions")}
|
|
1129
|
+
`);
|
|
1130
|
+
process.stderr.write(` ${source_default.gray("\u2500".repeat(70))}
|
|
1131
|
+
|
|
1132
|
+
`);
|
|
1133
|
+
for (const session of sessions) {
|
|
1134
|
+
const id = session.sessionId.slice(0, 8);
|
|
1135
|
+
const date = new Date(session.startedAt).toLocaleString();
|
|
1136
|
+
const count = session.eventCount ?? 0;
|
|
1137
|
+
process.stderr.write(
|
|
1138
|
+
` ${source_default.yellow(id)} ${source_default.gray(date)} ${count} events ${source_default.gray(session.serverCommand)}
|
|
1139
|
+
`
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
process.stderr.write("\n");
|
|
1143
|
+
logger.close();
|
|
1144
|
+
}
|
|
1145
|
+
function replaySession(sessionId, dbPath, verbose = false) {
|
|
1146
|
+
const logger = new WiretapLogger(dbPath);
|
|
1147
|
+
const sessions = logger.listSessions();
|
|
1148
|
+
const match = sessions.find(
|
|
1149
|
+
(s) => s.sessionId === sessionId || s.sessionId.startsWith(sessionId)
|
|
1150
|
+
);
|
|
1151
|
+
if (!match) {
|
|
1152
|
+
process.stderr.write(`Session "${sessionId}" not found.
|
|
1153
|
+
`);
|
|
1154
|
+
logger.close();
|
|
1155
|
+
process.exit(1);
|
|
1156
|
+
}
|
|
1157
|
+
const events = logger.getSessionEvents(match.sessionId);
|
|
1158
|
+
process.stderr.write(`
|
|
1159
|
+
${source_default.bold("Replaying session")} ${source_default.yellow(match.sessionId.slice(0, 8))}
|
|
1160
|
+
`);
|
|
1161
|
+
process.stderr.write(` ${source_default.gray("server:")} ${match.serverCommand}
|
|
1162
|
+
`);
|
|
1163
|
+
process.stderr.write(` ${source_default.gray("started:")} ${new Date(match.startedAt).toLocaleString()}
|
|
1164
|
+
`);
|
|
1165
|
+
process.stderr.write(` ${source_default.gray("events:")} ${events.length}
|
|
1166
|
+
`);
|
|
1167
|
+
process.stderr.write(` ${source_default.gray("\u2500".repeat(50))}
|
|
1168
|
+
|
|
1169
|
+
`);
|
|
1170
|
+
for (const event of events) {
|
|
1171
|
+
displayEvent(
|
|
1172
|
+
{
|
|
1173
|
+
direction: event.direction,
|
|
1174
|
+
timestamp: event.timestamp,
|
|
1175
|
+
jsonrpcId: event.jsonrpc_id ? isNaN(Number(event.jsonrpc_id)) ? event.jsonrpc_id : Number(event.jsonrpc_id) : null,
|
|
1176
|
+
method: event.method,
|
|
1177
|
+
toolName: event.tool_name,
|
|
1178
|
+
params: event.params || "",
|
|
1179
|
+
payloadBytes: event.payload_bytes,
|
|
1180
|
+
latencyMs: event.latency_ms,
|
|
1181
|
+
sessionId: event.session_id
|
|
1182
|
+
},
|
|
1183
|
+
{ verbose }
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
process.stderr.write(`
|
|
1187
|
+
${source_default.gray("\u2500".repeat(50))}
|
|
1188
|
+
`);
|
|
1189
|
+
process.stderr.write(` ${source_default.gray("replay complete \u2014")} ${events.length} ${source_default.gray("events")}
|
|
1190
|
+
|
|
1191
|
+
`);
|
|
1192
|
+
logger.close();
|
|
1193
|
+
}
|
|
1194
|
+
function searchEvents(query, dbPath, verbose = false) {
|
|
1195
|
+
const logger = new WiretapLogger(dbPath);
|
|
1196
|
+
const events = logger.searchEvents(query);
|
|
1197
|
+
if (events.length === 0) {
|
|
1198
|
+
process.stderr.write(`No events matching "${query}".
|
|
1199
|
+
`);
|
|
1200
|
+
logger.close();
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
process.stderr.write(`
|
|
1204
|
+
${source_default.bold("Search results for")} "${query}" ${source_default.gray(`(${events.length} matches)`)}
|
|
1205
|
+
`);
|
|
1206
|
+
process.stderr.write(` ${source_default.gray("\u2500".repeat(50))}
|
|
1207
|
+
|
|
1208
|
+
`);
|
|
1209
|
+
for (const event of events) {
|
|
1210
|
+
displayEvent(
|
|
1211
|
+
{
|
|
1212
|
+
direction: event.direction,
|
|
1213
|
+
timestamp: event.timestamp,
|
|
1214
|
+
jsonrpcId: event.jsonrpc_id ? isNaN(Number(event.jsonrpc_id)) ? event.jsonrpc_id : Number(event.jsonrpc_id) : null,
|
|
1215
|
+
method: event.method,
|
|
1216
|
+
toolName: event.tool_name,
|
|
1217
|
+
params: event.params || "",
|
|
1218
|
+
payloadBytes: event.payload_bytes,
|
|
1219
|
+
latencyMs: event.latency_ms,
|
|
1220
|
+
sessionId: event.session_id
|
|
1221
|
+
},
|
|
1222
|
+
{ verbose }
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
process.stderr.write("\n");
|
|
1226
|
+
logger.close();
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
// src/index.ts
|
|
1230
|
+
var VERSION = "0.1.0";
|
|
1231
|
+
function printUsage() {
|
|
1232
|
+
process.stderr.write(`
|
|
1233
|
+
mcp-wiretap v${VERSION}
|
|
1234
|
+
See everything your AI agents do through MCP.
|
|
1235
|
+
|
|
1236
|
+
USAGE
|
|
1237
|
+
|
|
1238
|
+
Wrap an MCP server:
|
|
1239
|
+
npx mcp-wiretap [options] -- <server-command> [server-args...]
|
|
1240
|
+
|
|
1241
|
+
Export logs:
|
|
1242
|
+
npx mcp-wiretap export [--format json|csv] [--output file] [--all]
|
|
1243
|
+
|
|
1244
|
+
Browse sessions:
|
|
1245
|
+
npx mcp-wiretap sessions
|
|
1246
|
+
npx mcp-wiretap replay <session-id>
|
|
1247
|
+
npx mcp-wiretap search <query>
|
|
1248
|
+
|
|
1249
|
+
OPTIONS
|
|
1250
|
+
|
|
1251
|
+
--db <path> SQLite database location (default: ~/.mcp-wiretap/wiretap.db)
|
|
1252
|
+
--quiet Suppress terminal output, log only
|
|
1253
|
+
--verbose Show full payloads in terminal
|
|
1254
|
+
--filter <method> Only log specific methods (e.g., tools/call)
|
|
1255
|
+
--max-payload <bytes> Truncate logged payloads in display
|
|
1256
|
+
--no-color Disable color output
|
|
1257
|
+
--version Show version
|
|
1258
|
+
--help Show this help
|
|
1259
|
+
|
|
1260
|
+
EXAMPLES
|
|
1261
|
+
|
|
1262
|
+
# Wrap a filesystem MCP server
|
|
1263
|
+
npx mcp-wiretap -- npx -y @modelcontextprotocol/server-filesystem ~/Code
|
|
1264
|
+
|
|
1265
|
+
# Verbose output with method filter
|
|
1266
|
+
npx mcp-wiretap --verbose --filter tools/call -- npx -y @modelcontextprotocol/server-filesystem ~/Code
|
|
1267
|
+
|
|
1268
|
+
# Export last session to JSON
|
|
1269
|
+
npx mcp-wiretap export --format json --output audit.json
|
|
1270
|
+
|
|
1271
|
+
# Search across all sessions
|
|
1272
|
+
npx mcp-wiretap search "read_file"
|
|
1273
|
+
|
|
1274
|
+
`);
|
|
1275
|
+
}
|
|
1276
|
+
function parseArgs(argv) {
|
|
1277
|
+
const flags = {
|
|
1278
|
+
quiet: false,
|
|
1279
|
+
verbose: false,
|
|
1280
|
+
noColor: false,
|
|
1281
|
+
help: false,
|
|
1282
|
+
version: false,
|
|
1283
|
+
all: false
|
|
1284
|
+
};
|
|
1285
|
+
const positionalArgs = [];
|
|
1286
|
+
let serverCommand = null;
|
|
1287
|
+
let serverArgs = [];
|
|
1288
|
+
let subcommand = null;
|
|
1289
|
+
const dashDashIdx = argv.indexOf("--");
|
|
1290
|
+
let cliArgs;
|
|
1291
|
+
if (dashDashIdx !== -1) {
|
|
1292
|
+
cliArgs = argv.slice(0, dashDashIdx);
|
|
1293
|
+
const serverParts = argv.slice(dashDashIdx + 1);
|
|
1294
|
+
if (serverParts.length > 0) {
|
|
1295
|
+
serverCommand = serverParts[0];
|
|
1296
|
+
serverArgs = serverParts.slice(1);
|
|
1297
|
+
}
|
|
1298
|
+
} else {
|
|
1299
|
+
cliArgs = argv;
|
|
1300
|
+
}
|
|
1301
|
+
let i = 0;
|
|
1302
|
+
while (i < cliArgs.length) {
|
|
1303
|
+
const arg = cliArgs[i];
|
|
1304
|
+
switch (arg) {
|
|
1305
|
+
case "--db":
|
|
1306
|
+
flags.db = cliArgs[++i];
|
|
1307
|
+
break;
|
|
1308
|
+
case "--quiet":
|
|
1309
|
+
case "-q":
|
|
1310
|
+
flags.quiet = true;
|
|
1311
|
+
break;
|
|
1312
|
+
case "--verbose":
|
|
1313
|
+
case "-v":
|
|
1314
|
+
flags.verbose = true;
|
|
1315
|
+
break;
|
|
1316
|
+
case "--filter":
|
|
1317
|
+
flags.filter = cliArgs[++i];
|
|
1318
|
+
break;
|
|
1319
|
+
case "--max-payload":
|
|
1320
|
+
flags.maxPayload = parseInt(cliArgs[++i], 10);
|
|
1321
|
+
break;
|
|
1322
|
+
case "--no-color":
|
|
1323
|
+
flags.noColor = true;
|
|
1324
|
+
break;
|
|
1325
|
+
case "--help":
|
|
1326
|
+
case "-h":
|
|
1327
|
+
flags.help = true;
|
|
1328
|
+
break;
|
|
1329
|
+
case "--version":
|
|
1330
|
+
flags.version = true;
|
|
1331
|
+
break;
|
|
1332
|
+
case "--format":
|
|
1333
|
+
case "-f":
|
|
1334
|
+
flags.format = cliArgs[++i];
|
|
1335
|
+
break;
|
|
1336
|
+
case "--output":
|
|
1337
|
+
case "-o":
|
|
1338
|
+
flags.output = cliArgs[++i];
|
|
1339
|
+
break;
|
|
1340
|
+
case "--all":
|
|
1341
|
+
flags.all = true;
|
|
1342
|
+
break;
|
|
1343
|
+
default:
|
|
1344
|
+
if (!arg.startsWith("-")) {
|
|
1345
|
+
positionalArgs.push(arg);
|
|
1346
|
+
}
|
|
1347
|
+
break;
|
|
1348
|
+
}
|
|
1349
|
+
i++;
|
|
1350
|
+
}
|
|
1351
|
+
if (positionalArgs.length > 0) {
|
|
1352
|
+
const first = positionalArgs[0];
|
|
1353
|
+
if (["export", "sessions", "replay", "search"].includes(first)) {
|
|
1354
|
+
subcommand = first;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
return { subcommand, serverCommand, serverArgs, flags, positionalArgs };
|
|
1358
|
+
}
|
|
1359
|
+
function main() {
|
|
1360
|
+
const argv = process.argv.slice(2);
|
|
1361
|
+
const parsed = parseArgs(argv);
|
|
1362
|
+
if (parsed.flags.version) {
|
|
1363
|
+
process.stderr.write(`mcp-wiretap v${VERSION}
|
|
1364
|
+
`);
|
|
1365
|
+
process.exit(0);
|
|
1366
|
+
}
|
|
1367
|
+
if (parsed.flags.help) {
|
|
1368
|
+
printUsage();
|
|
1369
|
+
process.exit(0);
|
|
1370
|
+
}
|
|
1371
|
+
switch (parsed.subcommand) {
|
|
1372
|
+
case "export": {
|
|
1373
|
+
exportEvents({
|
|
1374
|
+
format: parsed.flags.format || "json",
|
|
1375
|
+
output: parsed.flags.output,
|
|
1376
|
+
all: parsed.flags.all,
|
|
1377
|
+
dbPath: parsed.flags.db
|
|
1378
|
+
});
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
case "sessions": {
|
|
1382
|
+
listSessions(parsed.flags.db);
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
case "replay": {
|
|
1386
|
+
const sessionId = parsed.positionalArgs[1];
|
|
1387
|
+
if (!sessionId) {
|
|
1388
|
+
process.stderr.write("Usage: mcp-wiretap replay <session-id>\n");
|
|
1389
|
+
process.exit(1);
|
|
1390
|
+
}
|
|
1391
|
+
replaySession(sessionId, parsed.flags.db, parsed.flags.verbose);
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
case "search": {
|
|
1395
|
+
const query = parsed.positionalArgs[1];
|
|
1396
|
+
if (!query) {
|
|
1397
|
+
process.stderr.write("Usage: mcp-wiretap search <query>\n");
|
|
1398
|
+
process.exit(1);
|
|
1399
|
+
}
|
|
1400
|
+
searchEvents(query, parsed.flags.db, parsed.flags.verbose);
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
if (!parsed.serverCommand) {
|
|
1405
|
+
process.stderr.write("Error: No server command provided.\n\n");
|
|
1406
|
+
process.stderr.write("Usage: npx mcp-wiretap [options] -- <server-command> [server-args...]\n\n");
|
|
1407
|
+
process.stderr.write("Example: npx mcp-wiretap -- npx -y @modelcontextprotocol/server-filesystem ~/Code\n\n");
|
|
1408
|
+
process.stderr.write("Run 'npx mcp-wiretap --help' for full usage.\n");
|
|
1409
|
+
process.exit(1);
|
|
1410
|
+
}
|
|
1411
|
+
const proxy = new WiretapProxy({
|
|
1412
|
+
command: parsed.serverCommand,
|
|
1413
|
+
args: parsed.serverArgs,
|
|
1414
|
+
dbPath: parsed.flags.db,
|
|
1415
|
+
displayOptions: {
|
|
1416
|
+
verbose: parsed.flags.verbose,
|
|
1417
|
+
quiet: parsed.flags.quiet,
|
|
1418
|
+
noColor: parsed.flags.noColor,
|
|
1419
|
+
maxPayload: parsed.flags.maxPayload || null,
|
|
1420
|
+
filter: parsed.flags.filter || null
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1423
|
+
proxy.start(parsed.serverCommand, parsed.serverArgs);
|
|
1424
|
+
}
|
|
1425
|
+
main();
|
|
1426
|
+
//# sourceMappingURL=index.js.map
|