Skip to main content
Glama
m3-dx.md•22.6 kB
# Milestone 3: Developer Experience **Status:** Partially Implemented (Single-Agent Mode Complete) **Target:** Excellent developer experience with easy setup, validation, deployment, and documentation --- ## Overview M3 focuses on making the gateway easy to use, configure, deploy, and troubleshoot. This milestone adds: - Single-agent mode for simplified development workflows - Configuration validation CLI to catch errors early - Docker container for easy deployment - Comprehensive documentation and examples - Testing infrastructure and utilities - Development tools and helpers **Key Success Metric:** Developers can go from zero to running gateway in <5 minutes with clear documentation and helpful tooling. --- ## Core Components ### 1. Single-Agent Mode ### 2. Configuration Validation CLI ### 3. Docker Container ### 4. Documentation ### 5. Testing Infrastructure ### 6. Development Tools --- ## Detailed Task Checklist ### Single-Agent Mode - [x] Implement default agent fallback - [x] Support `GATEWAY_DEFAULT_AGENT` environment variable - [x] Auto-inject agent_id when missing if default configured - [x] Log when using default agent - [x] Document single-agent use case - [x] Create fallback chain with "default" agent - [x] Priority 1: `GATEWAY_DEFAULT_AGENT` environment variable - [x] Priority 2: Agent named "default" in rules (if `deny_on_missing_agent` is false) - [x] Priority 3: Error if neither configured and `deny_on_missing_agent` is true - [x] Follow principle of least privilege (no implicit "allow all") - [x] Update middleware for single-agent mode - [x] Use fallback chain when agent_id missing - [x] Support optional agent_id parameter in all tools - [x] Maintain audit trail even in single-agent mode - [x] Add new error codes: `FALLBACK_AGENT_NOT_IN_RULES`, `NO_FALLBACK_CONFIGURED` - [ ] Create single-agent configuration helper - [ ] Generate simple config for single developer - [ ] Allow all servers and tools for default agent - [ ] Provide template configuration **Implemented Behavior:** The gateway now supports optional `agent_id` parameter with a secure fallback chain: 1. **Explicit agent_id** in tool call (highest priority) 2. **GATEWAY_DEFAULT_AGENT** environment variable 3. **"default" agent** in gateway rules (if `deny_on_missing_agent` is false) 4. **Error** if none configured **Security:** Follows principle of least privilege. When `deny_on_missing_agent` is `false`, it uses the "default" agent's explicit permissions - never grants implicit "allow all" access. **Example Single-Agent Config:** ```json { "agents": { "developer": { "allow": { "servers": ["*"], "tools": {"*": ["*"]} } } }, "defaults": { "deny_on_missing_agent": false } } ``` **Usage:** ```bash export GATEWAY_DEFAULT_AGENT=developer uv run python main.py # All tool calls now use "developer" agent when agent_id is not provided ``` **Example with Secure "default" Agent:** ```json { "agents": { "developer": { "allow": { "servers": ["*"], "tools": {"*": ["*"]} } }, "default": { "deny": { "servers": ["*"] } } }, "defaults": { "deny_on_missing_agent": false } } ``` ### Configuration Validation CLI - [ ] Create validation command - [ ] Add `uv run python -m src.cli validate` command - [ ] Validate .mcp.json structure - [ ] Validate .mcp-gateway-rules.json structure - [ ] Check for common errors - [ ] Provide helpful error messages - [ ] Implement validation checks - [ ] Validate JSON syntax (error) - [ ] Check for required fields (error) - [ ] Warn about servers in rules not in mcp-config (warning only - runtime allows this) - [ ] Validate environment variable references (warning) - [ ] Check for conflicting rules (info) - [ ] Test connectivity to configured servers (warning) - [ ] Add config generation commands - [ ] `generate-config --single-agent` for simple setup - [ ] `generate-config --multi-agent` for team setup - [ ] Interactive configuration builder - [ ] Create config examples - [ ] Example configs for common scenarios - [ ] Documentation for each config option - [ ] Best practices guide **Code Reference:** ```python # src/cli.py import click import json from pathlib import Path from typing import List, Tuple @click.group() def cli(): """Agent MCP Gateway CLI.""" pass @cli.command() @click.option("--mcp-config", default="./config/.mcp.json", help="Path to MCP servers config") @click.option("--rules", default="./config/.mcp-gateway-rules.json", help="Path to gateway rules") def validate(mcp_config: str, rules: str): """Validate gateway configuration files.""" click.echo("šŸ” Validating configuration files...") errors = [] warnings = [] # Validate MCP config mcp_errors, mcp_warnings = validate_mcp_config(mcp_config) errors.extend(mcp_errors) warnings.extend(mcp_warnings) # Validate rules config rules_errors, rules_warnings = validate_rules_config(rules, mcp_config) errors.extend(rules_errors) warnings.extend(rules_warnings) # Report results if errors: click.echo(f"\nāŒ Found {len(errors)} error(s):") for error in errors: click.echo(f" • {error}", err=True) if warnings: click.echo(f"\nāš ļø Found {len(warnings)} warning(s):") for warning in warnings: click.echo(f" • {warning}") if not errors and not warnings: click.echo("\nāœ… Configuration is valid!") return if errors: raise click.ClickException("Configuration validation failed") def validate_mcp_config(path: str) -> Tuple[List[str], List[str]]: """Validate MCP servers configuration.""" errors = [] warnings = [] try: with open(path, 'r') as f: config = json.load(f) except FileNotFoundError: errors.append(f"MCP config file not found: {path}") return errors, warnings except json.JSONDecodeError as e: errors.append(f"Invalid JSON in MCP config: {e}") return errors, warnings # Check structure if "mcpServers" not in config: errors.append("MCP config missing 'mcpServers' key") return errors, warnings servers = config["mcpServers"] if not servers: warnings.append("No MCP servers configured") # Validate each server for name, server_config in servers.items(): has_command = "command" in server_config has_url = "url" in server_config if not has_command and not has_url: errors.append(f"Server '{name}' missing 'command' or 'url'") if has_command and has_url: warnings.append(f"Server '{name}' has both 'command' and 'url' (will use url)") # Check for env var references if "env" in server_config: for key, value in server_config["env"].items(): if isinstance(value, str) and value.startswith("${") and value.endswith("}"): env_var = value[2:-1] import os if env_var not in os.environ: warnings.append(f"Server '{name}' references undefined env var: {env_var}") return errors, warnings def validate_rules_config(rules_path: str, mcp_path: str) -> Tuple[List[str], List[str]]: """Validate gateway rules configuration.""" errors = [] warnings = [] # Load both configs try: with open(rules_path, 'r') as f: rules = json.load(f) except FileNotFoundError: errors.append(f"Rules config file not found: {rules_path}") return errors, warnings except json.JSONDecodeError as e: errors.append(f"Invalid JSON in rules config: {e}") return errors, warnings try: with open(mcp_path, 'r') as f: mcp_config = json.load(f) except: # Already validated in validate_mcp_config return errors, warnings available_servers = set(mcp_config.get("mcpServers", {}).keys()) # Validate agents agents = rules.get("agents", {}) if not agents: warnings.append("No agents configured") for agent_id, agent_rules in agents.items(): # Check allow rules if "allow" in agent_rules: allow = agent_rules["allow"] if "servers" in allow: for server in allow["servers"]: if server != "*" and server not in available_servers: # Note: Runtime treats this as a warning, not an error # CLI can be stricter to help catch configuration issues early warnings.append(f"Agent '{agent_id}' references unknown server: {server}") # Check deny rules if "deny" in agent_rules: deny = agent_rules["deny"] if "servers" in deny: for server in deny["servers"]: if server != "*" and server not in available_servers: # Note: Runtime treats this as a warning, not an error warnings.append(f"Agent '{agent_id}' denies unknown server: {server}") return errors, warnings @cli.command() @click.option("--type", type=click.Choice(["single-agent", "multi-agent"]), default="single-agent") @click.option("--output", default="./config", help="Output directory") def generate_config(type: str, output: str): """Generate example configuration files.""" output_path = Path(output) output_path.mkdir(parents=True, exist_ok=True) if type == "single-agent": _generate_single_agent_config(output_path) else: _generate_multi_agent_config(output_path) click.echo(f"āœ… Generated {type} configuration in {output}") def _generate_single_agent_config(output_path: Path): """Generate single-agent configuration.""" mcp_config = { "mcpServers": { "example": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-example"], "env": {} } } } rules_config = { "agents": { "developer": { "allow": { "servers": ["*"], "tools": {"*": ["*"]} } } }, "defaults": { "deny_on_missing_agent": false } } with open(output_path / ".mcp.json", "w") as f: json.dump(mcp_config, f, indent=2) with open(output_path / ".mcp-gateway-rules.json", "w") as f: json.dump(rules_config, f, indent=2) if __name__ == "__main__": cli() ``` ### Docker Container - [ ] Create Dockerfile - [ ] Multi-stage build for smaller image - [ ] Python 3.12+ base image - [ ] Install uv for dependency management - [ ] Copy source code - [ ] Install dependencies - [ ] Set up non-root user - [ ] Expose port 8000 - [ ] Health check configuration - [ ] Create docker-compose.yml - [ ] Gateway service definition - [ ] Volume mounts for configs - [ ] Environment variable configuration - [ ] Network configuration - [ ] Example downstream services (optional) - [ ] Add container documentation - [ ] Build instructions - [ ] Run instructions - [ ] Configuration via environment variables - [ ] Volume mount examples - [ ] Docker Compose usage **Code Reference:** ```dockerfile # Dockerfile FROM python:3.12-slim AS builder # Install uv RUN pip install uv WORKDIR /app # Copy dependency files COPY pyproject.toml uv.lock ./ # Install dependencies RUN uv sync --frozen # Production stage FROM python:3.12-slim # Create non-root user RUN useradd -m -u 1000 gateway && \ mkdir -p /app /config /logs && \ chown -R gateway:gateway /app /config /logs WORKDIR /app # Copy dependencies from builder COPY --from=builder --chown=gateway:gateway /app/.venv /app/.venv # Copy application code COPY --chown=gateway:gateway src/ ./src/ COPY --chown=gateway:gateway main.py ./ # Switch to non-root user USER gateway # Environment variables ENV PATH="/app/.venv/bin:$PATH" \ GATEWAY_MCP_CONFIG="/config/.mcp.json" \ GATEWAY_RULES="/config/.mcp-gateway-rules.json" \ GATEWAY_TRANSPORT="http" \ GATEWAY_HTTP_HOST="0.0.0.0" \ GATEWAY_HTTP_PORT="8000" # Expose port EXPOSE 8000 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" # Run gateway CMD ["python", "main.py"] ``` ```yaml # docker-compose.yml version: '3.8' services: gateway: build: . ports: - "8000:8000" volumes: - ./config:/config:ro - ./logs:/logs environment: - GATEWAY_MCP_CONFIG=/config/.mcp.json - GATEWAY_RULES=/config/.mcp-gateway-rules.json - GATEWAY_TRANSPORT=http - GATEWAY_DEFAULT_AGENT=developer healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 3s retries: 3 restart: unless-stopped ``` ### Documentation - [ ] Create comprehensive README.md - [ ] Project overview - [ ] Quick start guide - [ ] Installation instructions - [ ] Configuration guide - [ ] Usage examples - [ ] API reference - [ ] Troubleshooting - [ ] Create user guides - [ ] Getting started tutorial - [ ] Configuration guide - [ ] Policy rules guide - [ ] Deployment guide - [ ] Monitoring guide - [ ] Create developer documentation - [ ] Architecture overview - [ ] Contributing guidelines - [ ] Development setup - [ ] Testing guide - [ ] Release process - [ ] Create example configurations - [ ] Single developer setup - [ ] Multi-agent team setup - [ ] Production deployment - [ ] Common use cases **README.md Outline:** ```markdown # Agent MCP Gateway Context Window-Preserving MCP Proxy with Dynamic Discovery ## Overview [Brief description of the problem and solution] ## Quick Start ### Installation ```bash # Clone repository git clone https://github.com/yourusername/agent-mcp-gateway.git cd agent-mcp-gateway # Install dependencies uv sync # Configure cp config/.mcp.json.example config/.mcp.json cp config/.mcp-gateway-rules.example.json config/.mcp-gateway-rules.json # Run GATEWAY_DEFAULT_AGENT=developer uv run python main.py ``` ### Docker Quick Start ```bash docker compose up ``` ## Configuration ### MCP Servers [Configuration guide] ### Gateway Rules [Policy configuration guide] ### Environment Variables [All environment variables] ## Usage ### Single-Agent Mode [Example] ### Multi-Agent Mode [Example] ## API Reference ### Tools - `list_servers(agent_id)` - `get_server_tools(agent_id, server, ...)` - `execute_tool(agent_id, server, tool, args, ...)` ## Deployment ### Docker [Docker deployment guide] ### Production Considerations [Security, monitoring, scaling] ## Development ### Running Tests ```bash uv run pytest ``` ### Contributing [Link to CONTRIBUTING.md] ## Troubleshooting [Common issues and solutions] ## License [License info] ``` ### Testing Infrastructure - [ ] Expand test suite - [ ] Unit tests for all modules (>80% coverage) - [ ] Integration tests for all workflows - [ ] Performance tests - [ ] Security tests - [ ] Add test utilities - [ ] Mock MCP servers for testing - [ ] Test fixtures for common scenarios - [ ] Helper functions for test setup - [ ] Performance benchmarking tools - [ ] Set up CI/CD - [ ] GitHub Actions workflow - [ ] Run tests on push - [ ] Build Docker image - [ ] Publish releases - [ ] Add testing documentation - [ ] How to run tests - [ ] How to write tests - [ ] Test coverage requirements - [ ] Performance benchmarks **Code Reference:** ```python # tests/conftest.py import pytest from fastmcp import FastMCP from src.config import load_mcp_config, load_gateway_rules from src.policy import PolicyEngine from src.gateway import gateway @pytest.fixture def test_mcp_config(): """Provide test MCP configuration.""" return { "mcpServers": { "test-server": { "command": "python", "args": ["-m", "tests.mock_server"] } } } @pytest.fixture def test_gateway_rules(): """Provide test gateway rules.""" return { "agents": { "test-agent": { "allow": { "servers": ["test-server"], "tools": {"test-server": ["*"]} } } }, "defaults": { "deny_on_missing_agent": true } } @pytest.fixture def policy_engine(test_gateway_rules): """Provide configured policy engine.""" return PolicyEngine(test_gateway_rules) @pytest.fixture async def mock_mcp_server(): """Provide a mock MCP server for testing.""" server = FastMCP(name="MockServer") @server.tool def test_tool(param: str) -> str: return f"Executed with: {param}" return server ``` ```yaml # .github/workflows/test.yml name: Tests on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v4 with: python-version: '3.12' - name: Install uv run: pip install uv - name: Install dependencies run: uv sync - name: Run tests run: uv run pytest --cov=src tests/ - name: Check coverage run: uv run pytest --cov=src --cov-fail-under=80 tests/ - name: Lint run: | uv run ruff check src/ uv run mypy src/ docker: runs-on: ubuntu-latest needs: test steps: - uses: actions/checkout@v3 - name: Build Docker image run: docker build -t agent-mcp-gateway:test . - name: Test Docker image run: | docker run -d --name test-gateway -p 8000:8000 agent-mcp-gateway:test sleep 5 curl -f http://localhost:8000/health docker stop test-gateway ``` ### Development Tools - [ ] Add development helpers - [ ] Hot reload for development - [ ] Debug mode with verbose logging - [ ] Interactive config builder - [ ] Log viewer utility - [ ] Create debugging tools - [ ] Policy evaluation tester - [ ] Request/response inspector - [ ] Performance profiler - [ ] Connection diagnostics - [ ] Add code quality tools - [ ] Linting with ruff - [ ] Type checking with mypy - [ ] Code formatting with ruff format - [ ] Pre-commit hooks **Code Reference:** ```python # src/dev_tools.py import click from src.policy import PolicyEngine @click.command() @click.option("--rules", default="./config/.mcp-gateway-rules.json") @click.argument("agent_id") @click.argument("server") @click.argument("tool") def test_policy(rules: str, agent_id: str, server: str, tool: str): """Test policy evaluation for a specific scenario.""" from src.config import load_gateway_rules gateway_rules = load_gateway_rules(rules) policy_engine = PolicyEngine(gateway_rules) # Test server access can_access_server = policy_engine.can_access_server(agent_id, server) click.echo(f"Server access: {'āœ… ALLOWED' if can_access_server else 'āŒ DENIED'}") if can_access_server: # Test tool access can_access_tool = policy_engine.can_access_tool(agent_id, server, tool) click.echo(f"Tool access: {'āœ… ALLOWED' if can_access_tool else 'āŒ DENIED'}") if not can_access_tool: reason = policy_engine.get_deny_reason(agent_id, server, tool) click.echo(f"Reason: {reason}") if __name__ == "__main__": test_policy() ``` ```toml # pyproject.toml additions [tool.ruff] line-length = 100 target-version = "py312" [tool.ruff.lint] select = ["E", "F", "I", "N", "W"] ignore = ["E501"] [tool.mypy] python_version = "3.12" strict = true warn_return_any = true warn_unused_configs = true [tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py"] python_classes = ["Test*"] python_functions = ["test_*"] addopts = "-v --cov=src --cov-report=html --cov-report=term" ``` --- ## Success Criteria ### Usability Requirements - [ ] Gateway runs with single command - [ ] Configuration validation prevents common errors - [ ] Clear error messages for all failure modes - [ ] Documentation covers all use cases - [ ] Examples work out of the box ### Developer Experience Requirements - [ ] Quick start in <5 minutes - [ ] Single-agent mode works without complex config - [ ] Config validation catches 90%+ of errors - [ ] Docker deployment works on first try - [ ] Comprehensive examples provided ### Quality Requirements - [ ] Test coverage >80% - [ ] All code linted and type-checked - [ ] CI/CD pipeline passes - [ ] Documentation is complete and accurate - [ ] Docker image <500MB ### Deployment Requirements - [ ] Docker image builds successfully - [ ] Docker Compose works out of box - [ ] Health checks enable monitoring - [ ] Logs are structured and useful - [ ] Easy to configure in production --- ## Documentation Checklist - [ ] README.md with quick start - [ ] Configuration guide - [ ] Deployment guide - [ ] API reference - [ ] Troubleshooting guide - [ ] Contributing guidelines - [ ] Example configurations - [ ] Architecture documentation - [ ] Development setup guide - [ ] Testing guide --- ## Dependencies **External:** - click (for CLI) - Docker (for containerization) - pytest (for testing) - ruff (for linting) - mypy (for type checking) **Internal:** - M0 (Foundation) - Core infrastructure - M1 (Core) - Gateway functionality - M2 (Production) - HTTP transport, health checks --- ## Documentation References - **FastMCP Server:** https://gofastmcp.com/servers/server - **Docker Best Practices:** https://docs.docker.com/develop/dev-best-practices/ - **Python Packaging:** https://packaging.python.org/ - **Testing Best Practices:** https://docs.pytest.org/ --- ## Notes - Single-agent mode is essential for quick prototyping and development - Configuration validation prevents most deployment issues - Docker makes deployment consistent across environments - Good documentation reduces support burden - Testing infrastructure enables confident refactoring - CI/CD automates quality checks - Developer tools make debugging easier - Examples are the best documentation

Latest Blog Posts

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/roddutra/agent-mcp-gateway'

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