Docs / Architect Labs / Lab 14

Lab 14 — MCP Integration

MCP (Model Context Protocol) permite que Architect acceda a herramientas externas como si fueran tools nativas. En este lab crearás un MCP server mock y lo integrarás con Architect.

Concepto clave

Nivel: Avanzado

Duración estimada: 30 minutos. Feature principal: MCP.

MCP (Model Context Protocol) permite que Architect acceda a herramientas externas como si fueran tools nativas. Architect actúa como MCP client que se conecta a MCP servers.

Setup

bash
mkdir -p ~/architect-labs/lab-14 && cd ~/architect-labs/lab-14
git init && mkdir -p src tests mcp-servers

Crear un MCP server mock local

Un servidor MCP simple que simula un issue tracker:

mcp-servers/issue-tracker.py

python
"""MCP Server mock que simula un issue tracker."""
import json
from http.server import HTTPServer, BaseHTTPRequestHandler

# Base de datos en memoria
ISSUES = {
    "BUG-001": {
        "id": "BUG-001",
        "title": "NullPointerException in UserService.getUser()",
        "description": "When user_id is None, getUser() throws NPE instead of returning None",
        "severity": "HIGH",
        "status": "open",
        "reporter": "qa-team",
        "stack_trace": (
            "Traceback (most recent call last):\n"
            "  File \"src/services/user_service.py\", line 15, in get_user\n"
            "    return self.users[user_id]\n"
            "KeyError: None"
        ),
        "steps_to_reproduce": [
            "1. Call get_user(None)",
            "2. Observe KeyError instead of None return"
        ]
    },
    "BUG-002": {
        "id": "BUG-002",
        "title": "Off-by-one in pagination",
        "description": "get_users(page=2, size=10) returns items 11-19 instead of 11-20",
        "severity": "MEDIUM",
        "status": "open",
        "reporter": "frontend-team",
        "stack_trace": "",
        "steps_to_reproduce": [
            "1. Add 25 users",
            "2. Call get_users(page=2, size=10)",
            "3. Observe only 9 results instead of 10"
        ]
    }
}

class MCPHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        content_length = int(self.headers['Content-Length'])
        body = json.loads(self.rfile.read(content_length))
        method = body.get("method", "")
        params = body.get("params", {})

        if method == "tools/list":
            result = {
                "tools": [
                    {
                        "name": "read_issue",
                        "description": "Read a bug ticket by ID",
                        "inputSchema": {
                            "type": "object",
                            "properties": {"issue_id": {"type": "string"}},
                            "required": ["issue_id"]
                        }
                    },
                    {
                        "name": "list_issues",
                        "description": "List all open issues",
                        "inputSchema": {
                            "type": "object",
                            "properties": {
                                "status": {"type": "string", "default": "open"}
                            }
                        }
                    },
                    {
                        "name": "add_comment",
                        "description": "Add a comment to an issue",
                        "inputSchema": {
                            "type": "object",
                            "properties": {
                                "issue_id": {"type": "string"},
                                "comment": {"type": "string"}
                            },
                            "required": ["issue_id", "comment"]
                        }
                    }
                ]
            }
        elif method == "tools/call":
            tool_name = params.get("name", "")
            args = params.get("arguments", {})
            if tool_name == "read_issue":
                issue = ISSUES.get(args["issue_id"])
                result = {"content": [{"type": "text", "text": json.dumps(issue, indent=2)}]}
            elif tool_name == "list_issues":
                issues = [
                    {"id": k, "title": v["title"], "severity": v["severity"]}
                    for k, v in ISSUES.items()
                    if v["status"] == args.get("status", "open")
                ]
                result = {"content": [{"type": "text", "text": json.dumps(issues, indent=2)}]}
            elif tool_name == "add_comment":
                result = {"content": [{"type": "text",
                    "text": f"Comment added to {args['issue_id']}"}]}
            else:
                result = {"error": f"Unknown tool: {tool_name}"}
        else:
            result = {"error": f"Unknown method: {method}"}

        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        response = {"jsonrpc": "2.0", "id": body.get("id"), "result": result}
        self.wfile.write(json.dumps(response).encode())

    def log_message(self, format, *args):
        pass

if __name__ == "__main__":
    server = HTTPServer(("localhost", 8090), MCPHandler)
    print("MCP Issue Tracker running on http://localhost:8090")
    server.serve_forever()

Crear el código buggy

src/user_service.py

python
class UserService:
    def __init__(self):
        self.users = {}
        self._next_id = 1

    def add_user(self, name, email):
        user_id = self._next_id
        self._next_id += 1
        self.users[user_id] = {"id": user_id, "name": name, "email": email}
        return user_id

    def get_user(self, user_id):
        # BUG: No maneja user_id=None
        return self.users[user_id]

    def get_users(self, page=1, size=10):
        # BUG: off-by-one en paginación
        all_users = list(self.users.values())
        start = (page - 1) * size
        end = start + size - 1  # BUG: debería ser start + size
        return all_users[start:end]

tests/test_user_service.py

python
import pytest
from src.user_service import UserService

@pytest.fixture
def service():
    s = UserService()
    for i in range(25):
        s.add_user(f"User{'{i}'}", f"user{'{i}'}@test.com")
    return s

def test_get_user_none(service):
    result = service.get_user(None)
    assert result is None

def test_pagination_page2(service):
    page2 = service.get_users(page=2, size=10)
    assert len(page2) == 10
bash
git add -A && git commit -m "initial: buggy service + mock MCP"

Ejercicio 1: Arrancar el MCP server

bash
# Terminal 1: arrancar el MCP mock
python mcp-servers/issue-tracker.py &
MCP_PID=$!
echo "MCP server running (PID: $MCP_PID)"

Ejercicio 2: Configurar Architect con MCP

.architect.yaml

yaml
llm:
  model: openai/gpt-4.1
  api_base: http://localhost:4000/v1
  api_key_env: LITELLM_API_KEY

mcp:
  servers:
    - name: issue-tracker
      url: http://localhost:8090/mcp
      auth:
        type: none

guardrails:
  protected_files:
    - "tests/**"
  max_files_modified: 5

Consejo

Los MCP servers se configuran en la sección mcp.servers. Las tools expuestas por el server aparecen automáticamente como tools nativas para Architect.

Ejercicio 3: Architect lee tickets y corrige bugs

bash
architect run "Usa la tool MCP list_issues para ver los bugs abiertos. \
  Luego lee el detalle de cada bug con read_issue. \
  Corrige los bugs en src/user_service.py según la descripción \
  de cada ticket. Ejecuta los tests para verificar." \
  --config .architect.yaml \
  --confirm-mode yolo \
  --report-file reports/mcp-run.json

Qué observar:

  • Architect llama list_issues para ver bugs
  • Llama read_issue para cada bug
  • Lee el stack trace y steps to reproduce
  • Aplica los fixes en el código
bash
export PYTHONPATH=.
pytest tests/ -v

Ejercicio 4: Comentar en ticket post-fix

bash
architect run "Usa la tool MCP add_comment para comentar en BUG-001 \
  que el bug ha sido corregido. Incluye un resumen del fix aplicado." \
  --config .architect.yaml \
  --confirm-mode yolo

Cleanup

bash
kill $MCP_PID 2>/dev/null

Resumen

ConceptoDescripción
mcp.serversLista de MCP servers a conectar
mcp.servers[].urlEndpoint del MCP server
mcp.servers[].authAutenticación (none, bearer, basic)
Tools MCPAparecen como tools nativas para el agente

Siguiente lab

Lab 15: OTel Tracing — Traza completa de la ejecución del agente.

END OF DOCUMENT

¿Necesitas más? Volver a la Librería →