99"""
1010
1111# Standard library imports
12+ import argparse
13+ import inspect
1214import sys
1315import logging
1416from contextlib import asynccontextmanager
@@ -52,6 +54,7 @@ def setup_indexing_performance_logging():
5254
5355# Initialize logging (no file handlers)
5456setup_indexing_performance_logging ()
57+ logger = logging .getLogger (__name__ )
5558
5659@dataclass
5760class CodeIndexerContext :
@@ -61,6 +64,24 @@ class CodeIndexerContext:
6164 file_count : int = 0
6265 file_watcher_service : FileWatcherService = None
6366
67+
68+ @dataclass
69+ class _CLIConfig :
70+ """Holds CLI configuration for bootstrap operations."""
71+ project_path : str | None = None
72+
73+
74+ class _BootstrapRequestContext :
75+ """Minimal request context to reuse business services during bootstrap."""
76+
77+ def __init__ (self , lifespan_context : CodeIndexerContext ):
78+ self .lifespan_context = lifespan_context
79+ self .session = None
80+ self .meta = None
81+
82+
83+ _CLI_CONFIG = _CLIConfig ()
84+
6485@asynccontextmanager
6586async def indexer_lifespan (_server : FastMCP ) -> AsyncIterator [CodeIndexerContext ]:
6687 """Manage the lifecycle of the Code Indexer MCP server."""
@@ -78,6 +99,23 @@ async def indexer_lifespan(_server: FastMCP) -> AsyncIterator[CodeIndexerContext
7899 )
79100
80101 try :
102+ # Bootstrap project path when provided via CLI.
103+ if _CLI_CONFIG .project_path :
104+ bootstrap_ctx = Context (
105+ request_context = _BootstrapRequestContext (context ),
106+ fastmcp = mcp
107+ )
108+ try :
109+ message = ProjectManagementService (bootstrap_ctx ).initialize_project (
110+ _CLI_CONFIG .project_path
111+ )
112+ logger .info ("Project initialized from CLI flag: %s" , message )
113+ except Exception as exc : # pylint: disable=broad-except
114+ logger .error ("Failed to initialize project from CLI flag: %s" , exc )
115+ raise RuntimeError (
116+ f"Failed to initialize project path '{ _CLI_CONFIG .project_path } '"
117+ ) from exc
118+
81119 # Provide context to the server
82120 yield context
83121 finally :
@@ -300,9 +338,55 @@ def configure_file_watcher(
300338# ----- PROMPTS -----
301339# Removed: analyze_code, code_search, set_project prompts
302340
303- def main ():
341+ def _parse_args (argv : list [str ] | None = None ) -> argparse .Namespace :
342+ """Parse CLI arguments for the MCP server."""
343+ parser = argparse .ArgumentParser (description = "Code Index MCP server" )
344+ parser .add_argument (
345+ "--project-path" ,
346+ dest = "project_path" ,
347+ help = "Set the project path on startup (equivalent to calling set_project_path)."
348+ )
349+ parser .add_argument (
350+ "--transport" ,
351+ choices = ["stdio" , "sse" , "streamable-http" ],
352+ default = "stdio" ,
353+ help = "Transport protocol to use (default: stdio)."
354+ )
355+ parser .add_argument (
356+ "--mount-path" ,
357+ dest = "mount_path" ,
358+ default = None ,
359+ help = "Mount path when using SSE transport."
360+ )
361+ return parser .parse_args (argv )
362+
363+
364+ def main (argv : list [str ] | None = None ):
304365 """Main function to run the MCP server."""
305- mcp .run ()
366+ args = _parse_args (argv )
367+
368+ # Store CLI configuration for lifespan bootstrap.
369+ _CLI_CONFIG .project_path = args .project_path
370+
371+ run_kwargs = {"transport" : args .transport }
372+ if args .transport == "sse" and args .mount_path :
373+ run_signature = inspect .signature (mcp .run )
374+ if "mount_path" in run_signature .parameters :
375+ run_kwargs ["mount_path" ] = args .mount_path
376+ else :
377+ logger .warning (
378+ "Ignoring --mount-path because this FastMCP version "
379+ "does not accept the parameter."
380+ )
381+
382+ try :
383+ mcp .run (** run_kwargs )
384+ except RuntimeError as exc :
385+ logger .error ("MCP server terminated with error: %s" , exc )
386+ raise SystemExit (1 ) from exc
387+ except Exception as exc : # pylint: disable=broad-except
388+ logger .error ("Unexpected MCP server error: %s" , exc )
389+ raise
306390
307391if __name__ == '__main__' :
308392 main ()
0 commit comments