scrapeless-mcp-server 0.3.0 → 0.4.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/README.md +97 -14
- package/build/cf.js +27 -0
- package/build/config.js +7 -12
- package/build/context.js +120 -0
- package/build/index.js +10 -13
- package/build/server.js +55 -97
- package/build/session-manager.js +55 -0
- package/build/tools/browser/browser.js +444 -0
- package/build/tools/deepserp/googleFlights.js +188 -0
- package/build/tools/deepserp/googleScholar.js +74 -0
- package/build/tools/deepserp/googleSearch.js +269 -0
- package/build/tools/deepserp/googleTrends.js +208 -0
- package/build/tools/index.js +7 -0
- package/build/tools/universal/scrapeHtml.js +22 -0
- package/build/tools/universal/scrapeMarkdown.js +22 -0
- package/build/tools/universal/scrapeScreenshot.js +48 -0
- package/build/tools/utils.js +86 -0
- package/build/transport.js +120 -0
- package/package.json +11 -2
- package/build/client.js +0 -40
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
|
-
###
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
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")
|
|
6
|
-
export const
|
|
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
|
-
|
|
15
|
-
|
|
9
|
+
baseApiUrl: BASE_URL,
|
|
10
|
+
apiKey: API_KEY,
|
|
16
11
|
};
|
package/build/context.js
ADDED
|
@@ -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 {
|
|
3
|
-
import {
|
|
4
|
-
|
|
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 (
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
18
|
-
|
|
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 {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
}
|