refactor: Enforce strict typed JsonSchema for all MCP tools

This commit is contained in:
Riz Ashraf committed 2026-09-12 22:20:01 +01:00
1 parent 686fea683d
commit 2d3aaed289
31 files changed
+2892 -308

No files matched your search

+4 -1
View File
@@ -5,13 +5,15 @@ edition = "2024"
[dependencies]
async-trait = "0.1.92"
axum = "0.8"
axum = { version = "0.8", features = ["ws"] }
bincode = "1.3.3"
clap = { version = "4.6.6", features = ["derive"] }
dashmap = "6.2.1"
dirs = "6.0.0"
futures-util = "0.3.34"
glob = "0.3.4"
git2 = { version = "0.19.0", default-features = false }
notify = "6.1.1"
redb = "4.2.0"
reqwest = { version = "0.12", default-features = false, features = ["stream", "rustls-tls"] }
schemars = "1.2.2"
@@ -24,6 +26,7 @@ tokio-util = { version = "0.7.19", features = ["io"] }
tracing = "0.1.44"
tracing-subscriber = "0.3.23"
uuid = { version = "1.26.0", features = ["v4"] }
tokio-tungstenite = "0.21.0"
[build-dependencies]
chrono = "0.4.45"
+29 -18
View File
@@ -710,32 +710,43 @@
}
}
// --- SSE Activity Feed ---
function setupSSE() {
const es = new EventSource('/sse');
// --- WebSocket Activity Feed ---
function setupWS() {
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(`${protocol}//${location.host}/ws?client=ui`);
const feed = document.getElementById('activity-feed');
es.addEventListener('activity', function(event) {
if (event.data.startsWith('/messages?')) return;
const div = document.createElement('div');
div.className = 'feed-entry';
const time = new Date().toLocaleTimeString();
div.innerHTML = `<span class="time">[${time}]</span> ${event.data}`;
feed.appendChild(div);
// Auto-scroll logic
const isScrolledToBottom = feed.scrollHeight - feed.clientHeight <= feed.scrollTop + 20;
if (isScrolledToBottom) {
feed.scrollTop = feed.scrollHeight;
ws.onmessage = function(event) {
try {
const data = JSON.parse(event.data);
if (data.type === 'activity') {
const div = document.createElement('div');
div.className = 'feed-entry';
const time = new Date().toLocaleTimeString();
div.innerHTML = `<span class="time">[${time}]</span> ${data.data}`;
feed.appendChild(div);
// Auto-scroll logic
const isScrolledToBottom = feed.scrollHeight - feed.clientHeight <= feed.scrollTop + 20;
if (isScrolledToBottom) {
feed.scrollTop = feed.scrollHeight;
}
}
} catch (e) {
// Ignore non-JSON or other messages for now
}
});
};
ws.onclose = function() {
console.log("WebSocket closed, attempting to reconnect in 3s...");
setTimeout(setupWS, 3000);
};
}
// --- Start ---
loadGraph();
loadTasks();
setupSSE();
setupWS();
// Listen to theme toggle changes to update graph font colors
const observer = new MutationObserver(() => updateGraphData());
+81 -59
View File
@@ -39,7 +39,8 @@ impl MemoryHandler {
}
"tools/list" => {
let tools = vec![
crate::mcp::tool_def::<CreateEntitiesTool>("create_entities", "Create new entities in the knowledge graph."),
crate::mcp::tool_def::<crate::tools::QueryGraphPathTool>("query_graph_path", "Traverse the knowledge graph to find a path between two entities."),
crate::mcp::tool_def::<CreateEntitiesTool>("create_entities", "Create new entities in the knowledge graph."),
crate::mcp::tool_def::<CreateRelationsTool>("create_relations", "Create new relations between entities in the knowledge graph."),
crate::mcp::tool_def::<AddObservationsTool>("add_observations", "Add new observations to existing entities in the knowledge graph."),
crate::mcp::tool_def::<DeleteEntitiesTool>("delete_entities", "Delete entities from the knowledge graph."),
@@ -120,7 +121,68 @@ impl MemoryHandler {
.unwrap_or(serde_json::Value::Object(Default::default()));
let result: Result<String, String> = match name {
"create_entities" => {
"query_graph_path" => {
let req: crate::tools::QueryGraphPathTool = match parse_args(args.clone()) {
Ok(r) => r,
Err(e) => return Some(crate::mcp::success(id.clone(), serde_json::json!({"isError": true, "content": [{"type": "text", "text": format!("Invalid args: {}", e)}] })))
};
let graph = self.state.get_full_graph();
let max_depth = req.max_depth.unwrap_or(5);
let mut queue = std::collections::VecDeque::new();
let mut visited = std::collections::HashSet::new();
let mut parents: std::collections::HashMap<String, (String, String)> = std::collections::HashMap::new();
queue.push_back(req.start_node.clone());
visited.insert(req.start_node.clone());
let mut found = false;
let mut current_depth = 0;
let mut nodes_at_current_depth = 1;
let mut nodes_at_next_depth = 0;
while let Some(current) = queue.pop_front() {
if current == req.end_node {
found = true;
break;
}
nodes_at_current_depth -= 1;
if current_depth < max_depth {
for rel in &graph.relations {
if rel.from == current && !visited.contains(&rel.to) {
visited.insert(rel.to.clone());
parents.insert(rel.to.clone(), (current.clone(), rel.relation_type.clone()));
queue.push_back(rel.to.clone());
nodes_at_next_depth += 1;
} else if rel.to == current && !visited.contains(&rel.from) {
visited.insert(rel.from.clone());
parents.insert(rel.from.clone(), (current.clone(), format!("inverse({})", rel.relation_type)));
queue.push_back(rel.from.clone());
nodes_at_next_depth += 1;
}
}
}
if nodes_at_current_depth == 0 {
current_depth += 1;
nodes_at_current_depth = nodes_at_next_depth;
nodes_at_next_depth = 0;
}
}
if found {
let mut path = Vec::new();
let mut curr = req.end_node.clone();
while curr != req.start_node {
let (parent, rel) = parents.get(&curr).unwrap();
path.push(format!("({}) --[{}]--> ({})", parent, rel, curr));
curr = parent.clone();
}
path.reverse();
Ok(format!("Path found:\n{}", path.join("\n")))
} else {
Ok(format!("No path found between {} and {} within depth {}", req.start_node, req.end_node, max_depth))
}
}
"create_entities" => {
let req: CreateEntitiesTool = match parse_args(args.clone()) {
Ok(r) => r,
Err(e) => {
@@ -131,19 +193,12 @@ impl MemoryHandler {
}
};
self.state.write_to_local_delta(|g| {
for e_val in req.entities {
if let Some(e) = if e_val.is_string() {
serde_json::from_str(e_val.as_str().unwrap()).ok()
} else {
serde_json::from_value(e_val).ok()
} {
let entity: Entity = e;
if !entity.name.is_empty() {
if let Ok(idx) = self.state.search_index.read() {
let _ = idx.index_entity(&entity);
}
g.entities.insert(entity.name.clone(), entity);
for entity in req.entities {
if !entity.name.is_empty() {
if let Ok(idx) = self.state.search_index.read() {
let _ = idx.index_entity(&entity);
}
g.entities.insert(entity.name.clone(), entity);
}
}
}).await;
@@ -160,16 +215,9 @@ impl MemoryHandler {
}
};
self.state.write_to_local_delta(|g| {
for r_val in req.relations {
if let Some(r) = if r_val.is_string() {
serde_json::from_str(r_val.as_str().unwrap()).ok()
} else {
serde_json::from_value(r_val).ok()
} {
let relation: Relation = r;
if !relation.from.is_empty() && !relation.to.is_empty() {
g.relations.push(relation);
}
for relation in req.relations {
if !relation.from.is_empty() && !relation.to.is_empty() {
g.relations.push(relation);
}
}
}).await;
@@ -185,20 +233,10 @@ impl MemoryHandler {
));
}
};
#[derive(Deserialize)]
struct ObsInput {
#[serde(rename = "entityName")]
entity_name: String,
contents: Vec<String>,
}
let full = self.state.get_full_graph();
self.state.write_to_local_delta(|g| {
for o_val in req.observations {
if let Some(o) = if o_val.is_string() {
serde_json::from_str::<ObsInput>(o_val.as_str().unwrap()).ok()
} else {
serde_json::from_value(o_val).ok()
} && let Some(full_e) = full.entities.get(&o.entity_name)
for o in req.observations {
if let Some(full_e) = full.entities.get(&o.entity_name)
{
let mut e =
g.entities.get(&o.entity_name).cloned().unwrap_or_else(
@@ -248,19 +286,9 @@ impl MemoryHandler {
));
}
};
#[derive(Deserialize)]
struct ObsDel {
#[serde(rename = "entityName")]
entity_name: String,
observations: Vec<String>,
}
self.state.apply_sync_write(|master| {
for d_val in req.deletions {
if let Some(d) = if d_val.is_string() {
serde_json::from_str::<ObsDel>(d_val.as_str().unwrap()).ok()
} else {
serde_json::from_value(d_val).ok()
} && let Some(e) = master.entities.get_mut(&d.entity_name)
for d in req.deletions {
if let Some(e) = master.entities.get_mut(&d.entity_name)
{
let to_rem: HashSet<_> = d.observations.into_iter().collect();
e.observations.retain(|o| !to_rem.contains(o));
@@ -281,17 +309,11 @@ impl MemoryHandler {
};
self.state.apply_sync_write(|master| {
let mut to_rem = HashSet::new();
for r_val in req.relations {
if let Some(r) = if r_val.is_string() {
serde_json::from_str::<Relation>(r_val.as_str().unwrap()).ok()
} else {
serde_json::from_value(r_val).ok()
} {
to_rem.insert(format!(
"{}|{}|{}|{}",
r.from, r.to, r.relation_type, r.namespace
));
}
for r in req.relations {
to_rem.insert(format!(
"{}|{}|{}|{}",
r.from, r.to, r.relation_type, r.namespace
));
}
master.relations.retain(|r| {
!to_rem.contains(&format!(
+3 -2
View File
@@ -1,5 +1,6 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use schemars::JsonSchema;
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CodeChange {
@@ -17,7 +18,7 @@ pub struct StickyNote {
pub fn default_namespace() -> String {
"global".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct Entity {
pub name: String,
#[serde(rename = "entityType")]
@@ -29,7 +30,7 @@ pub struct Entity {
#[serde(default)]
pub git_branch: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, JsonSchema)]
pub struct Relation {
pub from: String,
pub to: String,
-121
View File
@@ -1,121 +0,0 @@
use futures_util::StreamExt;
use std::sync::Arc;
use tokio::io::AsyncBufReadExt;
use tokio::sync::RwLock;
use tokio::sync::mpsc;
use tokio_util::io::StreamReader;
pub fn run_proxy(target_url: &str) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let (msg_tx, mut msg_rx) = mpsc::channel::<String>(100);
let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1);
tokio::task::spawn_blocking(move || {
let stdin = std::io::stdin();
let mut handle = stdin.lock();
let mut buffer = String::new();
while let Ok(bytes) = std::io::BufRead::read_line(&mut handle, &mut buffer) {
if bytes == 0 {
break;
}
let _ = msg_tx.blocking_send(buffer.clone());
buffer.clear();
}
let _ = shutdown_tx.blocking_send(());
});
let target_url = target_url.to_string();
let post_url = Arc::new(RwLock::new(String::new()));
let post_url_clone = Arc::clone(&post_url);
let client = reqwest::Client::builder().build().unwrap();
tokio::spawn(async move {
while let Some(msg) = msg_rx.recv().await {
loop {
let url = post_url_clone.read().await.clone();
if !url.is_empty() {
let res = client
.post(&url)
.header("Accept", "application/json, text/event-stream")
.header("Content-Type", "application/json")
.body(msg.clone())
.send()
.await;
if res.is_ok() {
break;
}
}
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
}
}
});
if shutdown_rx.try_recv().is_ok() {
return Ok(false);
}
let sse_url = format!("{}/sse", target_url);
let client = reqwest::Client::builder().build().unwrap();
match client
.get(&sse_url)
.header("Accept", "text/event-stream")
.send()
.await
{
Ok(resp) => {
if resp.status() == reqwest::StatusCode::GONE {
return Ok(true);
}
let stream = resp.bytes_stream().map(|res| {
res.map_err(std::io::Error::other)
});
let mut reader = tokio::io::BufReader::new(StreamReader::new(stream));
let mut line = String::new();
let mut is_message = false;
let mut is_endpoint = false;
loop {
tokio::select! {
_ = shutdown_rx.recv() => {
return Ok(false);
}
res = reader.read_line(&mut line) => {
match res {
Ok(bytes) => {
if bytes == 0 { break; }
let trimmed = line.trim();
if trimmed.starts_with("event: message") {
is_message = true;
is_endpoint = false;
} else if trimmed.starts_with("event: endpoint") {
is_endpoint = true;
is_message = false;
} else if let Some(stripped) = trimmed.strip_prefix("data: ") {
if is_message {
println!("{}", stripped);
is_message = false;
} else if is_endpoint {
let mut p = post_url.write().await;
*p = format!("{}{}", target_url, stripped);
is_endpoint = false;
}
}
line.clear();
}
Err(_) => break,
}
}
}
}
*post_url.write().await = String::new();
Ok(true)
}
Err(_) => Ok(true),
}
})
}
+286 -46
View File
@@ -1,276 +1,516 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// Create new entities in the knowledge graph.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct CreateEntitiesTool {
pub entities: Vec<serde_json::Value>,
/// Array of entities to create.
pub entities: Vec<crate::models::Entity>,
}
/// Create new relations between entities in the knowledge graph.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct CreateRelationsTool {
pub relations: Vec<serde_json::Value>,
/// Array of relations to create.
pub relations: Vec<crate::models::Relation>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ObservationInput {
#[serde(rename = "entityName")]
pub entity_name: String,
pub contents: Vec<String>,
}
/// Add new observations to existing entities in the knowledge graph.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct AddObservationsTool {
pub observations: Vec<serde_json::Value>,
/// Array of observations to add.
pub observations: Vec<ObservationInput>,
}
/// Delete entities from the knowledge graph.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct DeleteEntitiesTool {
/// Array of entity names to delete.
#[serde(rename = "entityNames")]
pub entity_names: Vec<String>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct DeleteObservationsTool {
pub deletions: Vec<serde_json::Value>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct DeleteRelationsTool {
pub relations: Vec<serde_json::Value>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ReadGraphTool {
pub namespace: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct SearchNodesTool {
pub query: String,
pub namespace: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct OpenNodesTool {
pub names: Vec<String>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct LogCodeChangeTool {
#[serde(rename = "filePath")]
pub file_path: String,
pub description: String,
pub git_commit: Option<String>,
pub git_branch: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct QueryRecentChangesTool {}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct VisualizeGraphTool {
pub query: Option<String>,
pub namespace: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct AddStickyNoteTool {
pub content: String,
}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ReadStickyNotesTool {}
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct CondenseEntityTool {
pub struct DeleteObservationInput {
#[serde(rename = "entityName")]
pub entity_name: String,
pub observations: Vec<String>,
}
/// Delete observations from existing entities.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct DeleteObservationsTool {
/// Array of observation deletions.
pub deletions: Vec<DeleteObservationInput>,
}
/// Delete relations between entities.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct DeleteRelationsTool {
/// Array of relations to delete.
pub relations: Vec<crate::models::Relation>,
}
/// Read the entire knowledge graph.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ReadGraphTool {
/// Optional namespace to restrict the read to.
pub namespace: Option<String>,
}
/// Search for entities in the knowledge graph by name or type.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct SearchNodesTool {
/// The search query.
pub query: String,
/// Optional namespace to restrict the search to.
pub namespace: Option<String>,
}
/// Open and retrieve full details of specific nodes in the knowledge graph.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct OpenNodesTool {
/// Array of entity names to open.
pub names: Vec<String>,
}
/// Log a significant code change or refactor in the memory system.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct LogCodeChangeTool {
/// The path of the file that was changed.
#[serde(rename = "filePath")]
pub file_path: String,
/// A description of the change.
pub description: String,
/// The associated git commit hash, if any.
pub git_commit: Option<String>,
/// The associated git branch, if any.
pub git_branch: Option<String>,
}
/// Query recently logged code changes.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct QueryRecentChangesTool {}
/// Generate a visual representation of the knowledge graph.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct VisualizeGraphTool {
/// Optional search query to filter the graph before visualization.
pub query: Option<String>,
/// Optional namespace to restrict the visualization to.
pub namespace: Option<String>,
}
/// Add a sticky note for unstructured thoughts or reminders.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct AddStickyNoteTool {
/// The content of the sticky note.
pub content: String,
}
/// Read all active sticky notes.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ReadStickyNotesTool {}
/// Condense or summarize an entity's observations to reduce size.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct CondenseEntityTool {
/// The name of the entity to condense.
#[serde(rename = "entityName")]
pub entity_name: String,
/// The condensed observations that will replace the existing ones.
pub summarized_observations: Vec<String>,
}
/// Add a new task to the task tracker.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct AddTaskTool {
/// The title of the task.
pub title: String,
/// A detailed description of the task.
pub description: String,
/// The associated git branch, if any.
pub git_branch: Option<String>,
}
/// Update the status of an existing task.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct UpdateTaskStatusTool {
/// The ID of the task to update.
pub id: String,
/// The new status of the task (e.g., 'pending' or 'completed').
#[schemars(description = "Must be 'pending' or 'completed'")]
pub status: String,
}
/// List all currently active tasks.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ListActiveTasksTool {
/// Optional git branch to filter tasks by.
pub git_branch: Option<String>,
}
/// Store a reusable code snippet.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct StoreSnippetTool {
/// The name of the snippet.
pub name: String,
/// The programming language of the snippet.
pub language: String,
/// The code snippet content.
pub code: String,
/// A description of what the snippet does.
pub description: String,
}
/// Search through stored code snippets.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct SearchSnippetsTool {
/// The search query.
pub query: String,
}
/// Delete a stored code snippet.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct DeleteSnippetTool {
/// The name of the snippet to delete.
pub name: String,
}
/// Log an architectural decision record (ADR).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct LogDecisionTool {
/// The title of the decision.
pub title: String,
/// The context or problem requiring a decision.
pub context: String,
/// The decision made.
pub decision: String,
/// The consequence of the decision.
pub consequence: String,
}
/// Query architectural decision records.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct QueryDecisionsTool {
/// Optional search query.
pub query: Option<String>,
}
/// Merge two entities in the knowledge graph into one.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct MergeEntitiesTool {
/// The name of the entity to merge from (will be deleted).
#[serde(rename = "sourceEntity")]
pub source_entity: String,
/// The name of the entity to merge into.
#[serde(rename = "targetEntity")]
pub target_entity: String,
}
/// Find orphaned entities (entities without any relations) in the graph.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct FindOrphansTool {}
/// Record a user preference or behavior to adapt future interactions.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct LearnPreferenceTool {
/// The key for the preference.
pub key: String,
/// The value of the preference.
pub value: String,
}
/// Read all learned user preferences.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ReadPreferencesTool {}
/// Log a complex error and its fix for future reference.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct LogErrorFixTool {
/// The error signature or stack trace.
pub signature: String,
/// The solution applied to fix the error.
pub solution: String,
/// The associated git commit hash, if any.
pub git_commit: Option<String>,
/// The associated git branch, if any.
pub git_branch: Option<String>,
}
/// Search through previously logged error fixes.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct SearchErrorFixesTool {
/// The search query.
pub query: String,
}
/// Pin a file to keep it explicitly in the context workspace.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct PinFileTool {
/// The namespace to pin the file in.
pub namespace: String,
/// The absolute path of the file to pin.
pub file_path: String,
/// The associated git branch, if any.
pub git_branch: Option<String>,
}
/// Unpin a file from the context workspace.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct UnpinFileTool {
/// The namespace the file is pinned in.
pub namespace: String,
/// The absolute path of the file to unpin.
pub file_path: String,
}
/// List all currently pinned files.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ListPinnedFilesTool {
/// Optional namespace to filter by.
pub namespace: Option<String>,
/// Optional git branch to filter by.
pub git_branch: Option<String>,
}
/// Add a summary of the current session.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct AddSessionSummaryTool {
/// The summary content.
pub summary: String,
/// The namespace to add the summary to.
pub namespace: String,
}
/// Get a timeline of major project events.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct GetProjectTimelineTool {
/// Optional namespace to restrict the timeline to.
pub namespace: Option<String>,
}
/// Leave a memo for the next session or agent.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct LeaveHandoffMemoTool {
/// The content of the memo.
pub content: String,
/// The namespace to leave the memo in.
pub namespace: String,
}
/// Read pending handoff memos.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ReadHandoffMemosTool {
/// Optional namespace to restrict the read to.
pub namespace: Option<String>,
}
/// Clear handoff memos after reading.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ClearHandoffMemosTool {
/// Array of memo IDs to clear.
pub ids: Vec<String>,
}
/// Update the environment fingerprint (e.g., OS, tool versions).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct UpdateEnvFingerprintTool {
/// The namespace to update the fingerprint for.
pub namespace: String,
/// A map of tool names to their versions.
pub tool_versions: std::collections::HashMap<String, String>,
}
/// Read the environment fingerprint.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ReadEnvFingerprintTool {
/// The namespace to read the fingerprint for.
pub namespace: String,
}
/// Log a required environment variable or configuration.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct LogEnvRequirementTool {
/// The namespace.
pub namespace: String,
/// The environment variable key (e.g., DATABASE_URL).
pub key: String,
/// A description of what the variable is used for.
pub description: String,
/// Whether the variable contains a secret.
pub is_secret: bool,
}
/// Add a new milestone.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct AddMilestoneTool {
/// The title of the milestone.
pub title: String,
/// The namespace for the milestone.
pub namespace: String,
}
/// Update the status of a milestone.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct UpdateMilestoneTool {
/// The ID of the milestone to update.
pub id: String,
/// The new status (e.g., 'active', 'completed').
pub status: String,
}
/// List all milestones.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ListMilestonesTool {
/// Optional namespace to restrict the list to.
pub namespace: Option<String>,
}
/// Generate a standup report for a specific time window.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct GenerateStandupReportTool {
/// The namespace to generate the report for.
pub namespace: String,
/// The number of hours to look back for activity.
pub hours_lookback: u64,
}
/// Register a new infrastructure environment.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct RegisterEnvironmentTool {
/// The namespace.
pub namespace: String,
/// The name of the environment (e.g., 'staging', 'prod').
pub name: String,
/// The URL or connection string for the environment.
pub url: String,
/// A description of the environment.
pub description: String,
/// Whether a VPN is required to access the environment.
pub requires_vpn: bool,
}
/// Get details about a registered environment.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct GetEnvironmentDetailsTool {
/// The namespace to retrieve details for.
pub namespace: String,
}
/// Add an item to the PR checklist.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct AddPrChecklistItemTool {
/// The namespace.
pub namespace: String,
/// The description of the checklist item.
pub description: String,
}
/// Get the PR checklist.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct GetPrChecklistTool {
/// The namespace.
pub namespace: String,
}
/// Clear the PR checklist.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ClearPrChecklistTool {
/// The namespace.
pub namespace: String,
}
/// Log a technical debt record.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct LogTechDebtTool {
/// The namespace.
pub namespace: String,
/// A description of the technical debt.
pub description: String,
/// The ideal solution to resolve the debt.
pub ideal_solution: String,
/// The associated git commit hash, if any.
pub git_commit: Option<String>,
/// The associated git branch, if any.
pub git_branch: Option<String>,
}
/// Resolve a technical debt record.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ResolveTechDebtTool {
/// The ID of the technical debt record to resolve.
pub id: String,
}
/// List technical debt records.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ListTechDebtTool {
/// The namespace.
pub namespace: String,
/// Whether to include resolved technical debt in the results.
pub include_resolved: bool,
}
/// Save the current context workspace.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct SaveContextWorkspaceTool {
/// The namespace for the workspace.
pub namespace: String,
/// The name to save the workspace as.
pub name: String,
/// Array of pinned file paths.
pub pinned_files: Vec<String>,
/// Array of active task IDs.
pub active_task_ids: Vec<String>,
}
/// Load a saved context workspace.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct LoadContextWorkspaceTool {
/// The namespace of the workspace.
pub namespace: String,
/// The name of the workspace to load.
pub name: String,
}
/// List all saved context workspaces.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct ListContextWorkspacesTool {
/// The namespace to list workspaces for.
pub namespace: String,
}
/// Search across all memory stores (Graph, Tasks, Snippets, ADRs, etc.).
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct OmniSearchTool {
/// The search query.
pub query: String,
/// Optional namespace to restrict the search to.
pub namespace: Option<String>,
}
/// Get a health digest of the project.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
pub struct GetProjectHealthTool {
/// The namespace to get health for.
pub namespace: String,
}
/// Traverse the knowledge graph to find a path between two entities.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct QueryGraphPathTool {
/// The starting entity name.
pub start_node: String,
/// The ending entity name.
pub end_node: String,
/// Optional maximum depth to search.
pub max_depth: Option<u32>,
}