Jeremiah Lowin commited on
Commit
7343f79
·
unverified ·
2 Parent(s): fe6c0c1526ca49

Merge pull request #860 from rsp2k/rsp2k-patch-1

Browse files

contrib.mcp_mixin: add support for enabled, mcp_tool: annotations, excl. parm, serializer

src/fastmcp/contrib/mcp_mixin/README.md CHANGED
@@ -1,26 +1,103 @@
 
 
1
  # MCP Mixin
2
 
3
  This module provides the `MCPMixin` base class and associated decorators (`@mcp_tool`, `@mcp_resource`, `@mcp_prompt`).
4
 
5
  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
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  ## Usage
8
 
9
  Inherit from `MCPMixin` and use the decorators on the methods you want to register.
10
 
11
  ```python
 
12
  from fastmcp import FastMCP
13
- from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool, mcp_resource
14
 
15
  class MyComponent(MCPMixin):
16
  @mcp_tool(name="my_tool", description="Does something cool.")
17
  def tool_method(self):
18
  return "Tool executed!"
19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  @mcp_resource(uri="component://data")
21
  def resource_method(self):
22
  return {"data": "some data"}
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  mcp_server = FastMCP()
25
  component = MyComponent()
26
 
@@ -36,4 +113,4 @@ component.register_all(mcp_server, prefix="my_comp")
36
  # Or 'my_tool' and 'component://data' are registered (if no prefix used)
37
  ```
38
 
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.
 
1
+ from mcp.types import ToolAnnotations
2
+
3
  # MCP Mixin
4
 
5
  This module provides the `MCPMixin` base class and associated decorators (`@mcp_tool`, `@mcp_resource`, `@mcp_prompt`).
6
 
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.
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
+
21
  ## Usage
22
 
23
  Inherit from `MCPMixin` and use the decorators on the methods you want to register.
24
 
25
  ```python
26
+ from mcp.types import ToolAnnotations
27
  from fastmcp import FastMCP
28
+ from fastmcp.contrib.mcp_mixin import MCPMixin, mcp_tool, mcp_resource, mcp_prompt
29
 
30
  class MyComponent(MCPMixin):
31
  @mcp_tool(name="my_tool", description="Does something cool.")
32
  def tool_method(self):
33
  return "Tool executed!"
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
+
82
  @mcp_resource(uri="component://data")
83
  def resource_method(self):
84
  return {"data": "some data"}
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
+
101
  mcp_server = FastMCP()
102
  component = MyComponent()
103
 
 
113
  # Or 'my_tool' and 'component://data' are registered (if no prefix used)
114
  ```
115
 
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.
src/fastmcp/contrib/mcp_mixin/mcp_mixin.py CHANGED
@@ -3,6 +3,8 @@
3
  from collections.abc import Callable
4
  from typing import TYPE_CHECKING, Any
5
 
 
 
6
  from fastmcp.prompts.prompt import Prompt
7
  from fastmcp.resources.resource import Resource
8
  from fastmcp.tools.tool import Tool
@@ -23,6 +25,10 @@ def mcp_tool(
23
  name: str | None = None,
24
  description: str | None = None,
25
  tags: set[str] | None = None,
 
 
 
 
26
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
27
  """Decorator to mark a method as an MCP tool for later registration."""
28
 
@@ -31,6 +37,10 @@ def mcp_tool(
31
  "name": name or func.__name__,
32
  "description": description,
33
  "tags": tags,
 
 
 
 
34
  }
35
  call_args = {k: v for k, v in call_args.items() if v is not None}
36
  setattr(func, _MCP_REGISTRATION_TOOL_ATTR, call_args)
@@ -46,6 +56,7 @@ def mcp_resource(
46
  description: str | None = None,
47
  mime_type: str | None = None,
48
  tags: set[str] | None = None,
 
49
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
50
  """Decorator to mark a method as an MCP resource for later registration."""
51
 
@@ -56,6 +67,7 @@ def mcp_resource(
56
  "description": description,
57
  "mime_type": mime_type,
58
  "tags": tags,
 
59
  }
60
  call_args = {k: v for k, v in call_args.items() if v is not None}
61
 
@@ -70,6 +82,7 @@ def mcp_prompt(
70
  name: str | None = None,
71
  description: str | None = None,
72
  tags: set[str] | None = None,
 
73
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
74
  """Decorator to mark a method as an MCP prompt for later registration."""
75
 
@@ -78,6 +91,7 @@ def mcp_prompt(
78
  "name": name or func.__name__,
79
  "description": description,
80
  "tags": tags,
 
81
  }
82
 
83
  call_args = {k: v for k, v in call_args.items() if v is not None}
 
3
  from collections.abc import Callable
4
  from typing import TYPE_CHECKING, Any
5
 
6
+ from mcp.types import ToolAnnotations
7
+
8
  from fastmcp.prompts.prompt import Prompt
9
  from fastmcp.resources.resource import Resource
10
  from fastmcp.tools.tool import Tool
 
25
  name: str | None = None,
26
  description: str | None = None,
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,
32
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
33
  """Decorator to mark a method as an MCP tool for later registration."""
34
 
 
37
  "name": name or func.__name__,
38
  "description": description,
39
  "tags": tags,
40
+ "annotations": annotations,
41
+ "exclude_args": exclude_args,
42
+ "serializer": serializer,
43
+ "enabled": enabled,
44
  }
45
  call_args = {k: v for k, v in call_args.items() if v is not None}
46
  setattr(func, _MCP_REGISTRATION_TOOL_ATTR, call_args)
 
56
  description: str | None = None,
57
  mime_type: str | None = None,
58
  tags: set[str] | None = None,
59
+ enabled: bool | None = None,
60
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
61
  """Decorator to mark a method as an MCP resource for later registration."""
62
 
 
67
  "description": description,
68
  "mime_type": mime_type,
69
  "tags": tags,
70
+ "enabled": enabled,
71
  }
72
  call_args = {k: v for k, v in call_args.items() if v is not None}
73
 
 
82
  name: str | None = None,
83
  description: str | None = None,
84
  tags: set[str] | None = None,
85
+ enabled: bool | None = None,
86
  ) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
87
  """Decorator to mark a method as an MCP prompt for later registration."""
88
 
 
91
  "name": name or func.__name__,
92
  "description": description,
93
  "tags": tags,
94
+ "enabled": enabled,
95
  }
96
 
97
  call_args = {k: v for k, v in call_args.items() if v is not None}