4
4
5
5
import inspect
6
6
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
12
9
from typing import Any , Generic , Literal
13
10
14
11
import anyio
15
12
import pydantic_core
16
- from pydantic import BaseModel , Field
13
+ from pydantic import BaseModel
17
14
from pydantic .networks import AnyUrl
18
15
from pydantic_settings import BaseSettings , SettingsConfigDict
19
16
from starlette .applications import Starlette
25
22
from starlette .types import Receive , Scope , Send
26
23
27
24
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
32
26
from mcp .server .auth .provider import OAuthAuthorizationServerProvider , ProviderTokenVerifier , TokenVerifier
33
27
from mcp .server .auth .settings import AuthSettings
34
28
from mcp .server .elicitation import ElicitationResult , ElicitSchemaModelT , elicit_with_validation
48
42
from mcp .server .streamable_http_manager import StreamableHTTPSessionManager
49
43
from mcp .server .transport_security import TransportSecuritySettings
50
44
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
57
46
from mcp .types import Prompt as MCPPrompt
58
47
from mcp .types import PromptArgument as MCPPromptArgument
59
48
from mcp .types import Resource as MCPResource
@@ -79,58 +68,57 @@ class Settings(BaseSettings, Generic[LifespanResultT]):
79
68
)
80
69
81
70
# Server settings
82
- debug : bool = False
83
- log_level : Literal ["DEBUG" , "INFO" , "WARNING" , "ERROR" , "CRITICAL" ] = "INFO"
71
+ debug : bool
72
+ log_level : Literal ["DEBUG" , "INFO" , "WARNING" , "ERROR" , "CRITICAL" ]
84
73
85
74
# HTTP settings
86
- host : str = "127.0.0.1"
87
- port : int = 8000
88
- mount_path : str = "/" # Mount path (e.g. "/github", defaults to root path)
89
- sse_path : str = "/sse"
90
- message_path : str = "/messages/"
91
- streamable_http_path : str = "/mcp"
75
+ host : str
76
+ port : int
77
+ mount_path : str
78
+ sse_path : str
79
+ message_path : str
80
+ streamable_http_path : str
92
81
93
82
# StreamableHTTP settings
94
- json_response : bool = False
95
- stateless_http : bool = False # If True, uses true stateless mode (new transport per request)
83
+ json_response : bool
84
+ stateless_http : bool
85
+ """Define if the server should create a new transport per request."""
96
86
97
87
# resource settings
98
- warn_on_duplicate_resources : bool = True
88
+ warn_on_duplicate_resources : bool
99
89
100
90
# tool settings
101
- warn_on_duplicate_tools : bool = True
91
+ warn_on_duplicate_tools : bool
102
92
103
93
# prompt settings
104
- warn_on_duplicate_prompts : bool = True
94
+ warn_on_duplicate_prompts : bool
105
95
106
- dependencies : list [str ] = Field (
107
- default_factory = list ,
108
- description = "List of dependencies to install in the server environment" ,
109
- )
96
+ # TODO(Marcelo): Investigate if this is used. If it is, it's probably a good idea to remove it.
97
+ dependencies : list [str ]
98
+ """A list of dependencies to install in the server environment."""
110
99
111
- lifespan : Callable [[FastMCP ], AbstractAsyncContextManager [LifespanResultT ]] | None = Field (
112
- None , description = "Lifespan context manager"
113
- )
100
+ lifespan : Callable [[FastMCP [LifespanResultT ]], AbstractAsyncContextManager [LifespanResultT ]] | None
101
+ """A async context manager that will be called when the server is started."""
114
102
115
- auth : AuthSettings | None = None
103
+ auth : AuthSettings | None
116
104
117
105
# Transport security settings (DNS rebinding protection)
118
- transport_security : TransportSecuritySettings | None = None
106
+ transport_security : TransportSecuritySettings | None
119
107
120
108
121
109
def lifespan_wrapper (
122
- app : FastMCP ,
123
- lifespan : Callable [[FastMCP ], AbstractAsyncContextManager [LifespanResultT ]],
124
- ) -> Callable [[MCPServer [LifespanResultT , Request ]], AbstractAsyncContextManager [object ]]:
110
+ app : FastMCP [ LifespanResultT ] ,
111
+ lifespan : Callable [[FastMCP [ LifespanResultT ] ], AbstractAsyncContextManager [LifespanResultT ]],
112
+ ) -> Callable [[MCPServer [LifespanResultT , Request ]], AbstractAsyncContextManager [LifespanResultT ]]:
125
113
@asynccontextmanager
126
- async def wrap (s : MCPServer [LifespanResultT , Request ]) -> AsyncIterator [object ]:
114
+ async def wrap (_ : MCPServer [LifespanResultT , Request ]) -> AsyncIterator [LifespanResultT ]:
127
115
async with lifespan (app ) as context :
128
116
yield context
129
117
130
118
return wrap
131
119
132
120
133
- class FastMCP :
121
+ class FastMCP ( Generic [ LifespanResultT ]) :
134
122
def __init__ (
135
123
self ,
136
124
name : str | None = None ,
@@ -140,14 +128,50 @@ def __init__(
140
128
event_store : EventStore | None = None ,
141
129
* ,
142
130
tools : list [Tool ] | None = None ,
143
- ** settings : Any ,
131
+ debug : bool = False ,
132
+ log_level : Literal ["DEBUG" , "INFO" , "WARNING" , "ERROR" , "CRITICAL" ] = "INFO" ,
133
+ host : str = "127.0.0.1" ,
134
+ port : int = 8000 ,
135
+ mount_path : str = "/" ,
136
+ sse_path : str = "/sse" ,
137
+ message_path : str = "/messages/" ,
138
+ streamable_http_path : str = "/mcp" ,
139
+ json_response : bool = False ,
140
+ stateless_http : bool = False ,
141
+ warn_on_duplicate_resources : bool = True ,
142
+ warn_on_duplicate_tools : bool = True ,
143
+ warn_on_duplicate_prompts : bool = True ,
144
+ dependencies : Collection [str ] = (),
145
+ lifespan : Callable [[FastMCP [LifespanResultT ]], AbstractAsyncContextManager [LifespanResultT ]] | None = None ,
146
+ auth : AuthSettings | None = None ,
147
+ transport_security : TransportSecuritySettings | None = None ,
144
148
):
145
- self .settings = Settings (** settings )
149
+ self .settings = Settings (
150
+ debug = debug ,
151
+ log_level = log_level ,
152
+ host = host ,
153
+ port = port ,
154
+ mount_path = mount_path ,
155
+ sse_path = sse_path ,
156
+ message_path = message_path ,
157
+ streamable_http_path = streamable_http_path ,
158
+ json_response = json_response ,
159
+ stateless_http = stateless_http ,
160
+ warn_on_duplicate_resources = warn_on_duplicate_resources ,
161
+ warn_on_duplicate_tools = warn_on_duplicate_tools ,
162
+ warn_on_duplicate_prompts = warn_on_duplicate_prompts ,
163
+ dependencies = list (dependencies ),
164
+ lifespan = lifespan ,
165
+ auth = auth ,
166
+ transport_security = transport_security ,
167
+ )
146
168
147
169
self ._mcp_server = MCPServer (
148
170
name = name or "FastMCP" ,
149
171
instructions = instructions ,
150
- lifespan = (lifespan_wrapper (self , self .settings .lifespan ) if self .settings .lifespan else default_lifespan ),
172
+ # TODO(Marcelo): It seems there's a type mismatch between the lifespan type from an FastMCP and Server.
173
+ # We need to create a Lifespan type that is a generic on the server type, like Starlette does.
174
+ lifespan = (lifespan_wrapper (self , self .settings .lifespan ) if self .settings .lifespan else default_lifespan ), # type: ignore
151
175
)
152
176
self ._tool_manager = ToolManager (tools = tools , warn_on_duplicate_tools = self .settings .warn_on_duplicate_tools )
153
177
self ._resource_manager = ResourceManager (warn_on_duplicate_resources = self .settings .warn_on_duplicate_resources )
@@ -257,7 +281,7 @@ async def list_tools(self) -> list[MCPTool]:
257
281
for info in tools
258
282
]
259
283
260
- def get_context (self ) -> Context [ServerSession , object , Request ]:
284
+ def get_context (self ) -> Context [ServerSession , LifespanResultT , Request ]:
261
285
"""
262
286
Returns a Context object. Note that the context will only be valid
263
287
during a request; outside a request, most methods will error.
0 commit comments