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
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def do_GET(self):
<html>
<body>
<h1>Authorization Failed</h1>
<p>Error: {query_params['error'][0]}</p>
<p>Error: {query_params["error"][0]}</p>
<p>You can close this window and return to the terminal.</p>
</body>
</html>
Expand Down
6 changes: 4 additions & 2 deletions examples/fastmcp/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Annotated, Self
from typing import Annotated, Self, TypeVar

import asyncpg
import numpy as np
Expand All @@ -35,6 +35,8 @@
DEFAULT_LLM_MODEL = "openai:gpt-4o"
DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small"

T = TypeVar("T")

mcp = FastMCP(
"memory",
dependencies=[
Expand All @@ -57,7 +59,7 @@ def cosine_similarity(a: list[float], b: list[float]) -> float:
return np.dot(a_array, b_array) / (np.linalg.norm(a_array) * np.linalg.norm(b_array))


async def do_ai[T](
async def do_ai(
user_prompt: str,
system_prompt: str,
result_type: type[T] | Annotated,
Expand Down
2 changes: 1 addition & 1 deletion examples/fastmcp/unicode_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
mcp = FastMCP()


@mcp.tool(description="🌟 A tool that uses various Unicode characters in its description: " "á é í ó ú ñ 漢字 🎉")
@mcp.tool(description="🌟 A tool that uses various Unicode characters in its description: á é í ó ú ñ 漢字 🎉")
def hello_unicode(name: str = "世界", greeting: str = "¡Hola") -> str:
"""
A simple tool that demonstrates Unicode handling in:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ async def authorize(self, client: OAuthClientInformationFull, params: Authorizat
}

# Build simple login URL that points to login page
auth_url = f"{self.auth_callback_url}" f"?state={state}" f"&client_id={client.client_id}"
auth_url = f"{self.auth_callback_url}?state={state}&client_id={client.client_id}"

return auth_url

Expand Down Expand Up @@ -117,7 +117,7 @@ async def get_login_page(self, state: str) -> HTMLResponse:
<p><strong>Username:</strong> demo_user<br>
<strong>Password:</strong> demo_password</p>

<form action="{self.server_url.rstrip('/')}/login/callback" method="post">
<form action="{self.server_url.rstrip("/")}/login/callback" method="post">
<input type="hidden" name="state" value="{state}">
<div class="form-group">
<label>Username:</label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ async def call_tool(name: str, arguments: dict) -> list[types.ContentBlock]:
for i in range(count):
await ctx.session.send_log_message(
level="info",
data=f"Notification {i+1}/{count} from caller: {caller}",
data=f"Notification {i + 1}/{count} from caller: {caller}",
logger="notification_stream",
related_request_id=ctx.request_id,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ async def call_tool(name: str, arguments: dict) -> list[types.ContentBlock]:
for i in range(count):
# Include more detailed message for resumability demonstration
notification_msg = (
f"[{i+1}/{count}] Event from '{caller}' - "
f"[{i + 1}/{count}] Event from '{caller}' - "
f"Use Last-Event-ID to resume if disconnected"
)
await ctx.session.send_log_message(
Expand All @@ -69,7 +69,7 @@ async def call_tool(name: str, arguments: dict) -> list[types.ContentBlock]:
# - nowhere (if GET request isn't supported)
related_request_id=ctx.request_id,
)
logger.debug(f"Sent notification {i+1}/{count} for caller: {caller}")
logger.debug(f"Sent notification {i + 1}/{count} for caller: {caller}")
if i < count - 1: # Don't wait after the last notification
await anyio.sleep(interval)

Expand Down
2 changes: 1 addition & 1 deletion src/mcp/cli/claude.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def get_uv_path() -> str:
uv_path = shutil.which("uv")
if not uv_path:
logger.error(
"uv executable not found in PATH, falling back to 'uv'. " "Please ensure uv is installed and in your PATH"
"uv executable not found in PATH, falling back to 'uv'. Please ensure uv is installed and in your PATH"
)
return "uv" # Fall back to just "uv" if not found
return uv_path
Expand Down
12 changes: 6 additions & 6 deletions src/mcp/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,10 @@ def _check_server_object(server_object: Any, object_name: str):
True if it's supported.
"""
if not isinstance(server_object, FastMCP):
logger.error(f"The server object {object_name} is of type " f"{type(server_object)} (expecting {FastMCP}).")
logger.error(f"The server object {object_name} is of type {type(server_object)} (expecting {FastMCP}).")
if isinstance(server_object, LowLevelServer):
logger.warning(
"Note that only FastMCP server is supported. Low level " "Server class is not yet supported."
"Note that only FastMCP server is supported. Low level Server class is not yet supported."
)
return False
return True
Expand All @@ -164,7 +164,7 @@ def _check_server_object(server_object: Any, object_name: str):
for name in ["mcp", "server", "app"]:
if hasattr(module, name):
if not _check_server_object(getattr(module, name), f"{file}:{name}"):
logger.error(f"Ignoring object '{file}:{name}' as it's not a valid " "server object")
logger.error(f"Ignoring object '{file}:{name}' as it's not a valid server object")
continue
return getattr(module, name)

Expand Down Expand Up @@ -269,7 +269,7 @@ def dev(
npx_cmd = _get_npx_command()
if not npx_cmd:
logger.error(
"npx not found. Please ensure Node.js and npm are properly installed " "and added to your system PATH."
"npx not found. Please ensure Node.js and npm are properly installed and added to your system PATH."
)
sys.exit(1)

Expand Down Expand Up @@ -371,7 +371,7 @@ def install(
typer.Option(
"--name",
"-n",
help="Custom name for the server (defaults to server's name attribute or" " file name)",
help="Custom name for the server (defaults to server's name attribute or file name)",
),
] = None,
with_editable: Annotated[
Expand Down Expand Up @@ -445,7 +445,7 @@ def install(
name = server.name
except (ImportError, ModuleNotFoundError) as e:
logger.debug(
"Could not import server (likely missing dependencies), using file" " name",
"Could not import server (likely missing dependencies), using file name",
extra={"error": str(e)},
)
name = file.stem
Expand Down
2 changes: 1 addition & 1 deletion src/mcp/client/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ async def initialize(self) -> types.InitializeResult:
)

if result.protocolVersion not in SUPPORTED_PROTOCOL_VERSIONS:
raise RuntimeError("Unsupported protocol version from the server: " f"{result.protocolVersion}")
raise RuntimeError(f"Unsupported protocol version from the server: {result.protocolVersion}")

await self.send_notification(
types.ClientNotification(types.InitializedNotification(method="notifications/initialized"))
Expand Down
4 changes: 2 additions & 2 deletions src/mcp/client/sse.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ async def sse_reader(
or url_parsed.scheme != endpoint_parsed.scheme
):
error_msg = (
"Endpoint origin does not match " f"connection origin: {endpoint_url}"
f"Endpoint origin does not match connection origin: {endpoint_url}"
)
logger.error(error_msg)
raise ValueError(error_msg)
Expand Down Expand Up @@ -125,7 +125,7 @@ async def post_writer(endpoint_url: str):
),
)
response.raise_for_status()
logger.debug("Client message sent successfully: " f"{response.status_code}")
logger.debug(f"Client message sent successfully: {response.status_code}")
except Exception as exc:
logger.error(f"Error in post_writer: {exc}")
finally:
Expand Down
2 changes: 1 addition & 1 deletion src/mcp/server/auth/handlers/authorize.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class AuthorizationRequest(BaseModel):
state: str | None = Field(None, description="Optional state parameter")
scope: str | None = Field(
None,
description="Optional scope; if specified, should be " "a space-separated list of scope strings",
description="Optional scope; if specified, should be a space-separated list of scope strings",
)
resource: str | None = Field(
None,
Expand Down
2 changes: 1 addition & 1 deletion src/mcp/server/auth/handlers/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ async def handle(self, request: Request) -> Response:
return PydanticJSONResponse(
content=RegistrationErrorResponse(
error="invalid_client_metadata",
error_description="grant_types must be authorization_code " "and refresh_token",
error_description="grant_types must be authorization_code and refresh_token",
),
status_code=400,
)
Expand Down
8 changes: 3 additions & 5 deletions src/mcp/server/auth/handlers/token.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,7 @@ async def handle(self, request: Request):
return self.response(
TokenErrorResponse(
error="unsupported_grant_type",
error_description=(
f"Unsupported grant type (supported grant types are " f"{client_info.grant_types})"
),
error_description=(f"Unsupported grant type (supported grant types are {client_info.grant_types})"),
)
)

Expand Down Expand Up @@ -166,7 +164,7 @@ async def handle(self, request: Request):
return self.response(
TokenErrorResponse(
error="invalid_request",
error_description=("redirect_uri did not match the one " "used when creating auth code"),
error_description=("redirect_uri did not match the one used when creating auth code"),
)
)

Expand Down Expand Up @@ -222,7 +220,7 @@ async def handle(self, request: Request):
return self.response(
TokenErrorResponse(
error="invalid_scope",
error_description=(f"cannot request scope `{scope}` " "not provided by refresh token"),
error_description=(f"cannot request scope `{scope}` not provided by refresh token"),
)
)

Expand Down
4 changes: 2 additions & 2 deletions src/mcp/server/fastmcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ async def async_tool(x: int, context: Context) -> str:
# Check if user passed function directly instead of calling decorator
if callable(name):
raise TypeError(
"The @tool decorator was used incorrectly. " "Did you forget to call it? Use @tool() instead of @tool"
"The @tool decorator was used incorrectly. Did you forget to call it? Use @tool() instead of @tool"
)

def decorator(fn: AnyFunction) -> AnyFunction:
Expand Down Expand Up @@ -497,7 +497,7 @@ def decorator(fn: AnyFunction) -> AnyFunction:

if uri_params != func_params:
raise ValueError(
f"Mismatch between URI parameters {uri_params} " f"and function parameters {func_params}"
f"Mismatch between URI parameters {uri_params} and function parameters {func_params}"
)

# Register as template
Expand Down
2 changes: 1 addition & 1 deletion src/mcp/server/fastmcp/tools/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Tool(BaseModel):
description: str = Field(description="Description of what the tool does")
parameters: dict[str, Any] = Field(description="JSON schema for tool parameters")
fn_metadata: FuncMetadata = Field(
description="Metadata about the function including a pydantic model for tool" " arguments"
description="Metadata about the function including a pydantic model for tool arguments"
)
is_async: bool = Field(description="Whether the tool is async")
context_kwarg: str | None = Field(None, description="Name of the kwarg that should receive context")
Expand Down
2 changes: 1 addition & 1 deletion src/mcp/shared/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def validate_redirect_uri(self, redirect_uri: AnyUrl | None) -> AnyUrl:
elif len(self.redirect_uris) == 1:
return self.redirect_uris[0]
else:
raise InvalidRedirectUriError("redirect_uri must be specified when client " "has multiple registered URIs")
raise InvalidRedirectUriError("redirect_uri must be specified when client has multiple registered URIs")


class OAuthClientInformationFull(OAuthClientMetadata):
Expand Down
4 changes: 2 additions & 2 deletions src/mcp/shared/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,15 +402,15 @@ async def _receive_loop(self) -> None:
except Exception as e:
# For other validation errors, log and continue
logging.warning(
f"Failed to validate notification: {e}. " f"Message was: {message.message.root}"
f"Failed to validate notification: {e}. Message was: {message.message.root}"
)
else: # Response or error
stream = self._response_streams.pop(message.message.root.id, None)
if stream:
await stream.send(message.message.root)
else:
await self._handle_incoming(
RuntimeError("Received response with an unknown " f"request ID: {message}")
RuntimeError(f"Received response with an unknown request ID: {message}")
)

except anyio.ClosedResourceError:
Expand Down
3 changes: 1 addition & 2 deletions tests/client/test_resource_cleanup.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ async def mock_send(*args, **kwargs):

# Verify that no response streams were leaked
assert len(session._response_streams) == initial_stream_count, (
f"Expected {initial_stream_count} response streams after request, "
f"but found {len(session._response_streams)}"
f"Expected {initial_stream_count} response streams after request, but found {len(session._response_streams)}"
)

# Clean up
Expand Down
2 changes: 1 addition & 1 deletion tests/server/fastmcp/auth/test_auth_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ async def test_client_registration_invalid_uri(self, test_client: httpx.AsyncCli
assert "error" in error_data
assert error_data["error"] == "invalid_client_metadata"
assert error_data["error_description"] == (
"redirect_uris.0: Input should be a valid URL, " "relative URL without a base"
"redirect_uris.0: Input should be a valid URL, relative URL without a base"
)

@pytest.mark.anyio
Expand Down
2 changes: 1 addition & 1 deletion tests/server/fastmcp/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ def everything_streamable_http_server(
time.sleep(0.1)
attempt += 1
else:
raise RuntimeError(f"Comprehensive StreamableHTTP server failed to start after " f"{max_attempts} attempts")
raise RuntimeError(f"Comprehensive StreamableHTTP server failed to start after {max_attempts} attempts")

yield

Expand Down
Loading
Loading