# MCP Wrapper Architecture

Detailed architecture documentation for the MCP wrapper implementation.

## Overview

This implementation provides a **minimal, non-invasive wrapper** that bridges:
- **MCP** (Model Context Protocol) - used by Claude Desktop and AI assistants
- **A2A** (Agent-to-Agent) - Google's agent communication protocol
- **Synqed** - inbox-based multi-agent coordination framework

## Design Goals

1. **Zero Invention**: Use only existing primitives from A2A + Synqed
2. **Strict Contracts**: Maintain A2A message format and semantics
3. **No New Abstractions**: Avoid creating custom agent types or protocols
4. **Production Grade**: Fully typed, logged, and error-handled
5. **Clean Separation**: MCP layer is purely a translation wrapper

## Component Architecture

### Layer 1: MCP Protocol Layer

```
┌─────────────────────────────────────────────┐
│          MCP Client (Claude)                │
│                                             │
│  - Discovers tools via list_tools()         │
│  - Calls tools via call_tool()              │
│  - Receives structured responses            │
└──────────────────┬──────────────────────────┘
                   │ MCP Protocol
                   │ (stdio or SSE)
                   ▼
┌─────────────────────────────────────────────┐
│        MCP Server (FastMCP)                 │
│                                             │
│  - Registers tool endpoints                 │
│  - Validates input schemas                  │
│  - Routes to tool handlers                  │
│  - Returns JSON responses                   │
└──────────────────┬──────────────────────────┘
                   │
```

**Key Files:**
- `mcp/server.py`: FastMCP server implementation
- `mcp/__init__.py`: Package exports

**Responsibilities:**
- Tool registration and discovery
- Schema validation
- Transport handling (stdio/SSE)
- Response formatting

### Layer 2: Tool Translation Layer

```
                   │
                   ▼
┌─────────────────────────────────────────────┐
│           Tool Handlers                     │
│                                             │
│  salesforce_query_leads()                   │
│  zoom_create_meeting()                      │
│  content_creator_generate()                 │
│                                             │
│  - Parse MCP tool arguments                 │
│  - Construct A2A task payloads              │
│  - Call A2AClient.send_task()               │
│  - Format responses as JSON                 │
└──────────────────┬──────────────────────────┘
                   │
```

**Key Files:**
- `mcp/tools/salesforce.py`
- `mcp/tools/zoom.py`
- `mcp/tools/content_creator.py`
- `mcp/tools/__init__.py`

**Responsibilities:**
- Input parameter mapping
- A2A task construction
- Response parsing and formatting
- Error handling and logging

### Layer 3: A2A Client Layer

```
                   │
                   ▼
┌─────────────────────────────────────────────┐
│          A2AClient                          │
│                                             │
│  send_task(agent, task_type, payload)       │
│                                             │
│  - Resolve AgentId from email/uri           │
│  - Construct task message JSON              │
│  - Route via MessageRouter                  │
│  - Return acknowledgment                    │
└──────────────────┬──────────────────────────┘
                   │
```

**Key Files:**
- `mcp/a2a/client.py`
- `mcp/a2a/__init__.py`

**Responsibilities:**
- AgentId resolution
- Task message formatting
- Router integration
- Message ID tracking

### Layer 4: Synqed Infrastructure

```
                   │
                   ▼
┌─────────────────────────────────────────────┐
│         MessageRouter                       │
│                                             │
│  route_local_message()                      │
│                                             │
│  - Lookup agent in registry                 │
│  - Add message to agent inbox               │
│  - Generate message_id                      │
│  - Maintain transcript                      │
└──────────────────┬──────────────────────────┘
                   │
                   ▼
┌─────────────────────────────────────────────┐
│         Agent Runtime                       │
│                                             │
│  - Receive message in memory                │
│  - Execute agent logic function             │
│  - Return structured response               │
└─────────────────────────────────────────────┘
```

**Key Components (from Synqed):**
- `MessageRouter`: Routes messages between agents
- `AgentRuntimeRegistry`: Registry of agent prototypes
- `Agent`: Agent runtime with logic function
- `AgentMemory`: Inbox message storage
- `AgentId`: Agent addressing (agent://org/name)

**Responsibilities:**
- Message routing and delivery
- Agent lifecycle management
- Memory/inbox management
- Transcript maintenance

## Data Flow

### Tool Invocation Flow

```
1. MCP Client calls tool
   ↓
   {
     "name": "salesforce_query_leads",
     "arguments": {
       "query": "SELECT * FROM Lead"
     }
   }

2. MCP Server routes to handler
   ↓
   async def salesforce_query_leads(query: str) -> str:
       result = await salesforce.query_leads(a2a_client, query)
       return json.dumps(result)

3. Tool handler constructs A2A task
   ↓
   {
     "task_type": "query_leads",
     "payload": {
       "query": "SELECT * FROM Lead"
     }
   }

4. A2AClient sends via router
   ↓
   await router.route_local_message(
       workspace_id="mcp_workspace",
       sender="MCPServer",
       recipient="salesforce",
       content=json.dumps(task)
   )

5. Agent receives and processes
   ↓
   async def salesforce_agent_logic(context):
       task = json.loads(context.latest_message.content)
       # Process task...
       return context.send("MCPServer", json.dumps(response))

6. Response flows back to MCP client
   ↓
   {
     "status": "success",
     "results": [...],
     "count": 3
   }
```

## Message Formats

### MCP Tool Call

```json
{
  "name": "salesforce_query_leads",
  "arguments": {
    "query": "SELECT Id, Name FROM Lead"
  }
}
```

### A2A Task Message

```json
{
  "task_type": "query_leads",
  "payload": {
    "query": "SELECT Id, Name FROM Lead",
    "format": "json"
  }
}
```

### Agent Response

```json
{
  "status": "success",
  "data": [
    {"Id": "001", "Name": "John Doe"},
    {"Id": "002", "Name": "Jane Smith"}
  ],
  "count": 2,
  "query": "SELECT Id, Name FROM Lead"
}
```

## Agent Addressing

### AgentId Format

Agents are addressed using Synqed's AgentId system:

```python
# Email-like format
AgentId.from_email_like("salesforce@tools")
# → AgentId(org="tools", name="salesforce")

# URI format
AgentId.from_uri("agent://tools/salesforce")
# → AgentId(org="tools", name="salesforce")

# Canonical URI
agent.to_uri()
# → "agent://tools/salesforce"
```

### Agent Registration

Agents are registered in `AgentRuntimeRegistry`:

```python
# Create agent
salesforce_agent = Agent(
    name="salesforce",
    description="Salesforce integration",
    logic=salesforce_agent_logic,
    role="tools"
)

# Register as prototype
AgentRuntimeRegistry.register("salesforce", salesforce_agent)

# Register in router
router.register_agent("salesforce", salesforce_agent)
```

## Workspace Integration

### Current Implementation

The current implementation uses a **standalone router** for simplicity:

```python
router = MessageRouter()
router.register_agent("salesforce", salesforce_agent)
a2a_client = A2AClient(router)
```

### Full Workspace Integration (Optional)

For production deployments, integrate with Synqed's Workspace system:

```python
from synqed import WorkspaceManager, Workspace

# Create workspace
workspace = await workspace_manager.create_workspace(
    task_tree_node=task_node,
    parent_workspace_id=None
)

# Use workspace router
a2a_client = A2AClient(workspace.router)

# Route messages through workspace
await workspace.route_message(
    sender="MCPServer",
    recipient="salesforce",
    content=task_json
)
```

## Error Handling

### Layer-by-Layer Error Handling

1. **MCP Server Layer**:
   - Validates tool arguments against schemas
   - Catches exceptions from tool handlers
   - Returns structured error responses

2. **Tool Handler Layer**:
   - Try/except around A2A client calls
   - Returns error status in response JSON
   - Logs errors with context

3. **A2A Client Layer**:
   - Catches router errors (agent not found)
   - Validates AgentId before sending
   - Returns structured error responses

4. **Agent Logic Layer**:
   - Try/except around task processing
   - Returns error status to sender
   - Logs errors with task details

### Error Response Format

```json
{
  "status": "error",
  "error": "Agent 'unknown' is not registered",
  "task_type": "query_leads",
  "timestamp": "2025-11-22T..."
}
```

## Logging Strategy

### Log Levels

- **INFO**: Major operations (task sent, response received, server started)
- **DEBUG**: Detailed flow (message routing, agent lookup)
- **WARNING**: Recoverable issues (agent not found, retrying)
- **ERROR**: Unrecoverable errors (invalid JSON, task failed)

### Log Format

```
2025-11-22 10:00:00 - mcp.a2a.client - INFO - sending a2a task to agent://tools/salesforce: type=query_leads
2025-11-22 10:00:01 - mcp.a2a.client - INFO - task sent successfully: message_id=msg-workspace-salesforce-abc123
```

## Performance Considerations

### Message Routing

- **Synchronous**: MessageRouter operates synchronously within the event loop
- **No Network**: All routing is in-process (no HTTP calls)
- **Minimal Overhead**: Simple dictionary lookups and function calls

### Memory Usage

- **Agent Instances**: Each agent has its own memory (inbox)
- **Transcript**: MessageRouter maintains full conversation history
- **Bounded Growth**: Consider transcript size limits in production

### Scalability

Current implementation is **single-process**. For multi-process:

1. Use RemoteA2AAgent for remote agents
2. Deploy agents as separate services
3. Use HTTP/gRPC for inter-agent communication
4. Consider message queues for async processing

## Extension Points

### Adding New Tools

1. Create tool module in `mcp/tools/`
2. Implement tool function and schema
3. Create agent logic function
4. Register agent in AgentRuntimeRegistry
5. Register tool in MCP server

See `mcp/README.md` for detailed steps.

### Supporting New Transports

MCP supports multiple transports:

- **stdio**: For Claude Desktop, terminal apps
- **SSE**: For web clients, browsers
- **WebSocket**: For bidirectional streaming (future)

FastMCP handles transport abstraction automatically.

### Integration with Other Frameworks

The A2A client can route to agents built with **any framework**:

- Synqed agents (current implementation)
- LangChain agents (via RemoteA2AAgent)
- AutoGen agents (via A2A adapter)
- Custom agents (implement A2A protocol)

Use `RemoteA2AAgent` to connect to external agents:

```python
from synqed import RemoteA2AAgent, AgentRuntimeRegistry

remote_agent = RemoteA2AAgent(
    url="https://my-agent.example.com",
    name="external_agent"
)

AgentRuntimeRegistry.register_remote(
    role="external_agent",
    agent=remote_agent
)
```

## Security Considerations

### Current Implementation

- No authentication (suitable for local development)
- No input validation beyond schema checks
- Agents run in same process

### Production Recommendations

1. **Authentication**: Add bearer tokens or API keys
2. **Authorization**: Validate agent permissions per tool
3. **Input Sanitization**: Validate all payload data
4. **Rate Limiting**: Limit requests per client
5. **Isolation**: Run agents in separate processes/containers
6. **Encryption**: Use TLS for SSE transport

## Testing Strategy

### Unit Tests

Test individual components in isolation:

```python
# Test A2A client
async def test_a2a_client():
    router = MessageRouter()
    client = A2AClient(router)
    result = await client.send_task(...)
    assert result["status"] == "success"

# Test tool handlers
async def test_salesforce_tool():
    result = await salesforce.query_leads(client, "SELECT * FROM Lead")
    assert "results" in result
```

### Integration Tests

Test full flow from MCP to agent:

```python
async def test_full_flow():
    # Setup agents and server
    agents = create_agents()
    server = setup_mcp_server(agents)
    
    # Call tool
    result = await call_mcp_tool("salesforce_query_leads", {...})
    
    # Verify response
    assert result["status"] == "success"
```

### End-to-End Tests

Test with real MCP client (Claude Desktop):

1. Start MCP server: `python demo.py`
2. Configure Claude Desktop with MCP config
3. Ask Claude to use the tools
4. Verify results in logs and UI

## Deployment

### Local Development

```bash
# Install dependencies
pip install -r requirements.txt
pip install fastmcp

# Run demo
python demo.py
```

### Production Deployment

1. **Containerize**: Create Docker image with all dependencies
2. **Environment**: Set up env vars for API keys, URLs
3. **Monitoring**: Add metrics, health checks, alerting
4. **Scaling**: Deploy multiple instances behind load balancer
5. **Persistence**: Use database for agent registry if needed

### Claude Desktop Integration

1. Install Claude Desktop
2. Edit config: `~/Library/Application Support/Claude/claude_desktop_config.json`
3. Add MCP server entry
4. Restart Claude Desktop
5. Verify tools appear in Claude UI

## Maintenance

### Code Organization

- Keep tool modules independent
- Avoid shared mutable state
- Use type hints everywhere
- Document public APIs
- Log all important operations

### Version Control

- Tag releases (v1.0.0, v1.1.0)
- Maintain changelog
- Document breaking changes
- Keep dependencies updated

### Monitoring

- Log all tool invocations
- Track success/error rates
- Monitor response times
- Alert on failures

## Future Enhancements

### Potential Improvements

1. **Streaming Responses**: Support streaming for long-running tasks
2. **Caching**: Cache frequent queries (e.g., Salesforce schema)
3. **Retries**: Automatic retry on transient failures
4. **Timeouts**: Configurable timeouts per tool
5. **Metrics**: Prometheus metrics for observability
6. **Validation**: Enhanced input validation and sanitization
7. **Documentation**: OpenAPI/Swagger docs for tools
8. **Testing**: Property-based testing, fuzzing

### A2A Protocol Extensions

- Support A2A task subscriptions (long-running tasks)
- Implement A2A task cancellation
- Add A2A task progress updates
- Support A2A streaming responses

## References

### External Documentation

- [MCP Specification](https://modelcontextprotocol.io/)
- [A2A Protocol](https://github.com/google/A2A)
- [Synqed Documentation](../README.md)
- [FastMCP Documentation](https://github.com/jlowin/fastmcp)

### Related Code

- `synqed-python/src/synqed/a2a_remote_agent.py`: RemoteA2AAgent implementation
- `synqed-python/src/synqed/router.py`: MessageRouter implementation
- `synqed-python/src/synqed/agent.py`: Agent runtime
- `a2a-samples/samples/python/agents/a2a_mcp/`: Example A2A MCP integration

