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:
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user