diff --git a/package.json b/package.json index 3df77c1..5b11cb8 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,6 @@ "@modelcontextprotocol/sdk": "^1.13.1", "commander": "^14.0.0", "dotenv": "^16.4.6", - "sharp": "^0.33.0", "zod": "^3.25.67" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 942eeb1..b8736f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,9 +25,6 @@ importers: dotenv: specifier: ^16.4.6 version: 16.6.1 - sharp: - specifier: ^0.33.0 - version: 0.33.5 zod: specifier: ^3.25.67 version: 3.25.76 @@ -429,12 +426,6 @@ packages: peerDependencies: zod: ">=3.25.76 <4 || >=4.1 <5" - "@emnapi/runtime@1.7.1": - resolution: - { - integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==, - } - "@esbuild/aix-ppc64@0.25.6": resolution: { @@ -781,168 +772,6 @@ packages: } engines: { node: ">=18.18" } - "@img/sharp-darwin-arm64@0.33.5": - resolution: - { - integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [arm64] - os: [darwin] - - "@img/sharp-darwin-x64@0.33.5": - resolution: - { - integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [x64] - os: [darwin] - - "@img/sharp-libvips-darwin-arm64@1.0.4": - resolution: - { - integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==, - } - cpu: [arm64] - os: [darwin] - - "@img/sharp-libvips-darwin-x64@1.0.4": - resolution: - { - integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==, - } - cpu: [x64] - os: [darwin] - - "@img/sharp-libvips-linux-arm64@1.0.4": - resolution: - { - integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==, - } - cpu: [arm64] - os: [linux] - - "@img/sharp-libvips-linux-arm@1.0.5": - resolution: - { - integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==, - } - cpu: [arm] - os: [linux] - - "@img/sharp-libvips-linux-s390x@1.0.4": - resolution: - { - integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==, - } - cpu: [s390x] - os: [linux] - - "@img/sharp-libvips-linux-x64@1.0.4": - resolution: - { - integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==, - } - cpu: [x64] - os: [linux] - - "@img/sharp-libvips-linuxmusl-arm64@1.0.4": - resolution: - { - integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==, - } - cpu: [arm64] - os: [linux] - - "@img/sharp-libvips-linuxmusl-x64@1.0.4": - resolution: - { - integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==, - } - cpu: [x64] - os: [linux] - - "@img/sharp-linux-arm64@0.33.5": - resolution: - { - integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [arm64] - os: [linux] - - "@img/sharp-linux-arm@0.33.5": - resolution: - { - integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [arm] - os: [linux] - - "@img/sharp-linux-s390x@0.33.5": - resolution: - { - integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [s390x] - os: [linux] - - "@img/sharp-linux-x64@0.33.5": - resolution: - { - integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [x64] - os: [linux] - - "@img/sharp-linuxmusl-arm64@0.33.5": - resolution: - { - integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [arm64] - os: [linux] - - "@img/sharp-linuxmusl-x64@0.33.5": - resolution: - { - integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [x64] - os: [linux] - - "@img/sharp-wasm32@0.33.5": - resolution: - { - integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [wasm32] - - "@img/sharp-win32-ia32@0.33.5": - resolution: - { - integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [ia32] - os: [win32] - - "@img/sharp-win32-x64@0.33.5": - resolution: - { - integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - cpu: [x64] - os: [win32] - "@inquirer/external-editor@1.0.1": resolution: { @@ -1891,19 +1720,6 @@ packages: integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, } - color-string@1.9.1: - resolution: - { - integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==, - } - - color@4.2.3: - resolution: - { - integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==, - } - engines: { node: ">=12.5.0" } - colorette@2.0.20: resolution: { @@ -2112,13 +1928,6 @@ packages: } engines: { node: ">=8" } - detect-libc@2.1.2: - resolution: - { - integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==, - } - engines: { node: ">=8" } - devtools-protocol@0.0.1312386: resolution: { @@ -3103,12 +2912,6 @@ packages: } engines: { node: ">= 0.4" } - is-arrayish@0.3.4: - resolution: - { - integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==, - } - is-async-function@2.1.1: resolution: { @@ -4651,13 +4454,6 @@ packages: integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==, } - sharp@0.33.5: - resolution: - { - integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==, - } - engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } - shebang-command@2.0.0: resolution: { @@ -4729,12 +4525,6 @@ packages: } engines: { node: ">=14" } - simple-swizzle@0.2.4: - resolution: - { - integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==, - } - simple-wcswidth@1.1.2: resolution: { @@ -5806,11 +5596,6 @@ snapshots: dependencies: zod: 3.25.76 - "@emnapi/runtime@1.7.1": - dependencies: - tslib: 2.8.1 - optional: true - "@esbuild/aix-ppc64@0.25.6": optional: true @@ -5958,81 +5743,6 @@ snapshots: "@humanwhocodes/retry@0.4.3": {} - "@img/sharp-darwin-arm64@0.33.5": - optionalDependencies: - "@img/sharp-libvips-darwin-arm64": 1.0.4 - optional: true - - "@img/sharp-darwin-x64@0.33.5": - optionalDependencies: - "@img/sharp-libvips-darwin-x64": 1.0.4 - optional: true - - "@img/sharp-libvips-darwin-arm64@1.0.4": - optional: true - - "@img/sharp-libvips-darwin-x64@1.0.4": - optional: true - - "@img/sharp-libvips-linux-arm64@1.0.4": - optional: true - - "@img/sharp-libvips-linux-arm@1.0.5": - optional: true - - "@img/sharp-libvips-linux-s390x@1.0.4": - optional: true - - "@img/sharp-libvips-linux-x64@1.0.4": - optional: true - - "@img/sharp-libvips-linuxmusl-arm64@1.0.4": - optional: true - - "@img/sharp-libvips-linuxmusl-x64@1.0.4": - optional: true - - "@img/sharp-linux-arm64@0.33.5": - optionalDependencies: - "@img/sharp-libvips-linux-arm64": 1.0.4 - optional: true - - "@img/sharp-linux-arm@0.33.5": - optionalDependencies: - "@img/sharp-libvips-linux-arm": 1.0.5 - optional: true - - "@img/sharp-linux-s390x@0.33.5": - optionalDependencies: - "@img/sharp-libvips-linux-s390x": 1.0.4 - optional: true - - "@img/sharp-linux-x64@0.33.5": - optionalDependencies: - "@img/sharp-libvips-linux-x64": 1.0.4 - optional: true - - "@img/sharp-linuxmusl-arm64@0.33.5": - optionalDependencies: - "@img/sharp-libvips-linuxmusl-arm64": 1.0.4 - optional: true - - "@img/sharp-linuxmusl-x64@0.33.5": - optionalDependencies: - "@img/sharp-libvips-linuxmusl-x64": 1.0.4 - optional: true - - "@img/sharp-wasm32@0.33.5": - dependencies: - "@emnapi/runtime": 1.7.1 - optional: true - - "@img/sharp-win32-ia32@0.33.5": - optional: true - - "@img/sharp-win32-x64@0.33.5": - optional: true - "@inquirer/external-editor@1.0.1(@types/node@24.10.1)": dependencies: chardet: 2.1.0 @@ -6733,16 +6443,6 @@ snapshots: color-name@1.1.4: {} - color-string@1.9.1: - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.4 - - color@4.2.3: - dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 - colorette@2.0.20: {} combined-stream@1.0.8: @@ -6850,8 +6550,6 @@ snapshots: detect-indent@6.1.0: {} - detect-libc@2.1.2: {} - devtools-protocol@0.0.1312386: optional: true @@ -7625,8 +7323,6 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - is-arrayish@0.3.4: {} - is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -8561,32 +8257,6 @@ snapshots: setprototypeof@1.2.0: {} - sharp@0.33.5: - dependencies: - color: 4.2.3 - detect-libc: 2.1.2 - semver: 7.7.2 - optionalDependencies: - "@img/sharp-darwin-arm64": 0.33.5 - "@img/sharp-darwin-x64": 0.33.5 - "@img/sharp-libvips-darwin-arm64": 1.0.4 - "@img/sharp-libvips-darwin-x64": 1.0.4 - "@img/sharp-libvips-linux-arm": 1.0.5 - "@img/sharp-libvips-linux-arm64": 1.0.4 - "@img/sharp-libvips-linux-s390x": 1.0.4 - "@img/sharp-libvips-linux-x64": 1.0.4 - "@img/sharp-libvips-linuxmusl-arm64": 1.0.4 - "@img/sharp-libvips-linuxmusl-x64": 1.0.4 - "@img/sharp-linux-arm": 0.33.5 - "@img/sharp-linux-arm64": 0.33.5 - "@img/sharp-linux-s390x": 0.33.5 - "@img/sharp-linux-x64": 0.33.5 - "@img/sharp-linuxmusl-arm64": 0.33.5 - "@img/sharp-linuxmusl-x64": 0.33.5 - "@img/sharp-wasm32": 0.33.5 - "@img/sharp-win32-ia32": 0.33.5 - "@img/sharp-win32-x64": 0.33.5 - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -8636,10 +8306,6 @@ snapshots: signal-exit@4.1.0: {} - simple-swizzle@0.2.4: - dependencies: - is-arrayish: 0.3.4 - simple-wcswidth@1.1.2: {} slash@3.0.0: {} diff --git a/src/context.ts b/src/context.ts index 00a27e5..b23762d 100644 --- a/src/context.ts +++ b/src/context.ts @@ -2,7 +2,6 @@ import type { Stagehand } from "@browserbasehq/stagehand"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import type { Config } from "../config.d.ts"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; -import { listResources, readResource } from "./mcp/resources.js"; import { SessionManager } from "./sessionManager.js"; import type { MCPTool } from "./types/types.js"; @@ -17,6 +16,7 @@ export class Context { public readonly config: Config; private server: Server; private sessionManager: SessionManager; + private screenshots: Map = new Map(); // currentSessionId is a getter that delegates to SessionManager to ensure synchronization // This prevents desync between Context and SessionManager session tracking @@ -104,12 +104,34 @@ export class Context { } } + /** + * Register a screenshot in this context's storage + */ + registerScreenshot(name: string, data: string): void { + this.screenshots.set(name, data); + } + + /** + * Clear all screenshots in this context + */ + clearScreenshots(): void { + this.screenshots.clear(); + } + /** * List resources * Documentation: https://modelcontextprotocol.io/docs/concepts/resources */ listResources() { - return listResources(); + return { + resources: [ + ...Array.from(this.screenshots.keys()).map((name) => ({ + uri: `screenshot://${name}`, + mimeType: "image/png", + name: `Screenshot: ${name}`, + })), + ], + }; } /** @@ -117,6 +139,22 @@ export class Context { * Documentation: https://modelcontextprotocol.io/docs/concepts/resources */ readResource(uri: string) { - return readResource(uri); + if (uri.startsWith("screenshot://")) { + const name = uri.split("://")[1]; + const screenshot = this.screenshots.get(name); + if (screenshot) { + return { + contents: [ + { + uri, + mimeType: "image/png", + blob: screenshot, + }, + ], + }; + } + } + + throw new Error(`Resource not found: ${uri}`); } } diff --git a/src/index.ts b/src/index.ts index f77c6a4..21b7502 100644 --- a/src/index.ts +++ b/src/index.ts @@ -139,6 +139,17 @@ export default function ({ config }: { config: z.infer }) { const contextId = randomUUID(); const context = new Context(server.server, internalConfig, contextId); + // Cleanup handler for when the MCP connection closes (SHTTP/Smithery) + server.server.onclose = async () => { + try { + await context.getSessionManager().closeAllSessions(); + } catch (err) { + process.stderr.write( + `[Cleanup] Error during session cleanup: ${err instanceof Error ? err.message : String(err)}\n`, + ); + } + }; + server.server.registerCapabilities({ resources: { subscribe: true, diff --git a/src/mcp/resources.ts b/src/mcp/resources.ts index 5b9a327..ce9a64a 100644 --- a/src/mcp/resources.ts +++ b/src/mcp/resources.ts @@ -2,6 +2,9 @@ * Resources module for the Browserbase MCP server * Contains resources definitions and handlers for resource-related requests * Docs: https://modelcontextprotocol.io/docs/concepts/resources + * + * Note: Screenshot storage has been moved to Context class for per-session isolation. + * Each MCP session has its own Context instance with isolated screenshot storage. */ // Define the resources @@ -9,88 +12,3 @@ export const RESOURCES = []; // Define the resource templates export const RESOURCE_TEMPLATES = []; - -// Store screenshots in a map -export const screenshots = new Map(); - -// Track screenshots by session so we can purge them on session end -// key: sessionId (internal/current session id), value: set of screenshot names -const sessionIdToScreenshotNames = new Map>(); - -export function registerScreenshot( - sessionId: string, - name: string, - base64: string, -) { - screenshots.set(name, base64); - let set = sessionIdToScreenshotNames.get(sessionId); - if (!set) { - set = new Set(); - sessionIdToScreenshotNames.set(sessionId, set); - } - set.add(name); -} - -export function clearScreenshotsForSession(sessionId: string) { - const set = sessionIdToScreenshotNames.get(sessionId); - if (set) { - for (const name of set) { - screenshots.delete(name); - } - sessionIdToScreenshotNames.delete(sessionId); - } -} - -export function clearAllScreenshots() { - screenshots.clear(); - sessionIdToScreenshotNames.clear(); -} - -/** - * Handle listing resources request - * @returns A list of available resources including screenshots - */ -export function listResources() { - return { - resources: [ - ...Array.from(screenshots.keys()).map((name) => ({ - uri: `screenshot://${name}`, - mimeType: "image/png", - name: `Screenshot: ${name}`, - })), - ], - }; -} - -/** - * Handle listing resource templates request - * @returns An empty resource templates list response - */ -export function listResourceTemplates() { - return { resourceTemplates: [] }; -} - -/** - * Read a resource by its URI - * @param uri The URI of the resource to read - * @returns The resource content or throws if not found - */ -export function readResource(uri: string) { - if (uri.startsWith("screenshot://")) { - const name = uri.split("://")[1]; - const screenshot = screenshots.get(name); - if (screenshot) { - return { - contents: [ - { - uri, - mimeType: "image/png", - blob: screenshot, - }, - ], - }; - } - } - - throw new Error(`Resource not found: ${uri}`); -} diff --git a/src/sessionManager.ts b/src/sessionManager.ts index 602f869..6eb4ad5 100644 --- a/src/sessionManager.ts +++ b/src/sessionManager.ts @@ -1,6 +1,5 @@ import { Stagehand } from "@browserbasehq/stagehand"; import type { Config } from "../config.d.ts"; -import { clearScreenshotsForSession } from "./mcp/resources.js"; import type { BrowserSession, CreateSessionParams } from "./types/types.js"; import { randomUUID } from "crypto"; @@ -243,16 +242,6 @@ export class SessionManager { process.stderr.write( `[SessionManager] Successfully closed Stagehand and browser for session: ${sessionIdToLog}\n`, ); - // After close, purge any screenshots associated with this session - try { - clearScreenshotsForSession(sessionIdToLog); - } catch (err) { - process.stderr.write( - `[SessionManager] WARN - Failed to clear screenshots after close for ${sessionIdToLog}: ${ - err instanceof Error ? err.message : String(err) - }\n`, - ); - } } catch (closeError) { process.stderr.write( `[SessionManager] WARN - Error closing Stagehand for session ${sessionIdToLog}: ${ diff --git a/src/tools/screenshot.ts b/src/tools/screenshot.ts index facc895..4a30b8b 100644 --- a/src/tools/screenshot.ts +++ b/src/tools/screenshot.ts @@ -1,9 +1,7 @@ import { z } from "zod"; -import sharp from "sharp"; import type { Tool, ToolSchema, ToolResult } from "./tool.js"; import type { Context } from "../context.js"; import type { ToolActionResult } from "../types/types.js"; -import { registerScreenshot } from "../mcp/resources.js"; /** * Screenshot @@ -37,57 +35,12 @@ async function handleScreenshot( } // We're taking a full page screenshot to give context of the entire page, similar to a snapshot - // Enable Page domain if needed - await page.sendCDP("Page.enable"); + const screenshotBuffer = await page.screenshot({ + fullPage: true, + }); - // Use CDP to capture screenshot - const { data } = await page.sendCDP<{ data: string }>( - "Page.captureScreenshot", - { - format: "png", - fromSurface: true, - }, - ); - - // data is already base64 string from CDP - let screenshotBase64 = data; - - // Scale down image if needed for Claude's vision API - // Claude constraints: max 1568px on any edge AND max 1.15 megapixels - // Reference: https://docs.anthropic.com/en/docs/build-with-claude/vision#evaluate-image-size - const imageBuffer = Buffer.from(data, "base64"); - const metadata = await sharp(imageBuffer).metadata(); - - if (metadata.width && metadata.height) { - const pixels = metadata.width * metadata.height; - - // Min of: width constraint, height constraint, and megapixel constraint - const shrink = Math.min( - 1568 / metadata.width, - 1568 / metadata.height, - Math.sqrt((1.15 * 1024 * 1024) / pixels), - ); - - // Only resize if we need to shrink (shrink < 1) - if (shrink < 1) { - const newWidth = Math.floor(metadata.width * shrink); - const newHeight = Math.floor(metadata.height * shrink); - - process.stderr.write( - `[Screenshot] Scaling image from ${metadata.width}x${metadata.height} (${(pixels / (1024 * 1024)).toFixed(2)}MP) to ${newWidth}x${newHeight} (${((newWidth * newHeight) / (1024 * 1024)).toFixed(2)}MP) for Claude vision API\n`, - ); - - const resizedBuffer = await sharp(imageBuffer) - .resize(newWidth, newHeight, { - fit: "inside", - withoutEnlargement: true, - }) - .png() - .toBuffer(); - - screenshotBase64 = resizedBuffer.toString("base64"); - } - } + // Convert buffer to base64 string and store in memory + const screenshotBase64 = screenshotBuffer.toString("base64"); const name = params.name ? `screenshot-${params.name}-${new Date() .toISOString() @@ -95,9 +48,8 @@ async function handleScreenshot( : `screenshot-${new Date().toISOString().replace(/:/g, "-")}` + context.config.browserbaseProjectId; - // Associate with current mcp session id and store in memory /src/mcp/resources.ts - const sessionId = context.currentSessionId; - registerScreenshot(sessionId, name, screenshotBase64); + // Store screenshot in this context (scoped to this MCP session) + context.registerScreenshot(name, screenshotBase64); // Notify the client that the resources changed const serverInstance = context.getServer(); diff --git a/src/transport.ts b/src/transport.ts index 4885bf9..fa886dc 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -53,10 +53,11 @@ async function handleStreamable( sessionIdGenerator: () => sessionId, }); sessions.set(sessionId, transport); + const server = await serverList.create(); transport.onclose = () => { if (transport.sessionId) sessions.delete(transport.sessionId); + serverList.close(server); }; - const server = await serverList.create(); await server.connect(transport); return await transport.handleRequest(req, res); }