Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/clients.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,25 @@ pub trait RegistryClient: Send + Sync {
) -> Result<Value>;

fn query_dead_code(&self, repo_id: &str, include_pub: bool, limit: usize) -> Result<Value>;

fn save_relation(
&self,
from: &str,
to: &str,
relation_type: &str,
confidence: f64,
) -> Result<Value>;

fn query_relations(
&self,
entity_id: &str,
direction: &str,
relation_type: Option<&str>,
) -> Result<Value>;

fn delete_relations(&self, from: &str, to: &str, relation_type: Option<&str>) -> Result<Value>;

fn list_vault_notes(&self) -> Result<Value>;
}

/// Knowledge engine operations.
Expand Down Expand Up @@ -110,3 +129,19 @@ pub trait SearchClient: Send + Sync {
limit: usize,
) -> Result<Vec<(String, f32)>>;
}

/// Workflow management exposed to MCP tools.
pub trait WorkflowClient: Send + Sync {
fn list_workflows(&self) -> Result<Value>;
fn get_workflow(&self, workflow_id: &str) -> Result<Value>;
fn run_workflow(&self, workflow_id: &str, inputs: Value) -> Result<Value>;
fn get_execution(&self, exec_id: i64) -> Result<Value>;
}

/// Vault (Markdown knowledge-base) operations exposed to MCP tools.
pub trait VaultClient: Send + Sync {
fn list_vault_notes(&self) -> Result<Value>;
fn read_vault_note(&self, path: &str) -> Result<Value>;
fn get_backlinks(&self, note_id: &str) -> Result<Value>;
fn build_vault_graph(&self, repo_id: Option<&str>) -> Result<Value>;
}
90 changes: 8 additions & 82 deletions src/mcp/tools/relations.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 juice094
use crate::clients::RegistryClient;
use crate::mcp::McpTool;

#[derive(Clone)]
Expand Down Expand Up @@ -83,10 +84,7 @@ Returns: success boolean and relation details."#,
}));
}

let conn = ctx.conn()?;
if let Err(e) =
crate::registry::relation::save_relation(&conn, &from, &to, &rel_type, confidence)
{
if let Err(e) = ctx.save_relation(&from, &to, &rel_type, confidence) {
let msg = e.to_string();
if msg.contains("foreign key constraint") || msg.contains("FOREIGN KEY") {
return Ok(serde_json::json!({
Expand Down Expand Up @@ -162,73 +160,9 @@ Returns: JSON array of relations with to_entity_id, relation_type, confidence, a
}));
}

let conn = ctx.conn()?;
let results = match direction {
"bidirectional" => {
let rows = crate::registry::relation::find_related_entities(
&conn,
&entity_id,
relation_type,
)?;
rows.into_iter()
.map(|(from, to, rt, conf, created)| {
serde_json::json!({
"from_entity_id": from,
"to_entity_id": to,
"relation_type": rt,
"confidence": conf,
"created_at": created
})
})
.collect::<Vec<_>>()
}
"incoming" => {
let mut stmt = conn.prepare(
"SELECT from_entity_id, relation_type, confidence, created_at FROM relations
WHERE to_entity_id = ?1
ORDER BY confidence DESC",
)?;
let rows = stmt.query_map([&entity_id], |row| {
Ok((
row.get::<_, String>(0)?,
row.get::<_, String>(1)?,
row.get::<_, f64>(2)?,
row.get::<_, String>(3)?,
))
})?;
let filtered: Vec<_> = if let Some(rt) = relation_type.filter(|s| !s.is_empty()) {
rows.filter(|r| r.as_ref().map(|(_, t, _, _)| t == rt).unwrap_or(false))
.collect::<Result<Vec<_>, _>>()?
} else {
rows.collect::<Result<Vec<_>, _>>()?
};
filtered
.into_iter()
.map(|(from, rt, conf, created)| {
serde_json::json!({
"from_entity_id": from,
"relation_type": rt,
"confidence": conf,
"created_at": created
})
})
.collect::<Vec<_>>()
}
_ => {
let rows =
crate::registry::relation::list_relations(&conn, &entity_id, relation_type)?;
rows.into_iter()
.map(|(to, rt, conf, created)| {
serde_json::json!({
"to_entity_id": to,
"relation_type": rt,
"confidence": conf,
"created_at": created
})
})
.collect::<Vec<_>>()
}
};
let value = ctx.query_relations(&entity_id, direction, relation_type)?;
let results =
value.get("relations").and_then(|v| v.as_array()).cloned().unwrap_or_default();

Ok(serde_json::json!({
"success": true,
Expand Down Expand Up @@ -297,17 +231,9 @@ Returns: success boolean and count of deleted relations."#,
}));
}

let conn = ctx.conn()?;
let count = match rel_type.as_deref().filter(|s| !s.is_empty()) {
Some(rt) => conn.execute(
"DELETE FROM relations WHERE from_entity_id = ?1 AND to_entity_id = ?2 AND relation_type = ?3",
rusqlite::params![&from, &to, rt],
)?,
None => conn.execute(
"DELETE FROM relations WHERE from_entity_id = ?1 AND to_entity_id = ?2",
rusqlite::params![&from, &to],
)?,
};
let value =
ctx.delete_relations(&from, &to, rel_type.as_deref().filter(|s| !s.is_empty()))?;
let count = value.get("deleted").and_then(|v| v.as_u64()).unwrap_or(0) as usize;

Ok(serde_json::json!({
"success": true,
Expand Down
Loading
Loading