|
8 | 8 | CallToolResult,
|
9 | 9 | Tool,
|
10 | 10 | ListResourcesRequestSchema,
|
11 |
| - ListResourceTemplatesRequestSchema |
| 11 | + ListResourceTemplatesRequestSchema, |
| 12 | + ReadResourceRequestSchema |
12 | 13 | } from "@modelcontextprotocol/sdk/types.js";
|
13 | 14 |
|
14 | 15 | import { Stagehand } from "@browserbasehq/stagehand";
|
@@ -134,28 +135,18 @@ const TOOLS: Tool[] = [
|
134 | 135 | },
|
135 | 136 | {
|
136 | 137 | name: "screenshot",
|
137 |
| - description: "Take a screenshot of the current page. Use this tool to learn where you are on the page when controlling the browser with Stagehand.", |
| 138 | + description: "Takes a screenshot of the current page. Use this tool to learn where you are on the page when controlling the browser with Stagehand. Only use this tool when the other tools are not sufficient to get the information you need.", |
138 | 139 | inputSchema: {
|
139 | 140 | type: "object",
|
140 |
| - properties: { |
141 |
| - fullPage: { |
142 |
| - type: "boolean", |
143 |
| - description: "Whether to take a screenshot of the full page (true) or just the visible viewport (false). Default is false." |
144 |
| - }, |
145 |
| - path: { |
146 |
| - type: "string", |
147 |
| - description: "Optional. Custom file path where the screenshot should be saved. If not provided, a default path will be used." |
148 |
| - } |
149 |
| - } |
| 141 | + properties: {}, |
150 | 142 | },
|
151 | 143 | },
|
152 | 144 | ];
|
153 |
| - |
154 | 145 | // Global state
|
155 | 146 | let stagehand: Stagehand | undefined;
|
156 | 147 | let serverInstance: Server | undefined;
|
157 |
| -const consoleLogs: string[] = []; |
158 | 148 | const operationLogs: string[] = [];
|
| 149 | +const screenshots = new Map<string, string>(); |
159 | 150 |
|
160 | 151 | function log(message: string, level: 'info' | 'error' | 'debug' = 'info') {
|
161 | 152 | const timestamp = new Date().toISOString();
|
@@ -401,34 +392,33 @@ async function handleToolCall(
|
401 | 392 |
|
402 | 393 | case "screenshot":
|
403 | 394 | try {
|
404 |
| - const fullPage = args.fullPage === true; |
405 |
| - |
406 |
| - // Create a screenshots directory next to the logs directory |
407 |
| - const SCREENSHOTS_DIR = path.join(__dirname, '../screenshots'); |
408 |
| - if (!fs.existsSync(SCREENSHOTS_DIR)) { |
409 |
| - fs.mkdirSync(SCREENSHOTS_DIR, { recursive: true }); |
410 |
| - } |
| 395 | + |
| 396 | + const screenshotBuffer = await stagehand.page.screenshot({ |
| 397 | + fullPage: false |
| 398 | + }); |
411 | 399 |
|
412 |
| - // Generate a filename based on timestamp if path not provided |
413 |
| - const screenshotPath = args.path || path.join(SCREENSHOTS_DIR, `screenshot-${new Date().toISOString().replace(/:/g, '-')}.png`); |
| 400 | + // Convert buffer to base64 string and store in memory |
| 401 | + const screenshotBase64 = screenshotBuffer.toString('base64'); |
| 402 | + const name = `screenshot-${new Date().toISOString().replace(/:/g, '-')}`; |
| 403 | + screenshots.set(name, screenshotBase64); |
414 | 404 |
|
415 |
| - // If a custom path is provided, ensure its directory exists |
416 |
| - if (args.path) { |
417 |
| - const customDir = path.dirname(screenshotPath); |
418 |
| - if (!fs.existsSync(customDir)) { |
419 |
| - fs.mkdirSync(customDir, { recursive: true }); |
420 |
| - } |
| 405 | + //notify the client that the resources changed |
| 406 | + if (serverInstance) { |
| 407 | + serverInstance.notification({ |
| 408 | + method: "notifications/resources/list_changed", |
| 409 | + }); |
421 | 410 | }
|
422 | 411 |
|
423 |
| - // Take the screenshot |
424 |
| - // making fullpage false temporarily |
425 |
| - await stagehand.page.screenshot({ path: screenshotPath, fullPage: false }); |
426 |
| - |
427 | 412 | return {
|
428 | 413 | content: [
|
429 | 414 | {
|
430 | 415 | type: "text",
|
431 |
| - text: `Screenshot taken and saved to: ${screenshotPath}`, |
| 416 | + text: `Screenshot taken with name: ${name}`, |
| 417 | + }, |
| 418 | + { |
| 419 | + type: "image", |
| 420 | + data: screenshotBase64, |
| 421 | + mimeType: "image/png", |
432 | 422 | },
|
433 | 423 | ],
|
434 | 424 | isError: false,
|
@@ -536,8 +526,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
536 | 526 | server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
|
537 | 527 | try {
|
538 | 528 | logRequest('ListResources', request.params);
|
539 |
| - // Return an empty list since we don't have any resources defined |
540 |
| - const response = { resources: [] }; |
| 529 | + const response = { |
| 530 | + resources: [ |
| 531 | + ...Array.from(screenshots.keys()).map((name) => ({ |
| 532 | + uri: `screenshot://${name}`, |
| 533 | + mimeType: "image/png", |
| 534 | + name: `Screenshot: ${name}`, |
| 535 | + })), |
| 536 | + ] |
| 537 | + }; |
541 | 538 | const sanitizedResponse = sanitizeMessage(response);
|
542 | 539 | logResponse('ListResources', JSON.parse(sanitizedResponse));
|
543 | 540 | return JSON.parse(sanitizedResponse);
|
@@ -571,6 +568,28 @@ server.setRequestHandler(ListResourceTemplatesRequestSchema, async (request) =>
|
571 | 568 | }
|
572 | 569 | });
|
573 | 570 |
|
| 571 | +server.setRequestHandler(ReadResourceRequestSchema, async (request) => { |
| 572 | + const uri = request.params.uri.toString(); |
| 573 | + |
| 574 | + if (uri.startsWith("screenshot://")) { |
| 575 | + const name = uri.split("://")[1]; |
| 576 | + const screenshot = screenshots.get(name); |
| 577 | + if (screenshot) { |
| 578 | + return { |
| 579 | + contents: [ |
| 580 | + { |
| 581 | + uri, |
| 582 | + mimeType: "image/png", |
| 583 | + blob: screenshot, |
| 584 | + }, |
| 585 | + ], |
| 586 | + }; |
| 587 | + } |
| 588 | + } |
| 589 | + |
| 590 | + throw new Error(`Resource not found: ${uri}`); |
| 591 | +}); |
| 592 | + |
574 | 593 | // Run the server
|
575 | 594 | async function runServer() {
|
576 | 595 | const transport = new StdioServerTransport();
|
|
0 commit comments