feat: dogfood the Image as this repo's Forgejo MCP server (#31) #34

Merged
piscis merged 1 commit from piscis/forgejo-mcp-server-dogfood into main 2026-09-30 18:49:49 +00:00
Owner

Closes #31

Summary

The repo's .mcp.json gets a forgejo server that runs the Image over stdio. Every session in this repo now uses the same config a Consumer copies. The change only adds lines, and context7 isn't touched.

 {
   "mcpServers": {
+    "forgejo": {
+      "type": "stdio",
+      "command": "docker",
+      "args": [
+        "run", "-i", "--rm", "--pull=always",
+        "-e", "FORGEJO_URL=${FORGEJO_URL:-https://code.vicoli.de}",
+        "-e", "FORGEJO_ACCESS_TOKEN",
+        "code.vicoli.de/vicoli-oss/forgejo-mcp:3"
+      ]
+    },
     "context7": {

The args follow the same order as the README's Claude Code snippet (run -i --rm, URL, token, image). There are two deliberate differences: --pull=always, and the FORGEJO_URL default. forgejo sits before context7, so context7 doesn't need a trailing comma and the diff contains only + lines.

Evidence

1. Valid JSON, context7 untouched

$ jq . .mcp.json >/dev/null && echo "jq: OK"
jq: OK
$ git diff origin/main -- .mcp.json | grep -c '^-[^-]'
0

2. Handshake with the token. This runs the exact configured command, with ${FORGEJO_URL:-…} expanded by hand and the token mapped with FORGEJO_ACCESS_TOKEN=$FORGEJO_TOKEN. The input is initialize → notifications/initialized → tools/list:

stdout: 2 lines, 0 non-JSON lines
{"id":1,"serverInfo":{"name":"Forgejo MCP Server","version":"3.2.0"},"protocolVersion":"2025-06-18"}
{"id":2,"toolCount":156,"first":["add_issue_dependency","add_issue_labels","add_issue_time",...]}
includes: create_issue, create_pull_request, get_file_content, get_issue_by_index, list_my_repos, list_repo_issues
stderr: "Connection verification successful … server_version 16.0.5+gitea-1.22.0" … "MCP server ready for stdio communication"

The same result through Claude Code itself: claude -p --mcp-config .mcp.json --strict-mcp-config, reading the init event, with FORGEJO_URL unset:

{"mcp_servers":[{"name":"forgejo","status":"connected"},{"name":"context7","status":"connected"}],"forgejoTools":156,"context7Tools":2}

That run also shows the ${FORGEJO_URL:-https://code.vicoli.de} default is applied. The Image run with an empty URL exits 1 (FATAL Missing required configuration {"missing": "url"}), so if the default weren't applied, forgejo would have failed. Overriding the URL is honoured too, and a failure stays inside forgejo:

FORGEJO_URL=https://bogus.invalid
{"mcp_servers":[{"name":"forgejo","status":"failed"},{"name":"context7","status":"connected"}],"forgejoTools":0,"context7Tools":2}

3. Without the token. Caveat: the server does not fail at startup:

FORGEJO_ACCESS_TOKEN and FORGEJO_URL unset
{"mcp_servers":[{"name":"forgejo","status":"connected"},{"name":"context7","status":"connected"}],"forgejoTools":156,"context7Tools":2}

tools/call get_my_user_info →
{"jsonrpc":"2.0","id":3,"error":{"code":-32603,"message":"get user info err: token is required"}}

Upstream's startup check (Connection verification) doesn't need authentication, so a tokenless server comes up with token_configured: false, and the failure shows up on each tool call instead. That failure stays inside forgejo, and context7 is unaffected, which is what the criterion needs. But it isn't the "refuses to start" behaviour the issue text implies. (claude mcp list didn't help here: in a fresh worktree both servers only show ⏸ Pending approval, so I used --mcp-config.)

4. No secret in the committed file

$ grep -cF "$FORGEJO_TOKEN" .mcp.json
0
$ grep -nEi 'token|key|secret|password' .mcp.json
9:        "-e", "FORGEJO_ACCESS_TOKEN",
17:        "CONTEXT7_API_KEY": "${CONTEXT7_API_KEY}"
$ grep -nE '[0-9a-f]{32,}' .mcp.json   # no hits

Both hits are variable names, not values.

README follow-ups (not changed here)

  • The README snippet has no --pull=always, so a Consumer on :3 stays on whatever they pulled first and never follows the major line. The dogfood config reads better here, and the README probably should add it.
  • claude mcp list / claude mcp get display the arg as FORGEJO_URL=${FORGEJO_URL} and drop the :-default. That's only how they print it, since the default is applied at spawn (see 2), but a reader could find it confusing.

Merge Danger

Door: two-way

Reverting one commit removes the server.

Blast Radius: contributors

Only sessions opened in this repo are affected. The first time, they're asked to approve forgejo. After that, each session pulls the Image (--pull=always), so starting offline means forgejo fails while context7 keeps working. If the token isn't exported, forgejo still connects, but every tool call returns token is required. Consumers and the published Image are not affected.

Closes #31 ## Summary The repo's `.mcp.json` gets a `forgejo` server that runs the Image over stdio. Every session in this repo now uses the same config a Consumer copies. The change only adds lines, and context7 isn't touched. ```diff { "mcpServers": { + "forgejo": { + "type": "stdio", + "command": "docker", + "args": [ + "run", "-i", "--rm", "--pull=always", + "-e", "FORGEJO_URL=${FORGEJO_URL:-https://code.vicoli.de}", + "-e", "FORGEJO_ACCESS_TOKEN", + "code.vicoli.de/vicoli-oss/forgejo-mcp:3" + ] + }, "context7": { ``` The args follow the same order as the README's Claude Code snippet (`run -i --rm`, URL, token, image). There are two deliberate differences: `--pull=always`, and the `FORGEJO_URL` default. `forgejo` sits *before* `context7`, so context7 doesn't need a trailing comma and the diff contains only `+` lines. ## Evidence **1. Valid JSON, context7 untouched** ``` $ jq . .mcp.json >/dev/null && echo "jq: OK" jq: OK $ git diff origin/main -- .mcp.json | grep -c '^-[^-]' 0 ``` **2. Handshake with the token.** This runs the exact configured command, with `${FORGEJO_URL:-…}` expanded by hand and the token mapped with `FORGEJO_ACCESS_TOKEN=$FORGEJO_TOKEN`. The input is `initialize` → `notifications/initialized` → `tools/list`: ``` stdout: 2 lines, 0 non-JSON lines {"id":1,"serverInfo":{"name":"Forgejo MCP Server","version":"3.2.0"},"protocolVersion":"2025-06-18"} {"id":2,"toolCount":156,"first":["add_issue_dependency","add_issue_labels","add_issue_time",...]} includes: create_issue, create_pull_request, get_file_content, get_issue_by_index, list_my_repos, list_repo_issues stderr: "Connection verification successful … server_version 16.0.5+gitea-1.22.0" … "MCP server ready for stdio communication" ``` The same result through Claude Code itself: `claude -p --mcp-config .mcp.json --strict-mcp-config`, reading the `init` event, with `FORGEJO_URL` **unset**: ``` {"mcp_servers":[{"name":"forgejo","status":"connected"},{"name":"context7","status":"connected"}],"forgejoTools":156,"context7Tools":2} ``` That run also shows the `${FORGEJO_URL:-https://code.vicoli.de}` default is applied. The Image run with an empty URL exits 1 (`FATAL Missing required configuration {"missing": "url"}`), so if the default weren't applied, forgejo would have failed. Overriding the URL is honoured too, and a failure stays inside forgejo: ``` FORGEJO_URL=https://bogus.invalid {"mcp_servers":[{"name":"forgejo","status":"failed"},{"name":"context7","status":"connected"}],"forgejoTools":0,"context7Tools":2} ``` **3. Without the token.** Caveat: the server does **not** fail at startup: ``` FORGEJO_ACCESS_TOKEN and FORGEJO_URL unset {"mcp_servers":[{"name":"forgejo","status":"connected"},{"name":"context7","status":"connected"}],"forgejoTools":156,"context7Tools":2} tools/call get_my_user_info → {"jsonrpc":"2.0","id":3,"error":{"code":-32603,"message":"get user info err: token is required"}} ``` Upstream's startup check (`Connection verification`) doesn't need authentication, so a tokenless server comes up with `token_configured: false`, and the failure shows up on each tool call instead. That failure stays inside forgejo, and context7 is unaffected, which is what the criterion needs. But it isn't the "refuses to start" behaviour the issue text implies. (`claude mcp list` didn't help here: in a fresh worktree both servers only show `⏸ Pending approval`, so I used `--mcp-config`.) **4. No secret in the committed file** ``` $ grep -cF "$FORGEJO_TOKEN" .mcp.json 0 $ grep -nEi 'token|key|secret|password' .mcp.json 9: "-e", "FORGEJO_ACCESS_TOKEN", 17: "CONTEXT7_API_KEY": "${CONTEXT7_API_KEY}" $ grep -nE '[0-9a-f]{32,}' .mcp.json # no hits ``` Both hits are variable names, not values. ### README follow-ups (not changed here) - The README snippet has no `--pull=always`, so a Consumer on `:3` stays on whatever they pulled first and never follows the major line. The dogfood config reads better here, and the README probably should add it. - `claude mcp list` / `claude mcp get` display the arg as `FORGEJO_URL=${FORGEJO_URL}` and drop the `:-default`. That's only how they print it, since the default *is* applied at spawn (see 2), but a reader could find it confusing. ## Merge Danger **Door:** two-way Reverting one commit removes the server. **Blast Radius:** contributors Only sessions opened in this repo are affected. The first time, they're asked to approve `forgejo`. After that, each session pulls the Image (`--pull=always`), so starting offline means forgejo fails while context7 keeps working. If the token isn't exported, forgejo still connects, but every tool call returns `token is required`. Consumers and the published Image are not affected.
feat: dogfood the Image as this repo's Forgejo MCP server (#31)
All checks were successful
ci / build (pull_request) Successful in 13s
e995120302
piscis merged commit 9ca0d3aade into main 2026-09-30 18:49:49 +00:00
piscis deleted branch piscis/forgejo-mcp-server-dogfood 2026-09-30 18:49:49 +00:00
Sign in to join this conversation.
No description provided.