From b2283b0c3f23563376e8672abc0e9f085c3f8e9b Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Tue, 14 Apr 2026 19:33:48 +0200 Subject: [PATCH] docs: document mcp server usage --- CHANGELOG.md | 21 +++++++++ README.md | 12 ++++++ docs/mcp.md | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 docs/mcp.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 611b702..4db8281 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,27 @@ follow semantic versioning: major for incompatible CLI or JSON contract changes, minor for new subcommands or additive flags, patch for fixes that preserve the contract. +## [Unreleased] + +### Added + +- `metin_release_mcp` package and `metin-release-mcp` console script — a + thin Model Context Protocol stdio server that wraps the Phase 1 + `release …` subcommands as eight MCP tools (`release_inspect`, + `release_build_manifest`, `release_sign`, `release_diff_remote`, + `release_upload_blobs`, `release_promote`, `release_verify_public`, + `release_publish`). Each tool's input schema mirrors the CLI's + argparse signature 1:1; the wrapper shells out to + `metin-release --json …` and returns the parsed envelope + verbatim with zero duplicated business logic. +- `[mcp]` optional dependency group pulling in the official `mcp` + Python SDK. +- `docs/mcp.md` — MCP server usage guide. +- `tests/mcp/` — 45 new tests covering the CLI runner (success path, + error-envelope passthrough, unparseable output, missing binary), + tool schemas (mirror checks against each command's argparse), and + dispatch translation (booleans, optionals, paths with spaces). + ## [0.1.0] - 2026-04-14 First Phase 1 drop. Implements the minimum asset release path the diff --git a/README.md b/README.md index 6563cc9..e40c6c9 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,15 @@ Add `--json` to get a machine-parseable envelope on stdout. Exit codes: | 4 | reserved (ERP sync, Phase 2+) | See `docs/cli.md` for the full command reference. + +## MCP server + +The `metin-release-mcp` console script (Phase 3) exposes each Phase 1 +subcommand as an MCP tool over stdio. Install with the `mcp` extra: + +``` +pip install -e .[mcp] +metin-release-mcp --help +``` + +See `docs/mcp.md` for tool list, client wiring, and error handling. diff --git a/docs/mcp.md b/docs/mcp.md new file mode 100644 index 0000000..193ff13 --- /dev/null +++ b/docs/mcp.md @@ -0,0 +1,120 @@ +# metin-release-mcp + +Thin [Model Context Protocol](https://modelcontextprotocol.io/) server that +wraps the Phase 1 `metin-release` CLI. Each `release …` subcommand is exposed +as an MCP tool. The server contains no release business logic: it shells out +to the real CLI with `--json` and returns the parsed envelope verbatim. + +## Install + +The server ships as an optional extra alongside the main CLI: + +``` +pip install -e '.[mcp]' +``` + +This installs the `mcp` Python SDK and adds a `metin-release-mcp` console +script plus a `python -m metin_release_mcp` module entry. + +## Running + +The server speaks MCP over stdio, so you wire it into an MCP-capable client +(Claude Desktop, Claude Code, etc.) as a stdio command. Example client entry: + +```json +{ + "mcpServers": { + "metin-release": { + "command": "metin-release-mcp", + "env": { + "METIN_RELEASE_BINARY": "/usr/local/bin/metin-release" + } + } + } +} +``` + +You can also poke at it directly: + +- `metin-release-mcp --help` — list registered tools +- `metin-release-mcp --list-tools` — dump the full tool JSON schemas +- `metin-release-mcp --version` + +## Tools + +| Tool | CLI subcommand | +|---|---| +| `release_inspect` | `metin-release release inspect` | +| `release_build_manifest` | `metin-release release build-manifest` | +| `release_sign` | `metin-release release sign` | +| `release_diff_remote` | `metin-release release diff-remote` | +| `release_upload_blobs` | `metin-release release upload-blobs` | +| `release_promote` | `metin-release release promote` | +| `release_verify_public` | `metin-release release verify-public` | +| `release_publish` | `metin-release release publish` | + +Tool input keys match CLI flag names with `_` instead of `-` +(`--base-url` → `base_url`, `--dry-run` → `dry_run`). Boolean fields +correspond to argparse `store_true` flags: pass `true` to set the flag, +omit or pass `false` to leave it off. + +### Example invocation + +```json +{ + "name": "release_inspect", + "arguments": { + "source": "/srv/metin/client" + } +} +``` + +The server runs `metin-release release inspect --json --source /srv/metin/client` +and returns the full JSON envelope: + +```json +{ + "ok": true, + "command": "release inspect", + "status": "inspected", + "stats": { + "source_path": "/srv/metin/client", + "file_count": 9166, + "total_bytes": 3523473920, + "launcher_present": true, + "main_exe_present": true + } +} +``` + +## CLI resolution + +On every tool call the server resolves the `metin-release` binary in this order: + +1. `METIN_RELEASE_BINARY` environment variable, if set and non-empty +2. `shutil.which("metin-release")` against `PATH` + +If neither resolves, the tool call returns a wrapper-level error envelope +(`error.code = "cli_not_found"`). + +## Error handling + +The server never invents its own release-level errors. There are three paths: + +- **Success** — CLI exits 0 with a valid JSON envelope → envelope returned as-is +- **CLI-level failure** — CLI exits non-zero with an `{"ok": false, "error": …}` + envelope → that envelope is returned as-is, plus the CLI's stderr is + attached as a diagnostic text block +- **Wrapper failure** — binary missing, unparseable stdout, unknown tool, + invalid input → synthetic envelope with one of + `cli_not_found`, `cli_unparseable_output`, `unknown_tool`, + `invalid_tool_input` + +## Environment variables + +| Variable | Purpose | +|---|---| +| `METIN_RELEASE_BINARY` | Override the `metin-release` binary path. | + +Any env vars the wrapped CLI honours (`METIN_RELEASE_MAKE_MANIFEST`, +`METIN_RELEASE_SIGN_MANIFEST`) are inherited by the subprocess unchanged.