Skip to main content

ai_crew_sync/tools/
notes.rs

1use rmcp::{
2    ErrorData, Json, handler::server::wrapper::Parameters, service::RequestContext, tool,
3    tool_router,
4};
5use schemars::JsonSchema;
6use serde::Deserialize;
7
8use super::{Bus, auth_of};
9use crate::{
10    model::{Ack, NoteInfo, NoteList, NoteRef},
11    store::notes,
12};
13
14fn default_limit() -> i64 {
15    50
16}
17
18#[derive(Debug, Deserialize, JsonSchema)]
19pub struct SetNoteArgs {
20    /// Namespace for the note, typically a repository or project name.
21    /// Defaults to "global". At most 64 bytes.
22    #[serde(default)]
23    pub scope: Option<String>,
24    /// Short identifier, e.g. "deploy-runbook" or "why-we-dropped-redis".
25    /// At most 256 bytes: a key is a name, the content goes in `value`.
26    pub key: String,
27    /// The content. Write it for a teammate's agent reading it cold, with no
28    /// memory of this conversation. Hard limit 1 MiB.
29    pub value: String,
30    /// Optional tags for filtering, e.g. ["infra", "postmortem"].
31    #[serde(default)]
32    pub tags: Option<Vec<String>>,
33}
34
35#[derive(Debug, Deserialize, JsonSchema)]
36pub struct GetNoteArgs {
37    /// Namespace. Defaults to "global".
38    #[serde(default)]
39    pub scope: Option<String>,
40    /// The note key.
41    pub key: String,
42}
43
44#[derive(Debug, Deserialize, JsonSchema)]
45pub struct ListNotesArgs {
46    /// Restrict to one namespace. Omit to list every scope.
47    #[serde(default)]
48    pub scope: Option<String>,
49    /// Only return notes carrying this tag.
50    #[serde(default)]
51    pub tag: Option<String>,
52    /// Maximum notes to return (1-200).
53    #[serde(default = "default_limit")]
54    pub limit: i64,
55}
56
57#[derive(Debug, Deserialize, JsonSchema)]
58pub struct SearchNotesArgs {
59    /// Full-text search terms, matched against note keys and content.
60    pub query: String,
61    /// Restrict to one namespace.
62    #[serde(default)]
63    pub scope: Option<String>,
64    /// Maximum notes to return (1-200).
65    #[serde(default = "default_limit")]
66    pub limit: i64,
67}
68
69#[tool_router(router = notes_router, vis = "pub")]
70impl Bus {
71    #[tool(
72        description = "Write or overwrite a shared note: a decision, a gotcha, the state \
73                       of a deploy. This is the team's durable memory, readable by every \
74                       teammate's agent. Previous versions are kept."
75    )]
76    async fn set_note(
77        &self,
78        ctx: RequestContext<rmcp::RoleServer>,
79        Parameters(args): Parameters<SetNoteArgs>,
80    ) -> Result<Json<NoteInfo>, ErrorData> {
81        let auth = auth_of(&ctx)?;
82        let input = notes::SetInput {
83            scope: args.scope,
84            key: args.key,
85            value: args.value,
86            tags: args.tags,
87        };
88        Ok(Json(notes::set_note(&self.db, &auth, input).await?))
89    }
90
91    #[tool(
92        description = "Read one shared note by scope and key. Returns found=false rather \
93                       than erroring when the note does not exist."
94    )]
95    async fn get_note(
96        &self,
97        ctx: RequestContext<rmcp::RoleServer>,
98        Parameters(args): Parameters<GetNoteArgs>,
99    ) -> Result<Json<NoteRef>, ErrorData> {
100        let auth = auth_of(&ctx)?;
101        Ok(Json(
102            notes::get_note(&self.db, &auth, args.scope, &args.key).await?,
103        ))
104    }
105
106    #[tool(
107        description = "List shared notes, optionally filtered by scope or tag. Use this \
108                       to discover what the team already wrote down."
109    )]
110    async fn list_notes(
111        &self,
112        ctx: RequestContext<rmcp::RoleServer>,
113        Parameters(args): Parameters<ListNotesArgs>,
114    ) -> Result<Json<NoteList>, ErrorData> {
115        let auth = auth_of(&ctx)?;
116        Ok(Json(
117            notes::list_notes(&self.db, &auth, args.scope, args.tag, args.limit).await?,
118        ))
119    }
120
121    #[tool(description = "Full-text search the team's shared notes.")]
122    async fn search_notes(
123        &self,
124        ctx: RequestContext<rmcp::RoleServer>,
125        Parameters(args): Parameters<SearchNotesArgs>,
126    ) -> Result<Json<NoteList>, ErrorData> {
127        let auth = auth_of(&ctx)?;
128        Ok(Json(
129            notes::search_notes(&self.db, &auth, &args.query, args.scope, args.limit).await?,
130        ))
131    }
132
133    #[tool(description = "Delete a shared note. Its revision history goes with it.")]
134    async fn delete_note(
135        &self,
136        ctx: RequestContext<rmcp::RoleServer>,
137        Parameters(args): Parameters<GetNoteArgs>,
138    ) -> Result<Json<Ack>, ErrorData> {
139        let auth = auth_of(&ctx)?;
140        Ok(Json(
141            notes::delete_note(&self.db, &auth, args.scope, &args.key).await?,
142        ))
143    }
144}