python-sdk icon indicating copy to clipboard operation
python-sdk copied to clipboard

Document recommended FastMCP server layout and tool version organization (server.py + tools/ modules)

Open dgenio opened this issue 1 month ago • 0 comments

Description

Summary

The current Python SDK docs and examples focus on minimal, single-file servers (e.g. weather.py with all tools in one module). This is great for getting started but leaves a gap for users who are building larger, multi-tool or multi-team MCP servers.

With https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1575 introducing tool versioning support, it would be useful to document a recommended FastMCP server layout that scales well, including:

  • A clear separation between MCP wiring and tool implementations.
  • How to organize tools into modules.
  • How to implement and register multiple versions of a tool in Python.

Motivation

As soon as a server grows beyond a couple of tools, authors start reinventing patterns:

  • One giant server.py file with all tools mixed in.
  • Ad-hoc imports where helpers and MCP wiring are interleaved.
  • Inconsistent placement of new versions of tools (new file vs. new function vs. new package).

A light-weight, documented pattern in the Python SDK would help:

  • Teams share a consistent “shape” for MCP servers.
  • New contributors can navigate repositories more easily.
  • Versioned tools from SEP-1575 can be implemented in a predictable way.

Proposed pattern

1. Server entrypoint (server.py)

  • A single entrypoint file that handles:
    • Creating the FastMCP instance.
    • Configuring transports and logging.
    • Registering tools imported from tools/.
    • The main() function and if __name__ == "__main__": guard.

Example (simplified):

# server.py
from mcp.server.fastmcp import FastMCP
from .tools.get_info import get_info_v1, get_info_v2

mcp = FastMCP("example-server")

# Register versioned tools (SEP-1575)
mcp.add_tool(get_info_v1, name="get_info", version="1.0.0")
mcp.add_tool(get_info_v2, name="get_info", version="2.0.0")

def main() -> None:
    mcp.run(transport="stdio")

if __name__ == "__main__":
    main()

2. Tools package (tools/)

  • A Python package tools/ with one module per conceptual tool:

    • tools/get_info.py
    • tools/list_invoices.py
    • etc.
  • Each module contains:

    • Pure business logic helpers.
    • One or more versioned handler functions for MCP.

Example:

# tools/get_info.py

from typing import Any

def _get_info_core(resource_id: str) -> dict[str, Any]:
    # Shared logic across all versions
    ...

async def get_info_v1(resource_id: str) -> str:
    """Legacy text-only version."""
    data = _get_info_core(resource_id)
    return f"{data['name']}\n{data['description']}"

async def get_info_v2(resource_id: str) -> dict[str, Any]:
    """Structured JSON version."""
    return _get_info_core(resource_id)

The decorators can either live here (using FastMCP as a global) or be applied in server.py when registering tools, depending on the desired separation of concerns.

3. Versioned tools with SEP-1575

  • For SEP-1575-based versioning, the recommended SDK usage would be:
# server.py

from mcp.server.fastmcp import FastMCP
from .tools.get_info import get_info_v1, get_info_v2

mcp = FastMCP("example-server")

@mcp.tool(name="get_info", version="1.0.0")
async def get_info_v1_handler(resource_id: str) -> str:
    return await get_info_v1(resource_id)

@mcp.tool(name="get_info", version="2.0.0")
async def get_info_v2_handler(resource_id: str) -> dict[str, Any]:
    return await get_info_v2(resource_id)

Or, using add_tool if that is preferred over decorators.

This shows:

  • Stable tool name: "get_info".
  • Multiple versions via the version argument.
  • A natural place to factor shared logic.

Proposed changes in this repo

  1. Documentation

    • Add a new section under the server docs (e.g. Server Patterns or Scaling your server) describing this layout and its rationale.

    • Include a short code example of:

      • server.py wiring.
      • tools/<tool>.py module with multiple versions.
      • How to use version with FastMCP decorators or add_tool.
  2. Example project

    • Add an examples/versioned_server/ example (or similar):

      • Uses the proposed layout.

      • Demonstrates:

        • Multiple tools in tools/.
        • One tool with two versions (1.x, 2.x).
        • How clients can call different versions (e.g. via tool_requirements in a small client script).
  3. Cross-link from tutorials

    • From the existing “Build a server” quickstart, add a short “Next steps” link:

      • “For larger projects, see the recommended server layout in [link].”

Alternatives / Open Questions

  • Should the SDK encourage decorators-in-module vs. decorators-in-server patterns, or present both as valid?
  • Should there be a simple CLI template (e.g. mcp init) that scaffolds this layout?
  • How tightly should this layout guidance be coupled to SEP-1575 (e.g. only documented once tool versioning is merged and stable)?

How I can help

I am happy to:

  • Draft the documentation page for the proposed layout.
  • Contribute a small examples/versioned_server/ example aligned with SEP-1575.
  • Iterate on naming and structure based on maintainer feedback.

References

No response

dgenio avatar Nov 28 '25 10:11 dgenio