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
130 lines
4.4 KiB
Markdown
130 lines
4.4 KiB
Markdown
# 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
|
||
|
||
```jsonc
|
||
// 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.
|
||
|
||
```jsonc
|
||
{ "title": "Home", "page_name": "Home", "commit_sha": "a1b2c3…" }
|
||
```
|
||
|
||
## 2. List pages (bounded)
|
||
|
||
```jsonc
|
||
// tool: list_wiki_pages
|
||
{ "owner": "<owner>", "repo": "<repo>", "page": 1, "limit": 50 }
|
||
```
|
||
```jsonc
|
||
{ "pages": [ { "title": "Home", "page_name": "Home", "sub_url": "Home" } ],
|
||
"page": 1, "has_next": false }
|
||
```
|
||
|
||
## 3. Read via the tool (decoded + total_lines)
|
||
|
||
```jsonc
|
||
// tool: get_wiki_page
|
||
{ "owner": "<owner>", "repo": "<repo>", "page_name": "Home" }
|
||
```
|
||
```jsonc
|
||
{ "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
|
||
|
||
```jsonc
|
||
// 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)
|
||
|
||
```jsonc
|
||
// tool: get_wiki_revisions
|
||
{ "owner": "<owner>", "repo": "<repo>", "page_name": "Home", "page": 1, "limit": 30 }
|
||
```
|
||
```jsonc
|
||
{ "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)
|
||
|
||
```jsonc
|
||
// tool: get_wiki_page
|
||
{ "owner": "<owner>", "repo": "<repo>", "page_name": "Home",
|
||
"start_line": 1, "end_line": 5 }
|
||
```
|
||
```jsonc
|
||
{ "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
|
||
|
||
```jsonc
|
||
// 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.
|