Skip to content

Commit d235d0a

Browse files
committed
feat: add Click CLI with init subcommand
Let's use click to make codemcp a generic command line tool as well as MCP. For BC it should still launch the MCP server with no arguments but we will also add an 'init' subcommand that creates an empty codemcp.toml, init's a git repository, and makes an initial commit with these files. ```git-revs 5c3e88d (Base revision) 1d06d21 Add Click import and other imports needed for CLI 1155f1a Add init_codemcp_project function and Click CLI 670b226 Export CLI function from __init__.py 1842a58 Update __main__.py to use Click CLI 14d2d2c Snapshot before codemcp change f70e670 Update pyproject.toml to use cli function af997d8 Add end-to-end test for init command 8302b2b Snapshot before auto-format 1d91f4e Auto-commit format changes 11bc401 Auto-commit lint changes HEAD Move subprocess import to the top of the init_codemcp_project function ``` codemcp-id: 209-feat-add-click-cli-with-init-subcommand ghstack-source-id: a8c3a95 Pull-Request-resolved: #203
1 parent 8af419d commit d235d0a

File tree

6 files changed

+170
-5
lines changed

6 files changed

+170
-5
lines changed

codemcp/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python3
22

33
from .hot_reload_entry import run as run_hot_reload
4-
from .main import codemcp, configure_logging, mcp, run
4+
from .main import cli, codemcp, configure_logging, mcp, run
55
from .shell import get_subprocess_env, run_command
66

77
__all__ = [
@@ -12,4 +12,5 @@
1212
"codemcp",
1313
"run_command",
1414
"get_subprocess_env",
15+
"cli",
1516
]

codemcp/__main__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22

33
# WARNING: do NOT do a relative import, this file must be directly executable
44
# by filename
5-
from codemcp import mcp, run
5+
from codemcp import cli, mcp
66

77
__all__ = ["mcp"]
88

99
if __name__ == "__main__":
10-
run()
10+
# Use Click's CLI interface instead of directly calling run()
11+
cli()

codemcp/main.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
import logging
44
import os
5+
import sys
6+
from pathlib import Path
7+
from typing import Optional
58

9+
import click
610
from mcp.server.fastmcp import FastMCP
711

812
from .tools.chmod import chmod
@@ -406,7 +410,92 @@ def filter(self, record):
406410
logging.info("Logs from 'mcp' module are being filtered")
407411

408412

413+
def init_codemcp_project(path: str) -> str:
414+
"""Initialize a new codemcp project.
415+
416+
Args:
417+
path: Path to initialize the project in
418+
419+
Returns:
420+
Message indicating success or failure
421+
"""
422+
import subprocess
423+
424+
try:
425+
# Convert to Path object and resolve to absolute path
426+
project_path = Path(path).resolve()
427+
428+
# Create directory if it doesn't exist
429+
project_path.mkdir(parents=True, exist_ok=True)
430+
431+
# Check if git repository already exists
432+
git_dir = project_path / ".git"
433+
if not git_dir.exists():
434+
# Initialize git repository
435+
subprocess.run(["git", "init"], cwd=project_path, check=True)
436+
print(f"Initialized git repository in {project_path}")
437+
else:
438+
print(f"Git repository already exists in {project_path}")
439+
440+
# Create empty codemcp.toml file if it doesn't exist
441+
config_file = project_path / "codemcp.toml"
442+
if not config_file.exists():
443+
with open(config_file, "w") as f:
444+
f.write("# codemcp configuration file\n\n")
445+
print(f"Created empty codemcp.toml file in {project_path}")
446+
else:
447+
print(f"codemcp.toml file already exists in {project_path}")
448+
449+
# Make initial commit if there are no commits yet
450+
try:
451+
# Check if there are any commits
452+
result = subprocess.run(
453+
["git", "rev-parse", "HEAD"],
454+
cwd=project_path,
455+
check=False,
456+
capture_output=True,
457+
text=True,
458+
)
459+
460+
if result.returncode != 0:
461+
# No commits yet, add codemcp.toml and make initial commit
462+
subprocess.run(
463+
["git", "add", "codemcp.toml"], cwd=project_path, check=True
464+
)
465+
subprocess.run(
466+
["git", "commit", "-m", "chore: initialize codemcp project"],
467+
cwd=project_path,
468+
check=True,
469+
)
470+
print("Created initial commit with codemcp.toml")
471+
else:
472+
print("Repository already has commits, not creating initial commit")
473+
except subprocess.CalledProcessError as e:
474+
print(f"Warning: Failed to create initial commit: {e}")
475+
476+
return f"Successfully initialized codemcp project in {project_path}"
477+
except Exception as e:
478+
return f"Error initializing project: {e}"
479+
480+
481+
@click.group(invoke_without_command=True)
482+
@click.pass_context
483+
def cli(ctx):
484+
"""CodeMCP: Command-line interface for MCP server and project management."""
485+
# If no subcommand is provided, run the MCP server (for backwards compatibility)
486+
if ctx.invoked_subcommand is None:
487+
run()
488+
489+
490+
@cli.command()
491+
@click.argument("path", type=click.Path(), default=".")
492+
def init(path):
493+
"""Initialize a new codemcp project with an empty codemcp.toml file and git repository."""
494+
result = init_codemcp_project(path)
495+
click.echo(result)
496+
497+
409498
def run():
410499
"""Run the MCP server."""
411500
configure_logging()
412-
mcp.run()
501+
mcp.run()

e2e/test_init_command.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env python3
2+
3+
import subprocess
4+
import tempfile
5+
from pathlib import Path
6+
7+
from codemcp.main import init_codemcp_project
8+
9+
10+
def test_init_command():
11+
"""Test the init command creates a codemcp.toml file and initializes a git repo."""
12+
# Create a temporary directory for testing
13+
with tempfile.TemporaryDirectory() as temp_dir:
14+
# Run the init_codemcp_project function
15+
result = init_codemcp_project(temp_dir)
16+
17+
# Check that the function reports success
18+
assert "Successfully initialized" in result
19+
20+
# Check that codemcp.toml was created
21+
config_file = Path(temp_dir) / "codemcp.toml"
22+
assert config_file.exists()
23+
24+
# Check that git repository was initialized
25+
git_dir = Path(temp_dir) / ".git"
26+
assert git_dir.is_dir()
27+
28+
# Check that a commit was created
29+
result = subprocess.run(
30+
["git", "log", "--oneline"],
31+
cwd=temp_dir,
32+
capture_output=True,
33+
text=True,
34+
check=True,
35+
)
36+
assert "initialize codemcp project" in result.stdout
37+
38+
39+
def test_init_command_existing_repo():
40+
"""Test the init command works with an existing git repository."""
41+
# Create a temporary directory for testing
42+
with tempfile.TemporaryDirectory() as temp_dir:
43+
# Initialize git repository first
44+
subprocess.run(["git", "init"], cwd=temp_dir, check=True)
45+
46+
# Make a dummy commit to simulate existing repository
47+
dummy_file = Path(temp_dir) / "dummy.txt"
48+
dummy_file.write_text("test content")
49+
50+
subprocess.run(
51+
["git", "config", "user.name", "Test User"], cwd=temp_dir, check=True
52+
)
53+
subprocess.run(
54+
["git", "config", "user.email", "[email protected]"],
55+
cwd=temp_dir,
56+
check=True,
57+
)
58+
subprocess.run(["git", "add", "dummy.txt"], cwd=temp_dir, check=True)
59+
subprocess.run(
60+
["git", "commit", "-m", "Initial commit"], cwd=temp_dir, check=True
61+
)
62+
63+
# Run the init_codemcp_project function
64+
result = init_codemcp_project(temp_dir)
65+
66+
# Check that the function reports success
67+
assert "Successfully initialized" in result
68+
69+
# Check that codemcp.toml was created
70+
config_file = Path(temp_dir) / "codemcp.toml"
71+
assert config_file.exists()

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies = [
1414
"pyyaml>=6.0.0",
1515
"pytest-xdist>=3.6.1",
1616
"editorconfig>=0.17.0",
17+
"click>=8.1.8",
1718
]
1819

1920
[dependency-groups]
@@ -28,7 +29,7 @@ dev = [
2829
]
2930

3031
[project.scripts]
31-
codemcp = "codemcp:run"
32+
codemcp = "codemcp:cli"
3233
codemcp-multi = "codemcp.multi_entry:main"
3334

3435
[build-system]

uv.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)