Skip to content

Commit 990a8b2

Browse files
authored
fix: clear session screenshots in the mcp (#120)
* ensuring screenshots are deleted by mapping sessionids to the screenshot then deleting them after * changesets * addressing greptile comments
1 parent e567228 commit 990a8b2

File tree

6 files changed

+110
-3
lines changed

6 files changed

+110
-3
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @browserbasehq/mcp-server-browserbase
22

3+
## 2.1.2
4+
5+
### Patch Changes
6+
7+
- fixing screenshot map behavior
8+
39
## 2.1.1
410

511
### Patch Changes

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@browserbasehq/mcp-server-browserbase",
3-
"version": "2.1.1",
3+
"version": "2.1.2",
44
"description": "MCP server for AI web browser automation using Browserbase and Stagehand",
55
"mcpName": "io.github.browserbase/mcp-server-browserbase",
66
"license": "Apache-2.0",

src/mcp/resources.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,39 @@ export const RESOURCE_TEMPLATES = [];
1313
// Store screenshots in a map
1414
export const screenshots = new Map<string, string>();
1515

16+
// Track screenshots by session so we can purge them on session end
17+
// key: sessionId (internal/current session id), value: set of screenshot names
18+
const sessionIdToScreenshotNames = new Map<string, Set<string>>();
19+
20+
export function registerScreenshot(
21+
sessionId: string,
22+
name: string,
23+
base64: string,
24+
) {
25+
screenshots.set(name, base64);
26+
let set = sessionIdToScreenshotNames.get(sessionId);
27+
if (!set) {
28+
set = new Set();
29+
sessionIdToScreenshotNames.set(sessionId, set);
30+
}
31+
set.add(name);
32+
}
33+
34+
export function clearScreenshotsForSession(sessionId: string) {
35+
const set = sessionIdToScreenshotNames.get(sessionId);
36+
if (set) {
37+
for (const name of set) {
38+
screenshots.delete(name);
39+
}
40+
sessionIdToScreenshotNames.delete(sessionId);
41+
}
42+
}
43+
44+
export function clearAllScreenshots() {
45+
screenshots.clear();
46+
sessionIdToScreenshotNames.clear();
47+
}
48+
1649
/**
1750
* Handle listing resources request
1851
* @returns A list of available resources including screenshots

src/sessionManager.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Page, BrowserContext } from "@browserbasehq/stagehand";
22
import type { Config } from "../config.d.ts";
33
import type { Cookie } from "playwright-core";
44
import { createStagehandInstance } from "./stagehandStore.js";
5+
import { clearScreenshotsForSession } from "./mcp/resources.js";
56
import type { BrowserSession } from "./types/types.js";
67

78
// Global state for managing browser sessions
@@ -131,6 +132,21 @@ export async function createNewBrowserSession(
131132
);
132133
setActiveSessionId(defaultSessionId);
133134
}
135+
136+
// Purge any screenshots associated with both internal and Browserbase IDs
137+
try {
138+
clearScreenshotsForSession(newSessionId);
139+
const bbId = browserbaseSessionId;
140+
if (bbId) {
141+
clearScreenshotsForSession(bbId);
142+
}
143+
} catch (err) {
144+
process.stderr.write(
145+
`[SessionManager] WARN - Failed to clear screenshots on disconnect for ${newSessionId}: ${
146+
err instanceof Error ? err.message : String(err)
147+
}\n`,
148+
);
149+
}
134150
});
135151

136152
// Add cookies to the context if they are provided in the config
@@ -192,6 +208,20 @@ async function closeBrowserGracefully(
192208
process.stderr.write(
193209
`[SessionManager] Successfully closed Stagehand and browser for session: ${sessionIdToLog}\n`,
194210
);
211+
// After close, purge any screenshots associated with both internal and Browserbase IDs
212+
try {
213+
clearScreenshotsForSession(sessionIdToLog);
214+
const bbId = session?.stagehand?.browserbaseSessionID;
215+
if (bbId) {
216+
clearScreenshotsForSession(bbId);
217+
}
218+
} catch (err) {
219+
process.stderr.write(
220+
`[SessionManager] WARN - Failed to clear screenshots after close for ${sessionIdToLog}: ${
221+
err instanceof Error ? err.message : String(err)
222+
}\n`,
223+
);
224+
}
195225
} catch (closeError) {
196226
process.stderr.write(
197227
`[SessionManager] WARN - Error closing Stagehand for session ${sessionIdToLog}: ${
@@ -335,6 +365,17 @@ export async function cleanupSession(sessionId: string): Promise<void> {
335365
// Remove from browsers map
336366
browsers.delete(sessionId);
337367

368+
// Always purge screenshots for this (internal) session id
369+
try {
370+
clearScreenshotsForSession(sessionId);
371+
} catch (err) {
372+
process.stderr.write(
373+
`[SessionManager] WARN - Failed to clear screenshots during cleanup for ${sessionId}: ${
374+
err instanceof Error ? err.message : String(err)
375+
}\n`,
376+
);
377+
}
378+
338379
// Clear default session reference if this was the default
339380
if (sessionId === defaultSessionId && defaultBrowserSession) {
340381
defaultBrowserSession = null;

src/stagehandStore.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { randomUUID } from "crypto";
22
import { Stagehand, Page } from "@browserbasehq/stagehand";
33
import { StagehandSession, CreateSessionParams } from "./types/types.js";
44
import type { Config } from "../config.d.ts";
5+
import { clearScreenshotsForSession } from "./mcp/resources.js";
56

67
// Store for all active sessions
78
const store = new Map<string, StagehandSession>();
@@ -110,6 +111,18 @@ export const create = async (
110111
const disconnectHandler = () => {
111112
process.stderr.write(`[StagehandStore] Session disconnected: ${id}\n`);
112113
store.delete(id);
114+
// Purge by internal store ID and Browserbase session ID
115+
try {
116+
clearScreenshotsForSession(id);
117+
const bbId = session.metadata?.bbSessionId;
118+
if (bbId) {
119+
clearScreenshotsForSession(bbId);
120+
}
121+
} catch {
122+
process.stderr.write(
123+
`[StagehandStore] Error clearing screenshots for session ${id}\n`,
124+
);
125+
}
113126
};
114127

115128
browser.on("disconnected", disconnectHandler);
@@ -158,6 +171,18 @@ export const remove = async (id: string): Promise<void> => {
158171

159172
await session.stagehand.close();
160173
process.stderr.write(`[StagehandStore] Session closed: ${id}\n`);
174+
// Purge by internal store ID and Browserbase session ID
175+
try {
176+
clearScreenshotsForSession(id);
177+
const bbId = session.metadata?.bbSessionId;
178+
if (bbId) {
179+
clearScreenshotsForSession(bbId);
180+
}
181+
} catch {
182+
process.stderr.write(
183+
`[StagehandStore] Error clearing screenshots for session ${id}\n`,
184+
);
185+
}
161186
} catch (error) {
162187
process.stderr.write(
163188
`[StagehandStore] Error closing session ${id}: ${

src/tools/screenshot.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { z } from "zod";
22
import type { Tool, ToolSchema, ToolResult } from "./tool.js";
33
import type { Context } from "../context.js";
44
import type { ToolActionResult } from "../types/types.js";
5-
import { screenshots } from "../mcp/resources.js";
5+
import { registerScreenshot } from "../mcp/resources.js";
66

77
const ScreenshotInputSchema = z.object({
88
name: z.string().optional().describe("The name of the screenshot"),
@@ -40,7 +40,9 @@ async function handleScreenshot(
4040
.replace(/:/g, "-")}`
4141
: `screenshot-${new Date().toISOString().replace(/:/g, "-")}` +
4242
context.config.browserbaseProjectId;
43-
screenshots.set(name, screenshotBase64);
43+
// Associate with current session id and store in memory
44+
const sessionId = context.currentSessionId;
45+
registerScreenshot(sessionId, name, screenshotBase64);
4446

4547
// Notify the client that the resources changed
4648
const serverInstance = context.getServer();

0 commit comments

Comments
 (0)