@mambalabsdev/mcp-site-crawler-links-tables 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +114 -2
- package/SECURITY.md +31 -0
- package/build/index.js +206 -0
- package/package.json +48 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mamba Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,115 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Site Crawler for Links and Tables MCP Server
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@mambalabsdev/mcp-site-crawler-links-tables) [](https://github.com/mambalabsdev/mcp-site-crawler-links-tables/blob/main/LICENSE)
|
|
4
|
+
|
|
5
|
+
An MCP server for the Mamba Labs Site Crawler for Links and Tables actor on Apify. Crawl a website and get its links, download links, tables, and lists as rows.
|
|
6
|
+
|
|
7
|
+
## What's Inside
|
|
8
|
+
|
|
9
|
+
- [What it does](#what-it-does)
|
|
10
|
+
- [Quick start](#quick-start)
|
|
11
|
+
- [Prerequisites](#prerequisites)
|
|
12
|
+
- [Example prompts](#example-prompts)
|
|
13
|
+
- [Tool](#tool)
|
|
14
|
+
- [Inputs](#inputs)
|
|
15
|
+
- [Pricing](#pricing)
|
|
16
|
+
- [Full actor documentation](#full-actor-documentation)
|
|
17
|
+
- [License](#license)
|
|
18
|
+
|
|
19
|
+
## What it does
|
|
20
|
+
|
|
21
|
+
Give a start address. The actor crawls the site over plain HTTP, following links and the sitemap, and returns one row per page with internal links, external links, file download links, every HTML table as rows, and every list. The output is deterministic, with no AI. robots.txt is obeyed by default. Pages built by JavaScript return little, because no browser runs.
|
|
22
|
+
|
|
23
|
+
All of the work runs on Apify. This package is a thin client that starts the actor run, waits for it, and hands back the dataset rows.
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
You need Node.js 18 or newer and an Apify account with an API token.
|
|
28
|
+
|
|
29
|
+
Add this to your Claude Desktop config (`claude_desktop_config.json`), Cursor, or any MCP client:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcpServers": {
|
|
34
|
+
"mamba-site-crawler-links-tables": {
|
|
35
|
+
"command": "npx",
|
|
36
|
+
"args": [
|
|
37
|
+
"-y",
|
|
38
|
+
"@mambalabsdev/mcp-site-crawler-links-tables"
|
|
39
|
+
],
|
|
40
|
+
"env": {
|
|
41
|
+
"APIFY_TOKEN": "your-apify-token"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Restart the client. The `crawl_site_links_tables` tool appears.
|
|
49
|
+
|
|
50
|
+
## Prerequisites
|
|
51
|
+
|
|
52
|
+
- Node.js 18 or newer.
|
|
53
|
+
- An Apify API token from [console.apify.com/account/integrations](https://console.apify.com/account/integrations), set as `APIFY_TOKEN`. The server lists its tool without a token; a call needs one.
|
|
54
|
+
|
|
55
|
+
## Example prompts
|
|
56
|
+
|
|
57
|
+
- "List every external download link on this website."
|
|
58
|
+
- "Pull all the tables from these pages into rows."
|
|
59
|
+
- "Crawl this site and show me which pages link out to other domains."
|
|
60
|
+
|
|
61
|
+
## Tool
|
|
62
|
+
|
|
63
|
+
`crawl_site_links_tables`. One tool. Example arguments:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"start_urls": [
|
|
68
|
+
"https://webscraper.io/test-sites/tables"
|
|
69
|
+
],
|
|
70
|
+
"extract": [
|
|
71
|
+
"tables",
|
|
72
|
+
"links"
|
|
73
|
+
],
|
|
74
|
+
"max_pages": 10,
|
|
75
|
+
"max_depth": 1
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Inputs
|
|
80
|
+
|
|
81
|
+
Every buyer facing input of the actor. Omitted inputs take the actor's own defaults.
|
|
82
|
+
|
|
83
|
+
| Input | Type | Required | Description |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| `start_urls` | array | no | Web addresses to start from, up to 100. A bare domain such as example.com is read as https. |
|
|
86
|
+
| `max_pages` | number | no | Pages to read across the whole run, up to 10,000. Default 100. |
|
|
87
|
+
| `max_depth` | number | no | How many links to follow from a start address. 0 reads only the start address. Default 10. |
|
|
88
|
+
| `same_domain_only` | string | no | yes (default): follow links only on the start address's host and its subdomains. no: also follow links to other sites, up to Maximum pages. Links to other sites are listed either way. |
|
|
89
|
+
| `extract` | array | no | Any of links, tables, lists, download_links. Default links, tables, lists. download_links adds one separate row per file link, charged as download-link-row; the download_links field on page rows is always filled at no extra charge. |
|
|
90
|
+
| `download_extensions` | array | no | File extensions that count as download links. Default pdf, zip, xlsx, xls, docx, doc, pptx, csv, mp3, mp4, exe, dmg, msi, pkg, gz, tar, tgz, 7z, rar, iso, apk. Files are listed, never downloaded. |
|
|
91
|
+
| `use_sitemap` | string | no | yes (default): also queue the addresses in /sitemap.xml, which finds pages no link reaches. no: follow links only. |
|
|
92
|
+
| `obey_robots` | string | no | yes (default): skip pages robots.txt disallows and honor its crawl delay up to 5 seconds. no: read them, only if you have the right to. |
|
|
93
|
+
| `max_charge_usd` | string | no | Optional. The run stops before a charge that would pass this amount. Summary rows are always returned. |
|
|
94
|
+
| `proxy_mode` | string | no | pool (default): the Apify proxy pool only, charged as page-row; a page that refuses it gets a free blocked row. auto: a refused page is retried on residential addresses, and a page read that way is charged as residential-page-row. residential: every page uses residential addresses and is charged as residential-page-row. |
|
|
95
|
+
|
|
96
|
+
## Pricing
|
|
97
|
+
|
|
98
|
+
Pay per event on Apify, billed to your Apify account. Prices in USD.
|
|
99
|
+
|
|
100
|
+
| Event | Free plan | Bronze | Silver | Gold |
|
|
101
|
+
|---|---|---|---|---|
|
|
102
|
+
| `page-row`, one page read on the proxy pool | $0.006 | $0.0057 | $0.0054 | $0.0051 |
|
|
103
|
+
| `residential-page-row`, one page read on residential addresses, only with proxy_mode auto or residential | $0.02 | $0.019 | $0.018 | $0.017 |
|
|
104
|
+
| `download-link-row`, one separate file link row, only when `extract` includes `download_links` | $0.0001 | $0.000095 | $0.00009 | $0.000085 |
|
|
105
|
+
| `apify-actor-start`, per run start | $0.0055 | $0.0055 | $0.0055 | $0.0055 |
|
|
106
|
+
|
|
107
|
+
Failed or refused pages, summary rows, pages skipped by robots.txt, and refused input are free. Mamba Labs pays the platform costs of the run, Apify proxy traffic included. Mamba Labs pays the platform costs of the run, Apify proxy traffic included.
|
|
108
|
+
|
|
109
|
+
## Full actor documentation
|
|
110
|
+
|
|
111
|
+
[apify.com/mambalabs/site-crawler-links-tables](https://apify.com/mambalabs/site-crawler-links-tables)
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT. See [LICENSE](LICENSE).
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
The latest published `1.x` release of
|
|
6
|
+
`@mambalabsdev/mcp-site-crawler-links-tables` receives security updates.
|
|
7
|
+
|
|
8
|
+
| Version | Supported |
|
|
9
|
+
| ------- | ------------------ |
|
|
10
|
+
| 1.x | :white_check_mark: |
|
|
11
|
+
| < 1.0 | :x: |
|
|
12
|
+
|
|
13
|
+
## Reporting a vulnerability
|
|
14
|
+
|
|
15
|
+
Please do not open a public GitHub issue for security reports.
|
|
16
|
+
|
|
17
|
+
Email security reports to **labs@mambamindsetgrowth.com** with:
|
|
18
|
+
|
|
19
|
+
- a description of the issue and its impact,
|
|
20
|
+
- steps to reproduce, and
|
|
21
|
+
- any relevant logs or proof of concept.
|
|
22
|
+
|
|
23
|
+
We aim to acknowledge reports within 3 business days and to provide a remediation
|
|
24
|
+
timeline after triage. Coordinated disclosure is appreciated.
|
|
25
|
+
|
|
26
|
+
## Handling of credentials
|
|
27
|
+
|
|
28
|
+
This server reads an `APIFY_TOKEN` from the environment and uses it only to call
|
|
29
|
+
the Apify API on your behalf. The token is never logged or written to disk by this
|
|
30
|
+
package. Treat your `APIFY_TOKEN` as a secret and rotate it at
|
|
31
|
+
https://console.apify.com/account/integrations if you believe it has been exposed.
|
package/build/index.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const pkg = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
|
|
10
|
+
// Distinctive UA so Apify run meta.userAgent marks MCP-originated runs.
|
|
11
|
+
const USER_AGENT = `mambalabs-mcp ${pkg.name}@${pkg.version}`;
|
|
12
|
+
// Drop undefined values so optional inputs are not sent to the actor.
|
|
13
|
+
function compact(obj) {
|
|
14
|
+
const out = {};
|
|
15
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
16
|
+
if (v !== undefined)
|
|
17
|
+
out[k] = v;
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
// How long the actor run itself is allowed to take, in seconds. Fleet standard
|
|
22
|
+
// (standing build rule 5, 2026-10-05). A run of 50 searches of 100 posts each
|
|
23
|
+
const ACTOR_RUN_TIMEOUT_SECS = 1800;
|
|
24
|
+
// How long this wrapper waits for that run, in milliseconds. The actor's own
|
|
25
|
+
// timeout plus two minutes, so the run's own TIMED-OUT status is what the
|
|
26
|
+
// caller sees rather than the wrapper giving up first and reporting nothing.
|
|
27
|
+
const WRAPPER_WAIT_MS = (ACTOR_RUN_TIMEOUT_SECS + 120) * 1000;
|
|
28
|
+
const POLL_INTERVAL_MS = 3000;
|
|
29
|
+
const TERMINAL = new Set(["SUCCEEDED", "FAILED", "TIMED-OUT", "ABORTED", "ABORTING"]);
|
|
30
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
31
|
+
// Shared caller. actorPath is the actor's immutable Apify actor ID (a stable key
|
|
32
|
+
// that survives Store renames). The /v2/acts/{id} endpoint accepts it directly,
|
|
33
|
+
// so a Store rename never breaks these calls.
|
|
34
|
+
//
|
|
35
|
+
// START AND POLL, NOT RUN-SYNC. Apify's synchronous endpoints carry a platform
|
|
36
|
+
// ceiling of 300 seconds on the HTTP wait itself and answer 408 past it whatever
|
|
37
|
+
// the timeout parameter says, so a long run reads as a timeout even though the
|
|
38
|
+
// actor goes on to finish. Starting the run, polling it to a terminal status and
|
|
39
|
+
// then reading the dataset is the only way to wait as long as the actor needs.
|
|
40
|
+
//
|
|
41
|
+
// The token is read here rather than at module load, so the tool registers
|
|
42
|
+
// unconditionally and a server started without APIFY_TOKEN still advertises its
|
|
43
|
+
// capabilities instead of reporting none.
|
|
44
|
+
async function runActor(actorPath, actorLabel, input) {
|
|
45
|
+
const APIFY_TOKEN = process.env.APIFY_TOKEN;
|
|
46
|
+
if (!APIFY_TOKEN) {
|
|
47
|
+
return { isError: true, content: [{ type: "text", text: "APIFY_TOKEN is not set. Create a token at https://console.apify.com/account/integrations and set it as the APIFY_TOKEN environment variable." }] };
|
|
48
|
+
}
|
|
49
|
+
const headers = {
|
|
50
|
+
Authorization: `Bearer ${APIFY_TOKEN}`,
|
|
51
|
+
"Content-Type": "application/json",
|
|
52
|
+
"User-Agent": USER_AGENT,
|
|
53
|
+
};
|
|
54
|
+
const httpError = async (response) => {
|
|
55
|
+
let detail = "";
|
|
56
|
+
try {
|
|
57
|
+
const body = (await response.json());
|
|
58
|
+
if (body?.error?.message)
|
|
59
|
+
detail = ` ${body.error.message}`;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
detail = "";
|
|
63
|
+
}
|
|
64
|
+
switch (response.status) {
|
|
65
|
+
case 400:
|
|
66
|
+
return `The ${actorLabel} run was rejected as invalid input.${detail}`;
|
|
67
|
+
case 401:
|
|
68
|
+
return "Invalid Apify token. Check your APIFY_TOKEN environment variable.";
|
|
69
|
+
case 402:
|
|
70
|
+
return "Insufficient Apify credits. Check your account balance at https://console.apify.com/billing";
|
|
71
|
+
default:
|
|
72
|
+
return `Apify request to ${actorLabel} failed with status ${response.status}.${detail}`;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
// 1. Start the run.
|
|
76
|
+
let started;
|
|
77
|
+
try {
|
|
78
|
+
started = await fetch(`https://api.apify.com/v2/acts/${actorPath}/runs?timeout=${ACTOR_RUN_TIMEOUT_SECS}`, { method: "POST", headers, body: JSON.stringify(input) });
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
82
|
+
return { isError: true, content: [{ type: "text", text: `Could not reach the Apify API: ${message}` }] };
|
|
83
|
+
}
|
|
84
|
+
if (!started.ok) {
|
|
85
|
+
return { isError: true, content: [{ type: "text", text: await httpError(started) }] };
|
|
86
|
+
}
|
|
87
|
+
let run;
|
|
88
|
+
try {
|
|
89
|
+
run = (await started.json()).data ?? {};
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
93
|
+
return { isError: true, content: [{ type: "text", text: `The ${actorLabel} run start returned a response that could not be parsed: ${message}` }] };
|
|
94
|
+
}
|
|
95
|
+
const runId = run.id;
|
|
96
|
+
if (!runId) {
|
|
97
|
+
return { isError: true, content: [{ type: "text", text: `The ${actorLabel} run start returned no run id, so there is nothing to wait for.` }] };
|
|
98
|
+
}
|
|
99
|
+
// 2. Poll to a terminal status.
|
|
100
|
+
const deadline = Date.now() + WRAPPER_WAIT_MS;
|
|
101
|
+
let status = run.status ?? "READY";
|
|
102
|
+
let datasetId = run.defaultDatasetId;
|
|
103
|
+
while (!TERMINAL.has(status)) {
|
|
104
|
+
if (Date.now() >= deadline) {
|
|
105
|
+
return {
|
|
106
|
+
isError: true,
|
|
107
|
+
content: [{ type: "text", text: `The ${actorLabel} run ${runId} was still ${status} after ${Math.round(WRAPPER_WAIT_MS / 1000)} seconds and this call stopped waiting. The run itself is still on Apify: read it at https://console.apify.com/actors/runs/${runId}` }],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
await sleep(POLL_INTERVAL_MS);
|
|
111
|
+
let poll;
|
|
112
|
+
try {
|
|
113
|
+
poll = await fetch(`https://api.apify.com/v2/actor-runs/${runId}`, { headers });
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
+
return { isError: true, content: [{ type: "text", text: `Lost contact with the Apify API while waiting for ${actorLabel} run ${runId}: ${message}` }] };
|
|
118
|
+
}
|
|
119
|
+
if (!poll.ok) {
|
|
120
|
+
return { isError: true, content: [{ type: "text", text: await httpError(poll) }] };
|
|
121
|
+
}
|
|
122
|
+
const body = (await poll.json());
|
|
123
|
+
status = body.data?.status ?? status;
|
|
124
|
+
datasetId = body.data?.defaultDatasetId ?? datasetId;
|
|
125
|
+
}
|
|
126
|
+
// 3. A run that did not succeed is a failure the caller must see, never an
|
|
127
|
+
// empty success. Surfacing it here is what keeps a crashed run from reading
|
|
128
|
+
// as "no results found".
|
|
129
|
+
if (status !== "SUCCEEDED") {
|
|
130
|
+
return {
|
|
131
|
+
isError: true,
|
|
132
|
+
content: [{ type: "text", text: `The ${actorLabel} run did not succeed (run ID: ${runId}, status: ${status}).` }],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (!datasetId) {
|
|
136
|
+
return { isError: true, content: [{ type: "text", text: `The ${actorLabel} run ${runId} succeeded but reported no dataset, so there is nothing to return.` }] };
|
|
137
|
+
}
|
|
138
|
+
// 4. Read the dataset.
|
|
139
|
+
let ds;
|
|
140
|
+
try {
|
|
141
|
+
ds = await fetch(`https://api.apify.com/v2/datasets/${datasetId}/items?format=json`, { headers });
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
145
|
+
return { isError: true, content: [{ type: "text", text: `Could not read the ${actorLabel} dataset: ${message}` }] };
|
|
146
|
+
}
|
|
147
|
+
if (!ds.ok) {
|
|
148
|
+
return { isError: true, content: [{ type: "text", text: await httpError(ds) }] };
|
|
149
|
+
}
|
|
150
|
+
let items;
|
|
151
|
+
try {
|
|
152
|
+
items = await ds.json();
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
156
|
+
return { isError: true, content: [{ type: "text", text: `The ${actorLabel} run returned a response that could not be parsed: ${message}` }] };
|
|
157
|
+
}
|
|
158
|
+
if (!Array.isArray(items)) {
|
|
159
|
+
const asObj = items;
|
|
160
|
+
const detail = asObj?.error?.message
|
|
161
|
+
? `${asObj.error.message}`
|
|
162
|
+
: JSON.stringify(items);
|
|
163
|
+
return { isError: true, content: [{ type: "text", text: `The ${actorLabel} run did not return a dataset. ${detail}` }] };
|
|
164
|
+
}
|
|
165
|
+
return { content: [{ type: "text", text: JSON.stringify(items, null, 2) }] };
|
|
166
|
+
}
|
|
167
|
+
// The actor's number and on or off inputs are text fields (Apify checks input
|
|
168
|
+
// types before the actor starts, and Clay sends text), so numbers and booleans
|
|
169
|
+
// from the model are sent as text.
|
|
170
|
+
function asActorInput(args) {
|
|
171
|
+
const out = {};
|
|
172
|
+
for (const [k, v] of Object.entries(compact(args))) {
|
|
173
|
+
out[k] = typeof v === "number" || typeof v === "boolean" ? String(v) : v;
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
const server = new McpServer({
|
|
178
|
+
name: "mamba-site-crawler-links-tables",
|
|
179
|
+
version: pkg.version,
|
|
180
|
+
});
|
|
181
|
+
// Site Crawler for Links and Tables (immutable actor ID lLLJ6JxZ4XqN8ULz0). Every buyer facing input of the actor is exposed.
|
|
182
|
+
server.registerTool("crawl_site_links_tables", {
|
|
183
|
+
title: "Crawl a website for links and tables",
|
|
184
|
+
description: "Crawl a website from a start address over plain HTTP and return one row per page with internal links, external links, file download links, HTML tables as rows, and lists. No AI and no browser, so pages built by JavaScript return little. Obeys robots.txt by default. Pay per event on Apify: 0.006 USD per page.",
|
|
185
|
+
annotations: {
|
|
186
|
+
title: "Crawl a website for links and tables",
|
|
187
|
+
readOnlyHint: true,
|
|
188
|
+
destructiveHint: false,
|
|
189
|
+
idempotentHint: true,
|
|
190
|
+
openWorldHint: true,
|
|
191
|
+
},
|
|
192
|
+
inputSchema: {
|
|
193
|
+
start_urls: z.array(z.string()).optional().describe("Web addresses to start from, up to 100. A bare domain such as example.com is read as https."),
|
|
194
|
+
max_pages: z.number().optional().describe("Pages to read across the whole run, up to 10,000. Default 100."),
|
|
195
|
+
max_depth: z.number().optional().describe("How many links to follow from a start address. 0 reads only the start address. Default 10."),
|
|
196
|
+
same_domain_only: z.string().optional().describe("yes (default): follow links only on the start address's host and its subdomains. no: also follow links to other sites, up to Maximum pages. Links to other sites are listed either way."),
|
|
197
|
+
extract: z.array(z.string()).optional().describe("Any of links, tables, lists, download_links. Default links, tables, lists. download_links adds one separate row per file link, charged as download-link-row; the download_links field on page rows is always filled at no extra charge."),
|
|
198
|
+
download_extensions: z.array(z.string()).optional().describe("File extensions that count as download links. Default pdf, zip, xlsx, xls, docx, doc, pptx, csv, mp3, mp4, exe, dmg, msi, pkg, gz, tar, tgz, 7z, rar, iso, apk. Files are listed, never downloaded."),
|
|
199
|
+
use_sitemap: z.string().optional().describe("yes (default): also queue the addresses in /sitemap.xml, which finds pages no link reaches. no: follow links only."),
|
|
200
|
+
obey_robots: z.string().optional().describe("yes (default): skip pages robots.txt disallows and honor its crawl delay up to 5 seconds. no: read them, only if you have the right to."),
|
|
201
|
+
max_charge_usd: z.string().optional().describe("Optional. The run stops before a charge that would pass this amount. Summary rows are always returned."),
|
|
202
|
+
proxy_mode: z.enum(["pool", "auto", "residential"]).optional().describe("pool (default): the Apify proxy pool only, charged as page-row; a page that refuses it gets a free blocked row. auto: a refused page is retried on residential addresses, and a page read that way is charged as residential-page-row. residential: every page uses residential addresses and is charged as residential-page-row."),
|
|
203
|
+
},
|
|
204
|
+
}, async (args) => runActor("lLLJ6JxZ4XqN8ULz0", "Site Crawler for Links and Tables", asActorInput(args)));
|
|
205
|
+
const transport = new StdioServerTransport();
|
|
206
|
+
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mambalabsdev/mcp-site-crawler-links-tables",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Site Crawler for Links and Tables. Crawl a site and get links, download links, HTML tables as rows, and lists. No code, no AI. Runs on Apify. Clay-ready output.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mcp-site-crawler-links-tables": "./build/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"build",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"SECURITY.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc && chmod +x build/index.js",
|
|
17
|
+
"test": "npm run build && node --test test/*.test.js",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"mcp",
|
|
22
|
+
"model-context-protocol",
|
|
23
|
+
"web-crawler",
|
|
24
|
+
"link-extractor",
|
|
25
|
+
"table-extractor",
|
|
26
|
+
"download-links",
|
|
27
|
+
"site-crawler",
|
|
28
|
+
"apify",
|
|
29
|
+
"clay"
|
|
30
|
+
],
|
|
31
|
+
"author": "Mamba Labs <labs@mambamindsetgrowth.com>",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"homepage": "https://apify.com/mambalabs/site-crawler-links-tables",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/mambalabsdev/mcp-site-crawler-links-tables"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.32.2",
|
|
43
|
+
"zod": "^4.4.3"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^26.2.0",
|
|
47
|
+
"typescript": "5.9.3"
|
|
48
|
+
},
|
|
49
|
+
"mcpName": "com.mambabuilt/mcp-site-crawler-links-tables"
|
|
50
|
+
}
|