2222from .project_settings import ProjectSettings
2323from .services import (
2424 ProjectService , IndexService , SearchService ,
25- FileService , SettingsService
25+ FileService , SettingsService , FileWatcherService
2626)
2727from .services .settings_service import manage_temp_directory
2828from .utils import (
@@ -37,6 +37,7 @@ class CodeIndexerContext:
3737 file_count : int = 0
3838 file_index : dict = field (default_factory = dict )
3939 index_cache : dict = field (default_factory = dict )
40+ file_watcher_service : FileWatcherService = None
4041
4142@asynccontextmanager
4243async def indexer_lifespan (_server : FastMCP ) -> AsyncIterator [CodeIndexerContext ]:
@@ -49,17 +50,23 @@ async def indexer_lifespan(_server: FastMCP) -> AsyncIterator[CodeIndexerContext
4950 # Initialize settings manager with skip_load=True to skip loading files
5051 settings = ProjectSettings (base_path , skip_load = True )
5152
52- # Initialize context
53+ # Initialize context - file watcher will be initialized later when project path is set
5354 context = CodeIndexerContext (
5455 base_path = base_path ,
55- settings = settings
56+ settings = settings ,
57+ file_watcher_service = None
5658 )
5759
5860 try :
5961 print ("Server ready. Waiting for user to set project path..." )
6062 # Provide context to the server
6163 yield context
6264 finally :
65+ # Stop file watcher if it was started
66+ if context .file_watcher_service :
67+ print ("Stopping file watcher service..." )
68+ await context .file_watcher_service .stop_monitoring ()
69+
6370 # Only save index if project path has been set
6471 if context .base_path and context .index_cache :
6572 print (f"Saving index for project: { context .base_path } " )
@@ -203,18 +210,18 @@ def refresh_index(ctx: Context) -> str:
203210 Manually refresh the project index when files have been added/removed/moved.
204211
205212 Use when:
206- - After AI/LLM has created, modified, or deleted files
207- - After git operations (checkout, merge, pull) that change files
213+ - File watcher is disabled or unavailable
214+ - After large-scale operations (git checkout, merge, pull) that change many files
215+ - When you want immediate index rebuild without waiting for file watcher debounce
208216 - When find_files results seem incomplete or outdated
209- - For immediate refresh without waiting for auto-refresh rate limits
217+ - For troubleshooting suspected index synchronization issues
210218
211219 Important notes for LLMs:
212- - This tool bypasses the 5-second rate limit that applies to auto-refresh
213- - Always available for immediate use when you know files have changed
220+ - Always available as backup when file watcher is not working
214221 - Performs full project re-indexing for complete accuracy
215222 - Use when you suspect the index is stale after file system changes
216- - **Always call this after modifying files programmatically **
217- - Essential for find_files tool to see new/changed files
223+ - **Call this after programmatic file modifications if file watcher seems unresponsive **
224+ - Complements the automatic file watcher system
218225
219226 Returns:
220227 Success message with total file count
@@ -254,6 +261,75 @@ def refresh_search_tools(ctx: Context) -> str:
254261 """
255262 return SearchService (ctx ).refresh_search_tools ()
256263
264+ @mcp .tool ()
265+ @handle_mcp_tool_errors (return_type = 'dict' )
266+ def get_file_watcher_status (ctx : Context ) -> Dict [str , Any ]:
267+ """Get file watcher service status and statistics."""
268+ try :
269+ # Get file watcher service from context
270+ file_watcher_service = None
271+ if hasattr (ctx .request_context .lifespan_context , 'file_watcher_service' ):
272+ file_watcher_service = ctx .request_context .lifespan_context .file_watcher_service
273+
274+ if not file_watcher_service :
275+ return {"status" : "not_initialized" , "message" : "File watcher service not initialized" }
276+
277+ # Get status from file watcher service
278+ status = file_watcher_service .get_status ()
279+
280+ # Add index service status
281+ index_service = IndexService (ctx )
282+ rebuild_status = index_service .get_rebuild_status ()
283+ status ["rebuild_status" ] = rebuild_status
284+
285+ # Add configuration
286+ if hasattr (ctx .request_context .lifespan_context , 'settings' ) and ctx .request_context .lifespan_context .settings :
287+ file_watcher_config = ctx .request_context .lifespan_context .settings .get_file_watcher_config ()
288+ status ["configuration" ] = file_watcher_config
289+
290+ return status
291+
292+ except Exception as e :
293+ return {"status" : "error" , "message" : f"Failed to get file watcher status: { e } " }
294+
295+ @mcp .tool ()
296+ @handle_mcp_tool_errors (return_type = 'str' )
297+ def configure_file_watcher (
298+ ctx : Context ,
299+ enabled : bool = None ,
300+ debounce_seconds : float = None ,
301+ additional_exclude_patterns : list = None
302+ ) -> str :
303+ """Configure file watcher service settings."""
304+ try :
305+ # Get settings from context
306+ if not hasattr (ctx .request_context .lifespan_context , 'settings' ) or not ctx .request_context .lifespan_context .settings :
307+ return "Settings not available - project path not set"
308+
309+ settings = ctx .request_context .lifespan_context .settings
310+
311+ # Build updates dictionary
312+ updates = {}
313+ if enabled is not None :
314+ updates ["enabled" ] = enabled
315+ if debounce_seconds is not None :
316+ updates ["debounce_seconds" ] = debounce_seconds
317+ if additional_exclude_patterns is not None :
318+ updates ["additional_exclude_patterns" ] = additional_exclude_patterns
319+
320+ if not updates :
321+ return "No configuration changes specified"
322+
323+ # Update configuration
324+ settings .update_file_watcher_config (updates )
325+
326+ # If file watcher is running, we would need to restart it for changes to take effect
327+ # For now, just return success message with note about restart
328+ return f"File watcher configuration updated: { updates } . Restart may be required for changes to take effect."
329+
330+ except Exception as e :
331+ return f"Failed to update file watcher configuration: { e } "
332+
257333# ----- PROMPTS -----
258334
259335@mcp .prompt ()
0 commit comments