Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ build/
*.egg
.graphify/
graphify-out/
.worktrees/
.graphify_*.json
.graphify_python
.claude/
Expand Down
18 changes: 4 additions & 14 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
# graphify MCP server as a shared HTTP service (issue #1143).
#
# Build: docker build -t graphify .
# Run: docker run -p 8080:8080 -v "$(pwd)/graphify-out:/data" graphify \
# /data/graph.json --transport http --host 0.0.0.0 --api-key "$SECRET"
#
# Builds from source so the image includes the Streamable HTTP transport even
# before it lands on PyPI. The graph.json is mounted at runtime (-v), never
# baked into the image.
# graphify MCP server. Mount a repository containing graphify-out/graph.json.
FROM python:3.12-slim

WORKDIR /app
COPY . /app

# The [mcp] extra pulls mcp + starlette + uvicorn, which the HTTP transport needs.
RUN pip install --no-cache-dir ".[mcp]"

# Run as a non-root user the server is network-exposed.
# Run as a non-root user because the server is network-exposed.
RUN useradd --create-home --uid 10001 graphify
USER graphify

EXPOSE 8080

ENTRYPOINT ["python", "-m", "graphify.serve"]
CMD ["/data/graph.json", "--transport", "http", "--host", "0.0.0.0", "--port", "8080"]
ENTRYPOINT ["graphify"]
CMD ["/data", "--mcp", "--transport", "http", "--host", "0.0.0.0", "--port", "8080"]
31 changes: 29 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -485,15 +485,42 @@ The default `127.0.0.1` bind is loopback-only. Set `--host 0.0.0.0` **and** `--a

```bash
docker build -t graphify .
docker run -p 8080:8080 -v "$(pwd)/graphify-out:/data" graphify \
/data/graph.json --transport http --host 0.0.0.0 --api-key "$SECRET"
docker run -p 8080:8080 -v "$(pwd):/data:ro" graphify /data --mcp --transport http --host 0.0.0.0 --api-key "$SECRET"
```

> **WSL / Linux note:** Ubuntu ships `python3`, not `python`. Use a venv to avoid conflicts:
> ```bash
> python3 -m venv .venv && .venv/bin/pip install "graphifyy[mcp]"
> ```

### Multi-graph MCP server

Serve multiple repositories containing `graphify-out/graph.json` from one MCP endpoint. This is useful for multi-repo setups, monorepos with per-service graphs, or comparing codebases.

```bash
# Serve over stdio (the default transport).
graphify ../frontend ../backend --mcp

# Serve over HTTP.
graphify ../frontend ../backend --mcp --transport http --host 0.0.0.0 --port 8080
```

Each repository path must contain `graphify-out/graph.json`. To run the documented two-repository example in Docker, arrange the Compose directory as:

```text
repos/
frontend/graphify-out/graph.json
backend/graphify-out/graph.json
```

Then start the public CLI through Compose:

```bash
docker compose -f docker-compose.multi.yml up --build
```

Tools: same as single-graph (`query_graph`, `get_node`, `get_neighbors`, etc.) plus `list_graphs` and `use_graph`. Each tool accepts an optional `graph` parameter to target a specific graph, or use `use_graph` to set a session default.

---

## Environment variables
Expand Down
11 changes: 11 additions & 0 deletions docker-compose.multi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Quick start: docker compose -f docker-compose.multi.yml up --build
# Runs: graphify /repos/frontend /repos/backend --mcp
services:
graphify-mcp:
build: .
ports:
- "127.0.0.1:8080:8080"
volumes:
- ./repos/frontend:/repos/frontend:ro
- ./repos/backend:/repos/backend:ro
command: ["/repos/frontend", "/repos/backend", "--mcp", "--transport", "http", "--host", "0.0.0.0", "--port", "8080"]
124 changes: 124 additions & 0 deletions graphify/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,122 @@ def main() -> None:
raise


def _start_mcp_registry(registry) -> None:
from graphify.serve import serve

serve(registry=registry)


def _serve_mcp_repositories(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression_serve_mcp_repositories()

6 callers depend on it (afferent coupling).

Grounded coupling-delta finding (deterministic), not an LLM guess.

paths: list[str],
*,
transport: str,
host: str = "127.0.0.1",
port: int = 8080,
api_key: str | None = None,
) -> None:
from graphify.serve import GraphRegistry

if not paths:
print("error: --mcp requires at least one repository path", file=sys.stderr)
raise SystemExit(1)
resolved = [Path(path).resolve() for path in paths]
names = [path.name for path in resolved]
duplicate = next((name for name in names if names.count(name) > 1), None)
if duplicate is not None:
print(f"error: duplicate graph name {duplicate!r}; repository basenames must be unique", file=sys.stderr)
raise SystemExit(1)
graph_paths = []
for repo in resolved:
graph_path = repo / _GRAPHIFY_OUT / "graph.json"
try:
exists = graph_path.is_file()
except OSError as exc:
print(f"error: could not read graph: {graph_path} ({exc})", file=sys.stderr)
raise SystemExit(1) from None
if not exists:
print(f"error: graph not found: {graph_path}", file=sys.stderr)
raise SystemExit(1)
graph_paths.append(graph_path)
try:
registry = GraphRegistry.from_paths(graph_paths)
except OSError as exc:
print(f"error: could not read graph: {exc}", file=sys.stderr)
raise SystemExit(1) from None
if transport == "http":
from graphify.serve import serve_http

serve_http(registry=registry, host=host, port=port, api_key=api_key)
else:
_start_mcp_registry(registry)


def _run_mcp_cli(args: list[str]) -> bool:
if "--mcp" not in args:
return False

paths: list[str] = []
transport = "stdio"
host = "127.0.0.1"
port = 8080
api_key: str | None = None
index = 0
while index < len(args):
arg = args[index]
if arg == "--mcp":
index += 1
elif arg == "--transport":
index += 1
if index == len(args) or args[index] not in {"stdio", "http"}:
print("error: --transport must be stdio or http", file=sys.stderr)
raise SystemExit(1)
transport = args[index]
index += 1
elif arg == "--host":
index += 1
if index == len(args):
print("error: --host requires a value", file=sys.stderr)
raise SystemExit(1)
if args[index].startswith("-"):
print(f"error: unrecognized MCP option: {args[index]}", file=sys.stderr)
raise SystemExit(1)
host = args[index]
index += 1
elif arg == "--port":
index += 1
if index == len(args):
print("error: --port requires an integer", file=sys.stderr)
raise SystemExit(1)
try:
port = int(args[index])
except ValueError:
print("error: --port requires an integer", file=sys.stderr)
raise SystemExit(1) from None
index += 1
elif arg == "--api-key":
index += 1
if index == len(args):
print("error: --api-key requires a value", file=sys.stderr)
raise SystemExit(1)
if args[index].startswith("-"):
print(f"error: unrecognized MCP option: {args[index]}", file=sys.stderr)
raise SystemExit(1)
api_key = args[index]
index += 1
elif arg.startswith("-"):
print(f"error: unrecognized MCP option: {arg}", file=sys.stderr)
raise SystemExit(1)
else:
paths.append(arg)
index += 1

if transport == "stdio" and host == "127.0.0.1" and port == 8080 and api_key is None:
_serve_mcp_repositories(paths, transport=transport)
else:
_serve_mcp_repositories(paths, transport=transport, host=host, port=port, api_key=api_key)
return True


def _run_cli() -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Health regression_run_cli()

high coupling complexity (Ca·Ce = 15).

Grounded coupling-delta finding (deterministic), not an LLM guess.

for _stream in (sys.stdout, sys.stderr):
if _stream is not None and hasattr(_stream, "reconfigure"):
Expand All @@ -502,10 +618,18 @@ def _run_cli() -> None:
print(f"graphify {__version__}")
return

if _run_mcp_cli(sys.argv[1:]):
return

if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help", "-?"):
print("Usage: graphify <command>")
print()
print("Commands:")
print(" <repo>... --mcp serve existing repository graphs over MCP stdio")
print(" --transport stdio|http transport (default: stdio)")
print(" --host HOST HTTP bind host (default: 127.0.0.1)")
print(" --port PORT HTTP bind port (default: 8080)")
print(" --api-key KEY require this key for HTTP requests")
print(" install [--platform P] copy skill to platform config dir (claude|windows|codebuddy|codex|opencode|aider|amp|agents|claw|droid|trae|trae-cn|gemini|cursor|antigravity|hermes|kiro|pi|devin)")
print(" uninstall remove graphify from all detected platforms in one shot")
print(" --purge also delete graphify-out/ directory")
Expand Down
Loading