Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf893a743a | |||
| 2c9d4ed3d2 | |||
| 4f0c2f9bb0 | |||
| d79c2fd94a |
18
CHANGELOG.md
18
CHANGELOG.md
@@ -5,6 +5,24 @@ All notable changes to MCP Hub will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [3.6.0] — 2026-04-02
|
||||||
|
|
||||||
|
### Gitea Plugin — Public Release (Track F.16)
|
||||||
|
|
||||||
|
Gitea plugin fully reviewed, tested, and published for public use. Two missing tools added, comprehensive test suite created.
|
||||||
|
|
||||||
|
#### Added
|
||||||
|
- **`update_webhook` tool**: Update existing webhook configuration, events, and active status (admin scope)
|
||||||
|
- **`delete_label` tool**: Delete labels from repositories (write scope)
|
||||||
|
- **`tests/test_gitea_plugin.py`**: Comprehensive test suite — 85+ tests covering client init, headers, tool specs, all 5 handler groups, health check, and plugin delegation
|
||||||
|
|
||||||
|
#### Changed
|
||||||
|
- **Gitea plugin now public**: Added `gitea` to `DEFAULT_PUBLIC_PLUGINS` — available to all OAuth users
|
||||||
|
- **Tool count**: 565 → 567 (added update_webhook + delete_label)
|
||||||
|
- **env.example**: Updated default `ENABLED_PLUGINS` to include `gitea`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## [3.5.0] — 2026-04-02
|
## [3.5.0] — 2026-04-02
|
||||||
|
|
||||||
### FastMCP Upgrade & Legacy Cleanup (Track F.15)
|
### FastMCP Upgrade & Legacy Cleanup (Track F.15)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
**MCP Hub** — a Python MCP (Model Context Protocol) server that manages multiple self-hosted services through a unified plugin architecture. Supports 9 plugin types (WordPress, WooCommerce, WordPress Advanced, Gitea, n8n, Supabase, OpenPanel, Appwrite, Directus) with 565 tools total. The tool count stays constant regardless of how many sites are configured.
|
**MCP Hub** — a Python MCP (Model Context Protocol) server that manages multiple self-hosted services through a unified plugin architecture. Supports 9 plugin types (WordPress, WooCommerce, WordPress Advanced, Gitea, n8n, Supabase, OpenPanel, Appwrite, Directus) with 567 tools total. The tool count stays constant regardless of how many sites are configured.
|
||||||
|
|
||||||
## Quick Setup
|
## Quick Setup
|
||||||
|
|
||||||
@@ -185,14 +185,14 @@ Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
|
|||||||
|
|
||||||
v4 development cycle with 15 phases. See `docs/ROADMAP.md` (Track F) and `.claude/plans/structured-popping-adleman.md` for full plan.
|
v4 development cycle with 15 phases. See `docs/ROADMAP.md` (Track F) and `.claude/plans/structured-popping-adleman.md` for full plan.
|
||||||
|
|
||||||
**Active plugins for public users**: WordPress, WooCommerce, Supabase, OpenPanel (configurable via `ENABLED_PLUGINS` env var)
|
**Active plugins for public users**: WordPress, WooCommerce, Supabase, OpenPanel, Gitea (configurable via `ENABLED_PLUGINS` env var)
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
|
|
||||||
- Test files exist in both `tests/` (proper) and root directory (legacy `test_*.py`). Run `pytest tests/` for organized tests only.
|
- Test files exist in both `tests/` (proper) and root directory (legacy `test_*.py`). Run `pytest tests/` for organized tests only.
|
||||||
- `wordpress-plugin/` contains companion WP plugins (openpanel, airano-mcp-seo-bridge) — these are PHP, not Python
|
- `wordpress-plugin/` contains companion WP plugins (openpanel, airano-mcp-seo-bridge) — these are PHP, not Python
|
||||||
- `env.example` has "FUTURE" labels for Supabase/Gitea but both are fully implemented
|
- `env.example` has "FUTURE" labels for Supabase/Gitea but both are fully implemented
|
||||||
- 4 plugins are tested for public use: WordPress, WooCommerce, Supabase, OpenPanel. Others are admin-only or disabled.
|
- 5 plugins are tested for public use: WordPress, WooCommerce, Supabase, OpenPanel, Gitea. Others are admin-only or disabled.
|
||||||
- OAuth Clients (Client ID/Secret) are for MCP endpoint auth, NOT the same as GitHub/Google dashboard login
|
- OAuth Clients (Client ID/Secret) are for MCP endpoint auth, NOT the same as GitHub/Google dashboard login
|
||||||
- All sites stored in SQLite (`core/database.py`), managed via web dashboard
|
- All sites stored in SQLite (`core/database.py`), managed via web dashboard
|
||||||
- Dashboard templates live in `core/templates/` (included in pip package as `package_data`)
|
- Dashboard templates live in `core/templates/` (included in pip package as `package_data`)
|
||||||
@@ -208,4 +208,4 @@ v4 development cycle with 15 phases. See `docs/ROADMAP.md` (Track F) and `.claud
|
|||||||
- Required env vars: `MASTER_API_KEY`, `OAUTH_JWT_SECRET_KEY`, `OAUTH_BASE_URL`
|
- Required env vars: `MASTER_API_KEY`, `OAUTH_JWT_SECRET_KEY`, `OAUTH_BASE_URL`
|
||||||
- OAuth env vars: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `PUBLIC_URL`
|
- OAuth env vars: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `PUBLIC_URL`
|
||||||
- User limits: `MAX_SITES_PER_USER=10`, `USER_RATE_LIMIT_PER_MIN=30`, `USER_RATE_LIMIT_PER_HR=500`
|
- User limits: `MAX_SITES_PER_USER=10`, `USER_RATE_LIMIT_PER_MIN=30`, `USER_RATE_LIMIT_PER_HR=500`
|
||||||
- Plugin visibility: `ENABLED_PLUGINS=wordpress,woocommerce,supabase,openpanel` (default)
|
- Plugin visibility: `ENABLED_PLUGINS=wordpress,woocommerce,supabase,openpanel,gitea` (default)
|
||||||
|
|||||||
@@ -850,7 +850,12 @@ async def get_all_projects(
|
|||||||
is_admin = False
|
is_admin = False
|
||||||
current_user_id = None
|
current_user_id = None
|
||||||
if user_session:
|
if user_session:
|
||||||
if hasattr(user_session, "user_type") and user_session.user_type == "master" or isinstance(user_session, dict) and user_session.get("role") == "admin":
|
if (
|
||||||
|
hasattr(user_session, "user_type")
|
||||||
|
and user_session.user_type == "master"
|
||||||
|
or isinstance(user_session, dict)
|
||||||
|
and user_session.get("role") == "admin"
|
||||||
|
):
|
||||||
is_admin = True
|
is_admin = True
|
||||||
elif isinstance(user_session, dict) and "user_id" in user_session:
|
elif isinstance(user_session, dict) and "user_id" in user_session:
|
||||||
current_user_id = user_session["user_id"]
|
current_user_id = user_session["user_id"]
|
||||||
|
|||||||
@@ -8,14 +8,14 @@ Usage:
|
|||||||
from core.plugin_visibility import get_public_plugin_types, is_plugin_public
|
from core.plugin_visibility import get_public_plugin_types, is_plugin_public
|
||||||
|
|
||||||
public_types = get_public_plugin_types() # {"wordpress", "woocommerce", "supabase"}
|
public_types = get_public_plugin_types() # {"wordpress", "woocommerce", "supabase"}
|
||||||
if is_plugin_public("gitea"): # False
|
if is_plugin_public("gitea"): # True
|
||||||
...
|
...
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Default plugins available to public (OAuth) users
|
# Default plugins available to public (OAuth) users
|
||||||
DEFAULT_PUBLIC_PLUGINS = {"wordpress", "woocommerce", "supabase", "openpanel"}
|
DEFAULT_PUBLIC_PLUGINS = {"wordpress", "woocommerce", "supabase", "openpanel", "gitea"}
|
||||||
|
|
||||||
|
|
||||||
def _parse_plugins(val: str) -> set[str]:
|
def _parse_plugins(val: str) -> set[str]:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
Usage:
|
Usage:
|
||||||
from core.settings import get_setting
|
from core.settings import get_setting
|
||||||
|
|
||||||
enabled = await get_setting("ENABLED_PLUGINS", "wordpress,woocommerce,supabase,openpanel")
|
enabled = await get_setting("ENABLED_PLUGINS", "wordpress,woocommerce,supabase,openpanel,gitea")
|
||||||
max_sites = int(await get_setting("MAX_SITES_PER_USER", "10"))
|
max_sites = int(await get_setting("MAX_SITES_PER_USER", "10"))
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ _cached_plugins: set[str] | None = None
|
|||||||
|
|
||||||
# Default values for all managed settings
|
# Default values for all managed settings
|
||||||
SETTING_DEFAULTS: dict[str, str] = {
|
SETTING_DEFAULTS: dict[str, str] = {
|
||||||
"ENABLED_PLUGINS": "wordpress,woocommerce,supabase,openpanel",
|
"ENABLED_PLUGINS": "wordpress,woocommerce,supabase,openpanel,gitea",
|
||||||
"MAX_SITES_PER_USER": "10",
|
"MAX_SITES_PER_USER": "10",
|
||||||
"USER_RATE_LIMIT_PER_MIN": "30",
|
"USER_RATE_LIMIT_PER_MIN": "30",
|
||||||
"USER_RATE_LIMIT_PER_HR": "500",
|
"USER_RATE_LIMIT_PER_HR": "500",
|
||||||
|
|||||||
90
docs/prompts/F16-gitea-public-release.md
Normal file
90
docs/prompts/F16-gitea-public-release.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# F.16 — Gitea Plugin Review, Test & Public Release
|
||||||
|
|
||||||
|
> Session prompt. Copy and paste into a new Claude Code conversation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prompt:
|
||||||
|
|
||||||
|
```
|
||||||
|
از مهارت project-ops برای آشنایی با محیط استفاده کن. پروژه mcphub-internal روی branch Phase-1 است.
|
||||||
|
فایلهای مرجع:
|
||||||
|
- docs/plans/2026-03-25-v4-development-cycle.md
|
||||||
|
- CLAUDE.md
|
||||||
|
- CHANGELOG.md
|
||||||
|
|
||||||
|
## وضعیت فعلی
|
||||||
|
- نسخه: v3.5.0
|
||||||
|
- 565 ابزار در 9 پلاگین (4 پلاگین عمومی: WordPress, WooCommerce, Supabase, OpenPanel)
|
||||||
|
- FastMCP: `>=3.0.0,<4.0.0`
|
||||||
|
- CI سبز
|
||||||
|
- F.15 تکمیل شده — FastMCP 3.x upgrade + legacy cleanup
|
||||||
|
|
||||||
|
## هدف: فاز F.16 — Gitea Plugin Review, Test & Public Enablement
|
||||||
|
|
||||||
|
مشابه F.10 (OpenPanel) — بررسی، تست و فعالسازی عمومی پلاگین Gitea.
|
||||||
|
|
||||||
|
### بخش 1: بررسی و تحلیل
|
||||||
|
1. **ساختار پلاگین**: `plugins/gitea/` شامل 56 ابزار در 5 هندلر:
|
||||||
|
- `handlers/repositories.py` — 16 tools (CRUD repos, branches, tags, files)
|
||||||
|
- `handlers/issues.py` — 12 tools (issues, comments, labels, milestones)
|
||||||
|
- `handlers/pull_requests.py` — 15 tools (PRs, reviews, merge, diff)
|
||||||
|
- `handlers/users.py` — 8 tools (users, orgs, teams)
|
||||||
|
- `handlers/webhooks.py` — 5 tools (CRUD webhooks, test)
|
||||||
|
2. **مشکلات شناساییشده**:
|
||||||
|
- `update_webhook` در client.py هست ولی به عنوان tool expose نشده
|
||||||
|
- هیچ تست اختصاصیای ندارد (0 تست!)
|
||||||
|
- فعلاً admin-only هست (نه در ENABLED_PLUGINS)
|
||||||
|
3. **تست واقعی**: سایت Gitea از MCPHub MCP endpoint:
|
||||||
|
- از ابزارهای Supabase MCP (`mcp.example.com`) استفاده کنید تا سایت Gitea موجود را پیدا کنید
|
||||||
|
- یا یک Gitea instance جدید از طریق داشبورد اضافه کنید
|
||||||
|
- هر دسته tool را با API واقعی تست کنید
|
||||||
|
|
||||||
|
### بخش 2: تست نوشتن
|
||||||
|
مشابه `tests/test_openpanel_plugin.py` (الگو — 767 خط, 62 تست):
|
||||||
|
1. `tests/test_gitea_plugin.py` بنویسید:
|
||||||
|
- Client initialization + health check
|
||||||
|
- Tool spec validation (نامها، پارامترها، type ها)
|
||||||
|
- Handler delegation (mock client)
|
||||||
|
- Error handling
|
||||||
|
- Pagination
|
||||||
|
2. هدف: حداقل 80 تست (56 tool + edge cases)
|
||||||
|
|
||||||
|
### بخش 3: رفع مشکلات
|
||||||
|
1. `update_webhook` را به عنوان tool expose کنید
|
||||||
|
2. هر tool که در تست واقعی مشکل دارد را fix کنید
|
||||||
|
3. توضیحات service page برای Gitea بنویسید
|
||||||
|
4. health check endpoint را بررسی کنید
|
||||||
|
|
||||||
|
### بخش 4: فعالسازی عمومی
|
||||||
|
1. `core/plugin_visibility.py`: اضافه کردن `gitea` به `DEFAULT_PUBLIC_PLUGINS`
|
||||||
|
2. `env.example`: آپدیت ENABLED_PLUGINS default
|
||||||
|
3. `glama.json`: آپدیت description اگر لازمه
|
||||||
|
|
||||||
|
### بخش 5: Release
|
||||||
|
1. Version bump: `3.5.0` → `3.6.0`
|
||||||
|
2. CHANGELOG update
|
||||||
|
3. Lint: `uvx --python 3.12 black .` && `uvx ruff check --fix .`
|
||||||
|
4. Commit + push Phase-1
|
||||||
|
5. Sync: `python3.11 scripts/community-build/sync.py --output ../mcphub/`
|
||||||
|
6. بعد از sync حتماً `uvx --python 3.12 black .` در public repo هم بزنید!
|
||||||
|
7. Commit + push public repo
|
||||||
|
|
||||||
|
### مراحل پیشنهادی
|
||||||
|
1. **تحلیل**: خواندن کامل هر handler + client + schemas
|
||||||
|
2. **تست واقعی**: اتصال به Gitea instance و تست ابزارها
|
||||||
|
3. **پلن اجرایی**: ارائه پلن و تایید قبل از شروع
|
||||||
|
4. **تست نوشتن**: test_gitea_plugin.py
|
||||||
|
5. **رفع مشکلات**: fix هر tool مشکلدار
|
||||||
|
6. **فعالسازی**: ENABLED_PLUGINS update
|
||||||
|
7. **Release**: v3.6.0
|
||||||
|
|
||||||
|
### نکات فنی
|
||||||
|
- Private repo: /config/workspace/mcphub-internal (branch Phase-1)
|
||||||
|
- Public repo: /config/workspace/mcphub (branch main)
|
||||||
|
- Lint: `uvx --python 3.12 black .` && `uvx ruff check --fix .`
|
||||||
|
- Sync: `python3.11 scripts/community-build/sync.py --output ../mcphub/`
|
||||||
|
- ایمیل git عمومی: hi.airano@gmail.com
|
||||||
|
- **مهم**: اول پلن بده، بدون تایید شروع نکن
|
||||||
|
- **الگوی مرجع**: F.10 (OpenPanel) — بخش "Phase F.10" در plan و `tests/test_openpanel_plugin.py`
|
||||||
|
```
|
||||||
@@ -32,8 +32,8 @@ DASHBOARD_SESSION_SECRET=
|
|||||||
# ============================================
|
# ============================================
|
||||||
# Comma-separated list of plugins visible to public (OAuth) users.
|
# Comma-separated list of plugins visible to public (OAuth) users.
|
||||||
# Admin users always see all plugins regardless of this setting.
|
# Admin users always see all plugins regardless of this setting.
|
||||||
# Default (if not set): wordpress,woocommerce,supabase,openpanel
|
# Default (if not set): wordpress,woocommerce,supabase,openpanel,gitea
|
||||||
# ENABLED_PLUGINS=wordpress,woocommerce,supabase,openpanel
|
# ENABLED_PLUGINS=wordpress,woocommerce,supabase,openpanel,gitea
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# ADMIN SYSTEM (Track F.4)
|
# ADMIN SYSTEM (Track F.4)
|
||||||
|
|||||||
36
glama.json
Normal file
36
glama.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://glama.ai/mcp/schemas/server.json",
|
||||||
|
"maintainers": ["airano-ir"],
|
||||||
|
"name": "mcphub",
|
||||||
|
"display_name": "MCP Hub",
|
||||||
|
"description": "Unified MCP server for managing WordPress, WooCommerce, Gitea, n8n, Supabase, OpenPanel, Appwrite, and Directus with 567 tools, multi-site support, and OAuth 2.1 authentication.",
|
||||||
|
"repository": "https://github.com/airano-ir/mcphub",
|
||||||
|
"homepage": "https://mcp.palebluedot.live",
|
||||||
|
"version": "3.6.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"runtime": "python",
|
||||||
|
"transport": ["stdio", "streamable-http"],
|
||||||
|
"categories": ["developer-tools", "cms", "databases", "automation"],
|
||||||
|
"keywords": ["mcp", "wordpress", "woocommerce", "supabase", "gitea", "n8n", "openpanel", "appwrite", "directus", "self-hosted", "multi-site"],
|
||||||
|
"author": {
|
||||||
|
"name": "airano-ir",
|
||||||
|
"url": "https://github.com/airano-ir"
|
||||||
|
},
|
||||||
|
"capabilities": {
|
||||||
|
"tools": true,
|
||||||
|
"prompts": false,
|
||||||
|
"resources": false
|
||||||
|
},
|
||||||
|
"relatedServers": [
|
||||||
|
{
|
||||||
|
"name": "WordPress MCP",
|
||||||
|
"url": "https://glama.ai/mcp/servers/developer-in-world/wordpress-mcp",
|
||||||
|
"description": "WordPress REST API MCP server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Supabase MCP",
|
||||||
|
"url": "https://glama.ai/mcp/servers/supabase-community/supabase-mcp",
|
||||||
|
"description": "Official Supabase MCP server"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -295,6 +295,25 @@ def get_tool_specifications() -> list[dict[str, Any]]:
|
|||||||
},
|
},
|
||||||
"scope": "write",
|
"scope": "write",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "delete_label",
|
||||||
|
"method_name": "delete_label",
|
||||||
|
"description": "Delete a label from a repository.",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||||
|
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||||
|
"label_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Label ID to delete",
|
||||||
|
"minimum": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["owner", "repo", "label_id"],
|
||||||
|
},
|
||||||
|
"scope": "write",
|
||||||
|
},
|
||||||
# === MILESTONES ===
|
# === MILESTONES ===
|
||||||
{
|
{
|
||||||
"name": "list_milestones",
|
"name": "list_milestones",
|
||||||
@@ -499,6 +518,13 @@ async def create_label(
|
|||||||
return json.dumps(result, indent=2)
|
return json.dumps(result, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_label(client: GiteaClient, owner: str, repo: str, label_id: int) -> str:
|
||||||
|
"""Delete a label"""
|
||||||
|
await client.delete_label(owner, repo, label_id)
|
||||||
|
result = {"success": True, "message": f"Label {label_id} deleted successfully"}
|
||||||
|
return json.dumps(result, indent=2)
|
||||||
|
|
||||||
|
|
||||||
# Milestone operations
|
# Milestone operations
|
||||||
async def list_milestones(
|
async def list_milestones(
|
||||||
client: GiteaClient, owner: str, repo: str, state: str | None = None
|
client: GiteaClient, owner: str, repo: str, state: str | None = None
|
||||||
|
|||||||
@@ -526,12 +526,13 @@ async def merge_pull_request(
|
|||||||
delete_branch_after_merge: bool = False,
|
delete_branch_after_merge: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Merge a pull request"""
|
"""Merge a pull request"""
|
||||||
data = {
|
data: dict = {"Do": method}
|
||||||
"Do": method,
|
if title is not None:
|
||||||
"MergeTitleField": title,
|
data["MergeTitleField"] = title
|
||||||
"MergeMessageField": message,
|
if message is not None:
|
||||||
"delete_branch_after_merge": delete_branch_after_merge,
|
data["MergeMessageField"] = message
|
||||||
}
|
if delete_branch_after_merge:
|
||||||
|
data["delete_branch_after_merge"] = True
|
||||||
merge_result = await client.merge_pull_request(owner, repo, pr_number, data)
|
merge_result = await client.merge_pull_request(owner, repo, pr_number, data)
|
||||||
result = {
|
result = {
|
||||||
"success": True,
|
"success": True,
|
||||||
|
|||||||
@@ -159,6 +159,79 @@ def get_tool_specifications() -> list[dict[str, Any]]:
|
|||||||
},
|
},
|
||||||
"scope": "read",
|
"scope": "read",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "update_webhook",
|
||||||
|
"method_name": "update_webhook",
|
||||||
|
"description": "Update an existing webhook's configuration, events, or active status.",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"owner": {"type": "string", "description": "Repository owner", "minLength": 1},
|
||||||
|
"repo": {"type": "string", "description": "Repository name", "minLength": 1},
|
||||||
|
"webhook_id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Webhook ID to update",
|
||||||
|
"minimum": 1,
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"anyOf": [{"type": "string", "format": "uri"}, {"type": "null"}],
|
||||||
|
"description": "New webhook URL",
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"create",
|
||||||
|
"delete",
|
||||||
|
"fork",
|
||||||
|
"push",
|
||||||
|
"issues",
|
||||||
|
"issue_assign",
|
||||||
|
"issue_label",
|
||||||
|
"issue_milestone",
|
||||||
|
"issue_comment",
|
||||||
|
"pull_request",
|
||||||
|
"pull_request_assign",
|
||||||
|
"pull_request_label",
|
||||||
|
"pull_request_milestone",
|
||||||
|
"pull_request_comment",
|
||||||
|
"pull_request_review_approved",
|
||||||
|
"pull_request_review_rejected",
|
||||||
|
"pull_request_review_comment",
|
||||||
|
"pull_request_sync",
|
||||||
|
"wiki",
|
||||||
|
"repository",
|
||||||
|
"release",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{"type": "null"},
|
||||||
|
],
|
||||||
|
"description": "Updated list of events to trigger webhook",
|
||||||
|
},
|
||||||
|
"content_type": {
|
||||||
|
"anyOf": [
|
||||||
|
{"type": "string", "enum": ["json", "form"]},
|
||||||
|
{"type": "null"},
|
||||||
|
],
|
||||||
|
"description": "Content type for webhook payload",
|
||||||
|
},
|
||||||
|
"secret": {
|
||||||
|
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||||
|
"description": "Secret for webhook signature verification",
|
||||||
|
},
|
||||||
|
"active": {
|
||||||
|
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||||
|
"description": "Activate or deactivate webhook",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": ["owner", "repo", "webhook_id"],
|
||||||
|
},
|
||||||
|
"scope": "admin",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -220,3 +293,40 @@ async def get_webhook(client: GiteaClient, owner: str, repo: str, webhook_id: in
|
|||||||
webhook = await client.get_webhook(owner, repo, webhook_id)
|
webhook = await client.get_webhook(owner, repo, webhook_id)
|
||||||
result = {"success": True, "webhook": webhook}
|
result = {"success": True, "webhook": webhook}
|
||||||
return json.dumps(result, indent=2)
|
return json.dumps(result, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
async def update_webhook(
|
||||||
|
client: GiteaClient,
|
||||||
|
owner: str,
|
||||||
|
repo: str,
|
||||||
|
webhook_id: int,
|
||||||
|
url: str | None = None,
|
||||||
|
events: list[str] | None = None,
|
||||||
|
content_type: str | None = None,
|
||||||
|
secret: str | None = None,
|
||||||
|
active: bool | None = None,
|
||||||
|
) -> str:
|
||||||
|
"""Update a webhook"""
|
||||||
|
data: dict = {}
|
||||||
|
if events is not None:
|
||||||
|
data["events"] = events
|
||||||
|
if active is not None:
|
||||||
|
data["active"] = active
|
||||||
|
|
||||||
|
config: dict = {}
|
||||||
|
if url is not None:
|
||||||
|
config["url"] = url
|
||||||
|
if content_type is not None:
|
||||||
|
config["content_type"] = content_type
|
||||||
|
if secret is not None:
|
||||||
|
config["secret"] = secret
|
||||||
|
if config:
|
||||||
|
data["config"] = config
|
||||||
|
|
||||||
|
webhook = await client.update_webhook(owner, repo, webhook_id, data)
|
||||||
|
result = {
|
||||||
|
"success": True,
|
||||||
|
"message": f"Webhook {webhook_id} updated successfully",
|
||||||
|
"webhook": webhook,
|
||||||
|
}
|
||||||
|
return json.dumps(result, indent=2)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "mcphub-server"
|
name = "mcphub-server"
|
||||||
version = "3.5.0"
|
version = "3.6.0"
|
||||||
description = "AI-native management hub for WordPress, WooCommerce, and self-hosted services via Model Context Protocol (MCP)"
|
description = "AI-native management hub for WordPress, WooCommerce, and self-hosted services via Model Context Protocol (MCP)"
|
||||||
authors = [
|
authors = [
|
||||||
{name = "MCP Hub", email = "contact@mcphub.dev"}
|
{name = "MCP Hub", email = "contact@mcphub.dev"}
|
||||||
|
|||||||
1135
tests/test_gitea_plugin.py
Normal file
1135
tests/test_gitea_plugin.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -78,11 +78,11 @@ class TestIsPluginPublic:
|
|||||||
os.environ.pop("ENABLED_PLUGINS", None)
|
os.environ.pop("ENABLED_PLUGINS", None)
|
||||||
assert is_plugin_public("supabase") is True
|
assert is_plugin_public("supabase") is True
|
||||||
|
|
||||||
def test_default_gitea_not_public(self):
|
def test_default_gitea_public(self):
|
||||||
"""Gitea is not public by default."""
|
"""Gitea is public by default (since F.16)."""
|
||||||
with patch.dict(os.environ, {}, clear=False):
|
with patch.dict(os.environ, {}, clear=False):
|
||||||
os.environ.pop("ENABLED_PLUGINS", None)
|
os.environ.pop("ENABLED_PLUGINS", None)
|
||||||
assert is_plugin_public("gitea") is False
|
assert is_plugin_public("gitea") is True
|
||||||
|
|
||||||
def test_default_wordpress_advanced_not_public(self):
|
def test_default_wordpress_advanced_not_public(self):
|
||||||
"""WordPress Advanced is not public by default."""
|
"""WordPress Advanced is not public by default."""
|
||||||
@@ -118,8 +118,9 @@ class TestSiteApiIntegration:
|
|||||||
assert "wordpress" in fields
|
assert "wordpress" in fields
|
||||||
assert "woocommerce" in fields
|
assert "woocommerce" in fields
|
||||||
assert "supabase" in fields
|
assert "supabase" in fields
|
||||||
|
assert "gitea" in fields
|
||||||
|
assert "openpanel" in fields
|
||||||
assert "wordpress_advanced" not in fields
|
assert "wordpress_advanced" not in fields
|
||||||
assert "gitea" not in fields
|
|
||||||
assert "n8n" not in fields
|
assert "n8n" not in fields
|
||||||
|
|
||||||
def test_get_user_plugin_names_filtered(self):
|
def test_get_user_plugin_names_filtered(self):
|
||||||
@@ -133,8 +134,9 @@ class TestSiteApiIntegration:
|
|||||||
assert "wordpress" in names
|
assert "wordpress" in names
|
||||||
assert "woocommerce" in names
|
assert "woocommerce" in names
|
||||||
assert "supabase" in names
|
assert "supabase" in names
|
||||||
|
assert "gitea" in names
|
||||||
|
assert "openpanel" in names
|
||||||
assert "wordpress_advanced" not in names
|
assert "wordpress_advanced" not in names
|
||||||
assert "gitea" not in names
|
|
||||||
|
|
||||||
def test_custom_env_changes_fields(self):
|
def test_custom_env_changes_fields(self):
|
||||||
"""Custom ENABLED_PLUGINS changes which credential fields are returned."""
|
"""Custom ENABLED_PLUGINS changes which credential fields are returned."""
|
||||||
|
|||||||
Reference in New Issue
Block a user