~ knotless Search/
↑↓ move↵ open⇧↵ all results

grain.social/grain

photography-focused photo sharing platform

grain.social
1 collaborator
0
2

mcp: grain-mcp moderates, and never shows a model child safety

Merged chadtmiller.com asked to merge into feature/rust from grain.social/grain:admin/mcp

Only what this pull adds. It is built on grain runs on a Rust server modelled on knotless, without hatk.

crates/grain/src/moderation.rs+16−0
@@ -24,6 +24,7 @@ pub const UPDATE_STATUS: &str = "com.atproto.admin.updateSubjectStatus";
pub const GET_STATUS: &str = "com.atproto.admin.getSubjectStatus";
pub const LIST_REPORTS: &str = "social.grain.admin.listReports";
pub const RESOLVE_REPORT: &str = "social.grain.admin.resolveReport";
pub const GET_REPORT: &str = "social.grain.admin.getReport";
pub const APPLY_LABEL: &str = "social.grain.admin.applyLabel";
pub const NEGATE_LABEL: &str = "social.grain.admin.negateLabel";
pub const LIST_DEFINITIONS: &str = "social.grain.admin.listLabelDefinitions";
@@ -37,6 +38,7 @@ pub const CALLED: &[&str] = &[
GET_STATUS,
LIST_REPORTS,
RESOLVE_REPORT,
GET_REPORT,
APPLY_LABEL,
NEGATE_LABEL,
LIST_DEFINITIONS,
@@ -196,6 +198,13 @@ impl Appview {
self.query(LIST_REPORTS, &params).await
}
/// The report `id`, in any state.
pub async fn report_by_id(&self, id: i64) -> Result<Value> {
let id = id.to_string();
let found = self.query(GET_REPORT, &[("id", &id)]).await?;
Ok(found["report"].clone())
}
/// Closes the open report `id`: resolving it gives out the label it
/// asked for, dismissing it does not.
pub async fn close(&self, id: i64, resolve: bool) -> Result<()> {
@@ -243,6 +252,13 @@ impl Appview {
let did = account(&self.ctx, who).await?;
self.query(GET_SUBJECT, &[("did", &did)]).await
}
/// The labels standing on `patterns`, each a subject or a prefix
/// ending in `*`. Anyone may ask.
pub async fn query_labels(&self, patterns: &[&str]) -> Result<Value> {
let params: Vec<(&str, &str)> = patterns.iter().map(|p| ("uriPatterns", *p)).collect();
self.xrpc.query(QUERY_LABELS, &params, None).await
}
}
#[cfg(test)]
crates/grain-mcp/src/lib.rsnew+498−0
498 lines changed: not loaded until asked for.
crates/grain-mcp/src/main.rsnew+18−0
@@ -0,0 +1,18 @@
//! `grain-mcp`: grain's moderation over MCP on stdin and stdout
//! ([`grain_mcp`]).
fn main() -> std::process::ExitCode {
if std::env::args().len() > 1 {
eprintln!("usage: grain-mcp");
return std::process::ExitCode::from(2);
}
let ctx = grain::env::Ctx::new(grain::env::Env::from_process());
let rt = tokio::runtime::Runtime::new().expect("a runtime builds");
match rt.block_on(grain_mcp::serve_stdio(ctx)) {
Ok(()) => std::process::ExitCode::SUCCESS,
Err(err) => {
eprintln!("grain-mcp: {err:#}");
std::process::ExitCode::FAILURE
}
}
}
crates/grain-mcp/tests/mcp.rsnew+352−0
@@ -0,0 +1,352 @@
//! grain-mcp driven by an MCP client, as an admin of grain-serve on a port
//! of its own, with service tokens from the reference PDS: everything it
//! does, and that nothing a child-safety report or label is about reaches
//! the client.
#[path = "../../grain/tests/common/mod.rs"]
mod net;
use grain::env::Ctx;
use grain_serve::App;
use grain_serve::index::moderation::NewReport;
use grain_serve::index::{Index, Label};
use rmcp::ServiceExt;
use rmcp::model::CallToolRequestParams;
use serde_json::{Value, json};
use std::sync::{Arc, Mutex};
const NOW: &str = "2026-10-31T12:00:00.000Z";
/// The appview's DID: one of where it serves would name its port, which
/// no atproto DID does.
const AUD: &str = "did:web:grain.test";
/// An account a child-safety report is about, and one with a `csae` label.
const BOB: &str = "did:plc:bob";
const DAVE: &str = "did:plc:dave";
/// An account with nothing about the safety of a child.
const CAROL: &str = "did:plc:carol";
/// What must never reach the client.
const SECRETS: &[&str] = &[
"the words of the child-safety report",
"a gallery a child-safety report is about",
"a gallery of an account labeled csae",
"a spam report on an account with a child-safety report",
];
struct World {
base: String,
index: Arc<Index>,
dir: tempfile::TempDir,
}
async fn world(admin: &str) -> World {
let dir = tempfile::tempdir().unwrap();
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let base = format!("http://{}", listener.local_addr().unwrap());
let index = Arc::new(Index::memory().unwrap());
let mut app = App::bare(net::ctx(&dir.path().join("serve"), &[]));
app.index = Some(index.clone());
app.did = Some(AUD.into());
app.admins = vec![admin.to_string()];
tokio::spawn(grain_serve::serve(listener, Arc::new(app)));
for (did, handle, title) in [
(BOB, "bob.test", SECRETS[1]),
(DAVE, "dave.test", SECRETS[2]),
(CAROL, "carol.test", "Carol's rocks"),
] {
index.track(did).unwrap();
index.set_handle(did, handle).unwrap();
let uri = format!("at://{did}/social.grain.gallery/3l3qo2vuowo2a");
let cid = grain::drisl::cid_text(&grain::drisl::cid(uri.as_bytes()));
let record = json!({"$type": "social.grain.gallery", "title": title, "createdAt": NOW});
index.put_at(&uri, &cid, &record, NOW).unwrap();
}
World { base, index, dir }
}
impl World {
/// `account` on this machine, at this appview, logging in by password.
fn ctx(&self, account: &net::Account) -> Ctx {
net::ctx(
&self.dir.path().join(&account.handle),
&[
("GRAIN_APPVIEW", &self.base),
("GRAIN_APPVIEW_DID", AUD),
("GRAIN_IDENTIFIER", &account.did),
("GRAIN_PASSWORD", &account.password),
],
)
}
}
/// An MCP client of grain-mcp, keeping every text it is given.
struct Client {
peer: rmcp::service::RunningService<rmcp::RoleClient, ()>,
seen: Mutex<Vec<String>>,
}
impl Client {
async fn of(ctx: Ctx) -> Client {
let (server_io, client_io) = tokio::io::duplex(1 << 20);
tokio::spawn(async move {
let running = grain_mcp::Server::new(ctx).serve(server_io).await.unwrap();
let _ = running.waiting().await;
});
let peer = ().serve(client_io).await.unwrap();
Client {
peer,
seen: Mutex::default(),
}
}
/// The tool's text, and whether it was an error.
async fn call(&self, tool: &str, args: Value) -> (String, bool) {
let params = CallToolRequestParams::new(tool.to_string())
.with_arguments(args.as_object().unwrap().clone());
let res = self.peer.call_tool(params).await.unwrap();
let text = res.content[0].as_text().unwrap().text.clone();
self.seen.lock().unwrap().push(text.clone());
(text, res.is_error == Some(true))
}
async fn json(&self, tool: &str, args: Value) -> Value {
let (text, err) = self.call(tool, args).await;
assert!(!err, "{tool}: {text}");
serde_json::from_str(&text).unwrap()
}
/// The tool's error.
async fn refused(&self, tool: &str, args: Value) -> String {
let (text, err) = self.call(tool, args).await;
assert!(err, "{tool} was not refused: {text}");
text
}
}
#[tokio::test]
async fn an_admin_moderates_and_never_sees_what_child_safety_is_about() {
let (admin, someone) = (net::account().await, net::account().await);
let w = world(&admin.did).await;
w.index
.label(&Label {
src: AUD.into(),
uri: DAVE.into(),
val: "csae".into(),
neg: false,
cts: NOW.into(),
exp: None,
})
.unwrap();
// Someone reports, by MCP too.
let theirs = Client::of(w.ctx(&someone)).await;
let args = json!({"subject": BOB, "reason": "child-safety", "text": SECRETS[0]});
let csae = theirs.json("report", args).await["id"].as_i64().unwrap();
let args = json!({"subject": CAROL, "reason": "spam", "text": "ads"});
let spam = theirs.json("report", args).await["id"].as_i64().unwrap();
let args = json!({"subject": BOB, "reason": "spam", "text": SECRETS[3]});
let on_bob = theirs.json("report", args).await["id"].as_i64().unwrap();
let why = theirs.refused("list_reports", json!({})).await;
assert!(why.contains("is not an admin of this server"), "{why}");
let c = Client::of(w.ctx(&admin)).await;
// The tools say what they withhold.
let tools = c.peer.list_all_tools().await.unwrap();
for name in [
"list_reports",
"resolve_report",
"takedown",
"label",
"account",
] {
let tool = tools.iter().find(|t| t.name == name).unwrap();
let about = tool.description.as_deref().unwrap_or_default();
assert!(about.contains("child-safety"), "{name}: {about}");
assert!(about.contains("grain command line"), "{name}: {about}");
}
let info = c.peer.peer_info().unwrap();
let said = info.instructions.as_deref().unwrap();
assert!(said.contains("never shown here"), "{said}");
// A child-safety report is listed by its ids alone, and so is any
// other report on its account.
let listed = c.json("list_reports", json!({})).await;
assert_eq!(listed["total"], 3);
let reports = listed["reports"].as_array().unwrap();
let of = |id: i64| reports.iter().find(|r| r["id"] == id).unwrap().clone();
let (shown, plain) = (of(csae), of(spam));
assert_eq!(shown["subject"], BOB);
assert_eq!(shown["label"], "csae");
assert_eq!(shown["status"], "open");
assert_eq!(shown["withheld"], grain_mcp::HUMAN);
assert!(shown.get("reason").is_none(), "{shown}");
let beside = of(on_bob);
assert_eq!(
(&beside["label"], &beside["subjectDid"]),
(&json!("spam"), &json!(BOB))
);
assert_eq!(beside["withheld"], grain_mcp::HUMAN);
assert!(beside.get("reason").is_none(), "{beside}");
assert_eq!(plain["reason"], "ads");
let only = c
.json("list_reports", json!({"label": "spam", "limit": 1}))
.await;
assert_eq!(only["reports"].as_array().unwrap().len(), 1);
// An account with a child-safety report, or a csae label, is shown
// without its records; one with neither, whole.
for actor in [BOB, DAVE] {
let account = c.json("account", json!({"actor": actor})).await;
assert_eq!(account["childSafety"], true, "{account}");
assert_eq!(account["withheld"], grain_mcp::HUMAN);
assert!(account.get("records").is_none(), "{account}");
assert_eq!(account["counts"][0]["count"], 1, "{account}");
}
let bob = c.json("account", json!({"actor": BOB})).await;
let ids: Vec<&Value> = bob["reports"]
.as_array()
.unwrap()
.iter()
.map(|r| &r["id"])
.collect();
assert_eq!(ids.len(), 2, "{bob}");
assert!(
ids.contains(&&json!(csae)) && ids.contains(&&json!(on_bob)),
"{bob}"
);
let carol = c.json("account", json!({"actor": CAROL})).await;
assert_eq!(carol["childSafety"], false);
assert_eq!(carol["records"][0]["value"]["title"], "Carol's rocks");
assert_eq!(carol["reports"][0]["reason"], "ads");
// A report child safety by its reason type alone, whatever label it
// was filed under, is as much a person's.
let by_reason = w
.index
.file_report(&NewReport {
subject: DAVE,
subject_did: DAVE,
label: "other",
reason_type: Some("tools.ozone.report.defs#reasonChildSafetyGroom"),
reason: Some(SECRETS[0]),
reported_by: &someone.did,
created_at: NOW,
})
.unwrap()
.id;
let others = c.json("list_reports", json!({"label": "other"})).await;
assert_eq!(others["reports"][0]["id"], by_reason);
assert_eq!(others["reports"][0]["withheld"], grain_mcp::HUMAN);
let args = json!({"id": by_reason, "action": "dismiss"});
assert_eq!(c.refused("resolve_report", args).await, grain_mcp::HUMAN);
assert_eq!(w.index.report(by_reason).unwrap().unwrap().status, "open");
// Acting on child safety is for a person.
for (tool, args) in [
("resolve_report", json!({"id": csae, "action": "resolve"})),
("resolve_report", json!({"id": csae, "action": "dismiss"})),
("resolve_report", json!({"id": on_bob, "action": "resolve"})),
("takedown", json!({"subject": BOB})),
("takedown", json!({"subject": DAVE, "applied": false})),
(
"takedown",
json!({"subject": format!("at://{BOB}/social.grain.gallery/3l3qo2vuowo2a")}),
),
("label", json!({"subject": BOB, "val": "spam"})),
("label", json!({"subject": CAROL, "val": "csae"})),
(
"label",
json!({"subject": DAVE, "val": "csae", "negate": true}),
),
] {
assert_eq!(
c.refused(tool, args.clone()).await,
grain_mcp::HUMAN,
"{tool} {args}"
);
}
assert_eq!(w.index.report(csae).unwrap().unwrap().status, "open");
// Everything else is the model's to do.
let closed = c
.json("resolve_report", json!({"id": spam, "action": "resolve"}))
.await;
assert_eq!(closed, json!({"id": spam, "status": "resolved"}));
let why = c
.refused("resolve_report", json!({"id": spam, "action": "maybe"}))
.await;
assert_eq!(why, "action is resolve or dismiss");
let why = c
.refused("resolve_report", json!({"id": spam, "action": "dismiss"}))
.await;
assert!(why.contains("ReportNotFound"), "{why}");
// One it cannot read by its id is not closed: it fails closed.
let why = c
.refused("resolve_report", json!({"id": 999, "action": "dismiss"}))
.await;
assert!(why.starts_with("report 999 could not be read"), "{why}");
assert!(why.contains("ReportNotFound"), "{why}");
c.json("takedown", json!({"subject": CAROL})).await;
let status = c.json("moderation_status", json!({"subject": CAROL})).await;
assert_eq!(status["takedown"]["applied"], true);
c.json("takedown", json!({"subject": CAROL, "applied": false}))
.await;
let labeled = c
.json("label", json!({"subject": CAROL, "val": "nudity"}))
.await;
assert_eq!(
labeled,
json!({"subject": CAROL, "val": "nudity", "neg": false})
);
let labels = c.json("query_labels", json!({"patterns": [CAROL]})).await;
let vals: Vec<&str> = labels["labels"]
.as_array()
.unwrap()
.iter()
.map(|l| l["val"].as_str().unwrap())
.collect();
assert_eq!(vals, ["spam", "nudity"]);
let defs = c.json("list_label_definitions", json!({})).await;
assert_eq!(
defs["definitions"].as_array().unwrap().len(),
grain::labels::DEFINITIONS.len()
);
// Nothing a child-safety report or label is about was ever said.
let seen = c.seen.lock().unwrap().join("\n") + &theirs.seen.lock().unwrap().join("\n");
for secret in SECRETS {
assert!(!seen.contains(secret), "{secret} reached the client");
}
}
#[tokio::test]
async fn with_no_login_a_tool_says_so() {
let w = world("did:plc:nobody").await;
let dir = w.dir.path().join("nobody");
let c = Client::of(net::ctx(&dir, &[("GRAIN_APPVIEW", &w.base)])).await;
assert_eq!(
c.refused("list_label_definitions", json!({})).await,
"not logged in"
);
}
#[test]
fn the_binary_takes_no_arguments() {
let bin = env!("CARGO_BIN_EXE_grain-mcp");
let out = std::process::Command::new(bin)
.arg("--nope")
.output()
.unwrap();
assert_eq!(out.status.code(), Some(2));
assert_eq!(
String::from_utf8_lossy(&out.stderr).trim(),
"usage: grain-mcp"
);
// With no client on stdin, it ends.
let out = std::process::Command::new(bin)
.stdin(std::process::Stdio::null())
.env("GRAIN_CONFIG_DIR", tempfile::tempdir().unwrap().path())
.output()
.unwrap();
assert_ne!(out.status.code(), Some(2), "{out:?}");
}
crates/grain-mcp/Cargo.tomlnew+22−0
@@ -0,0 +1,22 @@
[package]
name = "grain-mcp"
description = "An MCP server for grain's moderation, which never shows a model what a child-safety report is about"
version.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
[dependencies]
grain.workspace = true
anyhow.workspace = true
rmcp = { version = "3.6", features = ["server", "transport-io", "macros"] }
schemars = "1"
serde.workspace = true
serde_json.workspace = true
tokio.workspace = true
[dev-dependencies]
grain-serve.workspace = true
reqwest.workspace = true
rmcp = { version = "3.6", features = ["client"] }
tempfile.workspace = true
crates/grain-serve/src/moderation.rs+26−2
@@ -42,8 +42,8 @@ use std::sync::Arc;
use unicode_segmentation::UnicodeSegmentation;
pub use grain::moderation::{
APPLY_LABEL, CREATE_REPORT, GET_STATUS, GET_SUBJECT, LIST_DEFINITIONS, LIST_REPORTS,
NEGATE_LABEL, QUERY_LABELS, RESOLVE_REPORT, UPDATE_STATUS,
APPLY_LABEL, CREATE_REPORT, GET_REPORT, GET_STATUS, GET_SUBJECT, LIST_DEFINITIONS,
LIST_REPORTS, NEGATE_LABEL, QUERY_LABELS, RESOLVE_REPORT, UPDATE_STATUS,
};
const REPO_REF: &str = "com.atproto.admin.defs#repoRef";
@@ -65,6 +65,7 @@ pub fn routes(router: Router<Arc<App>>) -> Router<Arc<App>> {
.route(&at(UPDATE_STATUS), post(update_subject_status))
.route(&at(GET_STATUS), get(get_subject_status))
.route(&at(LIST_REPORTS), get(list_reports))
.route(&at(GET_REPORT), get(get_report))
.route(&at(RESOLVE_REPORT), post(resolve_report))
.route(&at(APPLY_LABEL), post(apply_label))
.route(&at(NEGATE_LABEL), post(negate_label))
@@ -374,6 +375,29 @@ pub async fn list_reports(
Ok(Json(out))
}
/// `social.grain.admin.getReport`: a report by its id, in any state.
pub async fn get_report(
State(app): State<Arc<App>>,
headers: HeaderMap,
uri: Uri,
) -> Result<Json<Value>, Failure> {
let index = index(&app)?;
admin(&app, &headers, GET_REPORT).await?;
let params = params(&uri);
let id = params.get("id").map(str::parse::<i64>);
let Some(Ok(id)) = id else {
return Err(Failure::invalid("id is a report's id"));
};
match index.report(id)? {
Some(report) => Ok(Json(json!({ "report": report_view(&report) }))),
None => Err(Failure::new(
StatusCode::BAD_REQUEST,
"ReportNotFound",
format!("no report {id}"),
)),
}
}
#[derive(Deserialize)]
struct Resolution {
id: i64,
crates/grain-serve/tests/moderation.rs+22−0
@@ -26,6 +26,7 @@ const CAROL: &str = "did:plc:carol";
const REPORT: &str = "/xrpc/com.atproto.moderation.createReport";
const LIST: &str = "/xrpc/social.grain.admin.listReports";
const RESOLVE: &str = "/xrpc/social.grain.admin.resolveReport";
const GET: &str = "/xrpc/social.grain.admin.getReport";
const UPDATE: &str = "/xrpc/com.atproto.admin.updateSubjectStatus";
const STATUS: &str = "/xrpc/com.atproto.admin.getSubjectStatus";
const APPLY: &str = "/xrpc/social.grain.admin.applyLabel";
@@ -257,6 +258,7 @@ async fn only_an_admin_with_a_token_for_the_method_moderates() {
let w = world().await;
let calls = [
(LIST, None),
(GET, None),
(STATUS, None),
(DEFINITIONS, None),
(SUBJECT, None),
@@ -332,6 +334,26 @@ async fn resolving_a_report_gives_out_its_label_and_dismissing_it_does_not() {
);
let dismiss = json!({"id": nudity["id"], "action": "dismiss"});
assert_eq!(w.post(&w.admin, RESOLVE, dismiss).await.0, 200);
// A report is read by its id, in any state.
let (status, got) = w.get(&w.admin, &format!("{GET}?id={}", spam["id"])).await;
assert_eq!(status, 200);
assert_eq!(
(&got["report"]["id"], &got["report"]["status"]),
(&spam["id"], &json!("resolved"))
);
let (status, out) = w.get(&w.admin, &format!("{GET}?id=999")).await;
assert_eq!(
(status, out["error"].as_str()),
(400, Some("ReportNotFound"))
);
for wrong in ["", "?id=one"] {
let (status, out) = w.get(&w.admin, &format!("{GET}{wrong}")).await;
assert_eq!(
(status, out["error"].as_str()),
(400, Some("InvalidRequest")),
"{wrong}"
);
}
assert!(w.index.labels(CAROL).unwrap().is_empty());
let none = json!({"id": 999, "action": "dismiss"});
assert_eq!(
lexicons/social/grain/admin/getReport.jsonnew+24−0
@@ -0,0 +1,24 @@
{
"lexicon": 1,
"id": "social.grain.admin.getReport",
"defs": {
"main": {
"type": "query",
"description": "A report by its id, in any state. Requires an admin session.",
"parameters": {
"type": "params",
"required": ["id"],
"properties": { "id": { "type": "integer" } }
},
"output": {
"encoding": "application/json",
"schema": {
"type": "object",
"required": ["report"],
"properties": { "report": { "type": "ref", "ref": "social.grain.admin.defs#reportView" } }
}
},
"errors": [{ "name": "ReportNotFound", "description": "No report by that id." }]
}
}
}
Cargo.lock+297−0
@@ -52,6 +52,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.104"
@@ -267,6 +276,18 @@ dependencies = [
"rand_core 0.10.1",
]
[[package]]
name = "chrono"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
dependencies = [
"iana-time-zone",
"num-traits",
"serde",
"windows-link",
]
[[package]]
name = "ciborium"
version = "0.2.2"
@@ -333,6 +354,12 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -398,6 +425,40 @@ dependencies = [
"cipher",
]
[[package]]
name = "darling"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff"
dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 3.0.9",
]
[[package]]
name = "darling_macro"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
dependencies = [
"darling_core",
"quote",
"syn 3.0.9",
]
[[package]]
name = "data-encoding"
version = "2.11.1"
@@ -444,6 +505,12 @@ dependencies = [
"syn 3.0.9",
]
[[package]]
name = "dyn-clone"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
[[package]]
name = "ecdsa"
version = "0.16.9"
@@ -804,6 +871,22 @@ dependencies = [
"tempfile",
]
[[package]]
name = "grain-mcp"
version = "0.1.0"
dependencies = [
"anyhow",
"grain",
"grain-serve",
"reqwest",
"rmcp",
"schemars",
"serde",
"serde_json",
"tempfile",
"tokio",
]
[[package]]
name = "grain-serve"
version = "0.1.0"
@@ -1040,6 +1123,30 @@ dependencies = [
"tracing",
]
[[package]]
name = "iana-time-zone"
version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "icu_collections"
version = "2.4.0"
@@ -1123,6 +1230,12 @@ dependencies = [
"zerovec",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "1.1.0"
@@ -1184,6 +1297,8 @@ checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
dependencies = [
"equivalent",
"hashbrown 0.17.1",
"serde",
"serde_core",
]
[[package]]
@@ -1402,6 +1517,12 @@ dependencies = [
"sha2",
]
[[package]]
name = "pastey"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4"
[[package]]
name = "pem-rfc7468"
version = "0.7.0"
@@ -1679,6 +1800,26 @@ dependencies = [
"once_cell",
]
[[package]]
name = "ref-cast"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
]
[[package]]
name = "regex-automata"
version = "0.4.18"
@@ -1786,6 +1927,44 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rmcp"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8be15815186873ea063bf005011d16c356804973489e40c39116ae3a2878d1f"
dependencies = [
"base64 0.23.1",
"chrono",
"futures",
"indexmap",
"libc",
"pastey",
"pin-project-lite",
"rmcp-macros",
"schemars",
"serde",
"serde_json",
"thiserror",
"tokio",
"tokio-stream",
"tokio-util",
"tracing",
"uuid",
]
[[package]]
name = "rmcp-macros"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b0748e054a5f5598b3213cf72099ae6b2745ba4d996c59ccf455150a7dbb839"
dependencies = [
"darling",
"proc-macro2",
"quote",
"serde_json",
"syn 3.0.9",
]
[[package]]
name = "roxmltree"
version = "0.21.2"
@@ -1886,6 +2065,32 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "schemars"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e"
dependencies = [
"chrono",
"dyn-clone",
"ref-cast",
"schemars_derive",
"serde",
"serde_json",
]
[[package]]
name = "schemars_derive"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1c3a92094fa7d61aa124645844facb6b554dfc797136d0f5fd1f890e2bffc69"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 3.0.9",
]
[[package]]
name = "sec1"
version = "0.7.3"
@@ -1930,6 +2135,17 @@ dependencies = [
"syn 3.0.9",
]
[[package]]
name = "serde_derive_internals"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.9",
]
[[package]]
name = "serde_json"
version = "1.0.154"
@@ -2122,6 +2338,12 @@ dependencies = [
"float-cmp",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
@@ -2301,6 +2523,17 @@ dependencies = [
"tokio",
]
[[package]]
name = "tokio-stream"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-tungstenite"
version = "0.29.0"
@@ -2573,6 +2806,17 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3b425286980a367278453f3b6d28cdcb134b574e5a35824a6ff5eebad50bb6"
[[package]]
name = "uuid"
version = "1.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514"
dependencies = [
"getrandom 0.4.3",
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "valuable"
version = "0.1.1"
@@ -2714,12 +2958,65 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
Cargo.toml+1−1
@@ -1,5 +1,5 @@
[workspace]
members = ["crates/grain", "crates/grain-import", "crates/grain-serve"]
members = ["crates/grain", "crates/grain-import", "crates/grain-mcp", "crates/grain-serve"]
resolver = "3"
[workspace.package]
hatk.generated.client.ts+1−1
@@ -3,7 +3,7 @@
// to avoid pulling in server-only dependencies.
export type { XrpcSchema } from './hatk.generated.ts'
import type { XrpcSchema } from './hatk.generated.ts'
export type { BskyActorProfile, Post, Postgate, Threadgate, BskyGraphFollow, GetSubjectStatus, UpdateSubjectStatus, QueryLabels, AtprotoModerationCreateReport, GermnetworkDeclaration, HatkApplyWrites, HatkCreateReport, DescribeCollections, HatkDescribeFeeds, DescribeLabels, HatkGetFeed, HatkGetPreferences, GetRecord, GetRecords, PutPreference, HatkSearchRecords, HatkUploadBlob, OpensocialDeclaration, Search, GrainActorGetFavorites, GrainActorGetPreferences, GetProfile, GrainActorGetSuggestedFollows, GrainActorProfile, PutPreferences, SearchActors, GrainActorSearchActorsTypeahead, ActOnReview, AddRepos, ApplyLabel, GetInfo, GetRepo, GetReviewQueue, GetReviewStats, GetReviewSubject, ListLabelDefinitions, ListReports, ListRepos, NegateLabel, RemoveRepos, ResolveReport, ResyncRepos, GrainAdminSearchRecords, StartScan, Comment, GetThread, Favorite, GrainFeedDescribeFeeds, GrainFeedGetFeed, Gallery, GrainGalleryDeleteGallery, GrainGalleryGetCameras, GrainGalleryGetFavorites, GrainGalleryGetGallery, GrainGalleryGetLocations, GrainGalleryItem, GrainGallerySearchGalleries, Block, GrainGraphFollow, GrainGraphGetBlocks, GrainGraphGetFollowers, GrainGraphGetFollowing, GrainGraphGetKnownFollowers, GrainGraphGetMutes, MuteActor, UnmuteActor, GrainGroupCreateGroup, Decline, GrainGroupGetGroup, GrainGroupGetGroupHost, GetSubmissions, GrainGroupItem, GrainGroupJoinGroup, GrainGroupLeaveGroup, GrainGroupListGroups, CreateComment, CreateGallery, DeleteComment, GrainGroupPoolDeleteGallery, GrainGroupPoolGetGallery, ListFeed, ListGalleries, PutFavorite, Submission, GetUnreadCount, ListNotifications, RegisterPush, UnregisterPush, UpdateSeen, Photo, Exif, AddMember, GrainPrivateGalleryCreatePrivateGallery, GrainPrivateGalleryGetPrivateGallery, GrainPrivateGalleryGetSpaceSupport, ListMembers, GrainPrivateGalleryListPrivateGalleries, ListSharedPrivateGalleries, GrainRepoApplyWrites, GrainRepoCreateRecord, GrainRepoDeleteRecord, GrainRepoPutRecord, GrainRepoUploadBlob, GrainServerDeleteAccount, GrainServerGetSitemap, Story, GetArchive, GetAuthors, GrainStoryGetStories, GrainStoryGetStory, MarkViewed, IndexRecords, AddSpaceMember, GrainUnspeccedCreateGroup, CreatePoolComment, CreatePoolGallery, GrainUnspeccedCreatePrivateGallery, GrainUnspeccedDeleteAccount, GrainUnspeccedDeleteGallery, DeletePoolComment, DeletePoolGallery, GetActorFavorites, GetActorProfile, GrainUnspeccedGetBlocks, GrainUnspeccedGetCameras, GetCommentThread, GrainUnspeccedGetFollowers, GrainUnspeccedGetFollowing, GrainUnspeccedGetGallery, GetGalleryFavorites, GrainUnspeccedGetGroup, GrainUnspeccedGetGroupHost, GetGroupSubmissions, GrainUnspeccedGetKnownFollowers, GrainUnspeccedGetLocations, GrainUnspeccedGetMutes, GetNotifications, GetPoolGallery, GrainUnspeccedGetPrivateGallery, GrainUnspeccedGetSitemap, GetSpaceMembers, GrainUnspeccedGetSpaceSupport, GrainUnspeccedGetStories, GrainUnspeccedGetStory, GetStoryArchive, GetStoryAuthors, GrainUnspeccedGetSuggestedFollows, GrainUnspeccedJoinGroup, GrainUnspeccedLeaveGroup, GrainUnspeccedListGroups, ListPoolFeed, ListPoolGalleries, GrainUnspeccedListPrivateGalleries, ListSharedGalleries, MarkStoriesViewed, PutPoolFavorite, GrainUnspeccedSearchActorsTypeahead, GrainUnspeccedSearchGalleries, SearchProfiles, RecordRegistry, CreateRecord, DeleteRecord, PutRecord, Nux, MutedWord, SavedFeed, StatusView, BskyActorDefsProfileView, BskyActorDefsViewerState, FeedViewPref, LabelersPref, InterestsPref, KnownFollowers, MutedWordsPref, SavedFeedsPref, ThreadViewPref, DeclaredAgePref, HiddenPostsPref, LabelerPrefItem, AdultContentPref, BskyAppStatePref, ContentLabelPref, ProfileViewBasic, SavedFeedsPrefV2, VerificationView, ProfileAssociated, VerificationPrefs, VerificationState, PersonalDetailsPref, BskyActorDefsProfileViewDetailed, BskyAppProgressGuide, LiveEventPreferences, ProfileAssociatedChat, ProfileAssociatedGerm, PostInteractionSettingsPref, ProfileAssociatedActivitySubscription, BskyEmbedDefsAspectRatio, ExternalView, External, ViewExternal, ImagesView, Image, ViewImage, RecordView, ViewRecord, ViewBlocked, ViewDetached, ViewNotFound, RecordWithMediaView, VideoView, Caption, PostView, BskyFeedDefsReplyRef, ReasonPin, BlockedPost, Interaction, BskyFeedDefsViewerState, FeedViewPost, NotFoundPost, ReasonRepost, BlockedAuthor, GeneratorView, ThreadContext, ThreadViewPost, ThreadgateView, SkeletonFeedPost, SkeletonReasonPin, GeneratorViewerState, SkeletonReasonRepost, Entity, PostReplyRef, TextSlice, DisableRule, ListRule, MentionRule, FollowerRule, FollowingRule, ListView, ListItemView, Relationship, ListViewBasic, NotFoundActor, ListViewerState, StarterPackView, StarterPackViewBasic, LabelerView, LabelerPolicies, LabelerViewerState, LabelerViewDetailed, Preference, Preferences, RecordDeleted, ChatPreference, ActivitySubscription, FilterablePreference, SubjectActivitySubscription, Tag, Link, Mention, ByteSlice, AtprotoAdminDefsRepoRef, RepoBlobRef, StatusAttr, Label, SelfLabels, SelfLabel, LabelValueDefinition, LabelValueDefinitionStrings, ModTool, GermnetworkDeclarationMessageMe, HatkApplyWritesCreate, HatkApplyWritesUpdate, HatkApplyWritesDelete, HatkApplyWritesCreateResult, HatkApplyWritesUpdateResult, HatkApplyWritesDeleteResult, HatkCreateReportRepoRef, LabelDefinition, LabelLocale, Result, MentionLabel, EmbedInfo, SearchAspectRatio, SubscopeInfo, GrainActorDefsProfileView, GrainActorDefsProfileViewDetailed, GrainActorDefsMessageMe, GrainActorDefsViewerState, GrainActorDefsProfileSearchResult, TypeaheadItem, SuggestedFollow, PinnedFeedsPref, PinnedFeed, PhotoMetadataPref, ThemePref, NotificationsPref, NotificationPref, ReportView, SignalView, ReviewItem, GrainAdminDefsRecordView, CollectionCount, ScanProgress, ClassifierCount, RepoView, StatusCount, CommentView, GrainCommentDefsViewerState, GrainDefsAspectRatio, FeedView, GalleryView, CrossPostInfo, GrainGalleryDefsViewerState, GrainGalleryDefsFavoriteItem, GrainGalleryDefsCameraItem, GrainGalleryDefsLocationItem, GrainGraphDefsFollowerItem, GrainGraphDefsViewerState, GrainGraphDefsFollowingItem, KnownFollowerItem, GrainGraphDefsBlockItem, GrainGraphDefsMuteItem, GroupView, GrainGroupDefsViewerState, SubmissionState, GroupRef, SubmissionView, Rule, GrainGroupPoolDefsPoolFeedItem, GrainGroupPoolDefsPoolGallery, GrainGroupPoolDefsPoolComment, GrainGroupPoolDefsPoolAuthor, GrainNotificationDefsNotificationItem, PhotoView, ExifView, GalleryState, GrainPrivateGalleryDefsPhotoInput, GrainPrivateGalleryDefsPrivateGallery, GrainPrivateGalleryDefsPrivateItem, GrainPrivateGalleryDefsPrivateGalleryRef, GrainPrivateGalleryDefsSharedGalleryRef, GrainPrivateGalleryDefsMemberView, GrainRepoApplyWritesCreate, GrainRepoApplyWritesUpdate, GrainRepoApplyWritesDelete, GrainRepoApplyWritesCreateResult, GrainRepoApplyWritesUpdateResult, GrainRepoApplyWritesDeleteResult, Written, GrainServerDefsSitemapEntry, StoryView, GrainStoryDefsViewerState, GrainStoryDefsStoryAuthor, CreatePoolGalleryPhotoInput, GrainUnspeccedCreatePrivateGalleryPhotoInput, GrainUnspeccedGetBlocksBlockItem, GrainUnspeccedGetCamerasCameraItem, GrainUnspeccedGetFollowersFollowerItem, GrainUnspeccedGetFollowersViewerState, GrainUnspeccedGetFollowingFollowingItem, GrainUnspeccedGetFollowingViewerState, GetGalleryFavoritesFavoriteItem, GrainUnspeccedGetKnownFollowersFollowerItem, GrainUnspeccedGetLocationsLocationItem, GrainUnspeccedGetMutesMuteItem, GetNotificationsNotificationItem, GetPoolGalleryPoolComment, GetPoolGalleryPoolAuthor, GrainUnspeccedGetPrivateGalleryPrivateGallery, GrainUnspeccedGetPrivateGalleryPrivateItem, GrainUnspeccedGetSitemapSitemapEntry, GetSpaceMembersMemberView, GetStoryAuthorsStoryAuthor, SuggestedItem, ListPoolFeedPoolFeedItem, ListPoolGalleriesPoolGallery, GrainUnspeccedListPrivateGalleriesPrivateGalleryRef, ListSharedGalleriesSharedGalleryRef, SearchProfilesProfileSearchResult } from './hatk.generated.ts'
export type { BskyActorProfile, Post, Postgate, Threadgate, BskyGraphFollow, GetSubjectStatus, UpdateSubjectStatus, QueryLabels, AtprotoModerationCreateReport, GermnetworkDeclaration, HatkApplyWrites, HatkCreateReport, DescribeCollections, HatkDescribeFeeds, DescribeLabels, HatkGetFeed, HatkGetPreferences, GetRecord, GetRecords, PutPreference, HatkSearchRecords, HatkUploadBlob, OpensocialDeclaration, Search, GrainActorGetFavorites, GrainActorGetPreferences, GetProfile, GrainActorGetSuggestedFollows, GrainActorProfile, PutPreferences, SearchActors, GrainActorSearchActorsTypeahead, ActOnReview, AddRepos, ApplyLabel, GetInfo, GetRepo, GetReport, GetReviewQueue, GetReviewStats, GetReviewSubject, ListLabelDefinitions, ListReports, ListRepos, NegateLabel, RemoveRepos, ResolveReport, ResyncRepos, GrainAdminSearchRecords, StartScan, Comment, GetThread, Favorite, GrainFeedDescribeFeeds, GrainFeedGetFeed, Gallery, GrainGalleryDeleteGallery, GrainGalleryGetCameras, GrainGalleryGetFavorites, GrainGalleryGetGallery, GrainGalleryGetLocations, GrainGalleryItem, GrainGallerySearchGalleries, Block, GrainGraphFollow, GrainGraphGetBlocks, GrainGraphGetFollowers, GrainGraphGetFollowing, GrainGraphGetKnownFollowers, GrainGraphGetMutes, MuteActor, UnmuteActor, GrainGroupCreateGroup, Decline, GrainGroupGetGroup, GrainGroupGetGroupHost, GetSubmissions, GrainGroupItem, GrainGroupJoinGroup, GrainGroupLeaveGroup, GrainGroupListGroups, CreateComment, CreateGallery, DeleteComment, GrainGroupPoolDeleteGallery, GrainGroupPoolGetGallery, ListFeed, ListGalleries, PutFavorite, Submission, GetUnreadCount, ListNotifications, RegisterPush, UnregisterPush, UpdateSeen, Photo, Exif, AddMember, GrainPrivateGalleryCreatePrivateGallery, GrainPrivateGalleryGetPrivateGallery, GrainPrivateGalleryGetSpaceSupport, ListMembers, GrainPrivateGalleryListPrivateGalleries, ListSharedPrivateGalleries, GrainRepoApplyWrites, GrainRepoCreateRecord, GrainRepoDeleteRecord, GrainRepoPutRecord, GrainRepoUploadBlob, GrainServerDeleteAccount, GrainServerGetSitemap, Story, GetArchive, GetAuthors, GrainStoryGetStories, GrainStoryGetStory, MarkViewed, IndexRecords, AddSpaceMember, GrainUnspeccedCreateGroup, CreatePoolComment, CreatePoolGallery, GrainUnspeccedCreatePrivateGallery, GrainUnspeccedDeleteAccount, GrainUnspeccedDeleteGallery, DeletePoolComment, DeletePoolGallery, GetActorFavorites, GetActorProfile, GrainUnspeccedGetBlocks, GrainUnspeccedGetCameras, GetCommentThread, GrainUnspeccedGetFollowers, GrainUnspeccedGetFollowing, GrainUnspeccedGetGallery, GetGalleryFavorites, GrainUnspeccedGetGroup, GrainUnspeccedGetGroupHost, GetGroupSubmissions, GrainUnspeccedGetKnownFollowers, GrainUnspeccedGetLocations, GrainUnspeccedGetMutes, GetNotifications, GetPoolGallery, GrainUnspeccedGetPrivateGallery, GrainUnspeccedGetSitemap, GetSpaceMembers, GrainUnspeccedGetSpaceSupport, GrainUnspeccedGetStories, GrainUnspeccedGetStory, GetStoryArchive, GetStoryAuthors, GrainUnspeccedGetSuggestedFollows, GrainUnspeccedJoinGroup, GrainUnspeccedLeaveGroup, GrainUnspeccedListGroups, ListPoolFeed, ListPoolGalleries, GrainUnspeccedListPrivateGalleries, ListSharedGalleries, MarkStoriesViewed, PutPoolFavorite, GrainUnspeccedSearchActorsTypeahead, GrainUnspeccedSearchGalleries, SearchProfiles, RecordRegistry, CreateRecord, DeleteRecord, PutRecord, Nux, MutedWord, SavedFeed, StatusView, BskyActorDefsProfileView, BskyActorDefsViewerState, FeedViewPref, LabelersPref, InterestsPref, KnownFollowers, MutedWordsPref, SavedFeedsPref, ThreadViewPref, DeclaredAgePref, HiddenPostsPref, LabelerPrefItem, AdultContentPref, BskyAppStatePref, ContentLabelPref, ProfileViewBasic, SavedFeedsPrefV2, VerificationView, ProfileAssociated, VerificationPrefs, VerificationState, PersonalDetailsPref, BskyActorDefsProfileViewDetailed, BskyAppProgressGuide, LiveEventPreferences, ProfileAssociatedChat, ProfileAssociatedGerm, PostInteractionSettingsPref, ProfileAssociatedActivitySubscription, BskyEmbedDefsAspectRatio, ExternalView, External, ViewExternal, ImagesView, Image, ViewImage, RecordView, ViewRecord, ViewBlocked, ViewDetached, ViewNotFound, RecordWithMediaView, VideoView, Caption, PostView, BskyFeedDefsReplyRef, ReasonPin, BlockedPost, Interaction, BskyFeedDefsViewerState, FeedViewPost, NotFoundPost, ReasonRepost, BlockedAuthor, GeneratorView, ThreadContext, ThreadViewPost, ThreadgateView, SkeletonFeedPost, SkeletonReasonPin, GeneratorViewerState, SkeletonReasonRepost, Entity, PostReplyRef, TextSlice, DisableRule, ListRule, MentionRule, FollowerRule, FollowingRule, ListView, ListItemView, Relationship, ListViewBasic, NotFoundActor, ListViewerState, StarterPackView, StarterPackViewBasic, LabelerView, LabelerPolicies, LabelerViewerState, LabelerViewDetailed, Preference, Preferences, RecordDeleted, ChatPreference, ActivitySubscription, FilterablePreference, SubjectActivitySubscription, Tag, Link, Mention, ByteSlice, AtprotoAdminDefsRepoRef, RepoBlobRef, StatusAttr, Label, SelfLabels, SelfLabel, LabelValueDefinition, LabelValueDefinitionStrings, ModTool, GermnetworkDeclarationMessageMe, HatkApplyWritesCreate, HatkApplyWritesUpdate, HatkApplyWritesDelete, HatkApplyWritesCreateResult, HatkApplyWritesUpdateResult, HatkApplyWritesDeleteResult, HatkCreateReportRepoRef, LabelDefinition, LabelLocale, Result, MentionLabel, EmbedInfo, SearchAspectRatio, SubscopeInfo, GrainActorDefsProfileView, GrainActorDefsProfileViewDetailed, GrainActorDefsMessageMe, GrainActorDefsViewerState, GrainActorDefsProfileSearchResult, TypeaheadItem, SuggestedFollow, PinnedFeedsPref, PinnedFeed, PhotoMetadataPref, ThemePref, NotificationsPref, NotificationPref, ReportView, SignalView, ReviewItem, GrainAdminDefsRecordView, CollectionCount, ScanProgress, ClassifierCount, RepoView, StatusCount, CommentView, GrainCommentDefsViewerState, GrainDefsAspectRatio, FeedView, GalleryView, CrossPostInfo, GrainGalleryDefsViewerState, GrainGalleryDefsFavoriteItem, GrainGalleryDefsCameraItem, GrainGalleryDefsLocationItem, GrainGraphDefsFollowerItem, GrainGraphDefsViewerState, GrainGraphDefsFollowingItem, KnownFollowerItem, GrainGraphDefsBlockItem, GrainGraphDefsMuteItem, GroupView, GrainGroupDefsViewerState, SubmissionState, GroupRef, SubmissionView, Rule, GrainGroupPoolDefsPoolFeedItem, GrainGroupPoolDefsPoolGallery, GrainGroupPoolDefsPoolComment, GrainGroupPoolDefsPoolAuthor, GrainNotificationDefsNotificationItem, PhotoView, ExifView, GalleryState, GrainPrivateGalleryDefsPhotoInput, GrainPrivateGalleryDefsPrivateGallery, GrainPrivateGalleryDefsPrivateItem, GrainPrivateGalleryDefsPrivateGalleryRef, GrainPrivateGalleryDefsSharedGalleryRef, GrainPrivateGalleryDefsMemberView, GrainRepoApplyWritesCreate, GrainRepoApplyWritesUpdate, GrainRepoApplyWritesDelete, GrainRepoApplyWritesCreateResult, GrainRepoApplyWritesUpdateResult, GrainRepoApplyWritesDeleteResult, Written, GrainServerDefsSitemapEntry, StoryView, GrainStoryDefsViewerState, GrainStoryDefsStoryAuthor, CreatePoolGalleryPhotoInput, GrainUnspeccedCreatePrivateGalleryPhotoInput, GrainUnspeccedGetBlocksBlockItem, GrainUnspeccedGetCamerasCameraItem, GrainUnspeccedGetFollowersFollowerItem, GrainUnspeccedGetFollowersViewerState, GrainUnspeccedGetFollowingFollowingItem, GrainUnspeccedGetFollowingViewerState, GetGalleryFavoritesFavoriteItem, GrainUnspeccedGetKnownFollowersFollowerItem, GrainUnspeccedGetLocationsLocationItem, GrainUnspeccedGetMutesMuteItem, GetNotificationsNotificationItem, GetPoolGalleryPoolComment, GetPoolGalleryPoolAuthor, GrainUnspeccedGetPrivateGalleryPrivateGallery, GrainUnspeccedGetPrivateGalleryPrivateItem, GrainUnspeccedGetSitemapSitemapEntry, GetSpaceMembersMemberView, GetStoryAuthorsStoryAuthor, SuggestedItem, ListPoolFeedPoolFeedItem, ListPoolGalleriesPoolGallery, GrainUnspeccedListPrivateGalleriesPrivateGalleryRef, ListSharedGalleriesSharedGalleryRef, SearchProfilesProfileSearchResult } from './hatk.generated.ts'
const _procedures = new Set(['com.atproto.admin.updateSubjectStatus', 'com.atproto.moderation.createReport', 'dev.hatk.applyWrites', 'dev.hatk.createRecord', 'dev.hatk.createReport', 'dev.hatk.deleteRecord', 'dev.hatk.putPreference', 'dev.hatk.putRecord', 'social.grain.actor.putPreferences', 'social.grain.admin.actOnReview', 'social.grain.admin.addRepos', 'social.grain.admin.applyLabel', 'social.grain.admin.negateLabel', 'social.grain.admin.removeRepos', 'social.grain.admin.resolveReport', 'social.grain.admin.resyncRepos', 'social.grain.admin.startScan', 'social.grain.gallery.deleteGallery', 'social.grain.graph.muteActor', 'social.grain.graph.unmuteActor', 'social.grain.group.createGroup', 'social.grain.group.joinGroup', 'social.grain.group.leaveGroup', 'social.grain.group.pool.createComment', 'social.grain.group.pool.createGallery', 'social.grain.group.pool.deleteComment', 'social.grain.group.pool.deleteGallery', 'social.grain.group.pool.putFavorite', 'social.grain.notification.registerPush', 'social.grain.notification.unregisterPush', 'social.grain.notification.updateSeen', 'social.grain.privateGallery.addMember', 'social.grain.privateGallery.createPrivateGallery', 'social.grain.repo.applyWrites', 'social.grain.repo.createRecord', 'social.grain.repo.deleteRecord', 'social.grain.repo.putRecord', 'social.grain.server.deleteAccount', 'social.grain.story.markViewed', 'social.grain.sync.indexRecords', 'social.grain.unspecced.addSpaceMember', 'social.grain.unspecced.createGroup', 'social.grain.unspecced.createPoolComment', 'social.grain.unspecced.createPoolGallery', 'social.grain.unspecced.createPrivateGallery', 'social.grain.unspecced.deleteAccount', 'social.grain.unspecced.deleteGallery', 'social.grain.unspecced.deletePoolComment', 'social.grain.unspecced.deletePoolGallery', 'social.grain.unspecced.joinGroup', 'social.grain.unspecced.leaveGroup', 'social.grain.unspecced.markStoriesViewed', 'social.grain.unspecced.putPoolFavorite'])
const _blobInputs = new Set(['dev.hatk.uploadBlob', 'social.grain.repo.uploadBlob'])
hatk.generated.ts+4−0