MCP Screenshot Server
by sethbang
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import puppeteer from 'puppeteer';
import path from 'path';
import fs from 'fs';
interface ScreenshotOptions {
url: string;
width?: number;
height?: number;
fullPage?: boolean;
outputPath?: string;
}
class ScreenshotServer {
private server: Server;
private outputDir: string;
constructor() {
this.server = new Server(
{
name: 'screenshot-server',
version: '0.1.0',
},
{
capabilities: {
tools: {
take_screenshot: {
name: 'take_screenshot',
description: 'Capture a screenshot of any web page or local GUI',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'URL to capture (can be http://, https://, or file:///)',
},
width: {
type: 'number',
description: 'Viewport width in pixels',
minimum: 1,
maximum: 3840,
},
height: {
type: 'number',
description: 'Viewport height in pixels',
minimum: 1,
maximum: 2160,
},
fullPage: {
type: 'boolean',
description: 'Capture full scrollable page',
},
outputPath: {
type: 'string',
description: 'Custom output path (optional)',
},
},
required: ['url'],
},
},
},
},
}
);
// Create Screenshots directory in project directory
const projectDir = path.dirname(new URL(import.meta.url).pathname);
console.error(`Project directory: ${projectDir}`);
this.outputDir = path.join(projectDir, '..', 'Screenshots');
console.error(`Creating Screenshots directory at: ${this.outputDir}`);
if (!fs.existsSync(this.outputDir)) {
fs.mkdirSync(this.outputDir, { recursive: true });
}
console.error(`Screenshots directory created/verified at: ${this.outputDir}`);
this.setupToolHandlers();
// Error handling
this.server.onerror = (error) => console.error('[MCP Error]', error);
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'take_screenshot',
description: 'Capture a screenshot of any web page or local GUI',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'URL to capture (can be http://, https://, or file:///)',
},
width: {
type: 'number',
description: 'Viewport width in pixels',
minimum: 1,
maximum: 3840,
},
height: {
type: 'number',
description: 'Viewport height in pixels',
minimum: 1,
maximum: 2160,
},
fullPage: {
type: 'boolean',
description: 'Capture full scrollable page',
},
outputPath: {
type: 'string',
description: 'Custom output path (optional)',
},
},
required: ['url'],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== 'take_screenshot') {
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
const options = request.params.arguments as unknown as ScreenshotOptions;
if (!options?.url) {
throw new McpError(
ErrorCode.InvalidParams,
'URL is required'
);
}
try {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
const page = await browser.newPage();
// Set viewport if dimensions provided
if (options.width && options.height) {
await page.setViewport({
width: options.width,
height: options.height,
});
}
// Navigate to URL
await page.goto(options.url, {
waitUntil: 'networkidle0',
timeout: 30000,
});
// Generate filename with timestamp
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const filename = `screenshot-${timestamp}.png`;
// If outputPath is provided, ensure it's relative to current working directory
const outputPath = options.outputPath
? path.join(process.cwd(), options.outputPath)
: path.join(this.outputDir, filename);
// Ensure output directory exists
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Take screenshot
await page.screenshot({
path: outputPath,
fullPage: options.fullPage || false,
});
await browser.close();
// Return path relative to current working directory
const relativePath = path.relative(process.cwd(), outputPath);
return {
content: [
{
type: 'text',
text: `Screenshot saved to: ${relativePath}`,
},
],
};
} catch (error: any) {
return {
content: [
{
type: 'text',
text: `Screenshot error: ${error.message}`,
},
],
isError: true,
};
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Screenshot MCP server running on stdio');
}
}
const server = new ScreenshotServer();
server.run().catch(console.error);