Skip to main content
Glama
server_standalone.py9.24 kB
#!/usr/bin/env python3 # /// script # requires-python = ">=3.8" # dependencies = [ # "mcp", # "pillow", # "matplotlib", # "numpy", # ] # /// """ Standalone Mind Map MCP Server Supports running from GitHub URL with uv run This version downloads the src modules from GitHub if they're not available locally. """ import asyncio import base64 import json import argparse import sys import os import urllib.request import tempfile import importlib.util import time # Set UTF-8 encoding for Windows if sys.platform == 'win32': os.environ['PYTHONIOENCODING'] = 'utf-8' if hasattr(sys.stdout, 'reconfigure'): sys.stdout.reconfigure(encoding='utf-8') if hasattr(sys.stderr, 'reconfigure'): sys.stderr.reconfigure(encoding='utf-8') from contextlib import asynccontextmanager from typing import Any, Dict, List, Optional from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import ( Tool, TextContent, ImageContent, EmbeddedResource, CallToolResult, CallToolRequest ) # Try to import src modules, if not available, download from GitHub def ensure_src_modules(): """Ensure src modules are available, download from GitHub if needed""" try: from src import mind_map_center from src import mind_map_free from src import mind_map_horizontal return mind_map_center, mind_map_free, mind_map_horizontal except ImportError: # If running from GitHub URL, download src modules with caching import hashlib # Use user's cache directory for persistent storage if sys.platform == 'win32': cache_base = os.path.join(os.environ.get('LOCALAPPDATA', tempfile.gettempdir()), 'mind-map-mcp') else: cache_base = os.path.join(os.path.expanduser('~'), '.cache', 'mind-map-mcp') cache_dir = os.path.join(cache_base, 'src') os.makedirs(cache_dir, exist_ok=True) base_url = "https://raw.githubusercontent.com/sawyer-shi/mind-map-mcp/master/src/" modules = ["mind_map_center.py", "mind_map_free.py", "mind_map_horizontal.py"] # Create __init__.py if it doesn't exist init_file = os.path.join(cache_dir, "__init__.py") if not os.path.exists(init_file): with open(init_file, "w", encoding="utf-8") as f: f.write("") # Download modules (with caching) for module_name in modules: file_path = os.path.join(cache_dir, module_name) url = base_url + module_name # Only download if file doesn't exist or is old (older than 1 day) should_download = True if os.path.exists(file_path): file_age = time.time() - os.path.getmtime(file_path) if file_age < 86400: # 1 day should_download = False if should_download: try: print(f"Downloading {module_name} from GitHub...", file=sys.stderr) with urllib.request.urlopen(url, timeout=10) as response: content = response.read().decode('utf-8') with open(file_path, "w", encoding="utf-8") as f: f.write(content) except Exception as e: if not os.path.exists(file_path): print(f"Failed to download {module_name}: {e}", file=sys.stderr) raise else: print(f"Download failed, using cached {module_name}", file=sys.stderr) # Add to sys.path and import sys.path.insert(0, cache_base) from src import mind_map_center from src import mind_map_free from src import mind_map_horizontal return mind_map_center, mind_map_free, mind_map_horizontal # Initialize modules try: mind_map_center, mind_map_free, mind_map_horizontal = ensure_src_modules() except Exception as e: print(f"Error loading modules: {e}", file=sys.stderr) print("Please ensure you have cloned the repository locally.", file=sys.stderr) sys.exit(1) # Initialize the MCP server server = Server("mind-map-mcp") # Helper function to execute the tool logic and convert to MCP response def execute_tool(tool_class, markdown_content: str) -> List[Any]: tool_instance = tool_class() params = { "markdown_content": markdown_content, "filename": "mindmap" } generator = tool_instance._invoke(params) content = [] try: for message in generator: if message["type"] == "blob": blob_data = message["blob"] mime_type = message["meta"].get("mime_type", "image/png") encoded_data = base64.b64encode(blob_data).decode("utf-8") content.append(ImageContent( type="image", data=encoded_data, mimeType=mime_type )) elif message["type"] == "text": content.append(TextContent( type="text", text=message["text"] )) elif message["type"] == "json": json_str = json.dumps(message["data"], ensure_ascii=False, indent=2) content.append(TextContent( type="text", text=f"Metadata:\n{json_str}" )) except Exception as e: content.append(TextContent( type="text", text=f"Error executing tool: {str(e)}" )) return content @server.list_tools() async def handle_list_tools() -> List[Tool]: return [ Tool( name="create_center_mindmap", description="Create a radial/center layout mind map from Markdown text. Best for core-concept maps.", inputSchema={ "type": "object", "properties": { "markdown_content": { "type": "string", "description": "The markdown content to visualize as a mind map. Use headers (#) or list items (-/1.) to define hierarchy." } }, "required": ["markdown_content"] } ), Tool( name="create_horizontal_mindmap", description="Create a horizontal (left-to-right) layout mind map from Markdown text. Best for timelines or process flows.", inputSchema={ "type": "object", "properties": { "markdown_content": { "type": "string", "description": "The markdown content to visualize as a mind map." } }, "required": ["markdown_content"] } ), Tool( name="create_free_mindmap", description="Create a smart/free structure mind map. Automatically chooses between Center and Horizontal layouts based on complexity.", inputSchema={ "type": "object", "properties": { "markdown_content": { "type": "string", "description": "The markdown content to visualize as a mind map." } }, "required": ["markdown_content"] } ) ] @server.call_tool() async def handle_call_tool(name: str, arguments: dict | None) -> List[TextContent | ImageContent | EmbeddedResource]: if not arguments: raise ValueError("Missing arguments") markdown_content = arguments.get("markdown_content") if not markdown_content: raise ValueError("Missing markdown_content") if name == "create_center_mindmap": tool_class = mind_map_center.get_tool() return execute_tool(tool_class, markdown_content) elif name == "create_horizontal_mindmap": tool_class = mind_map_horizontal.get_tool() return execute_tool(tool_class, markdown_content) elif name == "create_free_mindmap": tool_class = mind_map_free.get_tool() return execute_tool(tool_class, markdown_content) else: raise ValueError(f"Unknown tool: {name}") def run_stdio(): """Run the server using stdio transport""" async def _run(): async with stdio_server() as (read, write): await server.run(read, write, server.create_initialization_options()) asyncio.run(_run()) if __name__ == "__main__": parser = argparse.ArgumentParser(description="Mind Map MCP Server (Standalone)") parser.add_argument("mode", nargs="?", default="stdio", choices=["stdio"], help="Mode to run the server in") args = parser.parse_args() if args.mode == "stdio": run_stdio() else: print("Only stdio mode is supported in standalone version", file=sys.stderr) sys.exit(1)

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/sawyer-shi/mind-map-mcp'

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