# homelab-mcp-gitea A **Model Context Protocol (MCP) server** that exposes a self-hosted **Gitea** instance as a set of tools, so any MCP-aware client (Claude Desktop, Hermes Agent, mcp-cli, the MCP Inspector) can list repos, fetch files, open issues, list PRs, and merge PRs through natural language — without hand-rolling `curl` invocations every time. This is the **first** package of a planned `homelab-mcp-*` family: `homelab-mcp-gitea` → `homelab-mcp-portainer` → `homelab-mcp-nas` … each one a thin MCP wrapper over an existing self-hosted service. ## What is MCP, in one paragraph MCP (Model Context Protocol) is the open standard (Anthropic, late 2024) that lets an LLM client talk to "tool servers" the same way regardless of which host it runs on. A server exposes: - **tools** (named functions with typed arguments and return values) - **resources** (named blobs the client can read) - **prompts** (templated prompts the client can render) …over **stdio** or **HTTP+SSE**, using **JSON-RPC 2.0** as the wire format. The protocol is deliberately minimal so any language can implement it; the Python and TypeScript SDKs are the most mature. In this project, each `GiteaClient` method becomes an MCP `tool` decorated with `@mcp.tool()`. The server runs over stdio (the default and simplest transport); the client launches it as a subprocess and exchanges JSON-RPC messages on stdin/stdout. ## Tools exposed | Tool | What it does | |-----------------|---------------------------------------------------------| | `list_repos` | List repos visible to the authenticated user | | `get_repo` | Get one repo's metadata | | `get_file` | Read a file's text content (base64-decoded) | | `create_issue` | Open a new issue on a repo | | `list_open_prs` | List open pull requests for a repo | | `merge_pr` | Merge a PR by index (with the Gitea `do` field trap fix)| The `merge_pr` tool is non-trivial on purpose: Gitea's merge endpoint is the famous `[Do]: Required` footgun (see the `gitea` skill, pitfall #13). The client sends `{"do": "merge"}` (lowercase, two chars) and treats a non-empty response body at HTTP 200 as failure. The tests pin both halves of this contract so it can't regress. ## Setup ```bash cd ~/homelab-mcp-gitea uv sync --all-groups cp .env.example .env # Edit .env: set GITEA_URL and GITEA_TOKEN uv run pytest # 14 tests, no network required uv run ruff check src tests uv run mypy src ``` ## Running the server ```bash # Foreground stdio transport (the default — what MCP clients expect) uv run homelab-mcp-gitea ``` The server reads `GITEA_URL` and `GITEA_TOKEN` from the environment on every tool call (no global state), so the same server process can be reused across requests without leaking credentials between calls. ## Connecting from a client ### Claude Desktop (`~/.config/claude_desktop_config.json` on Linux) ```json { "mcpServers": { "homelab-gitea": { "command": "uv", "args": ["--directory", "/home/caleidos/homelab-mcp-gitea", "run", "homelab-mcp-gitea"], "env": { "GITEA_URL": "https://gitlab.impresion3d.pro", "GITEA_TOKEN": "" } } } } ``` Restart Claude Desktop. The `homelab-gitea` server appears with its 6 tools under the "tools" menu. ### MCP Inspector (browser-based debugger) ```bash npx @modelcontextprotocol/inspector uv --directory ~/homelab-mcp-gitea run homelab-mcp-gitea ``` Opens a web UI where you can call each tool by hand and see the raw JSON-RPC. ## Architecture ``` src/homelab_mcp_gitea/ client.py # httpx-based Gitea REST client, no MCP knowledge server.py # FastMCP server; thin wrapper that maps tools -> client calls tests/ test_client.py # respx-mocked unit tests for every HTTP call test_server.py # tool registration + routing smoke tests ``` The split is deliberate: `client.py` knows nothing about MCP, so it can be reused from a plain Python REPL or a CLI, and `server.py` is small enough to read in one sitting (~120 lines including docstrings). ## What's NOT here (and why) - **No FastAPI / Celery / Alembic / Docker**. Those belong to the `python-project-template-internal` stack, which targets deployable web apps. An MCP server is a long-lived subprocess, not an HTTP service. - **No CI-to-NAS deploy**. Local-only for v0.1; we can add Gitea Actions + SSH deploy when there's a reason to keep the server running unattended. - **No auth scopes, no rate limiting, no audit log**. The Gitea token scopes are enforced server-side; this MCP just forwards. ## Next steps in the `homelab-mcp-*` family - `homelab-mcp-portainer`: list containers, restart, tail logs, redeploy stack. Requires the Portainer token + endpoint ID. - `homelab-mcp-nas`: SSH-based filesystem + Container Station ops. The trickier one (askpass / key auth, see `qnap-nas` skill pitfall J).