Skip to main content
Glama
README.md
# ops-assistant-mcp

An [MCP](https://modelcontextprotocol.io) server that exposes internal documents and a SaaS
board (Monday.com, as an example integration) as tools, plus small command-line agents that call
those tools with either Gemini or Claude.

The goal is a compact, readable reference for the general pattern: expose narrow tools over MCP,
let a model call them, control what it is allowed to change, and keep a record of what it did.

```
question ─► agent.py / agent_gemini.py ──MCP (stdio)──► server.py
             (LLM tool-calling loop)                      ├─ search_knowledge_base  [read]
                                                          ├─ list_board_items       [read]
                                                          └─ create_board_item      [write, two-step]
                                  every tool call ─► guard.py ─► audit.jsonl
```

## Tools

| Tool | Access | What it does |
|---|---|---|
| `search_knowledge_base` | read | Full-text search (SQLite FTS5, BM25) over markdown files; returns passages with source file and heading. |
| `list_board_items` | read | Lists items on a Monday.com board. |
| `create_board_item` | write | Creates a board item through a preview step and a single-use confirmation token. |

## Design notes

- **Keyword search instead of a vector database.** For policies, naming conventions and price lists,
  questions are usually exact-term lookups. FTS5 needs no embedding service and the index is
  rebuilt from the markdown files at startup. Common question words are ignored and weak matches
  are dropped, so an unanswerable question returns an explicit `NO_MATCHES` result instead of noise.
  A semantic or hybrid layer would be the next step for fuzzier questions.
- **Read-only by default.** Write tools are refused unless `OPS_WRITE_ENABLED=1`.
- **Allowlist.** `OPS_ALLOWED_TOOLS=search_knowledge_base,list_board_items` limits which tools can run.
- **Audit log.** Every tool call, including denied ones, appends a JSON line to `audit.jsonl`
  (tool, access type, arguments, outcome, duration).
- **Dry-run mode.** Without `MONDAY_API_TOKEN`, the Monday client serves a local sample board and
  keeps writes in memory, so everything runs without an external account.

## Safety model: what is and isn't enforced

Enforced by the server:
- A write can only execute with a `confirmation_token` that the server issued in an earlier
  preview call, for the **same arguments**. Tokens are single-use and expire after 5 minutes.
- Writes are disabled unless the operator turns them on; tool access can be restricted by allowlist.

Enforced by the command-line agents:
- When the model tries to execute a write, the terminal asks the person to approve it
  (`OPS_AUTO_APPROVE=1` skips the prompt for automated runs).

Not guaranteed:
- The server cannot know whether a human saw the preview. Human approval lives in the client, so
  any other client built on this server must implement its own approval step.
- The audit log is a local file with no tamper protection, and there is no per-user authentication
  or permission model; permissions are per deployment.

## Quick start

Requires Python 3.11+.

```bash
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev,gemini]"        # or ".[dev,claude]", or both
pytest
```

Choose a model provider and set its key:

```bash
export GEMINI_API_KEY=...             # Google AI Studio
python -m ops_assistant.agent_gemini "How many PTO days carry over, and when do they expire?"

export ANTHROPIC_API_KEY=...          # Anthropic Console
python -m ops_assistant.agent "How many PTO days carry over, and when do they expire?"
```

Model names change over time. Set `GEMINI_MODEL` or `ANTHROPIC_MODEL` to a current model from the
provider's model list. Free-tier quotas are limited per model, and each question can use several
requests. Inspect what the model did with `tail -n 5 audit.jsonl`.

The `docs/` folder and `fixtures/board.json` contain fictional sample data.

## Configuration

| Variable | Purpose |
|---|---|
| `GEMINI_API_KEY` / `ANTHROPIC_API_KEY` | Credentials for the agent you run |
| `GEMINI_MODEL` / `ANTHROPIC_MODEL` | Model override |
| `OPS_DOCS_DIR` | Folder of markdown files to index (default: `docs/`) |
| `OPS_WRITE_ENABLED=1` | Allow write tools |
| `OPS_ALLOWED_TOOLS` | Comma-separated tool allowlist |
| `OPS_AUTO_APPROVE=1` | Skip the terminal approval prompt for writes |
| `OPS_AUDIT_LOG` | Audit log path (default: `audit.jsonl`) |
| `MONDAY_API_TOKEN` | Use a real Monday.com account instead of the sample board |
| `MONDAY_API_VERSION` | Optional Monday API version pin |

## Adding another integration

1. Write a small client module (see `monday.py`): authentication, one method per operation, errors raised clearly.
2. In `server.py`, add a function per operation with `@mcp.tool()` and `@guarded("read")` or `@guarded("write")`.
   Write the docstring for the model: it becomes the tool description.
3. For anything that changes data, reuse the preview-and-token pattern from `create_board_item`,
   and add the tool name to `WRITE_TOOLS` in `common.py` so the agents ask for approval.
4. Add tests with the HTTP layer mocked, like `tests/test_monday_live_shape.py`.

## Tests

19 tests run offline without credentials: search behavior, the audit log and permissions, the
two-step write flow (including token reuse, argument mismatch and expiry), the approval prompt, an
end-to-end run of the real server over stdio, both agent loops against scripted model replies, and
the shape of Monday.com requests with HTTP mocked.

## Limitations

- The Monday.com client is exercised only through mocked HTTP and the dry-run fixture; it has not
  been validated against a live account.
- Calls to Gemini and Claude are not covered by automated tests.
- Search is keyword-based and the index is rebuilt in memory on each start.
- Pending write tokens are stored in memory, so they do not survive a server restart.

## License

MIT, see [LICENSE](LICENSE).

Maintenance

ActivityMaintained
ResponsivenessNo issues