Course EN
← back to chapter

Resources MCP

Un bot de soporte que responde estrictamente desde documentos de políticas — envíos,

Showcase — Resources MCP

Un bot de soporte que responde estrictamente desde documentos de políticas — envíos, devoluciones, garantía, cuentas. Los docs son resources en un servidor MCP: datos de solo lectura direccionados por URIs doc://. El backend los lista (resources/list), los lee (resources/read), y pone ese texto frente al modelo como grounding. Donde las tools son acciones, los resources son contexto — el cliente los jala, el modelo responde, y cualquier cosa que los docs no cubran recibe un honesto "no cubierto."

Córrelo

bash bootstrap-secrets.sh              # reads ../../../../.env, writes secrets/
docker compose up --build              # default: PROVIDER=openai

Abre http://localhost:3000.

Para correrlo con Gemini en su lugar:

PROVIDER=gemini docker compose up --build

Qué hay aquí

  • backend/mcp_server.py — el servidor MCP: una base de conocimiento de soporte que expone resources/list y resources/read sobre stdio. Sin modelo, solo el protocolo.
  • backend/mcp_client.py — el cliente MCP hecho desde cero (compartido con los otros showcases): handshake, luego resources/list / resources/read.
  • backend/ai_openai.py / backend/ai_gemini.py — jalan cada resource al contexto y responden la pregunta con grounding en él.
  • backend/main.py — loader de FastAPI idéntico; lee PROVIDER y despacha.
  • frontend/app/page.tsx — textarea + resultado.

Prueba "¿puedo devolver un artículo de venta final?" o "¿la garantía cubre daños por agua?" — las respuestas vienen de los docs, no de la memoria del modelo.

Detenlo

docker compose down

Ejecútalo en tu máquina

Descarga el proyecto como ZIP y córrelo con Docker. Levanta un backend FastAPI y un frontend Next.js en localhost:3000.

Descargar mcp-resources.zip

unzip mcp-resources.zip
cd mcp-resources
bash bootstrap-secrets.sh   # one-time: pulls API keys into ./secrets
docker compose up --build   # default provider: openai
# or:  PROVIDER=gemini docker compose up --build

Escribe algo, elige un proveedor y ejecuta el mismo código de Código contra la API real. Requiere iniciar sesión.


  

Los mismos módulos que ejecuta el botón Run. El proyecto completo (frontend, Dockerfile, compose) está en el ZIP, pestaña README.

backend/ai_openai.py

"""Showcase 2 (OpenAI): resources as read-only context.

Resources aren't actions the model takes — they're data the client pulls in.
So the flow is: list the server's resources, read them over MCP, and put that
text in front of the model as grounding. No tool loop; the model answers a
support question using only what the knowledge base says.
"""
from openai import OpenAI

from mcp_client import MCPClient, content_text

_client = OpenAI()

_MODEL = "gpt-5.4-nano"


def _load_context(mcp: MCPClient) -> str:
    """Pull every resource the server offers into one grounding blob."""
    blocks = []
    for r in mcp.list_resources():                      # resources/list
        text = content_text(mcp.read_resource(r["uri"]))  # resources/read
        blocks.append(f"## {r['name']} ({r['uri']})\n{text}")
    return "\n\n".join(blocks)


def run(question: str) -> str:
    with MCPClient() as mcp:
        context = _load_context(mcp)
        response = _client.responses.create(
            model=_MODEL,
            instructions="You are a support agent. Answer using ONLY the policy "
                         "documents below. If they don't cover it, say so — don't "
                         "guess.\n\n" + context,
            input=[{"role": "user", "content": question.strip()}],
        )
        return response.output_text

backend/ai_gemini.py

"""Showcase 2 (Gemini): the same resources, a different model.

Same MCP server, same `resources/list` + `resources/read`. Only the model
call changes. The documents are pulled in as context and the model answers
strictly from them.
"""
import os

from google import genai
from google.genai import types

from mcp_client import MCPClient, content_text

_client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])

_MODEL = "gemini-3.1-flash-lite"


def _load_context(mcp: MCPClient) -> str:
    blocks = []
    for r in mcp.list_resources():                      # resources/list
        text = content_text(mcp.read_resource(r["uri"]))  # resources/read
        blocks.append(f"## {r['name']} ({r['uri']})\n{text}")
    return "\n\n".join(blocks)


def run(question: str) -> str:
    with MCPClient() as mcp:
        context = _load_context(mcp)
        response = _client.models.generate_content(
            model=_MODEL,
            contents=[types.Content(role="user", parts=[types.Part(text=question.strip())])],
            config=types.GenerateContentConfig(
                system_instruction="You are a support agent. Answer using ONLY the "
                                   "policy documents below. If they don't cover it, "
                                   "say so — don't guess.\n\n" + context,
            ),
        )
        return response.text or ""

Archivos del proyecto

  • .gitignore
  • README.es.md
  • README.md
  • backend/Dockerfile
  • backend/ai_gemini.py
  • backend/ai_openai.py
  • backend/main.py
  • backend/mcp_client.py
  • backend/mcp_server.py
  • backend/requirements.txt
  • bootstrap-secrets.sh
  • docker-compose.yml
  • frontend/Dockerfile
  • frontend/app/layout.tsx
  • frontend/app/page.tsx
  • frontend/next.config.ts
  • frontend/package.json
  • frontend/tsconfig.json