Skip to content

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

@dgenio

Description

@dgenio

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 modelcontextprotocol/modelcontextprotocol#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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions