fastbrowser_cli 1.0.30 → 1.0.33
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 +1 -2
- package/dist/fastbrowser_cli/fastbrowser_cli.js +11 -19
- package/dist/fastbrowser_cli/fastbrowser_cli.js.map +1 -1
- package/dist/fastbrowser_cli/libs/query-builder.d.ts +2 -0
- package/dist/fastbrowser_cli/libs/query-builder.d.ts.map +1 -1
- package/dist/fastbrowser_cli/libs/query-builder.js +4 -0
- package/dist/fastbrowser_cli/libs/query-builder.js.map +1 -1
- package/dist/fastbrowser_httpd/libs/tool-schemas.d.ts +2 -0
- package/dist/fastbrowser_httpd/libs/tool-schemas.d.ts.map +1 -1
- package/dist/fastbrowser_mcp/fastbrowser_mcp.d.ts.map +1 -1
- package/dist/fastbrowser_mcp/fastbrowser_mcp.js +147 -22
- package/dist/fastbrowser_mcp/fastbrowser_mcp.js.map +1 -1
- package/dist/fastbrowser_mcp/libs/mcp_my_client.d.ts.map +1 -1
- package/dist/fastbrowser_mcp/libs/mcp_my_client.js +8 -0
- package/dist/fastbrowser_mcp/libs/mcp_my_client.js.map +1 -1
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.d.ts.map +1 -1
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.js +15 -2
- package/dist/fastbrowser_mcp/libs/mcp_target_helper.js.map +1 -1
- package/dist/fastbrowser_mcp/libs/schemas.d.ts +4 -0
- package/dist/fastbrowser_mcp/libs/schemas.d.ts.map +1 -1
- package/dist/fastbrowser_mcp/libs/schemas.js +6 -0
- package/dist/fastbrowser_mcp/libs/schemas.js.map +1 -1
- package/dist/shared/logger.d.ts +86 -0
- package/dist/shared/logger.d.ts.map +1 -0
- package/dist/shared/logger.js +269 -0
- package/dist/shared/logger.js.map +1 -0
- package/docs/brainstorm_scrap_by_ai.md +1 -1
- package/docs/feature_support_cli.md +7 -8
- package/docs/target_tools/target_tools_chrome_devtools.md +963 -0
- package/docs/target_tools/target_tools_playwright.md +763 -0
- package/examples/linkedin_cli/linked_dm.sh +16 -0
- package/examples/linkedin_cli/linked_post.sh +13 -0
- package/examples/linkedin_cli/linkedin.snapshot.txt +1245 -0
- package/examples/todomvc/todomvc.a11y.txt +44 -0
- package/examples/todomvc/todomvc.sh +11 -0
- package/examples/wttj_cli/fastbrowser_helper.ts +39 -0
- package/examples/wttj_cli/wttf_job-original.a11y.txt +652 -0
- package/examples/wttj_cli/wttf_job.a11y.txt +317 -0
- package/examples/{welcometothejungle/wttj-job.ts → wttj_cli/wttj_job copy.ts } +60 -11
- package/examples/wttj_cli/wttj_job.ts +179 -0
- package/examples/wttj_cli/wttj_search.ts +162 -0
- package/package.json +10 -4
- package/skills/fastbrowser/SKILL.md +10 -11
- package/skills/fastbrowser-script/SKILL.md +4 -4
- package/src/fastbrowser_cli/fastbrowser_cli.ts +14 -25
- package/src/fastbrowser_cli/libs/query-builder.ts +6 -0
- package/src/fastbrowser_mcp/fastbrowser_mcp.ts +181 -26
- package/src/fastbrowser_mcp/libs/mcp_my_client.ts +17 -0
- package/src/fastbrowser_mcp/libs/mcp_target_helper.ts +15 -2
- package/src/fastbrowser_mcp/libs/schemas.ts +6 -0
- package/src/shared/logger.ts +317 -0
- package/test.a11y.txt +828 -0
- package/tests/query-builder.test.ts +51 -11
- package/examples/welcometothejungle/fastbrowser_helper.ts +0 -39
- package/examples/welcometothejungle/wttj-search.ts +0 -82
- /package/examples/{post-to-x.sh → twitter_cli/twitter_post.sh} +0 -0
|
@@ -11,6 +11,7 @@ import { z } from "zod";
|
|
|
11
11
|
import * as A11yParse from "a11y_parse";
|
|
12
12
|
|
|
13
13
|
// local imports
|
|
14
|
+
import { Logger } from "../shared/logger.js"
|
|
14
15
|
import { McpMyClient } from "./libs/mcp_my_client.js";
|
|
15
16
|
import { McpProxy } from "./libs/mcp_proxy.js";
|
|
16
17
|
import { ResponseFormatter } from "./libs/response_formatter.js";
|
|
@@ -40,6 +41,10 @@ export {
|
|
|
40
41
|
type QuerySelectorsFirstInput,
|
|
41
42
|
};
|
|
42
43
|
|
|
44
|
+
const logger = Logger.fromMetaUrl(import.meta.url, {
|
|
45
|
+
allToStderr: true,
|
|
46
|
+
});
|
|
47
|
+
|
|
43
48
|
///////////////////////////////////////////////////////////////////////////////
|
|
44
49
|
///////////////////////////////////////////////////////////////////////////////
|
|
45
50
|
//
|
|
@@ -106,18 +111,32 @@ class MainHelper {
|
|
|
106
111
|
selector: string,
|
|
107
112
|
selectedNodes: A11yParse.AxNode[],
|
|
108
113
|
withAncestors: boolean,
|
|
114
|
+
withChildren: boolean,
|
|
109
115
|
): string {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
const nodeCount = selectedNodes.length;
|
|
117
|
+
const pluralS = nodeCount > 1 ? 's' : '';
|
|
118
|
+
const ancestorsText = withAncestors ? ', with ancestors' : '';
|
|
119
|
+
const childrenText = withChildren ? ', with children' : '';
|
|
120
|
+
let text: string = `## Node${pluralS} found for selector '${selector}' (${nodeCount} node${pluralS}${ancestorsText}${childrenText}):`;
|
|
121
|
+
if (selectedNodes.length === 0) {
|
|
122
|
+
if (text.length > 0) text += '\n';
|
|
123
|
+
text += "No node found";
|
|
124
|
+
} else if (withAncestors === true) {
|
|
125
|
+
const subsetTree = A11yParse.A11yTree.buildSubsetTree(selectedNodes, {
|
|
126
|
+
withAncestors: true,
|
|
127
|
+
withChildren,
|
|
128
|
+
});
|
|
129
|
+
if (text.length > 0) text += '\n';
|
|
130
|
+
text += A11yParse.A11yDisplay.stringifyTree(subsetTree);
|
|
131
|
+
} else if (withChildren === true) {
|
|
132
|
+
for (const selectedNode of selectedNodes) {
|
|
133
|
+
if (text.length > 0) text += '\n';
|
|
134
|
+
text += A11yParse.A11yDisplay.stringifyTree(selectedNode);
|
|
117
135
|
}
|
|
118
136
|
} else {
|
|
119
137
|
for (const selectedNode of selectedNodes) {
|
|
120
|
-
text
|
|
138
|
+
if (text.length > 0) text += '\n';
|
|
139
|
+
text += A11yParse.A11yDisplay.stringifyNode(selectedNode);
|
|
121
140
|
}
|
|
122
141
|
}
|
|
123
142
|
text += '\n';
|
|
@@ -144,7 +163,7 @@ class MainHelper {
|
|
|
144
163
|
selectedNodes.splice(querySelector.limit);
|
|
145
164
|
}
|
|
146
165
|
|
|
147
|
-
responseTexts.push(this._formatSelectedNodes(querySelector.selector, selectedNodes, querySelector.withAncestors));
|
|
166
|
+
responseTexts.push(this._formatSelectedNodes(querySelector.selector, selectedNodes, querySelector.withAncestors, querySelector.withChildren));
|
|
148
167
|
}
|
|
149
168
|
|
|
150
169
|
// join the response texts for all selectors and return
|
|
@@ -167,7 +186,7 @@ class MainHelper {
|
|
|
167
186
|
for (const querySelector of querySelectors.selectors) {
|
|
168
187
|
const selectedNodes = A11yParse.A11yQuery.querySelectorAll(a11yTree, querySelector.selector);
|
|
169
188
|
const firstNode = selectedNodes.length > 0 ? [selectedNodes[0]] : [];
|
|
170
|
-
responseTexts.push(this._formatSelectedNodes(querySelector.selector, firstNode, querySelector.withAncestors));
|
|
189
|
+
responseTexts.push(this._formatSelectedNodes(querySelector.selector, firstNode, querySelector.withAncestors, querySelector.withChildren));
|
|
171
190
|
}
|
|
172
191
|
|
|
173
192
|
return responseTexts.join('\n');
|
|
@@ -195,10 +214,16 @@ class MainHelper {
|
|
|
195
214
|
inputSchema: z.object({}),
|
|
196
215
|
},
|
|
197
216
|
async () => {
|
|
217
|
+
// log the events
|
|
218
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.listPages}: listing pages`);
|
|
219
|
+
|
|
198
220
|
const toolConfig = await McpTargetHelper.targetToolListPages(mcpTarget);
|
|
199
221
|
const callToolResult = await mcpClient.callTool(toolConfig.toolName, toolConfig.toolArgs);
|
|
200
222
|
let outputStr = await ResponseFormatter.formatListPages(mcpTarget, callToolResult);
|
|
201
223
|
|
|
224
|
+
// log the events
|
|
225
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.listPages}: output:`)
|
|
226
|
+
logger.warn(`${outputStr}`);
|
|
202
227
|
return {
|
|
203
228
|
content: [{ type: "text", text: outputStr }],
|
|
204
229
|
};
|
|
@@ -228,11 +253,18 @@ class MainHelper {
|
|
|
228
253
|
// const callToolResult = await mcpClient.callTool(toolConfig.toolName, toolConfig.toolArgs);
|
|
229
254
|
// let outputStr = await ResponseFormatter.formatNewPage(mcpTarget, callToolResult, url);
|
|
230
255
|
|
|
256
|
+
// log the events
|
|
257
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.newPage}: url=${url}`);
|
|
258
|
+
|
|
259
|
+
|
|
231
260
|
// so working around this by calling the navigate_page tool instead of new_page when the target is playwright
|
|
232
261
|
const toolConfig = await McpTargetHelper.targetToolNavigatePage(mcpTarget, url);
|
|
233
262
|
const callToolResult = await mcpClient.callTool(toolConfig.toolName, toolConfig.toolArgs);
|
|
234
263
|
let outputStr = await ResponseFormatter.formatNavigatePage(mcpTarget, callToolResult);
|
|
235
264
|
|
|
265
|
+
// log the events
|
|
266
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.newPage}: output:`)
|
|
267
|
+
logger.warn(`${outputStr}`);
|
|
236
268
|
|
|
237
269
|
return {
|
|
238
270
|
content: [{ type: "text", text: outputStr }],
|
|
@@ -255,10 +287,17 @@ class MainHelper {
|
|
|
255
287
|
}),
|
|
256
288
|
},
|
|
257
289
|
async ({ pageId }: { pageId: number }) => {
|
|
290
|
+
// log the events
|
|
291
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.closePage}: pageId=${pageId}`);
|
|
292
|
+
|
|
258
293
|
const toolConfig = await McpTargetHelper.targetToolClosePage(mcpTarget, pageId);
|
|
259
294
|
const callToolResult = await mcpClient.callTool(toolConfig.toolName, toolConfig.toolArgs);
|
|
260
295
|
let outputStr = await ResponseFormatter.formatClosePage(mcpTarget, callToolResult, pageId);
|
|
261
296
|
|
|
297
|
+
// log the events
|
|
298
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.closePage}: output:`)
|
|
299
|
+
logger.warn(`${outputStr}`);
|
|
300
|
+
|
|
262
301
|
return {
|
|
263
302
|
content: [{ type: "text", text: outputStr }],
|
|
264
303
|
};
|
|
@@ -280,10 +319,17 @@ class MainHelper {
|
|
|
280
319
|
}),
|
|
281
320
|
},
|
|
282
321
|
async ({ url }: { url: string }) => {
|
|
322
|
+
// log the events
|
|
323
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.navigatePage}: url=${url}`);
|
|
324
|
+
|
|
283
325
|
const toolConfig = await McpTargetHelper.targetToolNavigatePage(mcpTarget, url);
|
|
284
326
|
const callToolResult = await mcpClient.callTool(toolConfig.toolName, toolConfig.toolArgs);
|
|
285
327
|
let outputStr = await ResponseFormatter.formatNavigatePage(mcpTarget, callToolResult);
|
|
286
328
|
|
|
329
|
+
// log the events
|
|
330
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.navigatePage}: output:`)
|
|
331
|
+
logger.warn(`${outputStr}`);
|
|
332
|
+
|
|
287
333
|
return {
|
|
288
334
|
content: [{ type: "text", text: outputStr }],
|
|
289
335
|
};
|
|
@@ -303,9 +349,16 @@ class MainHelper {
|
|
|
303
349
|
inputSchema: z.object({}),
|
|
304
350
|
},
|
|
305
351
|
async () => {
|
|
352
|
+
// log the events
|
|
353
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.takeSnapshot}: taking snapshot`);
|
|
354
|
+
|
|
306
355
|
const a11yText: string = await MainHelper._getA11yText(mcpClient);
|
|
307
356
|
let outputStr = a11yText
|
|
308
357
|
|
|
358
|
+
// log the events
|
|
359
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.takeSnapshot}: output:`);
|
|
360
|
+
logger.warn(`${outputStr}`);
|
|
361
|
+
|
|
309
362
|
return {
|
|
310
363
|
content: [{ type: "text", text: outputStr }],
|
|
311
364
|
};
|
|
@@ -342,9 +395,16 @@ class MainHelper {
|
|
|
342
395
|
inputSchema: QuerySelectorsInputSchema,
|
|
343
396
|
},
|
|
344
397
|
async (querySelectorsInput: QuerySelectorsInput) => {
|
|
398
|
+
// log the events
|
|
399
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.querySelectorsAll}: querying selectors: ${JSON.stringify(querySelectorsInput)}`);
|
|
400
|
+
|
|
345
401
|
// query the accessibility tree with the provided selector
|
|
346
402
|
const outputText: string = await MainHelper.querySelectorsAll(mcpClient, querySelectorsInput);
|
|
347
403
|
|
|
404
|
+
// log the events
|
|
405
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.querySelectorsAll}: output:`);
|
|
406
|
+
logger.warn(`${outputText}`);
|
|
407
|
+
|
|
348
408
|
return {
|
|
349
409
|
content: [{ type: "text", text: outputText }],
|
|
350
410
|
};
|
|
@@ -367,7 +427,15 @@ class MainHelper {
|
|
|
367
427
|
inputSchema: QuerySelectorsFirstInputSchema,
|
|
368
428
|
},
|
|
369
429
|
async (querySelectorsInput: QuerySelectorsFirstInput) => {
|
|
430
|
+
// log the events
|
|
431
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.querySelectors}: querying selectors: ${JSON.stringify(querySelectorsInput)}`);
|
|
432
|
+
|
|
370
433
|
const outputText: string = await MainHelper.querySelectors(mcpClient, querySelectorsInput);
|
|
434
|
+
|
|
435
|
+
// log the events
|
|
436
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.querySelectors}: output:`);
|
|
437
|
+
logger.warn(`${outputText}`);
|
|
438
|
+
|
|
371
439
|
return {
|
|
372
440
|
content: [{ type: "text", text: outputText }],
|
|
373
441
|
};
|
|
@@ -389,6 +457,9 @@ class MainHelper {
|
|
|
389
457
|
}),
|
|
390
458
|
},
|
|
391
459
|
async ({ keys }: { keys: string }) => {
|
|
460
|
+
// log the events
|
|
461
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.pressKeys}: pressing keys: ${keys}`);
|
|
462
|
+
|
|
392
463
|
// Build the list of keys to send, splitting regular characters into individual key presses, but keeping special keys as-is
|
|
393
464
|
const keysToSend: string[] = [];
|
|
394
465
|
const keysSplit = keys.split(',').map((key) => key.trim());
|
|
@@ -419,6 +490,10 @@ class MainHelper {
|
|
|
419
490
|
|
|
420
491
|
let outputText = await ResponseFormatter.formatPressKeys(mcpTarget, keysToSend);
|
|
421
492
|
|
|
493
|
+
// log the events
|
|
494
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.pressKeys}: output:`);
|
|
495
|
+
logger.warn(`${outputText}`);
|
|
496
|
+
|
|
422
497
|
// return a response indicating which keys were pressed
|
|
423
498
|
return {
|
|
424
499
|
content: [{ type: "text", text: outputText }],
|
|
@@ -444,12 +519,17 @@ class MainHelper {
|
|
|
444
519
|
},
|
|
445
520
|
},
|
|
446
521
|
async ({ selector }: { selector: string }) => {
|
|
447
|
-
|
|
522
|
+
// log the events
|
|
523
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.click}: clicking selector: ${selector}`);
|
|
448
524
|
|
|
525
|
+
const uid = await MainHelper._resolveSelectorToUid(mcpClient, selector);
|
|
449
526
|
const toolConfig = await McpTargetHelper.targetToolClick(mcpTarget, uid);
|
|
450
527
|
const callToolResult = await mcpClient.callTool(toolConfig.toolName, toolConfig.toolArgs);
|
|
451
528
|
let outputText = await ResponseFormatter.formatClick(mcpTarget, callToolResult);
|
|
452
529
|
|
|
530
|
+
// log the events
|
|
531
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.click}: output:`);
|
|
532
|
+
|
|
453
533
|
return {
|
|
454
534
|
content: [{ type: "text", text: outputText }],
|
|
455
535
|
};
|
|
@@ -477,6 +557,9 @@ class MainHelper {
|
|
|
477
557
|
},
|
|
478
558
|
},
|
|
479
559
|
async ({ elements }: { elements: { selector: string; value: string }[] }) => {
|
|
560
|
+
// log the events
|
|
561
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.fillForm}: filling form with elements: ${JSON.stringify(elements)}`);
|
|
562
|
+
|
|
480
563
|
const resolved = [];
|
|
481
564
|
for (const element of elements) {
|
|
482
565
|
const uid = await MainHelper._resolveSelectorToUid(mcpClient, element.selector);
|
|
@@ -485,32 +568,36 @@ class MainHelper {
|
|
|
485
568
|
value: element.value,
|
|
486
569
|
});
|
|
487
570
|
}
|
|
571
|
+
let callToolResult: CallToolResult;
|
|
488
572
|
if (mcpTarget === 'chrome_devtools') {
|
|
489
573
|
const toolConfig = await McpTargetHelper.targetToolFillForm(mcpTarget, resolved);
|
|
490
|
-
|
|
491
|
-
// const callToolResult = await mcpClient.callTool('fill_form', { elements: resolved });
|
|
492
|
-
return callToolResult
|
|
574
|
+
callToolResult = await mcpClient.callTool(toolConfig.toolName, toolConfig.toolArgs);
|
|
493
575
|
} else if (mcpTarget === 'playwright') {
|
|
494
576
|
type Field = {
|
|
495
577
|
name: string; // Human readable name for the field, e.g. "Email address"
|
|
496
578
|
// type can be textbox, checkbox, radio, combobox, or slider
|
|
497
579
|
type: 'textbox' | 'checkbox' | 'radio' | 'combobox' | 'slider';
|
|
498
580
|
// the uid of the field's corresponding node in the accessibility tree
|
|
499
|
-
|
|
581
|
+
target: string;
|
|
500
582
|
// the value to fill into the field - for checkboxes this can be "checked" or "unchecked", for radio buttons this can be "selected", for comboboxes this can be the option to select, and for sliders this can be the value to set the slider to
|
|
501
583
|
value: string
|
|
502
584
|
};
|
|
503
585
|
const fields: Field[] = resolved.map((element, index) => ({
|
|
504
586
|
name: `Field ${index + 1}`,
|
|
505
587
|
type: 'textbox', // for simplicity, we assume all fields are textboxes in this example
|
|
506
|
-
|
|
588
|
+
target: element.uid,
|
|
507
589
|
value: element.value,
|
|
508
590
|
}));
|
|
509
|
-
|
|
510
|
-
return callToolResult
|
|
591
|
+
callToolResult = await mcpClient.callTool('browser_fill_form', { fields: fields });
|
|
511
592
|
} else {
|
|
512
593
|
throw new Error(`Unsupported MCP target: ${mcpTarget}`);
|
|
513
594
|
}
|
|
595
|
+
|
|
596
|
+
// log the events
|
|
597
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.fillForm}: output:`);
|
|
598
|
+
logger.warn(`${JSON.stringify(callToolResult)}`);
|
|
599
|
+
|
|
600
|
+
return callToolResult
|
|
514
601
|
}
|
|
515
602
|
);
|
|
516
603
|
|
|
@@ -530,6 +617,10 @@ class MainHelper {
|
|
|
530
617
|
inputSchema: timezoneSchema,
|
|
531
618
|
},
|
|
532
619
|
async ({ timezone }) => {
|
|
620
|
+
// log the events
|
|
621
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.getCurrentDateTime}: getting current date and time with timezone: ${timezone ?? 'local system timezone'}`);
|
|
622
|
+
|
|
623
|
+
|
|
533
624
|
const date = new Date();
|
|
534
625
|
const options: Intl.DateTimeFormatOptions = {
|
|
535
626
|
timeZone: timezone,
|
|
@@ -542,6 +633,10 @@ class MainHelper {
|
|
|
542
633
|
timeZoneName: "short",
|
|
543
634
|
};
|
|
544
635
|
const formatted = new Intl.DateTimeFormat("en-US", options).format(date);
|
|
636
|
+
|
|
637
|
+
// log the events
|
|
638
|
+
logger.warn(`${mcpTarget}:${McpTargetHelper.EXTERNAL_TOOL_NAME.getCurrentDateTime}: output: ${formatted}`);
|
|
639
|
+
|
|
545
640
|
return {
|
|
546
641
|
content: [{ type: "text", text: formatted }],
|
|
547
642
|
};
|
|
@@ -564,6 +659,11 @@ class MainHelper {
|
|
|
564
659
|
mcpTarget: FastBrowserMcpTarget,
|
|
565
660
|
verbose?: boolean
|
|
566
661
|
}): Promise<void> {
|
|
662
|
+
// Redirect process.stderr to a log file, so that the MCP communication is not polluted by logs.
|
|
663
|
+
const logFile = Path.resolve(import.meta.dirname, `../../outputs/fastbrowser_mcp_${new Date().toISOString()}.log`);
|
|
664
|
+
const logStream = Fs.createWriteStream(logFile, { flags: 'a' });
|
|
665
|
+
process.stderr.write = logStream.write.bind(logStream);
|
|
666
|
+
|
|
567
667
|
///////////////////////////////////////////////////////////////////////////////
|
|
568
668
|
///////////////////////////////////////////////////////////////////////////////
|
|
569
669
|
// mcp client
|
|
@@ -616,11 +716,13 @@ class MainHelper {
|
|
|
616
716
|
{
|
|
617
717
|
selector: "link, button",
|
|
618
718
|
withAncestors: true,
|
|
719
|
+
withChildren: false,
|
|
619
720
|
limit: 0,
|
|
620
721
|
},
|
|
621
722
|
{
|
|
622
723
|
selector: 'heading[level="1"]',
|
|
623
724
|
withAncestors: false,
|
|
725
|
+
withChildren: false,
|
|
624
726
|
limit: 0,
|
|
625
727
|
},
|
|
626
728
|
],
|
|
@@ -646,19 +748,64 @@ class MainHelper {
|
|
|
646
748
|
for (const toolName of toolsToProxys) {
|
|
647
749
|
await mcpProxy.proxyToolCall(mcpClient, toolName)
|
|
648
750
|
}
|
|
751
|
+
|
|
649
752
|
///////////////////////////////////////////////////////////////////////////////
|
|
650
753
|
///////////////////////////////////////////////////////////////////////////////
|
|
651
754
|
// .querySelectorsAll tool implementation
|
|
652
755
|
///////////////////////////////////////////////////////////////////////////////
|
|
653
756
|
///////////////////////////////////////////////////////////////////////////////
|
|
654
|
-
const mcpServer = await mcpProxy.getMcpServer();
|
|
655
757
|
|
|
758
|
+
const mcpServer = await mcpProxy.getMcpServer();
|
|
656
759
|
await MainHelper.initExternalTools(mcpServer, mcpClient);
|
|
657
760
|
|
|
658
|
-
|
|
659
761
|
// Connect the MCP proxy server to start accepting connections from MCP clients (e.g. LLM agents).
|
|
660
762
|
await mcpProxy.connect();
|
|
661
763
|
}
|
|
764
|
+
|
|
765
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
766
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
767
|
+
// .commandTargetTools: connect to the target MCP and print each tool's name, description, and input schema.
|
|
768
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
769
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
770
|
+
|
|
771
|
+
static async commandTargetTools({
|
|
772
|
+
mcpTarget,
|
|
773
|
+
}: {
|
|
774
|
+
mcpTarget: FastBrowserMcpTarget,
|
|
775
|
+
}): Promise<void> {
|
|
776
|
+
const { command: mcpCommand, args: mcpArgs } = McpTargetHelper.mcpArgs(mcpTarget);
|
|
777
|
+
|
|
778
|
+
const mcpClient = new McpMyClient({
|
|
779
|
+
name: 'fastbrowser_target_tools_client',
|
|
780
|
+
version: '1.0.0',
|
|
781
|
+
mcpTarget,
|
|
782
|
+
transport: {
|
|
783
|
+
type: 'stdio',
|
|
784
|
+
command: mcpCommand,
|
|
785
|
+
args: mcpArgs,
|
|
786
|
+
},
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
await mcpClient.connect();
|
|
790
|
+
try {
|
|
791
|
+
const tools = await mcpClient.listTools();
|
|
792
|
+
console.log(`# Tools available on MCP target '${mcpTarget}' (${tools.length})\n`);
|
|
793
|
+
for (const tool of tools) {
|
|
794
|
+
console.log(`## ${tool.name}`);
|
|
795
|
+
console.log('');
|
|
796
|
+
console.log(`### Description`)
|
|
797
|
+
console.log(`${tool.description ?? '(no description)'}`);
|
|
798
|
+
console.log('');
|
|
799
|
+
console.log(`### Input schema`);
|
|
800
|
+
console.log("```");
|
|
801
|
+
console.log(JSON.stringify(tool.inputSchema, null, 2));
|
|
802
|
+
console.log("```");
|
|
803
|
+
console.log('');
|
|
804
|
+
}
|
|
805
|
+
} finally {
|
|
806
|
+
await mcpClient.close();
|
|
807
|
+
}
|
|
808
|
+
}
|
|
662
809
|
}
|
|
663
810
|
|
|
664
811
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -668,12 +815,6 @@ class MainHelper {
|
|
|
668
815
|
///////////////////////////////////////////////////////////////////////////////
|
|
669
816
|
|
|
670
817
|
async function main() {
|
|
671
|
-
// Redirect process.stderr to a log file, so that the MCP communication is not polluted by logs.
|
|
672
|
-
const logFile = Path.resolve(import.meta.dirname, `../../outputs/fastbrowser_mcp_${new Date().toISOString()}.log`);
|
|
673
|
-
const logStream = Fs.createWriteStream(logFile, { flags: 'a' });
|
|
674
|
-
process.stderr.write = logStream.write.bind(logStream);
|
|
675
|
-
|
|
676
|
-
|
|
677
818
|
// throw Error("This entry point is not meant to be run directly. Please run one of the npm scripts defined in package.json, e.g. 'npm run start:fastbrowser_mcp' or 'npm run inspect:fastbrowser_mcp:chrome_devtools'");
|
|
678
819
|
|
|
679
820
|
const program = new Command();
|
|
@@ -693,6 +834,20 @@ async function main() {
|
|
|
693
834
|
});
|
|
694
835
|
});
|
|
695
836
|
|
|
837
|
+
program
|
|
838
|
+
.command('target_tools')
|
|
839
|
+
.description('List the tools exposed by the target MCP (name, description, input schema)')
|
|
840
|
+
.addOption(
|
|
841
|
+
new Option('-b, --mcp_target <mcpTarget>', 'the MCP target to introspect')
|
|
842
|
+
.choices(['chrome_devtools', 'playwright'])
|
|
843
|
+
.default('playwright')
|
|
844
|
+
)
|
|
845
|
+
.action(async (options: { mcp_target: FastBrowserMcpTarget }) => {
|
|
846
|
+
await MainHelper.commandTargetTools({
|
|
847
|
+
mcpTarget: options.mcp_target,
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
|
|
696
851
|
// display help if no command is provided
|
|
697
852
|
if (process.argv.length < 3) {
|
|
698
853
|
program.help();
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
1
3
|
// node import
|
|
4
|
+
import { Logger } from "../../shared/logger.js"
|
|
2
5
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
6
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4
7
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -6,6 +9,12 @@ import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
|
6
9
|
import type { CallToolResult, Prompt, Resource, Tool } from "@modelcontextprotocol/sdk/types.js";
|
|
7
10
|
import { FastBrowserMcpTarget } from "../fastbrowser_types.js";
|
|
8
11
|
|
|
12
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
13
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
14
|
+
//
|
|
15
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
16
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
17
|
+
|
|
9
18
|
export type StdioConfig = {
|
|
10
19
|
type: "stdio";
|
|
11
20
|
command: string;
|
|
@@ -34,6 +43,10 @@ export interface McpClientOptions {
|
|
|
34
43
|
///////////////////////////////////////////////////////////////////////////////
|
|
35
44
|
///////////////////////////////////////////////////////////////////////////////
|
|
36
45
|
|
|
46
|
+
const logger = Logger.fromMetaUrl(import.meta.url, {
|
|
47
|
+
allToStderr: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
37
50
|
// TODO remove this function - it seems to do vastly nothing - more confusing that helpful
|
|
38
51
|
// - i use the McpServer directly, and i got a wrapper for the client... discrepancy is confusing
|
|
39
52
|
export class McpMyClient {
|
|
@@ -57,9 +70,11 @@ export class McpMyClient {
|
|
|
57
70
|
|
|
58
71
|
async connect(): Promise<void> {
|
|
59
72
|
if (this.connected) return;
|
|
73
|
+
logger.warn(`McpMyClient:connect: Connecting to MCP with target ${this.mcpTarget}`);
|
|
60
74
|
|
|
61
75
|
this.transport = this.createTransport(this.options.transport);
|
|
62
76
|
await this.client.connect(this.transport);
|
|
77
|
+
logger.warn(`McpMyClient:connect: Connected to MCP with target ${this.mcpTarget}`);
|
|
63
78
|
this.connected = true;
|
|
64
79
|
}
|
|
65
80
|
|
|
@@ -72,6 +87,7 @@ export class McpMyClient {
|
|
|
72
87
|
async listTools(): Promise<Tool[]> {
|
|
73
88
|
this.assertConnected();
|
|
74
89
|
const { tools } = await this.client.listTools();
|
|
90
|
+
logger.warn(`McpMyClient:listTools: ${JSON.stringify(tools, null, 2)}`);
|
|
75
91
|
return tools;
|
|
76
92
|
}
|
|
77
93
|
|
|
@@ -80,6 +96,7 @@ export class McpMyClient {
|
|
|
80
96
|
args: Record<string, unknown> = {},
|
|
81
97
|
): Promise<CallToolResult> {
|
|
82
98
|
this.assertConnected();
|
|
99
|
+
logger.warn(`McpMyClient:callTool: name: ${name} args: ${JSON.stringify(args, null, 2)}`);
|
|
83
100
|
return this.client.callTool({ name, arguments: args }) as Promise<CallToolResult>;
|
|
84
101
|
}
|
|
85
102
|
|
|
@@ -182,9 +182,21 @@ export class McpTargetHelper {
|
|
|
182
182
|
|
|
183
183
|
static async targetToolClick(mcpTarget: FastBrowserMcpTarget, uid: string): Promise<TargetToolConfig> {
|
|
184
184
|
if (mcpTarget === 'chrome_devtools') {
|
|
185
|
-
|
|
185
|
+
const toolConfig: TargetToolConfig = {
|
|
186
|
+
toolName: 'click',
|
|
187
|
+
toolArgs: {
|
|
188
|
+
uid
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
return toolConfig;
|
|
186
192
|
} else if (mcpTarget === 'playwright') {
|
|
187
|
-
|
|
193
|
+
const toolConfig: TargetToolConfig = {
|
|
194
|
+
toolName: 'browser_click',
|
|
195
|
+
toolArgs: {
|
|
196
|
+
target: uid
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
return toolConfig;
|
|
188
200
|
} else {
|
|
189
201
|
throw new Error(`Unsupported MCP target: ${mcpTarget}`);
|
|
190
202
|
}
|
|
@@ -194,6 +206,7 @@ export class McpTargetHelper {
|
|
|
194
206
|
if (mcpTarget === 'chrome_devtools') {
|
|
195
207
|
return { toolName: 'fill_form', toolArgs: { elements } };
|
|
196
208
|
} else if (mcpTarget === 'playwright') {
|
|
209
|
+
// FIXME browser_fill_form tool exist in playwright MCP... implement it later
|
|
197
210
|
throw new Error(`Fill form tool is not supported for Playwright MCP target yet`);
|
|
198
211
|
} else {
|
|
199
212
|
throw new Error(`Unsupported MCP target: ${mcpTarget}`);
|
|
@@ -18,6 +18,9 @@ export const QuerySelectorInputSchema = z.object({
|
|
|
18
18
|
withAncestors: z.boolean()
|
|
19
19
|
.describe("Whether to include ancestor nodes in the result")
|
|
20
20
|
.default(false),
|
|
21
|
+
withChildren: z.boolean()
|
|
22
|
+
.describe("Whether to include descendant nodes (subtree) of each matched node in the result")
|
|
23
|
+
.default(false),
|
|
21
24
|
});
|
|
22
25
|
|
|
23
26
|
export const QuerySelectorsInputSchema = z.object({
|
|
@@ -31,6 +34,9 @@ export const QuerySelectorFirstInputSchema = z.object({
|
|
|
31
34
|
withAncestors: z.boolean()
|
|
32
35
|
.describe("Whether to include ancestor nodes in the result")
|
|
33
36
|
.default(false),
|
|
37
|
+
withChildren: z.boolean()
|
|
38
|
+
.describe("Whether to include descendant nodes (subtree) of each matched node in the result")
|
|
39
|
+
.default(false),
|
|
34
40
|
});
|
|
35
41
|
|
|
36
42
|
export const QuerySelectorsFirstInputSchema = z.object({
|