fastmcp 2.2.4__py3-none-any.whl → 2.2.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
fastmcp/server/context.py CHANGED
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations as _annotations
2
2
 
3
- from typing import Any, Generic, Literal
3
+ from typing import Any, Generic
4
4
 
5
+ from mcp import LoggingLevel
5
6
  from mcp.server.lowlevel.helper_types import ReadResourceContents
6
7
  from mcp.server.session import ServerSessionT
7
8
  from mcp.shared.context import LifespanContextT, RequestContext
@@ -122,19 +123,20 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
122
123
 
123
124
  async def log(
124
125
  self,
125
- level: Literal["debug", "info", "warning", "error"],
126
126
  message: str,
127
- *,
127
+ level: LoggingLevel | None = None,
128
128
  logger_name: str | None = None,
129
129
  ) -> None:
130
130
  """Send a log message to the client.
131
131
 
132
132
  Args:
133
- level: Log level (debug, info, warning, error)
134
133
  message: Log message
134
+ level: Optional log level. One of "debug", "info", "notice", "warning", "error", "critical",
135
+ "alert", or "emergency". Default is "info".
135
136
  logger_name: Optional logger name
136
- **extra: Additional structured data to include
137
137
  """
138
+ if level is None:
139
+ level = "info"
138
140
  await self.request_context.session.send_log_message(
139
141
  level=level, data=message, logger=logger_name
140
142
  )
@@ -159,21 +161,21 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
159
161
  return self.request_context.session
160
162
 
161
163
  # Convenience methods for common log levels
162
- async def debug(self, message: str, **extra: Any) -> None:
164
+ async def debug(self, message: str, logger_name: str | None = None) -> None:
163
165
  """Send a debug log message."""
164
- await self.log("debug", message, **extra)
166
+ await self.log(level="debug", message=message, logger_name=logger_name)
165
167
 
166
- async def info(self, message: str, **extra: Any) -> None:
168
+ async def info(self, message: str, logger_name: str | None = None) -> None:
167
169
  """Send an info log message."""
168
- await self.log("info", message, **extra)
170
+ await self.log(level="info", message=message, logger_name=logger_name)
169
171
 
170
- async def warning(self, message: str, **extra: Any) -> None:
172
+ async def warning(self, message: str, logger_name: str | None = None) -> None:
171
173
  """Send a warning log message."""
172
- await self.log("warning", message, **extra)
174
+ await self.log(level="warning", message=message, logger_name=logger_name)
173
175
 
174
- async def error(self, message: str, **extra: Any) -> None:
176
+ async def error(self, message: str, logger_name: str | None = None) -> None:
175
177
  """Send an error log message."""
176
- await self.log("error", message, **extra)
178
+ await self.log(level="error", message=message, logger_name=logger_name)
177
179
 
178
180
  async def list_roots(self) -> list[Root]:
179
181
  """List the roots available to the server, as indicated by the client."""
fastmcp/server/openapi.py CHANGED
@@ -1,19 +1,21 @@
1
1
  """FastMCP server implementation for OpenAPI integration."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import enum
4
6
  import json
5
7
  import re
6
8
  from dataclasses import dataclass
7
9
  from re import Pattern
8
- from typing import Any, Literal
10
+ from typing import TYPE_CHECKING, Any, Literal
9
11
 
10
12
  import httpx
11
- from mcp.types import TextContent
13
+ from mcp.types import EmbeddedResource, ImageContent, TextContent
12
14
  from pydantic.networks import AnyUrl
13
15
 
14
16
  from fastmcp.resources import Resource, ResourceTemplate
15
17
  from fastmcp.server.server import FastMCP
16
- from fastmcp.tools.tool import Tool
18
+ from fastmcp.tools.tool import Tool, _convert_to_content
17
19
  from fastmcp.utilities import openapi
18
20
  from fastmcp.utilities.func_metadata import func_metadata
19
21
  from fastmcp.utilities.logging import get_logger
@@ -22,6 +24,12 @@ from fastmcp.utilities.openapi import (
22
24
  format_description_with_responses,
23
25
  )
24
26
 
27
+ if TYPE_CHECKING:
28
+ from mcp.server.session import ServerSessionT
29
+ from mcp.shared.context import LifespanContextT
30
+
31
+ from fastmcp.server import Context
32
+
25
33
  logger = get_logger(__name__)
26
34
 
27
35
  HttpMethod = Literal["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]
@@ -117,6 +125,7 @@ class OpenAPITool(Tool):
117
125
  fn_metadata: Any,
118
126
  is_async: bool = True,
119
127
  tags: set[str] = set(),
128
+ timeout: float | None = None,
120
129
  ):
121
130
  super().__init__(
122
131
  name=name,
@@ -130,6 +139,7 @@ class OpenAPITool(Tool):
130
139
  )
131
140
  self._client = client
132
141
  self._route = route
142
+ self._timeout = timeout
133
143
 
134
144
  async def _execute_request(self, *args, **kwargs):
135
145
  """Execute the HTTP request based on the route configuration."""
@@ -139,19 +149,37 @@ class OpenAPITool(Tool):
139
149
  path = self._route.path
140
150
 
141
151
  # Replace path parameters with values from kwargs
152
+ # Path parameters should never be None as they're typically required
153
+ # but we'll handle that case anyway
142
154
  path_params = {
143
155
  p.name: kwargs.get(p.name)
144
156
  for p in self._route.parameters
145
157
  if p.location == "path"
158
+ and p.name in kwargs
159
+ and kwargs.get(p.name) is not None
146
160
  }
161
+
162
+ # Ensure all path parameters are provided
163
+ required_path_params = {
164
+ p.name
165
+ for p in self._route.parameters
166
+ if p.location == "path" and p.required
167
+ }
168
+ missing_params = required_path_params - path_params.keys()
169
+ if missing_params:
170
+ raise ValueError(f"Missing required path parameters: {missing_params}")
171
+
147
172
  for param_name, param_value in path_params.items():
148
173
  path = path.replace(f"{{{param_name}}}", str(param_value))
149
174
 
150
- # Prepare query parameters
175
+ # Prepare query parameters - filter out None and empty strings
151
176
  query_params = {
152
177
  p.name: kwargs.get(p.name)
153
178
  for p in self._route.parameters
154
- if p.location == "query" and p.name in kwargs
179
+ if p.location == "query"
180
+ and p.name in kwargs
181
+ and kwargs.get(p.name) is not None
182
+ and kwargs.get(p.name) != ""
155
183
  }
156
184
 
157
185
  # Prepare headers - fix typing by ensuring all values are strings
@@ -198,7 +226,7 @@ class OpenAPITool(Tool):
198
226
  params=query_params,
199
227
  headers=headers,
200
228
  json=json_data,
201
- timeout=30.0, # Default timeout
229
+ timeout=self._timeout,
202
230
  )
203
231
 
204
232
  # Raise for 4xx/5xx responses
@@ -229,9 +257,14 @@ class OpenAPITool(Tool):
229
257
  # Handle request errors (connection, timeout, etc.)
230
258
  raise ValueError(f"Request error: {str(e)}")
231
259
 
232
- async def run(self, arguments: dict[str, Any], context: Any = None) -> Any:
260
+ async def run(
261
+ self,
262
+ arguments: dict[str, Any],
263
+ context: Context[ServerSessionT, LifespanContextT] | None = None,
264
+ ) -> list[TextContent | ImageContent | EmbeddedResource]:
233
265
  """Run the tool with arguments and optional context."""
234
- return await self._execute_request(**arguments, context=context)
266
+ response = await self._execute_request(**arguments, context=context)
267
+ return _convert_to_content(response)
235
268
 
236
269
 
237
270
  class OpenAPIResource(Resource):
@@ -246,6 +279,7 @@ class OpenAPIResource(Resource):
246
279
  description: str,
247
280
  mime_type: str = "application/json",
248
281
  tags: set[str] = set(),
282
+ timeout: float | None = None,
249
283
  ):
250
284
  super().__init__(
251
285
  uri=AnyUrl(uri), # Convert string to AnyUrl
@@ -256,8 +290,11 @@ class OpenAPIResource(Resource):
256
290
  )
257
291
  self._client = client
258
292
  self._route = route
293
+ self._timeout = timeout
259
294
 
260
- async def read(self) -> str | bytes:
295
+ async def read(
296
+ self, context: Context[ServerSessionT, LifespanContextT] | None = None
297
+ ) -> str | bytes:
261
298
  """Fetch the resource data by making an HTTP request."""
262
299
  try:
263
300
  # Extract path parameters from the URI if present
@@ -268,30 +305,44 @@ class OpenAPIResource(Resource):
268
305
  if "{" in path and "}" in path:
269
306
  # Extract the resource ID from the URI (the last part after the last slash)
270
307
  parts = resource_uri.split("/")
308
+
271
309
  if len(parts) > 1:
272
310
  # Find all path parameters in the route path
273
311
  path_params = {}
274
312
 
275
- # Extract parameters from the URI
276
- param_value = parts[
277
- -1
278
- ] # The last part contains the parameter value
279
-
280
- # Find the path parameter name from the route path
313
+ # Find the path parameter names from the route path
281
314
  param_matches = re.findall(r"\{([^}]+)\}", path)
282
315
  if param_matches:
283
- # Assume the last parameter in the URI is for the first path parameter in the route
284
- path_param_name = param_matches[0]
285
- path_params[path_param_name] = param_value
316
+ # Reverse sorting from creation order (traversal is backwards)
317
+ param_matches.sort(reverse=True)
318
+ # Number of sent parameters is number of parts -1 (assuming first part is resource identifier)
319
+ expected_param_count = len(parts) - 1
320
+ # Map parameters from the end of the URI to the parameters in the path
321
+ # Last parameter in URI (parts[-1]) maps to last parameter in path, and so on
322
+ for i, param_name in enumerate(param_matches):
323
+ # Ensure we don't use resource identifier as parameter
324
+ if i < expected_param_count:
325
+ # Get values from the end of parts
326
+ param_value = parts[-1 - i]
327
+ path_params[param_name] = param_value
286
328
 
287
329
  # Replace path parameters with their values
288
330
  for param_name, param_value in path_params.items():
289
331
  path = path.replace(f"{{{param_name}}}", str(param_value))
290
332
 
333
+ # Filter any query parameters - get query parameters and filter out None/empty values
334
+ query_params = {}
335
+ for param in self._route.parameters:
336
+ if param.location == "query" and hasattr(self, f"_{param.name}"):
337
+ value = getattr(self, f"_{param.name}")
338
+ if value is not None and value != "":
339
+ query_params[param.name] = value
340
+
291
341
  response = await self._client.request(
292
342
  method=self._route.method,
293
343
  url=path,
294
- timeout=30.0, # Default timeout
344
+ params=query_params,
345
+ timeout=self._timeout,
295
346
  )
296
347
 
297
348
  # Raise for 4xx/5xx responses
@@ -339,6 +390,7 @@ class OpenAPIResourceTemplate(ResourceTemplate):
339
390
  description: str,
340
391
  parameters: dict[str, Any],
341
392
  tags: set[str] = set(),
393
+ timeout: float | None = None,
342
394
  ):
343
395
  super().__init__(
344
396
  uri_template=uri_template,
@@ -347,11 +399,18 @@ class OpenAPIResourceTemplate(ResourceTemplate):
347
399
  fn=lambda **kwargs: None,
348
400
  parameters=parameters,
349
401
  tags=tags,
402
+ context_kwarg=None,
350
403
  )
351
404
  self._client = client
352
405
  self._route = route
406
+ self._timeout = timeout
353
407
 
354
- async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource:
408
+ async def create_resource(
409
+ self,
410
+ uri: str,
411
+ params: dict[str, Any],
412
+ context: Context[ServerSessionT, LifespanContextT] | None = None,
413
+ ) -> Resource:
355
414
  """Create a resource with the given parameters."""
356
415
  # Generate a URI for this resource instance
357
416
  uri_parts = []
@@ -367,6 +426,7 @@ class OpenAPIResourceTemplate(ResourceTemplate):
367
426
  description=self.description or f"Resource for {self._route.path}",
368
427
  mime_type="application/json",
369
428
  tags=set(self._route.tags or []),
429
+ timeout=self._timeout,
370
430
  )
371
431
 
372
432
 
@@ -414,6 +474,7 @@ class FastMCPOpenAPI(FastMCP):
414
474
  client: httpx.AsyncClient,
415
475
  name: str | None = None,
416
476
  route_maps: list[RouteMap] | None = None,
477
+ timeout: float | None = None,
417
478
  **settings: Any,
418
479
  ):
419
480
  """
@@ -424,13 +485,13 @@ class FastMCPOpenAPI(FastMCP):
424
485
  client: httpx AsyncClient for making HTTP requests
425
486
  name: Optional name for the server
426
487
  route_maps: Optional list of RouteMap objects defining route mappings
427
- default_mime_type: Default MIME type for resources
488
+ timeout: Optional timeout (in seconds) for all requests
428
489
  **settings: Additional settings for FastMCP
429
490
  """
430
491
  super().__init__(name=name or "OpenAPI FastMCP", **settings)
431
492
 
432
493
  self._client = client
433
-
494
+ self._timeout = timeout
434
495
  http_routes = openapi.parse_openapi_to_http_routes(openapi_spec)
435
496
 
436
497
  # Process routes
@@ -488,6 +549,7 @@ class FastMCPOpenAPI(FastMCP):
488
549
  fn_metadata=func_metadata(_openapi_passthrough),
489
550
  is_async=True,
490
551
  tags=set(route.tags or []),
552
+ timeout=self._timeout,
491
553
  )
492
554
  # Register the tool by directly assigning to the tools dictionary
493
555
  self._tool_manager._tools[tool_name] = tool
@@ -516,6 +578,7 @@ class FastMCPOpenAPI(FastMCP):
516
578
  name=resource_name,
517
579
  description=enhanced_description,
518
580
  tags=set(route.tags or []),
581
+ timeout=self._timeout,
519
582
  )
520
583
  # Register the resource by directly assigning to the resources dictionary
521
584
  self._resource_manager._resources[str(resource.uri)] = resource
@@ -561,6 +624,7 @@ class FastMCPOpenAPI(FastMCP):
561
624
  description=enhanced_description,
562
625
  parameters=template_params_schema,
563
626
  tags=set(route.tags or []),
627
+ timeout=self._timeout,
564
628
  )
565
629
  # Register the template by directly assigning to the templates dictionary
566
630
  self._resource_manager._templates[uri_template_str] = template
@@ -573,13 +637,4 @@ class FastMCPOpenAPI(FastMCP):
573
637
 
574
638
  context = self.get_context()
575
639
  result = await self._tool_manager.call_tool(name, arguments, context=context)
576
-
577
- # For other tools, ensure the response is wrapped in TextContent
578
- if isinstance(result, dict | str):
579
- if isinstance(result, dict):
580
- result_text = json.dumps(result)
581
- else:
582
- result_text = result
583
- return [TextContent(text=result_text, type="text")]
584
-
585
640
  return result
fastmcp/server/proxy.py CHANGED
@@ -1,4 +1,6 @@
1
- from typing import Any, cast
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, cast
2
4
  from urllib.parse import quote
3
5
 
4
6
  import mcp.types
@@ -25,6 +27,12 @@ from fastmcp.tools.tool import Tool
25
27
  from fastmcp.utilities.func_metadata import func_metadata
26
28
  from fastmcp.utilities.logging import get_logger
27
29
 
30
+ if TYPE_CHECKING:
31
+ from mcp.server.session import ServerSessionT
32
+ from mcp.shared.context import LifespanContextT
33
+
34
+ from fastmcp.server import Context
35
+
28
36
  logger = get_logger(__name__)
29
37
 
30
38
 
@@ -33,12 +41,12 @@ def _proxy_passthrough():
33
41
 
34
42
 
35
43
  class ProxyTool(Tool):
36
- def __init__(self, client: "Client", **kwargs):
44
+ def __init__(self, client: Client, **kwargs):
37
45
  super().__init__(**kwargs)
38
46
  self._client = client
39
47
 
40
48
  @classmethod
41
- async def from_client(cls, client: "Client", tool: mcp.types.Tool) -> "ProxyTool":
49
+ async def from_client(cls, client: Client, tool: mcp.types.Tool) -> ProxyTool:
42
50
  return cls(
43
51
  client=client,
44
52
  name=tool.name,
@@ -50,8 +58,10 @@ class ProxyTool(Tool):
50
58
  )
51
59
 
52
60
  async def run(
53
- self, arguments: dict[str, Any], context: Context | None = None
54
- ) -> Any:
61
+ self,
62
+ arguments: dict[str, Any],
63
+ context: Context[ServerSessionT, LifespanContextT] | None = None,
64
+ ) -> list[TextContent | ImageContent | EmbeddedResource]:
55
65
  # the client context manager will swallow any exceptions inside a TaskGroup
56
66
  # so we return the raw result and raise an exception ourselves
57
67
  async with self._client:
@@ -64,17 +74,15 @@ class ProxyTool(Tool):
64
74
 
65
75
 
66
76
  class ProxyResource(Resource):
67
- def __init__(
68
- self, client: "Client", *, _value: str | bytes | None = None, **kwargs
69
- ):
77
+ def __init__(self, client: Client, *, _value: str | bytes | None = None, **kwargs):
70
78
  super().__init__(**kwargs)
71
79
  self._client = client
72
80
  self._value = _value
73
81
 
74
82
  @classmethod
75
83
  async def from_client(
76
- cls, client: "Client", resource: mcp.types.Resource
77
- ) -> "ProxyResource":
84
+ cls, client: Client, resource: mcp.types.Resource
85
+ ) -> ProxyResource:
78
86
  return cls(
79
87
  client=client,
80
88
  uri=resource.uri,
@@ -83,7 +91,9 @@ class ProxyResource(Resource):
83
91
  mime_type=resource.mimeType,
84
92
  )
85
93
 
86
- async def read(self) -> str | bytes:
94
+ async def read(
95
+ self, context: Context[ServerSessionT, LifespanContextT] | None = None
96
+ ) -> str | bytes:
87
97
  if self._value is not None:
88
98
  return self._value
89
99
 
@@ -98,14 +108,14 @@ class ProxyResource(Resource):
98
108
 
99
109
 
100
110
  class ProxyTemplate(ResourceTemplate):
101
- def __init__(self, client: "Client", **kwargs):
111
+ def __init__(self, client: Client, **kwargs):
102
112
  super().__init__(**kwargs)
103
113
  self._client = client
104
114
 
105
115
  @classmethod
106
116
  async def from_client(
107
- cls, client: "Client", template: mcp.types.ResourceTemplate
108
- ) -> "ProxyTemplate":
117
+ cls, client: Client, template: mcp.types.ResourceTemplate
118
+ ) -> ProxyTemplate:
109
119
  return cls(
110
120
  client=client,
111
121
  uri_template=template.uriTemplate,
@@ -115,7 +125,12 @@ class ProxyTemplate(ResourceTemplate):
115
125
  parameters={},
116
126
  )
117
127
 
118
- async def create_resource(self, uri: str, params: dict[str, Any]) -> ProxyResource:
128
+ async def create_resource(
129
+ self,
130
+ uri: str,
131
+ params: dict[str, Any],
132
+ context: Context[ServerSessionT, LifespanContextT] | None = None,
133
+ ) -> ProxyResource:
119
134
  # dont use the provided uri, because it may not be the same as the
120
135
  # uri_template on the remote server.
121
136
  # quote params to ensure they are valid for the uri_template
@@ -144,14 +159,12 @@ class ProxyTemplate(ResourceTemplate):
144
159
 
145
160
 
146
161
  class ProxyPrompt(Prompt):
147
- def __init__(self, client: "Client", **kwargs):
162
+ def __init__(self, client: Client, **kwargs):
148
163
  super().__init__(**kwargs)
149
164
  self._client = client
150
165
 
151
166
  @classmethod
152
- async def from_client(
153
- cls, client: "Client", prompt: mcp.types.Prompt
154
- ) -> "ProxyPrompt":
167
+ async def from_client(cls, client: Client, prompt: mcp.types.Prompt) -> ProxyPrompt:
155
168
  return cls(
156
169
  client=client,
157
170
  name=prompt.name,
@@ -160,14 +173,18 @@ class ProxyPrompt(Prompt):
160
173
  fn=_proxy_passthrough,
161
174
  )
162
175
 
163
- async def render(self, arguments: dict[str, Any]) -> list[Message]:
176
+ async def render(
177
+ self,
178
+ arguments: dict[str, Any],
179
+ context: Context[ServerSessionT, LifespanContextT] | None = None,
180
+ ) -> list[Message]:
164
181
  async with self._client:
165
182
  result = await self._client.get_prompt(self.name, arguments)
166
183
  return [Message(role=m.role, content=m.content) for m in result]
167
184
 
168
185
 
169
186
  class FastMCPProxy(FastMCP):
170
- def __init__(self, client: "Client", **kwargs):
187
+ def __init__(self, client: Client, **kwargs):
171
188
  super().__init__(**kwargs)
172
189
  self.client = client
173
190
 
fastmcp/server/server.py CHANGED
@@ -1,5 +1,7 @@
1
1
  """FastMCP - A more ergonomic interface for MCP servers."""
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import datetime
4
6
  from collections.abc import AsyncIterator, Awaitable, Callable
5
7
  from contextlib import (
@@ -63,7 +65,7 @@ class MountedServer:
63
65
  def __init__(
64
66
  self,
65
67
  prefix: str,
66
- server: "FastMCP",
68
+ server: FastMCP,
67
69
  tool_separator: str | None = None,
68
70
  resource_separator: str | None = None,
69
71
  prompt_separator: str | None = None,
@@ -149,7 +151,7 @@ class TimedCache:
149
151
 
150
152
 
151
153
  @asynccontextmanager
152
- async def default_lifespan(server: "FastMCP") -> AsyncIterator[Any]:
154
+ async def default_lifespan(server: FastMCP) -> AsyncIterator[Any]:
153
155
  """Default lifespan context manager that does nothing.
154
156
 
155
157
  Args:
@@ -162,8 +164,8 @@ async def default_lifespan(server: "FastMCP") -> AsyncIterator[Any]:
162
164
 
163
165
 
164
166
  def _lifespan_wrapper(
165
- app: "FastMCP",
166
- lifespan: Callable[["FastMCP"], AbstractAsyncContextManager[LifespanResultT]],
167
+ app: FastMCP,
168
+ lifespan: Callable[[FastMCP], AbstractAsyncContextManager[LifespanResultT]],
167
169
  ) -> Callable[
168
170
  [MCPServer[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]
169
171
  ]:
@@ -182,7 +184,11 @@ class FastMCP(Generic[LifespanResultT]):
182
184
  name: str | None = None,
183
185
  instructions: str | None = None,
184
186
  lifespan: (
185
- Callable[["FastMCP"], AbstractAsyncContextManager[LifespanResultT]] | None
187
+ Callable[
188
+ [FastMCP[LifespanResultT]],
189
+ AbstractAsyncContextManager[LifespanResultT],
190
+ ]
191
+ | None
186
192
  ) = None,
187
193
  tags: set[str] | None = None,
188
194
  **settings: Any,
@@ -273,7 +279,7 @@ class FastMCP(Generic[LifespanResultT]):
273
279
  self._mcp_server.get_prompt()(self._mcp_get_prompt)
274
280
  self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates)
275
281
 
276
- def get_context(self) -> "Context[ServerSession, LifespanResultT]":
282
+ def get_context(self) -> Context[ServerSession, LifespanResultT]:
277
283
  """
278
284
  Returns a Context object. Note that the context will only be valid
279
285
  during a request; outside a request, most methods will error.
@@ -398,9 +404,10 @@ class FastMCP(Generic[LifespanResultT]):
398
404
  server.
399
405
  """
400
406
  if self._resource_manager.has_resource(uri):
401
- resource = await self._resource_manager.get_resource(uri)
407
+ context = self.get_context()
408
+ resource = await self._resource_manager.get_resource(uri, context=context)
402
409
  try:
403
- content = await resource.read()
410
+ content = await resource.read(context=context)
404
411
  return [
405
412
  ReadResourceContents(content=content, mime_type=resource.mime_type)
406
413
  ]
@@ -424,7 +431,10 @@ class FastMCP(Generic[LifespanResultT]):
424
431
 
425
432
  """
426
433
  if self._prompt_manager.has_prompt(name):
427
- messages = await self._prompt_manager.render_prompt(name, arguments)
434
+ context = self.get_context()
435
+ messages = await self._prompt_manager.render_prompt(
436
+ name, arguments=arguments or {}, context=context
437
+ )
428
438
  return GetPromptResult(messages=pydantic_core.to_jsonable_python(messages))
429
439
  else:
430
440
  for server in self._mounted_servers.values():
@@ -562,6 +572,10 @@ class FastMCP(Generic[LifespanResultT]):
562
572
  - bytes for binary content
563
573
  - other types will be converted to JSON
564
574
 
575
+ Resources can optionally request a Context object by adding a parameter with the
576
+ Context type annotation. The context provides access to MCP capabilities like
577
+ logging, progress reporting, and session information.
578
+
565
579
  If the URI contains parameters (e.g. "resource://{param}") or the function
566
580
  has parameters, it will be registered as a template resource.
567
581
 
@@ -586,6 +600,11 @@ class FastMCP(Generic[LifespanResultT]):
586
600
  def get_weather(city: str) -> str:
587
601
  return f"Weather for {city}"
588
602
 
603
+ @server.resource("resource://{city}/weather")
604
+ def get_weather_with_context(city: str, ctx: Context) -> str:
605
+ ctx.info(f"Fetching weather for {city}")
606
+ return f"Weather for {city}"
607
+
589
608
  @server.resource("resource://{city}/weather")
590
609
  async def get_weather(city: str) -> str:
591
610
  data = await fetch_weather(city)
@@ -639,6 +658,10 @@ class FastMCP(Generic[LifespanResultT]):
639
658
  ) -> Callable[[AnyFunction], AnyFunction]:
640
659
  """Decorator to register a prompt.
641
660
 
661
+ Prompts can optionally request a Context object by adding a parameter with the
662
+ Context type annotation. The context provides access to MCP capabilities like
663
+ logging, progress reporting, and session information.
664
+
642
665
  Args:
643
666
  name: Optional name for the prompt (defaults to function name)
644
667
  description: Optional description of what the prompt does
@@ -655,6 +678,17 @@ class FastMCP(Generic[LifespanResultT]):
655
678
  }
656
679
  ]
657
680
 
681
+ @server.prompt()
682
+ def analyze_with_context(table_name: str, ctx: Context) -> list[Message]:
683
+ ctx.info(f"Analyzing table {table_name}")
684
+ schema = read_table_schema(table_name)
685
+ return [
686
+ {
687
+ "role": "user",
688
+ "content": f"Analyze this schema:\n{schema}"
689
+ }
690
+ ]
691
+
658
692
  @server.prompt()
659
693
  async def analyze_file(path: str) -> list[Message]:
660
694
  content = await read_file(path)
@@ -738,7 +772,7 @@ class FastMCP(Generic[LifespanResultT]):
738
772
  def mount(
739
773
  self,
740
774
  prefix: str,
741
- server: "FastMCP",
775
+ server: FastMCP[LifespanResultT],
742
776
  tool_separator: str | None = None,
743
777
  resource_separator: str | None = None,
744
778
  prompt_separator: str | None = None,
@@ -763,7 +797,7 @@ class FastMCP(Generic[LifespanResultT]):
763
797
  async def import_server(
764
798
  self,
765
799
  prefix: str,
766
- server: "FastMCP",
800
+ server: FastMCP[LifespanResultT],
767
801
  tool_separator: str | None = None,
768
802
  resource_separator: str | None = None,
769
803
  prompt_separator: str | None = None,
@@ -837,7 +871,7 @@ class FastMCP(Generic[LifespanResultT]):
837
871
  @classmethod
838
872
  def from_openapi(
839
873
  cls, openapi_spec: dict[str, Any], client: httpx.AsyncClient, **settings: Any
840
- ) -> "FastMCPOpenAPI":
874
+ ) -> FastMCPOpenAPI:
841
875
  """
842
876
  Create a FastMCP server from an OpenAPI specification.
843
877
  """
@@ -847,8 +881,8 @@ class FastMCP(Generic[LifespanResultT]):
847
881
 
848
882
  @classmethod
849
883
  def from_fastapi(
850
- cls, app: "Any", name: str | None = None, **settings: Any
851
- ) -> "FastMCPOpenAPI":
884
+ cls, app: Any, name: str | None = None, **settings: Any
885
+ ) -> FastMCPOpenAPI:
852
886
  """
853
887
  Create a FastMCP server from a FastAPI application.
854
888
  """
@@ -866,7 +900,7 @@ class FastMCP(Generic[LifespanResultT]):
866
900
  )
867
901
 
868
902
  @classmethod
869
- def from_client(cls, client: "Client", **settings: Any) -> "FastMCPProxy":
903
+ def from_client(cls, client: Client, **settings: Any) -> FastMCPProxy:
870
904
  """
871
905
  Create a FastMCP proxy server from a FastMCP client.
872
906
  """