├── .gitignore ├── tsconfig.json ├── package.json ├── LICENSE ├── README.md └── src └── index.ts /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "screenshotone-mcp-server", 3 | "version": "1.0.0", 4 | "description": "Render website screenshots of any website and get them as images.", 5 | "main": "build/index.js", 6 | "type": "module", 7 | "scripts": { 8 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [], 12 | "author": "Dmytro Krasun ", 13 | "license": "MIT", 14 | "dependencies": { 15 | "@modelcontextprotocol/sdk": "^1.5.0", 16 | "zod": "^3.24.2" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^22.13.4", 20 | "typescript": "^5.7.3" 21 | }, 22 | "bin": { 23 | "screenshot": "./build/index.js" 24 | }, 25 | "files": [ 26 | "build" 27 | ] 28 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2025 ScreenshotOne.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 21 | OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScreenshotOne MCP Server 2 | 3 | An official implementation of an [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server for [ScreenshotOne](https://screenshotone.com). 4 | 5 | [A few more words about why it was built and some thoughts about the future of MCP](https://screenshotone.com/blog/mcp-server/). 6 | 7 | 8 | ScreenshotOne Server MCP server 9 | 10 | 11 | ## Tools 12 | 13 | - `render-website-screenshot`: Render a screenshot of a website and returns it as an image. 14 | 15 | ## Usage 16 | 17 | ### Build it 18 | 19 | Always install dependencies and build it first: 20 | 21 | ```bash 22 | npm install && npm run build 23 | ``` 24 | 25 | ### Get your ScreenshotOne API key 26 | 27 | Sign up at [ScreenshotOne](https://screenshotone.com) and get your API key. 28 | 29 | ### With Claude for Desktop 30 | 31 | Add the following to your `~/Library/Application\ Support/Claude/claude_desktop_config.json`: 32 | 33 | ```json 34 | { 35 | "mcpServers": { 36 | "screenshotone": { 37 | "command": "node", 38 | "args": ["path/to/screenshotone/mcp/build/index.js"], 39 | "env": { 40 | "SCREENSHOTONE_API_KEY": "" 41 | } 42 | } 43 | } 44 | } 45 | ``` 46 | 47 | ### Standalone or for other projects 48 | 49 | ```bash 50 | SCREENSHOTONE_API_KEY=your_api_key && node build/index.js 51 | ``` 52 | 53 | ## License 54 | 55 | `ScreenshotOne MCP Server` is licensed [under the MIT License](LICENSE). 56 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; 2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 3 | import { z } from "zod"; 4 | 5 | const SCREENSHOTONE_BASE_URL = "https://api.screenshotone.com"; 6 | const apiKey = process.env.SCREENSHOTONE_API_KEY!; 7 | 8 | const server = new McpServer({ 9 | name: "screenshotone", 10 | description: 11 | "Render website screenshots of any website and get them as images.", 12 | version: "1.0.0", 13 | }); 14 | 15 | async function makeScreenshotOneRequest( 16 | url: string 17 | ): Promise { 18 | try { 19 | const response = await fetch(url); 20 | if (!response.ok) { 21 | return { 22 | error: `Failed to render a screenshot status: ${response.status}`, 23 | }; 24 | } 25 | 26 | return (await response.arrayBuffer()) as T; 27 | } catch (error) { 28 | return { 29 | error: `Failed to render a screenshot: ${error}`, 30 | }; 31 | } 32 | } 33 | 34 | server.tool( 35 | "render-website-screenshot", 36 | "Renders a screenshot of a website and returns it as an image or a JSON with the cache URL (preferred for full-page screenshots).", 37 | { 38 | url: z.string().url().describe("URL of the website to screenshot"), 39 | block_banners: z 40 | .boolean() 41 | .default(true) 42 | .describe("Block cookie, GDPR, and other banners and popups"), 43 | block_ads: z.boolean().default(true).describe("Block ads"), 44 | image_quality: z 45 | .number() 46 | .min(1) 47 | .max(100) 48 | .default(80) 49 | .describe("Image quality"), 50 | full_page: z 51 | .boolean() 52 | .default(false) 53 | .describe("Render the full page screenshot of the website"), 54 | response_type: z 55 | .enum(["json", "by_format"]) 56 | .default("by_format") 57 | .describe( 58 | "Response type: JSON (when the cache URL is needed) or the image itself" 59 | ), 60 | cache: z 61 | .boolean() 62 | .default(false) 63 | .describe("Cache the screenshot to get the cache URL"), 64 | cache_key: z 65 | .string() 66 | .regex(/^[a-zA-Z0-9]+$/) 67 | .optional() 68 | .describe( 69 | "Cache key to generate a new cache URL for each screenshot, e.g. timestamp" 70 | ), 71 | }, 72 | async ({ 73 | url, 74 | block_banners, 75 | block_ads, 76 | image_quality, 77 | full_page, 78 | response_type, 79 | cache, 80 | cache_key, 81 | }) => { 82 | let screenshotUrl = `${SCREENSHOTONE_BASE_URL}/take?url=${encodeURIComponent( 83 | url 84 | )}&response_type=${response_type}&cache=${cache}&format=jpeg&image_quality=${image_quality}&access_key=${apiKey}&block_cookie_banners=${block_banners}&block_banners_by_heuristics=${block_banners}&block_ads=${block_ads}&full_page=${full_page}`; 85 | 86 | if (cache && cache_key) { 87 | screenshotUrl += `&cache_key=${cache_key}`; 88 | } 89 | 90 | const screenshot = await makeScreenshotOneRequest( 91 | screenshotUrl 92 | ); 93 | 94 | if ("error" in screenshot) { 95 | return { 96 | content: [ 97 | { 98 | type: "text", 99 | text: `Failed to retrieve screenshot for ${url}: ${screenshot.error}`, 100 | }, 101 | ], 102 | }; 103 | } 104 | 105 | return { 106 | content: [ 107 | { 108 | type: "image", 109 | mimeType: "image/jpeg", 110 | data: Buffer.from(screenshot).toString("base64"), 111 | }, 112 | ], 113 | }; 114 | } 115 | ); 116 | 117 | async function main() { 118 | const transport = new StdioServerTransport(); 119 | await server.connect(transport); 120 | console.error("ScreenshotOneMCP Server running on stdio"); 121 | } 122 | 123 | main().catch((error) => { 124 | console.error("Fatal error in main():", error); 125 | process.exit(1); 126 | }); 127 | --------------------------------------------------------------------------------