Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,115 @@ Browser MCP is an MCP server + Chrome extension that allows you to automate your
- 👤 Logged In: Uses your existing browser profile, keeping you logged into all your services.
- 🥷🏼 Stealth: Avoids basic bot detection and CAPTCHAs by using your real browser fingerprint.

## Authentication

Browser MCP now supports authentication using a user-generated token (up to 64 characters). This token must be set both on the server and in the browser extension.

### Setting the Token

- **On the server:**
- Pass the token as a CLI argument: `--token <your-token>`
- Or set the environment variable: `BROWSER_MCP_AUTH_TOKEN=<your-token>`
- **In the browser extension:**
- After enabling the extension, you will be prompted to enter the same token. Enter the exact token you used for the server.

### How it works
- The browser extension will send the token to the server when connecting.
- The server will only accept connections and requests from extensions that provide the correct token.
- If the token is missing or incorrect, the connection will be rejected.

**Note:**
- The token can be any string up to 64 characters. It is recommended to use a strong, random value.
- Keep your token secret. Anyone with access to the token can control your browser via Browser MCP.

## Setting Environment Variables for Chrome (Browser Extension)

To allow the Browser MCP Chrome extension to read your authentication token, you may need to set an environment variable for Chrome. This is especially useful if the extension is configured to read the token from your environment.

### On macOS/Linux:

1. Open your terminal.
2. Start Chrome with the environment variable set:

```sh
BROWSER_MCP_AUTH_TOKEN=<your-token> open -a "/Applications/Google Chrome.app"
```
Or, if you use `google-chrome` from the command line:
```sh
BROWSER_MCP_AUTH_TOKEN=<your-token> google-chrome
```

### On Windows:

1. Open Command Prompt (cmd.exe).
2. Run:
```cmd
set BROWSER_MCP_AUTH_TOKEN=<your-token>
start chrome
```

> **Note:**
> Replace `<your-token>` with your actual token. This ensures Chrome (and the extension) can access the token from the environment.

## Example: MCP Client Configuration

To use Browser MCP with an MCP-compatible client (such as Cursor, VS Code, or other tools), add the following to your MCP client configuration file (e.g., `.mcp.json` or `settings.json`):

```json
{
"mcpServers": {
"browsermcp": {
"command": "npx",
"args": ["@browsermcp/mcp@latest", "--token", "<your-token>"]
}
}
}
```

- Replace `<your-token>` with your chosen authentication token (up to 64 characters).
- This will ensure the MCP client starts the Browser MCP server with the correct token.

## Extension Options: Token and Port Override

The Browser MCP Chrome extension now includes an options page where you can:
- Set your authentication token (up to 64 characters).
- Set a port override (if you want to use a port other than the default 9234).

To access the options page:
1. Go to `chrome://extensions/` in Chrome.
2. Find "Browser MCP" and click "Details".
3. Click "Extension options" or "Options".
4. Enter your token and (optionally) the port, then save.

The extension will use these settings for all future connections.

## Extension Capabilities

The extension now reports its capabilities to the server and clients, including:
- Extension name and version
- Supported features: `token-auth`, `port-override`, `capabilities-report`

You (or your client) can request these capabilities via a message to the extension, or they will be sent automatically after authentication.

## Example: MCP Client Configuration (with Port Override)

If you want to specify a custom port, update your MCP client configuration as follows:

```json
{
"mcpServers": {
"browsermcp": {
"command": "npx",
"args": ["@browsermcp/mcp@latest", "--token", "<your-token>", "--port", "<your-port>"]
}
}
}
```
- Replace `<your-token>` with your authentication token.
- Replace `<your-port>` with your chosen port (if overriding the default).

If you do not specify a port, the default (9234) will be used.

## Contributing

This repo contains all the core MCP code for Browser MCP, but currently cannot yet be built on its own due to dependencies on utils and types from the monorepo where it's developed.
Expand Down
3 changes: 3 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"options_page": "options.html"
}
32 changes: 32 additions & 0 deletions options.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Browser MCP Extension Options</title>
<style>
body { font-family: Arial, sans-serif; margin: 2em; }
label { display: block; margin-top: 1em; }
input[type="text"], input[type="number"] { width: 300px; }
button { margin-top: 1em; }
</style>
</head>
<body>
<h2>Browser MCP Extension Settings</h2>
<form id="options-form">
<label>
Authentication Token (up to 64 characters):
<input type="text" id="token" maxlength="64" required />
</label>
<label>
Port Override (optional):
<input type="number" id="port" min="1" max="65535" placeholder="Default: 9234" />
</label>
<div>
<button type="submit">Save</button>
<button type="button" id="restore">Restore Defaults</button>
</div>
</form>
<div id="status"></div>
<script src="options.js"></script>
</body>
</html>
10 changes: 10 additions & 0 deletions src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const noConnectionMessage = `No connection to browser extension. In order to pro

export class Context {
private _ws: WebSocket | undefined;
private _authenticated: boolean = false;

get ws(): WebSocket {
if (!this._ws) {
Expand All @@ -25,6 +26,14 @@ export class Context {
return !!this._ws;
}

setAuthenticated(auth: boolean) {
this._authenticated = auth;
}

isAuthenticated(): boolean {
return this._authenticated;
}

async sendSocketMessage<T extends MessageType<SocketMessageMap>>(
type: T,
payload: MessagePayload<SocketMessageMap, T>,
Expand All @@ -48,5 +57,6 @@ export class Context {
return;
}
await this._ws.close();
this._authenticated = false;
}
}
38 changes: 25 additions & 13 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,38 @@ const snapshotTools: Tool[] = [

const resources: Resource[] = [];

async function createServer(): Promise<Server> {
return createServerWithTools({
name: appConfig.name,
version: packageJSON.version,
tools: snapshotTools,
resources,
});
}
let authToken: string | undefined = process.env.BROWSER_MCP_AUTH_TOKEN;

/**
* Note: Tools must be defined *before* calling `createServer` because only declarations are hoisted, not the initializations
*/
program
.option('-t, --token <token>', 'Authentication token (up to 64 characters)')
.version("Version " + packageJSON.version)
.name(packageJSON.name)
.action(async () => {
const server = await createServer();
.action(async (options) => {
if (options.token) {
if (options.token.length > 64) {
console.error('Token must be 64 characters or less.');
process.exit(1);
}
authToken = options.token;
}
if (!authToken) {
console.error('Authentication token required. Set via --token or BROWSER_MCP_AUTH_TOKEN.');
process.exit(1);
}
const server = await createServer(authToken);
setupExitWatchdog(server);

const transport = new StdioServerTransport();
await server.connect(transport);
});
program.parse(process.argv);

async function createServer(authToken: string): Promise<Server> {
return createServerWithTools({
name: appConfig.name,
version: packageJSON.version,
tools: snapshotTools,
resources,
authToken,
});
}
54 changes: 48 additions & 6 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ type Options = {
version: string;
tools: Tool[];
resources: Resource[];
authToken: string;
};

export async function createServerWithTools(options: Options): Promise<Server> {
const { name, version, tools, resources } = options;
const { name, version, tools, resources, authToken } = options;
const context = new Context();
const server = new Server(
{ name, version },
Expand All @@ -33,22 +34,61 @@ export async function createServerWithTools(options: Options): Promise<Server> {

const wss = await createWebSocketServer();
wss.on("connection", (websocket) => {
// Close any existing connections
if (context.hasWs()) {
context.ws.close();
}
context.ws = websocket;
let authenticated = false;
const authTimeout = setTimeout(() => {
if (!authenticated) {
websocket.close(4001, "Authentication required");
}
}, 5000);
websocket.once("message", (data) => {
try {
const msg = JSON.parse(data.toString());
if (typeof msg.token === "string" && msg.token === authToken) {
authenticated = true;
clearTimeout(authTimeout);
// Close any existing connections
if (context.hasWs()) {
context.ws.close();
}
context.ws = websocket;
context.setAuthenticated(true);
} else {
websocket.close(4002, "Invalid authentication token");
}
} catch {
websocket.close(4003, "Malformed authentication message");
}
});
websocket.on("close", () => {
context.setAuthenticated(false);
});
});

function ensureAuthenticated() {
if (!context.isAuthenticated()) {
return {
content: [{ type: "text", text: "Not authenticated. Please connect with a valid token." }],
isError: true,
};
}
return null;
}

server.setRequestHandler(ListToolsRequestSchema, async () => {
const err = ensureAuthenticated();
if (err) return err;
return { tools: tools.map((tool) => tool.schema) };
});

server.setRequestHandler(ListResourcesRequestSchema, async () => {
const err = ensureAuthenticated();
if (err) return err;
return { resources: resources.map((resource) => resource.schema) };
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
const err = ensureAuthenticated();
if (err) return err;
const tool = tools.find((tool) => tool.schema.name === request.params.name);
if (!tool) {
return {
Expand All @@ -71,6 +111,8 @@ export async function createServerWithTools(options: Options): Promise<Server> {
});

server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const err = ensureAuthenticated();
if (err) return err;
const resource = resources.find(
(resource) => resource.schema.uri === request.params.uri,
);
Expand Down
46 changes: 46 additions & 0 deletions src/tools/background.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/// <reference types="chrome" />
// Utility to get token and port from storage
function getMcpSettings(callback: (settings: { token: string; port: number }) => void): void {
chrome.storage.local.get(['mcpToken', 'mcpPort'], (items: { [key: string]: any }) => {
callback({
token: items.mcpToken || '',
port: items.mcpPort || 9234 // default port
});
});
}

// Example: Use settings for WebSocket connection
function connectToMcpServer(): void {
getMcpSettings(({ token, port }) => {
const ws = new WebSocket(`ws://localhost:${port}`);
ws.onopen = () => {
ws.send(JSON.stringify({ token }));
// Optionally, send capabilities after auth
ws.send(JSON.stringify({ type: 'capabilities', data: getCapabilities() }));
};
// ... handle other ws events ...
});
}

// Capabilities reporting
type Capabilities = {
extension: string;
version: string;
features: string[];
};

function getCapabilities(): Capabilities {
return {
extension: 'browser-mcp',
version: chrome.runtime.getManifest().version,
features: ['token-auth', 'port-override', 'capabilities-report']
};
}

// Listen for messages (optional, e.g., for getCapabilities)
chrome.runtime.onMessage.addListener((request: any, sender: chrome.runtime.MessageSender, sendResponse: (response?: any) => void) => {
if (request.type === 'getCapabilities') {
sendResponse(getCapabilities());
}
// ... handle other messages ...
});
Loading