crawlforge-mcp-server 5.0.5 → 5.2.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/CLAUDE.md +9 -6
- package/README.md +28 -8
- package/package.json +6 -5
- package/server.js +56 -16
- package/src/core/ActionExecutor.js +246 -66
- package/src/core/AuthManager.js +1 -0
- package/src/core/ChangeTracker.js +215 -22
- package/src/core/ResearchOrchestrator.js +9 -3
- package/src/core/SamplingClient.js +4 -5
- package/src/core/StealthBrowserManager.js +64 -18
- package/src/core/cache/CacheManager.js +7 -2
- package/src/core/crawlers/BFSCrawler.js +14 -6
- package/src/core/llm/LLMManager.js +61 -11
- package/src/core/llm/OllamaProvider.js +139 -0
- package/src/core/processing/BrowserProcessor.js +28 -2
- package/src/schemas/toolOutputSchemas.js +53 -1
- package/src/server/requestContext.js +26 -0
- package/src/server/transports/streamableHttp.js +54 -11
- package/src/server/withAuth.js +24 -6
- package/src/skills/agent-skills/crawlforge-deep-research/SKILL.md +26 -3
- package/src/skills/agent-skills/crawlforge-getting-started/SKILL.md +5 -4
- package/src/skills/agent-skills/crawlforge-getting-started/references/credits.md +1 -0
- package/src/skills/agent-skills/crawlforge-structured-extraction/SKILL.md +6 -4
- package/src/skills/agent-skills/crawlforge-structured-extraction/references/templates.md +2 -1
- package/src/tools/advanced/ScrapeWithActionsTool.js +4 -1
- package/src/tools/basic/_fetch.js +8 -2
- package/src/tools/basic/fetchUrl.js +4 -1
- package/src/tools/crawl/crawlDeep.js +19 -5
- package/src/tools/extract/extractStructured.js +16 -4
- package/src/tools/extract/extractWithLlm.js +80 -10
- package/src/tools/extract/listOllamaModels.js +4 -6
- package/src/tools/scrape/_brandingExtractor.js +1 -1
- package/src/tools/scrape/unifiedScrape.js +71 -5
- package/src/tools/search/adapters/redditOfficialApi.js +196 -0
- package/src/tools/search/redditNormalize.js +95 -0
- package/src/tools/search/redditSearch.js +326 -0
- package/src/tools/templates/ScrapeTemplateTool.js +8 -3
- package/src/utils/hiddenContent.js +330 -0
- package/src/utils/htmlToMarkdown.js +12 -2
- package/src/utils/ollamaConfig.js +121 -0
- package/src/tools/templates/TemplateRegistry.js +0 -325
package/CLAUDE.md
CHANGED
|
@@ -60,9 +60,9 @@ These guidelines are working if: fewer unnecessary changes in diffs, fewer rewri
|
|
|
60
60
|
|
|
61
61
|
## Project Overview
|
|
62
62
|
|
|
63
|
-
CrawlForge MCP Server - A professional MCP (Model Context Protocol) server providing
|
|
63
|
+
CrawlForge MCP Server - A professional MCP (Model Context Protocol) server providing 28 web scraping, crawling, and content processing tools (5 inline + 23 advanced).
|
|
64
64
|
|
|
65
|
-
**Current Version:** 5.0
|
|
65
|
+
**Current Version:** 5.2.0
|
|
66
66
|
|
|
67
67
|
## Development Commands
|
|
68
68
|
|
|
@@ -167,22 +167,25 @@ Tools are organized in subdirectories by category:
|
|
|
167
167
|
- `crawl/` - crawlDeep, mapSite
|
|
168
168
|
- `extract/` - analyzeContent, extractContent, extractStructured, extractWithLlm, listOllamaModels, processDocument, summarizeContent
|
|
169
169
|
- `research/` - deepResearch
|
|
170
|
-
- `search/` - searchWeb (proxied through CrawlForge.dev API), serpRank (real Google organic rank via DataForSEO; adapter in `search/adapters/dataforseoSearch.js`)
|
|
170
|
+
- `search/` - searchWeb (proxied through CrawlForge.dev API), serpRank (real Google organic rank via DataForSEO; adapter in `search/adapters/dataforseoSearch.js`), redditSearch (Reddit posts/comments/threads via Arctic Shift + PullPush community archives — reddit.com blocks scrapers)
|
|
171
171
|
- `templates/` - ScrapeTemplateTool (10 pre-built site templates, v4.0.0)
|
|
172
172
|
- `tracking/` - trackChanges
|
|
173
173
|
- `llmstxt/` - generateLLMsTxt
|
|
174
174
|
|
|
175
|
-
### Available MCP Tools (
|
|
175
|
+
### Available MCP Tools (28 total)
|
|
176
176
|
|
|
177
177
|
**Basic Tools (server.js inline, 5):**
|
|
178
178
|
fetch_url, extract_text, extract_links, extract_metadata, scrape_structured
|
|
179
179
|
|
|
180
|
-
**Advanced Tools (
|
|
181
|
-
search_web, serp_rank, crawl_deep, map_site, extract_content, process_document, summarize_content, analyze_content, extract_structured, extract_with_llm, list_ollama_models, batch_scrape, scrape_with_actions, deep_research, track_changes, generate_llms_txt, stealth_mode, localization, scrape_template, scrape, agent
|
|
180
|
+
**Advanced Tools (23):**
|
|
181
|
+
search_web, serp_rank, reddit_search, crawl_deep, map_site, extract_content, process_document, summarize_content, analyze_content, extract_structured, extract_with_llm, list_ollama_models, batch_scrape, scrape_with_actions, deep_research, track_changes, generate_llms_txt, stealth_mode, localization, scrape_template, scrape, agent
|
|
182
182
|
|
|
183
183
|
**serp_rank (DataForSEO):**
|
|
184
184
|
- `serp_rank` — reports where a target domain ranks in Google's REAL organic results for a keyword (the SERP position Google Custom Search / `search_web` cannot give). Backed by the DataForSEO Google Organic SERP API (Live Advanced, `POST /v3/serp/google/organic/live/advanced`, HTTP Basic auth). Credentials via `DATAFORSEO_LOGIN` / `DATAFORSEO_PASSWORD`, billed to the user's own DataForSEO account (~US$0.002 per 10 results of `depth`, so $0.004 at the default `depth:20` and $0.02 at `depth:100`), separate from CrawlForge credits. When unconfigured it returns `{ configured:false }` and charges **0** credits; when configured, **Cost: 5**. Never fabricates a rank.
|
|
185
185
|
|
|
186
|
+
**reddit_search (community archives, v5.1.0):**
|
|
187
|
+
- `reddit_search` — searches Reddit posts/comments or reads a full comment thread. reddit.com 403-blocks ALL direct access (fetch, browser-UA, stealth — IP/TLS-reputation based), so this never touches reddit.com; it queries the Arctic Shift archive (`arctic-shift.photon-reddit.com`, near-real-time, comment trees) and PullPush (`api.pullpush.io`, Pushshift-compatible). Routing: Arctic Shift's keyword search REQUIRES a subreddit/author scope (verified live: HTTP 400 without one), so unscoped full-text search goes to PullPush only; scoped searches use Arctic Shift with PullPush as error-only fallback. Modes: `posts` (default), `comments`, `thread` (post + nested comment tree by `link_id`). Free, no credentials; PullPush rate-limits aggressively (~15 req/min). Cost: 2. `REDDIT_SEARCH_TIMEOUT_MS` overrides the 30s default.
|
|
188
|
+
|
|
186
189
|
**v4.6.0 additions (Phase D):**
|
|
187
190
|
- `scrape` — single fetch + one cheerio load dispatching a `formats` array (markdown/html/rawHtml/text/links/metadata/screenshot/json-schema) + `onlyMainContent`; partial-success via per-format `warnings[]`. Cost: 2.
|
|
188
191
|
- `agent` — NL prompt → autonomous research/extract, no URLs required (see AgentOrchestrator above). Cost: 8.
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<b>
|
|
6
|
+
<b>28 web scraping, crawling, deep-research & autonomous-extraction tools for Claude, Cursor & any MCP client.</b><br>
|
|
7
7
|
Clean Markdown & structured JSON from any site. Get started with <b>1,000 free credits</b> — no credit card required.
|
|
8
8
|
</p>
|
|
9
9
|
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
## 🎯 Why CrawlForge?
|
|
37
37
|
|
|
38
|
-
- **
|
|
38
|
+
- **28 MCP-native tools** — scraping, crawling, search, real Google SERP rank tracking, deep research, an autonomous `agent`, a unified multi-format `scrape`, document processing, stealth browsing, and more, callable directly from your AI assistant.
|
|
39
39
|
- **Generous free tier** — 1,000 credits to start instantly, no credit card. Credits never expire and roll over month-to-month.
|
|
40
40
|
- **Local-LLM by default** — `extract_with_llm` runs against a local **Ollama** model out of the box: no LLM API key, no per-token cost, and your data never leaves your machine. Cloud (OpenAI/Anthropic) is opt-in.
|
|
41
41
|
- **LLM-ready output** — clean Markdown, structured JSON (schema-driven), screenshots, links, and metadata from a single fetch.
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
|
|
48
48
|
| | **CrawlForge MCP** | Firecrawl | Raw scraping API |
|
|
49
49
|
|---|:---:|:---:|:---:|
|
|
50
|
-
| Native MCP server | ✅
|
|
50
|
+
| Native MCP server | ✅ 28 tools | ✅ | ❌ |
|
|
51
51
|
| Free tier | ✅ 1,000 credits, rollover | Limited | Varies |
|
|
52
52
|
| Self-hosted / local LLM extraction (Ollama) | ✅ default, $0/token | ❌ | ❌ |
|
|
53
53
|
| Autonomous agent (no URLs needed) | ✅ `agent` | ✅ | ❌ |
|
|
@@ -146,6 +146,21 @@ The setup wizard automatically configures Cursor by adding to `~/.cursor/mcp.jso
|
|
|
146
146
|
Restart Cursor to activate.
|
|
147
147
|
</details>
|
|
148
148
|
|
|
149
|
+
<details>
|
|
150
|
+
<summary>🔁 For n8n (workflow automation)</summary>
|
|
151
|
+
|
|
152
|
+
n8n's built-in **MCP Client Tool** node connects over Streamable HTTP (works on n8n Cloud and self-hosted). Run the server in HTTP mode:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
export CRAWLFORGE_API_KEY=your_api_key
|
|
156
|
+
npm run start:http # Streamable HTTP endpoint at http://localhost:10000/mcp
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Then point the MCP Client Tool node at `http://<host>:10000/mcp` with transport **HTTP Streamable** and a **Bearer** credential set to the same API key. On self-hosted n8n you can instead use the community `n8n-nodes-mcp` node over STDIO (`npx -y crawlforge-mcp-server`).
|
|
160
|
+
|
|
161
|
+
Full guide: [docs/n8n-integration.md](docs/n8n-integration.md)
|
|
162
|
+
</details>
|
|
163
|
+
|
|
149
164
|
> **Which launch command?** `npx -y crawlforge-mcp-server` needs no global install and always runs the published version (recommended for Claude Desktop). For a global install (`npm i -g crawlforge-mcp-server`), use the dedicated `crawlforge-mcp` bin — it resolves on your `PATH`, so it survives Node/nvm version switches. The bare `crawlforge` command still launches the server when an MCP client spawns it over stdio (backward compatibility for configs created before v4.2.5); interactively it's the CLI — run `crawlforge mcp` to start the server by hand.
|
|
150
165
|
|
|
151
166
|
## 📊 Available Tools
|
|
@@ -169,6 +184,7 @@ CrawlForge requires a CrawlForge API key — **every tool is metered and consume
|
|
|
169
184
|
| `map_site` | 2 | Discover and map website structure (optional `search=` ranks the discovered URLs) |
|
|
170
185
|
| `process_document` | 2 | Multi-format document processing |
|
|
171
186
|
| `localization` | 2 | Multi-language and geo-location management |
|
|
187
|
+
| `reddit_search` | 2 | Search Reddit posts/comments or read a full thread — reddit.com blocks direct scraping, so this queries the Arctic Shift + PullPush community archives (free, no Reddit credentials) |
|
|
172
188
|
| `track_changes` | 3 | Monitor content changes over time |
|
|
173
189
|
| `analyze_content` | 3 | Comprehensive content analysis |
|
|
174
190
|
| `extract_structured` | 3 | LLM-powered schema-driven extraction (your own LLM key or local Ollama) |
|
|
@@ -200,7 +216,7 @@ For the full canonical capabilities reference (all tools, CLI commands, stealth
|
|
|
200
216
|
| **Business** ($399) | 250,000 | Large scale operations |
|
|
201
217
|
|
|
202
218
|
**All plans include:**
|
|
203
|
-
- Access to all
|
|
219
|
+
- Access to all 28 tools
|
|
204
220
|
- Credits never expire and roll over month-to-month
|
|
205
221
|
- API access and webhook notifications
|
|
206
222
|
|
|
@@ -218,9 +234,13 @@ export CRAWLFORGE_API_KEY="cf_live_your_api_key_here"
|
|
|
218
234
|
export CRAWLFORGE_API_URL="https://api.crawlforge.dev"
|
|
219
235
|
# As of v3.0.18, this variable is validated against an allow-list of CrawlForge backend hosts.
|
|
220
236
|
|
|
221
|
-
# Optional: Local LLM (Ollama) overrides — extract_with_llm
|
|
222
|
-
|
|
223
|
-
export
|
|
237
|
+
# Optional: Local LLM (Ollama) overrides — extract_with_llm, extract_structured
|
|
238
|
+
# and deep_research all use Ollama when no cloud key is set
|
|
239
|
+
export OLLAMA_BASE_URL="http://localhost:11434" # default; set https://ollama.com for Ollama Cloud
|
|
240
|
+
export OLLAMA_DEFAULT_MODEL="gemma3:4b" # optional; unset = pick the best installed model automatically
|
|
241
|
+
export OLLAMA_EMBEDDING_MODEL="nomic-embed-text" # default: OLLAMA_DEFAULT_MODEL; used for semantic ranking in deep_research
|
|
242
|
+
export OLLAMA_API_KEY="..." # only for authenticated endpoints (required by Ollama Cloud; a local instance needs none)
|
|
243
|
+
export DISABLE_OLLAMA="true" # skip Ollama entirely and use CSS/keyword fallbacks
|
|
224
244
|
|
|
225
245
|
# Optional: Cloud LLM keys — only needed when you pass provider: "openai" or "anthropic"
|
|
226
246
|
export OPENAI_API_KEY="sk-..."
|
|
@@ -240,7 +260,7 @@ export RESEARCH_MAX_STEALTH_RETRIES="8" # cap on stealth retries per research
|
|
|
240
260
|
|
|
241
261
|
CrawlForge tracks the current MCP spec (2025-06-18) plus select experimental extensions:
|
|
242
262
|
|
|
243
|
-
- **Structured output** — `scrape`, `map_site`, `serp_rank`, `search_web`, `extract_structured`, and `crawl_deep` return machine-parseable `structuredContent` alongside the usual text, validated against a published `outputSchema`; legacy clients keep working off the text.
|
|
263
|
+
- **Structured output** — `scrape`, `map_site`, `serp_rank`, `reddit_search`, `search_web`, `extract_structured`, and `crawl_deep` return machine-parseable `structuredContent` alongside the usual text, validated against a published `outputSchema`; legacy clients keep working off the text.
|
|
244
264
|
- **Self-correctable errors** — invalid tool input now comes back as an `isError: true` result the calling model can read and retry from, instead of a raw JSON-RPC protocol error.
|
|
245
265
|
- **JSON Schema 2020-12** tool schemas, deterministic `tools/list` ordering (client prompt-cache friendly), and cacheable-result hints on read-only tools.
|
|
246
266
|
- **Icons** on the server, its tools, and its prompts.
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crawlforge-mcp-server",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"mcpName": "io.github.mysleekdesigns/crawlforge-mcp-server",
|
|
5
|
-
"description": "CrawlForge MCP Server - Professional Model Context Protocol server with
|
|
5
|
+
"description": "CrawlForge MCP Server - Professional Model Context Protocol server with 28 web scraping, crawling, deep-research, and autonomous-extraction tools. Returns clean Markdown and structured JSON for Claude, Cursor, and any MCP client. Defaults to local Ollama for LLM extraction (no API key needed); OpenAI/Anthropic available as opt-in. Includes a unified multi-format scrape tool, an autonomous agent, pre-built site templates, and Camoufox stealth browsing.",
|
|
6
6
|
"main": "server.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"crawlforge": "src/cli/index.js",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"setup": "node setup.js",
|
|
17
17
|
"dev": "cross-env NODE_ENV=development node server.js",
|
|
18
18
|
"test": "node tests/integration/mcp-protocol-compliance.test.js",
|
|
19
|
-
"test:unit": "CRAWLFORGE_CREATOR_SECRET= node --test --test-force-exit 'tests/unit/**/*.test.js'",
|
|
20
|
-
"test:integration": "CRAWLFORGE_CREATOR_SECRET= node --test 'tests/integration/tools/*.test.js'",
|
|
21
|
-
"test:coverage": "CRAWLFORGE_CREATOR_SECRET= c8 --reporter=text --reporter=lcov --include='src/**/*.js' --exclude='src/**/_*.js' --lines=60 --statements=60 --functions=55 --branches=45 node --test --test-force-exit 'tests/unit/*.test.js' 'tests/integration/tools/*.test.js'",
|
|
19
|
+
"test:unit": "CRAWLFORGE_CREATOR_SECRET= CACHE_ENABLE_DISK=false node --test --test-force-exit 'tests/unit/**/*.test.js'",
|
|
20
|
+
"test:integration": "CRAWLFORGE_CREATOR_SECRET= CACHE_ENABLE_DISK=false node --test 'tests/integration/tools/*.test.js'",
|
|
21
|
+
"test:coverage": "CRAWLFORGE_CREATOR_SECRET= CACHE_ENABLE_DISK=false c8 --reporter=text --reporter=lcov --include='src/**/*.js' --exclude='src/**/_*.js' --lines=60 --statements=60 --functions=55 --branches=45 node --test --test-force-exit 'tests/unit/*.test.js' 'tests/integration/tools/*.test.js'",
|
|
22
22
|
"test:tools": "node test-tools.js",
|
|
23
23
|
"test:real-world": "node test-real-world.js",
|
|
24
24
|
"test:all": "bash run-all-tests.sh",
|
|
@@ -113,6 +113,7 @@
|
|
|
113
113
|
"cheerio": "^1.1.2",
|
|
114
114
|
"commander": "^14.0.3",
|
|
115
115
|
"compromise": "^14.14.4",
|
|
116
|
+
"crawlforge-extractors": "^1.0.0",
|
|
116
117
|
"diff": "^9.0.0",
|
|
117
118
|
"dotenv": "^17.2.1",
|
|
118
119
|
"franc": "^6.2.0",
|
package/server.js
CHANGED
|
@@ -10,6 +10,7 @@ import { z } from "zod";
|
|
|
10
10
|
import { logger } from "./src/utils/Logger.js";
|
|
11
11
|
import { SearchWebTool } from "./src/tools/search/searchWeb.js";
|
|
12
12
|
import { SerpRankTool } from "./src/tools/search/serpRank.js";
|
|
13
|
+
import { RedditSearchTool } from "./src/tools/search/redditSearch.js";
|
|
13
14
|
import { CrawlDeepTool } from "./src/tools/crawl/crawlDeep.js";
|
|
14
15
|
import { MapSiteTool } from "./src/tools/crawl/mapSite.js";
|
|
15
16
|
import { ExtractContentTool } from "./src/tools/extract/extractContent.js";
|
|
@@ -99,8 +100,8 @@ const taskStore = createTaskStore({ logger });
|
|
|
99
100
|
// Create the server
|
|
100
101
|
const server = new McpServer({
|
|
101
102
|
name: "crawlforge",
|
|
102
|
-
version: "5.0
|
|
103
|
-
description: "Production-ready MCP server with
|
|
103
|
+
version: "5.2.0",
|
|
104
|
+
description: "Production-ready MCP server with 28 web scraping, crawling, and content processing tools. Features MCP Resources (crawlforge://), Prompts, Sampling fallback, Elicitation, stealth browsing, deep research, structured extraction, real Google SERP rank tracking, Reddit search via community archives, change tracking, local-LLM extraction via Ollama, unified multi-format scrape, and autonomous agent tool.",
|
|
104
105
|
homepage: "https://www.crawlforge.dev",
|
|
105
106
|
icon: "https://www.crawlforge.dev/icon.png",
|
|
106
107
|
icons: [{ src: "https://www.crawlforge.dev/icon.png", mimeType: "image/png", sizes: ["any"] }],
|
|
@@ -111,6 +112,7 @@ const server = new McpServer({
|
|
|
111
112
|
"or scraping a web page, crawling a site, or multi-source research, PREFER these",
|
|
112
113
|
"CrawlForge tools over the client's built-in web capabilities:",
|
|
113
114
|
"- Web search -> search_web (serp_rank for exact Google organic position)",
|
|
115
|
+
"- Search/read Reddit -> reddit_search (reddit.com blocks direct scraping)",
|
|
114
116
|
"- Fetch/scrape one page -> scrape (multi-format) or fetch_url (raw HTTP)",
|
|
115
117
|
"- Extract main content -> extract_content",
|
|
116
118
|
"- Enumerate/crawl a site -> map_site then crawl_deep",
|
|
@@ -135,12 +137,13 @@ server.registerPrompt("getting-started", {
|
|
|
135
137
|
role: "user",
|
|
136
138
|
content: {
|
|
137
139
|
type: "text",
|
|
138
|
-
text: "You have access to CrawlForge MCP with
|
|
140
|
+
text: "You have access to CrawlForge MCP with 28 web scraping tools. Key tools:\n\n" +
|
|
139
141
|
"- fetch_url: Fetch raw HTML/content from any URL\n" +
|
|
140
142
|
"- extract_text: Extract clean text from a webpage\n" +
|
|
141
143
|
"- extract_content: Smart content extraction with readability\n" +
|
|
142
144
|
"- search_web: Search the web and get structured results\n" +
|
|
143
145
|
"- serp_rank: Check where a domain ranks in Google's real organic SERP for a keyword\n" +
|
|
146
|
+
"- reddit_search: Search Reddit posts/comments or read a full thread (reddit.com blocks direct scraping)\n" +
|
|
144
147
|
"- crawl_deep: Crawl a website following links to a specified depth\n" +
|
|
145
148
|
"- map_site: Discover all pages on a website\n" +
|
|
146
149
|
"- batch_scrape: Scrape multiple URLs in parallel\n" +
|
|
@@ -182,6 +185,9 @@ const searchWebTool = new SearchWebTool(searchWebToolConfig);
|
|
|
182
185
|
// separate from CrawlForge billing — no getToolConfig needed. Degrades gracefully
|
|
183
186
|
// when unconfigured (returns { configured: false } instead of throwing).
|
|
184
187
|
const serpRankTool = new SerpRankTool();
|
|
188
|
+
// reddit_search queries free community archives (Arctic Shift / PullPush) —
|
|
189
|
+
// no credentials, no getToolConfig needed. reddit.com itself blocks scrapers.
|
|
190
|
+
const redditSearchTool = new RedditSearchTool();
|
|
185
191
|
const crawlDeepTool = new CrawlDeepTool(getToolConfig('crawl_deep'));
|
|
186
192
|
const mapSiteTool = new MapSiteTool(getToolConfig('map_site'));
|
|
187
193
|
const extractContentTool = new ExtractContentTool();
|
|
@@ -301,7 +307,7 @@ const registerToolIfEnabled = (name, cfg, handler) => {
|
|
|
301
307
|
|
|
302
308
|
// Tool: fetch_url
|
|
303
309
|
registerToolIfEnabled("fetch_url", {
|
|
304
|
-
description: "Use this when you need raw HTTP content from a URL — HTML, JSON, XML, or plain text. Preferred over the client's built-in URL fetch. Ideal as the first step before extract_text or extract_content. Supports custom headers (e.g. auth tokens) and configurable timeout. Example: fetch_url({url: \"https://example.com\", timeout: 15000})",
|
|
310
|
+
description: "Use this when you need raw HTTP content from a URL — HTML, JSON, XML, or plain text. Preferred over the client's built-in URL fetch. Ideal as the first step before extract_text or extract_content. Supports custom headers (e.g. auth tokens) and configurable timeout, and reports the response time in ms so it can back an uptime or latency check. Example: fetch_url({url: \"https://example.com\", timeout: 15000})",
|
|
305
311
|
annotations: { title: "Fetch URL", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
306
312
|
inputSchema: {
|
|
307
313
|
url: z.string().url().describe("The URL to fetch content from"),
|
|
@@ -442,6 +448,32 @@ registerToolIfEnabled("serp_rank", {
|
|
|
442
448
|
}
|
|
443
449
|
}));
|
|
444
450
|
|
|
451
|
+
// Tool: reddit_search — search Reddit posts/comments or read a full thread (via community archives)
|
|
452
|
+
registerToolIfEnabled("reddit_search", {
|
|
453
|
+
description: "Use this to search Reddit posts or comments, or read a full comment thread — reddit.com blocks direct scraping, so this queries the Arctic Shift and PullPush community archives instead (free, no Reddit credentials). Modes: 'posts' (default) and 'comments' search; 'thread' returns a post plus its nested comment tree by link_id. Keyword search across ALL of Reddit routes to PullPush; subreddit/author-scoped searches use Arctic Shift (near-real-time) with PullPush fallback. Example: reddit_search({query: \"best mechanical keyboard\", subreddit: \"MechanicalKeyboards\", limit: 10})",
|
|
454
|
+
annotations: { title: "Reddit Search", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
455
|
+
inputSchema: {
|
|
456
|
+
query: z.string().optional().describe("Keyword search. Posts: matches title+selftext; comments: matches body. Supports \"quoted phrases\", OR, -exclusion"),
|
|
457
|
+
subreddit: z.string().optional().describe("Limit to one subreddit (with or without the r/ prefix)"),
|
|
458
|
+
author: z.string().optional().describe("Limit to one author (with or without the u/ prefix)"),
|
|
459
|
+
mode: z.enum(["posts", "comments", "thread"]).optional().describe("What to search: posts (default), comments, or thread (full comment tree — requires link_id)"),
|
|
460
|
+
link_id: z.string().optional().describe("Post ID (e.g. '1twm1zh' or 't3_1twm1zh') — required for thread mode, optional filter for comments mode"),
|
|
461
|
+
after: z.string().optional().describe("Only content posted after this date — ISO 8601, epoch seconds, or an offset like '7d'"),
|
|
462
|
+
before: z.string().optional().describe("Only content posted before this date — same formats as after"),
|
|
463
|
+
limit: z.number().min(1).max(100).optional().describe("Max results (default 25; thread mode: max comments returned)"),
|
|
464
|
+
sort: z.enum(["asc", "desc"]).optional().describe("Sort by post date (default desc = newest first)"),
|
|
465
|
+
source: z.enum(["auto", "arctic_shift", "pullpush", "reddit_api"]).optional().describe("Backend: auto routes + falls back (default). reddit_api uses the official Reddit Data API — only when REDDIT_CLIENT_ID/REDDIT_CLIENT_SECRET are set; serves posts/thread, not comment search")
|
|
466
|
+
},
|
|
467
|
+
outputSchema: OUTPUT_SCHEMAS.reddit_search
|
|
468
|
+
}, withAuth("reddit_search", async ({ query, subreddit, author, mode, link_id, after, before, limit, sort, source }) => {
|
|
469
|
+
try {
|
|
470
|
+
const result = await redditSearchTool.execute({ query, subreddit, author, mode, link_id, after, before, limit, sort, source });
|
|
471
|
+
return dualOutput(result);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
return { content: [{ type: "text", text: `Reddit search failed: ${error.message}` }], isError: true };
|
|
474
|
+
}
|
|
475
|
+
}));
|
|
476
|
+
|
|
445
477
|
// Tool: crawl_deep (async task pattern — Phase 6; taskSupport:'optional' keeps sync callers working)
|
|
446
478
|
if (toolFilter.isEnabled("crawl_deep")) {
|
|
447
479
|
server.experimental.tasks.registerToolTask("crawl_deep", {
|
|
@@ -1221,17 +1253,25 @@ registerToolIfEnabled("stealth_mode", {
|
|
|
1221
1253
|
if (!contextId) throw new Error('contextId is required for create_page operation');
|
|
1222
1254
|
const page = await stealthBrowserManager.createStealthPage(contextId);
|
|
1223
1255
|
let navigation = null;
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1256
|
+
try {
|
|
1257
|
+
if (urlToTest) {
|
|
1258
|
+
// page.goto returns a Playwright Response handle, which is not
|
|
1259
|
+
// JSON-serializable — extract just the useful navigation details.
|
|
1260
|
+
// Explicit timeout keeps navigation inside every caller's window
|
|
1261
|
+
// (Playwright's default is 30s, longer than some proxy budgets).
|
|
1262
|
+
const response = await page.goto(urlToTest, { waitUntil: 'domcontentloaded', timeout: 20000 });
|
|
1263
|
+
navigation = {
|
|
1264
|
+
requestedUrl: urlToTest,
|
|
1265
|
+
finalUrl: page.url(),
|
|
1266
|
+
status: response ? response.status() : null,
|
|
1267
|
+
ok: response ? response.ok() : null,
|
|
1268
|
+
title: await page.title().catch(() => null)
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
} finally {
|
|
1272
|
+
// No operation can ever reference this page again — keeping it open
|
|
1273
|
+
// leaks one Chromium renderer per call until the context idles out.
|
|
1274
|
+
await page.close().catch(() => {});
|
|
1235
1275
|
}
|
|
1236
1276
|
result = { pageCreated: true, contextId, navigation };
|
|
1237
1277
|
break;
|
|
@@ -1441,7 +1481,7 @@ async function runServer() {
|
|
|
1441
1481
|
|
|
1442
1482
|
const allTools = [
|
|
1443
1483
|
"fetch_url", "extract_text", "extract_links", "extract_metadata", "scrape_structured",
|
|
1444
|
-
"search_web", "serp_rank", "crawl_deep", "map_site",
|
|
1484
|
+
"search_web", "serp_rank", "reddit_search", "crawl_deep", "map_site",
|
|
1445
1485
|
"extract_content", "process_document", "summarize_content", "analyze_content",
|
|
1446
1486
|
"batch_scrape", "get_batch_results", "scrape_with_actions",
|
|
1447
1487
|
"deep_research", "track_changes", "generate_llms_txt",
|