Spaces:
Sleeping
Sleeping
| #!/usr/bin/env python3 | |
| """ | |
| Command-line interface for the OpenProblems Spatial Transcriptomics MCP Server. | |
| """ | |
| import asyncio | |
| import click | |
| import logging | |
| import sys | |
| from pathlib import Path | |
| from .main import main as run_server | |
| def cli(verbose, config): | |
| """OpenProblems Spatial Transcriptomics MCP Server CLI.""" | |
| if verbose: | |
| logging.basicConfig(level=logging.DEBUG) | |
| else: | |
| logging.basicConfig(level=logging.INFO) | |
| if config: | |
| # TODO: Load configuration from file | |
| click.echo(f"Using configuration from: {config}") | |
| def serve(host, port, transport): | |
| """Start the MCP server.""" | |
| click.echo("π Starting OpenProblems Spatial Transcriptomics MCP Server") | |
| click.echo(f" Transport: {transport}") | |
| if transport == "http": | |
| click.echo(f" Host: {host}") | |
| click.echo(f" Port: {port}") | |
| click.echo(" Note: HTTP transport is not yet implemented") | |
| sys.exit(1) | |
| try: | |
| asyncio.run(run_server()) | |
| except KeyboardInterrupt: | |
| click.echo("\nπ Server stopped") | |
| except Exception as e: | |
| click.echo(f"β Server error: {e}", err=True) | |
| sys.exit(1) | |
| def test(): | |
| """Run the test suite.""" | |
| import subprocess | |
| click.echo("π§ͺ Running test suite...") | |
| try: | |
| result = subprocess.run(["pytest", "tests/", "-v"], | |
| capture_output=True, text=True) | |
| click.echo(result.stdout) | |
| if result.stderr: | |
| click.echo(result.stderr, err=True) | |
| if result.returncode == 0: | |
| click.echo("β All tests passed!") | |
| else: | |
| click.echo("β Some tests failed") | |
| sys.exit(1) | |
| except FileNotFoundError: | |
| click.echo("β pytest not found. Install with: pip install pytest", err=True) | |
| sys.exit(1) | |
| def demo(): | |
| """Run the interactive demo client.""" | |
| click.echo("π¬ Starting MCP client demo...") | |
| try: | |
| import subprocess | |
| result = subprocess.run([sys.executable, "examples/simple_client.py"]) | |
| sys.exit(result.returncode) | |
| except Exception as e: | |
| click.echo(f"β Demo error: {e}", err=True) | |
| sys.exit(1) | |
| def doctor(check_tools, check_deps): | |
| """Diagnose installation and configuration issues.""" | |
| click.echo("π OpenProblems MCP Server Health Check") | |
| click.echo("=" * 50) | |
| all_good = True | |
| # Check Python imports | |
| click.echo("\nπ¦ Python Dependencies:") | |
| dependencies = [ | |
| ("mcp", "MCP Python SDK"), | |
| ("yaml", "PyYAML"), | |
| ("docker", "Docker Python client"), | |
| ("pandas", "Pandas"), | |
| ("numpy", "NumPy"), | |
| ] | |
| for module, description in dependencies: | |
| try: | |
| __import__(module) | |
| click.echo(f" β {description}") | |
| except ImportError: | |
| click.echo(f" β {description} - not installed") | |
| all_good = False | |
| # Check external tools | |
| if check_tools: | |
| click.echo("\nπ οΈ External Tools:") | |
| tools = [ | |
| ("nextflow", "Nextflow workflow engine"), | |
| ("viash", "Viash component framework"), | |
| ("docker", "Docker containerization"), | |
| ("java", "Java runtime (required for Nextflow)"), | |
| ] | |
| import subprocess | |
| for tool, description in tools: | |
| try: | |
| result = subprocess.run([tool, "--version"], | |
| capture_output=True, timeout=10) | |
| if result.returncode == 0: | |
| click.echo(f" β {description}") | |
| else: | |
| click.echo(f" β {description} - not working properly") | |
| all_good = False | |
| except (subprocess.TimeoutExpired, FileNotFoundError): | |
| click.echo(f" β {description} - not found") | |
| all_good = False | |
| # Check directories | |
| click.echo("\nπ Directory Structure:") | |
| directories = ["data", "work", "logs", "cache"] | |
| for directory in directories: | |
| path = Path(directory) | |
| if path.exists(): | |
| if path.is_dir(): | |
| click.echo(f" β {directory}/ - exists") | |
| else: | |
| click.echo(f" β {directory} - exists but not a directory") | |
| all_good = False | |
| else: | |
| click.echo(f" β οΈ {directory}/ - missing (will be created)") | |
| try: | |
| path.mkdir(exist_ok=True) | |
| click.echo(f" Created {directory}/") | |
| except Exception as e: | |
| click.echo(f" Failed to create: {e}") | |
| all_good = False | |
| # Check server module | |
| click.echo("\nπ₯οΈ Server Module:") | |
| try: | |
| from . import main | |
| click.echo(" β MCP server module - importable") | |
| # Test basic functionality | |
| import asyncio | |
| async def test_handlers(): | |
| try: | |
| resources = await main.handle_list_resources() | |
| tools = await main.handle_list_tools() | |
| click.echo(f" β Server handlers - working ({len(resources)} resources, {len(tools)} tools)") | |
| except Exception as e: | |
| click.echo(f" β Server handlers - error: {e}") | |
| return False | |
| return True | |
| handler_ok = asyncio.run(test_handlers()) | |
| all_good = all_good and handler_ok | |
| except ImportError as e: | |
| click.echo(f" β MCP server module - import error: {e}") | |
| all_good = False | |
| # Summary | |
| click.echo("\n" + "=" * 50) | |
| if all_good: | |
| click.echo("β All checks passed! Your setup is ready.") | |
| else: | |
| click.echo("β Some issues found. Please fix them before running the server.") | |
| click.echo("\nFor help, see: docs/SETUP.md") | |
| sys.exit(1) | |
| def download_docs(): | |
| """Download and cache documentation from OpenProblems, Nextflow, and Viash.""" | |
| click.echo("π Downloading documentation from OpenProblems, Nextflow, and Viash...") | |
| async def download(): | |
| from .documentation_generator_simple import DocumentationGenerator | |
| try: | |
| generator = DocumentationGenerator() | |
| documentation = await generator.generate_all_documentation() | |
| click.echo("\nπ Documentation download complete!") | |
| total_chars = 0 | |
| for source, content in documentation.items(): | |
| chars = len(content) | |
| total_chars += chars | |
| click.echo(f" β {source}: {chars:,} characters") | |
| click.echo(f"\nπ Total: {total_chars:,} characters of documentation cached!") | |
| click.echo(" Documentation is now available in your MCP server resources.") | |
| except Exception as e: | |
| click.echo(f"β Failed to download documentation: {e}") | |
| sys.exit(1) | |
| asyncio.run(download()) | |
| def tool(tool_name, arguments): | |
| """Execute a specific MCP tool directly.""" | |
| click.echo(f"π§ Executing tool: {tool_name}") | |
| # Parse arguments (simple key=value format) | |
| tool_args = {} | |
| for arg in arguments: | |
| if "=" in arg: | |
| key, value = arg.split("=", 1) | |
| tool_args[key] = value | |
| else: | |
| click.echo(f"β Invalid argument format: {arg}") | |
| click.echo(" Use: key=value format") | |
| sys.exit(1) | |
| click.echo(f" Arguments: {tool_args}") | |
| async def run_tool(): | |
| from .main import handle_call_tool | |
| try: | |
| result = await handle_call_tool(tool_name, tool_args) | |
| click.echo("\nπ Result:") | |
| for item in result: | |
| click.echo(item.text) | |
| except Exception as e: | |
| click.echo(f"β Tool execution failed: {e}", err=True) | |
| sys.exit(1) | |
| asyncio.run(run_tool()) | |
| def web(port, share): | |
| """Launch the Gradio web interface for testing MCP tools.""" | |
| click.echo("π Starting OpenProblems MCP Server Web Interface...") | |
| click.echo(f" Port: {port}") | |
| if share: | |
| click.echo(" Sharing: Enabled (creating public link)") | |
| try: | |
| from .gradio_interface import launch_gradio_interface | |
| launch_gradio_interface(share=share, server_port=port) | |
| except ImportError: | |
| click.echo("β Gradio not installed. Install with: pip install gradio", err=True) | |
| sys.exit(1) | |
| except Exception as e: | |
| click.echo(f"β Web interface error: {e}", err=True) | |
| sys.exit(1) | |
| def info(): | |
| """Show server information and available tools/resources.""" | |
| click.echo("π OpenProblems Spatial Transcriptomics MCP Server") | |
| click.echo(" Version: 0.1.0") | |
| click.echo(" Protocol: Model Context Protocol (MCP)") | |
| click.echo(" Purpose: Spatial transcriptomics workflow automation") | |
| async def show_info(): | |
| from .main import handle_list_resources, handle_list_tools | |
| try: | |
| resources = await handle_list_resources() | |
| tools = await handle_list_tools() | |
| click.echo(f"\nπ Available Resources ({len(resources)}):") | |
| for resource in resources: | |
| click.echo(f" β’ {resource.name}") | |
| click.echo(f" URI: {resource.uri}") | |
| click.echo(f" Description: {resource.description}") | |
| click.echo() | |
| click.echo(f"π οΈ Available Tools ({len(tools)}):") | |
| for tool in tools: | |
| click.echo(f" β’ {tool.name}") | |
| click.echo(f" Description: {tool.description}") | |
| required = tool.inputSchema.get("required", []) | |
| if required: | |
| click.echo(f" Required parameters: {', '.join(required)}") | |
| click.echo() | |
| except Exception as e: | |
| click.echo(f"β Error getting server info: {e}", err=True) | |
| asyncio.run(show_info()) | |
| def main(): | |
| """Main CLI entry point.""" | |
| cli() | |
| if __name__ == "__main__": | |
| main() | |