Skip to main content
Glama

MCPeasy

README.md22.6 kB
# MCPeasy _the easiest way to set-up and self-host your own multi-MCP server with streamable http transport and multi-client and key management_ A production-grade multi-tenant MCP server that provides different tools and configurations to different clients using API key-based routing. ## Architecture - **FastMCP 2.6**: Core MCP implementation following https://gofastmcp.com/llms-full.txt - **FastAPI**: Web framework with API key-based URL routing - **PostgreSQL**: Multi-tenant data storage with SQLAlchemy - **Streamable HTTP**: All subservers provide streamable transport - **Multi-tenancy**: Clients can have multiple API keys with tool-specific configurations ## Key Features - **Multi-tenant design**: Clients manage multiple rotatable API keys - **Per-tool configuration**: Each client can configure tools differently (e.g., custom email addresses) - **Dynamic tool sets**: Different clients get different tool combinations - **Tool auto-discovery**: Modular tool system with automatic registration - **Custom tools support**: Add organization-specific tools in your fork with namespaced directories - **Per-resource configuration**: Each client can access different resources with custom settings - **Dynamic resource sets**: Different clients get different resource combinations - **Resource auto-discovery**: Modular resource system with automatic registration - **Enhanced tool responses**: Multiple content types (text, JSON, markdown, file) for optimal LLM integration - **Environment-based discovery**: Simple environment variable configuration for tool/resource enablement - **Shared infrastructure**: Database, logging, and configuration shared across servers - **Admin interface**: Web-based client and API key management with CORE/CUSTOM tool source badges - **Production ready**: Built for Fly deployment with Neon database - **High performance**: Background task processing, request timeouts, configuration caching, and optimized database connections ## Quick Start 1. **Setup environment**: ```bash cp .env.example .env # Edit .env with your database URL, admin password, and session secret ``` 2. **Start all services with Docker Compose** (recommended): ```bash docker-compose up ``` 3. **Access the services**: ``` http://localhost:8000 # Main API endpoint http://localhost:3000 # Admin interface (on prod: https://yourdomain.com/admin). Log in with username 'superadmin', pass: your 'SUPERADMIN_PASSWORD' from .env http://localhost:8080 # Database inspector (Adminer) ``` That's it! Docker Compose handles all dependencies, database setup, and migrations automatically. ## API Endpoints - `GET /health` - Health check - `GET /admin` - Admin login page - `GET /admin/clients` - Client management dashboard - `POST /admin/clients` - Create new client - `GET /admin/clients/{id}/keys` - Manage API keys for client - `POST /admin/clients/{id}/keys` - Generate new API key - `GET /admin/clients/{id}/tools` - Configure tools for client - `GET|POST /mcp/{api_key}` - MCP endpoint (streamable) ## Client & API Key Management ### Creating Clients 1. Visit `/` (in development localhost:3000) and login with superadmin password 2. Create client with name and description 3. Generate API keys for the client 4. Configure tools and resources with their settings per client ### Managing API Keys - **Multiple keys per client**: Production, staging, development keys - **Key rotation**: Generate new keys without losing configuration - **Expiry management**: Set expiration dates for keys - **Secure deletion**: Deactivate compromised keys immediately ### Tool Configuration Each client must explicitly configure tools to access them: - **Simple tools**: `echo`, `get_weather` - click "Add" to enable (no configuration needed) - **Configurable tools**: `send_email` - click "Configure" to set from address, SMTP settings - **Per-client settings**: Same tool, different configuration per client - **Strict access control**: Only configured tools are visible and callable ### Available Tools **Namespaced Tool System:** All tools are organized in namespaces for better organization and conflict avoidance: **Core Tools (namespace: `core/`):** - `core/echo` - Simple echo tool for testing (no configuration needed) - `core/weather` - Weather information (no configuration needed) - `core/send_email` - Send emails (requires: from_email, optional: smtp_server) - `core/datetime` - Date and time utilities - `core/scrape` - Web scraping functionality - `core/youtube_lookup` - YouTube video information **Custom Tools (namespace: `{org}/`):** - `myorg/send_invoice` - Custom tool would live here - Custom tools can be added in organization-specific namespaces - Each deployment can control which tools are available via environment configuration - Custom tools show with purple "CUSTOM" badges in admin UI vs blue "CORE" badges **Tool Discovery:** - Use `TOOLS=__all__` to automatically discover and enable all available tools - Or specify exact tools: `TOOLS='core/echo,core/weather,myorg/send_invoice'` - Directory structure: `src/tools/{namespace}/{tool_name}/tool.py` ### Tool Call Tracking All tool executions are automatically tracked in the database for monitoring and auditing: - **Complete tracking**: Input arguments, output data, execution time, and errors - **Per-client logging**: Track usage patterns by client and API key - **Performance monitoring**: Execution time tracking in milliseconds - **Error logging**: Failed tool calls with detailed error messages - **Automatic**: No configuration needed - all tool calls are logged transparently ### Resource Configuration Each client must explicitly configure resources to access them: - **Simple resources**: click "Add" to enable with default settings - **Configurable resources**: click "Configure" to set category filters, article limits, search permissions - **Per-client settings**: Same resource, different configuration per client (e.g., different category access) - **Strict access control**: Only configured resources are visible and accessible ### Available Resources **Namespaced Resource System:** Resources follow the same namespacing pattern as tools for better organization: **Custom Resources (namespace: `{org}/`):** - `myorg/knowledge` - Example of a namespaced resource - Custom resources can be added in organization-specific namespaces - Each deployment can control which resources are available via environment configuration **Resource Discovery:** - Use `RESOURCES=__all__` to automatically discover and enable all available resources - Or specify exact resources: `RESOURCES='myorg/product_catalog'` - Directory structure: `src/resources/{namespace}/{resource_name}/resource.py` ### Resource Auto-Seeding Resources can automatically seed initial data when their table is empty, perfect for: - **Reference data**: Countries, categories, product catalogs - **Demo content**: Sample articles, documentation - **Initial configuration**: Default settings, presets **How It Works:** 1. Resource checks if its table is empty on first initialization 2. If empty, loads seed data from configured source (CSV/JSON file or URL) 3. Inserts data into database with proper field mapping 4. Only runs once - subsequent startups skip seeding **Setup Example:** ```python class ProductCatalogResource(BaseResource): name = "myorg/products" seed_source = "seeds/initial_products.csv" # Local file # seed_source = "https://cdn.myorg.com/products.csv" # Or remote URL async def _get_model_class(self): from .models import Product return Product ``` **Supported Formats:** - **CSV**: Column names match model fields, empty strings become NULL - **JSON**: Array of objects with field names as keys - **Remote URLs**: Fetch seed data from CDNs or APIs **Example Seed Files:** ```csv # seeds/products.csv id,name,category,price,description 1,Widget A,widgets,29.99,Premium widget 2,Widget B,widgets,39.99,Deluxe widget ``` ```json // seeds/products.json [ {"id": 1, "name": "Widget A", "category": "widgets", "price": 29.99}, {"id": 2, "name": "Widget B", "category": "widgets", "price": 39.99} ] ``` ## Configuration ### Environment Variables ```bash DATABASE_URL=postgresql://user:pass@host:port/db PORT=8000 SESSION_SECRET=your_secure_session_key_here SUPERADMIN_PASSWORD=your_secure_password # Tool and resource discovery TOOLS=__all__ # Enable all discovered tools, or list specific ones like 'core/echo,m38/calculator' RESOURCES=__all__ # Enable all discovered resources, or list specific ones like 'knowledge' # Tool execution queue configuration TOOL_MAX_WORKERS=20 # Max concurrent tool executions (default: 20) TOOL_QUEUE_SIZE=200 # Max queued requests (default: 200) ``` see .env.example for more ### Multi-Tenant Architecture The system uses three main entities: - **Clients**: Organizations or users (e.g., "ACME Corp") with UUID identifiers - **API Keys**: Multiple rotatable keys per client - **Tool Configurations**: Per-client tool settings stored as JSON with strict access control - **Resource Configurations**: Per-client resource settings stored as JSON with strict access control ## Custom Tools Development MCPeasy supports adding organization-specific tools using a simplified namespaced directory structure. When forking this repository, you can add your custom tools directly without worrying about merge conflicts. ### Quick Custom Tool Setup 1. **Fork the repository**: Create your own fork of mcpeasy 2. **Create namespace directory**: `mkdir -p src/tools/yourorg` 3. **Add your tool**: Create `src/tools/yourorg/yourtool/tool.py` with your tool implementation 4. **Auto-discovery**: Tool automatically discovered as `yourorg/yourtool` 5. **Configure environment**: - Use `TOOLS=__all__` to enable all tools automatically - Or specify: `TOOLS='core/echo,yourorg/yourtool'` 6. **Enable for clients**: Use admin UI to configure tools per client 7. **Stay updated**: Pull upstream changes from mcpeasy main branch when needed ### Directory Structure ``` src/tools/ ├── core/ # Core mcpeasy tools │ ├── echo/ │ ├── weather/ │ └── send_email/ ├── m38/ # Example custom namespace │ └── calculator/ └── yourorg/ # Your organization's tools ├── invoice_generator/ ├── crm_integration/ └── custom_reports/ ``` ### Enhanced Tool Response Types Custom tools support multiple content types for optimal LLM integration: ```python # Structured data (recommended for LLM processing) return ToolResult.json({"result": 42, "status": "success"}) # Human-readable text return ToolResult.text("Operation completed successfully!") # Markdown formatting return ToolResult.markdown("# Success\n\n**Result**: 42") # File references return ToolResult.file("s3://bucket/report.pdf", mime_type="application/pdf") # Error handling return ToolResult.error("Invalid operation: division by zero") ``` ### Running Synchronous Code in Tools If your custom tool needs to run synchronous (blocking) code, use `asyncio.to_thread()` to avoid blocking the async event loop: ```python import asyncio from src.tools.base import BaseTool, ToolResult class MyCustomTool(BaseTool): @property def name(self) -> str: return "my_custom_tool" async def execute(self, arguments: dict, config: dict = None) -> ToolResult: # For CPU-bound or blocking I/O operations result = await asyncio.to_thread(self._blocking_operation, arguments) return ToolResult.json(result) def _blocking_operation(self, arguments: dict): # This runs in a thread pool, safe to block import time time.sleep(5) # Example: blocking operation return {"processed": True, "data": arguments} ``` **Important**: Never use blocking operations directly in the `execute()` method as it will block the entire event loop and affect other tool executions. ## Custom Resources Development MCPeasy supports adding organization-specific resources with automatic data seeding capabilities. Just like with tools, add your custom resources directly to your fork. ### Quick Custom Resource Setup 1. **In your fork**: Navigate to your mcpeasy fork 2. **Create namespace directory**: `mkdir -p src/resources/yourorg` 3. **Add your resource**: Create `src/resources/yourorg/yourresource/resource.py` with implementation 4. **Auto-discovery**: Resource automatically discovered as `yourorg/yourresource` 5. **Configure environment**: - Use `RESOURCES=__all__` to enable all resources automatically - Or specify: `RESOURCES='knowledge,yourorg/yourresource'` 6. **Optional seeding**: Add `seed_source` and `seeds/` directory for initial data 7. **Enable for clients**: Use admin UI to configure resources per client ### Directory Structure ``` src/resources/ ├── knowledge/ # Core resources (simple) ├── myorg/ # Namespaced resources │ └── knowledge/ │ ├── resource.py │ ├── models.py │ └── seeds/ │ ├── articles.csv │ └── categories.json └── yourorg/ # Your organization's resources ├── product_catalog/ │ ├── resource.py │ ├── models.py │ └── seeds/ │ └── products.csv └── customer_data/ ├── resource.py └── models.py ``` ### Custom Resource with Auto-Seeding ```python from src.resources.base import BaseResource from src.resources.types import MCPResource, ResourceContent class ProductCatalogResource(BaseResource): name = "yourorg/products" description = "Product catalog with pricing and inventory" uri_scheme = "products" # Optional: Auto-seed when table is empty seed_source = "seeds/initial_products.csv" async def _get_model_class(self): from .models import Product return Product async def list_resources(self, config=None): # Implementation with client-specific filtering pass async def read_resource(self, uri: str, config=None): # Implementation with access control pass ``` ### Templates and Documentation - **Templates**: Complete tool/resource templates in `templates/` directory with auto-seeding examples - **Best practices**: Examples show proper dependency management, configuration, and data seeding - **Namespace organization**: Clean separation between core and custom tools/resources - **Environment variable discovery**: Simple TOOLS and RESOURCES configuration - **Seed data examples**: CSV and JSON seed file templates included ## Development ### Docker Development (Recommended) ```bash # Start all services with live reload docker-compose up # Access services: # - App: http://localhost:8000 # - Admin: http://localhost:3000 # - Database Inspector: http://localhost:8080 ``` Live reload on both frontend and backend ### Database Inspector (Adminer) When running with Docker Compose, Adminer provides a lightweight web interface to inspect your PostgreSQL database: - **URL**: `http://localhost:8080` - **Login credentials**: - Server: `db` - Username: `postgres` - Password: `postgres` - Database: `mcp` **Features**: - Browse all tables (clients, api_keys, tool_configurations, resource_configurations, tool_calls) - View table data and relationships - Run SQL queries - Export data - Monitor database schema changes - Analyze tool usage patterns and performance metrics ### Local Development - **Dependencies**: Managed with `uv` - **Code structure**: Modular design with SQLAlchemy models, session auth, admin UI - **Database**: PostgreSQL with async SQLAlchemy and Alembic migrations - **Authentication**: Session-based admin authentication with secure cookies - **Migrations**: Automatic database migrations with Alembic - **Testing**: Run development server with auto-reload ## Testing MCP Endpoints ### Using MCP Inspector (Recommended) 1. **Get token URL**: From admin dashboard, copy the MCP URL for your token 2. **Install inspector**: `npx @modelcontextprotocol/inspector` 3. **Open inspector**: Visit http://localhost:6274 in browser (include proxy auth if needed, following instructions at inspector launch) 4. **Add server**: Enter your MCP URL: `http://localhost:8000/mcp/{token}` 5. **Configure tools and resources**: In admin interface, add/configure tools and resources for your client 6. **Test functionality**: Click on configured tools and resources to test them (unconfigured items won't appear) **✅ Verified Working**: The MCP Inspector successfully connects and displays only configured tools and resources! ### Manual Testing ```bash # Test capability discovery curl http://localhost:8000/mcp/{your_api_key} # Test echo tool (no configuration needed) curl -X POST http://localhost:8000/mcp/{your_api_key} \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "echo", "arguments": {"message": "Hello MCP!"} } }' # Test send_email tool (uses client-specific configuration) curl -X POST http://localhost:8000/mcp/{your_api_key} \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "send_email", "arguments": { "to": "user@example.com", "subject": "Test", "body": "This uses my configured from address!" } } }' # Test knowledge resource (uses client-specific configuration) curl -X POST http://localhost:8000/mcp/{your_api_key} \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "resources/read", "params": { "uri": "knowledge://search?q=api" } }' ``` ## Database Migrations The system uses Alembic for database migrations with **automatic execution on Docker startup** for the best developer experience. ### Migration Workflow (Simplified) ```bash # 1. Create a new migration after making model changes ./migrate.sh create "add user preferences table" # 2. Restart the app (migrations apply automatically) docker-compose restart app # That's it! No manual migration commands needed. ``` ### Available Migration Commands The `./migrate.sh` script provides all migration functionality: ```bash # Create new migration (auto-starts database if needed) ./migrate.sh create "migration message" # Apply pending migrations manually (optional) ./migrate.sh upgrade # Check current migration status ./migrate.sh status # View migration history ./migrate.sh history ``` ### How It Works 1. **Development**: Use `./migrate.sh create "message"` to generate migration files 2. **Automatic Application**: Migrations run automatically when Docker containers start 3. **No Manual Steps**: The Docker containers handle `alembic upgrade head` on startup 4. **Database Dependency**: Docker waits for database health check before running migrations 5. **Volume Mounting**: Migration files are immediately available in containers via volume mounts ### Model Organization Models are organized in separate files by domain: - `src/models/base.py` - SQLAlchemy Base class - `src/models/client.py` - Client and APIKey models - `src/models/configuration.py` - Tool and Resource configurations - `src/models/knowledge.py` - Knowledge base models - `src/models/tool_call.py` - Tool call tracking and auditing ### Migration Workflow 1. **Make model changes** in the appropriate model files 2. **Generate migration**: The system auto-detects changes and creates migration files 3. **Review migration**: Check the generated SQL in `src/migrations/versions/` 4. **Deploy**: Migrations run automatically on startup in production ### Production Migration Behavior - ✅ **Automatic execution**: Migrations run on app startup - ✅ **Safe rollouts**: Failed migrations prevent app startup - ✅ **Version tracking**: Database tracks current migration state - ✅ **Idempotent**: Safe to run multiple times ## Performance & Scalability The system is optimized for production workloads with several performance enhancements: - **Queue-based execution**: Bounded concurrency with configurable worker pools prevents server overload - **Fair scheduling**: FIFO queue ensures all clients get served during traffic bursts - **Background processing**: Tool call logging moved to background tasks for faster response times - **Extended timeouts**: 3-minute timeouts support long-running tools (configurable) - **Configuration caching**: 5-minute TTL cache reduces database queries for configuration lookups - **Connection pooling**: Optimized PostgreSQL connection management with pre-ping validation - **Multi-worker setup**: 2 workers optimized for Fly.io deployment with automatic recycling - **Queue monitoring**: Real-time queue metrics available at `/metrics/queue` endpoint ### Queue Configuration Control tool execution concurrency and queue behavior: ```bash # Environment variables for .env TOOL_MAX_WORKERS=20 # Concurrent tool executions (default: 20) TOOL_QUEUE_SIZE=200 # Maximum queued requests (default: 200) # Recommended settings by server size: # Small servers: TOOL_MAX_WORKERS=5, TOOL_QUEUE_SIZE=50 # Production: TOOL_MAX_WORKERS=50, TOOL_QUEUE_SIZE=500 ``` ### Queue Monitoring ```bash # Check queue health and capacity curl http://localhost:8000/metrics/queue # Response includes: { "queue_depth": 3, # Current requests waiting "max_workers": 20, # Maximum concurrent executions "max_queue_size": 200, # Maximum queue capacity "workers_started": 20, # Number of active workers "is_started": true # Queue system status } ``` ## Deployment - **Platform**: Recommended deployment with Fly.io. NB! In some situations (e.g. if your MCP client connected to this runs inside cloudflare workers - you should set `force_https = false` in your fly.toml, because otherwise you may get endless redirect issues on the MCP client side) - **Database**: Any postgres will do, tested on Neon PostgreSQL with automatic migrations - **Environment**: Production-ready with proper error handling and migration safety - **Workers**: 2 Uvicorn workers with 1000 request recycling for optimal memory management

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/GeorgeStrakhov/mcpeasy'

If you have feedback or need assistance with the MCP directory API, please join our Discord server