Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Typically, each MCP server is implemented with an MCP SDK:
These servers aim to demonstrate MCP features and the official SDKs.

- **[Everything](src/everything)** - Reference / test server with prompts, resources, and tools.
- **[Exogram](src/exogram)** - Execution boundary and governance authority runtime for autonomous AI.
- **[Fetch](src/fetch)** - Web content fetching and conversion for efficient LLM usage.
- **[Filesystem](src/filesystem)** - Secure file operations with configurable access controls.
- **[Git](src/git)** - Tools to read, search, and manipulate Git repositories.
Expand Down
1 change: 1 addition & 0 deletions src/exogram/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.10
36 changes: 36 additions & 0 deletions src/exogram/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Exogram MCP Server

A Model Context Protocol server providing tools to evaluate, commit, and audit agentic actions through the Exogram Authority Runtime.

This server enables AI models (like Claude Desktop) to request cryptographic authorization before executing state-changing tools (such as database writes, payment updates, or API mutations) and securely store/retrieve records in the Exogram vault.

## Tools

- `exogram_evaluate_action`: Request authorization for a tool call. Returns a token if ALLOWED, or blocks execution if it violates policy constraints.
- `exogram_commit_action`: Commit an executed action token to the immutable audit ledger.
- `exogram_store_record`: Store a fact/record in the encrypted trust vault (scrubs PII, matches conflicts).
- `exogram_search_records`: Search vault records using semantic similarity.

## Configuration

Add this to your Claude Desktop configuration file:

```json
{
"mcpServers": {
"exogram": {
"command": "uv",
"args": [
"run",
"--package",
"mcp-server-exogram",
"mcp-server-exogram"
],
"env": {
"EXOGRAM_API_URL": "https://api.exogram.ai",
"EXOGRAM_BEARER_TOKEN": "<YOUR_EXOGRAM_BEARER_TOKEN>"
}
}
}
}
```
31 changes: 31 additions & 0 deletions src/exogram/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[project]
name = "mcp-server-exogram"
version = "0.1.0"
description = "A Model Context Protocol server providing tools to evaluate, commit, and audit actions through the Exogram Authority Runtime"
readme = "README.md"
requires-python = ">=3.10"
authors = [{ name = "Exogram Team", email = "engineering@exogram.ai" }]
keywords = ["mcp", "security", "governance", "runtime", "llm"]
license = { text = "MIT" }
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
]
dependencies = [
"httpx>=0.27",
"mcp>=1.1.2",
"pydantic>=2.0.0",
]

[project.scripts]
mcp-server-exogram = "mcp_server_exogram:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.uv]
dev-dependencies = ["pyright>=1.1.389", "ruff>=0.7.3"]
6 changes: 6 additions & 0 deletions src/exogram/src/mcp_server_exogram/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .server import mcp

def main():
mcp.run()

__all__ = ["main", "mcp"]
206 changes: 206 additions & 0 deletions src/exogram/src/mcp_server_exogram/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# pyright: reportUnknownParameterType=false
from typing import Any
import os
import httpx
import json
from mcp.server.fastmcp import FastMCP

# Initialize the proxy server
mcp = FastMCP("Exogram Execution Control Plane")

API_URL = os.getenv("EXOGRAM_API_URL", "https://api.exogram.ai")

def get_headers() -> dict:
token = os.getenv("EXOGRAM_BEARER_TOKEN")
if not token:
raise ValueError("EXOGRAM_BEARER_TOKEN environment variable is missing. Check Claude Desktop config.")
return {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"User-Agent": "Exogram-MCP-Claude-Desktop/1.0"
}

# Local store for payload between evaluate→commit
# Maps token → payload so commit can forward the exact same payload
_pending_payloads: dict[str, Any] = {}


@mcp.tool()
def exogram_evaluate_action(action_type: str, namespace: str, agent_id: str, payload: str) -> str:
"""
Request cryptographic authorization to execute a state changing action.
Must be called before writing to any database or external API.
"""
url = f"{API_URL}/v2/evaluate"

try:
parsed_payload = json.loads(payload) if isinstance(payload, str) else payload
except json.JSONDecodeError:
parsed_payload = payload

request_data = {
"action_type": action_type,
"namespace": namespace,
"agent_id": agent_id,
"payload": parsed_payload
}

try:
headers = get_headers()
except ValueError as e:
return f"STATUS: CONFIG ERROR. {str(e)}"

with httpx.Client(timeout=10.0) as client:
try:
response = client.post(url, json=request_data, headers=headers)

if response.status_code == 200:
token = response.json().get("token")
# Store the payload so commit can forward it
_pending_payloads[token] = parsed_payload
return f"STATUS: ALLOWED. Execution Token Issued: {token}"
elif response.status_code == 403:
return f"STATUS: BLOCKED. Policy violation: {response.text}"
elif response.status_code == 429:
return f"STATUS: RATE LIMITED. {response.text}"
else:
return f"STATUS: ERROR. Code {response.status_code}: {response.text}"

except Exception as e:
return f"STATUS: NETWORK FAILURE. Could not reach Exogram Control Plane: {str(e)}"


@mcp.tool()
def exogram_commit_action(token: str, status: str) -> str:
"""
Commit a previously authorized action to the immutable audit ledger.
Must be called immediately after the action is executed.
"""
url = f"{API_URL}/v2/commit"

# Retrieve the original payload that was evaluated
payload = _pending_payloads.pop(token, {})

request_data = {
"token": token,
"status": status,
"payload": payload
}

try:
headers = get_headers()
except ValueError as e:
return f"STATUS: CONFIG ERROR. {str(e)}"

with httpx.Client(timeout=10.0) as client:
try:
response = client.post(url, json=request_data, headers=headers)

if response.status_code in [200, 409]:
return f"STATUS: COMMITTED. Audit log updated. Server Response: {response.text}"
else:
return f"STATUS: COMMIT ERROR. Code {response.status_code}: {response.text}"

except Exception as e:
return f"STATUS: NETWORK FAILURE. Could not reach Exogram Control Plane: {str(e)}"


@mcp.tool()
def exogram_store_record(content: str, source: str = "mcp-claude", namespace: str = "default") -> str:
"""
Store a fact or record in Exogram's encrypted trust vault.
This persists the content to the ledger with encryption, PII scrubbing,
vector embedding, and conflict detection.

Use this to save important facts, user preferences, or any information
that should be retained across sessions.

Args:
content: The fact or record to store (e.g. "User prefers dark mode")
source: Where this came from. Use "mcp-claude" for Claude Desktop entries.
namespace: Logical grouping (default: "default")
"""
url = f"{API_URL}/v2/vault/store"

request_data = {
"content": content,
"source": source,
"source_llm": "anthropic",
"namespace": namespace,
}

try:
headers = get_headers()
except ValueError as e:
return f"STATUS: CONFIG ERROR. {str(e)}"

with httpx.Client(timeout=15.0) as client:
try:
response = client.post(url, json=request_data, headers=headers)

if response.status_code == 200:
data = response.json()
memory_id = data.get("memory_id", "unknown")
conflicts = data.get("conflicts_detected", 0)
result = f"STATUS: STORED. Record ID: {memory_id}"
if conflicts > 0:
result += f" | {conflicts} conflict(s) detected"
return result
elif response.status_code == 429:
return f"STATUS: RATE LIMITED. {response.text}"
elif response.status_code == 401:
return f"STATUS: AUTH FAILED. Bearer token may be expired. Re-run the MCP installer."
else:
return f"STATUS: STORE ERROR. Code {response.status_code}: {response.text}"

except Exception as e:
return f"STATUS: NETWORK FAILURE. Could not reach Exogram Vault: {str(e)}"


@mcp.tool()
def exogram_search_records(query: str, top_k: int = 5) -> str:
"""
Search through stored ledger records using semantic similarity.
Returns the most relevant records matching the query.

Args:
query: What to search for (e.g. "user's favorite color")
top_k: Number of results to return (1-20, default 5)
"""
url = f"{API_URL}/v2/vault/search"

request_data = {
"query": query,
"top_k": min(max(top_k, 1), 20),
}

try:
headers = get_headers()
except ValueError as e:
return f"STATUS: CONFIG ERROR. {str(e)}"

with httpx.Client(timeout=15.0) as client:
try:
response = client.post(url, json=request_data, headers=headers)

if response.status_code == 200:
data = response.json()
results = data.get("results", [])
if not results:
return "No matching records found."

lines = [f"Found {len(results)} matching records:"]
for i, r in enumerate(results, 1):
content = r.get("content", r.get("claim", "Unknown"))
score = r.get("score", r.get("similarity", 0))
lines.append(f" {i}. [{score:.2f}] {content}")
return "\n".join(lines)
else:
return f"STATUS: SEARCH ERROR. Code {response.status_code}: {response.text}"

except Exception as e:
return f"STATUS: NETWORK FAILURE. Could not reach Exogram Vault: {str(e)}"


if __name__ == "__main__":
mcp.run()
Loading
Loading