diff --git a/.github/workflows/rag-evaluation.yml b/.github/workflows/rag-evaluation.yml index 148321d..a5446b5 100644 --- a/.github/workflows/rag-evaluation.yml +++ b/.github/workflows/rag-evaluation.yml @@ -49,7 +49,7 @@ jobs: env: # Override MCP container environment for OpenAI + vector sync VECTOR_SYNC_ENABLED: "true" - VECTOR_SYNC_SCAN_INTERVAL: "30" + VECTOR_SYNC_SCAN_INTERVAL: "5" OPENAI_API_KEY: ${{ secrets.GITHUB_TOKEN }} OPENAI_BASE_URL: "https://models.github.ai/inference" OPENAI_EMBEDDING_MODEL: ${{ inputs.embedding_model }} diff --git a/nextcloud_mcp_server/app.py b/nextcloud_mcp_server/app.py index b4ab820..f1185d6 100644 --- a/nextcloud_mcp_server/app.py +++ b/nextcloud_mcp_server/app.py @@ -243,6 +243,25 @@ def validate_pkce_support(discovery: dict, discovery_url: str) -> None: click.echo(f"✓ PKCE support validated: {code_challenge_methods}") +@dataclass +class VectorSyncState: + """ + Module-level state for vector sync background tasks. + + This singleton bridges the Starlette server lifespan (where background tasks run) + and FastMCP session lifespans (where MCP tools need access to the streams). + """ + + document_send_stream: Optional[MemoryObjectSendStream] = None + document_receive_stream: Optional[MemoryObjectReceiveStream] = None + shutdown_event: Optional[anyio.Event] = None + scanner_wake_event: Optional[anyio.Event] = None + + +# Module-level singleton for vector sync state +_vector_sync_state = VectorSyncState() + + @dataclass class AppContext: """Application context for BasicAuth mode.""" @@ -580,8 +599,16 @@ async def app_lifespan_basic(server: FastMCP) -> AsyncIterator[AppContext]: initialize_document_processors() # Yield client context - scanner runs at server level (starlette_lifespan) + # Include vector sync state from module singleton (set by starlette_lifespan) try: - yield AppContext(client=client, storage=storage) + yield AppContext( + client=client, + storage=storage, + document_send_stream=_vector_sync_state.document_send_stream, + document_receive_stream=_vector_sync_state.document_receive_stream, + shutdown_event=_vector_sync_state.shutdown_event, + scanner_wake_event=_vector_sync_state.scanner_wake_event, + ) finally: logger.info("Shutting down BasicAuth session") await client.close() @@ -1228,6 +1255,13 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None = app.state.shutdown_event = shutdown_event app.state.scanner_wake_event = scanner_wake_event + # Also store in module singleton for FastMCP session lifespans + _vector_sync_state.document_send_stream = send_stream + _vector_sync_state.document_receive_stream = receive_stream + _vector_sync_state.shutdown_event = shutdown_event + _vector_sync_state.scanner_wake_event = scanner_wake_event + logger.info("Vector sync state stored in module singleton") + # Also share with browser_app for /app route for route in app.routes: if isinstance(route, Mount) and route.path == "/app":