forgejo-mcp/openspec/changes/add-wiki-support/demo.md
Christoph Görn aad9b361db
docs: 📝 third-pass survival check refinements for add-wiki-support
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
2026-05-31 15:59:45 +02:00

4.4 KiB
Raw Permalink Blame History

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-mcp built 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_lines is 4, not 3: the body ends in \n, and the shared sliceLines split (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/markdown sidecar: 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-sdk wiki 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.