Skip to content

Commit a6f413d

Browse files
committed
Unpack settings in FastMCP
1 parent 35777b9 commit a6f413d

File tree

3 files changed

+54
-29
lines changed

3 files changed

+54
-29
lines changed

src/mcp/server/fastmcp/server.py

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@
44

55
import inspect
66
import re
7-
from collections.abc import AsyncIterator, Awaitable, Callable, Iterable, Sequence
8-
from contextlib import (
9-
AbstractAsyncContextManager,
10-
asynccontextmanager,
11-
)
7+
from collections.abc import AsyncIterator, Awaitable, Callable, Collection, Iterable, Sequence
8+
from contextlib import AbstractAsyncContextManager, asynccontextmanager
129
from typing import Any, Generic, Literal
1310

1411
import anyio
@@ -25,10 +22,7 @@
2522
from starlette.types import Receive, Scope, Send
2623

2724
from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
28-
from mcp.server.auth.middleware.bearer_auth import (
29-
BearerAuthBackend,
30-
RequireAuthMiddleware,
31-
)
25+
from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend, RequireAuthMiddleware
3226
from mcp.server.auth.provider import OAuthAuthorizationServerProvider, ProviderTokenVerifier, TokenVerifier
3327
from mcp.server.auth.settings import AuthSettings
3428
from mcp.server.elicitation import ElicitationResult, ElicitSchemaModelT, elicit_with_validation
@@ -48,12 +42,7 @@
4842
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
4943
from mcp.server.transport_security import TransportSecuritySettings
5044
from mcp.shared.context import LifespanContextT, RequestContext, RequestT
51-
from mcp.types import (
52-
AnyFunction,
53-
ContentBlock,
54-
GetPromptResult,
55-
ToolAnnotations,
56-
)
45+
from mcp.types import AnyFunction, ContentBlock, GetPromptResult, ToolAnnotations
5746
from mcp.types import Prompt as MCPPrompt
5847
from mcp.types import PromptArgument as MCPPromptArgument
5948
from mcp.types import Resource as MCPResource
@@ -108,7 +97,7 @@ class Settings(BaseSettings, Generic[LifespanResultT]):
10897
description="List of dependencies to install in the server environment",
10998
)
11099

111-
lifespan: Callable[[FastMCP], AbstractAsyncContextManager[LifespanResultT]] | None = Field(
100+
lifespan: Callable[[FastMCP[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]] | None = Field(
112101
None, description="Lifespan context manager"
113102
)
114103

@@ -119,18 +108,18 @@ class Settings(BaseSettings, Generic[LifespanResultT]):
119108

120109

121110
def lifespan_wrapper(
122-
app: FastMCP,
123-
lifespan: Callable[[FastMCP], AbstractAsyncContextManager[LifespanResultT]],
124-
) -> Callable[[MCPServer[LifespanResultT, Request]], AbstractAsyncContextManager[object]]:
111+
app: FastMCP[LifespanResultT],
112+
lifespan: Callable[[FastMCP[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]],
113+
) -> Callable[[MCPServer[LifespanResultT, Request]], AbstractAsyncContextManager[LifespanResultT]]:
125114
@asynccontextmanager
126-
async def wrap(s: MCPServer[LifespanResultT, Request]) -> AsyncIterator[object]:
115+
async def wrap(_: MCPServer[LifespanResultT, Request]) -> AsyncIterator[LifespanResultT]:
127116
async with lifespan(app) as context:
128117
yield context
129118

130119
return wrap
131120

132121

133-
class FastMCP:
122+
class FastMCP(Generic[LifespanResultT]):
134123
def __init__(
135124
self,
136125
name: str | None = None,
@@ -140,14 +129,50 @@ def __init__(
140129
event_store: EventStore | None = None,
141130
*,
142131
tools: list[Tool] | None = None,
143-
**settings: Any,
132+
debug: bool = False,
133+
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO",
134+
host: str = "127.0.0.1",
135+
port: int = 8000,
136+
mount_path: str = "/",
137+
sse_path: str = "/sse",
138+
message_path: str = "/messages/",
139+
streamable_http_path: str = "/mcp",
140+
json_response: bool = False,
141+
stateless_http: bool = False,
142+
warn_on_duplicate_resources: bool = True,
143+
warn_on_duplicate_tools: bool = True,
144+
warn_on_duplicate_prompts: bool = True,
145+
dependencies: Collection[str] = (),
146+
lifespan: Callable[[FastMCP[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]] | None = None,
147+
auth: AuthSettings | None = None,
148+
transport_security: TransportSecuritySettings | None = None,
144149
):
145-
self.settings = Settings(**settings)
150+
self.settings = Settings(
151+
debug=debug,
152+
log_level=log_level,
153+
host=host,
154+
port=port,
155+
mount_path=mount_path,
156+
sse_path=sse_path,
157+
message_path=message_path,
158+
streamable_http_path=streamable_http_path,
159+
json_response=json_response,
160+
stateless_http=stateless_http,
161+
warn_on_duplicate_resources=warn_on_duplicate_resources,
162+
warn_on_duplicate_tools=warn_on_duplicate_tools,
163+
warn_on_duplicate_prompts=warn_on_duplicate_prompts,
164+
dependencies=list(dependencies),
165+
lifespan=lifespan,
166+
auth=auth,
167+
transport_security=transport_security,
168+
)
146169

147170
self._mcp_server = MCPServer(
148171
name=name or "FastMCP",
149172
instructions=instructions,
150-
lifespan=(lifespan_wrapper(self, self.settings.lifespan) if self.settings.lifespan else default_lifespan),
173+
# TODO(Marcelo): It seems there's a type mismatch between the lifespan type from an FastMCP and Server.
174+
# We need to create a Lifespan type that is a generic on the server type, like Starlette does.
175+
lifespan=(lifespan_wrapper(self, self.settings.lifespan) if self.settings.lifespan else default_lifespan), # type: ignore
151176
)
152177
self._tool_manager = ToolManager(tools=tools, warn_on_duplicate_tools=self.settings.warn_on_duplicate_tools)
153178
self._resource_manager = ResourceManager(warn_on_duplicate_resources=self.settings.warn_on_duplicate_resources)
@@ -257,7 +282,7 @@ async def list_tools(self) -> list[MCPTool]:
257282
for info in tools
258283
]
259284

260-
def get_context(self) -> Context[ServerSession, object, Request]:
285+
def get_context(self) -> Context[ServerSession, LifespanResultT, Request]:
261286
"""
262287
Returns a Context object. Note that the context will only be valid
263288
during a request; outside a request, most methods will error.

src/mcp/server/lowlevel/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ async def main():
9393

9494
logger = logging.getLogger(__name__)
9595

96-
LifespanResultT = TypeVar("LifespanResultT")
96+
LifespanResultT = TypeVar("LifespanResultT", default=Any)
9797
RequestT = TypeVar("RequestT", default=Any)
9898

9999
# type aliases for tool call results
@@ -118,7 +118,7 @@ def __init__(
118118

119119

120120
@asynccontextmanager
121-
async def lifespan(server: Server[LifespanResultT, RequestT]) -> AsyncIterator[object]:
121+
async def lifespan(_: Server[LifespanResultT, RequestT]) -> AsyncIterator[dict[str, Any]]:
122122
"""Default lifespan context manager that does nothing.
123123
124124
Args:

tests/shared/test_progress_notifications.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ async def run_server():
4242
serv_sesh = server_session
4343
async for message in server_session.incoming_messages:
4444
try:
45-
await server._handle_message(message, server_session, ())
45+
await server._handle_message(message, server_session, {})
4646
except Exception as e:
4747
raise e
4848

@@ -252,7 +252,7 @@ async def run_server():
252252
) as server_session:
253253
async for message in server_session.incoming_messages:
254254
try:
255-
await server._handle_message(message, server_session, ())
255+
await server._handle_message(message, server_session, {})
256256
except Exception as e:
257257
raise e
258258

0 commit comments

Comments
 (0)