Skip to content
Merged
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
52 changes: 48 additions & 4 deletions .env.template.mcp
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,56 @@
# for you.
# =============================================================================

# ---- Required ---------------------------------------------------------------
# Bearer token shared between MCP Context Forge and any client (GitPilot,
# HomePilot, custom CLIs). Rotate by editing this file and `make stop-mcp`
# / `make run-mcp`. The token never leaves your machine.
# ---- Forge credentials ------------------------------------------------------
# THREE DIFFERENT SECRETS. Treating them as one is what produced
# "Invalid authentication credentials" on every registration: a JWT *signing
# secret* is not a bearer token, and Forge rejects it as one. See
# docs/MCP_AUTH.md.
#
# MCP_FORGE_JWT_SECRET signs Forge's tokens. Never sent anywhere.
# MCP_FORGE_ADMIN_PASSWORD used to OBTAIN a token (and to log into the UI).
# MCP_FORGE_API_TOKEN a token, and the only value ever sent as Bearer.
#
# MCP_AUTH_TOKEN is the historical single value and still works as the signing
# secret, so existing setups keep running unchanged.
MCP_AUTH_TOKEN=change-me-please-32-bytes-of-randomness

# Optional overrides — set these and MCP_AUTH_TOKEN stops being used for them.
#MCP_FORGE_JWT_SECRET=
MCP_FORGE_ADMIN_EMAIL=admin@example.com
#MCP_FORGE_ADMIN_PASSWORD=

# A Forge API token (Forge UI → Tokens, or `mcpgateway.utils.create_jwt_token`).
# Set this for production: it is revocable and needs no admin password on disk.
# Left empty, GitPilot logs in with the admin credentials above and caches the
# resulting JWT in .mcp/forge-token (owner-only, gitignored).
#MCP_FORGE_API_TOKEN=

# ---- Reusing a Forge that is already running --------------------------------
# HomePilot (and other stacks in the family) run their own Context Forge.
# GitPilot adopts a healthy one instead of starting a second: two gateways
# split the tool registry in half and fight over :4444. A Forge GitPilot did
# not start is never stopped by `make stop-mcp`.
#
# MCP_FORGE_URL pin a specific gateway (skips discovery entirely)
# MCP_FORGE_ADOPT auto (default) | never — 'never' always starts our own
# MCP_ADVERTISE_HOST how an adopted Forge reaches our servers; defaults to
# host.docker.internal, which is right on Docker Desktop,
# Rancher and WSL2, and is provided on Linux via
# extra_hosts: host-gateway.
#MCP_FORGE_URL=http://localhost:4444
#MCP_FORGE_ADOPT=auto
#MCP_ADVERTISE_HOST=host.docker.internal

# ---- Forge runtime ----------------------------------------------------------
# Local dev serves plain HTTP, and a browser silently drops a Secure cookie
# over HTTP — which presents as "login works, then returns to the login page".
# Both stay off locally; a TLS deployment sets ENVIRONMENT=production and
# SECURE_COOKIES=true.
#MCP_FORGE_ENVIRONMENT=development
#MCP_FORGE_SECURE_COOKIES=false
#MCP_FORGE_AUTH_REQUIRED=false

# ---- Upstream repo URLs (override only if you fork) -------------------------
MCP_FORGE_REPO=https://github.com/ruslanmv/mcp-context-forge.git
MCP_POSTGRE_REPO=https://github.com/ruslanmv/mcp-postgre-server.git
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ frontend/.vite/
*.gpctx
reports/
.mcp.env
# Cached Forge JWT (owner-only); re-minted on demand, never shared.
.mcp/
mcp-stack/

# GitPilot dry-run/CLI output artifacts
Expand Down
103 changes: 49 additions & 54 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
UV ?= uv
PYTHON ?= python3.11
PORT ?= 8000
# GitPilot drifts to the next free port when PORT is taken; PORT_WINDOW is how
# far it may drift, and therefore how far `make stop` has to look.
PORT_WINDOW ?= 20
PORT_LAST := $(shell expr $(PORT) + $(PORT_WINDOW) - 1)
# Keep uv's cache beside the project so WSL /mnt/c checkouts do not copy
# wheels from Linux home-dir cache across filesystems on every install.
UV_CACHE_DIR ?= .uv-cache
Expand All @@ -28,7 +32,7 @@ DOCKER_COMPOSE := $(shell if command -v docker > /dev/null && docker compose ver
mcp mcp-down mcp-logs gateway gateway-down gateway-logs gateway-register \
install-mcp run-mcp run-all run-all-local stop-mcp logs-mcp sync-mcp uninstall-mcp \
fix-line-endings install-mcp-workflows register-mcp-servers \
stop-soft stop-all smoke-mcp
stop-soft stop-all smoke-mcp status-mcp

## Show available targets
help:
Expand All @@ -46,7 +50,7 @@ help:
@echo " make dev Alias for install-dev"
@echo " make run Run MCP stack + GitPilot backend/frontend"
@echo " make run-bare Run GitPilot backend + frontend WITHOUT MCP (no Docker required)"
@echo " make stop Stop all processes on ports 8000 and 5173"
@echo " make stop Stop all processes on ports $(PORT)-$(PORT_LAST) and 5173"
@echo " make test Run tests with pytest via uv"
@echo " make benchmark Run code generation benchmark (all tiers)"
@echo " make benchmark-quick Run quick benchmark (tier 1 smoke test)"
Expand Down Expand Up @@ -195,27 +199,35 @@ run: run-mcp run-bare
## any environment where Docker is unavailable. The MCP Servers tab
## will show the gateway as Unreachable; clicking Sync is a no-op.
run-bare:
@echo "🚀 Starting GitPilot on http://127.0.0.1:$(PORT)..."
@# 1. Already a healthy GitPilot? → skip backend boot, go straight to frontend.
@# 1. Already a healthy GitPilot on the preferred port? → reuse it, and point
@# the frontend proxy at it rather than starting a second backend.
@if curl -sf http://127.0.0.1:$(PORT)/api/ping > /dev/null 2>&1; then \
echo "✅ GitPilot backend already running on :$(PORT) — skipping start."; \
echo "🎨 Starting frontend dev server on http://localhost:5173..."; \
cd frontend && exec npm run dev -- --open; \
GITPILOT_PORT=$(PORT) exec bash scripts/run-frontend.sh --open; \
fi
@# 2. Port held by something *else*? → stop and ask the user to clean up.
@if lsof -i:$(PORT) -sTCP:LISTEN > /dev/null 2>&1 || \
nc -z 127.0.0.1 $(PORT) > /dev/null 2>&1; then \
echo "⚠️ Port $(PORT) is held by a non-GitPilot process. Run 'make stop' first."; \
exit 1; \
fi
@trap 'kill 0' EXIT; \
$(UV_ENV) $(UV) run --no-dev python -m gitpilot serve --host 127.0.0.1 --port $(PORT) --no-open & \
@# 2. Otherwise pick the port for real: the preferred one, or the next free.
@# A port held by something else (another app, an old run) moves GitPilot
@# instead of stopping it. The server re-checks at bind time and writes
@# the port it actually took, so a race can never leave us polling the
@# wrong one.
@PORTFILE="$$(mktemp -t gitpilot-port.XXXXXX)"; \
RUNPORT=$$($(UV_ENV) $(UV) run --no-dev python -m gitpilot free-port --port $(PORT) 2>/dev/null || echo $(PORT)); \
if [ "$$RUNPORT" != "$(PORT)" ]; then \
echo "⚠️ Port $(PORT) is in use — starting GitPilot on $$RUNPORT instead."; \
fi; \
echo "🚀 Starting GitPilot on http://127.0.0.1:$$RUNPORT..."; \
trap 'kill 0; rm -f "$$PORTFILE"' EXIT; \
$(UV_ENV) $(UV) run --no-dev python -m gitpilot serve --host 127.0.0.1 \
--port $$RUNPORT --no-strict-port --port-file "$$PORTFILE" --no-open & \
BACKEND_PID=$$!; \
echo "⏳ Waiting for backend to be ready (up to 60s for WSL/first-start)..."; \
READY=0; \
for i in $$(seq 1 30); do \
if curl -sf http://127.0.0.1:$(PORT)/api/ping > /dev/null 2>&1; then \
echo "✅ Backend is ready after $$((i * 2))s"; \
BOUND=$$(cat "$$PORTFILE" 2>/dev/null | tr -d "[:space:]"); \
[ -n "$$BOUND" ] && RUNPORT=$$BOUND; \
if curl -sf http://127.0.0.1:$$RUNPORT/api/ping > /dev/null 2>&1; then \
echo "✅ Backend is ready after $$((i * 2))s on http://127.0.0.1:$$RUNPORT"; \
READY=1; \
break; \
fi; \
Expand All @@ -228,24 +240,27 @@ run-bare:
echo "⚠️ Backend took longer than 60s. Starting frontend anyway — the frontend"; \
echo " will keep polling /api/ping and recover when the backend comes online."; \
fi; \
echo "🎨 Starting frontend dev server on http://localhost:5173..."; \
cd frontend && npm run dev -- --open
echo "🎨 Starting frontend dev server on http://localhost:5173 (API → :$$RUNPORT)..."; \
GITPILOT_PORT=$$RUNPORT bash scripts/run-frontend.sh --open

## Stop all running processes (ports 8000 and 5173) AND the MCP stack.
## Now that `make run` starts the MCP Context Forge stack by default, `make
## stop` is symmetric: it stops both GitPilot and Forge. `stop-mcp` is
## idempotent — running it when nothing is up is a clean no-op.
stop:
@echo "🛑 Attempting to stop processes on ports $(PORT) and 5173..."
@echo "🛑 Attempting to stop processes on ports $(PORT)-$(PORT_LAST) and 5173..."

@# Stop anything on backend port $(PORT)
@pids=$$(sudo lsof -t -i:$(PORT) -sTCP:LISTEN); \
if [ -n "$$pids" ]; then \
echo "Killing $$pids on port $(PORT)..."; \
sudo kill -9 $$pids; \
else \
echo "No process found on port $(PORT)."; \
fi
@# Stop GitPilot anywhere in the port window it may have drifted into.
@found=0; \
for port in $$(seq $(PORT) $(PORT_LAST)); do \
pids=$$(sudo lsof -t -i:$$port -sTCP:LISTEN 2>/dev/null); \
if [ -n "$$pids" ]; then \
echo "Killing $$pids on port $$port..."; \
sudo kill -9 $$pids; \
found=1; \
fi; \
done; \
[ $$found -eq 0 ] && echo "No process found on ports $(PORT)-$(PORT_LAST)." || true

@# Stop anything on frontend port 5173
@pids=$$(sudo lsof -t -i:5173 -sTCP:LISTEN); \
Expand All @@ -264,8 +279,8 @@ stop:
## owns; never prompts for a password. Suitable for `make run-all` to call
## as a pre-step so a stale backend can't hide newly-pulled code paths.
stop-soft:
@echo "🛑 Stopping user-owned GitPilot processes on :$(PORT) and :5173..."
@for port in $(PORT) 5173; do \
@echo "🛑 Stopping user-owned GitPilot processes on :$(PORT)-$(PORT_LAST) and :5173..."
@for port in $$(seq $(PORT) $(PORT_LAST)) 5173; do \
pids=$$(lsof -t -i:$$port -sTCP:LISTEN 2>/dev/null || true); \
if [ -n "$$pids" ]; then \
for pid in $$pids; do \
Expand Down Expand Up @@ -764,32 +779,13 @@ run-mcp: install-mcp
echo " Or run without MCP: make run-bare"; \
exit 1; \
fi
@echo "🚀 Starting MCP Context Forge stack..."
docker compose --env-file .mcp.env -f docker-compose.mcp.yml --profile mcp up -d
@set -a; . ./.mcp.env; set +a; \
forge_port="$${MCP_FORGE_PORT:-4444}"; \
echo "⏳ Waiting for MCP Context Forge on http://localhost:$$forge_port/health..."; \
ready=0; \
for i in $$(seq 1 60); do \
if curl -fsS "http://localhost:$$forge_port/health" >/dev/null 2>&1; then \
echo "✅ MCP Context Forge reachable after $$((i * 2))s."; \
ready=1; \
break; \
fi; \
sleep 2; \
done; \
if [ $$ready -ne 1 ]; then \
echo "❌ MCP Context Forge did not become host-reachable on http://localhost:$$forge_port."; \
echo " Tail logs with: make logs-mcp"; \
exit 1; \
fi
@set -a; . ./.mcp.env; set +a; \
echo "✅ Forge: http://localhost:$${MCP_FORGE_PORT:-4444}"; \
echo " Postgre: http://localhost:$${MCP_POSTGRE_PORT:-8080}"; \
echo " Inspector: http://localhost:$${MCP_INSPECTOR_PORT:-8081}"; \
echo " Milvus (opt-in): docker compose --env-file .mcp.env -f docker-compose.mcp.yml --profile milvus up -d"
@bash scripts/mcp-stack.sh up
@bash scripts/register-mcp-servers.sh

## Report which Forge is in use, who started it, and whether auth works.
status-mcp:
@bash scripts/mcp-stack.sh status

## Register the 3 MCP servers with Forge (idempotent; called by run-mcp).
register-mcp-servers:
@bash scripts/register-mcp-servers.sh
Expand Down Expand Up @@ -821,8 +817,7 @@ run-all-local:

## Stop the MCP stack (volumes preserved)
stop-mcp:
@docker compose --env-file .mcp.env -f docker-compose.mcp.yml --profile mcp down 2>/dev/null || true
@echo "🛑 MCP stack stopped (volumes kept). 'make uninstall-mcp' to remove data."
@bash scripts/mcp-stack.sh down

## Rotate the MCP_AUTH_TOKEN end-to-end and re-init Forge with the new
## token. The escape hatch when `make run-mcp`'s auto-recovery isn't
Expand Down
20 changes: 8 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,6 @@ Experience the application in action through our hosted demo environment:
[![Live Demo Preview](assets/2026-04-07-16-17-56.png)](https://huggingface.co/spaces/ruslanmv/gitpilot)

🔗 **Access the live demo:**

[https://gitpilot.ruslanmv.com](https://gitpilot.ruslanmv.com)
or in Hugging Face
[https://huggingface.co/spaces/ruslanmv/gitpilot](https://huggingface.co/spaces/ruslanmv/gitpilot)

### Option 3: Python CLI (fastest)
Expand All @@ -126,18 +123,19 @@ gitpilot serve

Open [http://localhost:8000](http://localhost:8000) and you're done.

If something else already holds :8000, GitPilot starts on the next free port and
tells you which — no `make stop` first. A port you ask for yourself
(`gitpilot serve --port 9000`, or `GITPILOT_PORT=9000`) is honoured strictly
instead, because a proxy or container map may depend on that exact number; add
`--no-strict-port` to let it drift anyway. See
[docs/PORTS.md](docs/PORTS.md).

> **Heads up:** the PyPI package is published as **`gitcopilot`** (the name `gitpilot` was already taken) but the command you run is `gitpilot`. Python **3.11** or **3.12** required.

---

## VS Code Extension

[Open in Microsoft Market place](https://marketplace.visualstudio.com/items?itemName=ruslanmv.gitpilot-vscode)
![assets/vs1.jpg](assets/vs1.jpg)




![assets/2026-04-07-14-15-33.png](assets/2026-04-07-14-15-33.png)
The sidebar panel gives you everything in one place:

| Feature | What it does |
Expand Down Expand Up @@ -190,7 +188,6 @@ You send a request

> **Note:** Simple questions (e.g. "explain this code") may return a direct answer without generating a multi-step plan. This is expected — the planner activates for tasks that require file changes or multi-step execution.

![assets/vs2.jpg](assets/vs2.jpg)
### Code generation and Apply Patch

When you ask GitPilot to create or edit files, the response includes structured `edits` — not just text. The **Apply Patch** button writes them directly to your workspace.
Expand Down Expand Up @@ -228,7 +225,6 @@ How it works under the hood:
| **Claude** | Add your Anthropic API key | Paid |
| **Watsonx** | Add IBM credentials | Paid |


---

## Web App
Expand Down
Binary file removed assets/vs1.jpg
Binary file not shown.
Binary file removed assets/vs2.jpg
Binary file not shown.
2 changes: 1 addition & 1 deletion deploy/huggingface/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ ENV HOME=/home/user \
GITPILOT_PROVIDER=ollabridge \
OLLABRIDGE_BASE_URL=https://ruslanmv-ollabridge.hf.space \
GITPILOT_OLLABRIDGE_MODEL=qwen2.5:1.5b \
CORS_ORIGINS="https://gitpilot.ruslanmv.com,http://localhost:5173" \
CORS_ORIGINS="*" \
GITPILOT_CONFIG_DIR=/tmp/gitpilot

WORKDIR $HOME/app
Expand Down
22 changes: 19 additions & 3 deletions docker-compose.mcp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,29 @@ services:
HOST: 0.0.0.0
PORT: "4444"
AUTH_REQUIRED: "${MCP_FORGE_AUTH_REQUIRED:-false}"
JWT_SECRET_KEY: ${MCP_AUTH_TOKEN:?Run "make install-mcp" first or copy .env.template.mcp -> .mcp.env}
# The signing secret. It is NOT a bearer token — Forge rejects it as one
# (see docs/MCP_AUTH.md), so nothing ever sends it. MCP_AUTH_TOKEN stays
# as the fallback so existing .mcp.env files keep working.
JWT_SECRET_KEY: ${MCP_FORGE_JWT_SECRET:-${MCP_AUTH_TOKEN:?Run "make install-mcp" first or copy .env.template.mcp -> .mcp.env}}
BASIC_AUTH_USER: ${MCP_FORGE_ADMIN_USER:-admin}
BASIC_AUTH_PASSWORD: ${MCP_AUTH_TOKEN}
BASIC_AUTH_PASSWORD: ${MCP_FORGE_ADMIN_PASSWORD:-${MCP_AUTH_TOKEN}}
# Email login is what the Admin UI and /auth/email/login use; these are
# the credentials `forge_login` exchanges for a JWT.
PLATFORM_ADMIN_EMAIL: ${MCP_FORGE_ADMIN_EMAIL:-admin@example.com}
PLATFORM_ADMIN_PASSWORD: ${MCP_FORGE_ADMIN_PASSWORD:-${MCP_AUTH_TOKEN}}
MCPGATEWAY_UI_ENABLED: "true"
MCPGATEWAY_ADMIN_API_ENABLED: "true"
MCP_AUTH_TOKEN: ${MCP_AUTH_TOKEN}
# Forge marks its session cookie Secure when either of these says so, and
# a browser drops a Secure cookie sent over plain HTTP — which looks
# exactly like "login succeeds, then bounces back to the login page".
# Local dev is HTTP, so both are off here; a TLS deployment sets both.
ENVIRONMENT: ${MCP_FORGE_ENVIRONMENT:-development}
SECURE_COOKIES: "${MCP_FORGE_SECURE_COOKIES:-false}"
LOG_LEVEL: ${MCP_FORGE_LOG_LEVEL:-INFO}
# Lets an adopted Forge (and ours) dial the host on native Linux, where
# host.docker.internal is not defined by default.
extra_hosts:
- "host.docker.internal:host-gateway"
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:4444/health || exit 1"]
interval: 10s
Expand Down
Loading
Loading