chadtmiller.com/knotless

Git hosted in your atproto account, on any stock PDS, fast enough for agents.

git clone knotless://chadtmiller.com/knotless

needs the knotless git helper. set it up

MCP: edit_item and delete_comment tools

chadtmiller.com committed on Oct 22, 2026 parent 8fabb64 commit 4d3a16d
Showing 1 changed file with 51 additions and 1 deletion
crates/knotless-mcp/src/lib.rs+51−1
@@ -363,6 +363,24 @@ pub struct Remark {
pub verdict: Option<String>,
}
#[derive(Deserialize, schemars::JsonSchema)]
pub struct Edit {
pub repo: String,
/// The at:// URI of your pull, issue or comment.
pub uri: String,
/// A pull's or issue's new title.
pub title: Option<String>,
/// The new body, in Markdown. Empty takes a pull's or issue's body away.
pub body: Option<String>,
}
#[derive(Deserialize, schemars::JsonSchema)]
pub struct Unsay {
pub repo: String,
/// The at:// URI of your comment or review.
pub uri: String,
}
#[derive(Deserialize, schemars::JsonSchema)]
pub struct SetState {
pub repo: String,
@@ -789,6 +807,7 @@ impl Server {
"verdict": c.value["verdict"],
"commit": c.value["commit"],
"createdAt": c.value["createdAt"],
"updatedAt": c.value["updatedAt"],
})
})
.collect();
@@ -858,6 +877,37 @@ impl Server {
)
}
#[tool(
description = "Change what your pull request, issue or comment says: a pull's or issue's title and body, or a comment's body. Only its author can. This is the way to update a pull's description."
)]
async fn edit_item(&self, Parameters(p): Parameters<Edit>) -> Result<CallToolResult, McpError> {
answer(
async {
let url = repo_url(&p.repo)?;
let (title, body) = (p.title.as_deref(), p.body.as_deref());
forge::edit(&self.ctx, &url, &p.uri, title, body).await?;
Ok(json!({"edited": p.uri}))
}
.await,
)
}
#[tool(
description = "Delete your comment or review. Only its author can; a repository's owner hides others' with hide_item instead. Pulls and issues are closed with set_state, not deleted."
)]
async fn delete_comment(
&self,
Parameters(p): Parameters<Unsay>,
) -> Result<CallToolResult, McpError> {
answer(
async {
forge::delete_comment(&self.ctx, &repo_url(&p.repo)?, &p.uri).await?;
Ok(json!({"deleted": p.uri}))
}
.await,
)
}
#[tool(
description = "Close or reopen a pull request or issue. Its author, the owner and collaborators may."
)]
@@ -1043,7 +1093,7 @@ impl Server {
#[tool_handler(
name = "knotless",
instructions = "Git repositories stored in atproto accounts. Name a repository as handle/name. Read with list_tree, read_file, search, log and diff: no clone needed. Change files with commit: no checkout needed. Collaborate with fork, open_pull, open_issue, list_items, show_item, comment, set_state and merge. Moderate your repositories with hide_item and block_account, and report with report."
instructions = "Git repositories stored in atproto accounts. Name a repository as handle/name. Read with list_tree, read_file, search, log and diff: no clone needed. Change files with commit: no checkout needed. Collaborate with fork, open_pull, open_issue, list_items, show_item, comment, set_state and merge; change what you wrote with edit_item, and take a comment back with delete_comment. Moderate your repositories with hide_item and block_account, and report with report."
)]
impl ServerHandler for Server {}