fastmcp 2.9.1__py3-none-any.whl → 2.10.0__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 (42) hide show
  1. fastmcp/cli/cli.py +16 -1
  2. fastmcp/cli/run.py +4 -0
  3. fastmcp/client/auth/oauth.py +5 -82
  4. fastmcp/client/client.py +114 -24
  5. fastmcp/client/elicitation.py +63 -0
  6. fastmcp/client/transports.py +50 -36
  7. fastmcp/contrib/component_manager/README.md +170 -0
  8. fastmcp/contrib/component_manager/__init__.py +4 -0
  9. fastmcp/contrib/component_manager/component_manager.py +186 -0
  10. fastmcp/contrib/component_manager/component_service.py +225 -0
  11. fastmcp/contrib/component_manager/example.py +59 -0
  12. fastmcp/prompts/prompt.py +12 -4
  13. fastmcp/resources/resource.py +8 -3
  14. fastmcp/resources/template.py +5 -0
  15. fastmcp/server/auth/auth.py +15 -0
  16. fastmcp/server/auth/providers/bearer.py +41 -3
  17. fastmcp/server/auth/providers/bearer_env.py +4 -0
  18. fastmcp/server/auth/providers/in_memory.py +15 -0
  19. fastmcp/server/context.py +144 -4
  20. fastmcp/server/elicitation.py +160 -0
  21. fastmcp/server/http.py +1 -9
  22. fastmcp/server/low_level.py +4 -2
  23. fastmcp/server/middleware/__init__.py +14 -1
  24. fastmcp/server/middleware/logging.py +11 -0
  25. fastmcp/server/middleware/middleware.py +10 -6
  26. fastmcp/server/openapi.py +19 -77
  27. fastmcp/server/proxy.py +13 -6
  28. fastmcp/server/server.py +76 -11
  29. fastmcp/settings.py +0 -17
  30. fastmcp/tools/tool.py +209 -57
  31. fastmcp/tools/tool_manager.py +2 -3
  32. fastmcp/tools/tool_transform.py +125 -26
  33. fastmcp/utilities/cli.py +106 -0
  34. fastmcp/utilities/components.py +5 -1
  35. fastmcp/utilities/json_schema_type.py +648 -0
  36. fastmcp/utilities/openapi.py +69 -0
  37. fastmcp/utilities/types.py +50 -19
  38. {fastmcp-2.9.1.dist-info → fastmcp-2.10.0.dist-info}/METADATA +3 -2
  39. {fastmcp-2.9.1.dist-info → fastmcp-2.10.0.dist-info}/RECORD +42 -33
  40. {fastmcp-2.9.1.dist-info → fastmcp-2.10.0.dist-info}/WHEEL +0 -0
  41. {fastmcp-2.9.1.dist-info → fastmcp-2.10.0.dist-info}/entry_points.txt +0 -0
  42. {fastmcp-2.9.1.dist-info → fastmcp-2.10.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,170 @@
1
+ # Component Manager – Contrib Module for FastMCP
2
+
3
+ The **Component Manager** provides a unified API for enabling and disabling tools, resources, and prompts at runtime in a FastMCP server. This module is useful for dynamic control over which components are active, enabling advanced features like feature toggling, admin interfaces, or automation workflows.
4
+
5
+ ---
6
+
7
+ ## 🔧 Features
8
+
9
+ - Enable/disable **tools**, **resources**, and **prompts** via HTTP endpoints.
10
+ - Supports **local** and **mounted (server)** components.
11
+ - Customizable **API root path**.
12
+ - Optional **Auth scopes** for secured access.
13
+ - Fully integrates with FastMCP with minimal configuration.
14
+
15
+ ---
16
+
17
+ ## 📦 Installation
18
+
19
+ This module is part of the `fastmcp.contrib` package. No separate installation is required if you're already using **FastMCP**.
20
+
21
+ ---
22
+
23
+ ## 🚀 Usage
24
+
25
+ ### Basic Setup
26
+
27
+ ```python
28
+ from fastmcp import FastMCP
29
+ from fastmcp.contrib.component_manager import set_up_component_manager
30
+
31
+ mcp = FastMCP(name="Component Manager", instructions="This is a test server with component manager.")
32
+ set_up_component_manager(server=mcp)
33
+ ```
34
+
35
+ ---
36
+
37
+ ## 🔗 API Endpoints
38
+
39
+ All endpoints are registered at `/` by default, or under the custom path if one is provided.
40
+
41
+ ### Tools
42
+
43
+ ```http
44
+ POST /tools/{tool_name}/enable
45
+ POST /tools/{tool_name}/disable
46
+ ```
47
+
48
+ ### Resources
49
+
50
+ ```http
51
+ POST /resources/{uri:path}/enable
52
+ POST /resources/{uri:path}/disable
53
+ ```
54
+
55
+ * Supports template URIs as well
56
+ ```http
57
+ POST /resources/example://test/{id}/enable
58
+ POST /resources/example://test/{id}/disable
59
+ ```
60
+
61
+ ### Prompts
62
+
63
+ ```http
64
+ POST /prompts/{prompt_name}/enable
65
+ POST /prompts/{prompt_name}/disable
66
+ ```
67
+ ---
68
+
69
+ #### 🧪 Example Response
70
+
71
+ ```http
72
+ HTTP/1.1 200 OK
73
+ Content-Type: application/json
74
+
75
+ {
76
+ "message": "Disabled tool: example_tool"
77
+ }
78
+
79
+ ```
80
+
81
+ ---
82
+
83
+ ## ⚙️ Configuration Options
84
+
85
+ ### Custom Root Path
86
+
87
+ To mount the API under a different path:
88
+
89
+ ```python
90
+ set_up_component_manager(server=mcp, path="/admin")
91
+ ```
92
+
93
+ ### Securing Endpoints with Auth Scopes
94
+
95
+ If your server uses authentication:
96
+
97
+ ```python
98
+ mcp = FastMCP(name="Component Manager", instructions="This is a test server with component manager.", auth=auth)
99
+ set_up_component_manager(server=mcp, required_scopes=["write", "read"])
100
+ ```
101
+
102
+ ---
103
+
104
+ ## 🧪 Example: Enabling a Tool with Curl
105
+
106
+ ```bash
107
+ curl -X POST \
108
+ -H "Authorization: Bearer YOUR_TOKEN_HERE" \
109
+ -H "Content-Type: application/json" \
110
+ http://localhost:8001/tools/example_tool/enable
111
+ ```
112
+
113
+ ---
114
+
115
+ ## 🧱 Working with Mounted Servers
116
+
117
+ You can also combine different configurations when working with mounted servers — for example, using different scopes:
118
+
119
+ ```python
120
+ mcp = FastMCP(name="Component Manager", instructions="This is a test server with component manager.", auth=auth)
121
+ set_up_component_manager(server=mcp, required_scopes=["mcp:write"])
122
+
123
+ mounted = FastMCP(name="Component Manager", instructions="This is a test server with component manager.", auth=auth)
124
+ set_up_component_manager(server=mounted, required_scopes=["mounted:write"])
125
+
126
+ mcp.mount(server=mounted, prefix="mo")
127
+ ```
128
+
129
+ This allows you to grant different levels of access:
130
+
131
+ ```bash
132
+ # Accessing the main server gives you control over both local and mounted components
133
+ curl -X POST \
134
+ -H "Authorization: Bearer YOUR_TOKEN_HERE" \
135
+ -H "Content-Type: application/json" \
136
+ http://localhost:8001/tools/mo_example_tool/enable
137
+
138
+ # Accessing the mounted server gives you control only over its own components
139
+ curl -X POST \
140
+ -H "Authorization: Bearer YOUR_TOKEN_HERE" \
141
+ -H "Content-Type: application/json" \
142
+ http://localhost:8002/tools/example_tool/enable
143
+ ```
144
+
145
+ ---
146
+
147
+ ## ⚙️ How It Works
148
+
149
+ - `set_up_component_manager()` registers API routes for tools, resources, and prompts.
150
+ - The `ComponentService` class exposes async methods to enable/disable components.
151
+ - Each endpoint returns a success message in JSON or a 404 error if the component isn't found.
152
+
153
+ ---
154
+
155
+ ## 🧩 Extending
156
+
157
+ You can subclass `ComponentService` for custom behavior or mount its routes elsewhere as needed.
158
+
159
+ ---
160
+
161
+ ## Maintenance Notice
162
+
163
+ This module is not officially maintained by the core FastMCP team. It is an independent extension developed by [gorocode](https://github.com/gorocode).
164
+
165
+ If you encounter any issues or wish to contribute, please feel free to open an issue or submit a pull request, and kindly notify me. I'd love to stay up to date.
166
+
167
+
168
+ ## 📄 License
169
+
170
+ This module follows the license of the main [FastMCP](https://github.com/jlowin/fastmcp) project.
@@ -0,0 +1,4 @@
1
+ from .component_manager import set_up_component_manager
2
+ from .component_service import ComponentService
3
+
4
+ __all__ = ["set_up_component_manager", "ComponentService"]
@@ -0,0 +1,186 @@
1
+ """
2
+ Routes and helpers for managing tools, resources, and prompts in FastMCP.
3
+ Provides endpoints for enabling/disabling components via HTTP, with optional authentication scopes.
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from mcp.server.auth.middleware.bearer_auth import RequireAuthMiddleware
9
+ from starlette.applications import Starlette
10
+ from starlette.exceptions import HTTPException as StarletteHTTPException
11
+ from starlette.requests import Request
12
+ from starlette.responses import JSONResponse
13
+ from starlette.routing import Mount, Route
14
+
15
+ from fastmcp.contrib.component_manager.component_service import ComponentService
16
+ from fastmcp.exceptions import NotFoundError
17
+ from fastmcp.server.server import FastMCP
18
+
19
+
20
+ def set_up_component_manager(
21
+ server: FastMCP, path: str = "/", required_scopes: list[str] | None = None
22
+ ):
23
+ """Set up routes for enabling/disabling tools, resources, and prompts.
24
+ Args:
25
+ server: The FastMCP server instance
26
+ path: Path used to mount all component-related routes on the server
27
+ required_scopes: Optional list of scopes required for these routes. Applies only if authentication is enabled.
28
+ """
29
+
30
+ service = ComponentService(server)
31
+ routes: list[Route] = []
32
+ mounts: list[Mount] = []
33
+ route_configs = {
34
+ "tool": {
35
+ "param": "tool_name",
36
+ "enable": service._enable_tool,
37
+ "disable": service._disable_tool,
38
+ },
39
+ "resource": {
40
+ "param": "uri:path",
41
+ "enable": service._enable_resource,
42
+ "disable": service._disable_resource,
43
+ },
44
+ "prompt": {
45
+ "param": "prompt_name",
46
+ "enable": service._enable_prompt,
47
+ "disable": service._disable_prompt,
48
+ },
49
+ }
50
+
51
+ if required_scopes is None:
52
+ routes.extend(build_component_manager_endpoints(route_configs, path))
53
+ else:
54
+ if path != "/":
55
+ mounts.append(
56
+ build_component_manager_mount(route_configs, path, required_scopes)
57
+ )
58
+ else:
59
+ mounts.append(
60
+ build_component_manager_mount(
61
+ {"tool": route_configs["tool"]}, "/tools", required_scopes
62
+ )
63
+ )
64
+ mounts.append(
65
+ build_component_manager_mount(
66
+ {"resource": route_configs["resource"]},
67
+ "/resources",
68
+ required_scopes,
69
+ )
70
+ )
71
+ mounts.append(
72
+ build_component_manager_mount(
73
+ {"prompt": route_configs["prompt"]}, "/prompts", required_scopes
74
+ )
75
+ )
76
+
77
+ server._additional_http_routes.extend(routes)
78
+ server._additional_http_routes.extend(mounts)
79
+
80
+
81
+ def make_endpoint(action, component, config):
82
+ """
83
+ Factory for creating Starlette endpoint functions for enabling/disabling a component.
84
+ Args:
85
+ action: 'enable' or 'disable'
86
+ component: The component type (e.g., 'tool', 'resource', or 'prompt')
87
+ config: Dict with param and handler functions for the component
88
+ Returns:
89
+ An async endpoint function for Starlette.
90
+ """
91
+
92
+ async def endpoint(request: Request):
93
+ name = request.path_params[config["param"].split(":")[0]]
94
+
95
+ try:
96
+ await config[action](name)
97
+ return JSONResponse(
98
+ {"message": f"{action.capitalize()}d {component}: {name}"}
99
+ )
100
+ except NotFoundError:
101
+ raise StarletteHTTPException(
102
+ status_code=404,
103
+ detail=f"Unknown {component}: {name}",
104
+ )
105
+
106
+ return endpoint
107
+
108
+
109
+ def make_route(action, component, config, required_scopes, root_path) -> Route:
110
+ """
111
+ Creates a Starlette Route for enabling/disabling a component.
112
+ Args:
113
+ action: 'enable' or 'disable'
114
+ component: The component type
115
+ config: Dict with param and handler functions
116
+ required_scopes: Optional list of required auth scopes
117
+ root_path: The base path for the route
118
+ Returns:
119
+ A Starlette Route object.
120
+ """
121
+ endpoint = make_endpoint(action, component, config)
122
+
123
+ if required_scopes is not None and root_path in [
124
+ "/tools",
125
+ "/resources",
126
+ "/prompts",
127
+ ]:
128
+ path = f"/{{{config['param']}}}/{action}"
129
+ else:
130
+ if root_path != "/" and required_scopes is None:
131
+ path = f"{root_path}/{component}s/{{{config['param']}}}/{action}"
132
+ else:
133
+ path = f"/{component}s/{{{config['param']}}}/{action}"
134
+
135
+ return Route(path, endpoint=endpoint, methods=["POST"])
136
+
137
+
138
+ def build_component_manager_endpoints(
139
+ route_configs, root_path, required_scopes=None
140
+ ) -> list[Route]:
141
+ """
142
+ Build a list of Starlette Route objects for all components/actions.
143
+ Args:
144
+ route_configs: Dict describing component types and their handlers
145
+ root_path: The base path for the routes
146
+ required_scopes: Optional list of required auth scopes
147
+ Returns:
148
+ List of Starlette Route objects for component management.
149
+ """
150
+ component_management_routes: list[Route] = []
151
+
152
+ for component in route_configs:
153
+ config: dict[str, Any] = route_configs[component]
154
+ for action in ["enable", "disable"]:
155
+ component_management_routes.append(
156
+ make_route(action, component, config, required_scopes, root_path)
157
+ )
158
+
159
+ return component_management_routes
160
+
161
+
162
+ def build_component_manager_mount(route_configs, root_path, required_scopes) -> Mount:
163
+ """
164
+ Build a Starlette Mount with authentication for component management routes.
165
+ Args:
166
+ route_configs: Dict describing component types and their handlers
167
+ root_path: The base path for the mount
168
+ required_scopes: List of required auth scopes
169
+ Returns:
170
+ A Starlette Mount object with authentication middleware.
171
+ """
172
+ component_management_routes: list[Route] = []
173
+
174
+ for component in route_configs:
175
+ config: dict[str, Any] = route_configs[component]
176
+ for action in ["enable", "disable"]:
177
+ component_management_routes.append(
178
+ make_route(action, component, config, required_scopes, root_path)
179
+ )
180
+
181
+ return Mount(
182
+ f"{root_path}",
183
+ app=RequireAuthMiddleware(
184
+ Starlette(routes=component_management_routes), required_scopes
185
+ ),
186
+ )
@@ -0,0 +1,225 @@
1
+ """
2
+ ComponentService: Provides async management of tools, resources, and prompts for FastMCP servers.
3
+ Handles enabling/disabling components both locally and across mounted servers.
4
+ """
5
+
6
+ from fastmcp.exceptions import NotFoundError
7
+ from fastmcp.prompts.prompt import Prompt
8
+ from fastmcp.resources.resource import Resource
9
+ from fastmcp.resources.template import ResourceTemplate
10
+ from fastmcp.server.server import FastMCP, has_resource_prefix, remove_resource_prefix
11
+ from fastmcp.tools.tool import Tool
12
+ from fastmcp.utilities.logging import get_logger
13
+
14
+ logger = get_logger(__name__)
15
+
16
+
17
+ class ComponentService:
18
+ """Service for managing components like tools, resources, and prompts."""
19
+
20
+ def __init__(self, server: FastMCP):
21
+ self._server = server
22
+ self._tool_manager = server._tool_manager
23
+ self._resource_manager = server._resource_manager
24
+ self._prompt_manager = server._prompt_manager
25
+
26
+ async def _enable_tool(self, key: str) -> Tool:
27
+ """Handle 'enableTool' requests.
28
+
29
+ Args:
30
+ key: The key of the tool to enable
31
+
32
+ Returns:
33
+ The tool that was enabled
34
+ """
35
+ logger.debug("Enabling tool: %s", key)
36
+
37
+ # 1. Check local tools first. The server will have already applied its filter.
38
+ if key in self._server._tool_manager._tools:
39
+ tool: Tool = await self._server.get_tool(key)
40
+ tool.enable()
41
+ return tool
42
+
43
+ # 2. Check mounted servers using the filtered protocol path.
44
+ for mounted in reversed(self._tool_manager._mounted_servers):
45
+ if mounted.prefix:
46
+ if key.startswith(f"{mounted.prefix}_"):
47
+ tool_key = key.removeprefix(f"{mounted.prefix}_")
48
+ mounted_service = ComponentService(mounted.server)
49
+ tool = await mounted_service._enable_tool(tool_key)
50
+ return tool
51
+ else:
52
+ continue
53
+ raise NotFoundError(f"Unknown tool: {key}")
54
+
55
+ async def _disable_tool(self, key: str) -> Tool:
56
+ """Handle 'disableTool' requests.
57
+
58
+ Args:
59
+ key: The key of the tool to disable
60
+
61
+ Returns:
62
+ The tool that was disabled
63
+ """
64
+ logger.debug("Disable tool: %s", key)
65
+
66
+ # 1. Check local tools first. The server will have already applied its filter.
67
+ if key in self._server._tool_manager._tools:
68
+ tool: Tool = await self._server.get_tool(key)
69
+ tool.disable()
70
+ return tool
71
+
72
+ # 2. Check mounted servers using the filtered protocol path.
73
+ for mounted in reversed(self._tool_manager._mounted_servers):
74
+ if mounted.prefix:
75
+ if key.startswith(f"{mounted.prefix}_"):
76
+ tool_key = key.removeprefix(f"{mounted.prefix}_")
77
+ mounted_service = ComponentService(mounted.server)
78
+ tool = await mounted_service._disable_tool(tool_key)
79
+ return tool
80
+ else:
81
+ continue
82
+ raise NotFoundError(f"Unknown tool: {key}")
83
+
84
+ async def _enable_resource(self, key: str) -> Resource | ResourceTemplate:
85
+ """Handle 'enableResource' requests.
86
+
87
+ Args:
88
+ key: The key of the resource to enable
89
+
90
+ Returns:
91
+ The resource that was enabled
92
+ """
93
+ logger.debug("Enabling resource: %s", key)
94
+
95
+ # 1. Check local resources first. The server will have already applied its filter.
96
+ if key in self._resource_manager._resources:
97
+ resource: Resource = await self._server.get_resource(key)
98
+ resource.enable()
99
+ return resource
100
+ if key in self._resource_manager._templates:
101
+ template: ResourceTemplate = await self._server.get_resource_template(key)
102
+ template.enable()
103
+ return template
104
+
105
+ # 2. Check mounted servers using the filtered protocol path.
106
+ for mounted in reversed(self._resource_manager._mounted_servers):
107
+ if mounted.prefix:
108
+ if has_resource_prefix(
109
+ key,
110
+ mounted.prefix,
111
+ mounted.resource_prefix_format,
112
+ ):
113
+ key = remove_resource_prefix(
114
+ key,
115
+ mounted.prefix,
116
+ mounted.resource_prefix_format,
117
+ )
118
+ mounted_service = ComponentService(mounted.server)
119
+ mounted_resource: (
120
+ Resource | ResourceTemplate
121
+ ) = await mounted_service._enable_resource(key)
122
+ return mounted_resource
123
+ else:
124
+ continue
125
+ raise NotFoundError(f"Unknown resource: {key}")
126
+
127
+ async def _disable_resource(self, key: str) -> Resource | ResourceTemplate:
128
+ """Handle 'disableResource' requests.
129
+
130
+ Args:
131
+ key: The key of the resource to disable
132
+
133
+ Returns:
134
+ The resource that was disabled
135
+ """
136
+ logger.debug("Disable resource: %s", key)
137
+
138
+ # 1. Check local resources first. The server will have already applied its filter.
139
+ if key in self._resource_manager._resources:
140
+ resource: Resource = await self._server.get_resource(key)
141
+ resource.disable()
142
+ return resource
143
+ if key in self._resource_manager._templates:
144
+ template: ResourceTemplate = await self._server.get_resource_template(key)
145
+ template.disable()
146
+ return template
147
+
148
+ # 2. Check mounted servers using the filtered protocol path.
149
+ for mounted in reversed(self._resource_manager._mounted_servers):
150
+ if mounted.prefix:
151
+ if has_resource_prefix(
152
+ key,
153
+ mounted.prefix,
154
+ mounted.resource_prefix_format,
155
+ ):
156
+ key = remove_resource_prefix(
157
+ key,
158
+ mounted.prefix,
159
+ mounted.resource_prefix_format,
160
+ )
161
+ mounted_service = ComponentService(mounted.server)
162
+ mounted_resource: (
163
+ Resource | ResourceTemplate
164
+ ) = await mounted_service._disable_resource(key)
165
+ return mounted_resource
166
+ else:
167
+ continue
168
+ raise NotFoundError(f"Unknown resource: {key}")
169
+
170
+ async def _enable_prompt(self, key: str) -> Prompt:
171
+ """Handle 'enablePrompt' requests.
172
+
173
+ Args:
174
+ key: The key of the prompt to enable
175
+
176
+ Returns:
177
+ The prompt that was enable
178
+ """
179
+ logger.debug("Enabling prompt: %s", key)
180
+
181
+ # 1. Check local prompts first. The server will have already applied its filter.
182
+ if key in self._server._prompt_manager._prompts:
183
+ prompt: Prompt = await self._server.get_prompt(key)
184
+ prompt.enable()
185
+ return prompt
186
+
187
+ # 2. Check mounted servers using the filtered protocol path.
188
+ for mounted in reversed(self._prompt_manager._mounted_servers):
189
+ if mounted.prefix:
190
+ if key.startswith(f"{mounted.prefix}_"):
191
+ prompt_key = key.removeprefix(f"{mounted.prefix}_")
192
+ mounted_service = ComponentService(mounted.server)
193
+ prompt = await mounted_service._enable_prompt(prompt_key)
194
+ return prompt
195
+ else:
196
+ continue
197
+ raise NotFoundError(f"Unknown prompt: {key}")
198
+
199
+ async def _disable_prompt(self, key: str) -> Prompt:
200
+ """Handle 'disablePrompt' requests.
201
+
202
+ Args:
203
+ key: The key of the prompt to disable
204
+
205
+ Returns:
206
+ The prompt that was disabled
207
+ """
208
+
209
+ # 1. Check local prompts first. The server will have already applied its filter.
210
+ if key in self._server._prompt_manager._prompts:
211
+ prompt: Prompt = await self._server.get_prompt(key)
212
+ prompt.disable()
213
+ return prompt
214
+
215
+ # 2. Check mounted servers using the filtered protocol path.
216
+ for mounted in reversed(self._prompt_manager._mounted_servers):
217
+ if mounted.prefix:
218
+ if key.startswith(f"{mounted.prefix}_"):
219
+ prompt_key = key.removeprefix(f"{mounted.prefix}_")
220
+ mounted_service = ComponentService(mounted.server)
221
+ prompt = await mounted_service._disable_prompt(prompt_key)
222
+ return prompt
223
+ else:
224
+ continue
225
+ raise NotFoundError(f"Unknown prompt: {key}")
@@ -0,0 +1,59 @@
1
+ from fastmcp import FastMCP
2
+ from fastmcp.contrib.component_manager import set_up_component_manager
3
+ from fastmcp.server.auth.providers.bearer import BearerAuthProvider, RSAKeyPair
4
+
5
+ key_pair = RSAKeyPair.generate()
6
+
7
+ auth = BearerAuthProvider(
8
+ public_key=key_pair.public_key,
9
+ issuer="https://dev.example.com",
10
+ audience="my-dev-server",
11
+ required_scopes=["mcp:read"],
12
+ )
13
+
14
+ # Build main server
15
+ mcp_token = key_pair.create_token(
16
+ subject="dev-user",
17
+ issuer="https://dev.example.com",
18
+ audience="my-dev-server",
19
+ scopes=["mcp:write", "mcp:read"],
20
+ )
21
+ mcp = FastMCP(
22
+ name="Component Manager",
23
+ instructions="This is a test server with component manager.",
24
+ auth=auth,
25
+ )
26
+
27
+ # Set up main server component manager
28
+ set_up_component_manager(server=mcp, required_scopes=["mcp:write"])
29
+
30
+ # Build mounted server
31
+ mounted_token = key_pair.create_token(
32
+ subject="dev-user",
33
+ issuer="https://dev.example.com",
34
+ audience="my-dev-server",
35
+ scopes=["mounted:write", "mcp:read"],
36
+ )
37
+ mounted = FastMCP(
38
+ name="Component Manager",
39
+ instructions="This is a test server with component manager.",
40
+ auth=auth,
41
+ )
42
+
43
+ # Set up mounted server component manager
44
+ set_up_component_manager(server=mounted, required_scopes=["mounted:write"])
45
+
46
+ # Mount
47
+ mcp.mount(server=mounted, prefix="mo")
48
+
49
+
50
+ @mcp.resource("resource://greeting")
51
+ def get_greeting() -> str:
52
+ """Provides a simple greeting message."""
53
+ return "Hello from FastMCP Resources!"
54
+
55
+
56
+ @mounted.tool("greeting")
57
+ def get_info() -> str:
58
+ """Provides a simple info."""
59
+ return "You are using component manager contrib module!"