@decodo/mcp-server 1.0.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 ADDED
@@ -0,0 +1,169 @@
1
+ # Decodo MCP Server
2
+
3
+ [![smithery badge](https://smithery.ai/badge/@Decodo/decodo-mcp-server)](https://smithery.ai/server/@Decodo/decodo-mcp-server)
4
+
5
+ <p align="center">
6
+ <a href="https://dashboard.decodo.com/register?page=scrapers%2Fpricing&utm_source=github.com&utm_medium=social&utm_campaign=scraper_api_github"><img src="https://github.com/user-attachments/assets/13b08523-32b0-4c85-8e99-580d7c2a9055"></a>
7
+
8
+ [![](https://dcbadge.vercel.app/api/server/Ja8dqKgvbZ)](https://discord.gg/Ja8dqKgvbZ)
9
+
10
+ This repository provides a Model Context Protocol (MCP) server that connects LLMs and applications
11
+ to Decodo's platform. The server facilitates integration between MCP-compatible clients and Decodo's
12
+ services, streamlining access to our tools and capabilities.
13
+
14
+ ## Features
15
+
16
+ - Easy web data access. Simplified retrieval of information from websites and online sources.
17
+ - Geographic flexibility. Access content regardless of regional restrictions.
18
+ - Enhanced privacy. Browse and collect data while maintaining anonymity.
19
+ - Reliable scraping. Advanced techniques to avoid detection and blocks.
20
+ - Simple integration. Seamless setup with popular MCP clients like Claude Desktop, Cursor, and
21
+ Windsurf.
22
+
23
+ ## Quick start via Smithery (Recommended)
24
+
25
+ Visit the `decodo-mcp-server` page on
26
+ [Smithery](https://smithery.ai/server/@Decodo/decodo-mcp-server), select your preferred MCP client
27
+ and generate installation instructions.
28
+
29
+ ![Smithery interface](img/smithery.png 'Smithery UI')
30
+
31
+ ## Obtain Web Scraping API credentials
32
+
33
+ You'll need the Decodo Advanced Web Scraping API credentials, which you can get by starting a free
34
+ trial on the [dashboard](https://dashboard.decodo.com/). Once you have a plan activated, take a note
35
+ of your generated username and password:
36
+
37
+ ![Decodo dashboard](img/auth.png 'Decodo dashboard')
38
+
39
+ ## Running the MCP server locally (manual)
40
+
41
+ ### Prerequisites
42
+
43
+ - Node.js 18.0+
44
+ - An MCP client - popular choices are [Claude Desktop](https://claude.ai/download) and
45
+ [Cursor](https://www.cursor.com/)
46
+
47
+ ### Step-by-step guide
48
+
49
+ 1. Clone this repository:
50
+
51
+ ```
52
+ git clone https://github.com/Decodo/decodo-mcp-server
53
+ ```
54
+
55
+ 2. Run the following commands in the terminal:
56
+
57
+ ```
58
+ cd decodo-mcp-server
59
+ npm install
60
+ npm run build
61
+ ```
62
+
63
+ 3. Take note of your build location:
64
+
65
+ ```
66
+ cd build/
67
+ pwd
68
+ ```
69
+
70
+ Adding `index.js` to the end of this directory, your build file location should look something like
71
+ this:
72
+
73
+ ```
74
+ /Users/your.user/projects/decodo-mcp/build/index.js
75
+ ```
76
+
77
+ 4. Update your MCP client with the server information:
78
+
79
+ ### Claude Desktop
80
+
81
+ [Follow the guide here](https://modelcontextprotocol.io/quickstart/user) to find the setup file,
82
+ then update `claude_desktop_config.json` to look like this:
83
+
84
+ ```
85
+ {
86
+ "mcpServers": {
87
+ "decodo-mcp": {
88
+ "command": "node",
89
+ "args": ["/Users/your.user/projects/decodo-mcp/build/index.js"],
90
+ "env": {
91
+ "SCRAPER_API_USERNAME": "your_username",
92
+ "SCRAPER_API_PASSWORD": "your_password"
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ ```
99
+
100
+ ### Cursor
101
+
102
+ For installation instructions, see the
103
+ [Cursor documentation](https://docs.cursor.com/context/model-context-protocol#installing-mcp-servers).
104
+
105
+ ## Tools
106
+
107
+ The server exposes the following tools:
108
+
109
+ | Tool | Description | Example prompt |
110
+ | ---------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- |
111
+ | `scrape_as_markdown` | Scrapes any target URL, expects a URL to be given via prompt. Returns results in Markdown. | Scrape peacock.com from a US IP address and tell me the pricing. |
112
+ | `google_search_parsed` | Scrapes Google Search for a given query, and returns parsed results. | Scrape Google Search for shoes and tell me the top position. |
113
+ | `amazon_search_parsed` | Scrapes Amazon Search for a given query, and returns parsed results. | Scrape Amazon Search for toothbrushes |
114
+
115
+ ## Parameters
116
+
117
+ The following parameters are inferred from user prompts:
118
+
119
+ | Parameter | Description |
120
+ | -------------- | ---------------------------------------------------------------------------------------------------- |
121
+ | `jsRender` | Renders target URL in a headless browser. |
122
+ | `geo` | Sets the country from which the request will originate. |
123
+ | `locale` | Sets the locale of the request. |
124
+ | `tokenLimit` | Truncates the response content up to this limit. Useful if the context window is small. |
125
+ | `fullResponse` | Skips automatic truncation and returns full content. If context window is small, may throw warnings. |
126
+
127
+ ## Examples
128
+
129
+ ### Scraping geo-restricted content
130
+
131
+ Query your AI agent with the following prompt:
132
+
133
+ ```
134
+ Scrape peacock.com from a German IP address and tell me the pricing.
135
+ ```
136
+
137
+ This prompt will say that peacock.com is geo-restricted. To bypass the geo-restriction:
138
+
139
+ ```
140
+ Scrape peacock.com from a US IP address and tell me the pricing.
141
+ ```
142
+
143
+ ### Limiting number of response tokens
144
+
145
+ If your agent has a small context window, the content returned from scraping will be automatically
146
+ truncated, in order to avoid context-overflow. You can increase the number of tokens returned within
147
+ your prompt:
148
+
149
+ ```
150
+ Scrape hacker news, return 50k tokens.
151
+ ```
152
+
153
+ If your agent has a big context window, tell it to return `full content`:
154
+
155
+ ```
156
+ Scrape hacker news, return full content.
157
+ ```
158
+
159
+ ## Related repositories
160
+
161
+ [Web Scraping API](https://github.com/Decodo/Web-Scraping-API)
162
+
163
+ [Google Maps scraper](https://github.com/Decodo/google-maps-scraper)
164
+
165
+ [Amazon scraper](https://github.com/Decodo/Amazon-scraper)
166
+
167
+ ## License
168
+
169
+ All code is released under the [MIT License](https://github.com/Decodo/Decodo/blob/master/LICENSE).
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __rest = (this && this.__rest) || function (s, e) {
3
+ var t = {};
4
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
5
+ t[p] = s[p];
6
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
7
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
8
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
9
+ t[p[i]] = s[p[i]];
10
+ }
11
+ return t;
12
+ };
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.ScraperApiClient = void 0;
18
+ const axios_1 = __importDefault(require("axios"));
19
+ class ScraperApiClient {
20
+ constructor({ auth }) {
21
+ this.transformScrapingParams = ({ scrapingParams, }) => {
22
+ const { jsRender } = scrapingParams, rest = __rest(scrapingParams, ["jsRender"]);
23
+ const transformed = Object.assign(Object.assign({}, (jsRender && { headless: 'html' })), rest);
24
+ return transformed;
25
+ };
26
+ this.transformResponse = ({ res }) => {
27
+ const content = res.data.results[0].content;
28
+ return Object.assign(Object.assign({}, res), { data: content });
29
+ };
30
+ this.scrape = async ({ scrapingParams }) => {
31
+ const transformedParams = this.transformScrapingParams({ scrapingParams });
32
+ const res = await axios_1.default.request({
33
+ url: 'https://scraper-api.decodo.com/v2/scrape',
34
+ method: 'POST',
35
+ headers: {
36
+ authorization: `Basic ${this.auth}`,
37
+ 'x-integration': 'mcp',
38
+ },
39
+ data: Object.assign({}, transformedParams),
40
+ });
41
+ const response = this.transformResponse({ res });
42
+ return response;
43
+ };
44
+ this.auth = auth;
45
+ }
46
+ }
47
+ exports.ScraperApiClient = ScraperApiClient;
48
+ //# sourceMappingURL=scraper-api-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scraper-api-client.js","sourceRoot":"","sources":["../../src/clients/scraper-api-client.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,kDAA6C;AAI7C,MAAa,gBAAgB;IAG3B,YAAY,EAAE,IAAI,EAAoB;QAItC,4BAAuB,GAAG,CAAC,EACzB,cAAc,GAGf,EAAoB,EAAE;YACrB,MAAM,EAAE,QAAQ,KAAc,cAAc,EAAvB,IAAI,UAAK,cAAc,EAAtC,YAAqB,CAAiB,CAAC;YAC7C,MAAM,WAAW,mCAAQ,CAAC,QAAQ,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,GAAK,IAAI,CAAE,CAAC;YAEvE,OAAO,WAAW,CAAC;QACrB,CAAC,CAAC;QAEF,sBAAiB,GAAG,CAAI,EAAE,GAAG,EAAqD,EAAE,EAAE;YACpF,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;YAE5C,uCAAY,GAAG,KAAE,IAAI,EAAE,OAAO,IAAG;QACnC,CAAC,CAAC;QAEF,WAAM,GAAG,KAAK,EAAc,EAAE,cAAc,EAAyC,EAAE,EAAE;YACvF,MAAM,iBAAiB,GAAG,IAAI,CAAC,uBAAuB,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;YAE3E,MAAM,GAAG,GAAG,MAAM,eAAK,CAAC,OAAO,CAA4B;gBACzD,GAAG,EAAE,0CAA0C;gBAC/C,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,aAAa,EAAE,SAAS,IAAI,CAAC,IAAI,EAAE;oBACnC,eAAe,EAAE,KAAK;iBACvB;gBACD,IAAI,oBACC,iBAAiB,CACrB;aACF,CAAC,CAAC;YAEH,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;YAEjD,OAAO,QAAQ,CAAC;QAClB,CAAC,CAAC;QAtCA,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CAsCF;AA3CD,4CA2CC"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/clients/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SCRAPER_API_TARGETS = void 0;
4
+ var SCRAPER_API_TARGETS;
5
+ (function (SCRAPER_API_TARGETS) {
6
+ SCRAPER_API_TARGETS["GOOGLE_SEARCH"] = "google_search";
7
+ SCRAPER_API_TARGETS["AMAZON_SEARCH"] = "amazon_search";
8
+ SCRAPER_API_TARGETS["REDDIT_POST"] = "reddit_post";
9
+ SCRAPER_API_TARGETS["REDDIT_SUBREDDIT"] = "reddit_subreddit";
10
+ })(SCRAPER_API_TARGETS || (exports.SCRAPER_API_TARGETS = SCRAPER_API_TARGETS = {}));
11
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAAA,IAAY,mBAMX;AAND,WAAY,mBAAmB;IAC7B,sDAA+B,CAAA;IAC/B,sDAA+B,CAAA;IAE/B,kDAA2B,CAAA;IAC3B,4DAAqC,CAAA;AACvC,CAAC,EANW,mBAAmB,mCAAnB,mBAAmB,QAM9B"}
package/build/index.js ADDED
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("dotenv/config");
4
+ const process_1 = require("process");
5
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
+ const sapi_mcp_server_1 = require("./sapi-mcp-server");
7
+ if (process.env.ENABLE_MCPS_LOGGER) {
8
+ import('mcps-logger/console');
9
+ }
10
+ const parseEnvsOrExit = () => {
11
+ const envs = ['SCRAPER_API_USERNAME', 'SCRAPER_API_PASSWORD'];
12
+ for (const envKey of envs) {
13
+ if (!process.env[envKey]) {
14
+ (0, process_1.exit)(`env ${envKey} missing`);
15
+ }
16
+ }
17
+ return {
18
+ sapiUsername: process.env['SCRAPER_API_USERNAME'],
19
+ sapiPassword: process.env['SCRAPER_API_PASSWORD'],
20
+ };
21
+ };
22
+ async function main() {
23
+ const transport = new stdio_js_1.StdioServerTransport();
24
+ // if there are no envs, some MCP clients will fail silently
25
+ const { sapiUsername, sapiPassword } = parseEnvsOrExit();
26
+ const sapiMcpServer = new sapi_mcp_server_1.ScraperAPIMCPServer({
27
+ sapiUsername,
28
+ sapiPassword,
29
+ });
30
+ await sapiMcpServer.connect(transport);
31
+ console.error('MCP Server running on stdio');
32
+ }
33
+ main().catch(error => {
34
+ console.error('Fatal error in main():', error);
35
+ process.exit(1);
36
+ });
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,yBAAuB;AAEvB,qCAA+B;AAC/B,wEAAiF;AACjF,uDAAwD;AAExD,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC;IACnC,MAAM,CAAC,qBAAqB,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,eAAe,GAAG,GAA2B,EAAE;IACnD,MAAM,IAAI,GAAG,CAAC,sBAAsB,EAAE,sBAAsB,CAAC,CAAC;IAE9D,KAAK,MAAM,MAAM,IAAI,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACzB,IAAA,cAAI,EAAC,OAAO,MAAM,UAAU,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED,OAAO;QACL,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAW;QAC3D,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAW;KAC5D,CAAC;AACJ,CAAC,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,+BAAoB,EAAE,CAAC;IAE7C,4DAA4D;IAC5D,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,eAAe,EAAE,CAAC;IAEzD,MAAM,aAAa,GAAG,IAAI,qCAAmB,CAAC;QAC5C,YAAY;QACZ,YAAY;KACb,CAAC,CAAC;IACH,MAAM,aAAa,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEvC,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC;AAC/C,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;IAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ScraperAPIMCPServer = void 0;
4
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
5
+ const scraper_api_client_1 = require("./clients/scraper-api-client");
6
+ const tools_1 = require("./tools");
7
+ class ScraperAPIMCPServer {
8
+ constructor({ sapiUsername, sapiPassword }) {
9
+ this.server = new mcp_js_1.McpServer({
10
+ name: 'decodo',
11
+ version: '0.1.0',
12
+ capabilities: {
13
+ logging: {},
14
+ resources: {},
15
+ tools: {},
16
+ },
17
+ });
18
+ const auth = Buffer.from(`${sapiUsername}:${sapiPassword}`).toString('base64');
19
+ this.sapiClient = new scraper_api_client_1.ScraperApiClient({ auth });
20
+ this.registerTools();
21
+ this.registerResources();
22
+ }
23
+ connect(transport) {
24
+ this.server.connect(transport);
25
+ }
26
+ registerTools() {
27
+ // scrape
28
+ tools_1.ScrapeAsMarkdownTool.register({ server: this.server, sapiClient: this.sapiClient });
29
+ // targets
30
+ tools_1.GoogleSearchParsedTool.register({ server: this.server, sapiClient: this.sapiClient });
31
+ tools_1.AmazonSearchParsedTool.register({ server: this.server, sapiClient: this.sapiClient });
32
+ tools_1.RedditPostTool.register({ server: this.server, sapiClient: this.sapiClient });
33
+ tools_1.RedditSubredditTool.register({ server: this.server, sapiClient: this.sapiClient });
34
+ }
35
+ registerResources() {
36
+ // todo: expose info about all targets available
37
+ }
38
+ }
39
+ exports.ScraperAPIMCPServer = ScraperAPIMCPServer;
40
+ //# sourceMappingURL=sapi-mcp-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sapi-mcp-server.js","sourceRoot":"","sources":["../src/sapi-mcp-server.ts"],"names":[],"mappings":";;;AAAA,oEAAoE;AAEpE,qEAAgE;AAChE,mCAMiB;AAEjB,MAAa,mBAAmB;IAK9B,YAAY,EAAE,YAAY,EAAE,YAAY,EAAkD;QACxF,IAAI,CAAC,MAAM,GAAG,IAAI,kBAAS,CAAC;YAC1B,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,OAAO;YAChB,YAAY,EAAE;gBACZ,OAAO,EAAE,EAAE;gBACX,SAAS,EAAE,EAAE;gBACb,KAAK,EAAE,EAAE;aACV;SACF,CAAC,CAAC;QAEH,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,IAAI,YAAY,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAE/E,IAAI,CAAC,UAAU,GAAG,IAAI,qCAAgB,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAEjD,IAAI,CAAC,aAAa,EAAE,CAAC;QACrB,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC3B,CAAC;IAED,OAAO,CAAC,SAA+B;QACrC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC;IAED,aAAa;QACX,SAAS;QACT,4BAAoB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAEpF,UAAU;QACV,8BAAsB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QACtF,8BAAsB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QACtF,sBAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAC9E,2BAAmB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;IACrF,CAAC;IAED,iBAAiB;QACf,gDAAgD;IAClD,CAAC;CACF;AA1CD,kDA0CC"}
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ var _a;
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.AmazonSearchParsedTool = void 0;
8
+ const zod_1 = __importDefault(require("zod"));
9
+ const constants_1 = require("../constants");
10
+ const utils_1 = require("../utils");
11
+ const zod_types_1 = require("../zod/zod-types");
12
+ class AmazonSearchParsedTool {
13
+ }
14
+ exports.AmazonSearchParsedTool = AmazonSearchParsedTool;
15
+ _a = AmazonSearchParsedTool;
16
+ AmazonSearchParsedTool.FIELDS_WITH_HIGH_CHAR_COUNT = ['suggested', 'amazons_choices', 'refinements'];
17
+ AmazonSearchParsedTool.transformAutoParsedResponse = ({ obj }) => {
18
+ for (const fieldToRemove of _a.FIELDS_WITH_HIGH_CHAR_COUNT) {
19
+ obj = (0, utils_1.removeKeyFromNestedObject)({ obj, keyToRemove: fieldToRemove });
20
+ }
21
+ const text = JSON.stringify(obj);
22
+ return text;
23
+ };
24
+ AmazonSearchParsedTool.register = ({ server, sapiClient, }) => {
25
+ server.registerTool('amazon_search_parsed', {
26
+ description: 'Scrape Amazon Search results with automatic parsing',
27
+ inputSchema: {
28
+ query: zod_1.default.string().describe('Search query'),
29
+ geo: zod_types_1.zodGeo,
30
+ jsRender: zod_types_1.zodJsRender,
31
+ },
32
+ }, async (scrapingParams) => {
33
+ const params = Object.assign(Object.assign({}, scrapingParams), { target: constants_1.SCRAPER_API_TARGETS.AMAZON_SEARCH, parse: true });
34
+ const { data } = await sapiClient.scrape({ scrapingParams: params });
35
+ const text = _a.transformAutoParsedResponse({ obj: data });
36
+ return {
37
+ content: [
38
+ {
39
+ type: 'text',
40
+ text,
41
+ },
42
+ ],
43
+ };
44
+ });
45
+ };
46
+ //# sourceMappingURL=amazon-search-parsed-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"amazon-search-parsed-tool.js","sourceRoot":"","sources":["../../src/tools/amazon-search-parsed-tool.ts"],"names":[],"mappings":";;;;;;;AAAA,8CAAoB;AAIpB,4CAAmD;AACnD,oCAAqD;AACrD,gDAAkE;AAElE,MAAa,sBAAsB;;AAAnC,wDAoDC;;AAnDQ,kDAA2B,GAAG,CAAC,WAAW,EAAE,iBAAiB,EAAE,aAAa,CAAC,AAAlD,CAAmD;AAE9E,kDAA2B,GAAG,CAAC,EAAE,GAAG,EAAmB,EAAU,EAAE;IACxE,KAAK,MAAM,aAAa,IAAI,EAAsB,CAAC,2BAA2B,EAAE,CAAC;QAC/E,GAAG,GAAG,IAAA,iCAAyB,EAAC,EAAE,GAAG,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAEjC,OAAO,IAAI,CAAC;AACd,CAAC,AARiC,CAQhC;AAEK,+BAAQ,GAAG,CAAC,EACjB,MAAM,EACN,UAAU,GAIX,EAAE,EAAE;IACH,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,WAAW,EAAE,qDAAqD;QAClE,WAAW,EAAE;YACX,KAAK,EAAE,aAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;YAC1C,GAAG,EAAE,kBAAM;YACX,QAAQ,EAAE,uBAAW;SACtB;KACF,EACD,KAAK,EAAE,cAAiC,EAAE,EAAE;QAC1C,MAAM,MAAM,GAAG,gCACV,cAAc,KACjB,MAAM,EAAE,+BAAmB,CAAC,aAAa,EACzC,KAAK,EAAE,IAAI,GACe,CAAC;QAE7B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,UAAU,CAAC,MAAM,CAAS,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC;QAE7E,MAAM,IAAI,GAAG,EAAI,CAAC,2BAA2B,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QAE7D,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI;iBACL;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC,AAtCc,CAsCb"}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ var _a;
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.GoogleSearchParsedTool = void 0;
8
+ const zod_1 = __importDefault(require("zod"));
9
+ const constants_1 = require("../constants");
10
+ const utils_1 = require("../utils");
11
+ const zod_types_1 = require("../zod/zod-types");
12
+ class GoogleSearchParsedTool {
13
+ }
14
+ exports.GoogleSearchParsedTool = GoogleSearchParsedTool;
15
+ _a = GoogleSearchParsedTool;
16
+ GoogleSearchParsedTool.FIELDS_WITH_HIGH_CHAR_COUNT = [
17
+ 'images',
18
+ 'image_data',
19
+ 'related_searches_urls',
20
+ 'factoids',
21
+ 'people_also_buy_from',
22
+ 'what_people_are_saying',
23
+ ];
24
+ GoogleSearchParsedTool.transformAutoParsedResponse = ({ obj }) => {
25
+ for (const fieldToRemove of _a.FIELDS_WITH_HIGH_CHAR_COUNT) {
26
+ obj = (0, utils_1.removeKeyFromNestedObject)({ obj, keyToRemove: fieldToRemove });
27
+ }
28
+ const text = JSON.stringify(obj);
29
+ return text;
30
+ };
31
+ GoogleSearchParsedTool.register = ({ server, sapiClient, }) => {
32
+ server.registerTool('google_search_parsed', {
33
+ description: 'Scrape Google Search results with automatic parsing',
34
+ inputSchema: {
35
+ query: zod_1.default.string().describe('Search query'),
36
+ geo: zod_types_1.zodGeo,
37
+ locale: zod_types_1.zodLocale,
38
+ jsRender: zod_types_1.zodJsRender,
39
+ },
40
+ }, async (scrapingParams) => {
41
+ const params = Object.assign(Object.assign({}, scrapingParams), { target: constants_1.SCRAPER_API_TARGETS.GOOGLE_SEARCH, parse: true });
42
+ const { data } = await sapiClient.scrape({ scrapingParams: params });
43
+ const text = _a.transformAutoParsedResponse({ obj: data });
44
+ return {
45
+ content: [
46
+ {
47
+ type: 'text',
48
+ text,
49
+ },
50
+ ],
51
+ };
52
+ });
53
+ };
54
+ //# sourceMappingURL=google-search-parsed-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-search-parsed-tool.js","sourceRoot":"","sources":["../../src/tools/google-search-parsed-tool.ts"],"names":[],"mappings":";;;;;;;AAAA,8CAAoB;AAIpB,4CAAmD;AACnD,oCAAqD;AACrD,gDAAkE;AAElE,MAAa,sBAAsB;;AAAnC,wDA4DC;;AA3DQ,kDAA2B,GAAG;IACnC,QAAQ;IACR,YAAY;IACZ,uBAAuB;IACvB,UAAU;IACV,sBAAsB;IACtB,wBAAwB;CACzB,AAPiC,CAOhC;AAEK,kDAA2B,GAAG,CAAC,EAAE,GAAG,EAAmB,EAAU,EAAE;IACxE,KAAK,MAAM,aAAa,IAAI,EAAsB,CAAC,2BAA2B,EAAE,CAAC;QAC/E,GAAG,GAAG,IAAA,iCAAyB,EAAC,EAAE,GAAG,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAEjC,OAAO,IAAI,CAAC;AACd,CAAC,AARiC,CAQhC;AAEK,+BAAQ,GAAG,CAAC,EACjB,MAAM,EACN,UAAU,GAIX,EAAE,EAAE;IACH,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,WAAW,EAAE,qDAAqD;QAClE,WAAW,EAAE;YACX,KAAK,EAAE,aAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;YAC1C,GAAG,EAAE,kBAAM;YACX,MAAM,EAAE,qBAAS;YACjB,QAAQ,EAAE,uBAAW;SACtB;KACF,EACD,KAAK,EAAE,cAAiC,EAAE,EAAE;QAC1C,MAAM,MAAM,GAAG,gCACV,cAAc,KACjB,MAAM,EAAE,+BAAmB,CAAC,aAAa,EACzC,KAAK,EAAE,IAAI,GACe,CAAC;QAE7B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,UAAU,CAAC,MAAM,CAAS,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC;QAE7E,MAAM,IAAI,GAAG,EAAI,CAAC,2BAA2B,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;QAE7D,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI;iBACL;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC,AAvCc,CAuCb"}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./amazon-search-parsed-tool"), exports);
18
+ __exportStar(require("./google-search-parsed-tool"), exports);
19
+ __exportStar(require("./reddit-subreddit-tool"), exports);
20
+ __exportStar(require("./reddit-post-tool"), exports);
21
+ __exportStar(require("./scrape-as-markdown-tool"), exports);
22
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,8DAA4C;AAC5C,8DAA4C;AAC5C,0DAAwC;AACxC,qDAAmC;AACnC,4DAA0C"}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ var _a;
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.RedditPostTool = void 0;
8
+ const zod_1 = __importDefault(require("zod"));
9
+ const constants_1 = require("../constants");
10
+ class RedditPostTool {
11
+ }
12
+ exports.RedditPostTool = RedditPostTool;
13
+ _a = RedditPostTool;
14
+ RedditPostTool.register = ({ server, sapiClient, }) => {
15
+ server.registerTool('reddit_post', {
16
+ description: 'Scrape a specific Reddit post',
17
+ inputSchema: {
18
+ url: zod_1.default.string().describe('reddit post URL'),
19
+ },
20
+ }, async (scrapingParams) => {
21
+ const params = Object.assign(Object.assign({}, scrapingParams), { target: constants_1.SCRAPER_API_TARGETS.REDDIT_POST });
22
+ const { data } = await sapiClient.scrape({ scrapingParams: params });
23
+ const text = JSON.stringify(data, null, 2);
24
+ return {
25
+ content: [
26
+ {
27
+ type: 'text',
28
+ text,
29
+ },
30
+ ],
31
+ };
32
+ });
33
+ };
34
+ //# sourceMappingURL=reddit-post-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reddit-post-tool.js","sourceRoot":"","sources":["../../src/tools/reddit-post-tool.ts"],"names":[],"mappings":";;;;;;;AAAA,8CAAoB;AAIpB,4CAAmD;AAEnD,MAAa,cAAc;;AAA3B,wCAqCC;;AApCQ,uBAAQ,GAAG,CAAC,EACjB,MAAM,EACN,UAAU,GAIX,EAAE,EAAE;IACH,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;QACE,WAAW,EAAE,+BAA+B;QAC5C,WAAW,EAAE;YACX,GAAG,EAAE,aAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iBAAiB,CAAC;SAC5C;KACF,EACD,KAAK,EAAE,cAAiC,EAAE,EAAE;QAC1C,MAAM,MAAM,GAAG,gCACV,cAAc,KACjB,MAAM,EAAE,+BAAmB,CAAC,WAAW,GACb,CAAC;QAE7B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,UAAU,CAAC,MAAM,CAAS,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC;QAE7E,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAE3C,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI;iBACL;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC,AAnCc,CAmCb"}
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ var _a;
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.RedditSubredditTool = void 0;
8
+ const zod_1 = __importDefault(require("zod"));
9
+ const constants_1 = require("../constants");
10
+ class RedditSubredditTool {
11
+ }
12
+ exports.RedditSubredditTool = RedditSubredditTool;
13
+ _a = RedditSubredditTool;
14
+ RedditSubredditTool.register = ({ server, sapiClient, }) => {
15
+ server.registerTool('reddit_subreddit', {
16
+ description: 'Scrape Reddit subreddit results with automatic parsing',
17
+ inputSchema: {
18
+ url: zod_1.default.string().describe('URL to subreddit'),
19
+ },
20
+ }, async (scrapingParams) => {
21
+ const params = Object.assign(Object.assign({}, scrapingParams), { target: constants_1.SCRAPER_API_TARGETS.REDDIT_SUBREDDIT });
22
+ const { data } = await sapiClient.scrape({ scrapingParams: params });
23
+ const text = JSON.stringify(data, null, 2);
24
+ return {
25
+ content: [
26
+ {
27
+ type: 'text',
28
+ text,
29
+ },
30
+ ],
31
+ };
32
+ });
33
+ };
34
+ //# sourceMappingURL=reddit-subreddit-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reddit-subreddit-tool.js","sourceRoot":"","sources":["../../src/tools/reddit-subreddit-tool.ts"],"names":[],"mappings":";;;;;;;AAAA,8CAAoB;AAIpB,4CAAmD;AAEnD,MAAa,mBAAmB;;AAAhC,kDAqCC;;AApCQ,4BAAQ,GAAG,CAAC,EACjB,MAAM,EACN,UAAU,GAIX,EAAE,EAAE;IACH,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;QACE,WAAW,EAAE,wDAAwD;QACrE,WAAW,EAAE;YACX,GAAG,EAAE,aAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC;SAC7C;KACF,EACD,KAAK,EAAE,cAAiC,EAAE,EAAE;QAC1C,MAAM,MAAM,GAAG,gCACV,cAAc,KACjB,MAAM,EAAE,+BAAmB,CAAC,gBAAgB,GAClB,CAAC;QAE7B,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,UAAU,CAAC,MAAM,CAAS,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC;QAE7E,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAE3C,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI;iBACL;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC,AAnCc,CAmCb"}
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ var _a;
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ScrapeAsMarkdownTool = void 0;
8
+ const zod_1 = __importDefault(require("zod"));
9
+ const node_html_markdown_1 = require("node-html-markdown");
10
+ const zod_types_1 = require("../zod/zod-types");
11
+ class ScrapeAsMarkdownTool {
12
+ }
13
+ exports.ScrapeAsMarkdownTool = ScrapeAsMarkdownTool;
14
+ _a = ScrapeAsMarkdownTool;
15
+ ScrapeAsMarkdownTool.LARGE_CONTENT_SYMBOL_COUNT = 10000;
16
+ ScrapeAsMarkdownTool.isResponseOverLimit = (content) => {
17
+ return content.length > _a.LARGE_CONTENT_SYMBOL_COUNT;
18
+ };
19
+ ScrapeAsMarkdownTool.shouldTruncateResponse = ({ content, shouldShowFullResponse, }) => {
20
+ if (shouldShowFullResponse) {
21
+ return false;
22
+ }
23
+ return _a.isResponseOverLimit(content);
24
+ };
25
+ ScrapeAsMarkdownTool.truncateResponse = ({ content, limit }) => {
26
+ return content.substring(0, limit);
27
+ };
28
+ ScrapeAsMarkdownTool.transformResponse = ({ html, tokenLimit, shouldShowFullResponse, }) => {
29
+ const markdown = node_html_markdown_1.NodeHtmlMarkdown.translate(html, {});
30
+ if (_a.shouldTruncateResponse({ content: markdown, shouldShowFullResponse })) {
31
+ const truncated = _a.truncateResponse({
32
+ content: markdown,
33
+ limit: tokenLimit || _a.LARGE_CONTENT_SYMBOL_COUNT,
34
+ });
35
+ return { markdown: truncated, isTruncated: true };
36
+ }
37
+ return { markdown, isTruncated: false };
38
+ };
39
+ ScrapeAsMarkdownTool.register = ({ server, sapiClient, }) => {
40
+ server.tool('scrape_as_markdown', 'Scrape the contents of a website and return Markdown-formatted results', {
41
+ url: zod_1.default.string().describe('URL to scrape'),
42
+ geo: zod_types_1.zodGeo,
43
+ locale: zod_types_1.zodLocale,
44
+ jsRender: zod_types_1.zodJsRender,
45
+ tokenLimit: zod_types_1.zodTokenLimit,
46
+ fullResponse: zod_types_1.zodFullResponse,
47
+ }, async (scrapingParams) => {
48
+ const { data } = await sapiClient.scrape({ scrapingParams });
49
+ const { markdown, isTruncated } = _a.transformResponse({
50
+ html: data,
51
+ tokenLimit: scrapingParams.tokenLimit,
52
+ shouldShowFullResponse: scrapingParams.fullResponse,
53
+ });
54
+ return {
55
+ content: [
56
+ {
57
+ type: 'text',
58
+ text: isTruncated
59
+ ? `The website content is over ${_a.LARGE_CONTENT_SYMBOL_COUNT} symbols, therefore, the content has been truncated. If you wish to obtain the full response, just say "full response". Alternatively, you say a specific token limit in the prompt.`
60
+ : 'Full website content retrieved.',
61
+ },
62
+ {
63
+ type: 'text',
64
+ text: markdown,
65
+ },
66
+ ],
67
+ };
68
+ });
69
+ };
70
+ //# sourceMappingURL=scrape-as-markdown-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrape-as-markdown-tool.js","sourceRoot":"","sources":["../../src/tools/scrape-as-markdown-tool.ts"],"names":[],"mappings":";;;;;;;AAAA,8CAAoB;AAIpB,2DAAsD;AACtD,gDAAkG;AAElG,MAAa,oBAAoB;;AAAjC,oDA4FC;;AA3FQ,+CAA0B,GAAG,KAAM,AAAT,CAAU;AAEpC,wCAAmB,GAAG,CAAC,OAAe,EAAE,EAAE;IAC/C,OAAO,OAAO,CAAC,MAAM,GAAG,EAAI,CAAC,0BAA0B,CAAC;AAC1D,CAAC,AAFyB,CAExB;AAEK,2CAAsB,GAAG,CAAC,EAC/B,OAAO,EACP,sBAAsB,GAIvB,EAAE,EAAE;IACH,IAAI,sBAAsB,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,EAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC3C,CAAC,AAZ4B,CAY3B;AAEK,qCAAgB,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,EAAsC,EAAE,EAAE;IACnF,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC,AAFsB,CAErB;AAEK,sCAAiB,GAAG,CAAC,EAC1B,IAAI,EACJ,UAAU,EACV,sBAAsB,GAKvB,EAAE,EAAE;IACH,MAAM,QAAQ,GAAG,qCAAgB,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAEtD,IAAI,EAAI,CAAC,sBAAsB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,sBAAsB,EAAE,CAAC,EAAE,CAAC;QAC/E,MAAM,SAAS,GAAG,EAAI,CAAC,gBAAgB,CAAC;YACtC,OAAO,EAAE,QAAQ;YACjB,KAAK,EAAE,UAAU,IAAI,EAAI,CAAC,0BAA0B;SACrD,CAAC,CAAC;QAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IACpD,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAC1C,CAAC,AArBuB,CAqBtB;AAEK,6BAAQ,GAAG,CAAC,EACjB,MAAM,EACN,UAAU,GAIX,EAAE,EAAE;IACH,MAAM,CAAC,IAAI,CACT,oBAAoB,EACpB,wEAAwE,EACxE;QACE,GAAG,EAAE,aAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC;QACzC,GAAG,EAAE,kBAAM;QACX,MAAM,EAAE,qBAAS;QACjB,QAAQ,EAAE,uBAAW;QACrB,UAAU,EAAE,yBAAa;QACzB,YAAY,EAAE,2BAAe;KAC9B,EACD,KAAK,EAAE,cAAiC,EAAE,EAAE;QAC1C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;QAE7D,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,EAAI,CAAC,iBAAiB,CAAC;YACvD,IAAI,EAAE,IAAI;YACV,UAAU,EAAE,cAAc,CAAC,UAAU;YACrC,sBAAsB,EAAE,cAAc,CAAC,YAAY;SACpD,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,WAAW;wBACf,CAAC,CAAC,+BAA+B,EAAI,CAAC,0BAA0B,sLAAsL;wBACtP,CAAC,CAAC,iCAAiC;iBACtC;gBACD;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,QAAQ;iBACf;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC,AA3Cc,CA2Cb"}
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ var _a;
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ScrapeTool = void 0;
8
+ const zod_1 = __importDefault(require("zod"));
9
+ const node_html_markdown_1 = require("node-html-markdown");
10
+ const zod_types_1 = require("../zod/zod-types");
11
+ class ScrapeTool {
12
+ }
13
+ exports.ScrapeTool = ScrapeTool;
14
+ _a = ScrapeTool;
15
+ ScrapeTool.LARGE_CONTENT_SYMBOL_COUNT = 10000;
16
+ ScrapeTool.isResponseOverLimit = (content) => {
17
+ return content.length > _a.LARGE_CONTENT_SYMBOL_COUNT;
18
+ };
19
+ ScrapeTool.shouldTruncateResponse = ({ content, shouldShowFullResponse, }) => {
20
+ if (shouldShowFullResponse) {
21
+ return false;
22
+ }
23
+ return _a.isResponseOverLimit(content);
24
+ };
25
+ ScrapeTool.truncateResponse = ({ content, limit }) => {
26
+ return content.substring(0, limit);
27
+ };
28
+ ScrapeTool.transformResponse = ({ html, tokenLimit, shouldShowFullResponse, }) => {
29
+ const markdown = node_html_markdown_1.NodeHtmlMarkdown.translate(html, {});
30
+ if (_a.shouldTruncateResponse({ content: markdown, shouldShowFullResponse })) {
31
+ const truncated = _a.truncateResponse({
32
+ content: markdown,
33
+ limit: tokenLimit || _a.LARGE_CONTENT_SYMBOL_COUNT,
34
+ });
35
+ return { markdown: truncated, isTruncated: true };
36
+ }
37
+ return { markdown, isTruncated: false };
38
+ };
39
+ ScrapeTool.register = ({ server, sapiClient, }) => {
40
+ server.tool('scrape', 'Scrape the contents of a website', {
41
+ url: zod_1.default.string().describe('URL to scrape'),
42
+ geo: zod_types_1.zodGeo,
43
+ locale: zod_types_1.zodLocale,
44
+ jsRender: zod_types_1.zodJsRender,
45
+ tokenLimit: zod_types_1.zodTokenLimit,
46
+ fullResponse: zod_types_1.zodFullResponse,
47
+ }, async (scrapingParams) => {
48
+ const { data } = await sapiClient.scrape({ scrapingParams });
49
+ const { markdown, isTruncated } = _a.transformResponse({
50
+ html: data,
51
+ tokenLimit: scrapingParams.tokenLimit,
52
+ shouldShowFullResponse: scrapingParams.fullResponse,
53
+ });
54
+ return {
55
+ content: [
56
+ {
57
+ type: 'text',
58
+ text: isTruncated
59
+ ? `The website content is over ${_a.LARGE_CONTENT_SYMBOL_COUNT} symbols, therefore, I have truncated the content. If you wish to obtain the full response, just say "full response". Alternatively, you can tell me a specific token limit.`
60
+ : 'Full website content retrieved.',
61
+ },
62
+ {
63
+ type: 'text',
64
+ text: markdown,
65
+ },
66
+ ],
67
+ };
68
+ });
69
+ };
70
+ //# sourceMappingURL=scrape-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrape-tool.js","sourceRoot":"","sources":["../../src/tools/scrape-tool.ts"],"names":[],"mappings":";;;;;;;AAAA,8CAAoB;AAIpB,2DAAsD;AACtD,gDAAkG;AAElG,MAAa,UAAU;;AAAvB,gCA4FC;;AA3FQ,qCAA0B,GAAG,KAAM,AAAT,CAAU;AAEpC,8BAAmB,GAAG,CAAC,OAAe,EAAE,EAAE;IAC/C,OAAO,OAAO,CAAC,MAAM,GAAG,EAAI,CAAC,0BAA0B,CAAC;AAC1D,CAAC,AAFyB,CAExB;AAEK,iCAAsB,GAAG,CAAC,EAC/B,OAAO,EACP,sBAAsB,GAIvB,EAAE,EAAE;IACH,IAAI,sBAAsB,EAAE,CAAC;QAC3B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,EAAI,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC3C,CAAC,AAZ4B,CAY3B;AAEK,2BAAgB,GAAG,CAAC,EAAE,OAAO,EAAE,KAAK,EAAsC,EAAE,EAAE;IACnF,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC,AAFsB,CAErB;AAEK,4BAAiB,GAAG,CAAC,EAC1B,IAAI,EACJ,UAAU,EACV,sBAAsB,GAKvB,EAAE,EAAE;IACH,MAAM,QAAQ,GAAG,qCAAgB,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAEtD,IAAI,EAAI,CAAC,sBAAsB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,sBAAsB,EAAE,CAAC,EAAE,CAAC;QAC/E,MAAM,SAAS,GAAG,EAAI,CAAC,gBAAgB,CAAC;YACtC,OAAO,EAAE,QAAQ;YACjB,KAAK,EAAE,UAAU,IAAI,EAAI,CAAC,0BAA0B;SACrD,CAAC,CAAC;QAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IACpD,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAC1C,CAAC,AArBuB,CAqBtB;AAEK,mBAAQ,GAAG,CAAC,EACjB,MAAM,EACN,UAAU,GAIX,EAAE,EAAE;IACH,MAAM,CAAC,IAAI,CACT,QAAQ,EACR,kCAAkC,EAClC;QACE,GAAG,EAAE,aAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC;QACzC,GAAG,EAAE,kBAAM;QACX,MAAM,EAAE,qBAAS;QACjB,QAAQ,EAAE,uBAAW;QACrB,UAAU,EAAE,yBAAa;QACzB,YAAY,EAAE,2BAAe;KAC9B,EACD,KAAK,EAAE,cAAiC,EAAE,EAAE;QAC1C,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC;QAE7D,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,EAAI,CAAC,iBAAiB,CAAC;YACvD,IAAI,EAAE,IAAI;YACV,UAAU,EAAE,cAAc,CAAC,UAAU;YACrC,sBAAsB,EAAE,cAAc,CAAC,YAAY;SACpD,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,WAAW;wBACf,CAAC,CAAC,+BAA+B,EAAI,CAAC,0BAA0B,8KAA8K;wBAC9O,CAAC,CAAC,iCAAiC;iBACtC;gBACD;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,QAAQ;iBACf;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC,AA3Cc,CA2Cb"}
package/build/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/build/utils.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.removeKeyFromNestedObject = void 0;
4
+ const removeKeyFromNestedObject = ({ obj, keyToRemove, }) => {
5
+ if (typeof obj !== 'object' || obj === null) {
6
+ return obj;
7
+ }
8
+ if (Array.isArray(obj)) {
9
+ return obj.map(item => (0, exports.removeKeyFromNestedObject)({ obj: item, keyToRemove }));
10
+ }
11
+ const newObj = {};
12
+ for (const key in obj) {
13
+ if (key === keyToRemove) {
14
+ continue;
15
+ }
16
+ newObj[key] = (0, exports.removeKeyFromNestedObject)({ obj: obj[key], keyToRemove });
17
+ }
18
+ return newObj;
19
+ };
20
+ exports.removeKeyFromNestedObject = removeKeyFromNestedObject;
21
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AAAO,MAAM,yBAAyB,GAAG,CAAC,EACxC,GAAG,EACH,WAAW,GAIZ,EAAU,EAAE;IACX,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAC5C,OAAO,GAAG,CAAC;IACb,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAA,iCAAyB,EAAC,EAAE,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;IAChF,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,CAAC;IAElB,KAAK,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QACtB,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;YACxB,SAAS;QACX,CAAC;QAED,MAAM,CAAC,GAAG,CAAC,GAAG,IAAA,iCAAyB,EAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAC;AA1BW,QAAA,yBAAyB,6BA0BpC"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.zodFullResponse = exports.zodTokenLimit = exports.zodJsRender = exports.zodLocale = exports.zodGeo = void 0;
7
+ const zod_1 = __importDefault(require("zod"));
8
+ exports.zodGeo = zod_1.default
9
+ .string()
10
+ .describe('Geolocation of the desired request, expressed as a country name')
11
+ .optional();
12
+ exports.zodLocale = zod_1.default.string().describe('Locale of the desired request').optional();
13
+ exports.zodJsRender = zod_1.default
14
+ .boolean()
15
+ .describe('Should the request be opened in a headless browser, false by default')
16
+ .optional();
17
+ exports.zodTokenLimit = zod_1.default
18
+ .number()
19
+ .describe(`The number of tokens to return in the response - anything above this limit will be truncated`)
20
+ .optional();
21
+ exports.zodFullResponse = zod_1.default
22
+ .boolean()
23
+ .describe(`If true, content will not be truncated`)
24
+ .optional();
25
+ //# sourceMappingURL=zod-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zod-types.js","sourceRoot":"","sources":["../../src/zod/zod-types.ts"],"names":[],"mappings":";;;;;;AAAA,8CAAoB;AAEP,QAAA,MAAM,GAAG,aAAC;KACpB,MAAM,EAAE;KACR,QAAQ,CAAC,iEAAiE,CAAC;KAC3E,QAAQ,EAAE,CAAC;AAED,QAAA,SAAS,GAAG,aAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC,CAAC,QAAQ,EAAE,CAAC;AAE5E,QAAA,WAAW,GAAG,aAAC;KACzB,OAAO,EAAE;KACT,QAAQ,CAAC,sEAAsE,CAAC;KAChF,QAAQ,EAAE,CAAC;AAED,QAAA,aAAa,GAAG,aAAC;KAC3B,MAAM,EAAE;KACR,QAAQ,CACP,8FAA8F,CAC/F;KACA,QAAQ,EAAE,CAAC;AAED,QAAA,eAAe,GAAG,aAAC;KAC7B,OAAO,EAAE;KACT,QAAQ,CAAC,wCAAwC,CAAC;KAClD,QAAQ,EAAE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@decodo/mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "bin": {
6
+ "decodo-mcp": "./build/index.js"
7
+ },
8
+ "main": "index.js",
9
+ "scripts": {
10
+ "build": "tsc && chmod 755 build/index.js",
11
+ "dev": "pnpm run build && mcp-inspector",
12
+ "test": "jest"
13
+ },
14
+ "keywords": [],
15
+ "author": "",
16
+ "license": "ISC",
17
+ "files": [
18
+ "build"
19
+ ],
20
+ "devDependencies": {
21
+ "@modelcontextprotocol/inspector": "^0.14.0",
22
+ "@types/jest": "^30.0.0",
23
+ "@types/node": "^24.0.0",
24
+ "jest": "^30.0.0",
25
+ "mcps-logger": "^1.0.0",
26
+ "ts-jest": "^29.4.0",
27
+ "typescript": "^5.8.3",
28
+ "zod": "^3.25.57"
29
+ },
30
+ "dependencies": {
31
+ "@modelcontextprotocol/sdk": "^1.12.3",
32
+ "axios": "^1.9.0",
33
+ "dotenv": "^16.5.0",
34
+ "node-html-markdown": "^1.3.0"
35
+ }
36
+ }