@amonstack/gitea-mcp 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,15 +18,17 @@ The server communicates over stdio and wraps the [Gitea REST API
18
18
 
19
19
  ## Installation
20
20
 
21
- ### From GitHub Packages
21
+ ### From npm
22
22
 
23
23
  ```bash
24
- npm set @amonstack:registry https://npm.pkg.github.com
25
- npm set //npm.pkg.github.com/:_authToken YOUR_GITHUB_PAT
26
24
  npm install -g @amonstack/gitea-mcp
27
25
  ```
28
26
 
29
- Your GitHub PAT needs the `read:packages` scope.
27
+ Or run directly without global install:
28
+
29
+ ```bash
30
+ npx @amonstack/gitea-mcp
31
+ ```
30
32
 
31
33
  ### Build from source
32
34
 
@@ -95,6 +97,18 @@ Add to your opencode MCP configuration:
95
97
 
96
98
  If you built from source, use `node /path/to/gitea-mcp/dist/cli.js` instead.
97
99
 
100
+ opencode also loads native **skills** — one per action (find, create, update,
101
+ label, summarize, plan milestones, resolve repo) — that teach the assistant the
102
+ safest workflow for that action, including pre-use checks and pitfalls. Install
103
+ them once:
104
+
105
+ ```bash
106
+ gitea-mcp skills install # global (~/.config/opencode/skills/)
107
+ gitea-mcp skills install --project # this project (./.opencode/skills/)
108
+ ```
109
+
110
+ Then restart opencode. See [AI Guidance & Skills](#ai-guidance--skills) below.
111
+
98
112
  ### Other MCP Clients
99
113
 
100
114
  Any client that supports stdio-based MCP servers can use `gitea-mcp`. After
@@ -158,6 +172,42 @@ gitea-mcp
158
172
  | `list_my_repos` | List repositories accessible to the authenticated user |
159
173
  | `resolve_repo` | Auto-detect `owner` and `repo` from a local git remote URL |
160
174
 
175
+ ## AI Guidance & Skills
176
+
177
+ The server ships guidance so assistants use the tools correctly and safely,
178
+ through three channels:
179
+
180
+ - **`instructions` (on connect)** — a concise strategy the server sends during the
181
+ MCP handshake; capable clients inject it into the system prompt automatically.
182
+ - **Tool descriptions** — every tool's description flags its key risk (pagination,
183
+ label ID-vs-name, destructive scope) and a minimal usage example.
184
+ - **Prompts & resources** — workflow templates (`triage_issues`,
185
+ `summarize_issue`, `audit_labels`, `milestone_report`) and on-demand reference
186
+ docs (field reference, label guide, tool cookbook) for clients that surface them.
187
+
188
+ ### opencode skills
189
+
190
+ For opencode, the server ships a set of **action-scoped skills** — one per
191
+ workflow, so the assistant loads only the guidance it needs (and never, say,
192
+ delete instructions while creating). Install them with the
193
+ `gitea-mcp skills install` command shown in the opencode section above.
194
+
195
+ | Skill | Invoke when |
196
+ |-------|-------------|
197
+ | `gitea-find-issues` | discovering / reading / triaging issues |
198
+ | `gitea-create-issue` | creating an issue (after a duplicate check) |
199
+ | `gitea-update-issue` | editing fields, closing, clearing assignee/milestone |
200
+ | `gitea-label-issue` | adding / replacing / removing / clearing labels on an issue |
201
+ | `gitea-manage-labels` | creating or editing label definitions |
202
+ | `gitea-summarize-issue` | reading and summarizing an issue's discussion |
203
+ | `gitea-plan-milestones` | creating / editing / closing milestones |
204
+ | `gitea-resolve-repo` | resolving owner/repo or listing repositories |
205
+
206
+ Each skill is a short, AI-facing action flow (purpose, when to use, when not to,
207
+ rules, and what to check first). Destructive single-tool actions (delete issue /
208
+ comment / label / milestone) are intentionally left to the tool descriptions so
209
+ they never contaminate a creative workflow.
210
+
161
211
  ## Development
162
212
 
163
213
  ```bash
@@ -175,15 +225,8 @@ npm ci
175
225
  | `make test-integration` | Run integration tests (needs live Gitea instance) |
176
226
  | `make dev` | Run directly with tsx |
177
227
 
178
- ### Project layout
179
-
180
- ```
181
- src/
182
- cli.ts # Entry point
183
- server.ts # MCP server setup and tool registration
184
- tools.ts # Zod input schemas for all tools
185
- gitea-client.ts # REST client wrapping Gitea /api/v1 endpoints
186
- ```
228
+ For the full architecture — module layout, dependency graph, core patterns, and
229
+ the guide to adding a new tool — see [`docs/architecture.md`](docs/architecture.md).
187
230
 
188
231
  ## License
189
232
 
package/README.zh-CN.md CHANGED
@@ -17,15 +17,17 @@ opencode、Cursor 等)后,AI 助手即可通过自然语言在 Gitea 实例
17
17
 
18
18
  ## 安装
19
19
 
20
- ### 从 GitHub Packages 安装
20
+ ### 从 npm 安装
21
21
 
22
22
  ```bash
23
- npm set @amonstack:registry https://npm.pkg.github.com
24
- npm set //npm.pkg.github.com/:_authToken <你的 GitHub PAT>
25
23
  npm install -g @amonstack/gitea-mcp
26
24
  ```
27
25
 
28
- GitHub PAT 需要 `read:packages` 权限。
26
+ 或无需全局安装,直接运行:
27
+
28
+ ```bash
29
+ npx @amonstack/gitea-mcp
30
+ ```
29
31
 
30
32
  ### 从源码构建
31
33
 
@@ -94,6 +96,17 @@ node dist/cli.js
94
96
 
95
97
  如果从源码构建,将 `command` 改为 `node /path/to/gitea-mcp/dist/cli.js`。
96
98
 
99
+ opencode 还会加载原生**技能(skills)**——按动作拆分(查找、创建、更新、打
100
+ 标签、总结、规划里程碑、解析仓库等),教会助手针对该动作的最安全工作流,含
101
+ 使用前检查与各类陷阱。一次性安装:
102
+
103
+ ```bash
104
+ gitea-mcp skills install # 全局(~/.config/opencode/skills/)
105
+ gitea-mcp skills install --project # 仅当前项目(./.opencode/skills/)
106
+ ```
107
+
108
+ 然后重启 opencode。详见下方 [AI 引导与技能](#ai-引导与技能)。
109
+
97
110
  ### 其他 MCP 客户端
98
111
 
99
112
  任何支持 stdio 方式运行 MCP 服务端的客户端都可以使用。安装完成后设置环境
@@ -157,6 +170,39 @@ gitea-mcp
157
170
  | `list_my_repos` | 列出当前用户可访问的仓库 |
158
171
  | `resolve_repo` | 从本地 git 仓库的远程地址自动检测 `owner` 和 `repo` |
159
172
 
173
+ ## AI 引导与技能
174
+
175
+ 服务端内置了引导内容,让助手正确、安全地使用工具,分三个通道:
176
+
177
+ - **`instructions`(连接时)** —— 服务端在 MCP 握手时下发一份精炼策略;支持的
178
+ 客户端会自动注入系统提示。
179
+ - **工具描述** —— 每个工具的描述都标出其关键风险(分页、标签 ID 与名称、破坏性
180
+ 作用范围)和最小用法示例。
181
+ - **Prompts 与 Resources** —— 工作流模板(`triage_issues`、`summarize_issue`、
182
+ `audit_labels`、`milestone_report`)与按需参考文档(字段参考、标签指南、工具
183
+ 食谱),供支持的客户端使用。
184
+
185
+ ### opencode 技能
186
+
187
+ 对 opencode,服务端内置了一组**按动作划分的技能**——每个工作流一个,助手只
188
+ 加载所需指引(避免例如创建时把删除说明一并带入造成幻觉)。用上面 opencode
189
+ 小节展示的 `gitea-mcp skills install` 命令安装。
190
+
191
+ | 技能 | 何时触发 |
192
+ |------|----------|
193
+ | `gitea-find-issues` | 发现 / 读取 / 分流 issues |
194
+ | `gitea-create-issue` | 创建 issue(先做查重) |
195
+ | `gitea-update-issue` | 编辑字段、关闭、清空负责人/里程碑 |
196
+ | `gitea-label-issue` | 为 issue 增加 / 替换 / 移除 / 清空标签 |
197
+ | `gitea-manage-labels` | 创建或编辑标签定义 |
198
+ | `gitea-summarize-issue` | 读取并总结某 issue 的讨论 |
199
+ | `gitea-plan-milestones` | 创建 / 编辑 / 关闭里程碑 |
200
+ | `gitea-resolve-repo` | 解析 owner/repo 或列出仓库 |
201
+
202
+ 每个技能都是面向 AI 的简短动作流程(目的、何时用、何时不用、规则、先检查什么)。
203
+ 破坏性的单工具操作(删除 issue / 评论 / 标签 / 里程碑)有意仅保留在工具描述里,
204
+ 不会污染创建类工作流。
205
+
160
206
  ## 二次开发
161
207
 
162
208
  ```bash
@@ -174,15 +220,8 @@ npm ci
174
220
  | `make test-integration` | 运行集成测试(需要可用的 Gitea 实例) |
175
221
  | `make dev` | 通过 tsx 直接运行 |
176
222
 
177
- ### 项目结构
178
-
179
- ```
180
- src/
181
- cli.ts # 入口文件
182
- server.ts # MCP 服务端搭建与工具注册
183
- tools.ts # 所有工具的 Zod 输入模式定义
184
- gitea-client.ts # 封装 Gitea /api/v1 端点的 REST 客户端
185
- ```
223
+ 完整的架构说明(模块布局、依赖关系、核心模式,以及新增工具的指引)请参阅
224
+ [`docs/architecture.md`](docs/architecture.md)。
186
225
 
187
226
  ## 许可证
188
227
 
@@ -0,0 +1,63 @@
1
+ # Gitea MCP — usage strategy
2
+
3
+ You manage Gitea issues, comments, labels, and milestones through this MCP server.
4
+ Every tool returns Gitea's JSON verbatim as text. Follow these rules to use them
5
+ correctly.
6
+
7
+ ## Resolve owner/repo FIRST (most common failure)
8
+
9
+ Most tools target one repository and need both `owner` and `repo`. They resolve in
10
+ this order:
11
+
12
+ 1. The `owner` / `repo` arguments you pass to the call.
13
+ 2. `GITEA_DEFAULT_OWNER` / `GITEA_DEFAULT_REPO` env vars (set at server start).
14
+ 3. The `resolve_repo` tool (detects owner/repo from a local git remote).
15
+
16
+ If neither argument nor env var is set, the call errors. Before a batch of work on a
17
+ repo you have not used this session, call `resolve_repo` (or confirm the env defaults)
18
+ once and reuse the result — do not guess.
19
+
20
+ ## Labels: IDs vs names (critical gotcha)
21
+
22
+ Label endpoints are NOT consistent — mixing them up silently fails or hits the wrong label:
23
+
24
+ - `add_issue_labels`, `replace_issue_labels` → take label **names** (`string[]`).
25
+ - `remove_issue_label` → takes a label **ID** (`number`). You must `list_labels` first to get IDs.
26
+ - `create_issue` / `update_issue` `labels` field → takes label **IDs** (`number[]`).
27
+
28
+ Always `list_labels` before any label mutation so you use the right identifier, and
29
+ prefer `replace_issue_labels` (name-based) when you want a known final set.
30
+
31
+ ## Comments use comment IDs, not issue numbers
32
+
33
+ `update_comment` and `delete_comment` take the comment `id`, NOT the issue `index`.
34
+ `list_comments` returns each comment's `id`. Never reuse an issue number where a
35
+ comment id is required.
36
+
37
+ ## Pagination
38
+
39
+ List endpoints are 1-based: `page` starts at 1, `limit` max 100. To fetch everything,
40
+ page forward until a page returns fewer than `limit` items (or an empty page). Do not
41
+ assume one page is complete.
42
+
43
+ ## Destructive operations — confirm before running
44
+
45
+ These are irreversible on most Gitea instances (no trash/recycle):
46
+
47
+ - `delete_issue`, `delete_label`, `delete_milestone`, `delete_comment`
48
+ - `clear_issue_labels`, `replace_issue_labels` (replaces the ENTIRE label set)
49
+
50
+ Confirm the target id/index and scope with the user before calling. For labels,
51
+ `replace_issue_labels` overwrites — read current labels first if any must survive.
52
+
53
+ ## Search vs list
54
+
55
+ - `list_issues` — one repo, paginated, filterable by state/labels.
56
+ - `search_issues` — across ALL repos the token can see, by keyword/type/state. Use it
57
+ for "find issues about X" or duplicate detection, not for listing one repo.
58
+
59
+ ## Error format
60
+
61
+ Failed calls throw `Gitea API error (<status>): <body>`. Read the status: 401/403 →
62
+ token scope/expiry; 404 → wrong owner/repo or no permission; 409 → conflict;
63
+ 422 → validation. Do not retry blindly on 4xx.
@@ -0,0 +1,48 @@
1
+ # Gitea object field reference
2
+
3
+ The shapes of the JSON objects these tools return (verbatim from the Gitea API).
4
+ Use this to read results correctly and pick the right identifier.
5
+
6
+ ## Issue
7
+ - `id` (number) — internal id. NOT used by tools; tools use `number`.
8
+ - `number` (number) — the issue index shown in the URL (#42). This is the `index`
9
+ passed to get_issue / update_issue / delete_issue / comments tools.
10
+ - `title` (string), `body` (string?), `state` ("open" | "closed")
11
+ - `html_url`, `url` (string)
12
+ - `comments` (number) — comment COUNT, not the comments themselves
13
+ - `labels` (Label[])
14
+ - `assignee` (User?), `assignees` (User[]?)
15
+ - `milestone` (Milestone?)
16
+ - `repository` (Repository) — present on search_issues results
17
+ - `created_at`, `updated_at` (string ISO); `closed_at` (string?, when closed)
18
+
19
+ ## Label
20
+ - `id` (number) — used by remove_issue_label / update_label / delete_label
21
+ - `name` (string) — used by add_issue_labels / replace_issue_labels
22
+ - `color` (string) — 6-digit hex, with or without "#"
23
+ - `description` (string?)
24
+
25
+ ## User
26
+ - `id` (number), `login` (string), `full_name` (string?), `avatar_url` (string),
27
+ `email` (string?)
28
+
29
+ ## Milestone
30
+ - `id` (number) — used by get/update/delete_milestone; also the `milestone` value on
31
+ create/update_issue
32
+ - `title` (string), `description` (string?), `state` ("open" | "closed")
33
+ - `open_issues` (number), `closed_issues` (number) — counts for progress
34
+ - `due_on` (string?, ISO)
35
+
36
+ ## Comment
37
+ - `id` (number) — used by update_comment / delete_comment. NOT the issue number.
38
+ - `body` (string) — Markdown
39
+ - `html_url` (string), `created_at`, `updated_at` (string ISO)
40
+ - `user` (User) — comment author
41
+
42
+ ## Repository (embedded on issues)
43
+ - `id` (number), `full_name` (string "owner/repo"), `name` (string),
44
+ `owner` ({ login: string })
45
+
46
+ ## Repo (from list_my_repos)
47
+ - `id`, `full_name`, `name`, `owner` (User), `description?`, `html_url`,
48
+ `default_branch?`, `created_at`, `updated_at`
@@ -0,0 +1,47 @@
1
+ # Label management guide
2
+
3
+ Labels are the most error-prone area because endpoints mix **names** and **ids**.
4
+
5
+ ## The name-vs-id matrix (memorize)
6
+
7
+ | Operation | Identifier | Type |
8
+ |---|---|---|
9
+ | `add_issue_labels` | name | `string[]` |
10
+ | `replace_issue_labels` | name | `string[]` |
11
+ | `remove_issue_label` | **id** | `number` |
12
+ | `create_issue` / `update_issue` `.labels` | **id** | `number[]` |
13
+ | `update_label` / `delete_label` | **id** | `number` |
14
+
15
+ **Workflow rule:** call `list_labels` first and keep the `id`↔`name` map for the
16
+ whole session. Translate before any call that needs the other form.
17
+
18
+ ## Conventions
19
+
20
+ - **Color:** 6-digit hex, with or without `#`. Keep a consistent palette per repo.
21
+ - **Names are unique** per repo. Rename with `update_label` (by id); never recreate
22
+ to rename.
23
+ - **Scoped/exclusive labels** use `scope/name` (e.g. `priority/low`, `priority/high`).
24
+ Within a scope, labels are mutually exclusive — adding `priority/high` removes
25
+ `priority/low`. Use this intentionally for single-value dimensions (priority,
26
+ type, status); use plain labels for multi-value dimensions (topic, team).
27
+
28
+ ## Safe operations
29
+
30
+ - **Add one label without disturbing others** → `add_issue_labels(["name"])`
31
+ (additive, by name).
32
+ - **Remove one label** → `remove_issue_label(id)` (by id; get it from the issue's
33
+ `labels`).
34
+ - **Set an exact known set** → `replace_issue_labels(["a","b"])` — but read the
35
+ current labels first and confirm, since it removes everything not listed.
36
+ - **Bulk cleanup** → `delete_label(id)` removes the label from EVERY issue; only
37
+ after user confirmation.
38
+
39
+ ## Anti-patterns
40
+
41
+ - Passing a label **name** to `remove_issue_label` (needs id) — hits the wrong id
42
+ or 404s.
43
+ - Passing a label **id** to `add_issue_labels` (needs name) — 404s.
44
+ - Using `update_issue({ labels })` to "add" a label — it REPLACES the whole set.
45
+ Use `add_issue_labels` instead.
46
+ - Deleting a label to "remove it from one issue" — that removes it from all issues.
47
+ Use `remove_issue_label` for one issue.
@@ -0,0 +1,51 @@
1
+ # Tool cookbook — task → tool recipes
2
+
3
+ Quick recipes for common goals. Always resolve owner/repo first (explicit args,
4
+ `GITEA_DEFAULT_OWNER`/`REPO`, or `resolve_repo`).
5
+
6
+ ## Discover where to work
7
+ - One local repo → `resolve_repo({})` once, reuse `{owner, repo}`.
8
+ - Across all repos → `list_my_repos({ page: 1, limit: 20 })`, page as needed.
9
+
10
+ ## Read / report
11
+ - One issue's full picture → `get_issue` then `list_comments` (mind: list_comments
12
+ is one default page; long threads may be truncated).
13
+ - All open issues in a repo → `list_issues({ state: "open", page: 1, limit: 50 })`,
14
+ page until a page returns < 50.
15
+ - Issues across repos by keyword / duplicate check → `search_issues({ query, type:
16
+ "issues" })`.
17
+ - Milestone progress → `list_milestones({ state: "all" })` (default omits closed!).
18
+
19
+ ## Create
20
+ - New issue with labels → `list_labels` (to get ids) → `create_issue({ title, body,
21
+ labels: [ids] })`. Or create then `add_issue_labels([names])`.
22
+
23
+ ## Edit (non-destructive)
24
+ - Change title/body/assignee → `update_issue` (PATCH; only given fields change).
25
+ - Add ONE label → `add_issue_labels(["name"])` (additive, by name).
26
+ - Remove ONE label → `remove_issue_label(id)` (by id).
27
+
28
+ ## Destructive (confirm first)
29
+ - Close an issue → `update_issue({ state: "closed" })` (preferred over delete).
30
+ - Delete an issue → `delete_issue` (irreversible).
31
+ - Replace all labels → `replace_issue_labels(["a","b"])` (overwrites whole set).
32
+ - Clear all labels → `clear_issue_labels`.
33
+ - Delete a label → `delete_label(id)` (removes from EVERY issue).
34
+ - Delete a milestone → prefer `update_milestone({ state: "closed" })`; `delete_milestone`
35
+ detaches its issues (they keep existing, milestone becomes null).
36
+
37
+ ## Pagination pattern (all list tools)
38
+ ```
39
+ page = 1
40
+ loop:
41
+ res = list_X({ ..., page, limit })
42
+ process(res)
43
+ if res.length < limit: break
44
+ page += 1
45
+ ```
46
+
47
+ ## Error triage
48
+ - 401/403 → token scope/expiry. Ask user; don't loop.
49
+ - 404 → wrong owner/repo or no permission.
50
+ - 409 → conflict (e.g. duplicate label name).
51
+ - 422 → validation (bad color, malformed date).
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: gitea-create-issue
3
+ description: Invoke to CREATE / FILE / OPEN a new Gitea issue. The flow checks for an existing duplicate first, then creates. Do NOT invoke to edit/close (gitea-update-issue), delete (no skill — destructive), read/find (gitea-find-issues), or attach labels (gitea-label-issue).
4
+ ---
5
+
6
+ # gitea-create-issue
7
+
8
+ Open one new issue after ruling out a duplicate. Tools: `search_issues`, `create_issue`.
9
+
10
+ ## Prerequisites
11
+ - Resolve `owner`+`repo`: pass explicitly, else `GITEA_DEFAULT_OWNER`/`GITEA_DEFAULT_REPO`, else `resolve_repo` (gitea-resolve-repo). Never guess — wrong values create the issue in the wrong repo.
12
+
13
+ ## Flow
14
+ 1. DUPLICATE CHECK (always first): `search_issues({ query: "<title keywords>", type: "issues" })`. If a match exists, comment on it (gitea-summarize-issue, then `create_comment`) instead of creating a duplicate. Do NOT skip this — creation does not de-duplicate.
15
+ 2. CREATE: `create_issue({ title, body?, assignees? })`.
16
+
17
+ ## create_issue
18
+ - RULES: `title` required; `body` is Markdown. Use `assignees` (array); do NOT also set the deprecated single `assignee`.
19
+ - Do NOT pass `labels` or `milestone` here: `labels` expect IDs (error-prone) and milestone needs an id. Create WITHOUT them, then attach labels by name via `add_issue_labels` (gitea-label-issue) and set the milestone via `update_issue` (gitea-update-issue).
20
+ - CHECK FIRST: confirm no duplicate (step 1) and that `owner`/`repo` are correct before writing.
21
+ - CHECK AFTER: the returned object's `number` is the new issue index.
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: gitea-find-issues
3
+ description: Invoke to DISCOVER or READ Gitea issues — listing one repo's issues, cross-repo keyword search, reading a single issue, or triage. Do NOT invoke to create/edit/close/delete (gitea-create-issue / gitea-update-issue), to manage labels (gitea-label-issue), or to read the comment discussion (gitea-summarize-issue).
4
+ ---
5
+
6
+ # gitea-find-issues
7
+
8
+ Read-only issue discovery. Tools: `list_issues`, `search_issues`, `get_issue`.
9
+
10
+ ## Prerequisites
11
+ - Resolve `owner`+`repo` for `list_issues`/`get_issue`: pass explicitly, else `GITEA_DEFAULT_OWNER`/`GITEA_DEFAULT_REPO`, else `resolve_repo` (gitea-resolve-repo). Never guess — wrong values 404 or silently target the wrong repo.
12
+ - Paginate 1-based, `limit` ≤ 100. A page is final ONLY when it returns fewer than `limit` items. Always set `limit` (default page size is server-controlled).
13
+
14
+ ## Choose the tool
15
+ - ONE repo's issues, filtered by state/labels → `list_issues`.
16
+ - ACROSS repos by keyword, or duplicate check → `search_issues` (global; no owner/repo).
17
+ - ONE issue's full detail → `get_issue`.
18
+
19
+ ## list_issues
20
+ - RULES: `labels` = comma-separated NAMES, AND-matched; a mistyped or non-existent name returns EMPTY with no error. Results include pull requests (no `type` filter here); identify a PR by a non-null `pull_request` field in the JSON.
21
+ - CHECK FIRST: confirm label names via `list_labels` (gitea-manage-labels) before filtering.
22
+ - CHECK AFTER: if `length === limit`, fetch the next page before treating the list as complete.
23
+
24
+ ## search_issues
25
+ - RULES: GLOBAL — no owner/repo; each result carries its own `repository`. Returns issues AND PRs by default; set `type: "issues"` to exclude PRs (`type: "pulls"` for PRs only). `labels` = names. `query` matches title + body.
26
+ - CHECK FIRST: set `type` deliberately.
27
+ - CHECK AFTER: page fully if completeness matters.
28
+
29
+ ## get_issue
30
+ - RULES: pass `index` = the issue `number` (URL #42 → 42), never the internal `id`.
31
+ - NOTE: the `comments` field is a COUNT only — to read the discussion use `list_comments` (gitea-summarize-issue).
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: gitea-label-issue
3
+ description: Invoke to ATTACH labels to ONE Gitea issue — add, replace, remove one, or clear all. Covers the label name-vs-id pitfall. Do NOT invoke to create/edit/delete the repo's label definitions (gitea-manage-labels) or to create/edit the issue (gitea-create-issue / gitea-update-issue).
4
+ ---
5
+
6
+ # gitea-label-issue
7
+
8
+ Manage the labels ON one issue. Tools: `list_labels`, `add_issue_labels`, `replace_issue_labels`, `remove_issue_label`, `clear_issue_labels`.
9
+
10
+ ## Prerequisites
11
+ - Resolve `owner`+`repo`: pass explicitly, else `GITEA_DEFAULT_OWNER`/`GITEA_DEFAULT_REPO`, else `resolve_repo` (gitea-resolve-repo).
12
+
13
+ ## CRITICAL — name vs id (the endpoints are inconsistent)
14
+ - BY NAME (string[]): `add_issue_labels`, `replace_issue_labels`.
15
+ - BY ID (number): `remove_issue_label` (param `id`).
16
+ Resolve names→ids via `list_labels` when you need an id.
17
+
18
+ ## Choose the tool
19
+ - ADD labels (keep existing) → `add_issue_labels({ index, labels: [names] })`.
20
+ - SET the exact label set (overwrites all) → `replace_issue_labels({ index, labels: [names] })`.
21
+ - REMOVE ONE label → `remove_issue_label({ index, id })` (id, not name).
22
+ - REMOVE ALL → `clear_issue_labels({ index })`.
23
+
24
+ ## Rules
25
+ - `index` = issue `number` (URL #N → N).
26
+ - Adding a non-existent NAME → 404. Confirm names via `list_labels` first.
27
+ - Scoped/exclusive labels (`scope/name`) are mutually exclusive within a scope — adding one may replace another in the same scope.
28
+ - CHECK FIRST: for `replace`, `get_issue` (gitea-find-issues) to see current labels so you don't drop one unintentionally; for `remove`, `list_labels` to resolve the id.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: gitea-manage-labels
3
+ description: Invoke to CREATE or EDIT a Gitea label in the repo's taxonomy (the set of available label definitions). Do NOT invoke to attach labels to an issue (gitea-label-issue) or to delete a label (destructive — no skill).
4
+ ---
5
+
6
+ # gitea-manage-labels
7
+
8
+ Create or edit the repo's label definitions. Tools: `list_labels`, `create_label`, `update_label`.
9
+
10
+ ## Prerequisites
11
+ - Resolve `owner`+`repo`: pass explicitly, else `GITEA_DEFAULT_OWNER`/`GITEA_DEFAULT_REPO`, else `resolve_repo` (gitea-resolve-repo).
12
+
13
+ ## list_labels
14
+ - Enumerate available labels (name, id, color, description). Use to resolve names→ids and to check name uniqueness before create. Paginate 1-based, `limit` ≤ 100.
15
+
16
+ ## create_label
17
+ - RULES: `name` required and UNIQUE per repo (duplicate → conflict). `color` = 6-digit hex, with or without leading `#` (e.g. `#ff0000` or `ff0000`). `description` optional.
18
+ - CHECK FIRST: `list_labels` to avoid a name collision.
19
+
20
+ ## update_label
21
+ - RULES: identifies the label by `id` (number). PATCH — only `name` / `color` / `description` you pass change; omit = unchanged. `color` stays 6-digit hex.
22
+ - NOTE: renaming or recoloring a label updates it on EVERY issue that currently has it.
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: gitea-plan-milestones
3
+ description: Invoke to CREATE, EDIT, or CLOSE a Gitea milestone, or to list/inspect milestones for planning. Do NOT invoke to delete a milestone (destructive — no skill) or to set an issue's milestone (that is gitea-update-issue).
4
+ ---
5
+
6
+ # gitea-plan-milestones
7
+
8
+ Create / edit / close milestones. Tools: `list_milestones`, `get_milestone`, `create_milestone`, `update_milestone`.
9
+
10
+ ## Prerequisites
11
+ - Resolve `owner`+`repo`: pass explicitly, else `GITEA_DEFAULT_OWNER`/`GITEA_DEFAULT_REPO`, else `resolve_repo` (gitea-resolve-repo).
12
+
13
+ ## list_milestones — DEFAULT-OPEN TRAP
14
+ - Default returns ONLY open milestones. Pass `state: "all"` or `state: "closed"` to see the rest, or you will silently miss closed/completed milestones. Paginate 1-based, `limit` ≤ 100.
15
+
16
+ ## create_milestone
17
+ - RULES: `title` required; `description` optional; `due_on` = ISO 8601 (e.g. `2025-12-31T23:59:59Z`). Returns the new milestone including its `id`.
18
+
19
+ ## update_milestone
20
+ - RULES: identifies by `id` (number). PATCH — only `title` / `description` / `due_on` / `state` you pass change; omit = unchanged. State is `open` | `closed`.
21
+ - NOTE: CLOSING a milestone does NOT close its issues — they stay open. Close issues separately (gitea-update-issue) before or after.
22
+
23
+ ## get_milestone
24
+ - Read one milestone by `id`: includes `open_issues` / `closed_issues` counts for progress (progress = closed / (open + closed)).
@@ -0,0 +1,18 @@
1
+ ---
2
+ name: gitea-resolve-repo
3
+ description: Invoke to RESOLVE which owner/repo the other gitea tools should target (auto-detect from a git remote) or to LIST repositories the token can access. Run FIRST when the target repository is unknown. Do NOT invoke for issue/comment/label/milestone work once owner/repo are known.
4
+ ---
5
+
6
+ # gitea-resolve-repo
7
+
8
+ Discover the target repository. Tools: `resolve_repo`, `list_my_repos`.
9
+
10
+ ## resolve_repo
11
+ - Reads the `origin` remote of the git repo at `path` (default: current directory) and parses owner/repo from SSH (`git@gitea.example:owner/repo.git`) or HTTPS (`https://gitea.example/owner/repo.git`) URLs.
12
+ - Throws if there is no `origin` remote or the URL is unparseable. Do NOT guess owner/repo from context — call this, or ask the user.
13
+
14
+ ## list_my_repos
15
+ - Lists repositories the token can access. GLOBAL. Returns large objects — paginate (`limit` ≤ 100) and read only the fields you need (name, full_name, owner.login).
16
+
17
+ ## Resolution order (every repo-scoped tool)
18
+ explicit `owner`/`repo` args → `GITEA_DEFAULT_OWNER`/`GITEA_DEFAULT_REPO` → `resolve_repo`. If none resolve, the tool throws `owner and repo are required`. Wrong values 404 or silently target the wrong repo — never guess.
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: gitea-summarize-issue
3
+ description: Invoke to READ and SUMMARIZE an issue's discussion — the issue body plus its comment thread. Do NOT invoke to create/edit an issue (gitea-create-issue / gitea-update-issue), post or edit a comment (no skill — single tool), or manage labels/milestones.
4
+ ---
5
+
6
+ # gitea-summarize-issue
7
+
8
+ Read an issue and its discussion, then synthesize. Tools: `get_issue`, `list_comments`.
9
+
10
+ ## Prerequisites
11
+ - Resolve `owner`+`repo`: pass explicitly, else `GITEA_DEFAULT_OWNER`/`GITEA_DEFAULT_REPO`, else `resolve_repo` (gitea-resolve-repo).
12
+
13
+ ## Flow
14
+ 1. `get_issue({ index })` — `index` = issue `number` (URL #N → N), never the internal `id`. The `comments` field is only a COUNT.
15
+ 2. `list_comments({ index })` — returns the comment thread, oldest-first.
16
+
17
+ ## list_comments — TRUNCATION TRAP
18
+ - This tool passes NO page/limit and returns at most the server's default page size. For long threads the list is SILENTLY TRUNCATED. If the `comments` count (from `get_issue`) exceeds the number returned, warn that part of the thread is missing and do NOT claim a complete summary.
19
+
20
+ ## Synthesis
21
+ - Report the issue state (open/closed), the body's ask, then the thread's consensus / decision / open questions. Attribute by author. Flag unresolved disagreement explicitly — do not paper over it.
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: gitea-update-issue
3
+ description: Invoke to EDIT fields, CLOSE, REOPEN, or CLEAR assignee/milestone on ONE existing Gitea issue. Do NOT invoke to create (gitea-create-issue), delete (destructive — no skill), find/read (gitea-find-issues), or change a single label (gitea-label-issue).
4
+ ---
5
+
6
+ # gitea-update-issue
7
+
8
+ Edit one issue's fields or change its state. Tools: `get_issue`, `update_issue`.
9
+
10
+ ## Prerequisites
11
+ - Resolve `owner`+`repo`: pass explicitly, else `GITEA_DEFAULT_OWNER`/`GITEA_DEFAULT_REPO`, else `resolve_repo` (gitea-resolve-repo).
12
+ - There is NO optimistic locking — always read current state first.
13
+
14
+ ## Flow
15
+ 1. READ CURRENT: `get_issue({ index })` — `index` = issue `number` (URL #N → N), never the internal `id`.
16
+ 2. APPLY: `update_issue({ index, ...changedFields })`.
17
+
18
+ ## update_issue
19
+ - RULES: PATCH — only fields you pass change; omit = unchanged. To CLEAR a field pass its EMPTY form, do NOT omit it: `milestone: 0` clears the milestone, `assignees: []` clears assignees. Use `assignees` (array), not the deprecated `assignee`. State values are `open` | `closed`.
20
+ - NOT FOR single-label changes: `labels` here are IDs and REPLACE the entire set — to add/remove one label use `add_issue_labels` / `remove_issue_label` (gitea-label-issue).
21
+ - CHECK FIRST: `get_issue` for current values if state may have changed since your last read; state the intended change to the user before writing.
package/dist/cli.js CHANGED
@@ -1,15 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  import { runServer } from "./server.js";
3
- const baseUrl = process.env.GITEA_BASE_URL;
4
- const token = process.env.GITEA_TOKEN;
5
- const defaultOwner = process.env.GITEA_DEFAULT_OWNER;
6
- const defaultRepo = process.env.GITEA_DEFAULT_REPO;
7
- if (!baseUrl || !token) {
8
- console.error("GITEA_BASE_URL and GITEA_TOKEN environment variables are required");
9
- process.exit(1);
3
+ const argv = process.argv.slice(2);
4
+ if (argv[0] === "skills") {
5
+ // `gitea-mcp skills ...` manages the bundled opencode skill. It needs no Gitea
6
+ // credentials, so it is dispatched before the env-var guard below.
7
+ const { runSkillsCommand } = await import("./skills.js");
8
+ runSkillsCommand(argv.slice(1)).catch((err) => {
9
+ console.error("Fatal error:", err);
10
+ process.exit(1);
11
+ });
12
+ }
13
+ else {
14
+ const baseUrl = process.env.GITEA_BASE_URL;
15
+ const token = process.env.GITEA_TOKEN;
16
+ const defaultOwner = process.env.GITEA_DEFAULT_OWNER;
17
+ const defaultRepo = process.env.GITEA_DEFAULT_REPO;
18
+ if (!baseUrl || !token) {
19
+ console.error("GITEA_BASE_URL and GITEA_TOKEN environment variables are required");
20
+ process.exit(1);
21
+ }
22
+ runServer(baseUrl, token, defaultOwner, defaultRepo).catch((err) => {
23
+ console.error("Fatal error:", err);
24
+ process.exit(1);
25
+ });
10
26
  }
11
- runServer(baseUrl, token, defaultOwner, defaultRepo).catch((err) => {
12
- console.error("Fatal error:", err);
13
- process.exit(1);
14
- });
15
27
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;AAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;AACtC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;AACrD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;AAEnD,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;IACvB,OAAO,CAAC,KAAK,CAAC,mEAAmE,CAAC,CAAC;IACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACjE,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAEnC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;IACzB,+EAA+E;IAC/E,mEAAmE;IACnE,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IACzD,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QAC5C,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC;KAAM,CAAC;IACN,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IACtC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IACrD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAEnD,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,CAAC,mEAAmE,CAAC,CAAC;QACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACjE,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}