brave-mcp 0.1.2 → 0.2.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/README.md +10 -0
- package/build/src/McpContext.js +4 -4
- package/build/src/McpPage.js +3 -0
- package/build/src/McpResponse.js +43 -4
- package/build/src/PageCollector.js +4 -5
- package/build/src/bin/brave-devtools-mcp-cli-options.js +9 -0
- package/build/src/bin/brave-devtools.js +1 -0
- package/build/src/formatters/NetworkFormatter.js +23 -6
- package/build/src/index.js +7 -0
- package/build/src/telemetry/ClearcutLogger.js +15 -8
- package/build/src/telemetry/watchdog/ClearcutSender.js +4 -3
- package/build/src/third_party/devtools-formatter-worker.js +6 -15432
- package/build/src/third_party/index.js +23 -182509
- package/build/src/tools/lighthouse.js +2 -2
- package/build/src/tools/memory.js +2 -1
- package/build/src/tools/pages.js +4 -1
- package/build/src/tools/performance.js +1 -1
- package/build/src/tools/screencast.js +2 -1
- package/build/src/tools/screenshot.js +2 -2
- package/build/src/tools/tools.js +2 -0
- package/build/src/tools/webmcp.js +63 -0
- package/build/src/utils/files.js +4 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
control and inspect a live Brave browser. It acts as a Model-Context-Protocol
|
|
9
9
|
(MCP) server, giving your AI coding assistant access to the full power of
|
|
10
10
|
DevTools for reliable automation, in-depth debugging, and performance analysis.
|
|
11
|
+
A [CLI](docs/cli.md) is also provided for use without MCP.
|
|
11
12
|
|
|
12
13
|
## [Tool reference](./docs/tool-reference.md) | [Changelog](./CHANGELOG.md) | [Contributing](./CONTRIBUTING.md) | [Troubleshooting](./docs/troubleshooting.md) | [Design Principles](./docs/design-principles.md)
|
|
13
14
|
|
|
@@ -692,6 +693,10 @@ The Brave DevTools MCP server supports the following configuration options:
|
|
|
692
693
|
Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.
|
|
693
694
|
- **Type:** boolean
|
|
694
695
|
|
|
696
|
+
- **`--experimentalWebmcp`/ `--experimental-webmcp`**
|
|
697
|
+
Set to true to enable debugging WebMCP tools. Requires Chrome 149+ with the following flags: `--enable-features=WebMCPTesting,DevToolsWebMCPSupport`
|
|
698
|
+
- **Type:** boolean
|
|
699
|
+
|
|
695
700
|
- **`--chromeArg`/ `--chrome-arg`**
|
|
696
701
|
Additional arguments for Chrome. Only applies when Chrome is launched by chrome-devtools-mcp.
|
|
697
702
|
- **Type:** array
|
|
@@ -729,6 +734,11 @@ The Brave DevTools MCP server supports the following configuration options:
|
|
|
729
734
|
Exposes a "slim" set of 3 tools covering navigation, script execution and screenshots only. Useful for basic browser tasks.
|
|
730
735
|
- **Type:** boolean
|
|
731
736
|
|
|
737
|
+
- **`--redactNetworkHeaders`/ `--redact-network-headers`**
|
|
738
|
+
If true, redacts some of the network headers considered senstive before returning to the client.
|
|
739
|
+
- **Type:** boolean
|
|
740
|
+
- **Default:** `false`
|
|
741
|
+
|
|
732
742
|
<!-- END AUTO GENERATED OPTIONS -->
|
|
733
743
|
|
|
734
744
|
Pass them via the `args` property in the JSON configuration. For example:
|
package/build/src/McpContext.js
CHANGED
|
@@ -13,7 +13,7 @@ import { PredefinedNetworkConditions } from './third_party/index.js';
|
|
|
13
13
|
import { listPages } from './tools/pages.js';
|
|
14
14
|
import { CLOSE_PAGE_ERROR } from './tools/ToolDefinition.js';
|
|
15
15
|
import { ExtensionRegistry, } from './utils/ExtensionRegistry.js';
|
|
16
|
-
import { saveTemporaryFile } from './utils/files.js';
|
|
16
|
+
import { ensureExtension, saveTemporaryFile } from './utils/files.js';
|
|
17
17
|
import { getNetworkMultiplierFromString } from './WaitForHelper.js';
|
|
18
18
|
const DEFAULT_TIMEOUT = 5_000;
|
|
19
19
|
const NAVIGATION_TIMEOUT = 10_000;
|
|
@@ -56,7 +56,7 @@ export class McpContext {
|
|
|
56
56
|
uncaughtError: event => {
|
|
57
57
|
collect(event);
|
|
58
58
|
},
|
|
59
|
-
|
|
59
|
+
devtoolsAggregatedIssue: event => {
|
|
60
60
|
collect(event);
|
|
61
61
|
},
|
|
62
62
|
};
|
|
@@ -590,9 +590,9 @@ export class McpContext {
|
|
|
590
590
|
async saveTemporaryFile(data, filename) {
|
|
591
591
|
return await saveTemporaryFile(data, filename);
|
|
592
592
|
}
|
|
593
|
-
async saveFile(data,
|
|
593
|
+
async saveFile(data, clientProvidedFilePath, extension) {
|
|
594
594
|
try {
|
|
595
|
-
const filePath = path.resolve(
|
|
595
|
+
const filePath = ensureExtension(path.resolve(clientProvidedFilePath), extension);
|
|
596
596
|
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
597
597
|
await fs.writeFile(filePath, data);
|
|
598
598
|
return { filename: filePath };
|
package/build/src/McpPage.js
CHANGED
package/build/src/McpResponse.js
CHANGED
|
@@ -122,16 +122,21 @@ export class McpResponse {
|
|
|
122
122
|
#consoleDataOptions;
|
|
123
123
|
#listExtensions;
|
|
124
124
|
#listInPageTools;
|
|
125
|
+
#listWebMcpTools;
|
|
125
126
|
#devToolsData;
|
|
126
127
|
#tabId;
|
|
127
128
|
#args;
|
|
128
129
|
#page;
|
|
130
|
+
#redactNetworkHeaders = true;
|
|
129
131
|
constructor(args) {
|
|
130
132
|
this.#args = args;
|
|
131
133
|
}
|
|
132
134
|
setPage(page) {
|
|
133
135
|
this.#page = page;
|
|
134
136
|
}
|
|
137
|
+
setRedactNetworkHeaders(value) {
|
|
138
|
+
this.#redactNetworkHeaders = value;
|
|
139
|
+
}
|
|
135
140
|
attachDevToolsData(data) {
|
|
136
141
|
this.#devToolsData = data;
|
|
137
142
|
}
|
|
@@ -158,6 +163,9 @@ export class McpResponse {
|
|
|
158
163
|
this.#listInPageTools = true;
|
|
159
164
|
}
|
|
160
165
|
}
|
|
166
|
+
setListWebMcpTools() {
|
|
167
|
+
this.#listWebMcpTools = true;
|
|
168
|
+
}
|
|
161
169
|
setIncludeNetworkRequests(value, options) {
|
|
162
170
|
if (!value) {
|
|
163
171
|
this.#networkRequestsOptions = undefined;
|
|
@@ -258,6 +266,9 @@ export class McpResponse {
|
|
|
258
266
|
get snapshotParams() {
|
|
259
267
|
return this.#snapshotParams;
|
|
260
268
|
}
|
|
269
|
+
get listWebMcpTools() {
|
|
270
|
+
return this.#listWebMcpTools;
|
|
271
|
+
}
|
|
261
272
|
async handle(toolName, context) {
|
|
262
273
|
if (this.#includePages) {
|
|
263
274
|
await context.createPagesSnapshot();
|
|
@@ -275,8 +286,8 @@ export class McpResponse {
|
|
|
275
286
|
if (textSnapshot) {
|
|
276
287
|
const formatter = new SnapshotFormatter(textSnapshot);
|
|
277
288
|
if (this.#snapshotParams.filePath) {
|
|
278
|
-
await context.saveFile(new TextEncoder().encode(formatter.toString()), this.#snapshotParams.filePath);
|
|
279
|
-
snapshot =
|
|
289
|
+
const result = await context.saveFile(new TextEncoder().encode(formatter.toString()), this.#snapshotParams.filePath, '.txt');
|
|
290
|
+
snapshot = result.filename;
|
|
280
291
|
}
|
|
281
292
|
else {
|
|
282
293
|
snapshot = formatter;
|
|
@@ -295,7 +306,8 @@ export class McpResponse {
|
|
|
295
306
|
fetchData: true,
|
|
296
307
|
requestFilePath: this.#attachedNetworkRequestOptions?.requestFilePath,
|
|
297
308
|
responseFilePath: this.#attachedNetworkRequestOptions?.responseFilePath,
|
|
298
|
-
saveFile: (data, filename) => context.saveFile(data, filename),
|
|
309
|
+
saveFile: (data, filename, extension) => context.saveFile(data, filename, extension),
|
|
310
|
+
redactNetworkHeaders: this.#redactNetworkHeaders,
|
|
299
311
|
});
|
|
300
312
|
detailedNetworkRequest = formatter;
|
|
301
313
|
}
|
|
@@ -337,6 +349,11 @@ export class McpResponse {
|
|
|
337
349
|
inPageTools = await getToolGroup(page);
|
|
338
350
|
page.inPageTools = inPageTools;
|
|
339
351
|
}
|
|
352
|
+
let webmcpTools;
|
|
353
|
+
if (this.#listWebMcpTools && this.#args.experimentalWebmcp) {
|
|
354
|
+
const page = this.#page ?? context.getSelectedMcpPage();
|
|
355
|
+
webmcpTools = page.getWebMcpTools();
|
|
356
|
+
}
|
|
340
357
|
let consoleMessages;
|
|
341
358
|
if (this.#consoleDataOptions?.include) {
|
|
342
359
|
if (!this.#page) {
|
|
@@ -399,7 +416,8 @@ export class McpResponse {
|
|
|
399
416
|
selectedInDevToolsUI: context.getNetworkRequestStableId(request) ===
|
|
400
417
|
this.#networkRequestsOptions?.networkRequestIdInDevToolsUI,
|
|
401
418
|
fetchData: false,
|
|
402
|
-
saveFile: (data, filename) => context.saveFile(data, filename),
|
|
419
|
+
saveFile: (data, filename, extension) => context.saveFile(data, filename, extension),
|
|
420
|
+
redactNetworkHeaders: this.#redactNetworkHeaders,
|
|
403
421
|
})));
|
|
404
422
|
}
|
|
405
423
|
}
|
|
@@ -414,6 +432,7 @@ export class McpResponse {
|
|
|
414
432
|
extensions,
|
|
415
433
|
lighthouseResult: this.#attachedLighthouseResult,
|
|
416
434
|
inPageTools,
|
|
435
|
+
webmcpTools,
|
|
417
436
|
});
|
|
418
437
|
}
|
|
419
438
|
format(toolName, context, data) {
|
|
@@ -622,6 +641,26 @@ Call ${handleDialog.name} to handle it before continuing.`);
|
|
|
622
641
|
response.push(toolDefinitionsMessage);
|
|
623
642
|
}
|
|
624
643
|
}
|
|
644
|
+
if (this.#listWebMcpTools && data.webmcpTools) {
|
|
645
|
+
structuredContent.webmcpTools = data.webmcpTools.map(({ name, description, inputSchema, annotations }) => ({
|
|
646
|
+
name,
|
|
647
|
+
description,
|
|
648
|
+
inputSchema,
|
|
649
|
+
annotations,
|
|
650
|
+
}));
|
|
651
|
+
response.push('## WebMCP tools');
|
|
652
|
+
if (data.webmcpTools.length === 0) {
|
|
653
|
+
response.push('No WebMCP tools available.');
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
const webmcpToolsMessage = data.webmcpTools
|
|
657
|
+
.map(tool => {
|
|
658
|
+
return `name="${tool.name}", description="${tool.description}", inputSchema=${JSON.stringify(tool.inputSchema)}, annotations=${JSON.stringify(tool.annotations)}`;
|
|
659
|
+
})
|
|
660
|
+
.join('\n');
|
|
661
|
+
response.push(webmcpToolsMessage);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
625
664
|
if (this.#networkRequestsOptions?.include && data.networkRequests) {
|
|
626
665
|
const requests = data.networkRequests;
|
|
627
666
|
response.push('## Network requests');
|
|
@@ -206,7 +206,7 @@ class PageEventSubscriber {
|
|
|
206
206
|
async subscribe() {
|
|
207
207
|
this.#resetIssueAggregator();
|
|
208
208
|
this.#page.on('framenavigated', this.#onFrameNavigated);
|
|
209
|
-
this.#
|
|
209
|
+
this.#page.on('issue', this.#onIssueAdded);
|
|
210
210
|
this.#session.on('Runtime.exceptionThrown', this.#onExceptionThrown);
|
|
211
211
|
try {
|
|
212
212
|
await this.#session.send('Audits.enable');
|
|
@@ -219,7 +219,7 @@ class PageEventSubscriber {
|
|
|
219
219
|
this.#seenKeys.clear();
|
|
220
220
|
this.#seenIssues.clear();
|
|
221
221
|
this.#page.off('framenavigated', this.#onFrameNavigated);
|
|
222
|
-
this.#
|
|
222
|
+
this.#page.off('issue', this.#onIssueAdded);
|
|
223
223
|
this.#session.off('Runtime.exceptionThrown', this.#onExceptionThrown);
|
|
224
224
|
if (this.#issueAggregator) {
|
|
225
225
|
this.#issueAggregator.removeEventListener("AggregatedIssueUpdated" /* DevTools.IssueAggregatorEvents.AGGREGATED_ISSUE_UPDATED */, this.#onAggregatedIssue);
|
|
@@ -233,7 +233,7 @@ class PageEventSubscriber {
|
|
|
233
233
|
return;
|
|
234
234
|
}
|
|
235
235
|
this.#seenIssues.add(event.data);
|
|
236
|
-
this.#page.emit('
|
|
236
|
+
this.#page.emit('devtoolsAggregatedIssue', event.data);
|
|
237
237
|
};
|
|
238
238
|
#onExceptionThrown = (event) => {
|
|
239
239
|
this.#page.emit('uncaughtError', new UncaughtError(event.exceptionDetails, this.#targetId));
|
|
@@ -248,9 +248,8 @@ class PageEventSubscriber {
|
|
|
248
248
|
this.#seenIssues.clear();
|
|
249
249
|
this.#resetIssueAggregator();
|
|
250
250
|
};
|
|
251
|
-
#onIssueAdded = (
|
|
251
|
+
#onIssueAdded = (inspectorIssue) => {
|
|
252
252
|
try {
|
|
253
|
-
const inspectorIssue = data.issue;
|
|
254
253
|
const issue = DevTools.createIssuesFromProtocolIssue(null,
|
|
255
254
|
// @ts-expect-error Protocol types diverge.
|
|
256
255
|
inspectorIssue)[0];
|
|
@@ -168,6 +168,10 @@ export const cliOptions = {
|
|
|
168
168
|
type: 'boolean',
|
|
169
169
|
describe: 'Exposes experimental screencast tools (requires ffmpeg). Install ffmpeg https://www.ffmpeg.org/download.html and ensure it is available in the MCP server PATH.',
|
|
170
170
|
},
|
|
171
|
+
experimentalWebmcp: {
|
|
172
|
+
type: 'boolean',
|
|
173
|
+
describe: 'Set to true to enable debugging WebMCP tools. Requires Brave with the following flags: `--enable-features=WebMCPTesting,DevToolsWebMCPSupport`',
|
|
174
|
+
},
|
|
171
175
|
braveArg: {
|
|
172
176
|
type: 'array',
|
|
173
177
|
describe: 'Additional arguments for Brave. Only applies when Brave is launched by brave-devtools-mcp.',
|
|
@@ -236,6 +240,11 @@ export const cliOptions = {
|
|
|
236
240
|
describe: 'Set by Brave DevTools CLI if the MCP server is started via the CLI client (this arg exists for usage stats)',
|
|
237
241
|
hidden: true,
|
|
238
242
|
},
|
|
243
|
+
redactNetworkHeaders: {
|
|
244
|
+
type: 'boolean',
|
|
245
|
+
describe: 'If true, redacts some of the network headers considered senstive before returning to the client.',
|
|
246
|
+
default: false,
|
|
247
|
+
},
|
|
239
248
|
};
|
|
240
249
|
export function parseArguments(version, argv = process.argv) {
|
|
241
250
|
const yargsInstance = yargs(hideBin(argv))
|
|
@@ -32,6 +32,7 @@ delete startCliOptions.viewport;
|
|
|
32
32
|
// tools, they need to be enabled during CLI generation.
|
|
33
33
|
delete startCliOptions.experimentalPageIdRouting;
|
|
34
34
|
delete startCliOptions.experimentalVision;
|
|
35
|
+
delete startCliOptions.experimentalWebmcp;
|
|
35
36
|
delete startCliOptions.experimentalInteropTools;
|
|
36
37
|
delete startCliOptions.experimentalScreencast;
|
|
37
38
|
delete startCliOptions.categoryEmulation;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
* */
|
|
6
6
|
import { isUtf8 } from 'node:buffer';
|
|
7
|
+
import { DevTools, } from '../third_party/index.js';
|
|
7
8
|
const BODY_CONTEXT_SIZE_LIMIT = 10000;
|
|
8
9
|
export class NetworkFormatter {
|
|
9
10
|
#request;
|
|
@@ -40,8 +41,8 @@ export class NetworkFormatter {
|
|
|
40
41
|
throw new Error('saveFile is not provided');
|
|
41
42
|
}
|
|
42
43
|
if (data) {
|
|
43
|
-
await this.#options.saveFile(Buffer.from(data), this.#options.requestFilePath);
|
|
44
|
-
this.#requestBodyFilePath =
|
|
44
|
+
const result = await this.#options.saveFile(Buffer.from(data), this.#options.requestFilePath, '.network-request');
|
|
45
|
+
this.#requestBodyFilePath = result.filename;
|
|
45
46
|
}
|
|
46
47
|
else {
|
|
47
48
|
this.#requestBody = requestBodyNotAvailableMessage;
|
|
@@ -66,8 +67,8 @@ export class NetworkFormatter {
|
|
|
66
67
|
if (!this.#options.saveFile) {
|
|
67
68
|
throw new Error('saveFile is not provided');
|
|
68
69
|
}
|
|
69
|
-
await this.#options.saveFile(buffer, this.#options.responseFilePath);
|
|
70
|
-
this.#responseBodyFilePath =
|
|
70
|
+
const result = await this.#options.saveFile(buffer, this.#options.responseFilePath, '.network-response');
|
|
71
|
+
this.#responseBodyFilePath = result.filename;
|
|
71
72
|
}
|
|
72
73
|
catch {
|
|
73
74
|
// Flatten error handling for buffer() failure and save failure
|
|
@@ -96,6 +97,16 @@ export class NetworkFormatter {
|
|
|
96
97
|
selectedInDevToolsUI: this.#options.selectedInDevToolsUI,
|
|
97
98
|
};
|
|
98
99
|
}
|
|
100
|
+
#redactNetworkHeaders(headers) {
|
|
101
|
+
const headersList = Object.entries(headers).map(item => {
|
|
102
|
+
return { name: item[0], value: item[1] };
|
|
103
|
+
});
|
|
104
|
+
const redacted = DevTools.NetworkRequestFormatter.sanitizeHeaders(headersList);
|
|
105
|
+
return redacted.reduce((acc, item) => {
|
|
106
|
+
acc[item.name] = item.value;
|
|
107
|
+
return acc;
|
|
108
|
+
}, {});
|
|
109
|
+
}
|
|
99
110
|
toJSONDetailed() {
|
|
100
111
|
const redirectChain = this.#request.redirectChain();
|
|
101
112
|
const formattedRedirectChain = redirectChain.reverse().map(request => {
|
|
@@ -105,15 +116,21 @@ export class NetworkFormatter {
|
|
|
105
116
|
const formatter = new NetworkFormatter(request, {
|
|
106
117
|
requestId: id,
|
|
107
118
|
saveFile: this.#options.saveFile,
|
|
119
|
+
redactNetworkHeaders: this.#options.redactNetworkHeaders,
|
|
108
120
|
});
|
|
109
121
|
return formatter.toJSON();
|
|
110
122
|
});
|
|
123
|
+
const responseHeaders = this.#request.response()?.headers();
|
|
111
124
|
return {
|
|
112
125
|
...this.toJSON(),
|
|
113
|
-
requestHeaders: this.#
|
|
126
|
+
requestHeaders: this.#options.redactNetworkHeaders
|
|
127
|
+
? this.#redactNetworkHeaders(this.#request.headers())
|
|
128
|
+
: this.#request.headers(),
|
|
114
129
|
requestBody: this.#requestBody,
|
|
115
130
|
requestBodyFilePath: this.#requestBodyFilePath,
|
|
116
|
-
responseHeaders: this.#
|
|
131
|
+
responseHeaders: this.#options.redactNetworkHeaders && responseHeaders
|
|
132
|
+
? this.#redactNetworkHeaders(responseHeaders)
|
|
133
|
+
: this.#request.response()?.headers(),
|
|
117
134
|
responseBody: this.#responseBody,
|
|
118
135
|
responseBodyFilePath: this.#responseBodyFilePath,
|
|
119
136
|
failure: this.#request.failure()?.errorText,
|
package/build/src/index.js
CHANGED
|
@@ -119,6 +119,10 @@ export async function createMcpServer(serverArgs, options) {
|
|
|
119
119
|
!serverArgs.experimentalScreencast) {
|
|
120
120
|
return;
|
|
121
121
|
}
|
|
122
|
+
if (tool.annotations.conditions?.includes('experimentalWebmcp') &&
|
|
123
|
+
!serverArgs.experimentalWebmcp) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
122
126
|
const schema = 'pageScoped' in tool &&
|
|
123
127
|
tool.pageScoped &&
|
|
124
128
|
serverArgs.experimentalPageIdRouting &&
|
|
@@ -141,6 +145,7 @@ export async function createMcpServer(serverArgs, options) {
|
|
|
141
145
|
const response = serverArgs.slim
|
|
142
146
|
? new SlimMcpResponse(serverArgs)
|
|
143
147
|
: new McpResponse(serverArgs);
|
|
148
|
+
response.setRedactNetworkHeaders(serverArgs.redactNetworkHeaders);
|
|
144
149
|
if ('pageScoped' in tool && tool.pageScoped) {
|
|
145
150
|
const page = serverArgs.experimentalPageIdRouting &&
|
|
146
151
|
params.pageId &&
|
|
@@ -189,6 +194,8 @@ export async function createMcpServer(serverArgs, options) {
|
|
|
189
194
|
finally {
|
|
190
195
|
void clearcutLogger?.logToolInvocation({
|
|
191
196
|
toolName: tool.name,
|
|
197
|
+
params,
|
|
198
|
+
schema,
|
|
192
199
|
success,
|
|
193
200
|
latencyMs: bucketizeLatency(Date.now() - startTime),
|
|
194
201
|
});
|
|
@@ -38,14 +38,15 @@ export function getZodType(zodType) {
|
|
|
38
38
|
throw new Error(`Unsupported zod type for tool parameter: ${typeName}`);
|
|
39
39
|
}
|
|
40
40
|
export function transformArgName(zodType, name) {
|
|
41
|
+
const snakeCaseName = name.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
|
41
42
|
if (zodType === 'ZodString') {
|
|
42
|
-
return `${
|
|
43
|
+
return `${snakeCaseName}_length`;
|
|
43
44
|
}
|
|
44
45
|
else if (zodType === 'ZodArray') {
|
|
45
|
-
return `${
|
|
46
|
+
return `${snakeCaseName}_count`;
|
|
46
47
|
}
|
|
47
48
|
else {
|
|
48
|
-
return
|
|
49
|
+
return snakeCaseName;
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
export function transformArgType(zodType) {
|
|
@@ -168,15 +169,21 @@ export class ClearcutLogger {
|
|
|
168
169
|
}
|
|
169
170
|
}
|
|
170
171
|
async logToolInvocation(args) {
|
|
172
|
+
const tool_invocation = {
|
|
173
|
+
tool_name: args.toolName,
|
|
174
|
+
success: args.success,
|
|
175
|
+
latency_ms: args.latencyMs,
|
|
176
|
+
};
|
|
177
|
+
if (Object.keys(args.params).length > 0) {
|
|
178
|
+
tool_invocation.tool_params = {
|
|
179
|
+
[`${args.toolName}_params`]: sanitizeParams(args.params, args.schema),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
171
182
|
this.#watchdog.send({
|
|
172
183
|
type: WatchdogMessageType.LOG_EVENT,
|
|
173
184
|
payload: {
|
|
174
185
|
mcp_client: this.#mcpClient,
|
|
175
|
-
tool_invocation:
|
|
176
|
-
tool_name: args.toolName,
|
|
177
|
-
success: args.success,
|
|
178
|
-
latency_ms: args.latencyMs,
|
|
179
|
-
},
|
|
186
|
+
tool_invocation: tool_invocation,
|
|
180
187
|
},
|
|
181
188
|
});
|
|
182
189
|
}
|
|
@@ -42,13 +42,14 @@ export class ClearcutSender {
|
|
|
42
42
|
this.#sessionId = crypto.randomUUID();
|
|
43
43
|
this.#sessionCreated = Date.now();
|
|
44
44
|
}
|
|
45
|
-
|
|
46
|
-
this.#addToBuffer({
|
|
45
|
+
const eventToSend = {
|
|
47
46
|
...event,
|
|
48
47
|
session_id: this.#sessionId,
|
|
49
48
|
app_version: this.#appVersion,
|
|
50
49
|
os_type: this.#osType,
|
|
51
|
-
}
|
|
50
|
+
};
|
|
51
|
+
logger('Enqueing telemetry event', JSON.stringify(eventToSend, null, 2));
|
|
52
|
+
this.#addToBuffer(eventToSend);
|
|
52
53
|
if (!this.#timerStarted) {
|
|
53
54
|
this.#timerStarted = true;
|
|
54
55
|
this.#scheduleFlush(this.#flushIntervalMs);
|