feat: Initial Porkbun DNS MCP Light server
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m32s
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m32s
- 3 specific tools: ping, list_domains, list_dns_records - 1 pass-through tool: porkbun_api for full API access - Safety system with READ/WRITE/INFRA access levels - Embedded API documentation as MCP resource - Starlette wrapper with /health endpoint - Gitea Actions CI workflow for Docker build
This commit is contained in:
17
.env.example
Normal file
17
.env.example
Normal file
@@ -0,0 +1,17 @@
|
||||
# Porkbun API Credentials
|
||||
# Get your keys at: https://porkbun.com/account/api
|
||||
PORKBUN_API_KEY=pk1_your_api_key_here
|
||||
PORKBUN_SECRET_KEY=sk1_your_secret_key_here
|
||||
|
||||
# Optional: API base URL (default shown)
|
||||
# PORKBUN_API_BASE=https://api.porkbun.com/api/json/v3
|
||||
|
||||
# Safety Controls
|
||||
# Default: read-only mode (safest)
|
||||
PORKBUN_ALLOW_WRITES=false
|
||||
PORKBUN_ALLOW_INFRA=false
|
||||
|
||||
# Safety Levels:
|
||||
# - READ (default): List domains, retrieve records, ping
|
||||
# - WRITE: Enable DNS record create/edit/delete, DNSSEC, URL forwarding
|
||||
# - INFRA: Enable nameserver and glue record changes (requires WRITE)
|
||||
35
.gitea/workflows/build.yaml
Normal file
35
.gitea/workflows/build.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Build and Push Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: catthehacker/ubuntu:act-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Gitea Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: gitea.ext.ben.io
|
||||
username: b3nw
|
||||
password: ${{ secrets.CR_PAT }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
gitea.ext.ben.io/b3nw/porkbun-dns-mcp:latest
|
||||
gitea.ext.ben.io/b3nw/porkbun-dns-mcp:${{ github.sha }}
|
||||
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# Environment
|
||||
.env
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
.eggs/
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
23
Dockerfile
Normal file
23
Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install uv for fast dependency management
|
||||
RUN pip install uv
|
||||
|
||||
# Copy project files
|
||||
COPY pyproject.toml .
|
||||
COPY server.py .
|
||||
|
||||
# Install dependencies
|
||||
RUN uv pip install --system -e .
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8000
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD python -c "import httpx; r = httpx.get('http://localhost:8000/health'); exit(0 if r.status_code == 200 else 1)"
|
||||
|
||||
# Run server
|
||||
CMD ["python", "server.py"]
|
||||
81
README.md
Normal file
81
README.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Porkbun DNS MCP
|
||||
|
||||
A lightweight MCP server for managing DNS records via the Porkbun API.
|
||||
|
||||
## Features
|
||||
|
||||
- **3 specific tools** for common operations (ping, list domains, list DNS records)
|
||||
- **1 pass-through tool** for full API access
|
||||
- **Safety system** with READ/WRITE/INFRA access levels
|
||||
- **Embedded API documentation** as MCP resource
|
||||
|
||||
## Safety Levels
|
||||
|
||||
| Level | Operations | Environment Variable |
|
||||
|-------|-----------|---------------------|
|
||||
| READ | List domains, retrieve records, ping | Default |
|
||||
| WRITE | Create/edit/delete DNS records, DNSSEC | `PORKBUN_ALLOW_WRITES=true` |
|
||||
| INFRA | Nameserver changes, glue records | `PORKBUN_ALLOW_INFRA=true` |
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Local Development
|
||||
|
||||
```bash
|
||||
# Create .env from template
|
||||
cp .env.example .env
|
||||
|
||||
# Add your Porkbun API keys to .env
|
||||
# Get keys at: https://porkbun.com/account/api
|
||||
|
||||
# Install dependencies
|
||||
uv pip install -e .
|
||||
|
||||
# Run server
|
||||
python server.py
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
-e PORKBUN_API_KEY=pk1_xxx \
|
||||
-e PORKBUN_SECRET_KEY=sk1_xxx \
|
||||
-e PORKBUN_ALLOW_WRITES=false \
|
||||
-p 8000:8000 \
|
||||
gitea.ext.ben.io/b3nw/porkbun-dns-mcp:latest
|
||||
```
|
||||
|
||||
## MCP Tools
|
||||
|
||||
### `ping`
|
||||
Test API connectivity and return your public IP address.
|
||||
|
||||
### `list_domains`
|
||||
List all domains in your Porkbun account.
|
||||
|
||||
### `list_dns_records`
|
||||
Retrieve DNS records for a specific domain.
|
||||
|
||||
### `porkbun_api`
|
||||
Execute any Porkbun API call. Refer to the `porkbun://api-reference` resource for endpoint documentation.
|
||||
|
||||
## MCP Resources
|
||||
|
||||
- `porkbun://api-reference` - Full API documentation
|
||||
- `porkbun://safety-status` - Current safety configuration
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|----------|----------|---------|-------------|
|
||||
| `PORKBUN_API_KEY` | Yes | - | Porkbun API key |
|
||||
| `PORKBUN_SECRET_KEY` | Yes | - | Porkbun secret key |
|
||||
| `PORKBUN_ALLOW_WRITES` | No | `false` | Enable write operations |
|
||||
| `PORKBUN_ALLOW_INFRA` | No | `false` | Enable infrastructure changes |
|
||||
| `PORT` | No | `8000` | Server port |
|
||||
|
||||
## Endpoints
|
||||
|
||||
- `GET /health` - Health check
|
||||
- `POST /mcp/` - MCP SSE endpoint
|
||||
20
pyproject.toml
Normal file
20
pyproject.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[project]
|
||||
name = "porkbun-dns-mcp"
|
||||
version = "0.1.0"
|
||||
description = "MCP Light server for Porkbun DNS management"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"fastmcp>=2.0.0",
|
||||
"httpx>=0.27.0",
|
||||
"python-dotenv>=1.0.0",
|
||||
"starlette>=0.40.0",
|
||||
"uvicorn>=0.32.0",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["."]
|
||||
include = ["server.py"]
|
||||
418
server.py
Normal file
418
server.py
Normal file
@@ -0,0 +1,418 @@
|
||||
"""Porkbun DNS MCP Light Server.
|
||||
|
||||
A lightweight MCP server for managing DNS records via the Porkbun API.
|
||||
Implements a safety system with READ, WRITE, and INFRA access levels.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from dotenv import load_dotenv
|
||||
from fastmcp import FastMCP
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Mount, Route
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# Configuration
|
||||
PORKBUN_API_KEY = os.getenv("PORKBUN_API_KEY", "")
|
||||
PORKBUN_SECRET_KEY = os.getenv("PORKBUN_SECRET_KEY", "")
|
||||
PORKBUN_API_BASE = os.getenv("PORKBUN_API_BASE", "https://api.porkbun.com/api/json/v3")
|
||||
|
||||
# Safety controls (defaults to read-only)
|
||||
ALLOW_WRITES = os.getenv("PORKBUN_ALLOW_WRITES", "false").lower() == "true"
|
||||
ALLOW_INFRA = os.getenv("PORKBUN_ALLOW_INFRA", "false").lower() == "true"
|
||||
|
||||
# Endpoint classifications for safety system
|
||||
INFRA_ENDPOINTS = [
|
||||
"/domain/updateNs/",
|
||||
"/domain/createGlue/",
|
||||
"/domain/updateGlue/",
|
||||
"/domain/deleteGlue/",
|
||||
]
|
||||
|
||||
WRITE_ENDPOINTS = [
|
||||
"/dns/create/",
|
||||
"/dns/edit/",
|
||||
"/dns/editByNameType/",
|
||||
"/dns/delete/",
|
||||
"/dns/deleteByNameType/",
|
||||
"/dns/createDnssecRecord/",
|
||||
"/dns/deleteDnssecRecord/",
|
||||
"/domain/addUrlForward/",
|
||||
"/domain/deleteUrlForward/",
|
||||
]
|
||||
|
||||
# API Documentation for agent self-service
|
||||
API_DOCS = """
|
||||
# Porkbun API Reference
|
||||
|
||||
Base URL: https://api.porkbun.com/api/json/v3
|
||||
All endpoints use POST method. Authentication is handled automatically.
|
||||
|
||||
## Safety Levels
|
||||
- READ: Default, all retrieve/list operations
|
||||
- WRITE: Requires PORKBUN_ALLOW_WRITES=true - DNS record modifications
|
||||
- INFRA: Requires PORKBUN_ALLOW_INFRA=true - Nameserver and glue record changes
|
||||
|
||||
## DNS Record Management
|
||||
|
||||
### Create DNS Record
|
||||
Endpoint: /dns/create/{DOMAIN}
|
||||
Body: {"name": "subdomain", "type": "A", "content": "1.2.3.4", "ttl": "600", "prio": "0"}
|
||||
- name: Subdomain (blank for root, * for wildcard)
|
||||
- type: A, AAAA, MX, CNAME, ALIAS, TXT, NS, SRV, TLSA, CAA, HTTPS, SVCB, SSHFP
|
||||
- content: Record value
|
||||
- ttl: Time to live (minimum 600)
|
||||
- prio: Priority (for MX, SRV)
|
||||
|
||||
### Edit DNS Record by ID
|
||||
Endpoint: /dns/edit/{DOMAIN}/{RECORD_ID}
|
||||
Body: {"name": "subdomain", "type": "A", "content": "1.2.3.5", "ttl": "600"}
|
||||
|
||||
### Edit DNS Record by Name/Type
|
||||
Endpoint: /dns/editByNameType/{DOMAIN}/{TYPE}/{SUBDOMAIN}
|
||||
Body: {"content": "1.2.3.5", "ttl": "600"}
|
||||
|
||||
### Delete DNS Record by ID
|
||||
Endpoint: /dns/delete/{DOMAIN}/{RECORD_ID}
|
||||
Body: {}
|
||||
|
||||
### Delete DNS Record by Name/Type
|
||||
Endpoint: /dns/deleteByNameType/{DOMAIN}/{TYPE}/{SUBDOMAIN}
|
||||
Body: {}
|
||||
|
||||
### Retrieve All DNS Records
|
||||
Endpoint: /dns/retrieve/{DOMAIN}
|
||||
Body: {}
|
||||
|
||||
### Retrieve DNS Record by ID
|
||||
Endpoint: /dns/retrieve/{DOMAIN}/{RECORD_ID}
|
||||
Body: {}
|
||||
|
||||
### Retrieve DNS Records by Name/Type
|
||||
Endpoint: /dns/retrieveByNameType/{DOMAIN}/{TYPE}/{SUBDOMAIN}
|
||||
Body: {}
|
||||
|
||||
## DNSSEC Management
|
||||
|
||||
### Create DNSSEC Record
|
||||
Endpoint: /dns/createDnssecRecord/{DOMAIN}
|
||||
Body: {"keyTag": "64087", "alg": "13", "digestType": "2", "digest": "..."}
|
||||
|
||||
### Get DNSSEC Records
|
||||
Endpoint: /dns/getDnssecRecords/{DOMAIN}
|
||||
Body: {}
|
||||
|
||||
### Delete DNSSEC Record
|
||||
Endpoint: /dns/deleteDnssecRecord/{DOMAIN}/{KEYTAG}
|
||||
Body: {}
|
||||
|
||||
## Domain Management
|
||||
|
||||
### List All Domains
|
||||
Endpoint: /domain/listAll
|
||||
Body: {"start": "0", "includeLabels": "yes"}
|
||||
|
||||
### Get Nameservers
|
||||
Endpoint: /domain/getNs/{DOMAIN}
|
||||
Body: {}
|
||||
|
||||
### Update Nameservers (INFRA level)
|
||||
Endpoint: /domain/updateNs/{DOMAIN}
|
||||
Body: {"ns": ["ns1.example.com", "ns2.example.com"]}
|
||||
|
||||
### Check Domain Availability
|
||||
Endpoint: /domain/checkDomain/{DOMAIN}
|
||||
Body: {}
|
||||
|
||||
## URL Forwarding
|
||||
|
||||
### Add URL Forward
|
||||
Endpoint: /domain/addUrlForward/{DOMAIN}
|
||||
Body: {"subdomain": "", "location": "https://example.com", "type": "temporary", "includePath": "no", "wildcard": "yes"}
|
||||
|
||||
### Get URL Forwarding
|
||||
Endpoint: /domain/getUrlForwarding/{DOMAIN}
|
||||
Body: {}
|
||||
|
||||
### Delete URL Forward
|
||||
Endpoint: /domain/deleteUrlForward/{DOMAIN}/{RECORD_ID}
|
||||
Body: {}
|
||||
|
||||
## Glue Records (INFRA level)
|
||||
|
||||
### Create Glue Record
|
||||
Endpoint: /domain/createGlue/{DOMAIN}/{SUBDOMAIN}
|
||||
Body: {"ips": ["192.168.1.1", "2001:db8::1"]}
|
||||
|
||||
### Update Glue Record
|
||||
Endpoint: /domain/updateGlue/{DOMAIN}/{SUBDOMAIN}
|
||||
Body: {"ips": ["192.168.1.2"]}
|
||||
|
||||
### Delete Glue Record
|
||||
Endpoint: /domain/deleteGlue/{DOMAIN}/{SUBDOMAIN}
|
||||
Body: {}
|
||||
|
||||
### Get Glue Records
|
||||
Endpoint: /domain/getGlue/{DOMAIN}
|
||||
Body: {}
|
||||
|
||||
## SSL Certificates
|
||||
|
||||
### Retrieve SSL Bundle
|
||||
Endpoint: /ssl/retrieve/{DOMAIN}
|
||||
Body: {}
|
||||
Returns: certificatechain, privatekey, publickey
|
||||
|
||||
## Utility
|
||||
|
||||
### Ping (Test Auth)
|
||||
Endpoint: /ping
|
||||
Body: {}
|
||||
Returns: Your public IP address
|
||||
"""
|
||||
|
||||
|
||||
class PorkbunClient:
|
||||
"""HTTP client for Porkbun API with safety controls."""
|
||||
|
||||
def __init__(self, api_key: str, secret_key: str, base_url: str):
|
||||
self.api_key = api_key
|
||||
self.secret_key = secret_key
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.client = httpx.Client(timeout=30.0)
|
||||
|
||||
def _get_auth_body(self) -> dict[str, str]:
|
||||
"""Return authentication payload."""
|
||||
return {
|
||||
"apikey": self.api_key,
|
||||
"secretapikey": self.secret_key,
|
||||
}
|
||||
|
||||
def _classify_endpoint(self, endpoint: str) -> str:
|
||||
"""Classify endpoint by safety level."""
|
||||
if any(endpoint.startswith(e) for e in INFRA_ENDPOINTS):
|
||||
return "INFRA"
|
||||
if any(endpoint.startswith(e) for e in WRITE_ENDPOINTS):
|
||||
return "WRITE"
|
||||
return "READ"
|
||||
|
||||
def _validate_safety(self, endpoint: str) -> tuple[bool, str | None]:
|
||||
"""Check if endpoint is allowed under current safety settings."""
|
||||
level = self._classify_endpoint(endpoint)
|
||||
|
||||
if level == "INFRA":
|
||||
if not ALLOW_INFRA:
|
||||
return False, (
|
||||
"INFRA operation blocked. This endpoint modifies domain infrastructure "
|
||||
"(nameservers/glue records). Set PORKBUN_ALLOW_INFRA=true to enable."
|
||||
)
|
||||
if not ALLOW_WRITES:
|
||||
return False, (
|
||||
"INFRA operations require PORKBUN_ALLOW_WRITES=true in addition to "
|
||||
"PORKBUN_ALLOW_INFRA=true."
|
||||
)
|
||||
|
||||
if level == "WRITE":
|
||||
if not ALLOW_WRITES:
|
||||
return False, (
|
||||
"WRITE operation blocked. This endpoint modifies DNS records. "
|
||||
"Set PORKBUN_ALLOW_WRITES=true to enable."
|
||||
)
|
||||
|
||||
return True, None
|
||||
|
||||
def request(
|
||||
self, endpoint: str, body: dict[str, Any] | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""Execute API request with safety checks."""
|
||||
# Normalize endpoint
|
||||
if not endpoint.startswith("/"):
|
||||
endpoint = f"/{endpoint}"
|
||||
|
||||
# Safety validation
|
||||
allowed, error_msg = self._validate_safety(endpoint)
|
||||
if not allowed:
|
||||
return {
|
||||
"status": "BLOCKED",
|
||||
"reason": error_msg,
|
||||
"endpoint": endpoint,
|
||||
"category": self._classify_endpoint(endpoint),
|
||||
}
|
||||
|
||||
# Build request body with auth
|
||||
request_body = self._get_auth_body()
|
||||
if body:
|
||||
request_body.update(body)
|
||||
|
||||
try:
|
||||
response = self.client.post(
|
||||
f"{self.base_url}{endpoint}",
|
||||
json=request_body,
|
||||
)
|
||||
return response.json()
|
||||
except httpx.HTTPError as e:
|
||||
return {"status": "ERROR", "message": f"HTTP error: {e}"}
|
||||
except json.JSONDecodeError:
|
||||
return {"status": "ERROR", "message": "Invalid JSON response from API"}
|
||||
|
||||
def ping(self) -> dict[str, Any]:
|
||||
"""Test API connectivity."""
|
||||
return self.request("/ping")
|
||||
|
||||
def close(self):
|
||||
"""Close HTTP client."""
|
||||
self.client.close()
|
||||
|
||||
|
||||
# Initialize client and MCP
|
||||
client = PorkbunClient(PORKBUN_API_KEY, PORKBUN_SECRET_KEY, PORKBUN_API_BASE)
|
||||
mcp = FastMCP("porkbun-dns")
|
||||
|
||||
|
||||
# --- MCP Resources ---
|
||||
|
||||
|
||||
@mcp.resource("porkbun://api-reference")
|
||||
def get_api_docs() -> str:
|
||||
"""Returns the Porkbun API documentation for using the porkbun_api tool."""
|
||||
return API_DOCS
|
||||
|
||||
|
||||
@mcp.resource("porkbun://safety-status")
|
||||
def get_safety_status() -> str:
|
||||
"""Returns current safety configuration."""
|
||||
return json.dumps(
|
||||
{
|
||||
"allow_writes": ALLOW_WRITES,
|
||||
"allow_infra": ALLOW_INFRA,
|
||||
"mode": "INFRA" if ALLOW_INFRA else ("WRITE" if ALLOW_WRITES else "READ"),
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
# --- MCP Tools ---
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def ping() -> str:
|
||||
"""Test API connectivity and return your public IP address.
|
||||
|
||||
Useful for verifying credentials and for dynamic DNS scenarios.
|
||||
"""
|
||||
result = client.ping()
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def list_domains(start: int = 0, include_labels: bool = False) -> str:
|
||||
"""List all domains in your Porkbun account.
|
||||
|
||||
Args:
|
||||
start: Pagination offset (domains returned in chunks of 1000)
|
||||
include_labels: Include domain labels in response
|
||||
|
||||
Returns:
|
||||
JSON string with domain list and details
|
||||
"""
|
||||
body = {"start": str(start)}
|
||||
if include_labels:
|
||||
body["includeLabels"] = "yes"
|
||||
|
||||
result = client.request("/domain/listAll", body)
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def list_dns_records(domain: str, record_id: str | None = None) -> str:
|
||||
"""Retrieve DNS records for a domain.
|
||||
|
||||
Args:
|
||||
domain: The domain name (e.g., 'example.com')
|
||||
record_id: Optional specific record ID to retrieve
|
||||
|
||||
Returns:
|
||||
JSON string with DNS records
|
||||
"""
|
||||
endpoint = f"/dns/retrieve/{domain}"
|
||||
if record_id:
|
||||
endpoint = f"{endpoint}/{record_id}"
|
||||
|
||||
result = client.request(endpoint)
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def porkbun_api(endpoint: str, body: str = "{}") -> str:
|
||||
"""Execute a raw Porkbun API call.
|
||||
|
||||
Use the 'porkbun://api-reference' resource for endpoint documentation.
|
||||
Safety controls apply: WRITE and INFRA operations may be blocked.
|
||||
|
||||
Args:
|
||||
endpoint: API path (e.g., '/dns/create/example.com')
|
||||
body: JSON string of additional parameters (auth is added automatically)
|
||||
|
||||
Returns:
|
||||
JSON string with API response
|
||||
"""
|
||||
try:
|
||||
parsed_body = json.loads(body) if body else {}
|
||||
except json.JSONDecodeError as e:
|
||||
return json.dumps({"status": "ERROR", "message": f"Invalid JSON body: {e}"})
|
||||
|
||||
result = client.request(endpoint, parsed_body)
|
||||
return json.dumps(result, indent=2)
|
||||
|
||||
|
||||
# --- Health Check & App ---
|
||||
|
||||
|
||||
async def health(request):
|
||||
"""Health check endpoint for Docker/orchestration."""
|
||||
if not PORKBUN_API_KEY or not PORKBUN_SECRET_KEY:
|
||||
return JSONResponse(
|
||||
{"status": "unhealthy", "reason": "Missing API credentials"},
|
||||
status_code=503,
|
||||
)
|
||||
|
||||
# Test API connectivity
|
||||
result = client.ping()
|
||||
if result.get("status") == "SUCCESS":
|
||||
return JSONResponse(
|
||||
{
|
||||
"status": "healthy",
|
||||
"mode": "INFRA"
|
||||
if ALLOW_INFRA
|
||||
else ("WRITE" if ALLOW_WRITES else "READ"),
|
||||
}
|
||||
)
|
||||
|
||||
return JSONResponse(
|
||||
{"status": "unhealthy", "reason": result.get("message", "API ping failed")},
|
||||
status_code=503,
|
||||
)
|
||||
|
||||
|
||||
def create_app() -> Starlette:
|
||||
"""Create the ASGI application."""
|
||||
mcp_app = mcp.http_app()
|
||||
return Starlette(
|
||||
routes=[
|
||||
Route("/health", health),
|
||||
Mount("/", app=mcp_app),
|
||||
],
|
||||
lifespan=mcp_app.lifespan,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
port = int(os.getenv("PORT", "8000"))
|
||||
uvicorn.run(create_app(), host="0.0.0.0", port=port)
|
||||
Reference in New Issue
Block a user