
Fetch the unfetchable. A stealthy, headless browser service for AI agents.
GhostFetch bypasses anti-bot protections to fetch content from difficult sites (like X.com and LinkedIn) and converts it into clean, LLM-ready Markdown. It handles the complexity of headless browsing, proxy rotation, and fingerprinting so your agent doesn't have to.
Powered by the š¦ Phantom Fox ā slipping past every wall, unseen.
Fetching content for AI agents is hard. Simple requests or curl calls fail on modern sites due to JavaScript rendering and anti-bot checks. Heavy browser automation tools are slow and complex to manage.
GhostFetch solves this by providing:
URL ā GhostFetch (Headless Browser + Ghost Protocol) ā Markdown ā AI Agent
The fastest way to get started is via pip.
pip install ghostfetch
Browsers will auto-install on the first run.
ghostfetch "https://x.com/user/status/123"
Output:
{
"metadata": { "title": "...", "author": "..." },
"markdown": "Captured content in markdown format..."
}
# Open a visible browser to log in ā session is saved for reuse
ghostfetch auth login --domain linkedin.com --login-url https://www.linkedin.com/login
# Fetch content using the saved session
ghostfetch "https://www.linkedin.com/in/profile" --auth-session-id <SESSION_ID>
pip install ghostfetch
# Usage:
# ghostfetch "url" (CLI)
# from ghostfetch import fetch (Python SDK)
docker run -p 8000:8000 iarsalanshah/ghostfetch
# Service available at http://localhost:8000
git clone https://github.com/iArsalanshah/GhostFetch.git
cd GhostFetch
pip install -e .
playwright install chromium
# JSON output for parsing
ghostfetch "https://example.com" --json
# Metadata only
ghostfetch "https://example.com" --metadata-only
# Fetch behind a login wall
ghostfetch "https://linkedin.com/in/profile" --auth-session-id abc123
# Manage authenticated sessions
ghostfetch auth login --domain linkedin.com
ghostfetch auth status
ghostfetch auth revoke <SESSION_ID>
from ghostfetch import fetch
# Simple fetch
result = fetch("https://example.com")
print(result['markdown'])
# Fetch behind a login wall
result = fetch("https://linkedin.com/in/profile", auth_session_id="abc123")
print(result['markdown'])
Start the server:
ghostfetch serve
By default, fetch endpoints require API key auth:
export GHOSTFETCH_API_KEY="replace-with-strong-token"
Synchronous Fetch (Blocks until done):
curl -H "X-API-Key: $GHOSTFETCH_API_KEY" \
"http://localhost:8000/fetch/sync?url=https://example.com"
Asynchronous Fetch (Background Job):
curl -X POST "http://localhost:8000/fetch" \
-H "Content-Type: application/json" \
-H "X-API-Key: $GHOSTFETCH_API_KEY" \
-d '{"url": "https://example.com", "callback_url": "https://yourapp.com/webhook"}'
Fetch with Authenticated Session:
curl -X POST "http://localhost:8000/fetch/sync" \
-H "Content-Type: application/json" \
-H "X-API-Key: $GHOSTFETCH_API_KEY" \
-d '{"url": "https://linkedin.com/in/profile", "auth_session_id": "abc123"}'
Import an Auth Session (programmatic):
curl -X POST "http://localhost:8000/auth/sessions/import" \
-H "Content-Type: application/json" \
-H "X-API-Key: $GHOSTFETCH_API_KEY" \
-d '{"domain": "linkedin.com", "storage_state": {...}, "ttl_seconds": 86400}'
List Auth Sessions:
curl -H "X-API-Key: $GHOSTFETCH_API_KEY" \
"http://localhost:8000/auth/sessions"
Revoke an Auth Session:
curl -X DELETE "http://localhost:8000/auth/sessions/abc123" \
-H "X-API-Key: $GHOSTFETCH_API_KEY"
Check Health:
curl "http://localhost:8000/health"
Check Job Status:
curl -H "X-API-Key: $GHOSTFETCH_API_KEY" \
"http://localhost:8000/job/a1b2c3d4-e5f6-7890"
All successful fetches return a standardized JSON structure:
{
"metadata": {
"title": "Page Title",
"author": "Author Name",
"publish_date": "2023-01-01",
"images": ["image_url.jpg"]
},
"markdown": "# Page Title\n\nExtracted content...",
"url": "https://example.com/original-url",
"status": "success"
}
Auth Wall Detection: When fetching login-gated pages without a valid session, the response status will be one of:
auth_required ā The page requires a login.auth_expired ā The saved session has expired.auth_challenge ā An additional security challenge (e.g., CAPTCHA) was encountered.GhostFetch is configured via environment variables.
| Variable | Default | Description |
|---|---|---|
MAX_CONCURRENT_BROWSERS | 2 | Max concurrent browser contexts |
MIN_DOMAIN_DELAY | 30 | Seconds between requests to same domain |
JITTER_MIN | 3.0 | Minimum random wait time after page load |
JITTER_MAX | 7.0 | Maximum random wait time after page load |
GHOSTFETCH_PORT | 8000 | Port for the API server |
PROXY_STRATEGY | round_robin | round_robin or random |
GHOSTFETCH_API_KEY | empty | Required API key for fetch endpoints when auth is enabled |
REQUIRE_API_KEY | true | Enable X-API-Key enforcement on fetch endpoints |
BLOCK_PRIVATE_NETWORKS | true | Blocks localhost/private IP targets to reduce SSRF risk |
CALLBACK_ALLOWED_HOSTS | empty | Optional comma-separated callback host allowlist |
CALLBACK_MAX_ATTEMPTS | 3 | Max retries for webhook callback delivery |
CALLBACK_RETRY_BASE_SECONDS | 1.0 | Base delay used for exponential callback retry |
GITHUB_TOKEN | empty | Token used for posting GitHub issue comments via API |
LOG_FORMAT | text | Set to json for structured logs |
GHOSTFETCH_DEBUG | false | Enables development reload mode when running python main.py |
STORAGE_DIR | storage | Directory for persistent sessions, auth sessions, and logs |
Proxies:
Create a proxies.txt file in the working directory with one proxy per line:
http://user:pass@host:port
Concurrency note:
MAX_CONCURRENT_BROWSERS is a global browser-context cap shared by both sync and async fetch paths.
Auth session storage note:
Authenticated session state files are stored under STORAGE_DIR/auth_sessions/ and may contain sensitive cookies. Keep STORAGE_DIR on a private filesystem with restricted access.
For GitHub integration, MCP Server configuration (Claude Desktop), and production deployment guides (Docker Compose, Proxy strategies), please see:
š Advanced Usage & Deployment Guideā
GhostFetch is packaged as a portable Agent Plugin conforming to the open, vendor-neutral Agent Plugins 1.0.0ā standard. Any conformant agent client (Cursor, Claude Code, OpenAI Codex, etc.) can discover and load GhostFetch as a plugin.
The plugin consists of two manifest files at the repository root:
plugin.json ā The Agent Plugins manifest declaring the plugin metadata (name, version, author, license, keywords).mcp.json ā The MCP server connection configuration in Agent Plugins format.Additionally, the plugin includes:
skills/ghostfetch/SKILL.md ā An Agent Skill (per the Agent Skills specificationā ) that teaches agents how to use GhostFetch.Conformant agent clients can load GhostFetch by pointing to this repository:
# Example: clone and install as a plugin
git clone https://github.com/iArsalanshah/GhostFetch.git
# The client reads plugin.json + mcp.json to discover capabilities
The mcp.json declares a single MCP server ghostfetch that runs via stdio:
python -m ghostfetch.mcp_serverSYNC_TIMEOUT_DEFAULT=120, MAX_SYNC_TIMEOUT=300You can also run the MCP server directly without a plugin client:
# Start the MCP server (stdio transport)
python -m ghostfetch.mcp_server
# Or with custom timeouts
SYNC_TIMEOUT_DEFAULT=60 MAX_SYNC_TIMEOUT=180 python -m ghostfetch.mcp_server
The server exposes GhostFetch's fetching capabilities as MCP tools that any MCP-compatible client can invoke.
playwright install chromium.timeout in request or SYNC_TIMEOUT_DEFAULT env var.MAX_CONCURRENT_BROWSERS.PRs welcome. Open an issue for major changes.
For educational and research purposes only. Users are responsible for complying with the Terms of Service, robots.txt, and applicable laws of the websites they access. This tool should not be used for unauthorized scraping or circumventing security measures in violation of law.
MIT License. See LICENSEā for details.
Content type
Image
Digest
sha256:c2f01883dā¦
Size
489.4 MB
Last updated
2 months ago
docker pull iarsalanshah/ghostfetch