@@ -109,7 +109,7 @@ Semantica ships **native plugin bundles** for Claude Code, Cursor, and Codex โ

Continue
-REST API
+MCP server + plugin
|

@@ -146,7 +146,17 @@ Semantica ships **native plugin bundles** for Claude Code, Cursor, and Codex โ
### Plugin Bundles (Claude Code ยท Cursor ยท Codex)
-Native plugin bundles live under [`plugins/`](plugins/) โ install once, works across all three tools.
+Native plugin bundles live under [`plugins/`](plugins/). Each directory contains a `plugin.json`, `marketplace.json`, and `README.md`.
+
+| Bundle | Directory | Tools |
+|---|---|---|
+| Claude Code | [`plugins/.claude-plugin/`](plugins/.claude-plugin/) | 17 skills ยท 3 agents ยท hooks |
+| Cursor | [`plugins/.cursor-plugin/`](plugins/.cursor-plugin/) | 17 skills ยท 3 agents ยท hooks |
+| Codex CLI | [`plugins/.codex-plugin/`](plugins/.codex-plugin/) | 17 skills ยท 3 agents |
+| Windsurf | [`plugins/.windsurf-plugin/`](plugins/.windsurf-plugin/) | 17 skills ยท 3 agents ยท MCP config |
+| Cline | [`plugins/.cline-plugin/`](plugins/.cline-plugin/) | 17 skills ยท 3 agents ยท MCP config |
+| Continue | [`plugins/.continue-plugin/`](plugins/.continue-plugin/) | 17 skills ยท 3 agents ยท MCP config |
+| VS Code | [`plugins/.vscode-plugin/`](plugins/.vscode-plugin/) | 17 skills ยท 3 agents ยท MCP config |
**17 domain skills:**
@@ -182,9 +192,36 @@ Native plugin bundles live under [`plugins/`](plugins/) โ install once, works
โ [`plugins/.claude-plugin/README.md`](plugins/.claude-plugin/README.md)
+### MCP Server (expose Semantica to any MCP-aware tool)
+
+Semantica ships a full **MCP server** (`semantica/mcp_server.py`) โ run it once and any MCP-compatible tool connects automatically:
+
+```bash
+python -m semantica.mcp_server
+```
+
+Add to your tool's config (Claude Desktop, Windsurf, Cline, Continue, VS Code, Roo Code):
+
+```json
+{
+ "mcpServers": {
+ "semantica": {
+ "command": "python",
+ "args": ["-m", "semantica.mcp_server"]
+ }
+ }
+}
+```
+
+**12 tools exposed:** `extract_entities`, `extract_relations`, `record_decision`, `query_decisions`, `find_precedents`, `get_causal_chain`, `add_entity`, `add_relationship`, `run_reasoning`, `get_graph_analytics`, `export_graph`, `get_graph_summary`
+
+**3 resources:** `semantica://graph/summary`, `semantica://decisions/list`, `semantica://schema/info`
+
+See [`plugins/.claude-plugin/README.md`](plugins/.claude-plugin/README.md) for per-tool config snippets.
+
### MCP Client (Ingest from MCP Servers)
-Semantica includes an **MCP client** (`semantica/ingest/mcp_client.py`) that lets you pull data from any Python/FastMCP server into a knowledge graph:
+Semantica also includes an **MCP client** (`semantica/ingest/mcp_client.py`) that lets you pull data from any Python/FastMCP server into a knowledge graph:
```python
from semantica.ingest import MCPClient
@@ -825,13 +862,13 @@ Start the Semantica server (`python -m semantica.server`, port 8000) and point a
| [Claude Code](https://claude.com/product/claude-code) | **Native plugin** | `plugins/.claude-plugin/` โ 17 skills, 3 agents, `hooks.json` |
| [Cursor](https://cursor.com) | **Native plugin** | `plugins/.cursor-plugin/` โ same 17 skills + 3 agents |
| [Codex CLI](https://github.com/openai/codex) | **Native plugin** | `plugins/.codex-plugin/` โ same 17 skills + 3 agents |
-| [Windsurf](https://windsurf.com) | REST API | Point at `http://localhost:8000/api` |
-| [Claude Desktop](https://claude.ai/download) | REST API | Point at `http://localhost:8000/api` |
-| [VS Code](https://github.com/microsoft/vscode) | REST API | Use any REST client extension |
+| [Windsurf](https://windsurf.com) | **MCP server** + plugin | `plugins/.windsurf-plugin/` ยท add `python -m semantica.mcp_server` to `~/.codeium/windsurf/mcp_config.json` |
+| [Claude Desktop](https://claude.ai/download) | **MCP server** | Add `python -m semantica.mcp_server` to `claude_desktop_config.json` |
+| [VS Code](https://github.com/microsoft/vscode) | **MCP server** + plugin | `plugins/.vscode-plugin/` ยท add to `settings.json` under `mcp.servers` |
| [GitHub Copilot](https://github.com/features/copilot) | REST API | Use via Copilot Chat custom tools |
-| [Cline](https://github.com/cline/cline) | REST API | Add as a custom tool endpoint |
-| [Roo Code](https://github.com/RooCodeInc/Roo-Code) | REST API | Add as a custom tool endpoint |
-| [Continue](https://github.com/continuedev/continue) | REST API | Add to `~/.continue/config.json` as context provider |
+| [Cline](https://github.com/cline/cline) | **MCP server** + plugin | `plugins/.cline-plugin/` ยท add server in Cline MCP settings panel |
+| [Roo Code](https://github.com/RooCodeInc/Roo-Code) | **MCP server** | Add `python -m semantica.mcp_server` in Roo Code MCP settings |
+| [Continue](https://github.com/continuedev/continue) | **MCP server** + plugin | `plugins/.continue-plugin/` ยท add to `~/.continue/config.json` under `mcpServers` |
| [Goose](https://github.com/block/goose) | REST API | Add to Goose toolset config |
| [Kilo Code](https://github.com/Kilo-Org/kilocode) | REST API | Add as custom REST tool |
| [Aider](https://github.com/Aider-AI/aider) | REST API | Pass context from the API into prompts |
diff --git a/plugins/.claude-plugin/README.md b/plugins/.claude-plugin/README.md
index 8aca646c..2b6beaad 100644
--- a/plugins/.claude-plugin/README.md
+++ b/plugins/.claude-plugin/README.md
@@ -2,13 +2,21 @@
Semantica ships a shared plugin bundle under `plugins/` with skills, agents, and hooks for knowledge graphs, context graphs, decision intelligence, reasoning, explainability, provenance, ontology, and export workflows.
-This README is for community users who want to install or reuse the plugin package across Claude, Cursor, and Codex.
+This README covers installation across every supported platform.
## Supported Platforms
-- Claude Code
-- Cursor
-- Codex
+| Platform | Method | Config file |
+|---|---|---|
+| Claude Code | Native plugin bundle | `plugins/.claude-plugin/plugin.json` |
+| Cursor | Native plugin bundle | `plugins/.cursor-plugin/plugin.json` |
+| Codex CLI | Native plugin bundle | `plugins/.codex-plugin/plugin.json` |
+| Windsurf | MCP server + plugin bundle | `plugins/.windsurf-plugin/plugin.json` |
+| Cline (VS Code) | MCP server + plugin bundle | `plugins/.cline-plugin/plugin.json` |
+| Continue | MCP server | `plugins/.continue-plugin/plugin.json` |
+| VS Code | MCP server | `plugins/.vscode-plugin/plugin.json` |
+| Claude Desktop | MCP server | โ (see MCP section below) |
+| Any MCP client | MCP server | `python -m semantica.mcp_server` |
## Prerequisites
@@ -23,12 +31,16 @@ cd semantica
```text
plugins/
- skills/
- agents/
- hooks/
- .claude-plugin/
- .cursor-plugin/
- .codex-plugin/
+ skills/ โ 17 domain skills
+ agents/ โ 3 specialized agents
+ hooks/ โ hooks.json
+ .claude-plugin/ โ Claude Code manifest
+ .cursor-plugin/ โ Cursor manifest
+ .codex-plugin/ โ Codex CLI manifest
+ .windsurf-plugin/โ Windsurf manifest + MCP config
+ .cline-plugin/ โ Cline manifest + MCP config
+ .continue-plugin/โ Continue manifest + MCP config
+ .vscode-plugin/ โ VS Code manifest + MCP config
```
## Plugin Contents
@@ -128,6 +140,114 @@ After installing on any platform, these are good smoke tests:
4. `/semantica:explain decision `
5. `/semantica:validate graph`
+## MCP Server (Windsurf ยท Cline ยท Continue ยท VS Code ยท Claude Desktop ยท Any tool)
+
+Semantica includes a full MCP server (`semantica/mcp_server.py`) that exposes 12 tools and 3 resources over stdio โ compatible with any MCP-aware tool.
+
+### Start the server
+
+```bash
+python -m semantica.mcp_server
+```
+
+### Configure in your tool
+
+**Claude Desktop** โ `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
+
+```json
+{
+ "mcpServers": {
+ "semantica": {
+ "command": "python",
+ "args": ["-m", "semantica.mcp_server"]
+ }
+ }
+}
+```
+
+**Windsurf** โ `~/.codeium/windsurf/mcp_config.json`:
+
+```json
+{
+ "mcpServers": {
+ "semantica": {
+ "command": "python",
+ "args": ["-m", "semantica.mcp_server"]
+ }
+ }
+}
+```
+
+**Cline** โ Cline MCP settings panel โ Add server:
+
+```json
+{
+ "semantica": {
+ "command": "python",
+ "args": ["-m", "semantica.mcp_server"]
+ }
+}
+```
+
+**Continue** โ `~/.continue/config.json`:
+
+```json
+{
+ "mcpServers": [
+ {
+ "name": "semantica",
+ "command": "python",
+ "args": ["-m", "semantica.mcp_server"]
+ }
+ ]
+}
+```
+
+**VS Code** โ `settings.json`:
+
+```json
+{
+ "mcp.servers": {
+ "semantica": {
+ "command": "python",
+ "args": ["-m", "semantica.mcp_server"]
+ }
+ }
+}
+```
+
+### Available MCP tools
+
+| Tool | Description |
+|---|---|
+| `extract_entities` | Named entity recognition from text |
+| `extract_relations` | Relation and triplet extraction from text |
+| `record_decision` | Record a decision with full context and metadata |
+| `query_decisions` | Query recorded decisions by natural language or category |
+| `find_precedents` | Find past decisions similar to a scenario |
+| `get_causal_chain` | Trace upstream/downstream causal chain from a decision |
+| `add_entity` | Add a node/entity to the knowledge graph |
+| `add_relationship` | Add a directed edge between two entities |
+| `run_reasoning` | Run IF/THEN rules over facts to derive new facts |
+| `get_graph_analytics` | PageRank centrality and community detection |
+| `export_graph` | Export graph as Turtle, JSON-LD, N-Triples, or JSON |
+| `get_graph_summary` | Node count, decision count, graph status |
+
+### Available MCP resources
+
+| URI | Description |
+|---|---|
+| `semantica://graph/summary` | High-level graph statistics |
+| `semantica://decisions/list` | All recorded decisions |
+| `semantica://schema/info` | Server info and capability list |
+
+### Environment variables
+
+| Variable | Description |
+|---|---|
+| `SEMANTICA_KG_PATH` | Path to a persisted graph to load on start |
+| `SEMANTICA_LOG_LEVEL` | Log level: DEBUG, INFO, WARNING (default: WARNING) |
+
## Community Notes
- Keep plugin name/version/keywords updated in each manifest before publishing.
diff --git a/plugins/.cline-plugin/README.md b/plugins/.cline-plugin/README.md
new file mode 100644
index 00000000..c097b82b
--- /dev/null
+++ b/plugins/.cline-plugin/README.md
@@ -0,0 +1,24 @@
+# Semantica โ Cline Plugin
+
+Adds all 17 Semantica skills, 3 agents, and hook configuration to Cline (VS Code extension).
+
+## MCP Server Setup (recommended)
+
+In Cline settings, add a new MCP server:
+
+```json
+{
+ "semantica": {
+ "command": "python",
+ "args": ["-m", "semantica.mcp_server"],
+ "env": {}
+ }
+}
+```
+
+Cline will discover all 12 Semantica tools automatically on connection.
+
+## Requirements
+
+- Python 3.8+
+- `pip install semantica`
diff --git a/plugins/.cline-plugin/marketplace.json b/plugins/.cline-plugin/marketplace.json
new file mode 100644
index 00000000..335fb8fc
--- /dev/null
+++ b/plugins/.cline-plugin/marketplace.json
@@ -0,0 +1,17 @@
+{
+ "name": "semantica-cline",
+ "plugins": [
+ {
+ "name": "semantica",
+ "description": "Semantica plugin for Cline: knowledge graph skills, reasoning, extraction, and visualization.",
+ "source": "./",
+ "category": "Productivity",
+ "tags": [
+ "knowledge-graph",
+ "reasoning",
+ "semantica",
+ "cline"
+ ]
+ }
+ ]
+}
diff --git a/plugins/.cline-plugin/plugin.json b/plugins/.cline-plugin/plugin.json
new file mode 100644
index 00000000..81c004c4
--- /dev/null
+++ b/plugins/.cline-plugin/plugin.json
@@ -0,0 +1,35 @@
+{
+ "name": "semantica-cline",
+ "displayName": "Semantica Cline Plugin",
+ "description": "Semantica plugin for Cline: knowledge graph skills, decision intelligence, reasoning, extraction, and visualization.",
+ "version": "0.1.0",
+ "author": {
+ "name": "Semantica Contributors"
+ },
+ "homepage": "https://github.com/Hawksight-AI/semantica",
+ "repository": "https://github.com/Hawksight-AI/semantica",
+ "license": "MIT",
+ "keywords": [
+ "semantica",
+ "knowledge graph",
+ "cline",
+ "context graphs",
+ "decision intelligence",
+ "explainability",
+ "causal analysis",
+ "provenance",
+ "ontology",
+ "graph analytics",
+ "semantic extraction",
+ "visualization",
+ "reasoning",
+ "mcp"
+ ],
+ "skills": "../skills",
+ "agents": "../agents",
+ "hooks": "../hooks/hooks.json",
+ "mcp": {
+ "server": "python -m semantica.mcp_server",
+ "transport": "stdio"
+ }
+}
diff --git a/plugins/.continue-plugin/README.md b/plugins/.continue-plugin/README.md
new file mode 100644
index 00000000..b1a4ca2d
--- /dev/null
+++ b/plugins/.continue-plugin/README.md
@@ -0,0 +1,26 @@
+# Semantica โ Continue Plugin
+
+Adds Semantica as an MCP server and context provider to [Continue.dev](https://continue.dev).
+
+## MCP Server Setup
+
+Add to `~/.continue/config.json`:
+
+```json
+{
+ "mcpServers": [
+ {
+ "name": "semantica",
+ "command": "python",
+ "args": ["-m", "semantica.mcp_server"]
+ }
+ ]
+}
+```
+
+Continue will show all Semantica tools in the `@semantica` context provider dropdown.
+
+## Requirements
+
+- Python 3.8+
+- `pip install semantica`
diff --git a/plugins/.continue-plugin/marketplace.json b/plugins/.continue-plugin/marketplace.json
new file mode 100644
index 00000000..0ff7e099
--- /dev/null
+++ b/plugins/.continue-plugin/marketplace.json
@@ -0,0 +1,17 @@
+{
+ "name": "semantica-continue",
+ "plugins": [
+ {
+ "name": "semantica",
+ "description": "Semantica plugin for Continue.dev: knowledge graph context provider, reasoning, and extraction.",
+ "source": "./",
+ "category": "Productivity",
+ "tags": [
+ "knowledge-graph",
+ "reasoning",
+ "semantica",
+ "continue"
+ ]
+ }
+ ]
+}
diff --git a/plugins/.continue-plugin/plugin.json b/plugins/.continue-plugin/plugin.json
new file mode 100644
index 00000000..da93dc38
--- /dev/null
+++ b/plugins/.continue-plugin/plugin.json
@@ -0,0 +1,33 @@
+{
+ "name": "semantica-continue",
+ "displayName": "Semantica Continue Plugin",
+ "description": "Semantica plugin for Continue.dev: knowledge graph context provider, decision intelligence, reasoning, and semantic extraction.",
+ "version": "0.1.0",
+ "author": {
+ "name": "Semantica Contributors"
+ },
+ "homepage": "https://github.com/Hawksight-AI/semantica",
+ "repository": "https://github.com/Hawksight-AI/semantica",
+ "license": "MIT",
+ "keywords": [
+ "semantica",
+ "knowledge graph",
+ "continue",
+ "context provider",
+ "decision intelligence",
+ "explainability",
+ "causal analysis",
+ "provenance",
+ "ontology",
+ "semantic extraction",
+ "reasoning",
+ "mcp"
+ ],
+ "skills": "../skills",
+ "agents": "../agents",
+ "hooks": "../hooks/hooks.json",
+ "mcp": {
+ "server": "python -m semantica.mcp_server",
+ "transport": "stdio"
+ }
+}
diff --git a/plugins/.vscode-plugin/README.md b/plugins/.vscode-plugin/README.md
new file mode 100644
index 00000000..5a0b7531
--- /dev/null
+++ b/plugins/.vscode-plugin/README.md
@@ -0,0 +1,36 @@
+# Semantica โ VS Code Plugin
+
+Adds Semantica as an MCP server to VS Code (via GitHub Copilot Chat or any MCP-aware extension).
+
+## MCP Server Setup
+
+Add to your VS Code `settings.json`:
+
+```json
+{
+ "github.copilot.chat.mcp.servers": {
+ "semantica": {
+ "command": "python",
+ "args": ["-m", "semantica.mcp_server"]
+ }
+ }
+}
+```
+
+Or if using the VS Code MCP extension directly:
+
+```json
+{
+ "mcp.servers": {
+ "semantica": {
+ "command": "python",
+ "args": ["-m", "semantica.mcp_server"]
+ }
+ }
+}
+```
+
+## Requirements
+
+- Python 3.8+
+- `pip install semantica`
diff --git a/plugins/.vscode-plugin/marketplace.json b/plugins/.vscode-plugin/marketplace.json
new file mode 100644
index 00000000..76f4b2a2
--- /dev/null
+++ b/plugins/.vscode-plugin/marketplace.json
@@ -0,0 +1,17 @@
+{
+ "name": "semantica-vscode",
+ "plugins": [
+ {
+ "name": "semantica",
+ "description": "Semantica plugin for VS Code: knowledge graph skills, reasoning, extraction, and visualization.",
+ "source": "./",
+ "category": "Productivity",
+ "tags": [
+ "knowledge-graph",
+ "reasoning",
+ "semantica",
+ "vscode"
+ ]
+ }
+ ]
+}
diff --git a/plugins/.vscode-plugin/plugin.json b/plugins/.vscode-plugin/plugin.json
new file mode 100644
index 00000000..a39c097e
--- /dev/null
+++ b/plugins/.vscode-plugin/plugin.json
@@ -0,0 +1,35 @@
+{
+ "name": "semantica-vscode",
+ "displayName": "Semantica VS Code Plugin",
+ "description": "Semantica plugin for VS Code: knowledge graph skills, decision intelligence, reasoning, extraction, and visualization via MCP server.",
+ "version": "0.1.0",
+ "author": {
+ "name": "Semantica Contributors"
+ },
+ "homepage": "https://github.com/Hawksight-AI/semantica",
+ "repository": "https://github.com/Hawksight-AI/semantica",
+ "license": "MIT",
+ "keywords": [
+ "semantica",
+ "knowledge graph",
+ "vscode",
+ "context graphs",
+ "decision intelligence",
+ "explainability",
+ "causal analysis",
+ "provenance",
+ "ontology",
+ "graph analytics",
+ "semantic extraction",
+ "visualization",
+ "reasoning",
+ "mcp"
+ ],
+ "skills": "../skills",
+ "agents": "../agents",
+ "hooks": "../hooks/hooks.json",
+ "mcp": {
+ "server": "python -m semantica.mcp_server",
+ "transport": "stdio"
+ }
+}
diff --git a/plugins/.windsurf-plugin/README.md b/plugins/.windsurf-plugin/README.md
new file mode 100644
index 00000000..4718e807
--- /dev/null
+++ b/plugins/.windsurf-plugin/README.md
@@ -0,0 +1,29 @@
+# Semantica โ Windsurf Plugin
+
+Adds all 17 Semantica skills, 3 agents, and hook configuration to Windsurf.
+
+## MCP Server Setup (recommended)
+
+Add to your Windsurf MCP config (`~/.codeium/windsurf/mcp_config.json`):
+
+```json
+{
+ "mcpServers": {
+ "semantica": {
+ "command": "python",
+ "args": ["-m", "semantica.mcp_server"]
+ }
+ }
+}
+```
+
+Windsurf will then have access to all 12 Semantica tools (extract, record_decision, query_decisions, find_precedents, get_causal_chain, add_entity, add_relationship, run_reasoning, get_graph_analytics, export_graph, and more) directly in the AI panel.
+
+## Skills
+
+All 17 skills under `plugins/skills/` are available as slash commands once the plugin is loaded.
+
+## Requirements
+
+- Python 3.8+
+- `pip install semantica`
diff --git a/plugins/.windsurf-plugin/marketplace.json b/plugins/.windsurf-plugin/marketplace.json
new file mode 100644
index 00000000..e5ea82db
--- /dev/null
+++ b/plugins/.windsurf-plugin/marketplace.json
@@ -0,0 +1,17 @@
+{
+ "name": "semantica-windsurf",
+ "plugins": [
+ {
+ "name": "semantica",
+ "description": "Semantica plugin for Windsurf: knowledge graph skills, reasoning, extraction, and visualization.",
+ "source": "./",
+ "category": "Productivity",
+ "tags": [
+ "knowledge-graph",
+ "reasoning",
+ "semantica",
+ "windsurf"
+ ]
+ }
+ ]
+}
diff --git a/plugins/.windsurf-plugin/plugin.json b/plugins/.windsurf-plugin/plugin.json
new file mode 100644
index 00000000..cbf45713
--- /dev/null
+++ b/plugins/.windsurf-plugin/plugin.json
@@ -0,0 +1,35 @@
+{
+ "name": "semantica-windsurf",
+ "displayName": "Semantica Windsurf Plugin",
+ "description": "Semantica plugin for Windsurf: knowledge graph skills, decision intelligence, reasoning, extraction, and visualization.",
+ "version": "0.1.0",
+ "author": {
+ "name": "Semantica Contributors"
+ },
+ "homepage": "https://github.com/Hawksight-AI/semantica",
+ "repository": "https://github.com/Hawksight-AI/semantica",
+ "license": "MIT",
+ "keywords": [
+ "semantica",
+ "knowledge graph",
+ "windsurf",
+ "context graphs",
+ "decision intelligence",
+ "explainability",
+ "causal analysis",
+ "provenance",
+ "ontology",
+ "graph analytics",
+ "semantic extraction",
+ "visualization",
+ "reasoning",
+ "mcp"
+ ],
+ "skills": "../skills",
+ "agents": "../agents",
+ "hooks": "../hooks/hooks.json",
+ "mcp": {
+ "server": "python -m semantica.mcp_server",
+ "transport": "stdio"
+ }
+}
diff --git a/semantica/mcp_server.py b/semantica/mcp_server.py
new file mode 100644
index 00000000..ee1fb805
--- /dev/null
+++ b/semantica/mcp_server.py
@@ -0,0 +1,606 @@
+"""
+Semantica MCP Server
+
+Exposes Semantica's knowledge graph, decision intelligence, semantic extraction,
+reasoning, and analytics capabilities as an MCP (Model Context Protocol) server
+over stdio โ compatible with Claude Desktop, Windsurf, Cline, Continue, VS Code,
+Roo Code, and any other MCP-aware tool.
+
+Usage
+-----
+Configure in your tool's MCP settings:
+
+ Claude Desktop / Windsurf / Cline / Continue / VS Code:
+ {
+ "mcpServers": {
+ "semantica": {
+ "command": "python",
+ "args": ["-m", "semantica.mcp_server"]
+ }
+ }
+ }
+
+Run directly:
+ python -m semantica.mcp_server
+
+Environment variables:
+ SEMANTICA_KG_PATH โ path to a persisted graph to load on start (optional)
+ SEMANTICA_LOG_LEVEL โ log level: DEBUG, INFO, WARNING (default: WARNING)
+"""
+
+from __future__ import annotations
+
+import json
+import logging
+import os
+import sys
+from typing import Any
+
+# โโ logging โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+_log_level = getattr(logging, os.environ.get("SEMANTICA_LOG_LEVEL", "WARNING").upper(), logging.WARNING)
+logging.basicConfig(stream=sys.stderr, level=_log_level,
+ format="%(asctime)s [semantica-mcp] %(levelname)s %(message)s")
+log = logging.getLogger("semantica.mcp_server")
+
+# โโ lazy graph session โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+_graph: Any = None
+
+
+def _get_graph():
+ global _graph
+ if _graph is None:
+ from semantica.context import ContextGraph
+ _graph = ContextGraph(advanced_analytics=True)
+ kg_path = os.environ.get("SEMANTICA_KG_PATH")
+ if kg_path and os.path.exists(kg_path):
+ try:
+ _graph.load(kg_path)
+ log.info("Loaded graph from %s", kg_path)
+ except Exception as exc:
+ log.warning("Could not load graph from %s: %s", kg_path, exc)
+ return _graph
+
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# Tool implementations
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+def _tool_extract_entities(args: dict) -> dict:
+ """Extract named entities from text."""
+ text = args.get("text", "")
+ if not text:
+ return {"error": "text is required"}
+ from semantica.semantic_extract import NamedEntityRecognizer
+ from semantica.semantic_extract.cache import _result_cache
+ _result_cache.clear()
+ entities = NamedEntityRecognizer().extract(text)
+ return {
+ "entities": [
+ {"label": getattr(e, "label", str(e)),
+ "type": getattr(e, "type", None),
+ "start": getattr(e, "start", None),
+ "end": getattr(e, "end", None)}
+ for e in (entities or [])
+ ]
+ }
+
+
+def _tool_extract_relations(args: dict) -> dict:
+ """Extract relations and triplets from text."""
+ text = args.get("text", "")
+ if not text:
+ return {"error": "text is required"}
+ from semantica.semantic_extract import RelationExtractor, TripletExtractor
+ from semantica.semantic_extract.cache import _result_cache
+ _result_cache.clear()
+ relations = RelationExtractor().extract(text)
+ triplets = TripletExtractor().extract(text)
+ return {
+ "relations": [
+ {"source": getattr(r, "source", None),
+ "type": getattr(r, "type", None),
+ "target": getattr(r, "target", None)}
+ for r in (relations or [])
+ ],
+ "triplets": [
+ {"subject": getattr(t, "subject", None),
+ "predicate": getattr(t, "predicate", None),
+ "object": getattr(t, "object", None)}
+ for t in (triplets or [])
+ ],
+ }
+
+
+def _tool_record_decision(args: dict) -> dict:
+ """Record a decision with full context into the graph."""
+ required = ["category", "scenario", "reasoning", "outcome", "confidence"]
+ for field in required:
+ if field not in args:
+ return {"error": f"missing required field: {field}"}
+ graph = _get_graph()
+ decision_id = graph.record_decision(
+ category=args["category"],
+ scenario=args["scenario"],
+ reasoning=args["reasoning"],
+ outcome=args["outcome"],
+ confidence=float(args["confidence"]),
+ entities=args.get("entities", []),
+ decision_maker=args.get("decision_maker", "mcp_client"),
+ valid_from=args.get("valid_from"),
+ valid_until=args.get("valid_until"),
+ )
+ return {"decision_id": decision_id, "status": "recorded"}
+
+
+def _tool_query_decisions(args: dict) -> dict:
+ """Query decisions by natural language or structured filters."""
+ query = args.get("query", "")
+ category = args.get("category")
+ limit = int(args.get("limit", 10))
+ graph = _get_graph()
+ try:
+ if query:
+ results = graph.find_similar_decisions(query, max_results=limit)
+ elif category:
+ nodes = graph.find_nodes(node_type="decision")
+ results = [n for n in nodes if n.get("category") == category][:limit]
+ else:
+ results = graph.find_nodes(node_type="decision")[:limit]
+ return {"decisions": results if isinstance(results, list) else list(results)}
+ except Exception as exc:
+ return {"error": str(exc), "decisions": []}
+
+
+def _tool_find_precedents(args: dict) -> dict:
+ """Find past decisions similar to a given scenario."""
+ scenario = args.get("scenario", "")
+ if not scenario:
+ return {"error": "scenario is required"}
+ max_results = int(args.get("max_results", 5))
+ graph = _get_graph()
+ try:
+ precedents = graph.find_similar_decisions(scenario, max_results=max_results)
+ return {"precedents": precedents if isinstance(precedents, list) else list(precedents)}
+ except Exception as exc:
+ return {"error": str(exc), "precedents": []}
+
+
+def _tool_get_causal_chain(args: dict) -> dict:
+ """Get the causal chain for a decision."""
+ decision_id = args.get("decision_id", "")
+ if not decision_id:
+ return {"error": "decision_id is required"}
+ direction = args.get("direction", "downstream")
+ max_depth = int(args.get("max_depth", 5))
+ graph = _get_graph()
+ try:
+ from semantica.context.causal_analyzer import CausalChainAnalyzer
+ analyzer = CausalChainAnalyzer(graph_store=graph)
+ chain = analyzer.get_causal_chain(decision_id, direction=direction, max_depth=max_depth)
+ return {"chain": chain if isinstance(chain, list) else list(chain)}
+ except Exception as exc:
+ return {"error": str(exc), "chain": []}
+
+
+def _tool_add_entity(args: dict) -> dict:
+ """Add a node/entity to the knowledge graph."""
+ node_id = args.get("id", "")
+ label = args.get("label", node_id)
+ node_type = args.get("type", "Entity")
+ if not node_id:
+ return {"error": "id is required"}
+ graph = _get_graph()
+ graph.add_node(node_id=node_id, label=label, node_type=node_type,
+ metadata=args.get("metadata", {}))
+ return {"status": "added", "id": node_id}
+
+
+def _tool_add_relationship(args: dict) -> dict:
+ """Add a relationship (edge) between two entities."""
+ source = args.get("source", "")
+ target = args.get("target", "")
+ rel_type = args.get("type", "RELATED_TO")
+ if not source or not target:
+ return {"error": "source and target are required"}
+ graph = _get_graph()
+ graph.add_edge(source_id=source, target_id=target, edge_type=rel_type,
+ metadata=args.get("metadata", {}))
+ return {"status": "added", "source": source, "target": target, "type": rel_type}
+
+
+def _tool_run_reasoning(args: dict) -> dict:
+ """Run forward-chaining reasoning rules over a set of facts."""
+ facts = args.get("facts", [])
+ rules = args.get("rules", [])
+ if not facts or not rules:
+ return {"error": "facts and rules are required"}
+ from semantica.reasoning import Reasoner
+ reasoner = Reasoner()
+ for rule in rules:
+ reasoner.add_rule(rule)
+ derived = reasoner.infer_facts(facts)
+ return {"derived_facts": derived if isinstance(derived, list) else list(derived)}
+
+
+def _tool_get_graph_analytics(args: dict) -> dict:
+ """Compute graph analytics: centrality, community detection, metrics."""
+ graph = _get_graph()
+ try:
+ from semantica.kg import CentralityCalculator, CommunityDetector
+ centrality = CentralityCalculator().calculate_pagerank(graph)
+ communities = CommunityDetector().detect_communities(graph)
+ node_count = len(list(graph.find_nodes()))
+ edge_count = getattr(graph, "edge_count", lambda: 0)()
+ return {
+ "node_count": node_count,
+ "edge_count": edge_count,
+ "top_nodes_by_pagerank": sorted(
+ centrality.items() if hasattr(centrality, "items") else [],
+ key=lambda x: x[1], reverse=True
+ )[:10],
+ "community_count": len(communities) if isinstance(communities, (list, dict)) else 0,
+ }
+ except Exception as exc:
+ return {"error": str(exc)}
+
+
+def _tool_export_graph(args: dict) -> dict:
+ """Export the current knowledge graph to a serialised format."""
+ fmt = args.get("format", "json-ld")
+ graph = _get_graph()
+ try:
+ from semantica.export import RDFExporter, JSONExporter
+ if fmt in ("turtle", "ttl", "nt", "xml", "json-ld"):
+ result = RDFExporter().export_to_rdf(graph, format=fmt)
+ else:
+ result = JSONExporter().export(graph)
+ return {"format": fmt, "data": result}
+ except Exception as exc:
+ return {"error": str(exc)}
+
+
+def _tool_get_graph_summary(args: dict) -> dict:
+ """Return a high-level summary of the current graph."""
+ graph = _get_graph()
+ try:
+ node_count = len(list(graph.find_nodes()))
+ decisions = graph.find_nodes(node_type="decision")
+ return {
+ "node_count": node_count,
+ "decision_count": len(list(decisions)),
+ "graph_ready": True,
+ }
+ except Exception as exc:
+ return {"error": str(exc), "graph_ready": False}
+
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# MCP protocol tables
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+TOOLS = [
+ {
+ "name": "extract_entities",
+ "description": "Extract named entities (people, places, organisations, concepts) from text using Semantica NER.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "text": {"type": "string", "description": "Input text to extract entities from"}
+ },
+ "required": ["text"],
+ },
+ "_handler": _tool_extract_entities,
+ },
+ {
+ "name": "extract_relations",
+ "description": "Extract relations and (subject, predicate, object) triplets from text.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "text": {"type": "string", "description": "Input text to extract relations from"}
+ },
+ "required": ["text"],
+ },
+ "_handler": _tool_extract_relations,
+ },
+ {
+ "name": "record_decision",
+ "description": "Record a decision into the Semantica knowledge graph with full context, causal links, and metadata.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "category": {"type": "string", "description": "Decision category, e.g. 'loan_approval'"},
+ "scenario": {"type": "string", "description": "Natural-language situation description"},
+ "reasoning": {"type": "string", "description": "Why this decision was made"},
+ "outcome": {"type": "string", "description": "Decision outcome, e.g. 'approved'"},
+ "confidence": {"type": "number", "description": "Confidence score 0โ1"},
+ "decision_maker":{"type": "string", "description": "Who/what made the decision"},
+ "valid_from": {"type": "string", "description": "ISO date validity start (optional)"},
+ "valid_until": {"type": "string", "description": "ISO date validity end (optional)"},
+ },
+ "required": ["category", "scenario", "reasoning", "outcome", "confidence"],
+ },
+ "_handler": _tool_record_decision,
+ },
+ {
+ "name": "query_decisions",
+ "description": "Query recorded decisions by natural language, category, or get all recent decisions.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "query": {"type": "string", "description": "Natural language query (optional)"},
+ "category": {"type": "string", "description": "Filter by category (optional)"},
+ "limit": {"type": "integer", "description": "Max results (default 10)"},
+ },
+ },
+ "_handler": _tool_query_decisions,
+ },
+ {
+ "name": "find_precedents",
+ "description": "Find past decisions similar to a given scenario using hybrid similarity search.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "scenario": {"type": "string", "description": "Scenario description to find precedents for"},
+ "max_results": {"type": "integer", "description": "Max results (default 5)"},
+ },
+ "required": ["scenario"],
+ },
+ "_handler": _tool_find_precedents,
+ },
+ {
+ "name": "get_causal_chain",
+ "description": "Trace the causal chain upstream or downstream from a decision.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "decision_id": {"type": "string", "description": "Decision ID to trace"},
+ "direction": {"type": "string", "enum": ["upstream", "downstream"], "description": "Trace direction"},
+ "max_depth": {"type": "integer", "description": "Max chain depth (default 5)"},
+ },
+ "required": ["decision_id"],
+ },
+ "_handler": _tool_get_causal_chain,
+ },
+ {
+ "name": "add_entity",
+ "description": "Add a node/entity to the Semantica knowledge graph.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "id": {"type": "string", "description": "Unique node ID"},
+ "label": {"type": "string", "description": "Human-readable label"},
+ "type": {"type": "string", "description": "Node type, e.g. 'Person', 'Organisation'"},
+ "metadata": {"type": "object", "description": "Additional properties"},
+ },
+ "required": ["id"],
+ },
+ "_handler": _tool_add_entity,
+ },
+ {
+ "name": "add_relationship",
+ "description": "Add a directed relationship (edge) between two entities in the knowledge graph.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "source": {"type": "string", "description": "Source node ID"},
+ "target": {"type": "string", "description": "Target node ID"},
+ "type": {"type": "string", "description": "Relationship type, e.g. 'WORKS_AT'"},
+ "metadata": {"type": "object", "description": "Additional edge properties"},
+ },
+ "required": ["source", "target"],
+ },
+ "_handler": _tool_add_relationship,
+ },
+ {
+ "name": "run_reasoning",
+ "description": "Run forward-chaining IF/THEN rules over a set of facts to derive new facts.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "facts": {
+ "type": "array", "items": {"type": "string"},
+ "description": "List of fact strings, e.g. ['Person(John)', 'Employee(John)']",
+ },
+ "rules": {
+ "type": "array", "items": {"type": "string"},
+ "description": "IF/THEN rule strings, e.g. ['IF Employee(?x) THEN WorkerBee(?x)']",
+ },
+ },
+ "required": ["facts", "rules"],
+ },
+ "_handler": _tool_run_reasoning,
+ },
+ {
+ "name": "get_graph_analytics",
+ "description": "Compute PageRank centrality and community detection over the knowledge graph.",
+ "inputSchema": {"type": "object", "properties": {}},
+ "_handler": _tool_get_graph_analytics,
+ },
+ {
+ "name": "export_graph",
+ "description": "Export the current knowledge graph. Formats: turtle, ttl, nt, xml, json-ld, json.",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "format": {
+ "type": "string",
+ "enum": ["turtle", "ttl", "nt", "xml", "json-ld", "json"],
+ "description": "Export format (default: json-ld)",
+ }
+ },
+ },
+ "_handler": _tool_export_graph,
+ },
+ {
+ "name": "get_graph_summary",
+ "description": "Return a high-level summary of the current knowledge graph: node count, decision count, status.",
+ "inputSchema": {"type": "object", "properties": {}},
+ "_handler": _tool_get_graph_summary,
+ },
+]
+
+RESOURCES = [
+ {
+ "uri": "semantica://graph/summary",
+ "name": "Graph Summary",
+ "description": "High-level statistics about the current knowledge graph",
+ "mimeType": "application/json",
+ },
+ {
+ "uri": "semantica://decisions/list",
+ "name": "Decisions",
+ "description": "List of all recorded decisions in the graph",
+ "mimeType": "application/json",
+ },
+ {
+ "uri": "semantica://schema/info",
+ "name": "Schema Info",
+ "description": "Semantica server info and available capabilities",
+ "mimeType": "application/json",
+ },
+]
+
+
+def _read_resource(uri: str) -> dict:
+ if uri == "semantica://graph/summary":
+ return _tool_get_graph_summary({})
+ if uri == "semantica://decisions/list":
+ return _tool_query_decisions({"limit": 50})
+ if uri == "semantica://schema/info":
+ return {
+ "name": "Semantica",
+ "version": "0.4.0",
+ "tools": [t["name"] for t in TOOLS],
+ "resources": [r["uri"] for r in RESOURCES],
+ }
+ return {"error": f"Unknown resource URI: {uri}"}
+
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# JSON-RPC / MCP protocol handler
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+SERVER_INFO = {
+ "name": "semantica",
+ "version": "0.4.0",
+}
+
+CAPABILITIES = {
+ "tools": {"listChanged": False},
+ "resources": {"listChanged": False, "subscribe": False},
+}
+
+
+def _handle(req: dict) -> dict | None:
+ """Dispatch a single JSON-RPC request; return None for notifications."""
+ method = req.get("method", "")
+ params = req.get("params") or {}
+ req_id = req.get("id")
+
+ def ok(result):
+ return {"jsonrpc": "2.0", "id": req_id, "result": result}
+
+ def err(code, message):
+ return {"jsonrpc": "2.0", "id": req_id, "error": {"code": code, "message": message}}
+
+ # Notifications (no id) โ acknowledge silently
+ if req_id is None and method.startswith("notifications/"):
+ return None
+
+ if method == "initialize":
+ return ok({
+ "protocolVersion": "2024-11-05",
+ "capabilities": CAPABILITIES,
+ "serverInfo": SERVER_INFO,
+ })
+
+ if method == "notifications/initialized":
+ return None
+
+ if method == "ping":
+ return ok({})
+
+ if method == "tools/list":
+ tools_out = [
+ {"name": t["name"], "description": t["description"], "inputSchema": t["inputSchema"]}
+ for t in TOOLS
+ ]
+ return ok({"tools": tools_out})
+
+ if method == "tools/call":
+ name = params.get("name", "")
+ arguments = params.get("arguments") or {}
+ handler = next((t["_handler"] for t in TOOLS if t["name"] == name), None)
+ if handler is None:
+ return err(-32601, f"Unknown tool: {name}")
+ try:
+ result = handler(arguments)
+ text = json.dumps(result, ensure_ascii=False, indent=2)
+ return ok({"content": [{"type": "text", "text": text}]})
+ except Exception as exc:
+ log.exception("Tool %s raised", name)
+ return err(-32603, str(exc))
+
+ if method == "resources/list":
+ return ok({"resources": RESOURCES})
+
+ if method == "resources/read":
+ uri = params.get("uri", "")
+ data = _read_resource(uri)
+ text = json.dumps(data, ensure_ascii=False, indent=2)
+ return ok({"contents": [{"uri": uri, "mimeType": "application/json", "text": text}]})
+
+ if method == "prompts/list":
+ return ok({"prompts": []})
+
+ return err(-32601, f"Method not found: {method}")
+
+
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+# stdio event loop
+# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+
+def _run_stdio():
+ log.info("Semantica MCP server starting on stdio")
+ # Use binary stdin/stdout for reliable newline handling on Windows
+ stdin = sys.stdin.buffer
+ stdout = sys.stdout.buffer
+
+ while True:
+ try:
+ line = stdin.readline()
+ if not line:
+ break
+ line = line.strip()
+ if not line:
+ continue
+ try:
+ req = json.loads(line)
+ except json.JSONDecodeError as exc:
+ resp = {"jsonrpc": "2.0", "id": None,
+ "error": {"code": -32700, "message": f"Parse error: {exc}"}}
+ stdout.write(json.dumps(resp).encode() + b"\n")
+ stdout.flush()
+ continue
+
+ resp = _handle(req)
+ if resp is not None:
+ stdout.write(json.dumps(resp, ensure_ascii=False).encode() + b"\n")
+ stdout.flush()
+ except EOFError:
+ break
+ except KeyboardInterrupt:
+ break
+ except Exception as exc:
+ log.exception("Unhandled error in MCP loop: %s", exc)
+
+ log.info("Semantica MCP server stopped")
+
+
+def main():
+ _run_stdio()
+
+
+if __name__ == "__main__":
+ main()
|