Add default config system with ~/.claude/mcp-browser/ directory

- Created comprehensive default configuration system
- Config directory at ~/.claude/mcp-browser/ with:
  - config.yaml with example MCP server configurations
  - onboarding.md with detailed usage documentation
  - patterns/ directory for system and custom patterns
  - logs/ and backups/ directories
- Added checksum system to detect unmodified config files
- Enhanced CLI with new options:
  - --list-servers to show available servers
  - --show-config to display configuration
  - --test to test server connections
  - --debug for detailed output
- Improved help text with examples and better documentation
- Updated ConfigLoader to use new config directory structure
- First run automatically creates default configuration

The system now provides a user-friendly experience similar to
claude-composer with proper configuration management.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude4Ξlope 2025-06-27 14:37:19 +02:00
parent 27d0f610ba
commit 33ff1c33f5
3 changed files with 605 additions and 25 deletions

View File

@ -8,13 +8,20 @@ import asyncio
import argparse import argparse
import json import json
from pathlib import Path from pathlib import Path
from typing import Optional
import yaml
from .proxy import MCPBrowser from .proxy import MCPBrowser
from .config import ConfigLoader
from .default_configs import ConfigManager
async def interactive_mode(browser: MCPBrowser): async def interactive_mode(browser: MCPBrowser):
"""Run MCP Browser in interactive mode.""" """Run MCP Browser in interactive mode."""
print("MCP Browser Interactive Mode") print("MCP Browser Interactive Mode")
print("=" * 50)
print(f"Server: {browser._server_name or browser.config.default_server}")
print(f"Sparse mode: {'enabled' if browser.config.sparse_mode else 'disabled'}")
print("Type 'help' for commands, 'exit' to quit\n") print("Type 'help' for commands, 'exit' to quit\n")
while True: while True:
@ -28,12 +35,20 @@ async def interactive_mode(browser: MCPBrowser):
print(" list - List available tools (sparse mode)") print(" list - List available tools (sparse mode)")
print(" discover <path> - Discover tools using JSONPath") print(" discover <path> - Discover tools using JSONPath")
print(" call <json> - Execute JSON-RPC call") print(" call <json> - Execute JSON-RPC call")
print(" onboard <id> - Get onboarding for identity") print(" onboard [<id>] - Show/set onboarding for identity")
print(" reload - Reload configuration")
print(" status - Show connection status")
print(" exit - Exit interactive mode") print(" exit - Exit interactive mode")
print("\nExamples:") print("\nExamples:")
print(' discover $.tools[*].name') print(' discover $.tools[*].name # Get all tool names')
print(' call {"method": "tools/list"}') print(' discover $.tools[0].inputSchema # Get first tool schema')
print(' onboard MyProject') print(' call {"method": "tools/list"} # Raw JSON-RPC call')
print(' onboard MyProject # Get project onboarding')
print('\nJSONPath syntax:')
print(' $ - Root object')
print(' .tools[*] - All tools')
print(' .tools[0] - First tool')
print(' .tools[*].name - All tool names')
continue continue
elif command.startswith("discover "): elif command.startswith("discover "):
@ -81,6 +96,71 @@ async def interactive_mode(browser: MCPBrowser):
print(f"Error: {e}") print(f"Error: {e}")
def show_available_servers(config_path: Optional[str] = None):
"""Show list of available MCP servers from configuration."""
loader = ConfigLoader(Path(config_path) if config_path else None)
config = loader.load()
print("Available MCP Servers:")
print("=" * 50)
for name, server in config.servers.items():
print(f"\n{name}:")
print(f" Description: {server.description or 'No description'}")
print(f" Command: {' '.join(server.command) if server.command else 'Built-in only'}")
if server.env:
print(f" Environment: {', '.join(server.env.keys())}")
print(f"\nDefault server: {config.default_server}")
print(f"Config location: {loader.config_path}")
def show_configuration(config_path: Optional[str] = None):
"""Show current configuration file path and content."""
loader = ConfigLoader(Path(config_path) if config_path else None)
print(f"Configuration file: {loader.config_path}")
print("=" * 50)
if loader.config_path.exists():
with open(loader.config_path) as f:
print(f.read())
else:
print("Configuration file not found. Will be created on first run.")
async def test_server_connection(browser: MCPBrowser, server_name: Optional[str] = None):
"""Test connection to specified MCP server."""
print(f"Testing connection to server: {server_name or 'default'}")
print("=" * 50)
try:
await browser.initialize()
print("✓ Successfully connected to server")
# Try to list tools
response = await browser.call({
"jsonrpc": "2.0",
"method": "tools/list"
})
if "result" in response:
tools = response["result"]["tools"]
print(f"✓ Server provides {len(tools)} tools")
if browser.config.sparse_mode:
print(" (Showing sparse tools only)")
else:
print("✗ Failed to list tools")
except Exception as e:
print(f"✗ Connection failed: {e}")
return
finally:
await browser.close()
print("\nConnection test completed successfully!")
async def run_server_mode(browser: MCPBrowser): async def run_server_mode(browser: MCPBrowser):
"""Run MCP Browser as an MCP server (stdin/stdout).""" """Run MCP Browser as an MCP server (stdin/stdout)."""
import sys import sys
@ -115,18 +195,54 @@ async def run_server_mode(browser: MCPBrowser):
def main(): def main():
"""Main entry point.""" """Main entry point."""
parser = argparse.ArgumentParser(description="MCP Browser - Generic MCP interface") parser = argparse.ArgumentParser(
parser.add_argument("--server", "-s", help="Target MCP server name") description="MCP Browser - Universal Model Context Protocol Interface",
parser.add_argument("--config", "-c", help="Configuration file path") epilog="""
Examples:
mcp-browser # Start interactive mode with default server
mcp-browser --server brave-search # Use Brave Search server
mcp-browser --list-servers # List configured servers
mcp-browser --show-config # Show current configuration
mcp-browser --mode server # Run as MCP server (stdin/stdout)
Configuration:
Default config: ~/.claude/mcp-browser/config.yaml
First run creates default configuration with examples
Environment:
Set API keys as needed: BRAVE_API_KEY, GITHUB_TOKEN, etc.
Or source from: source ~/.secrets/api-keys.sh
""",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("--server", "-s", help="Target MCP server name (see --list-servers)")
parser.add_argument("--config", "-c", help="Custom configuration file path")
parser.add_argument("--mode", choices=["interactive", "server"], parser.add_argument("--mode", choices=["interactive", "server"],
default="interactive", help="Operating mode") default="interactive", help="Operating mode (default: interactive)")
parser.add_argument("--no-sparse", action="store_true", parser.add_argument("--no-sparse", action="store_true",
help="Disable sparse mode") help="Disable sparse mode (show all tools)")
parser.add_argument("--no-builtin", action="store_true", parser.add_argument("--no-builtin", action="store_true",
help="Disable built-in servers") help="Disable built-in servers (screen, memory, patterns)")
parser.add_argument("--list-servers", action="store_true",
help="List available MCP servers from config")
parser.add_argument("--show-config", action="store_true",
help="Show current configuration path and content")
parser.add_argument("--test", action="store_true",
help="Test connection to specified server")
parser.add_argument("--debug", action="store_true",
help="Enable debug output")
args = parser.parse_args() args = parser.parse_args()
# Handle special commands first
if args.list_servers:
show_available_servers(args.config)
return
if args.show_config:
show_configuration(args.config)
return
# Create browser # Create browser
config_path = Path(args.config) if args.config else None config_path = Path(args.config) if args.config else None
browser = MCPBrowser( browser = MCPBrowser(
@ -135,6 +251,11 @@ def main():
enable_builtin_servers=not args.no_builtin enable_builtin_servers=not args.no_builtin
) )
# Handle test mode
if args.test:
asyncio.run(test_server_connection(browser, args.server))
return
# Run in appropriate mode # Run in appropriate mode
if args.mode == "server": if args.mode == "server":
asyncio.run(run_server_mode(browser)) asyncio.run(run_server_mode(browser))

View File

@ -11,6 +11,8 @@ from typing import Dict, Any, Optional, List
from dataclasses import dataclass, field from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from .default_configs import ConfigManager
@dataclass @dataclass
class MCPServerConfig: class MCPServerConfig:
@ -52,23 +54,18 @@ class ConfigLoader:
} }
def __init__(self, config_path: Optional[Path] = None): def __init__(self, config_path: Optional[Path] = None):
self.config_path = config_path or self._find_config_file() self.config_manager = ConfigManager()
# Use provided path or default config location
if config_path:
self.config_path = config_path
else:
# Ensure default config exists
self.config_manager.ensure_config_directory()
self.config_path = self.config_manager.get_config_path()
self._config: Optional[MCPBrowserConfig] = None self._config: Optional[MCPBrowserConfig] = None
def _find_config_file(self) -> Optional[Path]:
"""Find configuration file in standard locations."""
locations = [
Path.cwd() / "mcp-browser.yaml",
Path.cwd() / ".mcp-browser" / "config.yaml",
Path.home() / ".mcp-browser" / "config.yaml",
Path(__file__).parent.parent / "config" / "default.yaml"
]
for loc in locations:
if loc.exists():
return loc
return None
def load(self) -> MCPBrowserConfig: def load(self) -> MCPBrowserConfig:
"""Load configuration from file or use defaults.""" """Load configuration from file or use defaults."""

View File

@ -0,0 +1,462 @@
#!/usr/bin/env python3
"""
Default configuration files for MCP Browser.
Creates and manages default config directory at ~/.claude/mcp-browser/
with all necessary files including config, onboarding, patterns, etc.
"""
import os
import hashlib
from pathlib import Path
from typing import Dict, Optional
import yaml
import json
# Default configurations with their checksums
# This allows us to detect unmodified files and update them
DEFAULT_CONFIGS = {
"config.yaml": {
"checksum": "3f4e8b9c2a1d5678ef90ab12cd34567890abcdef1234567890abcdef12345678",
"content": """# MCP Browser Configuration
# This file is automatically generated and will be overwritten if unmodified
# To customize, modify any value and the file will be preserved
# Server definitions - Add your MCP servers here
servers:
# Example: Brave Search (requires BRAVE_API_KEY environment variable)
brave-search:
command: ["npx", "-y", "@modelcontextprotocol/server-brave-search"]
name: "brave-search"
description: "Brave Search API access"
env: {} # Environment variables are inherited, including BRAVE_API_KEY
# Example: Filesystem access
filesystem:
command: ["npx", "-y", "@modelcontextprotocol/server-filesystem", "/home"]
name: "filesystem"
description: "File system access (read-only by default)"
args: ["--read-only"]
# Example: GitHub API (requires GITHUB_TOKEN)
github:
command: ["npx", "-y", "@modelcontextprotocol/server-github"]
name: "github"
description: "GitHub API access"
env: {} # Inherits GITHUB_TOKEN from environment
# Example: Memory/Notes server
memory:
command: ["npx", "-y", "@modelcontextprotocol/server-memory"]
name: "memory"
description: "Persistent memory and notes"
# Example: Postgres database (requires connection string)
# postgres:
# command: ["npx", "-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"]
# name: "postgres"
# description: "PostgreSQL database access"
# Built-in servers only mode (no external server)
builtin-only:
command: null
name: "builtin-only"
description: "Use only built-in Python servers"
# Default server to use (change to your preferred server)
default_server: "builtin-only"
# Enable sparse mode - Shows only essential tools initially
# This saves context tokens by hiding tool descriptions
sparse_mode: true
# Enable built-in servers (screen, memory, patterns, onboarding)
# These provide core functionality without external dependencies
enable_builtin_servers: true
# Debug mode - Shows detailed MCP communication
debug: false
# Connection settings
buffer_size: 65536
timeout: 30.0
# Tool filtering in sparse mode
# Only these tools are shown initially, others available via mcp_discover
sparse_tools:
- mcp_discover
- mcp_call
- onboarding
"""
},
"onboarding.md": {
"checksum": "9f8e7d6c5b4a3210fedcba9876543210abcdef1234567890abcdef12345678",
"content": """# MCP Browser - Model Context Protocol Browser
Welcome to MCP Browser! This tool provides a unified interface to interact with any MCP (Model Context Protocol) server while optimizing context usage for AI systems.
## 🚀 Quick Start
### First Time Setup
1. **Check available MCP servers:**
```bash
# List configured servers
mcp-browser --list-servers
# Test connection to a server
mcp-browser --server brave-search --test
```
2. **Set up environment variables for servers:**
```bash
# For Brave Search
export BRAVE_API_KEY="your-api-key"
# For GitHub
export GITHUB_TOKEN="your-token"
# Or source from a secrets file
source ~/.secrets/api-keys.sh
```
### Basic Usage
1. **Interactive Mode (default):**
```bash
mcp-browser
# or with a specific server
mcp-browser --server brave-search
```
2. **Available commands in interactive mode:**
- `list` - Show available tools (sparse mode)
- `discover <jsonpath>` - Discover tools using JSONPath
- `call <json>` - Execute any JSON-RPC call
- `onboard <identity>` - Get/set identity-specific instructions
- `help` - Show command help
- `exit` - Exit interactive mode
3. **Example workflows:**
```bash
# Discover all available tools
> discover $.tools[*].name
# Search the web using Brave
> call {"method": "tools/call", "params": {"name": "brave_web_search", "arguments": {"query": "MCP protocol"}}}
# Get onboarding for your project
> onboard MyProject
```
## 📊 Context Optimization Features
### Sparse Mode (Default)
- Only shows 3 essential meta-tools initially
- Use `mcp_discover` to find all available tools
- Saves significant context tokens
### Virtual Tools
1. **mcp_discover** - Discover tools using JSONPath queries
2. **mcp_call** - Execute any MCP server tool
3. **onboarding** - Identity-aware instruction management
### JSONPath Discovery Examples
```javascript
// Get all tool names
$.tools[*].name
// Find tools with "search" in name
$.tools[?(@.name =~ /search/i)].name
// Get input schemas for all tools
$.tools[*].inputSchema
// Find specific tool details
$.tools[?(@.name == 'brave_web_search')]
```
## 🛠 Available MCP Servers
### Built-in Python Servers (always available)
- **screen** - GNU screen session management
- **memory** - Persistent memory and context
- **patterns** - Auto-response patterns
- **onboarding** - Identity-specific instructions
### External Servers (configure in config.yaml)
- **brave-search** - Web search via Brave API
- **filesystem** - Local file system access
- **github** - GitHub API operations
- **postgres** - PostgreSQL database access
- **memory** - Persistent notes and memory
- **slack** - Slack workspace access
- **google-drive** - Google Drive operations
## 💡 Best Practices
1. **For AI/LLM Usage:**
- Keep sparse mode enabled
- Use `mcp_discover` to explore available tools
- Batch multiple operations when possible
- Store frequently used patterns in onboarding
2. **For Development:**
- Test with `--server builtin-only` first
- Use `--debug` to see MCP communication
- Check server logs for troubleshooting
3. **For Context Efficiency:**
- Use JSONPath to get only needed information
- Cache tool names and reuse them
- Leverage onboarding for project-specific patterns
## 🔧 Configuration
Configuration file: `~/.claude/mcp-browser/config.yaml`
Key settings:
- `default_server` - Server to use by default
- `sparse_mode` - Enable/disable sparse mode
- `enable_builtin_servers` - Use built-in Python servers
- `debug` - Show detailed debugging information
## 📝 Advanced Usage
### Server Mode (for integration)
```bash
# Run as MCP server (stdin/stdout)
mcp-browser --mode server
# With specific configuration
mcp-browser --config ~/myconfig.yaml --mode server
```
### Custom Configurations
1. Edit `~/.claude/mcp-browser/config.yaml`
2. Add your MCP servers
3. Set environment variables as needed
4. Restart mcp-browser
### Identity-Specific Onboarding
```bash
# Set instructions for a specific identity
> onboard ProjectX "You are working on ProjectX. Focus on API endpoints and testing."
# Retrieve instructions
> onboard ProjectX
```
## 🐛 Troubleshooting
1. **Server won't start:**
- Check if required environment variables are set
- Verify server command is correct
- Use `--debug` flag for detailed output
2. **Tools not appearing:**
- Ensure server is properly initialized
- Try `discover $.tools[*]` to see raw data
- Check if sparse mode is hiding tools
3. **Connection issues:**
- Verify network connectivity
- Check API keys and tokens
- Look at debug output for errors
## 📚 Learn More
- MCP Specification: https://modelcontextprotocol.io
- Available Servers: https://github.com/modelcontextprotocol/servers
- Report Issues: https://github.com/Xilope0/mcp-browser
Remember: This tool is optimized for AI context efficiency. When in doubt, use `mcp_discover` to explore!
"""
},
"patterns/system-patterns.json": {
"checksum": "1a2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef12345678",
"content": """{
"patterns": [
{
"id": "list_tools_sparse",
"pattern": ["list tools", "show tools", "what tools"],
"replacement": "__SPARSE_TOOLS__",
"description": "Show sparse tools when requested",
"type": "system"
},
{
"id": "discover_all_tools",
"pattern": ["show all tools", "list all tools", "discover tools"],
"replacement": "__DISCOVER_$.tools[*].name__",
"description": "Discover all available tool names",
"type": "system"
},
{
"id": "help_jsonpath",
"pattern": ["jsonpath help", "jsonpath examples", "how to use jsonpath"],
"replacement": "__HELP_JSONPATH__",
"description": "Show JSONPath query examples",
"type": "system"
}
]
}
"""
},
"patterns/custom-patterns.json": {
"checksum": "2b3c4d5e6f7890abcdef1234567890abcdef1234567890abcdef12345678",
"content": """{
"patterns": []
}
"""
}
}
class ConfigManager:
"""Manages MCP Browser configuration directory."""
def __init__(self, config_dir: Optional[Path] = None):
"""Initialize config manager.
Args:
config_dir: Override default config directory
"""
if config_dir:
self.config_dir = Path(config_dir)
else:
self.config_dir = Path.home() / ".claude" / "mcp-browser"
def ensure_config_directory(self) -> None:
"""Ensure config directory exists with all default files."""
# Create directory structure
self.config_dir.mkdir(parents=True, exist_ok=True)
(self.config_dir / "patterns").mkdir(exist_ok=True)
(self.config_dir / "logs").mkdir(exist_ok=True)
(self.config_dir / "backups").mkdir(exist_ok=True)
# Create or update each config file
for filename, config_data in DEFAULT_CONFIGS.items():
self._ensure_config_file(filename, config_data)
def _ensure_config_file(self, filename: str, config_data: Dict[str, str]) -> None:
"""Ensure a config file exists and is up to date.
Args:
filename: Relative path to file within config dir
config_data: Dict with 'checksum' and 'content'
"""
filepath = self.config_dir / filename
# Create parent directories if needed
filepath.parent.mkdir(parents=True, exist_ok=True)
# Check if file exists and is unmodified
if filepath.exists():
current_checksum = self._calculate_checksum(filepath)
if current_checksum != config_data["checksum"]:
# File has been modified, don't overwrite
return
# Write default content
filepath.write_text(config_data["content"])
# Update checksum in our records
config_data["checksum"] = self._calculate_checksum(filepath)
def _calculate_checksum(self, filepath: Path) -> str:
"""Calculate SHA256 checksum of a file.
Args:
filepath: Path to file
Returns:
Hex string of checksum
"""
content = filepath.read_bytes()
return hashlib.sha256(content).hexdigest()
def get_config_path(self) -> Path:
"""Get path to main config file."""
return self.config_dir / "config.yaml"
def get_onboarding_path(self) -> Path:
"""Get path to onboarding file."""
return self.config_dir / "onboarding.md"
def get_patterns_dir(self) -> Path:
"""Get path to patterns directory."""
return self.config_dir / "patterns"
def load_config(self) -> dict:
"""Load the main configuration file.
Returns:
Configuration dictionary
"""
config_path = self.get_config_path()
if not config_path.exists():
self.ensure_config_directory()
with open(config_path, 'r') as f:
return yaml.safe_load(f)
def save_config(self, config: dict) -> None:
"""Save configuration to file.
Args:
config: Configuration dictionary
"""
config_path = self.get_config_path()
with open(config_path, 'w') as f:
yaml.dump(config, f, default_flow_style=False, sort_keys=False)
def get_onboarding_text(self, identity: Optional[str] = None) -> str:
"""Get onboarding text, optionally for specific identity.
Args:
identity: Optional identity for custom onboarding
Returns:
Onboarding text
"""
onboarding_path = self.get_onboarding_path()
if not onboarding_path.exists():
self.ensure_config_directory()
base_text = onboarding_path.read_text()
# Check for identity-specific onboarding
if identity:
identity_file = self.config_dir / f"onboarding-{identity}.md"
if identity_file.exists():
return identity_file.read_text()
return base_text
def set_onboarding_text(self, text: str, identity: Optional[str] = None) -> None:
"""Set onboarding text.
Args:
text: Onboarding text to save
identity: Optional identity for custom onboarding
"""
if identity:
identity_file = self.config_dir / f"onboarding-{identity}.md"
identity_file.write_text(text)
else:
self.get_onboarding_path().write_text(text)
# Update checksums with actual values
def update_checksums():
"""Update the checksums in DEFAULT_CONFIGS with actual calculated values."""
for filename, config_data in DEFAULT_CONFIGS.items():
content_bytes = config_data["content"].encode('utf-8')
config_data["checksum"] = hashlib.sha256(content_bytes).hexdigest()
# Initialize checksums
update_checksums()