diff --git a/DOCKER_README.md b/DOCKER_README.md index 0d851dd..34cdbc5 100644 --- a/DOCKER_README.md +++ b/DOCKER_README.md @@ -2,7 +2,7 @@ **The AI-native management hub for WordPress, WooCommerce, and self-hosted services.** -633 tools across 10 plugins (incl. Coolify). Connect your sites, stores, repos, and databases — manage them all through Claude, ChatGPT, Cursor, or any MCP client. +8 plugins, hundreds of tools. Connect your sites, stores, repos, and databases — manage them all through Claude, ChatGPT, Cursor, or any MCP client. > **Don't want to self-host?** Try the hosted instance at **[mcp.palebluedot.live](https://mcp.palebluedot.live)** — log in with GitHub or Google, add your sites, and connect your AI client in minutes. @@ -37,6 +37,8 @@ curl http://localhost:8000/health # http://localhost:8000/dashboard ``` +Log in with your `MASTER_API_KEY`, then add sites via **My Sites → Add Service**. + ### 4. Connect your AI client **Claude Desktop** (`claude_desktop_config.json`): @@ -55,7 +57,7 @@ curl http://localhost:8000/health } ``` -**VS Code** (`.vscode/mcp.json`): +**VS Code / Claude Code** (`.vscode/mcp.json` or `.mcp.json`): ```json { @@ -71,7 +73,7 @@ curl http://localhost:8000/health } ``` -> Use a plugin-specific endpoint (e.g., `/wordpress/mcp`) instead of `/mcp` to reduce tool count and save tokens. See [Endpoints](#endpoints) below. +> Use a plugin-specific endpoint (e.g., `/wordpress/mcp`) instead of `/mcp` to keep the tool list focused and save tokens. See [Endpoints](#endpoints) below. ## Authentication @@ -81,20 +83,18 @@ MCP Hub uses **Bearer token** authentication: Authorization: Bearer YOUR_API_KEY ``` -> `X-API-Key` header and query parameter auth are **not** supported. - ## Endpoints Use the most specific endpoint for your use case: -| Endpoint | Tools | `site` param? | Best for | -|----------|------:|:-------------:|----------| -| `/u/{user_id}/{alias}/mcp` | 22-100 | No (pre-scoped) | Hosted/OAuth users | -| `/project/{alias}/mcp` | 22-100 | No (pre-scoped) | Single-site workflow | -| `/{plugin}/mcp` | 23-101 | Yes | Multi-site management | -| `/mcp` | 633 | Yes | Admin & discovery only | +| Endpoint | Use case | +|----------|----------| +| `/u/{user_id}/{alias}/mcp` | Hosted/OAuth users — single pre-scoped service | +| `/project/{alias}/mcp` | Single-site workflow (recommended) | +| `/{plugin}/mcp` | Multi-site management for one service type | +| `/mcp` | Admin & discovery — all enabled tools | -Available plugin endpoints: `/wordpress/mcp`, `/woocommerce/mcp`, `/wordpress-specialist/mcp`, `/gitea/mcp`, `/n8n/mcp`, `/supabase/mcp`, `/openpanel/mcp`, `/appwrite/mcp`, `/directus/mcp`, `/system/mcp` +Available plugin endpoints: `/wordpress/mcp`, `/woocommerce/mcp`, `/wordpress-specialist/mcp`, `/gitea/mcp`, `/n8n/mcp`, `/supabase/mcp`, `/openpanel/mcp`, `/coolify/mcp`, `/system/mcp` ## Using Docker Compose @@ -118,61 +118,71 @@ volumes: mcphub-logs: ``` -> **WP-CLI tools** (cache flush, database export, plugin updates via CLI) require Docker socket access. -> Add `WORDPRESS_SITE1_CONTAINER=your-wp-container-name` to your `.env` and uncomment the Docker socket volume above. - ```bash docker compose up -d ``` +> **WP-CLI tools** (cache flush, database export, plugin management via CLI) require Docker socket access. Uncomment the socket volume and set the `container` field when adding the WordPress site in the dashboard. + ## After Starting | URL | Description | |-----|-------------| | `http://localhost:8000/health` | Health check & status | -| `http://localhost:8000/dashboard` | Web dashboard (manage sites, API keys, OAuth clients, health monitoring). Login via MASTER_API_KEY or GitHub/Google OAuth | -| `http://localhost:8000/mcp` | MCP endpoint (connect AI clients here) | +| `http://localhost:8000/dashboard` | Web dashboard — manage sites, API keys, OAuth clients, health | +| `http://localhost:8000/mcp` | MCP endpoint (connect your AI client here) | ## Environment Variables -| Variable | Required | Description | -|----------|----------|-------------| -| `MASTER_API_KEY` | Recommended | API key for authentication. If omitted, a temporary key is auto-generated and printed to logs | -| `WORDPRESS_SITE1_URL` | For WP | WordPress site URL | -| `WORDPRESS_SITE1_USERNAME` | For WP | WordPress admin username | -| `WORDPRESS_SITE1_APP_PASSWORD` | For WP | WordPress Application Password | -| `WORDPRESS_SITE1_ALIAS` | Recommended | Friendly name (e.g., `myblog`) | -| `WORDPRESS_SITE1_CONTAINER` | For WP-CLI | Docker container name of your WordPress site (enables cache/db/system tools) | -| `OAUTH_JWT_SECRET_KEY` | For OAuth | JWT secret for ChatGPT auto-registration (not needed for Claude/Cursor) | -| `OAUTH_BASE_URL` | For OAuth | Public URL of your server (not needed for Claude/Cursor) | -| `GITHUB_CLIENT_ID` | For Social Login | GitHub OAuth App client ID | -| `GITHUB_CLIENT_SECRET` | For Social Login | GitHub OAuth App client secret | -| `GOOGLE_CLIENT_ID` | For Social Login | Google OAuth client ID | -| `GOOGLE_CLIENT_SECRET` | For Social Login | Google OAuth client secret | -| `PUBLIC_URL` | For Social Login | Public URL for OAuth callbacks (e.g., `https://mcp.example.com`) | -| `ENCRYPTION_KEY` | For Multi-user | AES-256 key for encrypting user site credentials. Generate: `python -c "import os, base64; print(base64.b64encode(os.urandom(32)).decode())"` | +### Required -> **CONTAINER**: Required for WP-CLI tools (cache flush, database export, system info) and **all** WordPress Advanced tools. Find your container name: `docker ps --filter name=wordpress`. Also requires Docker socket mount. +| Variable | Description | +|----------|-------------| +| `MASTER_API_KEY` | API key for admin access. If omitted, a temporary key is auto-generated and printed to logs. | -> **OAuth**: Only needed for ChatGPT Remote MCP auto-registration. For Claude Desktop, Claude Code, Cursor, and VS Code — just use `MASTER_API_KEY` with Bearer token auth. +### Optional — OAuth & Social Login -> **Social Login**: Enable GitHub/Google login for multi-user mode. Users can add sites via the dashboard and get personal MCP endpoints (`/u/{user_id}/{alias}/mcp`). +| Variable | Description | +|----------|-------------| +| `OAUTH_JWT_SECRET_KEY` | JWT secret for ChatGPT Remote MCP auto-registration. Not needed for Claude/Cursor/VS Code. | +| `OAUTH_BASE_URL` | Public URL of your server. Required for OAuth flows. | +| `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` | Enable GitHub social login for multi-user mode. | +| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | Enable Google social login for multi-user mode. | +| `PUBLIC_URL` | Public URL for OAuth callbacks (e.g., `https://mcp.example.com`). | -Add more sites with `SITE2`, `SITE3`, etc. See [full configuration guide](https://github.com/airano-ir/mcphub/blob/main/docs/getting-started.md). +### Optional — Multi-user & Encryption -## Supported Plugins +| Variable | Default | Description | +|----------|---------|-------------| +| `ENCRYPTION_KEY` | — | AES-256-GCM key for encrypting user site credentials. Generate: `python -c "import os, base64; print(base64.b64encode(os.urandom(32)).decode())"` | +| `MAX_SITES_PER_USER` | `10` | Maximum sites per user account (also configurable via the dashboard Settings page). | +| `USER_RATE_LIMIT_PER_MIN` | `30` | Per-user rate limit (requests/minute). Configurable via dashboard. | +| `USER_RATE_LIMIT_PER_HR` | `500` | Per-user rate limit (requests/hour). Configurable via dashboard. | +| `ENABLED_PLUGINS` | `wordpress,woocommerce,supabase,openpanel,gitea` | Comma-separated list of plugins visible to public/OAuth users. Admin always sees all plugins. | -| Plugin | Tools | Env Prefix | -|--------|-------|------------| -| WordPress | 67 | `WORDPRESS_` | -| WooCommerce | 28 | `WOOCOMMERCE_` | -| WordPress Specialist | 51 | (DB-managed via dashboard) | -| Gitea | 56 | `GITEA_` | -| n8n | 56 | `N8N_` | -| Supabase | 70 | `SUPABASE_` | -| OpenPanel | 73 | `OPENPANEL_` | -| Appwrite | 100 | `APPWRITE_` | -| Directus | 100 | `DIRECTUS_` | +### Optional — Logging + +| Variable | Default | Description | +|----------|---------|-------------| +| `LOG_LEVEL` | `INFO` | Logging verbosity: `DEBUG`, `INFO`, `WARNING`, `ERROR` | + +> **Site configuration** (URLs, credentials) is managed via the web dashboard — not environment variables. Log in and go to **My Sites → Add Service**. + +## 8 Plugins, Hundreds of Tools + +| Plugin | Approx. Tools | What you can do | +|--------|------:|-----------------| +| WordPress | ~70 | Posts, pages, media (incl. AI image gen), users, menus, taxonomies, SEO | +| WooCommerce | ~30 | Products, orders, customers, coupons, reports, shipping | +| WordPress Specialist | ~50 | Plugins, themes, options, cron, page editing, DB inspection, bulk fan-out | +| Gitea | ~65 | Repos, issues, PRs, releases, webhooks, orgs, batch file ops | +| n8n | ~55 | Workflows, executions, credentials, variables, audit | +| Supabase | ~70 | Database, auth, storage, edge functions, realtime | +| OpenPanel | ~40 | Events, export, insights, profiles, projects | +| Coolify | ~65 | Apps, deployments, servers, projects, databases, services | +| System | ~25 | Health monitoring, API keys, OAuth management, audit logs | + +> **WordPress Specialist** requires [Airano MCP Bridge](https://wordpress.org/plugins/airano-mcp-bridge/) (v2.18.0+) installed on your WordPress site. ## Links diff --git a/core/dashboard/routes.py b/core/dashboard/routes.py index a201ebd..27a6322 100644 --- a/core/dashboard/routes.py +++ b/core/dashboard/routes.py @@ -775,6 +775,152 @@ DASHBOARD_TRANSLATIONS = { "uploads to /wp/v2/media; Consumer Key/Secret do not work for that. Optional." ), "api_keys.user_intro": "Use these to authenticate MCP clients to your hub.", + "api_keys.admin_intro": "Personal and machine keys for MCP clients. Each key has separate access and an independent log.", + "api_keys.admin_empty_cta": "Create one to authenticate MCP clients.", + "api_keys.admin_warning": ( + "Admin access grants full system control including destructive operations " + "(delete, write env, system tools). Anyone with this key can act on all your " + "sites. Unless the client truly needs it, choose a narrower scope." + ), + "api_keys.user_empty_cta": "Create one to connect a client.", + "api_keys.description": "Description", + "api_keys.description_placeholder": "What is this key for?", + "api_keys.expiry_label": "Expiry (days, optional)", + "api_keys.expiry_placeholder": "Leave blank for no expiry", + "api_keys.sensitive_warning": ( + "Reads backup files and environment variables that often contain sensitive data. " + "Treat this key like a credential and do not share it over unencrypted channels." + ), + "api_keys.confirm_delete": 'Permanently delete "{name}"?\nThis cannot be undone.', + "api_keys.confirm_delete_user": 'Delete "{name}"?\nThis key will stop working immediately.', + "api_keys.confirm_revoke": 'Revoke "{name}"?\nThis key will stop working immediately.', + "api_keys.toast_deleted": "Key deleted", + "api_keys.toast_revoked": "Key revoked", + # Audit log + "audit.intro": ( + "Every authentication, tool call, and settings change. GDPR-compliant. " + "Filters are applied server-side." + ), + "audit.col.time": "Time", + "audit.col.actor": "User", + "audit.col.event": "Event", + "audit.col.level": "Level", + "audit.col.message": "Message", + "audit.col.result": "Result", + "audit.col.target": "Target", + "audit.col.duration": "Duration", + "audit.search_placeholder": "Search user / event / target / message…", + "audit.event_type_placeholder": "Event type (e.g. tool_call)", + "audit.date_filter_title": "Filter by a day (YYYY-MM-DD)", + "audit.level.info": "Info", + "audit.level.warn": "Warning", + "audit.no_entries": "No entries found.", + "audit.zero_entries": "No entries", + "audit.clear_filters": "Clear filters", + "audit.page_label": "Page", + "audit.page_size": "Page size", + "audit.per_page": "{n} per page", + "audit.range_of": "{from}–{to} of {total}", + # Badges + "badge.admin": "Admin", + "badge.elevated": "Elevated", + "badge.sensitive": "Sensitive", + # Connect page + "connect.connect_x": "Connect {name}", + "connect.client.claude-code.desc": "CLI · Developer", + "connect.client.vscode.desc": "Extension · Preview", + "connect.client.cursor.desc": "JSON config", + "connect.client.chatgpt.desc": "OAuth · Apps SDK", + "connect.client.gemini.desc": "CLI · Token", + "connect.client.custom.name": "Custom client", + "connect.client.custom.desc": "Any MCP client", + "connect.json.paste_into": "Paste this into your {name} MCP config", + "connect.json.location_hint": "Settings → MCP Servers · File path differs by client", + "connect.json.compatible": "Compatible:", + "connect.json.custom_mcp": "Custom MCP", + "connect.json.token_once": "Token shown only once", + "connect.json.token_once_body": "Save it somewhere safe. You can rotate it anytime from API Keys.", + "connect.claude.step1": "Use this connector URL", + "connect.claude.step1_body": "Enter this URL in Claude.ai Connectors in your browser when it asks for the MCP endpoint.", + "connect.claude.step2": "Confirm in Claude.ai", + "connect.claude.step2_body_prefix": "Claude will show:", + "connect.claude.step2_body_suffix": "Confirm to continue.", + "connect.claude.step3": "You're connected", + "connect.claude.step3_body": "You'll see the new client in your overview. Ask Claude to list your sites.", + "connect.claude.prompt_text": "MCP Hub wants access to N tools", + "connect.claude.open_desktop": "Open Claude Desktop", + "connect.claude.link_lifetime": "Link valid for 10 minutes · One-time use", + "connect.cli.step1": "Run this in your terminal", + "connect.cli.step2": "Confirm", + "connect.oauth.register_btn": "Register OAuth client", + "connect.oauth.step1": "Register the OAuth app (once)", + "connect.oauth.step1_body": "The hub creates an OAuth client and gives you a Redirect URL to paste into your AI tool manifest.", + "connect.oauth.step2": "Sign in via the AI client", + "connect.oauth.step2_body": 'Users see a "Sign in with MCP Hub" button. You confirm access once; the token refreshes automatically.', + "connect.tier.admin_warning": ( + "Admin exposes destructive operations (delete posts, set options, install plugins, " + "write env) on this site. Anyone with this site's token can act with full access. " + "Unless the agent needs everything, choose a narrower scope." + ), + "connect.tier.install_warning": ( + "Installer is an elevated scope — installs run code from the WordPress/theme " + "repository on your site. Only use when the client needs this access." + ), + "connect.tier.sensitive_warning": ( + "Sensitive reads is an elevated scope — includes backups and environment variables. " + "Only use when the client needs this access." + ), + "connect.toast.scope_updated": "Tool access updated to {scope}", + "connect.toast.scope_failed": "Update failed: {error}", + # OAuth clients + "oauth.empty": "You have no OAuth clients yet.", + "oauth.register_first": "Register your first client", + "oauth.none": "— None —", + "oauth.allowed_scope": "Allowed scope", + "oauth.confirm_delete": 'Delete OAuth client "{name}"?\nUsers signed in via this client will be disconnected.', + "oauth.admin_warning": ( + "Admin scope on a third-party OAuth client allows that app to act on all your " + "sites on behalf of users. Only use for trusted applications you control." + ), + "oauth.sensitive_warning": "Sensitive reads exposes backups and environment variables to the OAuth client.", + "oauth.invalid_uris": "{n} URI is not a valid http(s) address:", + "oauth.valid_uris": "{n} valid URI.", + "oauth.toast_created": "Client created", + "oauth.toast_deleted": "Client deleted", + "oauth.toast_delete_failed": "Delete failed: {error}", + "oauth.toast_create_failed": "Create failed: {error}", + # Sites + "sites.dialog_add_title": "Add site", + "sites.dialog_add_submit": "Add site", + "sites.dialog_edit_title": "Edit site", + "sites.dialog_edit_submit": "Save changes", + "sites.field_plugin_type": "Plugin", + "sites.field_url": "URL", + "sites.field_alias": "Alias", + "sites.alias_placeholder": "short-site-id", + "sites.alias_hint": "Short ID the AI sees as `site=…`. Lowercase letters, digits and hyphens only.", + "sites.credentials": "Credentials", + "sites.cred_unchanged": "Leave blank to keep the current value", + "sites.show_advanced": "Show advanced fields", + "sites.hide_advanced": "Hide advanced fields", + "sites.manage_tools": "Manage tools", + "sites.toast_created": "Site created", + "sites.toast_updated": "Site updated", + # Tier hints (used on the Connect page scope selector) + "tier.read.hint": "List and inspect resources — read-only.", + "tier.read_sensitive.hint": "Includes backups, environment variables, and other sensitive reads.", + "tier.write.hint": "Create / update / delete resources and configuration.", + "tier.editor.hint": "Pages, posts, content editing (wordpress_specialist F.19.5).", + "tier.settings.hint": "Options, databases, identity, cron (wordpress_specialist F.19.6).", + "tier.install.hint": "Install plugins/themes from the repository. Treat as elevated access.", + "tier.deploy.hint": "Run deployments and lifecycle, without editing.", + "tier.admin.hint": "Full system control including destructive operations.", + "tier.custom.hint": "After selecting this, enable/disable tools manually.", + # Pagination + "previous": "Previous", + "next": "Next", + # Status labels + "status.revoked": "Revoked", # Audit / generic "event_type": "Event type", "level": "Level", diff --git a/docs/getting-started.md b/docs/getting-started.md index 8a5f57c..b1351d0 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -310,21 +310,6 @@ OPENPANEL_SITE1_CLIENT_SECRET=your-client-secret # Required OPENPANEL_SITE1_ALIAS=my-analytics # Recommended ``` -#### Appwrite -```env -APPWRITE_SITE1_URL=https://appwrite.example.com # Required -APPWRITE_SITE1_API_KEY=your-api-key # Required -APPWRITE_SITE1_PROJECT_ID=your-project-id # Required -APPWRITE_SITE1_ALIAS=my-appwrite # Recommended -``` - -#### Directus -```env -DIRECTUS_SITE1_URL=https://directus.example.com # Required -DIRECTUS_SITE1_TOKEN=your-admin-token # Required -DIRECTUS_SITE1_ALIAS=my-directus # Recommended -``` - > **Note:** Use `APP_PASSWORD` (WordPress Application Password), not `PASSWORD`. ### Configuration Tips @@ -534,8 +519,7 @@ Use the most specific endpoint for your use case to minimize token usage: | `/n8n/mcp` | n8n | 56 | | `/supabase/mcp` | Supabase | 70 | | `/openpanel/mcp` | OpenPanel | 42 | -| `/appwrite/mcp` | Appwrite | 100 | -| `/directus/mcp` | Directus | 100 | +| `/coolify/mcp` | Coolify | ~65 | | `/system/mcp` | System Management | 24 | **Plugin endpoint vs Project endpoint:** diff --git a/tests/test_csrf_interceptor.py b/tests/test_csrf_interceptor.py index 7922d72..d8adeed 100644 --- a/tests/test_csrf_interceptor.py +++ b/tests/test_csrf_interceptor.py @@ -135,7 +135,7 @@ def user_client(monkeypatch, user_row, patched_db): class TestRenderedCsrfMeta: def test_keys_page_has_non_empty_csrf_meta(self, user_client): - r = user_client.get("/dashboard/keys") + r = user_client.get("/dashboard-legacy/keys") assert r.status_code == 200 # The meta tag must be present *and* populated with a real token. assert ' string; }) { if (!stats) return null;