Skip to main content
Glama
muhammadwaqasmbd

mcp-server-template

mcp-server-template

Un punto de partida listo para producción para un servidor MCP.

La guía de inicio rápido de la documentación de MCP te da una herramienta funcionando en diez líneas. Esto es lo que terminas añadiendo durante las tres semanas siguientes, una vez que algo que no controlas llama a esa herramienta.

@mcp.tool()
def add(a: int, b: int) -> int:
    return a + b          # fine on a laptop

Lo que falta ahí no son funciones. Es lo que ocurre cuando la herramienta se cuelga, lanza una excepción, devuelve una novela o recibe cuarenta llamadas a la vez — y qué se le permite ver al modelo cuando eso sucede.


El problema que esto resuelve

Quien llama a una herramienta MCP es un modelo de lenguaje, lo que cambia la ingeniería.

  • Un modelo no puede leer un stack trace, pero lo repetirá encantado a tu usuario. Así que un traceback filtrado es a la vez inútil y una divulgación.

  • Un modelo tiene su propio plazo límite. Una herramienta que se cuelga no produce una respuesta lenta; produce una conversación muerta.

  • Un modelo no puede distinguir un resultado truncado de uno completo. Desbordar su contexto en silencio no lanza un error: degrada la respuesta, y te enteras por un cliente.

  • Un modelo reintentará si se lo permites. Así que «no encontrado» y «el upstream está caído» tienen que ser respuestas distintas, o castigará a un servicio por un registro que nunca existió.

Cada uno de esos problemas se gestiona una sola vez, en un solo lugar, de modo que una herramienta añadida un viernes por la tarde hereda las mismas protecciones que una que se escribió con cuidado el primer día.


Related MCP server: mcp-starter-kit

Lo que obtienes

Tiempo de espera por herramienta

Cancelación real, no un aviso posterior. Devuelve un error retryable sobre el que el modelo puede actuar

Límite de concurrencia

Ejecución en paralelo acotada, para que una ráfaga no pueda avasallar a lo que llamen tus herramientas

Barrera de errores

Los errores declarados llegan al llamador; los inesperados se convierten en internal_error sin detalles, y el traceback va al log

Ocultación de secretos

Se aplica a los logs y a los mensajes salientes, porque las claves se escapan a través de cadenas de excepción interpoladas más a menudo que por el código

Truncado visible

Los resultados sobredimensionados se cortan con un marcador, nunca silenciosamente

IDs de correlación

Un id por llamada, en el log y en el error que el usuario puede citarte de nuevo

Registros estructurados en stderr

stdout pertenece al protocolo: un print() perdido corrompe el flujo

Configuración de fallo rápido

Una configuración errónea detiene el servidor al arrancar, no en la primera solicitud

Pruebas sin conexión

La suite se ejecuta incluso en un tren. Sin claves vivas, sin red


Inicio rápido

git clone https://github.com/muhammadwaqasmbd/mcp-server-template
cd mcp-server-template
make install
make test
make run          # stdio, ready for a desktop MCP client

También se puede servir por red:

TRANSPORT=streamable-http PORT=8000 python -m mcp_server_template

Apunta hacia él un cliente de escritorio

{
  "mcpServers": {
    "template": {
      "command": "python",
      "args": ["-m", "mcp_server_template"],
      "cwd": "/absolute/path/to/mcp-server-template"
    }
  }
}

Añadir tu propia herramienta

Escribe la función. Nada más.

# src/mcp_server_template/tools/orders.py
from ..errors import InvalidInput, UpstreamUnavailable

async def cancel_order(order_id: str) -> dict:
    """Cancel an order. Returns the order's new state."""
    if not order_id.strip():
        raise InvalidInput("order_id must not be empty")     # model can fix this
    ...
    raise UpstreamUnavailable("order service timed out")     # model may retry

Regístrala detrás del guard:

mcp.tool(name="cancel_order", description="Cancel an order by id.")(
    guard.wrap(orders.cancel_order)
)

Ahora ya tiene el tiempo de espera, el límite de concurrencia, la barrera de errores, el truncamiento y el registro. Y no ha tenido que escribir ni gota de eso.

Lanza InvalidInput cuando el modelo pueda arreglarlo. Lanza UpstreamUnavailable cuando reintentar pueda funcionar. Devuelve algo normal para los resultados que son simplemente false — un registro que no existe es una respuesta, no una excepción.


Arquitectura

server.py      the ONLY module that imports the MCP SDK
   │
   ├── guard.py          timeout · concurrency · error boundary · truncation · timing
   ├── errors.py         what a model is allowed to see, and secret redaction
   ├── observability.py  JSON logs on stderr, correlation ids
   ├── config.py         validated once at boot, immutable thereafter
   └── tools/            plain functions. No protocol knowledge. No decorators

La flecha de dependencia apunta en una sola dirección: que las herramientas no saben nada de MCP, y que el guard no sabe nada de tus herramientas. Por eso las pruebas levan en milisegundos sin servidor y por eso un cambio en el SDK toca exactamente un solo archivo.


Lo que deliberadamente no hace

Ser sincero con las limitaciones es más útil que una lista más larga de funcionalidades.

  • Sin autenticación. Por stdio, la seguridad con el sistema operativo es causada por el límite. Si lo expones por HTTP, pon delante autenticación real — el SDK lo soporta, y conectarlo aquí supondría un modelo de amenazas aún para decidir.

  • No hay lógica de reintentos dentro de las herramientas. El guard informa de si blow falla es reintable; decidir reintentar corresponde al llamante, que tiene el contexto y el presupuesto.

  • Sin límite de tasa por llamante. El límite de concurrencia acota el trabajo total, no la equidad por identidad. Si lo necesitas, primero necesitas identidad.

  • Sin persistencia, cola ni programador. Un servidor de herramientas que se convierte silenciosamente en un ejecutor de trabajo es un sistema distribuido que nos ha diseñado.

  • Sin resultados parciales en streaming. Merece la pena añadirlo para herramientas de larga duración; se ha omitido porque complica la barrera de errores y la mayoría de las herramientas lo necesitan.


Pruebas

make test

La suite está hecha deliberadamente para el fallo, no para la cobertura. Declara que una herramienta colgada se cancela, que una excepción inesperada no puede filtrar su mensaje, que el resultado sobredimensionado se trunca visiblemente, que el límite de concurrencia aguanta bajo diez llamadas simultáneas, y que una herramienta sincronizada bloqueante se muera de hambre en el bucle de eventos.


Licencia

MIT — ver LICENSE.

Creado por Muhammad Waqas, que dedica la mayor parte de su tiempo a sistemas de agentes en las industrias reguladas, donde una respuesta errónea pero segura es un incidente que hay que informar.

Available Tools

3 tools
fetch_recordA

Look up a record by key. Returns found=false when absent; that is not an error.

NameRequiredDescriptionDefault
keyYes
simulate_outageNo

Output Schema

NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden of behavioral disclosure. It does add one valuable behavioral detail: a missing record is reported via 'found=false' and is not treated as an error. This is useful context. However, it does not state that the operation is read-only, nor does it explain the simulate_outage parameter's behavior. The description provides some behavioral clarity but leaves notable gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, direct, and front-loaded with the primary action. It avoids verbosity and includes only the most essential information. The key behavioral note about 'found=false' is presented efficiently. No wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple fetch tool, the description covers the main action and the most important edge case (absent record). However, it omits any explanation of the 'simulate_outage' parameter, which is part of the tool's interface. While an output schema exists and relieves the description of detailing return values, the parameter gap remains. The description is adequate for basic usage but incomplete for full correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It implicitly clarifies that 'key' is the lookup identifier, but it offers no additional detail about the parameter's format or acceptable values. The 'simulate_outage' parameter is entirely unexplained—an agent cannot infer its purpose or effect from the description. Given the low coverage, this is a significant shortfall.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the primary action: 'Look up a record by key.' This is a specific verb (look up) with a clear resource (record) and method (by key). It is immediately distinct from sibling tools like summarise_lengths and slow_operation, as those suggest different operations. No ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies when to use the tool (when you need to retrieve a record by its key) but provides no explicit guidance on alternatives or exclusion conditions. It does not mention any context where this tool should be avoided or mention sibling tools. This qualifies as implied usage, which meets the minimum viable threshold.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

slow_operationA

Sleeps, to demonstrate the server's timeout behaviour.

NameRequiredDescriptionDefault
secondsNo

Output Schema

NameRequiredDescription
resultYes

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must carry the full burden of behavioral disclosure. It states the core behavior (sleeping) and the intended effect (timeout demonstration), but does not mention the adjustable 'seconds' parameter, what happens after the sleep (return value or error), or any side effects. This is partially transparent but incomplete for a tool with no annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, short sentence that front-loads the core action ('Sleeps') and purpose. There is no wasted wording, making it highly efficient and easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple tool with one optional parameter and an output schema, the description is fairly complete. It conveys the tool's purpose and behavior, and the output schema covers return values. However, it could explicitly mention that the sleep duration is configurable via the 'seconds' parameter, which is currently only in the schema. Overall, it is adequate for a demo tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has one parameter 'seconds' with default 60 and 0% description coverage. The description does not mention this parameter at all, so it fails to connect the sleep duration to the parameter. The parameter name is somewhat self-explanatory, but the description provides no added meaning, leaving the agent to guess how the parameter influences the operation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Sleeps' and the resource 'server's timeout behaviour', making the tool's purpose unmistakable. It distinguishes itself from siblings 'summarise_lengths' and 'fetch_record' which clearly serve different functions.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for demonstrating timeout behaviour, which gives context, but it does not explicitly mention when to use this tool versus alternatives. No exclusions or conditions are provided, leaving the agent to infer that it is for testing timeouts only.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

summarise_lengthsA

Return per-item character counts and a total for a list of strings.

NameRequiredDescriptionDefault
itemsYes

Output Schema

NameRequiredDescription

No output parameters

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It states the behavior accurately but does not discuss edge cases (e.g., empty list, null elements) or any side effects. For a pure computation tool, this is adequate but minimal disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single, front-loaded sentence with no wasted words. The core functionality is stated immediately, making it quick for an agent to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple, and an output schema exists, so the description does not need to explain return values. Combined with the clear single parameter and the presence of the output schema, the description is complete for an agent to invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate for the undocumented 'items' parameter. It clarifies that the parameter is a list of strings, which aligns with the schema. However, it does not add details about constraints (e.g., whether null is allowed) or the order of results. The description adds minimal value beyond the schema's type declaration.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns per-item character counts and a total for a list of strings, specifying a concrete verb and resource. It does not explicitly differentiate from siblings, but the siblings (fetch_record, slow_operation) are obviously distinct in purpose, so the lack of explicit differentiation isn't a major gap.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The purpose implies when to use it (whenever you need length summaries), but no explicit guidance is given about when not to use it or which alternatives to prefer. Since the alternatives are clearly unrelated, the implied usage is sufficient, though not explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.0
    • First observedfetch_record
    • First observedslow_operation
    • First observedsummarise_lengths

TDQS

A4/5.0

Scored across 3 tools

Disambiguation5/5

Each tool addresses a separate concern: one computes string lengths, one looks up records, and one simulates a timeout. There is no overlap or realistic chance of choosing the wrong tool.

Naming Consistency4/5

Two tools follow a clear snake_case verb_noun pattern (summarise_lengths, fetch_record), but slow_operation reads as adjective_noun. The naming is still readable and predictable overall.

Tool Count5/5

Three tools is a reasonable size for a template server. Each tool exists to demonstrate a distinct capability, so none feel redundant or excessive.

Completeness5/5

As a template, the set covers the demonstrated scenarios: a utility operation, a record lookup with a defined absent-case, and a timeout example. There are no obvious dead ends or missing pieces for that purpose.

Maintenance

ActivityStale
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    C
    maintenance
    A production-ready foundation for building secure, observable MCP servers with built-in authentication, rate limiting, and reference tools like database-query and semantic-search.
    15
    33 npm
    MIT
  • F
    license
    Not graded
    quality
    D
    maintenance
    A production-ready, security-first starting point for building Model Context Protocol servers, enforcing safe defaults like dry-run and tenant isolation.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    An enterprise MCP server scaffold that provides secure, governed access to internal tools through RBAC, audit logging, rate limiting, and prompt-injection boundaries, with a FastAPI control plane and OpenTelemetry observability.
    MIT