Jeremiah Lowin commited on
Commit
7657683
·
unverified ·
2 Parent(s): 49b24514f16c08

Merge pull request #237 from jlowin/nits

Browse files
examples/smart_home/src/smart_home/lights/server.py CHANGED
@@ -11,15 +11,28 @@ from smart_home.lights.hue_utils import _get_bridge, handle_phue_error
11
  class HueAttributes(TypedDict, total=False):
12
  """TypedDict for optional light attributes."""
13
 
14
- on: NotRequired[bool]
15
- bri: NotRequired[Annotated[int, Field(ge=0, le=254)]]
16
- hue: NotRequired[Annotated[int, Field(ge=0, le=65535)]]
17
- sat: NotRequired[Annotated[int, Field(ge=0, le=254)]]
18
- xy: NotRequired[list[float]]
19
- ct: NotRequired[Annotated[int, Field(ge=153, le=500)]]
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  alert: NotRequired[Literal["none", "select", "lselect"]]
21
  effect: NotRequired[Literal["none", "colorloop"]]
22
- transitiontime: NotRequired[int] # deciseconds
23
 
24
 
25
  lights_mcp = FastMCP(
@@ -161,7 +174,7 @@ def activate_scene(group_name: str, scene_name: str) -> dict[str, Any]:
161
  scenes_data = bridge.get_scene()
162
  scene_found = False
163
  scene_in_correct_group = False
164
- for sid, sinfo in scenes_data.items():
165
  if sinfo.get("name") == scene_name:
166
  scene_found = True
167
  # 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
217
  }
218
 
219
  try:
220
- result = bridge.set_light(light_name, attributes)
221
  return {
222
  "light": light_name,
223
  "set_attributes": attributes,
@@ -243,7 +256,7 @@ def set_group_attributes(group_name: str, attributes: HueAttributes) -> dict[str
243
  }
244
 
245
  try:
246
- result = bridge.set_group(group_name, attributes)
247
  return {
248
  "group": group_name,
249
  "set_attributes": attributes,
@@ -269,7 +282,7 @@ def list_lights_by_group() -> dict[str, list[str]] | list[str]:
269
  lights_data = bridge.get_light_objects("id") # dict {light_id: {details}}
270
 
271
  lights_by_group: dict[str, list[str]] = {}
272
- for group_id, group_details in groups_data.items():
273
  group_name = group_details.get("name")
274
  light_ids = group_details.get("lights", [])
275
  if group_name and light_ids:
@@ -282,11 +295,10 @@ def list_lights_by_group() -> dict[str, list[str]] | list[str]:
282
  if light_name:
283
  light_names.append(light_name)
284
  if light_names:
285
- light_names.sort() # Keep light list sorted
286
  lights_by_group[group_name] = light_names
287
 
288
  return lights_by_group
289
 
290
  except (PhueException, Exception) as e:
291
- # Return error as list
292
  return [f"Error listing lights by group: {e}"]
 
11
  class HueAttributes(TypedDict, total=False):
12
  """TypedDict for optional light attributes."""
13
 
14
+ on: NotRequired[Annotated[bool, Field(description="on/off state")]]
15
+ bri: NotRequired[Annotated[int, Field(ge=0, le=254, description="brightness")]]
16
+ hue: NotRequired[
17
+ Annotated[
18
+ int,
19
+ Field(
20
+ ge=0,
21
+ le=254,
22
+ description="saturation",
23
+ ),
24
+ ]
25
+ ]
26
+ xy: NotRequired[Annotated[list[float], Field(description="xy color coordinates")]]
27
+ ct: NotRequired[
28
+ Annotated[
29
+ int,
30
+ Field(ge=153, le=500, description="color temperature"),
31
+ ]
32
+ ]
33
  alert: NotRequired[Literal["none", "select", "lselect"]]
34
  effect: NotRequired[Literal["none", "colorloop"]]
35
+ transitiontime: NotRequired[Annotated[int, Field(description="deciseconds")]]
36
 
37
 
38
  lights_mcp = FastMCP(
 
174
  scenes_data = bridge.get_scene()
175
  scene_found = False
176
  scene_in_correct_group = False
177
+ for sinfo in scenes_data.values():
178
  if sinfo.get("name") == scene_name:
179
  scene_found = True
180
  # Check if this scene is associated with the target group ID
 
230
  }
231
 
232
  try:
233
+ result = bridge.set_light(light_name, dict(attributes))
234
  return {
235
  "light": light_name,
236
  "set_attributes": attributes,
 
256
  }
257
 
258
  try:
259
+ result = bridge.set_group(group_name, dict(attributes))
260
  return {
261
  "group": group_name,
262
  "set_attributes": attributes,
 
282
  lights_data = bridge.get_light_objects("id") # dict {light_id: {details}}
283
 
284
  lights_by_group: dict[str, list[str]] = {}
285
+ for group_details in groups_data.values():
286
  group_name = group_details.get("name")
287
  light_ids = group_details.get("lights", [])
288
  if group_name and light_ids:
 
295
  if light_name:
296
  light_names.append(light_name)
297
  if light_names:
298
+ light_names.sort()
299
  lights_by_group[group_name] = light_names
300
 
301
  return lights_by_group
302
 
303
  except (PhueException, Exception) as e:
 
304
  return [f"Error listing lights by group: {e}"]
src/fastmcp/__init__.py CHANGED
@@ -14,6 +14,7 @@ __all__ = [
14
  "FastMCP",
15
  "Context",
16
  "client",
 
17
  "settings",
18
  "Image",
19
  ]
 
14
  "FastMCP",
15
  "Context",
16
  "client",
17
+ "Client",
18
  "settings",
19
  "Image",
20
  ]
src/fastmcp/server/server.py CHANGED
@@ -1,5 +1,7 @@
1
  """FastMCP - A more ergonomic interface for MCP servers."""
2
 
 
 
3
  import datetime
4
  from collections.abc import AsyncIterator, Awaitable, Callable
5
  from contextlib import (
@@ -63,7 +65,7 @@ class MountedServer:
63
  def __init__(
64
  self,
65
  prefix: str,
66
- server: "FastMCP",
67
  tool_separator: str | None = None,
68
  resource_separator: str | None = None,
69
  prompt_separator: str | None = None,
@@ -149,7 +151,7 @@ class TimedCache:
149
 
150
 
151
  @asynccontextmanager
152
- async def default_lifespan(server: "FastMCP") -> AsyncIterator[Any]:
153
  """Default lifespan context manager that does nothing.
154
 
155
  Args:
@@ -162,8 +164,8 @@ async def default_lifespan(server: "FastMCP") -> AsyncIterator[Any]:
162
 
163
 
164
  def _lifespan_wrapper(
165
- app: "FastMCP",
166
- lifespan: Callable[["FastMCP"], AbstractAsyncContextManager[LifespanResultT]],
167
  ) -> Callable[
168
  [MCPServer[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]
169
  ]:
@@ -182,7 +184,11 @@ class FastMCP(Generic[LifespanResultT]):
182
  name: str | None = None,
183
  instructions: str | None = None,
184
  lifespan: (
185
- Callable[["FastMCP"], AbstractAsyncContextManager[LifespanResultT]] | None
 
 
 
 
186
  ) = None,
187
  tags: set[str] | None = None,
188
  **settings: Any,
@@ -273,7 +279,7 @@ class FastMCP(Generic[LifespanResultT]):
273
  self._mcp_server.get_prompt()(self._mcp_get_prompt)
274
  self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates)
275
 
276
- def get_context(self) -> "Context[ServerSession, LifespanResultT]":
277
  """
278
  Returns a Context object. Note that the context will only be valid
279
  during a request; outside a request, most methods will error.
@@ -766,7 +772,7 @@ class FastMCP(Generic[LifespanResultT]):
766
  def mount(
767
  self,
768
  prefix: str,
769
- server: "FastMCP",
770
  tool_separator: str | None = None,
771
  resource_separator: str | None = None,
772
  prompt_separator: str | None = None,
@@ -791,7 +797,7 @@ class FastMCP(Generic[LifespanResultT]):
791
  async def import_server(
792
  self,
793
  prefix: str,
794
- server: "FastMCP",
795
  tool_separator: str | None = None,
796
  resource_separator: str | None = None,
797
  prompt_separator: str | None = None,
@@ -865,7 +871,7 @@ class FastMCP(Generic[LifespanResultT]):
865
  @classmethod
866
  def from_openapi(
867
  cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, **settings: Any
868
- ) -> "FastMCPOpenAPI":
869
  """
870
  Create a FastMCP server from an OpenAPI specification.
871
  """
@@ -875,8 +881,8 @@ class FastMCP(Generic[LifespanResultT]):
875
 
876
  @classmethod
877
  def from_fastapi(
878
- cls, app: "Any", name: str | None = None, **settings: Any
879
- ) -> "FastMCPOpenAPI":
880
  """
881
  Create a FastMCP server from a FastAPI application.
882
  """
@@ -894,7 +900,7 @@ class FastMCP(Generic[LifespanResultT]):
894
  )
895
 
896
  @classmethod
897
- def from_client(cls, client: "Client", **settings: Any) -> "FastMCPProxy":
898
  """
899
  Create a FastMCP proxy server from a FastMCP client.
900
  """
 
1
  """FastMCP - A more ergonomic interface for MCP servers."""
2
 
3
+ from __future__ import annotations
4
+
5
  import datetime
6
  from collections.abc import AsyncIterator, Awaitable, Callable
7
  from contextlib import (
 
65
  def __init__(
66
  self,
67
  prefix: str,
68
+ server: FastMCP,
69
  tool_separator: str | None = None,
70
  resource_separator: str | None = None,
71
  prompt_separator: str | None = None,
 
151
 
152
 
153
  @asynccontextmanager
154
+ async def default_lifespan(server: FastMCP) -> AsyncIterator[Any]:
155
  """Default lifespan context manager that does nothing.
156
 
157
  Args:
 
164
 
165
 
166
  def _lifespan_wrapper(
167
+ app: FastMCP,
168
+ lifespan: Callable[[FastMCP], AbstractAsyncContextManager[LifespanResultT]],
169
  ) -> Callable[
170
  [MCPServer[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]
171
  ]:
 
184
  name: str | None = None,
185
  instructions: str | None = None,
186
  lifespan: (
187
+ Callable[
188
+ [FastMCP[LifespanResultT]],
189
+ AbstractAsyncContextManager[LifespanResultT],
190
+ ]
191
+ | None
192
  ) = None,
193
  tags: set[str] | None = None,
194
  **settings: Any,
 
279
  self._mcp_server.get_prompt()(self._mcp_get_prompt)
280
  self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates)
281
 
282
+ def get_context(self) -> Context[ServerSession, LifespanResultT]:
283
  """
284
  Returns a Context object. Note that the context will only be valid
285
  during a request; outside a request, most methods will error.
 
772
  def mount(
773
  self,
774
  prefix: str,
775
+ server: FastMCP[LifespanResultT],
776
  tool_separator: str | None = None,
777
  resource_separator: str | None = None,
778
  prompt_separator: str | None = None,
 
797
  async def import_server(
798
  self,
799
  prefix: str,
800
+ server: FastMCP[LifespanResultT],
801
  tool_separator: str | None = None,
802
  resource_separator: str | None = None,
803
  prompt_separator: str | None = None,
 
871
  @classmethod
872
  def from_openapi(
873
  cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, **settings: Any
874
+ ) -> FastMCPOpenAPI:
875
  """
876
  Create a FastMCP server from an OpenAPI specification.
877
  """
 
881
 
882
  @classmethod
883
  def from_fastapi(
884
+ cls, app: Any, name: str | None = None, **settings: Any
885
+ ) -> FastMCPOpenAPI:
886
  """
887
  Create a FastMCP server from a FastAPI application.
888
  """
 
900
  )
901
 
902
  @classmethod
903
+ def from_client(cls, client: Client, **settings: Any) -> FastMCPProxy:
904
  """
905
  Create a FastMCP proxy server from a FastMCP client.
906
  """