# SPIKE-001: MCP Integration Pattern

**Status:** Completed
**Date:** 2025-12-29
**Author:** Architecture Team
**Related Issue:** #1

---

## Objective

Research the optimal pattern for integrating Model Context Protocol (MCP) servers with FastAPI backend, focusing on unified singleton servers with project/agent scoping.

## Research Questions

1. What is the recommended MCP SDK for Python/FastAPI?
2. How should we structure unified MCP servers vs per-project servers?
3. What is the best pattern for project/agent scoping in MCP tools?
4. How do we handle authentication between Syndarix and MCP servers?

## Findings

### 1. FastMCP 2.0 - Recommended Framework

**FastMCP** is a high-level, Pythonic framework for building MCP servers that significantly reduces boilerplate compared to the low-level MCP SDK.

**Key Features:**
- Decorator-based tool registration (`@mcp.tool()`)
- Built-in context management for resources and prompts
- Support for server-sent events (SSE) and stdio transports
- Type-safe with Pydantic model support
- Async-first design compatible with FastAPI

**Installation:**
```bash
pip install fastmcp
```

**Basic Example:**
```python
from fastmcp import FastMCP

mcp = FastMCP("syndarix-knowledge-base")

@mcp.tool()
def search_knowledge(
    project_id: str,
    query: str,
    scope: str = "project"
) -> list[dict]:
    """Search the knowledge base with project scoping."""
    # Implementation here
    return results

@mcp.resource("project://{project_id}/config")
def get_project_config(project_id: str) -> dict:
    """Get project configuration."""
    return config
```

### 2. Unified Singleton Pattern (Recommended)

**Decision:** Use unified singleton MCP servers instead of per-project servers.

**Architecture:**
```
┌─────────────────────────────────────────────────────────┐
│                    Syndarix Backend                      │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐      │
│  │   Agent 1   │  │   Agent 2   │  │   Agent 3   │      │
│  │ (project A) │  │ (project A) │  │ (project B) │      │
│  └──────┬──────┘  └──────┬──────┘  └──────┬──────┘      │
│         │                │                │              │
│         └────────────────┼────────────────┘              │
│                          │                               │
│                          ▼                               │
│  ┌─────────────────────────────────────────────────┐    │
│  │              MCP Client (Singleton)              │    │
│  │   Maintains connections to all MCP servers       │    │
│  └─────────────────────────────────────────────────┘    │
└──────────────────────────┬──────────────────────────────┘
                           │
           ┌───────────────┼───────────────┐
           │               │               │
           ▼               ▼               ▼
    ┌────────────┐  ┌────────────┐  ┌────────────┐
    │ Git MCP    │  │   KB MCP   │  │  LLM MCP   │
    │ (Singleton)│  │ (Singleton)│  │ (Singleton)│
    └────────────┘  └────────────┘  └────────────┘
```

**Why Singleton:**
- Resource efficiency (one process per MCP type)
- Shared connection pools
- Centralized logging and monitoring
- Simpler deployment (7 services vs N×7)
- Cross-project learning possible (if needed)

**Scoping Pattern:**
```python
@mcp.tool()
def search_knowledge(
    project_id: str,       # Required - scopes to project
    agent_id: str,         # Required - identifies calling agent
    query: str,
    scope: Literal["project", "global"] = "project"
) -> SearchResults:
    """
    All tools accept project_id and agent_id for:
    - Access control validation
    - Audit logging
    - Context filtering
    """
    # Validate agent has access to project
    validate_access(agent_id, project_id)

    # Log the access
    log_tool_usage(agent_id, project_id, "search_knowledge")

    # Perform scoped search
    if scope == "project":
        return search_project_kb(project_id, query)
    else:
        return search_global_kb(query)
```

### 3. MCP Server Registry Architecture

```python
# mcp/registry.py
from dataclasses import dataclass
from typing import Dict

@dataclass
class MCPServerConfig:
    name: str
    port: int
    transport: str  # "sse" or "stdio"
    enabled: bool = True

MCP_SERVERS: Dict[str, MCPServerConfig] = {
    "llm_gateway": MCPServerConfig("llm-gateway", 9001, "sse"),
    "git": MCPServerConfig("git-mcp", 9002, "sse"),
    "knowledge_base": MCPServerConfig("kb-mcp", 9003, "sse"),
    "issues": MCPServerConfig("issues-mcp", 9004, "sse"),
    "file_system": MCPServerConfig("fs-mcp", 9005, "sse"),
    "code_analysis": MCPServerConfig("code-mcp", 9006, "sse"),
    "cicd": MCPServerConfig("cicd-mcp", 9007, "sse"),
}
```

### 4. Authentication Pattern

**MCP OAuth 2.0 Integration:**
```python
from fastmcp import FastMCP
from fastmcp.auth import OAuth2Bearer

mcp = FastMCP(
    "syndarix-mcp",
    auth=OAuth2Bearer(
        token_url="https://syndarix.local/oauth/token",
        scopes=["mcp:read", "mcp:write"]
    )
)
```

**Internal Service Auth (Recommended for v1):**
```python
# For internal deployment, use service tokens
@mcp.tool()
def create_issue(
    service_token: str,  # Validated internally
    project_id: str,
    title: str,
    body: str
) -> Issue:
    validate_service_token(service_token)
    # ... implementation
```

### 5. FastAPI Integration Pattern

```python
# app/mcp/client.py
from mcp import ClientSession
from mcp.client.sse import sse_client
from contextlib import asynccontextmanager

class MCPClientManager:
    def __init__(self):
        self._sessions: dict[str, ClientSession] = {}

    async def connect_all(self):
        """Connect to all configured MCP servers."""
        for name, config in MCP_SERVERS.items():
            if config.enabled:
                session = await self._connect_server(config)
                self._sessions[name] = session

    async def call_tool(
        self,
        server: str,
        tool_name: str,
        arguments: dict
    ) -> Any:
        """Call a tool on a specific MCP server."""
        session = self._sessions[server]
        result = await session.call_tool(tool_name, arguments)
        return result.content

# Usage in FastAPI
mcp_client = MCPClientManager()

@app.on_event("startup")
async def startup():
    await mcp_client.connect_all()

@app.post("/api/v1/knowledge/search")
async def search_knowledge(request: SearchRequest):
    result = await mcp_client.call_tool(
        "knowledge_base",
        "search_knowledge",
        {
            "project_id": request.project_id,
            "agent_id": request.agent_id,
            "query": request.query
        }
    )
    return result
```

## Recommendations

### Immediate Actions

1. **Use FastMCP 2.0** for all MCP server implementations
2. **Implement unified singleton pattern** with explicit scoping
3. **Use SSE transport** for MCP server connections
4. **Service tokens** for internal auth (v1), OAuth 2.0 for future

### MCP Server Priority

1. **LLM Gateway** - Critical for agent operation
2. **Knowledge Base** - Required for RAG functionality
3. **Git MCP** - Required for code delivery
4. **Issues MCP** - Required for project management
5. **File System** - Required for workspace operations
6. **Code Analysis** - Enhance code quality
7. **CI/CD** - Automate deployments

### Code Organization

```
syndarix/
├── backend/
│   └── app/
│       └── mcp/
│           ├── __init__.py
│           ├── client.py         # MCP client manager
│           ├── registry.py       # Server configurations
│           └── schemas.py        # Tool argument schemas
└── mcp_servers/
    ├── llm_gateway/
    │   ├── __init__.py
    │   ├── server.py
    │   └── tools.py
    ├── knowledge_base/
    ├── git/
    ├── issues/
    ├── file_system/
    ├── code_analysis/
    └── cicd/
```

## References

- [FastMCP Documentation](https://gofastmcp.com)
- [MCP Protocol Specification](https://spec.modelcontextprotocol.io)
- [Anthropic MCP SDK](https://github.com/anthropics/anthropic-sdk-mcp)

## Decision

**Adopt FastMCP 2.0** with unified singleton servers and explicit project/agent scoping for all MCP integrations.

---

*Spike completed. Findings will inform ADR-001: MCP Integration Architecture.*
