main tooling

This commit is contained in:
Luiz Felipe Costa 2025-09-23 02:54:18 -03:00
parent 004d5614e2
commit a160d00082
7 changed files with 2239 additions and 32 deletions

109
Makefile
View file

@ -1,24 +1,19 @@
# Ghost MCP Development Makefile # Ghost MCP Development Makefile
.PHONY: help install install-uv start-ghost stop-ghost setup-tokens test test-connection run dev clean logs status check-deps .PHONY: help install install-local deps-install-python deps-install-dev deps-deps-install-uv install-pip venv start-ghost stop-ghost restart-ghost setup-tokens test test-unit test-integration test-coverage test-fast test-parallel test-connection clean-test run dev format lint clean logs status check-deps setup docs
# Default target .PHONY: help
help: ## Show this help message help: ## Show this help message
@echo "Ghost MCP Development Commands" @echo "Available targets:"
@echo "============================="
@echo ""
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
@echo ""
@echo "Quick start:"
@echo " make install-uv # Install uv package manager (if not installed)"
@echo " make install # Install Python dependencies"
@echo " make start-ghost # Start Ghost and database containers"
@echo " make setup-tokens # Extract API keys and create .env file"
@echo " make test # Test the implementation"
@echo " make run # Run the MCP server"
# Python environment setup # Python environment setup
install-uv: ## Install uv package manager venv: ## Create a virtual environment
python3 -m venv venv
./venv/bin/pip install -U pip setuptools
./venv/bin/pip install -e ".[dev]"
deps-deps-install-uv: ## Install uv package manager
@echo "📦 Installing uv package manager..." @echo "📦 Installing uv package manager..."
@if command -v uv >/dev/null 2>&1; then \ @if command -v uv >/dev/null 2>&1; then \
echo "✅ uv is already installed"; \ echo "✅ uv is already installed"; \
@ -29,18 +24,34 @@ install-uv: ## Install uv package manager
echo "✅ uv installed successfully"; \ echo "✅ uv installed successfully"; \
fi fi
install: ## Install Python dependencies using uv # Install the MCP server system-wide
install: ## Install the MCP server system-wide
claude mcp remove ghost-mcp -s user || true
claude mcp add ghost-mcp -s user -- \
bash -c "cd $(PWD) && uv run python -m ghost_mcp.server"
# Install the MCP server in the project scope only
install-local: ## Install the MCP server in the project scope only
claude mcp remove ghost-mcp || true
claude mcp add ghost-mcp -- \
bash -c "cd $(PWD) && uv run python -m ghost_mcp.server"
deps-install-python: ## Install Python dependencies using uv
@echo "📦 Installing Python dependencies with uv..." @echo "📦 Installing Python dependencies with uv..."
@if ! command -v uv >/dev/null 2>&1; then \ @if ! command -v uv >/dev/null 2>&1; then \
echo "❌ uv not found. Run 'make install-uv' first"; \ echo "❌ uv not found. Run 'make deps-deps-install-uv' first"; \
exit 1; \ exit 1; \
fi fi
uv sync uv sync
@echo "✅ Dependencies installed successfully" @echo "✅ Dependencies installed successfully"
# Install dev dependencies
deps-install-dev: ## Install development dependencies
uv sync --extra dev
install-pip: ## Install Python dependencies using pip (fallback) install-pip: ## Install Python dependencies using pip (fallback)
@echo "📦 Installing Python dependencies with pip..." @echo "📦 Installing Python dependencies with pip..."
python -m pip install -e . python -m pip install -e ".[dev]"
@echo "✅ Dependencies installed successfully" @echo "✅ Dependencies installed successfully"
# Docker environment # Docker environment
@ -84,7 +95,25 @@ setup-tokens: ## Extract API keys from Ghost database and create .env file
@echo "🔑 Setting up API tokens..." @echo "🔑 Setting up API tokens..."
./scripts/setup-tokens.sh ./scripts/setup-tokens.sh
# Testing # Testing targets
test: ## Run all tests
uv run pytest tests/ -v
test-unit: ## Run unit tests only
uv run pytest tests/test_models.py tests/test_client.py -v
test-integration: ## Run integration tests
uv run pytest tests/test_mcp_tools.py tests/test_server.py -v
test-coverage: ## Run tests with coverage report
uv run pytest tests/ --cov=. --cov-report=html --cov-report=term
test-fast: ## Run tests with fail-fast and short traceback
uv run pytest tests/ -x --tb=short
test-parallel: ## Run tests in parallel
uv run pytest tests/ -n auto
test-connection: ## Test Ghost API connectivity test-connection: ## Test Ghost API connectivity
@if [ ! -f .env ]; then \ @if [ ! -f .env ]; then \
echo "❌ .env file not found. Run 'make setup-tokens' first"; \ echo "❌ .env file not found. Run 'make setup-tokens' first"; \
@ -92,16 +121,9 @@ test-connection: ## Test Ghost API connectivity
fi fi
@python scripts/test-connection.py @python scripts/test-connection.py
test: check-deps test-connection ## Run all tests # Clean up test artifacts
@echo "🧪 Running comprehensive tests..." clean-test: ## Clean up test artifacts
@echo "Testing MCP tools registration..." rm -rf .coverage htmlcov/ .pytest_cache/ tests/__pycache__/ __pycache__/
@python -c "\
import sys; \
sys.path.insert(0, 'src'); \
from ghost_mcp.server import mcp; \
print(f'✅ FastMCP server initialized'); \
print(f' Tools registered: {len([attr for attr in dir(mcp) if not attr.startswith(\"_\")])}+')"
@echo "✅ All tests passed!"
# Running the server # Running the server
run: check-deps ## Run the Ghost MCP server run: check-deps ## Run the Ghost MCP server
@ -165,17 +187,40 @@ check-deps: ## Check if all dependencies are available
exit 1; \ exit 1; \
fi fi
clean: ## Clean up development environment # Code quality
.PHONY: format
format: ## Format code with ruff and black
@echo "🎨 Formatting code..."
uv run ruff format .
uv run black .
@echo "✅ Code formatting completed"
.PHONY: lint
lint: ## Run linting with ruff and mypy
@echo "🔍 Running linters..."
uv run ruff check .
uv run mypy src/
@echo "✅ Linting completed"
clean: ## Clean up temporary files and development environment
@echo "🧹 Cleaning up development environment..." @echo "🧹 Cleaning up development environment..."
docker-compose down -v find . -type f -name "*.pyc" -delete || true
rm -f .env find . -type d -name "__pycache__" -delete || true
find . -type d -name ".pytest_cache" -delete || true
rm -rf .ruff_cache/ || true
rm -rf venv/ || true
rm -rf .pytest_cache/ || true
rm -rf htmlcov/ || true
rm -f .coverage* coverage.xml || true
docker-compose down -v || true
rm -f .env || true
@if command -v uv >/dev/null 2>&1; then \ @if command -v uv >/dev/null 2>&1; then \
uv clean; \ uv clean; \
fi fi
@echo "✅ Cleanup complete" @echo "✅ Cleanup complete"
# Development workflow # Development workflow
setup: install-uv install start-ghost setup-tokens ## Complete setup from scratch setup: deps-deps-install-uv deps-install-python start-ghost setup-tokens ## Complete setup from scratch
@echo "" @echo ""
@echo "🎉 Ghost MCP setup complete!" @echo "🎉 Ghost MCP setup complete!"
@echo "" @echo ""

259
README.md Normal file
View file

@ -0,0 +1,259 @@
# Ghost MCP Server
A comprehensive Model Context Protocol (MCP) server for Ghost CMS, providing both read-only Content API and read/write Admin API access through FastMCP.
## 🎯 Features
- **Complete Ghost API Coverage**: Both Content API (read-only) and Admin API (read/write)
- **15+ MCP Tools**: Posts, pages, tags, authors, settings, and more
- **Modern Python Implementation**: FastMCP 2.12.3 with async/await
- **JWT Authentication**: Secure Admin API access with token caching
- **Robust Error Handling**: 5 error categories with comprehensive logging
- **Configuration Management**: Environment variables with precedence
- **Development Tools**: Complete Docker setup and automation scripts
## 🚀 Quick Start
### Prerequisites
- Docker and Docker Compose
- Python 3.10+ (with uv recommended)
### Setup
```bash
# Clone the repository
git clone <repository-url>
cd ghost-mcp
# Complete setup from scratch
make setup
# Or step by step:
make install-uv # Install uv package manager
make install # Install Python dependencies
make start-ghost # Start Ghost and database
make setup-tokens # Extract API keys and create .env
make test # Test the implementation
```
### Usage
```bash
# Run the MCP server
make run
# Development mode with auto-reload
make dev
# Check system status
make status
# View container logs
make logs
```
## 📋 Available MCP Tools
### Content API Tools (Read-only)
- `get_posts` - Get published posts with filtering/pagination
- `get_post_by_id` - Get single post by ID
- `get_post_by_slug` - Get single post by slug
- `search_posts` - Search posts by title/content
- `get_pages` - Get published pages
- `get_page_by_id` - Get single page by ID
- `get_page_by_slug` - Get single page by slug
- `get_tags` - Get tags with filtering
- `get_tag_by_id` - Get single tag by ID
- `get_tag_by_slug` - Get single tag by slug
- `get_authors` - Get authors
- `get_author_by_id` - Get single author by ID
- `get_author_by_slug` - Get single author by slug
- `get_settings` - Get public settings
- `get_site_info` - Get basic site information
### Admin API Tools (Read/Write)
- `create_post` - Create new posts with full options
- `update_post` - Update existing posts
- `delete_post` - Delete posts
- `get_admin_posts` - Get posts including drafts
- `create_page` - Create new pages
- `create_tag` - Create new tags
### Utility Tools
- `check_ghost_connection` - Test connectivity and configuration
## 🔧 Configuration
Configuration is managed through environment variables with precedence:
1. Environment variables
2. `.env` file
3. Default values
### Required Variables
```bash
GHOST_URL=http://localhost:2368
GHOST_CONTENT_API_KEY=your_content_api_key_here
GHOST_ADMIN_API_KEY=your_admin_api_key_here
```
### Optional Variables
```bash
GHOST_VERSION=v5.0
GHOST_MODE=auto # auto, readonly, readwrite
GHOST_TIMEOUT=30
GHOST_MAX_RETRIES=3
GHOST_RETRY_BACKOFF_FACTOR=2.0
LOG_LEVEL=info # debug, info, warning, error
LOG_STRUCTURED=true
LOG_REQUEST_ID=true
```
## 🛠️ Development
### Project Structure
```
ghost-mcp/
├── src/ghost_mcp/
│ ├── server.py # FastMCP server entry point
│ ├── client.py # Ghost API client
│ ├── config.py # Configuration management
│ ├── auth/ # Authentication modules
│ ├── tools/
│ │ ├── content/ # Content API tools
│ │ └── admin/ # Admin API tools
│ ├── types/ # Type definitions
│ └── utils/ # Utilities
├── scripts/ # Setup and test scripts
├── contracts/ # API documentation
├── docker-compose.yml # Ghost + MySQL setup
├── Makefile # Development commands
└── pyproject.toml # Python project config
```
### Available Commands
```bash
make help # Show all commands
make setup # Complete setup from scratch
make install # Install dependencies with uv
make start-ghost # Start Ghost containers
make setup-tokens # Generate API keys
make test # Run tests
make test-connection # Test API connectivity
make run # Run MCP server
make status # Check system status
make clean # Clean up everything
```
## 🐳 Docker Environment
The project includes a complete Docker Compose setup:
- **Ghost**: Latest Ghost 5.x with Alpine Linux
- **MySQL 8.0**: Database with health checks
- **Development optimized**: Logging, auto-restart, volume persistence
### URLs
- **Ghost Admin**: http://localhost:2368/ghost/
- **Ghost Site**: http://localhost:2368/
- **Database**: localhost:3306
## 🔐 API Authentication
### Content API
- Uses query parameter authentication
- 26-character hex API key
- Read-only access to published content
### Admin API
- Uses JWT token authentication
- Format: `id:secret` (24-char + 64-char hex)
- 5-minute token expiration with automatic renewal
- Full read/write access
## 📊 Error Handling
Comprehensive error handling with 5 categories:
1. **Network Errors**: Connection timeouts, DNS failures
2. **Authentication Errors**: Invalid keys, expired tokens
3. **Ghost API Errors**: Ghost-specific errors with codes
4. **Validation Errors**: Invalid parameters, malformed data
5. **File Upload Errors**: Media upload failures
All errors include:
- Unique error ID
- Category classification
- Context information
- Request ID for tracing
## 🧪 Testing
```bash
# Test Ghost API connectivity
make test-connection
# Run all tests
make test
# Test specific functionality
python scripts/test-connection.py
```
## 📝 Logging
Structured logging with configurable levels:
- **Request IDs**: Track requests across components
- **Structured format**: JSON output for production
- **Context preservation**: Error context and debugging info
- **Performance metrics**: Request timing and retry information
## 🔄 Retry Logic
Robust retry mechanism:
- **Exponential backoff**: Configurable base delay and multiplier
- **Jitter**: Prevents thundering herd problems
- **Max retries**: Configurable retry limits
- **Circuit breaker**: Fail fast after repeated failures
## 📚 Documentation
- **API Contracts**: Complete Ghost API documentation in `contracts/`
- **Type Definitions**: Full type coverage with Pydantic models
- **Examples**: Working examples for all tools
- **Development Guide**: Step-by-step setup and usage
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch
3. Make changes with tests
4. Run `make test` to verify
5. Submit a pull request
## 📄 License
MIT License - see LICENSE file for details
## 🆘 Support
- **Issues**: GitHub issues for bugs and features
- **Documentation**: Check `contracts/` for API details
- **Logs**: Use `make logs` for troubleshooting
- **Status**: Use `make status` for system health
---
**Built with FastMCP 2.12.3 and Ghost 5.x** 🚀

View file

@ -27,6 +27,7 @@ dev = [
"pytest>=7.4.0", "pytest>=7.4.0",
"pytest-asyncio>=0.21.0", "pytest-asyncio>=0.21.0",
"pytest-cov>=4.1.0", "pytest-cov>=4.1.0",
"pytest-xdist>=3.3.0",
"black>=23.0.0", "black>=23.0.0",
"ruff>=0.1.0", "ruff>=0.1.0",
"mypy>=1.7.0", "mypy>=1.7.0",

1
tests/__init__.py Normal file
View file

@ -0,0 +1 @@
"""Tests for Ghost MCP server."""

29
tests/test_client.py Normal file
View file

@ -0,0 +1,29 @@
"""Tests for Ghost API client."""
import pytest
from unittest.mock import AsyncMock, patch
from ghost_mcp.client import GhostClient
from ghost_mcp.config import config
class TestGhostClient:
"""Test Ghost API client."""
def test_client_initialization(self):
"""Test client initialization."""
client = GhostClient()
assert client.base_url.endswith("/")
assert client.timeout == config.ghost.timeout
def test_build_url(self):
"""Test URL building."""
client = GhostClient()
url = client._build_url("posts/", "content")
assert url.endswith("ghost/api/content/posts/")
def test_build_admin_url(self):
"""Test Admin API URL building."""
client = GhostClient()
url = client._build_url("posts/", "admin")
assert url.endswith("ghost/api/admin/posts/")

59
tests/test_models.py Normal file
View file

@ -0,0 +1,59 @@
"""Tests for Ghost MCP models and types."""
import pytest
from pydantic import ValidationError
from ghost_mcp.types.ghost import GhostPost, PostStatus, VisibilityType
from ghost_mcp.types.errors import GhostMCPError, ErrorCategory
from ghost_mcp.config import GhostConfig, LogLevel
class TestGhostModels:
"""Test Ghost data models."""
def test_post_status_enum(self):
"""Test post status enumeration."""
assert PostStatus.DRAFT == "draft"
assert PostStatus.PUBLISHED == "published"
assert PostStatus.SCHEDULED == "scheduled"
def test_visibility_enum(self):
"""Test visibility enumeration."""
assert VisibilityType.PUBLIC == "public"
assert VisibilityType.MEMBERS == "members"
class TestErrorModels:
"""Test error models."""
def test_ghost_mcp_error(self):
"""Test base error class."""
error = GhostMCPError("Test error", ErrorCategory.NETWORK)
assert error.category == ErrorCategory.NETWORK
assert str(error) == "Test error"
assert error.id is not None
def test_error_to_dict(self):
"""Test error serialization."""
error = GhostMCPError("Test error", ErrorCategory.VALIDATION, code="TEST_001")
error_dict = error.to_dict()
assert error_dict["message"] == "Test error"
assert error_dict["category"] == "VALIDATION"
assert error_dict["code"] == "TEST_001"
class TestConfig:
"""Test configuration models."""
def test_ghost_config_defaults(self):
"""Test Ghost configuration defaults."""
config = GhostConfig()
assert str(config.url) == "http://localhost:2368"
assert config.version == "v5.0"
assert config.timeout == 30
def test_log_level_enum(self):
"""Test log level enumeration."""
assert LogLevel.INFO == "info"
assert LogLevel.DEBUG == "debug"
assert LogLevel.ERROR == "error"

1813
uv.lock generated Normal file

File diff suppressed because it is too large Load diff