forgejo-projects-mcp 0.1.0rc1__tar.gz

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.
@@ -0,0 +1,20 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Tooling caches
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+
13
+ # Virtual environments
14
+ .venv
15
+
16
+ # Credentials
17
+ .env
18
+
19
+ # MkDocs build output
20
+ site/
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org>
@@ -0,0 +1,331 @@
1
+ Metadata-Version: 2.5
2
+ Name: forgejo-projects-mcp
3
+ Version: 0.1.0rc1
4
+ Summary: MCP server for managing Forgejo Projects/Kanban boards
5
+ Author-email: UnwantedForeignCloudProvider <donot@m3ss4ge.me>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.14
8
+ Requires-Dist: mcp[cli]>=2.1.1
9
+ Requires-Dist: playwright>=1.62.0
10
+ Requires-Dist: python-dotenv>=1.0.0
11
+ Description-Content-Type: text/markdown
12
+
13
+ # forgejo-projects-mcp
14
+
15
+ An MCP server that lets an AI agent manage **Forgejo Projects / Kanban boards**,
16
+ which Forgejo does **not** expose over its REST API.
17
+
18
+ It works by driving the same internal web routes the browser uses, authenticated
19
+ with a session cookie. HTTP is done through Playwright's `APIRequestContext`, so
20
+ **no browser binary is downloaded** — only the `playwright` Python package is
21
+ needed. See `forgejo-projects-automation-reference.md` for the reverse-engineered
22
+ endpoints this is built on.
23
+
24
+ ## ⚠️ This is a janky backend — do not rely on it for production
25
+
26
+ Forgejo exposes **no API** for Projects/Kanban, so this tool resorts to
27
+ **browser-style automation**: it logs in with a username and password, keeps a
28
+ session cookie, and calls Forgejo's **undocumented, unversioned internal web
29
+ routes** — scraping HTML to recover ids and board state. That is a fragile
30
+ approach by nature:
31
+
32
+ - These routes are **not a public contract**. A Forgejo upgrade (even a minor one)
33
+ can change markup or routes and silently break tools here.
34
+ - State is recovered by **HTML scraping and regex**, not a structured API, so
35
+ parsing can drift.
36
+ - It authenticates as a **real user with a password**, not a scoped API token,
37
+ and performs writes with no transactional guarantees.
38
+ - It was verified against **one instance (v15.0.7)** only.
39
+
40
+ Treat it as a **best-effort convenience / stop-gap for personal or experimental
41
+ use**. Do **not** put it on a critical path, run it against data you can't afford
42
+ to lose, or depend on it for production workflows. If/when Forgejo ships a real
43
+ Projects API, migrate to it. Use at your own risk; test against a throwaway repo
44
+ first.
45
+
46
+ ## Prerequisites
47
+
48
+ - **[uv](https://docs.astral.sh/uv/)** — used to install and run the tool.
49
+ Install it from the official guide:
50
+ <https://docs.astral.sh/uv/getting-started/installation/>.
51
+
52
+ ## Installation
53
+
54
+ All methods install a `forgejo-projects-mcp` executable onto your PATH (in uv's
55
+ tool bin directory). If uv warns that the directory isn't on your PATH, run
56
+ `uv tool update-shell` once and restart your shell. Verify with
57
+ `forgejo-projects-mcp --help` (or `uv tool list`).
58
+
59
+ No `playwright install` step is needed — the tool uses Playwright's HTTP layer,
60
+ not a real browser.
61
+
62
+ ### Latest release (PyPI)
63
+
64
+ ```bash
65
+ uv tool install forgejo-projects-mcp
66
+ uv tool upgrade forgejo-projects-mcp # update later
67
+ ```
68
+
69
+ ### Beta testing (latest from source)
70
+
71
+ Installs the current `main` branch straight from GitHub — newer than the last
72
+ release, and not guaranteed stable:
73
+
74
+ ```bash
75
+ uv tool install git+https://github.com/UnwantedForeignCloudProvider/ForgejoProjectsMCP
76
+ uv tool upgrade forgejo-projects-mcp # re-pull the latest main
77
+ ```
78
+
79
+ ### Local build (from a clone)
80
+
81
+ For development, or to install a specific checkout:
82
+
83
+ ```bash
84
+ git clone https://github.com/UnwantedForeignCloudProvider/ForgejoProjectsMCP
85
+ cd ForgejoProjectsMCP
86
+ uv tool install .
87
+ ```
88
+
89
+ To pick up local code changes automatically, install with
90
+ `uv tool install --editable .`; to update after pulling changes,
91
+ `uv tool install . --force`. To remove any of the above:
92
+ `uv tool uninstall forgejo-projects-mcp`.
93
+
94
+ ## Configuration
95
+
96
+ Credentials come from environment variables:
97
+
98
+ | Variable | Example |
99
+ |---|---|
100
+ | `FORGEJO_URL` | `https://forge.example.com` |
101
+ | `FORGEJO_USERNAME` | `your-username` |
102
+ | `FORGEJO_PASSWORD` | `your-password` |
103
+
104
+ A `.env` file in the working directory is **loaded automatically** (via
105
+ python-dotenv) — copy `.env.example` to `.env` and fill it in; no `source`/
106
+ `export` needed. Real environment variables already set (and an MCP client's own
107
+ `env` block) take precedence. See `.env.example` for the full list, including the
108
+ optional `FORGEJO_MCP_MAX_CONCURRENCY`, `FORGEJO_MCP_RPS`, and
109
+ `FORGEJO_MCP_LOG_LEVEL`.
110
+
111
+ The authenticated session is cached at
112
+ `<config>/forgejo_projects_mcp/storage_state.json` and refreshed automatically
113
+ when it expires. `<config>` is `$XDG_CONFIG_HOME` if set, otherwise `~/.config`
114
+ — resolved in an OS-agnostic way (Linux, macOS, Windows) via `Path.home()`.
115
+
116
+ ## Run
117
+
118
+ ```bash
119
+ export FORGEJO_URL=... FORGEJO_USERNAME=... FORGEJO_PASSWORD=...
120
+ uv run forgejo-projects-mcp # stdio MCP server
121
+ ```
122
+
123
+ ### Register with an MCP client
124
+
125
+ Once installed, reference the command directly:
126
+
127
+ ```json
128
+ {
129
+ "mcpServers": {
130
+ "forgejo-projects-mcp": {
131
+ "command": "forgejo-projects-mcp",
132
+ "env": {
133
+ "FORGEJO_URL": "https://forge.example.com",
134
+ "FORGEJO_USERNAME": "your-username",
135
+ "FORGEJO_PASSWORD": "your-password"
136
+ }
137
+ }
138
+ }
139
+ }
140
+ ```
141
+
142
+ If your MCP client doesn't inherit your shell PATH, use the absolute path to the
143
+ executable instead (find it with `which forgejo-projects-mcp`, or
144
+ `where forgejo-projects-mcp` on Windows).
145
+
146
+ ## Agent installation
147
+
148
+ After `uv tool install .`, the `forgejo-projects-mcp` stdio command is on your
149
+ PATH. Register it with your agent below (replace the credential values). If the
150
+ command isn't found, use its absolute path (`which forgejo-projects-mcp`).
151
+
152
+ <details>
153
+ <summary><b>Claude Code</b></summary>
154
+
155
+ ```bash
156
+ claude mcp add forgejo-projects-mcp \
157
+ -e FORGEJO_URL=https://forge.example.com \
158
+ -e FORGEJO_USERNAME=your-username \
159
+ -e FORGEJO_PASSWORD=your-password \
160
+ -- forgejo-projects-mcp
161
+ ```
162
+
163
+ </details>
164
+
165
+ <details>
166
+ <summary><b>Codex</b></summary>
167
+
168
+ ```bash
169
+ codex mcp add forgejo-projects-mcp \
170
+ --env FORGEJO_URL=https://forge.example.com \
171
+ --env FORGEJO_USERNAME=your-username \
172
+ --env FORGEJO_PASSWORD=your-password \
173
+ -- forgejo-projects-mcp
174
+ ```
175
+
176
+ </details>
177
+
178
+ <details>
179
+ <summary><b>Qwen Code</b></summary>
180
+
181
+ ```bash
182
+ qwen mcp add forgejo-projects-mcp \
183
+ -e FORGEJO_URL=https://forge.example.com \
184
+ -e FORGEJO_USERNAME=your-username \
185
+ -e FORGEJO_PASSWORD=your-password \
186
+ forgejo-projects-mcp
187
+ ```
188
+
189
+ </details>
190
+
191
+ <details>
192
+ <summary><b>OpenClaw</b></summary>
193
+
194
+ ```bash
195
+ openclaw mcp add forgejo-projects-mcp \
196
+ --command forgejo-projects-mcp \
197
+ --env FORGEJO_URL=https://forge.example.com \
198
+ --env FORGEJO_USERNAME=your-username \
199
+ --env FORGEJO_PASSWORD=your-password
200
+ ```
201
+
202
+ </details>
203
+
204
+ <details>
205
+ <summary><b>opencode</b></summary>
206
+
207
+ opencode's `opencode mcp add` is an interactive wizard (no inline env flags), so
208
+ add it to `opencode.json` instead:
209
+
210
+ ```json
211
+ {
212
+ "mcp": {
213
+ "forgejo-projects-mcp": {
214
+ "type": "local",
215
+ "command": ["forgejo-projects-mcp"],
216
+ "environment": {
217
+ "FORGEJO_URL": "https://forge.example.com",
218
+ "FORGEJO_USERNAME": "your-username",
219
+ "FORGEJO_PASSWORD": "your-password"
220
+ }
221
+ }
222
+ }
223
+ }
224
+ ```
225
+
226
+ </details>
227
+
228
+ <details>
229
+ <summary><b>Hermes</b></summary>
230
+
231
+ Hermes is config-file based — add to `~/.hermes/config.yaml`:
232
+
233
+ ```yaml
234
+ mcp_servers:
235
+ forgejo-projects-mcp:
236
+ command: forgejo-projects-mcp
237
+ env:
238
+ FORGEJO_URL: https://forge.example.com
239
+ FORGEJO_USERNAME: your-username
240
+ FORGEJO_PASSWORD: your-password
241
+ ```
242
+
243
+ </details>
244
+
245
+ ## Tools
246
+
247
+ **Session & discovery**
248
+ - `forgejo_status` — check authentication
249
+ - `authenticate(force=False)` — log in / refresh session
250
+ - `list_repositories(query, limit, page)` — repos the user can access (pick one to work in)
251
+
252
+ **Projects**
253
+ - `list_projects(owner, repo, state)`
254
+ - `create_project(owner, repo, title, description, card_type)`
255
+ - `get_project(owner, repo, project_id)` — board with columns + cards
256
+ - `update_project(...)`, `close_project(...)`, `reopen_project(...)`, `delete_project(...)`
257
+
258
+ **Columns**
259
+ - `create_column`, `edit_column`, `delete_column`, `set_default_column`
260
+
261
+ **Cards / issues**
262
+ - `create_issue(... project_id=)` — create an issue, optionally straight onto a board
263
+ - `add_issues_to_project`, `remove_issues_from_project`
264
+ - `move_card(owner, repo, project_id, column_id, issue_numbers)`
265
+ - `bulk_move_cards(owner, repo, project_id, moves)` — move many cards, each to its
266
+ own column, in one call (`moves` = list of `{issue_number, column_id}`)
267
+ - `delete_issue`
268
+
269
+ **Bulk reads** (run concurrently, rate-limited)
270
+ - `bulk_read_issues(owner, repo, issue_numbers, state="all")` — lightweight
271
+ summaries (number, title, state, milestone)
272
+ - `read_card(owner, repo, number)` — one card's full content (body + comments) ⚠️
273
+ - `read_column(owner, repo, project_id, column_id, state="all", milestone=None)` ⚠️
274
+ - `read_milestone(owner, repo, milestone_id, state="all", project=None)` ⚠️
275
+ - `read_project(owner, repo, project_id, state="all", milestone=None)` ⚠️
276
+
277
+ Optional filters on the readers use direct values (no name lookup): `state`
278
+ (`open`/`closed`/`all`), and a `milestone`/`project` **id** (each tool omits the
279
+ filter that is already its own subject).
280
+
281
+ The full readers take `limit`/`offset` to cap and page results, and return
282
+ `total` / `returned` / `truncated` / `error_count` so cost and completeness are
283
+ explicit. `bulk_read_issues` returns `count` (successful only) plus a separate
284
+ `errors` list.
285
+
286
+ ⚠️ = network- and token-expensive; use only when needed. Concurrency and request
287
+ rate are tunable via `FORGEJO_MCP_MAX_CONCURRENCY` (default 8) and
288
+ `FORGEJO_MCP_RPS` (default 5).
289
+
290
+ **Error signaling.** Tool failures are returned as MCP errors (`isError: true`)
291
+ with a `[CODE] message` (e.g. `[NOT_FOUND]`, `[INVALID_STATE]`,
292
+ `[MILESTONE_NOT_FOUND]`, `[NETWORK_ERROR]`) — agents can detect failure without
293
+ parsing content. Invalid `state` values and missing projects/columns/milestones/
294
+ issues are hard errors, not silent empty results. Individual issues that fail to
295
+ read inside a bulk call are reported inline instead (partial success).
296
+
297
+ **Milestones**
298
+ - `list_milestones`, `create_milestone`, `edit_milestone`,
299
+ `close_milestone`, `reopen_milestone`, `delete_milestone`
300
+
301
+ Issue arguments use the **repo issue number** (what you see as `#N`); the server
302
+ resolves the internal id automatically.
303
+
304
+ ## CLI (no MCP client needed)
305
+
306
+ For harnesses that can't speak MCP, `forgejo-projects-cli` exposes **every tool
307
+ as a subcommand**, generated from the same tool definitions and dispatched
308
+ in-process — so it stays in sync automatically. It reads the same
309
+ `FORGEJO_URL` / `FORGEJO_USERNAME` / `FORGEJO_PASSWORD` env vars, prints the JSON
310
+ result to stdout, logs to stderr, and exits non-zero on an error result.
311
+
312
+ ```bash
313
+ forgejo-projects-cli --help # lists every tool
314
+ forgejo-projects-cli <tool> --help # options for one tool
315
+
316
+ forgejo-projects-cli list_repositories --query kanban
317
+ forgejo-projects-cli create_project --owner o --repo r --title "Q3"
318
+ forgejo-projects-cli read_project --owner o --repo r --project_id 3 --state open
319
+ forgejo-projects-cli bulk_move_cards --owner o --repo r --project_id 3 \
320
+ --moves '[{"issue_number": 5, "column_id": 12}]'
321
+ ```
322
+
323
+ Options mirror each tool's parameters (`--owner`, `--repo`, …); list/object
324
+ parameters (`--issue_numbers`, `--moves`) take a JSON string.
325
+
326
+ ## Notes
327
+
328
+ - Tested against Forgejo **v15.0.7**. The web routes are internal and unversioned,
329
+ so a major Forgejo upgrade may require adjusting `client.py`.
330
+ - The Forgejo session cookie does **not** authorize `/api/v1`, so everything runs
331
+ through the web routes.
@@ -0,0 +1,319 @@
1
+ # forgejo-projects-mcp
2
+
3
+ An MCP server that lets an AI agent manage **Forgejo Projects / Kanban boards**,
4
+ which Forgejo does **not** expose over its REST API.
5
+
6
+ It works by driving the same internal web routes the browser uses, authenticated
7
+ with a session cookie. HTTP is done through Playwright's `APIRequestContext`, so
8
+ **no browser binary is downloaded** — only the `playwright` Python package is
9
+ needed. See `forgejo-projects-automation-reference.md` for the reverse-engineered
10
+ endpoints this is built on.
11
+
12
+ ## ⚠️ This is a janky backend — do not rely on it for production
13
+
14
+ Forgejo exposes **no API** for Projects/Kanban, so this tool resorts to
15
+ **browser-style automation**: it logs in with a username and password, keeps a
16
+ session cookie, and calls Forgejo's **undocumented, unversioned internal web
17
+ routes** — scraping HTML to recover ids and board state. That is a fragile
18
+ approach by nature:
19
+
20
+ - These routes are **not a public contract**. A Forgejo upgrade (even a minor one)
21
+ can change markup or routes and silently break tools here.
22
+ - State is recovered by **HTML scraping and regex**, not a structured API, so
23
+ parsing can drift.
24
+ - It authenticates as a **real user with a password**, not a scoped API token,
25
+ and performs writes with no transactional guarantees.
26
+ - It was verified against **one instance (v15.0.7)** only.
27
+
28
+ Treat it as a **best-effort convenience / stop-gap for personal or experimental
29
+ use**. Do **not** put it on a critical path, run it against data you can't afford
30
+ to lose, or depend on it for production workflows. If/when Forgejo ships a real
31
+ Projects API, migrate to it. Use at your own risk; test against a throwaway repo
32
+ first.
33
+
34
+ ## Prerequisites
35
+
36
+ - **[uv](https://docs.astral.sh/uv/)** — used to install and run the tool.
37
+ Install it from the official guide:
38
+ <https://docs.astral.sh/uv/getting-started/installation/>.
39
+
40
+ ## Installation
41
+
42
+ All methods install a `forgejo-projects-mcp` executable onto your PATH (in uv's
43
+ tool bin directory). If uv warns that the directory isn't on your PATH, run
44
+ `uv tool update-shell` once and restart your shell. Verify with
45
+ `forgejo-projects-mcp --help` (or `uv tool list`).
46
+
47
+ No `playwright install` step is needed — the tool uses Playwright's HTTP layer,
48
+ not a real browser.
49
+
50
+ ### Latest release (PyPI)
51
+
52
+ ```bash
53
+ uv tool install forgejo-projects-mcp
54
+ uv tool upgrade forgejo-projects-mcp # update later
55
+ ```
56
+
57
+ ### Beta testing (latest from source)
58
+
59
+ Installs the current `main` branch straight from GitHub — newer than the last
60
+ release, and not guaranteed stable:
61
+
62
+ ```bash
63
+ uv tool install git+https://github.com/UnwantedForeignCloudProvider/ForgejoProjectsMCP
64
+ uv tool upgrade forgejo-projects-mcp # re-pull the latest main
65
+ ```
66
+
67
+ ### Local build (from a clone)
68
+
69
+ For development, or to install a specific checkout:
70
+
71
+ ```bash
72
+ git clone https://github.com/UnwantedForeignCloudProvider/ForgejoProjectsMCP
73
+ cd ForgejoProjectsMCP
74
+ uv tool install .
75
+ ```
76
+
77
+ To pick up local code changes automatically, install with
78
+ `uv tool install --editable .`; to update after pulling changes,
79
+ `uv tool install . --force`. To remove any of the above:
80
+ `uv tool uninstall forgejo-projects-mcp`.
81
+
82
+ ## Configuration
83
+
84
+ Credentials come from environment variables:
85
+
86
+ | Variable | Example |
87
+ |---|---|
88
+ | `FORGEJO_URL` | `https://forge.example.com` |
89
+ | `FORGEJO_USERNAME` | `your-username` |
90
+ | `FORGEJO_PASSWORD` | `your-password` |
91
+
92
+ A `.env` file in the working directory is **loaded automatically** (via
93
+ python-dotenv) — copy `.env.example` to `.env` and fill it in; no `source`/
94
+ `export` needed. Real environment variables already set (and an MCP client's own
95
+ `env` block) take precedence. See `.env.example` for the full list, including the
96
+ optional `FORGEJO_MCP_MAX_CONCURRENCY`, `FORGEJO_MCP_RPS`, and
97
+ `FORGEJO_MCP_LOG_LEVEL`.
98
+
99
+ The authenticated session is cached at
100
+ `<config>/forgejo_projects_mcp/storage_state.json` and refreshed automatically
101
+ when it expires. `<config>` is `$XDG_CONFIG_HOME` if set, otherwise `~/.config`
102
+ — resolved in an OS-agnostic way (Linux, macOS, Windows) via `Path.home()`.
103
+
104
+ ## Run
105
+
106
+ ```bash
107
+ export FORGEJO_URL=... FORGEJO_USERNAME=... FORGEJO_PASSWORD=...
108
+ uv run forgejo-projects-mcp # stdio MCP server
109
+ ```
110
+
111
+ ### Register with an MCP client
112
+
113
+ Once installed, reference the command directly:
114
+
115
+ ```json
116
+ {
117
+ "mcpServers": {
118
+ "forgejo-projects-mcp": {
119
+ "command": "forgejo-projects-mcp",
120
+ "env": {
121
+ "FORGEJO_URL": "https://forge.example.com",
122
+ "FORGEJO_USERNAME": "your-username",
123
+ "FORGEJO_PASSWORD": "your-password"
124
+ }
125
+ }
126
+ }
127
+ }
128
+ ```
129
+
130
+ If your MCP client doesn't inherit your shell PATH, use the absolute path to the
131
+ executable instead (find it with `which forgejo-projects-mcp`, or
132
+ `where forgejo-projects-mcp` on Windows).
133
+
134
+ ## Agent installation
135
+
136
+ After `uv tool install .`, the `forgejo-projects-mcp` stdio command is on your
137
+ PATH. Register it with your agent below (replace the credential values). If the
138
+ command isn't found, use its absolute path (`which forgejo-projects-mcp`).
139
+
140
+ <details>
141
+ <summary><b>Claude Code</b></summary>
142
+
143
+ ```bash
144
+ claude mcp add forgejo-projects-mcp \
145
+ -e FORGEJO_URL=https://forge.example.com \
146
+ -e FORGEJO_USERNAME=your-username \
147
+ -e FORGEJO_PASSWORD=your-password \
148
+ -- forgejo-projects-mcp
149
+ ```
150
+
151
+ </details>
152
+
153
+ <details>
154
+ <summary><b>Codex</b></summary>
155
+
156
+ ```bash
157
+ codex mcp add forgejo-projects-mcp \
158
+ --env FORGEJO_URL=https://forge.example.com \
159
+ --env FORGEJO_USERNAME=your-username \
160
+ --env FORGEJO_PASSWORD=your-password \
161
+ -- forgejo-projects-mcp
162
+ ```
163
+
164
+ </details>
165
+
166
+ <details>
167
+ <summary><b>Qwen Code</b></summary>
168
+
169
+ ```bash
170
+ qwen mcp add forgejo-projects-mcp \
171
+ -e FORGEJO_URL=https://forge.example.com \
172
+ -e FORGEJO_USERNAME=your-username \
173
+ -e FORGEJO_PASSWORD=your-password \
174
+ forgejo-projects-mcp
175
+ ```
176
+
177
+ </details>
178
+
179
+ <details>
180
+ <summary><b>OpenClaw</b></summary>
181
+
182
+ ```bash
183
+ openclaw mcp add forgejo-projects-mcp \
184
+ --command forgejo-projects-mcp \
185
+ --env FORGEJO_URL=https://forge.example.com \
186
+ --env FORGEJO_USERNAME=your-username \
187
+ --env FORGEJO_PASSWORD=your-password
188
+ ```
189
+
190
+ </details>
191
+
192
+ <details>
193
+ <summary><b>opencode</b></summary>
194
+
195
+ opencode's `opencode mcp add` is an interactive wizard (no inline env flags), so
196
+ add it to `opencode.json` instead:
197
+
198
+ ```json
199
+ {
200
+ "mcp": {
201
+ "forgejo-projects-mcp": {
202
+ "type": "local",
203
+ "command": ["forgejo-projects-mcp"],
204
+ "environment": {
205
+ "FORGEJO_URL": "https://forge.example.com",
206
+ "FORGEJO_USERNAME": "your-username",
207
+ "FORGEJO_PASSWORD": "your-password"
208
+ }
209
+ }
210
+ }
211
+ }
212
+ ```
213
+
214
+ </details>
215
+
216
+ <details>
217
+ <summary><b>Hermes</b></summary>
218
+
219
+ Hermes is config-file based — add to `~/.hermes/config.yaml`:
220
+
221
+ ```yaml
222
+ mcp_servers:
223
+ forgejo-projects-mcp:
224
+ command: forgejo-projects-mcp
225
+ env:
226
+ FORGEJO_URL: https://forge.example.com
227
+ FORGEJO_USERNAME: your-username
228
+ FORGEJO_PASSWORD: your-password
229
+ ```
230
+
231
+ </details>
232
+
233
+ ## Tools
234
+
235
+ **Session & discovery**
236
+ - `forgejo_status` — check authentication
237
+ - `authenticate(force=False)` — log in / refresh session
238
+ - `list_repositories(query, limit, page)` — repos the user can access (pick one to work in)
239
+
240
+ **Projects**
241
+ - `list_projects(owner, repo, state)`
242
+ - `create_project(owner, repo, title, description, card_type)`
243
+ - `get_project(owner, repo, project_id)` — board with columns + cards
244
+ - `update_project(...)`, `close_project(...)`, `reopen_project(...)`, `delete_project(...)`
245
+
246
+ **Columns**
247
+ - `create_column`, `edit_column`, `delete_column`, `set_default_column`
248
+
249
+ **Cards / issues**
250
+ - `create_issue(... project_id=)` — create an issue, optionally straight onto a board
251
+ - `add_issues_to_project`, `remove_issues_from_project`
252
+ - `move_card(owner, repo, project_id, column_id, issue_numbers)`
253
+ - `bulk_move_cards(owner, repo, project_id, moves)` — move many cards, each to its
254
+ own column, in one call (`moves` = list of `{issue_number, column_id}`)
255
+ - `delete_issue`
256
+
257
+ **Bulk reads** (run concurrently, rate-limited)
258
+ - `bulk_read_issues(owner, repo, issue_numbers, state="all")` — lightweight
259
+ summaries (number, title, state, milestone)
260
+ - `read_card(owner, repo, number)` — one card's full content (body + comments) ⚠️
261
+ - `read_column(owner, repo, project_id, column_id, state="all", milestone=None)` ⚠️
262
+ - `read_milestone(owner, repo, milestone_id, state="all", project=None)` ⚠️
263
+ - `read_project(owner, repo, project_id, state="all", milestone=None)` ⚠️
264
+
265
+ Optional filters on the readers use direct values (no name lookup): `state`
266
+ (`open`/`closed`/`all`), and a `milestone`/`project` **id** (each tool omits the
267
+ filter that is already its own subject).
268
+
269
+ The full readers take `limit`/`offset` to cap and page results, and return
270
+ `total` / `returned` / `truncated` / `error_count` so cost and completeness are
271
+ explicit. `bulk_read_issues` returns `count` (successful only) plus a separate
272
+ `errors` list.
273
+
274
+ ⚠️ = network- and token-expensive; use only when needed. Concurrency and request
275
+ rate are tunable via `FORGEJO_MCP_MAX_CONCURRENCY` (default 8) and
276
+ `FORGEJO_MCP_RPS` (default 5).
277
+
278
+ **Error signaling.** Tool failures are returned as MCP errors (`isError: true`)
279
+ with a `[CODE] message` (e.g. `[NOT_FOUND]`, `[INVALID_STATE]`,
280
+ `[MILESTONE_NOT_FOUND]`, `[NETWORK_ERROR]`) — agents can detect failure without
281
+ parsing content. Invalid `state` values and missing projects/columns/milestones/
282
+ issues are hard errors, not silent empty results. Individual issues that fail to
283
+ read inside a bulk call are reported inline instead (partial success).
284
+
285
+ **Milestones**
286
+ - `list_milestones`, `create_milestone`, `edit_milestone`,
287
+ `close_milestone`, `reopen_milestone`, `delete_milestone`
288
+
289
+ Issue arguments use the **repo issue number** (what you see as `#N`); the server
290
+ resolves the internal id automatically.
291
+
292
+ ## CLI (no MCP client needed)
293
+
294
+ For harnesses that can't speak MCP, `forgejo-projects-cli` exposes **every tool
295
+ as a subcommand**, generated from the same tool definitions and dispatched
296
+ in-process — so it stays in sync automatically. It reads the same
297
+ `FORGEJO_URL` / `FORGEJO_USERNAME` / `FORGEJO_PASSWORD` env vars, prints the JSON
298
+ result to stdout, logs to stderr, and exits non-zero on an error result.
299
+
300
+ ```bash
301
+ forgejo-projects-cli --help # lists every tool
302
+ forgejo-projects-cli <tool> --help # options for one tool
303
+
304
+ forgejo-projects-cli list_repositories --query kanban
305
+ forgejo-projects-cli create_project --owner o --repo r --title "Q3"
306
+ forgejo-projects-cli read_project --owner o --repo r --project_id 3 --state open
307
+ forgejo-projects-cli bulk_move_cards --owner o --repo r --project_id 3 \
308
+ --moves '[{"issue_number": 5, "column_id": 12}]'
309
+ ```
310
+
311
+ Options mirror each tool's parameters (`--owner`, `--repo`, …); list/object
312
+ parameters (`--issue_numbers`, `--moves`) take a JSON string.
313
+
314
+ ## Notes
315
+
316
+ - Tested against Forgejo **v15.0.7**. The web routes are internal and unversioned,
317
+ so a major Forgejo upgrade may require adjusting `client.py`.
318
+ - The Forgejo session cookie does **not** authorize `/api/v1`, so everything runs
319
+ through the web routes.