scrapeless-mcp-server 0.3.0 → 0.4.1

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/README.md CHANGED
@@ -6,7 +6,8 @@ Model Context Protocol (MCP) is an open protocol that enables seamless integrati
6
6
 
7
7
  Seamlessly integrate real-time Google SERP(Google Search, Google Flight, Google Map, Google Jobs....) results into your LLM applications using the Scrapeless MCP server. This server acts as a bridge between LLMs (like ChatGPT, Claude, etc.) and Scrapeless's Google SERP, enabling dynamic context retrieval for AI workflows, chatbots, and research tools.
8
8
 
9
- 👉 Live MCP Endpoint:
9
+ 👉 Live MCP Endpoint:
10
+
10
11
  - [mcp.so](https://mcp.so/server/scrapelessMcpServer/scrapeless-ai)
11
12
  - [glama.ai](https://glama.ai/mcp/servers/@scrapeless-ai/scrapeless-mcp-server)
12
13
 
@@ -18,31 +19,90 @@ This project provides several MCP servers that enable AI assistants like Claude
18
19
 
19
20
  - Google Search
20
21
 
22
+ ## Supported Transport Protocols
23
+
24
+ - **Stdio** `default`: The transport runs locally on your machine and communicates via standard input/output streams.
25
+ - **Streamable HTTP** `--mode=streamable_http`: The new MCP transport protocol that simplifies client-server communication through a single HTTP endpoint
26
+
21
27
  ## Tools
22
28
 
23
- ### 1. Search Tool
24
- - Name: `google-search`
25
- - Description: Search the web using Scrapeless
26
- - Parameters:
27
- * `query` (required): Parameter defines the query you want to search. You can use anything that you would use in a regular Google search. e.g. inurl:, site:, intitle:.
28
- * `gl` (optional, default: "us"): Parameter defines the country to use for the Google search. It's a two-letter country code. (e.g., us for the United States, uk for United Kingdom, or fr for France).
29
- * `hl` (optional, default: "en"): Parameter defines the language to use for the Google search. It's a two-letter language code. (e.g., en for English, es for Spanish, or fr for French).
29
+ ### Browser Tools
30
+
31
+ - `browser_goto`
32
+ Navigate browser to a specified URL.
33
+
34
+ - `browser_go_back`
35
+ Go back one step in browser history.
36
+
37
+ - `browser_go_forward`
38
+ Go forward one step in browser history.
39
+
40
+ - `browser_click`
41
+ Click a specific element on the page.
42
+
43
+ - `browser_type`
44
+ Type text into a specified input field.
45
+
46
+ - `browser_wait_for`
47
+ Wait for a specific page element to appear.
48
+
49
+ - `browser_wait`
50
+ Pause execution for a fixed duration.
51
+
52
+ - `browser_screenshot`
53
+ Capture a screenshot of the current page.
30
54
 
55
+ - `browser_get_html`
56
+ Get the full HTML of the current page.
57
+
58
+ - `browser_get_text`
59
+ Get all visible text from the current page.
60
+
61
+ - `browser_scroll`
62
+ Scroll to the bottom of the page.
63
+
64
+ - `browser_scroll_to`
65
+ Scroll a specific element into view.
66
+
67
+ ### Universal Tools
68
+
69
+ - `scrape_html`
70
+ Scrape a URL and return its full HTML content.
71
+ - `scrape_markdown`
72
+ Scrape a URL and return its content as Markdown.
73
+
74
+ - `scrape_screenshot`
75
+ Capture a high-quality screenshot of any webpage.
76
+
77
+ ### Deepserp Tools
78
+
79
+ - `google_search`
80
+ universal information search engine.retrieves any data information.explanatory queries (why, how).comparative analysis requests.
81
+ - `google_scholar`
82
+ Search for academic papers on Google Scholar.
83
+
84
+ - `google_flights`
85
+ Exclusive flight information query tool.
86
+
87
+ - `google_trends`
88
+ Get trending search data from Google Trends.
31
89
 
32
90
  ## Setup Guide
33
91
 
34
92
  ### 1. Get Scrapeless Key
93
+
35
94
  1. Register at [Scrapeless](https://app.scrapeless.com/passport/register?utm_source=github&utm_medium=mcp)
36
95
  2. [Get your free trial](https://app.scrapeless.com/landing/guide?utm_source=github&utm_medium=mcp)
37
96
  3. [Generate API Key](https://app.scrapeless.com/dashboard/settings/api-key?utm_source=github&utm_medium=mcp)
38
97
 
39
-
40
98
  ### 2. Configure
41
99
 
100
+ #### Stdio
101
+
42
102
  ```json
43
103
  {
44
104
  "mcpServers": {
45
- "scrapelessMcpServer": {
105
+ "ScrapelessMcpServer": {
46
106
  "command": "npx",
47
107
  "args": ["-y", "scrapeless-mcp-server"],
48
108
  "env": {
@@ -53,17 +113,38 @@ This project provides several MCP servers that enable AI assistants like Claude
53
113
  }
54
114
  ```
55
115
 
116
+ #### Streamable
117
+
118
+ 1. Run locally
119
+
120
+ ```shell
121
+ npx scrapeless-mcp-server --mode=streamable_http --SCRAPELESS_KEY=YOUR_SCRAPELESS_KEY
122
+ ```
123
+
124
+ 2. Add mcp configuration
125
+
126
+ ```json
127
+ {
128
+ "mcpServers": {
129
+ "ScrapelessMcpServer": {
130
+ "type": "streamable-http",
131
+ "url": "http://127.0.0.1:9593/mcp",
132
+ "disabled": false
133
+ }
134
+ }
135
+ }
136
+ ```
56
137
 
57
138
  ## Example Queries
58
139
 
59
140
  Here are some examples of how to use these servers with Claude Desktop:
60
141
 
61
142
  ### Google Search
143
+
62
144
  ```
63
145
  Please search for "climate change solutions" and summarize the top results.
64
146
  ```
65
147
 
66
-
67
148
  ## Installation
68
149
 
69
150
  ### Prerequisites
@@ -74,22 +155,24 @@ Please search for "climate change solutions" and summarize the top results.
74
155
  ### Install from Source
75
156
 
76
157
  1. Clone the repository:
158
+
77
159
  ```bash
78
160
  git clone https://github.com/scrapeless-ai/scrapeless-mcp-server.git
79
161
  cd scrapeless-mcp-server
80
162
  ```
81
163
 
82
164
  2. Install dependencies:
165
+
83
166
  ```bash
84
167
  npm install
85
168
  ```
86
169
 
87
-
88
170
  3. Build the server:
171
+
89
172
  ```bash
90
173
  npm run build
91
174
  ```
92
175
 
93
-
94
176
  ## Community
95
- - [MCP Server Discord](https://backend.scrapeless.com/app/api/v1/public/links/discord)
177
+
178
+ - [MCP Server Discord](https://backend.scrapeless.com/app/api/v1/public/links/discord)
package/build/cf.js ADDED
@@ -0,0 +1,27 @@
1
+ import { McpAgent } from "agents/mcp";
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { initMcpTools, serverOptions } from "./server.js";
4
+ import { API_KEY_NAME } from "./config.js";
5
+ export class CfMcpServer extends McpAgent {
6
+ server = new McpServer(serverOptions);
7
+ async init() {
8
+ initMcpTools(this.server, this.props.apiKey);
9
+ }
10
+ }
11
+ export default {
12
+ fetch(request, env, ctx) {
13
+ const url = new URL(request.url);
14
+ const apiKeyHeader = request.headers.get(API_KEY_NAME);
15
+ if (!apiKeyHeader) {
16
+ return new Response(`Unauthorized: Missing ${API_KEY_NAME} header`, { status: 401 });
17
+ }
18
+ ctx.props = { apiKey: apiKeyHeader };
19
+ if (url.pathname === "/sse" || url.pathname === "/sse/message") {
20
+ return CfMcpServer.serveSSE("/sse").fetch(request, env, ctx);
21
+ }
22
+ if (url.pathname === "/mcp") {
23
+ return CfMcpServer.serve("/mcp").fetch(request, env, ctx);
24
+ }
25
+ return new Response("Not found", { status: 404 });
26
+ },
27
+ };
package/build/config.js CHANGED
@@ -1,16 +1,11 @@
1
1
  import { getParamValue } from "@chatmcp/sdk/utils/index.js";
2
- const API_KEY = process.env.SCRAPELESS_KEY?.trim() || getParamValue("SCRAPELESS_KEY");
3
- const BASE_URL = process.env.SCRAPELESS_BASE_URL?.trim() || "https://api.scrapeless.com";
2
+ export const API_KEY = process.env.SCRAPELESS_KEY?.trim() || getParamValue("SCRAPELESS_KEY");
3
+ export const BASE_URL = process.env.SCRAPELESS_BASE_URL?.trim() || "https://api.scrapeless.com";
4
+ export const API_KEY_NAME = "x-api-token";
4
5
  export const ServerMode = getParamValue("mode") || "stdio";
5
- export const ServerPort = getParamValue("port") || 9593;
6
- export const ServerEndpoint = getParamValue("endpoint") || "/rest";
7
- if (!API_KEY) {
8
- throw new Error("❌ Missing environment variable: SCRAPELESS_KEY");
9
- }
10
- export const TOOL_ENDPOINTS = {
11
- SCRAPER: "/api/v1/scraper/request",
12
- };
6
+ export const ServerPort = getParamValue("port");
7
+ export const ServerHostname = getParamValue("hostname") || "0.0.0.0";
13
8
  export const SCRAPELESS_CONFIG = {
14
- baseURL: BASE_URL,
15
- token: API_KEY,
9
+ baseApiUrl: BASE_URL,
10
+ apiKey: API_KEY,
16
11
  };
@@ -0,0 +1,120 @@
1
+ import { SessionManager } from "./session-manager.js";
2
+ import { uuid } from "./tools/utils.js";
3
+ export class Context {
4
+ sessionManager;
5
+ currentSessionId = "default-session-id";
6
+ apiKey;
7
+ constructor(apiKey) {
8
+ this.sessionManager = new SessionManager();
9
+ this.apiKey = apiKey;
10
+ }
11
+ getSession(id) {
12
+ return this.sessionManager.getSession(`${id ?? this.currentSessionId}-${this.apiKey}`);
13
+ }
14
+ async run(tool, params) {
15
+ if (!this.apiKey) {
16
+ return {
17
+ content: [
18
+ {
19
+ type: "text",
20
+ text: `API key is missing.`,
21
+ },
22
+ ],
23
+ };
24
+ }
25
+ if (params.sessionId && params.sessionId !== this.currentSessionId) {
26
+ this.currentSessionId = params.sessionId;
27
+ }
28
+ const toolName = tool.name;
29
+ if (toolName === "browser_create") {
30
+ const newSessionId = uuid();
31
+ try {
32
+ await this.sessionManager.createSession(`${newSessionId}-${this.apiKey}`, this.apiKey);
33
+ return {
34
+ content: [
35
+ {
36
+ type: "text",
37
+ text: `New browser session created with ID: ${newSessionId}`,
38
+ },
39
+ ],
40
+ };
41
+ }
42
+ catch (error) {
43
+ return {
44
+ content: [
45
+ {
46
+ type: "text",
47
+ text: `Failed to create browser session. Please try again later.`,
48
+ },
49
+ ],
50
+ };
51
+ }
52
+ }
53
+ if (toolName === "browser_close") {
54
+ const sessionId = params?.sessionId;
55
+ if (!sessionId) {
56
+ return {
57
+ content: [
58
+ {
59
+ type: "text",
60
+ text: `Session ID is missing.`,
61
+ },
62
+ ],
63
+ };
64
+ }
65
+ try {
66
+ await this.sessionManager.closeSession(`${sessionId}-${this.apiKey}`);
67
+ return {
68
+ content: [
69
+ {
70
+ type: "text",
71
+ text: `Browser session with ID: ${sessionId} has been closed.`,
72
+ },
73
+ ],
74
+ };
75
+ }
76
+ catch (error) {
77
+ return {
78
+ content: [
79
+ {
80
+ type: "text",
81
+ text: `Failed to close browser session with ID: ${sessionId}. Please try again later.`,
82
+ },
83
+ ],
84
+ };
85
+ }
86
+ }
87
+ let session = null;
88
+ session = this.sessionManager.getSession(`${this.currentSessionId}-${this.apiKey}`);
89
+ if (!session) {
90
+ const newSessionId = uuid();
91
+ await this.sessionManager.createSession(`${newSessionId}-${this.apiKey}`, this.apiKey);
92
+ this.currentSessionId = newSessionId;
93
+ }
94
+ let toolActionOutput = undefined;
95
+ let actionSucceeded = false;
96
+ try {
97
+ let action = undefined;
98
+ action = tool.handle;
99
+ if (action) {
100
+ toolActionOutput = await action(this, params);
101
+ actionSucceeded = true;
102
+ }
103
+ }
104
+ catch (e) {
105
+ }
106
+ finally {
107
+ if (actionSucceeded && toolActionOutput !== undefined) {
108
+ return toolActionOutput;
109
+ }
110
+ return {
111
+ content: [
112
+ {
113
+ type: "text",
114
+ text: `${toolName} action completed successfully.`,
115
+ },
116
+ ],
117
+ };
118
+ }
119
+ }
120
+ }
package/build/index.js CHANGED
@@ -1,21 +1,18 @@
1
1
  #!/usr/bin/env node
2
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
- import { server } from "./server.js";
4
- import { RestServerTransport } from "@chatmcp/sdk/server/rest.js";
5
- import { ServerMode, ServerPort, ServerEndpoint } from "./config.js";
2
+ import { ServerPort, API_KEY, ServerHostname } from "./config.js";
3
+ import { startHttpTransport, startStdioTransport } from "./transport.js";
4
+ process.env.SCRAPELESS_IS_ONLINE = "true";
6
5
  async function main() {
7
6
  try {
8
- if (ServerMode === "rest") {
9
- const transport = new RestServerTransport({
10
- port: ServerPort,
11
- endpoint: ServerEndpoint,
12
- });
13
- await server.connect(transport);
14
- await transport.startServer();
7
+ if (ServerPort) {
8
+ // See use the /see endpoint
9
+ // Streamable HTTP use the /mcp endpoint
10
+ startHttpTransport(Number(ServerPort), ServerHostname);
15
11
  }
16
12
  else {
17
- const transport = new StdioServerTransport();
18
- await server.connect(transport);
13
+ if (!API_KEY)
14
+ throw new Error("❌ Missing environment variable: SCRAPELESS_KEY");
15
+ startStdioTransport();
19
16
  }
20
17
  }
21
18
  catch (error) {
package/build/server.js CHANGED
@@ -1,102 +1,60 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { z } from "zod";
3
- import { ScrapelessClient } from "./client.js";
4
- import { SCRAPELESS_CONFIG, TOOL_ENDPOINTS } from "./config.js";
5
- const scrapelessClient = new ScrapelessClient(SCRAPELESS_CONFIG);
6
- export const server = new McpServer({
2
+ import { ScrapelessClient } from "@scrapeless-ai/sdk";
3
+ import { SCRAPELESS_CONFIG, API_KEY } from "./config.js";
4
+ import * as toolsList from './tools/index.js';
5
+ import * as browserTools from './tools/browser/browser.js';
6
+ import { Context } from './context.js';
7
+ export const serverOptions = {
7
8
  name: "scrapeless-mcp-server",
8
9
  version: "0.2.0",
9
10
  capabilities: { resources: {}, tools: {} },
10
- });
11
- server.tool("google-search", "Fetch Google Search Results", {
12
- query: z
13
- .string()
14
- .describe("Parameter defines the query you want to search. You can use anything that you would use in a regular Google search. e.g. inurl:, site:, intitle:. We also support advanced search query parameters such as as_dt and as_eq."),
15
- gl: z
16
- .string()
17
- .optional()
18
- .describe("Parameter defines the country to use for the Google search. It's a two-letter country code. (e.g., us for the United States, uk for United Kingdom, or fr for France)."),
19
- hl: z
20
- .string()
21
- .optional()
22
- .describe("Parameter defines the language to use for the Google search. It's a two-letter language code. (e.g., en for English, es for Spanish, or fr for French)."),
23
- }, async ({ query, gl = "us", hl = "en" }) => {
24
- const response = await scrapelessClient.sendRequest(TOOL_ENDPOINTS.SCRAPER, "scraper.google.search", {
25
- q: query,
26
- gl,
27
- hl,
28
- location: "",
29
- });
30
- return {
31
- content: response.content.map((item) => ({
32
- type: "text",
33
- text: item.text,
34
- })),
35
- };
36
- });
37
- server.tool("google-flights-search", "Search for flights using Google Flights via Scrapeless API", {
38
- departure_id: z
39
- .string()
40
- .describe("Airport code for departure (e.g., CDG for Paris Charles de Gaulle)"),
41
- arrival_id: z
42
- .string()
43
- .describe("Airport code for arrival (e.g., BCN for Barcelona)"),
44
- outbound_date: z.string().describe("Departure date in YYYY-MM-DD format"),
45
- return_date: z
46
- .string()
47
- .optional()
48
- .describe("Return date in YYYY-MM-DD format for round trips"),
49
- gl: z
50
- .string()
51
- .optional()
52
- .describe("Country code (e.g., us for United States, fr for France)"),
53
- hl: z
54
- .string()
55
- .optional()
56
- .describe("Language code (e.g., en for English, fr for French)"),
57
- currency: z.string().optional().describe("Currency code (e.g., USD, EUR)"),
58
- travel_class: z
59
- .string()
60
- .optional()
61
- .describe("Travel class (1 for Economy, 2 for Business, 3 for First Class)"),
62
- adults: z.string().optional().describe("Number of adult passengers"),
63
- children: z.string().optional().describe("Number of children passengers"),
64
- max_price: z
65
- .string()
66
- .optional()
67
- .describe("Maximum price to filter results"),
68
- stops: z.string().optional().describe("Filter by number of stops"),
69
- emissions: z
70
- .string()
71
- .optional()
72
- .describe("Filter for emissions (e.g., 'Less emissions only')"),
73
- data_type: z
74
- .string()
75
- .optional()
76
- .default("1")
77
- .describe("Type of flight (1 for Round trip, 2 for One-way)"),
78
- }, async (params) => {
79
- const searchParams = {
80
- departure_id: params.departure_id,
81
- arrival_id: params.arrival_id,
82
- outbound_date: params.outbound_date,
83
- return_date: params.return_date,
84
- data_type: params.data_type || "1",
85
- gl: params.gl || "us",
86
- hl: params.hl || "en",
87
- currency: params.currency,
88
- travel_class: params.travel_class,
89
- adults: params.adults,
90
- children: params.children,
91
- max_price: params.max_price,
92
- stops: params.stops,
93
- emissions: params.emissions,
94
- };
95
- const response = await scrapelessClient.sendRequest(TOOL_ENDPOINTS.SCRAPER, "scraper.google.flights", searchParams);
96
- return {
97
- content: response.content.map((item) => ({
98
- type: "text",
99
- text: item.text,
100
- })),
11
+ };
12
+ export const createMcpServer = (apiKey) => {
13
+ const server = new McpServer(serverOptions);
14
+ initMcpTools(server, apiKey);
15
+ return server.server;
16
+ };
17
+ export const initMcpTools = (server, apiKey) => {
18
+ const getScrapelessClient = () => {
19
+ if (apiKey) {
20
+ return new ScrapelessClient({
21
+ apiKey: apiKey,
22
+ baseApiUrl: SCRAPELESS_CONFIG.baseApiUrl,
23
+ });
24
+ }
25
+ // Fallback for Stdio mode or when no API key is provided
26
+ return new ScrapelessClient(SCRAPELESS_CONFIG);
101
27
  };
102
- });
28
+ // tools registration
29
+ Object.values(toolsList).forEach((tool) => {
30
+ server.tool(tool.name, tool.description, tool.inputSchema, (params) => tool.handle(params, getScrapelessClient()));
31
+ });
32
+ const context = new Context(apiKey ?? API_KEY);
33
+ Object.values(browserTools).forEach((tool) => {
34
+ server.tool(tool.name, tool.description, tool.inputSchema, async (params) => {
35
+ const result = await context.run(tool, params);
36
+ return result;
37
+ });
38
+ });
39
+ };
40
+ export class ServerList {
41
+ _servers = [];
42
+ _serverFactory;
43
+ constructor(serverFactory) {
44
+ this._serverFactory = serverFactory;
45
+ }
46
+ async create() {
47
+ const server = await this._serverFactory();
48
+ this._servers.push(server);
49
+ return server;
50
+ }
51
+ async close(server) {
52
+ const index = this._servers.indexOf(server);
53
+ if (index !== -1)
54
+ this._servers.splice(index, 1);
55
+ await server.close();
56
+ }
57
+ async closeAll() {
58
+ await Promise.all(this._servers.map(server => server.close()));
59
+ }
60
+ }
@@ -0,0 +1,55 @@
1
+ import { Scrapeless } from "@scrapeless-ai/sdk";
2
+ import puppeteer from "puppeteer-core";
3
+ export class SessionManager {
4
+ sessions;
5
+ constructor() {
6
+ this.sessions = new Map();
7
+ }
8
+ async createSession(id, apiKey) {
9
+ if (!this.sessions.has(id)) {
10
+ this.sessions.set(id, { browser: null, page: null, closed: true });
11
+ }
12
+ const scrapelessClient = new Scrapeless({ apiKey });
13
+ const session = this.sessions.get(id);
14
+ const { browserWSEndpoint } = scrapelessClient.browser.create();
15
+ const browser = await puppeteer.connect({
16
+ browserWSEndpoint,
17
+ defaultViewport: null,
18
+ });
19
+ const pages = await browser.pages();
20
+ const page = pages.length > 0 ? pages[0] : await browser.newPage();
21
+ browser.on("disconnected", () => {
22
+ session.closed = true;
23
+ session.browser = null;
24
+ session.page = null;
25
+ });
26
+ session.browser = browser;
27
+ session.page = page;
28
+ session.closed = false;
29
+ return session;
30
+ }
31
+ getSession(id) {
32
+ if (!this.sessions.has(id))
33
+ return null;
34
+ const session = this.sessions.get(id);
35
+ if (!session.browser || !session.browser?.connected) {
36
+ session.browser = null;
37
+ session.page = null;
38
+ session.closed = true;
39
+ this.sessions.delete(id);
40
+ return null;
41
+ }
42
+ return session;
43
+ }
44
+ async closeSession(id) {
45
+ const session = this.sessions.get(id);
46
+ if (session) {
47
+ session.page?.close().catch(() => { });
48
+ session.browser?.disconnect().catch(() => { });
49
+ session.closed = true;
50
+ session.browser = null;
51
+ session.page = null;
52
+ this.sessions.delete(id);
53
+ }
54
+ }
55
+ }