forked from CodeGraphContext/CodeGraphContext
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
183 lines (161 loc) · 7.51 KB
/
main.py
File metadata and controls
183 lines (161 loc) · 7.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# src/codegraphcontext/cli/main.py
"""
This module defines the command-line interface (CLI) for the CodeGraphContext application.
It uses the Typer library to create a user-friendly and well-documented CLI.
Commands:
- setup: Runs an interactive wizard to configure the Neo4j database connection.
- start: Launches the main MCP server.
- tool: A placeholder for directly calling server tools (for debugging).
- help: Displays help information.
- version: Show the installed version.
"""
import typer
from rich.console import Console
import asyncio
import logging
import json
import os
from pathlib import Path
from dotenv import load_dotenv, find_dotenv
from importlib.metadata import version as pkg_version, PackageNotFoundError
from codegraphcontext.server import MCPServer
from .setup_wizard import run_setup_wizard
# Set the log level for the noisy neo4j logger to WARNING to keep the output clean.
logging.getLogger("neo4j").setLevel(logging.WARNING)
# Initialize the Typer app and Rich console for formatted output.
app = typer.Typer(
name="cgc",
help="CodeGraphContext: An MCP server for AI-powered code analysis.",
add_completion=False,
)
console = Console(stderr=True)
# Configure basic logging for the application.
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(name)s - %(message)s')
def get_version() -> str:
"""
Try to read version from the installed package metadata.
Fallback to a dev version if not installed.
"""
try:
return pkg_version("codegraphcontext") # must match [project].name in pyproject.toml
except PackageNotFoundError:
return "0.0.0 (dev)"
@app.command()
def setup():
"""
Runs the interactive setup wizard to configure the server and database connection.
This helps users set up a local Docker-based Neo4j instance or connect to a remote one.
"""
run_setup_wizard()
@app.command()
def start():
"""
Starts the CodeGraphContext MCP server, which listens for JSON-RPC requests from stdin.
It first attempts to load Neo4j credentials from various sources before starting.
"""
console.print("[bold green]Starting CodeGraphContext Server...[/bold green]")
# The server needs Neo4j credentials. It attempts to load them in the following order of priority:
# 1. From a local `mcp.json` file in the current working directory.
# 2. From a global `.env` file at `~/.codegraphcontext/.env`.
# 3. From any `.env` file found by searching upwards from the current directory.
# 1. Prefer loading environment variables from mcp.json in the current directory.
mcp_file_path = Path.cwd() / "mcp.json"
if mcp_file_path.exists():
try:
with open(mcp_file_path, "r") as f:
mcp_config = json.load(f)
server_env = mcp_config.get("mcpServers", {}).get("CodeGraphContext", {}).get("env", {})
for key, value in server_env.items():
os.environ[key] = value
console.print("[green]Loaded Neo4j credentials from local mcp.json.[/green]")
except Exception as e:
console.print(f"[bold red]Error loading mcp.json:[/bold red] {e}")
console.print("[yellow]Attempting to start server without mcp.json environment variables.[/yellow]")
else:
# 2. If no local mcp.json, try to load from the global config directory.
global_env_path = Path.home() / ".codegraphcontext" / ".env"
if global_env_path.exists():
try:
load_dotenv(dotenv_path=global_env_path)
console.print(f"[green]Loaded Neo4j credentials from global .env file: {global_env_path}[/green]")
except Exception as e:
console.print(f"[bold red]Error loading global .env file from {global_env_path}:[/bold red] {e}")
console.print("[yellow]Attempting to start server without .env environment variables.[/yellow]")
else:
# 3. Fallback: try to load from any .env file found by searching up the directory tree.
try:
dotenv_path = find_dotenv(usecwd=True, raise_error_if_not_found=False)
if dotenv_path:
load_dotenv(dotenv_path)
console.print(f"[green]Loaded Neo4j credentials from discovered .env file: {dotenv_path}[/green]")
else:
console.print("[yellow]No local mcp.json or global .env file found. Attempting to start server without explicit Neo4j credentials.[/yellow]")
except Exception as e:
console.print(f"[bold red]Error loading .env file:[/bold red] {e}")
console.print("[yellow]Attempting to start server without .env environment variables.[/yellow]")
server = None
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
# Initialize and run the main server.
server = MCPServer(loop=loop)
loop.run_until_complete(server.run())
except ValueError as e:
# This typically happens if credentials are still not found after all checks.
console.print(f"[bold red]Configuration Error:[/bold red] {e}")
console.print("Please run `cgc setup` to configure the server.")
except KeyboardInterrupt:
# Handle graceful shutdown on Ctrl+C.
console.print("\n[bold yellow]Server stopped by user.[/bold yellow]")
finally:
# Ensure server and event loop are properly closed.
if server:
server.shutdown()
loop.close()
@app.command()
def tool(
name: str = typer.Argument(..., help="The name of the tool to call."),
args: str = typer.Argument("{}", help="A JSON string of arguments for the tool."),
):
"""
Directly call a CodeGraphContext tool from the command line.
IMPORTANT: This is a placeholder for debugging and does not connect to a running
server. It creates a new, temporary server instance for each call, so it cannot
be used to check the status of jobs started by `cgc start`.
"""
console.print(f"Calling tool [bold cyan]{name}[/bold cyan] with args: {args}")
console.print("[yellow]Note: This is a placeholder for direct tool invocation.[/yellow]")
@app.command()
def help(ctx: typer.Context):
"""Show the main help message and exit."""
root_ctx = ctx.parent or ctx
typer.echo(root_ctx.get_help())
@app.command("version")
def version_cmd():
"""Show the application version."""
console.print(f"CodeGraphContext [bold cyan]{get_version()}[/bold cyan]")
@app.callback(invoke_without_command=True)
def main(
ctx: typer.Context,
version_: bool = typer.Option(
None,
"--version",
"-v",
help="Show the application version and exit.",
is_eager=True,
),
):
"""
Main entry point for the cgc CLI application.
If no subcommand is provided, it displays a welcome message with instructions.
"""
if version_:
console.print(f"CodeGraphContext [bold cyan]{get_version()}[/bold cyan]")
raise typer.Exit()
if ctx.invoked_subcommand is None:
console.print("[bold green]👋 Welcome to CodeGraphContext (cgc)![/bold green]\n")
console.print("👉 Run [cyan]cgc setup[/cyan] to configure the server and database.")
console.print("👉 Run [cyan]cgc start[/cyan] to launch the server.")
console.print("👉 Run [cyan]cgc help[/cyan] to see all available commands.\n")
console.print("👉 Run [cyan]cgc --version[/cyan] to check the version.\n")
console.print("👉 Running [green]codegraphcontext [white]works the same as using [green]cgc")