Skip to content

fix(api): bind the gateway's MCPServer to the serving event loop so index jobs run - #1747

Merged
Shashankss1205 merged 1 commit into
CodeGraphContext:mainfrom
CSchneiderHav:fix/gateway-server-loop-binding
Oct 15, 2026
Merged

Shashankss1205 merged 1 commit into
CodeGraphContext:mainfrom
CSchneiderHav:fix/gateway-server-loop-binding

Conversation

@CSchneiderHav

Copy link
Copy Markdown

Summary

get_server() in api/router.py is a sync FastAPI dependency, so it runs in the threadpool. There MCPServer.__init__ finds no running loop and falls back to a fresh asyncio.new_event_loop() that nothing ever runs. Every indexing job is scheduled onto it with run_coroutine_threadsafe and stays pending forever whenever the first request to the gateway is a REST call. Details and reproduction: #1744.

Change

  • get_server becomes async def and passes loop=asyncio.get_running_loop() to MCPServer, so the singleton is bound to the serving loop regardless of which request builds it. This also removes the first-request race where concurrent REST calls built several servers.
  • The two direct callers in api/mcp_sse.py await get_server().
  • tests/unit/api/test_router_server_loop.py: asserts the server is bound to the serving loop through a REST request, and that SSE list_tools still gets a server. Fails on unpatched main, passes with this change.

Trade-off

MCPServer.__init__ (context resolution, DB driver creation) now runs once on the loop during the first request. The alternative is a lifespan hook, which would also give a place to call shutdown(), but then the gateway refuses to start while the DB is down. Happy to switch if you prefer that.

Closes #1744.


Disclosure: the investigation, this description and the patch were prepared with AI assistance (Claude Code) and reviewed by a human before posting.

get_server() was a sync FastAPI dependency and ran in the threadpool, where
MCPServer found no running loop and fell back to a fresh one nobody runs.
Index jobs scheduled onto it with run_coroutine_threadsafe stayed pending
forever whenever the first request to the gateway was a REST call.

Make the dependency async and pass the running loop explicitly; await it in
the two SSE handlers. Adds a regression test that fails on main.

Closes CodeGraphContext#1744
@vercel

vercel Bot commented Sep 29, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the shashankss1205's projects Team on Vercel.

A member of the Team first needs to authorize it.

@Shashankss1205 Shashankss1205 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Serving-loop singleton creation fixes REST-first pending jobs. API tests pass, including combination with #1751. Preserve await calls when resolving their shared file conflict.

Integration detail: under MCP 2, handle_list_tools(ctx, params) and handle_call_tool(ctx, params) must both use server = await get_server(). The loop regression must call asyncio.run(mcp_sse.handle_list_tools(None, None)).tools.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

cgc api start: index jobs stay pending forever when the server is first created by a REST request

2 participants