Skip to main content
Glama

Katamari MCP Server

by ciphernaut
cli.py25 kB
""" Development CLI Tools for Capability Management Command-line interface for managing capabilities, validation, and testing. """ import argparse import asyncio import json import sys from pathlib import Path from typing import List, Optional from .validator import PackageValidator, ValidationResult from .testing import CapabilityTester, TestType class DevCLI: """Development CLI for Katamari MCP capabilities.""" def __init__(self): self.validator = PackageValidator() self.tester = CapabilityTester() def create_parser(self) -> argparse.ArgumentParser: """Create the argument parser for the CLI.""" parser = argparse.ArgumentParser( prog="katamari-dev", description="Katamari MCP Development Tools" ) subparsers = parser.add_subparsers(dest="command", help="Available commands") # Validate command validate_parser = subparsers.add_parser("validate", help="Validate a capability package") validate_parser.add_argument("path", help="Path to the capability package") validate_parser.add_argument("--output", "-o", help="Output file for validation report") validate_parser.add_argument("--json", action="store_true", help="Output JSON format") validate_parser.add_argument("--python-version", default="3.9", help="Target Python version") # Test command test_parser = subparsers.add_parser("test", help="Test capabilities") test_parser.add_argument("path", nargs="?", default=".", help="Path to test (default: current directory)") test_parser.add_argument("--suite", "-s", help="Specific test suite to run") test_parser.add_argument("--type", "-t", choices=[t.value for t in TestType], help="Test type filter") test_parser.add_argument("--isolated", action="store_true", help="Run tests in isolated environments") test_parser.add_argument("--output", "-o", help="Output file for test report") test_parser.add_argument("--json", action="store_true", help="Output JSON format") # List command list_parser = subparsers.add_parser("list", help="List available capabilities") list_parser.add_argument("path", nargs="?", default=".", help="Path to search (default: current directory)") list_parser.add_argument("--type", choices=["capability", "test"], help="Filter by type") # Create command create_parser = subparsers.add_parser("create", help="Create a new capability") create_parser.add_argument("name", help="Name of the capability") create_parser.add_argument("--path", "-p", default=".", help="Path to create capability (default: current directory)") create_parser.add_argument("--template", choices=["basic", "advanced", "web"], default="basic", help="Capability template") # Check command check_parser = subparsers.add_parser("check", help="Quick health check of the environment") check_parser.add_argument("--detailed", action="store_true", help="Show detailed information") return parser async def cmd_validate(self, args) -> int: """Execute the validate command.""" package_path = Path(args.path) if not package_path.exists(): print(f"❌ Error: Path '{package_path}' does not exist") return 1 if not package_path.is_dir(): print(f"❌ Error: Path '{package_path}' is not a directory") return 1 print(f"🔍 Validating package: {package_path}") # Update validator with target Python version self.validator.compatibility_validator.target_python_version = args.python_version # Perform validation result = self.validator.validate_package(package_path) # Output results if args.json: output_data = { "is_valid": result.is_valid, "package_hash": result.package_hash, "metadata": result.metadata, "issues": [issue.__dict__ for issue in result.issues] } if args.output: with open(args.output, 'w') as f: json.dump(output_data, f, indent=2) print(f"📄 JSON report saved to: {args.output}") else: print(json.dumps(output_data, indent=2)) else: report = self.validator.generate_report(result) if args.output: with open(args.output, 'w') as f: f.write(report) print(f"📄 Report saved to: {args.output}") else: print(report) return 0 if result.is_valid else 1 async def cmd_test(self, args) -> int: """Execute the test command.""" test_path = Path(args.path) if not test_path.exists(): print(f"❌ Error: Path '{test_path}' does not exist") return 1 print(f"🧪 Testing capabilities in: {test_path}") # Configure tester self.tester.isolated = args.isolated # Discover and register capabilities if test_path.is_dir(): # Look for capability directories for item in test_path.iterdir(): if item.is_dir() and (item / "__init__.py").exists(): suite = self.tester.register_capability(item) # Discover tests discovered_tests = self.tester.discover_tests(item) for test in discovered_tests: suite.add_test(test) # Run tests if args.suite: if args.suite not in self.tester.test_suites: print(f"❌ Error: Test suite '{args.suite}' not found") return 1 results = {args.suite: await self.tester.run_test_suite(args.suite)} else: results = await self.tester.run_all_tests() # Generate report if args.json: output_data = { "results": { suite: [result.to_dict() for result in suite_results] for suite, suite_results in results.items() } } if args.output: with open(args.output, 'w') as f: json.dump(output_data, f, indent=2) print(f"📄 JSON report saved to: {args.output}") else: print(json.dumps(output_data, indent=2)) else: report = self.tester.generate_report(results) if args.output: with open(args.output, 'w') as f: f.write(report) print(f"📄 Report saved to: {args.output}") else: print(report) # Cleanup self.tester.cleanup() # Return non-zero if any tests failed total_failed = sum( 1 for suite_results in results.values() for result in suite_results if result.status in ["failed", "error"] ) return 1 if total_failed > 0 else 0 def cmd_list(self, args) -> int: """Execute the list command.""" search_path = Path(args.path) if not search_path.exists(): print(f"❌ Error: Path '{search_path}' does not exist") return 1 print(f"📋 Listing items in: {search_path}") if args.type == "capability" or not args.type: print("\n🔧 Capabilities:") for item in search_path.rglob("*/"): if (item / "__init__.py").exists(): rel_path = item.relative_to(search_path) print(f" • {rel_path}") if args.type == "test" or not args.type: print("\n🧪 Test Files:") for test_file in search_path.rglob("test_*.py"): rel_path = test_file.relative_to(search_path) print(f" • {rel_path}") for test_file in search_path.rglob("*_test.py"): rel_path = test_file.relative_to(search_path) print(f" • {rel_path}") return 0 def cmd_create(self, args) -> int: """Execute the create command.""" create_path = Path(args.path) capability_path = create_path / args.name if capability_path.exists(): print(f"❌ Error: Capability '{args.name}' already exists") return 1 print(f"🏗️ Creating capability: {args.name}") # Create directory structure capability_path.mkdir(parents=True, exist_ok=True) # Create __init__.py init_content = self._get_capability_template(args.template, args.name) with open(capability_path / "__init__.py", 'w') as f: f.write(init_content) # Create test file test_content = self._get_test_template(args.template, args.name) with open(capability_path / f"test_{args.name}.py", 'w') as f: f.write(test_content) # Create README readme_content = self._get_readme_template(args.template, args.name) with open(capability_path / "README.md", 'w') as f: f.write(readme_content) print(f"✅ Capability '{args.name}' created successfully at: {capability_path}") print(f"📝 Next steps:") print(f" 1. Edit {capability_path / '__init__.py'} to implement your capability") print(f" 2. Run tests: katamari-dev test {capability_path}") print(f" 3. Validate: katamari-dev validate {capability_path}") return 0 def cmd_check(self, args) -> int: """Execute the check command.""" print("🔍 Katamari MCP Environment Check") print("=" * 40) # Check Python version import sys print(f"Python Version: {sys.version}") # Check dependencies try: import pydantic print(f"✅ Pydantic: {pydantic.__version__}") except ImportError: print("❌ Pydantic: Not installed") try: import mcp print(f"✅ MCP: {mcp.__version__}") except ImportError: print("❌ MCP: Not installed") # Check project structure current_path = Path.cwd() print(f"\n📁 Current Directory: {current_path}") required_files = ["pyproject.toml", "README.md"] for file in required_files: if (current_path / file).exists(): print(f"✅ {file}: Found") else: print(f"❌ {file}: Not found") # Check capabilities directory capabilities_dir = current_path / "katamari_mcp" / "capabilities" if capabilities_dir.exists(): capabilities = list(capabilities_dir.iterdir()) print(f"✅ Capabilities: {len(capabilities)} found") if args.detailed: for cap in capabilities: if cap.is_dir(): print(f" • {cap.name}") else: print("❌ Capabilities directory not found") # Check ACP components acp_dir = current_path / "katamari_mcp" / "acp" if acp_dir.exists(): acp_files = list(acp_dir.glob("*.py")) print(f"✅ ACP Components: {len(acp_files)} found") if args.detailed: for file in acp_files: print(f" • {file.name}") else: print("❌ ACP directory not found") return 0 def _get_capability_template(self, template_type: str, name: str) -> str: """Get capability template content.""" if template_type == "basic": return f'''""" {name} Capability Basic capability template for Katamari MCP. """ from typing import Any, Dict, List from mcp.types import Tool class {name.title()}Capability: """Basic {name} capability implementation.""" def __init__(self): self.name = "{name}" self.version = "1.0.0" async def get_tools(self) -> List[Tool]: """Get available tools from this capability.""" return [ Tool( name="{name}_basic", description="Basic {name} functionality", inputSchema={{ "type": "object", "properties": {{ "input": {{ "type": "string", "description": "Input for {name} operation" }} }}, "required": ["input"] }} ) ] async def handle_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]: """Handle a tool call.""" if tool_name == "{name}_basic": return await self._handle_basic(arguments.get("input")) else: raise ValueError(f"Unknown tool: {{tool_name}}") async def _handle_basic(self, input_data: str) -> Dict[str, Any]: """Handle basic {name} operation.""" # Basic processing implementation processed_data = input_data.strip().upper() if input_data else "" return {{ "success": True, "result": f"Processed: {{processed_data}}", "capability": self.name }} # Export the capability capability = {name.title()}Capability() ''' elif template_type == "advanced": return f'''""" {name} Capability (Advanced) Advanced capability template with error handling and logging. """ import logging from typing import Any, Dict, List, Optional from mcp.types import Tool logger = logging.getLogger(__name__) class {name.title()}Capability: """Advanced {name} capability with comprehensive features.""" def __init__(self): self.name = "{name}" self.version = "1.0.0" self.logger = logging.getLogger(f"capability.{{self.name}}") async def get_tools(self) -> List[Tool]: """Get available tools from this capability.""" return [ Tool( name="{name}_process", description="Process data with {name} capability", inputSchema={{ "type": "object", "properties": {{ "data": {{ "type": "string", "description": "Data to process" }}, "options": {{ "type": "object", "description": "Processing options" }} }}, "required": ["data"] }} ), Tool( name="{name}_validate", description="Validate input for {name} capability", inputSchema={{ "type": "object", "properties": {{ "input": {{ "type": "string", "description": "Input to validate" }} }}, "required": ["input"] }} ) ] async def handle_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]: """Handle a tool call with error handling.""" try: self.logger.info(f"Handling tool call: {{tool_name}}") if tool_name == "{name}_process": return await self._handle_process(arguments) elif tool_name == "{name}_validate": return await self._handle_validate(arguments) else: raise ValueError(f"Unknown tool: {{tool_name}}") except Exception as e: self.logger.error(f"Error in {{tool_name}}: {{e}}") return {{ "success": False, "error": str(e), "tool": tool_name }} async def _handle_process(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """Handle process operation.""" data = arguments.get("data") options = arguments.get("options", {{}}) # Advanced processing implementation self.logger.info(f"Processing data with options: {{options}}") processed_result = data if options.get("uppercase", False): processed_result = str(processed_result).upper() if options.get("reverse", False): processed_result = str(processed_result)[::-1] return {{ "success": True, "result": f"Processed: {{processed_result}}", "options_used": options, "capability": self.name }} async def _handle_validate(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """Handle validation operation.""" input_data = arguments.get("input") # Basic validation implementation is_valid = bool(input_data and len(input_data.strip()) > 0) return {{ "success": True, "valid": is_valid, "input": input_data, "capability": self.name }} # Export the capability capability = {name.title()}Capability() ''' else: # web template return f'''""" {name} Capability (Web) Web-focused capability template with HTTP handling. """ import aiohttp from typing import Any, Dict, List, Optional from mcp.types import Tool class {name.title()}Capability: """Web-focused {name} capability.""" def __init__(self): self.name = "{name}" self.version = "1.0.0" self.session: Optional[aiohttp.ClientSession] = None async def __aenter__(self): """Async context manager entry.""" self.session = aiohttp.ClientSession() return self async def __aexit__(self, exc_type, exc_val, exc_tb): """Async context manager exit.""" if self.session: await self.session.close() async def get_tools(self) -> List[Tool]: """Get available tools from this capability.""" return [ Tool( name="{name}_fetch", description="Fetch data from web API", inputSchema={{ "type": "object", "properties": {{ "url": {{ "type": "string", "description": "URL to fetch" }}, "method": {{ "type": "string", "enum": ["GET", "POST"], "default": "GET" }} }}, "required": ["url"] }} ) ] async def handle_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]: """Handle a tool call.""" if tool_name == "{name}_fetch": return await self._handle_fetch(arguments) else: raise ValueError(f"Unknown tool: {{tool_name}}") async def _handle_fetch(self, arguments: Dict[str, Any]) -> Dict[str, Any]: """Handle web fetch operation.""" url = arguments.get("url") method = arguments.get("method", "GET") if not self.session: self.session = aiohttp.ClientSession() try: async with self.session.request(method, url) as response: content = await response.text() return {{ "success": True, "status": response.status, "content": content[:1000], # Limit content size "url": url, "capability": self.name }} except Exception as e: return {{ "success": False, "error": str(e), "url": url, "capability": self.name }} # Export the capability capability = {name.title()}Capability() ''' def _get_test_template(self, template_type: str, name: str) -> str: """Get test template content.""" return f'''""" Tests for {name} Capability Test suite for the {name} capability. """ import pytest import asyncio from katamari_mcp.capabilities.{name} import capability class Test{name.title()}: """Test class for {name} capability.""" @pytest.fixture async def cap(self): """Fixture providing the capability instance.""" return capability @pytest.mark.asyncio async def test_get_tools(self, cap): """Test getting available tools.""" tools = await cap.get_tools() assert len(tools) > 0 assert all(hasattr(tool, 'name') for tool in tools) @pytest.mark.asyncio async def test_basic_functionality(self, cap): """Test basic functionality.""" tools = await cap.get_tools() if tools: tool_name = tools[0].name # Test with valid input result = await cap.handle_call(tool_name, {{"input": "test"}}) assert result is not None @pytest.mark.asyncio async def test_error_handling(self, cap): """Test error handling.""" # Test with invalid tool name with pytest.raises(ValueError): await cap.handle_call("invalid_tool", {{}}) @pytest.mark.asyncio async def test_input_validation(self, cap): """Test input validation.""" tools = await cap.get_tools() if tools: tool_name = tools[0].name # Test with missing required input result = await cap.handle_call(tool_name, {{}}) # Should handle gracefully (either raise error or return error result) assert result is not None ''' def _get_readme_template(self, template_type: str, name: str) -> str: """Get README template content.""" return f'''# {name.title()} Capability {template_type.title()} capability for Katamari MCP. ## Description This capability provides {name} functionality for the Katamari MCP system. ## Features - Basic {name} operations - Error handling and validation - Comprehensive test suite ## Usage ### Tools Available 1. `{name}_basic` - Basic {name} functionality ### Example Usage ```python # Import the capability from katamari_mcp.capabilities.{name} import capability # Get available tools tools = await capability.get_tools() # Use a tool result = await capability.handle_call("{name}_basic", {{ "input": "example data" }}) ``` ## Development ### Running Tests ```bash katamari-dev test {name} ``` ### Validation ```bash katamari-dev validate {name} ``` ## Requirements - Python 3.9+ - Katamari MCP framework ## License MIT License ''' async def run(self, args: Optional[List[str]] = None) -> int: """Run the CLI with the given arguments.""" parser = self.create_parser() parsed_args = parser.parse_args(args) if not parsed_args.command: parser.print_help() return 1 try: if parsed_args.command == "validate": return await self.cmd_validate(parsed_args) elif parsed_args.command == "test": return await self.cmd_test(parsed_args) elif parsed_args.command == "list": return self.cmd_list(parsed_args) elif parsed_args.command == "create": return self.cmd_create(parsed_args) elif parsed_args.command == "check": return self.cmd_check(parsed_args) else: print(f"❌ Unknown command: {parsed_args.command}") return 1 except KeyboardInterrupt: print("\n⚠️ Operation cancelled by user") return 130 except Exception as e: print(f"❌ Error: {e}") return 1 async def main(): """Main entry point for the CLI.""" cli = DevCLI() return await cli.run() if __name__ == "__main__": import sys sys.exit(asyncio.run(main()))

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/ciphernaut/katamari-mcp'

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