From 00b5dc82fc841159b0190511a6303da8359c1e47 Mon Sep 17 00:00:00 2001 From: Dvir Dukhan <12258836+DvirDukhan@users.noreply.github.com> Date: Fri, 29 May 2026 21:02:59 +0300 Subject: [PATCH] feat(api): /api/v2/* MCP-parity endpoints Add seven new HTTP endpoints that wrap the existing async tool functions in api.mcp.tools.* directly, with no logic duplication (FastMCP's @app.tool returns the original callable, so importing them and awaiting from a FastAPI route works as-is): POST /api/v2/search_code POST /api/v2/get_callers POST /api/v2/get_callees POST /api/v2/get_dependencies POST /api/v2/impact_analysis POST /api/v2/find_path POST /api/v2/ask Each accepts a small pydantic request body matching the MCP tool's kwargs (project, branch, symbol_id, ...) and returns the function's native payload unchanged. Auth follows the existing read-route pattern (public_or_auth). Motivation: the React UI keeps using the existing UI-shaped routes (/api/graph_entities, /api/get_neighbors, /api/find_paths, ...). Agent integrations (and our SWE-bench harness) want the agent-shaped verb surface the MCP server already exposes. Until now the only way to get that over HTTP was to reshape responses client-side. With these endpoints, an HTTP client and an MCP client get byte-identical output for the same query. No existing routes change; this is purely additive. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- api/index.py | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/api/index.py b/api/index.py index 95c6085f..d5ec68da 100644 --- a/api/index.py +++ b/api/index.py @@ -237,6 +237,119 @@ async def find_paths(data: FindPathsRequest, _=Depends(public_or_auth)): return {"status": "success", "branch": g.branch, "paths": paths} +# --------------------------------------------------------------------------- +# v2 agent endpoints (parity with the MCP transport) +# --------------------------------------------------------------------------- +# +# These wrap the same async functions the FastMCP server exposes so that the +# HTTP-track agent CLI (`cg`) and the stdio MCP-track CLI (`cg-mcp`) expose +# an identical verb surface against an identical implementation. The only +# difference between tracks is transport (HTTP vs stdio MCP). +# +# Implementation: import the tool functions directly from api.mcp.tools.* — +# FastMCP's @app.tool decorator returns the original callable unchanged. +from api.mcp.tools.structural import ( # noqa: E402 + search_code as _mcp_search_code, + get_callers as _mcp_get_callers, + get_callees as _mcp_get_callees, + get_dependencies as _mcp_get_dependencies, + impact_analysis as _mcp_impact_analysis, + find_path as _mcp_find_path, +) +from api.mcp.tools.ask import ask as _mcp_ask # noqa: E402 + + +class _SearchCodeRequest(BaseModel): + project: str + prefix: str + branch: Optional[str] = None + limit: int = 10 + + +class _SymbolRequest(BaseModel): + project: str + symbol_id: int + branch: Optional[str] = None + limit: int = 50 + + +class _ImpactRequest(BaseModel): + project: str + symbol_id: int + branch: Optional[str] = None + direction: str = "IN" + depth: int = 3 + + +class _FindPathRequest(BaseModel): + project: str + source_id: int + dest_id: int + branch: Optional[str] = None + max_paths: int = 10 + + +class _AskRequest(BaseModel): + project: str + question: str + branch: Optional[str] = None + + +@app.post('/api/v2/search_code') +async def v2_search_code(data: _SearchCodeRequest, _=Depends(public_or_auth)): + return await _mcp_search_code( + data.prefix, data.project, branch=data.branch, limit=data.limit + ) + + +@app.post('/api/v2/get_callers') +async def v2_get_callers(data: _SymbolRequest, _=Depends(public_or_auth)): + return await _mcp_get_callers( + data.symbol_id, data.project, branch=data.branch, limit=data.limit + ) + + +@app.post('/api/v2/get_callees') +async def v2_get_callees(data: _SymbolRequest, _=Depends(public_or_auth)): + return await _mcp_get_callees( + data.symbol_id, data.project, branch=data.branch, limit=data.limit + ) + + +@app.post('/api/v2/get_dependencies') +async def v2_get_dependencies(data: _SymbolRequest, _=Depends(public_or_auth)): + return await _mcp_get_dependencies( + data.symbol_id, data.project, branch=data.branch, limit=data.limit + ) + + +@app.post('/api/v2/impact_analysis') +async def v2_impact_analysis(data: _ImpactRequest, _=Depends(public_or_auth)): + return await _mcp_impact_analysis( + data.symbol_id, + data.project, + branch=data.branch, + direction=data.direction, + depth=data.depth, + ) + + +@app.post('/api/v2/find_path') +async def v2_find_path(data: _FindPathRequest, _=Depends(public_or_auth)): + return await _mcp_find_path( + data.source_id, + data.dest_id, + data.project, + branch=data.branch, + max_paths=data.max_paths, + ) + + +@app.post('/api/v2/ask') +async def v2_ask(data: _AskRequest, _=Depends(public_or_auth)): + return await _mcp_ask(data.question, data.project, branch=data.branch) + + @app.post('/api/chat') async def chat(data: ChatRequest, _=Depends(public_or_auth)): """Chat with the CodeGraph language model."""