Skip to content

Commit 7cd4b80

Browse files
authored
Merge pull request #38 from tkattkat/stagehand-mcp---add-screenshots-as-a-resource
stagehand mcp - add screenshots as a resource
2 parents 78b783d + b089bf6 commit 7cd4b80

File tree

2 files changed

+57
-43
lines changed

2 files changed

+57
-43
lines changed

stagehand/README.md

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,14 +64,9 @@ A Model Context Protocol (MCP) server that provides AI-powered web automation ca
6464

6565
### Resources
6666

67-
The server provides access to two types of resources:
67+
The server provides access to one resource:
6868

69-
1. **Console Logs** (`console://logs`)
70-
71-
- Browser console output in text format
72-
- Includes all console messages from the browser
73-
74-
2. **Screenshots** (`screenshot://<name>`)
69+
**Screenshots** (`screenshot://<name>`)
7570
- PNG images of captured screenshots
7671
- Accessible via the screenshot name specified during capture
7772

stagehand/src/index.ts

Lines changed: 55 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import {
88
CallToolResult,
99
Tool,
1010
ListResourcesRequestSchema,
11-
ListResourceTemplatesRequestSchema
11+
ListResourceTemplatesRequestSchema,
12+
ReadResourceRequestSchema
1213
} from "@modelcontextprotocol/sdk/types.js";
1314

1415
import { Stagehand } from "@browserbasehq/stagehand";
@@ -134,28 +135,18 @@ const TOOLS: Tool[] = [
134135
},
135136
{
136137
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.",
138139
inputSchema: {
139140
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: {},
150142
},
151143
},
152144
];
153-
154145
// Global state
155146
let stagehand: Stagehand | undefined;
156147
let serverInstance: Server | undefined;
157-
const consoleLogs: string[] = [];
158148
const operationLogs: string[] = [];
149+
const screenshots = new Map<string, string>();
159150

160151
function log(message: string, level: 'info' | 'error' | 'debug' = 'info') {
161152
const timestamp = new Date().toISOString();
@@ -401,34 +392,33 @@ async function handleToolCall(
401392

402393
case "screenshot":
403394
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+
});
411399

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);
414404

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+
});
421410
}
422411

423-
// Take the screenshot
424-
// making fullpage false temporarily
425-
await stagehand.page.screenshot({ path: screenshotPath, fullPage: false });
426-
427412
return {
428413
content: [
429414
{
430415
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",
432422
},
433423
],
434424
isError: false,
@@ -536,8 +526,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
536526
server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
537527
try {
538528
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+
};
541538
const sanitizedResponse = sanitizeMessage(response);
542539
logResponse('ListResources', JSON.parse(sanitizedResponse));
543540
return JSON.parse(sanitizedResponse);
@@ -571,6 +568,28 @@ server.setRequestHandler(ListResourceTemplatesRequestSchema, async (request) =>
571568
}
572569
});
573570

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+
574593
// Run the server
575594
async function runServer() {
576595
const transport = new StdioServerTransport();

0 commit comments

Comments
 (0)