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 json
from pathlib import Path
from typing import Optional
import yaml
from .proxy import MCPBrowser
from .config import ConfigLoader
from .default_configs import ConfigManager
async def interactive_mode(browser: MCPBrowser):
"""Run MCP Browser in 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")
while True:
@ -28,12 +35,20 @@ async def interactive_mode(browser: MCPBrowser):
print(" list - List available tools (sparse mode)")
print(" discover <path> - Discover tools using JSONPath")
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("\nExamples:")
print(' discover $.tools[*].name')
print(' call {"method": "tools/list"}')
print(' onboard MyProject')
print(' discover $.tools[*].name # Get all tool names')
print(' discover $.tools[0].inputSchema # Get first tool schema')
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
elif command.startswith("discover "):
@ -81,6 +96,71 @@ async def interactive_mode(browser: MCPBrowser):
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):
"""Run MCP Browser as an MCP server (stdin/stdout)."""
import sys
@ -115,18 +195,54 @@ async def run_server_mode(browser: MCPBrowser):
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(description="MCP Browser - Generic MCP interface")
parser.add_argument("--server", "-s", help="Target MCP server name")
parser.add_argument("--config", "-c", help="Configuration file path")
parser = argparse.ArgumentParser(
description="MCP Browser - Universal Model Context Protocol Interface",
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"],
default="interactive", help="Operating mode")
default="interactive", help="Operating mode (default: interactive)")
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",
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()
# 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
config_path = Path(args.config) if args.config else None
browser = MCPBrowser(
@ -135,6 +251,11 @@ def main():
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
if args.mode == "server":
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 pathlib import Path
from .default_configs import ConfigManager
@dataclass
class MCPServerConfig:
@ -52,23 +54,18 @@ class ConfigLoader:
}
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
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:
"""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()