Skip to content

Conversation

@malaykurwa
Copy link

@malaykurwa malaykurwa commented Oct 3, 2025

This PR implements SEP-1575 Tool Versioning support, enabling MCP servers to declare semantic versions for their tools
and allowing clients to specify version constraints when making tool calls.

Motivation and Context

Currently, MCP tools lack versioning support, which can lead to breaking changes when tools are updated. This creates instability for clients that depend on specific tool behaviors. The tool
versioning feature addresses this by:

  • Preventing Breaking Changes: Clients can specify version constraints to ensure compatibility
  • Semantic Versioning: Tools can declare versions following SemVer 2.0.0 standard
  • Backwards Compatibility: Existing un-versioned tools continue to work normally
  • Clear Error Handling: Provides meaningful error messages when version requirements cannot be satisfied

How Has This Been Tested?

The implementation includes comprehensive testing:

  • Unit Tests: Version parsing, constraint satisfaction, and version selection algorithms
  • Error Scenarios: Testing version conflicts and unsatisfied requirements
    Test files:
  • examples/test_versioning.py - Comprehensive test suite covering all versioning functionality
  • examples/tool_versioning_example.py - Interactive demonstration of versioning features

Breaking Changes

No breaking changes - This implementation is fully backwards compatible:

  • Existing un-versioned tools continue to work without modification
  • Legacy clients without version requirements use the latest stable version
  • All existing APIs remain unchanged
  • New functionality is purely additive

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Key Implementation Details

Schema Updates:

  • Added optional version field to tool definitions
  • Added optional tool_requirements field to tool call requests
  • Added UNSATISFIED_TOOL_VERSION error code
    Version Constraint Syntax:
  • Caret (^): Allows non-breaking updates (^1.2.3>=1.2.3 <2.0.0)
  • Tilde (~): Allows patch-level updates (~1.2.3>=1.2.3 <1.3.0)
  • Comparison Operators: >, >=, <, <=
  • Exact Version: "1.2.3" (no operator)
    New Files:
  • src/mcp/server/fastmcp/utilities/versioning.py - Core versioning utilities
  • examples/test_versioning.py - Comprehensive test suite
  • examples/tool_versioning_example.py - Interactive examples
  • TOOL_VERSIONING.md - Complete implementation documentation
    Modified Files:
  • src/mcp/types.py - Added version fields and error codes
  • src/mcp/server/fastmcp/tools/base.py - Updated Tool class with version support
  • src/mcp/server/fastmcp/tools/tool_manager.py - Enhanced tool management with versioning
  • src/mcp/server/fastmcp/server.py - Updated FastMCP API with version parameters
  • src/mcp/server/lowlevel/server.py - Low-level server versioning support

Usage Examples

Server-side (creating versioned tools):

  @server.tool(version="1.0.0")
  def get_weather_v1(location: str) -> str:
      return f"Weather in {location}: Sunny, 72°F"
  server.add_tool(get_weather_v2, version="2.0.0")

Client-side (using version constraints):

  result = await server.call_tool(
      "get_weather",
      {"location": "New York"},
      tool_requirements={"get_weather": "^1.0.0"}
  )

This implementation provides a solid foundation for tool versioning while maintaining full backwards compatibility with existing MCP deployments.

This PR description provides a comprehensive overview of your tool versioning implementation, highlighting the key features, testing approach, backwards compatibility, and technical details that
reviewers will need to understand the changes.

@dgenio
Copy link

dgenio commented Nov 28, 2025

Thanks for driving modelcontextprotocol/modelcontextprotocol#1575 and for the detailed write-up – this is a very helpful feature for people running larger MCP servers.

One aspect that might be worth making more explicit (either in TOOL_VERSIONING.md or a follow-up doc) is how server authors should organize versioned tools in real-world codebases, especially for Python/FastMCP.

Right now, many examples and tutorials show “all tools in a single server.py file”, which is great for small demos. As soon as a server grows beyond a few tools or involves multiple teams, authors start looking for patterns like:

  • Where do I put v1 vs v2 of a tool?
  • How do I share core logic between versions without copy-paste?
  • How many versions should my server expose at once?

Given the design in this PR, a natural pattern for Python/FastMCP seems to be:

  • Stable tool name, version in metadata
    Keep the tool name stable (e.g. "get_info") and express the actual version with the new version field.

  • One module per conceptual tool
    Have tools/get_info.py contain the core logic plus multiple handlers:

    # tools/get_info.py
    async def get_info_v1_core(...): ...
    async def get_info_v2_core(...): ...
  • Multiple registered handlers sharing the same name
    In server.py (or via decorators inside the module), register multiple versions of the same tool:

    @server.tool(name="get_info", version="1.0.0")
    async def get_info_v1(...):
        return await get_info_v1_core(...)
    
    @server.tool(name="get_info", version="2.0.0")
    async def get_info_v2(...):
        return await get_info_v2_core(...)
    
    # Clients can then use:
    # tool_requirements={"get_info": "^1.0.0"}

This pattern:

  • Leverages the new version field instead of encoding versions in the name.
  • Keeps the visible tool surface manageable for hosts and UIs (one conceptual tool, multiple versions).
  • Scales well to multi-team servers where each tool/module has a clear owner.

Given modelcontextprotocol/modelcontextprotocol#986 is also standardizing tool-name format, it might be helpful to:

  • Explicitly recommend against baking versions into the tool name (get_info_v1, get_info_v2) when the version field and tool_requirements are available.
  • Show at least one concrete “multi-version tool” example that follows this layout, so server authors have something to copy.

If you think this belongs more in the general docs than in TOOL_VERSIONING.md, I am happy to open a separate issue and/or PR to draft a “Recommended layout for versioned tools in Python / FastMCP” section that lines up with this implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants