fastmcp 2.8.1__py3-none-any.whl → 2.9.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.
Files changed (43) hide show
  1. fastmcp/cli/cli.py +99 -1
  2. fastmcp/cli/run.py +1 -3
  3. fastmcp/client/auth/oauth.py +1 -2
  4. fastmcp/client/client.py +23 -7
  5. fastmcp/client/logging.py +1 -2
  6. fastmcp/client/messages.py +126 -0
  7. fastmcp/client/transports.py +17 -2
  8. fastmcp/contrib/mcp_mixin/README.md +79 -2
  9. fastmcp/contrib/mcp_mixin/mcp_mixin.py +14 -0
  10. fastmcp/prompts/prompt.py +109 -13
  11. fastmcp/prompts/prompt_manager.py +119 -43
  12. fastmcp/resources/resource.py +27 -1
  13. fastmcp/resources/resource_manager.py +249 -76
  14. fastmcp/resources/template.py +44 -2
  15. fastmcp/server/auth/providers/bearer.py +62 -13
  16. fastmcp/server/context.py +113 -10
  17. fastmcp/server/http.py +8 -0
  18. fastmcp/server/low_level.py +35 -0
  19. fastmcp/server/middleware/__init__.py +6 -0
  20. fastmcp/server/middleware/error_handling.py +206 -0
  21. fastmcp/server/middleware/logging.py +165 -0
  22. fastmcp/server/middleware/middleware.py +236 -0
  23. fastmcp/server/middleware/rate_limiting.py +231 -0
  24. fastmcp/server/middleware/timing.py +156 -0
  25. fastmcp/server/proxy.py +250 -140
  26. fastmcp/server/server.py +446 -280
  27. fastmcp/settings.py +2 -2
  28. fastmcp/tools/tool.py +22 -2
  29. fastmcp/tools/tool_manager.py +114 -45
  30. fastmcp/tools/tool_transform.py +42 -16
  31. fastmcp/utilities/components.py +22 -2
  32. fastmcp/utilities/inspect.py +326 -0
  33. fastmcp/utilities/json_schema.py +67 -23
  34. fastmcp/utilities/mcp_config.py +13 -7
  35. fastmcp/utilities/openapi.py +75 -5
  36. fastmcp/utilities/tests.py +1 -1
  37. fastmcp/utilities/types.py +90 -1
  38. {fastmcp-2.8.1.dist-info → fastmcp-2.9.1.dist-info}/METADATA +2 -2
  39. fastmcp-2.9.1.dist-info/RECORD +78 -0
  40. fastmcp-2.8.1.dist-info/RECORD +0 -69
  41. {fastmcp-2.8.1.dist-info → fastmcp-2.9.1.dist-info}/WHEEL +0 -0
  42. {fastmcp-2.8.1.dist-info → fastmcp-2.9.1.dist-info}/entry_points.txt +0 -0
  43. {fastmcp-2.8.1.dist-info → fastmcp-2.9.1.dist-info}/licenses/LICENSE +0 -0
fastmcp/cli/cli.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """FastMCP CLI tools."""
2
2
 
3
+ import asyncio
3
4
  import importlib.metadata
4
5
  import importlib.util
5
6
  import os
@@ -11,6 +12,7 @@ from typing import Annotated
11
12
 
12
13
  import dotenv
13
14
  import typer
15
+ from pydantic import TypeAdapter
14
16
  from rich.console import Console
15
17
  from rich.table import Table
16
18
  from typer import Context, Exit
@@ -19,6 +21,7 @@ import fastmcp
19
21
  from fastmcp.cli import claude
20
22
  from fastmcp.cli import run as run_module
21
23
  from fastmcp.server.server import FastMCP
24
+ from fastmcp.utilities.inspect import FastMCPInfo, inspect_fastmcp
22
25
  from fastmcp.utilities.logging import get_logger
23
26
 
24
27
  logger = get_logger("cli")
@@ -232,7 +235,7 @@ def run(
232
235
  typer.Option(
233
236
  "--transport",
234
237
  "-t",
235
- help="Transport protocol to use (stdio, streamable-http, or sse)",
238
+ help="Transport protocol to use (stdio, http, or sse)",
236
239
  ),
237
240
  ] = None,
238
241
  host: Annotated[
@@ -435,3 +438,98 @@ def install(
435
438
  else:
436
439
  logger.error(f"Failed to install {name} in Claude app")
437
440
  sys.exit(1)
441
+
442
+
443
+ @app.command()
444
+ def inspect(
445
+ server_spec: str = typer.Argument(
446
+ ...,
447
+ help="Python file to inspect, optionally with :object suffix",
448
+ ),
449
+ output: Annotated[
450
+ Path,
451
+ typer.Option(
452
+ "--output",
453
+ "-o",
454
+ help="Output file path for the JSON report (default: server-info.json)",
455
+ ),
456
+ ] = Path("server-info.json"),
457
+ ) -> None:
458
+ """Inspect a FastMCP server and generate a JSON report.
459
+
460
+ This command analyzes a FastMCP server (v1.x or v2.x) and generates
461
+ a comprehensive JSON report containing information about the server's
462
+ name, instructions, version, tools, prompts, resources, templates,
463
+ and capabilities.
464
+
465
+ Examples:
466
+ fastmcp inspect server.py
467
+ fastmcp inspect server.py -o report.json
468
+ fastmcp inspect server.py:mcp -o analysis.json
469
+ fastmcp inspect path/to/server.py:app -o /tmp/server-info.json
470
+ """
471
+
472
+ # Parse the server specification
473
+ file, server_object = run_module.parse_file_path(server_spec)
474
+
475
+ logger.debug(
476
+ "Inspecting server",
477
+ extra={
478
+ "file": str(file),
479
+ "server_object": server_object,
480
+ "output": str(output),
481
+ },
482
+ )
483
+
484
+ try:
485
+ # Import the server
486
+ server = run_module.import_server(file, server_object)
487
+
488
+ # Get server information
489
+ async def get_info():
490
+ return await inspect_fastmcp(server)
491
+
492
+ try:
493
+ # Try to use existing event loop if available
494
+ asyncio.get_running_loop()
495
+ # If there's already a loop running, we need to run in a thread
496
+ import concurrent.futures
497
+
498
+ with concurrent.futures.ThreadPoolExecutor() as executor:
499
+ future = executor.submit(asyncio.run, get_info())
500
+ info = future.result()
501
+ except RuntimeError:
502
+ # No running loop, safe to use asyncio.run
503
+ info = asyncio.run(get_info())
504
+
505
+ info_json = TypeAdapter(FastMCPInfo).dump_json(info, indent=2)
506
+
507
+ # Ensure output directory exists
508
+ output.parent.mkdir(parents=True, exist_ok=True)
509
+
510
+ # Write JSON report (always pretty-printed)
511
+ with output.open("w", encoding="utf-8") as f:
512
+ f.write(info_json.decode("utf-8"))
513
+
514
+ logger.info(f"Server inspection complete. Report saved to {output}")
515
+
516
+ # Print summary to console
517
+ console.print(
518
+ f"[bold green]✓[/bold green] Inspected server: [bold]{info.name}[/bold]"
519
+ )
520
+ console.print(f" Tools: {len(info.tools)}")
521
+ console.print(f" Prompts: {len(info.prompts)}")
522
+ console.print(f" Resources: {len(info.resources)}")
523
+ console.print(f" Templates: {len(info.templates)}")
524
+ console.print(f" Report saved to: [cyan]{output}[/cyan]")
525
+
526
+ except Exception as e:
527
+ logger.error(
528
+ f"Failed to inspect server: {e}",
529
+ extra={
530
+ "server_spec": server_spec,
531
+ "error": str(e),
532
+ },
533
+ )
534
+ console.print(f"[bold red]✗[/bold red] Failed to inspect server: {e}")
535
+ sys.exit(1)
fastmcp/cli/run.py CHANGED
@@ -4,14 +4,12 @@ import importlib.util
4
4
  import re
5
5
  import sys
6
6
  from pathlib import Path
7
- from typing import Any, Literal
7
+ from typing import Any
8
8
 
9
9
  from fastmcp.utilities.logging import get_logger
10
10
 
11
11
  logger = get_logger("cli.run")
12
12
 
13
- TransportType = Literal["stdio", "streamable-http", "sse"]
14
-
15
13
 
16
14
  def is_url(path: str) -> bool:
17
15
  """Check if a string is a URL."""
@@ -306,8 +306,7 @@ def OAuth(
306
306
  httpx.AsyncClient (or appropriate FastMCP client/transport instance)
307
307
 
308
308
  Args:
309
- mcp_url: Full URL to the MCP endpoint (e.g.,
310
- "http://host/mcp/sse")
309
+ mcp_url: Full URL to the MCP endpoint (e.g. "http://host/mcp/sse/")
311
310
  scopes: OAuth scopes to request. Can be a
312
311
  space-separated string or a list of strings.
313
312
  client_name: Name for this client during registration
fastmcp/client/client.py CHANGED
@@ -7,6 +7,7 @@ from typing import Any, Generic, Literal, cast, overload
7
7
  import anyio
8
8
  import httpx
9
9
  import mcp.types
10
+ import pydantic_core
10
11
  from exceptiongroup import catch
11
12
  from mcp import ClientSession
12
13
  from pydantic import AnyUrl
@@ -14,10 +15,10 @@ from pydantic import AnyUrl
14
15
  import fastmcp
15
16
  from fastmcp.client.logging import (
16
17
  LogHandler,
17
- MessageHandler,
18
18
  create_log_callback,
19
19
  default_log_handler,
20
20
  )
21
+ from fastmcp.client.messages import MessageHandler, MessageHandlerT
21
22
  from fastmcp.client.progress import ProgressHandler, default_progress_handler
22
23
  from fastmcp.client.roots import (
23
24
  RootsHandler,
@@ -142,7 +143,7 @@ class Client(Generic[ClientTransportT]):
142
143
  roots: RootsList | RootsHandler | None = None,
143
144
  sampling_handler: SamplingHandler | None = None,
144
145
  log_handler: LogHandler | None = None,
145
- message_handler: MessageHandler | None = None,
146
+ message_handler: MessageHandlerT | MessageHandler | None = None,
146
147
  progress_handler: ProgressHandler | None = None,
147
148
  timeout: datetime.timedelta | float | int | None = None,
148
149
  init_timeout: datetime.timedelta | float | int | None = None,
@@ -508,13 +509,13 @@ class Client(Generic[ClientTransportT]):
508
509
 
509
510
  # --- Prompt ---
510
511
  async def get_prompt_mcp(
511
- self, name: str, arguments: dict[str, str] | None = None
512
+ self, name: str, arguments: dict[str, Any] | None = None
512
513
  ) -> mcp.types.GetPromptResult:
513
514
  """Send a prompts/get request and return the complete MCP protocol result.
514
515
 
515
516
  Args:
516
517
  name (str): The name of the prompt to retrieve.
517
- arguments (dict[str, str] | None, optional): Arguments to pass to the prompt. Defaults to None.
518
+ arguments (dict[str, Any] | None, optional): Arguments to pass to the prompt. Defaults to None.
518
519
 
519
520
  Returns:
520
521
  mcp.types.GetPromptResult: The complete response object from the protocol,
@@ -523,17 +524,32 @@ class Client(Generic[ClientTransportT]):
523
524
  Raises:
524
525
  RuntimeError: If called while the client is not connected.
525
526
  """
526
- result = await self.session.get_prompt(name=name, arguments=arguments)
527
+ # Serialize arguments for MCP protocol - convert non-string values to JSON
528
+ serialized_arguments: dict[str, str] | None = None
529
+ if arguments:
530
+ serialized_arguments = {}
531
+ for key, value in arguments.items():
532
+ if isinstance(value, str):
533
+ serialized_arguments[key] = value
534
+ else:
535
+ # Use pydantic_core.to_json for consistent serialization
536
+ serialized_arguments[key] = pydantic_core.to_json(value).decode(
537
+ "utf-8"
538
+ )
539
+
540
+ result = await self.session.get_prompt(
541
+ name=name, arguments=serialized_arguments
542
+ )
527
543
  return result
528
544
 
529
545
  async def get_prompt(
530
- self, name: str, arguments: dict[str, str] | None = None
546
+ self, name: str, arguments: dict[str, Any] | None = None
531
547
  ) -> mcp.types.GetPromptResult:
532
548
  """Retrieve a rendered prompt message list from the server.
533
549
 
534
550
  Args:
535
551
  name (str): The name of the prompt to retrieve.
536
- arguments (dict[str, str] | None, optional): Arguments to pass to the prompt. Defaults to None.
552
+ arguments (dict[str, Any] | None, optional): Arguments to pass to the prompt. Defaults to None.
537
553
 
538
554
  Returns:
539
555
  mcp.types.GetPromptResult: The complete response object from the protocol,
fastmcp/client/logging.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from collections.abc import Awaitable, Callable
2
2
  from typing import TypeAlias
3
3
 
4
- from mcp.client.session import LoggingFnT, MessageHandlerFnT
4
+ from mcp.client.session import LoggingFnT
5
5
  from mcp.types import LoggingMessageNotificationParams
6
6
 
7
7
  from fastmcp.utilities.logging import get_logger
@@ -10,7 +10,6 @@ logger = get_logger(__name__)
10
10
 
11
11
  LogMessage: TypeAlias = LoggingMessageNotificationParams
12
12
  LogHandler: TypeAlias = Callable[[LogMessage], Awaitable[None]]
13
- MessageHandler: TypeAlias = MessageHandlerFnT
14
13
 
15
14
 
16
15
  async def default_log_handler(message: LogMessage) -> None:
@@ -0,0 +1,126 @@
1
+ from typing import TypeAlias
2
+
3
+ import mcp.types
4
+ from mcp.client.session import MessageHandlerFnT
5
+ from mcp.shared.session import RequestResponder
6
+
7
+ Message: TypeAlias = (
8
+ RequestResponder[mcp.types.ServerRequest, mcp.types.ClientResult]
9
+ | mcp.types.ServerNotification
10
+ | Exception
11
+ )
12
+
13
+ MessageHandlerT: TypeAlias = MessageHandlerFnT
14
+
15
+
16
+ class MessageHandler:
17
+ """
18
+ This class is used to handle MCP messages sent to the client. It is used to handle all messages,
19
+ requests, notifications, and exceptions. Users can override any of the hooks
20
+ """
21
+
22
+ async def __call__(
23
+ self,
24
+ message: RequestResponder[mcp.types.ServerRequest, mcp.types.ClientResult]
25
+ | mcp.types.ServerNotification
26
+ | Exception,
27
+ ) -> None:
28
+ return await self.dispatch(message)
29
+
30
+ async def dispatch(self, message: Message) -> None:
31
+ # handle all messages
32
+ await self.on_message(message)
33
+
34
+ match message:
35
+ # requests
36
+ case RequestResponder():
37
+ # handle all requests
38
+ await self.on_request(message)
39
+
40
+ # handle specific requests
41
+ match message.request.root:
42
+ case mcp.types.PingRequest():
43
+ await self.on_ping(message.request.root)
44
+ case mcp.types.ListRootsRequest():
45
+ await self.on_list_roots(message.request.root)
46
+ case mcp.types.CreateMessageRequest():
47
+ await self.on_create_message(message.request.root)
48
+
49
+ # notifications
50
+ case mcp.types.ServerNotification():
51
+ # handle all notifications
52
+ await self.on_notification(message)
53
+
54
+ # handle specific notifications
55
+ match message.root:
56
+ case mcp.types.CancelledNotification():
57
+ await self.on_cancelled(message.root)
58
+ case mcp.types.ProgressNotification():
59
+ await self.on_progress(message.root)
60
+ case mcp.types.LoggingMessageNotification():
61
+ await self.on_logging_message(message.root)
62
+ case mcp.types.ToolListChangedNotification():
63
+ await self.on_tool_list_changed(message.root)
64
+ case mcp.types.ResourceListChangedNotification():
65
+ await self.on_resource_list_changed(message.root)
66
+ case mcp.types.PromptListChangedNotification():
67
+ await self.on_prompt_list_changed(message.root)
68
+ case mcp.types.ResourceUpdatedNotification():
69
+ await self.on_resource_updated(message.root)
70
+
71
+ case Exception():
72
+ await self.on_exception(message)
73
+
74
+ async def on_message(self, message: Message) -> None:
75
+ pass
76
+
77
+ async def on_request(
78
+ self, message: RequestResponder[mcp.types.ServerRequest, mcp.types.ClientResult]
79
+ ) -> None:
80
+ pass
81
+
82
+ async def on_ping(self, message: mcp.types.PingRequest) -> None:
83
+ pass
84
+
85
+ async def on_list_roots(self, message: mcp.types.ListRootsRequest) -> None:
86
+ pass
87
+
88
+ async def on_create_message(self, message: mcp.types.CreateMessageRequest) -> None:
89
+ pass
90
+
91
+ async def on_notification(self, message: mcp.types.ServerNotification) -> None:
92
+ pass
93
+
94
+ async def on_exception(self, message: Exception) -> None:
95
+ pass
96
+
97
+ async def on_progress(self, message: mcp.types.ProgressNotification) -> None:
98
+ pass
99
+
100
+ async def on_logging_message(
101
+ self, message: mcp.types.LoggingMessageNotification
102
+ ) -> None:
103
+ pass
104
+
105
+ async def on_tool_list_changed(
106
+ self, message: mcp.types.ToolListChangedNotification
107
+ ) -> None:
108
+ pass
109
+
110
+ async def on_resource_list_changed(
111
+ self, message: mcp.types.ResourceListChangedNotification
112
+ ) -> None:
113
+ pass
114
+
115
+ async def on_prompt_list_changed(
116
+ self, message: mcp.types.PromptListChangedNotification
117
+ ) -> None:
118
+ pass
119
+
120
+ async def on_resource_updated(
121
+ self, message: mcp.types.ResourceUpdatedNotification
122
+ ) -> None:
123
+ pass
124
+
125
+ async def on_cancelled(self, message: mcp.types.CancelledNotification) -> None:
126
+ pass
@@ -9,6 +9,7 @@ import warnings
9
9
  from collections.abc import AsyncIterator, Callable
10
10
  from pathlib import Path
11
11
  from typing import Any, Literal, TypedDict, TypeVar, cast, overload
12
+ from urllib.parse import urlparse, urlunparse
12
13
 
13
14
  import anyio
14
15
  import httpx
@@ -159,6 +160,13 @@ class SSETransport(ClientTransport):
159
160
  url = str(url)
160
161
  if not isinstance(url, str) or not url.startswith("http"):
161
162
  raise ValueError("Invalid HTTP/S URL provided for SSE.")
163
+
164
+ # Ensure the URL path ends with a trailing slash to avoid automatic redirects
165
+ parsed = urlparse(url)
166
+ if not parsed.path.endswith("/"):
167
+ parsed = parsed._replace(path=parsed.path + "/")
168
+ url = urlunparse(parsed)
169
+
162
170
  self.url = url
163
171
  self.headers = headers or {}
164
172
  self._set_auth(auth)
@@ -227,6 +235,13 @@ class StreamableHttpTransport(ClientTransport):
227
235
  url = str(url)
228
236
  if not isinstance(url, str) or not url.startswith("http"):
229
237
  raise ValueError("Invalid HTTP/S URL provided for Streamable HTTP.")
238
+
239
+ # Ensure the URL path ends with a trailing slash to avoid automatic redirects
240
+ parsed = urlparse(url)
241
+ if not parsed.path.endswith("/"):
242
+ parsed = parsed._replace(path=parsed.path + "/")
243
+ url = urlunparse(parsed)
244
+
230
245
  self.url = url
231
246
  self.headers = headers or {}
232
247
  self._set_auth(auth)
@@ -721,11 +736,11 @@ class MCPConfigTransport(ClientTransport):
721
736
  "mcpServers": {
722
737
  "weather": {
723
738
  "url": "https://weather-api.example.com/mcp",
724
- "transport": "streamable-http"
739
+ "transport": "http"
725
740
  },
726
741
  "calendar": {
727
742
  "url": "https://calendar-api.example.com/mcp",
728
- "transport": "streamable-http"
743
+ "transport": "http"
729
744
  }
730
745
  }
731
746
  }
@@ -1,26 +1,103 @@
1
+ from mcp.types import ToolAnnotations
2
+
1
3
  # MCP Mixin
2
4
 
3
5
  This module provides the `MCPMixin` base class and associated decorators (`@mcp_tool`, `@mcp_resource`, `@mcp_prompt`).
4
6
 
5
7
  It allows developers to easily define classes whose methods can be registered as tools, resources, or prompts with a `FastMCP` server instance using the `register_all()`, `register_tools()`, `register_resources()`, or `register_prompts()` methods provided by the mixin.
6
8
 
9
+ Includes support for
10
+ Tools:
11
+ * [enable/disable](https://gofastmcp.com/servers/tools#disabling-tools)
12
+ * [annotations](https://gofastmcp.com/servers/tools#annotations-2)
13
+ * [excluded arguments](https://gofastmcp.com/servers/tools#excluding-arguments)
14
+
15
+ Prompts:
16
+ * [enable/disable](https://gofastmcp.com/servers/prompts#disabling-prompts)
17
+
18
+ Resources:
19
+ * [enable/disabe](https://gofastmcp.com/servers/resources#disabling-resources)
20
+
7
21
  ## Usage
8
22
 
9
23
  Inherit from `MCPMixin` and use the decorators on the methods you want to register.
10
24
 
11
25
  ```python
26
+ from mcp.types import ToolAnnotations
12
27
  from fastmcp import FastMCP
13
- from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool, mcp_resource
28
+ from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool, mcp_resource, mcp_prompt
14
29
 
15
30
  class MyComponent(MCPMixin):
16
31
  @mcp_tool(name="my_tool", description="Does something cool.")
17
32
  def tool_method(self):
18
33
  return "Tool executed!"
19
34
 
35
+ # example of disabled tool
36
+ @mcp_tool(name="my_tool", description="Does something cool.", enabled=False)
37
+ def disabled_tool_method(self):
38
+ # This function can't be called by client because it's disabled
39
+ return "You'll never get here!"
40
+
41
+ # example of excluded parameter tool
42
+ @mcp_tool(
43
+ name="my_tool", description="Does something cool.",
44
+ enabled=False, exclude_args=['delete_everything'],
45
+ )
46
+ def excluded_param_tool_method(self, delete_everything=False):
47
+ # MCP tool calls can't pass the "delete_everything" argument
48
+ if delete_everything:
49
+ return "Nothing to delete, I bet you're not a tool :)"
50
+ return "You might be a tool if..."
51
+
52
+ # example tool w/annotations
53
+ @mcp_tool(
54
+ name="my_tool", description="Does something cool.",
55
+ annotations=ToolAnnotations(
56
+ title="Attn LLM, use this tool first!",
57
+ readOnlyHint=False,
58
+ destructiveHint=False,
59
+ idempotentHint=False,
60
+ )
61
+ )
62
+ def tool_method(self):
63
+ return "Tool executed!"
64
+
65
+ # example tool w/everything
66
+ @mcp_tool(
67
+ name="my_tool", description="Does something cool.",
68
+ enabled=True,
69
+ exclude_args=['delete_all'],
70
+ annotations=ToolAnnotations(
71
+ title="Attn LLM, use this tool first!",
72
+ readOnlyHint=False,
73
+ destructiveHint=False,
74
+ idempotentHint=False,
75
+ )
76
+ )
77
+ def tool_method(self, delete_all=False):
78
+ if delete_all:
79
+ return "99 records deleted. I bet you're not a tool :)"
80
+ return "Tool executed, but you might be a tool!"
81
+
20
82
  @mcp_resource(uri="component://data")
21
83
  def resource_method(self):
22
84
  return {"data": "some data"}
23
85
 
86
+ # Disabled resource
87
+ @mcp_resource(uri="component://data", enabled=False)
88
+ def resource_method(self):
89
+ return {"data": "some data"}
90
+
91
+ # prompt
92
+ @mcp_prompt(name="A prompt")
93
+ def prompt_method(self, name):
94
+ return f"Whats up {name}?"
95
+
96
+ # disabled prompt
97
+ @mcp_prompt(name="A prompt", enabled=False)
98
+ def prompt_method(self, name):
99
+ return f"Whats up {name}?"
100
+
24
101
  mcp_server = FastMCP()
25
102
  component = MyComponent()
26
103
 
@@ -36,4 +113,4 @@ component.register_all(mcp_server, prefix="my_comp")
36
113
  # Or 'my_tool' and 'component://data' are registered (if no prefix used)
37
114
  ```
38
115
 
39
- The `prefix` argument in registration methods is optional. If omitted, methods are registered with their original decorated names/URIs. Individual separators (`tools_separator`, `resources_separator`, `prompts_separator`) can also be provided to `register_all` to change the separator for specific types.
116
+ The `prefix` argument in registration methods is optional. If omitted, methods are registered with their original decorated names/URIs. Individual separators (`tools_separator`, `resources_separator`, `prompts_separator`) can also be provided to `register_all` to change the separator for specific types.
@@ -3,6 +3,8 @@
3
3
  from collections.abc import Callable
4
4
  from typing import TYPE_CHECKING, Any
5
5
 
6
+ from mcp.types import ToolAnnotations
7
+
6
8
  from fastmcp.prompts.prompt import Prompt
7
9
  from fastmcp.resources.resource import Resource
8
10
  from fastmcp.tools.tool import Tool
@@ -23,6 +25,10 @@ def mcp_tool(
23
25
  name: str | None = None,
24
26
  description: str | None = None,
25
27
  tags: set[str] | None = None,
28
+ annotations: ToolAnnotations | dict[str, Any] | None = None,
29
+ exclude_args: list[str] | None = None,
30
+ serializer: Callable[[Any], str] | None = None,
31
+ enabled: bool | None = None,
26
32
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
27
33
  """Decorator to mark a method as an MCP tool for later registration."""
28
34
 
@@ -31,6 +37,10 @@ def mcp_tool(
31
37
  "name": name or func.__name__,
32
38
  "description": description,
33
39
  "tags": tags,
40
+ "annotations": annotations,
41
+ "exclude_args": exclude_args,
42
+ "serializer": serializer,
43
+ "enabled": enabled,
34
44
  }
35
45
  call_args = {k: v for k, v in call_args.items() if v is not None}
36
46
  setattr(func, _MCP_REGISTRATION_TOOL_ATTR, call_args)
@@ -46,6 +56,7 @@ def mcp_resource(
46
56
  description: str | None = None,
47
57
  mime_type: str | None = None,
48
58
  tags: set[str] | None = None,
59
+ enabled: bool | None = None,
49
60
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
50
61
  """Decorator to mark a method as an MCP resource for later registration."""
51
62
 
@@ -56,6 +67,7 @@ def mcp_resource(
56
67
  "description": description,
57
68
  "mime_type": mime_type,
58
69
  "tags": tags,
70
+ "enabled": enabled,
59
71
  }
60
72
  call_args = {k: v for k, v in call_args.items() if v is not None}
61
73
 
@@ -70,6 +82,7 @@ def mcp_prompt(
70
82
  name: str | None = None,
71
83
  description: str | None = None,
72
84
  tags: set[str] | None = None,
85
+ enabled: bool | None = None,
73
86
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
74
87
  """Decorator to mark a method as an MCP prompt for later registration."""
75
88
 
@@ -78,6 +91,7 @@ def mcp_prompt(
78
91
  "name": name or func.__name__,
79
92
  "description": description,
80
93
  "tags": tags,
94
+ "enabled": enabled,
81
95
  }
82
96
 
83
97
  call_args = {k: v for k, v in call_args.items() if v is not None}