Repository: gradio-app/gradio
File: gradio/mcp.py (Python)

Code before:

    def get_selected_tools_from_request(
        self, request: Request | None
    ) -> set[str] | None:
        """
        Extract the selected tools from the request query parameters.
        Returns None if no tools parameter is specified (meaning all tools are available).
        Adds the tool prefix if needed.
        """
        if request is None:
            return None

        query_params = dict(getattr(request, "query_params", {}))
        if "tools" in query_params:
            tools = query_params["tools"].split(",")
            full_tool_names = set()
            for tool in tools:
                # Check if the tool already has the prefix or if it exists as-is
                if tool in self.tool_to_endpoint:
                    full_tool_names.add(tool)
                elif (
                    self.tool_prefix
                    and (self.tool_prefix + tool) in self.tool_to_endpoint
                ):
                    full_tool_names.add(self.tool_prefix + tool)
                else:
                    # Tool not found, add it anyway to let the error be handled later
                    full_tool_names.add(tool)
            return full_tool_names
        return None

    @staticmethod
    def valid_and_unique_tool_name(
        tool_name: str, existing_tool_names: set[str]
    ) -> str:
        """
        Sanitizes a tool name to make it a valid MCP tool name (only
        alphanumeric characters, underscores, <= 128 characters)
        and is unique among the existing tool names.
        """
        tool_name = re.sub(r"[^a-zA-Z0-9]", "_", tool_name)
        tool_name = tool_name[:120]  # Leave room for suffix if needed
        tool_name_base = tool_name
        suffix = 1
        while tool_name in existing_tool_names:
            tool_name = tool_name_base + f"_{suffix}"
            suffix += 1
        return tool_name

    def get_tool_to_endpoint(self) -> dict[str, str]:
        """

Review comment (suggestion):
Given we always add the tool to the set, I think this could be simplified:

```python
if self.tool_prefix and (self.tool_prefix + tool) in self.tool_to_endpoint:
     full_tool_names.add(self.tool_prefix + tool)
else:
     full_tool_names.add(tool)
 ```

Code after:

    def get_selected_tools_from_request(
        self, request: Request | None
    ) -> list[str] | None:
        """
        Extract the selected tools from the request query parameters and return the full tool names (with the tool prefix).
        Returns None if no tools parameter is specified (meaning all tools are available).
        """
        if request is None:
            return None
        query_params = dict(getattr(request, "query_params", {}))
        if "tools" in query_params:
            tools = query_params["tools"].split(",")
            full_tool_names = [self.tool_prefix + tool for tool in tools]
            return full_tool_names
        return None

    @staticmethod
    def valid_and_unique_tool_name(
        tool_name: str, existing_tool_names: set[str]
    ) -> str:
        """
        Sanitizes a tool name to make it a valid MCP tool name (only
        alphanumeric characters, underscores, <= 128 characters)
        and is unique among the existing tool names.
        """
        tool_name = re.sub(r"[^a-zA-Z0-9]", "_", tool_name)
        tool_name = tool_name[:120]  # Leave room for suffix if needed
        tool_name_base = tool_name
        suffix = 1
        while tool_name in existing_tool_names:
            tool_name = tool_name_base + f"_{suffix}"
            suffix += 1
        return tool_name

    def get_tool_to_endpoint(self) -> dict[str, str]:
        """
        Gets all of the tools that are exposed by the Gradio app and also
        creates a mapping from the tool names to the endpoint names in the API docs.
        """
        tool_to_endpoint = {}
        for endpoint_name, endpoint_info in self.api_info["named_endpoints"].items():
            if endpoint_info["show_api"]:
                block_fn = self.get_block_fn_from_endpoint_name(endpoint_name)
                if block_fn is None or block_fn.fn is None:
                    continue
                fn_name = (
                    getattr(block_fn.fn, "__name__", None)
                    or (
                        hasattr(block_fn.fn, "__class__")
                        and getattr(block_fn.fn.__class__, "__name__", None)