From d24367581bb3f3f3d620043b9acebcc6c91cd9a1 Mon Sep 17 00:00:00 2001 From: zzstoatzz Date: Thu, 24 Apr 2025 00:48:00 -0500 Subject: [PATCH 1/2] merge conflict --- .../src/smart_home/lights/server.py | 38 ++++++++++++------- src/fastmcp/__init__.py | 1 + src/fastmcp/server/server.py | 10 +++-- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/examples/smart_home/src/smart_home/lights/server.py b/examples/smart_home/src/smart_home/lights/server.py index bd4d0fccb..c95382cc0 100644 --- a/examples/smart_home/src/smart_home/lights/server.py +++ b/examples/smart_home/src/smart_home/lights/server.py @@ -11,15 +11,28 @@ from smart_home.lights.hue_utils import _get_bridge, handle_phue_error class HueAttributes(TypedDict, total=False): """TypedDict for optional light attributes.""" - on: NotRequired[bool] - bri: NotRequired[Annotated[int, Field(ge=0, le=254)]] - hue: NotRequired[Annotated[int, Field(ge=0, le=65535)]] - sat: NotRequired[Annotated[int, Field(ge=0, le=254)]] - xy: NotRequired[list[float]] - ct: NotRequired[Annotated[int, Field(ge=153, le=500)]] + on: NotRequired[Annotated[bool, Field(description="on/off state")]] + bri: NotRequired[Annotated[int, Field(ge=0, le=254, description="brightness")]] + hue: NotRequired[ + Annotated[ + int, + Field( + ge=0, + le=254, + description="saturation", + ), + ] + ] + xy: NotRequired[Annotated[list[float], Field(description="xy color coordinates")]] + ct: NotRequired[ + Annotated[ + int, + Field(ge=153, le=500, description="color temperature"), + ] + ] alert: NotRequired[Literal["none", "select", "lselect"]] effect: NotRequired[Literal["none", "colorloop"]] - transitiontime: NotRequired[int] # deciseconds + transitiontime: NotRequired[Annotated[int, Field(description="deciseconds")]] lights_mcp = FastMCP( @@ -161,7 +174,7 @@ def activate_scene(group_name: str, scene_name: str) -> dict[str, Any]: scenes_data = bridge.get_scene() scene_found = False scene_in_correct_group = False - for sid, sinfo in scenes_data.items(): + for sinfo in scenes_data.values(): if sinfo.get("name") == scene_name: scene_found = True # Check if this scene is associated with the target group ID @@ -217,7 +230,7 @@ def set_light_attributes(light_name: str, attributes: HueAttributes) -> dict[str } try: - result = bridge.set_light(light_name, attributes) + result = bridge.set_light(light_name, dict(attributes)) return { "light": light_name, "set_attributes": attributes, @@ -243,7 +256,7 @@ def set_group_attributes(group_name: str, attributes: HueAttributes) -> dict[str } try: - result = bridge.set_group(group_name, attributes) + result = bridge.set_group(group_name, dict(attributes)) return { "group": group_name, "set_attributes": attributes, @@ -269,7 +282,7 @@ def list_lights_by_group() -> dict[str, list[str]] | list[str]: lights_data = bridge.get_light_objects("id") # dict {light_id: {details}} lights_by_group: dict[str, list[str]] = {} - for group_id, group_details in groups_data.items(): + for group_details in groups_data.values(): group_name = group_details.get("name") light_ids = group_details.get("lights", []) if group_name and light_ids: @@ -282,11 +295,10 @@ def list_lights_by_group() -> dict[str, list[str]] | list[str]: if light_name: light_names.append(light_name) if light_names: - light_names.sort() # Keep light list sorted + light_names.sort() lights_by_group[group_name] = light_names return lights_by_group except (PhueException, Exception) as e: - # Return error as list return [f"Error listing lights by group: {e}"] diff --git a/src/fastmcp/__init__.py b/src/fastmcp/__init__.py index d0394d9fa..63b7d8e51 100644 --- a/src/fastmcp/__init__.py +++ b/src/fastmcp/__init__.py @@ -14,6 +14,7 @@ __all__ = [ "FastMCP", "Context", "client", + "Client", "settings", "Image", ] diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 6f2167410..fb7386178 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -182,7 +182,11 @@ class FastMCP(Generic[LifespanResultT]): name: str | None = None, instructions: str | None = None, lifespan: ( - Callable[["FastMCP"], AbstractAsyncContextManager[LifespanResultT]] | None + Callable[ + ["FastMCP[LifespanResultT]"], + AbstractAsyncContextManager[LifespanResultT], + ] + | None ) = None, tags: set[str] | None = None, **settings: Any, @@ -738,7 +742,7 @@ class FastMCP(Generic[LifespanResultT]): def mount( self, prefix: str, - server: "FastMCP", + server: "FastMCP[LifespanResultT]", tool_separator: str | None = None, resource_separator: str | None = None, prompt_separator: str | None = None, @@ -763,7 +767,7 @@ class FastMCP(Generic[LifespanResultT]): async def import_server( self, prefix: str, - server: "FastMCP", + server: "FastMCP[LifespanResultT]", tool_separator: str | None = None, resource_separator: str | None = None, prompt_separator: str | None = None, From 3fb089a5b6d97307077abb9bfdcd82753f95d31a Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Sat, 26 Apr 2025 07:56:36 -0400 Subject: [PATCH 2/2] Use PEP 649 annotations --- src/fastmcp/server/server.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index b1547292e..65dd33ea4 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -1,5 +1,7 @@ """FastMCP - A more ergonomic interface for MCP servers.""" +from __future__ import annotations + import datetime from collections.abc import AsyncIterator, Awaitable, Callable from contextlib import ( @@ -63,7 +65,7 @@ class MountedServer: def __init__( self, prefix: str, - server: "FastMCP", + server: FastMCP, tool_separator: str | None = None, resource_separator: str | None = None, prompt_separator: str | None = None, @@ -149,7 +151,7 @@ class TimedCache: @asynccontextmanager -async def default_lifespan(server: "FastMCP") -> AsyncIterator[Any]: +async def default_lifespan(server: FastMCP) -> AsyncIterator[Any]: """Default lifespan context manager that does nothing. Args: @@ -162,8 +164,8 @@ async def default_lifespan(server: "FastMCP") -> AsyncIterator[Any]: def _lifespan_wrapper( - app: "FastMCP", - lifespan: Callable[["FastMCP"], AbstractAsyncContextManager[LifespanResultT]], + app: FastMCP, + lifespan: Callable[[FastMCP], AbstractAsyncContextManager[LifespanResultT]], ) -> Callable[ [MCPServer[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT] ]: @@ -183,7 +185,7 @@ class FastMCP(Generic[LifespanResultT]): instructions: str | None = None, lifespan: ( Callable[ - ["FastMCP[LifespanResultT]"], + [FastMCP[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT], ] | None @@ -277,7 +279,7 @@ class FastMCP(Generic[LifespanResultT]): self._mcp_server.get_prompt()(self._mcp_get_prompt) self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates) - def get_context(self) -> "Context[ServerSession, LifespanResultT]": + def get_context(self) -> Context[ServerSession, LifespanResultT]: """ Returns a Context object. Note that the context will only be valid during a request; outside a request, most methods will error. @@ -770,7 +772,7 @@ class FastMCP(Generic[LifespanResultT]): def mount( self, prefix: str, - server: "FastMCP[LifespanResultT]", + server: FastMCP[LifespanResultT], tool_separator: str | None = None, resource_separator: str | None = None, prompt_separator: str | None = None, @@ -795,7 +797,7 @@ class FastMCP(Generic[LifespanResultT]): async def import_server( self, prefix: str, - server: "FastMCP[LifespanResultT]", + server: FastMCP[LifespanResultT], tool_separator: str | None = None, resource_separator: str | None = None, prompt_separator: str | None = None, @@ -869,7 +871,7 @@ class FastMCP(Generic[LifespanResultT]): @classmethod def from_openapi( cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, **settings: Any - ) -> "FastMCPOpenAPI": + ) -> FastMCPOpenAPI: """ Create a FastMCP server from an OpenAPI specification. """ @@ -879,8 +881,8 @@ class FastMCP(Generic[LifespanResultT]): @classmethod def from_fastapi( - cls, app: "Any", name: str | None = None, **settings: Any - ) -> "FastMCPOpenAPI": + cls, app: Any, name: str | None = None, **settings: Any + ) -> FastMCPOpenAPI: """ Create a FastMCP server from a FastAPI application. """ @@ -898,7 +900,7 @@ class FastMCP(Generic[LifespanResultT]): ) @classmethod - def from_client(cls, client: "Client", **settings: Any) -> "FastMCPProxy": + def from_client(cls, client: Client, **settings: Any) -> FastMCPProxy: """ Create a FastMCP proxy server from a FastMCP client. """