Safari Screenshot MCP Server
by rogerheykoop
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema, Tool } from '@modelcontextprotocol/sdk/types.js';
import { takeScreenshot } from './screenshot.js';
import chalk from 'chalk';
import { z } from 'zod';
interface ViewportSize {
width: number;
height: number;
}
interface ViewportSizes {
[key: string]: ViewportSize;
}
// Common viewport sizes
const VIEWPORT_SIZES: ViewportSizes = {
desktop: { width: 1920, height: 1080 },
laptop: { width: 1366, height: 768 },
'tablet-landscape': { width: 1024, height: 768 },
'tablet-portrait': { width: 768, height: 1024 },
'mobile-large': { width: 428, height: 926 },
'mobile-medium': { width: 390, height: 844 },
'mobile-small': { width: 375, height: 667 },
};
// Define interface for screenshot arguments
interface ScreenshotArgs {
url: string;
outputPath?: string;
width?: number;
height?: number;
waitTime?: number;
zoomLevel?: number;
}
// Type guard for screenshot arguments
function isScreenshotArgs(args: unknown): args is ScreenshotArgs {
return typeof args === 'object' && args !== null && 'url' in args && typeof (args as { url: string }).url === 'string';
}
// Define the tool properly using the Tool type
const SCREENSHOT_TOOL: Tool = {
name: 'take_screenshot',
description: 'Take a screenshot of a webpage using Safari on macOS',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'URL to capture',
},
outputPath: {
type: 'string',
description: 'Path where the screenshot will be saved (default: ./screenshots/[hostname]-[timestamp].png)',
default: '',
},
width: {
type: 'number',
description: 'Window width in pixels (default: 1024)',
default: 1024,
},
height: {
type: 'number',
description: 'Window height in pixels (default: 768)',
default: 768,
},
waitTime: {
type: 'number',
description: 'Time to wait for page load in seconds (default: 3)',
default: 3,
},
zoomLevel: {
type: 'number',
description: 'Zoom level (1 = 100%, 0.5 = 50%, 2 = 200%)',
default: 1,
},
},
required: ['url'],
},
};
// Server implementation
const server = new Server(
{
name: 'safari-screenshot',
version: '1.0.6',
description: 'Take screenshots using Safari on macOS',
},
{
capabilities: {
tools: {
take_screenshot: SCREENSHOT_TOOL,
},
},
}
);
// Tool handlers using the correct schemas
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [SCREENSHOT_TOOL],
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const { name, arguments: args } = request.params;
if (!args) {
throw new Error('No arguments provided');
}
switch (name) {
case 'take_screenshot': {
if (!isScreenshotArgs(args)) {
throw new Error('Invalid arguments for take_screenshot');
}
const outputPath = args.outputPath || `./screenshots/${new URL(args.url).hostname}-${Date.now()}.png`;
const result = await takeScreenshot({
url: args.url,
outputPath,
width: args.width,
height: args.height,
waitTime: args.waitTime,
zoomLevel: args.zoomLevel,
});
return {
content: [
{
type: 'text',
text: `Screenshot saved to: ${result.path}`,
},
],
isError: false,
};
}
default:
return {
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
isError: true,
};
}
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
});
// Wrap server startup in async function
async function runServer() {
try {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Safari Screenshot MCP Server running on stdio');
} catch (error) {
console.error('Fatal error running server:', error);
process.exit(1);
}
}
// Run the server
runServer().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});