Silence ty deprecation diagnostics and drop stale sampling doc mentions

This commit is contained in:
Jeremiah Lowin 2026-07-26 14:45:55 -04:00
commit 90f2e190d0
No known key found for this signature in database
6 changed files with 15 additions and 22 deletions

View file

@ -238,7 +238,7 @@ Custom spans are most useful around work that is expensive or hard to debug:
- External calls such as databases, vector stores, HTTP APIs, or queue operations
- Multi-step tool logic where one stage dominates latency
- Prompt or resource generation that fans out to other systems
- Sampling calls made from inside a tool via `ctx.sample(...)`
- LLM calls a tool makes to a model provider
Avoid wrapping every small helper function or simple in-memory transformation. That usually adds noise without making traces easier to interpret.
@ -286,9 +286,9 @@ async def docs_resource(slug: str) -> str:
return await load_doc(slug)
```
### Sampling calls inside tools
### LLM calls inside tools
If your tool uses `ctx.sample(...)`, keep the LLM work nested under the tool span so traces show both application logic and model latency together.
A tool that [calls an LLM directly](/servers/sampling) should keep the model work nested under the tool span, so traces show application logic and model latency together.
For providers with their own OTEL integrations, prefer enabling that instrumentation rather than manually creating a span around every model call. For example, if you use Google GenAI, `logfire.instrument_google_genai()` will emit child spans with token and request metadata under the active FastMCP tool span.

View file

@ -1063,15 +1063,9 @@ async def process_data(data_uri: str, ctx: Context) -> dict:
# Report progress
await ctx.report_progress(progress=50, total=100)
# Example request to the client's LLM for help
summary = await ctx.sample(f"Summarize this in 10 words: {data[:200]}")
await ctx.report_progress(progress=100, total=100)
return {
"length": len(data),
"summary": summary.text
}
return {"length": len(data)}
```
The Context object provides access to:
@ -1079,7 +1073,6 @@ The Context object provides access to:
- **Logging**: `ctx.debug()`, `ctx.info()`, `ctx.warning()`, `ctx.error()`
- **Progress Reporting**: `ctx.report_progress(progress, total)`
- **Resource Access**: `ctx.read_resource(uri)`
- **LLM Sampling**: `ctx.sample(...)`
- **Request Information**: `ctx.request_id`, `ctx.client_id`
For full documentation on the Context object and all its capabilities, see the [Context documentation](/servers/context).

View file

@ -16,7 +16,7 @@ def fastmcp_server():
@mcp.tool
async def list_roots(context: Context) -> list[str]:
result = await context.session.list_roots()
result = await context.session.list_roots() # ty: ignore[deprecated]
return [str(r.uri) for r in result.roots]
return mcp
@ -58,11 +58,9 @@ class TestClientRoots:
async def roots_handler(ctx) -> list[Root]:
calls.append(ctx)
return [Root(uri="file://from/handler")] # ty: ignore[invalid-argument-type]
return [Root(uri="file://from/handler")]
async with Client(
fastmcp_server, mode="legacy", roots=roots_handler
) as client:
async with Client(fastmcp_server, mode="legacy", roots=roots_handler) as client:
result = await client.call_tool("list_roots", {})
assert len(calls) == 1

View file

@ -23,7 +23,7 @@ async def _sample(
answering a legacy server, so the stand-in server reaches the SDK session
directly.
"""
result = await context.session.create_message(
result = await context.session.create_message( # ty: ignore[deprecated]
messages=messages,
system_prompt=system_prompt,
max_tokens=512,
@ -72,7 +72,9 @@ def fastmcp_server():
),
SamplingMessage(
role="assistant",
content=TextContent(type="text", text="How can I assist you today?"),
content=TextContent(
type="text", text="How can I assist you today?"
),
),
],
)

View file

@ -66,13 +66,13 @@ async def _backend_list_roots(context: Context) -> list[Root]:
FastMCP's server API. These helpers reach the SDK session directly to stand
in for a legacy upstream, which is the only thing the proxy relay forwards.
"""
result = await context.session.list_roots()
result = await context.session.list_roots() # ty: ignore[deprecated]
return result.roots
async def _backend_sample(context: Context) -> str:
"""Issue a handshake-era `sampling/createMessage` from a backend server."""
result = await context.session.create_message(
result = await context.session.create_message( # ty: ignore[deprecated]
messages=[
SamplingMessage(
role="user", content=TextContent(type="text", text="Hello, world!")

View file

@ -307,7 +307,7 @@ def test_removed_server_initiated_methods_are_absent(name):
def test_server_sampling_handler_kwargs_are_rejected(kwarg):
"""The server-side sampling handler existed only to answer `ctx.sample()`."""
with pytest.raises(TypeError, match="SEP-2577"):
FastMCP("gone", **{kwarg: None})
FastMCP("gone", **{kwarg: None}) # ty: ignore[invalid-argument-type]
@pytest.mark.parametrize("mode", MODERN_MODES)