https://github.com/ptbsare/pages-mcp-server
https://github.com/ptbsare/pages-mcp-server
Last synced: 28 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/ptbsare/pages-mcp-server
- Owner: ptbsare
- License: gpl-3.0
- Created: 2026-05-30T14:52:22.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-08T13:09:09.000Z (30 days ago)
- Last Synced: 2026-06-08T13:15:28.661Z (29 days ago)
- Language: TypeScript
- Size: 812 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# Pages MCP Server
Self-hosted MCP server for deploying static websites and sharing files/folders. Deploy HTML pages, entire sites, or individual files via MCP tools or REST API — get shareable public URLs instantly.
## Table of Contents
- [Features](#features)
- [Quick Start](#quick-start)
- [Architecture](#architecture)
- [MCP Tools](#mcp-tools)
- [API Reference](#api-reference)
- [Client Modes](#client-modes)
- [Security](#security)
- [Configuration](#configuration)
- [Docker](#docker)
- [Nginx Reverse Proxy](#nginx-reverse-proxy)
- [License](#license)
## Features
- **Deploy HTML** — send raw HTML string or local `.html` file
- **Deploy Folder** — zip a local site folder (CSS, JS, images…), deploy all files
- **Share Files/Folders** — file listing with preview (text with syntax highlighting, images with HEIC conversion)
- **Public Pages** — all pages at `/s/:shareId`, no auth required
- **File Shares** — files/folders at `/f/:shareId` with directory navigation and zip download
- **MCP Protocol** — 5 tools: `deploy_html`, `deploy_folder`, `deploy_file`, `list_pages`, `delete_page`
- **Admin Dashboard** — web UI for managing pages, API tokens, and 2FA
- **Lock/Unlock** — prevent auto-cleanup of important shares
- **Auto-cleanup** — delete expired shares (configurable via `SHARE_EXPIRE_DAYS`)
- **2FA (TOTP)** — optional two-factor authentication for admin access
- **Docker** — multi-arch images, multi-stage build, non-root user
## Quick Start
### Via npx (recommended)
```bash
# Start server
npx github:ptbsare/pages-mcp-server server \
--port 3000 \
--domain https://mysite.com \
--admin-user admin \
--admin-pass secret
```
After starting, access the **admin dashboard** at `https://mysite.com:3000/`. Create API tokens from there.
**Stdio MCP client** (for AI assistants):
```bash
npx github:ptbsare/pages-mcp-server client \
--url https://mysite.com:3000 \
--auth-token your-api-token
```
**Interactive CLI**:
```bash
npx github:ptbsare/pages-mcp-server client \
--url https://mysite.com:3000 \
--interactive
```
### Via Docker
```bash
docker run -d \
--name pages-mcp \
-p 3000:3000 \
-e DOMAIN=https://mysite.com \
-e ADMIN_USERNAME=admin \
-e ADMIN_PASSWORD=secret \
-e OUT_PORT=3000 \
-v pages-data:/data \
ghcr.io/ptbsare/pages-mcp-server/pages-mcp-server:latest
```
### Via Source
```bash
git clone https://github.com/ptbsare/pages-mcp-server
cd pages-mcp-server
npm install
npm run build
node dist/server/index.js
```
## Architecture
```
┌──────────────────────────────────────────────────────────────┐
│ Server (Express) │
│ │
│ / → Admin Dashboard (Basic auth + 2FA) │
│ /mcp → MCP JSON-RPC (Bearer token auth) │
│ /api/deploy → Deploy REST API (Bearer token auth) │
│ /api/admin → Admin REST API (Basic auth + 2FA) │
│ /s/:shareId → Public static page (no auth) │
│ /f/:shareId → File share page (no auth) │
│ /f/:shareId/raw/** → Direct file download (no auth) │
│ /health → Health check │
│ │
│ sql.js (SQLite) + File Storage │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Client │
│ │
│ Mode 1: HTTP MCP Client (direct /mcp JSON-RPC calls) │
│ Mode 2: Stdio MCP Server (local proxy to remote, for AI) │
│ Mode 3: Interactive CLI (manual commands) │
└──────────────────────────────────────────────────────────────┘
```
## MCP Tools
| Tool | Description | Required Args | Optional Args |
|------|-------------|---------------|---------------|
| `deploy_html` | Deploy HTML string or local file as a public page | `value` (string) or `path` (local .html file) | `name`, `description` |
| `deploy_folder` | Deploy a local folder as a static website (must contain index.html) | `path` (string) | `name`, `description` |
| `deploy_file` | Share a local file or folder | `path` (string) | `name`, `description` |
| `list_pages` | List all deployed pages and shares | — | `limit`, `offset` |
| `delete_page` | Delete a page or share by database ID | `id` (string) | — |
**Client-side path validation** (applies to `deploy_html`, `deploy_folder`, `deploy_file`):
| Environment Variable | Default | Description |
|----------------------|---------|-------------|
| `DEPLOY_ALLOW_ALL` | — | Set to `"1"` to disable all path restrictions |
| `DEPLOY_ALLOW_PATHS` | — | Comma-separated allowed path prefixes (e.g. `/tmp,/home/user`) |
| `DEPLOY_BLOCK_PATHS` | `/etc,/var,/usr,...` | Comma-separated blocked path prefixes |
| `DEPLOY_BLOCK_ROOT_DIRS` | `/root/.ssh,/root/.gnupg,/root/.aws,...` | Comma-sensitive directories under `/root` to block |
## API Reference
### Authentication
| Layer | Method | Used For |
|-------|--------|----------|
| **Admin** | HTTP Basic + optional TOTP 2FA | Dashboard, token management, page CRUD |
| **API/MCP** | Bearer token (`Authorization: Bearer `) | Deploy, list, delete |
### URL Types
| Pattern | Type | Description |
|---------|------|-------------|
| `/s/:shareId` | Static Page | HTML/CSS/JS website |
| `/s/:shareId/*` | Page Resource | Sub-resources (images, CSS, JS) |
| `/f/:shareId` | File Share | File listing with preview |
| `/f/:shareId/raw/**` | File Download | Direct file access |
| `/f/:shareId/list/**` | Directory API | JSON directory listing |
### REST Endpoints
**Deploy HTML:**
```bash
curl -X POST https://mysite.com/api/deploy/html \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{"value": "
Hello
", "name": "My Page"}'
```
**Deploy Folder (zip):**
```bash
base64 -w0 site.zip | xargs -I{} curl -X POST https://mysite.com/api/deploy/folder \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{"zipBase64": "{}", "name": "My Site"}'
```
**Deploy File:**
```bash
# Single file
curl -X POST "https://mysite.com/api/deploy/file?filename=report.pdf" \
-H "Authorization: Bearer " \
-H "Content-Type: application/octet-stream" \
--data-binary @report.pdf
# Folder (zip)
curl -X POST "https://mysite.com/api/deploy/file?filename=my-site.zip&name=My Site" \
-H "Authorization: Bearer " \
-H "Content-Type: application/octet-stream" \
--data-binary @my-site.zip
```
**Token Management:**
```bash
curl -u admin:password https://mysite.com/api/admin/tokens # List
curl -X POST -u admin:password https://mysite.com/api/admin/tokens -d '{"name": "CI"}' # Create
curl -X DELETE -u admin:password https://mysite.com/api/admin/tokens/ # Delete
```
**Page Management:**
```bash
curl -u admin:password https://mysite.com/api/admin/pages # List
curl -X POST -u admin:password https://mysite.com/api/admin/pages//lock -d '{"locked": true}'
curl -X PUT -u admin:password https://mysite.com/api/admin/pages/ -d '{"name": "New Name"}'
curl -X DELETE -u admin:password https://mysite.com/api/admin/pages/
```
## Client Modes
### Mode 1: HTTP MCP Client (direct)
For programmatic use with `fetch`:
```json
{
"mcpServers": {
"pages-mcp": {
"url": "https://mysite.com:3000/mcp",
"headers": { "Authorization": "Bearer your-api-token" }
}
}
}
```
### Mode 2: Stdio MCP Server (for AI assistants)
For Cursor, Claude Desktop, etc. — runs locally, proxies to remote:
```json
{
"mcpServers": {
"pages-mcp": {
"command": "npx",
"args": [
"github:ptbsare/pages-mcp-server",
"client",
"--url", "https://mysite.com:3000",
"--auth-token", "your-api-token"
]
}
}
}
```
### Mode 3: Interactive CLI
```bash
npx github:ptbsare/pages-mcp-server client --url https://mysite.com:3000 --interactive
```
## Security
### Rate Limiting (App Layer)
| Limiter | Threshold | Scope |
|---------|-----------|-------|
| `deployLimiter` | 20 req / 15 min / IP | `/api/deploy/*` |
| `adminLimiter` | 100 req / 15 min / IP | `/api/admin/*` |
| `otpLimiter` | 10 req / 15 min / IP | `/api/admin/otp/verify` |
> **Note:** `/s/` and `/f/` public endpoints have **no rate limiting** by design.
### CORS
Only same-origin requests allowed. No cross-origin API access.
### Path Validation (Client-Side)
Deploy paths validated before upload:
- **Extension**: only `.html` / `.htm` for `deploy_html`
- **Content**: must contain `` tag for `deploy_html`
- **Path whitelist**: `DEPLOY_ALLOW_PATHS`, `DEPLOY_BLOCK_PATHS`, `DEPLOY_BLOCK_ROOT_DIRS`
- **Bypass**: `DEPLOY_ALLOW_ALL=1` disables all checks
### Admin 2FA
Optional TOTP-based two-factor authentication (Google Authenticator, Authy, etc.).
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `PORT` | 3000 | Internal server port |
| `OUT_PORT` | Same as `PORT` | External port for public URLs. Set when using Docker port mapping (e.g. `38300`). Standard ports (80/443) auto-omitted from URLs. |
| `DOMAIN` | `http://localhost:3000` | Base domain without port |
| `ADMIN_USERNAME` | `admin` | Admin dashboard username |
| `ADMIN_PASSWORD` | `admin123` | Admin dashboard password |
| `AUTH_TOKEN` | — | Initial API token(s), comma-separated. Seeded on first startup only. |
| `DB_PATH` | `~/.pages-mcp/pages.db` | SQLite database path |
| `STORAGE_PATH` | `~/.pages-mcp/storage` | File storage path |
| `SHARE_EXPIRE_DAYS` | `0` (disabled) | Auto-delete shares older than N days |
| `STRICT_SHARE_CSP` | (disabled) | Set to `1` to enforce `script-src 'self'` on share pages. Default allows external scripts (`* 'unsafe-inline'`) |
> **Note:** `AUTH_TOKEN` is only for initial seeding. Manage tokens via the admin dashboard after setup.
## Docker
### Images
| Tag | When |
|-----|------|
| `beta` | Every push to `main` |
| `latest` | On version tag push (e.g. `v1.2.3`) |
| `v{version}` | On tag push |
| `v{major}.{minor}` | On tag push |
### Port Mapping
| Scenario | PORT | OUT_PORT | Public URL |
|----------|------|----------|------------|
| Direct (no Docker) | 3000 | 3000 | `http://domain:3000` |
| Docker (same port) | 3000 | 3000 | `http://domain:3000` |
| Docker (mapped) | 3000 | 38300 | `http://domain:38300` |
| Standard HTTPS | 443 | 443 | `https://domain` (port omitted) |
### Volumes
All persistent data lives under `/data` inside the container:
| Path | Description | Required |
|------|-------------|----------|
| `/data/db/pages.db` | SQLite database | **Yes** |
| `/data/storage/` | Deployed files | **Yes** |
### Docker Compose
```yaml
version: "3.8"
services:
pages-mcp:
image: ghcr.io/ptbsare/pages-mcp-server/pages-mcp-server:latest
ports: ["3000:3000"]
environment:
- PORT=3000
- DOMAIN=http://localhost:3000
- ADMIN_USERNAME=admin
- ADMIN_PASSWORD=changeme
- DB_PATH=/data/db/pages.db
- STORAGE_PATH=/data/storage
volumes: [pages-data:/data]
restart: unless-stopped
volumes:
pages-data:
driver: local
```
## Nginx Reverse Proxy
Example config included in repo (`nginx.conf`). Key locations:
```nginx
upstream pages_mcp {
server 127.0.0.1:38300;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name your.domain.com;
# Strip server CSP, use nginx's
proxy_hide_header Content-Security-Policy;
location / { proxy_pass http://pages_mcp; }
location /api/ { proxy_pass http://pages_mcp; }
location /api/deploy { proxy_pass http://pages_mcp; client_max_body_size 1000m; }
location /mcp { proxy_pass http://pages_mcp; }
location /s/ { proxy_pass http://pages_mcp; }
location /f/ { proxy_pass http://pages_mcp; client_max_body_size 1000m; }
location /health { proxy_pass http://pages_mcp; access_log off; }
}
```
**Notes:**
- `proxy_hide_header Content-Security-Policy` — required for CSP per-page in app layer
- `client_max_body_size 1000m` — required on `/api/deploy` and `/f/` for large uploads
## License
[GPL v3](LICENSE)