works on X

This commit is contained in:
Aaron Elijah Mars
2025-06-26 15:58:29 +02:00
parent bc1f1d4f3c
commit 76854cc8d0
2 changed files with 1730 additions and 798 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,9 @@ async function handleMCPRequest(request) {
switch (method) {
case "initialize":
// RESPOND IMMEDIATELY - don't wait for extension
console.error(`MCP client initializing: ${params?.clientInfo?.name || "unknown"}`);
console.error(
`MCP client initializing: ${params?.clientInfo?.name || "unknown"}`
);
result = {
protocolVersion: "2024-11-05",
capabilities: {
@@ -40,19 +42,31 @@ async function handleMCPRequest(request) {
},
serverInfo: {
name: "browser-mcp-server",
version: "1.0.0",
version: "2.0.0",
},
instructions: "Browser automation tools via Chrome Extension bridge. Extension may take a moment to connect."
instructions:
"🎯 Enhanced browser automation with anti-detection bypass for Twitter/X, LinkedIn, Facebook. Extension may take a moment to connect.",
};
break;
case "tools/list":
// Debug logging
console.error(`Tools/list called. Extension connected: ${chromeExtensionSocket && chromeExtensionSocket.readyState === WebSocket.OPEN}, Available tools: ${availableTools.length}`);
console.error(
`Tools/list called. Extension connected: ${
chromeExtensionSocket &&
chromeExtensionSocket.readyState === WebSocket.OPEN
}, Available tools: ${availableTools.length}`
);
// Return tools from extension if available, otherwise fallback tools
if (chromeExtensionSocket && chromeExtensionSocket.readyState === WebSocket.OPEN && availableTools.length > 0) {
console.error(`Returning ${availableTools.length} tools from extension`);
if (
chromeExtensionSocket &&
chromeExtensionSocket.readyState === WebSocket.OPEN &&
availableTools.length > 0
) {
console.error(
`Returning ${availableTools.length} tools from extension`
);
result = {
tools: availableTools.map((tool) => ({
name: tool.name,
@@ -64,22 +78,25 @@ async function handleMCPRequest(request) {
// Return basic fallback tools
console.error("Extension not connected, returning fallback tools");
result = {
tools: getFallbackTools()
tools: getFallbackTools(),
};
}
break;
case "tools/call":
if (!chromeExtensionSocket || chromeExtensionSocket.readyState !== WebSocket.OPEN) {
if (
!chromeExtensionSocket ||
chromeExtensionSocket.readyState !== WebSocket.OPEN
) {
// Extension not connected - return helpful error
result = {
content: [
{
type: "text",
text: "❌ Chrome Extension not connected. Please install and activate the browser extension, then try again.\n\nSetup instructions:\n1. Go to chrome://extensions/\n2. Enable Developer mode\n3. Click 'Load unpacked' and select the extension folder\n4. Ensure the extension is active",
text: "❌ Chrome Extension not connected. Please install and activate the browser extension, then try again.\n\nSetup instructions:\n1. Go to chrome://extensions/\n2. Enable Developer mode\n3. Click 'Load unpacked' and select the extension folder\n4. Ensure the extension is active\n\n🎯 Features: Anti-detection bypass for Twitter/X, LinkedIn, Facebook + universal automation",
},
],
isError: true
isError: true,
};
} else {
// Extension connected - try the tool call
@@ -88,10 +105,10 @@ async function handleMCPRequest(request) {
params.name,
params.arguments || {}
);
// Format response based on tool type
const formattedResult = formatToolResult(params.name, toolResult);
result = {
content: [
{
@@ -99,7 +116,7 @@ async function handleMCPRequest(request) {
text: formattedResult,
},
],
isError: false
isError: false,
};
} catch (error) {
result = {
@@ -109,7 +126,7 @@ async function handleMCPRequest(request) {
text: `❌ Tool execution failed: ${error.message}`,
},
],
isError: true
isError: true,
};
}
}
@@ -142,153 +159,373 @@ async function handleMCPRequest(request) {
}
}
// Remove static tools - they were causing duplicates
// All tools now come from the extension only
// Format tool results for better MCP response
// Enhanced tool result formatting with anti-detection support
function formatToolResult(toolName, result) {
const metadata = {
tool: toolName,
execution_time: result.execution_time || 0,
timestamp: new Date().toISOString()
timestamp: new Date().toISOString(),
};
switch (toolName) {
case 'page_analyze':
if (result.elements && result.elements.length > 0) {
const summary = `Found ${result.elements.length} relevant elements using ${result.method}:\n\n` +
result.elements.map(el =>
`${el.name} (${el.type}) - Confidence: ${Math.round(el.confidence * 100)}%\n Selector: ${el.selector}\n Element ID: ${el.id}`
).join('\n\n');
return `${summary}\n\n${JSON.stringify(metadata, null, 2)}`;
} else {
return `No relevant elements found for intent: "${result.intent_hint || 'unknown'}"\n\n${JSON.stringify(metadata, null, 2)}`;
}
case 'page_extract_content':
const contentSummary = `Extracted ${result.content_type} content using ${result.method}:\n\n`;
if (result.content) {
const preview = typeof result.content === 'string'
? result.content.substring(0, 500) + (result.content.length > 500 ? '...' : '')
: JSON.stringify(result.content, null, 2).substring(0, 500);
return `${contentSummary}${preview}\n\n${JSON.stringify(metadata, null, 2)}`;
} else {
return `${contentSummary}No content found\n\n${JSON.stringify(metadata, null, 2)}`;
}
case 'element_click':
return `✅ Successfully clicked element: ${result.element_name || result.element_id}\n` +
`Click type: ${result.click_type || 'left'}\n\n${JSON.stringify(metadata, null, 2)}`;
case 'element_fill':
return `✅ Successfully filled element: ${result.element_name || result.element_id}\n` +
`Value: "${result.value}"\n\n${JSON.stringify(metadata, null, 2)}`;
case 'page_navigate':
return `✅ Successfully navigated to: ${result.url || 'unknown URL'}\n\n${JSON.stringify(metadata, null, 2)}`;
case 'page_wait_for':
return `✅ Condition met: ${result.condition_type || 'unknown'}\n` +
`Wait time: ${result.wait_time || 0}ms\n\n${JSON.stringify(metadata, null, 2)}`;
case "page_analyze":
return formatPageAnalyzeResult(result, metadata);
case "page_extract_content":
return formatContentExtractionResult(result, metadata);
case "element_click":
return formatElementClickResult(result, metadata);
case "element_fill":
return formatElementFillResult(result, metadata);
case "page_navigate":
return `✅ Successfully navigated to: ${
result.url || "unknown URL"
}\n\n${JSON.stringify(metadata, null, 2)}`;
case "page_wait_for":
return (
`✅ Condition met: ${result.condition_type || "unknown"}\n` +
`Wait time: ${result.wait_time || 0}ms\n\n${JSON.stringify(
metadata,
null,
2
)}`
);
default:
// Legacy tools or unknown tools
return JSON.stringify(result, null, 2);
}
}
// Fallback tools when extension is not connected
function formatPageAnalyzeResult(result, metadata) {
if (result.elements && result.elements.length > 0) {
const platformInfo = result.summary?.anti_detection_platform
? `\n🎯 Anti-detection platform detected: ${result.summary.anti_detection_platform}`
: "";
const summary =
`Found ${result.elements.length} relevant elements using ${result.method}:${platformInfo}\n\n` +
result.elements
.map((el) => {
const readyStatus = el.ready ? "✅ Ready" : "⚠️ Not ready";
const stateInfo = el.state === "disabled" ? " (disabled)" : "";
return `${el.name} (${el.type}) - Confidence: ${el.conf}% ${readyStatus}${stateInfo}\n Element ID: ${el.id}`;
})
.join("\n\n");
return `${summary}\n\n${JSON.stringify(metadata, null, 2)}`;
} else {
const intentHint = result.intent_hint || "unknown";
const platformInfo = result.summary?.anti_detection_platform
? `\nPlatform: ${result.summary.anti_detection_platform}`
: "";
return `No relevant elements found for intent: "${intentHint}"${platformInfo}\n\n${JSON.stringify(
metadata,
null,
2
)}`;
}
}
function formatContentExtractionResult(result, metadata) {
const contentSummary = `Extracted ${result.content_type} content using ${result.method}:\n\n`;
if (result.content) {
const preview =
typeof result.content === "string"
? result.content.substring(0, 500) +
(result.content.length > 500 ? "..." : "")
: JSON.stringify(result.content, null, 2).substring(0, 500);
return `${contentSummary}${preview}\n\n${JSON.stringify(
metadata,
null,
2
)}`;
} else if (result.summary) {
// Enhanced summarized content response
const summaryText = formatContentSummary(
result.summary,
result.content_type
);
return `${contentSummary}${summaryText}\n\n${JSON.stringify(
metadata,
null,
2
)}`;
} else {
return `${contentSummary}No content found\n\n${JSON.stringify(
metadata,
null,
2
)}`;
}
}
function formatContentSummary(summary, contentType) {
switch (contentType) {
case "article":
return (
`📰 Article: "${summary.title}"\n` +
`📝 Word count: ${summary.word_count}\n` +
`⏱️ Reading time: ${summary.reading_time} minutes\n` +
`🖼️ Has media: ${summary.has_images || summary.has_videos}\n` +
`Preview: ${summary.preview}`
);
case "search_results":
return (
`🔍 Search Results Summary:\n` +
`📊 Total results: ${summary.total_results}\n` +
`🏆 Quality score: ${summary.quality_score}/100\n` +
`📈 Average relevance: ${Math.round(summary.avg_score * 100)}%\n` +
`🌐 Top domains: ${summary.top_domains
?.map((d) => d.domain)
.join(", ")}\n` +
`📝 Result types: ${summary.result_types?.join(", ")}`
);
case "posts":
return (
`📱 Social Posts Summary:\n` +
`📊 Post count: ${summary.post_count}\n` +
`📝 Average length: ${summary.avg_length} characters\n` +
`❤️ Total engagement: ${summary.engagement_total}\n` +
`🖼️ Posts with media: ${summary.has_media_count}\n` +
`👥 Unique authors: ${summary.authors}\n` +
`📋 Post types: ${summary.post_types?.join(", ")}`
);
default:
return JSON.stringify(summary, null, 2);
}
}
function formatElementClickResult(result, metadata) {
return (
`✅ Successfully clicked element: ${
result.element_name || result.element_id
}\n` +
`Click type: ${result.click_type || "left"}\n\n${JSON.stringify(
metadata,
null,
2
)}`
);
}
function formatElementFillResult(result, metadata) {
// Enhanced formatting for anti-detection bypass methods
const methodEmojis = {
twitter_direct_bypass: "🐦 Twitter Direct Bypass",
linkedin_direct_bypass: "💼 LinkedIn Direct Bypass",
facebook_direct_bypass: "📘 Facebook Direct Bypass",
generic_direct_bypass: "🎯 Generic Direct Bypass",
standard_fill: "🔧 Standard Fill",
anti_detection_bypass: "🛡️ Anti-Detection Bypass",
};
const methodDisplay = methodEmojis[result.method] || result.method;
const successIcon = result.success ? "✅" : "❌";
let fillResult = `${successIcon} Element fill ${
result.success ? "completed" : "failed"
} using ${methodDisplay}\n`;
fillResult += `📝 Target: ${result.element_name || result.element_id}\n`;
fillResult += `💬 Input: "${result.value}"\n`;
if (result.actual_value) {
fillResult += `📄 Result: "${result.actual_value}"\n`;
}
// Add bypass-specific information
if (
result.method?.includes("bypass") &&
result.execCommand_result !== undefined
) {
fillResult += `🔧 execCommand success: ${result.execCommand_result}\n`;
}
if (!result.success && result.method?.includes("bypass")) {
fillResult += `\n⚠️ Direct bypass failed - page may have enhanced detection. Try refreshing the page.\n`;
}
return `${fillResult}\n${JSON.stringify(metadata, null, 2)}`;
}
// Enhanced fallback tools when extension is not connected
function getFallbackTools() {
return [
{
name: "page_analyze",
description: "Analyze current page structure (Extension required)",
description:
"🎯 Analyze page structure with anti-detection bypass (Extension required)",
inputSchema: {
type: "object",
properties: {
intent_hint: { type: "string", description: "What user wants to do" }
intent_hint: {
type: "string",
description:
"What user wants to do: post_tweet, search, login, etc.",
},
phase: {
type: "string",
enum: ["discover", "detailed"],
default: "discover",
description:
"Analysis phase: 'discover' for quick scan, 'detailed' for full analysis",
},
},
required: ["intent_hint"]
}
required: ["intent_hint"],
},
},
{
name: "page_extract_content",
description: "Extract structured content (Extension required)",
description:
"📄 Extract structured content with smart summarization (Extension required)",
inputSchema: {
type: "object",
properties: {
content_type: { type: "string", enum: ["article", "search_results", "posts"] }
content_type: {
type: "string",
enum: ["article", "search_results", "posts"],
description: "Type of content to extract",
},
summarize: {
type: "boolean",
default: true,
description:
"Return summary instead of full content (saves tokens)",
},
},
required: ["content_type"]
}
required: ["content_type"],
},
},
{
name: "element_click",
description: "Click page elements (Extension required)",
description:
"🖱️ Click page elements with smart targeting (Extension required)",
inputSchema: {
type: "object",
properties: {
element_id: { type: "string", description: "Element ID from page_analyze" }
element_id: {
type: "string",
description: "Element ID from page_analyze",
},
click_type: {
type: "string",
enum: ["left", "right", "double"],
default: "left",
},
},
required: ["element_id"]
}
required: ["element_id"],
},
},
{
name: "element_fill",
description: "Fill input fields (Extension required)",
description:
"✍️ Fill input fields with anti-detection bypass for Twitter/X, LinkedIn, Facebook (Extension required)",
inputSchema: {
type: "object",
properties: {
element_id: { type: "string", description: "Element ID" },
value: { type: "string", description: "Text to input" }
element_id: {
type: "string",
description: "Element ID from page_analyze",
},
value: {
type: "string",
description: "Text to input",
},
clear_first: {
type: "boolean",
default: true,
description: "Clear existing content before filling",
},
},
required: ["element_id", "value"]
}
required: ["element_id", "value"],
},
},
{
name: "page_navigate",
description: "Navigate to URLs (Extension required)",
description:
"🧭 Navigate to URLs with wait conditions (Extension required)",
inputSchema: {
type: "object",
properties: {
url: { type: "string", description: "URL to navigate to" }
url: { type: "string", description: "URL to navigate to" },
wait_for: {
type: "string",
description: "CSS selector to wait for after navigation",
},
},
required: ["url"]
}
required: ["url"],
},
},
{
name: "page_wait_for",
description: "Wait for elements (Extension required)",
description: "Wait for elements or conditions (Extension required)",
inputSchema: {
type: "object",
properties: {
condition_type: { type: "string", enum: ["element_visible", "text_present"] }
condition_type: {
type: "string",
enum: ["element_visible", "text_present"],
description: "Type of condition to wait for",
},
selector: {
type: "string",
description: "CSS selector (for element_visible condition)",
},
text: {
type: "string",
description: "Text to wait for (for text_present condition)",
},
},
required: ["condition_type"]
}
required: ["condition_type"],
},
},
{
name: "get_analytics",
description: "📊 Get performance analytics and token usage metrics",
inputSchema: {
type: "object",
properties: {},
additionalProperties: false,
},
},
{
name: "clear_analytics",
description: "🗑️ Clear analytics data and reset performance tracking",
inputSchema: {
type: "object",
properties: {},
additionalProperties: false,
},
},
{
name: "browser_navigate",
description: "Navigate to URLs - legacy (Extension required)",
description: "🌐 Navigate to URLs - legacy (Extension required)",
inputSchema: {
type: "object",
properties: {
url: { type: "string", description: "URL to navigate to" }
url: { type: "string", description: "URL to navigate to" },
},
required: ["url"]
}
required: ["url"],
},
},
{
name: "browser_execute_script",
description: "Execute JavaScript (Extension required - limited by CSP)",
description:
"⚡ Execute JavaScript (Extension required - limited by CSP)",
inputSchema: {
type: "object",
properties: {
code: { type: "string", description: "JavaScript code" }
code: { type: "string", description: "JavaScript code" },
},
required: ["code"]
}
}
required: ["code"],
},
},
];
}
@@ -357,8 +594,14 @@ wss.on("connection", (ws) => {
if (message.type === "register") {
availableTools = message.tools;
console.error(`✅ Registered ${availableTools.length} browser tools from extension`);
console.error(`Tools: ${availableTools.map(t => t.name).join(', ')}`);
console.error(
`✅ Registered ${availableTools.length} browser tools from extension`
);
console.error(
`🎯 Enhanced tools with anti-detection bypass: ${availableTools
.map((t) => t.name)
.join(", ")}`
);
} else if (message.type === "ping") {
// Respond to ping with pong
ws.send(JSON.stringify({ type: "pong", timestamp: Date.now() }));
@@ -420,14 +663,25 @@ app.get("/health", (req, res) => {
status: "ok",
chromeExtensionConnected: chromeExtensionSocket !== null,
availableTools: availableTools.length,
features: [
"Anti-detection bypass for Twitter/X, LinkedIn, Facebook",
"Two-phase intelligent page analysis",
"Smart content extraction with summarization",
"Element state detection and interaction readiness",
"Performance analytics and token optimization",
],
});
});
app.listen(3001, () => {
console.error("🎯 Enhanced Browser MCP Server with Anti-Detection Features");
console.error(
"Health check endpoint available at http://localhost:3001/health"
);
});
console.error("Browser MCP Server started");
console.error("Waiting for Chrome Extension connection on ws://localhost:3000");
console.error("🚀 Enhanced Browser MCP Server started");
console.error(
"🔌 Waiting for Chrome Extension connection on ws://localhost:3000"
);
console.error("🎯 Features: Anti-detection bypass + intelligent automation");