Skip to main content

agentmux_mcp/tools/
memory.rs

1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Personal Memory.
5
6use super::*;
7
8pub(super) async fn call(name: &str, arguments: &Value, cx: &ToolCtx<'_>) -> Result<String> {
9    let ToolCtx { local_url, auth_key, block_id, client, .. } = *cx;
10    match name {
11        "MemoryList" => {
12            require_agent_env(local_url, auth_key, block_id)?;
13            let agent_id = agent_slug()?;
14            let url = format!("{}/api/v1/agent/memory/list", local_url.trim_end_matches('/'));
15            let resp = client
16                .get(&url)
17                .header(AUTH_KEY_HEADER, auth_key)
18                .query(&[("agent_id", agent_id.as_str())])
19                .send()
20                .await
21                .map_err(|e| anyhow::anyhow!("request failed: {e}"))?;
22            if !resp.status().is_success() {
23                let status = resp.status();
24                let text = resp.text().await.unwrap_or_default();
25                anyhow::bail!("memory/list failed: HTTP {status} — {text}");
26            }
27            let result: Value = resp
28                .json()
29                .await
30                .map_err(|e| anyhow::anyhow!("response parse failed: {e}"))?;
31            Ok(serde_json::to_string_pretty(&result).unwrap_or_else(|_| result.to_string()))
32        }
33        "MemoryRead" => {
34            let filename = arguments
35                .get("filename")
36                .and_then(|v| v.as_str())
37                .filter(|s| !s.is_empty())
38                .ok_or_else(|| anyhow::anyhow!("missing required parameter: filename"))?;
39            require_agent_env(local_url, auth_key, block_id)?;
40            let agent_id = agent_slug()?;
41            let url = format!("{}/api/v1/agent/memory/read", local_url.trim_end_matches('/'));
42            let resp = client
43                .get(&url)
44                .header(AUTH_KEY_HEADER, auth_key)
45                .query(&[("agent_id", agent_id.as_str()), ("filename", filename)])
46                .send()
47                .await
48                .map_err(|e| anyhow::anyhow!("request failed: {e}"))?;
49            if !resp.status().is_success() {
50                let status = resp.status();
51                let text = resp.text().await.unwrap_or_default();
52                anyhow::bail!("memory/read failed: HTTP {status} — {text}");
53            }
54            let result: Value = resp
55                .json()
56                .await
57                .map_err(|e| anyhow::anyhow!("response parse failed: {e}"))?;
58            // Surface the file content directly when present; fall back to the raw body.
59            Ok(result
60                .get("content")
61                .and_then(|v| v.as_str())
62                .map(str::to_string)
63                .unwrap_or_else(|| {
64                    serde_json::to_string_pretty(&result).unwrap_or_else(|_| result.to_string())
65                }))
66        }
67        "MemoryWrite" => {
68            let filename = arguments
69                .get("filename")
70                .and_then(|v| v.as_str())
71                .filter(|s| !s.is_empty())
72                .ok_or_else(|| anyhow::anyhow!("missing required parameter: filename"))?;
73            let content = arguments
74                .get("content")
75                .and_then(|v| v.as_str())
76                .ok_or_else(|| anyhow::anyhow!("missing required parameter: content"))?;
77            require_agent_env(local_url, auth_key, block_id)?;
78            let agent_id = agent_slug()?;
79            let url = format!("{}/api/v1/agent/memory/write", local_url.trim_end_matches('/'));
80            let mut body = json!({
81                "agent_id": agent_id,
82                "filename": filename,
83                "content": content,
84            });
85            // Pass provenance through verbatim when the caller supplied it —
86            // advisory metadata for the version history, see
87            // SPEC_MEMORY_VERSION_CONTROL_AND_ARMORY_AUDIT_2026_08_19.md §4.1.
88            if let Some(provenance) = arguments.get("provenance") {
89                body["provenance"] = provenance.clone();
90            }
91            let resp = client
92                .post(&url)
93                .header(AUTH_KEY_HEADER, auth_key)
94                .json(&body)
95                .send()
96                .await
97                .map_err(|e| anyhow::anyhow!("request failed: {e}"))?;
98            if !resp.status().is_success() {
99                let status = resp.status();
100                let text = resp.text().await.unwrap_or_default();
101                anyhow::bail!("memory/write failed: HTTP {status} — {text}");
102            }
103            Ok(format!("Wrote memory file \"{filename}\""))
104        }
105        "MemoryHistory" => {
106            let filename = arguments
107                .get("filename")
108                .and_then(|v| v.as_str())
109                .filter(|s| !s.is_empty())
110                .ok_or_else(|| anyhow::anyhow!("missing required parameter: filename"))?;
111            require_agent_env(local_url, auth_key, block_id)?;
112            let agent_id = agent_slug()?;
113            let url = format!("{}/api/v1/agent/memory/history", local_url.trim_end_matches('/'));
114            let resp = client
115                .get(&url)
116                .header(AUTH_KEY_HEADER, auth_key)
117                .query(&[("agent_id", agent_id.as_str()), ("filename", filename)])
118                .send()
119                .await
120                .map_err(|e| anyhow::anyhow!("request failed: {e}"))?;
121            if !resp.status().is_success() {
122                let status = resp.status();
123                let text = resp.text().await.unwrap_or_default();
124                anyhow::bail!("memory/history failed: HTTP {status} — {text}");
125            }
126            let result: Value = resp
127                .json()
128                .await
129                .map_err(|e| anyhow::anyhow!("response parse failed: {e}"))?;
130            Ok(serde_json::to_string_pretty(&result).unwrap_or_else(|_| result.to_string()))
131        }
132        "MemoryDiff" => {
133            let from_version_id = arguments
134                .get("from_version_id")
135                .and_then(|v| v.as_str())
136                .filter(|s| !s.is_empty())
137                .ok_or_else(|| anyhow::anyhow!("missing required parameter: from_version_id"))?;
138            let to_version_id = arguments
139                .get("to_version_id")
140                .and_then(|v| v.as_str())
141                .filter(|s| !s.is_empty())
142                .ok_or_else(|| anyhow::anyhow!("missing required parameter: to_version_id"))?;
143            require_agent_env(local_url, auth_key, block_id)?;
144            let agent_id = agent_slug()?;
145            let url = format!("{}/api/v1/agent/memory/diff", local_url.trim_end_matches('/'));
146            let resp = client
147                .get(&url)
148                .header(AUTH_KEY_HEADER, auth_key)
149                .query(&[("agent_id", agent_id.as_str()), ("from_version_id", from_version_id), ("to_version_id", to_version_id)])
150                .send()
151                .await
152                .map_err(|e| anyhow::anyhow!("request failed: {e}"))?;
153            if !resp.status().is_success() {
154                let status = resp.status();
155                let text = resp.text().await.unwrap_or_default();
156                anyhow::bail!("memory/diff failed: HTTP {status} — {text}");
157            }
158            let result: Value = resp
159                .json()
160                .await
161                .map_err(|e| anyhow::anyhow!("response parse failed: {e}"))?;
162            Ok(result
163                .get("diff")
164                .and_then(|v| v.as_str())
165                .map(str::to_string)
166                .unwrap_or_else(|| {
167                    serde_json::to_string_pretty(&result).unwrap_or_else(|_| result.to_string())
168                }))
169        }
170        "MemoryRevert" => {
171            let filename = arguments
172                .get("filename")
173                .and_then(|v| v.as_str())
174                .filter(|s| !s.is_empty())
175                .ok_or_else(|| anyhow::anyhow!("missing required parameter: filename"))?;
176            let target_version_id = arguments
177                .get("target_version_id")
178                .and_then(|v| v.as_str())
179                .filter(|s| !s.is_empty())
180                .ok_or_else(|| anyhow::anyhow!("missing required parameter: target_version_id"))?;
181            require_agent_env(local_url, auth_key, block_id)?;
182            let agent_id = agent_slug()?;
183            let url = format!("{}/api/v1/agent/memory/revert", local_url.trim_end_matches('/'));
184            let body = json!({
185                "agent_id": agent_id,
186                "filename": filename,
187                "target_version_id": target_version_id,
188            });
189            let resp = client
190                .post(&url)
191                .header(AUTH_KEY_HEADER, auth_key)
192                .json(&body)
193                .send()
194                .await
195                .map_err(|e| anyhow::anyhow!("request failed: {e}"))?;
196            if !resp.status().is_success() {
197                let status = resp.status();
198                let text = resp.text().await.unwrap_or_default();
199                anyhow::bail!("memory/revert failed: HTTP {status} — {text}");
200            }
201            Ok(format!("Reverted \"{filename}\" to version {target_version_id}"))
202        }
203        _ => Err(not_in_family()),
204    }
205}