mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-23 05:54:19 +02:00
Add automatic MCP list change notifications and client message handling
Implements comprehensive notification system for tools, resources, and prompts with automatic client updates and flexible message handlers. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c8da2432c3
commit
38036b1f42
22 changed files with 1013 additions and 58 deletions
129
docs/clients/messages.mdx
Normal file
129
docs/clients/messages.mdx
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
---
|
||||
title: Message Handling
|
||||
sidebarTitle: Messages
|
||||
description: Handle MCP messages, requests, and notifications with custom message handlers.
|
||||
icon: envelope
|
||||
---
|
||||
|
||||
import { VersionBadge } from "/snippets/version-badge.mdx";
|
||||
|
||||
<VersionBadge version="2.9.0" />
|
||||
|
||||
MCP clients can receive various types of messages from servers, including requests that need responses and notifications that don't. The message handler provides a unified way to process all these messages.
|
||||
|
||||
## Function-Based Handler
|
||||
|
||||
The simplest way to handle messages is with a function that receives all messages:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
|
||||
async def message_handler(message):
|
||||
"""Handle all MCP messages from the server."""
|
||||
if hasattr(message, 'root'):
|
||||
method = message.root.method
|
||||
print(f"Received: {method}")
|
||||
|
||||
# Handle specific notifications
|
||||
if method == "notifications/tools/list_changed":
|
||||
print("Tools have changed - might want to refresh tool cache")
|
||||
elif method == "notifications/resources/list_changed":
|
||||
print("Resources have changed")
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
message_handler=message_handler,
|
||||
)
|
||||
```
|
||||
|
||||
## Message Handler Class
|
||||
|
||||
For fine-grained targeting, FastMCP provides a `MessageHandler` class you can subclass to take advantage of specific hooks:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.client.messages import MessageHandler
|
||||
import mcp.types
|
||||
|
||||
class MyMessageHandler(MessageHandler):
|
||||
async def on_tool_list_changed(
|
||||
self, notification: mcp.types.ToolListChangedNotification
|
||||
) -> None:
|
||||
"""Handle tool list changes specifically."""
|
||||
print("Tool list changed - refreshing available tools")
|
||||
|
||||
client = Client(
|
||||
"my_mcp_server.py",
|
||||
message_handler=MyMessageHandler(),
|
||||
)
|
||||
```
|
||||
|
||||
### Available Handler Methods
|
||||
|
||||
All handler methods receive a single argument - the specific message type:
|
||||
|
||||
<Card icon="code" title="Message Handler Methods">
|
||||
<ResponseField name="on_message(message)" type="Any MCP message">
|
||||
Called for ALL messages (requests and notifications)
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_request(request)" type="mcp.types.ClientRequest">
|
||||
Called for requests that expect responses
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_notification(notification)" type="mcp.types.ServerNotification">
|
||||
Called for notifications (fire-and-forget)
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_tool_list_changed(notification)" type="mcp.types.ToolListChangedNotification">
|
||||
Called when the server's tool list changes
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_resource_list_changed(notification)" type="mcp.types.ResourceListChangedNotification">
|
||||
Called when the server's resource list changes
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_prompt_list_changed(notification)" type="mcp.types.PromptListChangedNotification">
|
||||
Called when the server's prompt list changes
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_progress(notification)" type="mcp.types.ProgressNotification">
|
||||
Called for progress updates during long-running operations
|
||||
</ResponseField>
|
||||
|
||||
<ResponseField name="on_logging_message(notification)" type="mcp.types.LoggingMessageNotification">
|
||||
Called for log messages from the server
|
||||
</ResponseField>
|
||||
</Card>
|
||||
|
||||
## Example: Handling Tool Changes
|
||||
|
||||
Here's a practical example of handling tool list changes:
|
||||
|
||||
```python
|
||||
from fastmcp.client.messages import MessageHandler
|
||||
import mcp.types
|
||||
|
||||
class ToolCacheHandler(MessageHandler):
|
||||
def __init__(self):
|
||||
self.cached_tools = []
|
||||
|
||||
async def on_tool_list_changed(
|
||||
self, notification: mcp.types.ToolListChangedNotification
|
||||
) -> None:
|
||||
"""Clear tool cache when tools change."""
|
||||
print("Tools changed - clearing cache")
|
||||
self.cached_tools = [] # Force refresh on next access
|
||||
|
||||
client = Client("server.py", message_handler=ToolCacheHandler())
|
||||
```
|
||||
|
||||
## Handling Requests
|
||||
|
||||
While the message handler receives server-initiated requests, for most use cases you should use the dedicated callback parameters instead:
|
||||
|
||||
- **Sampling requests**: Use [`sampling_handler`](/clients/sampling)
|
||||
- **Progress requests**: Use [`progress_handler`](/clients/progress)
|
||||
- **Log requests**: Use [`log_handler`](/clients/logging)
|
||||
|
||||
The message handler is primarily for monitoring and handling notifications rather than responding to requests.
|
||||
|
|
@ -76,9 +76,7 @@
|
|||
{
|
||||
"group": "Authentication",
|
||||
"icon": "shield-check",
|
||||
"pages": [
|
||||
"servers/auth/bearer"
|
||||
]
|
||||
"pages": ["servers/auth/bearer"]
|
||||
},
|
||||
"servers/middleware",
|
||||
"servers/openapi",
|
||||
|
|
@ -87,10 +85,7 @@
|
|||
{
|
||||
"group": "Deployment",
|
||||
"icon": "upload",
|
||||
"pages": [
|
||||
"deployment/running-server",
|
||||
"deployment/asgi"
|
||||
]
|
||||
"pages": ["deployment/running-server", "deployment/asgi"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -114,6 +109,7 @@
|
|||
"clients/logging",
|
||||
"clients/progress",
|
||||
"clients/sampling",
|
||||
"clients/messages",
|
||||
"clients/roots"
|
||||
]
|
||||
},
|
||||
|
|
@ -121,10 +117,7 @@
|
|||
{
|
||||
"group": "Authentication",
|
||||
"icon": "user-shield",
|
||||
"pages": [
|
||||
"clients/auth/oauth",
|
||||
"clients/auth/bearer"
|
||||
]
|
||||
"pages": ["clients/auth/oauth", "clients/auth/bearer"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
@ -163,17 +156,12 @@
|
|||
},
|
||||
{
|
||||
"anchor": "What's New",
|
||||
"pages": [
|
||||
"updates",
|
||||
"changelog"
|
||||
]
|
||||
"pages": ["updates", "changelog"]
|
||||
},
|
||||
{
|
||||
"anchor": "Community",
|
||||
"icon": "users",
|
||||
"pages": [
|
||||
"community/showcase"
|
||||
]
|
||||
"pages": ["community/showcase"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -275,6 +275,25 @@ async def generate_example(concept: str, ctx: Context) -> str:
|
|||
|
||||
See [Client Sampling](/clients/client#llm-sampling) for more details on how clients handle these requests.
|
||||
|
||||
### Component Changes
|
||||
|
||||
<VersionBadge version="2.9.1" />
|
||||
|
||||
FastMCP automatically sends list change notifications when components (such as tools, resources, or prompts) are added, removed, enabled, or disabled. In rare cases where you need to manually trigger these notifications, you can use the context methods:
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
async def custom_tool_management(ctx: Context) -> str:
|
||||
"""Example of manual notification after custom tool changes."""
|
||||
# After making custom changes to tools
|
||||
await ctx.send_tool_list_changed()
|
||||
await ctx.send_resource_list_changed()
|
||||
await ctx.send_prompt_list_changed()
|
||||
return "Notifications sent"
|
||||
```
|
||||
|
||||
These methods are primarily used internally by FastMCP's automatic notification system and most users will not need to invoke them directly.
|
||||
|
||||
### Request Information
|
||||
|
||||
Access metadata about the current request and client.
|
||||
|
|
|
|||
|
|
@ -237,7 +237,8 @@ def seasonal_prompt(): return "Happy Holidays!"
|
|||
seasonal_prompt.disable()
|
||||
seasonal_prompt.enable()
|
||||
```
|
||||
### Asynchronous Prompts
|
||||
|
||||
### Async Prompts
|
||||
|
||||
FastMCP seamlessly supports both standard (`def`) and asynchronous (`async def`) functions as prompts.
|
||||
|
||||
|
|
@ -280,7 +281,26 @@ async def generate_report_request(report_type: str, ctx: Context) -> str:
|
|||
|
||||
For full documentation on the Context object and all its capabilities, see the [Context documentation](/servers/context).
|
||||
|
||||
### Notifications
|
||||
|
||||
<VersionBadge version="2.9.1" />
|
||||
|
||||
FastMCP automatically sends `notifications/prompts/list_changed` notifications to connected clients when prompts are added, enabled, or disabled. This allows clients to stay up-to-date with the current prompt set without manually polling for changes.
|
||||
|
||||
```python
|
||||
@mcp.prompt
|
||||
def example_prompt() -> str:
|
||||
return "Hello!"
|
||||
|
||||
# These operations trigger notifications:
|
||||
mcp.add_prompt(example_prompt) # Sends prompts/list_changed notification
|
||||
example_prompt.disable() # Sends prompts/list_changed notification
|
||||
example_prompt.enable() # Sends prompts/list_changed notification
|
||||
```
|
||||
|
||||
Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications.
|
||||
|
||||
Clients can handle these notifications using a [message handler](/clients/messages) to automatically refresh their prompt lists or update their interfaces.
|
||||
|
||||
## Server Behavior
|
||||
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ get_config.disable()
|
|||
get_config.enable()
|
||||
```
|
||||
|
||||
|
||||
### Accessing MCP Context
|
||||
|
||||
<VersionBadge version="2.2.5" />
|
||||
|
|
@ -172,7 +173,7 @@ async def get_details(name: str, ctx: Context) -> dict:
|
|||
For full documentation on the Context object and all its capabilities, see the [Context documentation](/servers/context).
|
||||
|
||||
|
||||
### Asynchronous Resources
|
||||
### Async Resources
|
||||
|
||||
Use `async def` for resource functions that perform I/O operations (e.g., reading from a database or network) to avoid blocking the server.
|
||||
|
||||
|
|
@ -278,6 +279,27 @@ mcp.add_resource(special_resource, key="internal://data-v2") # Will be stored a
|
|||
|
||||
Note that this parameter is only available when using `add_resource()` directly and not through the `@resource` decorator, as URIs are provided explicitly when using the decorator.
|
||||
|
||||
### Notifications
|
||||
|
||||
<VersionBadge version="2.9.1" />
|
||||
|
||||
FastMCP automatically sends `notifications/resources/list_changed` notifications to connected clients when resources or templates are added, enabled, or disabled. This allows clients to stay up-to-date with the current resource set without manually polling for changes.
|
||||
|
||||
```python
|
||||
@mcp.resource("data://example")
|
||||
def example_resource() -> str:
|
||||
return "Hello!"
|
||||
|
||||
# These operations trigger notifications:
|
||||
mcp.add_resource(example_resource) # Sends resources/list_changed notification
|
||||
example_resource.disable() # Sends resources/list_changed notification
|
||||
example_resource.enable() # Sends resources/list_changed notification
|
||||
```
|
||||
|
||||
Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications.
|
||||
|
||||
Clients can handle these notifications using a [message handler](/clients/messages) to automatically refresh their resource lists or update their interfaces.
|
||||
|
||||
## Resource Templates
|
||||
|
||||
Resource Templates allow clients to request resources whose content depends on parameters embedded in the URI. Define a template using the **same `@mcp.resource` decorator**, but include `{parameter_name}` placeholders in the URI string and add corresponding arguments to your function signature.
|
||||
|
|
|
|||
|
|
@ -415,6 +415,28 @@ FastMCP supports these standard annotations:
|
|||
|
||||
Remember that annotations help make better user experiences but should be treated as advisory hints. They help client applications present appropriate UI elements and safety controls, but won't enforce security boundaries on their own. Always focus on making your annotations accurately represent what your tool actually does.
|
||||
|
||||
### Notifications
|
||||
|
||||
<VersionBadge version="2.9.1" />
|
||||
|
||||
FastMCP automatically sends `notifications/tools/list_changed` notifications to connected clients when tools are added, removed, enabled, or disabled. This allows clients to stay up-to-date with the current tool set without manually polling for changes.
|
||||
|
||||
```python
|
||||
@mcp.tool
|
||||
def example_tool() -> str:
|
||||
return "Hello!"
|
||||
|
||||
# These operations trigger notifications:
|
||||
mcp.add_tool(example_tool) # Sends tools/list_changed notification
|
||||
example_tool.disable() # Sends tools/list_changed notification
|
||||
example_tool.enable() # Sends tools/list_changed notification
|
||||
mcp.remove_tool("example_tool") # Sends tools/list_changed notification
|
||||
```
|
||||
|
||||
Notifications are only sent when these operations occur within an active MCP request context (e.g., when called from within a tool or other MCP operation). Operations performed during server initialization do not trigger notifications.
|
||||
|
||||
Clients can handle these notifications using a [message handler](/clients/messages) to automatically refresh their tool lists or update their interfaces.
|
||||
|
||||
## MCP Context
|
||||
|
||||
Tools can access MCP features like logging, reading resources, or reporting progress through the `Context` object. To use it, add a parameter to your tool function with the type hint `Context`.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue