Skip to content

Commit dd35634

Browse files
add context handler
1 parent 8633f55 commit dd35634

File tree

6 files changed

+293
-6
lines changed

6 files changed

+293
-6
lines changed

browserbase/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,33 @@ The server communicates over stdio according to the Model Context Protocol.
4040
* `Dockerfile`: For building a Docker image
4141
* Configuration files (`.json`, `.ts`, `.mjs`, `.npmignore`)
4242

43+
## Contexts for Persistence
44+
45+
This server supports Browserbase's Contexts feature, which allows persisting cookies, authentication, and cached data across browser sessions:
46+
47+
1. **Creating a Context**:
48+
```
49+
browserbase_create_context: Creates a new context, optionally with a friendly name
50+
```
51+
52+
2. **Using a Context with a Session**:
53+
```
54+
browserbase_create_session: Now accepts a 'context' parameter with:
55+
- id: The context ID to use
56+
- name: Alternative to ID, the friendly name of the context
57+
- persist: Whether to save changes (cookies, cache) back to the context (default: true)
58+
```
59+
60+
3. **Deleting a Context**:
61+
```
62+
browserbase_delete_context: Deletes a context when you no longer need it
63+
```
64+
65+
Contexts make it much easier to:
66+
- Maintain login state across sessions
67+
- Reduce page load times by preserving cache
68+
- Avoid CAPTCHAs and detection by reusing browser fingerprints
69+
4370
## TODO
4471
4572
* Implement true `ref`-based interaction logic for click, type, drag, hover, select_option.

browserbase/package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

browserbase/src/sessionManager.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,34 @@ const defaultSessionId = "default"; // Consistent ID for the default session
1717
// Function to create a new browser session
1818
async function createNewBrowserSession(
1919
newSessionId: string,
20+
options?: {
21+
contextId?: string;
22+
persistContext?: boolean;
23+
}
2024
): Promise<BrowserSession> {
2125
console.error(`Creating new browser session with ID: ${newSessionId}`);
2226
const bb = new Browserbase({
2327
apiKey: process.env.BROWSERBASE_API_KEY!,
2428
});
2529

26-
const session = await bb.sessions.create({
30+
// Prepare session creation options
31+
const sessionOptions: any = {
2732
projectId: process.env.BROWSERBASE_PROJECT_ID!,
2833
proxies: true, // Consider making configurable
29-
});
34+
};
35+
36+
// Add context settings if provided
37+
if (options?.contextId) {
38+
sessionOptions.browserSettings = {
39+
context: {
40+
id: options.contextId,
41+
persist: options.persistContext !== false, // Default to true if not specified
42+
},
43+
};
44+
console.error(`Using context: ${options.contextId} with persist: ${options.persistContext !== false}`);
45+
}
46+
47+
const session = await bb.sessions.create(sessionOptions);
3048
console.error("Browserbase session created:", session.id);
3149

3250
const browser = await chromium.connectOverCDP(session.connectUrl);

browserbase/src/tools/context.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2+
import { Browserbase } from "@browserbasehq/sdk";
3+
4+
// Store contexts in memory
5+
// In a production app, these should be persisted to a database
6+
const contexts = new Map<string, string>();
7+
8+
export async function handleCreateContext(args: any): Promise<CallToolResult> {
9+
try {
10+
const bb = new Browserbase({
11+
apiKey: process.env.BROWSERBASE_API_KEY!,
12+
});
13+
14+
console.error("Creating new Browserbase context");
15+
const context = await bb.contexts.create({
16+
projectId: process.env.BROWSERBASE_PROJECT_ID!,
17+
});
18+
19+
console.error(`Successfully created context: ${context.id}`);
20+
21+
// Store context ID with optional name if provided
22+
const contextName = args.name || context.id;
23+
contexts.set(contextName, context.id);
24+
25+
return {
26+
content: [
27+
{
28+
type: "text",
29+
text: `Created new Browserbase context with ID: ${context.id}${args.name ? ` and name: ${args.name}` : ''}`,
30+
},
31+
],
32+
isError: false,
33+
};
34+
} catch (error) {
35+
console.error(
36+
`Failed to create Browserbase context: ${(error as Error).message}`
37+
);
38+
return {
39+
content: [
40+
{
41+
type: "text",
42+
text: `Failed to create Browserbase context: ${(error as Error).message}`,
43+
},
44+
],
45+
isError: true,
46+
};
47+
}
48+
}
49+
50+
export async function handleDeleteContext(args: any): Promise<CallToolResult> {
51+
try {
52+
if (!args.contextId && !args.name) {
53+
return {
54+
content: [{ type: "text", text: "Missing required argument: either contextId or name must be provided" }],
55+
isError: true,
56+
};
57+
}
58+
59+
const bb = new Browserbase({
60+
apiKey: process.env.BROWSERBASE_API_KEY!,
61+
});
62+
63+
// Resolve context ID either directly or by name
64+
let contextId = args.contextId;
65+
if (!contextId && args.name) {
66+
contextId = contexts.get(args.name);
67+
if (!contextId) {
68+
return {
69+
content: [{ type: "text", text: `Context with name "${args.name}" not found` }],
70+
isError: true,
71+
};
72+
}
73+
}
74+
75+
console.error(`Deleting Browserbase context: ${contextId}`);
76+
77+
// Delete from Browserbase API
78+
// The SDK may not have a delete method directly, so we use the REST API
79+
const response = await fetch(`https://api.browserbase.com/v1/contexts/${contextId}`, {
80+
method: 'DELETE',
81+
headers: {
82+
'X-BB-API-Key': process.env.BROWSERBASE_API_KEY!,
83+
},
84+
});
85+
86+
if (response.status !== 204) {
87+
const errorText = await response.text();
88+
throw new Error(`Failed to delete context with status ${response.status}: ${errorText}`);
89+
}
90+
91+
// Remove from local store
92+
if (args.name) {
93+
contexts.delete(args.name);
94+
}
95+
96+
// Delete by ID too (in case it was stored multiple ways)
97+
for (const [name, id] of contexts.entries()) {
98+
if (id === contextId) {
99+
contexts.delete(name);
100+
}
101+
}
102+
103+
console.error(`Successfully deleted context: ${contextId}`);
104+
return {
105+
content: [
106+
{
107+
type: "text",
108+
text: `Deleted Browserbase context with ID: ${contextId}`,
109+
},
110+
],
111+
isError: false,
112+
};
113+
} catch (error) {
114+
console.error(
115+
`Failed to delete Browserbase context: ${(error as Error).message}`
116+
);
117+
return {
118+
content: [
119+
{
120+
type: "text",
121+
text: `Failed to delete Browserbase context: ${(error as Error).message}`,
122+
},
123+
],
124+
isError: true,
125+
};
126+
}
127+
}
128+
129+
// Helper function to get a context ID from name or direct ID
130+
export function getContextId(nameOrId: string): string | undefined {
131+
// First check if it's a direct context ID
132+
if (nameOrId.length > 20) { // Assumption: context IDs are long strings
133+
return nameOrId;
134+
}
135+
136+
// Otherwise, look it up by name
137+
return contexts.get(nameOrId);
138+
}

browserbase/src/tools/definitions.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,38 @@ import { Tool } from "@modelcontextprotocol/sdk/types.js";
22

33
// Tool Definitions
44
export const TOOLS: Tool[] = [
5+
{
6+
name: "browserbase_create_context",
7+
description: "Create a new Browserbase context for reusing cookies, authentication, and cached data across browser sessions",
8+
inputSchema: {
9+
type: "object",
10+
properties: {
11+
name: {
12+
type: "string",
13+
description: "Optional friendly name to reference this context later (otherwise, you'll need to use the returned ID)",
14+
},
15+
},
16+
required: [],
17+
},
18+
},
19+
{
20+
name: "browserbase_delete_context",
21+
description: "Delete a Browserbase context when you no longer need it",
22+
inputSchema: {
23+
type: "object",
24+
properties: {
25+
contextId: {
26+
type: "string",
27+
description: "The context ID to delete (required if name not provided)",
28+
},
29+
name: {
30+
type: "string",
31+
description: "The friendly name of the context to delete (required if contextId not provided)",
32+
},
33+
},
34+
required: [],
35+
},
36+
},
537
{
638
// Kept as browserbase_* as it's specific to this multi-session implementation
739
name: "browserbase_create_session",
@@ -14,6 +46,25 @@ export const TOOLS: Tool[] = [
1446
description:
1547
"A unique ID for the session (optional, uses a generated ID if not provided)",
1648
},
49+
context: {
50+
type: "object",
51+
properties: {
52+
id: {
53+
type: "string",
54+
description: "The context ID to use for this session (to reuse cookies and cache)",
55+
},
56+
name: {
57+
type: "string",
58+
description: "The friendly name of the context to use (alternative to id)",
59+
},
60+
persist: {
61+
type: "boolean",
62+
description: "Whether to save changes to the context. Set to true to store cookies and cache for future use.",
63+
default: true,
64+
},
65+
},
66+
description: "Context settings to enable persistence of cookies, authentication, and cached data across sessions",
67+
},
1768
},
1869
required: [],
1970
},

browserbase/src/tools/handlers.ts

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; // Needed fo
1818

1919
// Import specific tool handlers
2020
import { handleNavigate } from "./navigate.js";
21+
import { handleCreateContext, handleDeleteContext, getContextId } from "./context.js";
2122
import {
2223
handleSnapshot,
2324
handleTakeScreenshot,
@@ -62,6 +63,15 @@ export async function handleToolCall(
6263
let sessionObj: BrowserSession | null = null;
6364
const targetSessionId = args.sessionId || defaultSessionId;
6465

66+
// --- Context management tools (require no session) ---
67+
if (name === "browserbase_create_context") {
68+
return handleCreateContext(args);
69+
}
70+
71+
if (name === "browserbase_delete_context") {
72+
return handleDeleteContext(args);
73+
}
74+
6575
// --- browserbase_create_session ---
6676
if (name === "browserbase_create_session") {
6777
const newSessionId = args.sessionId || `session_${Date.now()}`;
@@ -78,15 +88,48 @@ export async function handleToolCall(
7888
isError: false,
7989
};
8090
}
81-
await createNewBrowserSession(newSessionId);
91+
92+
// Check for context settings
93+
const contextSettings = args.context;
94+
let contextId: string | undefined;
95+
96+
if (contextSettings) {
97+
// Get context ID either directly or by name
98+
if (contextSettings.id) {
99+
contextId = contextSettings.id;
100+
} else if (contextSettings.name) {
101+
contextId = getContextId(contextSettings.name);
102+
if (!contextId) {
103+
return {
104+
content: [
105+
{
106+
type: "text",
107+
text: `Context with name "${contextSettings.name}" not found`,
108+
},
109+
],
110+
isError: true,
111+
};
112+
}
113+
}
114+
}
115+
116+
// Pass context settings to session creation
117+
const sessionOptions = {
118+
contextId,
119+
persistContext: contextSettings?.persist !== false, // Default to true if not specified
120+
};
121+
122+
await createNewBrowserSession(newSessionId, sessionOptions);
82123
// Note: We don't need to update defaultBrowserSession here as
83124
// createNewBrowserSession doesn't automatically set the default.
84125
console.error(`Successfully created session: ${newSessionId}`);
85126
return {
86127
content: [
87128
{
88129
type: "text",
89-
text: `Created new browser session with ID: ${newSessionId}`,
130+
text: `Created new browser session with ID: ${newSessionId}${
131+
contextId ? ` using context: ${contextId}` : ''
132+
}`,
90133
},
91134
],
92135
isError: false,
@@ -153,8 +196,6 @@ export async function handleToolCall(
153196

154197
// --- Execute Tool Logic ---
155198
switch (name) {
156-
// browserbase_create_session handled above
157-
158199
case "browserbase_navigate":
159200
return handleNavigate(page, args, targetSessionId);
160201

0 commit comments

Comments
 (0)