mcp-toolserver
mcp-toolserver
Un servidor MCP que expone cuatro herramientas reales (búsqueda de documentos, SQL, aritmética e introspección del corpus), además de un cliente agente que se conecta a él, descubre esas herramientas en tiempo de ejecución y las encadena con Claude para responder preguntas que ninguna herramienta por sí sola podría responder.
Lo que esto demuestra
El Model Context Protocol — un protocolo abierto (Anthropic, noviembre de 2024) que estandariza cómo una aplicación de IA se conecta a herramientas y datos externos. Sin él, cada aplicación de IA necesita una integración a medida por cada herramienta, y cada herramienta necesita una integración a medida por cada aplicación de IA: un problema N×M. MCP lo convierte en N+M: quien provee una herramienta construye un servidor MCP, y cualquier cliente compatible con MCP puede usarlo sin código de integración a medida. Este repositorio es una instancia pequeña y concreta de eso: el servidor y el cliente de aquí no conocen los detalles internos del otro, solo el protocolo que hay entre ambos.
Descubrimiento dinámico de herramientas — el cliente agente nunca fija una lista de herramientas en el código. Llama a
list_tools()en el momento de conectarse y convierte lo que el servidor anuncie en ese momento al formato de uso de herramientas de Anthropic. Añade o quita una herramienta en el servidor y el cliente la detecta automáticamente, sin cambios en el código del cliente.Encadenamiento de herramientas en varios pasos — una sola pregunta puede requerir dos herramientas distintas en secuencia (busca un número y luego haz un cálculo con él), y el bucle del agente lo gestiona por sí mismo: Claude decide llamar a una segunda herramienta usando el resultado de la primera, sin que se le diga explícitamente.
Related MCP server: Sentinel Core Agent
Las cuatro herramientas
Herramienta | Signatura | Qué hace |
|
| Búsqueda semántica sobre el corpus de PDFs procesados por docmind (embeddings densos + Chroma). Devuelve |
|
| SQL de solo lectura sobre una pequeña base de datos de demostración de una empresa real ( |
|
| Evaluación aritmética ( |
|
| Inventario del corpus procesado: |
El docstring de cada herramienta es su descripción MCP: es lo que el LLM lee de verdad para decidir cuándo llamarla, así que están escritos para ese público, no para un humano que hojea el código fuente.
Ejecuciones de demostración en vivo
Las tres ejecuciones siguientes son reales contra la API real de Claude y un subproceso real de servidor MCP (no son rastros inventados). Empezamos con la que en realidad encadena dos herramientas, porque es el caso interesante.
1. Multi-paso: query_database → calculate
„¿Cuál es el salario promedio en Ingeniería y cuánto costaría en total un incremento del 12%?"
Answer:
Here's the breakdown for the Engineering department:
| Metric | Value |
|---|---|
| Average Salary | $141,600 |
| Total Current Payroll | $708,000 |
| Cost of 12% Raise | $84,960 |
| New Total Payroll | $792,960 |
A 12% raise across all Engineering employees would cost an additional $84,960,
bringing the department's total payroll from $708,000 to $792,960.
Iterations: 3
Tool calls:
1. query_database({'sql': "SELECT AVG(salary) as avg_salary, SUM(salary) as total_salary FROM employees WHERE department_id = (SELECT id FROM departments WHERE name = 'Engineering')"})
-> [{'avg_salary': 141600.0, 'total_salary': 708000}]
2. calculate({'expression': '708000 * 0.12'})
-> 84960.0Claude escribió el SQL por sí mismo, leyó el resultado y, luego, escribió y ejecutó la expresión aritmética por sí mismo: las entradas de herramienta de arriba son de Claude, no están escritas de antemano. (Comprobado a mano: 5 empleados de Ingeniería que suman 708 000 $ en los datos de ejemplo, ÷5 = 1416 en promedio, ×0,12 = 84 960 $).
2. Una sola herramienta: query_database
„¿Cuántos empleados hay en el departamento de Ingeniería?"
Answer:
There are 5 employees in the Engineering department.
Iterations: 2
Tool calls:
1. query_database({'sql': "SELECT COUNT(*) as employee_count FROM employees e JOIN departments d ON e.department_id = d.id WHERE d.name = 'Engineering'"})
-> [{'employee_count': 5}]3. Una sola herramienta: search_documents
„Qué es el RAG correctivo?"
Answer:
## Corrective RAG (CRAG)
Corrective RAG (CRAG) is an enhanced version of standard Retrieval-Augmented
Generation (RAG) that adds a self-correction step after the initial retrieval
phase. [...] Standard (vanilla) RAG simply takes the top-k retrieved documents
and passes them directly to the language model generator -- regardless of
whether those documents actually answer the question. CRAG improves on this by
checking retrieval quality before generation.
[... full answer continues with the retrieve -> grade -> (generate | rewrite &
retry) flow and the latency/LLM-call tradeoff, condensed here for length ...]
Iterations: 2
Tool calls:
1. search_documents({'query': 'corrective RAG'})
-> [5 chunks from langgraph_agents.pdf and llm_evaluation.pdf, scores 0.44-0.58]La respuesta se basa en el texto realmente recuperado (langgraph_agents.pdf de docmind), no en conocimiento general sobre CRAG, que Claude también posee, pero no se le solicitaba usar aquí.
Seguridad
query_database — protección en capas, no un control único:
Verificación de palabras clave y formato a nivel de aplicación — rechaza cualquier cosa que no sea una única sentencia
SELECT(oWITH ... SELECT) antes de que llegue a SQLite. BloqueaINSERT,UPDATE,DELETE,DROP,ALTER,CREATE,ATTACH,DETACH,PRAGMA,VACUUM,REINDEX, y rechaza de lleno múltiples sentencias (separadas por;).El modo de solo lectura nativo de SQLite — la conexión en sí se abre con
?mode=roen la URI. Lo aplica el motor SQLite, no el código de la aplicación, así que es el respaldo real para el caso de que el paso 1 tuviera una brecha: incluso una consulta que de alguna manera pasara la comprobación de palabras clave no puede escribir físicamente.Límite de filas — cada consulta se envuelve como
SELECT * FROM (<query>) LIMIT 500, de modo que ninguna consulta pueda devolver más de 500 filas, sin importar qué pida.Tiempo máximo de ejecución — un controlador de progreso de
sqlite3comprueba el tiempo transcurrido y aborta la sentencia si tarda demasiado.
calculate — lista blanca del AST, no eval(): la expresión se parse y se recorre manualmente con ast.parse(..., mode="eval"); solo se permiten los nodos de Constant (numérico), BinOp (+ - * / ** %) y UnaryOp (+/-). Cualquier otra cosa — una búsqueda Name, una llamada Call, un Attribute — no encuentra rama en el recorrido y genera un ValueError por construcción. Por eso calculate("__doct__('os').system('...')") falla: no es que se compare contra una lista negra de llamadas peligrosas; simplemente no hay ninguna ruta de código que pueda ejecutar un nodo Call en ningún momento.
Decisiones de diseño
Bucle de agente explícito, no el constructor de herramientas beta del SDK de Anthropic. El SDK sí incluye un puente MCP (
anthropic.lib.tools.mcp) que conecta las herramientas MCP directamente al constructor de herramientas. No se usó aquí porque el objetivo era un contrato de retorno específico e inspeccionable —{answer, tool_calls: [{tool, input, output}], iterations}—, lo que requiere llevar la contabilidad a mano en cada turno. El constructor de herramientas ocultaría exactamente la mecánica (control del bucle, seguimiento de cada llamada) que este proyecto quiere mostrar.Transporte stdio, no streamable-http. El cliente lanza el servidor como un subproceso propio a demanda; ambos viven dentro del mismo límite de confianza y no hay salto de red, por eso la simplicidad de stdio (sin puertos, sin necesidad de autenticación) encaja. Aun así, streamable-http también se permite (con
--transport streamable-httpo la variable envMCP_TRANSPORT) para el caso en que el servidor y el cliente sean procesos o máquinas realmente separados, pero nada de aquí se ha endurecido para ese escenario (ver Limitaciones).Límite de 8 ejecuciones. Acota el costo y la latencia en el peor caso de un bucle sin control — la misma lógica que el límite de sobrescritura de docmind. Las tres ejecuciones de demostración anteriores terminaron en 2 o 3 iteraciones; 8 es un techo generoso pensado para atrapar una petición realmente malformada o un comportamiento de modelo inestable, no algo que se suponga que se alcance en el uso normal.
Conexión con docmind
search_documents y list_documents leen directamente la colección Chroma persistida de docmind (DOCMIND_CHROMA_PATH, que por defecto apunta a data/chroma del proyecto hermano docmind), y hacen las consultas con el mismo modelo all-MiniLM-L6-v2 que docmind usó al procesar. Nada en la conexión es específico de docmind a nivel de código — es solo una colección Chroma en una ruta configurada —, así que este proyecto es un segundo consumidor real de esa información, no una copia. Es una pequeña prueba de que la capa de recuperación de docmind no está conectada solo a su propio backend FastAPI; puede hablarla de ella cualquier cliente con conocimiento de MCP que sepa dónde está la colección.
Limitaciones conocidas
La base de datos de demostración es pequeña y generada artificialmente (5 empleados, 3 departamentos): no hay prueba sobre una base a escala real ni con contenido adverso.
La lista negra de palabras SQL es una regex sobre el texto de la consulta, no un intérprete SQL real — puede bloquear en exceso (por ejemplo, una referencia legítima de una función de tabla
pragma_table_info()) y, en principio, dejar pasar un constructo que a nadie se le haya ocurrido probar. La conexión en modo solo lectura es el mecanismo de defensa que no depende de que la lista de bloqueo sea completa.calculatesolo admite literales numéricos y los seis operadores citados: sin funciones (sqrt,sin, ...), sin variables. Mínimo a propósito, no es un motor de expresiones en general.El servidor MCP no tiene autenticación seguro, sino perfecto para stdio (local al proceso, un solo límite de confianza). Si se usa sobre
streamable-httpcon su implementación actual, quien pueda alcanzar el puerto puede llamar a cualquier herramienta, incluidaquery_database.El límite de 8 iteraciones es un freno en seco, no una degradación elegante: una pregunta que necesite legítimamente más de ~4 viajes de ida y un solo turno obtiene un mensaje de «detenido tras 8 iteraciones» en lugar de una respuesta real.
No hay memoria de conversación entre ejecuciones de la CLI: cada llamada a
python -m toolserver.client.agent "..."empieza una conversación vacía sin historial.No hay streaming: cada iteración del bucle genera una llamada de
messages.createbloqueante; un loop largo o una generación de salida larga bloquea todo el turno.Las pruebas lo cubren todo con mock del cliente de Anthropic y del
Clientde MCP (por diseño: no hay llamadas reales a la API en la suite de pruebas). Eso significa que un drift de esquema en cualquiera de los dos SDK no lo cogería solopytest; las ejecuciones de demostración en vivo de arriba son la única comprobación contra la API real, y son manuales, no forman parte del CI.
Configuración y ejecución
Requiere Python 3.12, una ANTHROPIC_API_KEY y (para search_documents/list_documents) una réplica de docmind con su corpus ya incorporado.
git clone https://github.com/roshano3o3/mcp-toolserver.git
cd mcp-toolserver
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e .
cp .env.example .env # edit .env and set ANTHROPIC_API_KEYPor defecto, DOCMIND_CHROMA_PATH en .env.example apunta a data/chroma de una proyecto hermano docmind. Puedes adaptarla a donde esté tu corpus real de docmind o ignorarla: query_database, calculate y la ruta de error de list_documents sólo funcionan si no hay una copia de docmind.
Ejecuta el agente directamente (él mismo levanta el servidor MCP como subproceso, sin necesidad de arrancar por separado ningún proceso de servidor):
python -m toolserver.client.agent "How many employees are in the Engineering department?"O ejecuta el servidor MCP de forma independiente, por ejemplo para apuntar a otro cliente MCP:
python -m toolserver.server # stdio (default)
python -m toolserver.server --transport streamable-http # http://127.0.0.1:8765/mcp by defaultPruebas:
pytest
ruff check .Verificado contra el SDK instalado (no escrito de memoria)
mcp==2.0.0 es un cambio significativo respecto a la API antigua mcp.server.fastmcp.FastMCP; ese módulo no existe en esta versión. Todo lo siguiente se confirmó leyendo el código fuente del paquete instalado y hechas pruebas de humo en vivo contra él (en el proceso y con un subproceso real de stdio), y no recordado de los datos de entrenamiento:
Servidor:
from mcp.server.mcpserver import MCPServer:MCPServer("name"), herramientas registradas con@server.tool()(el paréntesis es obligatorio;@server.toolsin él lanza un error a propósito).server.run(transport="stdio" | "sse" | "streamable-http").Cliente:
from mcp.client import Client— el nuevo cliente unificado, que reemplaza el uso directo deClientSessionpara la mayoría de casos. Acepta unServer/MCPServeren el proceso, una cadena de URL o unTransport(por ejemplostdio_client(StdioServerParameters(...))).Descubrimiento:
wait client.list_tools()→ListToolsResult, donde cada herramienta traename,description,input_schema— el mismo nombre de campo que exige el formato de uso de herramientas de Anthropic, de modo que la conversión del lado del cliente es un mapeo casi directo, no un traductor de esquemas.Resultados de la herramienta:
CallToolResulttrae and.content(lista de bloques de contenido MCP, siempre poblado) como.structured_content(un dice "result": ...}), poblado cuando la función de la herramienta tiene una anotación de tipo de retorno — algo cierto para las cuatro herramientas de aquí).
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables Claude Code to perform programmatic tool calling by executing Python scripts that interact with multiple MCP servers in a single round-trip. This reduces latency and token consumption by keeping intermediate tool results within the local Python runtime instead of the conversation context.1MIT
- FlicenseNot gradedqualityDmaintenanceEnables file system operations, web scraping, and AI-powered search through MCP tools for use by LLM agents.1
- FlicenseNot gradedqualityBmaintenanceEnables automatic discovery and reuse of tools from Claude Code execution traces. Provides MCP tools that are distilled from real work, allowing you to reuse previously written scripts without manual effort.
- AlicenseNot gradedqualityBmaintenanceEnables document ingestion and typed knowledge graph queries through Claude MCP tools, allowing agents to extract, store, and retrieve typed entities and relations from documents.2MIT
Related MCP Connectors
Free OpenAI-compatible inference with signed provenance receipts and 3 focused MCP tools.
Hosted MCP with 91 agent tools: X, domains, SEO, Maps, Trends, Search, YouTube, TikTok, and more.
AI Reasoning Cache & Consensus Layer with 11 MCP tools via Streamable HTTP.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/roshano3o3/mcp-toolserver'
If you have feedback or need assistance with the MCP directory API, please join our Discord server