The relation graph tools are now wired in smeldr.dev/mcp. If your app calls app.Relations(store), six tools become available automatically — no new server option needed.
The six tools
| Tool | Role | What it does |
|---|---|---|
assert_relation | Author | Create an asserted edge. Not idempotent — each call produces a unique edge. Use get_relations first to check for duplicates. |
propose_relation | Author | Create an inferred edge for human/agent review. Stays edge_class="inferred" until promoted. |
get_relations | Author | Query edges by source, target, or both. Filter by kind and edge_class. |
preview_impact | Editor | See what would receive AfterRelationCascade before archiving or deleting an item. Read-only. |
upsert_relation_kind | Admin | Register or update a relation kind. Idempotent on type_name. |
list_relation_kinds | Author | Inspect the kind registry. |
Wiring
smeldr.CreateRelationTables(db)
store, _ := smeldr.NewRelationStore(db)
app := smeldr.New(cfg).Relations(store)
mcpSrv := mcp.New(app) // all six relation tools wired automatically
No mcp.WithRelations() option. The tools appear only when app.RelationStore() != nil.
Why assert_relation is not idempotent
MCPAssertRelation calls insertEdge, which generates a new UUID for every call. The ON CONFLICT clause in the DB only guards the edge's own ID — two calls with identical endpoints create two edge records. This is by design: the relation graph supports multiple parallel edges between the same pair (different confidence scores, different created_by_job values). Call get_relations first if you want to avoid duplicates.
Output format
All tools return snake_case field names (source_type, edge_class, relation_kind, etc.) even though the underlying RelationEdge struct uses db: tags. The MCP layer applies local conversion helpers to keep the output consistent with every other Smeldr tool.