Files
semantica/mcp
Sameer Kadam 798a7455e4 fix(mcp): complete persistence and setup fixes (#1394)
MCP's stdio transport uses stdout for JSON-RPC framing, so anything else written there corrupts every response after it. The original #1134 bug was progress-tracker output landing on stdout during tool calls that construct a `ContextGraph`, which is exactly what happens on any request that triggers reasoning or extraction. This PR closes out the remaining pieces of that fix: loading now goes through `load_from_file()` instead of the older `load()` path on the root graph, and mutations, `record_decision`, `add_entity`, `add_relationship`, now persist back to `SEMANTICA_KG_PATH` when it's configured, in both MCP server implementations (the root `mcp/` package and the packaged `semantica.mcp_server`), not just one.

Four things came out of review on top of that.

The stdio regression test originally exercised `get_graph_summary`, which doesn't touch the progress tracker at all, so it couldn't have caught the original bug. Swapped it for `run_reasoning`: `Reasoner.infer_with_results()` calls `progress_tracker.start_tracking()` directly, the exact call site that corrupted stdout before, so this is the minimal path that actually proves the fix. The test now spawns a real `python -m mcp` subprocess, sends it a `tools/call` for `run_reasoning`, and asserts every single line on stdout parses as JSON.

Loading a corrupt or unreadable `SEMANTICA_KG_PATH` used to fail silently and fall through to an empty graph, which meant the next mutation would happily save that empty graph over the original file. Both implementations now track whether the initial load actually succeeded. If it didn't, every mutation handler refuses to save and returns an error instead, so a broken file on disk stays broken rather than getting silently replaced with nothing. An empty file is treated differently: that's a fresh destination, not a corrupt one, and starts a normal empty graph without tripping the guard.

`save_to_file` used to `open(path, 'w')` and `json.dump` directly into the destination, so a crash or disk-full error mid-write could leave a truncated file as the only copy of the graph. It now writes to a temp file in the same directory, flushes, fsyncs, and only then `os.replace`s the destination, so the destination is always either the old contents or the new contents, never a partial write. The temp file gets cleaned up if anything fails before the replace.

And since a mutation is applied to the in-memory graph before the save happens, a save failure used to leave the in-memory graph ahead of what's on disk, an entity or decision the client thinks succeeded but that never made it to the file. `record_decision`, `add_entity`, and `add_relationship` all roll back the in-memory mutation now if `save_to_file` raises, so the client-visible state and the persisted state never diverge: either both hold the change or neither does.

104 tests passing across the MCP, persistence, and progress-tracking suites.
2026-09-03 03:35:20 +05:00
..

Semantica MCP Server

A fully modular Model Context Protocol server for the Semantica knowledge graph.
Connects Claude Code, Cursor, Windsurf, Cline, Continue, VS Code (GitHub Copilot), and any other MCP-compatible AI tool directly to your Semantica graph.


Quick start

# From the repo root — no extra install flag needed; the root mcp/ package is
# part of the repository and does not require an external MCP SDK.
pip install -e .

# Test the server (type a JSON-RPC request, press Enter)
python -m mcp

Or point your AI tool at it (see per-tool configs below).


Transport

stdio — the server reads newline-delimited JSON-RPC 2.0 from stdin and writes responses to stdout.
Log/debug output goes to stderr only.

python -m mcp [--debug]

Tools (17 total)

Extraction

Tool Description
extract_entities Named entity recognition (NER) — people, places, orgs, concepts
extract_relations Relation extraction + (subject, predicate, object) triplets
extract_all Full pipeline: NER + coreference + relations + events + triplets

Decision Intelligence

Tool Description
record_decision Record a decision with context, confidence, causal links
query_decisions Query decisions by natural language or structured filters
find_precedents Find past decisions similar to a scenario (hybrid similarity)
get_causal_chain Trace upstream/downstream causal chain from a decision
analyze_decision_impact Analyse downstream influence of a decision

Knowledge Graph

Tool Description
add_entity Add a node/entity to the graph
add_relationship Add a directed edge between two entities
search_graph Search nodes by label or ID substring
get_graph_summary Node/edge counts, decision count, type breakdown
get_graph_analytics PageRank, betweenness, degree centrality, community detection

Reasoning

Tool Description
run_reasoning Forward-chaining IF/THEN rules over facts
abductive_reasoning Generate plausible hypotheses for observations

Export & Provenance

Tool Description
export_graph Export graph to JSON, CSV, GraphML, Parquet, Turtle, N-Triples, RDF/XML, JSON-LD
get_provenance Audit history and source lineage for a node

Resources (4 total)

URI Description
semantica://graph/summary Live node/edge counts and type breakdown
semantica://decisions/list Most recent 50 decisions
semantica://schema/info Schema version, node/edge types, tool names
semantica://ontology/schema Full ontology schema

Per-tool configuration

Claude Code (~/.claude.json or .mcp.json)

Claude Code supports two MCP configuration scopes:

  • User scope~/.claude.json applies across all projects for your user account.
  • Project scope.mcp.json in your project root applies only to that project.

Both files use the same mcpServers structure:

{
  "mcpServers": {
    "semantica": {
      "command": "python",
      "args": ["-m", "mcp"],
      "env": {
        "PYTHONPATH": "/path/to/semantica"
      }
    }
  }
}

Why PYTHONPATH? The root mcp/ package is intentionally not included in the installed wheel, so python -m mcp only works when the repository is on Python's import path. Setting PYTHONPATH here ensures this works regardless of the working directory Claude uses when it launches the server.

Or add it via the CLI (user scope):

claude mcp add --scope user semantica \
  -e PYTHONPATH=/path/to/semantica \
  -- python -m mcp

Or for project scope (omit --scope user):

claude mcp add semantica \
  -e PYTHONPATH=/path/to/semantica \
  -- python -m mcp

Cursor (~/.cursor/mcp.json)

{
  "mcpServers": {
    "semantica": {
      "command": "python",
      "args": ["-m", "mcp"],
      "cwd": "/path/to/semantica"
    }
  }
}

Windsurf (~/.codeium/windsurf/mcp_config.json)

{
  "mcpServers": {
    "semantica": {
      "command": "python",
      "args": ["-m", "mcp"],
      "cwd": "/path/to/semantica"
    }
  }
}

Cline (VS Code extension settings)

In your VS Code settings.json:

{
  "cline.mcpServers": {
    "semantica": {
      "command": "python",
      "args": ["-m", "mcp"],
      "cwd": "/path/to/semantica"
    }
  }
}

Continue (~/.continue/config.json)

{
  "mcpServers": [
    {
      "name": "semantica",
      "command": "python",
      "args": ["-m", "mcp"],
      "cwd": "/path/to/semantica"
    }
  ]
}

VS Code (GitHub Copilot) — .vscode/mcp.json

{
  "servers": {
    "semantica": {
      "type": "stdio",
      "command": "python",
      "args": ["-m", "mcp"],
      "cwd": "${workspaceFolder}"
    }
  }
}

Amazon Q Developer

Add to your Q Developer MCP config:

{
  "mcpServers": {
    "semantica": {
      "command": "python",
      "args": ["-m", "mcp"],
      "cwd": "/path/to/semantica"
    }
  }
}

Environment variables

Variable Default Description
SEMANTICA_KG_PATH (in-memory only) Path to a JSON file used to load the graph on startup and persist mutations (record decisions, add entities/relationships) back to disk after each change. When unset the graph lives in memory only and is lost when the server exits.

Package structure

mcp/
├── __init__.py          # Package entry, re-exports SemanticaMCPServer + main
├── __main__.py          # python -m mcp entry point
├── server.py            # SemanticaMCPServer class + stdio event loop
├── session.py           # Lazy ContextGraph singleton (get_graph / reset_graph)
├── schemas.py           # JSON Schema definitions for all tool inputs
├── tools/
│   ├── __init__.py      # Assembles TOOL_DEFINITIONS list
│   ├── extraction.py    # NER, relation extraction, full pipeline
│   ├── decisions.py     # Record, query, precedents, causal chain, impact
│   ├── graph.py         # Add entity/relationship, search, summary, analytics
│   ├── reasoning.py     # Forward-chaining rules, abductive hypotheses
│   └── export.py        # Graph export (multi-format) + provenance
└── resources/
    ├── __init__.py      # Re-exports RESOURCE_DEFINITIONS + handle_resource_read
    └── registry.py      # URI → handler map for the 4 semantica:// resources