This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/docs/mcp-setup.md

196 lines
6.6 KiB
Markdown
Raw Normal View History

# MCP Bridge Setup — Qwen3 via Ollama
This document describes how the MCP (Model Context Protocol) bridge connects
Qwen3 models running in Ollama to Timmy's tool ecosystem.
## Architecture
```
User Prompt
┌──────────────┐ /api/chat ┌──────────────────┐
│ MCPBridge │ ──────────────────▶ │ Ollama (Qwen3) │
│ (Python) │ ◀────────────────── │ tool_calls JSON │
└──────┬───────┘ └──────────────────┘
│ Execute tool calls
┌──────────────────────────────────────────────┐
│ MCP Tool Handlers │
├──────────────┬───────────────┬───────────────┤
│ Gitea API │ Shell Exec │ Custom Tools │
│ (httpx) │ (ShellHand) │ (pluggable) │
└──────────────┴───────────────┴───────────────┘
```
## Bridge Options Evaluated
| Option | Verdict | Reason |
|--------|---------|--------|
| **Direct Ollama /api/chat** | **Selected** | Zero extra deps, native Qwen3 tool support, full control |
| qwen-agent MCP | Rejected | Adds heavy dependency (qwen-agent), overlaps with Agno |
| ollmcp | Rejected | External Go binary, limited error handling |
| mcphost | Rejected | Generic host, doesn't integrate with existing tool safety |
| ollama-mcp-bridge | Rejected | Purpose-built but unmaintained, Node.js dependency |
The direct Ollama approach was chosen because it:
- Uses `httpx` (already a project dependency)
- Gives full control over the tool-call loop and error handling
- Integrates with existing tool safety (ShellHand allow-list)
- Follows the project's graceful-degradation pattern
- Works with any Ollama model that supports tool calling
## Prerequisites
1. **Ollama** running locally (default: `http://localhost:11434`)
2. **Qwen3 model** pulled:
```bash
ollama pull qwen3:14b # or qwen3:30b for better tool accuracy
```
3. **Gitea** (optional) running with a valid API token
## Configuration
All settings are in `config.py` via environment variables or `.env`:
| Setting | Default | Description |
|---------|---------|-------------|
| `OLLAMA_URL` | `http://localhost:11434` | Ollama API endpoint |
| `OLLAMA_MODEL` | `qwen3:30b` | Default model for tool calling |
| `OLLAMA_NUM_CTX` | `4096` | Context window cap |
| `MCP_BRIDGE_TIMEOUT` | `60` | HTTP timeout for bridge calls (seconds) |
| `GITEA_URL` | `http://localhost:3000` | Gitea instance URL |
| `GITEA_TOKEN` | (empty) | Gitea API token |
| `GITEA_REPO` | `rockachopa/Timmy-time-dashboard` | Target repository |
## Usage
### Basic usage
```python
from timmy.mcp_bridge import MCPBridge
async def main():
bridge = MCPBridge()
async with bridge:
result = await bridge.run("List open issues in the repo")
print(result.content)
print(f"Tool calls: {len(result.tool_calls_made)}")
print(f"Latency: {result.latency_ms:.0f}ms")
```
### With custom tools
```python
from timmy.mcp_bridge import MCPBridge, MCPToolDef
async def my_handler(**kwargs):
return f"Processed: {kwargs}"
custom_tool = MCPToolDef(
name="my_tool",
description="Does something custom",
parameters={
"type": "object",
"properties": {
"input": {"type": "string", "description": "Input data"},
},
"required": ["input"],
},
handler=my_handler,
)
bridge = MCPBridge(extra_tools=[custom_tool])
```
### Selective tool loading
```python
# Gitea tools only (no shell)
bridge = MCPBridge(include_shell=False)
# Shell only (no Gitea)
bridge = MCPBridge(include_gitea=False)
# Custom model
bridge = MCPBridge(model="qwen3:14b")
```
## Available Tools
### Gitea Tools (enabled when `GITEA_TOKEN` is set)
| Tool | Description |
|------|-------------|
| `list_issues` | List issues by state (open/closed/all) |
| `create_issue` | Create a new issue with title and body |
| `read_issue` | Read details of a specific issue by number |
### Shell Tool (enabled by default)
| Tool | Description |
|------|-------------|
| `shell_exec` | Execute sandboxed shell commands (allow-list enforced) |
The shell tool uses the project's `ShellHand` with its allow-list of safe
commands (make, pytest, git, ls, cat, grep, etc.). Dangerous commands are
blocked.
## How Tool Calling Works
1. User prompt is sent to Ollama with tool definitions
2. Qwen3 generates a response — either text or `tool_calls` JSON
3. If tool calls are present, the bridge executes each one
4. Tool results are appended to the message history as `role: "tool"`
5. The updated history is sent back to the model
6. Steps 2-5 repeat until the model produces a final text response
7. Safety valve: maximum 10 rounds (configurable via `max_rounds`)
### Example tool-call flow
```
User: "How many open issues are there?"
Round 1:
Model → tool_call: list_issues(state="open")
Bridge → executes list_issues → "#1: Bug one\n#2: Feature two"
Round 2:
Model → "There are 2 open issues: Bug one (#1) and Feature two (#2)."
Bridge → returns BridgeResult(content="There are 2 open issues...")
```
## Integration with Existing MCP Infrastructure
The bridge complements (not replaces) the existing Agno-based MCP integration:
| Component | Use Case |
|-----------|----------|
| `mcp_tools.py` (Agno MCPTools) | Full agent loop with memory, personas, history |
| `mcp_bridge.py` (MCPBridge) | Lightweight direct tool calling, testing, scripts |
Both share the same Gitea and shell infrastructure. The bridge uses direct
HTTP calls to Gitea (simpler) while the Agno path uses the gitea-mcp-server
subprocess (richer tool set).
## Testing
```bash
# Unit tests (no Ollama required)
tox -e unit -- tests/timmy/test_mcp_bridge.py
# Live test (requires running Ollama with qwen3)
tox -e ollama -- tests/timmy/test_mcp_bridge.py
```
## Troubleshooting
| Problem | Solution |
|---------|----------|
| "Ollama connection failed" | Ensure `ollama serve` is running |
| "Model not found" | Run `ollama pull qwen3:14b` |
| Tool calls return errors | Check tool allow-list in ShellHand |
| "max tool-call rounds reached" | Model is looping — simplify the prompt |
| Gitea tools return empty | Check `GITEA_TOKEN` and `GITEA_URL` |