跳轉至

mcp_server

boring.mcp.server

Boring MCP Server - Optimized for Fast Startup (V14.0)

Target: < 300ms startup time

Optimization Strategies: 1. Lazy imports for heavy modules (chromadb, torch, etc.) 2. Deferred tool registration until first use 3. Profile-based tool filtering to reduce initial load 4. Background pre-warming for optional dependencies

get_server_instance()

Get the configured FastMCP server instance (raw). Use this for direct access without Smithery decorators (e.g. for http.py).

Source code in src/boring/mcp/server.py
def get_server_instance():
    """
    Get the configured FastMCP server instance (raw).
    Use this for direct access without Smithery decorators (e.g. for http.py).
    """
    # <!-- CACHEABLE_CONTENT_START -->
    os.environ["BORING_MCP_MODE"] = "1"

    if not instance.MCP_AVAILABLE:
        raise RuntimeError("'fastmcp' not found. Install with: pip install boring-aicoding[mcp]")

    # Register Resources
    @instance.mcp.resource("boring://logs")
    def get_logs() -> str:
        """Get recent server logs."""
        return "Log access not implemented in stdio mode"

    # Common Helpers
    helpers = {
        "get_project_root_or_error": get_project_root_or_error,
        "detect_project_root": detect_project_root,
        "configure_runtime": configure_runtime_for_project,
    }

    # Unified Tool Registration
    _register_all_tools(instance.mcp, audited, helpers)

    # <!-- CACHEABLE_CONTENT_END -->
    return instance.mcp

create_server()

Create and return a FastMCP server instance for Smithery deployment.

This function is called by Smithery to get the server instance. It must be decorated with @smithery.server() and return a FastMCP instance.

Note: Smithery uses HTTP transport, not stdio.

Source code in src/boring/mcp/server.py
def create_server():
    """
    Create and return a FastMCP server instance for Smithery deployment.

    This function is called by Smithery to get the server instance.
    It must be decorated with @smithery.server() and return a FastMCP instance.

    Note: Smithery uses HTTP transport, not stdio.
    """
    mcp_instance = get_server_instance()

    if os.environ.get("BORING_MCP_DEBUG") == "1":
        _log_stderr("mcp_server_creating")
        try:
            tool_count = len(_get_tools_robust(mcp_instance))
            _log_stderr("mcp_server_registered_tools", count=tool_count)
        except Exception as e:
            logger.debug("Failed to count tools in create_server: %s", e)

    return mcp_instance

run_server()

Main entry point for the Boring MCP server (stdio transport). Used for local CLI execution: boring-mcp

Source code in src/boring/mcp/server.py
def run_server():
    """
    Main entry point for the Boring MCP server (stdio transport).
    Used for local CLI execution: boring-mcp
    """
    # 0. Set MCP Mode flag to silence tool outputs (e.g. health check)
    os.environ["BORING_MCP_MODE"] = "1"

    if not instance.MCP_AVAILABLE:
        _log_stderr("mcp_fastmcp_missing")
        sys.exit(1)
        return

    # 1. Install stdout interceptor immediately
    interceptors.install_interceptors()

    # Helpers
    helpers = {
        "get_project_root_or_error": get_project_root_or_error,
        "detect_project_root": detect_project_root,
        "configure_runtime": configure_runtime_for_project,
    }

    # 2. Unified Tool Registration
    _register_all_tools(instance.mcp, audited, helpers)

    # 3. Profile & Router Setup
    profile = get_profile()
    get_tool_router()

    # V14.0: Hot Reload (Dev Mode)
    if os.environ.get("BORING_MCP_DEV") == "1":
        try:
            from .reloader import start_reloader

            start_reloader(instance.mcp)
        except Exception as e:
            _log_stderr("mcp_hot_reload_warning", error=str(e))

    # V10.29: Post-registration PROMPT filtering based on profile
    # FastMCP stores prompts in _prompts dict. We filter it directly.
    # This prevents context window bloat (52 prompts -> ~5 in LITE)
    from .tool_profiles import should_register_prompt

    if profile.prompts is not None:  # None means FULL profile (all prompts)
        # Access internal prompts storage
        # FastMCP 2.x usually has _prompts on the instance
        prompts_dict = getattr(instance.mcp, "_prompts", None)

        if prompts_dict and isinstance(prompts_dict, dict):
            original_prompt_count = len(prompts_dict)
            prompts_to_remove = [
                name for name in prompts_dict.keys() if not should_register_prompt(name, profile)
            ]

            for name in prompts_to_remove:
                del prompts_dict[name]

            filtered_prompt_count = len(prompts_dict)
            _log_stderr(
                "mcp_prompt_filter",
                original=original_prompt_count,
                filtered=filtered_prompt_count,
            )
        else:
            _log_stderr("mcp_prompt_filter_failed")

    # V10.26: Cache all tools BEFORE filtering for boring_discover
    # FastMCP 2.x uses various internal structures; use robust accessor
    tools_dict = _get_tools_robust(instance.mcp)
    instance._all_tools_cache = dict(tools_dict)

    # V10.26: Post-registration tool filtering based on profile
    # This removes tools not in the profile to reduce context window usage
    if profile.tools:  # Non-empty means we should filter (empty = FULL profile)
        # Always keep these essential tools regardless of profile
        essential_tools = {"boring", "boring_help"}
        allowed_tools = set(profile.tools) | essential_tools

        # Get current tools and filter
        original_count = len(tools_dict)
        tools_to_remove = [name for name in tools_dict.keys() if name not in allowed_tools]
        for tool_name in tools_to_remove:
            if tool_name in tools_dict:
                del tools_dict[tool_name]

        filtered_count = len(_get_tools_robust(instance.mcp))
        _log_stderr(
            "mcp_profile_filter",
            original=original_count,
            filtered=filtered_count,
            saved_tokens=(original_count - filtered_count) * 50,
        )

    # Vibe Coder Tutorial Hook - Show MCP intro on first launch
    # WRAPPED: Disabled in MCP Server mode to prevent stdout pollution (breaks JSON-RPC)
    # try:
    #     from ..tutorial import TutorialManager
    #
    #     TutorialManager().show_tutorial("mcp_intro")
    # except Exception as e:
    #     logger.debug("MCP tutorial hint unavailable: %s", e)

    # 4. Configured logging
    with _configure_logging():
        # Always check for optional RAG dependencies at startup
        import importlib.util

        rag_ok = importlib.util.find_spec("chromadb") and importlib.util.find_spec(
            "sentence_transformers"
        )
        if not rag_ok:
            _log_stderr(
                "mcp_rag_missing",
                python=sys.executable,
            )

        # V10.24: Show profile info
        tool_count = len(profile.tools) if profile.tools else "all"
        _log_stderr("mcp_tool_profile", profile=profile.name, tool_count=tool_count)

        if os.environ.get("BORING_MCP_DEBUG") == "1":
            _log_stderr("mcp_server_starting")
            _log_stderr("mcp_server_python", python=sys.executable)
            try:
                _log_stderr(
                    "mcp_server_registered_tools",
                    count=len(_get_tools_robust(instance.mcp)),
                )
            except Exception as e:
                logger.debug("Failed to count tools in run_server: %s", e)
            if rag_ok:
                _log_stderr("mcp_rag_found")

        # 3. Mark MCP as started (allows JSON-RPC traffic)
        if hasattr(sys.stdout, "mark_mcp_started"):
            sys.stdout.mark_mcp_started()

        # 4. Run the server
        # Explicitly use stdio transport
        try:
            instance.mcp.run(transport="stdio")
        except Exception as e:
            _log_stderr("mcp_critical_error", error=str(e))
            sys.exit(1)