chadtmiller.com/knotless
git that lives in your Atmosphere account
Pulls, issues, diffs and files are MCP resources
MergedThis adds six MCP resource templates (pulls, issues, a pull, an issue, a pull's diff, and a file) to knotless-mcp over stdio and to /mcp. repo_read and pull_diff move into Server::file and Server::diff, so each tool and its resource share one function. I read every file in the diff and found nothing that blocks the merge.
- URI parsing:
parsesplits on/before percent-decoding. A%2Fin a branch name or a DID therefore stays inside its own part, and a malformed or empty part becomesresource_not_found. The caller's own account is what makes the read, so a decoded part can't reach anything the caller couldn't already read. - Remote reads:
/mcpreads go through the sameturnlimits and logging as tool calls. Both paths now share onetoldfunction, so the tool logging and counts are unchanged. - Docs and metadata: the changeset, docs, lexicon known values and the tests for the new paths are all in the pull.
I did not run the checks. The body says CI is left to run them.
One small thing, not worth holding the pull for: an unknown URI is counted as mcp.resource with an empty subject. The test pins this (mcp.resource false), so it is deliberate. A placeholder such as unknown would read better in the operator's usage counts.
The only change since my approval is in tests/mcp.rs. The wait_until in agents_read_resources now waits for the pull list to show the comment (pulls[0]["comments"] == 1), which makes the test less likely to flake. Nothing I approved is affected.
9 passed, 1 skipped
What changes
Resources.
knotless-mcpover stdio and/mcpon a server offer six resource templates, so a client that reads MCP resources (omp'sread mcp://knotless://…, Claude Code's@mentions) opens a pull, an issue, a pull's diff or a file by URI, asread pr://Ndoes for GitHub. The URIs mirror knotless.dev's pages underknotless://:knotless://{owner}/{repo}/pullsand/issues: aspull_listandissue_listwith no queryknotless://{owner}/{repo}/pull/{did}/{rkey},/issue/{did}/{rkey}: aspull_showandissue_showofat://{did}/dev.knotless.pull/{rkey}(or.issue)knotless://{owner}/{repo}/pull/{did}/{rkey}/diff: aspull_diff, or itssaid/filessummary when too longknotless://{owner}/{repo}/file/{rev}/{+path}: asrepo_readatrev; a binary file as a base64 blobJSON answers are
application/json, diffs and filestext/plain. A part is percent-decoded as a template's expansion escapes it (topic%2Fmore,did%3Aplc%3A…).resources/listlists none. A URI no template makes is aresource_not_founderror naming the templates; a read that fails is an error with the tool's message (failed, with the sign-in-again hint); a server older than the appview asks for refuses reads as it refuses tools.--read-onlyserves them all.Docs.
docs/reference/mcp.mdhas a Resources section;docs/guide/agents.mdsays resources can be opened by URI, and that omp and Claude Code can installknotless-mcpwith a knotless skill as a plugin from https://knotless.dev/chadtmiller.com/agent-plugins.Shared bodies.
repo_readandpull_diffmove intoServer::fileandServer::diffbeside the existingitemsandshow; each tool and its resource call the same function. The server'sget_infois written out (tools and resources enabled, the same name and instructions, which now say resources exist).Remote.
/mcpforwards the resource methods; a read is made as the calling account, takes a turn in the same per-account limits as a tool call, and is logged asmcp resource/mcp resource failedand counted as themcp.resourceevent, named for the tool that answers it. The logging and counting a call gets is one function for both. The lexicons' known values,docs/observability.mdanddeploy/README.mdname them.Why
Agents read pulls, diffs and files as resources elsewhere (
pr://N); with knotless they had to make tool calls. Answering each resource with its tool's own function keeps the two the same, and URIs like the pages' are ones a user can write from a page they have open.How it was checked
agents_read_resources(tests/mcp.rs): the templates listed, resources listed empty, each template read against its tool for the same subject (a pull with a comment, its diff, an issue, both lists, a file on a/branch with a space in its path and the owner as an escaped DID), a binary file as a blob, a failing read with the tool's message, and malformed URIs asresource_not_foundnaming the templates.an_old_version_is_refused_and_a_new_one_told_of: a refused server refuses a read.tests/http.rs: the capability ininitialize, templates over/mcp, a read as the caller counted asmcp.resource repo_read true <did>, an unknown URI counted as failed, and a read past the minute's limit told to wait.cargo clippy -p knotless-mcp --testshere; the rest is left to CI.Shortcomings
start/endand a diff'spathsare the tools'.internal_errorwith the tool's message whatever the cause, as the tool cannot tell a missing pull from a failing PDS.Review
Start at
crates/knotless-mcp/src/resources.rs, thenRemote::read_resourceinhttp.rs.