fastmcp 2.1.0__py3-none-any.whl → 2.1.1__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/cli/cli.py CHANGED
@@ -323,6 +323,8 @@ def run(
323
323
  # Import and get server object
324
324
  server = _import_server(file, server_object)
325
325
 
326
+ logger.info(f'Found server "{server.name}" in {file}')
327
+
326
328
  # Run the server
327
329
  kwargs = {}
328
330
  if transport:
@@ -208,6 +208,28 @@ class PythonStdioTransport(StdioTransport):
208
208
  self.script_path = script_path
209
209
 
210
210
 
211
+ class FastMCPStdioTransport(StdioTransport):
212
+ """Transport for running FastMCP servers using the FastMCP CLI."""
213
+
214
+ def __init__(
215
+ self,
216
+ script_path: str | Path,
217
+ args: list[str] | None = None,
218
+ env: dict[str, str] | None = None,
219
+ cwd: str | None = None,
220
+ ):
221
+ script_path = Path(script_path).resolve()
222
+ if not script_path.is_file():
223
+ raise FileNotFoundError(f"Script not found: {script_path}")
224
+ if not str(script_path).endswith(".py"):
225
+ raise ValueError(f"Not a Python script: {script_path}")
226
+
227
+ super().__init__(
228
+ command="fastmcp", args=["run", str(script_path)], env=env, cwd=cwd
229
+ )
230
+ self.script_path = script_path
231
+
232
+
211
233
  class NodeStdioTransport(StdioTransport):
212
234
  """Transport for running Node.js scripts."""
213
235
 
fastmcp/exceptions.py CHANGED
@@ -17,5 +17,9 @@ class ToolError(FastMCPError):
17
17
  """Error in tool operations."""
18
18
 
19
19
 
20
+ class PromptError(FastMCPError):
21
+ """Error in prompt operations."""
22
+
23
+
20
24
  class InvalidSignature(Exception):
21
25
  """Invalid signature for use with FastMCP."""
@@ -1,4 +1,4 @@
1
- from .prompt import Prompt
1
+ from .prompt import Prompt, Message, UserMessage, AssistantMessage
2
2
  from .prompt_manager import PromptManager
3
3
 
4
- __all__ = ["Prompt", "PromptManager"]
4
+ __all__ = ["Prompt", "PromptManager", "Message", "UserMessage", "AssistantMessage"]
fastmcp/prompts/prompt.py CHANGED
@@ -27,27 +27,17 @@ class Message(BaseModel):
27
27
  super().__init__(content=content, **kwargs)
28
28
 
29
29
 
30
- class UserMessage(Message):
30
+ def UserMessage(content: str | CONTENT_TYPES, **kwargs: Any) -> Message:
31
31
  """A message from the user."""
32
+ return Message(content=content, role="user", **kwargs)
32
33
 
33
- role: Literal["user", "assistant"] = "user"
34
34
 
35
- def __init__(self, content: str | CONTENT_TYPES, **kwargs: Any):
36
- super().__init__(content=content, **kwargs)
37
-
38
-
39
- class AssistantMessage(Message):
35
+ def AssistantMessage(content: str | CONTENT_TYPES, **kwargs: Any) -> Message:
40
36
  """A message from the assistant."""
37
+ return Message(content=content, role="assistant", **kwargs)
41
38
 
42
- role: Literal["user", "assistant"] = "assistant"
43
-
44
- def __init__(self, content: str | CONTENT_TYPES, **kwargs: Any):
45
- super().__init__(content=content, **kwargs)
46
39
 
47
-
48
- message_validator = TypeAdapter[UserMessage | AssistantMessage](
49
- UserMessage | AssistantMessage
50
- )
40
+ message_validator = TypeAdapter[Message](Message)
51
41
 
52
42
  SyncPromptResult = (
53
43
  str | Message | dict[str, Any] | Sequence[str | Message | dict[str, Any]]
@@ -160,7 +150,7 @@ class Prompt(BaseModel):
160
150
  messages.append(message_validator.validate_python(msg))
161
151
  elif isinstance(msg, str):
162
152
  content = TextContent(type="text", text=msg)
163
- messages.append(UserMessage(content=content))
153
+ messages.append(Message(role="user", content=content))
164
154
  else:
165
155
  content = json.dumps(pydantic_core.to_jsonable_python(msg))
166
156
  messages.append(Message(role="user", content=content))
@@ -3,6 +3,7 @@
3
3
  from collections.abc import Awaitable, Callable
4
4
  from typing import Any
5
5
 
6
+ from fastmcp.exceptions import PromptError
6
7
  from fastmcp.prompts.prompt import Message, Prompt, PromptResult
7
8
  from fastmcp.settings import DuplicateBehavior
8
9
  from fastmcp.utilities.logging import get_logger
@@ -61,7 +62,7 @@ class PromptManager:
61
62
  """Render a prompt by name with arguments."""
62
63
  prompt = self.get_prompt(name)
63
64
  if not prompt:
64
- raise ValueError(f"Unknown prompt: {name}")
65
+ raise PromptError(f"Unknown prompt: {name}")
65
66
 
66
67
  return await prompt.render(arguments)
67
68
 
@@ -1,5 +1,4 @@
1
1
  from .resource import Resource
2
- from .resource_manager import ResourceManager
3
2
  from .template import ResourceTemplate
4
3
  from .types import (
5
4
  BinaryResource,
@@ -9,6 +8,7 @@ from .types import (
9
8
  HttpResource,
10
9
  TextResource,
11
10
  )
11
+ from .resource_manager import ResourceManager
12
12
 
13
13
  __all__ = [
14
14
  "Resource",
@@ -1,11 +1,14 @@
1
1
  """Resource manager functionality."""
2
2
 
3
+ import inspect
4
+ import re
3
5
  from collections.abc import Callable
4
6
  from typing import Any
5
7
 
6
8
  from pydantic import AnyUrl
7
9
 
8
- from fastmcp.resources.resource import Resource
10
+ from fastmcp.exceptions import ResourceError
11
+ from fastmcp.resources import FunctionResource, Resource
9
12
  from fastmcp.resources.template import ResourceTemplate
10
13
  from fastmcp.settings import DuplicateBehavior
11
14
  from fastmcp.utilities.logging import get_logger
@@ -21,16 +24,86 @@ class ResourceManager:
21
24
  self._templates: dict[str, ResourceTemplate] = {}
22
25
  self.duplicate_behavior = duplicate_behavior
23
26
 
24
- def add_resource(self, resource: Resource) -> Resource:
25
- """Add a resource to the manager.
27
+ def add_resource_or_template_from_fn(
28
+ self,
29
+ fn: Callable[..., Any],
30
+ uri: str,
31
+ name: str | None = None,
32
+ description: str | None = None,
33
+ mime_type: str | None = None,
34
+ tags: set[str] | None = None,
35
+ ) -> Resource | ResourceTemplate:
36
+ """Add a resource or template to the manager from a function.
26
37
 
27
38
  Args:
28
- resource: A Resource instance to add
39
+ fn: The function to register as a resource or template
40
+ uri: The URI for the resource or template
41
+ name: Optional name for the resource or template
42
+ description: Optional description of the resource or template
43
+ mime_type: Optional MIME type for the resource or template
44
+ tags: Optional set of tags for categorizing the resource or template
45
+
46
+ Returns:
47
+ The added resource or template. If a resource or template with the same URI already exists,
48
+ returns the existing resource or template.
49
+ """
50
+ # Check if this should be a template
51
+ has_uri_params = "{" in uri and "}" in uri
52
+ has_func_params = bool(inspect.signature(fn).parameters)
53
+
54
+ if has_uri_params and has_func_params:
55
+ return self.add_template_from_fn(
56
+ fn, uri, name, description, mime_type, tags
57
+ )
58
+ elif not has_uri_params and not has_func_params:
59
+ return self.add_resource_from_fn(
60
+ fn, uri, name, description, mime_type, tags
61
+ )
62
+ else:
63
+ raise ValueError(
64
+ "Invalid resource or template definition due to a "
65
+ "mismatch between URI parameters and function parameters."
66
+ )
67
+
68
+ def add_resource_from_fn(
69
+ self,
70
+ fn: Callable[..., Any],
71
+ uri: str,
72
+ name: str | None = None,
73
+ description: str | None = None,
74
+ mime_type: str | None = None,
75
+ tags: set[str] | None = None,
76
+ ) -> Resource:
77
+ """Add a resource to the manager from a function.
78
+
79
+ Args:
80
+ fn: The function to register as a resource
81
+ uri: The URI for the resource
82
+ name: Optional name for the resource
83
+ description: Optional description of the resource
84
+ mime_type: Optional MIME type for the resource
85
+ tags: Optional set of tags for categorizing the resource
29
86
 
30
87
  Returns:
31
88
  The added resource. If a resource with the same URI already exists,
32
89
  returns the existing resource.
33
90
  """
91
+ resource = FunctionResource(
92
+ uri=AnyUrl(uri),
93
+ name=name,
94
+ description=description,
95
+ mime_type=mime_type or "text/plain",
96
+ fn=fn,
97
+ tags=tags or set(),
98
+ )
99
+ return self.add_resource(resource)
100
+
101
+ def add_resource(self, resource: Resource) -> Resource:
102
+ """Add a resource to the manager.
103
+
104
+ Args:
105
+ resource: A Resource instance to add
106
+ """
34
107
  logger.debug(
35
108
  "Adding resource",
36
109
  extra={
@@ -63,6 +136,17 @@ class ResourceManager:
63
136
  tags: set[str] | None = None,
64
137
  ) -> ResourceTemplate:
65
138
  """Create a template from a function."""
139
+
140
+ # Validate that URI params match function params
141
+ uri_params = set(re.findall(r"{(\w+)}", uri_template))
142
+ func_params = set(inspect.signature(fn).parameters.keys())
143
+
144
+ if uri_params != func_params:
145
+ raise ValueError(
146
+ f"Mismatch between URI parameters {uri_params} "
147
+ f"and function parameters {func_params}"
148
+ )
149
+
66
150
  template = ResourceTemplate.from_function(
67
151
  fn,
68
152
  uri_template=uri_template,
@@ -122,7 +206,7 @@ class ResourceManager:
122
206
  except Exception as e:
123
207
  raise ValueError(f"Error creating resource from template: {e}")
124
208
 
125
- raise ValueError(f"Unknown resource: {uri}")
209
+ raise ResourceError(f"Unknown resource: {uri}")
126
210
 
127
211
  def list_resources(self) -> list[Resource]:
128
212
  """List all registered resources."""
fastmcp/server/context.py CHANGED
@@ -118,7 +118,7 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT]):
118
118
  assert self._fastmcp is not None, (
119
119
  "Context is not available outside of a request"
120
120
  )
121
- return await self._fastmcp.read_resource(uri)
121
+ return await self._fastmcp._mcp_read_resource(uri)
122
122
 
123
123
  async def log(
124
124
  self,
fastmcp/server/proxy.py CHANGED
@@ -1,11 +1,11 @@
1
1
  from typing import Any, cast
2
2
 
3
3
  import mcp.types
4
- from mcp.types import BlobResourceContents, PromptMessage, TextResourceContents
4
+ from mcp.types import BlobResourceContents, TextResourceContents
5
5
 
6
6
  import fastmcp
7
7
  from fastmcp.client import Client
8
- from fastmcp.prompts import Prompt
8
+ from fastmcp.prompts import Message, Prompt
9
9
  from fastmcp.resources import Resource, ResourceTemplate
10
10
  from fastmcp.server.context import Context
11
11
  from fastmcp.server.server import FastMCP
@@ -142,10 +142,10 @@ class ProxyPrompt(Prompt):
142
142
  fn=_proxy_passthrough,
143
143
  )
144
144
 
145
- async def render(self, arguments: dict[str, Any]) -> list[PromptMessage]:
145
+ async def render(self, arguments: dict[str, Any]) -> list[Message]:
146
146
  async with self._client:
147
147
  result = await self._client.get_prompt(self.name, arguments)
148
- return result.messages
148
+ return [Message(role=m.role, content=m.content) for m in result.messages]
149
149
 
150
150
 
151
151
  class FastMCPProxy(FastMCP):
fastmcp/server/server.py CHANGED
@@ -1,9 +1,7 @@
1
1
  """FastMCP - A more ergonomic interface for MCP servers."""
2
2
 
3
- import inspect
4
3
  import json
5
- import re
6
- from collections.abc import AsyncIterator, Callable
4
+ from collections.abc import AsyncIterator, Awaitable, Callable
7
5
  from contextlib import (
8
6
  AbstractAsyncContextManager,
9
7
  AsyncExitStack,
@@ -43,8 +41,12 @@ import fastmcp
43
41
  import fastmcp.settings
44
42
  from fastmcp.exceptions import ResourceError
45
43
  from fastmcp.prompts import Prompt, PromptManager
46
- from fastmcp.resources import FunctionResource, Resource, ResourceManager
44
+ from fastmcp.prompts.prompt import Message, PromptResult
45
+ from fastmcp.resources import Resource, ResourceManager
46
+ from fastmcp.resources.template import ResourceTemplate
47
47
  from fastmcp.tools import ToolManager
48
+ from fastmcp.tools.tool import Tool
49
+ from fastmcp.utilities.decorators import DecoratedFunction
48
50
  from fastmcp.utilities.logging import configure_logging, get_logger
49
51
  from fastmcp.utilities.types import Image
50
52
 
@@ -166,22 +168,32 @@ class FastMCP(Generic[LifespanResultT]):
166
168
  Args:
167
169
  transport: Transport protocol to use ("stdio" or "sse")
168
170
  """
171
+ logger.info(f'Starting server "{self.name}"...')
169
172
  anyio.run(self.run_async, transport)
170
173
 
171
174
  def _setup_handlers(self) -> None:
172
175
  """Set up core MCP protocol handlers."""
173
- self._mcp_server.list_tools()(self.list_tools)
176
+ self._mcp_server.list_tools()(self._mcp_list_tools)
174
177
  self._mcp_server.call_tool()(self.call_tool)
175
- self._mcp_server.list_resources()(self.list_resources)
176
- self._mcp_server.read_resource()(self.read_resource)
177
- self._mcp_server.list_prompts()(self.list_prompts)
178
- self._mcp_server.get_prompt()(self.get_prompt)
179
- self._mcp_server.list_resource_templates()(self.list_resource_templates)
178
+ self._mcp_server.list_resources()(self._mcp_list_resources)
179
+ self._mcp_server.read_resource()(self._mcp_read_resource)
180
+ self._mcp_server.list_prompts()(self._mcp_list_prompts)
181
+ self._mcp_server.get_prompt()(self._mcp_get_prompt)
182
+ self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates)
180
183
 
181
- async def list_tools(self) -> list[MCPTool]:
182
- """List all available tools."""
184
+ def list_tools(self) -> list[Tool]:
185
+ return self._tool_manager.list_tools()
186
+
187
+ async def _mcp_list_tools(self) -> list[MCPTool]:
188
+ """
189
+ List all available tools, in the format expected by the low-level MCP
190
+ server.
191
+
192
+ See `list_tools` for a more ergonomic way to list tools.
193
+ """
194
+
195
+ tools = self.list_tools()
183
196
 
184
- tools = self._tool_manager.list_tools()
185
197
  return [
186
198
  MCPTool(
187
199
  name=info.name,
@@ -214,10 +226,18 @@ class FastMCP(Generic[LifespanResultT]):
214
226
  converted_result = _convert_to_content(result)
215
227
  return converted_result
216
228
 
217
- async def list_resources(self) -> list[MCPResource]:
218
- """List all available resources."""
229
+ def list_resources(self) -> list[Resource]:
230
+ return self._resource_manager.list_resources()
231
+
232
+ async def _mcp_list_resources(self) -> list[MCPResource]:
233
+ """
234
+ List all available resources, in the format expected by the low-level MCP
235
+ server.
236
+
237
+ See `list_resources` for a more ergonomic way to list resources.
238
+ """
219
239
 
220
- resources = self._resource_manager.list_resources()
240
+ resources = self.list_resources()
221
241
  return [
222
242
  MCPResource(
223
243
  uri=resource.uri,
@@ -228,8 +248,18 @@ class FastMCP(Generic[LifespanResultT]):
228
248
  for resource in resources
229
249
  ]
230
250
 
231
- async def list_resource_templates(self) -> list[MCPResourceTemplate]:
232
- templates = self._resource_manager.list_templates()
251
+ def list_resource_templates(self) -> list[ResourceTemplate]:
252
+ return self._resource_manager.list_templates()
253
+
254
+ async def _mcp_list_resource_templates(self) -> list[MCPResourceTemplate]:
255
+ """
256
+ List all available resource templates, in the format expected by the low-level
257
+ MCP server.
258
+
259
+ See `list_resource_templates` for a more ergonomic way to list resource
260
+ templates.
261
+ """
262
+ templates = self.list_resource_templates()
233
263
  return [
234
264
  MCPResourceTemplate(
235
265
  uriTemplate=template.uri_template,
@@ -239,15 +269,27 @@ class FastMCP(Generic[LifespanResultT]):
239
269
  for template in templates
240
270
  ]
241
271
 
242
- async def read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
272
+ async def read_resource(self, uri: AnyUrl | str) -> str | bytes:
243
273
  """Read a resource by URI."""
274
+ resource = await self._resource_manager.get_resource(uri)
275
+ if not resource:
276
+ raise ResourceError(f"Unknown resource: {uri}")
277
+ return await resource.read()
278
+
279
+ async def _mcp_read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
280
+ """
281
+ Read a resource by URI, in the format expected by the low-level MCP
282
+ server.
283
+
284
+ See `read_resource` for a more ergonomic way to read resources.
285
+ """
244
286
 
245
287
  resource = await self._resource_manager.get_resource(uri)
246
288
  if not resource:
247
289
  raise ResourceError(f"Unknown resource: {uri}")
248
290
 
249
291
  try:
250
- content = await resource.read()
292
+ content = await self.read_resource(uri)
251
293
  return [ReadResourceContents(content=content, mime_type=resource.mime_type)]
252
294
  except Exception as e:
253
295
  logger.error(f"Error reading resource {uri}: {e}")
@@ -307,6 +349,7 @@ class FastMCP(Generic[LifespanResultT]):
307
349
  await context.report_progress(50, 100)
308
350
  return str(x)
309
351
  """
352
+
310
353
  # Check if user passed function directly instead of calling decorator
311
354
  if callable(name):
312
355
  raise TypeError(
@@ -316,7 +359,7 @@ class FastMCP(Generic[LifespanResultT]):
316
359
 
317
360
  def decorator(fn: AnyFunction) -> AnyFunction:
318
361
  self.add_tool(fn, name=name, description=description, tags=tags)
319
- return fn
362
+ return DecoratedFunction(fn)
320
363
 
321
364
  return decorator
322
365
 
@@ -326,8 +369,40 @@ class FastMCP(Generic[LifespanResultT]):
326
369
  Args:
327
370
  resource: A Resource instance to add
328
371
  """
372
+
329
373
  self._resource_manager.add_resource(resource)
330
374
 
375
+ def add_resource_fn(
376
+ self,
377
+ fn: AnyFunction,
378
+ uri: str,
379
+ name: str | None = None,
380
+ description: str | None = None,
381
+ mime_type: str | None = None,
382
+ tags: set[str] | None = None,
383
+ ) -> None:
384
+ """Add a resource or template to the server from a function.
385
+
386
+ If the URI contains parameters (e.g. "resource://{param}") or the function
387
+ has parameters, it will be registered as a template resource.
388
+
389
+ Args:
390
+ fn: The function to register as a resource
391
+ uri: The URI for the resource
392
+ name: Optional name for the resource
393
+ description: Optional description of the resource
394
+ mime_type: Optional MIME type for the resource
395
+ tags: Optional set of tags for categorizing the resource
396
+ """
397
+ self._resource_manager.add_resource_or_template_from_fn(
398
+ fn=fn,
399
+ uri=uri,
400
+ name=name,
401
+ description=description,
402
+ mime_type=mime_type,
403
+ tags=tags,
404
+ )
405
+
331
406
  def resource(
332
407
  self,
333
408
  uri: str,
@@ -382,52 +457,36 @@ class FastMCP(Generic[LifespanResultT]):
382
457
  )
383
458
 
384
459
  def decorator(fn: AnyFunction) -> AnyFunction:
385
- # Check if this should be a template
386
- has_uri_params = "{" in uri and "}" in uri
387
- has_func_params = bool(inspect.signature(fn).parameters)
388
-
389
- if has_uri_params or has_func_params:
390
- # Validate that URI params match function params
391
- uri_params = set(re.findall(r"{(\w+)}", uri))
392
- func_params = set(inspect.signature(fn).parameters.keys())
393
-
394
- if uri_params != func_params:
395
- raise ValueError(
396
- f"Mismatch between URI parameters {uri_params} "
397
- f"and function parameters {func_params}"
398
- )
399
-
400
- # Register as template
401
- self._resource_manager.add_template_from_fn(
402
- fn=fn,
403
- uri_template=uri,
404
- name=name,
405
- description=description,
406
- mime_type=mime_type or "text/plain",
407
- tags=tags,
408
- )
409
- else:
410
- # Register as regular resource
411
- resource = FunctionResource(
412
- uri=AnyUrl(uri),
413
- name=name,
414
- description=description,
415
- mime_type=mime_type or "text/plain",
416
- fn=fn,
417
- tags=tags or set(), # Default to empty set if None
418
- )
419
- self.add_resource(resource)
420
- return fn
460
+ self._resource_manager.add_resource_or_template_from_fn(
461
+ fn=fn,
462
+ uri=uri,
463
+ name=name,
464
+ description=description,
465
+ mime_type=mime_type,
466
+ tags=tags,
467
+ )
468
+ return DecoratedFunction(fn)
421
469
 
422
470
  return decorator
423
471
 
424
- def add_prompt(self, prompt: Prompt) -> None:
472
+ def add_prompt(
473
+ self,
474
+ fn: Callable[..., PromptResult | Awaitable[PromptResult]],
475
+ name: str | None = None,
476
+ description: str | None = None,
477
+ tags: set[str] | None = None,
478
+ ) -> None:
425
479
  """Add a prompt to the server.
426
480
 
427
481
  Args:
428
482
  prompt: A Prompt instance to add
429
483
  """
430
- self._prompt_manager.add_prompt(prompt)
484
+ self._prompt_manager.add_prompt_from_fn(
485
+ fn=fn,
486
+ name=name,
487
+ description=description,
488
+ tags=tags,
489
+ )
431
490
 
432
491
  def prompt(
433
492
  self,
@@ -477,11 +536,8 @@ class FastMCP(Generic[LifespanResultT]):
477
536
  )
478
537
 
479
538
  def decorator(func: AnyFunction) -> AnyFunction:
480
- prompt = Prompt.from_function(
481
- func, name=name, description=description, tags=tags
482
- )
483
- self.add_prompt(prompt)
484
- return func
539
+ self.add_prompt(func, name=name, description=description, tags=tags)
540
+ return DecoratedFunction(func)
485
541
 
486
542
  return decorator
487
543
 
@@ -494,15 +550,20 @@ class FastMCP(Generic[LifespanResultT]):
494
550
  self._mcp_server.create_initialization_options(),
495
551
  )
496
552
 
497
- async def run_sse_async(self) -> None:
553
+ async def run_sse_async(
554
+ self,
555
+ host: str | None = None,
556
+ port: int | None = None,
557
+ log_level: str | None = None,
558
+ ) -> None:
498
559
  """Run the server using SSE transport."""
499
560
  starlette_app = self.sse_app()
500
561
 
501
562
  config = uvicorn.Config(
502
563
  starlette_app,
503
- host=self.settings.host,
504
- port=self.settings.port,
505
- log_level=self.settings.log_level.lower(),
564
+ host=host or self.settings.host,
565
+ port=port or self.settings.port,
566
+ log_level=log_level or self.settings.log_level.lower(),
506
567
  )
507
568
  server = uvicorn.Server(config)
508
569
  await server.serve()
@@ -531,9 +592,20 @@ class FastMCP(Generic[LifespanResultT]):
531
592
  ],
532
593
  )
533
594
 
534
- async def list_prompts(self) -> list[MCPPrompt]:
535
- """List all available prompts."""
536
- prompts = self._prompt_manager.list_prompts()
595
+ def list_prompts(self) -> list[Prompt]:
596
+ """
597
+ List all available prompts.
598
+ """
599
+ return self._prompt_manager.list_prompts()
600
+
601
+ async def _mcp_list_prompts(self) -> list[MCPPrompt]:
602
+ """
603
+ List all available prompts, in the format expected by the low-level MCP
604
+ server.
605
+
606
+ See `list_prompts` for a more ergonomic way to list prompts.
607
+ """
608
+ prompts = self.list_prompts()
537
609
  return [
538
610
  MCPPrompt(
539
611
  name=prompt.name,
@@ -552,10 +624,21 @@ class FastMCP(Generic[LifespanResultT]):
552
624
 
553
625
  async def get_prompt(
554
626
  self, name: str, arguments: dict[str, Any] | None = None
555
- ) -> GetPromptResult:
627
+ ) -> list[Message]:
556
628
  """Get a prompt by name with arguments."""
629
+ return await self._prompt_manager.render_prompt(name, arguments)
630
+
631
+ async def _mcp_get_prompt(
632
+ self, name: str, arguments: dict[str, Any] | None = None
633
+ ) -> GetPromptResult:
634
+ """
635
+ Get a prompt by name with arguments, in the format expected by the low-level
636
+ MCP server.
637
+
638
+ See `get_prompt` for a more ergonomic way to get prompts.
639
+ """
557
640
  try:
558
- messages = await self._prompt_manager.render_prompt(name, arguments)
641
+ messages = await self.get_prompt(name, arguments)
559
642
 
560
643
  return GetPromptResult(messages=pydantic_core.to_jsonable_python(messages))
561
644
  except Exception as e:
fastmcp/tools/tool.py CHANGED
@@ -58,7 +58,10 @@ class Tool(BaseModel):
58
58
  is_async = inspect.iscoroutinefunction(fn)
59
59
 
60
60
  if context_kwarg is None:
61
- sig = inspect.signature(fn)
61
+ if isinstance(fn, classmethod):
62
+ sig = inspect.signature(fn.__func__)
63
+ else:
64
+ sig = inspect.signature(fn)
62
65
  for param_name, param in sig.parameters.items():
63
66
  if param.annotation is Context:
64
67
  context_kwarg = param_name