strawgate commited on
Commit
ce45db3
·
1 Parent(s): 40c10e7

Updates based on PR Feedback

Browse files
src/contrib/README.md ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # FastMCP Contrib Modules
2
+
3
+ This directory holds community-contributed modules for FastMCP. These modules extend FastMCP's functionality but are not officially maintained by the core team.
4
+
5
+ **Guarantees:**
6
+ * Modules in `contrib` may have different testing requirements or stability guarantees compared to the core library.
7
+ * Changes to the core FastMCP library might break modules in `contrib` without explicit warnings in the main changelog.
8
+
9
+ Use these modules at your own discretion. Contributions are welcome, but please include tests and documentation.
src/contrib/mcp_mixin/README.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 contrib.mcp_mixin.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
+
27
+ # Register all decorated methods with a prefix
28
+ # Useful if you will have multiple instantiated objects of the same class
29
+ # and want to avoid name collisions.
30
+ component.register_all(mcp_server, prefix="my_comp")
31
+
32
+ # Register without a prefix
33
+ # component.register_all(mcp_server)
34
+
35
+ # Now 'my_comp_my_tool' tool and 'my_comp+component://data' resource are registered (if prefix used)
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.
sample.py → src/contrib/mcp_mixin/example.py RENAMED
@@ -1,17 +1,17 @@
1
- """Sample code for FastMCP."""
2
 
3
- from src.fastmcp import FastMCP
4
- from src.fastmcp.utilities.registerable import (
5
- McpRegisterable,
6
  mcp_prompt,
7
  mcp_resource,
8
  mcp_tool,
9
  )
 
10
 
11
  mcp = FastMCP()
12
 
13
 
14
- class Sample(McpRegisterable):
15
  def __init__(self, name):
16
  self.name = name
17
 
@@ -39,7 +39,11 @@ second_sample.register_all(mcp_server=mcp, prefix="second")
39
 
40
 
41
  def main():
42
- mcp.run("sse")
 
 
 
 
43
 
44
 
45
  if __name__ == "__main__":
 
1
+ """Sample code for FastMCP using MCPMixin."""
2
 
3
+ from contrib.mcp_mixin.mcp_mixin import (
4
+ MCPMixin,
 
5
  mcp_prompt,
6
  mcp_resource,
7
  mcp_tool,
8
  )
9
+ from fastmcp import FastMCP
10
 
11
  mcp = FastMCP()
12
 
13
 
14
+ class Sample(MCPMixin):
15
  def __init__(self, name):
16
  self.name = name
17
 
 
39
 
40
 
41
  def main():
42
+ print("MCP Server running with registered components...")
43
+ print("Tools:", list(mcp.get_tools().keys()))
44
+ print("Resources:", list(mcp.get_resources().keys()))
45
+ print("Prompts:", [p.name for p in mcp.list_prompts()])
46
+ mcp.run()
47
 
48
 
49
  if __name__ == "__main__":
src/{fastmcp/utilities/registerable.py → contrib/mcp_mixin/mcp_mixin.py} RENAMED
@@ -1,15 +1,19 @@
1
- """Provides a base class and decorators for easy registration of class methods with FastMCP."""
2
 
3
  from collections.abc import Callable
4
  from typing import TYPE_CHECKING, Any
5
 
6
  if TYPE_CHECKING:
7
- from ..server import FastMCP
8
 
9
  _MCP_REGISTRATION_TOOL_ATTR = "_mcp_tool_registration"
10
  _MCP_REGISTRATION_RESOURCE_ATTR = "_mcp_resource_registration"
11
  _MCP_REGISTRATION_PROMPT_ATTR = "_mcp_prompt_registration"
12
 
 
 
 
 
13
 
14
  def mcp_tool(
15
  name: str | None = None,
@@ -80,81 +84,125 @@ def mcp_prompt(
80
  return decorator
81
 
82
 
83
- class McpRegisterable:
84
- """Base class for objects that can register tools, resources, and prompts
85
  with a FastMCP server instance using decorators.
 
 
 
 
 
86
  """
87
 
88
  def _get_methods_to_register(self, registration_type: str):
89
- """Retrieves all registration info for the specified type."""
90
-
91
  return [
92
  (
93
  getattr(self, method_name),
94
  getattr(getattr(self, method_name), registration_type).copy(),
95
  )
96
  for method_name in dir(self)
97
- if hasattr(getattr(self, method_name), registration_type)
 
98
  ]
99
 
100
- def register_tools(self, mcp_server: "FastMCP", prefix: str | None = None) -> None:
 
 
 
 
 
101
  """Registers all methods marked with @mcp_tool with the FastMCP server.
102
 
103
  Args:
104
  mcp_server: The FastMCP server instance to register tools with.
 
 
 
 
105
  """
106
-
107
  for method, registration_info in self._get_methods_to_register(
108
  _MCP_REGISTRATION_TOOL_ATTR
109
  ):
110
  if prefix:
111
- registration_info["name"] = f"{prefix}_{registration_info['name']}"
112
-
 
113
  mcp_server.add_tool(fn=method, **registration_info)
114
 
115
  def register_resources(
116
- self, mcp_server: "FastMCP", prefix: str | None = None
 
 
 
117
  ) -> None:
118
  """Registers all methods marked with @mcp_resource with the FastMCP server.
119
 
120
  Args:
121
  mcp_server: The FastMCP server instance to register resources with.
 
 
 
 
 
122
  """
123
-
124
  for method, registration_info in self._get_methods_to_register(
125
  _MCP_REGISTRATION_RESOURCE_ATTR
126
  ):
127
  if prefix:
128
- registration_info["name"] = f"{prefix}_{registration_info['name']}"
129
- registration_info["uri"] = f"{prefix}+{registration_info['uri']}"
130
-
 
 
 
131
  mcp_server.add_resource_fn(fn=method, **registration_info)
132
 
133
  def register_prompts(
134
- self, mcp_server: "FastMCP", prefix: str | None = None
 
 
 
135
  ) -> None:
136
  """Registers all methods marked with @mcp_prompt with the FastMCP server.
137
 
138
  Args:
139
  mcp_server: The FastMCP server instance to register prompts with.
 
 
 
 
140
  """
141
  for method, registration_info in self._get_methods_to_register(
142
  _MCP_REGISTRATION_PROMPT_ATTR
143
  ):
144
  if prefix:
145
- registration_info["name"] = f"{prefix}_{registration_info['name']}"
146
-
 
147
  mcp_server.add_prompt(fn=method, **registration_info)
148
 
149
  def register_all(
150
  self,
151
  mcp_server: "FastMCP",
152
  prefix: str | None = None,
153
- tools_prefix: str | None = None,
154
- resources_prefix: str | None = None,
155
- prompts_prefix: str | None = None,
156
  ) -> None:
157
- """Registers all marked tools, resources, and prompts."""
158
- self.register_tools(mcp_server, prefix=tools_prefix or prefix)
159
- self.register_resources(mcp_server, prefix=resources_prefix or prefix)
160
- self.register_prompts(mcp_server, prefix=prompts_prefix or prefix)
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Provides a base mixin class and decorators for easy registration of class methods with FastMCP."""
2
 
3
  from collections.abc import Callable
4
  from typing import TYPE_CHECKING, Any
5
 
6
  if TYPE_CHECKING:
7
+ from fastmcp.server import FastMCP
8
 
9
  _MCP_REGISTRATION_TOOL_ATTR = "_mcp_tool_registration"
10
  _MCP_REGISTRATION_RESOURCE_ATTR = "_mcp_resource_registration"
11
  _MCP_REGISTRATION_PROMPT_ATTR = "_mcp_prompt_registration"
12
 
13
+ _DEFAULT_SEPARATOR_TOOL = "_"
14
+ _DEFAULT_SEPARATOR_RESOURCE = "+"
15
+ _DEFAULT_SEPARATOR_PROMPT = "_"
16
+
17
 
18
  def mcp_tool(
19
  name: str | None = None,
 
84
  return decorator
85
 
86
 
87
+ class MCPMixin:
88
+ """Base mixin class for objects that can register tools, resources, and prompts
89
  with a FastMCP server instance using decorators.
90
+
91
+ This mixin provides methods like `register_all`, `register_tools`, etc.,
92
+ which iterate over the methods of the inheriting class, find methods
93
+ decorated with `@mcp_tool`, `@mcp_resource`, or `@mcp_prompt`, and
94
+ register them with the provided FastMCP server instance.
95
  """
96
 
97
  def _get_methods_to_register(self, registration_type: str):
98
+ """Retrieves all methods marked for a specific registration type."""
 
99
  return [
100
  (
101
  getattr(self, method_name),
102
  getattr(getattr(self, method_name), registration_type).copy(),
103
  )
104
  for method_name in dir(self)
105
+ if callable(getattr(self, method_name))
106
+ and hasattr(getattr(self, method_name), registration_type)
107
  ]
108
 
109
+ def register_tools(
110
+ self,
111
+ mcp_server: "FastMCP",
112
+ prefix: str | None = None,
113
+ separator: str = _DEFAULT_SEPARATOR_TOOL,
114
+ ) -> None:
115
  """Registers all methods marked with @mcp_tool with the FastMCP server.
116
 
117
  Args:
118
  mcp_server: The FastMCP server instance to register tools with.
119
+ prefix: Optional prefix to prepend to tool names. If provided, the
120
+ final name will be f"{prefix}{separator}{original_name}".
121
+ separator: The separator string used between prefix and original name.
122
+ Defaults to '_'.
123
  """
 
124
  for method, registration_info in self._get_methods_to_register(
125
  _MCP_REGISTRATION_TOOL_ATTR
126
  ):
127
  if prefix:
128
+ registration_info["name"] = (
129
+ f"{prefix}{separator}{registration_info['name']}"
130
+ )
131
  mcp_server.add_tool(fn=method, **registration_info)
132
 
133
  def register_resources(
134
+ self,
135
+ mcp_server: "FastMCP",
136
+ prefix: str | None = None,
137
+ separator: str = _DEFAULT_SEPARATOR_RESOURCE,
138
  ) -> None:
139
  """Registers all methods marked with @mcp_resource with the FastMCP server.
140
 
141
  Args:
142
  mcp_server: The FastMCP server instance to register resources with.
143
+ prefix: Optional prefix to prepend to resource names and URIs. If provided,
144
+ the final name will be f"{prefix}{separator}{original_name}" and the
145
+ final URI will be f"{prefix}{separator}{original_uri}".
146
+ separator: The separator string used between prefix and original name/URI.
147
+ Defaults to '+'.
148
  """
 
149
  for method, registration_info in self._get_methods_to_register(
150
  _MCP_REGISTRATION_RESOURCE_ATTR
151
  ):
152
  if prefix:
153
+ registration_info["name"] = (
154
+ f"{prefix}{separator}{registration_info['name']}"
155
+ )
156
+ registration_info["uri"] = (
157
+ f"{prefix}{separator}{registration_info['uri']}"
158
+ )
159
  mcp_server.add_resource_fn(fn=method, **registration_info)
160
 
161
  def register_prompts(
162
+ self,
163
+ mcp_server: "FastMCP",
164
+ prefix: str | None = None,
165
+ separator: str = _DEFAULT_SEPARATOR_PROMPT,
166
  ) -> None:
167
  """Registers all methods marked with @mcp_prompt with the FastMCP server.
168
 
169
  Args:
170
  mcp_server: The FastMCP server instance to register prompts with.
171
+ prefix: Optional prefix to prepend to prompt names. If provided, the
172
+ final name will be f"{prefix}{separator}{original_name}".
173
+ separator: The separator string used between prefix and original name.
174
+ Defaults to '_'.
175
  """
176
  for method, registration_info in self._get_methods_to_register(
177
  _MCP_REGISTRATION_PROMPT_ATTR
178
  ):
179
  if prefix:
180
+ registration_info["name"] = (
181
+ f"{prefix}{separator}{registration_info['name']}"
182
+ )
183
  mcp_server.add_prompt(fn=method, **registration_info)
184
 
185
  def register_all(
186
  self,
187
  mcp_server: "FastMCP",
188
  prefix: str | None = None,
189
+ tool_separator: str = _DEFAULT_SEPARATOR_TOOL,
190
+ resource_separator: str = _DEFAULT_SEPARATOR_RESOURCE,
191
+ prompt_separator: str = _DEFAULT_SEPARATOR_PROMPT,
192
  ) -> None:
193
+ """Registers all marked tools, resources, and prompts with the server.
194
+
195
+ This method calls `register_tools`, `register_resources`, and `register_prompts`
196
+ internally, passing the provided prefix and separators.
197
+
198
+ Args:
199
+ mcp_server: The FastMCP server instance to register with.
200
+ prefix: Optional prefix applied to all registered items unless overridden
201
+ by a specific separator argument.
202
+ tool_separator: Separator for tool names (defaults to '_').
203
+ resource_separator: Separator for resource names/URIs (defaults to '+').
204
+ prompt_separator: Separator for prompt names (defaults to '_').
205
+ """
206
+ self.register_tools(mcp_server, prefix=prefix, separator=tool_separator)
207
+ self.register_resources(mcp_server, prefix=prefix, separator=resource_separator)
208
+ self.register_prompts(mcp_server, prefix=prefix, separator=prompt_separator)
tests/contrib/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # This file makes Python treat the directory as a package.
tests/contrib/test_mcp_mixin.py ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Tests for the MCPMixin class."""
2
+
3
+ import pytest
4
+
5
+ from contrib.mcp_mixin.mcp_mixin import (
6
+ _DEFAULT_SEPARATOR_PROMPT,
7
+ _DEFAULT_SEPARATOR_RESOURCE,
8
+ _DEFAULT_SEPARATOR_TOOL,
9
+ MCPMixin,
10
+ mcp_prompt,
11
+ mcp_resource,
12
+ mcp_tool,
13
+ )
14
+ from fastmcp import FastMCP
15
+
16
+
17
+ class TestMCPMixin:
18
+ """Test suite for MCPMixin functionality."""
19
+
20
+ def test_initialization(self):
21
+ """Test that a class inheriting MCPMixin can be initialized."""
22
+
23
+ class MyMixin(MCPMixin):
24
+ pass
25
+
26
+ instance = MyMixin()
27
+ assert instance is not None
28
+
29
+ # --- Tool Registration Tests ---
30
+ @pytest.mark.parametrize(
31
+ "prefix, separator, expected_key, unexpected_key",
32
+ [
33
+ (
34
+ None,
35
+ _DEFAULT_SEPARATOR_TOOL,
36
+ "sample_tool",
37
+ f"None{_DEFAULT_SEPARATOR_TOOL}sample_tool",
38
+ ),
39
+ (
40
+ "pref",
41
+ _DEFAULT_SEPARATOR_TOOL,
42
+ f"pref{_DEFAULT_SEPARATOR_TOOL}sample_tool",
43
+ "sample_tool",
44
+ ),
45
+ (
46
+ "pref",
47
+ "-",
48
+ "pref-sample_tool",
49
+ f"pref{_DEFAULT_SEPARATOR_TOOL}sample_tool",
50
+ ),
51
+ ],
52
+ ids=["No prefix", "Default separator", "Custom separator"],
53
+ )
54
+ def test_tool_registration(self, prefix, separator, expected_key, unexpected_key):
55
+ """Test tool registration with prefix and separator variations."""
56
+ mcp = FastMCP()
57
+
58
+ class MyToolMixin(MCPMixin):
59
+ @mcp_tool()
60
+ def sample_tool(self):
61
+ pass
62
+
63
+ instance = MyToolMixin()
64
+ instance.register_tools(mcp, prefix=prefix, separator=separator)
65
+
66
+ registered_tools = mcp.get_tools()
67
+ assert expected_key in registered_tools
68
+ assert unexpected_key not in registered_tools
69
+
70
+ @pytest.mark.parametrize(
71
+ "prefix, separator, expected_uri_key, expected_name, unexpected_uri_key",
72
+ [
73
+ (
74
+ None,
75
+ _DEFAULT_SEPARATOR_RESOURCE,
76
+ "test://resource",
77
+ "sample_resource",
78
+ f"None{_DEFAULT_SEPARATOR_RESOURCE}test://resource",
79
+ ),
80
+ (
81
+ "pref",
82
+ _DEFAULT_SEPARATOR_RESOURCE,
83
+ f"pref{_DEFAULT_SEPARATOR_RESOURCE}test://resource",
84
+ f"pref{_DEFAULT_SEPARATOR_RESOURCE}sample_resource",
85
+ "test://resource",
86
+ ),
87
+ (
88
+ "pref",
89
+ "fff",
90
+ "prefffftest://resource",
91
+ "preffffsample_resource",
92
+ f"pref{_DEFAULT_SEPARATOR_RESOURCE}test://resource",
93
+ ),
94
+ ],
95
+ ids=["No prefix", "Default separator", "Custom separator"],
96
+ )
97
+ def test_resource_registration(
98
+ self, prefix, separator, expected_uri_key, expected_name, unexpected_uri_key
99
+ ):
100
+ """Test resource registration with prefix and separator variations."""
101
+ mcp = FastMCP()
102
+
103
+ class MyResourceMixin(MCPMixin):
104
+ @mcp_resource(uri="test://resource")
105
+ def sample_resource(self):
106
+ pass
107
+
108
+ instance = MyResourceMixin()
109
+ instance.register_resources(mcp, prefix=prefix, separator=separator)
110
+
111
+ registered_resources = mcp.get_resources()
112
+ assert expected_uri_key in registered_resources
113
+ assert registered_resources[expected_uri_key].name == expected_name
114
+ assert unexpected_uri_key not in registered_resources
115
+
116
+ @pytest.mark.parametrize(
117
+ "prefix, separator, expected_name, unexpected_name",
118
+ [
119
+ (
120
+ None,
121
+ _DEFAULT_SEPARATOR_PROMPT,
122
+ "sample_prompt",
123
+ f"None{_DEFAULT_SEPARATOR_PROMPT}sample_prompt",
124
+ ),
125
+ (
126
+ "pref",
127
+ _DEFAULT_SEPARATOR_PROMPT,
128
+ f"pref{_DEFAULT_SEPARATOR_PROMPT}sample_prompt",
129
+ "sample_prompt",
130
+ ),
131
+ (
132
+ "pref",
133
+ ":",
134
+ "pref:sample_prompt",
135
+ f"pref{_DEFAULT_SEPARATOR_PROMPT}sample_prompt",
136
+ ),
137
+ ],
138
+ ids = ["No prefix", "Default separator", "Custom separator"]
139
+ )
140
+ def test_prompt_registration(
141
+ self, prefix, separator, expected_name, unexpected_name
142
+ ):
143
+ """Test prompt registration with prefix and separator variations."""
144
+ mcp = FastMCP()
145
+
146
+ class MyPromptMixin(MCPMixin):
147
+ @mcp_prompt()
148
+ def sample_prompt(self):
149
+ pass
150
+
151
+ instance = MyPromptMixin()
152
+ instance.register_prompts(mcp, prefix=prefix, separator=separator)
153
+
154
+ registered_prompt_names = {p.name for p in mcp.list_prompts()}
155
+ assert expected_name in registered_prompt_names
156
+ assert unexpected_name not in registered_prompt_names
157
+
158
+ def test_register_all_no_prefix(self):
159
+ """Test register_all method registers all types without a prefix."""
160
+ mcp = FastMCP()
161
+
162
+ class MyFullMixin(MCPMixin):
163
+ @mcp_tool()
164
+ def tool_all(self):
165
+ pass
166
+
167
+ @mcp_resource(uri="res://all")
168
+ def resource_all(self):
169
+ pass
170
+
171
+ @mcp_prompt()
172
+ def prompt_all(self):
173
+ pass
174
+
175
+ instance = MyFullMixin()
176
+ instance.register_all(mcp)
177
+
178
+ assert "tool_all" in mcp.get_tools()
179
+ assert "res://all" in mcp.get_resources()
180
+ assert "prompt_all" in {p.name for p in mcp.list_prompts()}
181
+
182
+ def test_register_all_with_prefix_default_separators(self):
183
+ """Test register_all method registers all types with a prefix and default separators."""
184
+ mcp = FastMCP()
185
+
186
+ class MyFullMixinPrefixed(MCPMixin):
187
+ @mcp_tool()
188
+ def tool_all_p(self):
189
+ pass
190
+
191
+ @mcp_resource(uri="res://all_p")
192
+ def resource_all_p(self):
193
+ pass
194
+
195
+ @mcp_prompt()
196
+ def prompt_all_p(self):
197
+ pass
198
+
199
+ instance = MyFullMixinPrefixed()
200
+ instance.register_all(mcp, prefix="all")
201
+
202
+ assert f"all{_DEFAULT_SEPARATOR_TOOL}tool_all_p" in mcp.get_tools()
203
+ assert f"all{_DEFAULT_SEPARATOR_RESOURCE}res://all_p" in mcp.get_resources()
204
+ assert f"all{_DEFAULT_SEPARATOR_PROMPT}prompt_all_p" in {
205
+ p.name for p in mcp.list_prompts()
206
+ }
207
+
208
+ def test_register_all_with_prefix_custom_separators(self):
209
+ """Test register_all method registers all types with a prefix and custom separators."""
210
+ mcp = FastMCP()
211
+
212
+ class MyFullMixinCustomSep(MCPMixin):
213
+ @mcp_tool()
214
+ def tool_cust(self):
215
+ pass
216
+
217
+ @mcp_resource(uri="res://cust")
218
+ def resource_cust(self):
219
+ pass
220
+
221
+ @mcp_prompt()
222
+ def prompt_cust(self):
223
+ pass
224
+
225
+ instance = MyFullMixinCustomSep()
226
+ instance.register_all(
227
+ mcp,
228
+ prefix="cust",
229
+ tool_separator="-",
230
+ resource_separator="::",
231
+ prompt_separator=".",
232
+ )
233
+
234
+ assert "cust-tool_cust" in mcp.get_tools()
235
+ assert "cust::res://cust" in mcp.get_resources()
236
+ assert "cust.prompt_cust" in {p.name for p in mcp.list_prompts()}
237
+
238
+ # Check default separators weren't used
239
+ assert f"cust{_DEFAULT_SEPARATOR_TOOL}tool_cust" not in mcp.get_tools()
240
+ assert f"cust{_DEFAULT_SEPARATOR_RESOURCE}res://cust" not in mcp.get_resources()
241
+ assert f"cust{_DEFAULT_SEPARATOR_PROMPT}prompt_cust" not in {
242
+ p.name for p in mcp.list_prompts()
243
+ }