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 pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ dev = [
"pytest-timeout>=2.4.0",
"pytest-xdist>=3.6.1",
"ruff>=0.12.8",
"ty>=0.0.7",
"ty>=0.0.15",
"prek>=0.2.12",
"loq>=0.1.0a3",
"opentelemetry-exporter-otlp-proto-grpc>=1.39.0",
Expand Down
44 changes: 23 additions & 21 deletions src/fastmcp/client/sampling/handlers/anthropic.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
)

try:
from anthropic import AsyncAnthropic, NotGiven
from anthropic._types import NOT_GIVEN
from anthropic import AsyncAnthropic
from anthropic.types import (
Message,
MessageParam,
Expand Down Expand Up @@ -81,37 +80,40 @@ async def __call__(
model: ModelParam = self._select_model_from_preferences(params.modelPreferences)

# Convert MCP tools to Anthropic format
anthropic_tools: list[ToolParam] | NotGiven = NOT_GIVEN
anthropic_tools: list[ToolParam] | None = None
if params.tools:
anthropic_tools = self._convert_tools_to_anthropic(params.tools)

# Convert tool_choice to Anthropic format
# Returns None if mode is "none", signaling tools should be omitted
anthropic_tool_choice: ToolChoiceParam | NotGiven = NOT_GIVEN
anthropic_tool_choice: ToolChoiceParam | None = None
if params.toolChoice:
converted = self._convert_tool_choice_to_anthropic(params.toolChoice)
if converted is None:
# tool_choice="none" means don't use tools
anthropic_tools = NOT_GIVEN
anthropic_tools = None
else:
anthropic_tool_choice = converted

response = await self.client.messages.create(
model=model,
messages=anthropic_messages,
system=(
params.systemPrompt if params.systemPrompt is not None else NOT_GIVEN
),
temperature=(
params.temperature if params.temperature is not None else NOT_GIVEN
),
max_tokens=params.maxTokens,
stop_sequences=(
params.stopSequences if params.stopSequences is not None else NOT_GIVEN
),
tools=anthropic_tools,
tool_choice=anthropic_tool_choice,
)
# Build kwargs to avoid sentinel type compatibility issues across
# anthropic SDK versions (NotGiven vs Omit)
kwargs: dict[str, Any] = {
"model": model,
"messages": anthropic_messages,
"max_tokens": params.maxTokens,
}
if params.systemPrompt is not None:
kwargs["system"] = params.systemPrompt
if params.temperature is not None:
kwargs["temperature"] = params.temperature
if params.stopSequences is not None:
kwargs["stop_sequences"] = params.stopSequences
if anthropic_tools is not None:
kwargs["tools"] = anthropic_tools
if anthropic_tool_choice is not None:
kwargs["tool_choice"] = anthropic_tool_choice

response = await self.client.messages.create(**kwargs)

# Return appropriate result type based on whether tools were provided
if params.tools:
Expand Down
34 changes: 20 additions & 14 deletions src/fastmcp/client/sampling/handlers/openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
)

try:
from openai import NOT_GIVEN, AsyncOpenAI, NotGiven
from openai import AsyncOpenAI
from openai.types.chat import (
ChatCompletion,
ChatCompletionAssistantMessageParam,
Expand Down Expand Up @@ -70,26 +70,32 @@ async def __call__(
model: ChatModel = self._select_model_from_preferences(params.modelPreferences)

# Convert MCP tools to OpenAI format
openai_tools: list[ChatCompletionToolParam] | NotGiven = NOT_GIVEN
openai_tools: list[ChatCompletionToolParam] | None = None
if params.tools:
openai_tools = self._convert_tools_to_openai(params.tools)

# Convert tool_choice to OpenAI format
openai_tool_choice: ChatCompletionToolChoiceOptionParam | NotGiven = NOT_GIVEN
openai_tool_choice: ChatCompletionToolChoiceOptionParam | None = None
if params.toolChoice:
openai_tool_choice = self._convert_tool_choice_to_openai(params.toolChoice)

response = await self.client.chat.completions.create(
model=model,
messages=openai_messages,
temperature=(
params.temperature if params.temperature is not None else NOT_GIVEN
),
max_tokens=params.maxTokens,
stop=params.stopSequences if params.stopSequences else NOT_GIVEN,
tools=openai_tools,
tool_choice=openai_tool_choice,
)
# Build kwargs to avoid sentinel type compatibility issues across
# openai SDK versions (NotGiven vs Omit)
kwargs: dict[str, Any] = {
"model": model,
"messages": openai_messages,
"max_tokens": params.maxTokens,
}
if params.temperature is not None:
kwargs["temperature"] = params.temperature
if params.stopSequences:
kwargs["stop"] = params.stopSequences
if openai_tools is not None:
kwargs["tools"] = openai_tools
if openai_tool_choice is not None:
kwargs["tool_choice"] = openai_tool_choice

response = await self.client.chat.completions.create(**kwargs)

# Return appropriate result type based on whether tools were provided
if params.tools:
Expand Down
5 changes: 3 additions & 2 deletions tests/server/providers/openapi/test_openapi_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

import time
from typing import Any

import httpx
import pytest
Expand Down Expand Up @@ -72,7 +73,7 @@ def test_medium_schema_performance(self):
for performance testing in CI environments.
"""
# Create a medium-sized synthetic schema
schema = {
schema: dict[str, Any] = {
"openapi": "3.0.0",
"info": {"title": "Test API", "version": "1.0.0"},
"paths": {},
Expand All @@ -81,7 +82,7 @@ def test_medium_schema_performance(self):
# Generate multiple paths to create a reasonably sized schema
for i in range(100):
path = f"/test/{i}"
schema["paths"][path] = { # type: ignore[index]
schema["paths"][path] = {
"get": {
"operationId": f"test_{i}",
"parameters": [
Expand Down
42 changes: 21 additions & 21 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.