Skip to main content
Glama

get_server_tools

Discover available tools on downstream MCP servers with permission-based filtering. Use to find specific tools you can access and execute through the gateway.

Instructions

Discover tools available on a downstream MCP server accessed through this gateway. Returns only tools you have permission to use (filtered by policy rules). Use the returned tool definitions to call execute_tool.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
agent_idNoYour agent identifier (leave empty if not provided to you)
serverNoServer name from list_servers
namesNoFilter: comma-separated tool names
patternNoFilter: wildcard pattern (e.g., 'get_*')
max_schema_tokensNoLimit total tokens in returned schemas

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The main handler function for the 'get_server_tools' MCP tool. Retrieves tools from the specified downstream server via ProxyManager, applies policy-based filtering using PolicyEngine, supports filtering by explicit names, wildcard patterns, and token budgets, and returns a structured response with available tools.
    @gateway.tool
    async def get_server_tools(
        agent_id: Annotated[Optional[str], "Your agent identifier (leave empty if not provided to you)"] = None,
        server: Annotated[str, "Server name from list_servers"] = "",
        names: Annotated[Optional[str], "Filter: comma-separated tool names"] = None,
        pattern: Annotated[Optional[str], "Filter: wildcard pattern (e.g., 'get_*')"] = None,
        max_schema_tokens: Annotated[Optional[int], "Limit total tokens in returned schemas"] = None
    ) -> dict:
        """Discover tools available on a downstream MCP server accessed through this gateway. Returns only tools you have permission to use (filtered by policy rules). Use the returned tool definitions to call execute_tool."""
        # Defensive check (middleware should have resolved agent_id)
        if agent_id is None:
            raise ToolError("Internal error: agent_id not resolved by middleware")
    
        # Check for config changes (fallback mechanism for when file watching doesn't work)
        if _check_config_changes_fn:
            try:
                _check_config_changes_fn()
            except Exception:
                pass  # Don't let config check errors break tool execution
    
        # Parse comma-separated names string into list
        names_list: Optional[list[str]] = None
        if names is not None and names.strip():
            # Split by comma and trim whitespace from each name
            names_list = [name.strip() for name in names.split(",") if name.strip()]
            # If we ended up with an empty list after filtering, treat as None
            if not names_list:
                names_list = None
    
        # Get configurations from module-level storage
        policy_engine = _policy_engine
        proxy_manager = _proxy_manager
    
        if not policy_engine:
            return GetServerToolsResponse(
                tools=[],
                server=server,
                total_available=0,
                returned=0,
                tokens_used=None,
                error="PolicyEngine not initialized in gateway state"
            ).model_dump()
    
        if not proxy_manager:
            return GetServerToolsResponse(
                tools=[],
                server=server,
                total_available=0,
                returned=0,
                tokens_used=None,
                error="ProxyManager not initialized in gateway state"
            ).model_dump()
    
        # Validate agent can access server
        if not policy_engine.can_access_server(agent_id, server):
            return GetServerToolsResponse(
                tools=[],
                server=server,
                total_available=0,
                returned=0,
                tokens_used=None,
                error=f"Access denied: Agent '{agent_id}' cannot access server '{server}'"
            ).model_dump()
    
        # Get tools from downstream server
        try:
            all_tools = await proxy_manager.list_tools(server)
        except KeyError:
            return GetServerToolsResponse(
                tools=[],
                server=server,
                total_available=0,
                returned=0,
                tokens_used=None,
                error=f"Server '{server}' not found in configured servers"
            ).model_dump()
        except RuntimeError as e:
            return GetServerToolsResponse(
                tools=[],
                server=server,
                total_available=0,
                returned=0,
                tokens_used=None,
                error=f"Server unavailable: {str(e)}"
            ).model_dump()
        except Exception as e:
            return GetServerToolsResponse(
                tools=[],
                server=server,
                total_available=0,
                returned=0,
                tokens_used=None,
                error=f"Failed to retrieve tools: {str(e)}"
            ).model_dump()
    
        total_available = len(all_tools)
    
        # Filter tools based on criteria
        filtered_tools = []
        token_count = 0
    
        for tool in all_tools:
            tool_name = tool.name if hasattr(tool, 'name') else str(tool)
    
            # Filter by explicit names list
            if names_list is not None and tool_name not in names_list:
                continue
    
            # Filter by wildcard pattern
            if pattern is not None and not _matches_pattern(tool_name, pattern):
                continue
    
            # Filter by policy permissions
            if not policy_engine.can_access_tool(agent_id, server, tool_name):
                continue
    
            # Check token budget limit
            if max_schema_tokens is not None:
                tool_tokens = _estimate_tool_tokens(tool)
                if token_count + tool_tokens > max_schema_tokens:
                    # Stop adding tools - budget exceeded
                    break
                token_count += tool_tokens
    
            # Convert tool to ToolDefinition
            tool_definition = ToolDefinition(
                name=tool_name,
                description=tool.description if hasattr(tool, 'description') and tool.description else "",
                inputSchema=tool.inputSchema if hasattr(tool, 'inputSchema') else {}
            )
    
            filtered_tools.append(tool_definition)
    
        return GetServerToolsResponse(
            tools=filtered_tools,
            server=server,
            total_available=total_available,
            returned=len(filtered_tools),
            tokens_used=token_count if max_schema_tokens is not None else None
        ).model_dump()
  • Pydantic models defining the structure of tool definitions (ToolDefinition) and the response format (GetServerToolsResponse) for the get_server_tools tool. Input parameters are defined via Annotated types in the handler function signature.
    class ToolDefinition(BaseModel):
        """Tool definition from downstream server."""
        name: Annotated[str, Field(description="Tool name (use in execute_tool)")]
        description: Annotated[str, Field(description="What this tool does")]
        inputSchema: Annotated[dict, Field(description="JSON Schema defining required/optional parameters for execute_tool args")]
    
    
    class GetServerToolsResponse(BaseModel):
        """Response from get_server_tools."""
        tools: Annotated[list[ToolDefinition], Field(description="Tool definitions you can access")]
        server: Annotated[str, Field(description="Queried server name")]
        total_available: Annotated[int, Field(description="Total tools on server (may exceed returned if filtered by permissions/criteria)")]
        returned: Annotated[int, Field(description="Count of tools returned (less than total_available is normal due to filtering)")]
        tokens_used: Annotated[Optional[int], Field(description="Tokens used in schemas (if max_schema_tokens was set)")] = None
        error: Annotated[Optional[str], Field(description="Error message if request failed")] = None
  • src/gateway.py:291-291 (registration)
    FastMCP decorator that registers the get_server_tools function as a tool on the gateway server.
    @gateway.tool
  • Helper function used by get_server_tools to match tool names against wildcard patterns (e.g., 'get_*') using fnmatch.
    def _matches_pattern(tool_name: str, pattern: str) -> bool:
        """Check if tool name matches wildcard pattern.
    
        Uses glob-style pattern matching:
        - * matches any sequence of characters
        - ? matches any single character
        - [seq] matches any character in seq
        - [!seq] matches any character not in seq
    
        Args:
            tool_name: Name of the tool to match
            pattern: Pattern with wildcards (e.g., "get_*", "*_user")
    
        Returns:
            True if tool_name matches pattern, False otherwise
    
        Example:
            >>> _matches_pattern("get_user", "get_*")
            True
            >>> _matches_pattern("delete_user", "get_*")
            False
            >>> _matches_pattern("list_items", "*_items")
            True
        """
        return fnmatch.fnmatch(tool_name, pattern)
  • Helper function used by get_server_tools to estimate the token count of a tool definition for enforcing max_schema_tokens budget limits.
    def _estimate_tool_tokens(tool: Any) -> int:
        """Estimate token count for a tool definition.
    
        Estimates tokens based on name, description, and input schema JSON length.
        Uses rough approximation: characters / 4 = tokens (typical for English text).
    
        Args:
            tool: Tool object with name, description, and inputSchema attributes
    
        Returns:
            Estimated token count for the tool definition
    
        Example:
            >>> tool = Tool(name="get_user", description="Get user by ID", inputSchema={...})
            >>> _estimate_tool_tokens(tool)
            42
        """
        # Count name length
        name_len = len(tool.name) if hasattr(tool, 'name') and tool.name else 0
    
        # Count description length
        desc_len = len(tool.description) if hasattr(tool, 'description') and tool.description else 0
    
        # Count input schema length (convert to string for estimation)
        schema_len = 0
        if hasattr(tool, 'inputSchema') and tool.inputSchema:
            # Convert schema dict to string for rough character count
            import json
            try:
                schema_len = len(json.dumps(tool.inputSchema))
            except Exception:
                # If serialization fails, use a default estimate
                schema_len = 100
    
        # Total characters / 4 = rough token estimate
        total_chars = name_len + desc_len + schema_len
        return max(1, total_chars // 4)
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It effectively describes key behaviors: the tool returns filtered results based on permissions ('Returns only tools you have permission to use (filtered by policy rules)'), and it explains the output's purpose. However, it doesn't mention potential limitations like rate limits, error conditions, or whether the operation is idempotent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely concise with just two sentences that each serve a clear purpose. The first sentence states the tool's function and scope, while the second provides crucial usage guidance. There's no wasted language or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given that there's an output schema (which means the description doesn't need to explain return values), no annotations, and 100% schema coverage, the description provides exactly what's needed: clear purpose, usage guidance, and key behavioral context about permission filtering. It's complete for this tool's complexity level.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 100%, so all parameters are documented in the schema. The description doesn't add any specific parameter information beyond what's already in the schema descriptions. According to the scoring rules, when schema coverage is high (>80%), the baseline is 3 even with no parameter info in the description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Discover tools available'), identifies the target resource ('downstream MCP server accessed through this gateway'), and distinguishes it from siblings by mentioning its relationship to execute_tool. It goes beyond a simple restatement of the name to explain the discovery function.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use this tool ('Discover tools available on a downstream MCP server') and how to use its output ('Use the returned tool definitions to call execute_tool'). It clearly positions this as a prerequisite discovery step before using execute_tool, which is a sibling tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/roddutra/agent-mcp-gateway'

If you have feedback or need assistance with the MCP directory API, please join our Discord server