@zenrows/mcp 1.1.3 → 1.1.5

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/dist/http.js CHANGED
@@ -24,26 +24,51 @@ function extractApiKey(req) {
24
24
  return new URL(req.url).searchParams.get("apikey") ?? undefined;
25
25
  }
26
26
  const AUTH_SERVER = process.env.OAUTH_AUTH_SERVER ?? "https://app.zenrows.com";
27
+ const MCP_SERVER = process.env.MCP_SERVER ?? "https://mcp.zenrows.com";
27
28
  app.get("/mcp/.well-known/oauth-authorization-server", (c) => c.redirect("/.well-known/oauth-authorization-server", 301));
29
+ // RFC 9728 — OAuth Protected Resource Metadata
30
+ // MCP clients fetch this first to discover the authorization server(s) for this resource.
31
+ app.get("/.well-known/oauth-protected-resource", (c) => c.json({
32
+ resource: MCP_SERVER,
33
+ authorization_servers: [MCP_SERVER],
34
+ bearer_methods_supported: ["header", "query"],
35
+ scopes_supported: [],
36
+ }));
37
+ // RFC 8414 — OAuth Authorization Server Metadata
38
+ // Issuer must match the URL of the server serving this document (MCP_SERVER, not AUTH_SERVER),
39
+ // because the MCP server acts as an authorization server proxy for client discovery purposes.
28
40
  app.get("/.well-known/oauth-authorization-server", (c) => c.json({
29
- issuer: AUTH_SERVER,
41
+ issuer: MCP_SERVER,
30
42
  authorization_endpoint: `${AUTH_SERVER}/oauth/mcp/authorize`,
31
43
  token_endpoint: `${AUTH_SERVER}/oauth/mcp/token`,
44
+ registration_endpoint: `${MCP_SERVER}/register`,
32
45
  response_types_supported: ["code"],
33
46
  grant_types_supported: ["authorization_code"],
34
47
  code_challenge_methods_supported: ["S256"],
35
48
  token_endpoint_auth_methods_supported: ["none"],
36
49
  }));
50
+ // RFC 7591 — Dynamic Client Registration
51
+ // MCP clients (Claude.ai, Cursor, etc.) register themselves before initiating OAuth.
52
+ // Since ZenRows API keys are the real auth mechanism, client registration is stateless —
53
+ // we echo back a stable client_id derived from the request (or the one the client provides).
54
+ app.post("/register", async (c) => {
55
+ const body = await c.req.json().catch(() => ({}));
56
+ return c.json({
57
+ client_id: body.client_id ?? crypto.randomUUID(),
58
+ client_id_issued_at: Math.floor(Date.now() / 1000),
59
+ ...body,
60
+ }, 201);
61
+ });
37
62
  app.all("/mcp", async (c) => {
38
63
  const apiKey = extractApiKey(c.req.raw);
39
64
  if (!apiKey) {
40
65
  return c.json({
41
66
  error: "Missing API key. Use Authorization: Bearer <key> header or ?apikey=<key> query param.",
42
67
  }, 401, {
43
- "WWW-Authenticate": `Bearer realm="${AUTH_SERVER}", resource_metadata="https://mcp.zenrows.com/.well-known/oauth-authorization-server"`,
68
+ "WWW-Authenticate": `Bearer realm="${AUTH_SERVER}", resource_metadata="${MCP_SERVER}/.well-known/oauth-protected-resource"`,
44
69
  // CloudFront strips WWW-Authenticate — add Link header as RFC 8615 fallback
45
70
  // so MCP clients can still discover the OAuth server
46
- "Link": `<https://mcp.zenrows.com/.well-known/oauth-authorization-server>; rel="oauth-authorization-server"`,
71
+ "Link": `<${MCP_SERVER}/.well-known/oauth-protected-resource>; rel="oauth-protected-resource"`,
47
72
  });
48
73
  }
49
74
  const transport = new WebStandardStreamableHTTPServerTransport({
package/dist/server.js CHANGED
@@ -200,5 +200,60 @@ Examples:
200
200
  content: [{ type: "text", text: new TextDecoder().decode(buffer) }],
201
201
  };
202
202
  });
203
+ // ─── prompts ───────────────────────────────────────────────────────────────
204
+ server.registerPrompt("scrape_and_summarize", {
205
+ title: "Scrape and Summarize",
206
+ description: "Scrape a webpage and return a concise summary of its content.",
207
+ argsSchema: {
208
+ url: z.string().url().describe("The webpage URL to scrape and summarize"),
209
+ },
210
+ }, ({ url }) => ({
211
+ messages: [
212
+ {
213
+ role: "user",
214
+ content: {
215
+ type: "text",
216
+ text: `Scrape ${url} using the ZenRows MCP scrape tool and provide a concise summary of the main content. Include key points, headings, and any important data found on the page.`,
217
+ },
218
+ },
219
+ ],
220
+ }));
221
+ server.registerPrompt("extract_structured_data", {
222
+ title: "Extract Structured Data",
223
+ description: "Scrape a webpage and extract specific structured data using CSS selectors.",
224
+ argsSchema: {
225
+ url: z.string().url().describe("The webpage URL to extract data from"),
226
+ fields: z
227
+ .string()
228
+ .describe('JSON object mapping field names to CSS selectors, e.g. \'{"title":"h1","price":".price"}\''),
229
+ },
230
+ }, ({ url, fields }) => ({
231
+ messages: [
232
+ {
233
+ role: "user",
234
+ content: {
235
+ type: "text",
236
+ text: `Scrape ${url} using the ZenRows MCP scrape tool with css_extractor set to ${fields}. Return the extracted data as a clean JSON object.`,
237
+ },
238
+ },
239
+ ],
240
+ }));
241
+ server.registerPrompt("scrape_js_page", {
242
+ title: "Scrape JavaScript-Rendered Page",
243
+ description: "Scrape a page that requires JavaScript rendering (React, Vue, Angular, or any SPA).",
244
+ argsSchema: {
245
+ url: z.string().url().describe("The JavaScript-rendered page URL to scrape"),
246
+ },
247
+ }, ({ url }) => ({
248
+ messages: [
249
+ {
250
+ role: "user",
251
+ content: {
252
+ type: "text",
253
+ text: `Scrape ${url} using the ZenRows MCP scrape tool with js_render set to true. The page requires JavaScript execution to load its content. Return the full rendered content in markdown format.`,
254
+ },
255
+ },
256
+ ],
257
+ }));
203
258
  return server;
204
259
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zenrows/mcp",
3
- "version": "1.1.3",
3
+ "version": "1.1.5",
4
4
  "description": "ZenRows MCP server — Universal Scraper API for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {