File size: 6,412 Bytes
1a49d90
 
 
 
 
 
 
 
8612b4f
 
 
 
 
 
 
 
 
 
1a49d90
8612b4f
8430481
8612b4f
 
 
 
1a49d90
 
8612b4f
 
 
e9d44f5
 
8612b4f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1a49d90
8612b4f
8430481
e9d44f5
1a49d90
 
8612b4f
e9d44f5
 
8612b4f
1a49d90
 
e9d44f5
 
1a49d90
 
 
 
 
e9d44f5
 
8612b4f
1a49d90
 
8612b4f
 
e9d44f5
8612b4f
 
8430481
1a49d90
 
 
 
 
 
 
 
 
 
8430481
 
 
 
 
 
 
 
 
 
 
 
 
1a49d90
8430481
 
1a49d90
8430481
1a49d90
 
 
 
 
 
 
 
 
 
 
8430481
 
1a49d90
 
 
 
 
8430481
 
3476d1b
 
 
 
8430481
 
1a49d90
 
 
 
 
 
 
 
 
 
 
 
 
 
8612b4f
 
 
 
 
1a49d90
 
 
8612b4f
e9d44f5
 
8430481
e9d44f5
1a49d90
 
 
 
 
 
 
 
 
e9d44f5
 
 
 
 
1a49d90
 
 
e9d44f5
 
 
1a49d90
 
 
8430481
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
"""
Routes and helpers for managing tools, resources, and prompts in FastMCP.
Provides endpoints for enabling/disabling components via HTTP, with optional authentication scopes.
"""

from typing import Any

from mcp.server.auth.middleware.bearer_auth import RequireAuthMiddleware
from starlette.applications import Starlette
from starlette.exceptions import HTTPException as StarletteHTTPException
from starlette.requests import Request
from starlette.responses import JSONResponse
from starlette.routing import Mount, Route

from fastmcp.contrib.component_manager.component_service import ComponentService
from fastmcp.exceptions import NotFoundError
from fastmcp.server.server import FastMCP


def set_up_component_manager(
    server: FastMCP, path: str = "/", required_scopes: list[str] | None = None
):
    """Set up routes for enabling/disabling tools, resources, and prompts.
    Args:
        server: The FastMCP server instance
        path: Path used to mount all component-related routes on the server
        required_scopes: Optional list of scopes required for these routes. Applies only if authentication is enabled.
    """

    service = ComponentService(server)
    routes: list[Route] = []
    mounts: list[Mount] = []
    route_configs = {
        "tool": {
            "param": "tool_name",
            "enable": service._enable_tool,
            "disable": service._disable_tool,
        },
        "resource": {
            "param": "uri:path",
            "enable": service._enable_resource,
            "disable": service._disable_resource,
        },
        "prompt": {
            "param": "prompt_name",
            "enable": service._enable_prompt,
            "disable": service._disable_prompt,
        },
    }

    if required_scopes is None:
        routes.extend(build_component_manager_endpoints(route_configs, path))
    else:
        if path != "/":
            mounts.append(
                build_component_manager_mount(route_configs, path, required_scopes)
            )
        else:
            mounts.append(
                build_component_manager_mount(
                    {"tool": route_configs["tool"]}, "/tools", required_scopes
                )
            )
            mounts.append(
                build_component_manager_mount(
                    {"resource": route_configs["resource"]},
                    "/resources",
                    required_scopes,
                )
            )
            mounts.append(
                build_component_manager_mount(
                    {"prompt": route_configs["prompt"]}, "/prompts", required_scopes
                )
            )

    server._additional_http_routes.extend(routes)
    server._additional_http_routes.extend(mounts)


def make_endpoint(action, component, config):
    """
    Factory for creating Starlette endpoint functions for enabling/disabling a component.
    Args:
        action: 'enable' or 'disable'
        component: The component type (e.g., 'tool', 'resource', or 'prompt')
        config: Dict with param and handler functions for the component
    Returns:
        An async endpoint function for Starlette.
    """

    async def endpoint(request: Request):
        name = request.path_params[config["param"].split(":")[0]]

        try:
            await config[action](name)
            return JSONResponse(
                {"message": f"{action.capitalize()}d {component}: {name}"}
            )
        except NotFoundError:
            raise StarletteHTTPException(
                status_code=404,
                detail=f"Unknown {component}: {name}",
            )

    return endpoint


def make_route(action, component, config, required_scopes, root_path) -> Route:
    """
    Creates a Starlette Route for enabling/disabling a component.
    Args:
        action: 'enable' or 'disable'
        component: The component type
        config: Dict with param and handler functions
        required_scopes: Optional list of required auth scopes
        root_path: The base path for the route
    Returns:
        A Starlette Route object.
    """
    endpoint = make_endpoint(action, component, config)

    if required_scopes is not None and root_path in [
        "/tools",
        "/resources",
        "/prompts",
    ]:
        path = f"/{{{config['param']}}}/{action}"
    else:
        if root_path != "/" and required_scopes is None:
            path = f"{root_path}/{component}s/{{{config['param']}}}/{action}"
        else:
            path = f"/{component}s/{{{config['param']}}}/{action}"

    return Route(path, endpoint=endpoint, methods=["POST"])


def build_component_manager_endpoints(
    route_configs, root_path, required_scopes=None
) -> list[Route]:
    """
    Build a list of Starlette Route objects for all components/actions.
    Args:
        route_configs: Dict describing component types and their handlers
        root_path: The base path for the routes
        required_scopes: Optional list of required auth scopes
    Returns:
        List of Starlette Route objects for component management.
    """
    component_management_routes: list[Route] = []

    for component in route_configs:
        config: dict[str, Any] = route_configs[component]
        for action in ["enable", "disable"]:
            component_management_routes.append(
                make_route(action, component, config, required_scopes, root_path)
            )

    return component_management_routes


def build_component_manager_mount(route_configs, root_path, required_scopes) -> Mount:
    """
    Build a Starlette Mount with authentication for component management routes.
    Args:
        route_configs: Dict describing component types and their handlers
        root_path: The base path for the mount
        required_scopes: List of required auth scopes
    Returns:
        A Starlette Mount object with authentication middleware.
    """
    component_management_routes: list[Route] = []

    for component in route_configs:
        config: dict[str, Any] = route_configs[component]
        for action in ["enable", "disable"]:
            component_management_routes.append(
                make_route(action, component, config, required_scopes, root_path)
            )

    return Mount(
        f"{root_path}",
        app=RequireAuthMiddleware(
            Starlette(routes=component_management_routes), required_scopes
        ),
    )