main tooling
This commit is contained in:
parent
004d5614e2
commit
a160d00082
7 changed files with 2239 additions and 32 deletions
109
Makefile
109
Makefile
|
|
@ -1,24 +1,19 @@
|
|||
# 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
|
||||
@echo "Ghost MCP Development Commands"
|
||||
@echo "============================="
|
||||
@echo ""
|
||||
@echo "Available targets:"
|
||||
@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
|
||||
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..."
|
||||
@if command -v uv >/dev/null 2>&1; then \
|
||||
echo "✅ uv is already installed"; \
|
||||
|
|
@ -29,18 +24,34 @@ install-uv: ## Install uv package manager
|
|||
echo "✅ uv installed successfully"; \
|
||||
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..."
|
||||
@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; \
|
||||
fi
|
||||
uv sync
|
||||
@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)
|
||||
@echo "📦 Installing Python dependencies with pip..."
|
||||
python -m pip install -e .
|
||||
python -m pip install -e ".[dev]"
|
||||
@echo "✅ Dependencies installed successfully"
|
||||
|
||||
# Docker environment
|
||||
|
|
@ -84,7 +95,25 @@ setup-tokens: ## Extract API keys from Ghost database and create .env file
|
|||
@echo "🔑 Setting up API tokens..."
|
||||
./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
|
||||
@if [ ! -f .env ]; then \
|
||||
echo "❌ .env file not found. Run 'make setup-tokens' first"; \
|
||||
|
|
@ -92,16 +121,9 @@ test-connection: ## Test Ghost API connectivity
|
|||
fi
|
||||
@python scripts/test-connection.py
|
||||
|
||||
test: check-deps test-connection ## Run all tests
|
||||
@echo "🧪 Running comprehensive tests..."
|
||||
@echo "Testing MCP tools registration..."
|
||||
@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!"
|
||||
# Clean up test artifacts
|
||||
clean-test: ## Clean up test artifacts
|
||||
rm -rf .coverage htmlcov/ .pytest_cache/ tests/__pycache__/ __pycache__/
|
||||
|
||||
# Running the server
|
||||
run: check-deps ## Run the Ghost MCP server
|
||||
|
|
@ -165,17 +187,40 @@ check-deps: ## Check if all dependencies are available
|
|||
exit 1; \
|
||||
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..."
|
||||
docker-compose down -v
|
||||
rm -f .env
|
||||
find . -type f -name "*.pyc" -delete || true
|
||||
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 \
|
||||
uv clean; \
|
||||
fi
|
||||
@echo "✅ Cleanup complete"
|
||||
|
||||
# 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 "🎉 Ghost MCP setup complete!"
|
||||
@echo ""
|
||||
|
|
|
|||
259
README.md
Normal file
259
README.md
Normal 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** 🚀
|
||||
|
|
@ -27,6 +27,7 @@ dev = [
|
|||
"pytest>=7.4.0",
|
||||
"pytest-asyncio>=0.21.0",
|
||||
"pytest-cov>=4.1.0",
|
||||
"pytest-xdist>=3.3.0",
|
||||
"black>=23.0.0",
|
||||
"ruff>=0.1.0",
|
||||
"mypy>=1.7.0",
|
||||
|
|
|
|||
1
tests/__init__.py
Normal file
1
tests/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
"""Tests for Ghost MCP server."""
|
||||
29
tests/test_client.py
Normal file
29
tests/test_client.py
Normal 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
59
tests/test_models.py
Normal 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"
|
||||
Loading…
Add table
Reference in a new issue