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
32 changes: 30 additions & 2 deletions docs/book/src/reference/mcp-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ Quipu exposes its API as MCP (Model Context Protocol) tools for agent
integration. These tools are available when Quipu runs as a Bobbin subsystem
or standalone MCP server.

The registry (`tool_definitions()`) exposes **23 tools** in a default build, or
**24** when built with the `owl` feature (which adds `quipu_load_ontology`).
The registry (`tool_definitions()`) exposes **24 tools** in a default build, or
**25** when built with the `owl` feature (which adds `quipu_load_ontology`).

## Tool Reference

Expand Down Expand Up @@ -158,6 +158,34 @@ Unified knowledge context pipeline.
| `max_entities` | No | Max entities (default from pipeline config) |
| `expand_links` | No | Follow relationships to linked entities |

### `quipu_report`

Live graph report — graphify's `GRAPH_REPORT.md` equivalent, but queryable.
Read-only.

| Parameter | Required | Description |
|-----------|----------|-------------|
| `type` | No | Restrict the projection to this rdf:type IRI |
| `predicate` | No | Restrict the projection to edges with this predicate IRI |
| `hubs` | No | Number of top hubs to return (default: 10) |
| `surprises` | No | Number of surprising connections to return (default: 10) |
| `questions` | No | Number of suggested questions to return (default: 8) |

Returns three sections:

- `hubs` — "god-nodes": the most central entities by PageRank, each with its
`in_degree` as a secondary signal.
- `surprising_connections` — low-prior edges that bridge two otherwise-separate
Louvain communities. Rarer bridges (fewer edges crossing between the same two
communities — `bridge_rarity`) rank first; ties break toward bridges touching
higher-PageRank endpoints.
- `suggested_questions` — deterministic, template-generated prompts seeded by the
hubs and bridges above.

Plus a `graph` summary (`nodes`, `edges`, `communities`, `modularity`).
Communities here are emergent clustering for surfacing, **not** an access
boundary.

### `quipu_search_nodes`

Search for entities by natural-language query (text matching on names, labels,
Expand Down
15 changes: 15 additions & 0 deletions docs/book/src/reference/rest-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,21 @@ curl -s localhost:3030/project -X POST \
-d '{"algorithm": "in_degree", "limit": 10}'
```

### `GET|POST /report`

Live graph report: top hubs (god-nodes), surprising cross-community connections,
and auto-suggested questions (see `quipu_report` in the
[MCP tools reference](./mcp-tools.md)). Read-only. `GET` returns the report with
defaults; `POST` accepts an options body (`type`, `predicate`, `hubs`,
`surprises`, `questions`).

```bash
curl -s localhost:3030/report
curl -s localhost:3030/report -X POST \
-H "Content-Type: application/json" \
-d '{"hubs": 5, "surprises": 5, "questions": 6}'
```

### `POST /context`

Knowledge context pipeline.
Expand Down
41 changes: 41 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,47 @@ pub fn cmd_project(args: &[String], db_path: &str) {
}
}

/// `quipu report` — god-nodes + surprising connections + suggested questions
/// for the current graph (hq-ct27).
pub fn cmd_report(args: &[String], db_path: &str) {
let get_val = |flag: &str| -> Option<String> {
args.windows(2).find(|w| w[0] == flag).map(|w| w[1].clone())
};

let mut input = serde_json::json!({});
if let Some(t) = get_val("--type") {
input["type"] = serde_json::Value::String(t);
}
if let Some(p) = get_val("--predicate") {
input["predicate"] = serde_json::Value::String(p);
}
if let Some(h) = get_val("--hubs").and_then(|v| v.parse::<u64>().ok()) {
input["hubs"] = serde_json::json!(h);
}
if let Some(s) = get_val("--surprises").and_then(|v| v.parse::<u64>().ok()) {
input["surprises"] = serde_json::json!(s);
}
if let Some(q) = get_val("--questions").and_then(|v| v.parse::<u64>().ok()) {
input["questions"] = serde_json::json!(q);
}

let store = match quipu::Store::open(db_path) {
Ok(s) => s,
Err(e) => {
eprintln!("error opening store: {e}");
std::process::exit(1);
}
};

match quipu::tool_report(&store, &input) {
Ok(result) => println!("{}", serde_json::to_string_pretty(&result).unwrap()),
Err(e) => {
eprintln!("error: {e}");
std::process::exit(1);
}
}
}

pub fn cmd_impact(args: &[String], db_path: &str) {
let entity_iri = match args.get(2) {
Some(iri) if !iri.starts_with("--") => iri.as_str(),
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub mod provider;
pub mod rdf;
pub mod reasoner;
pub mod reconcile;
pub mod report;
pub mod resolution;
pub mod schema;
pub mod semweb;
Expand Down Expand Up @@ -87,6 +88,7 @@ pub use reconcile::{
GoResolver, ImportResolver, PythonResolver, ReconcileReport, RustResolver, default_resolvers,
reconcile,
};
pub use report::tool_report;
pub use resolution::{EntityCandidate, ResolutionResult, resolve_entity};
#[cfg(feature = "shacl")]
pub use shacl::{ValidationFeedback, Validator, validate_shapes};
Expand Down
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ fn main() {
"unravel" => cli::cmd_unravel(&args, db_path),
"impact" => cli::cmd_impact(&args, db_path),
"project" => cli::cmd_project(&args, db_path),
"report" => cli::cmd_report(&args, db_path),
"reason" => cli::cmd_reason(&args, db_path),
"episode" => cli_commands::cmd_episode(&args, db_path),
"retract" => cli_commands::cmd_retract(&args, db_path),
Expand Down Expand Up @@ -191,6 +192,7 @@ COMMANDS:
quipu unravel [--tx N] [--valid-at <date>] [--db <path>]
quipu impact <entity-IRI> [--remove] [--hops N] [--predicate <IRI>]... [--db <path>]
quipu project [--algorithm pagerank] [--seed <IRI>]... [--damping 0.85] [--predicate <IRI>] [--db <path>]
quipu report [--hubs N] [--surprises N] [--questions N] [--type <IRI>] [--predicate <IRI>] [--db <path>]
quipu reason [--rules <file.ttl>] [--db <path>]
quipu episode <file.json> [--db <path>]
quipu retract <entity-IRI> [--predicate <IRI>] [--db <path>]
Expand Down
14 changes: 14 additions & 0 deletions src/mcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,20 @@ pub fn tool_definitions() -> Vec<JsonValue> {
"required": ["query"]
}
}),
serde_json::json!({
"name": "quipu_report",
"description": "Generate a live graph report (graphify's GRAPH_REPORT.md equivalent, but queryable): top hubs / 'god-nodes' (by PageRank with in-degree as a secondary signal), surprising connections (low-prior edges that bridge two otherwise-separate Louvain communities — rarer bridges rank first), and auto-suggested questions seeded by those hubs and bridges. Read-only; derived from current graph structure. Communities here are emergent clustering for surfacing, NOT an access boundary.",
"inputSchema": {
"type": "object",
"properties": {
"type": { "type": "string", "description": "Restrict the projection to nodes of this rdf:type IRI" },
"predicate": { "type": "string", "description": "Restrict the projection to edges with this predicate IRI" },
"hubs": { "type": "integer", "description": "Number of top hubs to return (default: 10)" },
"surprises": { "type": "integer", "description": "Number of surprising connections to return (default: 10)" },
"questions": { "type": "integer", "description": "Number of suggested questions to return (default: 8)" }
}
}
}),
];

// OWL reasoning is gated behind the (non-default) `owl` feature; only
Expand Down
6 changes: 4 additions & 2 deletions src/mcp/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,17 +383,19 @@ fn test_tool_definitions() {
assert!(names.contains(&"quipu_context"));
// Named-query catalog tool (hq-h75).
assert!(names.contains(&"quipu_ask"));
// Graph report tool (hq-ct27).
assert!(names.contains(&"quipu_report"));

// quipu_load_ontology is only advertised when the `owl` feature compiles in
// its handler (hq-8wd) — otherwise the call would always fail.
#[cfg(feature = "owl")]
{
assert_eq!(defs.len(), 24);
assert_eq!(defs.len(), 25);
assert!(names.contains(&"quipu_load_ontology"));
}
#[cfg(not(feature = "owl"))]
{
assert_eq!(defs.len(), 23);
assert_eq!(defs.len(), 24);
assert!(!names.contains(&"quipu_load_ontology"));
}
}
Expand Down
Loading
Loading