chore: bump ruff (#1085)
This commit is contained in:
parent
6f43d1f170
commit
9301924f44
21 changed files with 56 additions and 57 deletions
|
|
@ -81,7 +81,7 @@ class CallbackHandler(BaseHTTPRequestHandler):
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
<h1>Authorization Failed</h1>
|
<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>
|
<p>You can close this window and return to the terminal.</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import os
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Annotated, Self
|
from typing import Annotated, Self, TypeVar
|
||||||
|
|
||||||
import asyncpg
|
import asyncpg
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
@ -35,6 +35,8 @@ REINFORCEMENT_FACTOR = 1.1
|
||||||
DEFAULT_LLM_MODEL = "openai:gpt-4o"
|
DEFAULT_LLM_MODEL = "openai:gpt-4o"
|
||||||
DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small"
|
DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small"
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
mcp = FastMCP(
|
mcp = FastMCP(
|
||||||
"memory",
|
"memory",
|
||||||
dependencies=[
|
dependencies=[
|
||||||
|
|
@ -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))
|
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,
|
user_prompt: str,
|
||||||
system_prompt: str,
|
system_prompt: str,
|
||||||
result_type: type[T] | Annotated,
|
result_type: type[T] | Annotated,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from mcp.server.fastmcp import FastMCP
|
||||||
mcp = FastMCP()
|
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:
|
def hello_unicode(name: str = "世界", greeting: str = "¡Hola") -> str:
|
||||||
"""
|
"""
|
||||||
A simple tool that demonstrates Unicode handling in:
|
A simple tool that demonstrates Unicode handling in:
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ class SimpleOAuthProvider(OAuthAuthorizationServerProvider):
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build simple login URL that points to login page
|
# 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
|
return auth_url
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ class SimpleOAuthProvider(OAuthAuthorizationServerProvider):
|
||||||
<p><strong>Username:</strong> demo_user<br>
|
<p><strong>Username:</strong> demo_user<br>
|
||||||
<strong>Password:</strong> demo_password</p>
|
<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}">
|
<input type="hidden" name="state" value="{state}">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Username:</label>
|
<label>Username:</label>
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ def main(
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
await ctx.session.send_log_message(
|
await ctx.session.send_log_message(
|
||||||
level="info",
|
level="info",
|
||||||
data=f"Notification {i+1}/{count} from caller: {caller}",
|
data=f"Notification {i + 1}/{count} from caller: {caller}",
|
||||||
logger="notification_stream",
|
logger="notification_stream",
|
||||||
related_request_id=ctx.request_id,
|
related_request_id=ctx.request_id,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ def main(
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
# Include more detailed message for resumability demonstration
|
# Include more detailed message for resumability demonstration
|
||||||
notification_msg = (
|
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"
|
f"Use Last-Event-ID to resume if disconnected"
|
||||||
)
|
)
|
||||||
await ctx.session.send_log_message(
|
await ctx.session.send_log_message(
|
||||||
|
|
@ -69,7 +69,7 @@ def main(
|
||||||
# - nowhere (if GET request isn't supported)
|
# - nowhere (if GET request isn't supported)
|
||||||
related_request_id=ctx.request_id,
|
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
|
if i < count - 1: # Don't wait after the last notification
|
||||||
await anyio.sleep(interval)
|
await anyio.sleep(interval)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ def get_uv_path() -> str:
|
||||||
uv_path = shutil.which("uv")
|
uv_path = shutil.which("uv")
|
||||||
if not uv_path:
|
if not uv_path:
|
||||||
logger.error(
|
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" # Fall back to just "uv" if not found
|
||||||
return uv_path
|
return uv_path
|
||||||
|
|
|
||||||
|
|
@ -150,10 +150,10 @@ def _import_server(file: Path, server_object: str | None = None):
|
||||||
True if it's supported.
|
True if it's supported.
|
||||||
"""
|
"""
|
||||||
if not isinstance(server_object, FastMCP):
|
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):
|
if isinstance(server_object, LowLevelServer):
|
||||||
logger.warning(
|
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 False
|
||||||
return True
|
return True
|
||||||
|
|
@ -164,7 +164,7 @@ def _import_server(file: Path, server_object: str | None = None):
|
||||||
for name in ["mcp", "server", "app"]:
|
for name in ["mcp", "server", "app"]:
|
||||||
if hasattr(module, name):
|
if hasattr(module, name):
|
||||||
if not _check_server_object(getattr(module, name), f"{file}:{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
|
continue
|
||||||
return getattr(module, name)
|
return getattr(module, name)
|
||||||
|
|
||||||
|
|
@ -269,7 +269,7 @@ def dev(
|
||||||
npx_cmd = _get_npx_command()
|
npx_cmd = _get_npx_command()
|
||||||
if not npx_cmd:
|
if not npx_cmd:
|
||||||
logger.error(
|
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)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
@ -371,7 +371,7 @@ def install(
|
||||||
typer.Option(
|
typer.Option(
|
||||||
"--name",
|
"--name",
|
||||||
"-n",
|
"-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,
|
] = None,
|
||||||
with_editable: Annotated[
|
with_editable: Annotated[
|
||||||
|
|
@ -445,7 +445,7 @@ def install(
|
||||||
name = server.name
|
name = server.name
|
||||||
except (ImportError, ModuleNotFoundError) as e:
|
except (ImportError, ModuleNotFoundError) as e:
|
||||||
logger.debug(
|
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)},
|
extra={"error": str(e)},
|
||||||
)
|
)
|
||||||
name = file.stem
|
name = file.stem
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,7 @@ class ClientSession(
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.protocolVersion not in SUPPORTED_PROTOCOL_VERSIONS:
|
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(
|
await self.send_notification(
|
||||||
types.ClientNotification(types.InitializedNotification(method="notifications/initialized"))
|
types.ClientNotification(types.InitializedNotification(method="notifications/initialized"))
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ async def sse_client(
|
||||||
or url_parsed.scheme != endpoint_parsed.scheme
|
or url_parsed.scheme != endpoint_parsed.scheme
|
||||||
):
|
):
|
||||||
error_msg = (
|
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)
|
logger.error(error_msg)
|
||||||
raise ValueError(error_msg)
|
raise ValueError(error_msg)
|
||||||
|
|
@ -125,7 +125,7 @@ async def sse_client(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
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:
|
except Exception as exc:
|
||||||
logger.error(f"Error in post_writer: {exc}")
|
logger.error(f"Error in post_writer: {exc}")
|
||||||
finally:
|
finally:
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class AuthorizationRequest(BaseModel):
|
||||||
state: str | None = Field(None, description="Optional state parameter")
|
state: str | None = Field(None, description="Optional state parameter")
|
||||||
scope: str | None = Field(
|
scope: str | None = Field(
|
||||||
None,
|
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(
|
resource: str | None = Field(
|
||||||
None,
|
None,
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ class RegistrationHandler:
|
||||||
return PydanticJSONResponse(
|
return PydanticJSONResponse(
|
||||||
content=RegistrationErrorResponse(
|
content=RegistrationErrorResponse(
|
||||||
error="invalid_client_metadata",
|
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,
|
status_code=400,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -119,9 +119,7 @@ class TokenHandler:
|
||||||
return self.response(
|
return self.response(
|
||||||
TokenErrorResponse(
|
TokenErrorResponse(
|
||||||
error="unsupported_grant_type",
|
error="unsupported_grant_type",
|
||||||
error_description=(
|
error_description=(f"Unsupported grant type (supported grant types are {client_info.grant_types})"),
|
||||||
f"Unsupported grant type (supported grant types are " f"{client_info.grant_types})"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -166,7 +164,7 @@ class TokenHandler:
|
||||||
return self.response(
|
return self.response(
|
||||||
TokenErrorResponse(
|
TokenErrorResponse(
|
||||||
error="invalid_request",
|
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"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -222,7 +220,7 @@ class TokenHandler:
|
||||||
return self.response(
|
return self.response(
|
||||||
TokenErrorResponse(
|
TokenErrorResponse(
|
||||||
error="invalid_scope",
|
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"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -390,7 +390,7 @@ class FastMCP:
|
||||||
# Check if user passed function directly instead of calling decorator
|
# Check if user passed function directly instead of calling decorator
|
||||||
if callable(name):
|
if callable(name):
|
||||||
raise TypeError(
|
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:
|
def decorator(fn: AnyFunction) -> AnyFunction:
|
||||||
|
|
@ -497,7 +497,7 @@ class FastMCP:
|
||||||
|
|
||||||
if uri_params != func_params:
|
if uri_params != func_params:
|
||||||
raise ValueError(
|
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
|
# Register as template
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class Tool(BaseModel):
|
||||||
description: str = Field(description="Description of what the tool does")
|
description: str = Field(description="Description of what the tool does")
|
||||||
parameters: dict[str, Any] = Field(description="JSON schema for tool parameters")
|
parameters: dict[str, Any] = Field(description="JSON schema for tool parameters")
|
||||||
fn_metadata: FuncMetadata = Field(
|
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")
|
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")
|
context_kwarg: str | None = Field(None, description="Name of the kwarg that should receive context")
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ class OAuthClientMetadata(BaseModel):
|
||||||
elif len(self.redirect_uris) == 1:
|
elif len(self.redirect_uris) == 1:
|
||||||
return self.redirect_uris[0]
|
return self.redirect_uris[0]
|
||||||
else:
|
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):
|
class OAuthClientInformationFull(OAuthClientMetadata):
|
||||||
|
|
|
||||||
|
|
@ -402,7 +402,7 @@ class BaseSession(
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# For other validation errors, log and continue
|
# For other validation errors, log and continue
|
||||||
logging.warning(
|
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
|
else: # Response or error
|
||||||
stream = self._response_streams.pop(message.message.root.id, None)
|
stream = self._response_streams.pop(message.message.root.id, None)
|
||||||
|
|
@ -410,7 +410,7 @@ class BaseSession(
|
||||||
await stream.send(message.message.root)
|
await stream.send(message.message.root)
|
||||||
else:
|
else:
|
||||||
await self._handle_incoming(
|
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:
|
except anyio.ClosedResourceError:
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,7 @@ async def test_send_request_stream_cleanup():
|
||||||
|
|
||||||
# Verify that no response streams were leaked
|
# Verify that no response streams were leaked
|
||||||
assert len(session._response_streams) == initial_stream_count, (
|
assert len(session._response_streams) == initial_stream_count, (
|
||||||
f"Expected {initial_stream_count} response streams after request, "
|
f"Expected {initial_stream_count} response streams after request, but found {len(session._response_streams)}"
|
||||||
f"but found {len(session._response_streams)}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Clean up
|
# Clean up
|
||||||
|
|
|
||||||
|
|
@ -658,7 +658,7 @@ class TestAuthEndpoints:
|
||||||
assert "error" in error_data
|
assert "error" in error_data
|
||||||
assert error_data["error"] == "invalid_client_metadata"
|
assert error_data["error"] == "invalid_client_metadata"
|
||||||
assert error_data["error_description"] == (
|
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
|
@pytest.mark.anyio
|
||||||
|
|
|
||||||
|
|
@ -696,7 +696,7 @@ def everything_streamable_http_server(
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
attempt += 1
|
attempt += 1
|
||||||
else:
|
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
|
yield
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue