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.
@@ -0,0 +1,86 @@
1
+ import { customAlphabet } from "nanoid";
2
+ const notRecordRoleList = [
3
+ 'Page Title',
4
+ 'Root Web Area',
5
+ 'RootWebArea',
6
+ 'generic',
7
+ 'none',
8
+ 'paragraph',
9
+ 'InlineTextBox',
10
+ 'image'
11
+ ];
12
+ function assembleSnapshot(snap, indent = 0) {
13
+ let str = '';
14
+ const spaces = ' '.repeat(indent);
15
+ for (const child of snap.children || []) {
16
+ if (child.role === 'Page Title')
17
+ continue;
18
+ if (child.role === 'Root Web Area')
19
+ continue;
20
+ if (!notRecordRoleList.includes(child.role)) {
21
+ str += `${spaces}- ${child.role}: ${child.name}\n`;
22
+ }
23
+ if (child.role === 'image') {
24
+ str += `${spaces} - image\n`;
25
+ }
26
+ if (child.children && child.children.length > 0) {
27
+ str += assembleSnapshot(child, !notRecordRoleList.includes(child.role) ? indent + 2 : indent);
28
+ }
29
+ }
30
+ return str;
31
+ }
32
+ export function defineTool(tool) {
33
+ return tool;
34
+ }
35
+ export async function wrapMcpResponse(request) {
36
+ try {
37
+ const data = await request();
38
+ return {
39
+ content: [
40
+ {
41
+ type: "text",
42
+ text: `Response:\n\n${JSON.stringify(data)}`,
43
+ },
44
+ ],
45
+ };
46
+ }
47
+ catch (error) {
48
+ return {
49
+ content: [
50
+ {
51
+ type: "text",
52
+ text: `Failed to fetch data. Error: ${error.message}`,
53
+ },
54
+ ],
55
+ };
56
+ }
57
+ }
58
+ export function wrapMcpBrowserResponse(content) {
59
+ return {
60
+ content: [
61
+ {
62
+ type: "text",
63
+ text: content,
64
+ },
65
+ ],
66
+ };
67
+ }
68
+ export function uuid() {
69
+ const nanoid1 = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 4);
70
+ const nanoid2 = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 4);
71
+ const nanoid3 = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 4);
72
+ const nanoid4 = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 6);
73
+ return `${nanoid1()}-${nanoid2()}-${nanoid3()}-${nanoid4()}`;
74
+ }
75
+ export async function snapshot(page) {
76
+ const snap = await page.accessibility.snapshot({ interestingOnly: false, includeIframes: true });
77
+ if (!snap) {
78
+ return "No accessibility snapshot available.";
79
+ }
80
+ const url = page.url();
81
+ const title = await page.title();
82
+ let snapshotString = `- Page URL: ${url}\n`;
83
+ snapshotString += `- Page Title: ${title}\n`;
84
+ snapshotString += `\`\`\`yaml\n- Page Snapshot\n\`\`\`yaml`;
85
+ return snapshotString + assembleSnapshot(snap, 0);
86
+ }
@@ -0,0 +1,120 @@
1
+ import http from "node:http";
2
+ import assert from "node:assert";
3
+ import crypto from "node:crypto";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
6
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
7
+ import { createMcpServer, ServerList } from "./server.js";
8
+ import { API_KEY_NAME } from "./config.js";
9
+ export async function startStdioTransport() {
10
+ const serverList = new ServerList(async () => createMcpServer());
11
+ const server = await serverList.create();
12
+ await server.connect(new StdioServerTransport());
13
+ }
14
+ async function handleSSE(req, res, url, sessions) {
15
+ const serverList = createRemoteServerList(req, res);
16
+ if (!serverList)
17
+ return;
18
+ if (req.method === "POST") {
19
+ const sessionId = url.searchParams.get("sessionId");
20
+ if (!sessionId) {
21
+ res.statusCode = 400;
22
+ return res.end("Missing sessionId");
23
+ }
24
+ const transport = sessions.get(sessionId);
25
+ if (!transport) {
26
+ res.statusCode = 404;
27
+ return res.end("Session not found");
28
+ }
29
+ return await transport.handlePostMessage(req, res);
30
+ }
31
+ else if (req.method === "GET") {
32
+ const transport = new SSEServerTransport("/sse", res);
33
+ sessions.set(transport.sessionId, transport);
34
+ const server = await serverList.create();
35
+ res.on("close", () => {
36
+ sessions.delete(transport.sessionId);
37
+ serverList.close(server).catch((e) => {
38
+ // eslint-disable-next-line no-console
39
+ // console.error(e);
40
+ });
41
+ });
42
+ return await server.connect(transport);
43
+ }
44
+ res.statusCode = 405;
45
+ res.end("Method not allowed");
46
+ }
47
+ async function handleStreamable(req, res, sessions) {
48
+ const serverList = createRemoteServerList(req, res);
49
+ if (!serverList)
50
+ return;
51
+ const sessionId = req.headers["mcp-session-id"];
52
+ if (sessionId) {
53
+ const transport = sessions.get(sessionId);
54
+ if (!transport) {
55
+ res.statusCode = 404;
56
+ res.end("Session not found");
57
+ return;
58
+ }
59
+ return await transport.handleRequest(req, res);
60
+ }
61
+ if (req.method === "POST") {
62
+ const transport = new StreamableHTTPServerTransport({
63
+ sessionIdGenerator: () => crypto.randomUUID(),
64
+ onsessioninitialized: (sessionId) => {
65
+ sessions.set(sessionId, transport);
66
+ },
67
+ });
68
+ transport.onclose = () => {
69
+ if (transport.sessionId)
70
+ sessions.delete(transport.sessionId);
71
+ };
72
+ const server = await serverList.create();
73
+ await server.connect(transport);
74
+ return await transport.handleRequest(req, res);
75
+ }
76
+ res.statusCode = 400;
77
+ res.end("Invalid request");
78
+ }
79
+ export function startHttpTransport(port, hostname) {
80
+ const sseSessions = new Map();
81
+ const streamableSessions = new Map();
82
+ const httpServer = http.createServer(async (req, res) => {
83
+ const url = new URL(`http://localhost${req.url}`);
84
+ if (url.pathname.startsWith("/mcp"))
85
+ await handleStreamable(req, res, streamableSessions);
86
+ else
87
+ await handleSSE(req, res, url, sseSessions);
88
+ });
89
+ httpServer.listen(port, hostname, () => {
90
+ const address = httpServer.address();
91
+ assert(address, "Could not bind server socket");
92
+ let url;
93
+ if (typeof address === "string") {
94
+ url = address;
95
+ }
96
+ else {
97
+ const resolvedPort = address.port;
98
+ let resolvedHost = address.family === "IPv4" ? address.address : `[${address.address}]`;
99
+ if (resolvedHost === "0.0.0.0" || resolvedHost === "[::]")
100
+ resolvedHost = "localhost";
101
+ url = `http://${resolvedHost}:${resolvedPort}`;
102
+ }
103
+ const message = [
104
+ `Listening on ${url}`,
105
+ "If your client supports streamable HTTP, you can use the /mcp endpoint instead.",
106
+ ].join("\n");
107
+ // eslint-disable-next-line no-console
108
+ console.log(message);
109
+ });
110
+ }
111
+ function createRemoteServerList(req, res) {
112
+ const apiKey = req.headers[API_KEY_NAME];
113
+ if (!apiKey) {
114
+ res.statusCode = 401;
115
+ res.end("Unauthorized: Missing x-api-token header");
116
+ return null;
117
+ }
118
+ const serverList = new ServerList(async () => createMcpServer(apiKey));
119
+ return serverList;
120
+ }
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "scrapeless-mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "scrapeless-mcp-server": "./build/index.js"
8
8
  },
9
9
  "scripts": {
10
- "build": "tsc && chmod 755 build/index.js"
10
+ "build": "tsc && chmod 755 build/index.js",
11
+ "start:http": "node build/index.js --port=9593",
12
+ "dev:http": "tsc && node build/index.js --port=9593"
11
13
  },
12
14
  "files": [
13
15
  "build"
@@ -29,10 +31,17 @@
29
31
  "dependencies": {
30
32
  "@chatmcp/sdk": "^1.0.5",
31
33
  "@modelcontextprotocol/sdk": "^1.8.0",
34
+ "@scrapeless-ai/sdk": "^1.6.1",
35
+ "agents": "^0.0.101",
32
36
  "axios": "^1.8.4",
37
+ "express": "^5.1.0",
38
+ "nanoid": "^5.1.5",
39
+ "puppeteer-core": "^24.12.0",
40
+ "yaml": "^2.8.0",
33
41
  "zod": "^3.24.2"
34
42
  },
35
43
  "devDependencies": {
44
+ "@types/express": "^5.0.3",
36
45
  "@types/node": "^22.13.14",
37
46
  "typescript": "^5.8.2"
38
47
  },
package/build/client.js DELETED
@@ -1,40 +0,0 @@
1
- import axios from "axios";
2
- export class ScrapelessClient {
3
- api;
4
- constructor(config) {
5
- this.api = axios.create({
6
- baseURL: config.baseURL,
7
- headers: {
8
- "Content-Type": "application/json",
9
- "x-api-token": config.token,
10
- },
11
- });
12
- }
13
- async sendRequest(endpoint, actor, inputData) {
14
- try {
15
- const response = await this.api.post(endpoint, {
16
- actor,
17
- input: inputData,
18
- });
19
- return {
20
- content: [
21
- {
22
- type: "text",
23
- text: `Response:\n\n${JSON.stringify(response.data)}`,
24
- },
25
- ],
26
- };
27
- }
28
- catch (error) {
29
- console.error("Scrapeless API Error:", error);
30
- return {
31
- content: [
32
- {
33
- type: "text",
34
- text: `Failed to fetch data. Error: ${error.message}`,
35
- },
36
- ],
37
- };
38
- }
39
- }
40
- }