Skip to main content
Glama
tool.py12.5 kB
# # MCP Foxxy Bridge - Tool Management Commands # # Copyright (C) 2024 Billy Bryant # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. # """Tool discovery and testing CLI commands.""" import argparse import json import logging from pathlib import Path from typing import Any import aiohttp from rich.console import Console from rich.prompt import Confirm from mcp_foxxy_bridge.cli.api_client import get_api_client_from_config from mcp_foxxy_bridge.cli.formatters import ToolFormatter async def handle_tool_list( args: Any, config_path: Path, config_dir: Path, console: Console, logger: logging.Logger, ) -> None: """Handle tool listing command from Click CLI. Args: args: Click command arguments containing server, format, and tag filters config_path: Path to the configuration file config_dir: Configuration directory (unused) console: Rich console for output logger: Logger for error reporting Lists available tools from MCP servers with filtering and formatting options. """ # Convert to argparse-style namespace for compatibility argparse_args = argparse.Namespace( tool_command="list", server=getattr(args, "server", None), format=getattr(args, "format", "table"), tag=getattr(args, "tag", None), ) await _tool_list(argparse_args, config_path, console, logger) async def handle_tool_command( args: argparse.Namespace, config_path: Path, config_dir: Path, console: Console, logger: logging.Logger, ) -> None: """Handle tool discovery, testing, and management commands. Args: args: Command line arguments with tool_command subcommand config_path: Path to the configuration file config_dir: Configuration directory (unused) console: Rich console for output logger: Logger for error reporting Routes to appropriate tool subcommand handler (list, test, call, search). """ # Check if no subcommand was provided if not hasattr(args, "tool_command") or args.tool_command is None: console.print("[yellow]Usage: foxxy-bridge tool <command>[/yellow]") console.print("Available commands: list, test, call, search") return if args.tool_command == "list": await _tool_list(args, config_path, console, logger) elif args.tool_command == "test": await _tool_test(args, config_path, console, logger) elif args.tool_command == "call": await _tool_call(args, config_path, console, logger) elif args.tool_command == "search": await _tool_search(args, config_path, console, logger) else: console.print(f"[red]Unknown tool command: {args.tool_command}[/red]") async def _tool_list( args: argparse.Namespace, config_path: Path, console: Console, logger: logging.Logger, ) -> None: """List available tools from MCP servers. Args: args: Command line arguments with server, format, and tag filters config_path: Path to the configuration file console: Rich console for output logger: Logger for error reporting Supports filtering by server, tag, or showing all tools. Output formats include table and JSON. """ """List available tools.""" try: api_client = get_api_client_from_config(str(config_path), console) if args.server: # List tools for specific server tools_data = await api_client.list_server_tools(args.server) elif args.tag: # List tools by tag tools_data = await api_client.list_tools_by_tag(args.tag) else: # List all tools tools_data = await api_client.list_all_tools() if args.format == "json": console.print(json.dumps(tools_data, indent=2)) else: ToolFormatter.format_tools_table(tools_data, console) # Show summary console.print(f"\n[dim]Total: {len(tools_data)} tool(s)[/dim]") except aiohttp.ClientError as e: console.print(f"[red]Failed to list tools: {e}[/red]") except Exception as e: console.print(f"[red]Error: {e}[/red]") logger.exception("Failed to list tools") async def _tool_test( args: argparse.Namespace, config_path: Path, console: Console, logger: logging.Logger, ) -> None: """Test connectivity and functionality of MCP server tools. Args: args: Command line arguments with server and test options config_path: Path to the configuration file console: Rich console for output logger: Logger for error reporting Tests that tools are available and callable from the specified server. Useful for verifying server configuration and connectivity. """ """Test tool functionality.""" try: api_client = get_api_client_from_config(str(config_path), console) # First, find the tool if args.server: tools_data = await api_client.list_server_tools(args.server) else: tools_data = await api_client.list_all_tools() matching_tools = [tool for tool in tools_data if tool.get("name") == args.name] if not matching_tools: console.print(f"[red]Tool '{args.name}' not found[/red]") return if len(matching_tools) > 1 and not args.server: console.print(f"[yellow]Multiple tools named '{args.name}' found:[/yellow]") for tool in matching_tools: console.print(f" - {tool.get('server', 'unknown')}") console.print("[yellow]Please specify --server to disambiguate[/yellow]") return tool = matching_tools[0] server_name = tool.get("server", "unknown") if args.dry_run: console.print( f"[blue]Would test tool '[cyan]{args.name}[/cyan]' on server '[cyan]{server_name}[/cyan]'[/blue]" ) console.print(f"Description: {tool.get('description', 'No description')}") if "inputSchema" in tool: console.print("Input Schema:") console.print(json.dumps(tool["inputSchema"], indent=2)) return # Perform basic test (call without arguments) console.print(f"Testing tool '[cyan]{args.name}[/cyan]' on server '[cyan]{server_name}[/cyan]'...") try: result = await api_client.call_tool(server_name, args.name, {}) ToolFormatter.format_tool_call_result(result, console) except aiohttp.ClientError as e: # This is expected for tools that require arguments if "arguments" in str(e).lower() or "required" in str(e).lower(): console.print("[yellow]Tool requires arguments (this is normal)[/yellow]") console.print("Tool test completed - server connection OK") else: console.print(f"[red]Tool test failed: {e}[/red]") except Exception as e: console.print(f"[red]Error: {e}[/red]") logger.exception("Failed to test tool") async def _tool_call( args: argparse.Namespace, config_path: Path, console: Console, logger: logging.Logger, ) -> None: """Execute a specific tool with provided arguments. Args: args: Command line arguments with server, tool name, and arguments config_path: Path to the configuration file console: Rich console for output logger: Logger for error reporting Calls the specified tool on the given server with provided arguments. Prompts for confirmation if force flag is not used. """ """Execute a tool with arguments.""" try: api_client = get_api_client_from_config(str(config_path), console) # Parse arguments arguments = {} if args.args: try: arguments = json.loads(args.args) except json.JSONDecodeError as e: console.print(f"[red]Invalid JSON arguments: {e}[/red]") return elif args.input_file: try: # Read file content arguments = json.loads(Path(args.input_file).read_text()) except (FileNotFoundError, json.JSONDecodeError) as e: console.print(f"[red]Error reading input file: {e}[/red]") return # Find the tool if server not specified if not args.server: tools_data = await api_client.list_all_tools() matching_tools = [tool for tool in tools_data if tool.get("name") == args.name] if not matching_tools: console.print(f"[red]Tool '{args.name}' not found[/red]") return if len(matching_tools) > 1: console.print(f"[yellow]Multiple tools named '{args.name}' found:[/yellow]") for tool in matching_tools: console.print(f" - {tool.get('server', 'unknown')}") console.print("[yellow]Please specify --server to disambiguate[/yellow]") return server_name = matching_tools[0].get("server", "unknown") else: server_name = args.server # Show what we're about to do console.print(f"Calling tool '[cyan]{args.name}[/cyan]' on server '[cyan]{server_name}[/cyan]'") if arguments: console.print(f"Arguments: {json.dumps(arguments, indent=2)}") # Confirm execution for potentially dangerous operations if not Confirm.ask("Execute tool?"): console.print("[yellow]Operation cancelled[/yellow]") return # Execute the tool with console.status("Executing tool..."): result = await api_client.call_tool(server_name, args.name, arguments) ToolFormatter.format_tool_call_result(result, console) except aiohttp.ClientError as e: console.print(f"[red]Tool execution failed: {e}[/red]") except Exception as e: console.print(f"[red]Error: {e}[/red]") logger.exception("Failed to execute tool") async def _tool_search( args: argparse.Namespace, config_path: Path, console: Console, logger: logging.Logger, ) -> None: """Search for tools matching specified criteria. Args: args: Command line arguments with search pattern and filters config_path: Path to the configuration file console: Rich console for output logger: Logger for error reporting Searches tool names and descriptions for matching patterns. Supports filtering by server and output formatting options. """ try: api_client = get_api_client_from_config(str(config_path), console) # Get tools to search if args.server: tools_data = await api_client.list_server_tools(args.server) else: tools_data = await api_client.list_all_tools() # Filter tools by search pattern pattern_lower = args.pattern.lower() matching_tools = [] for tool in tools_data: name = tool.get("name", "").lower() description = tool.get("description", "").lower() if pattern_lower in name or (args.description and pattern_lower in description): matching_tools.append(tool) if not matching_tools: console.print(f"[yellow]No tools found matching '[cyan]{args.pattern}[/cyan]'[/yellow]") return console.print(f"Found {len(matching_tools)} tool(s) matching '[cyan]{args.pattern}[/cyan]':") ToolFormatter.format_tools_table(matching_tools, console) except aiohttp.ClientError as e: console.print(f"[red]Failed to search tools: {e}[/red]") except Exception as e: console.print(f"[red]Error: {e}[/red]") logger.exception("Failed to search 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/billyjbryant/mcp-foxxy-bridge'

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