Third debate team (NEW-critiques-only + agent-ergonomics lens): 3 CONCEDE-PATCH, 1 WITHDRAWN. Prior C1-C8 surface held; new edges caught: - N1: sliceLines is unexported — add task 2.3a to export repo.SliceLines (or lift to shared pkg), get_file_content unchanged, copy-paste forbidden - N2 WITHDRAWN: feared template dispatch collision disproven — commit/issue/pr/status already coexist; mcp-go v0.17.0 matchesTemplate matches by literal-segment regex. Added optional dispatch regression test (3.5) - N3: update_wiki_page has no commit_sha precondition (vs update_file) — acknowledge lost-update window, task 5.6a (optional last_commit_sha if upstream supports) - N4: create-on-existing-title unspecified — conditional guided-error/overwrite-warning branch, task 5.6b Referee finding (verified vs mcp-go server.go:614): literal-slash resource URIs are rejected by matchesTemplate before ParseWiki, so the guided -32602 cannot fire at the resource layer — encoding guidance + get_wiki_page fallback moved to template description. Agent-ergonomics lens (5 gaps): naming convention note, use-returned-page_name, honest demo reframing (no over-claim of client auto-resolution), %2F dead-end fallback, total_lines-always-returned guidance. openspec validate --strict passes. Refs: #32
4.4 KiB
Showboat Demo — Forgejo wiki via forgejo-mcp
End-to-end proof of the whole wiki surface in one sitting: six tools + the
forgejo://…/wiki/… resource template, including the agent-discovery payoff (a bare URI
in a prompt resolves to page content with no tool call).
Prerequisites
forgejo-mcpbuilt from this change and configured against a Forgejo/Codeberg instance.- A token with repo write scope; the target repo has Wiki enabled in settings.
- A throwaway repo, referred to below as
<owner>/<repo>.
Replace
<owner>/<repo>throughout. Outputs shown are the expected shape; task 6.2 replaces them with real captured output.
1. Create a page
// tool: create_wiki_page
{ "owner": "<owner>", "repo": "<repo>", "title": "Home",
"content": "# Welcome\n\nThis wiki is driven by forgejo-mcp.\n" }
→ Page created. Content is sent base64-encoded under the hood; the caller only ever sees plain markdown.
{ "title": "Home", "page_name": "Home", "commit_sha": "a1b2c3…" }
2. List pages (bounded)
// tool: list_wiki_pages
{ "owner": "<owner>", "repo": "<repo>", "page": 1, "limit": 50 }
{ "pages": [ { "title": "Home", "page_name": "Home", "sub_url": "Home" } ],
"page": 1, "has_next": false }
3. Read via the tool (decoded + total_lines)
// tool: get_wiki_page
{ "owner": "<owner>", "repo": "<repo>", "page_name": "Home" }
{ "title": "Home", "commit_sha": "a1b2c3…", "total_lines": 4,
"content": "# Welcome\n\nThis wiki is driven by forgejo-mcp.\n" }
total_linesis 4, not 3: the body ends in\n, and the sharedsliceLinessplit (strings.Split(content, "\n")) counts the trailing newline as a final empty line.
4. Read via the resource URI — the money shot
Hand an agent a prompt containing only the bare URI:
Summarize forgejo://repo/<owner>/<repo>/wiki/Home
The result is one resources/read call instead of a get_wiki_page tool call,
returning:
application/json:{ owner, repo, page_name, title, commit_sha, recent_revisions: [...] }text/markdownsidecar: the decoded page body.
A client that auto-resolves resource URIs issues that resources/read transparently;
clients that do not (the common case today — auto-resolution of a bare URI embedded in
chat text is not yet universal across Claude Code/Desktop, Codex, Cursor) issue it
explicitly. Either way the wiki page becomes first-class, URI-addressable context — the
discoverability win — without over-claiming that every client resolves it with zero
prompting.
5. Edit the page
// tool: update_wiki_page
{ "owner": "<owner>", "repo": "<repo>", "page_name": "Home",
"content": "# Welcome\n\nThis wiki is driven by forgejo-mcp.\n\n## Links\n\n- See `list_wiki_pages`.\n" }
→ New commit. title omitted ⇒ page keeps its name.
6. Revision history (bounded)
// tool: get_wiki_revisions
{ "owner": "<owner>", "repo": "<repo>", "page_name": "Home", "page": 1, "limit": 30 }
{ "revisions": [ { "sha": "d4e5f6…", "author": "you", "message": "Update wiki page 'Home'" },
{ "sha": "a1b2c3…", "author": "you", "message": "Create wiki page 'Home'" } ],
"page": 1, "has_next": false }
7. Bounded read of a long page (resumability)
// tool: get_wiki_page
{ "owner": "<owner>", "repo": "<repo>", "page_name": "Home",
"start_line": 1, "end_line": 5 }
{ "title": "Home", "total_lines": 8, "start_line": 1, "end_line": 5,
"content": "# Welcome\n\nThis wiki is driven by forgejo-mcp.\n\n## Links" }
total_lines: 8 > end_line: 5 ⇒ the caller knows to request lines 6–8 next. (The
edited body ends in \n, so the split counts a trailing empty 8th line; the same
sliceLines routine and count as get_file_content.)
8. Cleanup
// tool: delete_wiki_page
{ "owner": "<owner>", "repo": "<repo>", "page_name": "Home" }
→ a 2xx (canonically 204 No Content) reported as success. list_wiki_pages now
returns an empty list.
What this proves
- All six tools work against a stock instance with no
forgejo-sdkwiki dependency. - Content round-trips correctly through base64 (caller never sees encoding).
- Every data-proportional response is bounded and resumable (
page/limit/has_next,start_line/end_line/total_lines). - A wiki page is discoverable by URI for agents and humans alike.