Skip to main content
Glama

ASUS Merlin Router MCP Server

by kcsoukup
MCP_SETUP_GUIDE.md26.8 kB
# MCP Setup Guide: Deep Dive A comprehensive guide to understanding and setting up Model Context Protocol (MCP) servers with Claude Code. --- ## Table of Contents 1. [What is MCP?](#what-is-mcp) 2. [MCP Architecture](#mcp-architecture) 3. [How Claude Code Uses MCP Servers](#how-claude-code-uses-mcp-servers) 4. [Setting Up an MCP Server](#setting-up-an-mcp-server) 5. [Configuration Deep Dive](#configuration-deep-dive) 6. [Testing and Debugging](#testing-and-debugging) 7. [Building Your Own MCP Servers](#building-your-own-mcp-servers) 8. [Troubleshooting](#troubleshooting) --- ## What is MCP? **Model Context Protocol (MCP)** is an open protocol that allows AI assistants like Claude to interact with external tools and data sources in a standardized way. ### Key Concepts: - **MCP Server**: A separate process that exposes tools/resources to Claude - **MCP Client**: Claude Code acts as the client, discovering and calling tools - **Tools**: Functions the MCP server provides (e.g., read file, query database, control router) - **Resources**: Data sources the server can provide (files, API responses, etc.) - **Prompts**: Pre-defined prompt templates the server can offer ### Why MCP? - **Extensibility**: Add new capabilities to Claude without modifying Claude itself - **Security**: Servers run in isolated processes with explicit permissions - **Standardization**: Single protocol for many different integrations - **Language Agnostic**: Write servers in any language (Python, TypeScript, Go, etc.) --- ## MCP Architecture ``` ┌─────────────────┐ │ Claude Code │ (MCP Client) │ (AI Agent) │ └────────┬────────┘ │ JSON-RPC over stdio │ ┌────┴─────┬──────────┬──────────┐ │ │ │ │ ┌───▼───┐ ┌──▼───┐ ┌───▼───┐ ┌───▼───┐ │ MCP │ │ MCP │ │ MCP │ │ MCP │ │Server │ │Server│ │Server │ │Server │ │ 1 │ │ 2 │ │ 3 │ │ 4 │ └───┬───┘ └──┬───┘ └───┬───┘ └───┬───┘ │ │ │ │ ▼ ▼ ▼ ▼ Router Database Files External API ``` ### Communication Flow: 1. **Startup**: Claude Code reads `~/.claude/settings.json` 2. **Launch**: Spawns each configured MCP server as a subprocess 3. **Discovery**: Servers advertise their tools via `list_tools()` method 4. **Usage**: Claude calls tools via JSON-RPC messages over stdin/stdout 5. **Results**: Server responds with structured data Claude can use 6. **Lifecycle**: Servers run for the duration of the Claude Code session ### Protocol Details: - **Transport**: Standard input/output (stdio) - **Format**: JSON-RPC 2.0 messages - **Lifecycle**: Request/response pattern - **Error Handling**: Structured error responses with codes --- ## How Claude Code Uses MCP Servers ### Configuration Discovery MCP server configuration locations vary depending on your Claude installation type: #### Claude Code (Native Installation) - **Configuration**: `~/.claude.json` (consolidated configuration file) - **Project-specific MCP servers**: Stored under `projects.<project-path>.mcpServers` in `~/.claude.json` - **User-level settings**: `~/.claude/settings.json` (optional, for global MCP servers) - **Example structure**: ```json { "installMethod": "native", "projects": { "/path/to/project": { "mcpServers": { "server-name": { ... } } } } } ``` #### Claude Code (NPM Installation) - **Primary**: `~/.claude/settings.json` - **Alternative**: `~/.config/claude-code/config.json` - **Project-specific**: May use `.claude/settings.json` within project directories #### Claude Desktop - **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` - **Linux**: `~/.config/Claude/claude_desktop_config.json` - **Configuration format**: ```json { "mcpServers": { "server-name": { "command": "/path/to/command", "args": ["arg1", "arg2"], "env": { "VAR": "value" } } } } ``` **Note**: Native Claude Code installations consolidate project settings (including MCP servers, permissions, and history) into `~/.claude.json`, while NPM installations and Claude Desktop use separate configuration files. ### Server Lifecycle ```python # 1. Claude Code starts up and reads settings.json { "mcpServers": { "server-name": { ... } } } # 2. Claude Code spawns server process subprocess = spawn(command, args, env) # 3. Server initializes and registers tools @app.list_tools() async def list_tools() -> list[Tool]: return [Tool(...), Tool(...)] # 4. Claude can now call tools result = await server.call_tool("tool_name", {"param": "value"}) # 5. Server runs until Claude Code exits ``` ### When Tools Are Available - **Immediately**: After Claude Code starts and servers initialize - **During conversation**: Claude decides when to use tools based on context - **User requests**: You can explicitly ask Claude to use specific tools - **Automatic**: Claude may proactively use tools when relevant --- ## Setting Up an MCP Server ### Step-by-Step: ASUS Router Example #### 1. Prepare the Environment ```bash # Navigate to project directory cd /path/to/mcp-asus-merlin # Create virtual environment with Python 3.11+ python3.11 -m venv venv # Activate virtual environment source venv/bin/activate # Install dependencies pip install -r requirements.txt ``` **Why virtual environment?** - Isolates dependencies from system Python - Prevents version conflicts - Makes deployment consistent - Easy to recreate on other machines #### 2. Configure Server Credentials Create/edit `.env` file: ```bash ROUTER_HOST=192.168.1.1 ROUTER_PORT=22 ROUTER_USER=admin ROUTER_PASSWORD=your_password_here # OR ROUTER_KEY_FILE=/home/user/.ssh/id_rsa ``` **Security Best Practices:** - Use SSH keys instead of passwords when possible - Never commit `.env` files to version control - Keep credentials in environment variables, not hardcoded - Use least-privilege accounts (create dedicated user if possible) #### 3. Test Server Manually ```bash # Export environment variables export $(cat .env | xargs) # Run server in stdio mode python asus_merlin_mcp.py ``` The server should start and wait for input. You'll see logging output if configured. **Press Ctrl+C to exit when done testing.** #### 4. Configure Claude Code/Desktop The configuration location depends on your installation type: ##### For Claude Code (Native Installation) The MCP server will be automatically added to your project in `~/.claude.json`: ```json { "installMethod": "native", "projects": { "/path/to/mcp-asus-merlin": { "mcpServers": { "asus-router": { "command": "/path/to/mcp-asus-merlin/venv/bin/python", "args": ["/path/to/mcp-asus-merlin/asus_merlin_mcp.py"], "env": { "ROUTER_HOST": "192.168.1.1", "ROUTER_PORT": "22", "ROUTER_USER": "admin", "ROUTER_KEY_FILE": "/home/user/.ssh/id_rsa" } } } } } } ``` ##### For Claude Code (NPM Installation) Edit `~/.claude/settings.json`: ```json { "mcpServers": { "asus-router": { "command": "/path/to/mcp-asus-merlin/venv/bin/python", "args": ["/path/to/mcp-asus-merlin/asus_merlin_mcp.py"], "env": { "ROUTER_HOST": "192.168.1.1", "ROUTER_PORT": "22", "ROUTER_USER": "admin", "ROUTER_KEY_FILE": "/home/user/.ssh/id_rsa" } } } } ``` ##### For Claude Desktop Edit the appropriate configuration file for your OS: **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json` **Windows**: `%APPDATA%\Claude\claude_desktop_config.json` **Linux**: `~/.config/Claude/claude_desktop_config.json` ```json { "mcpServers": { "asus-router": { "command": "/path/to/mcp-asus-merlin/venv/bin/python", "args": ["/path/to/mcp-asus-merlin/asus_merlin_mcp.py"], "env": { "ROUTER_HOST": "192.168.1.1", "ROUTER_PORT": "22", "ROUTER_USER": "admin", "ROUTER_KEY_FILE": "/home/user/.ssh/id_rsa" } } } } ``` **Configuration Notes:** - `command`: Full path to Python interpreter in venv - `args`: Full path to MCP server script - `env`: Environment variables passed to server process - Native Claude Code stores project-specific MCP servers in `~/.claude.json` - NPM installations and Claude Desktop use separate configuration files #### 5. Restart Claude Code ```bash # Exit Claude Code completely # Restart Claude Code # Or use restart command if available claude restart ``` **Why restart?** - MCP servers are loaded only at startup - Configuration changes require full restart - Changes to `settings.json` aren't hot-reloaded #### 6. Verify Installation Ask Claude: ``` What MCP servers do you have access to? ``` Claude should list `asus-router` with its available tools. #### 7. Test Tools ``` Get my router's uptime List connected devices What's my router's firmware version? ``` --- ## Configuration Deep Dive ### Complete Configuration Schema ```json { "mcpServers": { "server-identifier": { "command": "/path/to/executable", "args": [ "arg1", "arg2" ], "env": { "VAR1": "value1", "VAR2": "value2" }, "cwd": "/working/directory", "disabled": false } } } ``` ### Configuration Fields Explained #### `server-identifier` (string) - Unique name for this MCP server - Used in logs and when Claude references the server - Should be descriptive (e.g., "asus-router", "postgres-db", "file-system") #### `command` (string, required) - Path to executable that runs the MCP server - **Must be absolute path**, not relative - Common values: - `/path/to/venv/bin/python` - Python server - `/usr/bin/node` - Node.js server - `/usr/local/bin/go` - Go binary - `/path/to/compiled/binary` - Native executable #### `args` (array of strings, required) - Command-line arguments passed to the executable - First argument is typically the script/entry point - Examples: ```json ["server.py"] // Python script ["dist/index.js"] // JavaScript entry point ["run", "server"] // Go subcommand ``` #### `env` (object, optional) - Environment variables available to the server process - Merged with system environment - Use for: - Credentials (API keys, passwords) - Configuration (hosts, ports, paths) - Feature flags - Debug settings #### `cwd` (string, optional) - Working directory for the server process - Defaults to directory containing the executable - Useful if server expects to run from specific directory #### `disabled` (boolean, optional) - Set to `true` to prevent server from loading - Useful for temporarily disabling servers without removing config - Default: `false` ### Multiple MCP Servers You can configure multiple servers simultaneously: ```json { "mcpServers": { "asus-router": { "command": "/path/to/python", "args": ["asus_merlin_mcp.py"], "env": { "ROUTER_HOST": "192.168.0.1" } }, "postgres-db": { "command": "/path/to/node", "args": ["postgres-server.js"], "env": { "DB_CONNECTION": "postgresql://..." } }, "file-system": { "command": "/path/to/filesystem-server", "args": [], "cwd": "/home/user/documents" } } } ``` Claude will have access to all enabled servers concurrently. --- ## Testing and Debugging ### Manual Testing #### Test 1: Server Starts Successfully ```bash cd /path/to/mcp-server source venv/bin/activate export $(cat .env | xargs) python server.py ``` **Expected behavior:** - Server starts without errors - Logs show initialization messages - Process waits for stdin (doesn't exit immediately) **Troubleshooting:** - Import errors → Check dependencies installed - Connection errors → Verify credentials and connectivity - Permission errors → Check file permissions and paths #### Test 2: Server Responds to MCP Protocol You can manually send JSON-RPC messages: ```bash echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | python server.py ``` **Expected response:** ```json { "jsonrpc": "2.0", "id": 1, "result": { "tools": [...] } } ``` #### Test 3: Tool Execution ```bash echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_router_info","arguments":{}}}' | python server.py ``` ### Debugging with Logs Add detailed logging to your MCP server: ```python import logging logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('/tmp/mcp-server.log'), logging.StreamHandler() ] ) logger = logging.getLogger("mcp-server") logger.info("Server starting...") ``` **Log file locations:** - `/tmp/mcp-server.log` - Custom log file - `~/.claude/logs/` - Claude Code logs (may include MCP errors) - stderr - Captured by Claude Code ### Common Issues #### Server Not Appearing in Claude **Symptoms:** - Claude says "No MCP servers configured" - Server not listed when asked **Causes:** 1. Configuration file not in correct location 2. Invalid JSON syntax in settings.json 3. Server identifier misspelled 4. Server disabled with `"disabled": true` **Solutions:** ```bash # Verify config location cat ~/.claude/settings.json # Validate JSON syntax python3 -m json.tool ~/.claude/settings.json # Check for typos in server name ``` #### Server Starts But Tools Don't Work **Symptoms:** - Server appears in list - Tool calls fail or timeout **Causes:** 1. Network connectivity issues 2. Invalid credentials 3. Permissions problems 4. Server crashes on tool execution **Solutions:** ```bash # Test connectivity manually ssh user@host # Check server logs tail -f /tmp/mcp-server.log # Test tool in isolation python -c "from server import router; print(router.execute_command('uptime'))" ``` #### Environment Variables Not Working **Symptoms:** - Server can't find configuration - Connection failures despite correct credentials **Causes:** 1. Environment variables not set in settings.json 2. Variables not accessible to subprocess 3. Variables contain special characters needing escaping **Solutions:** ```json { "env": { "DEBUG": "true", "ROUTER_HOST": "192.168.0.1", "PATH": "/usr/local/bin:/usr/bin:/bin" } } ``` ### Using MCP Inspector (Optional) If you have the MCP Inspector tool installed: ```bash mcp inspect /path/to/venv/bin/python /path/to/server.py ``` This provides an interactive interface to test your MCP server. --- ## Building Your Own MCP Servers ### Server Structure Every MCP server needs: 1. **Entry point** - Main script that starts the server 2. **Tool definitions** - Declare what tools are available 3. **Tool handlers** - Implement tool functionality 4. **Communication layer** - Handle JSON-RPC protocol (usually via SDK) ### Python Example Structure ```python #!/usr/bin/env python3 import asyncio from mcp.server import Server from mcp.types import Tool, TextContent import mcp.server.stdio # Initialize server app = Server("my-server-name") # Define tools @app.list_tools() async def list_tools() -> list[Tool]: return [ Tool( name="example_tool", description="What this tool does", inputSchema={ "type": "object", "properties": { "param1": { "type": "string", "description": "Parameter description" } }, "required": ["param1"] } ) ] # Implement tool handler @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "example_tool": param1 = arguments.get("param1") result = f"You provided: {param1}" return [TextContent(type="text", text=result)] return [TextContent(type="text", text=f"Unknown tool: {name}")] # Run server async def main(): async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): await app.run(read_stream, write_stream, app.create_initialization_options()) if __name__ == "__main__": asyncio.run(main()) ``` ### Tool Design Patterns #### Pattern 1: Information Retrieval **Use case**: Get data from external source ```python Tool( name="get_data", description="Retrieve information from X", inputSchema={ "type": "object", "properties": { "query": {"type": "string", "description": "What to retrieve"} }, "required": ["query"] } ) ``` #### Pattern 2: State Modification **Use case**: Change something in external system ```python Tool( name="update_setting", description="Modify configuration", inputSchema={ "type": "object", "properties": { "key": {"type": "string", "description": "Setting name"}, "value": {"type": "string", "description": "New value"}, "confirm": {"type": "boolean", "description": "Confirmation flag"} }, "required": ["key", "value", "confirm"] } ) ``` **Best practice**: Require confirmation for destructive operations. #### Pattern 3: File Operations **Use case**: Upload/download files ```python Tool( name="upload_file", description="Upload file to destination", inputSchema={ "type": "object", "properties": { "local_path": {"type": "string"}, "remote_path": {"type": "string"} }, "required": ["local_path", "remote_path"] } ) ``` #### Pattern 4: Command Execution **Use case**: Run arbitrary commands (use cautiously) ```python Tool( name="execute_command", description="Execute command on remote system", inputSchema={ "type": "object", "properties": { "command": {"type": "string", "description": "Command to execute"} }, "required": ["command"] } ) ``` **Security warning**: Validate and sanitize all inputs! ### Input Validation Always validate tool inputs: ```python async def call_tool(name: str, arguments: dict): if name == "set_config": key = arguments.get("key") value = arguments.get("value") # Validation if not key or not isinstance(key, str): return [TextContent(type="text", text="Error: Invalid key")] if len(key) > 100: return [TextContent(type="text", text="Error: Key too long")] # Sanitization safe_key = key.strip() # Execute result = set_configuration(safe_key, value) return [TextContent(type="text", text=f"Set {safe_key} = {value}")] ``` ### Error Handling Return meaningful errors to Claude: ```python async def call_tool(name: str, arguments: dict): try: # Tool logic result = perform_operation(arguments) return [TextContent(type="text", text=result)] except ConnectionError as e: return [TextContent(type="text", text=f"Connection failed: {e}")] except PermissionError as e: return [TextContent(type="text", text=f"Permission denied: {e}")] except Exception as e: logger.error(f"Unexpected error: {e}", exc_info=True) return [TextContent(type="text", text=f"Error: {str(e)}")] ``` ### Response Formats Tools can return different content types: ```python from mcp.types import TextContent, ImageContent, EmbeddedResource # Text response return [TextContent(type="text", text="Plain text result")] # Multiple text blocks return [ TextContent(type="text", text="Section 1"), TextContent(type="text", text="Section 2") ] # Image response (base64 encoded) return [ImageContent( type="image", data="base64-encoded-image-data", mimeType="image/png" )] # Embedded resource return [EmbeddedResource( type="resource", resource={ "uri": "file:///path/to/file", "mimeType": "application/json", "text": json.dumps(data) } )] ``` ### Best Practices 1. **Tool Naming**: Use clear, descriptive names (`get_router_info` not `gri`) 2. **Descriptions**: Write from Claude's perspective (what Claude can do with this) 3. **Parameters**: Provide clear descriptions and types 4. **Validation**: Always validate inputs before use 5. **Errors**: Return helpful error messages, not stack traces 6. **Logging**: Log important operations for debugging 7. **Security**: Never trust user input, sanitize everything 8. **Documentation**: Include docstrings and comments 9. **Testing**: Test each tool independently before integration 10. **Versioning**: Track server version for compatibility --- ## Troubleshooting ### Diagnostic Checklist When things don't work, check in this order: #### ✓ Configuration File - [ ] File exists at `~/.claude/settings.json` - [ ] JSON syntax is valid - [ ] Server identifier is unique - [ ] Paths are absolute, not relative - [ ] No typos in field names #### ✓ Executable & Scripts - [ ] Python interpreter path is correct - [ ] Virtual environment exists - [ ] Server script exists at specified path - [ ] Server script has execute permissions - [ ] All dependencies installed in venv #### ✓ Environment Variables - [ ] Variables defined in `env` section - [ ] Variable values are correct - [ ] No unescaped special characters - [ ] Credentials are valid #### ✓ Network & Connectivity - [ ] Can reach target system from command line - [ ] Firewall allows connections - [ ] Credentials work manually - [ ] SSH keys have correct permissions (600) #### ✓ Server Logs - [ ] Check logs for startup errors - [ ] Look for connection failures - [ ] Verify tools are registered - [ ] Check for runtime exceptions ### Getting Help 1. **Check the logs**: Most issues show up in logs 2. **Test manually**: Run server outside of Claude Code 3. **Simplify**: Start with minimal working example 4. **Read errors**: Error messages often point to exact issue 5. **Search docs**: MCP documentation at modelcontextprotocol.io 6. **Community**: Ask in MCP community forums/Discord --- ## Example: Creating a Database MCP Server Let's walk through creating a PostgreSQL MCP server from scratch: ### 1. Project Structure ``` postgres-mcp/ ├── server.py # Main MCP server ├── requirements.txt # Dependencies ├── .env.example # Template for credentials └── README.md # Documentation ``` ### 2. Dependencies `requirements.txt`: ``` mcp psycopg2-binary ``` ### 3. Server Implementation `server.py`: ```python #!/usr/bin/env python3 import asyncio import os import psycopg2 from mcp.server import Server from mcp.types import Tool, TextContent import mcp.server.stdio # Configuration DB_CONFIG = { "host": os.getenv("DB_HOST", "localhost"), "port": int(os.getenv("DB_PORT", "5432")), "database": os.getenv("DB_NAME", "postgres"), "user": os.getenv("DB_USER", "postgres"), "password": os.getenv("DB_PASSWORD", ""), } app = Server("postgres-db") def execute_query(query: str): """Execute a SQL query and return results""" conn = psycopg2.connect(**DB_CONFIG) cur = conn.cursor() cur.execute(query) if cur.description: # SELECT query results = cur.fetchall() columns = [desc[0] for desc in cur.description] return {"columns": columns, "rows": results} else: # INSERT/UPDATE/DELETE conn.commit() return {"affected_rows": cur.rowcount} cur.close() conn.close() @app.list_tools() async def list_tools() -> list[Tool]: return [ Tool( name="query_database", description="Execute a SQL query on the PostgreSQL database", inputSchema={ "type": "object", "properties": { "query": { "type": "string", "description": "SQL query to execute" } }, "required": ["query"] } ) ] @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "query_database": query = arguments.get("query") try: result = execute_query(query) return [TextContent(type="text", text=str(result))] except Exception as e: return [TextContent(type="text", text=f"Error: {e}")] async def main(): async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): await app.run(read_stream, write_stream, app.create_initialization_options()) if __name__ == "__main__": asyncio.run(main()) ``` ### 4. Claude Code Configuration `~/.claude/settings.json`: ```json { "mcpServers": { "postgres-db": { "command": "/path/to/postgres-mcp/venv/bin/python", "args": ["/path/to/postgres-mcp/server.py"], "env": { "DB_HOST": "localhost", "DB_PORT": "5432", "DB_NAME": "mydb", "DB_USER": "dbuser", "DB_PASSWORD": "dbpass" } } } } ``` ### 5. Setup and Test ```bash cd postgres-mcp python3 -m venv venv source venv/bin/activate pip install -r requirements.txt # Test manually export DB_HOST=localhost DB_NAME=mydb DB_USER=dbuser DB_PASSWORD=dbpass python server.py ``` ### 6. Use with Claude After restarting Claude Code: ``` List all tables in the database Query the users table for active users Show me the schema for the orders table ``` --- ## Conclusion You now have a comprehensive understanding of: - ✓ What MCP is and how it works - ✓ How Claude Code discovers and uses MCP servers - ✓ How to configure MCP servers in settings.json - ✓ How to test and debug MCP servers - ✓ How to build your own custom MCP servers - ✓ Common patterns and best practices ### Next Steps 1. **Experiment**: Modify the ASUS router server to add new tools 2. **Create**: Build an MCP server for your own use case 3. **Share**: Contribute MCP servers to the community 4. **Learn more**: Visit [modelcontextprotocol.io](https://modelcontextprotocol.io) ### Resources - **MCP Documentation**: https://modelcontextprotocol.io - **MCP Python SDK**: https://github.com/modelcontextprotocol/python-sdk - **Example Servers**: https://github.com/modelcontextprotocol/servers - **Community**: MCP Discord/Forums --- **Happy building!** 🚀

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/kcsoukup/asus-merlin-mcp'

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