scrapeless-mcp-server 0.4.4 → 0.4.6

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
@@ -98,6 +98,17 @@ Scrapeless MCP Server supports both **Stdio** and **Streamable HTTP** transport
98
98
  }
99
99
  ```
100
100
 
101
+ #### Advanced Options
102
+
103
+ Customize browser session behavior with optional parameters. These can be set via environment variables (for Stdio) or HTTP headers (for Streamable HTTP):
104
+
105
+ | Stdio (Env Var) | Streamable HTTP (HTTP Header) | Description |
106
+ | ----------------------- | ----------------------------- | ------------------------------------------------------------ |
107
+ | BROWSER_PROFILE_ID | x-browser-profile-id | Specifies a reusable browser profile ID for session continuity. |
108
+ | BROWSER_PROFILE_PERSIST | x-browser-profile-persist | Enables persistent storage for cookies, local storage, etc. |
109
+ | BROWSER_SESSION_TTL | x-browser-session-ttl | Defines the **maximum session timeout** in seconds. The session will automatically expire after this duration of inactivity. |
110
+
111
+
101
112
  ## Integration with Claude Desktop
102
113
 
103
114
  1. Open **Claude Desktop**
package/build/cf.js CHANGED
@@ -5,7 +5,7 @@ import { API_KEY_NAME } from "./config.js";
5
5
  export class CfMcpServer extends McpAgent {
6
6
  server = new McpServer(serverOptions);
7
7
  async init() {
8
- initMcpTools(this.server, this.props.apiKey);
8
+ initMcpTools(this.server, this.props.headers, this.props.apiKey);
9
9
  }
10
10
  }
11
11
  export default {
@@ -15,7 +15,7 @@ export default {
15
15
  if (!apiKeyHeader) {
16
16
  return new Response(`Unauthorized: Missing ${API_KEY_NAME} header`, { status: 401 });
17
17
  }
18
- ctx.props = { apiKey: apiKeyHeader };
18
+ ctx.props = { apiKey: apiKeyHeader, headers: request.headers };
19
19
  if (url.pathname === "/sse" || url.pathname === "/sse/message") {
20
20
  return CfMcpServer.serveSSE("/sse").fetch(request, env, ctx);
21
21
  }
@@ -0,0 +1,40 @@
1
+ import { Context } from "./context.js";
2
+ import { API_KEY } from "./config.js";
3
+ export class ContextManager {
4
+ static instance;
5
+ contexts;
6
+ constructor() {
7
+ this.contexts = new Map();
8
+ }
9
+ static getInstance() {
10
+ if (!ContextManager.instance) {
11
+ ContextManager.instance = new ContextManager();
12
+ }
13
+ return ContextManager.instance;
14
+ }
15
+ getContext(apiKey) {
16
+ const key = apiKey ?? API_KEY;
17
+ if (!this.contexts.has(key)) {
18
+ console.log(`Creating new Context for API key: ${key?.substring(0, 8)}...`);
19
+ this.contexts.set(key, new Context(key));
20
+ }
21
+ else {
22
+ console.log(`Reusing existing Context for API key: ${key?.substring(0, 8)}...`);
23
+ }
24
+ return this.contexts.get(key);
25
+ }
26
+ clearContext(apiKey) {
27
+ const key = apiKey ?? API_KEY;
28
+ if (this.contexts.has(key)) {
29
+ this.contexts.delete(key);
30
+ console.log(`Cleared Context for API key: ${key?.substring(0, 8)}...`);
31
+ }
32
+ }
33
+ clearAllContexts() {
34
+ this.contexts.clear();
35
+ console.log("Cleared all Contexts");
36
+ }
37
+ getContextCount() {
38
+ return this.contexts.size;
39
+ }
40
+ }
package/build/context.js CHANGED
@@ -5,13 +5,13 @@ export class Context {
5
5
  currentSessionId = "default-session-id";
6
6
  apiKey;
7
7
  constructor(apiKey) {
8
- this.sessionManager = new SessionManager();
8
+ this.sessionManager = SessionManager.getInstance();
9
9
  this.apiKey = apiKey;
10
10
  }
11
11
  getSession(id) {
12
12
  return this.sessionManager.getSession(`${id ?? this.currentSessionId}-${this.apiKey}`);
13
13
  }
14
- async run(tool, params) {
14
+ async run(tool, params, headers) {
15
15
  if (!this.apiKey) {
16
16
  return {
17
17
  content: [
@@ -29,7 +29,7 @@ export class Context {
29
29
  if (toolName === "browser_create") {
30
30
  const newSessionId = uuid();
31
31
  try {
32
- await this.sessionManager.createSession(`${newSessionId}-${this.apiKey}`, this.apiKey);
32
+ await this.sessionManager.createSession(`${newSessionId}-${this.apiKey}`, this.apiKey, headers);
33
33
  return {
34
34
  content: [
35
35
  {
package/build/server.js CHANGED
@@ -1,20 +1,20 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { ScrapelessClient } from "@scrapeless-ai/sdk";
3
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';
4
+ import * as toolsList from "./tools/index.js";
5
+ import * as browserTools from "./tools/browser/browser.js";
6
+ import { ContextManager } from "./context-manager.js";
7
7
  export const serverOptions = {
8
8
  name: "scrapeless-mcp-server",
9
9
  version: "0.2.0",
10
10
  capabilities: { resources: {}, tools: {} },
11
11
  };
12
- export const createMcpServer = (apiKey) => {
12
+ export const createMcpServer = (options) => {
13
13
  const server = new McpServer(serverOptions);
14
- initMcpTools(server, apiKey);
14
+ initMcpTools(server, options?.headers, options?.apiKey);
15
15
  return server.server;
16
16
  };
17
- export const initMcpTools = (server, apiKey) => {
17
+ export const initMcpTools = (server, headers, apiKey) => {
18
18
  const getScrapelessClient = () => {
19
19
  if (apiKey) {
20
20
  return new ScrapelessClient({
@@ -29,10 +29,10 @@ export const initMcpTools = (server, apiKey) => {
29
29
  Object.values(toolsList).forEach((tool) => {
30
30
  server.tool(tool.name, tool.description, tool.inputSchema, (params) => tool.handle(params, getScrapelessClient()));
31
31
  });
32
- const context = new Context(apiKey ?? API_KEY);
32
+ const context = ContextManager.getInstance().getContext(apiKey ?? API_KEY);
33
33
  Object.values(browserTools).forEach((tool) => {
34
34
  server.tool(tool.name, tool.description, tool.inputSchema, async (params) => {
35
- const result = await context.run(tool, params);
35
+ const result = await context.run(tool, params, headers);
36
36
  return result;
37
37
  });
38
38
  });
@@ -55,6 +55,6 @@ export class ServerList {
55
55
  await server.close();
56
56
  }
57
57
  async closeAll() {
58
- await Promise.all(this._servers.map(server => server.close()));
58
+ await Promise.all(this._servers.map((server) => server.close()));
59
59
  }
60
60
  }
@@ -1,17 +1,37 @@
1
1
  import { Scrapeless } from "@scrapeless-ai/sdk";
2
+ import { getParamValue } from "@chatmcp/sdk/utils/index.js";
2
3
  import puppeteer from "puppeteer-core";
3
4
  export class SessionManager {
5
+ static instance;
4
6
  sessions;
5
7
  constructor() {
6
8
  this.sessions = new Map();
7
9
  }
8
- async createSession(id, apiKey) {
10
+ static getInstance() {
11
+ if (!SessionManager.instance) {
12
+ SessionManager.instance = new SessionManager();
13
+ }
14
+ return SessionManager.instance;
15
+ }
16
+ async createSession(id, apiKey, headers) {
9
17
  if (!this.sessions.has(id)) {
10
18
  this.sessions.set(id, { browser: null, page: null, closed: true });
11
19
  }
12
20
  const scrapelessClient = new Scrapeless({ apiKey });
13
21
  const session = this.sessions.get(id);
14
- const { browserWSEndpoint } = scrapelessClient.browser.create();
22
+ const { browserWSEndpoint } = scrapelessClient.browser.create({
23
+ session_ttl: Number(process.env.BROWSER_SESSION_TTL ||
24
+ getParamValue("BROWSER_SESSION_TTL") ||
25
+ headers?.["x-browser-session-ttl"] ||
26
+ 30000),
27
+ profile_id: process.env.BROWSER_PROFILE_ID ||
28
+ getParamValue("BROWSER_PROFILE_ID") ||
29
+ headers?.["x-browser-profile-id"] ||
30
+ "",
31
+ profile_persist: Boolean(process.env.BROWSER_PROFILE_PERSIST ||
32
+ getParamValue("BROWSER_PROFILE_PERSIST") ||
33
+ headers?.["x-browser-profile-persist"]),
34
+ });
15
35
  const browser = await puppeteer.connect({
16
36
  browserWSEndpoint,
17
37
  defaultViewport: null,
@@ -22,6 +42,7 @@ export class SessionManager {
22
42
  session.closed = true;
23
43
  session.browser = null;
24
44
  session.page = null;
45
+ this.sessions.delete(id);
25
46
  });
26
47
  session.browser = browser;
27
48
  session.page = page;
@@ -115,6 +115,6 @@ function createRemoteServerList(req, res) {
115
115
  res.end("Unauthorized: Missing x-api-token header");
116
116
  return null;
117
117
  }
118
- const serverList = new ServerList(async () => createMcpServer(apiKey));
118
+ const serverList = new ServerList(async () => createMcpServer({ headers: req.headers, apiKey }));
119
119
  return serverList;
120
120
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scrapeless-mcp-server",
3
- "version": "0.4.4",
3
+ "version": "0.4.6",
4
4
  "main": "index.js",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,7 +31,7 @@
31
31
  "dependencies": {
32
32
  "@chatmcp/sdk": "^1.0.5",
33
33
  "@modelcontextprotocol/sdk": "^1.8.0",
34
- "@scrapeless-ai/sdk": "^1.6.1",
34
+ "@scrapeless-ai/sdk": "^1.8.0",
35
35
  "agents": "^0.0.101",
36
36
  "axios": "^1.8.4",
37
37
  "express": "^5.1.0",