Files
homelab-mcp 0212913831 feat: initial homelab-mcp-gitea v0.1
MCP server exposing self-hosted Gitea (gitlab.impresion3d.pro) via stdio
JSON-RPC 2.0. Six tools: list_repos, get_repo, get_file, create_issue,
list_open_prs, merge_pr. The merge_pr tool pins the Gitea 'do'-field trap
(see skill gitea pitfall #13) — sends lowercase 'do', treats HTTP 200
with non-empty body as failure.

Stack: FastMCP + httpx + pydantic + uv, with respx-mocked pytest suite
(15 tests, 0.69s) and ruff + mypy strict green.

Verification: scripts/verify_stdio.py boots the server as a subprocess,
exchanges real MCP messages over stdio, and calls list_repos against the
live Gitea instance — all green.
2026-07-22 12:47:59 +00:00

4.9 KiB

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-giteahomelab-mcp-portainerhomelab-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

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

# 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)

{
  "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": "<your-token>"
      }
    }
  }
}

Restart Claude Desktop. The homelab-gitea server appears with its 6 tools under the "tools" menu.

MCP Inspector (browser-based debugger)

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).