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

Version-aware discovery for list_tools and related MCP APIs (Python SDK + protocol)

Open dgenio opened this issue 1 month ago • 1 comments

Description

Summary

I’d like the MCP Python SDK (and, where appropriate, the underlying MCP protocol/server behavior) to support version-aware discovery for list_tools and potentially related discovery endpoints such as list_prompts and list_resources.

The high-level goal is:

  • Allow clients to optionally request discovery results for a specific server/tool manifest version (e.g. via a version or server_version parameter).
  • Preserve current behavior when no version is provided (i.e. “latest” remains the default).
  • Ensure that, when a version is specified, the discovery payload reflects a consistent snapshot of tools/prompts/resources as they existed for that version (including schemas and descriptions).

This would give consumers a stable, versioned contract for discovery-based behavior while still allowing servers to evolve.


Motivation / Problem

Many MCP clients dynamically query list_tools (and similar APIs) to:

  • Enumerate which tools exist.
  • Inspect parameter schemas.
  • Infer capabilities from descriptions or metadata.

This works well initially, but the current unversioned behavior makes it hard to maintain stability over time:

  • New tools appearing in list_tools can subtly change how clients behave if they select tools based on presence/absence or ranking logic.
  • Breaking changes in tool schemas or descriptions (e.g. changing parameter shape, renaming fields, removing parameters) can break existing clients that were built against the previous schema.
  • Clients have no clean way to say “I support server/tool manifest version X” and expect discovery to reflect that contract.

In practice, this leads to:

  • Clients implementing their own ad-hoc “version pinning” workarounds.
  • Servers being more conservative in evolving tools, for fear of breaking unknown consumers.
  • A higher risk of “silent” breakages whenever discovery changes.

A version-aware discovery model would let:

  • Clients pin themselves to a known stable tool surface.
  • Servers evolve tools and descriptions more confidently.
  • The ecosystem adopt a more explicit, opt-in model for new capabilities.

Proposed behavior

At a conceptual level:

  1. Separate version domains

    • The MCP server exposes a server / manifest version (e.g. semver).
    • Individual tools and/or tool manifests can also have their own semantic versions.
    • The server tracks, for each server/manifest version, which tool versions were part of that snapshot.
  2. Version-aware discovery APIs

    • list_tools (and potentially list_prompts, list_resources) accepts an optional version parameter, e.g.:

      • version (generic)
      • or server_version / manifest_version (if we want to be explicit).
    • If no version is provided:

      • Behavior is identical to today — the server returns the latest set of tools and schemas.
    • If a version is provided:

      • Only tools that existed for that version are returned.
      • Each tool’s schema/description matches what was valid for that version (even if it changed later).
      • Tools introduced after that version are not listed.
      • Tools removed after that version but present at that time should still be listed.
  3. Backward compatibility

    • Servers that do not yet support version-aware discovery can safely ignore the parameter.
    • Clients should treat “parameter ignored” as equivalent to “latest”.
    • Once protocol support exists, servers can start honoring the parameter without breaking older clients.

This is primarily a protocol/spec-level feature, but the Python SDK can provide ergonomic access and clear semantics.


Potential API shape (Python SDK)

Assuming a Python SDK client similar to:

from mcp import MCPClient

client = MCPClient(...)

we could imagine something like:

# Current behavior: latest tools
tools_latest = client.list_tools()

# Explicitly pin to a known server/manifest version
tools_v1 = client.list_tools(version="1.2.0")

# Potentially, more structured typing for versions:
from mcp.versioning import Version

target_version = Version.parse("1.2.0")
tools_v2 = client.list_tools(version=target_version)

Possible Python SDK changes and helpers:

  • Client methods:

    • Extend existing discovery methods to accept an optional version parameter:

      • client.list_tools(version: Optional[str | Version] = None, ...)
      • client.list_prompts(version: Optional[str | Version] = None, ...)
      • client.list_resources(version: Optional[str | Version] = None, ...)
  • Version types / utilities:

    • A small Version helper (or re-export of a standard semver type) to:

      • Parse & validate semver strings.
      • Compare versions (<, <=, etc.).
  • Metadata on responses:

    • Discovery responses could optionally include the resolved version used by the server (e.g. if a client requested 1.2 and the server resolved to 1.2.3).
    • This would help clients log or enforce expected versions.

These examples are intentionally high-level; the exact API surface can follow existing SDK conventions.


Open questions / considerations

A few areas where maintainer and spec input would be especially valuable:

  1. Spec vs SDK boundaries

    • How should the version parameter be represented in the MCP protocol itself (transport-level field vs. higher-level option)?
    • Is the version attached to the server, to a manifest, or something else?
    • Should the protocol define a strongly-typed semver structure, or leave it as a string with recommended semantics?
  2. Source of truth for version snapshots

    • How does the server know which tools/schemas belong to a given version?

      • Git tag → manifest mapping?
      • Explicit versioned manifest files?
      • A server-managed registry of tool versions?
    • The SDK shouldn’t need to know the internal mechanism, but the spec likely needs to define expectations.

  3. Defaulting and negotiation

    • How do clients discover the latest supported version and their own minimum/maximum supported versions?

    • Is there a handshake / capabilities mechanism that could be extended to include version ranges?

    • Should the Python SDK expose a helper to:

      • Fetch server capabilities.
      • Negotiate a compatible version.
      • Cache the chosen version for subsequent discovery calls.
  4. Migration and rollout strategy

    • How do we introduce this without disrupting existing servers and clients?

      • e.g. “If version is omitted or unsupported, behave exactly as today.”
    • Should we encourage servers to start tagging versions even before they fully implement historical snapshots, so that the API remains future-compatible?

  5. Consistency across discovery endpoints

    • Should list_tools, list_prompts, and list_resources all share the same version parameter and semantics?
    • Are there other discovery-like APIs where version-awareness would be valuable?

Below is an initial proposal for how version bumps might work; it’s meant as a starting point for discussion, not a final rule.

Initial proposal: semver rules for tools vs server

As a starting point, here’s a strawman matrix for how different changes could map to semver bumps at the tool level vs the server/manifest level:

Change type Tool SemVer Server SemVer
Add a required parameter MAJOR MINOR
Add an optional parameter MAJOR MINOR
Add a new tool N.A. MINOR
Change tool description (meaningfully affects usage) MAJOR MINOR
Internal tool change that does not change contract MINOR PATCH
Bugfix PATCH

Rationale (open to discussion):

  • Tool-level MAJOR for any change that can break clients built against the previous tool contract, including:
    • New required params
    • Optional params that might change how agents select or call tools
    • Description changes that could affect prompt-based selection/behavior.
  • Server-level MINOR whenever the exposed tool surface changes (new tools, contract/signature changes).
  • Server-level PATCH for internal/server changes that don’t affect the observable tool contract but may impact behavior, performance, or correctness.
  • The for server bugfixes assumes tool-level bugfixes that don’t change the manifest don’t need to bump the server version; happy to adjust if we want every tool change to correspond to at least a PATCH on the server.

I’m happy to help iterate on the API shape and semantics, but the main goal of this issue is to capture the end-to-end desired behavior and highlight how the Python SDK can expose it once the underlying protocol support exists.

References

No response

dgenio avatar Nov 25 '25 06:11 dgenio

Small note connecting this to the broader work on tool versioning.

This proposal is related to, but distinct from, https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1766:

  • #1766 focuses on identifying and pinning specific tool versions (e.g., via semantic versions and digests) in discovery responses so clients can verify what they are calling.
  • This issue focuses on version-aware discovery at the manifest/server level — i.e., allowing a client to request list_tools/list_prompts/list_resources for a specific manifest/server version and receive a consistent snapshot of tools and schemas that were valid for that version.

Put differently:

  • #1766 answers: “Which version/digest of this tool is this?”
  • This issue answers: “Which set of tools/schemas do I see when I target manifest/server version X?”

I think these two pieces are complementary: a spec-level story for tool-level versions/digests plus a version-aware discovery mechanism would together give clients a much stronger basis for stability, pinning, and reproducibility.

dgenio avatar Nov 27 '25 16:11 dgenio

Thank you for putting together such a detailed proposal! This is a really thoughtful take on solving the versioning and discovery stability problem.

Unfortunately, as this changes the underlying MCP specification itself, it would first require an SEP to be submitted and accepted into the protocol specification before we can make any changes to the SDK.

You mentioned issue #1766 in the spec repo which is related - if you'd like to contribute to these discussions or submit a new SEP for version-aware discovery, that would be the right place to start. Once something is accepted into the spec, we'd be happy to implement it in the SDK.

AI Disclaimer

maxisbey avatar Dec 02 '25 15:12 maxisbey