Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ COPY --from=node-base /usr/local/bin/node /usr/local/bin/node
COPY --from=node-base /usr/local/lib/node_modules /usr/local/lib/node_modules

# Install netcat for wait loop in start.sh and system build tools
RUN apt-get update && apt-get install -y --no-install-recommends \
RUN apt-get update \
&& apt-get install -y -f \
&& apt-get install -y --no-install-recommends \
netcat-openbsd \
git \
build-essential \
Expand Down
8 changes: 8 additions & 0 deletions api/mcp/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""MCP server module for code-graph.

Exposes the code-graph indexer and graph queries as MCP tools so AI coding
agents (Claude Code, Cursor, Copilot, Roo/Cline) can drive the indexer over
the standard Model Context Protocol stdio transport.

Entry point: ``cgraph-mcp`` (defined in ``pyproject.toml``).
"""
26 changes: 26 additions & 0 deletions api/mcp/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""FastMCP server for code-graph.

This is the scaffold (T1). It instantiates a single FastMCP app, exposes it
as ``app`` for tests and embedders, and registers a ``main()`` entry point
that runs the server over stdio. Tools are registered in later tickets
(T4-T8, T11) by importing this module's ``app`` and decorating functions
with ``@app.tool(...)``.
"""

from __future__ import annotations

from mcp.server.fastmcp import FastMCP

app: FastMCP = FastMCP("code-graph")


def main() -> None:
"""Run the MCP server over stdio.

Console-script entry point for ``cgraph-mcp``.
"""
app.run(transport="stdio")


if __name__ == "__main__":
main()
321 changes: 321 additions & 0 deletions docs/MCP_SERVER_DESIGN.md

Large diffs are not rendered by default.

Binary file added docs/code-graph-mcp-v4.docx
Binary file not shown.
15 changes: 14 additions & 1 deletion e2e/seed_test_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import os
import sys
import logging
from pathlib import Path

import graphrag_sdk

logging.basicConfig(
level=logging.INFO,
Expand All @@ -14,8 +17,11 @@
from falkordb import FalkorDB
from api.project import Project

# Use the installed graphrag-sdk (pinned to 0.8.2 via uv.lock) as the e2e
# fixture. Upstream HEAD has the new v1.0 API which the tests aren't built for.
GRAPHRAG_SDK_PATH = Path(graphrag_sdk.__file__).parent

REPOS = [
"https://github.com/FalkorDB/GraphRAG-SDK",
"https://github.com/pallets/flask",
]

Expand Down Expand Up @@ -61,6 +67,13 @@ def ensure_calls_edges(graph_name: str) -> None:


def main():
logger.info(
"Seeding graphrag-sdk %s from %s",
getattr(graphrag_sdk, "__version__", "?"),
GRAPHRAG_SDK_PATH,
)
Project(name="GraphRAG-SDK", path=GRAPHRAG_SDK_PATH, url=None).analyze_sources()

for url in REPOS:
logger.info("Seeding %s ...", url)
proj = Project.from_git_repository(url)
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,19 @@ dependencies = [
"javatools>=1.6.0,<2.0.0",
"pygit2>=1.17.0,<2.0.0",
"typer>=0.24.0,<1.0.0",
"mcp>=1.0.0,<2.0.0",
]
Comment on lines 23 to 27

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

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

Adding mcp as a core dependency pulls in heavier transitive deps (notably pyjwt[crypto]cryptography, plus python-multipart, sse-starlette, etc. per uv.lock). Please confirm this footprint is acceptable for all installs of the base package; if MCP is optional, consider moving it under an extra to avoid requiring compiled wheels for users who don’t use cgraph-mcp.

Copilot uses AI. Check for mistakes.

[project.scripts]
cgraph = "api.cli:app"
cgraph-mcp = "api.mcp.server:main"

Comment on lines 29 to 32

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

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

The design/issue text specifies the console-script entry point as api.mcp.server:app, but pyproject.toml wires cgraph-mcp to api.mcp.server:main. Please align the entry point target with the documented contract (either change the script target back to :app if supported by the MCP SDK, or update the docs/issue references to :main).

Copilot uses AI. Check for mistakes.
[project.optional-dependencies]
test = [
"pytest>=9.0.2,<10.0.0",
"ruff>=0.11.0,<1.0.0",
"httpx>=0.28.0,<1.0.0",
"anyio>=4.0,<5.0",
]

[build-system]
Expand Down
Empty file added tests/mcp/__init__.py
Empty file.
65 changes: 65 additions & 0 deletions tests/mcp/test_scaffold.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Scaffold smoke tests for the cgraph-mcp server (T1).

These tests prove the bare module is wired correctly:

1. The FastMCP ``app`` instance is importable.
2. The ``cgraph-mcp`` console script spawns a working stdio MCP server.
3. A client can complete the MCP handshake and ``list_tools`` returns 0
tools (no tools are registered yet — they land in T4-T8, T11).

When tool tickets land they should ADD tests, not modify these — these
guard the scaffold itself.
"""

from __future__ import annotations

import shutil

import anyio
import pytest
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

STDIO_TIMEOUT = 10 # seconds — prevents CI from hanging if the server fails to start


@pytest.fixture
def anyio_backend() -> str:
"""Pin the anyio backend to asyncio so transitive trio installs don't double-run tests."""
return "asyncio"


def test_app_is_importable() -> None:
"""The FastMCP instance can be imported and is named ``code-graph``."""
from api.mcp.server import app

assert app is not None
assert app.name == "code-graph"


def test_main_entry_point_exists() -> None:
"""``main()`` is exposed for the console script."""
from api.mcp import server

assert callable(server.main)


@pytest.mark.anyio
async def test_stdio_server_lists_zero_tools() -> None:
"""Spawn ``cgraph-mcp`` over stdio and verify the protocol handshake.

The scaffold registers no tools, so ``list_tools`` must return an
empty list. Tool tickets (T4-T8, T11) extend this expectation.
"""
cgraph_mcp = shutil.which("cgraph-mcp")
assert cgraph_mcp is not None, (
"cgraph-mcp not on PATH; run `uv pip install -e .` first"
)

params = StdioServerParameters(command=cgraph_mcp, args=[])
with anyio.fail_after(STDIO_TIMEOUT):
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.list_tools()
assert result.tools == []
Loading
Loading