Add Docs MCP Server page, fix get_page path resolution - #467
Draft
simonhamp wants to merge 1 commit into
Draft
Conversation
Documents the docs MCP server on a standalone /mcp page linked from the footer, covering per-agent setup, the tools, and the REST endpoints. Also fixes two defects the page would otherwise have to warn about: - search_docs returned ids get_page could not resolve. The section was derived from the parent directory basename, so a page nested in a subsection came back as mobile/4/core/camera while the file lives at plugins/core/camera.md. Sections now carry their full relative path, which also matches the public docs URLs. The page-list cache key is bumped so entries cached under the old shape are not reused. - Removes /api/mcp/sse. It never sent the endpoint event the HTTP+SSE transport requires, so compliant clients connected and hung, and it held a PHP-FPM worker per connection in a keepalive loop. Clients use the Streamable HTTP endpoint at /api/mcp/message. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The docs MCP server has been live and completely undocumented — grepping
resources/formcp/MCP/Model Context Protocolreturned zero matches across both desktop and mobile docs. This adds a standalone page at/mcp, linked from the footer as "MCP".The page covers per-agent setup (Claude Code CLI +
.mcp.json, Cursor, VS Code'sserverskey, anmcp-remotebridge for stdio-only agents), what the four tools do, the raw-markdown URL trick, and the REST endpoints.It's one standalone page rather than a page per docs version, so the content doesn't have to be duplicated and kept in sync across
mobile/4anddesktop/2.Two defects fixed along the way
Verifying every claim against the live endpoint (rather than describing intent) surfaced two real bugs that the page would otherwise have had to warn readers about.
search_docsreturned ids thatget_pagecould not resolve. The section was derived asbasename(dirname($file)), soplugins/core/camera.mdcame back asmobile/4/core/camera— butgetPage()rebuilt the path as{platform}/{version}/core/camera.md, which doesn't exist. Searching for "camera" and fetching the top hit returnedPage not found. This hit every core plugin page in mobile v3 and v4, and broke exactly the search-then-fetch chain agents rely on.Sections now carry their full path relative to the version directory, which also makes the ids match the public docs URLs.
getPageByPathtreats everything between the version and the slug as the section, so existing 4-part paths are unaffected. A newsanitizeSectionPathvalidates each segment separately so traversal can't hide behind a separator. The page-list cache key is bumped tov2— without that, entries cached under the old shape would keep serving unresolvable paths for up to 24h after deploy even with correct code.Removed
/api/mcp/sse. SSE is a legitimate MCP transport (the 2024-11-05 HTTP+SSE revision), but this route didn't implement it: a compliant client's first expectation is anevent: endpointframe carrying the POST-back URI, and the stream emitted onlydata:lines — noevent:field anywhere in it. It also returned JSON-RPC responses in the POST body rather than over the stream, which is Streamable HTTP behaviour. So it was a hybrid implementing neither transport, and any conforming client would connect and hang. It additionally held a PHP-FPM worker per connection in awhile (true)keepalive loop. Nothing referenced it — no tests, no.mcp.json, no docs. Clients use the Streamable HTTP endpoint at/api/mcp/message, which is what the NativePHP Claude Code plugin already ships.The REST
pageroute is now a wildcard so nested pages resolve there too; existing 4-segment URLs are unchanged.Verification
Checked against the running app:
search_docsfor camera in mobile v4 returnsmobile/4/plugins/core/cameraandget_pageresolves it; flat paths,list_apisfor mobile v2, and navigation section ordering (plugins/corestill sorts directly afterplugins) all still work;/api/mcp/ssereturns 404.Tests
tests/Feature/DocsMcpServerPageTest.php(8 tests) plus two new traversal cases inMcpSecurityTest.The notable one is
every_search_result_path_can_be_fetched_by_get_page, which walks real search results and asserts each one resolves — it fails on the actual regression rather than on a hardcoded path. Also covered: the page renders with the endpoint, config snippets survive un-evaluated through Blade, the footer anchor matches both href and label,tools/listreturns exactly the four documented tools, themcp.sseroute is gone from the route table, and a guard that no duplicatemcp-server.mdreappears underresources/views/docs/.Notes for review
resources/views/mcp-content.mdrather than inline in the Blade file. Prettier's Blade plugin splits inline elements onto their own lines, which renders<code>query</code>,asquery ,— visible on/privacy-policytoday. A markdown heredoc inside@phpwas worse:prettier-plugin-bladeappends a stray'';after the heredoc terminator on every run. A separate.mdavoids both and gets Torchlight highlighting via the app's existingCommonMarkpipeline./api/mcp/healthalways reports"pages": 0, becausesearch('')tokenizes to nothing so every page scores 0 and is filtered out. The page documents health as a liveness check and doesn't mention the count.🤖 Generated with Claude Code