fix(qa): resolve remaining v3.0.1 issues — CI, code bugs, docs
CI: Fix black formatting in core/site_registry.py Code: list_projects uses SiteManager, WP Advanced health uses REST API Docs: Fix transport types, endpoint guide, env var reference for all plugins Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
### 1. Create a `.env` file
|
### 1. Create a `.env` file
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Required
|
# Recommended (auto-generates temp key if omitted — check container logs)
|
||||||
MASTER_API_KEY=your-secure-key-here
|
MASTER_API_KEY=your-secure-key-here
|
||||||
|
|
||||||
# Add at least one WordPress site
|
# Add at least one WordPress site
|
||||||
@@ -41,13 +41,14 @@ curl http://localhost:8000/health
|
|||||||
|
|
||||||
### 4. Connect your AI client
|
### 4. Connect your AI client
|
||||||
|
|
||||||
In Claude Desktop's `claude_desktop_config.json`:
|
**Claude Desktop** (`claude_desktop_config.json`):
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"mcphub": {
|
"mcphub": {
|
||||||
"url": "http://localhost:8000/mcp",
|
"type": "streamableHttp",
|
||||||
|
"url": "http://localhost:8000/wordpress/mcp",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Authorization": "Bearer your-secure-key-here"
|
"Authorization": "Bearer your-secure-key-here"
|
||||||
}
|
}
|
||||||
@@ -56,15 +57,45 @@ In Claude Desktop's `claude_desktop_config.json`:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**VS Code** (`.vscode/mcp.json`):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"servers": {
|
||||||
|
"mcphub": {
|
||||||
|
"type": "http",
|
||||||
|
"url": "http://localhost:8000/wordpress/mcp",
|
||||||
|
"headers": {
|
||||||
|
"Authorization": "Bearer your-secure-key-here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Use a plugin-specific endpoint (e.g., `/wordpress/mcp`) instead of `/mcp` to reduce tool count and save tokens. See [Endpoints](#endpoints) below.
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
MCP Hub uses **Bearer token** authentication. Include the `Authorization` header in all requests:
|
MCP Hub uses **Bearer token** authentication:
|
||||||
|
|
||||||
```
|
```
|
||||||
Authorization: Bearer YOUR_MASTER_API_KEY
|
Authorization: Bearer YOUR_API_KEY
|
||||||
```
|
```
|
||||||
|
|
||||||
This applies to both MCP clients and API calls. Query parameter auth is not supported.
|
> `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 |
|
||||||
|
|----------|------:|:-------------:|----------|
|
||||||
|
| `/project/{alias}/mcp` | 22-100 | No (pre-scoped) | Single-site workflow |
|
||||||
|
| `/{plugin}/mcp` | 23-101 | Yes | Multi-site management |
|
||||||
|
| `/mcp` | 596 | Yes | Admin & discovery only |
|
||||||
|
|
||||||
|
Available plugin endpoints: `/wordpress/mcp`, `/woocommerce/mcp`, `/wordpress-advanced/mcp`, `/gitea/mcp`, `/n8n/mcp`, `/supabase/mcp`, `/openpanel/mcp`, `/appwrite/mcp`, `/directus/mcp`, `/system/mcp`
|
||||||
|
|
||||||
## Using Docker Compose
|
## Using Docker Compose
|
||||||
|
|
||||||
|
|||||||
@@ -91,9 +91,7 @@ class ProjectManager:
|
|||||||
if config:
|
if config:
|
||||||
self._create_project_instance(plugin_type, project_id, config)
|
self._create_project_instance(plugin_type, project_id, config)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(
|
self.logger.debug(f"Legacy ProjectManager: skipped {plugin_type}/{project_id}: {e}")
|
||||||
f"Failed to create {plugin_type} project '{project_id}': {e}", exc_info=True
|
|
||||||
)
|
|
||||||
|
|
||||||
def _load_project_config(self, plugin_type: str, project_id: str) -> dict[str, Any] | None:
|
def _load_project_config(self, plugin_type: str, project_id: str) -> dict[str, Any] | None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -98,9 +98,7 @@ class SiteRegistry:
|
|||||||
for alias, full_ids in self.alias_conflicts.items():
|
for alias, full_ids in self.alias_conflicts.items():
|
||||||
winner = self.aliases.get(alias)
|
winner = self.aliases.get(alias)
|
||||||
losers = [fid for fid in full_ids if fid != winner]
|
losers = [fid for fid in full_ids if fid != winner]
|
||||||
self.logger.info(
|
self.logger.info(f" Alias '{alias}': {winner} (winner), {losers} (using full_id)")
|
||||||
f" Alias '{alias}': {winner} (winner), {losers} (using full_id)"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Reserved words that should NOT be interpreted as site IDs
|
# Reserved words that should NOT be interpreted as site IDs
|
||||||
RESERVED_SITE_WORDS = {
|
RESERVED_SITE_WORDS = {
|
||||||
|
|||||||
@@ -173,49 +173,123 @@ RATE_LIMIT_PER_DAY=10000
|
|||||||
|
|
||||||
### WordPress Plugin Requirements
|
### WordPress Plugin Requirements
|
||||||
|
|
||||||
Some WordPress tools require additional plugins on your WordPress site:
|
Some tools require specific WordPress plugins or infrastructure:
|
||||||
|
|
||||||
| MCP Tool Category | WordPress Plugin Required |
|
| Tools | Requirement |
|
||||||
|-------------------|--------------------------|
|
|-------|-------------|
|
||||||
| SEO tools (`get_post_seo`, `update_post_seo`) | **Rank Math** or **Yoast SEO** |
|
| `wordpress_get_post_seo`, `wordpress_update_post_seo`, `wordpress_get_product_seo`, `wordpress_update_product_seo` | **Yoast SEO** or **RankMath** |
|
||||||
| WP-CLI tools (`wp_cache_flush`, `wp_db_export`, etc.) | Docker socket access + `CONTAINER` env var |
|
| `wordpress_wp_cache_*`, `wordpress_wp_db_*`, `wordpress_wp_plugin_*`, `wordpress_wp_theme_*`, `wordpress_wp_core_*`, `wordpress_wp_search_replace_dry_run` (15 tools) | Docker socket + `CONTAINER` env var |
|
||||||
| WooCommerce tools | **WooCommerce** 3.0+ (separate `WOOCOMMERCE_` config) |
|
| `wordpress_advanced_*` database/system tools | Docker socket + `CONTAINER` env var |
|
||||||
|
| `woocommerce_*` | **WooCommerce** plugin (separate `WOOCOMMERCE_` config) |
|
||||||
|
|
||||||
### Docker Socket for WP-CLI Tools
|
### Docker Socket for WP-CLI / WordPress Advanced
|
||||||
|
|
||||||
WP-CLI tools (cache management, database export, plugin updates via CLI) require Docker socket access:
|
WP-CLI and WordPress Advanced system tools require Docker socket access:
|
||||||
|
|
||||||
1. Add the container name to your `.env`:
|
```yaml
|
||||||
```bash
|
services:
|
||||||
WORDPRESS_SITE1_CONTAINER=your-wp-container-name
|
mcphub:
|
||||||
```
|
image: airano/mcphub:latest
|
||||||
|
ports:
|
||||||
2. Mount the Docker socket in `docker-compose.yaml`:
|
- "8000:8000"
|
||||||
```yaml
|
|
||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
```
|
environment:
|
||||||
|
WORDPRESS_SITE1_URL: https://your-site.com
|
||||||
|
WORDPRESS_SITE1_USERNAME: admin
|
||||||
|
WORDPRESS_SITE1_APP_PASSWORD: xxxx xxxx xxxx xxxx
|
||||||
|
WORDPRESS_SITE1_CONTAINER: wordpress-container-name
|
||||||
|
WORDPRESS_SITE1_ALIAS: mysite
|
||||||
|
WORDPRESS_ADVANCED_SITE1_URL: https://your-site.com
|
||||||
|
WORDPRESS_ADVANCED_SITE1_USERNAME: admin
|
||||||
|
WORDPRESS_ADVANCED_SITE1_APP_PASSWORD: xxxx xxxx xxxx xxxx
|
||||||
|
WORDPRESS_ADVANCED_SITE1_CONTAINER: wordpress-container-name
|
||||||
|
WORDPRESS_ADVANCED_SITE1_ALIAS: mysite-admin
|
||||||
|
```
|
||||||
|
|
||||||
Without Docker socket, WP-CLI tools will return a "not available" message but all REST API tools work normally.
|
Without Docker socket:
|
||||||
|
- WP-CLI tools return "not available" errors
|
||||||
|
- WordPress Advanced database/system tools are unavailable
|
||||||
|
- All REST API tools (bulk operations, content management) work normally
|
||||||
|
|
||||||
### Environment Variable Naming Convention
|
### Environment Variable Reference
|
||||||
|
|
||||||
All site configuration follows the pattern: `{PLUGIN_PREFIX}_{SITE_ID}_{CONFIG_KEY}`
|
All site configuration follows the pattern: `{PLUGIN_TYPE}_{SITE_ID}_{CONFIG_KEY}=value`
|
||||||
|
|
||||||
| Plugin | Prefix | Example |
|
|
||||||
|--------|--------|---------|
|
|
||||||
| WordPress | `WORDPRESS_` | `WORDPRESS_SITE1_URL` |
|
|
||||||
| WooCommerce | `WOOCOMMERCE_` | `WOOCOMMERCE_STORE1_URL` |
|
|
||||||
| WordPress Advanced | `WORDPRESS_ADVANCED_` | `WORDPRESS_ADVANCED_SITE1_URL` |
|
|
||||||
| Gitea | `GITEA_` | `GITEA_REPO1_URL` |
|
|
||||||
| n8n | `N8N_` | `N8N_INSTANCE1_URL` |
|
|
||||||
| Supabase | `SUPABASE_` | `SUPABASE_PROJECT1_URL` |
|
|
||||||
| OpenPanel | `OPENPANEL_` | `OPENPANEL_INSTANCE1_URL` |
|
|
||||||
| Appwrite | `APPWRITE_` | `APPWRITE_PROJECT1_URL` |
|
|
||||||
| Directus | `DIRECTUS_` | `DIRECTUS_INSTANCE1_URL` |
|
|
||||||
|
|
||||||
- `SITE_ID` can be any alphanumeric identifier (e.g., `SITE1`, `PROD`, `MYBLOG`)
|
- `SITE_ID` can be any alphanumeric identifier (e.g., `SITE1`, `PROD`, `MYBLOG`)
|
||||||
- Add `_ALIAS` for a friendly name used in tool calls (e.g., `WORDPRESS_SITE1_ALIAS=myblog`)
|
- Multiple sites: change `SITE1` to `SITE2`, `SITE3`, etc.
|
||||||
|
|
||||||
|
#### WordPress
|
||||||
|
```env
|
||||||
|
WORDPRESS_SITE1_URL=https://your-site.com # Required
|
||||||
|
WORDPRESS_SITE1_USERNAME=admin # Required
|
||||||
|
WORDPRESS_SITE1_APP_PASSWORD=xxxx xxxx xxxx xxxx # Required (WordPress Application Password)
|
||||||
|
WORDPRESS_SITE1_ALIAS=my-site # Recommended
|
||||||
|
WORDPRESS_SITE1_CONTAINER=wp-container # Optional (for WP-CLI)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### WooCommerce
|
||||||
|
```env
|
||||||
|
WOOCOMMERCE_SITE1_URL=https://your-site.com # Required
|
||||||
|
WOOCOMMERCE_SITE1_CONSUMER_KEY=ck_xxx # Required
|
||||||
|
WOOCOMMERCE_SITE1_CONSUMER_SECRET=cs_xxx # Required
|
||||||
|
WOOCOMMERCE_SITE1_ALIAS=my-shop # Recommended
|
||||||
|
```
|
||||||
|
|
||||||
|
#### WordPress Advanced
|
||||||
|
```env
|
||||||
|
WORDPRESS_ADVANCED_SITE1_URL=https://your-site.com # Required
|
||||||
|
WORDPRESS_ADVANCED_SITE1_USERNAME=admin # Required
|
||||||
|
WORDPRESS_ADVANCED_SITE1_APP_PASSWORD=xxxx xxxx xxxx xxxx # Required
|
||||||
|
WORDPRESS_ADVANCED_SITE1_CONTAINER=wp-container # Required (for WP-CLI tools)
|
||||||
|
WORDPRESS_ADVANCED_SITE1_ALIAS=my-site-admin # Recommended
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Gitea
|
||||||
|
```env
|
||||||
|
GITEA_SITE1_URL=https://gitea.example.com # Required
|
||||||
|
GITEA_SITE1_TOKEN=your-access-token # Required
|
||||||
|
GITEA_SITE1_ALIAS=my-gitea # Recommended
|
||||||
|
```
|
||||||
|
|
||||||
|
#### n8n
|
||||||
|
```env
|
||||||
|
N8N_SITE1_URL=https://n8n.example.com # Required
|
||||||
|
N8N_SITE1_API_KEY=your-api-key # Required
|
||||||
|
N8N_SITE1_ALIAS=my-n8n # Recommended
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Supabase
|
||||||
|
```env
|
||||||
|
SUPABASE_SITE1_URL=https://your-project.supabase.co # Required
|
||||||
|
SUPABASE_SITE1_API_KEY=your-service-role-key # Required
|
||||||
|
SUPABASE_SITE1_ALIAS=my-supabase # Recommended
|
||||||
|
```
|
||||||
|
|
||||||
|
#### OpenPanel
|
||||||
|
```env
|
||||||
|
OPENPANEL_SITE1_URL=https://openpanel.example.com # Required
|
||||||
|
OPENPANEL_SITE1_CLIENT_ID=your-client-id # Required
|
||||||
|
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
|
### Configuration Tips
|
||||||
|
|
||||||
@@ -286,7 +360,13 @@ docker compose logs -f mcphub
|
|||||||
|
|
||||||
## Connect Your AI Client
|
## Connect Your AI Client
|
||||||
|
|
||||||
MCP Hub uses **SSE (Server-Sent Events)** transport over HTTP. All requests require **Bearer token** authentication via the `Authorization` header. Query parameter auth is not supported.
|
All requests require **Bearer token** authentication via the `Authorization` header:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: Bearer YOUR_API_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
> `X-API-Key` header and query parameter auth are **not** supported.
|
||||||
|
|
||||||
### Claude Desktop
|
### Claude Desktop
|
||||||
|
|
||||||
@@ -295,8 +375,9 @@ Add to `claude_desktop_config.json`:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"mcphub": {
|
"mcphub-wordpress": {
|
||||||
"url": "https://your-server:8000/mcp",
|
"type": "streamableHttp",
|
||||||
|
"url": "http://your-server:8000/wordpress/mcp",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Authorization": "Bearer YOUR_MASTER_API_KEY"
|
"Authorization": "Bearer YOUR_MASTER_API_KEY"
|
||||||
}
|
}
|
||||||
@@ -312,9 +393,9 @@ Add to `.mcp.json` in your project:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"mcphub": {
|
"mcphub-wordpress": {
|
||||||
"type": "sse",
|
"type": "http",
|
||||||
"url": "https://your-server:8000/mcp",
|
"url": "http://your-server:8000/wordpress/mcp",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Authorization": "Bearer YOUR_MASTER_API_KEY"
|
"Authorization": "Bearer YOUR_MASTER_API_KEY"
|
||||||
}
|
}
|
||||||
@@ -327,8 +408,8 @@ Add to `.mcp.json` in your project:
|
|||||||
|
|
||||||
Go to **Settings > MCP Servers > Add Server**:
|
Go to **Settings > MCP Servers > Add Server**:
|
||||||
|
|
||||||
- **Name**: MCP Hub
|
- **Name**: MCP Hub WordPress
|
||||||
- **URL**: `https://your-server:8000/mcp`
|
- **URL**: `http://your-server:8000/wordpress/mcp`
|
||||||
- **Headers**: `Authorization: Bearer YOUR_MASTER_API_KEY`
|
- **Headers**: `Authorization: Bearer YOUR_MASTER_API_KEY`
|
||||||
|
|
||||||
### VS Code + Copilot
|
### VS Code + Copilot
|
||||||
@@ -338,9 +419,9 @@ Add to `.vscode/mcp.json`:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"servers": {
|
"servers": {
|
||||||
"mcphub": {
|
"mcphub-wordpress": {
|
||||||
"type": "sse",
|
"type": "http",
|
||||||
"url": "https://your-server:8000/mcp",
|
"url": "http://your-server:8000/wordpress/mcp",
|
||||||
"headers": {
|
"headers": {
|
||||||
"Authorization": "Bearer YOUR_MASTER_API_KEY"
|
"Authorization": "Bearer YOUR_MASTER_API_KEY"
|
||||||
}
|
}
|
||||||
@@ -357,6 +438,8 @@ MCP Hub supports **Open Dynamic Client Registration** (RFC 7591). ChatGPT can au
|
|||||||
2. In ChatGPT, add MCP server: `https://your-server:8000/mcp`
|
2. In ChatGPT, add MCP server: `https://your-server:8000/mcp`
|
||||||
3. ChatGPT auto-discovers OAuth metadata and registers
|
3. ChatGPT auto-discovers OAuth metadata and registers
|
||||||
|
|
||||||
|
> **Important**: Use `"type": "streamableHttp"` for Claude Desktop and `"type": "http"` for VS Code/Claude Code. Using `"type": "sse"` will cause `400 Bad Request` errors.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Using MCP Tools
|
## Using MCP Tools
|
||||||
@@ -391,31 +474,40 @@ The `site` parameter accepts either a **site_id** (e.g., `site1`) or an **alias*
|
|||||||
|
|
||||||
### Multi-Endpoint Architecture
|
### Multi-Endpoint Architecture
|
||||||
|
|
||||||
Use specific endpoints to limit tool access and save tokens:
|
Use the most specific endpoint for your use case to minimize token usage:
|
||||||
|
|
||||||
```
|
| Endpoint | Use Case | Tools | `site` param? |
|
||||||
/mcp → All 596 tools (Master API Key)
|
|----------|----------|------:|:-------------:|
|
||||||
/system/mcp → System tools only (24 tools)
|
| `/project/{alias}/mcp` | Single-site workflow | 22-100 | No (pre-scoped) |
|
||||||
/wordpress/mcp → WordPress tools (67 tools)
|
| `/{plugin}/mcp` | Multi-site management | 23-101 | Yes |
|
||||||
/woocommerce/mcp → WooCommerce tools (28 tools)
|
| `/system/mcp` | System administration | 24 | N/A |
|
||||||
/gitea/mcp → Gitea tools (56 tools)
|
| `/mcp` | Admin & discovery only | 596 | Yes |
|
||||||
/n8n/mcp → n8n tools (56 tools)
|
|
||||||
/supabase/mcp → Supabase tools (70 tools)
|
|
||||||
/openpanel/mcp → OpenPanel tools (73 tools)
|
|
||||||
/appwrite/mcp → Appwrite tools (100 tools)
|
|
||||||
/directus/mcp → Directus tools (100 tools)
|
|
||||||
/project/{alias}/mcp → Per-project (auto-injects site)
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Recommendation**: Use plugin-specific endpoints (e.g., `/wordpress/mcp`) instead of `/mcp` when possible. This reduces the number of tools your AI client loads, saving context tokens and improving response quality.
|
> **Recommendation**: Always use the most specific endpoint. Using `/mcp` (596 tools) wastes context tokens and degrades AI response quality.
|
||||||
|
|
||||||
|
**Available plugin endpoints:**
|
||||||
|
|
||||||
|
| Endpoint | Plugin | Tools |
|
||||||
|
|----------|--------|------:|
|
||||||
|
| `/wordpress/mcp` | WordPress | 67 |
|
||||||
|
| `/woocommerce/mcp` | WooCommerce | 28 |
|
||||||
|
| `/wordpress-advanced/mcp` | WordPress Advanced | 22 |
|
||||||
|
| `/gitea/mcp` | Gitea | 56 |
|
||||||
|
| `/n8n/mcp` | n8n | 56 |
|
||||||
|
| `/supabase/mcp` | Supabase | 70 |
|
||||||
|
| `/openpanel/mcp` | OpenPanel | 73 |
|
||||||
|
| `/appwrite/mcp` | Appwrite | 100 |
|
||||||
|
| `/directus/mcp` | Directus | 100 |
|
||||||
|
| `/system/mcp` | System Management | 24 |
|
||||||
|
|
||||||
**Plugin endpoint vs Project endpoint:**
|
**Plugin endpoint vs Project endpoint:**
|
||||||
|
|
||||||
| Feature | Plugin endpoint (`/wordpress/mcp`) | Project endpoint (`/project/myblog/mcp`) |
|
| Feature | Plugin (`/wordpress/mcp`) | Project (`/project/{alias}/mcp`) |
|
||||||
|---------|-----------------------------------|----------------------------------------|
|
|---------|:------------------------:|:-------------------------------:|
|
||||||
| Tools loaded | All tools for that plugin type | Same tools, but `site` parameter auto-injected |
|
| `list_sites` tool | Yes | No |
|
||||||
| Site selection | Must pass `site` parameter manually | Site is auto-selected (no `site` param needed) |
|
| `site` parameter needed | Yes | No (pre-scoped) |
|
||||||
| Best for | Managing multiple sites of same type | Dedicated access to a single site |
|
| Tool count | N + 1 (includes `list_sites`) | N |
|
||||||
|
| Multi-site support | Yes | No (single site) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -112,22 +112,24 @@ class WordPressAdvancedPlugin(BasePlugin):
|
|||||||
Dict with health status and WP-CLI availability
|
Dict with health status and WP-CLI availability
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Test WP-CLI access (primary requirement for wordpress_advanced)
|
# Test REST API access (primary health indicator — most tools use REST)
|
||||||
wp_cli_version = await self.system.wp_cli_version()
|
|
||||||
wp_cli_available = bool(wp_cli_version.get("version"))
|
|
||||||
|
|
||||||
# Test REST API access with a public endpoint
|
|
||||||
rest_api_available = False
|
rest_api_available = False
|
||||||
try:
|
try:
|
||||||
# Use a public endpoint that doesn't require authentication
|
|
||||||
site_info = await self.client.get("/")
|
site_info = await self.client.get("/")
|
||||||
rest_api_available = bool(site_info.get("name"))
|
rest_api_available = bool(site_info.get("name"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning(f"REST API check failed (non-critical): {e}")
|
self.logger.warning(f"REST API check failed: {e}")
|
||||||
rest_api_available = False
|
|
||||||
|
# Test WP-CLI access (optional — only needed for database/system tools)
|
||||||
|
wp_cli_available = False
|
||||||
|
try:
|
||||||
|
wp_cli_version = await self.system.wp_cli_version()
|
||||||
|
wp_cli_available = bool(wp_cli_version.get("version"))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"healthy": wp_cli_available or rest_api_available,
|
"healthy": rest_api_available,
|
||||||
"wp_cli_available": wp_cli_available,
|
"wp_cli_available": wp_cli_available,
|
||||||
"rest_api_available": rest_api_available,
|
"rest_api_available": rest_api_available,
|
||||||
"features": {
|
"features": {
|
||||||
|
|||||||
68
server.py
68
server.py
@@ -1180,25 +1180,29 @@ def add_endpoint_middleware(endpoint_mcp, endpoint_name: str = "unknown"):
|
|||||||
|
|
||||||
# Internal implementation functions (not decorated) for system tools
|
# Internal implementation functions (not decorated) for system tools
|
||||||
async def _list_projects_impl() -> str:
|
async def _list_projects_impl() -> str:
|
||||||
"""Internal implementation for listing projects."""
|
"""Internal implementation for listing projects (reads from SiteManager)."""
|
||||||
try:
|
|
||||||
projects = project_manager.list_projects()
|
|
||||||
|
|
||||||
# Enrich with alias and endpoint info from SiteManager
|
|
||||||
all_sites = site_manager.list_all_sites()
|
|
||||||
site_lookup = {s["full_id"]: s for s in all_sites}
|
|
||||||
|
|
||||||
for project in projects:
|
|
||||||
full_id = project.get("id", "")
|
|
||||||
site_info = site_lookup.get(full_id, {})
|
|
||||||
alias = site_info.get("alias")
|
|
||||||
path_suffix = alias if alias and alias != site_info.get("site_id") else full_id
|
|
||||||
project["alias"] = alias
|
|
||||||
project["endpoint"] = f"/project/{path_suffix}/mcp"
|
|
||||||
|
|
||||||
result = {"total": len(projects), "projects": projects}
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
try:
|
||||||
|
all_sites = site_manager.list_all_sites()
|
||||||
|
projects = []
|
||||||
|
|
||||||
|
for site_info in all_sites:
|
||||||
|
alias = site_info.get("alias")
|
||||||
|
site_id = site_info.get("site_id")
|
||||||
|
full_id = site_info.get("full_id", "")
|
||||||
|
path_suffix = alias if alias and alias != site_id else full_id
|
||||||
|
projects.append(
|
||||||
|
{
|
||||||
|
"id": full_id,
|
||||||
|
"type": site_info.get("plugin_type", ""),
|
||||||
|
"project_id": site_id,
|
||||||
|
"alias": alias,
|
||||||
|
"endpoint": f"/project/{path_suffix}/mcp",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
result = {"total": len(projects), "projects": projects}
|
||||||
return json.dumps(result, indent=2)
|
return json.dumps(result, indent=2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error listing projects: {e}", exc_info=True)
|
logger.error(f"Error listing projects: {e}", exc_info=True)
|
||||||
@@ -1269,14 +1273,22 @@ async def get_project_info(project_id: str) -> str:
|
|||||||
Returns:
|
Returns:
|
||||||
JSON string with project information
|
JSON string with project information
|
||||||
"""
|
"""
|
||||||
|
import json
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
# Try legacy ProjectManager first
|
||||||
info = project_manager.get_project_info(project_id)
|
info = project_manager.get_project_info(project_id)
|
||||||
|
|
||||||
|
# Fallback to SiteManager if legacy finds nothing
|
||||||
|
if info is None:
|
||||||
|
for site_data in site_manager.list_all_sites():
|
||||||
|
if site_data["full_id"] == project_id:
|
||||||
|
info = site_data
|
||||||
|
break
|
||||||
|
|
||||||
if info is None:
|
if info is None:
|
||||||
return f"Project '{project_id}' not found. Use list_projects to see available projects."
|
return f"Project '{project_id}' not found. Use list_projects to see available projects."
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
return json.dumps(info, indent=2)
|
return json.dumps(info, indent=2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting project info: {e}", exc_info=True)
|
logger.error(f"Error getting project info: {e}", exc_info=True)
|
||||||
@@ -3111,7 +3123,6 @@ async def health_check(request: Request) -> JSONResponse:
|
|||||||
{
|
{
|
||||||
"status": "healthy",
|
"status": "healthy",
|
||||||
"uptime": uptime_seconds,
|
"uptime": uptime_seconds,
|
||||||
"projects": len(project_manager.projects),
|
|
||||||
"sites": site_manager.get_count(),
|
"sites": site_manager.get_count(),
|
||||||
"tools": _total_tool_count, # Total tools (plugin + system)
|
"tools": _total_tool_count, # Total tools (plugin + system)
|
||||||
"timestamp": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
|
"timestamp": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
|
||||||
@@ -3480,12 +3491,17 @@ Use get_endpoints() to see all available MCP endpoints."""
|
|||||||
@system_mcp.tool()
|
@system_mcp.tool()
|
||||||
async def get_project_info(project_id: str) -> str:
|
async def get_project_info(project_id: str) -> str:
|
||||||
"""Get detailed information about a specific project."""
|
"""Get detailed information about a specific project."""
|
||||||
|
import json
|
||||||
|
|
||||||
try:
|
try:
|
||||||
info = project_manager.get_project_info(project_id)
|
info = project_manager.get_project_info(project_id)
|
||||||
|
if info is None:
|
||||||
|
for site_data in site_manager.list_all_sites():
|
||||||
|
if site_data["full_id"] == project_id:
|
||||||
|
info = site_data
|
||||||
|
break
|
||||||
if info is None:
|
if info is None:
|
||||||
return f"Project '{project_id}' not found. Use list_projects to see available projects."
|
return f"Project '{project_id}' not found. Use list_projects to see available projects."
|
||||||
import json
|
|
||||||
|
|
||||||
return json.dumps(info, indent=2)
|
return json.dumps(info, indent=2)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting project info: {e}", exc_info=True)
|
logger.error(f"Error getting project info: {e}", exc_info=True)
|
||||||
@@ -4507,10 +4523,10 @@ def main():
|
|||||||
logger.info(f"Host: {args.host}")
|
logger.info(f"Host: {args.host}")
|
||||||
logger.info(f"Port: {args.port}")
|
logger.info(f"Port: {args.port}")
|
||||||
|
|
||||||
# Check if any projects were discovered
|
# Check if any sites were discovered (SiteManager is the primary discovery system)
|
||||||
if len(project_manager.projects) == 0:
|
if site_manager.get_count() == 0:
|
||||||
logger.warning("No projects discovered! Check your environment variables.")
|
logger.warning("No sites discovered! Check your environment variables.")
|
||||||
logger.warning("Expected format: {PLUGIN_TYPE}_{PROJECT_ID}_{CONFIG_KEY}=value")
|
logger.warning("Expected format: {PLUGIN_TYPE}_{SITE_ID}_{CONFIG_KEY}=value")
|
||||||
logger.warning("Example: WORDPRESS_SITE1_URL=https://example.com")
|
logger.warning("Example: WORDPRESS_SITE1_URL=https://example.com")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user