Skip to main content
Glama
README.md
# DevPilot

DevPilot is an MCP-based local development agent. An LLM reasons over the user's request and available tools, calls MCP tools (git / docker / filesystem), observes the results, and loops until it can produce a final answer.

---

## Quick start

1. Copy `.env.example` to `.env` and set `OPENAI_API_KEY` (OpenRouter / Groq / OpenAI-compatible).
2. From the repo root:

```bash
PYTHONPATH=client poetry run python client/client.py
```

3. Type a prompt. Type `quit` to exit.

Hiring-manager demo (broken backend container):

```bash
PYTHONPATH=client poetry run python demo/run_demo.py
```

See `demo/README.md` for the talking script and a no-Docker backup.

Optional env vars:

| Variable | Default | Purpose |
|----------|---------|---------|
| `OPENAI_API_KEY` | (required) | API key |
| `OPENAI_BASE_URL` | `https://openrouter.ai/api/v1` | Compatible API base URL |
| `OPENAI_MODEL` | `openai/gpt-4o-mini` | Model name |

---

## Architecture

```text
                   User
                     │
                     ▼
          Conversation Manager
                     │
                     ▼
              Tool Registry (MCP)
                     │
        list_tools() + inputSchema
                     │
                     ▼
                 LLM (Planner)
                     │
     ┌───────────────┴────────────────┐
     │                                │
     ▼                                ▼
 Tool Call(s)                  Final Answer
     │                                │
     ▼                                ▼
  Executor                         Exit
     │
     ▼
 Tool Result(s)
     │
     ▼
Conversation Manager
     │
     └───────────────► Back to LLM
```

---

## File map

| Stage | File | Role |
|-------|------|------|
| Entry | `client/client.py` | Starts MCP server over stdio, REPL, prints answers |
| Loop | `client/agent/main.py` | Orchestrates plan → execute → update history (max 8 turns) |
| History | `client/agent/conversation.py` | Stores user / assistant / tool messages |
| Schemas | `client/agent/registry.py` | Formats MCP tool schemas for the LLM |
| Planner | `client/agent/llm.py` | LLM returns `tool_call` / `final_answer` / `clarification` / `error` |
| Execute | `client/agent/executor.py` | Calls `session.call_tool(...)` over MCP |
| Format | `client/agent/context.py` | Pretty-prints tool results into conversation |
| Models | `client/agent/models.py` | `AgentStatus`, `ToolCall`, `ToolResult`, etc. |
| MCP app | `server/app.py` | `FastMCP("DevPilot")` |
| Tools | `server/server.py` | `@mcp.tool` registrations |
| Impl | `server/git/*`, `server/doocker/*`, `server/filesystem/*` | Real commands |
| Helpers | `server/utils/{validator,Commands,response}.py` | Validate paths, run subprocess, `ok`/`fail` dicts |

---

## Request flow (by file)

### 1. Boot — `client/client.py`

- Loads `.env`
- Spawns the MCP server: `poetry run python -m server.server` over **stdio**
- Creates an MCP `ClientSession`, calls `initialize()`, then `list_tools()`
- Starts a REPL and passes each prompt into `agent_main`

### 2. Agent loop — `client/agent/main.py`

Builds:

- `ConversationManager` — history
- `ToolRegistry` — tool schemas
- `LLMPlanner` — next decision
- `Executor` — MCP tool calls

Then:

1. `conversation.add_user(prompt)`
2. Loop up to `MAX_TURNS = 8`:
   - `planner.plan(conversation, registry)`
   - If `tool_call` → record calls → `executor.execute` → record results → continue
   - If `final_answer` / `clarification` / `error` → return

### 3. Conversation — `client/agent/conversation.py`

Stores messages as `user`, `assistant`, or `tool`.  
`to_prompt_text()` is what the LLM sees every turn.

### 4. Tool registry — `client/agent/registry.py`

Turns MCP `list_tools()` into JSON schema text (name, description, properties, required).

### 5. LLM planner — `client/agent/llm.py`

Receives:

- System prompt (DevPilot rules)
- Available tool schemas
- Conversation history

Returns a single JSON object parsed into `AgentResponse`.

### 6. Executor — `client/agent/executor.py`

For each `ToolCall`:

- `await session.call_tool(tool_name, tool_args)`
- Collects text content into `ToolResult`

The executor never decides — it only runs tools.

### 7. MCP server — `server/server.py` + implementations

`@mcp.tool` handlers delegate to modules under:

- `server/git/` — status, branch, log, diff, commit, push, pull, stash, …
- `server/doocker/` — ps, logs, inspect, exec, start, stop, restart, …
- `server/filesystem/` — read/write/list/search/find/delete/symbols

Helpers:

- `validator` — ensure path is a git repo / file / directory
- `Commands.run_cmd` / `run_shell_cmd` — subprocess wrappers
- `response.ok` / `fail` — standard `{success, result|message}` dicts

### 8. Context — `client/agent/context.py`

Formats each `ToolResult` into readable text, then appends it to the conversation for the next LLM turn.

---

## Example: git status + branch

**User**

```text
What's the status of my repo at /Users/nitin/Desktop/local-git-mcp? Which branch am I on?
```

### Turn 1 — LLM (`client/agent/llm.py`)

```json
{
  "status": "tool_call",
  "tool_calls": [
    {
      "tool_name": "git_status",
      "tool_args": {
        "repo_path": "/Users/nitin/Desktop/local-git-mcp"
      }
    },
    {
      "tool_name": "git_current_branch",
      "tool_args": {
        "repo_path": "/Users/nitin/Desktop/local-git-mcp"
      }
    }
  ]
}
```

### Executor → MCP → server

| Tool | Path | Command |
|------|------|---------|
| `git_status` | `server/git/status.py` | `git -C <path> status` |
| `git_current_branch` | `server/git/current_branch.py` | `git -C <path> …` (current branch) |

**Tool result shape**

```python
{"success": True, "result": "<command stdout>"}
```

Results are formatted by `context.py` and stored in the conversation.

### Turn 2 — LLM

```json
{
  "status": "final_answer",
  "answer": "You're on branch main. The working tree has unstaged changes in client/agent/llm.py."
}
```

`client/client.py` prints:

```text
DevPilot> You're on branch main. The working tree has unstaged changes in client/agent/llm.py.
```

---

## Example: docker debugging

**User**

```text
Why is my backend container failing?
```

```text
LLM → docker_ps()
       backend -> exited, db -> running

LLM → docker_logs(container="backend")
       panic: database connection refused

LLM → final_answer
       The backend exits because it cannot connect to the database.
```

---

## LLM response types

### Tool call

```json
{
  "status": "tool_call",
  "tool_calls": [
    {
      "tool_name": "docker_logs",
      "tool_args": { "container": "backend" }
    }
  ]
}
```

### Final answer

```json
{
  "status": "final_answer",
  "answer": "The backend container exits because it cannot connect to the database."
}
```

### Clarification

```json
{
  "status": "clarification",
  "answer": "Which repository path should I inspect?"
}
```

### Error

```json
{
  "status": "error",
  "error": "Unable to execute the requested tool."
}
```

---

## Agent loop (simplified)

```python
conversation.add_user(prompt)

for _ in range(MAX_TURNS):
    response = planner.plan(conversation, registry)

    if response.status == TOOL_CALL:
        results = await executor.execute(response.tool_calls)
        conversation.add_tool_calls_and_results(...)
        continue

    return response  # final_answer | clarification | error
```

---

## Design principles

- **LLM reasons; MCP tools execute** — the executor never makes decisions.
- **Discoverable tools** — schemas come from MCP `list_tools()`, not hard-coded client logic.
- **Conversation is stateful** — multi-step flows (e.g. `docker_ps` → `docker_logs`) work naturally.
- **Structured LLM I/O** — forced JSON statuses; the agent must not invent tool output.
- **Deterministic tools** — validators + subprocess; standard `ok`/`fail` responses.
- **Bounded loop** — stops after `MAX_TURNS` (8) to avoid infinite tool calling.

---

## Available tools (high level)

**Git:** status, current branch, branch list, log, diff, show, checkout, create branch, commit, push, pull, stash  

**Docker:** ps, images, logs, inspect, exec, start, stop, restart  

**Filesystem:** read/write file, list directory, search text, find files, file info, delete file, list/read symbols