# MCP tools The tools `knotless-mcp` offers over stdio, and a public `knotless-serve` offers at `/mcp`. They name a repository as `handle/name`. Reads answer from a local cache mirror, writes go through the same code as the CLI, and errors come back as tool errors for the agent to read. One a PDS refused for want of scope, as a login from before knotless asked for more is, adds that the user signs in again: reconnecting knotless in their assistant, or running `knotless login` for a knotless on their machine. One the login asked for, but its PDS did not grant, says so instead: for a write in a space, that the PDS granted the collections the space type's lexicon listed when the user signed in, and that signing in again grants this one once knotless's lexicon lists it. On a server, each account's login with its PDS can end while its assistant's token still works: a PDS ends a session after a lifetime of its own. Once the PDS turns down refreshing it, the server forgets the session, answers the next call `401` and the next token refresh `invalid_grant`, so the assistant has its user sign in again. A refresh that fails any other way is logged, and the call goes on. [Agents](../guide/agents.md) says how to connect a client. Pulls, issues, diffs and files are [resources](#resources) to read by URI too. Over stdio, `knotless-mcp` asks its appview about versions once a day, as the CLI does ([Command reference](cli.md#knotless)). With a newer release out, the first tool's answer ends with a text saying so. Older than the appview asks for, every tool fails, saying why and to run `knotless upgrade`, unless `KNOTLESS_IGNORE_MINIMUM` is set. `/mcp` on a server runs that server's own build and does neither. ## The tools are the commands Every command `knotless ` has one tool, `_`, with `-` as `_`: `knotless repo star` is `repo_star`, `knotless pull update-branch` is `pull_update_branch`. A tool takes what its command takes. A parameter is the flag's name with `-` as `_` (`--keep-branch` is `keep_branch`, `--undo` is `undo`), and the positionals are named as the command's usage names them (`repo`, `uri`, `name`). Where a command has an undo, the tool takes `undo: true`. A verb that works on a pull or an issue exists as both tools (`pull_close`, `issue_close`). Every tool is a command, and nothing is on the MCP alone. The commands with no tool act on the machine they run on: `login`, `logout`, `upgrade`, `repo mirror`, `workflow pin` and `release download`. [The command reference](cli.md) has each command's flags and what it prints; this page has what a tool takes beyond that and what it answers. The tools are listed by group, as the commands are. [Actions](actions.md) lines them up against the commands, the methods and the pages. `knotless-mcp --read-only` offers only the tools that read, as `status`, `repo_list`, `repo_refs`, `repo_tree`, `repo_read`, `repo_blame`, `repo_search`, `repo_log`, `repo_diff`, `pull_diff`, `pull_show`, `issue_show`, `pull_list`, `issue_list`, `run_list`, `workflow_list` and `release_read` (`READS` in `crates/knotless-mcp/src/lib.rs` lists them all), and runs no other, whatever login it has: for an agent that may read but must change nothing, as the [review agent](../guide/reviews.md) is. ## status | Tool | Command | What it does | | --- | --- | --- | | `status` | `knotless status` | a `knotless-serve`'s health, from `/_status`, for `server` (the appview by default), with `repo` (`handle/name`) its mirror too | ## account | Tool | Command | What it does | | --- | --- | --- | | `account_profile` | `knotless account profile` | how you show on knotless, over your Bluesky profile, and, with `bot`, whether the account is a bot, which the feed leaves out | | `account_email`, `account_email_add`, `account_email_remove` | `knotless account email [add \| remove]` | `account_email` says how many addresses you claim, `{claimed}`; `account_email_add` claims an `address` as yours and `account_email_remove` takes it back, each answering `{changed, claimed}`, how many you claim now | | `account_follow` | `knotless account follow` | follows a `handle` on knotless, or with `undo` unfollows it, answering `{following, uri}` | | `account_follows`, `account_followers` | `knotless account follows`, `knotless account followers` | `account_follows` lists whom a `handle` follows, yours by default, as `{follows: [{handle, uri, createdAt}]}`; `account_followers` who follows it, from the appview, as `{followers: [{did, handle}]}` | | `account_search` | `knotless account search` | the accounts owning public repositories whose handle holds `text`, as the appview's search finds them, at most `limit` (10 by default, at most 50), as `{owners: [{owner: {did, handle}, repoCount}]}` | | `account_delegate` | `knotless account delegate` | hands your writes to a gateway (the appview's, by default) so collaborators can push, approved in the browser | | `account_delete_data` | `knotless account delete-data` | without `confirm`, what knotless data your account holds, as `getAccountData` answers it; with `confirm`, your handle, deletes it all, answering `{deleted, loggedOut}` | ## repo | Tool | Command | What it does | | --- | --- | --- | | `repo_list`, `repo_refs`, `repo_tree`, `repo_log` | `knotless repo list`, `refs`, `tree`, `log` | read a repository, an account's repositories, a directory (`path`, `rev`) or the history (`rev`, `path`, `limit`, 20 by default) | | `repo_read` | `knotless repo read` | a file at `rev`, optionally only lines `start` to `end` ([long files](#long-files)) | | `repo_search` | `knotless repo search` | the lines holding a fixed string, in any case; see the parameters below | | `repo_blame` | `knotless repo blame` | who last changed each line of `path` at `rev` (the default branch if not given), as `git blame` finds it in the cache mirror: runs of lines, each `{sha, start, count, author: {name, email, date}, summary, previous}`, `start` counted from 1, `date` in UTC, and `previous`, where the commit's parent had the file, that parent and the file's path there | | `repo_diff` | `knotless repo diff` | what `head` changed since `base`; answers `No differences: …` when `head` changes nothing since `base` | | `repo_storage` | `knotless repo storage` | what a repository takes up on its PDS: its packs, their objects and bytes, and the bytes in blobs | | `repo_compact` | `knotless repo compact` | as the owner, fold a repository's older packs into one and drop old claims and deleted refs' leftover records, answering `{before, removed, claims, refs}` | | `repo_commit` | `knotless repo commit` | change files on a branch, optionally against a `base` commit, and with `batch`, as the owner, through the gateway; see the parameters below | | `repo_create` | `knotless repo create` | takes `links` (`[{url, title?}]`, its website first) and a `default_branch` (`main` if not given) and names the `runners` given (none, for an empty list), or else the appview's default runner, and returns them as `runners`: a private one's are members, invited, with a `warning` (null otherwise) if one could not be. It answers the `members` who may push to a new repository | | `repo_edit` | `knotless repo edit` | change a repository's description (empty drops it), its `links` (`[{url, title?}]`, which replace those it had; empty drops them), its `topics` (names, which replace those it had; empty drops them), and the branch its HEAD names, one of its branches once it has any, answering `{description, links, topics, defaultBranch}` | | `repo_rename` | `knotless repo rename` | rename; the old name redirects | | `repo_archive` | `knotless repo archive` | makes one you own read only, or with `undo` writable again, answering `{archivedAt}` (null once unarchived) | | `repo_delete` | `knotless repo delete` | delete a repository you own and everything in it, with what you wrote about it; `confirm` is its name again, or it refuses | | `repo_transfer`, `repo_accept` | `knotless repo transfer`, `knotless repo accept` | offer a public repository to `to`, or with `undo` withdraw the offer; take one offered to you | | `repo_fork` | `knotless repo fork` | copy a repository into your account, under `name` if given | | `repo_share` | `knotless repo share` | let `handle` read a private repository, or with `write` push too, which first delegates your writes to the gateway when you have not, approved in the browser as `account_delegate` is, then answers who may read and write it and, as a second text, `Delegated your writes to the gateway first.`; with `undo` takes access away | | `repo_members` | `knotless repo members` | who may read and push | | `repo_star`, `repo_stars` | `knotless repo star`, `knotless repo stars` | `repo_star` stars a public repository, or with `undo` takes the star back, answering `{starred, uri}`; `repo_stars` lists the repositories a `handle` starred, yours by default, as `{stars: [{repo, uri, createdAt}]}` | Parameters beyond a flag's name: | Tool | Parameter | | | --- | --- | --- | | `repo_commit` | `changes` | each `{path}` with one of `content`, `base64`, `edits` or `delete: true`; `edits` is a list of `{old, new}` made in turn in the file as the branch holds it, each `old` there exactly once, the file keeping its mode. A miss fails `edit of : old is not in the file`, or says how many times it is there; a path with no regular file fails `: no file there to edit`. On a branch that moved, edits are made in the file as it is there ([long files](#long-files)) | | `repo_search` | `path`, `context` | only files under `path`, a directory, file or glob; with `context`, runs of lines around the matches instead of lines, each `{path, start, end, matches, text}`, `matches` the line numbers that match and `text` the run's lines | | `repo_read` | `start`, `end` | only those lines of a text file, from 1, `end` by default its last, as `{start, end, lines, text}`, `lines` the file's count | | `repo_blame` | `start`, `end` | only the runs for those lines, `end` by default `start` | ## branch and tag | Tool | Command | What it does | | --- | --- | --- | | `branch_create`, `branch_delete`, `branch_rename` | `knotless branch create`, `delete`, `rename` | make, remove or rename a branch; a rename takes the open pulls on it along | | `branch_protect` | `knotless branch protect` | holds collaborators to a branch's rules, or with `undo` drops them. Takes `merges`, `queue`, `reviews` (how many approvals, 0 to 10), `code_owners`, `dismiss_stale` and `gates` (handles or DIDs whose review of a pull's head it waits for, see [gates](../design/collaboration.md#gates)); into such a branch `pull_merge` refuses a pull not reviewed as it asks, naming what is missing, unless the owner forces it (see [required reviews](../design/collaboration.md#required-reviews)) | | `branch_rules` | `knotless branch rules` | lists the rules, a rule that asks for code owners with `warnings` when CODEOWNERS on its branch gives you, the owner, a path alone (your own pull changing it never has a code owner's approval, as an author's never counts, and merges only by force); shows `queue` | | `tag_create`, `tag_delete` | `knotless tag create`, `knotless tag delete` | make or remove a tag, such as a release's version, at a commit, branch or tag | ## pull and issue | Tool | Command | What it does | | --- | --- | --- | | `pull_create`, `issue_create` | `knotless pull create`, `knotless issue create` | open one, following the repository's template: they refuse one with no body when there is a template. `pull_create` takes `base`, `head_repo`, `head_branch`, `title`, `body`, `images`, `draft` and `reviewers` | | `pull_template`, `issue_template` | `knotless pull template`, `knotless issue template` | what a repository asks a pull's or issue's title and body to follow: read before `pull_create` or `issue_create` | | `pull_list`, `issue_list` | `knotless pull list`, `knotless issue list` | takes a `query` in GitHub's search words ([collaboration.md](../design/collaboration.md#filtering-pulls-and-issues)), and says of each pull `draft`, `true` or `false` | | `pull_show`, `issue_show` | `knotless pull show`, `knotless issue show` | a pull or issue and its comments; on a pull, what it brings in and whether it merges cleanly, and on an issue `mentionedBy`, the pulls whose title or body names it, oldest first, each `{pull, closes}`, `closes` true where it names it after a closing keyword | | `pull_diff` | `knotless pull diff` | the diff a pull would apply, numbered as `comment_add`'s `lines` take them | | `pull_edit`, `issue_edit` | `knotless pull edit`, `knotless issue edit` | change your pull's or issue's title and text; `pull_edit` also whether your pull is a draft (`draft`, `ready`) and whom it asks to review it (`reviewers`, `no_reviewers`) | | `pull_close`, `pull_reopen`, `issue_close`, `issue_reopen` | `knotless pull close` and the rest | change an item's state: its author, the owner or a collaborator; refuses an item that is not there, a merged pull and a state it is in already | | `pull_label`, `issue_label` | `knotless pull label`, `knotless issue label` | put labels on a pull or issue as owner, collaborator or triager, or with `undo` take them off | | `pull_assign`, `issue_assign` | `knotless pull assign`, `knotless issue assign` | assign a pull or issue to accounts, or with `undo` take them off; as in [collaboration.md](../design/collaboration.md#assignees), the owner and collaborators assign. Answers `assignees` by DID | | `pull_ask_review` | `knotless pull ask-review` | ask accounts to review a pull, or with `undo` stop asking: the owner and collaborators, and a pull's author in the pull. Answers `assignees` and `reviewers`, by DID | | `pull_dismiss_review` | `knotless pull dismiss-review` | dismiss a reviewer's standing review of a pull, saying why: a comment naming it, so it counts for nothing, for or against, until they review again. As in [collaboration.md](../design/collaboration.md#dismissing-a-review): the owner and collaborators may, on their own pulls too. Answers the comment's `uri` | | `pull_react`, `issue_react`, `comment_react` | `knotless pull react`, `knotless issue react`, `knotless comment react` | reacts to a pull, issue or comment (`uri`) on `repo` (in its space, for a private one) with a `content` among GitHub's eight, or with `undo` takes it back, answering `{reacted, uri}` | | `pull_retarget` | `knotless pull retarget` | point a pull at other branches | | `pull_revert` | `knotless pull revert` | undoes a merged pull (`uri`) in one commit on `revert/` and opens a pull of it into the same branch, saying `reason` if given, answering `{pull, branch, commit}` ([reverting a pull](../design/collaboration.md#reverting-a-pull)) | | `pull_update_branch` | `knotless pull update-branch` | merges an open pull's (`uri`) base into its branch and pushes it, answering `{branch, commit, added}`, `commit` absent when the branch held its base already, and `added` the paths where both sides only added lines and the base's come first ([updating a pull's branch](../design/collaboration.md#updating-a-pulls-branch)) | | `pull_merge` | `knotless pull merge` | merge a pull, queue it or check it; see below | | Tool | Parameters | Answer | | --- | --- | --- | | `pull_create`, `issue_create`, `pull_edit`, `issue_edit`, `comment_add`, `comment_edit` | `images` | pictures to show after the body, each `{base64, alt}`, a PNG, JPEG, WebP or GIF: uploaded, made smaller when over 1 MB, and attached ([collaboration.md](../design/collaboration.md#pictures)); with an edit, only beside a `body` | | `pull_create` | `draft`, `reviewers` | open it as a draft, which neither merges nor queues until marked ready; ask these accounts, by handle or DID, to review it ([drafts and requested reviewers](../design/collaboration.md#drafts-and-requested-reviewers)) | | `pull_edit` | `draft`, `ready`, `reviewers`, `no_reviewers` | for your pull: `draft` makes it a draft, `ready` marks it ready; ask more accounts to review it, or stop asking some | | `pull_list`, `issue_list` | `query` | also `is:draft`, `-is:draft`, `review-requested:`, `assignee:` and `no:assignee` | | `pull_diff` | `repo`, `uri`, `paths` | the pull's unified diff, or with `paths` only those files' parts of it (for a merged pull, what its merge brought in, from the commits its state records, so it answers once its branch is gone), each line of a hunk led by its line in the old file and in the new (blank on the side it is not on) and ` \| `, as `comment_add`'s `lines` takes them: a `+` line is `side: new` at its new number, a `-` line `side: old` at its old number, an unchanged line `side: new` at its new number. File headers, `@@` lines and `\ No newline` are not numbered. A diff longer than 60,000 bytes so numbered, asked for without `paths`, answers instead `said` and `files`, each file's `path` (a renamed file's new one), `added`, `removed` and `lines`, to ask for by `paths` a few at a time, since an MCP client cuts off an answer that long | | `pull_show`, `issue_show` | `repo`, `uri` | `item.draft`, `true` or `false` for a pull, and `item.reviewers`, those it asks to review it, `{did, handle, pending}`, `pending` while the pull is open and they have not reviewed it, and `false` once it is merged or closed; for a pull, `item.reviews`: each reviewer's standing review, `{by, handle, verdict, commit, uri, outdated, maintainer, uncounted}`, `outdated` once the open pull has moved past `commit`, `maintainer` for the owner or a collaborator, and `uncounted`, when its branch asks for reviews, why an approval does not count; on each comment, its author's `handle` and `facets`, the accounts it mentions; on a comment on lines, `line` (`{path, side, line, startLine}`, `side` `new` and `startLine` the same as `line` where its record leaves them out) and `review`, which a review's own comment names too; on each comment of a thread on lines, and on no other, `resolved`; on a review its reviewer's later one stands in place of, `superseded: true`; and on an answer, `reply`; `reactions`, on it and on each comment, as `{"": count}` or null, as the appview counts them, or for a private repository as its space has them; for a pull, also `compare`, which for a merged pull is what its merge brought in (`base` where the branch was before it, `head` the merge), whether or not its branch is still there; for a merged pull a merged revert undid, `item.revertedBy`, that revert, and on a revert, `item.reverts`, the pull it undoes; for an open pull in the queue or ejected from it, `standing`: `{said, place, batch}` | ### pull_merge `pull_merge` takes `wait` (seconds, at most 300), `force` and `keep_branch`, as the CLI's `pull merge` takes `--wait`, `--force` and `--keep-branch`. Merged, it answers `{sha, method, deleted, kept, closed}`: the commit its branch is at, how (`merge` or `squash`), the head branch it deleted, or else why it kept it (asked to, in another repository such as a fork, the branch merged into or the default branch, or named by another open pull), and the issues it closed. With `queue: true`, on a branch protected with `queue`, it puts the pull in the merge queue instead and answers `{"queued": {base, position, of}}` at once, with `ejectedBefore`, why, when it is queued again at the very head the queue last ejected; it then ignores `wait`, `force` and `keep_branch`. Its description tells an agent to queue a pull once it builds, with the target branch merged in; once ejected, to read why and the failed checks' logs, fix it and queue again, and to queue the same head again only when the failure is not the pull's; and to stop after a second ejection for the same reason. Into a branch protected with `queue` (`branch_protect` takes `queue`, and `branch_rules` shows it), `pull_merge` without `force` queues the pull instead, answering `{"queued": {base, position, of}, "why": " takes pulls only through the merge queue"}`; see [queue-only branches](../design/merge-queue.md#queue-only-branches). With `auto: true`, on a branch with no merge queue, it sets the pull to merge by itself once its checks pass, and answers `{"autoMerge": "set", "commit": }`; with `undo: true` as well, it cancels that, answering `{"autoMerge": "cancelled"}`. `pull_show` then has `autoMerge`, where it stands in words. See [merge when checks pass](../design/collaboration.md#merge-when-checks-pass). With `check: true`, it merges nothing and answers whether it would now, as `knotless pull merge --check` says: `{state, reason, conflicts, checks, method, deletes, base, head, may: {merge, force, queue, base, head, rerun}, queueOnly, reviews, reviewsMissing, reviewsUncounted, codeOwnersWarnings}`: `reviews` is `passed`, `blocked`, or `none` when the branch asks for none, and `reviewsMissing`, `reviewsUncounted` (the approvals that do not count, and why) and `codeOwnersWarnings` are `[{text}]`. `may` is what the caller's part in the repository lets it do, not whether that would work now: `state` and `reason` say that, and a blocked pull's merge is refused, the owner's too, unless the owner forces it. When the caller may not merge the pull, `reason` says so too. Over MCP, a refusal for reviews names the `comment_add` tool with `verdict: "approve"`, and `pull_merge` with `force`, where the CLI names its commands. `standing.said` reads as `Queued, 3rd of 5`, `Queued, 1st of 2, running its checks`, `Queued, 1st of 2, running its checks again on main as it is now`, `Queued, 1st of 2, but its test failed. The runner ejects it, or runs it again if that run was on an older main`, `Queued, 2nd of 2, ready for the next batch`, `In a batch of 4 being checked`, `Landing`, or `Ejected: `, with the failed checks. ## comment | Tool | Command | What it does | | --- | --- | --- | | `comment_add` | `knotless comment add` | comments on a pull or issue, or reviews a pull: a `body`, and a `verdict` (`approve` or `changes`) for someone else's pull; a comment's URI as `uri` answers it in its thread. Answers `{uri, written}`: the first it wrote and all of them, and for an approval from someone who may not write to the repository, `note`, that it does not count toward a protected branch's reviews | | `comment_edit` | `knotless comment edit` | change your comment's text | | `comment_delete` | `knotless comment delete` | take your comment or review back | | `comment_react` | `knotless comment react` | as `pull_react`, to a comment | | `comment_resolve` | `knotless comment resolve` | resolve the thread a comment on a pull's lines starts, or with `undo` open it again: its author, the pull's, the owner or a collaborator | `comment_add` takes `lines`, comments on lines of a pull's diff at its head, each `{path, line, start_line?, side?, body}` (`side` `old` for lines it removes), each starting a thread: with a `body` or `verdict`, as that review's, all sent at once, otherwise on their own; at most 100. ## label and triager | Tool | Command | What it does | | --- | --- | --- | | `label_list`, `label_define`, `label_delete` | `knotless label list`, `define`, `delete` | a repository's labels: what it defines, defining one as owner (`name`, `color`, `description`, `exclusive`), or taking one away | | `triager_list`, `triager_add`, `triager_remove` | `knotless triager list`, `add`, `remove` | the accounts whose labels count, naming one as owner (`handle`, `scopes`) or dropping it | | `triager_eval` | `knotless triager eval` | how an account's labels agree with everyone else's, per scope, as `[{scope, labelled, compared, agreed}]` | ## queue | Tool | Command | What it does | | --- | --- | --- | | `queue_list` | `knotless queue list` | each pull waiting, in order, with its `place` and the `batch` it is in while that is checked or landing, or the `preflight` workflows it waits for, then the open pulls ejected | | `queue_leave` | `knotless queue leave` | takes a pull out, `{"left": }`; the owner and collaborators may | ## run, workflow and runner | Tool | Command | What it does | | --- | --- | --- | | `run_list` | `knotless run list` | what the repository's runners report of its workflows, waiting for runs to finish if asked; each log is its last 8000 bytes; `images` returns a check's pictures, at most 12 at once | | `run_rerun` | `knotless run rerun` | as owner, ask the runners to run a ref or a pull again, or one workflow of either, with `env` at a ref | | `run_cancel` | `knotless run cancel` | as owner, ask the runners to cancel a ref's or a pull's runs going or waiting, or one workflow's, `{"asked": }` | | `workflow_list` | `knotless workflow list` | the workflows at a `ref` (the default branch), and what runs each there: `{ref, workflows: [{name, push, pull, asked, schedule, error}]}`, `schedule` the `[{cron}]` it runs on at the default branch | | `runner_list`, `runner_add`, `runner_remove` | `knotless runner list`, `add`, `remove` | the accounts whose checks a repository shows; as owner, name or drop one: a private repository's is made a member and invited, again each time it is added | | `runner_minutes` | `knotless runner minutes` | the minutes each of a repository's runners counts for its owner's public repositories in 30 days, against its allowance, and when minutes free up | | `agent_list`, `agent_add`, `agent_remove` | `knotless agent list`, `add`, `remove` | the accounts whose pulls a repository's runners check as they move, as code no one vouches for; as owner, name or drop one | | `secret_list`, `secret_set`, `secret_remove` | `knotless secret list`, `set`, `remove` | the secrets a repository's owner, or an admin of the organization that owns it, gives its runners: `secret_set` sets a `name` with its `value`, `refs` to limit it to some refs' pushes and `runners` to some of the repository's runners; `secret_remove` takes one back. They answer each, one per runner, `name`, `runner`, `refs`, `key`, `updatedAt`, never a value. A value given here passes through the conversation: for one that should not, run `knotless secret set ` with it on stdin | | Tool | Parameter | | | --- | --- | --- | | `run_list` | `ref` | only the runs of pushes to that ref, in full, and those asked for there; a pull's are found by `pull`, a batch's by `queue` | | `run_list` | `queue` | only the runs of that branch's merge-queue batches, by its short name (`main`) | | `run_list` | `commit` | only the runs of that commit: its hex ID, or at least its first 4 digits | | `run_list` | `wait` | seconds to wait, at most 300, for a check to show and none to be queued or running; finding none by then is an error naming what was asked for | ## release | Tool | Command | What it does | | --- | --- | --- | | `release_yank` | `knotless release yank` | withdraw a `version` of a `package` from a repository you own, or with `undo` take that back ([yanking](../design/packages.md#yanking)), answering `{uri}`, the release's | | `release_publish`, `release_list`, `release_read` | `knotless release publish`, `list`, `read` | releases ([packages.md](../design/packages.md)): `release_publish` publishes a `version` of a `package` from a repository you own, its `files` each `{name, content}` or `{name, base64}`, with optional `commit`, `check` and `notes`, answering `{uri}`; `release_list` lists a repository's, of one `package` if given, as `[{package, version, latest, yanked, commit, check, notes, createdAt, files: [{name, size, sha256}]}]`; `release_read` reads one `file` of a `version` (the latest by default), checked, as `{version, content}` or `{version, base64}`, refusing a file over 1 MB | ## mod | Tool | Command | What it does | | --- | --- | --- | | `mod_hide` | `knotless mod hide` | as owner, hide a pull, issue or comment, or with `undo` show it again | | `mod_block`, `mod_blocks` | `knotless mod block`, `knotless mod blocks` | `mod_block` leaves out what a `handle` writes about your repositories, or one, or with `undo` stops; `mod_blocks` lists the accounts an account blocks, yours by default, the latest first: each `did`, `handle`, `repo` (the one repository, by its `at://` URI as when blocked, or null for all) and `createdAt` | | `mod_report` | `knotless mod report` | report to the appview | ## admin As the appview's operator: an account its `KNOTLESS_ADMIN_DIDS` names. | Tool | Command | What it does | | --- | --- | --- | | `admin_takedown`, `admin_status`, `admin_reports`, `admin_stats`, `admin_logs` | `knotless admin takedown`, `status`, `reports`, `stats`, `logs` | take a subject down or, with `undo`, put it back; whether it is taken down; the reports received; how the appview is used; the lines it logged | ## org An organization's members as you may see them, yours or an account's (`org`): the organization and its members see invitations and hidden members, anyone else public members, as `public: true` shows them to anyone. | Tool | Command | What it does | | --- | --- | --- | | `org_add` | `knotless org add` | invites an account (`admin: true` for an admin) or changes a member's role, keeping how it shows and when they joined, yours or, with `org`, one you are an admin of | | `org_remove` | `knotless org remove` | takes a member out or withdraws an invitation, yours or, with `org`, one you are an admin of | | `org_join` | `knotless org join` | with `visibility` (`public` or `hidden`) accepts an invitation or changes how it shows | | `org_leave` | `knotless org leave` | leaves | | `org_members` | `knotless org members` | the roster | | `org_list` | `knotless org list` | the organizations an account is a public member of | | `org_delete` | `knotless org delete` | ends yours | They answer `{org, name, organization, inside, gateway?, entries: [{did, handle?, role, invitedAt, expiresAt?, joinedAt?, standing}], changed}`: `changed` null, or for `org_add` `{org, who, role, was?, had?, first, private}` (`first` when it made the account an organization, `private` when the invitation is in the organization's space rather than its public records), for `org_remove` `{org, who, was?, organization}`, for `org_join` `{org, role, visibility}`, for `org_leave` `{left, warning}`, for `org_delete` `{removed: [entries]}`. An organization's admin changes its repositories' settings with `repo_edit`, `repo_archive`, `repo_compact`, `branch_protect`, `label_define`, `label_delete`, `triager_add`, `triager_remove`, `agent_add`, `agent_remove`, `runner_add`, `runner_remove`, `repo_share` and `secret_set` from their own login, through the appview; `repo_rename` and `repo_transfer` take the organization's own. ## notification | Tool | Command | What it does | | --- | --- | --- | | `notification_list` | `knotless notification list` | what others did that concerns you, from the appview (a `mention` reason for a pull, issue or comment that names you as `@handle`, `invite` for an organization's invitation you have yet to answer, its `about` the role it offers, and `checks-failed` and `ejected` for checks failing at your pull's latest run and the merge queue taking it out, from the runner, with `checks` those that failed, as `{workflow, status, check}`), the latest `limit` (50 by default, 1 to 100), as `{notifications: [{reason, author, about, repo, uri, subject, verdict, mentioned, lineComments, checks, new, createdAt}], markedSeen}`: `repo` the repository as `handle/name`, for `pull_show`; `subject` what it is about, the pull or issue a comment is on, else the pull or issue itself, or for a star the repository's record (null for a follow or an invitation); `verdict` a review's; `mentioned` true when it names you though it is heard of for another reason, as a review of your pull that names you is heard of once, as a `comment`; `lineComments` how many line comments a review was sent with; and `new` whether it was new before this listing; marks them all seen after, unless `peek`, and `markedSeen` says it did, so the next listing has them `new: false`. `kind` lists only `mentions`, `reviews`, `comments`, `pulls-and-issues`, `checks` or `stars-and-follows`, and marks none seen | | `notification_settings` | `knotless notification settings` | which notifications you hear of, kept by the appview: `on` and `off`, lists of reasons (`star`, `follow`, `pull`, `issue`, `comment`, `mention`, `review-requested`, `invite`, `checks-failed`, `ejected`, `subscribed`, `watching`), and `followed_only` or `anyone`, change them first; answers `{reasons: [{reason, enabled}], followed_only}` | | `notification_watch` | `knotless notification watch` | what you hear of a repository (`repo`, `handle/name`) at `level` `all` (the default), `releases` or `ignore`; `participating` forgets the choice, but is kept for your own repository, watched at `all` until you choose. Answers `{subject, level, createdAt}` | | `notification_subscribe`, `notification_mute`, `notification_unsubscribe` | `knotless notification subscribe`, `mute`, `unsubscribe` | what you hear of a pull or an issue (`uri`): every comment, nothing but a review asked of you, or as your part in it says again. Answer `{subject, level, createdAt}` | | `notification_subscriptions` | `knotless notification subscriptions` | every choice, the latest first, as `{subscriptions}` | ## theme | Tool | Command | What it does | | --- | --- | --- | | `theme_check` | `knotless theme check` | a `palette` checked, answering `{readable, shortfalls: [{text, on, ratio}]}`, each text that would not read at 4.5 to 1 on what it is drawn on | | `theme_publish` | `knotless theme publish` | publishes a `palette` with a `name` as a `dev.knotless.theme`, answering `{uri}`, and with `use` draws your pages in it; a palette that falls short is neither published nor used | | `theme_use` | `knotless theme use` | draws your pages in a `theme` (`system`, `light`, `dark`, `latte`, `mocha`, `dracula`, `pckt`, `plyr`, `blacksky`, a published one's `at://` URI, or `palette` with a `palette` of your own), answering `{using}`, the name of what they are drawn in | | `theme_delete` | `knotless theme delete` | deletes one of yours by URI, answering `{deleted}` | | `theme_list` | `knotless theme list` | lists themes, everyone's or a `handle`'s, the latest first, or with `find` those whose name starts so, as `{themes: [{uri, name, creator, palette, createdAt}]}` | ## Resources The same server offers resource templates (`resources/templates/list`), so a client that reads MCP resources opens a pull, an issue, a diff or a file by URI rather than by a tool call: omp as `read mcp://knotless://…`, Claude Code as an `@` mention. A URI mirrors the thing's page on knotless.dev under the `knotless://` scheme, `{owner}` a handle or DID and `{did}` and `{rkey}` those of the pull's or issue's `at://` URI, as its page has them. Each answers with what its tool answers, read as the account the tools act as, and `--read-only` serves them all: | Template | Answers as | Type | | --- | --- | --- | | `knotless://{owner}/{repo}/pulls` | `pull_list`, with no `query` | `application/json` | | `knotless://{owner}/{repo}/issues` | `issue_list`, with no `query` | `application/json` | | `knotless://{owner}/{repo}/pull/{did}/{rkey}` | `pull_show` of `at://{did}/dev.knotless.pull/{rkey}` | `application/json` | | `knotless://{owner}/{repo}/pull/{did}/{rkey}/diff` | `pull_diff`, with no `paths`: the numbered diff, or, too long to give whole, its `said` and `files` as JSON | `text/plain` | | `knotless://{owner}/{repo}/issue/{did}/{rkey}` | `issue_show` of `at://{did}/dev.knotless.issue/{rkey}` | `application/json` | | `knotless://{owner}/{repo}/file/{rev}/{+path}` | `repo_read` at `rev`: text as it is, a binary file as a base64 `blob` (`application/octet-stream`) | `text/plain` | A part is percent-escaped as a template's expansion escapes it: a branch `topic/x` is `topic%2Fx` in `{rev}`, a DID's `:` may be `%3A`, and `{+path}` keeps its `/`. `resources/list` lists none: there are too many to list. A URI no template makes is a `-32002` (resource not found) error naming the templates. A read that fails is an error with the message its tool's would have, and one past the account's share of a server's calls (see [Agents](../guide/agents.md#connect-a-remote-client)) says to wait. Older than the appview asks for, a read fails as a tool does. ## Long files An agent pays for what it sends and reads as tokens, so the tools let it change part of a long file without moving all of it: `repo_search` with `path` and `context` finds the lines and what is around them, and `repo_commit` with `edits` sends only what changes. `repo_read` with `start` and `end` reads a part when the search shows too little. The test `an_agent_changes_a_line_of_a_long_file` (`crates/knotless-mcp/tests/mcp.rs`) benches one change both ways, and fails if editing ever costs more than a fiftieth of rewriting: | One line of a 138 KB file | Sent | Received | About | Time | | --- | --- | --- | --- | --- | | Before: `repo_read`, then `repo_commit` with `content` | 143,115 B | 138,207 B | 70,330 tokens | 473 ms | | Now: `repo_search` with `context`, then `repo_commit` with `edits` | 313 B | 634 B | 236 tokens | 351 ms | Tokens are bytes over four. The run behind these numbers is the test's own, against the reference PDS on one machine; `cargo test -p knotless-mcp --test mcp an_agent_changes_a_line_of_a_long_file -- --nocapture` prints them again. The saving grows with the file: what an edit sends does not.