Skip to content

Commit f957409

Browse files
Merge pull request #53 from browserbase/fm/stg-365-add-cookies-and-context
Fm/stg 365 add cookies and context
2 parents e979bc1 + 71ac0e3 commit f957409

File tree

6 files changed

+739
-3
lines changed

6 files changed

+739
-3
lines changed

browserbase/README.md

+76
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,79 @@ to provide browser automation tools.
2222
```bash
2323
node dist/index.js
2424
```
25+
26+
The server communicates over stdio according to the Model Context Protocol.
27+
28+
## Structure
29+
30+
* `src/`: TypeScript source code
31+
* `index.ts`: Main entry point, env checks, shutdown
32+
* `server.ts`: MCP Server setup and request routing
33+
* `sessionManager.ts`: Handles Browserbase session creation/management
34+
* `tools/`: Tool definitions and implementations
35+
* `resources/`: Resource (screenshot) handling
36+
* `types.ts`: Shared TypeScript types
37+
* `dist/`: Compiled JavaScript output
38+
* `tests/`: Placeholder for tests
39+
* `utils/`: Placeholder for utility scripts
40+
* `Dockerfile`: For building a Docker image
41+
* Configuration files (`.json`, `.ts`, `.mjs`, `.npmignore`)
42+
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_context_create: Creates a new context, optionally with a friendly name
50+
```
51+
52+
2. **Using a Context with a Session**:
53+
```
54+
browserbase_session_create: 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_context_delete: 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+
70+
## Cookie Management
71+
72+
This server also provides direct cookie management capabilities:
73+
74+
1. **Adding Cookies**:
75+
```
76+
browserbase_cookies_add: Add cookies to the current browser session with full control over properties
77+
```
78+
79+
2. **Getting Cookies**:
80+
```
81+
browserbase_cookies_get: View all cookies in the current session (optionally filtered by URLs)
82+
```
83+
84+
3. **Deleting Cookies**:
85+
```
86+
browserbase_cookies_delete: Delete specific cookies or clear all cookies from the session
87+
```
88+
89+
These tools are useful for:
90+
- Setting authentication cookies without navigating to login pages
91+
- Backing up and restoring cookie state
92+
- Debugging cookie-related issues
93+
- Manipulating cookie attributes (expiration, security flags, etc.)
94+
95+
## TODO
96+
97+
* Implement true `ref`-based interaction logic for click, type, drag, hover, select_option.
98+
* Implement element-specific screenshots using `ref`.
99+
* Add more standard Playwright MCP tools (tabs, navigation, etc.).
100+
* Add tests.

browserbase/package-lock.json

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

browserbase/src/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import common from "./tools/common.js";
1818
import drag from "./tools/drag.js";
1919
import hover from "./tools/hover.js";
2020
import selectOption from "./tools/selectOption.js";
21+
import context from "./tools/context.js";
22+
import cookies from "./tools/cookies.js";
2123

2224
// Environment variables configuration
2325
const requiredEnvVars = {
@@ -49,6 +51,8 @@ async function main() {
4951
...getText, // Spread the array exported by getText.ts
5052
...navigate, // Spread the array exported by navigate.ts
5153
session, // Add the single tool object directly
54+
...context,
55+
...cookies,
5256
];
5357

5458
const toolsToUse = tools;

browserbase/src/sessionManager.ts

+22-3
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ export function getActiveSessionId(): string { // Added 'export function'
4848
// Function to create a new Browserbase session and connect Playwright
4949
export async function createNewBrowserSession(
5050
newSessionId: string,
51-
config: Config // Accept config object
51+
config: Config, // Accept config object
52+
options?: {
53+
contextId?: string;
54+
persistContext?: boolean;
55+
}
5256
): Promise<BrowserSession> {
5357
// Add runtime checks here (SHOULD ALREADY EXIST from manual edit)
5458
if (!config.browserbaseApiKey) {
@@ -63,11 +67,26 @@ export async function createNewBrowserSession(
6367
apiKey: config.browserbaseApiKey!,
6468
});
6569

66-
const session = await bb.sessions.create({
70+
// Prepare session creation options
71+
const sessionOptions: any = {
6772
// Use non-null assertion after check
6873
projectId: config.browserbaseProjectId!,
6974
proxies: true, // Consider making this configurable via Config
70-
});
75+
};
76+
77+
// Add context settings if provided
78+
if (options?.contextId) {
79+
sessionOptions.browserSettings = {
80+
context: {
81+
id: options.contextId,
82+
persist: options.persistContext !== false, // Default to true if not specified
83+
},
84+
};
85+
console.error(`Using context: ${options.contextId} with persist: ${options.persistContext !== false}`);
86+
}
87+
88+
const session = await bb.sessions.create(sessionOptions);
89+
console.error("Browserbase session created:", session.id);
7190

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

browserbase/src/tools/context.ts

+193
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import { z } from "zod";
2+
import type { Tool, ToolSchema, ToolContext, ToolResult } from "./tool.js";
3+
import { createSuccessResult, createErrorResult } from "./toolUtils.js";
4+
import type { Context } from "../context.js";
5+
import type { ToolActionResult } from "../context.js";
6+
import { Browserbase } from "@browserbasehq/sdk";
7+
8+
// Store contexts in memory
9+
// In a production app, these should be persisted to a database
10+
const contexts = new Map<string, string>();
11+
12+
// --- Tool: Create Context ---
13+
const CreateContextInputSchema = z.object({
14+
name: z
15+
.string()
16+
.optional()
17+
.describe("Optional friendly name to reference this context later (otherwise, you'll need to use the returned ID)"),
18+
});
19+
type CreateContextInput = z.infer<typeof CreateContextInputSchema>;
20+
21+
const createContextSchema: ToolSchema<typeof CreateContextInputSchema> = {
22+
name: "browserbase_context_create",
23+
description: "Create a new Browserbase context for reusing cookies, authentication, and cached data across browser sessions",
24+
inputSchema: CreateContextInputSchema,
25+
};
26+
27+
async function handleCreateContext(
28+
context: Context,
29+
params: CreateContextInput
30+
): Promise<ToolResult> {
31+
try {
32+
const config = context.getConfig();
33+
34+
if (!config.browserbaseApiKey || !config.browserbaseProjectId) {
35+
throw new Error("Browserbase API Key or Project ID is missing in the configuration");
36+
}
37+
38+
const bb = new Browserbase({
39+
apiKey: config.browserbaseApiKey,
40+
});
41+
42+
console.error("Creating new Browserbase context");
43+
const bbContext = await bb.contexts.create({
44+
projectId: config.browserbaseProjectId,
45+
});
46+
47+
console.error(`Successfully created context: ${bbContext.id}`);
48+
49+
// Store context ID with optional name if provided
50+
const contextName = params.name || bbContext.id;
51+
contexts.set(contextName, bbContext.id);
52+
53+
const result: ToolActionResult = {
54+
content: [
55+
{
56+
type: "text",
57+
text: `Created new Browserbase context with ID: ${bbContext.id}${params.name ? ` and name: ${params.name}` : ''}`,
58+
},
59+
],
60+
};
61+
62+
return {
63+
resultOverride: result,
64+
code: [],
65+
captureSnapshot: false,
66+
waitForNetwork: false,
67+
};
68+
} catch (error: any) {
69+
console.error(`CreateContext handle failed: ${error.message || error}`);
70+
throw new Error(`Failed to create Browserbase context: ${error.message || error}`);
71+
}
72+
}
73+
74+
// --- Tool: Delete Context ---
75+
const DeleteContextInputSchema = z.object({
76+
contextId: z
77+
.string()
78+
.optional()
79+
.describe("The context ID to delete (required if name not provided)"),
80+
name: z
81+
.string()
82+
.optional()
83+
.describe("The friendly name of the context to delete (required if contextId not provided)"),
84+
});
85+
type DeleteContextInput = z.infer<typeof DeleteContextInputSchema>;
86+
87+
const deleteContextSchema: ToolSchema<typeof DeleteContextInputSchema> = {
88+
name: "browserbase_context_delete",
89+
description: "Delete a Browserbase context when you no longer need it",
90+
inputSchema: DeleteContextInputSchema,
91+
};
92+
93+
async function handleDeleteContext(
94+
context: Context,
95+
params: DeleteContextInput
96+
): Promise<ToolResult> {
97+
try {
98+
const config = context.getConfig();
99+
100+
if (!config.browserbaseApiKey) {
101+
throw new Error("Browserbase API Key is missing in the configuration");
102+
}
103+
104+
if (!params.contextId && !params.name) {
105+
throw new Error("Missing required argument: either contextId or name must be provided");
106+
}
107+
108+
// Resolve context ID either directly or by name
109+
let contextId = params.contextId;
110+
if (!contextId && params.name) {
111+
contextId = contexts.get(params.name);
112+
if (!contextId) {
113+
throw new Error(`Context with name "${params.name}" not found`);
114+
}
115+
}
116+
117+
console.error(`Deleting Browserbase context: ${contextId}`);
118+
119+
// Delete from Browserbase API
120+
// The SDK may not have a delete method directly, so we use the REST API
121+
const response = await fetch(`https://api.browserbase.com/v1/contexts/${contextId}`, {
122+
method: 'DELETE',
123+
headers: {
124+
'X-BB-API-Key': config.browserbaseApiKey,
125+
},
126+
});
127+
128+
if (response.status !== 204) {
129+
const errorText = await response.text();
130+
throw new Error(`Failed to delete context with status ${response.status}: ${errorText}`);
131+
}
132+
133+
// Remove from local store
134+
if (params.name) {
135+
contexts.delete(params.name);
136+
}
137+
138+
// Delete by ID too (in case it was stored multiple ways)
139+
for (const [name, id] of contexts.entries()) {
140+
if (id === contextId) {
141+
contexts.delete(name);
142+
}
143+
}
144+
145+
console.error(`Successfully deleted context: ${contextId}`);
146+
147+
const result: ToolActionResult = {
148+
content: [
149+
{
150+
type: "text",
151+
text: `Deleted Browserbase context with ID: ${contextId}`,
152+
},
153+
],
154+
};
155+
156+
return {
157+
resultOverride: result,
158+
code: [],
159+
captureSnapshot: false,
160+
waitForNetwork: false,
161+
};
162+
} catch (error: any) {
163+
console.error(`DeleteContext handle failed: ${error.message || error}`);
164+
throw new Error(`Failed to delete Browserbase context: ${error.message || error}`);
165+
}
166+
}
167+
168+
// Helper function to get a context ID from name or direct ID (exported for use by session.ts)
169+
export function getContextId(nameOrId: string): string | undefined {
170+
// First check if it's a direct context ID
171+
if (nameOrId.length > 20) { // Assumption: context IDs are long strings
172+
return nameOrId;
173+
}
174+
175+
// Otherwise, look it up by name
176+
return contexts.get(nameOrId);
177+
}
178+
179+
// Define tools
180+
const createContextTool: Tool<typeof CreateContextInputSchema> = {
181+
capability: "core",
182+
schema: createContextSchema,
183+
handle: handleCreateContext,
184+
};
185+
186+
const deleteContextTool: Tool<typeof DeleteContextInputSchema> = {
187+
capability: "core",
188+
schema: deleteContextSchema,
189+
handle: handleDeleteContext,
190+
};
191+
192+
// Export as an array of tools
193+
export default [createContextTool, deleteContextTool];

0 commit comments

Comments
 (0)