From be85b58be4552939452aac5b6f1dfcf5a6f74f7c Mon Sep 17 00:00:00 2001 From: Riz Ashraf Date: Sat, 26 Sep 2026 08:07:28 +0100 Subject: [PATCH] fix: agent usability improvements - unified schema casing, fixed silent handler failures, and made namespaces strictly optional --- server/src/handlers/graph.rs | 20 +++++++++++++++++++- server/src/models.rs | 16 ++++++++-------- server/src/tools.rs | 31 +++++++++++++++++++++++++------ 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/server/src/handlers/graph.rs b/server/src/handlers/graph.rs index 4c11f57..70ddd0c 100644 --- a/server/src/handlers/graph.rs +++ b/server/src/handlers/graph.rs @@ -152,13 +152,25 @@ impl McpTool for CreateRelationsHandler { async fn execute(&self, args: Value, state: Arc) -> Result { let req: CreateRelationsTool = serde_json::from_value(args).map_err(|e| e.to_string())?; + let mut missing_nodes = std::collections::HashSet::new(); state.modify_graph(|g| { for relation in req.relations { if !relation.from.is_empty() && !relation.to.is_empty() { - g.relations.push(relation); + let from_exists = g.entities.contains_key(&relation.from); + let to_exists = g.entities.contains_key(&relation.to); + if from_exists && to_exists { + g.relations.push(relation); + } else { + if !from_exists { missing_nodes.insert(relation.from); } + if !to_exists { missing_nodes.insert(relation.to); } + } } } }); + if !missing_nodes.is_empty() { + let missing: Vec<_> = missing_nodes.into_iter().collect(); + return Ok(format!("Warning: Relations dropped due to missing entities: {}", missing.join(", "))); + } Ok("Relations created".to_string()) } } @@ -177,13 +189,19 @@ impl McpTool for AddObservationsHandler { async fn execute(&self, args: Value, state: Arc) -> Result { let req: AddObservationsTool = serde_json::from_value(args).map_err(|e| e.to_string())?; + let mut missing_entities = Vec::new(); state.modify_graph(|g| { for o in req.observations { if let Some(e) = g.entities.get_mut(&o.entity_name) { e.observations.extend(o.contents); + } else { + missing_entities.push(o.entity_name); } } }); + if !missing_entities.is_empty() { + return Ok(format!("Warning: Observations dropped for missing entities: {}", missing_entities.join(", "))); + } Ok("Observations added".to_string()) } } diff --git a/server/src/models.rs b/server/src/models.rs index 12c55f3..abcabd7 100644 --- a/server/src/models.rs +++ b/server/src/models.rs @@ -21,7 +21,7 @@ pub fn default_namespace() -> String { #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] pub struct Entity { pub name: String, - #[serde(rename = "entityType")] + #[serde(alias = "entityType")] pub entity_type: String, #[serde(default)] pub observations: Vec, @@ -34,7 +34,7 @@ pub struct Entity { pub struct Relation { pub from: String, pub to: String, - #[serde(rename = "relationType")] + #[serde(alias = "relationType")] pub relation_type: String, #[serde(default = "default_namespace")] pub namespace: String, @@ -50,7 +50,7 @@ pub struct KnowledgeGraph { pub struct AcceptanceCriteria { pub id: String, pub description: String, - #[serde(alias = "is_met", rename = "isMet")] + #[serde(alias = "is_met")] pub is_met: bool, } @@ -60,19 +60,19 @@ pub struct Task { pub title: String, pub status: String, pub description: String, - #[serde(alias = "created_at", rename = "createdAt")] + #[serde(alias = "created_at")] pub created_at: u64, - #[serde(alias = "updated_at", rename = "updatedAt")] + #[serde(alias = "updated_at")] pub updated_at: u64, - #[serde(alias = "git_branch", rename = "gitBranch")] + #[serde(alias = "git_branch")] pub git_branch: Option, #[serde(default)] - #[serde(alias = "parent_id", rename = "parentId")] + #[serde(alias = "parent_id")] pub parent_id: Option, #[serde(default)] pub dependencies: Vec, #[serde(default)] - #[serde(alias = "acceptance_criteria", rename = "acceptanceCriteria")] + #[serde(alias = "acceptance_criteria")] pub acceptance_criteria: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Default)] diff --git a/server/src/tools.rs b/server/src/tools.rs index 5a18231..26399c6 100644 --- a/server/src/tools.rs +++ b/server/src/tools.rs @@ -17,7 +17,7 @@ pub struct CreateRelationsTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct ObservationInput { - #[serde(rename = "entityName")] + #[serde(alias = "entityName")] pub entity_name: String, pub contents: Vec, } @@ -33,13 +33,13 @@ pub struct AddObservationsTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct DeleteEntitiesTool { /// Array of entity names to delete. - #[serde(rename = "entityNames")] + #[serde(alias = "entityNames")] pub entity_names: Vec, } #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct DeleteObservationInput { - #[serde(rename = "entityName")] + #[serde(alias = "entityName")] pub entity_name: String, pub observations: Vec, } @@ -134,7 +134,7 @@ pub struct ClearStickyNotesTool {} #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct CondenseEntityTool { /// The name of the entity to condense. - #[serde(rename = "entityName")] + #[serde(alias = "entityName")] pub entity_name: String, /// The condensed observations that will replace the existing ones. pub summarized_observations: Vec, @@ -230,10 +230,10 @@ pub struct QueryDecisionsTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct MergeEntitiesTool { /// The name of the entity to merge from (will be deleted). - #[serde(rename = "sourceEntity")] + #[serde(alias = "sourceEntity")] pub source_entity: String, /// The name of the entity to merge into. - #[serde(rename = "targetEntity")] + #[serde(alias = "targetEntity")] pub target_entity: String, } @@ -278,6 +278,7 @@ pub struct SearchErrorFixesTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct PinFileTool { /// The namespace to pin the file in. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, /// The absolute path of the file to pin. pub file_path: String, @@ -289,6 +290,7 @@ pub struct PinFileTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct UnpinFileTool { /// The namespace the file is pinned in. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, /// The absolute path of the file to unpin. pub file_path: String, @@ -309,6 +311,7 @@ pub struct AddSessionSummaryTool { /// The summary content. pub summary: String, /// The namespace to add the summary to. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, } @@ -326,6 +329,7 @@ pub struct LeaveHandoffMemoTool { /// The content of the memo. pub content: String, /// The namespace to leave the memo in. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, } @@ -347,6 +351,7 @@ pub struct ClearHandoffMemosTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct UpdateEnvFingerprintTool { /// The namespace to update the fingerprint for. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, /// A map of tool names to their versions. pub tool_versions: std::collections::HashMap, @@ -356,6 +361,7 @@ pub struct UpdateEnvFingerprintTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct ReadEnvFingerprintTool { /// The namespace to read the fingerprint for. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, } @@ -363,6 +369,7 @@ pub struct ReadEnvFingerprintTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct LogEnvRequirementTool { /// The namespace. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, /// The environment variable key (e.g., DATABASE_URL). pub key: String, @@ -378,6 +385,7 @@ pub struct AddMilestoneTool { /// The title of the milestone. pub title: String, /// The namespace for the milestone. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, } @@ -401,6 +409,7 @@ pub struct ListMilestonesTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct GenerateStandupReportTool { /// The namespace to generate the report for. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, /// The number of hours to look back for activity. pub hours_lookback: u64, @@ -410,6 +419,7 @@ pub struct GenerateStandupReportTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct RegisterEnvironmentTool { /// The namespace. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, /// The name of the environment (e.g., 'staging', 'prod'). pub name: String, @@ -425,6 +435,7 @@ pub struct RegisterEnvironmentTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct GetEnvironmentDetailsTool { /// The namespace to retrieve details for. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, } @@ -432,6 +443,7 @@ pub struct GetEnvironmentDetailsTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct AddPrChecklistItemTool { /// The namespace. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, /// The description of the checklist item. pub description: String, @@ -441,6 +453,7 @@ pub struct AddPrChecklistItemTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct GetPrChecklistTool { /// The namespace. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, } @@ -448,6 +461,7 @@ pub struct GetPrChecklistTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct ClearPrChecklistTool { /// The namespace. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, } @@ -455,6 +469,7 @@ pub struct ClearPrChecklistTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct LogTechDebtTool { /// The namespace. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, /// A description of the technical debt. pub description: String, @@ -477,6 +492,7 @@ pub struct ResolveTechDebtTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct ListTechDebtTool { /// The namespace. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, /// Whether to include resolved technical debt in the results. pub include_resolved: bool, @@ -486,6 +502,7 @@ pub struct ListTechDebtTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct SaveContextWorkspaceTool { /// The namespace for the workspace. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, /// The name to save the workspace as. pub name: String, @@ -499,6 +516,7 @@ pub struct SaveContextWorkspaceTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct LoadContextWorkspaceTool { /// The namespace of the workspace. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, /// The name of the workspace to load. pub name: String, @@ -524,6 +542,7 @@ pub struct OmniSearchTool { #[derive(Debug, Deserialize, Serialize, JsonSchema)] pub struct GetProjectHealthTool { /// The namespace to get health for. + #[serde(default = "crate::models::default_namespace")] pub namespace: String, }