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)