Skip to main content
Glama
README.md
# otama

An [MCP](https://modelcontextprotocol.io) server that gives AI assistants — Cursor, Claude
Desktop, or your own voice agent — access to your local git repositories.

Read operations run freely. Anything that writes returns a confirmation token and a
plain-English summary first, and only acts when called a second time with that token. The
assistant asks before it commits.

```
You:  commit my changes in api-service with message "fix auth timeout"
LLM:  Commit 3 changed files in api-service with message 'fix auth timeout'. Confirm?
You:  yes
LLM:  Committed — a4f21c9 fix auth timeout
```

## Why

Most filesystem MCP servers give a model unrestricted read and write access to a directory
tree. That is fine when you are watching every tool call. It is not fine when the model is
driving a voice assistant, running unattended, or has just misheard you.

`otama` assumes the model will occasionally be wrong and makes the consequences small:

- **A path sandbox.** Every path is resolved *before* it is checked against your configured
  roots. `..`, symlinks and absolute paths cannot escape.
- **Two-phase confirmation.** Write and execute tools called without a token do nothing but
  describe what they would do. Tokens are single-use, expire after two minutes, and are
  bound to the tool that issued them.
- **A command allowlist.** `run_command` refuses anything outside the list outright rather
  than offering it for confirmation. A mishearing should never be one "yes" away from
  `rm -rf`.

## Install

Requires Python 3.11+ and git on PATH.

```bash
git clone https://github.com/Ronith2906/otama.git
cd otama
python -m venv .venv
.venv/bin/pip install -r requirements.txt          # Windows: .venv\Scripts\pip.exe
cp otama.example.toml otama.toml
```

Edit `otama.toml` and set `project_roots` to the folder or folders containing your repos.
Use forward slashes on Windows.

```toml
[projects]
project_roots = ["C:/Users/you/code"]
```

Verify:

```bash
.venv/bin/python -c "import asyncio; from projects_server import otama; print([t.name for t in asyncio.run(otama.list_tools())])"
```

Ten tool names means you are running.

## Connect it

**Cursor** — `~/.cursor/mcp.json`. **Claude Desktop** — `claude_desktop_config.json`
(Settings → Developer → Edit Config). Same shape either way:

```json
{
  "mcpServers": {
    "otama-projects": {
      "command": "/absolute/path/to/otama/.venv/bin/python",
      "args": ["/absolute/path/to/otama/projects_server.py"]
    }
  }
}
```

On Windows use `\\` in JSON paths — `C:\\Users\\you\\otama\\.venv\\Scripts\\python.exe`.

Restart the app fully. A reload is not enough; MCP servers load at startup.

## Tools

**Read — run immediately**

| Tool | Returns |
|---|---|
| `list_projects` | Every repo or folder under your roots, with a `is_git_repo` flag |
| `project_tree` | Two-level tree, skipping `node_modules`, `.venv`, `dist` and friends |
| `read_file` | One text file, size-capped |
| `search_code` | Regex across a project, with file and line numbers |
| `git_status` | Branch, uncommitted files, last five commits |
| `git_diff` | Working-tree diff, truncated |
| `daily_brief` | What moved across every repo in the last N days |

**Write and execute — confirmation required**

| Tool | Does |
|---|---|
| `git_commit` | Stage all and commit |
| `write_file` | Create or overwrite a file |
| `run_command` | Run an allowlisted development command |

## Configuration

```toml
[projects]
project_roots            = ["C:/Users/you/code"]
max_file_bytes           = 200000   # refuse to read anything larger
command_timeout_seconds  = 120
confirmation_ttl_seconds = 120      # how long a token stays valid

# run_command refuses anything not on this list. This is not something a
# confirmation can override — add what you actually use.
allowed_commands = ["git","npm","npx","pnpm","yarn","node",
                    "python","py","pip","pytest","ruff","uv",
                    "dotnet","cargo","go"]
```

## Notes for contributors

The two-phase pattern lives in `stage()`, `redeem()` and `_expire()`. If you add a tool with
side effects, use it.

One subtlety worth preserving: `redeem()` does **not** consume the token when the tool kind
does not match. A model calling the wrong tool by mistake must not burn the user's approval
and force them to confirm all over again. This was a real bug, caught in testing.

New capability servers should follow the same shape — one file, one domain, read tools free,
write tools gated, refuse rather than confirm when the action is outside the envelope.

## Status

Early. The projects server is tested and in daily use; more capability servers (media, mail,
browser) are planned as part of a larger voice-assistant project. Issues and PRs welcome.

## License

MIT