@screenshotscout/mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +194 -0
- package/SECURITY.md +21 -0
- package/dist/core/capture-input.d.ts +85 -0
- package/dist/core/capture-input.js +332 -0
- package/dist/core/capture-tool.d.ts +25 -0
- package/dist/core/capture-tool.js +135 -0
- package/dist/core/client.d.ts +18 -0
- package/dist/core/client.js +16 -0
- package/dist/core/errors.d.ts +20 -0
- package/dist/core/errors.js +166 -0
- package/dist/core/image.d.ts +67 -0
- package/dist/core/image.js +387 -0
- package/dist/core/redaction.d.ts +15 -0
- package/dist/core/redaction.js +185 -0
- package/dist/core/result.d.ts +43 -0
- package/dist/core/result.js +140 -0
- package/dist/core/tool-metadata.d.ts +10 -0
- package/dist/core/tool-metadata.js +10 -0
- package/dist/http.d.ts +30 -0
- package/dist/http.js +458 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +10 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +16 -0
- package/dist/stdio.js +157 -0
- package/llms-install.md +114 -0
- package/package.json +103 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oleksii Velykyi
|
|
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, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Screenshot Scout MCP Server
|
|
2
|
+
|
|
3
|
+
Use [Screenshot Scout](https://screenshotscout.com/) from an MCP client to capture
|
|
4
|
+
HTTP or HTTPS webpages as images or PDFs.
|
|
5
|
+
|
|
6
|
+
This server exposes one tool, `capture_screenshot`. It supports full-page and
|
|
7
|
+
element captures, device and viewport controls, location selection, page
|
|
8
|
+
interaction and blocking options, image sizing and quality, PDF layout, caching,
|
|
9
|
+
temporary result URLs and eligible MCP image content.
|
|
10
|
+
|
|
11
|
+
## What you need
|
|
12
|
+
|
|
13
|
+
- A [Screenshot Scout account](https://screenshotscout.com/auth/signup) and an
|
|
14
|
+
access key from the [API keys page](https://screenshotscout.com/app/api-keys).
|
|
15
|
+
- Node.js 22 or newer for npm/stdio installation. Claude Desktop's MCPB runtime
|
|
16
|
+
is bundled by Claude.
|
|
17
|
+
- The optional secret key only when your selected API key requires signed
|
|
18
|
+
Screenshot Scout requests.
|
|
19
|
+
|
|
20
|
+
Every capture uses your Screenshot Scout account and is subject to its plan,
|
|
21
|
+
quota, and rate limits.
|
|
22
|
+
|
|
23
|
+
## Local stdio with npm
|
|
24
|
+
|
|
25
|
+
Start with this local stdio configuration:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"screenshotscout": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["-y", "@screenshotscout/mcp"],
|
|
33
|
+
"env": {
|
|
34
|
+
"SCREENSHOTSCOUT_ACCESS_KEY": "YOUR_ACCESS_KEY"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If the access key requires request signing, add the secret locally:
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
"SCREENSHOTSCOUT_SECRET_KEY": "YOUR_SECRET_KEY"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Keep personal configuration files out of source control. Credentials are process
|
|
48
|
+
environment values, not tool arguments. See
|
|
49
|
+
[client-specific copy-paste configurations](examples/README.md) for Claude
|
|
50
|
+
Desktop, Claude Code, Cursor, VS Code/GitHub Copilot, Devin, and Cline.
|
|
51
|
+
|
|
52
|
+
### Run from a source checkout
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
npm ci
|
|
56
|
+
npm run build
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Point the client at the absolute path to `dist/stdio.js` with `node`, and provide
|
|
60
|
+
the same environment variables shown above.
|
|
61
|
+
|
|
62
|
+
## Claude Desktop MCPB
|
|
63
|
+
|
|
64
|
+
To install the Claude Desktop extension:
|
|
65
|
+
|
|
66
|
+
1. Download `screenshotscout-mcp-<version>.mcpb` from that version's GitHub
|
|
67
|
+
release.
|
|
68
|
+
2. In Claude Desktop, open **Settings → Extensions → Advanced settings** and
|
|
69
|
+
choose **Install Extension…**.
|
|
70
|
+
3. Select the downloaded file.
|
|
71
|
+
4. Enter the required access key. Enter the secret key only for an API key that
|
|
72
|
+
requires signed requests.
|
|
73
|
+
|
|
74
|
+
Claude Desktop treats both fields as sensitive settings. The `v0.1.0` MCPB
|
|
75
|
+
supports Windows.
|
|
76
|
+
|
|
77
|
+
## Hosted Streamable HTTP
|
|
78
|
+
|
|
79
|
+
The hosted API-key endpoint is live at:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
https://mcp.screenshotscout.com/mcp/api-key
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
It is intended only for clients that can attach a static HTTP header:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
Authorization: Bearer YOUR_ACCESS_KEY
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The endpoint accepts an access key only. Never send a Screenshot Scout secret
|
|
92
|
+
key to it, and never put either key in the URL or a tool argument. Clients that
|
|
93
|
+
cannot attach a static Bearer header cannot use this endpoint.
|
|
94
|
+
|
|
95
|
+
API keys that require request signatures must instead use local stdio or MCPB,
|
|
96
|
+
or use a dedicated unsigned access key for the hosted endpoint.
|
|
97
|
+
|
|
98
|
+
## Tool: `capture_screenshot`
|
|
99
|
+
|
|
100
|
+
`capture_screenshot` sends one capture request for the supplied URL and options.
|
|
101
|
+
The target webpage is external, and its returned content must be treated as
|
|
102
|
+
untrusted.
|
|
103
|
+
|
|
104
|
+
### Inputs
|
|
105
|
+
|
|
106
|
+
Only `url` is required. Captures use a 1280×720 viewport by default. When no
|
|
107
|
+
format is specified, the tool returns JPEG at quality 60. `resultMode` defaults
|
|
108
|
+
to `"auto"`.
|
|
109
|
+
|
|
110
|
+
| Group | Inputs |
|
|
111
|
+
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
112
|
+
| Target and output | `url`; `format` (`png`, `jpg`, `jpeg`, `webp`, `gif`, `tiff`, `pdf`); `resultMode` (`auto`, `url_only`) |
|
|
113
|
+
| Location and viewport | `country` (two-letter country code), `device`, `deviceViewportWidth`, `deviceViewportHeight`, `colorScheme` (`auto`, `dark`, `light`), `fullPage` |
|
|
114
|
+
| Page preparation | `blockCookieBanners`, `blockAds`, `blockChatWidgets`, `selector`, `hideSelectors`, `clickSelectors` |
|
|
115
|
+
| Timing | `waitUntil` (`load`, `domcontentloaded`, `networkidle0`, `networkidle2`), `delay` (0–30 seconds), `navigationTimeout` (5–90 seconds), `timeout` (1–240 seconds) |
|
|
116
|
+
| Cache | `cache`, `cacheTtl` (14,400–2,592,000 seconds) |
|
|
117
|
+
| Output resizing | `imageWidth`, `imageHeight` (1–8,192; available for images and PDFs) |
|
|
118
|
+
| Image only | `imageQuality` (0–100, JPEG/WebP only) |
|
|
119
|
+
| PDF only | `pdfPaperFormat` (`letter`, `legal`, `tabloid`, `a4`, `a3`, `content`), `pdfLandscape`, `pdfPrintBackground`, `pdfMargin`, per-side margin fields, `pdfScale` (greater than 0 and at most 3) |
|
|
120
|
+
|
|
121
|
+
When both output dimensions are provided, their product cannot exceed 64,000,000
|
|
122
|
+
pixels. PDF margins accept non-negative values in `px`, `in`, `mm`, or `cm`.
|
|
123
|
+
`imageQuality` requires JPEG or WebP output, and PDF-only options require
|
|
124
|
+
`format: "pdf"`.
|
|
125
|
+
|
|
126
|
+
### Results
|
|
127
|
+
|
|
128
|
+
- PNG, JPEG, WebP, and GIF may be included as MCP image content when
|
|
129
|
+
`resultMode` is `auto`, the MIME type is eligible, dimensions are known and at
|
|
130
|
+
most 8,000 pixels per side, raw data is at most 5 MiB, and the complete
|
|
131
|
+
serialized result fits the current 128,000-byte server limit.
|
|
132
|
+
- A capture that is not eligible to embed remains successful and returns its
|
|
133
|
+
temporary URL plus an actionable omission reason.
|
|
134
|
+
- TIFF is URL-only.
|
|
135
|
+
- PDF bytes are never embedded. A PDF result includes safe text and structured
|
|
136
|
+
metadata, plus a resource link when Screenshot Scout provides a result URL.
|
|
137
|
+
- `resultMode: "url_only"` omits image bytes for every format.
|
|
138
|
+
|
|
139
|
+
MCP clients control whether returned image content or resource links are
|
|
140
|
+
displayed or made available to a model.
|
|
141
|
+
|
|
142
|
+
Structured metadata can include `screenshotUrl`, `screenshotUrlExpiresAt`,
|
|
143
|
+
`cacheStatus`, `format`, `mimeType`, `imageWidth`, `imageHeight`,
|
|
144
|
+
`inlineImageIncluded`, and `inlineImageOmissionReason`.
|
|
145
|
+
|
|
146
|
+
Treat result URLs as sensitive, temporary links and respect their reported
|
|
147
|
+
expiry.
|
|
148
|
+
|
|
149
|
+
## Example prompts
|
|
150
|
+
|
|
151
|
+
- “Capture `https://example.com` as a full-page PNG in dark mode. Return only a
|
|
152
|
+
URL.”
|
|
153
|
+
- “Take a 1280×720 JPEG screenshot of `https://example.com/pricing`, block cookie
|
|
154
|
+
banners and ads, and use quality 80.”
|
|
155
|
+
- “Create an A4 PDF of `https://example.com/report` with backgrounds enabled and
|
|
156
|
+
10 mm margins.”
|
|
157
|
+
|
|
158
|
+
## Privacy and security
|
|
159
|
+
|
|
160
|
+
The server sends the target URL and selected capture options to Screenshot Scout,
|
|
161
|
+
which loads the target website. Review the
|
|
162
|
+
[Screenshot Scout privacy policy](https://screenshotscout.com/privacy-policy)
|
|
163
|
+
before capturing private or regulated material.
|
|
164
|
+
|
|
165
|
+
- Do not capture pages you are not authorized to access.
|
|
166
|
+
- Do not paste credentials into prompts, tool inputs, URLs, issue reports, or
|
|
167
|
+
logs.
|
|
168
|
+
- Keep local access and secret keys in client-managed secret storage or private
|
|
169
|
+
environment configuration.
|
|
170
|
+
- The local stdio server adds no telemetry. Application logging for the hosted
|
|
171
|
+
service is limited to request method, response status, duration, and sanitized
|
|
172
|
+
unexpected errors. It is designed not to include credentials, target URLs,
|
|
173
|
+
screenshot URLs, request or response content, or image bytes.
|
|
174
|
+
- Review each target and capture request before allowing tool use. The tool is
|
|
175
|
+
open-world, consumes quota, and interacts with an external website.
|
|
176
|
+
- Report vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
|
|
177
|
+
|
|
178
|
+
## Development
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
npm ci
|
|
182
|
+
npm run format:check
|
|
183
|
+
npm run lint
|
|
184
|
+
npm run typecheck
|
|
185
|
+
npm test
|
|
186
|
+
npm run metadata:check
|
|
187
|
+
npm run registry:validate
|
|
188
|
+
npm run mcpb:validate
|
|
189
|
+
npm run mcpb:pack
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## License
|
|
193
|
+
|
|
194
|
+
[MIT](LICENSE) © Oleksii Velykyi
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
| Version | Supported |
|
|
6
|
+
| ------- | --------- |
|
|
7
|
+
| 0.1.x | Yes |
|
|
8
|
+
|
|
9
|
+
## Reporting a vulnerability
|
|
10
|
+
|
|
11
|
+
Email suspected vulnerabilities privately to
|
|
12
|
+
[support@screenshotscout.com](mailto:support@screenshotscout.com). Do not open a
|
|
13
|
+
public issue for an undisclosed vulnerability.
|
|
14
|
+
|
|
15
|
+
Never include live Screenshot Scout access keys, secret keys, authorization
|
|
16
|
+
headers, signatures, screenshot URLs, captured content, or other customer data
|
|
17
|
+
in a report. Revoke any credential that may have been exposed and use clearly
|
|
18
|
+
marked synthetic values in reproduction steps.
|
|
19
|
+
|
|
20
|
+
Include the affected version or commit, the observed impact, and minimal
|
|
21
|
+
reproduction details that do not contain secrets.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type BinaryCaptureOptions } from "@screenshotscout/sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export declare const CAPTURE_FORMATS: readonly ["png", "jpg", "jpeg", "webp", "gif", "tiff", "pdf"];
|
|
4
|
+
export declare const IMAGE_CAPTURE_FORMATS: readonly ["png", "jpg", "jpeg", "webp", "gif", "tiff"];
|
|
5
|
+
export declare const CAPTURE_RESULT_MODES: readonly ["auto", "url_only"];
|
|
6
|
+
/**
|
|
7
|
+
* The complete curated MCP input surface. Credentials and SDK-owned
|
|
8
|
+
* `responseType` controls are deliberately absent.
|
|
9
|
+
*/
|
|
10
|
+
export declare const captureScreenshotInputSchema: z.ZodObject<{
|
|
11
|
+
url: z.ZodString;
|
|
12
|
+
format: z.ZodDefault<z.ZodEnum<{
|
|
13
|
+
png: "png";
|
|
14
|
+
jpg: "jpg";
|
|
15
|
+
jpeg: "jpeg";
|
|
16
|
+
webp: "webp";
|
|
17
|
+
gif: "gif";
|
|
18
|
+
tiff: "tiff";
|
|
19
|
+
pdf: "pdf";
|
|
20
|
+
}>>;
|
|
21
|
+
country: z.ZodOptional<z.ZodString>;
|
|
22
|
+
device: z.ZodOptional<z.ZodString>;
|
|
23
|
+
deviceViewportWidth: z.ZodOptional<z.ZodNumber>;
|
|
24
|
+
deviceViewportHeight: z.ZodOptional<z.ZodNumber>;
|
|
25
|
+
colorScheme: z.ZodOptional<z.ZodEnum<{
|
|
26
|
+
auto: "auto";
|
|
27
|
+
dark: "dark";
|
|
28
|
+
light: "light";
|
|
29
|
+
}>>;
|
|
30
|
+
fullPage: z.ZodOptional<z.ZodBoolean>;
|
|
31
|
+
blockCookieBanners: z.ZodOptional<z.ZodBoolean>;
|
|
32
|
+
blockAds: z.ZodOptional<z.ZodBoolean>;
|
|
33
|
+
blockChatWidgets: z.ZodOptional<z.ZodBoolean>;
|
|
34
|
+
selector: z.ZodOptional<z.ZodString>;
|
|
35
|
+
hideSelectors: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
36
|
+
clickSelectors: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
37
|
+
waitUntil: z.ZodOptional<z.ZodEnum<{
|
|
38
|
+
load: "load";
|
|
39
|
+
domcontentloaded: "domcontentloaded";
|
|
40
|
+
networkidle0: "networkidle0";
|
|
41
|
+
networkidle2: "networkidle2";
|
|
42
|
+
}>>;
|
|
43
|
+
delay: z.ZodOptional<z.ZodNumber>;
|
|
44
|
+
navigationTimeout: z.ZodOptional<z.ZodNumber>;
|
|
45
|
+
timeout: z.ZodOptional<z.ZodNumber>;
|
|
46
|
+
cache: z.ZodOptional<z.ZodBoolean>;
|
|
47
|
+
cacheTtl: z.ZodOptional<z.ZodNumber>;
|
|
48
|
+
resultMode: z.ZodDefault<z.ZodEnum<{
|
|
49
|
+
auto: "auto";
|
|
50
|
+
url_only: "url_only";
|
|
51
|
+
}>>;
|
|
52
|
+
imageWidth: z.ZodOptional<z.ZodNumber>;
|
|
53
|
+
imageHeight: z.ZodOptional<z.ZodNumber>;
|
|
54
|
+
imageQuality: z.ZodOptional<z.ZodNumber>;
|
|
55
|
+
pdfPaperFormat: z.ZodOptional<z.ZodEnum<{
|
|
56
|
+
letter: "letter";
|
|
57
|
+
legal: "legal";
|
|
58
|
+
tabloid: "tabloid";
|
|
59
|
+
a4: "a4";
|
|
60
|
+
a3: "a3";
|
|
61
|
+
content: "content";
|
|
62
|
+
}>>;
|
|
63
|
+
pdfLandscape: z.ZodOptional<z.ZodBoolean>;
|
|
64
|
+
pdfPrintBackground: z.ZodOptional<z.ZodBoolean>;
|
|
65
|
+
pdfMargin: z.ZodOptional<z.ZodString>;
|
|
66
|
+
pdfMarginTop: z.ZodOptional<z.ZodString>;
|
|
67
|
+
pdfMarginRight: z.ZodOptional<z.ZodString>;
|
|
68
|
+
pdfMarginBottom: z.ZodOptional<z.ZodString>;
|
|
69
|
+
pdfMarginLeft: z.ZodOptional<z.ZodString>;
|
|
70
|
+
pdfScale: z.ZodOptional<z.ZodNumber>;
|
|
71
|
+
}, z.core.$strict>;
|
|
72
|
+
export type CaptureScreenshotInput = z.infer<typeof captureScreenshotInputSchema>;
|
|
73
|
+
export type CaptureResultMode = CaptureScreenshotInput["resultMode"];
|
|
74
|
+
export type MappedCaptureRequest = {
|
|
75
|
+
readonly targetUrl: string;
|
|
76
|
+
readonly resultMode: CaptureResultMode;
|
|
77
|
+
readonly captureOptions: Readonly<BinaryCaptureOptions>;
|
|
78
|
+
};
|
|
79
|
+
export declare function parseCaptureScreenshotInput(input: unknown): CaptureScreenshotInput;
|
|
80
|
+
/**
|
|
81
|
+
* Applies MCP-owned image defaults, then maps the curated fields to the
|
|
82
|
+
* identically named SDK fields. The SDK remains solely responsible for request
|
|
83
|
+
* serialization.
|
|
84
|
+
*/
|
|
85
|
+
export declare function mapCaptureInputToSdk(input: CaptureScreenshotInput): MappedCaptureRequest;
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { CaptureResponseType, } from "@screenshotscout/sdk";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export const CAPTURE_FORMATS = [
|
|
4
|
+
"png",
|
|
5
|
+
"jpg",
|
|
6
|
+
"jpeg",
|
|
7
|
+
"webp",
|
|
8
|
+
"gif",
|
|
9
|
+
"tiff",
|
|
10
|
+
"pdf",
|
|
11
|
+
];
|
|
12
|
+
export const IMAGE_CAPTURE_FORMATS = [
|
|
13
|
+
"png",
|
|
14
|
+
"jpg",
|
|
15
|
+
"jpeg",
|
|
16
|
+
"webp",
|
|
17
|
+
"gif",
|
|
18
|
+
"tiff",
|
|
19
|
+
];
|
|
20
|
+
export const CAPTURE_RESULT_MODES = ["auto", "url_only"];
|
|
21
|
+
const DEFAULT_VIEWPORT_WIDTH = 1_280;
|
|
22
|
+
const DEFAULT_VIEWPORT_HEIGHT = 720;
|
|
23
|
+
const DEFAULT_JPEG_QUALITY = 60;
|
|
24
|
+
const lossyImageFormatSet = new Set(["jpg", "jpeg", "webp"]);
|
|
25
|
+
const pdfFieldNames = [
|
|
26
|
+
"pdfPaperFormat",
|
|
27
|
+
"pdfLandscape",
|
|
28
|
+
"pdfPrintBackground",
|
|
29
|
+
"pdfMargin",
|
|
30
|
+
"pdfMarginTop",
|
|
31
|
+
"pdfMarginRight",
|
|
32
|
+
"pdfMarginBottom",
|
|
33
|
+
"pdfMarginLeft",
|
|
34
|
+
"pdfScale",
|
|
35
|
+
];
|
|
36
|
+
const nonBlankStringSchema = z
|
|
37
|
+
.string()
|
|
38
|
+
.refine((value) => value.trim().length > 0, "Must not be blank.");
|
|
39
|
+
const selectorListSchema = z
|
|
40
|
+
.array(nonBlankStringSchema)
|
|
41
|
+
.min(1, "Provide at least one non-blank selector.");
|
|
42
|
+
const cssLengthSchema = z
|
|
43
|
+
.string()
|
|
44
|
+
.trim()
|
|
45
|
+
.regex(/^(?:[0-9]+(?:\.[0-9]+)?)(?:\s*(?:px|in|mm|cm))?$/i, "Must be a non-negative CSS length in px, in, mm, or cm.");
|
|
46
|
+
const targetUrlSchema = z
|
|
47
|
+
.string()
|
|
48
|
+
.trim()
|
|
49
|
+
.min(1)
|
|
50
|
+
.max(8_192)
|
|
51
|
+
.refine((value) => {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = new URL(value);
|
|
54
|
+
return ((parsed.protocol === "http:" || parsed.protocol === "https:") &&
|
|
55
|
+
parsed.hostname.length > 0);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}, "Must be a valid HTTP or HTTPS URL.");
|
|
61
|
+
/**
|
|
62
|
+
* The complete curated MCP input surface. Credentials and SDK-owned
|
|
63
|
+
* `responseType` controls are deliberately absent.
|
|
64
|
+
*/
|
|
65
|
+
export const captureScreenshotInputSchema = z
|
|
66
|
+
.object({
|
|
67
|
+
url: targetUrlSchema.describe("HTTP or HTTPS page URL to capture."),
|
|
68
|
+
format: z
|
|
69
|
+
.enum(CAPTURE_FORMATS)
|
|
70
|
+
.default("jpeg")
|
|
71
|
+
.describe("Output image or document format. Defaults to JPEG."),
|
|
72
|
+
country: z
|
|
73
|
+
.string()
|
|
74
|
+
.trim()
|
|
75
|
+
.regex(/^[A-Za-z]{2}$/, "Use a two-letter ISO 3166-1 country code.")
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("Two-letter country code for the capture location."),
|
|
78
|
+
device: nonBlankStringSchema
|
|
79
|
+
.optional()
|
|
80
|
+
.describe("Screenshot Scout device preset name."),
|
|
81
|
+
deviceViewportWidth: z
|
|
82
|
+
.number()
|
|
83
|
+
.int()
|
|
84
|
+
.positive()
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("Viewport width override in CSS pixels. Captures use a 1280×720 viewport when no device or viewport dimension is supplied."),
|
|
87
|
+
deviceViewportHeight: z
|
|
88
|
+
.number()
|
|
89
|
+
.int()
|
|
90
|
+
.positive()
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("Viewport height override in CSS pixels. Captures use a 1280×720 viewport when no device or viewport dimension is supplied."),
|
|
93
|
+
colorScheme: z
|
|
94
|
+
.enum(["auto", "dark", "light"])
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("Preferred page color scheme."),
|
|
97
|
+
fullPage: z
|
|
98
|
+
.boolean()
|
|
99
|
+
.optional()
|
|
100
|
+
.describe("Capture the full scrollable page when true."),
|
|
101
|
+
blockCookieBanners: z
|
|
102
|
+
.boolean()
|
|
103
|
+
.optional()
|
|
104
|
+
.describe("Attempt to block common cookie banners."),
|
|
105
|
+
blockAds: z
|
|
106
|
+
.boolean()
|
|
107
|
+
.optional()
|
|
108
|
+
.describe("Attempt to block advertisements."),
|
|
109
|
+
blockChatWidgets: z
|
|
110
|
+
.boolean()
|
|
111
|
+
.optional()
|
|
112
|
+
.describe("Attempt to block common chat widgets."),
|
|
113
|
+
selector: nonBlankStringSchema
|
|
114
|
+
.optional()
|
|
115
|
+
.describe("CSS selector for an element-only capture."),
|
|
116
|
+
hideSelectors: selectorListSchema
|
|
117
|
+
.optional()
|
|
118
|
+
.describe("CSS selectors for elements to hide before capture."),
|
|
119
|
+
clickSelectors: selectorListSchema
|
|
120
|
+
.optional()
|
|
121
|
+
.describe("CSS selectors for elements to click before capture."),
|
|
122
|
+
waitUntil: z
|
|
123
|
+
.enum(["load", "domcontentloaded", "networkidle0", "networkidle2"])
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Page lifecycle event to await before capture."),
|
|
126
|
+
delay: z
|
|
127
|
+
.number()
|
|
128
|
+
.int()
|
|
129
|
+
.min(0)
|
|
130
|
+
.max(30)
|
|
131
|
+
.optional()
|
|
132
|
+
.describe("Additional delay before capture, in seconds."),
|
|
133
|
+
navigationTimeout: z
|
|
134
|
+
.number()
|
|
135
|
+
.int()
|
|
136
|
+
.min(5)
|
|
137
|
+
.max(90)
|
|
138
|
+
.optional()
|
|
139
|
+
.describe("Page navigation timeout, in seconds."),
|
|
140
|
+
timeout: z
|
|
141
|
+
.number()
|
|
142
|
+
.int()
|
|
143
|
+
.min(1)
|
|
144
|
+
.max(240)
|
|
145
|
+
.optional()
|
|
146
|
+
.describe("Overall capture timeout, in seconds."),
|
|
147
|
+
cache: z
|
|
148
|
+
.boolean()
|
|
149
|
+
.optional()
|
|
150
|
+
.describe("Allow Screenshot Scout result caching."),
|
|
151
|
+
cacheTtl: z
|
|
152
|
+
.number()
|
|
153
|
+
.int()
|
|
154
|
+
.min(14_400)
|
|
155
|
+
.max(2_592_000)
|
|
156
|
+
.optional()
|
|
157
|
+
.describe("Cache lifetime in seconds."),
|
|
158
|
+
resultMode: z
|
|
159
|
+
.enum(CAPTURE_RESULT_MODES)
|
|
160
|
+
.default("auto")
|
|
161
|
+
.describe("Use auto for eligible inline images or url_only to omit inline bytes."),
|
|
162
|
+
imageWidth: z
|
|
163
|
+
.number()
|
|
164
|
+
.int()
|
|
165
|
+
.min(1)
|
|
166
|
+
.max(8_192)
|
|
167
|
+
.optional()
|
|
168
|
+
.describe("Resize the captured content to this width in pixels before creating the image or PDF."),
|
|
169
|
+
imageHeight: z
|
|
170
|
+
.number()
|
|
171
|
+
.int()
|
|
172
|
+
.min(1)
|
|
173
|
+
.max(8_192)
|
|
174
|
+
.optional()
|
|
175
|
+
.describe("Resize the captured content to this height in pixels before creating the image or PDF."),
|
|
176
|
+
imageQuality: z
|
|
177
|
+
.number()
|
|
178
|
+
.int()
|
|
179
|
+
.min(0)
|
|
180
|
+
.max(100)
|
|
181
|
+
.optional()
|
|
182
|
+
.describe("JPEG or WebP output quality from 0 to 100. JPEG uses quality 60 when omitted."),
|
|
183
|
+
pdfPaperFormat: z
|
|
184
|
+
.enum(["letter", "legal", "tabloid", "a4", "a3", "content"])
|
|
185
|
+
.optional()
|
|
186
|
+
.describe("PDF paper size; requires format pdf."),
|
|
187
|
+
pdfLandscape: z
|
|
188
|
+
.boolean()
|
|
189
|
+
.optional()
|
|
190
|
+
.describe("Use landscape PDF orientation."),
|
|
191
|
+
pdfPrintBackground: z
|
|
192
|
+
.boolean()
|
|
193
|
+
.optional()
|
|
194
|
+
.describe("Include CSS backgrounds in PDF output."),
|
|
195
|
+
pdfMargin: cssLengthSchema
|
|
196
|
+
.optional()
|
|
197
|
+
.describe("PDF margin applied to all sides."),
|
|
198
|
+
pdfMarginTop: cssLengthSchema.optional().describe("PDF top margin."),
|
|
199
|
+
pdfMarginRight: cssLengthSchema.optional().describe("PDF right margin."),
|
|
200
|
+
pdfMarginBottom: cssLengthSchema.optional().describe("PDF bottom margin."),
|
|
201
|
+
pdfMarginLeft: cssLengthSchema.optional().describe("PDF left margin."),
|
|
202
|
+
pdfScale: z
|
|
203
|
+
.number()
|
|
204
|
+
.positive()
|
|
205
|
+
.max(3)
|
|
206
|
+
.optional()
|
|
207
|
+
.describe("PDF rendering scale greater than 0 and at most 3."),
|
|
208
|
+
})
|
|
209
|
+
.strict()
|
|
210
|
+
.superRefine((input, context) => {
|
|
211
|
+
if (input.format !== "pdf") {
|
|
212
|
+
for (const field of pdfFieldNames) {
|
|
213
|
+
if (input[field] !== undefined) {
|
|
214
|
+
context.addIssue({
|
|
215
|
+
code: "custom",
|
|
216
|
+
path: [field],
|
|
217
|
+
message: `${field} requires format "pdf".`,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (input.imageQuality !== undefined &&
|
|
223
|
+
!lossyImageFormatSet.has(input.format)) {
|
|
224
|
+
context.addIssue({
|
|
225
|
+
code: "custom",
|
|
226
|
+
path: ["imageQuality"],
|
|
227
|
+
message: 'imageQuality requires format "jpg", "jpeg", or "webp".',
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
if (input.imageWidth !== undefined &&
|
|
231
|
+
input.imageHeight !== undefined &&
|
|
232
|
+
input.imageWidth * input.imageHeight > 64_000_000) {
|
|
233
|
+
context.addIssue({
|
|
234
|
+
code: "custom",
|
|
235
|
+
path: ["imageHeight"],
|
|
236
|
+
message: "imageWidth multiplied by imageHeight must not exceed 64,000,000 pixels.",
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
export function parseCaptureScreenshotInput(input) {
|
|
241
|
+
return captureScreenshotInputSchema.parse(input);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Applies MCP-owned image defaults, then maps the curated fields to the
|
|
245
|
+
* identically named SDK fields. The SDK remains solely responsible for request
|
|
246
|
+
* serialization.
|
|
247
|
+
*/
|
|
248
|
+
export function mapCaptureInputToSdk(input) {
|
|
249
|
+
const hideSelectors = input.hideSelectors === undefined
|
|
250
|
+
? undefined
|
|
251
|
+
: Object.freeze([...input.hideSelectors]);
|
|
252
|
+
const clickSelectors = input.clickSelectors === undefined
|
|
253
|
+
? undefined
|
|
254
|
+
: Object.freeze([...input.clickSelectors]);
|
|
255
|
+
const useDefaultViewport = input.device === undefined &&
|
|
256
|
+
input.deviceViewportWidth === undefined &&
|
|
257
|
+
input.deviceViewportHeight === undefined;
|
|
258
|
+
const useDefaultJpegQuality = (input.format === "jpg" || input.format === "jpeg") &&
|
|
259
|
+
input.imageQuality === undefined;
|
|
260
|
+
const deviceViewportWidth = useDefaultViewport
|
|
261
|
+
? DEFAULT_VIEWPORT_WIDTH
|
|
262
|
+
: input.deviceViewportWidth;
|
|
263
|
+
const deviceViewportHeight = useDefaultViewport
|
|
264
|
+
? DEFAULT_VIEWPORT_HEIGHT
|
|
265
|
+
: input.deviceViewportHeight;
|
|
266
|
+
const imageQuality = useDefaultJpegQuality
|
|
267
|
+
? DEFAULT_JPEG_QUALITY
|
|
268
|
+
: input.imageQuality;
|
|
269
|
+
const captureOptions = {
|
|
270
|
+
format: input.format,
|
|
271
|
+
responseType: CaptureResponseType.BINARY,
|
|
272
|
+
...(input.country === undefined ? {} : { country: input.country }),
|
|
273
|
+
...(input.device === undefined ? {} : { device: input.device }),
|
|
274
|
+
...(deviceViewportWidth === undefined ? {} : { deviceViewportWidth }),
|
|
275
|
+
...(deviceViewportHeight === undefined ? {} : { deviceViewportHeight }),
|
|
276
|
+
...(input.colorScheme === undefined
|
|
277
|
+
? {}
|
|
278
|
+
: { colorScheme: input.colorScheme }),
|
|
279
|
+
...(input.fullPage === undefined ? {} : { fullPage: input.fullPage }),
|
|
280
|
+
...(input.blockCookieBanners === undefined
|
|
281
|
+
? {}
|
|
282
|
+
: { blockCookieBanners: input.blockCookieBanners }),
|
|
283
|
+
...(input.blockAds === undefined ? {} : { blockAds: input.blockAds }),
|
|
284
|
+
...(input.blockChatWidgets === undefined
|
|
285
|
+
? {}
|
|
286
|
+
: { blockChatWidgets: input.blockChatWidgets }),
|
|
287
|
+
...(input.selector === undefined ? {} : { selector: input.selector }),
|
|
288
|
+
...(hideSelectors === undefined ? {} : { hideSelectors }),
|
|
289
|
+
...(clickSelectors === undefined ? {} : { clickSelectors }),
|
|
290
|
+
...(input.waitUntil === undefined ? {} : { waitUntil: input.waitUntil }),
|
|
291
|
+
...(input.delay === undefined ? {} : { delay: input.delay }),
|
|
292
|
+
...(input.navigationTimeout === undefined
|
|
293
|
+
? {}
|
|
294
|
+
: { navigationTimeout: input.navigationTimeout }),
|
|
295
|
+
...(input.timeout === undefined ? {} : { timeout: input.timeout }),
|
|
296
|
+
...(input.cache === undefined ? {} : { cache: input.cache }),
|
|
297
|
+
...(input.cacheTtl === undefined ? {} : { cacheTtl: input.cacheTtl }),
|
|
298
|
+
...(input.imageWidth === undefined ? {} : { imageWidth: input.imageWidth }),
|
|
299
|
+
...(input.imageHeight === undefined
|
|
300
|
+
? {}
|
|
301
|
+
: { imageHeight: input.imageHeight }),
|
|
302
|
+
...(imageQuality === undefined ? {} : { imageQuality }),
|
|
303
|
+
...(input.pdfPaperFormat === undefined
|
|
304
|
+
? {}
|
|
305
|
+
: { pdfPaperFormat: input.pdfPaperFormat }),
|
|
306
|
+
...(input.pdfLandscape === undefined
|
|
307
|
+
? {}
|
|
308
|
+
: { pdfLandscape: input.pdfLandscape }),
|
|
309
|
+
...(input.pdfPrintBackground === undefined
|
|
310
|
+
? {}
|
|
311
|
+
: { pdfPrintBackground: input.pdfPrintBackground }),
|
|
312
|
+
...(input.pdfMargin === undefined ? {} : { pdfMargin: input.pdfMargin }),
|
|
313
|
+
...(input.pdfMarginTop === undefined
|
|
314
|
+
? {}
|
|
315
|
+
: { pdfMarginTop: input.pdfMarginTop }),
|
|
316
|
+
...(input.pdfMarginRight === undefined
|
|
317
|
+
? {}
|
|
318
|
+
: { pdfMarginRight: input.pdfMarginRight }),
|
|
319
|
+
...(input.pdfMarginBottom === undefined
|
|
320
|
+
? {}
|
|
321
|
+
: { pdfMarginBottom: input.pdfMarginBottom }),
|
|
322
|
+
...(input.pdfMarginLeft === undefined
|
|
323
|
+
? {}
|
|
324
|
+
: { pdfMarginLeft: input.pdfMarginLeft }),
|
|
325
|
+
...(input.pdfScale === undefined ? {} : { pdfScale: input.pdfScale }),
|
|
326
|
+
};
|
|
327
|
+
return Object.freeze({
|
|
328
|
+
targetUrl: input.url,
|
|
329
|
+
resultMode: input.resultMode,
|
|
330
|
+
captureOptions: Object.freeze(captureOptions),
|
|
331
|
+
});
|
|
332
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { McpServer, RegisteredTool } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { type CaptureScreenshotInput } from "./capture-input.js";
|
|
4
|
+
import type { ScreenshotScoutCaptureClient } from "./client.js";
|
|
5
|
+
import { type InlineImageMimeType } from "./image.js";
|
|
6
|
+
import type { RedactedDiagnostic } from "./redaction.js";
|
|
7
|
+
export type CaptureScreenshotToolDependencies = {
|
|
8
|
+
readonly client: ScreenshotScoutCaptureClient;
|
|
9
|
+
readonly clientSupportsInlineImage?: (mimeType: InlineImageMimeType) => boolean;
|
|
10
|
+
readonly maxSerializedResultBytes?: number;
|
|
11
|
+
readonly sensitiveValues?: readonly (string | null | undefined)[];
|
|
12
|
+
readonly onUnexpectedError?: (diagnostic: RedactedDiagnostic) => void;
|
|
13
|
+
};
|
|
14
|
+
export type CaptureScreenshotInvocationContext = {
|
|
15
|
+
readonly signal?: AbortSignal;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Invokes the official Screenshot Scout SDK after MCP schema validation.
|
|
19
|
+
* Result-content branching remains in the shared result layer.
|
|
20
|
+
*/
|
|
21
|
+
export declare function invokeCaptureScreenshot(input: CaptureScreenshotInput, dependencies: CaptureScreenshotToolDependencies, context?: CaptureScreenshotInvocationContext): Promise<CallToolResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Registers the one approved tool on a transport-neutral MCP server.
|
|
24
|
+
*/
|
|
25
|
+
export declare function registerCaptureScreenshotTool(server: McpServer, dependencies: CaptureScreenshotToolDependencies): RegisteredTool;
|