feat: dual-mode — MCP server and CLI skill in one file

Expose the same 66 Gitea tools two ways from server/gitea_mcp.py:
- MCP server (no subcommand -> mcp.run()), as before
- CLI: `<tool> --flags` prints one JSON object {success, data/error}

A new @tool decorator registers each function in both FastMCP and a TOOLS
registry; a CLI dispatcher builds argparse from each function's signature and
wraps results in the repo's JSON+success contract. Adds `list-tools` for
discovery. No tool logic duplicated.

- Rewrite skills/gitea/SKILL.md for CLI-first usage (MCP kept as an option)
- Sync version to 0.3.0 across plugin.json, pyproject.toml, SKILL.md
- Add tests/unit/test_cli.py (dispatch routing, flag typing, errors, main)
- Document dual-mode in README.md and CLAUDE.md
- Ignore coverage artifacts

Tests: 119 passed, 93% coverage.
This commit is contained in:
2026-06-24 10:44:46 +03:00
parent a6162a855c
commit 9a5750ddca
9 changed files with 543 additions and 130 deletions
+21 -5
View File
@@ -2,8 +2,19 @@
## Project overview
MCP server exposing Gitea REST API as 66 tools. Single source file: `server/gitea_mcp.py`.
Config in `config.json` (gitignored). Run via `uv run server/gitea_mcp.py`.
Gitea REST API exposed as 66 tools from a single source file: `server/gitea_mcp.py`.
Config in `config.json` (gitignored).
**Dual-mode — one file, two entrypoints over the same tool functions:**
- **MCP server:** no subcommand → `mcp.run()`. Launched by `.mcp.json`
(`uv run server/gitea_mcp.py --config config.json`). Tools = `mcp__gitea__<tool>`.
- **CLI skill:** `uv run server/gitea_mcp.py <tool> --flags` → one JSON object on
stdout (`{"success", "data"|"error"}`). Powers the `skills/gitea` Cline skill.
The split lives at the bottom of the file: every tool is registered in the `TOOLS`
dict by the `@tool` decorator, and `dispatch()` builds an argparse parser from each
function's signature. `main()` routes to CLI or server. No tool logic is duplicated.
## Commit style
@@ -12,20 +23,25 @@ Config in `config.json` (gitignored). Run via `uv run server/gitea_mcp.py`.
## Adding tools
Each tool is a `@mcp.tool()` function. Pattern:
Each tool is a `@tool` function (registers it for **both** the MCP server and the
CLI). Pattern:
```python
@mcp.tool()
@tool
def my_tool(owner: str, repo: str, instance: str = "") -> Any:
"""One-line docstring — becomes the tool description shown to the LLM."""
return GET(instance, f"/repos/{owner}/{repo}/something")
```
Rules:
- Use `@tool`, not `@mcp.tool()` — the former also registers the CLI subcommand.
- `instance: str = ""` is always the last parameter
- Use `_strip({...})` to drop `None` values before sending
- URL-encode path segments that may contain slashes via `_b(value)`
- Update the tool count in `README.md` after adding tools
- Keep type hints accurate the CLI builds its argparse flags from them
(`int`→typed, `bool``--flag`/`--no-flag`, `list[str]`→space-separated values).
- Update the tool count in `README.md`, the smoke test, and `test_cli.py` after
adding or removing tools.
---