Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
- name: Run tests (excluding integration and client_process)
run: |
if [ "${{ matrix.os }}" == "windows-latest" ]; then
uv run pytest --inline-snapshot=disable tests -m "not integration and not client_process" -v
uv run pytest --inline-snapshot=disable tests -m "not integration and not client_process"
else
uv run pytest --inline-snapshot=disable tests -m "not integration and not client_process" --numprocesses auto --maxprocesses 4 --dist worksteal
fi
Expand Down
65 changes: 63 additions & 2 deletions docs/clients/prompts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,29 @@ async with client:
"name": "Alice",
"role": "administrator"
})

# Access the personalized messages
for message in result.messages:
print(f"Generated message: {message.content}")
```

### Requesting Specific Versions

<VersionBadge version="3.0.0" />

When a server has multiple versions of a prompt, you can request a specific version instead of the default (highest) version.

```python
async with client:
# Get the highest version (default)
result = await client.get_prompt("summarize", {"text": "..."})

# Get a specific version
result_v1 = await client.get_prompt("summarize", {"text": "..."}, version="1.0")
```

To discover available versions, check the `meta.fastmcp.versions` field when listing prompts. See [Version Selection](#version-selection) below for more details.

## Automatic Argument Serialization

<VersionBadge version="2.9.0" />
Expand Down Expand Up @@ -213,4 +230,48 @@ async with client:

<Tip>
Prompt arguments and their expected types depend on the specific prompt implementation. Check the server's documentation or use `list_prompts()` to see available arguments for each prompt.
</Tip>
</Tip>

## Version Selection

<VersionBadge version="3.0.0" />

FastMCP servers can expose multiple versions of the same prompt. By default, clients receive and request the highest version, but you can request a specific version when needed.

### Discovering Versions

When a server registers multiple versions of a prompt, the `list_prompts()` response includes version information in the metadata. The `meta.fastmcp.version` field shows which version is being returned, while `meta.fastmcp.versions` lists all available versions sorted from highest to lowest.

```python
async with client:
prompts = await client.list_prompts()

for prompt in prompts:
if prompt.meta:
fastmcp_meta = prompt.meta.get("fastmcp", {})
version = fastmcp_meta.get("version")
all_versions = fastmcp_meta.get("versions")
if all_versions:
print(f"{prompt.name}: v{version} (available: {all_versions})")
```

Unversioned prompts omit these metadata fields entirely.

### Getting Specific Versions

Pass the `version` parameter to `get_prompt()` to render a specific version instead of the highest.

```python
async with client:
# Get the highest version (default)
result = await client.get_prompt("summarize", {"text": "..."})

# Get version 1.0 specifically
result_v1 = await client.get_prompt("summarize", {"text": "..."}, version="1.0")
```

If the requested version doesn't exist, the server raises a `NotFoundError`. This ensures you get exactly what you asked for rather than silently falling back to a different version.

<Note>
Version selection is a FastMCP extension to the MCP protocol. See [Versioning](/servers/versioning#requesting-specific-versions) for details on how this works at the protocol level for non-FastMCP clients.
</Note>
28 changes: 27 additions & 1 deletion docs/clients/resources.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,35 @@ async with client:
```

<Note>
The `meta` field is part of the standard MCP specification. FastMCP servers always include tags and other metadata within a `fastmcp` namespace (e.g., `meta.fastmcp.tags`) to avoid conflicts with user-defined metadata. Component versions are also included in the metadata when available (e.g., `meta.fastmcp.version`). Other MCP server implementations may not provide this metadata structure.
The `meta` field is part of the standard MCP specification. FastMCP servers always include tags and other metadata within a `fastmcp` namespace (e.g., `meta.fastmcp.tags`) to avoid conflicts with user-defined metadata. For versioned resources, `meta.fastmcp.version` shows the current version and `meta.fastmcp.versions` lists all available versions. Other MCP server implementations may not provide this metadata structure.
</Note>

### Version Information

<VersionBadge version="3.0.0" />

When a server registers multiple versions of a resource, the metadata includes version information.

```python
async with client:
resources = await client.list_resources()

for resource in resources:
if resource.meta:
fastmcp_meta = resource.meta.get("fastmcp", {})
version = fastmcp_meta.get("version")
all_versions = fastmcp_meta.get("versions")
if all_versions:
print(f"{resource.uri}: v{version} (available: {all_versions})")
```

To read a specific version, use the `version` parameter:

```python
# Read a specific version
content = await client.read_resource("data://config", version="1.0")
```
Comment on lines +106 to +111
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add error handling and expected output to the versioned read example.

Line 106-111 shows a direct versioned read but omits the NotFoundError path and any verification output. Adding a try/except with a minimal success print makes the snippet runnable and self-checking. As per coding guidelines.

💡 Suggested update
-# Read a specific version
-content = await client.read_resource("data://config", version="1.0")
+from fastmcp.exceptions import NotFoundError
+
+# Read a specific version
+try:
+    content = await client.read_resource("data://config", version="1.0")
+    print(content[0].text)  # Expected: config JSON for v1
+except NotFoundError:
+    print("Version 1.0 not available")


## Reading Resources

### Static Resources
Expand Down
47 changes: 46 additions & 1 deletion docs/clients/tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ async with client:
**Parameters:**
- `name`: The tool name (string)
- `arguments`: Dictionary of arguments to pass to the tool (optional)
- `version`: Specific tool version to call (optional, see [Version Selection](#version-selection) below)
- `timeout`: Maximum execution time in seconds (optional, overrides client-level timeout)
- `progress_handler`: Progress callback function (optional, overrides client-level handler)
- `meta`: Dictionary of metadata to send with the request (optional, see below)
Expand Down Expand Up @@ -292,4 +293,48 @@ async with client:

<Tip>
For multi-server clients, tool names are automatically prefixed with the server name (e.g., `weather_get_forecast` for a tool named `get_forecast` on the `weather` server).
</Tip>
</Tip>

## Version Selection

<VersionBadge version="3.0.0" />

FastMCP servers can expose multiple versions of the same tool. By default, clients receive and call the highest version, but you can request a specific version when needed.

### Discovering Versions

When a server registers multiple versions of a tool, the `list_tools()` response includes version information in the metadata. The `meta.fastmcp.version` field shows which version is being returned, while `meta.fastmcp.versions` lists all available versions sorted from highest to lowest.

```python
async with client:
tools = await client.list_tools()

for tool in tools:
if tool.meta:
fastmcp_meta = tool.meta.get("fastmcp", {})
version = fastmcp_meta.get("version")
all_versions = fastmcp_meta.get("versions")
if all_versions:
print(f"{tool.name}: v{version} (available: {all_versions})")
```

Unversioned tools omit these metadata fields entirely.

### Calling Specific Versions

Pass the `version` parameter to `call_tool()` to execute a specific version instead of the highest.

```python
async with client:
# Call the highest version (default)
result = await client.call_tool("calculate", {"x": 1, "y": 2})

# Call version 1.0 specifically
result_v1 = await client.call_tool("calculate", {"x": 1, "y": 2}, version="1.0")
```
Comment on lines +325 to +334
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add error handling for version-specific tool calls.

Line 325-334 uses version without showing the NotFoundError path or a success output for validation. A small try/except keeps the example runnable and self-checking. As per coding guidelines.

💡 Suggested update
 async with client:
     # Call the highest version (default)
     result = await client.call_tool("calculate", {"x": 1, "y": 2})
 
     # Call version 1.0 specifically
-    result_v1 = await client.call_tool("calculate", {"x": 1, "y": 2}, version="1.0")
+    from fastmcp.exceptions import NotFoundError
+    try:
+        result_v1 = await client.call_tool("calculate", {"x": 1, "y": 2}, version="1.0")
+        print(result_v1.data)  # Expected: 3
+    except NotFoundError:
+        print("Version 1.0 not available")


If the requested version doesn't exist, the server raises a `NotFoundError`. This ensures you get exactly what you asked for rather than silently falling back to a different version.

<Note>
Version selection is a FastMCP extension to the MCP protocol. See [Versioning](/servers/versioning#requesting-specific-versions) for details on how this works at the protocol level for non-FastMCP clients.
</Note>
44 changes: 41 additions & 3 deletions docs/development/v3-notes/v3-features.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,18 @@ def add(x: int, y: int, z: int = 0) -> int:
- Unversioned components sort lower than any versioned component
- The `v` prefix is normalized (`v1.0` equals `1.0`)

**Retrieving specific versions:**
**Version visibility in meta:**

List operations expose all available versions in the component's `meta` field:

```python
tools = await client.list_tools()
# Each tool's meta includes:
# - meta["fastmcp"]["version"]: the version of this component ("2.0")
# - meta["fastmcp"]["versions"]: all available versions ["2.0", "1.0"]
```

**Retrieving and calling specific versions:**

```python
# Get the highest version (default)
Expand All @@ -240,8 +251,35 @@ tool = await server.get_tool("add")
# Get a specific version
tool_v1 = await server.get_tool("add", version="1.0")

# Get all versions
all_versions = await server.get_tool_versions("add")
# Call a specific version
result = await server.call_tool("add", {"x": 1, "y": 2}, version="1.0")
```

**Client version requests:**

The FastMCP client supports version selection:

```python
async with Client(server) as client:
# Call specific tool version
result = await client.call_tool("add", {"x": 1, "y": 2}, version="1.0")

# Get specific prompt version
prompt = await client.get_prompt("my_prompt", {"text": "..."}, version="2.0")
```

For generic MCP clients, pass version via `_meta` in arguments:

```json
{
"x": 1,
"y": 2,
"_meta": {
"fastmcp": {
"version": "1.0"
}
}
}
```

**VersionFilter transform:**
Expand Down
80 changes: 77 additions & 3 deletions docs/servers/versioning.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,83 @@ def summarize(text: str, style: str = "concise") -> str:
return f"Summarize in a {style} style: {text}"
```

<Note>
Clients always receive the highest version of each component. Versioning controls what the server exposes, not what clients request.
</Note>
### Version Discovery

When clients list components, each versioned component includes metadata about all available versions. This lets clients discover what versions exist before deciding which to use. The `meta.fastmcp.versions` field contains all registered versions sorted from highest to lowest.

```python
from fastmcp import Client

async with Client(server) as client:
tools = await client.list_tools()

for tool in tools:
if tool.meta:
fastmcp_meta = tool.meta.get("fastmcp", {})
# Current version being returned (highest by default)
print(f"Version: {fastmcp_meta.get('version')}")
# All available versions for this component
print(f"Available: {fastmcp_meta.get('versions')}")
```

For a tool with versions `"1.0"` and `"2.0"`, listing returns the `2.0` implementation with `meta.fastmcp.version` set to `"2.0"` and `meta.fastmcp.versions` set to `["2.0", "1.0"]`. Unversioned components omit these fields entirely.

This discovery mechanism enables clients to make informed decisions about which version to request, support graceful degradation when newer versions introduce breaking changes, or display version information in developer tools.

## Requesting Specific Versions

By default, clients receive and invoke the highest version of each component. When you need a specific version, FastMCP provides two approaches: the FastMCP client API for Python applications, and the MCP protocol mechanism for any MCP-compatible client.

### FastMCP Client

The FastMCP client's `call_tool` and `get_prompt` methods accept an optional `version` parameter. When specified, the server executes that exact version instead of the highest.

```python
from fastmcp import Client

async with Client(server) as client:
# Call the highest version (default behavior)
result = await client.call_tool("calculate", {"x": 1, "y": 2})

# Call a specific version
result_v1 = await client.call_tool("calculate", {"x": 1, "y": 2}, version="1.0")

# Get a specific prompt version
prompt = await client.get_prompt("summarize", {"text": "..."}, version="1.0")
```

If the requested version doesn't exist, the server raises a `NotFoundError`. This ensures you get exactly what you asked for rather than silently falling back to a different version.

### MCP Protocol

For generic MCP clients that don't have built-in version support, pass the version through the `_meta` field in arguments. FastMCP servers extract the version from `_meta.fastmcp.version` before processing.

<CodeGroup>
```json Tool Call Arguments
{
"x": 1,
"y": 2,
"_meta": {
"fastmcp": {
"version": "1.0"
}
}
}
```

```json Prompt Arguments
{
"text": "Summarize this document...",
"_meta": {
"fastmcp": {
"version": "1.0"
}
}
}
```
</CodeGroup>

The `_meta` field is part of the MCP request params, not arguments, so your component implementation never sees it. This convention allows version selection to work across any MCP client without requiring protocol changes. The FastMCP client handles this automatically when you pass the `version` parameter.

## Version Comparison

Expand Down
Loading