mcp-web-engine 1.0.3 → 1.0.5
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 +84 -107
- package/bin/cli.js +71 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,132 +1,87 @@
|
|
|
1
1
|
# 🌐 MCP Web Engine & Search Gateway
|
|
2
2
|
|
|
3
|
-
> **
|
|
3
|
+
> **Privacy-First, Self-Hostable & SSRF-Hardened MCP Server & Web Engine for AI Agents**
|
|
4
|
+
> **MCP Specification 2026-07-28 Pure Stateless Core & Stdio / HTTP Dual Mode**
|
|
5
|
+
|
|
6
|
+
[](https://pypi.org/project/mcp-web-engine/)
|
|
7
|
+
[](https://www.npmjs.com/package/mcp-web-engine)
|
|
8
|
+
[](LICENSE)
|
|
4
9
|
|
|
5
10
|
---
|
|
6
11
|
|
|
7
|
-
##
|
|
12
|
+
## ⚡ Instant Quickstart with `uvx` (Recommended for AI Agents)
|
|
13
|
+
|
|
14
|
+
AI agents (Claude Desktop, Cursor, Windsurf, TurboLLM, Hermes Agent) can run `mcp-web-engine` in an isolated ephemeral environment with **zero configuration**:
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
```bash
|
|
17
|
+
# Run stdio MCP server in an isolated environment (10ms startup, 0% system contamination)
|
|
18
|
+
uvx mcp-web-engine
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or run via Git repository:
|
|
22
|
+
```bash
|
|
23
|
+
uvx --from git+https://github.com/Arbolencio/mcp-web-engine mcp-web-engine
|
|
24
|
+
```
|
|
11
25
|
|
|
12
26
|
---
|
|
13
27
|
|
|
14
28
|
## 📌 What it does & Why it exists
|
|
15
29
|
|
|
16
|
-
`
|
|
30
|
+
`mcp-web-engine` is a high-performance Model Context Protocol (MCP) server implementing the **MCP Specification 2026-07-28 Pure Stateless Core**. It provides AI Agents with secure, private, and SSRF-hardened access to multi-engine web search, raw content fetching, and clean HTML-to-Markdown extraction.
|
|
17
31
|
|
|
18
32
|
### Key Capabilities & Architecture
|
|
33
|
+
- **Dual Execution Mode:**
|
|
34
|
+
- **Stdio Mode (Default):** Runs JSON-RPC over `stdin`/`stdout` for direct LLM client integration (`uvx mcp-web-engine`).
|
|
35
|
+
- **HTTP/SSE Server Mode:** Runs FastAPI/Uvicorn server (`mcp-web-engine --serve`).
|
|
19
36
|
- **Pure Stateless Core (Spec 2026-07-28):** Zero sessions, zero state, zero initialize handshake required. Every request is completely independent.
|
|
20
|
-
- **Protocol Methods:** Implements `server/discover`, `tools/list`, and `tools/call` with standard JSON-RPC 2.0
|
|
37
|
+
- **Protocol Methods:** Implements `server/discover`, `tools/list`, and `tools/call` with standard JSON-RPC 2.0.
|
|
21
38
|
- **Zero Third-Party Tracking:** Powered by an internal SearXNG meta-search engine aggregating 70+ sources with DuckDuckGo fallback.
|
|
39
|
+
- **Automatic URL Normalization:** Handled transparently by `normalize_searxng_url()` — any provided `SEARXNG_URL` variation (`http://127.0.0.1:8082`, `http://127.0.0.1:8082/`, `http://127.0.0.1:8082/search`) automatically appends `/search` if missing.
|
|
22
40
|
- **SSRF Hardened:** Pre-request DNS resolution checks prevent agents from accessing internal networks (`localhost`, `127.0.0.1`, `192.168.x.x`, `169.254.169.254`). Step-by-step HTTP 301/302 redirect re-validation.
|
|
23
|
-
- **
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## 🏗️ Architecture
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
[ MCP Client / Claude / Cursor / Windsurf / Inspector ]
|
|
31
|
-
│
|
|
32
|
-
(HTTPS Header: Authorization: Bearer sk_mcp_...)
|
|
33
|
-
(HTTPS Header: MCP-Protocol-Version: 2026-07-28)
|
|
34
|
-
▼
|
|
35
|
-
[ Cloudflare Managed HTTPS Tunnel ]
|
|
36
|
-
│
|
|
37
|
-
▼
|
|
38
|
-
[ FastAPI Gateway (Port 5050) ]
|
|
39
|
-
├── Endpoint: POST /v1/mcp (Pure Stateless Core)
|
|
40
|
-
├── Security & Auth (Bearer Token + Sliding Window Rate Limiter)
|
|
41
|
-
├── SSRF Validator (Pre-request DNS + IP Subnet Filtering)
|
|
42
|
-
├── Method 1: server/discover (Server Metadata Discovery)
|
|
43
|
-
├── Method 2: tools/list (Tools Discovery)
|
|
44
|
-
└── Method 3: tools/call (Tool Execution Engine)
|
|
45
|
-
```
|
|
41
|
+
- **Zero `--break-system-packages`:** Executed 100% in sandboxed virtual environments created on-the-fly by `uv` or `venv`.
|
|
46
42
|
|
|
47
43
|
---
|
|
48
44
|
|
|
49
|
-
##
|
|
45
|
+
## 🔌 Integration Guides
|
|
50
46
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
47
|
+
### 1. TurboLLM / Local MCP Catalog
|
|
48
|
+
```typescript
|
|
49
|
+
{
|
|
50
|
+
id: 'mcp-web-engine',
|
|
51
|
+
name: 'MCP Web Engine',
|
|
52
|
+
cat: 'Search',
|
|
53
|
+
desc: 'Privacy-first, self-hostable & SSRF-hardened web search engine and scraper for AI agents.',
|
|
54
|
+
cmd: 'uvx mcp-web-engine',
|
|
55
|
+
uvx: true,
|
|
56
|
+
argNote: 'Requires a running SearXNG instance.',
|
|
57
|
+
envs: [
|
|
58
|
+
{ key: 'SEARXNG_URL', desc: 'SearXNG meta-search endpoint (default: http://127.0.0.1:8082/search)', required: true },
|
|
59
|
+
],
|
|
60
|
+
}
|
|
65
61
|
```
|
|
66
62
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
## 🔌 Connecting from MCP Clients (Cursor, Windsurf, Inspector, Claude Desktop)
|
|
70
|
-
|
|
71
|
-
### 1. Cursor / Windsurf / MCP Inspector (Direct HTTPS):
|
|
72
|
-
- **Server Endpoint:** `https://ultimate-ignored-over-light.trycloudflare.com/v1/mcp`
|
|
73
|
-
- **Headers:**
|
|
74
|
-
- `Authorization: Bearer YOUR_BETA_KEY_HERE`
|
|
75
|
-
- `MCP-Protocol-Version: 2026-07-28`
|
|
76
|
-
|
|
77
|
-
### 2. Claude Desktop (via `mcp-remote` bridge):
|
|
63
|
+
### 2. Claude Desktop (`claude_desktop_config.json`)
|
|
78
64
|
```json
|
|
79
65
|
{
|
|
80
66
|
"mcpServers": {
|
|
81
67
|
"mcp-web-engine": {
|
|
82
|
-
"command": "
|
|
83
|
-
"args": [
|
|
84
|
-
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
"--header",
|
|
88
|
-
"Authorization: Bearer YOUR_BETA_KEY_HERE"
|
|
89
|
-
]
|
|
68
|
+
"command": "uvx",
|
|
69
|
+
"args": ["mcp-web-engine"],
|
|
70
|
+
"env": {
|
|
71
|
+
"SEARXNG_URL": "http://127.0.0.1:8082/search"
|
|
72
|
+
}
|
|
90
73
|
}
|
|
91
74
|
}
|
|
92
75
|
}
|
|
93
76
|
```
|
|
94
77
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
## 🛠️ MCP 2026-07-28 Stateless Protocol Lifecycle
|
|
98
|
-
|
|
99
|
-
### 1. `server/discover` Discovery
|
|
100
|
-
```json
|
|
101
|
-
{
|
|
102
|
-
"jsonrpc": "2.0",
|
|
103
|
-
"id": 1,
|
|
104
|
-
"method": "server/discover",
|
|
105
|
-
"params": {}
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
### 2. `tools/list` Discovery
|
|
110
|
-
```json
|
|
111
|
-
{
|
|
112
|
-
"jsonrpc": "2.0",
|
|
113
|
-
"id": 2,
|
|
114
|
-
"method": "tools/list",
|
|
115
|
-
"params": {}
|
|
116
|
-
}
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
### 3. `tools/call` Execution
|
|
78
|
+
### 3. Cursor / Windsurf / Hermes Agent
|
|
120
79
|
```json
|
|
121
80
|
{
|
|
122
|
-
"
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
"name": "web_search",
|
|
127
|
-
"arguments": {
|
|
128
|
-
"query": "Model Context Protocol 2026 specification",
|
|
129
|
-
"limit": 5
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"mcp-web-engine": {
|
|
83
|
+
"command": "uvx",
|
|
84
|
+
"args": ["mcp-web-engine"]
|
|
130
85
|
}
|
|
131
86
|
}
|
|
132
87
|
}
|
|
@@ -134,28 +89,50 @@ curl -s https://ultimate-ignored-over-light.trycloudflare.com/health
|
|
|
134
89
|
|
|
135
90
|
---
|
|
136
91
|
|
|
137
|
-
##
|
|
92
|
+
## 🛠️ Provided MCP Tools
|
|
93
|
+
|
|
94
|
+
| Tool Name | Parameters | Description |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `web_search` | `query` (str), `limit` (int, default: 10) | Multi-engine meta-search via SearXNG returning structured JSON results |
|
|
97
|
+
| `fetch_url` | `url` (str), `max_bytes` (int, optional) | Fetches raw web content after strict SSRF security validation |
|
|
98
|
+
| `extract_markdown` | `url` (str), `max_bytes` (int, optional) | Scrapes a webpage and converts HTML into clean, LLM-optimized Markdown |
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## ⚙️ Configuration & Environment Variables
|
|
138
103
|
|
|
139
|
-
|
|
104
|
+
| Variable | Required | Default | Description |
|
|
105
|
+
|---|---|---|---|
|
|
106
|
+
| `SEARXNG_URL` | **Yes** | `http://127.0.0.1:8082/search` | SearXNG meta-search endpoint (Auto-normalizes trailing slashes and missing `/search`) |
|
|
107
|
+
| `PORT` | No | `5050` | HTTP/SSE server port (Used only when running in `--serve` mode) |
|
|
108
|
+
| `HOST` | No | `0.0.0.0` | HTTP/SSE server binding host |
|
|
109
|
+
| `API_KEY` | No | `""` | Optional authentication key for HTTP endpoints |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 🛠️ Development & Building
|
|
140
114
|
|
|
141
115
|
```bash
|
|
142
|
-
#
|
|
143
|
-
|
|
116
|
+
# Clone Repository
|
|
117
|
+
git clone https://github.com/Arbolencio/mcp-web-engine.git
|
|
118
|
+
cd mcp-web-engine
|
|
119
|
+
|
|
120
|
+
# Build Wheel for PyPI
|
|
121
|
+
python3 -m build --wheel
|
|
144
122
|
|
|
145
|
-
#
|
|
146
|
-
python
|
|
123
|
+
# Run locally in stdio mode via uv
|
|
124
|
+
uv run python -m mcp_web_engine.main
|
|
147
125
|
|
|
148
|
-
#
|
|
149
|
-
python
|
|
126
|
+
# Run locally in HTTP server mode via uv
|
|
127
|
+
uv run python -m mcp_web_engine.main --serve
|
|
150
128
|
```
|
|
151
129
|
|
|
152
130
|
---
|
|
153
131
|
|
|
154
|
-
## 🛡️ Security &
|
|
132
|
+
## 🛡️ Security & Environment Isolation
|
|
155
133
|
|
|
156
134
|
- **SSRF Hardening:** Blocks `localhost`, loopbacks, private subnets (`10.0.0.0/8`, `192.168.0.0/16`, `172.16.0.0/12`), percent-encoding bypasses (`%31%32%37...`), and re-validates `Location` headers on HTTP redirects step-by-step.
|
|
157
|
-
- **
|
|
158
|
-
- **Honest Positioning:** Designed for research, documentation retrieval, and web search. Auth-walled sites requiring login (e.g. private social feeds) are not supported.
|
|
135
|
+
- **Zero Package Pollution:** Execution via `uvx` or `bin/cli.js` never modifies global site-packages or uses dangerous flags like `--break-system-packages`.
|
|
159
136
|
|
|
160
137
|
---
|
|
161
138
|
|
package/bin/cli.js
CHANGED
|
@@ -1,55 +1,90 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* MCP Web Engine CLI Entry Point (npm
|
|
5
|
-
* Spawns
|
|
6
|
-
*
|
|
4
|
+
* MCP Web Engine CLI Entry Point (npm / npx fallback)
|
|
5
|
+
* Spawns MCP Web Engine in an ISOLATED Python environment without polluting global system packages.
|
|
6
|
+
*
|
|
7
|
+
* Recommended execution for AI agents:
|
|
8
|
+
* uvx mcp-web-engine
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
const { spawn, execSync } = require('child_process');
|
|
10
12
|
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const os = require('os');
|
|
11
15
|
|
|
12
|
-
const mainPy = path.join(__dirname, '..', 'main.py');
|
|
13
16
|
const reqTxt = path.join(__dirname, '..', 'requirements.txt');
|
|
14
17
|
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
function forwardChild(child) {
|
|
19
|
+
child.on('error', (err) => {
|
|
20
|
+
console.error('[mcp-web-engine] Failed to start process:', err.message);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
});
|
|
23
|
+
child.on('exit', (code, signal) => {
|
|
24
|
+
if (signal) {
|
|
25
|
+
process.exit(128 + (signal === 'SIGINT' ? 2 : 15));
|
|
26
|
+
}
|
|
27
|
+
process.exit(code ?? 0);
|
|
28
|
+
});
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
// Auto-check and install python dependencies if missing (pip logs sent to stderr to preserve stdio JSON-RPC)
|
|
29
|
-
try {
|
|
30
|
-
execSync(`${pythonCmd} -c "import fastapi, uvicorn, requests, bs4"`, { stdio: 'ignore' });
|
|
31
|
-
} catch (e) {
|
|
31
|
+
// 1. Try running with 'uv' if available (fastest, cleanest, isolated)
|
|
32
|
+
function tryUv() {
|
|
32
33
|
try {
|
|
33
|
-
execSync(
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
execSync('uv --version', { stdio: 'ignore' });
|
|
35
|
+
const repoRoot = path.join(__dirname, '..');
|
|
36
|
+
const child = spawn('uv', ['run', '--directory', repoRoot, 'python', '-m', 'mcp_web_engine.main', ...process.argv.slice(2)], {
|
|
37
|
+
stdio: 'inherit',
|
|
38
|
+
env: process.env
|
|
39
|
+
});
|
|
40
|
+
forwardChild(child);
|
|
41
|
+
return true;
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return false;
|
|
36
44
|
}
|
|
37
45
|
}
|
|
38
46
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
47
|
+
// 2. Fallback to dedicated isolated virtualenv in user cache dir (~/.cache/mcp-web-engine/venv)
|
|
48
|
+
function runInIsolatedVenv() {
|
|
49
|
+
const cacheDir = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
|
|
50
|
+
const venvDir = path.join(cacheDir, 'mcp-web-engine', 'venv');
|
|
51
|
+
const venvPython = process.platform === 'win32'
|
|
52
|
+
? path.join(venvDir, 'Scripts', 'python.exe')
|
|
53
|
+
: path.join(venvDir, 'bin', 'python');
|
|
54
|
+
const venvPip = process.platform === 'win32'
|
|
55
|
+
? path.join(venvDir, 'Scripts', 'pip.exe')
|
|
56
|
+
: path.join(venvDir, 'bin', 'pip');
|
|
44
57
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
});
|
|
58
|
+
function findSystemPython() {
|
|
59
|
+
for (const cmd of ['python3', 'python']) {
|
|
60
|
+
try {
|
|
61
|
+
execSync(`${cmd} --version`, { stdio: 'ignore' });
|
|
62
|
+
return cmd;
|
|
63
|
+
} catch (e) {}
|
|
64
|
+
}
|
|
65
|
+
throw new Error('Python 3 is required but was not found on PATH.');
|
|
66
|
+
}
|
|
49
67
|
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
68
|
+
// Ensure isolated venv exists and has requirements installed
|
|
69
|
+
if (!fs.existsSync(venvPython)) {
|
|
70
|
+
console.log('[mcp-web-engine] Creating isolated Python venv at:', venvDir);
|
|
71
|
+
const sysPython = findSystemPython();
|
|
72
|
+
fs.mkdirSync(path.dirname(venvDir), { recursive: true });
|
|
73
|
+
execSync(`${sysPython} -m venv "${venvDir}"`, { stdio: 'inherit' });
|
|
74
|
+
console.log('[mcp-web-engine] Installing dependencies into isolated venv...');
|
|
75
|
+
execSync(`"${venvPip}" install -r "${reqTxt}"`, { stdio: 'inherit' });
|
|
53
76
|
}
|
|
54
|
-
|
|
55
|
-
|
|
77
|
+
|
|
78
|
+
const repoRoot = path.join(__dirname, '..');
|
|
79
|
+
const env = { ...process.env, PYTHONPATH: path.join(repoRoot, 'src') };
|
|
80
|
+
|
|
81
|
+
const child = spawn(venvPython, ['-m', 'mcp_web_engine.main', ...process.argv.slice(2)], {
|
|
82
|
+
stdio: 'inherit',
|
|
83
|
+
env: env
|
|
84
|
+
});
|
|
85
|
+
forwardChild(child);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!tryUv()) {
|
|
89
|
+
runInIsolatedVenv();
|
|
90
|
+
}
|