@zenrows/mcp 1.0.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 +248 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +205 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ZenRows
|
|
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,248 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/zenrows_light.svg">
|
|
4
|
+
<img src="assets/zenrows_dark.svg" alt="ZenRows MCP" width="380">
|
|
5
|
+
</picture>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
Model Context Protocol server for the <a href="https://www.zenrows.com/products/universal-scraper">ZenRows Universal Scraper API</a>.<br>
|
|
10
|
+
Give any MCP-compatible AI assistant the ability to scrape any webpage — including JavaScript-rendered content and anti-bot protected sites.
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
**Claude Code**
|
|
18
|
+
```bash
|
|
19
|
+
claude mcp add zenrows -e ZENROWS_API_KEY=YOUR_API_KEY -- npx -y @zenrows/mcp
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or ask your AI assistant naturally once configured:
|
|
23
|
+
```
|
|
24
|
+
Scrape https://example.com and summarize the content.
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Tool
|
|
30
|
+
|
|
31
|
+
### `scrape`
|
|
32
|
+
|
|
33
|
+
Fetches a webpage and returns its content as clean markdown (default), plaintext, raw HTML, PDF, structured JSON, or a screenshot. See the [ZenRows API docs](https://docs.zenrows.com/universal-scraper-api/api-reference#parameter-overview) for full parameter reference.
|
|
34
|
+
|
|
35
|
+
| Parameter | Type | Default | Description |
|
|
36
|
+
|-----------|------|---------|-------------|
|
|
37
|
+
| `url` | string | **required** | Webpage URL to scrape |
|
|
38
|
+
| `js_render` | boolean | `false` | Enable JS rendering for SPAs and dynamic content |
|
|
39
|
+
| `premium_proxy` | boolean | `false` | Use residential proxies to bypass anti-bot systems |
|
|
40
|
+
| `proxy_country` | string | — | ISO 3166-1 alpha-2 country code (e.g. `US`, `GB`). Requires `premium_proxy` |
|
|
41
|
+
| `response_type` | `markdown` \| `plaintext` \| `pdf` \| `html` | `markdown` | Output format. `html` returns raw source (ZenRows default when no param is sent). Ignored when `autoparse`, `css_extractor`, `outputs`, or screenshot params are set |
|
|
42
|
+
| `autoparse` | boolean | — | Auto-extract structured JSON from the page |
|
|
43
|
+
| `css_extractor` | string | — | JSON map of CSS selectors: `{"title":"h1","price":".price"}` |
|
|
44
|
+
| `outputs` | string | — | Comma-separated data types to extract as JSON: `emails`, `headings`, `links`, `menus`, `images`, `videos`, `audios`. Use `*` for all |
|
|
45
|
+
| `screenshot` | boolean | — | Capture an above-the-fold screenshot. Returns an image |
|
|
46
|
+
| `screenshot_fullpage` | boolean | — | Capture a full-page screenshot. Returns an image |
|
|
47
|
+
| `screenshot_selector` | string | — | Capture a screenshot of a specific element via CSS selector |
|
|
48
|
+
| `wait_for` | string | — | CSS selector to wait for before capturing. Requires `js_render` |
|
|
49
|
+
| `wait` | number | — | Milliseconds to wait after load (max 30000). Requires `js_render` |
|
|
50
|
+
| `js_instructions` | string | — | JSON array of browser actions. Requires `js_render` |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## When to use which options
|
|
55
|
+
|
|
56
|
+
**Content doesn't appear or page is blank**
|
|
57
|
+
→ Enable `js_render: true`. The page likely uses React, Vue, or Angular.
|
|
58
|
+
|
|
59
|
+
**Getting 403 or blocked errors**
|
|
60
|
+
→ Add `premium_proxy: true`. For geo-restricted content, also set `proxy_country`.
|
|
61
|
+
|
|
62
|
+
**Content loads after a delay or interaction**
|
|
63
|
+
→ Use `wait_for` (CSS selector) or `wait` (milliseconds) with `js_render: true`.
|
|
64
|
+
→ For clicks or form inputs before scraping, use `js_instructions`.
|
|
65
|
+
|
|
66
|
+
**Only need specific data, not the full page**
|
|
67
|
+
→ Use `css_extractor` with a JSON map of selectors for precise extraction.
|
|
68
|
+
→ Use `autoparse` for structured pages like products or articles.
|
|
69
|
+
→ Use `outputs` to pull links, emails, images, or other content types.
|
|
70
|
+
|
|
71
|
+
**Need to verify what the page looks like**
|
|
72
|
+
→ Use `screenshot` or `screenshot_fullpage` for visual debugging.
|
|
73
|
+
→ Use `screenshot_selector` to capture a specific element.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
### Claude Desktop
|
|
80
|
+
|
|
81
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mcpServers": {
|
|
86
|
+
"zenrows": {
|
|
87
|
+
"command": "npx",
|
|
88
|
+
"args": ["-y", "@zenrows/mcp"],
|
|
89
|
+
"env": {
|
|
90
|
+
"ZENROWS_API_KEY": "YOUR_API_KEY"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Claude Code
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
claude mcp add zenrows -e ZENROWS_API_KEY=YOUR_API_KEY -- npx -y @zenrows/mcp
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Or add to your project's `.mcp.json`:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"mcpServers": {
|
|
108
|
+
"zenrows": {
|
|
109
|
+
"command": "npx",
|
|
110
|
+
"args": ["-y", "@zenrows/mcp"],
|
|
111
|
+
"env": {
|
|
112
|
+
"ZENROWS_API_KEY": "YOUR_API_KEY"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Cursor
|
|
120
|
+
|
|
121
|
+
Edit `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project):
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"mcpServers": {
|
|
126
|
+
"zenrows": {
|
|
127
|
+
"command": "npx",
|
|
128
|
+
"args": ["-y", "@zenrows/mcp"],
|
|
129
|
+
"env": {
|
|
130
|
+
"ZENROWS_API_KEY": "YOUR_API_KEY"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Windsurf
|
|
138
|
+
|
|
139
|
+
Edit `~/.codeium/windsurf/mcp_config.json`:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"mcpServers": {
|
|
144
|
+
"zenrows": {
|
|
145
|
+
"command": "npx",
|
|
146
|
+
"args": ["-y", "@zenrows/mcp"],
|
|
147
|
+
"env": {
|
|
148
|
+
"ZENROWS_API_KEY": "YOUR_API_KEY"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### VS Code (GitHub Copilot)
|
|
156
|
+
|
|
157
|
+
Edit `.vscode/mcp.json` in your project:
|
|
158
|
+
|
|
159
|
+
```json
|
|
160
|
+
{
|
|
161
|
+
"servers": {
|
|
162
|
+
"zenrows": {
|
|
163
|
+
"type": "stdio",
|
|
164
|
+
"command": "npx",
|
|
165
|
+
"args": ["-y", "@zenrows/mcp"],
|
|
166
|
+
"env": {
|
|
167
|
+
"ZENROWS_API_KEY": "YOUR_API_KEY"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Zed
|
|
175
|
+
|
|
176
|
+
Edit `~/.config/zed/settings.json`:
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"context_servers": {
|
|
181
|
+
"zenrows": {
|
|
182
|
+
"command": {
|
|
183
|
+
"path": "npx",
|
|
184
|
+
"args": ["-y", "@zenrows/mcp"],
|
|
185
|
+
"env": {
|
|
186
|
+
"ZENROWS_API_KEY": "YOUR_API_KEY"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### JetBrains IDEs
|
|
195
|
+
|
|
196
|
+
Go to **Settings → Tools → AI Assistant → Model Context Protocol** and add:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"mcpServers": {
|
|
201
|
+
"zenrows": {
|
|
202
|
+
"command": "npx",
|
|
203
|
+
"args": ["-y", "@zenrows/mcp"],
|
|
204
|
+
"env": {
|
|
205
|
+
"ZENROWS_API_KEY": "YOUR_API_KEY"
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Examples
|
|
215
|
+
|
|
216
|
+
Once configured, ask your AI assistant naturally:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
Scrape the pricing page at https://zenrows.com/pricing and summarize the plans.
|
|
220
|
+
|
|
221
|
+
Fetch https://example.com/ — it's a React SPA, so enable JS rendering.
|
|
222
|
+
|
|
223
|
+
Get the top 5 results from https://www.scrapingcourse.com/ecommerce/ and extract just the product names and prices.
|
|
224
|
+
|
|
225
|
+
Take a full-page screenshot of https://news.ycombinator.com to see the current layout.
|
|
226
|
+
|
|
227
|
+
Scrape https://protected-site.com — it keeps blocking me, use premium proxies.
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Development
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
git clone https://github.com/ZenRows/zenrows-mcp
|
|
236
|
+
cd zenrows-mcp
|
|
237
|
+
npm install
|
|
238
|
+
cp .env.example .env # add your API key
|
|
239
|
+
npm run dev # run with .env loaded (requires Node 20.6+)
|
|
240
|
+
npm run build # compile to dist/
|
|
241
|
+
npm run inspect # open MCP inspector UI
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## License
|
|
247
|
+
|
|
248
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const pkg = require("../package.json");
|
|
8
|
+
const ZENROWS_API_URL = "https://api.zenrows.com/v1/";
|
|
9
|
+
const apiKey = process.env.ZENROWS_API_KEY;
|
|
10
|
+
if (!apiKey) {
|
|
11
|
+
process.stderr.write("Error: ZENROWS_API_KEY environment variable is required\n");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const server = new McpServer({
|
|
15
|
+
name: "zenrows",
|
|
16
|
+
version: pkg.version,
|
|
17
|
+
});
|
|
18
|
+
// ─── scrape ──────────────────────────────────────────────────────────────────
|
|
19
|
+
server.registerTool("scrape", {
|
|
20
|
+
description: `Scrape any webpage and return its content using ZenRows.
|
|
21
|
+
|
|
22
|
+
Use this tool to fetch webpage content for analysis. By default it returns clean
|
|
23
|
+
markdown, which is ideal for LLM processing.
|
|
24
|
+
|
|
25
|
+
When to enable options:
|
|
26
|
+
- js_render: page uses React/Vue/Angular, loads content dynamically, or content
|
|
27
|
+
appears missing on the first attempt
|
|
28
|
+
- premium_proxy: site returns 403/blocked errors even with js_render enabled
|
|
29
|
+
- wait_for: specific content loads after initial render (requires js_render)
|
|
30
|
+
- css_extractor: you only need specific elements, not the whole page
|
|
31
|
+
- autoparse: structured data pages like products or articles
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
Basic: { url: "https://example.com" }
|
|
35
|
+
Dynamic: { url: "https://spa.com", js_render: true }
|
|
36
|
+
Protected:{ url: "https://protected.com", js_render: true, premium_proxy: true }
|
|
37
|
+
Extract: { url: "https://shop.com", css_extractor: '{"title":"h1","price":".price"}' }`,
|
|
38
|
+
inputSchema: {
|
|
39
|
+
url: z
|
|
40
|
+
.string()
|
|
41
|
+
.url()
|
|
42
|
+
.describe("The webpage URL to scrape"),
|
|
43
|
+
js_render: z
|
|
44
|
+
.boolean()
|
|
45
|
+
.optional()
|
|
46
|
+
.default(false)
|
|
47
|
+
.describe("Enable JavaScript rendering via headless browser. Required for SPAs " +
|
|
48
|
+
"(React, Vue, Angular) and pages that load content dynamically."),
|
|
49
|
+
premium_proxy: z
|
|
50
|
+
.boolean()
|
|
51
|
+
.optional()
|
|
52
|
+
.default(false)
|
|
53
|
+
.describe("Use premium residential proxies to bypass anti-bot protection. " +
|
|
54
|
+
"Required for heavily protected sites. Implies higher credit cost."),
|
|
55
|
+
proxy_country: z
|
|
56
|
+
.string()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("Country for geo-targeted scraping. ISO 3166-1 alpha-2 code (e.g. 'US', 'GB', 'DE'). " +
|
|
59
|
+
"Requires premium_proxy=true."),
|
|
60
|
+
response_type: z
|
|
61
|
+
.enum(["markdown", "plaintext", "pdf", "html"])
|
|
62
|
+
.optional()
|
|
63
|
+
.default("markdown")
|
|
64
|
+
.describe("Output format. 'markdown' (default) preserves structure and is ideal for LLMs. " +
|
|
65
|
+
"'plaintext' strips all formatting for pure text extraction. " +
|
|
66
|
+
"'pdf' returns a PDF of the page. " +
|
|
67
|
+
"'html' returns the raw HTML source (omits the response_type param; ZenRows default). " +
|
|
68
|
+
"Ignored when autoparse, css_extractor, outputs, or screenshot params are set."),
|
|
69
|
+
autoparse: z
|
|
70
|
+
.boolean()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Automatically extract structured data from the page into JSON. " +
|
|
73
|
+
"Best for product pages, articles, and listings."),
|
|
74
|
+
css_extractor: z
|
|
75
|
+
.string()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("Extract specific elements using CSS selectors. " +
|
|
78
|
+
'JSON object mapping names to selectors, e.g. \'{"title":"h1","price":".price-tag"}\'. ' +
|
|
79
|
+
"Returns JSON instead of full page content."),
|
|
80
|
+
wait_for: z
|
|
81
|
+
.string()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe("CSS selector to wait for before capturing. Use when key content loads " +
|
|
84
|
+
"after the initial page render. Requires js_render=true."),
|
|
85
|
+
wait: z
|
|
86
|
+
.number()
|
|
87
|
+
.int()
|
|
88
|
+
.min(0)
|
|
89
|
+
.max(30000)
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("Milliseconds to wait after page load before capturing content. " +
|
|
92
|
+
"Max 30000 (30s). Requires js_render=true."),
|
|
93
|
+
js_instructions: z
|
|
94
|
+
.string()
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("JSON array of browser interactions to run before scraping. Requires js_render=true. " +
|
|
97
|
+
'Example: [{"click":"#load-more"},{"wait":1000},{"wait_for":".results"}]'),
|
|
98
|
+
outputs: z
|
|
99
|
+
.string()
|
|
100
|
+
.optional()
|
|
101
|
+
.describe("Comma-separated list of data types to extract as structured JSON. " +
|
|
102
|
+
"Available: emails, headings, links, menus, images, videos, audios. " +
|
|
103
|
+
"Use '*' for all types. Returns JSON instead of full page content."),
|
|
104
|
+
screenshot: z
|
|
105
|
+
.boolean()
|
|
106
|
+
.optional()
|
|
107
|
+
.describe("Capture an above-the-fold screenshot of the page. " +
|
|
108
|
+
"Returns an image instead of text content. Useful for visual verification or debugging."),
|
|
109
|
+
screenshot_fullpage: z
|
|
110
|
+
.boolean()
|
|
111
|
+
.optional()
|
|
112
|
+
.describe("Capture a full-page screenshot including content below the fold. " +
|
|
113
|
+
"Returns an image instead of text content."),
|
|
114
|
+
screenshot_selector: z
|
|
115
|
+
.string()
|
|
116
|
+
.optional()
|
|
117
|
+
.describe("Capture a screenshot of a specific element using a CSS selector. " +
|
|
118
|
+
'Example: ".product-card". Returns an image instead of text content.'),
|
|
119
|
+
},
|
|
120
|
+
}, async (params) => {
|
|
121
|
+
const searchParams = new URLSearchParams({
|
|
122
|
+
apikey: apiKey,
|
|
123
|
+
url: params.url,
|
|
124
|
+
});
|
|
125
|
+
if (params.js_render || params.screenshot || params.screenshot_fullpage || params.screenshot_selector)
|
|
126
|
+
searchParams.set("js_render", "true");
|
|
127
|
+
if (params.premium_proxy)
|
|
128
|
+
searchParams.set("premium_proxy", "true");
|
|
129
|
+
if (params.proxy_country)
|
|
130
|
+
searchParams.set("proxy_country", params.proxy_country.toUpperCase());
|
|
131
|
+
if (params.autoparse)
|
|
132
|
+
searchParams.set("autoparse", "true");
|
|
133
|
+
if (params.css_extractor)
|
|
134
|
+
searchParams.set("css_extractor", params.css_extractor);
|
|
135
|
+
if (params.wait_for)
|
|
136
|
+
searchParams.set("wait_for", params.wait_for);
|
|
137
|
+
if (params.wait != null)
|
|
138
|
+
searchParams.set("wait", String(params.wait));
|
|
139
|
+
if (params.js_instructions)
|
|
140
|
+
searchParams.set("js_instructions", params.js_instructions);
|
|
141
|
+
if (params.outputs)
|
|
142
|
+
searchParams.set("outputs", params.outputs);
|
|
143
|
+
if (params.screenshot || params.screenshot_fullpage || params.screenshot_selector)
|
|
144
|
+
searchParams.set("screenshot", "true");
|
|
145
|
+
if (params.screenshot_fullpage)
|
|
146
|
+
searchParams.set("screenshot_fullpage", "true");
|
|
147
|
+
if (params.screenshot_selector)
|
|
148
|
+
searchParams.set("screenshot_selector", params.screenshot_selector);
|
|
149
|
+
// response_type is mutually exclusive with autoparse, css_extractor, outputs, and screenshot params.
|
|
150
|
+
// 'html' is the ZenRows default (no param); all other values are passed through.
|
|
151
|
+
const isScreenshot = params.screenshot || params.screenshot_fullpage || params.screenshot_selector;
|
|
152
|
+
const effectiveType = params.response_type ?? "markdown";
|
|
153
|
+
if (!params.autoparse && !params.css_extractor && !params.outputs && !isScreenshot && effectiveType !== "html") {
|
|
154
|
+
searchParams.set("response_type", effectiveType);
|
|
155
|
+
}
|
|
156
|
+
let response;
|
|
157
|
+
try {
|
|
158
|
+
response = await fetch(`${ZENROWS_API_URL}?${searchParams}`, {
|
|
159
|
+
headers: { "User-Agent": `zenrows/mcp ${pkg.version}` },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
return {
|
|
164
|
+
content: [
|
|
165
|
+
{
|
|
166
|
+
type: "text",
|
|
167
|
+
text: `Network error contacting ZenRows: ${err instanceof Error ? err.message : String(err)}`,
|
|
168
|
+
},
|
|
169
|
+
],
|
|
170
|
+
isError: true,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (!response.ok) {
|
|
174
|
+
const body = await response.text();
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: "text", text: `ZenRows error ${response.status}: ${body}` }],
|
|
177
|
+
isError: true,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
181
|
+
const buffer = await response.arrayBuffer();
|
|
182
|
+
const bytes = new Uint8Array(buffer);
|
|
183
|
+
const isPng = bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47;
|
|
184
|
+
const isJpeg = bytes[0] === 0xff && bytes[1] === 0xd8;
|
|
185
|
+
if (contentType.startsWith("image/") || isPng || isJpeg) {
|
|
186
|
+
const mimeType = isPng ? "image/png" : isJpeg ? "image/jpeg" : contentType.split(";")[0].trim();
|
|
187
|
+
const base64 = Buffer.from(buffer).toString("base64");
|
|
188
|
+
return {
|
|
189
|
+
content: [{ type: "image", data: base64, mimeType }],
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
content: [{ type: "text", text: new TextDecoder().decode(buffer) }],
|
|
194
|
+
};
|
|
195
|
+
});
|
|
196
|
+
// ─── boot ─────────────────────────────────────────────────────────────────────
|
|
197
|
+
async function main() {
|
|
198
|
+
const transport = new StdioServerTransport();
|
|
199
|
+
await server.connect(transport);
|
|
200
|
+
console.error("ZenRows MCP server running on stdio");
|
|
201
|
+
}
|
|
202
|
+
main().catch((error) => {
|
|
203
|
+
console.error("Fatal error in main():", error);
|
|
204
|
+
process.exit(1);
|
|
205
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zenrows/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "ZenRows MCP server — Universal Scraper API for AI coding assistants",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"zenrows-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
14
|
+
"dev": "node --env-file=.env --import tsx src/index.ts",
|
|
15
|
+
"inspect": "npm run build && npx @modelcontextprotocol/inspector node dist/index.js",
|
|
16
|
+
"prepare": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
20
|
+
"zod": "^3.23.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.0.0",
|
|
24
|
+
"tsx": "^4.19.0",
|
|
25
|
+
"typescript": "^5.6.0"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=20.6"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"mcp",
|
|
32
|
+
"zenrows",
|
|
33
|
+
"scraping",
|
|
34
|
+
"web-scraping",
|
|
35
|
+
"model-context-protocol",
|
|
36
|
+
"ai",
|
|
37
|
+
"llm"
|
|
38
|
+
],
|
|
39
|
+
"mcpName": "io.github.ZenRows/zenrows-mcp",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/ZenRows/zenrows-mcp"
|
|
44
|
+
}
|
|
45
|
+
}
|