Skip to content

Commit 89c6169

Browse files
Merge pull request #37 from codefrydev/MCPTools
MCP
2 parents 3f27d37 + ae8066c commit 89c6169

29 files changed

Lines changed: 2095 additions & 47 deletions

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@ pipeline-config.txt
3131
.coverage
3232
.agents/
3333
skills-lock.json
34-
.crawl_results.csv
34+
crawl_results.csv

AGENT.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ Developer reference for agents and contributors. User-facing overview: [README.m
2727
- **Pipeline storage** (crawl, edges, nodes, report payload, Lighthouse, keywords, warnings) lives in **PostgreSQL only**. Deliverables use the Export view, `GET /api/report/export`, or MCP `export_*` tools — not files written by the main pipeline step.
2828
- **Pool tuning:** `DB_POOL_MIN` / `DB_POOL_MAX` (Python), `PGPOOL_MAX` (Node). Bulk crawl writes via `executemany`; optional **`crawl_stream_to_db`** streams rows during fetch. Per-URL raw HTML: `crawl_page_html` table (migration `015`); API `GET/POST /api/crawl/page-html` (localhost).
2929
- **`web/` APIs:** `/api/report/*` read routes (payload, meta, history — not localhost-guarded; protect with `AUTH_*` when exposed); `/api/run` spawns Python (localhost); `/api/jobs`, `/api/jobs/[id]`, `/api/jobs/[id]/cancel` (localhost); `/api/crawl/browser-status`, `/api/crawl/page-html` (localhost); `/api/pipeline-config` GET/PUT; `/api/llm-config` GET/PUT; `/api/chat` POST (SSE); `/api/chat/sessions` GET/POST; `/api/ollama/status` (localhost); `/api/properties/{id}/google/links/import` POST; `PipelineRunnerFab` saves pipeline + LLM state before each run. Full route list: `web/app/api/**/route.ts`.
30-
- **MCP:** `python -m website_profiling.mcp` (stdio, **340 read-only audit tools**, domain-scoped via `WP_MCP_DOMAIN`). See `docs/MCP.md`. Requires `pip install -r requirements.txt`.
30+
- **MCP:** `python -m website_profiling.mcp` (stdio) or `python -m website_profiling.mcp.http` (remote Streamable HTTP). Configure at **`/mcp`** in the web UI. See `docs/MCP.md`.
3131
- **AI Chat UI:** `/chat` — property-scoped chat with saved sessions (`chat_sessions`, `chat_messages`; migration `012_chat_sessions`).
3232
- **Job store:** PostgreSQL `pipeline_jobs` when `DATABASE_URL` is set (`pipelineJobsDb.ts` — status, timestamps, truncated logs). In-memory map in `pipelineJobs.ts` holds live log tail and child process handles; stale rows reconciled via `PIPELINE_JOB_STALE_HOURS`.
3333
- **Schema head:** `015_crawl_page_html` (recent: `013` link_edges/discovery, `014` job log truncation, `015` per-URL HTML storage).
34-
- **Docker:** `Dockerfile` + `docker-compose.yml` (postgres + web); **`docker-compose.prod.yml`** (production); **`docker-compose.pull.yml`** for pre-built images (`WEB_IMAGE`); **`LIGHTHOUSE_CHROME_FLAGS`**
34+
- **Docker:** `Dockerfile` + `docker-compose.yml` (postgres + web); **`docker-compose.prod.yml`** (production + remote MCP on `:8000`); **`docker-compose.pull.yml`** for pre-built images (`WEB_IMAGE`); **`LIGHTHOUSE_CHROME_FLAGS`**
3535

3636
**Where to edit**
3737

@@ -42,7 +42,7 @@ Developer reference for agents and contributors. User-facing overview: [README.m
4242
| DB schema | `alembic/versions/` |
4343
| Local analysis | `analysis/local.py`, `requirements.txt` |
4444
| AI insights (LLM) | `llm/enrich.py`, `llm/agent.py`, `llm_config.py`, `requirements.txt` |
45-
| Audit query tools (MCP + chat) | `tools/audit_tools/`, `mcp/server.py`, `commands/chat_cmd.py` |
45+
| Audit query tools (MCP + chat) | `tools/audit_tools/`, `mcp/server.py`, `mcp/http_server.py`, `commands/chat_cmd.py` |
4646
| Config / CLI | `config.py` (`load_config`, `load_config_from_db`), `cli.py`, `input.txt.example` |
4747
| UI pipeline schema | `web/src/lib/pipelineConfigSchema.ts` |
4848
| UI LLM schema | `web/src/lib/llmConfigSchema.ts` |

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ Site Audit focuses on **honest, self-hosted technical SEO**. It is not a drop-in
9393
</tr>
9494
</table>
9595

96-
Also included: **AI chat** over audit data (optional), **Content studio** (write &amp; optimize with live SEO scoring), **340 MCP tools** (domain-scoped servers), image SEO, GEO/AEO readiness, keyword explorer (GSC + on-site), backlinks (GSC Links import), compare runs, and portfolio management for agencies.
96+
Also included: **AI chat** over audit data (optional), **Content studio** (write &amp; optimize with live SEO scoring), **340 MCP tools** (local stdio or remote Streamable HTTP), image SEO, GEO/AEO readiness, keyword explorer (GSC + on-site), backlinks (GSC Links import), compare runs, and portfolio management for agencies.
9797

9898
<img src="docs/assets/social-preview.png" alt="Site Audit — developer-friendly SEO audit preview" width="100%">
9999

@@ -109,7 +109,7 @@ WebsiteProfiling/
109109
│ ├── integrations/ # Google Search Console, GA4, Bing, CrUX
110110
│ ├── llm/ # AI enrich + chat agent
111111
│ ├── tools/ # Exports, audit query tools, MCP helpers
112-
│ ├── mcp/ # MCP server (340 read-only tools, domain bundles)
112+
│ ├── mcp/ # MCP server (stdio + remote HTTP, domain bundles)
113113
│ ├── db/ # PostgreSQL storage layer
114114
│ ├── commands/ # CLI subcommands
115115
│ ├── cli.py # Pipeline entrypoint

docker-compose.prod.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,27 @@ services:
5555
profiles:
5656
- worker
5757

58+
mcp:
59+
build:
60+
context: .
61+
dockerfile: Dockerfile
62+
depends_on:
63+
postgres:
64+
condition: service_healthy
65+
command: ['python', '-m', 'website_profiling.mcp.http']
66+
environment:
67+
WEBSITE_PROFILING_ROOT: /app
68+
DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-website_profiling}
69+
WP_MCP_HTTP_HOST: 0.0.0.0
70+
WP_MCP_HTTP_PORT: 8000
71+
WP_MCP_TOKEN: ${WP_MCP_TOKEN:?set WP_MCP_TOKEN}
72+
WP_MCP_ALLOWED_HOSTS: ${WP_MCP_ALLOWED_HOSTS:-}
73+
WP_MCP_ALLOWED_ORIGINS: ${WP_MCP_ALLOWED_ORIGINS:-}
74+
WP_MCP_DOMAIN: ${WP_MCP_DOMAIN:-core}
75+
WP_PROPERTY_ID: ${WP_PROPERTY_ID:-}
76+
ports:
77+
- '${MCP_PORT:-8000}:8000'
78+
5879
volumes:
5980
pg-data:
6081
profiling-data:

docker-compose.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,27 @@ services:
4141
retries: 3
4242
start_period: 15s
4343

44+
# Optional remote MCP (Streamable HTTP). Uncomment and set WP_MCP_TOKEN / WP_MCP_ALLOWED_HOSTS.
45+
# mcp:
46+
# build:
47+
# context: .
48+
# dockerfile: Dockerfile
49+
# image: website-profiling:latest
50+
# depends_on:
51+
# postgres:
52+
# condition: service_healthy
53+
# command: ['python', '-m', 'website_profiling.mcp.http']
54+
# environment:
55+
# WEBSITE_PROFILING_ROOT: /app
56+
# DATABASE_URL: postgres://profiling:profiling@postgres:5432/website_profiling
57+
# WP_MCP_HTTP_HOST: 0.0.0.0
58+
# WP_MCP_HTTP_PORT: 8000
59+
# WP_MCP_TOKEN: ${WP_MCP_TOKEN:-dev-mcp-token}
60+
# WP_MCP_ALLOWED_HOSTS: localhost,127.0.0.1
61+
# WP_MCP_DOMAIN: core
62+
# ports:
63+
# - "8000:8000"
64+
4465
volumes:
4566
pg-data:
4667
profiling-data:

docs/MCP.md

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The same tool catalog powers in-app **AI Chat** at `/chat`.
1313
- [Prerequisites](#prerequisites)
1414
- [Domain-scoped servers](#domain-scoped-servers)
1515
- [Configuration](#configuration)
16+
- [Remote Streamable HTTP](#remote-streamable-http)
1617
- [MCP resources](#mcp-resources)
1718
- [Tool reference](#tool-reference)
1819
- [In-app chat](#in-app-chat)
@@ -31,12 +32,14 @@ export DATABASE_URL=postgres://profiling:profiling@localhost:5432/website_profil
3132
export PYTHONPATH=src
3233
```
3334

34-
Start the server:
35+
Start the local stdio server:
3536

3637
```bash
3738
python -m website_profiling.mcp
3839
```
3940

41+
For remote access over HTTP, see [Remote Streamable HTTP](#remote-streamable-http).
42+
4043
---
4144

4245
## Domain-scoped servers
@@ -111,6 +114,80 @@ Add to `.cursor/mcp.json` or your MCP client settings:
111114

112115
---
113116

117+
## Remote Streamable HTTP
118+
119+
Use this when Site Audit runs on a hosted server and your MCP client (Cursor, Claude Desktop, etc.) connects over the network instead of spawning a local stdio subprocess.
120+
121+
### Start the HTTP server
122+
123+
Configure access on **MCP settings** (`/mcp`) in the web UI (recommended), or set environment variables. UI changes apply on the next MCP request without restarting the service.
124+
125+
```bash
126+
export DATABASE_URL=postgres://profiling:profiling@localhost:5432/website_profiling
127+
export PYTHONPATH=src
128+
export WP_MCP_HTTP_HOST=0.0.0.0
129+
export WP_MCP_HTTP_PORT=8000
130+
export WP_MCP_DOMAIN=core
131+
export WP_PROPERTY_ID=1
132+
133+
python -m website_profiling.mcp.http
134+
```
135+
136+
Set **MCP bearer token** and **Allowed hostnames** on the Secrets page (or via `WP_MCP_TOKEN` / `WP_MCP_ALLOWED_HOSTS`). Environment variables override saved values when set.
137+
138+
The MCP endpoint is `http://<host>:8000/mcp` by default (`WP_MCP_HTTP_PATH=/mcp`).
139+
140+
### Environment variables
141+
142+
| Variable | Default | Purpose |
143+
|----------|---------|---------|
144+
| `WP_MCP_HTTP_HOST` | `127.0.0.1` | Bind address (`0.0.0.0` for Docker) |
145+
| `WP_MCP_HTTP_PORT` | `8000` | Listen port |
146+
| `WP_MCP_HTTP_PATH` | `/mcp` | Mount path |
147+
| `WP_MCP_TOKEN` | unset | Bearer token (**required** when not binding localhost). Save on **Secrets → Remote MCP** or set here (env wins). |
148+
| `WP_MCP_ALLOWED_HOSTS` | unset | Comma-separated `Host` allowlist (**required** for non-localhost bind). Save on **Secrets → Remote MCP** or set here. |
149+
| `WP_MCP_ALLOWED_ORIGINS` | unset | Comma-separated `Origin` allowlist for browser clients |
150+
| `WP_MCP_JSON_RESPONSE` | `false` | JSON responses instead of SSE streams |
151+
| `WP_MCP_DOMAIN` | `core` | Tool bundle (same as stdio) |
152+
| `WP_PROPERTY_ID` | unset | Default property (same as stdio) |
153+
154+
**Security:** `WP_MCP_TOKEN` is required when `WP_MCP_HTTP_HOST` is not localhost. Tools are read-only but expose audit, GSC, and GA4 data — treat the token like a database credential.
155+
156+
**DNS rebinding protection:** Whenever a token **and** allowed hosts are configured — via the UI **or** environment variables — the HTTP service enforces the bearer token plus the Host/Origin allowlist in its own middleware, and the MCP SDK's built-in DNS-rebinding check is turned off (the middleware supersedes it). The SDK check only applies as a fallback on a non-localhost bind that has no remote access configured (a state the startup validation otherwise refuses to boot in). Either way, set `WP_MCP_ALLOWED_HOSTS` to the public hostname clients use (e.g. `audit.example.com`).
157+
158+
### Cursor / Claude Desktop (remote)
159+
160+
```json
161+
{
162+
"mcpServers": {
163+
"site-audit-remote": {
164+
"url": "https://audit.example.com/mcp",
165+
"headers": {
166+
"Authorization": "Bearer your-long-random-token"
167+
}
168+
}
169+
}
170+
}
171+
```
172+
173+
### Docker (production)
174+
175+
The `mcp` service in `docker-compose.prod.yml` includes an `mcp` service. Set `WP_MCP_TOKEN` and `WP_MCP_ALLOWED_HOSTS` in the environment **or** configure them on **Secrets → Remote MCP** after deploy.
176+
177+
Terminate TLS at your reverse proxy and route `/mcp` to the MCP container. Recommended proxy settings: `proxy_buffering off`, long `proxy_read_timeout`.
178+
179+
### Troubleshooting
180+
181+
| Symptom | Likely cause |
182+
|---------|----------------|
183+
| Server refuses to start | Missing token or allowed hosts on non-localhost bind (Secrets page or env) |
184+
| 401 Unauthorized | Wrong or missing `Authorization: Bearer` header |
185+
| 404 Not Found | Wrong path — endpoint is `/mcp` by default |
186+
| Blocked / bad request behind proxy | Host not listed in allowed hostnames (Secrets → Remote MCP) |
187+
| Connection refused | Firewall, wrong port, or MCP service not running |
188+
189+
---
190+
114191
## MCP resources
115192

116193
| URI | Content |

docs/OPS.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,20 @@ AUTH_DEFAULT_ROLE=client-readonly
117117

118118
Production also requires `AUTH_SECRET` and optionally `AUTH_USER` / `AUTH_PASSWORD` (see `docker-compose.prod.yml`).
119119

120+
### Remote MCP (Streamable HTTP)
121+
122+
The `mcp` service in `docker-compose.prod.yml` exposes read-only audit tools over HTTP at `/mcp`. Configure on **Secrets → Remote MCP** (`/secrets`) or via environment variables (env overrides saved values):
123+
124+
| Variable | Purpose |
125+
|----------|---------|
126+
| `WP_MCP_TOKEN` | Bearer token for MCP clients (`Authorization: Bearer …`) |
127+
| `WP_MCP_ALLOWED_HOSTS` | Public hostname allowlist (e.g. `audit.example.com`) |
128+
| `WP_MCP_ALLOWED_ORIGINS` | Optional `Origin` allowlist |
129+
| `WP_MCP_DOMAIN` | Tool bundle (`core` recommended for remote) |
130+
| `MCP_PORT` | Host port mapped to container `8000` (default `8000`) |
131+
132+
Terminate TLS at your reverse proxy; do not expose plain HTTP publicly. Configure token and allowed hostnames on **Secrets → Remote MCP** (`/secrets`, Remote MCP section).
133+
120134
### Read-only client dashboards
121135

122136
Set `AUTH_DEFAULT_ROLE=client-readonly` so session logins cannot run audits or save settings. The API returns 403 on mutations; the UI hides **Run audit** and disables save controls. Use `viewer` instead if chat access should also be blocked.

requirements.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ groq==1.4.0
4343
pyspellchecker==0.9.0
4444
html5lib==1.1
4545

46-
# MCP server for Cursor / Claude Desktop
47-
mcp~=1.0.0
46+
# MCP server for Cursor / Claude Desktop (stdio + remote Streamable HTTP)
47+
mcp>=1.19,<2
48+
uvicorn>=0.30
49+
starlette>=0.38
4850

4951
# Dev / test
5052
pytest==9.0.3

src/website_profiling/mcp/http.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Entry point: python -m website_profiling.mcp.http"""
2+
from .http_server import main
3+
4+
if __name__ == "__main__":
5+
main()

0 commit comments

Comments
 (0)