Skip to content

Commit 6b7e95c

Browse files
committed
setup sse
1 parent 02e5901 commit 6b7e95c

11 files changed

+388
-211
lines changed

browserbase/cli.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import './dist/program.js';

browserbase/config.d.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
export type Config = {
2+
browserbaseApiKey?: string;
3+
browserbaseProjectId?: string;
4+
/**
5+
* Whether or not to use Browserbase proxies
6+
* https://docs.browserbase.com/features/proxies
7+
*
8+
* @default false
9+
*/
10+
proxies? : boolean;
11+
/**
12+
* Potential Browserbase Context to use
13+
* Would be a context ID
14+
*/
15+
context?: string;
16+
/**
17+
* Whether or not to port to a server
18+
*
19+
*/
20+
server?: {
21+
/**
22+
* The port to listen on for SSE or MCP transport.
23+
*/
24+
port?: number;
25+
/**
26+
* The host to bind the server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.
27+
*/
28+
host?: string;
29+
},
30+
};

browserbase/index.d.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
2+
3+
import type { Config } from './config';
4+
5+
export declare function createServer(config?: Config): Promise<Server>;
6+
export {};

browserbase/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import { createServer } from './dist/index.js';
2+
export default { createServer };

browserbase/package-lock.json

+34-8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

browserbase/package.json

+16-3
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
"homepage": "https://modelcontextprotocol.io",
88
"bugs": "https://github.com/modelcontextprotocol/servers/issues",
99
"type": "module",
10-
"bin": {
11-
"mcp-server-browserbase": "dist/index.js"
10+
"engines": {
11+
"node": ">=18"
1212
},
1313
"files": [
1414
"dist"
@@ -19,17 +19,30 @@
1919
"watch": "tsc --watch",
2020
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
2121
},
22+
"exports": {
23+
"./package.json": "./package.json",
24+
".": {
25+
"types": "./index.d.ts",
26+
"default": "./index.js"
27+
}
28+
},
2229
"dependencies": {
2330
"@browserbasehq/sdk": "^2.0.0",
2431
"@modelcontextprotocol/sdk": "^1.10.2",
2532
"@types/yaml": "^1.9.6",
33+
"commander": "^13.1.0",
34+
"dotenv": "^16.5.0",
2635
"playwright": "^1.53.0-alpha-2025-05-05",
2736
"puppeteer-core": "^23.9.0",
2837
"yaml": "^2.7.1",
29-
"zod": "^3.24.3"
38+
"zod": "^3.24.3",
39+
"zod-to-json-schema": "^3.24.5"
3040
},
3141
"devDependencies": {
3242
"shx": "^0.3.4",
3343
"typescript": "^5.6.2"
44+
},
45+
"bin": {
46+
"mcp-server-browserbase": "cli.js"
3447
}
3548
}

browserbase/src/index.ts

+117-55
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import dotenv from "dotenv";
55
dotenv.config();
66

77
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8-
import { createServer } from "./server.js";
9-
import { resolveConfig, type CLIOptions } from "./config.js";
8+
import { Config } from "../config.js";
109
import type { Tool } from "./tools/tool.js";
1110

1211
import navigate from "./tools/navigate.js";
@@ -18,8 +17,13 @@ import common from "./tools/common.js";
1817
import drag from "./tools/drag.js";
1918
import hover from "./tools/hover.js";
2019
import selectOption from "./tools/selectOption.js";
21-
import context from "./tools/context.js";
20+
import contextTools from "./tools/context.js";
2221
import cookies from "./tools/cookies.js";
22+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
23+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
24+
import { z } from "zod";
25+
import { zodToJsonSchema } from "zod-to-json-schema";
26+
import { Context } from "./context.js";
2327

2428
// Environment variables configuration
2529
const requiredEnvVars = {
@@ -32,15 +36,28 @@ Object.entries(requiredEnvVars).forEach(([name, value]) => {
3236
if (!value) throw new Error(`${name} environment variable is required`);
3337
});
3438

35-
const serverVersion = "0.5.1";
36-
37-
async function main() {
38-
const cliOptions: CLIOptions = {};
39-
const config = await resolveConfig(cliOptions);
39+
// const serverVersion = "0.5.1";
4040

41+
export async function createServer(config: Config): Promise<Server> {
4142
// Assume true for captureSnapshot for keyboard, adjust if needed
4243
const captureSnapshotFlag = true;
4344

45+
// Create the server
46+
const server = new Server(
47+
{ name: "mcp-server-browserbase", version: "0.5.1" },
48+
{
49+
capabilities: {
50+
resources: { list: true, read: true },
51+
tools: { list: true, call: true },
52+
prompts: { list: true, get: true },
53+
notifications: { resources: { list_changed: true } },
54+
},
55+
}
56+
);
57+
58+
// Create the context, passing server instance and config
59+
const context = new Context(server, config);
60+
4461
const tools: Tool<any>[] = [
4562
...common,
4663
...snapshot,
@@ -51,58 +68,103 @@ async function main() {
5168
...getText, // Spread the array exported by getText.ts
5269
...navigate, // Spread the array exported by navigate.ts
5370
session, // Add the single tool object directly
54-
...context,
71+
...contextTools,
5572
...cookies,
5673
];
5774

58-
const toolsToUse = tools;
75+
const toolsMap = new Map(tools.map(tool => [tool.schema.name, tool]));
76+
// --- Setup Request Handlers ---
5977

60-
const server = createServer(
61-
{
62-
name: "Browserbase",
63-
version: serverVersion,
64-
tools: toolsToUse,
65-
},
66-
config
67-
);
68-
69-
const signals: NodeJS.Signals[] = ["SIGINT", "SIGTERM"];
70-
signals.forEach((signal) => {
71-
process.on(signal, async () => {
72-
console.error(`
73-
Received ${signal}. Shutting down gracefully...`);
74-
try {
75-
await server.close();
76-
console.error("Server closed.");
77-
} catch (shutdownError) {
78-
console.error("Error during shutdown:", shutdownError);
79-
} finally {
80-
process.exit(0);
81-
}
82-
});
78+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
79+
return { resources: context.listResources() };
8380
});
8481

85-
try {
86-
const transport = new StdioServerTransport();
87-
await server.connect(transport);
88-
console.log("Browserbase MCP server connected via stdio.");
89-
} catch (error) {
90-
console.error("Failed to connect server:", error);
91-
process.exit(1);
92-
}
93-
}
94-
95-
main().catch((err) => {
96-
console.error("Error starting server:", err);
97-
process.exit(1);
98-
});
82+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
83+
try {
84+
const resourceContent = context.readResource(request.params.uri.toString());
85+
return { contents: [resourceContent] };
86+
} catch (error) {
87+
console.error(`Error reading resource via context: ${error}`);
88+
throw error;
89+
}
90+
});
9991

100-
process.on("uncaughtException", (err) => {
101-
console.error("Unhandled exception:", err);
102-
process.exit(1);
103-
});
92+
server.setRequestHandler(ListToolsRequestSchema, async () => {
93+
return {
94+
tools: tools.map(tool => {
95+
let finalInputSchema;
96+
// Check if inputSchema is a Zod schema before converting
97+
if (tool.schema.inputSchema instanceof z.Schema) {
98+
// Add type assertion to help compiler
99+
finalInputSchema = zodToJsonSchema(tool.schema.inputSchema as any);
100+
} else if (typeof tool.schema.inputSchema === 'object' && tool.schema.inputSchema !== null) {
101+
// Assume it's already a valid JSON schema object
102+
finalInputSchema = tool.schema.inputSchema;
103+
} else {
104+
// Fallback or error handling if schema is neither
105+
console.error(`Warning: Tool '${tool.schema.name}' has an unexpected inputSchema type.`);
106+
finalInputSchema = { type: "object" }; // Default to empty object schema
107+
}
108+
109+
return {
110+
name: tool.schema.name,
111+
description: tool.schema.description,
112+
inputSchema: finalInputSchema,
113+
};
114+
}),
115+
};
116+
});
104117

105-
process.on("unhandledRejection", (reason, promise) => {
106-
console.error("Unhandled Rejection at:", promise, "reason:", reason);
107-
process.exit(1);
108-
});
118+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
119+
const logError = (message: string) => {
120+
// Ensure error logs definitely go to stderr
121+
process.stderr.write(`[server.ts Error] ${new Date().toISOString()} ${message}\\n`);
122+
};
123+
124+
const errorResult = (...messages: string[]) => {
125+
const result = {
126+
content: [{ type: 'text', text: messages.join('\\n') }],
127+
isError: true,
128+
};
129+
logError(`Returning error: ${JSON.stringify(result)}`); // Log the error structure
130+
return result;
131+
};
132+
133+
// Use the map built from the passed-in tools
134+
const tool = toolsMap.get(request.params.name);
135+
136+
if (!tool) {
137+
// Use the explicit error logger
138+
logError(`Tool "${request.params.name}" not found.`);
139+
// Check if it was a placeholder tool that wasn't implemented
140+
// This requires access to the original placeholder definitions,
141+
// maybe pass placeholder names/schemas separately or handle in Context?
142+
// For now, just return not found.
143+
return errorResult(`Tool "${request.params.name}" not found`);
144+
}
145+
146+
try {
147+
// Delegate execution to the context
148+
const result = await context.run(tool, request.params.arguments ?? {});
149+
// Log the successful result structure just before returning
150+
process.stderr.write(`[server.ts Success] ${new Date().toISOString()} Returning result for ${request.params.name}: ${JSON.stringify(result)}\\n`);
151+
return result;
152+
} catch (error) {
153+
// Use the explicit error logger
154+
const errorMessage = error instanceof Error ? error.message : String(error);
155+
logError(`Error running tool ${request.params.name} via context: ${errorMessage}`);
156+
logError(`Original error stack (if available): ${error instanceof Error ? error.stack : 'N/A'}`); // Log stack trace
157+
return errorResult(`Failed to run tool '${request.params.name}': ${errorMessage}`);
158+
}
159+
});
160+
161+
// Wrap server close to also close context
162+
const originalClose = server.close.bind(server);
163+
server.close = async () => {
164+
await context.close();
165+
await originalClose();
166+
};
167+
168+
// Return the configured server instance, DO NOT connect here
169+
return server;
170+
}

0 commit comments

Comments
 (0)