# Writing MCP Servers for Navi

This manual describes how to create, test, register, and maintain MCP servers that extend Navi's capabilities. Read this **before** you start building a new server.

## 1. Philosophy: Why MCP instead of user tools?

MCP servers run in isolated processes and communicate via the Model Context Protocol. They **cannot** crash Navi's core, they can be reloaded without restarting the server, and they scale to complex external integrations (APIs, databases, browsers, etc.). The trade-off is slightly more boilerplate than a single `tools/foo.py` file.

**Rule:** Every new capability that is not trivial (more than a simple datetime or notes lookup) should be built as an MCP server.

## 2. Directory structure

MCP servers live under:

```
mcp-servers/<server_name>/
├── pyproject.toml
├── README.md
└── app/
    ├── __init__.py
    └── mcp_server.py
```

- `<server_name>` — snake_case or kebab-case. Must match the key you will use in `mcp_servers.d/<name>.json`.
- `pyproject.toml` — Python package metadata and dependencies.
- `app/mcp_server.py` — the actual server code (FastMCP).

## 3. Creating a new server from the template

Use the built-in tool `create_mcp_server` (preferred) or copy the template manually:

```bash
cp -r mcp-servers/_template mcp-servers/my_server
cd mcp-servers/my_server
# Edit pyproject.toml: change name, description, add dependencies
# Edit app/mcp_server.py: add your tools and instructions
```

### 3.1 pyproject.toml

Minimal required fields:

```toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "mcp-server-myserver"
version = "0.1.0"
description = "What this server does"
requires-python = ">=3.11"
dependencies = [
    "mcp>=1.27",
    "pydantic>=2.0",
    # add your own: httpx, asyncpg, playwright, etc.
]

[project.scripts]
mcp-server-myserver = "app.mcp_server:main"

[tool.setuptools.packages.find]
where = ["."]
include = ["app*"]
```

### 3.2 app/mcp_server.py

Read the template at `mcp-servers/_template/app/mcp_server.py` first. It contains a working hello-world server with extensive inline comments.

Key sections you must edit:

1. **`INSTRUCTIONS`** — These are injected into Navi's system prompt. Describe:
   - What this server does and when to use it.
   - Recommended workflow (order of tool calls).
   - ABSOLUTE RULE about never bypassing these tools with filesystem/terminal.

2. **`mcp = FastMCP("name", instructions=INSTRUCTIONS)`** — The name should match the directory key.

3. **Tool functions** — Each tool:
   - Is an `async def`.
   - Uses `@mcp.tool(name="tool_name")`.
   - Parameters use `Annotated[T, Field(description="...")]` — never plain types.
   - Returns a plain `str` (JSON string for structured data is fine).
   - Raises on real errors.
   - Validates required params explicitly.

Example:

```python
@mcp.tool(name="search_docs")
async def search_docs_tool(
    query: Annotated[str, Field(description="Search query string.")],
    limit: Annotated[int, Field(description="Max results.")] = 10,
) -> str:
    """Search the documentation index."""
    if not query.strip():
        raise ValueError("query is required and cannot be empty.")
    results = await _do_search(query, limit)
    return json.dumps(results, ensure_ascii=False, indent=2)
```

## 4. Environment and installation

After creating files, you **must**:

1. Create a virtual environment:
   ```bash
   python -m venv .venv
   source .venv/bin/activate
   pip install -e .
   ```

2. Verify the server starts without crashing:
   ```bash
   timeout 5 python -m app.mcp_server || true
   ```
   If it prints a traceback, fix the code before proceeding.

## 5. Registering the server in Navi

Create a file `mcp_servers.d/<name>.json` in the project root. The filename (without `.json`) becomes the server name. Example for a server named `my_server`:

```json
{
  "transport": "stdio",
  "command": "/absolute/path/to/mcp-servers/my_server/.venv/bin/python",
  "args": ["-m", "app.mcp_server"],
  "cwd": "/absolute/path/to/mcp-servers/my_server",
  "env": {
    "MCP_TRANSPORT": "stdio"
  },
  "groups": {
    "default": ["search_docs", "read_doc"]
  },
  "instructions": "Optional extra instructions merged with the server's own INSTRUCTIONS."
}
```

**Critical fields:**
- `command` — absolute path to the venv's Python binary.
- `cwd` — absolute path to the server directory.
- The filename must be `<name>.json` (e.g. `my_server.json`).
- `args` — usually `["-m", "app.mcp_server"]`.
- `groups` — organize tools into named groups so profiles can reference them cleanly.

After editing `mcp_servers.d/<name>.json`, call `reload_tools` to connect the server and register its tools.

## 6. Testing

### 6.1 Check connection

Call `mcp_status`. You should see your server as `connected` with the correct tool count.

### 6.2 Test each tool

Call `test_mcp_tool` for every tool your server exposes:

```
test_mcp_tool(server_name="my_server", tool_name="search_docs", arguments={"query": "hello", "limit": 3})
```

If any tool fails, read the error output, fix the code in `app/mcp_server.py`, and repeat.

### 6.3 Manual stderr inspection

If `mcp_status` shows `disconnected` but the code looks correct, inspect stderr manually:

```bash
cd mcp-servers/my_server
.venv/bin/python -m app.mcp_server 2>&1 | head -n 20
```

## 7. Updating an MCP server

1. Edit the code in `mcp-servers/<name>/app/mcp_server.py`.
2. (Optional) If you added new dependencies, edit `pyproject.toml` and run `pip install -e .` inside the venv.
3. Call `reload_tools` to reconnect the server and re-register tools.
4. Call `test_mcp_tool` to verify.

## 8. Deleting an MCP server

1. Remove the server directory or move it to a backup location.
2. Remove the entry from `mcp_servers.d/<name>.json`.
3. Call `reload_tools`.

## 9. Connecting an external MCP server

If the server was written by someone else:

1. Clone or place the server code on disk.
2. Create its venv and install dependencies.
3. Read its README to learn tool names and required environment variables.
4. Add an entry to `mcp_servers.d/<name>.json` with the correct `command`, `cwd`, `args`, and `env`.
5. Define `groups` mapping the tools into logical sets.
6. Call `reload_tools`.
7. Call `test_mcp_tool` for a representative tool.

## 10. Common mistakes and debugging

| Symptom | Cause | Fix |
|---|---|---|
| `mcp_status` shows `disconnected` | Wrong `command` or `cwd` path | Double-check absolute paths |
| Traceback on startup | Syntax error or missing import | Run `python -m py_compile app/mcp_server.py` |
| `test_mcp_tool` returns `is_error=True` | Tool raised an exception | Fix the tool logic; check parameter validation |
| Tool schema missing descriptions | Used plain types instead of `Annotated[..., Field(...)]` | Add `Field(description=...)` to every parameter |
| Navi never calls the server | Profile does not map the server in `mcp_servers` | Edit the profile's `config.json` and add the server groups |
| Navi bypasses MCP with filesystem | `INSTRUCTIONS` missing ABSOLUTE RULE | Add explicit rule in server INSTRUCTIONS |

## 11. Workflow checklist for Navi

When asked to create a new MCP server:

1. Read this manual (`manuals/write_mcp_server.md`).
2. Read the template (`mcp-servers/_template/app/mcp_server.py`).
3. Call `create_mcp_server(name=..., description=...)` to scaffold the directory.
4. Edit `app/mcp_server.py` iteratively using `filesystem`.
5. Validate syntax: `code_exec` or `terminal` with `python -m py_compile ...`.
6. Test startup: `terminal` with `timeout 5 python -m app.mcp_server`.
7. Edit `mcp_servers.d/<name>.json` via `filesystem` to register the server.
8. Call `reload_tools`.
9. Call `test_mcp_tool` for every tool.
10. Write good `INSTRUCTIONS` inside `mcp_server.py`.
11. Report results to the user.
