Initial commit: BunnyCDN MCP server with K8s deployment

- FastMCP server with 12 tools (pullzone, cache, statistics, shield)
- Dockerfile with multi-stage build (python:3.11-slim + uv)
- K8s manifests (Deployment + Service)
- Gitea Actions CI/CD pipeline (build → push → deploy)
This commit is contained in:
kappa
2026-02-15 15:19:42 +09:00
commit 7269686179
16 changed files with 530 additions and 0 deletions

66
bunnycdn_mcp/client.py Normal file
View File

@@ -0,0 +1,66 @@
"""BunnyCDN REST API client using httpx."""
import httpx
from .config import BUNNY_API_BASE, BUNNY_API_KEY, REQUEST_TIMEOUT, logger
class BunnyClient:
"""Async HTTP client for BunnyCDN API."""
def __init__(self) -> None:
self._base = BUNNY_API_BASE
self._headers = {
"AccessKey": BUNNY_API_KEY,
"Accept": "application/json",
"Content-Type": "application/json",
}
async def get(self, path: str, params: dict | None = None) -> dict | list:
async with httpx.AsyncClient(timeout=REQUEST_TIMEOUT) as c:
resp = await c.get(
f"{self._base}{path}",
headers=self._headers,
params=params,
)
return self._handle(resp)
async def post(self, path: str, json: dict | None = None) -> dict | list | str:
async with httpx.AsyncClient(timeout=REQUEST_TIMEOUT) as c:
resp = await c.post(
f"{self._base}{path}",
headers=self._headers,
json=json,
)
return self._handle(resp)
async def delete(self, path: str) -> dict | str:
async with httpx.AsyncClient(timeout=REQUEST_TIMEOUT) as c:
resp = await c.delete(
f"{self._base}{path}",
headers=self._headers,
)
return self._handle(resp)
def _handle(self, resp: httpx.Response) -> dict | list | str:
if resp.status_code == 401:
raise BunnyAPIError("Authentication failed: invalid API key")
if resp.status_code == 404:
raise BunnyAPIError(f"Not found: {resp.url}")
if resp.status_code >= 500:
raise BunnyAPIError(f"Server error {resp.status_code}: {resp.text}")
if resp.status_code >= 400:
raise BunnyAPIError(f"Client error {resp.status_code}: {resp.text}")
if not resp.content:
return ""
try:
return resp.json()
except Exception:
return resp.text
class BunnyAPIError(Exception):
pass
client = BunnyClient()