From 5aacf468a42dd197a6ebaf15c0ea82114e1bbeb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Claude4=CE=9Elope?= Date: Fri, 27 Jun 2025 22:00:32 +0200 Subject: [PATCH] Fix MCP initialization timeout and add daemon management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix protocol version mismatch (0.1.0 -> 2024-11-05) - Fix server mode blocking on startup causing Claude Desktop timeout - Add --version flag to show version information - Implement proper daemon cleanup when starting new daemon - Add kill_daemon_with_children() to properly terminate process tree - Fix dictionary iteration error during shutdown - Ensure sparse mode is always enabled (3 tools only) - Fix async stdin reading in base server to prevent EOF loops - Add syslog support for server mode logging - Switch from stdin.read() to readline() for proper JSON-RPC handling - Add comprehensive MCP protocol test suite The tool now works correctly as an MCP proxy between Claude Desktop and MCP servers with additional built-in tools. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .tags | 381 ++++++++++++++++++++++++++++++++-- docs/STRUCTURE.md | 55 ++++- init-cmd.json | 1 + mcp_browser/client_main.py | 15 +- mcp_browser/daemon.py | 71 ++++++- mcp_browser/filter.py | 13 +- mcp_browser/logging_config.py | 54 ++++- mcp_browser/multi_server.py | 14 +- mcp_browser/proxy.py | 99 ++++++++- mcp_servers/base.py | 109 ++++++---- setup.py | 4 +- test_claude_desktop_flow.py | 171 +++++++++++++++ test_mcp_protocol.py | 260 +++++++++++++++++++++++ 13 files changed, 1156 insertions(+), 91 deletions(-) create mode 100644 init-cmd.json create mode 100644 test_claude_desktop_flow.py create mode 100644 test_mcp_protocol.py diff --git a/.tags b/.tags index b5d97c7..6285486 100644 --- a/.tags +++ b/.tags @@ -5,184 +5,537 @@ !_TAG_PROGRAM_URL http://ctags.sourceforge.net /official site/ !_TAG_PROGRAM_VERSION Development // AIAssistant examples/ai_optimized.py /^class AIAssistant:$/;" c +BaseMCPServer build/lib/mcp_servers/base.py /^class BaseMCPServer(ABC):$/;" c BaseMCPServer mcp_servers/base.py /^class BaseMCPServer(ABC):$/;" c +ConfigLoader build/lib/mcp_browser/config.py /^class ConfigLoader:$/;" c ConfigLoader mcp_browser/config.py /^class ConfigLoader:$/;" c +ConfigManager build/lib/mcp_browser/default_configs.py /^class ConfigManager:$/;" c +ConfigManager mcp_browser/default_configs.py /^class ConfigManager:$/;" c +DEFAULT_CONFIG build/lib/mcp_browser/config.py /^ DEFAULT_CONFIG = {$/;" v class:ConfigLoader DEFAULT_CONFIG mcp_browser/config.py /^ DEFAULT_CONFIG = {$/;" v class:ConfigLoader +DEFAULT_CONFIGS build/lib/mcp_browser/default_configs.py /^DEFAULT_CONFIGS = {$/;" v +DEFAULT_CONFIGS mcp_browser/default_configs.py /^DEFAULT_CONFIGS = {$/;" v +Decision build/lib/mcp_servers/memory/memory_server.py /^class Decision:$/;" c Decision mcp_servers/memory/memory_server.py /^class Decision:$/;" c GenerateAIDocs setup.py /^class GenerateAIDocs(Command):$/;" c +JSONRPCTestClient tests/test_integration.py /^class JSONRPCTestClient:$/;" c +JsonRpcBuffer build/lib/mcp_browser/buffer.py /^class JsonRpcBuffer:$/;" c JsonRpcBuffer mcp_browser/buffer.py /^class JsonRpcBuffer:$/;" c +MCPBrowser build/lib/mcp_browser/proxy.py /^class MCPBrowser:$/;" c MCPBrowser mcp_browser/proxy.py /^class MCPBrowser:$/;" c +MCPBrowserClient build/lib/mcp_browser/daemon.py /^class MCPBrowserClient:$/;" c +MCPBrowserClient mcp_browser/daemon.py /^class MCPBrowserClient:$/;" c +MCPBrowserConfig build/lib/mcp_browser/config.py /^class MCPBrowserConfig:$/;" c MCPBrowserConfig mcp_browser/config.py /^class MCPBrowserConfig:$/;" c +MCPBrowserDaemon build/lib/mcp_browser/daemon.py /^class MCPBrowserDaemon:$/;" c +MCPBrowserDaemon mcp_browser/daemon.py /^class MCPBrowserDaemon:$/;" c +MCPServer build/lib/mcp_browser/server.py /^class MCPServer:$/;" c MCPServer mcp_browser/server.py /^class MCPServer:$/;" c +MCPServerConfig build/lib/mcp_browser/config.py /^class MCPServerConfig:$/;" c MCPServerConfig mcp_browser/config.py /^class MCPServerConfig:$/;" c +MemoryServer build/lib/mcp_servers/memory/memory_server.py /^class MemoryServer(BaseMCPServer):$/;" c MemoryServer mcp_servers/memory/memory_server.py /^class MemoryServer(BaseMCPServer):$/;" c +MessageFilter build/lib/mcp_browser/filter.py /^class MessageFilter:$/;" c MessageFilter mcp_browser/filter.py /^class MessageFilter:$/;" c +MultiServerManager build/lib/mcp_browser/multi_server.py /^class MultiServerManager:$/;" c MultiServerManager mcp_browser/multi_server.py /^class MultiServerManager:$/;" c +OnboardingServer build/lib/mcp_servers/onboarding/onboarding_server.py /^class OnboardingServer(BaseMCPServer):$/;" c OnboardingServer mcp_servers/onboarding/onboarding_server.py /^class OnboardingServer(BaseMCPServer):$/;" c +Pattern build/lib/mcp_servers/memory/memory_server.py /^class Pattern:$/;" c Pattern mcp_servers/memory/memory_server.py /^class Pattern:$/;" c +PatternServer build/lib/mcp_servers/pattern_manager/pattern_server.py /^class PatternServer(BaseMCPServer):$/;" c PatternServer mcp_servers/pattern_manager/pattern_server.py /^class PatternServer(BaseMCPServer):$/;" c +RawIOFilter build/lib/mcp_browser/logging_config.py /^class RawIOFilter(logging.Filter):$/;" c +RawIOFilter mcp_browser/logging_config.py /^class RawIOFilter(logging.Filter):$/;" c +ScreenServer build/lib/mcp_servers/screen/screen_server.py /^class ScreenServer(BaseMCPServer):$/;" c ScreenServer mcp_servers/screen/screen_server.py /^class ScreenServer(BaseMCPServer):$/;" c +ServerNameAdapter build/lib/mcp_browser/logging_config.py /^class ServerNameAdapter(logging.LoggerAdapter):$/;" c +ServerNameAdapter mcp_browser/logging_config.py /^class ServerNameAdapter(logging.LoggerAdapter):$/;" c +TRACE build/lib/mcp_browser/logging_config.py /^TRACE = 5$/;" v +TRACE mcp_browser/logging_config.py /^TRACE = 5$/;" v +Task build/lib/mcp_servers/memory/memory_server.py /^class Task:$/;" c Task mcp_servers/memory/memory_server.py /^class Task:$/;" c +TestCommand setup.py /^class TestCommand(Command):$/;" c TestMCPBrowser tests/test_basic.py /^class TestMCPBrowser:$/;" c TestMessageFilter tests/test_basic.py /^class TestMessageFilter:$/;" c TestToolRegistry tests/test_basic.py /^class TestToolRegistry:$/;" c +ToolRegistry build/lib/mcp_browser/registry.py /^class ToolRegistry:$/;" c ToolRegistry mcp_browser/registry.py /^class ToolRegistry:$/;" c +VirtualToolHandler build/lib/mcp_browser/filter.py /^class VirtualToolHandler:$/;" c VirtualToolHandler mcp_browser/filter.py /^class VirtualToolHandler:$/;" c +__aenter__ build/lib/mcp_browser/daemon.py /^ async def __aenter__(self):$/;" m class:MCPBrowserClient file: +__aenter__ build/lib/mcp_browser/proxy.py /^ async def __aenter__(self):$/;" m class:MCPBrowser file: +__aenter__ mcp_browser/daemon.py /^ async def __aenter__(self):$/;" m class:MCPBrowserClient file: __aenter__ mcp_browser/proxy.py /^ async def __aenter__(self):$/;" m class:MCPBrowser file: +__aenter__ tests/test_integration.py /^ async def __aenter__(self):$/;" m class:JSONRPCTestClient file: +__aexit__ build/lib/mcp_browser/daemon.py /^ async def __aexit__(self, exc_type, exc_val, exc_tb):$/;" m class:MCPBrowserClient file: +__aexit__ build/lib/mcp_browser/proxy.py /^ async def __aexit__(self, exc_type, exc_val, exc_tb):$/;" m class:MCPBrowser file: +__aexit__ mcp_browser/daemon.py /^ async def __aexit__(self, exc_type, exc_val, exc_tb):$/;" m class:MCPBrowserClient file: __aexit__ mcp_browser/proxy.py /^ async def __aexit__(self, exc_type, exc_val, exc_tb):$/;" m class:MCPBrowser file: +__aexit__ tests/test_integration.py /^ async def __aexit__(self, exc_type, exc_val, exc_tb):$/;" m class:JSONRPCTestClient file: +__all__ build/lib/mcp_browser/__init__.py /^__all__ = ["MCPBrowser"]/;" v __all__ mcp_browser/__init__.py /^__all__ = ["MCPBrowser"]/;" v +__author__ build/lib/mcp_browser/__init__.py /^__author__ = "Claude4Ξlope"$/;" v +__author__ mcp_browser/__init__.py /^__author__ = "Claude4Ξlope"$/;" v +__email__ build/lib/mcp_browser/__init__.py /^__email__ = "xilope@esus.name"$/;" v +__email__ mcp_browser/__init__.py /^__email__ = "xilope@esus.name"$/;" v +__init__ build/lib/mcp_browser/buffer.py /^ def __init__(self):$/;" m class:JsonRpcBuffer +__init__ build/lib/mcp_browser/config.py /^ def __init__(self, config_path: Optional[Path] = None):$/;" m class:ConfigLoader +__init__ build/lib/mcp_browser/daemon.py /^ def __init__(self, browser: MCPBrowser, socket_path: Path):$/;" m class:MCPBrowserDaemon +__init__ build/lib/mcp_browser/daemon.py /^ def __init__(self, socket_path: Path):$/;" m class:MCPBrowserClient +__init__ build/lib/mcp_browser/default_configs.py /^ def __init__(self, config_dir: Optional[Path] = None):$/;" m class:ConfigManager +__init__ build/lib/mcp_browser/filter.py /^ def __init__(self, registry: ToolRegistry, server_callback: Callable):$/;" m class:VirtualToolHandler +__init__ build/lib/mcp_browser/filter.py /^ def __init__(self, registry: ToolRegistry, sparse_mode: bool = True):$/;" m class:MessageFilter +__init__ build/lib/mcp_browser/multi_server.py /^ def __init__(self, logger=None):$/;" m class:MultiServerManager +__init__ build/lib/mcp_browser/proxy.py /^ def __init__(self, config_path: Optional[Path] = None, server_name: Optional[str] = None,$/;" m class:MCPBrowser +__init__ build/lib/mcp_browser/registry.py /^ def __init__(self):$/;" m class:ToolRegistry +__init__ build/lib/mcp_browser/server.py /^ def __init__(self, config: MCPServerConfig, logger: Optional[logging.Logger] = None):$/;" m class:MCPServer +__init__ build/lib/mcp_servers/base.py /^ def __init__(self, name: str, version: str = "1.0.0"):$/;" m class:BaseMCPServer +__init__ build/lib/mcp_servers/memory/memory_server.py /^ def __init__(self):$/;" m class:MemoryServer +__init__ build/lib/mcp_servers/onboarding/onboarding_server.py /^ def __init__(self):$/;" m class:OnboardingServer +__init__ build/lib/mcp_servers/pattern_manager/pattern_server.py /^ def __init__(self):$/;" m class:PatternServer +__init__ build/lib/mcp_servers/screen/screen_server.py /^ def __init__(self):$/;" m class:ScreenServer __init__ examples/ai_optimized.py /^ def __init__(self):$/;" m class:AIAssistant __init__ mcp_browser/buffer.py /^ def __init__(self):$/;" m class:JsonRpcBuffer __init__ mcp_browser/config.py /^ def __init__(self, config_path: Optional[Path] = None):$/;" m class:ConfigLoader +__init__ mcp_browser/daemon.py /^ def __init__(self, browser: MCPBrowser, socket_path: Path):$/;" m class:MCPBrowserDaemon +__init__ mcp_browser/daemon.py /^ def __init__(self, socket_path: Path):$/;" m class:MCPBrowserClient +__init__ mcp_browser/default_configs.py /^ def __init__(self, config_dir: Optional[Path] = None):$/;" m class:ConfigManager __init__ mcp_browser/filter.py /^ def __init__(self, registry: ToolRegistry, server_callback: Callable):$/;" m class:VirtualToolHandler __init__ mcp_browser/filter.py /^ def __init__(self, registry: ToolRegistry, sparse_mode: bool = True):$/;" m class:MessageFilter -__init__ mcp_browser/multi_server.py /^ def __init__(self, debug: bool = False):$/;" m class:MultiServerManager +__init__ mcp_browser/multi_server.py /^ def __init__(self, logger=None):$/;" m class:MultiServerManager __init__ mcp_browser/proxy.py /^ def __init__(self, config_path: Optional[Path] = None, server_name: Optional[str] = None,$/;" m class:MCPBrowser __init__ mcp_browser/registry.py /^ def __init__(self):$/;" m class:ToolRegistry -__init__ mcp_browser/server.py /^ def __init__(self, config: MCPServerConfig, debug: bool = False):$/;" m class:MCPServer +__init__ mcp_browser/server.py /^ def __init__(self, config: MCPServerConfig, logger: Optional[logging.Logger] = None):$/;" m class:MCPServer __init__ mcp_servers/base.py /^ def __init__(self, name: str, version: str = "1.0.0"):$/;" m class:BaseMCPServer __init__ mcp_servers/memory/memory_server.py /^ def __init__(self):$/;" m class:MemoryServer __init__ mcp_servers/onboarding/onboarding_server.py /^ def __init__(self):$/;" m class:OnboardingServer __init__ mcp_servers/pattern_manager/pattern_server.py /^ def __init__(self):$/;" m class:PatternServer __init__ mcp_servers/screen/screen_server.py /^ def __init__(self):$/;" m class:ScreenServer +__init__ tests/test_integration.py /^ def __init__(self, timeout: float = 5.0):$/;" m class:JSONRPCTestClient +__license__ build/lib/mcp_browser/__init__.py /^__license__ = "GPLv3+"$/;" v +__license__ mcp_browser/__init__.py /^__license__ = "GPLv3+"$/;" v +__version__ build/lib/mcp_browser/__init__.py /^__version__ = "0.1.0"$/;" v __version__ mcp_browser/__init__.py /^__version__ = "0.1.0"$/;" v +_add_pattern build/lib/mcp_servers/pattern_manager/pattern_server.py /^ async def _add_pattern(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:PatternServer _add_pattern mcp_servers/pattern_manager/pattern_server.py /^ async def _add_pattern(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:PatternServer +_calculate_checksum build/lib/mcp_browser/default_configs.py /^ def _calculate_checksum(self, filepath: Path) -> str:$/;" m class:ConfigManager +_calculate_checksum mcp_browser/default_configs.py /^ def _calculate_checksum(self, filepath: Path) -> str:$/;" m class:ConfigManager +_create_session build/lib/mcp_servers/screen/screen_server.py /^ async def _create_session(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:ScreenServer _create_session mcp_servers/screen/screen_server.py /^ async def _create_session(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:ScreenServer +_decision_add build/lib/mcp_servers/memory/memory_server.py /^ async def _decision_add(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer _decision_add mcp_servers/memory/memory_server.py /^ async def _decision_add(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer +_delete_onboarding build/lib/mcp_servers/onboarding/onboarding_server.py /^ async def _delete_onboarding(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:OnboardingServer _delete_onboarding mcp_servers/onboarding/onboarding_server.py /^ async def _delete_onboarding(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:OnboardingServer +_ensure_config_file build/lib/mcp_browser/default_configs.py /^ def _ensure_config_file(self, filename: str, config_data: Dict[str, str]) -> None:$/;" m class:ConfigManager +_ensure_config_file mcp_browser/default_configs.py /^ def _ensure_config_file(self, filename: str, config_data: Dict[str, str]) -> None:$/;" m class:ConfigManager +_execute_command build/lib/mcp_servers/screen/screen_server.py /^ async def _execute_command(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:ScreenServer _execute_command mcp_servers/screen/screen_server.py /^ async def _execute_command(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:ScreenServer +_execute_pattern build/lib/mcp_servers/pattern_manager/pattern_server.py /^ async def _execute_pattern(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:PatternServer _execute_pattern mcp_servers/pattern_manager/pattern_server.py /^ async def _execute_pattern(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:PatternServer +_export_onboarding build/lib/mcp_servers/onboarding/onboarding_server.py /^ async def _export_onboarding(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:OnboardingServer _export_onboarding mcp_servers/onboarding/onboarding_server.py /^ async def _export_onboarding(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:OnboardingServer +_filter_tools_response build/lib/mcp_browser/filter.py /^ def _filter_tools_response(self, message: dict) -> dict:$/;" m class:MessageFilter _filter_tools_response mcp_browser/filter.py /^ def _filter_tools_response(self, message: dict) -> dict:$/;" m class:MessageFilter -_find_config_file mcp_browser/config.py /^ def _find_config_file(self) -> Optional[Path]:$/;" m class:ConfigLoader +_format_onboarding build/lib/mcp_servers/onboarding/onboarding_server.py /^ def _format_onboarding(self, identity: str, data: Dict[str, Any]) -> str:$/;" m class:OnboardingServer _format_onboarding mcp_servers/onboarding/onboarding_server.py /^ def _format_onboarding(self, identity: str, data: Dict[str, Any]) -> str:$/;" m class:OnboardingServer +_forward_to_server build/lib/mcp_browser/proxy.py /^ async def _forward_to_server(self, request: dict) -> dict:$/;" m class:MCPBrowser _forward_to_server mcp_browser/proxy.py /^ async def _forward_to_server(self, request: dict) -> dict:$/;" m class:MCPBrowser +_get_builtin_servers build/lib/mcp_browser/multi_server.py /^ def _get_builtin_servers(self) -> Dict[str, MCPServerConfig]:$/;" m class:MultiServerManager _get_builtin_servers mcp_browser/multi_server.py /^ def _get_builtin_servers(self) -> Dict[str, MCPServerConfig]:$/;" m class:MultiServerManager +_handle_call build/lib/mcp_browser/filter.py /^ async def _handle_call(self, message: dict) -> dict:$/;" m class:VirtualToolHandler _handle_call mcp_browser/filter.py /^ async def _handle_call(self, message: dict) -> dict:$/;" m class:VirtualToolHandler +_handle_client build/lib/mcp_browser/daemon.py /^ async def _handle_client(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):$/;" m class:MCPBrowserDaemon +_handle_client mcp_browser/daemon.py /^ async def _handle_client(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter):$/;" m class:MCPBrowserDaemon +_handle_discover build/lib/mcp_browser/filter.py /^ async def _handle_discover(self, message: dict) -> dict:$/;" m class:VirtualToolHandler _handle_discover mcp_browser/filter.py /^ async def _handle_discover(self, message: dict) -> dict:$/;" m class:VirtualToolHandler +_handle_message build/lib/mcp_browser/server.py /^ async def _handle_message(self, message: dict):$/;" m class:MCPServer _handle_message mcp_browser/server.py /^ async def _handle_message(self, message: dict):$/;" m class:MCPServer +_handle_onboarding build/lib/mcp_servers/onboarding/onboarding_server.py /^ async def _handle_onboarding(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:OnboardingServer _handle_onboarding mcp_servers/onboarding/onboarding_server.py /^ async def _handle_onboarding(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:OnboardingServer +_handle_server_message build/lib/mcp_browser/proxy.py /^ def _handle_server_message(self, message: dict):$/;" m class:MCPBrowser _handle_server_message mcp_browser/proxy.py /^ def _handle_server_message(self, message: dict):$/;" m class:MCPBrowser +_initialize_connection build/lib/mcp_browser/proxy.py /^ async def _initialize_connection(self):$/;" m class:MCPBrowser _initialize_connection mcp_browser/proxy.py /^ async def _initialize_connection(self):$/;" m class:MCPBrowser +_kill_session build/lib/mcp_servers/screen/screen_server.py /^ async def _kill_session(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:ScreenServer _kill_session mcp_servers/screen/screen_server.py /^ async def _kill_session(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:ScreenServer +_knowledge_add build/lib/mcp_servers/memory/memory_server.py /^ async def _knowledge_add(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer _knowledge_add mcp_servers/memory/memory_server.py /^ async def _knowledge_add(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer +_knowledge_get build/lib/mcp_servers/memory/memory_server.py /^ async def _knowledge_get(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer _knowledge_get mcp_servers/memory/memory_server.py /^ async def _knowledge_get(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer +_list_identities build/lib/mcp_servers/onboarding/onboarding_server.py /^ async def _list_identities(self) -> Dict[str, Any]:$/;" m class:OnboardingServer _list_identities mcp_servers/onboarding/onboarding_server.py /^ async def _list_identities(self) -> Dict[str, Any]:$/;" m class:OnboardingServer +_list_patterns build/lib/mcp_servers/pattern_manager/pattern_server.py /^ async def _list_patterns(self) -> Dict[str, Any]:$/;" m class:PatternServer _list_patterns mcp_servers/pattern_manager/pattern_server.py /^ async def _list_patterns(self) -> Dict[str, Any]:$/;" m class:PatternServer +_list_sessions build/lib/mcp_servers/screen/screen_server.py /^ async def _list_sessions(self) -> Dict[str, Any]:$/;" m class:ScreenServer _list_sessions mcp_servers/screen/screen_server.py /^ async def _list_sessions(self) -> Dict[str, Any]:$/;" m class:ScreenServer +_load_json build/lib/mcp_servers/memory/memory_server.py /^ def _load_json(self, filename: str, default: Any) -> Any:$/;" m class:MemoryServer _load_json mcp_servers/memory/memory_server.py /^ def _load_json(self, filename: str, default: Any) -> Any:$/;" m class:MemoryServer +_load_memory build/lib/mcp_servers/memory/memory_server.py /^ def _load_memory(self):$/;" m class:MemoryServer _load_memory mcp_servers/memory/memory_server.py /^ def _load_memory(self):$/;" m class:MemoryServer +_load_patterns build/lib/mcp_servers/pattern_manager/pattern_server.py /^ def _load_patterns(self) -> Dict[str, Dict[str, Any]]:$/;" m class:PatternServer _load_patterns mcp_servers/pattern_manager/pattern_server.py /^ def _load_patterns(self) -> Dict[str, Dict[str, Any]]:$/;" m class:PatternServer +_mark_offline build/lib/mcp_browser/server.py /^ def _mark_offline(self):$/;" m class:MCPServer +_mark_offline mcp_browser/server.py /^ def _mark_offline(self):$/;" m class:MCPServer +_matches_pattern build/lib/mcp_servers/pattern_manager/pattern_server.py /^ def _matches_pattern(self, text: str, trigger: List[str]) -> bool:$/;" m class:PatternServer _matches_pattern mcp_servers/pattern_manager/pattern_server.py /^ def _matches_pattern(self, text: str, trigger: List[str]) -> bool:$/;" m class:PatternServer +_memory_summary build/lib/mcp_servers/memory/memory_server.py /^ async def _memory_summary(self) -> Dict[str, Any]:$/;" m class:MemoryServer _memory_summary mcp_servers/memory/memory_server.py /^ async def _memory_summary(self) -> Dict[str, Any]:$/;" m class:MemoryServer +_merge_configs build/lib/mcp_browser/config.py /^ def _merge_configs(self, base: Dict[str, Any], override: Dict[str, Any]):$/;" m class:ConfigLoader _merge_configs mcp_browser/config.py /^ def _merge_configs(self, base: Dict[str, Any], override: Dict[str, Any]):$/;" m class:ConfigLoader +_pattern_add build/lib/mcp_servers/memory/memory_server.py /^ async def _pattern_add(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer _pattern_add mcp_servers/memory/memory_server.py /^ async def _pattern_add(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer +_pattern_resolve build/lib/mcp_servers/memory/memory_server.py /^ async def _pattern_resolve(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer _pattern_resolve mcp_servers/memory/memory_server.py /^ async def _pattern_resolve(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer +_peek_session build/lib/mcp_servers/screen/screen_server.py /^ async def _peek_session(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:ScreenServer _peek_session mcp_servers/screen/screen_server.py /^ async def _peek_session(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:ScreenServer +_process_request build/lib/mcp_browser/daemon.py /^ async def _process_request(self, line: str, writer: asyncio.StreamWriter):$/;" m class:MCPBrowserDaemon +_process_request mcp_browser/daemon.py /^ async def _process_request(self, line: str, writer: asyncio.StreamWriter):$/;" m class:MCPBrowserDaemon +_process_response build/lib/mcp_servers/pattern_manager/pattern_server.py /^ async def _process_response(self, response: Any, context: Dict[str, Any]) -> str:$/;" m class:PatternServer _process_response mcp_servers/pattern_manager/pattern_server.py /^ async def _process_response(self, response: Any, context: Dict[str, Any]) -> str:$/;" m class:PatternServer +_process_single_response build/lib/mcp_servers/pattern_manager/pattern_server.py /^ async def _process_single_response(self, response: str, context: Dict[str, Any]) -> str:$/;" m class:PatternServer _process_single_response mcp_servers/pattern_manager/pattern_server.py /^ async def _process_single_response(self, response: str, context: Dict[str, Any]) -> str:$/;" m class:PatternServer +_project_switch build/lib/mcp_servers/memory/memory_server.py /^ async def _project_switch(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer _project_switch mcp_servers/memory/memory_server.py /^ async def _project_switch(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer +_read_stderr build/lib/mcp_browser/server.py /^ async def _read_stderr(self):$/;" m class:MCPServer _read_stderr mcp_browser/server.py /^ async def _read_stderr(self):$/;" m class:MCPServer +_read_stdout build/lib/mcp_browser/server.py /^ async def _read_stdout(self):$/;" m class:MCPServer _read_stdout mcp_browser/server.py /^ async def _read_stdout(self):$/;" m class:MCPServer +_register_tools build/lib/mcp_servers/memory/memory_server.py /^ def _register_tools(self):$/;" m class:MemoryServer +_register_tools build/lib/mcp_servers/onboarding/onboarding_server.py /^ def _register_tools(self):$/;" m class:OnboardingServer +_register_tools build/lib/mcp_servers/pattern_manager/pattern_server.py /^ def _register_tools(self):$/;" m class:PatternServer +_register_tools build/lib/mcp_servers/screen/screen_server.py /^ def _register_tools(self):$/;" m class:ScreenServer _register_tools mcp_servers/memory/memory_server.py /^ def _register_tools(self):$/;" m class:MemoryServer _register_tools mcp_servers/onboarding/onboarding_server.py /^ def _register_tools(self):$/;" m class:OnboardingServer _register_tools mcp_servers/pattern_manager/pattern_server.py /^ def _register_tools(self):$/;" m class:PatternServer _register_tools mcp_servers/screen/screen_server.py /^ def _register_tools(self):$/;" m class:ScreenServer +_remove_pattern build/lib/mcp_servers/pattern_manager/pattern_server.py /^ async def _remove_pattern(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:PatternServer _remove_pattern mcp_servers/pattern_manager/pattern_server.py /^ async def _remove_pattern(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:PatternServer +_run_command build/lib/mcp_servers/screen/screen_server.py /^ async def _run_command(self, cmd: List[str]) -> subprocess.CompletedProcess:$/;" m class:ScreenServer _run_command mcp_servers/screen/screen_server.py /^ async def _run_command(self, cmd: List[str]) -> subprocess.CompletedProcess:$/;" m class:ScreenServer +_sanitize_identity build/lib/mcp_servers/onboarding/onboarding_server.py /^ def _sanitize_identity(self, identity: str) -> str:$/;" m class:OnboardingServer _sanitize_identity mcp_servers/onboarding/onboarding_server.py /^ def _sanitize_identity(self, identity: str) -> str:$/;" m class:OnboardingServer +_save_json build/lib/mcp_servers/memory/memory_server.py /^ def _save_json(self, filename: str, data: Any):$/;" m class:MemoryServer _save_json mcp_servers/memory/memory_server.py /^ def _save_json(self, filename: str, data: Any):$/;" m class:MemoryServer +_save_patterns build/lib/mcp_servers/pattern_manager/pattern_server.py /^ def _save_patterns(self):$/;" m class:PatternServer _save_patterns mcp_servers/pattern_manager/pattern_server.py /^ def _save_patterns(self):$/;" m class:PatternServer +_signal_handler build/lib/mcp_browser/daemon.py /^ def _signal_handler(self, signum, frame):$/;" m class:MCPBrowserDaemon +_signal_handler mcp_browser/daemon.py /^ def _signal_handler(self, signum, frame):$/;" m class:MCPBrowserDaemon +_task_add build/lib/mcp_servers/memory/memory_server.py /^ async def _task_add(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer _task_add mcp_servers/memory/memory_server.py /^ async def _task_add(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer +_task_list build/lib/mcp_servers/memory/memory_server.py /^ async def _task_list(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer _task_list mcp_servers/memory/memory_server.py /^ async def _task_list(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer +_task_update build/lib/mcp_servers/memory/memory_server.py /^ async def _task_update(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer _task_update mcp_servers/memory/memory_server.py /^ async def _task_update(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer +_test_pattern build/lib/mcp_servers/pattern_manager/pattern_server.py /^ async def _test_pattern(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:PatternServer _test_pattern mcp_servers/pattern_manager/pattern_server.py /^ async def _test_pattern(self, args: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:PatternServer +add_message_handler build/lib/mcp_browser/server.py /^ def add_message_handler(self, handler: Callable[[dict], None]):$/;" m class:MCPServer add_message_handler mcp_browser/server.py /^ def add_message_handler(self, handler: Callable[[dict], None]):$/;" m class:MCPServer +add_server build/lib/mcp_browser/multi_server.py /^ async def add_server(self, name: str, config: MCPServerConfig):$/;" m class:MultiServerManager add_server mcp_browser/multi_server.py /^ async def add_server(self, name: str, config: MCPServerConfig):$/;" m class:MultiServerManager +append build/lib/mcp_browser/buffer.py /^ def append(self, data: str) -> List[dict]:$/;" m class:JsonRpcBuffer append mcp_browser/buffer.py /^ def append(self, data: str) -> List[dict]:$/;" m class:JsonRpcBuffer +args build/lib/mcp_browser/__main__.py /^ args = parser.parse_args()$/;" v +args mcp_browser/__main__.py /^ args = parser.parse_args()$/;" v +async_main build/lib/mcp_browser/__main__.py /^async def async_main(browser: MCPBrowser):$/;" f async_main mcp_browser/__main__.py /^async def async_main(browser: MCPBrowser):$/;" f -author setup.py /^ author="AI Assistant",$/;" v -author_email setup.py /^ author_email="ai@anthropic.com",$/;" v +author setup.py /^ author="Claude4Ξlope",$/;" v +author_email setup.py /^ author_email="xilope@esus.name",$/;" v +browser build/lib/mcp_browser/__main__.py /^ browser = MCPBrowser($/;" v +browser mcp_browser/__main__.py /^ browser = MCPBrowser($/;" v +build_mcp_request build/lib/mcp_browser/__main__.py /^def build_mcp_request(args) -> Dict[str, Any]:$/;" f +build_mcp_request mcp_browser/__main__.py /^def build_mcp_request(args) -> Dict[str, Any]:$/;" f +build_request build/lib/mcp_browser/client_main.py /^def build_request(args) -> Dict[str, Any]:$/;" f +build_request mcp_browser/client_main.py /^def build_request(args) -> Dict[str, Any]:$/;" f +call build/lib/mcp_browser/daemon.py /^ async def call(self, request: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MCPBrowserClient +call build/lib/mcp_browser/proxy.py /^ async def call(self, jsonrpc_object: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MCPBrowser +call mcp_browser/daemon.py /^ async def call(self, request: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MCPBrowserClient call mcp_browser/proxy.py /^ async def call(self, jsonrpc_object: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MCPBrowser +call_tool build/lib/mcp_browser/__main__.py /^ call_tool = subparsers.add_parser("tools-call", help="Call a tool")$/;" v +call_tool mcp_browser/__main__.py /^ call_tool = subparsers.add_parser("tools-call", help="Call a tool")$/;" v classifiers setup.py /^ classifiers=[$/;" v +clear build/lib/mcp_browser/buffer.py /^ def clear(self):$/;" m class:JsonRpcBuffer clear mcp_browser/buffer.py /^ def clear(self):$/;" m class:JsonRpcBuffer +close build/lib/mcp_browser/daemon.py /^ async def close(self):$/;" m class:MCPBrowserClient +close build/lib/mcp_browser/proxy.py /^ async def close(self):$/;" m class:MCPBrowser close examples/ai_optimized.py /^ async def close(self):$/;" m class:AIAssistant +close mcp_browser/daemon.py /^ async def close(self):$/;" m class:MCPBrowserClient close mcp_browser/proxy.py /^ async def close(self):$/;" m class:MCPBrowser cmdclass setup.py /^ cmdclass={$/;" v +completion build/lib/mcp_browser/__main__.py /^ completion = subparsers.add_parser("completion", help="Get completion")$/;" v +completion mcp_browser/__main__.py /^ completion = subparsers.add_parser("completion", help="Get completion")$/;" v +config build/lib/mcp_browser/__main__.py /^ config = loader.load()$/;" v +config mcp_browser/__main__.py /^ config = loader.load()$/;" v +config_path build/lib/mcp_browser/__main__.py /^ config_path=config_path,$/;" v +config_path build/lib/mcp_browser/__main__.py /^ config_path = Path(args.config) if args.config else None$/;" v +config_path mcp_browser/__main__.py /^ config_path=config_path,$/;" v +config_path mcp_browser/__main__.py /^ config_path = Path(args.config) if args.config else None$/;" v +connect build/lib/mcp_browser/daemon.py /^ async def connect(self):$/;" m class:MCPBrowserClient +connect mcp_browser/daemon.py /^ async def connect(self):$/;" m class:MCPBrowserClient +content_text build/lib/mcp_servers/base.py /^ def content_text(self, text: str) -> Dict[str, Any]:$/;" m class:BaseMCPServer content_text mcp_servers/base.py /^ def content_text(self, text: str) -> Dict[str, Any]:$/;" m class:BaseMCPServer +create_browser build/lib/mcp_browser/proxy.py /^async def create_browser(config_path: Optional[Path] = None, $/;" f create_browser mcp_browser/proxy.py /^async def create_browser(config_path: Optional[Path] = None, $/;" f +debug_json build/lib/mcp_browser/utils.py /^def debug_json(label: str, data: Any):$/;" f +debug_print build/lib/mcp_browser/utils.py /^def debug_print(message: str):$/;" f demo_builtin_servers examples/builtin_servers_demo.py /^async def demo_builtin_servers():$/;" f demonstrate_mcp_browser examples/complete_demo.py /^async def demonstrate_mcp_browser():$/;" f description setup.py /^ description = 'Generate documentation for AI navigation'$/;" v class:GenerateAIDocs +description setup.py /^ description = 'Run unit and integration tests'$/;" v class:TestCommand description setup.py /^ description="A generic MCP browser with context optimization for AI systems",$/;" v +discover build/lib/mcp_browser/proxy.py /^ def discover(self, jsonpath: str) -> Any:$/;" m class:MCPBrowser +discover build/lib/mcp_browser/registry.py /^ def discover(self, jsonpath: str) -> Union[List[Any], Any, None]:$/;" m class:ToolRegistry discover mcp_browser/proxy.py /^ def discover(self, jsonpath: str) -> Any:$/;" m class:MCPBrowser discover mcp_browser/registry.py /^ def discover(self, jsonpath: str) -> Union[List[Any], Any, None]:$/;" m class:ToolRegistry +enable_builtin_servers build/lib/mcp_browser/__main__.py /^ enable_builtin_servers=not args.no_builtin$/;" v +enable_builtin_servers mcp_browser/__main__.py /^ enable_builtin_servers=not args.no_builtin$/;" v +ensure_config_directory build/lib/mcp_browser/default_configs.py /^ def ensure_config_directory(self) -> None:$/;" m class:ConfigManager +ensure_config_directory mcp_browser/default_configs.py /^ def ensure_config_directory(self) -> None:$/;" m class:ConfigManager entry_points setup.py /^ entry_points={$/;" v execute_user_request examples/ai_optimized.py /^ async def execute_user_request(self, user_request: str):$/;" m class:AIAssistant extras_require setup.py /^ extras_require={$/;" v +filter build/lib/mcp_browser/logging_config.py /^ def filter(self, record):$/;" m class:RawIOFilter +filter mcp_browser/logging_config.py /^ def filter(self, record):$/;" m class:RawIOFilter +filter_incoming build/lib/mcp_browser/filter.py /^ def filter_incoming(self, message: dict) -> Optional[dict]:$/;" m class:MessageFilter filter_incoming mcp_browser/filter.py /^ def filter_incoming(self, message: dict) -> Optional[dict]:$/;" m class:MessageFilter +filter_outgoing build/lib/mcp_browser/filter.py /^ def filter_outgoing(self, message: dict) -> Optional[dict]:$/;" m class:MessageFilter filter_outgoing mcp_browser/filter.py /^ def filter_outgoing(self, message: dict) -> Optional[dict]:$/;" m class:MessageFilter finalize_options setup.py /^ def finalize_options(self):$/;" m class:GenerateAIDocs +finalize_options setup.py /^ def finalize_options(self):$/;" m class:TestCommand +format_mcp_response build/lib/mcp_browser/__main__.py /^def format_mcp_response(args, request: Dict[str, Any], response: Dict[str, Any]):$/;" f +format_mcp_response mcp_browser/__main__.py /^def format_mcp_response(args, request: Dict[str, Any], response: Dict[str, Any]):$/;" f +format_response build/lib/mcp_browser/client_main.py /^def format_response(args, response: Dict[str, Any]):$/;" f +format_response mcp_browser/client_main.py /^def format_response(args, response: Dict[str, Any]):$/;" f +formatter_class build/lib/mcp_browser/__main__.py /^ formatter_class=argparse.RawDescriptionHelpFormatter$/;" v +formatter_class mcp_browser/__main__.py /^ formatter_class=argparse.RawDescriptionHelpFormatter$/;" v generate_api_summary setup.py /^ def generate_api_summary(self):$/;" m class:GenerateAIDocs generate_structure_doc setup.py /^ def generate_structure_doc(self):$/;" m class:GenerateAIDocs +get_all_tool_names build/lib/mcp_browser/registry.py /^ def get_all_tool_names(self) -> List[str]:$/;" m class:ToolRegistry get_all_tool_names mcp_browser/registry.py /^ def get_all_tool_names(self) -> List[str]:$/;" m class:ToolRegistry +get_all_tools build/lib/mcp_browser/multi_server.py /^ async def get_all_tools(self) -> List[Dict[str, Any]]:$/;" m class:MultiServerManager get_all_tools mcp_browser/multi_server.py /^ async def get_all_tools(self) -> List[Dict[str, Any]]:$/;" m class:MultiServerManager +get_config_path build/lib/mcp_browser/default_configs.py /^ def get_config_path(self) -> Path:$/;" m class:ConfigManager +get_config_path mcp_browser/default_configs.py /^ def get_config_path(self) -> Path:$/;" m class:ConfigManager +get_logger build/lib/mcp_browser/logging_config.py /^def get_logger(name: str, server: Optional[str] = None) -> logging.Logger:$/;" f +get_logger mcp_browser/logging_config.py /^def get_logger(name: str, server: Optional[str] = None) -> logging.Logger:$/;" f +get_onboarding_path build/lib/mcp_browser/default_configs.py /^ def get_onboarding_path(self) -> Path:$/;" m class:ConfigManager +get_onboarding_path mcp_browser/default_configs.py /^ def get_onboarding_path(self) -> Path:$/;" m class:ConfigManager +get_onboarding_text build/lib/mcp_browser/default_configs.py /^ def get_onboarding_text(self, identity: Optional[str] = None) -> str:$/;" m class:ConfigManager +get_onboarding_text mcp_browser/default_configs.py /^ def get_onboarding_text(self, identity: Optional[str] = None) -> str:$/;" m class:ConfigManager +get_patterns_dir build/lib/mcp_browser/default_configs.py /^ def get_patterns_dir(self) -> Path:$/;" m class:ConfigManager +get_patterns_dir mcp_browser/default_configs.py /^ def get_patterns_dir(self) -> Path:$/;" m class:ConfigManager +get_prompt build/lib/mcp_browser/__main__.py /^ get_prompt = subparsers.add_parser("prompts-get", help="Get a prompt")$/;" v +get_prompt mcp_browser/__main__.py /^ get_prompt = subparsers.add_parser("prompts-get", help="Get a prompt")$/;" v +get_socket_path build/lib/mcp_browser/daemon.py /^def get_socket_path(server_name: Optional[str] = None) -> Path:$/;" f +get_socket_path mcp_browser/daemon.py /^def get_socket_path(server_name: Optional[str] = None) -> Path:$/;" f +get_sparse_tools build/lib/mcp_browser/registry.py /^ def get_sparse_tools(self) -> List[Dict[str, Any]]:$/;" m class:ToolRegistry get_sparse_tools mcp_browser/registry.py /^ def get_sparse_tools(self) -> List[Dict[str, Any]]:$/;" m class:ToolRegistry +get_tool build/lib/mcp_browser/registry.py /^ def get_tool(self, name: str) -> Optional[Dict[str, Any]]:$/;" m class:ToolRegistry get_tool mcp_browser/registry.py /^ def get_tool(self, name: str) -> Optional[Dict[str, Any]]:$/;" m class:ToolRegistry +handle_mcp_command build/lib/mcp_browser/__main__.py /^async def handle_mcp_command(args):$/;" f +handle_mcp_command mcp_browser/__main__.py /^async def handle_mcp_command(args):$/;" f +handle_request build/lib/mcp_servers/base.py /^ async def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:BaseMCPServer handle_request mcp_servers/base.py /^ async def handle_request(self, request: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:BaseMCPServer +handle_systemd_socket build/lib/mcp_browser/daemon_main.py /^def handle_systemd_socket():$/;" f +handle_systemd_socket mcp_browser/daemon_main.py /^def handle_systemd_socket():$/;" f +handle_tool_call build/lib/mcp_browser/filter.py /^ async def handle_tool_call(self, message: dict) -> Optional[dict]:$/;" m class:VirtualToolHandler +handle_tool_call build/lib/mcp_servers/base.py /^ async def handle_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:BaseMCPServer +handle_tool_call build/lib/mcp_servers/memory/memory_server.py /^ async def handle_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer +handle_tool_call build/lib/mcp_servers/onboarding/onboarding_server.py /^ async def handle_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:OnboardingServer +handle_tool_call build/lib/mcp_servers/pattern_manager/pattern_server.py /^ async def handle_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:PatternServer +handle_tool_call build/lib/mcp_servers/screen/screen_server.py /^ async def handle_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:ScreenServer handle_tool_call mcp_browser/filter.py /^ async def handle_tool_call(self, message: dict) -> Optional[dict]:$/;" m class:VirtualToolHandler handle_tool_call mcp_servers/base.py /^ async def handle_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:BaseMCPServer handle_tool_call mcp_servers/memory/memory_server.py /^ async def handle_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MemoryServer handle_tool_call mcp_servers/onboarding/onboarding_server.py /^ async def handle_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:OnboardingServer handle_tool_call mcp_servers/pattern_manager/pattern_server.py /^ async def handle_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:PatternServer handle_tool_call mcp_servers/screen/screen_server.py /^ async def handle_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:ScreenServer +help build/lib/mcp_browser/__main__.py /^ help="Automatically use daemon if available")$/;" v +help build/lib/mcp_browser/__main__.py /^ help="Check daemon status")$/;" v +help build/lib/mcp_browser/__main__.py /^ help="Disable built-in servers (screen, memory, patterns)")$/;" v +help build/lib/mcp_browser/__main__.py /^ help="Disable sparse mode (show all tools)")$/;" v +help build/lib/mcp_browser/__main__.py /^ help="Enable debug output")$/;" v +help build/lib/mcp_browser/__main__.py /^ help="List available MCP servers from config")$/;" v +help build/lib/mcp_browser/__main__.py /^ help="Set logging level")$/;" v +help build/lib/mcp_browser/__main__.py /^ help="Show current configuration path and content")$/;" v +help build/lib/mcp_browser/__main__.py /^ help="Show program version and exit")$/;" v +help build/lib/mcp_browser/__main__.py /^ help="Start daemon in background")$/;" v +help build/lib/mcp_browser/__main__.py /^ help="Stop running daemon")$/;" v +help build/lib/mcp_browser/__main__.py /^ help="Test connection to specified server")$/;" v +help mcp_browser/__main__.py /^ help="Automatically use daemon if available")$/;" v +help mcp_browser/__main__.py /^ help="Check daemon status")$/;" v +help mcp_browser/__main__.py /^ help="Disable built-in servers (screen, memory, patterns)")$/;" v +help mcp_browser/__main__.py /^ help="Disable sparse mode (show all tools)")$/;" v +help mcp_browser/__main__.py /^ help="Enable debug output")$/;" v +help mcp_browser/__main__.py /^ help="List available MCP servers from config")$/;" v +help mcp_browser/__main__.py /^ help="Set logging level")$/;" v +help mcp_browser/__main__.py /^ help="Show current configuration path and content")$/;" v +help mcp_browser/__main__.py /^ help="Show program version and exit")$/;" v +help mcp_browser/__main__.py /^ help="Start daemon in background")$/;" v +help mcp_browser/__main__.py /^ help="Stop running daemon")$/;" v +help mcp_browser/__main__.py /^ help="Test connection to specified server")$/;" v +include_package_data setup.py /^ include_package_data=True,$/;" v +initialize build/lib/mcp_browser/proxy.py /^ async def initialize(self):$/;" m class:MCPBrowser initialize examples/ai_optimized.py /^ async def initialize(self):$/;" m class:AIAssistant initialize mcp_browser/proxy.py /^ async def initialize(self):$/;" m class:MCPBrowser initialize_options setup.py /^ def initialize_options(self):$/;" m class:GenerateAIDocs +initialize_options setup.py /^ def initialize_options(self):$/;" m class:TestCommand install_requires setup.py /^ install_requires=[$/;" v +interactive_mode build/lib/mcp_browser/__main__.py /^async def interactive_mode(browser: MCPBrowser):$/;" f interactive_mode mcp_browser/__main__.py /^async def interactive_mode(browser: MCPBrowser):$/;" f +interactive_mode_with_daemon build/lib/mcp_browser/__main__.py /^async def interactive_mode_with_daemon(socket_path: Path):$/;" f +interactive_mode_with_daemon mcp_browser/__main__.py /^async def interactive_mode_with_daemon(socket_path: Path):$/;" f +is_daemon_running build/lib/mcp_browser/daemon.py /^def is_daemon_running(socket_path: Path) -> bool:$/;" f +is_daemon_running mcp_browser/daemon.py /^def is_daemon_running(socket_path: Path) -> bool:$/;" f +is_virtual_tool build/lib/mcp_browser/filter.py /^ def is_virtual_tool(self, tool_name: str) -> bool:$/;" m class:MessageFilter is_virtual_tool mcp_browser/filter.py /^ def is_virtual_tool(self, tool_name: str) -> bool:$/;" m class:MessageFilter +jsonrpc build/lib/mcp_browser/__main__.py /^ jsonrpc = subparsers.add_parser("jsonrpc", help="Send raw JSON-RPC request")$/;" v +jsonrpc mcp_browser/__main__.py /^ jsonrpc = subparsers.add_parser("jsonrpc", help="Send raw JSON-RPC request")$/;" v keywords setup.py /^ keywords="mcp model-context-protocol ai llm tools json-rpc",$/;" v +kill_daemon_with_children build/lib/mcp_browser/daemon.py /^def kill_daemon_with_children(socket_path: Path) -> bool:$/;" f +kill_daemon_with_children mcp_browser/daemon.py /^def kill_daemon_with_children(socket_path: Path) -> bool:$/;" f +list_prompts build/lib/mcp_browser/__main__.py /^ list_prompts = subparsers.add_parser("prompts-list", help="List available prompts")$/;" v +list_prompts mcp_browser/__main__.py /^ list_prompts = subparsers.add_parser("prompts-list", help="List available prompts")$/;" v +list_resources build/lib/mcp_browser/__main__.py /^ list_resources = subparsers.add_parser("resources-list", help="List available resources")$/;" v +list_resources mcp_browser/__main__.py /^ list_resources = subparsers.add_parser("resources-list", help="List available resources")$/;" v +list_tools build/lib/mcp_browser/__main__.py /^ list_tools = subparsers.add_parser("tools-list", help="List available tools")$/;" v +list_tools mcp_browser/__main__.py /^ list_tools = subparsers.add_parser("tools-list", help="List available tools")$/;" v +load build/lib/mcp_browser/config.py /^ def load(self) -> MCPBrowserConfig:$/;" m class:ConfigLoader load mcp_browser/config.py /^ def load(self) -> MCPBrowserConfig:$/;" m class:ConfigLoader -long_description setup.py /^ long_description = fh.read()$/;" v class:GenerateAIDocs +load_config build/lib/mcp_browser/default_configs.py /^ def load_config(self) -> dict:$/;" m class:ConfigManager +load_config mcp_browser/default_configs.py /^ def load_config(self) -> dict:$/;" m class:ConfigManager +loader build/lib/mcp_browser/__main__.py /^ loader = ConfigLoader()$/;" v +loader mcp_browser/__main__.py /^ loader = ConfigLoader()$/;" v +long_description setup.py /^ long_description = fh.read()$/;" v long_description setup.py /^ long_description=long_description,$/;" v long_description_content_type setup.py /^ long_description_content_type="text\/markdown",$/;" v +main build/lib/mcp_browser/__main__.py /^def main():$/;" f +main build/lib/mcp_browser/client_main.py /^def main():$/;" f +main build/lib/mcp_browser/daemon_main.py /^def main():$/;" f main examples/ai_optimized.py /^async def main():$/;" f main examples/basic_usage.py /^async def main():$/;" f main mcp_browser/__main__.py /^def main():$/;" f +main mcp_browser/client_main.py /^def main():$/;" f +main mcp_browser/daemon_main.py /^def main():$/;" f +main tests/test_browser_functionality.py /^def main():$/;" f +main tests/test_integration.py /^async def main():$/;" f +mark_handled build/lib/mcp_browser/filter.py /^ def mark_handled(self, request_id: Union[str, int]):$/;" m class:MessageFilter mark_handled mcp_browser/filter.py /^ def mark_handled(self, request_id: Union[str, int]):$/;" m class:MessageFilter name setup.py /^ name="mcp-browser",$/;" v package_data setup.py /^ package_data={$/;" v -packages setup.py /^ packages=find_packages(),$/;" v +packages setup.py /^ packages=find_packages(include=['mcp_browser*', 'mcp_servers*']),$/;" v +pid_exists build/lib/mcp_browser/daemon.py /^ def pid_exists(pid):$/;" m class:psutil +pid_exists mcp_browser/daemon.py /^ def pid_exists(pid):$/;" m class:psutil +process build/lib/mcp_browser/logging_config.py /^ def process(self, msg, kwargs):$/;" m class:ServerNameAdapter +process mcp_browser/logging_config.py /^ def process(self, msg, kwargs):$/;" m class:ServerNameAdapter +psutil build/lib/mcp_browser/daemon.py /^ class psutil:$/;" c +psutil mcp_browser/daemon.py /^ class psutil:$/;" c +read_resource build/lib/mcp_browser/__main__.py /^ read_resource = subparsers.add_parser("resources-read", help="Read a resource")$/;" v +read_resource mcp_browser/__main__.py /^ read_resource = subparsers.add_parser("resources-read", help="Read a resource")$/;" v +register_tool build/lib/mcp_servers/base.py /^ def register_tool(self, name: str, description: str, input_schema: Dict[str, Any], $/;" m class:BaseMCPServer register_tool mcp_servers/base.py /^ def register_tool(self, name: str, description: str, input_schema: Dict[str, Any], $/;" m class:BaseMCPServer +route_tool_call build/lib/mcp_browser/multi_server.py /^ async def route_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MultiServerManager route_tool_call mcp_browser/multi_server.py /^ async def route_tool_call(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:$/;" m class:MultiServerManager +run build/lib/mcp_browser/client_main.py /^ async def run():$/;" f function:main +run build/lib/mcp_servers/base.py /^ async def run(self):$/;" m class:BaseMCPServer +run mcp_browser/client_main.py /^ async def run():$/;" f function:main run mcp_servers/base.py /^ async def run(self):$/;" m class:BaseMCPServer run setup.py /^ def run(self):$/;" m class:GenerateAIDocs +run setup.py /^ def run(self):$/;" m class:TestCommand +run_command build/lib/mcp_browser/client_main.py /^async def run_command(args, request: Dict[str, Any]):$/;" f +run_command mcp_browser/client_main.py /^async def run_command(args, request: Dict[str, Any]):$/;" f +run_daemon build/lib/mcp_browser/daemon_main.py /^async def run_daemon(args):$/;" f +run_daemon mcp_browser/daemon_main.py /^async def run_daemon(args):$/;" f +run_daemon_mode build/lib/mcp_browser/__main__.py /^async def run_daemon_mode(browser: MCPBrowser, socket_path: Path):$/;" f +run_daemon_mode mcp_browser/__main__.py /^async def run_daemon_mode(browser: MCPBrowser, socket_path: Path):$/;" f +run_mcp_server_mode build/lib/mcp_browser/client_main.py /^async def run_mcp_server_mode(args):$/;" f +run_mcp_server_mode mcp_browser/client_main.py /^async def run_mcp_server_mode(args):$/;" f +run_server_mode build/lib/mcp_browser/__main__.py /^async def run_server_mode(browser: MCPBrowser):$/;" f run_server_mode mcp_browser/__main__.py /^async def run_server_mode(browser: MCPBrowser):$/;" f +run_server_mode_with_daemon build/lib/mcp_browser/__main__.py /^async def run_server_mode_with_daemon(socket_path: Path):$/;" f +run_server_mode_with_daemon mcp_browser/__main__.py /^async def run_server_mode_with_daemon(socket_path: Path):$/;" f +save_config build/lib/mcp_browser/default_configs.py /^ def save_config(self, config: dict) -> None:$/;" m class:ConfigManager +save_config mcp_browser/default_configs.py /^ def save_config(self, config: dict) -> None:$/;" m class:ConfigManager +send_raw build/lib/mcp_browser/server.py /^ def send_raw(self, message: str):$/;" m class:MCPServer send_raw mcp_browser/server.py /^ def send_raw(self, message: str):$/;" m class:MCPServer +send_request build/lib/mcp_browser/server.py /^ async def send_request(self, method: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:$/;" m class:MCPServer send_request mcp_browser/server.py /^ async def send_request(self, method: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:$/;" m class:MCPServer +send_request tests/test_integration.py /^ async def send_request(self, method: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:$/;" m class:JSONRPCTestClient +server build/lib/mcp_servers/memory/memory_server.py /^ server = MemoryServer()$/;" v class:MemoryServer +server build/lib/mcp_servers/onboarding/onboarding_server.py /^ server = OnboardingServer()$/;" v class:OnboardingServer +server build/lib/mcp_servers/pattern_manager/pattern_server.py /^ server = PatternServer()$/;" v class:PatternServer +server build/lib/mcp_servers/screen/screen_server.py /^ server = ScreenServer()$/;" v class:ScreenServer server mcp_servers/memory/memory_server.py /^ server = MemoryServer()$/;" v class:MemoryServer server mcp_servers/onboarding/onboarding_server.py /^ server = OnboardingServer()$/;" v class:OnboardingServer server mcp_servers/pattern_manager/pattern_server.py /^ server = PatternServer()$/;" v class:PatternServer server mcp_servers/screen/screen_server.py /^ server = ScreenServer()$/;" v class:ScreenServer +server_name build/lib/mcp_browser/__main__.py /^ server_name=args.server,$/;" v +server_name mcp_browser/__main__.py /^ server_name=args.server,$/;" v +set_metadata build/lib/mcp_browser/registry.py /^ def set_metadata(self, key: str, value: Any):$/;" m class:ToolRegistry set_metadata mcp_browser/registry.py /^ def set_metadata(self, key: str, value: Any):$/;" m class:ToolRegistry -simulate_response tests/test_basic.py /^ async def simulate_response():$/;" f function:TestMCPBrowser.test_call_method +set_onboarding_text build/lib/mcp_browser/default_configs.py /^ def set_onboarding_text(self, text: str, identity: Optional[str] = None) -> None:$/;" m class:ConfigManager +set_onboarding_text mcp_browser/default_configs.py /^ def set_onboarding_text(self, text: str, identity: Optional[str] = None) -> None:$/;" m class:ConfigManager +setup_logging build/lib/mcp_browser/logging_config.py /^def setup_logging(debug: bool = False, log_file: Optional[Path] = None, $/;" f +setup_logging mcp_browser/logging_config.py /^def setup_logging(debug: bool = False, log_file: Optional[Path] = None, $/;" f +show_available_servers build/lib/mcp_browser/__main__.py /^def show_available_servers(config_path: Optional[str] = None):$/;" f +show_available_servers mcp_browser/__main__.py /^def show_available_servers(config_path: Optional[str] = None):$/;" f +show_configuration build/lib/mcp_browser/__main__.py /^def show_configuration(config_path: Optional[str] = None):$/;" f +show_configuration mcp_browser/__main__.py /^def show_configuration(config_path: Optional[str] = None):$/;" f +show_daemon_status build/lib/mcp_browser/__main__.py /^def show_daemon_status(args):$/;" f +show_daemon_status mcp_browser/__main__.py /^def show_daemon_status(args):$/;" f +signal_handler build/lib/mcp_browser/daemon_main.py /^ def signal_handler(signum):$/;" f function:run_daemon +signal_handler mcp_browser/daemon_main.py /^ def signal_handler(signum):$/;" f function:run_daemon +socket_path build/lib/mcp_browser/__main__.py /^ socket_path = get_socket_path(args.server)$/;" v +socket_path mcp_browser/__main__.py /^ socket_path = get_socket_path(args.server)$/;" v +start build/lib/mcp_browser/daemon.py /^ async def start(self):$/;" m class:MCPBrowserDaemon +start build/lib/mcp_browser/server.py /^ async def start(self):$/;" m class:MCPServer +start mcp_browser/daemon.py /^ async def start(self):$/;" m class:MCPBrowserDaemon start mcp_browser/server.py /^ async def start(self):$/;" m class:MCPServer +start_builtin_servers build/lib/mcp_browser/multi_server.py /^ async def start_builtin_servers(self):$/;" m class:MultiServerManager start_builtin_servers mcp_browser/multi_server.py /^ async def start_builtin_servers(self):$/;" m class:MultiServerManager +start_daemon_background build/lib/mcp_browser/__main__.py /^async def start_daemon_background(args):$/;" f +start_daemon_background mcp_browser/__main__.py /^async def start_daemon_background(args):$/;" f +start_daemon_if_needed build/lib/mcp_browser/client_main.py /^def start_daemon_if_needed(server_name: Optional[str] = None, timeout: float = 5.0) -> bool:$/;" f +start_daemon_if_needed mcp_browser/client_main.py /^def start_daemon_if_needed(server_name: Optional[str] = None, timeout: float = 5.0) -> bool:$/;" f +stop build/lib/mcp_browser/daemon.py /^ async def stop(self):$/;" m class:MCPBrowserDaemon +stop build/lib/mcp_browser/server.py /^ async def stop(self):$/;" m class:MCPServer +stop mcp_browser/daemon.py /^ async def stop(self):$/;" m class:MCPBrowserDaemon stop mcp_browser/server.py /^ async def stop(self):$/;" m class:MCPServer +stop_all build/lib/mcp_browser/multi_server.py /^ async def stop_all(self):$/;" m class:MultiServerManager stop_all mcp_browser/multi_server.py /^ async def stop_all(self):$/;" m class:MultiServerManager -test_call_method tests/test_basic.py /^ async def test_call_method(self):$/;" m class:TestMCPBrowser -test_claude_connection test_claude_connection.py /^async def test_claude_connection():$/;" f +stop_daemon build/lib/mcp_browser/__main__.py /^def stop_daemon(args):$/;" f +stop_daemon mcp_browser/__main__.py /^def stop_daemon(args):$/;" f +subparsers build/lib/mcp_browser/__main__.py /^ subparsers = parser.add_subparsers(dest="command", help="MCP methods")$/;" v +subparsers mcp_browser/__main__.py /^ subparsers = parser.add_subparsers(dest="command", help="MCP methods")$/;" v +test_basic_flow tests/test_integration.py /^async def test_basic_flow():$/;" f +test_brave_search_integration tests/test_brave_search.py /^async def test_brave_search_integration():$/;" f +test_browser_without_servers tests/test_basic.py /^ async def test_browser_without_servers(self):$/;" m class:TestMCPBrowser +test_browser_without_servers tests/test_browser_functionality.py /^async def test_browser_without_servers():$/;" f +test_claude_connection tests/test_claude_connection.py /^async def test_claude_connection():$/;" f test_discover_jsonpath tests/test_basic.py /^ def test_discover_jsonpath(self):$/;" m class:TestToolRegistry -test_discover_method tests/test_basic.py /^ async def test_discover_method(self):$/;" m class:TestMCPBrowser test_duplicate_error_filtering tests/test_basic.py /^ def test_duplicate_error_filtering(self):$/;" m class:TestMessageFilter -test_initialization tests/test_basic.py /^ async def test_initialization(self):$/;" m class:TestMCPBrowser -test_onboarding test_onboarding.py /^async def test_onboarding():$/;" f +test_error_handling tests/test_integration.py /^async def test_error_handling():$/;" f +test_message_filter tests/test_simple.py /^def test_message_filter():$/;" f +test_onboarding tests/test_onboarding.py /^async def test_onboarding():$/;" f +test_server_connection build/lib/mcp_browser/__main__.py /^async def test_server_connection(browser: MCPBrowser, server_name: Optional[str] = None):$/;" f +test_server_connection mcp_browser/__main__.py /^async def test_server_connection(browser: MCPBrowser, server_name: Optional[str] = None):$/;" f +test_sparse_mode tests/test_simple.py /^def test_sparse_mode():$/;" f test_sparse_mode_filtering tests/test_basic.py /^ def test_sparse_mode_filtering(self):$/;" m class:TestMessageFilter test_sparse_tools tests/test_basic.py /^ def test_sparse_tools(self):$/;" m class:TestToolRegistry +test_tool_registry tests/test_simple.py /^def test_tool_registry():$/;" f test_update_tools tests/test_basic.py /^ def test_update_tools(self):$/;" m class:TestToolRegistry +test_virtual_tool_handling tests/test_basic.py /^ async def test_virtual_tool_handling(self):$/;" m class:TestMCPBrowser +to_json build/lib/mcp_browser/registry.py /^ def to_json(self) -> str:$/;" m class:ToolRegistry to_json mcp_browser/registry.py /^ def to_json(self) -> str:$/;" m class:ToolRegistry +trace build/lib/mcp_browser/logging_config.py /^def trace(self, message, *args, **kwargs):$/;" f +trace mcp_browser/logging_config.py /^def trace(self, message, *args, **kwargs):$/;" f +update_checksums build/lib/mcp_browser/default_configs.py /^def update_checksums():$/;" f +update_checksums mcp_browser/default_configs.py /^def update_checksums():$/;" f +update_tools build/lib/mcp_browser/registry.py /^ def update_tools(self, tools: List[Dict[str, Any]]):$/;" m class:ToolRegistry update_tools mcp_browser/registry.py /^ def update_tools(self, tools: List[Dict[str, Any]]):$/;" m class:ToolRegistry -url setup.py /^ url="https:\/\/github.com\/anthropics\/mcp-browser",$/;" v +url setup.py /^ url="https:\/\/github.com\/Xilope0\/mcp-browser",$/;" v user_options setup.py /^ user_options = []$/;" v class:GenerateAIDocs +user_options setup.py /^ user_options = []$/;" v class:TestCommand +version build/lib/mcp_browser/__main__.py /^ version=f"%(prog)s {__version__}",$/;" v +version mcp_browser/__main__.py /^ version=f"%(prog)s {__version__}",$/;" v version setup.py /^ version="0.1.0",$/;" v diff --git a/docs/STRUCTURE.md b/docs/STRUCTURE.md index b1ed1a4..6243a9d 100644 --- a/docs/STRUCTURE.md +++ b/docs/STRUCTURE.md @@ -3,33 +3,84 @@ ``` ./ setup.py - test_claude_connection.py - test_onboarding.py + mcp_browser.egg-info/ + systemd/ + build/ + lib/ + mcp_servers/ + __init__.py + base.py + screen/ + __init__.py + screen_server.py + memory/ + __init__.py + memory_server.py + pattern_manager/ + __init__.py + pattern_server.py + onboarding/ + __init__.py + onboarding_server.py + mcp_browser/ + __init__.py + __main__.py + buffer.py + client_main.py + config.py + daemon.py + daemon_main.py + default_configs.py + filter.py + logging_config.py + multi_server.py + proxy.py + registry.py + server.py + utils.py + bdist.linux-x86_64/ docs/ examples/ ai_optimized.py basic_usage.py builtin_servers_demo.py complete_demo.py + dist/ tests/ test_basic.py + test_brave_search.py + test_browser_functionality.py + test_claude_connection.py + test_integration.py + test_onboarding.py + test_simple.py config/ mcp_servers/ + __init__.py base.py screen/ + __init__.py screen_server.py memory/ + __init__.py memory_server.py pattern_manager/ + __init__.py pattern_server.py onboarding/ + __init__.py onboarding_server.py mcp_browser/ __init__.py __main__.py buffer.py + client_main.py config.py + daemon.py + daemon_main.py + default_configs.py filter.py + logging_config.py multi_server.py proxy.py registry.py diff --git a/init-cmd.json b/init-cmd.json new file mode 100644 index 0000000..a0bb711 --- /dev/null +++ b/init-cmd.json @@ -0,0 +1 @@ +{"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-ai","version":"0.1.0"}},"jsonrpc":"2.0","id":0} diff --git a/mcp_browser/client_main.py b/mcp_browser/client_main.py index cf02d5f..300ea43 100644 --- a/mcp_browser/client_main.py +++ b/mcp_browser/client_main.py @@ -274,12 +274,23 @@ def main(): # Setup logging log_file = Path(args.log_file) if args.log_file else None + + # In server mode, use syslog unless a log file is specified + use_syslog = args.mode == "server" and not log_file + setup_logging( debug=args.debug, log_file=log_file, - log_level=args.log_level + log_level=args.log_level, + use_syslog=use_syslog ) + # Log startup message + logger = get_logger(__name__) + if args.mode == "server": + logger.debug("mcp-browser client started in server mode") + + # Handle modes if args.mode == "server": # Run as MCP server @@ -302,4 +313,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/mcp_browser/daemon.py b/mcp_browser/daemon.py index e02b9ae..f53c693 100644 --- a/mcp_browser/daemon.py +++ b/mcp_browser/daemon.py @@ -274,4 +274,73 @@ def is_daemon_running(socket_path: Path) -> bool: pid = int(pid_file.read_text().strip()) return psutil.pid_exists(pid) except (ValueError, psutil.Error): - return False \ No newline at end of file + return False + + +def kill_daemon_with_children(socket_path: Path) -> bool: + """Kill the daemon and all its child processes.""" + pid_file = socket_path.with_suffix('.pid') + if not pid_file.exists(): + return False + + try: + pid = int(pid_file.read_text().strip()) + + # Try to use psutil to kill the process tree + if hasattr(psutil, 'Process'): + try: + parent = psutil.Process(pid) + children = parent.children(recursive=True) + + # Kill children first + for child in children: + try: + child.terminate() + except psutil.NoSuchProcess: + pass + + # Kill parent + parent.terminate() + + # Wait a bit for graceful termination + import time + time.sleep(0.5) + + # Force kill any remaining processes + for child in children: + try: + child.kill() + except psutil.NoSuchProcess: + pass + + try: + parent.kill() + except psutil.NoSuchProcess: + pass + + return True + except (psutil.NoSuchProcess, psutil.AccessDenied): + pass + + # Fallback to simple kill + try: + os.kill(pid, signal.SIGTERM) + import time + time.sleep(0.5) + os.kill(pid, signal.SIGKILL) + except OSError: + pass + + return True + except (ValueError, OSError): + return False + finally: + # Clean up socket and PID file + try: + socket_path.unlink() + except FileNotFoundError: + pass + try: + pid_file.unlink() + except FileNotFoundError: + pass \ No newline at end of file diff --git a/mcp_browser/filter.py b/mcp_browser/filter.py index abe7ad5..d3bd6b4 100644 --- a/mcp_browser/filter.py +++ b/mcp_browser/filter.py @@ -5,17 +5,18 @@ Intercepts and modifies JSON-RPC messages to implement sparse mode and virtual tool injection. """ +import sys import json from typing import Dict, Any, Optional, List, Callable, Union from .registry import ToolRegistry class MessageFilter: - """Filter and transform JSON-RPC messages for sparse mode.""" + """Filter and transform JSON-RPC messages to always show sparse tools.""" def __init__(self, registry: ToolRegistry, sparse_mode: bool = True): self.registry = registry - self.sparse_mode = sparse_mode + # Ignore sparse_mode parameter - always use sparse mode self._handled_ids: set = set() def filter_outgoing(self, message: dict) -> Optional[dict]: @@ -48,9 +49,8 @@ class MessageFilter: self._handled_ids.discard(message.get("id")) return None - # Intercept tools/list responses for sparse mode - if (self.sparse_mode and - message.get("id") and + # ALWAYS intercept tools/list responses to show only sparse tools + if (message.get("id") and message.get("result", {}).get("tools")): return self._filter_tools_response(message) @@ -66,7 +66,8 @@ class MessageFilter: # Replace with sparse tools message = message.copy() message["result"] = message["result"].copy() - message["result"]["tools"] = self.registry.get_sparse_tools() + sparse_tools = self.registry.get_sparse_tools() + message["result"]["tools"] = sparse_tools return message diff --git a/mcp_browser/logging_config.py b/mcp_browser/logging_config.py index 324d5ba..78d1197 100644 --- a/mcp_browser/logging_config.py +++ b/mcp_browser/logging_config.py @@ -4,6 +4,7 @@ Logging configuration for MCP Browser. import logging import sys +import os from pathlib import Path from typing import Optional from datetime import datetime @@ -17,7 +18,8 @@ class ServerNameAdapter(logging.LoggerAdapter): return f"[{server}] {msg}", kwargs -def setup_logging(debug: bool = False, log_file: Optional[Path] = None, log_level: Optional[str] = None): +def setup_logging(debug: bool = False, log_file: Optional[Path] = None, + log_level: Optional[str] = None, use_syslog: bool = False): """ Configure logging for MCP Browser. @@ -25,6 +27,7 @@ def setup_logging(debug: bool = False, log_file: Optional[Path] = None, log_leve debug: Enable debug logging log_file: Optional file to write logs to log_level: Override log level (DEBUG, INFO, WARNING, ERROR) + use_syslog: Use syslog instead of console/file (for server mode) """ # Determine log level if log_level: @@ -52,16 +55,51 @@ def setup_logging(debug: bool = False, log_file: Optional[Path] = None, log_leve # Remove existing handlers root_logger.handlers.clear() - # Console handler (stderr) - console_handler = logging.StreamHandler(sys.stderr) - console_handler.setFormatter(formatter) - root_logger.addHandler(console_handler) - - # File handler if requested - if log_file: + if use_syslog: + # Use syslog for server mode - NEVER write to stdout/stderr + from logging.handlers import SysLogHandler + try: + # Try Unix socket first (most common on Linux) + syslog_handler = SysLogHandler( + facility=SysLogHandler.LOG_DAEMON, + address='/dev/log' + ) + except (OSError, FileNotFoundError): + # Fall back to UDP socket (for macOS, some Linux distros) + try: + syslog_handler = SysLogHandler( + facility=SysLogHandler.LOG_DAEMON, + address=('localhost', 514) + ) + except Exception: + # If syslog is not available, log to a file instead + import tempfile + fallback_log = Path(tempfile.gettempdir()) / 'mcp-browser-server.log' + file_handler = logging.FileHandler(fallback_log, mode='a') + file_handler.setFormatter(formatter) + root_logger.addHandler(file_handler) + return + + # Syslog has its own format, but we can prepend our app name + syslog_formatter = logging.Formatter('mcp-browser[%(process)d]: %(name)s - %(levelname)s - %(message)s') + syslog_handler.setFormatter(syslog_formatter) + root_logger.addHandler(syslog_handler) + elif log_file: + # File handler if requested file_handler = logging.FileHandler(log_file, mode='a') file_handler.setFormatter(formatter) root_logger.addHandler(file_handler) + + # Only add console handler if not logging to /dev/null + if str(log_file) != '/dev/null': + console_handler = logging.StreamHandler(sys.stderr) + console_handler.setFormatter(formatter) + root_logger.addHandler(console_handler) + else: + # Console handler (stderr) only if no file specified + console_handler = logging.StreamHandler(sys.stderr) + console_handler.setFormatter(formatter) + root_logger.addHandler(console_handler) # Set levels for specific loggers # Suppress some noisy libraries unless in debug mode diff --git a/mcp_browser/multi_server.py b/mcp_browser/multi_server.py index fcb629a..aee399d 100644 --- a/mcp_browser/multi_server.py +++ b/mcp_browser/multi_server.py @@ -62,7 +62,7 @@ class MultiServerManager: # Initialize each server await server.send_request("initialize", { - "protocolVersion": "0.1.0", + "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": { "name": "mcp-browser", @@ -84,7 +84,7 @@ class MultiServerManager: # Initialize await server.send_request("initialize", { - "protocolVersion": "0.1.0", + "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": { "name": "mcp-browser", @@ -148,8 +148,14 @@ class MultiServerManager: async def stop_all(self): """Stop all servers.""" - for name, server in self.servers.items(): + # Create a copy of the dictionary to avoid iteration errors + servers_copy = dict(self.servers) + + for name, server in servers_copy.items(): self.logger.info(f"Stopping server: {name}") - await server.stop() + try: + await server.stop() + except Exception as e: + self.logger.error(f"Error stopping server {name}: {e}") self.servers.clear() \ No newline at end of file diff --git a/mcp_browser/proxy.py b/mcp_browser/proxy.py index b6131b2..fc00fea 100644 --- a/mcp_browser/proxy.py +++ b/mcp_browser/proxy.py @@ -129,9 +129,6 @@ class MCPBrowser: } }) """ - if not self._initialized: - await self.initialize() - # Ensure request has an ID if "id" not in jsonrpc_object: jsonrpc_object = jsonrpc_object.copy() @@ -140,6 +137,32 @@ class MCPBrowser: request_id = jsonrpc_object["id"] + # Handle initialize request specially when acting as a server + if jsonrpc_object.get("method") == "initialize": + # Initialize ourselves if needed + if not self._initialized: + await self.initialize() + + # Return our capabilities + return { + "jsonrpc": "2.0", + "id": request_id, + "result": { + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {} + }, + "serverInfo": { + "name": "mcp-browser", + "version": "0.1.0" + } + } + } + + # Initialize if needed for other requests + if not self._initialized: + await self.initialize() + # Check if this is a virtual tool call if jsonrpc_object.get("method") == "tools/call": tool_name = jsonrpc_object.get("params", {}).get("name") @@ -173,16 +196,21 @@ class MCPBrowser: # In builtin-only mode, try to route to multi-server if self.multi_server: # Try to route based on method - if jsonrpc_object.get("method") == "tools/list": - # Apply filter to multi-server tools + method = jsonrpc_object.get("method") + + if method == "tools/list": + # Get all tools and apply sparse filter tools = await self.multi_server.get_all_tools() - filtered_tools = self.filter.filter_outgoing({ + response = { "jsonrpc": "2.0", "id": request_id, "result": {"tools": tools} - }) - return filtered_tools - elif jsonrpc_object.get("method") == "tools/call": + } + # Always filter to show only sparse tools + filtered_response = self.filter.filter_incoming(response) + return filtered_response + + elif method == "tools/call": # Route tool call to multi-server tool_name = jsonrpc_object.get("params", {}).get("name") args = jsonrpc_object.get("params", {}).get("arguments", {}) @@ -199,6 +227,57 @@ class MCPBrowser: "id": request_id, "error": {"code": -32603, "message": str(e)} } + + elif method == "prompts/list": + # No prompts in builtin-only mode + return { + "jsonrpc": "2.0", + "id": request_id, + "result": {"prompts": []} + } + + elif method == "prompts/get": + # No prompts available + return { + "jsonrpc": "2.0", + "id": request_id, + "error": {"code": -32602, "message": "Prompt not found"} + } + + elif method == "resources/list": + # No resources in builtin-only mode + return { + "jsonrpc": "2.0", + "id": request_id, + "result": {"resources": []} + } + + elif method == "resources/read": + # No resources available + return { + "jsonrpc": "2.0", + "id": request_id, + "error": {"code": -32602, "message": "Resource not found"} + } + + elif method == "completion/complete": + # No completions in builtin-only mode + return { + "jsonrpc": "2.0", + "id": request_id, + "result": {"completion": {"values": []}} + } + + else: + # Unknown method + return { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32601, + "message": f"Method '{method}' not found" + } + } # No server available return { @@ -269,7 +348,7 @@ class MCPBrowser: # Send initialize request directly to server init_response = await self.server.send_request("initialize", { - "protocolVersion": "0.1.0", + "protocolVersion": "2024-11-05", "capabilities": {}, "clientInfo": { "name": "mcp-browser", diff --git a/mcp_servers/base.py b/mcp_servers/base.py index 4a8d80e..9e2693a 100644 --- a/mcp_servers/base.py +++ b/mcp_servers/base.py @@ -105,58 +105,83 @@ class BaseMCPServer(ABC): """Run the MCP server, reading from stdin and writing to stdout.""" self._running = True - # Platform-specific non-blocking setup + # Log server startup + print(f"DEBUG: {self.name} server starting", file=sys.stderr, flush=True) + + # Use asyncio for stdin reading + loop = asyncio.get_event_loop() + stdin_reader = asyncio.StreamReader() + stdin_protocol = asyncio.StreamReaderProtocol(stdin_reader) + try: - import fcntl - import os - flags = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL) - fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK) - except ImportError: - # Windows doesn't have fcntl - pass - - buffer = "" + # Connect stdin to async reader + await loop.connect_read_pipe(lambda: stdin_protocol, sys.stdin) + except Exception as e: + print(f"ERROR: {self.name} failed to setup async stdin: {e}", file=sys.stderr, flush=True) + return + # Main server loop while self._running: try: - # Try to read available data - chunk = sys.stdin.read(4096) - if not chunk: - # EOF reached + # Read a line with timeout to prevent hanging + line = await asyncio.wait_for( + stdin_reader.readline(), + timeout=None # No timeout - we want to wait indefinitely for commands + ) + + if not line: + # Empty bytes means EOF + print(f"DEBUG: {self.name} detected EOF on stdin", file=sys.stderr, flush=True) break - buffer += chunk - # Process complete lines - while '\n' in buffer: - line, buffer = buffer.split('\n', 1) - line = line.strip() + # Decode and process line + line_str = line.decode('utf-8').strip() + if not line_str: + continue + + print(f"DEBUG: {self.name} received: {line_str}", file=sys.stderr, flush=True) + + try: + request = json.loads(line_str) + response = await self.handle_request(request) + response_str = json.dumps(response) + print(f"DEBUG: {self.name} sending: {response_str}", file=sys.stderr, flush=True) + print(response_str, flush=True) + except json.JSONDecodeError as e: + print(f"ERROR: {self.name} JSON decode error: {e}", file=sys.stderr, flush=True) + error_response = { + "jsonrpc": "2.0", + "id": None, + "error": { + "code": -32700, + "message": "Parse error" + } + } + print(json.dumps(error_response), flush=True) + except Exception as e: + print(f"ERROR: {self.name} request handling error: {e}", file=sys.stderr, flush=True) + error_response = { + "jsonrpc": "2.0", + "id": None, + "error": { + "code": -32603, + "message": str(e) + } + } + print(json.dumps(error_response), flush=True) - if line: - try: - request = json.loads(line) - response = await self.handle_request(request) - print(json.dumps(response), flush=True) - except json.JSONDecodeError: - pass - except Exception as e: - error_response = { - "jsonrpc": "2.0", - "id": None, - "error": { - "code": -32700, - "message": "Parse error" - } - } - print(json.dumps(error_response), flush=True) - - except BlockingIOError: - # No data available, sleep briefly - await asyncio.sleep(0.01) - except EOFError: - # stdin closed + except asyncio.CancelledError: + print(f"DEBUG: {self.name} cancelled", file=sys.stderr, flush=True) break except KeyboardInterrupt: + print(f"DEBUG: {self.name} interrupted", file=sys.stderr, flush=True) break + except Exception as e: + print(f"ERROR: {self.name} unexpected error: {e}", file=sys.stderr, flush=True) + # Continue running despite errors + await asyncio.sleep(0.1) + + print(f"DEBUG: {self.name} server exiting", file=sys.stderr, flush=True) def content_text(self, text: str) -> Dict[str, Any]: """Helper to create text content response.""" diff --git a/setup.py b/setup.py index d1d122c..e529400 100644 --- a/setup.py +++ b/setup.py @@ -205,9 +205,9 @@ setup( python_requires=">=3.8", entry_points={ "console_scripts": [ - "mcp-browser=mcp_browser.client_main:main", + "mcp-browser=mcp_browser.__main__:main", "mcp-browser-daemon=mcp_browser.daemon_main:main", - "mcp-browser-legacy=mcp_browser.__main__:main", + "mcp-browser-client=mcp_browser.client_main:main", ], }, cmdclass={ diff --git a/test_claude_desktop_flow.py b/test_claude_desktop_flow.py new file mode 100644 index 0000000..e0597d5 --- /dev/null +++ b/test_claude_desktop_flow.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +""" +Test the exact Claude Desktop flow to identify the timeout issue. +""" + +import asyncio +import json +import subprocess +import sys +import os + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + + +async def test_server_mode_timeout(): + """Test server mode with proper timeout handling.""" + print("Testing Claude Desktop flow with timeout...") + + # Start mcp-browser in server mode + proc = subprocess.Popen( + [sys.executable, "-m", "mcp_browser", "--mode", "server"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1 + ) + + try: + # Send initialize request like Claude Desktop does + init_request = { + "jsonrpc": "2.0", + "id": 0, + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": { + "name": "claude-desktop", + "version": "0.7.2" + } + } + } + + print(f"Sending: {json.dumps(init_request)}") + proc.stdin.write(json.dumps(init_request) + "\n") + proc.stdin.flush() + + # Wait for response with timeout + response_received = False + start_time = asyncio.get_event_loop().time() + + while asyncio.get_event_loop().time() - start_time < 5.0: # 5 second timeout + try: + # Non-blocking read + line = proc.stdout.readline() + if line: + response = json.loads(line.strip()) + print(f"Received: {json.dumps(response, indent=2)}") + response_received = True + break + except: + await asyncio.sleep(0.1) + continue + + await asyncio.sleep(0.1) + + if not response_received: + print("ERROR: No response received within 5 seconds!") + + # Check stderr for errors + stderr_output = proc.stderr.read() + if stderr_output: + print(f"STDERR: {stderr_output}") + else: + # Send initialized notification + initialized = { + "jsonrpc": "2.0", + "method": "initialized" + } + print(f"\nSending: {json.dumps(initialized)}") + proc.stdin.write(json.dumps(initialized) + "\n") + proc.stdin.flush() + + # Test tools/list + tools_request = { + "jsonrpc": "2.0", + "id": 1, + "method": "tools/list" + } + print(f"\nSending: {json.dumps(tools_request)}") + proc.stdin.write(json.dumps(tools_request) + "\n") + proc.stdin.flush() + + # Wait for tools response + start_time = asyncio.get_event_loop().time() + while asyncio.get_event_loop().time() - start_time < 2.0: + line = proc.stdout.readline() + if line: + response = json.loads(line.strip()) + print(f"Received: {json.dumps(response, indent=2)}") + break + await asyncio.sleep(0.1) + + finally: + proc.terminate() + proc.wait() + + +async def test_with_logging(): + """Test with debug logging enabled.""" + print("\n\nTesting with debug logging...") + + # Start mcp-browser in server mode with debug + proc = subprocess.Popen( + [sys.executable, "-m", "mcp_browser", "--mode", "server", "--debug", "--log-file", "/tmp/mcp-test.log"], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1 + ) + + try: + # Send initialize + init_request = { + "jsonrpc": "2.0", + "id": 0, + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": { + "name": "test-client", + "version": "1.0.0" + } + } + } + + proc.stdin.write(json.dumps(init_request) + "\n") + proc.stdin.flush() + + # Read with timeout + await asyncio.sleep(1.0) + + # Try to read response + output = proc.stdout.read() + if output: + print(f"STDOUT: {output}") + + stderr = proc.stderr.read() + if stderr: + print(f"STDERR: {stderr}") + + # Check log file + try: + with open("/tmp/mcp-test.log", "r") as f: + log_content = f.read() + if log_content: + print(f"\nLOG FILE:\n{log_content}") + except: + pass + + finally: + proc.terminate() + proc.wait() + + +if __name__ == "__main__": + asyncio.run(test_server_mode_timeout()) + asyncio.run(test_with_logging()) \ No newline at end of file diff --git a/test_mcp_protocol.py b/test_mcp_protocol.py new file mode 100644 index 0000000..a2275b1 --- /dev/null +++ b/test_mcp_protocol.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python3 +""" +Test MCP Protocol flow to understand and verify the initialization handshake. + +This test will: +1. Create a minimal MCP server that logs all interactions +2. Create an MCP browser client +3. Verify the complete initialization flow +4. Test tools/list and tools/call +""" + +import asyncio +import json +import sys +import os +import tempfile +from pathlib import Path + +# Add project to path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +from mcp_browser import MCPBrowser +from mcp_browser.daemon import MCPBrowserDaemon, MCPBrowserClient, get_socket_path + + +def log_test(msg): + """Log test messages with timestamp.""" + import datetime + timestamp = datetime.datetime.now().strftime("%H:%M:%S.%f")[:-3] + print(f"[{timestamp}] {msg}") + + +async def test_direct_initialization(): + """Test direct initialization without daemon.""" + log_test("=== TEST 1: Direct Initialization ===") + + # Create browser with correct config + browser = MCPBrowser(enable_builtin_servers=True) + + try: + log_test("Initializing browser...") + await browser.initialize() + log_test("✓ Browser initialized successfully") + + # Test tools/list + log_test("Calling tools/list...") + response = await browser.call({ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/list" + }) + + if "result" in response: + tools = response["result"].get("tools", []) + log_test(f"✓ Got {len(tools)} tools: {[t['name'] for t in tools]}") + else: + log_test(f"✗ Error in tools/list: {response}") + + except Exception as e: + log_test(f"✗ Error: {e}") + import traceback + traceback.print_exc() + finally: + await browser.close() + + +async def test_daemon_initialization(): + """Test initialization through daemon.""" + log_test("\n=== TEST 2: Daemon Initialization ===") + + # Use a temporary socket + with tempfile.TemporaryDirectory() as tmpdir: + socket_path = Path(tmpdir) / "test-mcp.sock" + + # Create browser and daemon + browser = MCPBrowser(enable_builtin_servers=True) + daemon = MCPBrowserDaemon(browser, socket_path) + + # Start daemon in background + daemon_task = asyncio.create_task(daemon.start()) + + try: + # Give daemon time to start + await asyncio.sleep(0.1) + + # Connect as client + async with MCPBrowserClient(socket_path) as client: + log_test("Connected to daemon") + + # Send initialize - this should be handled by the proxy + log_test("Sending initialize request...") + init_request = { + "jsonrpc": "2.0", + "id": 0, + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": { + "name": "test-client", + "version": "1.0.0" + } + } + } + + response = await client.call(init_request) + log_test(f"Initialize response: {json.dumps(response, indent=2)}") + + # Check response + if "result" in response: + result = response["result"] + if result.get("protocolVersion") == "2024-11-05": + log_test("✓ Got correct protocol version") + else: + log_test(f"✗ Wrong protocol version: {result.get('protocolVersion')}") + else: + log_test(f"✗ Error in initialize: {response}") + + # Test tools/list + log_test("\nSending tools/list request...") + tools_request = { + "jsonrpc": "2.0", + "id": 1, + "method": "tools/list" + } + + response = await client.call(tools_request) + if "result" in response: + tools = response["result"].get("tools", []) + log_test(f"✓ Got {len(tools)} tools: {[t['name'] for t in tools]}") + else: + log_test(f"✗ Error in tools/list: {response}") + + finally: + # Stop daemon + await daemon.stop() + daemon_task.cancel() + try: + await daemon_task + except asyncio.CancelledError: + pass + + +async def test_server_mode_initialization(): + """Test server mode (stdin/stdout) initialization.""" + log_test("\n=== TEST 3: Server Mode Initialization ===") + + # This simulates what Claude Desktop does + browser = MCPBrowser(enable_builtin_servers=True) + await browser.initialize() + + # Simulate initialize request from Claude Desktop + init_request = { + "jsonrpc": "2.0", + "id": 0, + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": { + "name": "claude-desktop", + "version": "0.7.2" + } + } + } + + log_test("Processing initialize request from Claude Desktop...") + response = await browser.call(init_request) + log_test(f"Response: {json.dumps(response, indent=2)}") + + # Check if we need to send initialized notification + if "result" in response: + log_test("✓ Initialize successful") + + # Claude Desktop would send initialized notification + initialized_notif = { + "jsonrpc": "2.0", + "method": "initialized" + } + log_test("Sending initialized notification...") + # Notifications don't expect responses + await browser.call(initialized_notif) + + # Now test tools/list + tools_request = { + "jsonrpc": "2.0", + "id": 1, + "method": "tools/list" + } + response = await browser.call(tools_request) + if "result" in response: + tools = response["result"].get("tools", []) + log_test(f"✓ Got {len(tools)} tools in sparse mode") + else: + log_test(f"✗ Error in tools/list: {response}") + + await browser.close() + + +async def test_double_handshake_issue(): + """Test the specific issue: Claude Desktop -> mcp-browser -> MCP servers.""" + log_test("\n=== TEST 4: Double Handshake Issue ===") + + # When Claude Desktop connects to mcp-browser in server mode: + # 1. Claude Desktop sends initialize to mcp-browser + # 2. mcp-browser should respond with its capabilities + # 3. mcp-browser internally initializes connections to MCP servers + # 4. But it should NOT forward the initialize request to MCP servers + + browser = MCPBrowser(enable_builtin_servers=True) + + # The browser should initialize its internal servers first + log_test("Browser initializing internal servers...") + await browser.initialize() + + # Now simulate Claude Desktop connecting + init_from_claude = { + "jsonrpc": "2.0", + "id": 0, + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": { + "name": "claude-desktop", + "version": "0.7.2" + } + } + } + + log_test("Claude Desktop sends initialize...") + response = await browser.call(init_from_claude) + + if "result" in response: + log_test("✓ mcp-browser responded to initialize") + log_test(f" Protocol: {response['result'].get('protocolVersion')}") + log_test(f" Server: {response['result'].get('serverInfo', {}).get('name')}") + else: + log_test(f"✗ Error: {response}") + + await browser.close() + + +async def main(): + """Run all tests.""" + log_test("Starting MCP Protocol Tests") + log_test("=" * 60) + + # Run tests + await test_direct_initialization() + await test_daemon_initialization() + await test_server_mode_initialization() + await test_double_handshake_issue() + + log_test("\n" + "=" * 60) + log_test("Tests completed!") + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file