brave-real-browser-mcp-server 2.27.0 → 2.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lsp/browser-automation-lsp.js +239 -0
- package/dist/tool-definitions.js +96 -2
- package/dist/unified-server.js +105 -4
- package/package.json +2 -2
|
@@ -349,6 +349,15 @@ connection.onInitialize(() => {
|
|
|
349
349
|
codeActionProvider: {
|
|
350
350
|
codeActionKinds: [CodeActionKind.QuickFix],
|
|
351
351
|
},
|
|
352
|
+
// Advanced LSP Features
|
|
353
|
+
definitionProvider: true,
|
|
354
|
+
referencesProvider: true,
|
|
355
|
+
workspaceSymbolProvider: true,
|
|
356
|
+
documentSymbolProvider: true,
|
|
357
|
+
renameProvider: {
|
|
358
|
+
prepareProvider: true,
|
|
359
|
+
},
|
|
360
|
+
documentFormattingProvider: true,
|
|
352
361
|
},
|
|
353
362
|
};
|
|
354
363
|
});
|
|
@@ -572,6 +581,236 @@ connection.onCodeAction((params) => {
|
|
|
572
581
|
// ============================================================
|
|
573
582
|
// HELPERS
|
|
574
583
|
// ============================================================
|
|
584
|
+
// Store document contents and tool locations
|
|
585
|
+
const documentToolLocations = new Map();
|
|
586
|
+
function findToolLocations(document) {
|
|
587
|
+
const text = document.getText();
|
|
588
|
+
const locations = [];
|
|
589
|
+
const toolCallPattern = /(\w+)\s*\(/g;
|
|
590
|
+
let match;
|
|
591
|
+
while ((match = toolCallPattern.exec(text)) !== null) {
|
|
592
|
+
const toolName = match[1];
|
|
593
|
+
if (TOOL_DEFINITIONS[toolName]) {
|
|
594
|
+
const startPos = document.positionAt(match.index);
|
|
595
|
+
const endPos = document.positionAt(match.index + toolName.length);
|
|
596
|
+
locations.push({
|
|
597
|
+
name: toolName,
|
|
598
|
+
range: { start: startPos, end: endPos },
|
|
599
|
+
uri: document.uri,
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
documentToolLocations.set(document.uri, locations);
|
|
604
|
+
return locations;
|
|
605
|
+
}
|
|
606
|
+
// ============================================================
|
|
607
|
+
// GO TO DEFINITION
|
|
608
|
+
// ============================================================
|
|
609
|
+
connection.onDefinition((params) => {
|
|
610
|
+
const document = documents.get(params.textDocument.uri);
|
|
611
|
+
if (!document)
|
|
612
|
+
return null;
|
|
613
|
+
const text = document.getText();
|
|
614
|
+
const offset = document.offsetAt(params.position);
|
|
615
|
+
// Find word at position
|
|
616
|
+
const wordStart = text.substring(0, offset).search(/\w+$/);
|
|
617
|
+
if (wordStart < 0)
|
|
618
|
+
return null;
|
|
619
|
+
const wordEnd = offset + text.substring(offset).search(/[^\w]|$/);
|
|
620
|
+
const word = text.substring(wordStart, wordEnd);
|
|
621
|
+
const def = TOOL_DEFINITIONS[word];
|
|
622
|
+
if (def) {
|
|
623
|
+
// Return the first occurrence of this tool in the document
|
|
624
|
+
const pattern = new RegExp(`\\b${word}\\s*\\(`);
|
|
625
|
+
const match = text.match(pattern);
|
|
626
|
+
if (match && match.index !== undefined) {
|
|
627
|
+
const startPos = document.positionAt(match.index);
|
|
628
|
+
const endPos = document.positionAt(match.index + word.length);
|
|
629
|
+
return {
|
|
630
|
+
uri: params.textDocument.uri,
|
|
631
|
+
range: { start: startPos, end: endPos },
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return null;
|
|
636
|
+
});
|
|
637
|
+
// ============================================================
|
|
638
|
+
// FIND ALL REFERENCES
|
|
639
|
+
// ============================================================
|
|
640
|
+
connection.onReferences((params) => {
|
|
641
|
+
const document = documents.get(params.textDocument.uri);
|
|
642
|
+
if (!document)
|
|
643
|
+
return [];
|
|
644
|
+
const text = document.getText();
|
|
645
|
+
const offset = document.offsetAt(params.position);
|
|
646
|
+
// Find word at position
|
|
647
|
+
const wordStart = text.substring(0, offset).search(/\w+$/);
|
|
648
|
+
if (wordStart < 0)
|
|
649
|
+
return [];
|
|
650
|
+
const wordEnd = offset + text.substring(offset).search(/[^\w]|$/);
|
|
651
|
+
const word = text.substring(wordStart, wordEnd);
|
|
652
|
+
const references = [];
|
|
653
|
+
// Search in all open documents
|
|
654
|
+
documents.all().forEach((doc) => {
|
|
655
|
+
const docText = doc.getText();
|
|
656
|
+
const pattern = new RegExp(`\\b${word}\\b`, 'g');
|
|
657
|
+
let match;
|
|
658
|
+
while ((match = pattern.exec(docText)) !== null) {
|
|
659
|
+
const startPos = doc.positionAt(match.index);
|
|
660
|
+
const endPos = doc.positionAt(match.index + word.length);
|
|
661
|
+
references.push({
|
|
662
|
+
uri: doc.uri,
|
|
663
|
+
range: { start: startPos, end: endPos },
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
return references;
|
|
668
|
+
});
|
|
669
|
+
// ============================================================
|
|
670
|
+
// DOCUMENT SYMBOLS
|
|
671
|
+
// ============================================================
|
|
672
|
+
connection.onDocumentSymbol((params) => {
|
|
673
|
+
const document = documents.get(params.textDocument.uri);
|
|
674
|
+
if (!document)
|
|
675
|
+
return [];
|
|
676
|
+
const text = document.getText();
|
|
677
|
+
const symbols = [];
|
|
678
|
+
// Find all tool calls as symbols
|
|
679
|
+
const toolCallPattern = /(\w+)\s*\(\s*\{[^}]*\}\s*\)/g;
|
|
680
|
+
let match;
|
|
681
|
+
while ((match = toolCallPattern.exec(text)) !== null) {
|
|
682
|
+
const toolName = match[1];
|
|
683
|
+
if (TOOL_DEFINITIONS[toolName]) {
|
|
684
|
+
const startPos = document.positionAt(match.index);
|
|
685
|
+
const endPos = document.positionAt(match.index + match[0].length);
|
|
686
|
+
symbols.push({
|
|
687
|
+
name: toolName,
|
|
688
|
+
kind: 12, // Function
|
|
689
|
+
range: { start: startPos, end: endPos },
|
|
690
|
+
selectionRange: { start: startPos, end: document.positionAt(match.index + toolName.length) },
|
|
691
|
+
detail: TOOL_DEFINITIONS[toolName].category,
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return symbols;
|
|
696
|
+
});
|
|
697
|
+
// ============================================================
|
|
698
|
+
// WORKSPACE SYMBOLS
|
|
699
|
+
// ============================================================
|
|
700
|
+
connection.onWorkspaceSymbol((params) => {
|
|
701
|
+
const query = params.query.toLowerCase();
|
|
702
|
+
const symbols = [];
|
|
703
|
+
// Return all matching tool definitions
|
|
704
|
+
for (const [name, def] of Object.entries(TOOL_DEFINITIONS)) {
|
|
705
|
+
if (name.toLowerCase().includes(query) || def.description.toLowerCase().includes(query)) {
|
|
706
|
+
symbols.push({
|
|
707
|
+
name: name,
|
|
708
|
+
kind: 12, // Function
|
|
709
|
+
location: {
|
|
710
|
+
uri: 'brave-mcp://tools/' + name,
|
|
711
|
+
range: { start: { line: 0, character: 0 }, end: { line: 0, character: name.length } },
|
|
712
|
+
},
|
|
713
|
+
containerName: def.category,
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return symbols;
|
|
718
|
+
});
|
|
719
|
+
// ============================================================
|
|
720
|
+
// RENAME SYMBOL
|
|
721
|
+
// ============================================================
|
|
722
|
+
connection.onPrepareRename((params) => {
|
|
723
|
+
const document = documents.get(params.textDocument.uri);
|
|
724
|
+
if (!document)
|
|
725
|
+
return null;
|
|
726
|
+
const text = document.getText();
|
|
727
|
+
const offset = document.offsetAt(params.position);
|
|
728
|
+
// Find word at position
|
|
729
|
+
const wordStart = text.substring(0, offset).search(/\w+$/);
|
|
730
|
+
if (wordStart < 0)
|
|
731
|
+
return null;
|
|
732
|
+
const wordEnd = offset + text.substring(offset).search(/[^\w]|$/);
|
|
733
|
+
const word = text.substring(wordStart, wordEnd);
|
|
734
|
+
// Don't allow renaming built-in tools
|
|
735
|
+
if (TOOL_DEFINITIONS[word]) {
|
|
736
|
+
return { message: 'Cannot rename built-in MCP tool' };
|
|
737
|
+
}
|
|
738
|
+
const startPos = document.positionAt(wordStart);
|
|
739
|
+
const endPos = document.positionAt(wordEnd);
|
|
740
|
+
return { range: { start: startPos, end: endPos }, placeholder: word };
|
|
741
|
+
});
|
|
742
|
+
connection.onRenameRequest((params) => {
|
|
743
|
+
const document = documents.get(params.textDocument.uri);
|
|
744
|
+
if (!document)
|
|
745
|
+
return null;
|
|
746
|
+
const text = document.getText();
|
|
747
|
+
const offset = document.offsetAt(params.position);
|
|
748
|
+
// Find word at position
|
|
749
|
+
const wordStart = text.substring(0, offset).search(/\w+$/);
|
|
750
|
+
if (wordStart < 0)
|
|
751
|
+
return null;
|
|
752
|
+
const wordEnd = offset + text.substring(offset).search(/[^\w]|$/);
|
|
753
|
+
const oldName = text.substring(wordStart, wordEnd);
|
|
754
|
+
const newName = params.newName;
|
|
755
|
+
// Don't allow renaming built-in tools
|
|
756
|
+
if (TOOL_DEFINITIONS[oldName]) {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
const changes = {};
|
|
760
|
+
// Replace all occurrences in all open documents
|
|
761
|
+
documents.all().forEach((doc) => {
|
|
762
|
+
const docText = doc.getText();
|
|
763
|
+
const pattern = new RegExp(`\\b${oldName}\\b`, 'g');
|
|
764
|
+
const edits = [];
|
|
765
|
+
let match;
|
|
766
|
+
while ((match = pattern.exec(docText)) !== null) {
|
|
767
|
+
const startPos = doc.positionAt(match.index);
|
|
768
|
+
const endPos = doc.positionAt(match.index + oldName.length);
|
|
769
|
+
edits.push({
|
|
770
|
+
range: { start: startPos, end: endPos },
|
|
771
|
+
newText: newName,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
if (edits.length > 0) {
|
|
775
|
+
changes[doc.uri] = edits;
|
|
776
|
+
}
|
|
777
|
+
});
|
|
778
|
+
return { changes };
|
|
779
|
+
});
|
|
780
|
+
// ============================================================
|
|
781
|
+
// FORMAT DOCUMENT
|
|
782
|
+
// ============================================================
|
|
783
|
+
connection.onDocumentFormatting((params) => {
|
|
784
|
+
const document = documents.get(params.textDocument.uri);
|
|
785
|
+
if (!document)
|
|
786
|
+
return [];
|
|
787
|
+
const text = document.getText();
|
|
788
|
+
const edits = [];
|
|
789
|
+
// Format MCP tool calls consistently
|
|
790
|
+
let formatted = text
|
|
791
|
+
// Normalize tool call opening: tool({
|
|
792
|
+
.replace(/(\w+)\s*\(\s*\{\s*/g, '$1({ ')
|
|
793
|
+
// Normalize tool call closing: })
|
|
794
|
+
.replace(/\s*\}\s*\)/g, ' })')
|
|
795
|
+
// Fix parameter spacing: key: value
|
|
796
|
+
.replace(/(\w+)\s*:\s*/g, '$1: ')
|
|
797
|
+
// Remove trailing commas before }
|
|
798
|
+
.replace(/,\s*\}/g, ' }')
|
|
799
|
+
// Normalize await spacing
|
|
800
|
+
.replace(/await\s+/g, 'await ');
|
|
801
|
+
if (formatted !== text) {
|
|
802
|
+
edits.push({
|
|
803
|
+
range: {
|
|
804
|
+
start: { line: 0, character: 0 },
|
|
805
|
+
end: document.positionAt(text.length),
|
|
806
|
+
},
|
|
807
|
+
newText: formatted,
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
return edits;
|
|
811
|
+
});
|
|
812
|
+
// ============================================================
|
|
813
|
+
// HELPERS
|
|
575
814
|
function formatToolDoc(def) {
|
|
576
815
|
const parts = [];
|
|
577
816
|
parts.push(`## ${def.name}`);
|
package/dist/tool-definitions.js
CHANGED
|
@@ -7,9 +7,103 @@ export const SERVER_INFO = {
|
|
|
7
7
|
// MCP capabilities with LSP-compatible streaming support
|
|
8
8
|
export const CAPABILITIES = {
|
|
9
9
|
tools: {},
|
|
10
|
-
resources: {},
|
|
11
|
-
prompts: {},
|
|
10
|
+
resources: { subscribe: true, listChanged: true },
|
|
11
|
+
prompts: { listChanged: true },
|
|
12
12
|
};
|
|
13
|
+
// MCP Resources - Dynamic browser state resources
|
|
14
|
+
export const RESOURCES = [
|
|
15
|
+
{
|
|
16
|
+
uri: 'browser://state',
|
|
17
|
+
name: 'Browser State',
|
|
18
|
+
description: 'Current browser instance state including page URL, title, and session info',
|
|
19
|
+
mimeType: 'application/json',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
uri: 'browser://page/content',
|
|
23
|
+
name: 'Page Content',
|
|
24
|
+
description: 'Current page HTML content',
|
|
25
|
+
mimeType: 'text/html',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
uri: 'browser://page/text',
|
|
29
|
+
name: 'Page Text',
|
|
30
|
+
description: 'Current page text content (no HTML)',
|
|
31
|
+
mimeType: 'text/plain',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
uri: 'browser://cookies',
|
|
35
|
+
name: 'Browser Cookies',
|
|
36
|
+
description: 'All cookies in current browser session',
|
|
37
|
+
mimeType: 'application/json',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
uri: 'browser://network/requests',
|
|
41
|
+
name: 'Network Requests',
|
|
42
|
+
description: 'Recent network requests captured during browsing',
|
|
43
|
+
mimeType: 'application/json',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
uri: 'browser://console/logs',
|
|
47
|
+
name: 'Console Logs',
|
|
48
|
+
description: 'Browser console log messages',
|
|
49
|
+
mimeType: 'application/json',
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
// MCP Prompts - Reusable automation workflows
|
|
53
|
+
export const PROMPTS = [
|
|
54
|
+
{
|
|
55
|
+
name: 'scrape_website',
|
|
56
|
+
description: 'Scrape content from a website with automatic navigation and extraction',
|
|
57
|
+
arguments: [
|
|
58
|
+
{ name: 'url', description: 'URL to scrape', required: true },
|
|
59
|
+
{ name: 'selector', description: 'CSS selector for target content', required: false },
|
|
60
|
+
{ name: 'outputFormat', description: 'Output format (json, markdown, text)', required: false },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'extract_download_links',
|
|
65
|
+
description: 'Extract all download links from a page with quality and size info',
|
|
66
|
+
arguments: [
|
|
67
|
+
{ name: 'url', description: 'Page URL to extract from', required: true },
|
|
68
|
+
{ name: 'fileTypes', description: 'File types to extract (mp4, mkv, pdf, etc.)', required: false },
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'monitor_page_changes',
|
|
73
|
+
description: 'Monitor a page for changes and notify when content updates',
|
|
74
|
+
arguments: [
|
|
75
|
+
{ name: 'url', description: 'URL to monitor', required: true },
|
|
76
|
+
{ name: 'selector', description: 'Element to watch for changes', required: true },
|
|
77
|
+
{ name: 'interval', description: 'Check interval in seconds', required: false },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'automate_login',
|
|
82
|
+
description: 'Automate login to a website with credentials',
|
|
83
|
+
arguments: [
|
|
84
|
+
{ name: 'url', description: 'Login page URL', required: true },
|
|
85
|
+
{ name: 'usernameSelector', description: 'Username field selector', required: true },
|
|
86
|
+
{ name: 'passwordSelector', description: 'Password field selector', required: true },
|
|
87
|
+
{ name: 'submitSelector', description: 'Submit button selector', required: true },
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'batch_screenshot',
|
|
92
|
+
description: 'Take screenshots of multiple URLs',
|
|
93
|
+
arguments: [
|
|
94
|
+
{ name: 'urls', description: 'Comma-separated list of URLs', required: true },
|
|
95
|
+
{ name: 'outputDir', description: 'Directory to save screenshots', required: true },
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'extract_video_stream',
|
|
100
|
+
description: 'Extract video streaming URL from a page (m3u8, mp4)',
|
|
101
|
+
arguments: [
|
|
102
|
+
{ name: 'url', description: 'Video page URL', required: true },
|
|
103
|
+
{ name: 'quality', description: 'Preferred quality (highest, 1080p, 720p)', required: false },
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
];
|
|
13
107
|
// Extended capabilities for streaming and auto-sync (for documentation/client info)
|
|
14
108
|
export const EXTENDED_CAPABILITIES = {
|
|
15
109
|
streaming: true,
|
package/dist/unified-server.js
CHANGED
|
@@ -17,10 +17,10 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
|
17
17
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
18
18
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
19
19
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
20
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ListPromptsRequestSchema, InitializeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
20
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, InitializeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
21
21
|
import { randomUUID } from 'crypto';
|
|
22
22
|
// Import core modules
|
|
23
|
-
import { TOOLS, SERVER_INFO, CAPABILITIES, TOOL_NAMES } from './tool-definitions.js';
|
|
23
|
+
import { TOOLS, SERVER_INFO, CAPABILITIES, TOOL_NAMES, RESOURCES, PROMPTS } from './tool-definitions.js';
|
|
24
24
|
import { getSharedEventBus } from './shared/event-bus.js';
|
|
25
25
|
import { getProgressNotifier } from './transport/progress-notifier.js';
|
|
26
26
|
import { getSessionManager } from './transport/session-manager.js';
|
|
@@ -69,8 +69,109 @@ mcpServer.setRequestHandler(InitializeRequestSchema, async (request) => {
|
|
|
69
69
|
};
|
|
70
70
|
});
|
|
71
71
|
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
72
|
-
mcpServer.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources:
|
|
73
|
-
mcpServer.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts:
|
|
72
|
+
mcpServer.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: RESOURCES }));
|
|
73
|
+
mcpServer.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: PROMPTS }));
|
|
74
|
+
// Read Resource Handler
|
|
75
|
+
mcpServer.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
76
|
+
const { uri } = request.params;
|
|
77
|
+
const page = getPageInstance();
|
|
78
|
+
debug(`ReadResource: ${uri}`);
|
|
79
|
+
try {
|
|
80
|
+
switch (uri) {
|
|
81
|
+
case 'browser://state': {
|
|
82
|
+
const state = page ? {
|
|
83
|
+
url: page.url(),
|
|
84
|
+
title: await page.title(),
|
|
85
|
+
isConnected: page.browser()?.connected ?? false,
|
|
86
|
+
viewport: page.viewport(),
|
|
87
|
+
} : { status: 'Browser not initialized' };
|
|
88
|
+
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(state, null, 2) }] };
|
|
89
|
+
}
|
|
90
|
+
case 'browser://page/content': {
|
|
91
|
+
if (!page)
|
|
92
|
+
throw new Error('Browser not initialized');
|
|
93
|
+
const html = await page.content();
|
|
94
|
+
return { contents: [{ uri, mimeType: 'text/html', text: html }] };
|
|
95
|
+
}
|
|
96
|
+
case 'browser://page/text': {
|
|
97
|
+
if (!page)
|
|
98
|
+
throw new Error('Browser not initialized');
|
|
99
|
+
const text = await page.evaluate(() => document.body.innerText);
|
|
100
|
+
return { contents: [{ uri, mimeType: 'text/plain', text }] };
|
|
101
|
+
}
|
|
102
|
+
case 'browser://cookies': {
|
|
103
|
+
if (!page)
|
|
104
|
+
throw new Error('Browser not initialized');
|
|
105
|
+
const cookies = await page.cookies();
|
|
106
|
+
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(cookies, null, 2) }] };
|
|
107
|
+
}
|
|
108
|
+
case 'browser://network/requests': {
|
|
109
|
+
// Return cached network requests from event bus
|
|
110
|
+
const history = eventBus.getHistory().filter(e => e.type.includes('network'));
|
|
111
|
+
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(history.slice(-50), null, 2) }] };
|
|
112
|
+
}
|
|
113
|
+
case 'browser://console/logs': {
|
|
114
|
+
const logs = eventBus.getHistory().filter(e => e.type.includes('console'));
|
|
115
|
+
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(logs.slice(-100), null, 2) }] };
|
|
116
|
+
}
|
|
117
|
+
default:
|
|
118
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
123
|
+
return { contents: [{ uri, mimeType: 'text/plain', text: `Error: ${errorMessage}` }] };
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
// Get Prompt Handler
|
|
127
|
+
mcpServer.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
128
|
+
const { name, arguments: args } = request.params;
|
|
129
|
+
debug(`GetPrompt: ${name}`);
|
|
130
|
+
const prompt = PROMPTS.find(p => p.name === name);
|
|
131
|
+
if (!prompt) {
|
|
132
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
133
|
+
}
|
|
134
|
+
// Generate prompt messages based on template
|
|
135
|
+
let messages = [];
|
|
136
|
+
switch (name) {
|
|
137
|
+
case 'scrape_website':
|
|
138
|
+
messages = [
|
|
139
|
+
{ role: 'user', content: { type: 'text', text: `Scrape the website at ${args?.url || '[URL]'}. ${args?.selector ? `Focus on elements matching: ${args.selector}` : 'Extract main content.'}` } },
|
|
140
|
+
];
|
|
141
|
+
break;
|
|
142
|
+
case 'extract_download_links':
|
|
143
|
+
messages = [
|
|
144
|
+
{ role: 'user', content: { type: 'text', text: `Extract all download links from ${args?.url || '[URL]'}. ${args?.fileTypes ? `Filter for: ${args.fileTypes}` : 'Include all file types.'}` } },
|
|
145
|
+
];
|
|
146
|
+
break;
|
|
147
|
+
case 'monitor_page_changes':
|
|
148
|
+
messages = [
|
|
149
|
+
{ role: 'user', content: { type: 'text', text: `Monitor ${args?.url || '[URL]'} for changes in element: ${args?.selector || '[SELECTOR]'}. Check every ${args?.interval || 60} seconds.` } },
|
|
150
|
+
];
|
|
151
|
+
break;
|
|
152
|
+
case 'automate_login':
|
|
153
|
+
messages = [
|
|
154
|
+
{ role: 'user', content: { type: 'text', text: `Automate login to ${args?.url || '[URL]'}. Use ${args?.usernameSelector || '#username'} for username, ${args?.passwordSelector || '#password'} for password, and ${args?.submitSelector || 'button[type=submit]'} to submit.` } },
|
|
155
|
+
];
|
|
156
|
+
break;
|
|
157
|
+
case 'batch_screenshot':
|
|
158
|
+
messages = [
|
|
159
|
+
{ role: 'user', content: { type: 'text', text: `Take screenshots of: ${args?.urls || '[URLs]'}. Save to: ${args?.outputDir || './screenshots'}` } },
|
|
160
|
+
];
|
|
161
|
+
break;
|
|
162
|
+
case 'extract_video_stream':
|
|
163
|
+
messages = [
|
|
164
|
+
{ role: 'user', content: { type: 'text', text: `Extract video streaming URL from ${args?.url || '[URL]'}. Prefer quality: ${args?.quality || 'highest'}` } },
|
|
165
|
+
];
|
|
166
|
+
break;
|
|
167
|
+
default:
|
|
168
|
+
messages = [{ role: 'user', content: { type: 'text', text: `Execute prompt: ${name}` } }];
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
description: prompt.description,
|
|
172
|
+
messages,
|
|
173
|
+
};
|
|
174
|
+
});
|
|
74
175
|
mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
75
176
|
const { name, arguments: args } = request.params;
|
|
76
177
|
debug(`Tool call: ${name}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.27.
|
|
3
|
+
"version": "2.27.2",
|
|
4
4
|
"description": "🦁 MCP server for Brave Real Browser - NPM Workspaces Monorepo with anti-detection features, SSE streaming, and LSP compatibility",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@modelcontextprotocol/sdk": "latest",
|
|
52
52
|
"@types/turndown": "latest",
|
|
53
|
-
"brave-real-browser": "^2.8.
|
|
53
|
+
"brave-real-browser": "^2.8.2",
|
|
54
54
|
"puppeteer-core": "^24.35.0",
|
|
55
55
|
"turndown": "latest",
|
|
56
56
|
"vscode-languageserver": "^9.0.1",
|