@arkheia/mcp-server 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +177 -89
- package/dist/index.js +219 -0
- package/dist/memory.js +187 -0
- package/dist/providers.js +320 -0
- package/dist/proxy-client.js +219 -0
- package/dist/tool-registry.js +90 -0
- package/package.json +36 -56
- package/scripts/setup.js +35 -13
- package/bin/arkheia-mcp.js +0 -207
- package/python/mcp_server/__init__.py +0 -0
- package/python/requirements.txt +0 -4
package/README.md
CHANGED
|
@@ -1,89 +1,177 @@
|
|
|
1
|
-
# Arkheia MCP Server — Fabrication Detection for AI Agents
|
|
2
|
-
|
|
3
|
-
Know when your AI is making things up.
|
|
4
|
-
|
|
5
|
-
Arkheia screens model responses for fabrication using behavioural fingerprinting. Works with Claude, GPT, Gemini, Grok, Llama, Mistral, and 30+ other models. One tool call. Real-time risk scoring.
|
|
6
|
-
|
|
7
|
-
Free tier: 1,500 detections/month. No credit card.
|
|
8
|
-
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
|
|
1
|
+
# Arkheia MCP Server — Fabrication Detection for AI Agents
|
|
2
|
+
|
|
3
|
+
Know when your AI is making things up.
|
|
4
|
+
|
|
5
|
+
Arkheia screens model responses for fabrication using behavioural fingerprinting. Works with Claude, GPT, Gemini, Grok, Llama, Mistral, and 30+ other models. One tool call. Real-time risk scoring.
|
|
6
|
+
|
|
7
|
+
Free tier: 1,500 detections/month. No credit card.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Requires:
|
|
13
|
+
- Node 18+
|
|
14
|
+
- Python 3.10–3.13 with working pyexpat
|
|
15
|
+
|
|
16
|
+
macOS note: Homebrew's current `brew install python` installs 3.14,
|
|
17
|
+
which has a broken pyexpat link. Use `brew install python@3.12` until
|
|
18
|
+
Homebrew ships a fix. Verify with:
|
|
19
|
+
python3.12 -c "import pyexpat, ensurepip"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g @arkheia/mcp-server
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Get a free API key at [arkheia.ai/mcp/account](https://arkheia.ai/mcp/account), or via the CLI:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
curl -X POST https://arkheia-proxy-production.up.railway.app/v1/provision \
|
|
32
|
+
-H "Content-Type: application/json" \
|
|
33
|
+
-d '{"email": "you@example.com"}'
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Set your key:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export ARKHEIA_API_KEY="ak_live_..."
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Register with your CLI
|
|
43
|
+
|
|
44
|
+
Each AI CLI has a slightly different `mcp add` command. Use the one that matches your tool. All assume you've installed globally with `npm install -g`.
|
|
45
|
+
|
|
46
|
+
### Claude Code
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
claude mcp add arkheia -s user \
|
|
50
|
+
-e ARKHEIA_API_KEY="$ARKHEIA_API_KEY" \
|
|
51
|
+
-- mcp-server
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Config lands in: `~/.claude.json` under `mcpServers.arkheia`
|
|
55
|
+
|
|
56
|
+
### Codex
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
codex mcp add arkheia \
|
|
60
|
+
--env ARKHEIA_API_KEY="$ARKHEIA_API_KEY" \
|
|
61
|
+
-- mcp-server
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Config lands in: `~/.codex/config.toml` under `[mcp_servers.arkheia.env]`
|
|
65
|
+
|
|
66
|
+
Note: `codex login --api-key` is deprecated. Use `printenv OPENAI_API_KEY | codex login --with-api-key` instead.
|
|
67
|
+
|
|
68
|
+
### Gemini
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
gemini mcp add -s user \
|
|
72
|
+
-e ARKHEIA_API_KEY="$ARKHEIA_API_KEY" \
|
|
73
|
+
arkheia mcp-server
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Config lands in: `~/.gemini/settings.json` under `mcpServers.arkheia`
|
|
77
|
+
|
|
78
|
+
**Gotcha:** `gemini mcp list` only shows project-scope servers. If you registered with `-s user`, verify by reading `~/.gemini/settings.json` directly.
|
|
79
|
+
|
|
80
|
+
**Gotcha:** Don't use `npx -y @arkheia/mcp-server` with Gemini — the `-y` flag gets eaten by Gemini's yargs parser as `--yolo`. Use the globally-installed `mcp-server` binary directly.
|
|
81
|
+
|
|
82
|
+
### Grok
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
grok mcp add arkheia \
|
|
86
|
+
-t stdio \
|
|
87
|
+
-c mcp-server \
|
|
88
|
+
-e ARKHEIA_API_KEY="$ARKHEIA_API_KEY"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Config lands in: `~/.grok/settings.json` under `mcpServers.arkheia` (note: env is nested under `transport`, unlike other CLIs)
|
|
92
|
+
|
|
93
|
+
## Verify it works
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Claude Code — live connection test
|
|
97
|
+
claude mcp list
|
|
98
|
+
|
|
99
|
+
# Codex — shows 'enabled' (not a live check)
|
|
100
|
+
codex mcp list
|
|
101
|
+
|
|
102
|
+
# Grok — best: spawns the server and lists all 9 tools
|
|
103
|
+
grok mcp test arkheia
|
|
104
|
+
|
|
105
|
+
# Gemini — no built-in test; start a session and try the tool
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Important:** MCP registrations are not hot-reloaded. Restart your CLI session after running `mcp add`.
|
|
109
|
+
|
|
110
|
+
## What You Get
|
|
111
|
+
|
|
112
|
+
| Tool | Description |
|
|
113
|
+
|------|-------------|
|
|
114
|
+
| `arkheia_verify` | Score any model response for fabrication risk (LOW/MEDIUM/HIGH) |
|
|
115
|
+
| `arkheia_audit_log` | Review your detection history |
|
|
116
|
+
| `run_grok` | Call Grok + screen for fabrication |
|
|
117
|
+
| `run_gemini` | Call Gemini + screen for fabrication |
|
|
118
|
+
| `run_ollama` | Call local Ollama model + screen |
|
|
119
|
+
| `run_together` | Call Together AI (Kimi, DeepSeek) + screen |
|
|
120
|
+
| `memory_store` | Persistent knowledge graph — upsert entity |
|
|
121
|
+
| `memory_retrieve` | Knowledge graph lookup |
|
|
122
|
+
| `memory_relate` | Create relationship between entities |
|
|
123
|
+
|
|
124
|
+
## 35+ Model Profiles
|
|
125
|
+
|
|
126
|
+
GPT-4o, GPT-5.4, Claude Opus/Sonnet/Haiku, Gemini 2.5/3.0, Grok 4, Llama, Mixtral, CodeLlama, Falcon, Phi4, Kimi K2.5, and more. If your model isn't listed, [let us know](mailto:dmurfet@arkheia.ai).
|
|
127
|
+
|
|
128
|
+
## Pricing
|
|
129
|
+
|
|
130
|
+
| Plan | Price | Detections |
|
|
131
|
+
|------|-------|------------|
|
|
132
|
+
| Free | $0 | 1,500/month |
|
|
133
|
+
| Single Contributor | $99/month | Unlimited |
|
|
134
|
+
| Professional | $499/month | Unlimited |
|
|
135
|
+
| Team | $1,999/month | Unlimited |
|
|
136
|
+
|
|
137
|
+
Manage your account at [arkheia.ai/mcp/account](https://arkheia.ai/mcp/account).
|
|
138
|
+
|
|
139
|
+
## Where API keys are stored
|
|
140
|
+
|
|
141
|
+
| CLI | Config file | Key location |
|
|
142
|
+
|-----|-------------|-------------|
|
|
143
|
+
| Claude Code | `~/.claude.json` | `mcpServers.arkheia.env.ARKHEIA_API_KEY` |
|
|
144
|
+
| Codex | `~/.codex/config.toml` | `[mcp_servers.arkheia.env]` section |
|
|
145
|
+
| Gemini | `~/.gemini/settings.json` | `mcpServers.arkheia.env.ARKHEIA_API_KEY` |
|
|
146
|
+
| Grok | `~/.grok/settings.json` | `mcpServers.arkheia.transport.env.ARKHEIA_API_KEY` |
|
|
147
|
+
|
|
148
|
+
## Troubleshooting
|
|
149
|
+
|
|
150
|
+
**"Python 3.10+ is required but not found"** — Install Python 3.12: `brew install python@3.12` (macOS) or download from [python.org](https://python.org).
|
|
151
|
+
|
|
152
|
+
**"No module named pip"** — Your Python installation has broken pip (common with Python 3.14 on macOS). Delete `~/.arkheia/venv` and switch to Python 3.12: `brew install python@3.12`.
|
|
153
|
+
|
|
154
|
+
**Server registered but tools not showing** — Restart your CLI session. MCP registrations are not hot-reloaded.
|
|
155
|
+
|
|
156
|
+
**API key rejected** — Check for trailing whitespace or `\r` characters. If your env file was created on Windows, run `dos2unix` on it. The server will warn about this on startup.
|
|
157
|
+
|
|
158
|
+
## Full Documentation
|
|
159
|
+
|
|
160
|
+
See the [GitHub repo](https://github.com/arkheiaai/arkheia-mcp) for:
|
|
161
|
+
- Complete setup guide for all agents
|
|
162
|
+
- CLAUDE.md template for automatic detection across projects
|
|
163
|
+
- Multi-agent quorum pattern
|
|
164
|
+
- Test prompts and examples
|
|
165
|
+
|
|
166
|
+
## Feedback
|
|
167
|
+
|
|
168
|
+
- **GitHub Issues:** https://github.com/arkheiaai/arkheia-mcp/issues
|
|
169
|
+
- **Email:** dmurfet@arkheia.ai
|
|
170
|
+
|
|
171
|
+
Every message is read by the founder.
|
|
172
|
+
|
|
173
|
+
## Links
|
|
174
|
+
|
|
175
|
+
- Website: https://arkheia.ai
|
|
176
|
+
- MCP Account: https://arkheia.ai/mcp/account
|
|
177
|
+
- GitHub: https://github.com/arkheiaai/arkheia-mcp
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
38
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
39
|
+
const zod_1 = require("zod");
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const os = __importStar(require("os"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const proxy_client_js_1 = require("./proxy-client.js");
|
|
44
|
+
const tool_registry_js_1 = require("./tool-registry.js");
|
|
45
|
+
const providers_js_1 = require("./providers.js");
|
|
46
|
+
const memory_js_1 = require("./memory.js");
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Config Loading
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
function loadConfig() {
|
|
51
|
+
const configPath = path.join(os.homedir(), '.arkheia', 'config.json');
|
|
52
|
+
if (!fs.existsSync(configPath))
|
|
53
|
+
return;
|
|
54
|
+
try {
|
|
55
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
56
|
+
if (config.api_key && !process.env.ARKHEIA_API_KEY) {
|
|
57
|
+
process.env.ARKHEIA_API_KEY = config.api_key;
|
|
58
|
+
process.stderr.write(`[arkheia] API key loaded from ${configPath}\n`);
|
|
59
|
+
}
|
|
60
|
+
if (config.proxy_url && !process.env.ARKHEIA_HOSTED_URL) {
|
|
61
|
+
process.env.ARKHEIA_HOSTED_URL = config.proxy_url;
|
|
62
|
+
process.stderr.write(`[arkheia] Hosted URL: ${config.proxy_url}\n`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
process.stderr.write(`[arkheia] Warning: Could not read ${configPath}\n`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function checkCRLF() {
|
|
70
|
+
for (const k of ['ARKHEIA_API_KEY', 'ARKHEIA_PROXY_URL', 'ARKHEIA_HOSTED_URL']) {
|
|
71
|
+
const v = process.env[k];
|
|
72
|
+
if (v && /[\r\n]/.test(v)) {
|
|
73
|
+
process.stderr.write(`[arkheia] WARNING: ${k} contains whitespace/newline characters. Run 'dos2unix' on your env file.\n`);
|
|
74
|
+
process.env[k] = v.trim();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Helpers
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
function wrapResult(result) {
|
|
82
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
83
|
+
}
|
|
84
|
+
function handleError(toolName, e) {
|
|
85
|
+
if (e instanceof tool_registry_js_1.PolicyViolation) {
|
|
86
|
+
return wrapResult({ error: e.reason, risk_level: "UNKNOWN" });
|
|
87
|
+
}
|
|
88
|
+
process.stderr.write(`[arkheia] ${toolName} error: ${e.message}\n`);
|
|
89
|
+
return wrapResult({ error: e.message, risk_level: "UNKNOWN" });
|
|
90
|
+
}
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Main
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
async function main() {
|
|
95
|
+
loadConfig();
|
|
96
|
+
checkCRLF();
|
|
97
|
+
const ARKHEIA_PROXY_URL = process.env.ARKHEIA_PROXY_URL || "http://localhost:8098";
|
|
98
|
+
const ARKHEIA_HOSTED_URL = process.env.ARKHEIA_HOSTED_URL || "https://arkheia-proxy-production.up.railway.app";
|
|
99
|
+
const ARKHEIA_API_KEY = process.env.ARKHEIA_API_KEY;
|
|
100
|
+
if (!ARKHEIA_API_KEY) {
|
|
101
|
+
process.stderr.write("[arkheia] Warning: ARKHEIA_API_KEY not set.\n" +
|
|
102
|
+
"Get a free API key at https://arkheia.ai/mcp\n" +
|
|
103
|
+
"Then set: export ARKHEIA_API_KEY=ak_live_...\n\n");
|
|
104
|
+
}
|
|
105
|
+
const proxy = new proxy_client_js_1.ProxyClient(ARKHEIA_PROXY_URL, 10.0, ARKHEIA_HOSTED_URL, ARKHEIA_API_KEY);
|
|
106
|
+
const server = new mcp_js_1.McpServer({ name: "arkheia-trust", version: "0.1.6" });
|
|
107
|
+
// ── Detection & audit ──────────────────────────────────────────
|
|
108
|
+
server.tool("arkheia_verify", "Verify whether an AI response shows signs of fabrication. Call this on EVERY model response before acting on it.", { prompt: zod_1.z.string(), response: zod_1.z.string(), model: zod_1.z.string() }, async ({ prompt, response, model }) => {
|
|
109
|
+
try {
|
|
110
|
+
(0, tool_registry_js_1.check)("arkheia_verify");
|
|
111
|
+
const result = await proxy.verify(prompt, response, model);
|
|
112
|
+
return wrapResult(result);
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
return handleError("arkheia_verify", e);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
server.tool("arkheia_audit_log", "Retrieve structured audit evidence for compliance review.", { session_id: zod_1.z.string().optional(), limit: zod_1.z.number().int().min(1).max(500).default(50) }, async ({ session_id, limit }) => {
|
|
119
|
+
try {
|
|
120
|
+
(0, tool_registry_js_1.check)("arkheia_audit_log");
|
|
121
|
+
const result = await proxy.get_audit_log(session_id, Math.min(limit, 500));
|
|
122
|
+
return wrapResult(result);
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
return handleError("arkheia_audit_log", e);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
// ── Provider wrappers ──────────────────────────────────────────
|
|
129
|
+
server.tool("run_grok", "Call xAI Grok and screen the response through Arkheia for fabrication.", { prompt: zod_1.z.string(), model: zod_1.z.string().default("grok-4-fast-non-reasoning") }, async ({ prompt, model }) => {
|
|
130
|
+
try {
|
|
131
|
+
(0, tool_registry_js_1.check)("run_grok");
|
|
132
|
+
const pr = await (0, providers_js_1.call_grok)(prompt, model);
|
|
133
|
+
if (pr.error)
|
|
134
|
+
return wrapResult({ ...pr, arkheia: { risk_level: "UNKNOWN", error: pr.error } });
|
|
135
|
+
const risk = await proxy.verify(prompt, pr.response, model);
|
|
136
|
+
return wrapResult({ ...pr, arkheia: risk });
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
return handleError("run_grok", e);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
server.tool("run_gemini", "Call Google Gemini and screen the response through Arkheia for fabrication.", { prompt: zod_1.z.string(), model: zod_1.z.string().default("gemini-2.5-flash") }, async ({ prompt, model }) => {
|
|
143
|
+
try {
|
|
144
|
+
(0, tool_registry_js_1.check)("run_gemini");
|
|
145
|
+
const pr = await (0, providers_js_1.call_gemini)(prompt, model);
|
|
146
|
+
if (pr.error)
|
|
147
|
+
return wrapResult({ ...pr, arkheia: { risk_level: "UNKNOWN", error: pr.error } });
|
|
148
|
+
const risk = await proxy.verify(prompt, pr.response, model);
|
|
149
|
+
return wrapResult({ ...pr, arkheia: risk });
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
return handleError("run_gemini", e);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
server.tool("run_ollama", "Call a local Ollama model and screen the response through Arkheia. No network egress.", { prompt: zod_1.z.string(), model: zod_1.z.string().default("phi4:14b") }, async ({ prompt, model }) => {
|
|
156
|
+
try {
|
|
157
|
+
(0, tool_registry_js_1.check)("run_ollama");
|
|
158
|
+
const pr = await (0, providers_js_1.call_ollama)(prompt, model);
|
|
159
|
+
if (pr.error)
|
|
160
|
+
return wrapResult({ ...pr, arkheia: { risk_level: "UNKNOWN", error: pr.error } });
|
|
161
|
+
const risk = await proxy.verify(prompt, pr.response, model);
|
|
162
|
+
return wrapResult({ ...pr, arkheia: risk });
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
return handleError("run_ollama", e);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
server.tool("run_together", "Call Together AI (Kimi K2.5, DeepSeek, etc.) and screen the response through Arkheia.", { prompt: zod_1.z.string(), model: zod_1.z.string().default("moonshotai/Kimi-K2.5") }, async ({ prompt, model }) => {
|
|
169
|
+
try {
|
|
170
|
+
(0, tool_registry_js_1.check)("run_together");
|
|
171
|
+
const pr = await (0, providers_js_1.call_together)(prompt, model);
|
|
172
|
+
if (pr.error)
|
|
173
|
+
return wrapResult({ ...pr, arkheia: { risk_level: "UNKNOWN", error: pr.error } });
|
|
174
|
+
const risk = await proxy.verify(prompt, pr.response, model);
|
|
175
|
+
return wrapResult({ ...pr, arkheia: risk });
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
return handleError("run_together", e);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
// ── Memory / Knowledge Graph ───────────────────────────────────
|
|
182
|
+
server.tool("memory_store", "Store an entity and its observations in the persistent knowledge graph. Entities are upserted by name+type.", { name: zod_1.z.string(), entity_type: zod_1.z.string(), observations: zod_1.z.array(zod_1.z.string()) }, async ({ name, entity_type, observations }) => {
|
|
183
|
+
try {
|
|
184
|
+
(0, tool_registry_js_1.check)("memory_store");
|
|
185
|
+
const result = await (0, memory_js_1.store_entity)(name, entity_type, observations);
|
|
186
|
+
return wrapResult(result);
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
return handleError("memory_store", e);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
server.tool("memory_retrieve", "Search entities in the persistent knowledge graph by name (case-insensitive).", { query: zod_1.z.string(), entity_type: zod_1.z.string().optional(), limit: zod_1.z.number().int().min(1).max(50).default(10) }, async ({ query, entity_type, limit }) => {
|
|
193
|
+
try {
|
|
194
|
+
(0, tool_registry_js_1.check)("memory_retrieve");
|
|
195
|
+
const result = await (0, memory_js_1.retrieve_entities)(query, entity_type, Math.min(limit, 50));
|
|
196
|
+
return wrapResult(result);
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
return handleError("memory_retrieve", e);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
server.tool("memory_relate", "Store a named relationship between two entities in the knowledge graph.", { from_entity: zod_1.z.string(), relation_type: zod_1.z.string(), to_entity: zod_1.z.string() }, async ({ from_entity, relation_type, to_entity }) => {
|
|
203
|
+
try {
|
|
204
|
+
(0, tool_registry_js_1.check)("memory_relate");
|
|
205
|
+
const result = await (0, memory_js_1.store_relation)(from_entity, relation_type, to_entity);
|
|
206
|
+
return wrapResult(result);
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
return handleError("memory_relate", e);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
// ── Start ──────────────────────────────────────────────────────
|
|
213
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
214
|
+
await server.connect(transport);
|
|
215
|
+
}
|
|
216
|
+
main().catch((err) => {
|
|
217
|
+
process.stderr.write(`[arkheia] Fatal error: ${err}\n`);
|
|
218
|
+
process.exit(1);
|
|
219
|
+
});
|
package/dist/memory.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.store_entity = store_entity;
|
|
40
|
+
exports.retrieve_entities = retrieve_entities;
|
|
41
|
+
exports.store_relation = store_relation;
|
|
42
|
+
const sql_js_1 = __importDefault(require("sql.js"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const crypto = __importStar(require("crypto"));
|
|
46
|
+
const os = __importStar(require("os"));
|
|
47
|
+
const logger = console;
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// DB setup — sql.js (pure JS, no native module)
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
function _db_path() {
|
|
52
|
+
return process.env.MEMORY_DB_PATH || path.join(os.homedir(), '.arkheia', 'memory.db');
|
|
53
|
+
}
|
|
54
|
+
let _db = null;
|
|
55
|
+
let _dbReady = null;
|
|
56
|
+
function _getDb() {
|
|
57
|
+
if (_dbReady)
|
|
58
|
+
return _dbReady;
|
|
59
|
+
_dbReady = (async () => {
|
|
60
|
+
const SQL = await (0, sql_js_1.default)();
|
|
61
|
+
const dbPath = _db_path();
|
|
62
|
+
const dir = path.dirname(dbPath);
|
|
63
|
+
if (!fs.existsSync(dir)) {
|
|
64
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
// Load existing DB or create new
|
|
67
|
+
if (fs.existsSync(dbPath)) {
|
|
68
|
+
const buffer = fs.readFileSync(dbPath);
|
|
69
|
+
_db = new SQL.Database(buffer);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
_db = new SQL.Database();
|
|
73
|
+
}
|
|
74
|
+
// Init schema
|
|
75
|
+
_db.run(`
|
|
76
|
+
CREATE TABLE IF NOT EXISTS entities (
|
|
77
|
+
entity_id TEXT PRIMARY KEY,
|
|
78
|
+
name TEXT NOT NULL,
|
|
79
|
+
entity_type TEXT NOT NULL,
|
|
80
|
+
created_at TEXT NOT NULL
|
|
81
|
+
);
|
|
82
|
+
CREATE TABLE IF NOT EXISTS observations (
|
|
83
|
+
obs_id TEXT PRIMARY KEY,
|
|
84
|
+
entity_id TEXT NOT NULL REFERENCES entities(entity_id),
|
|
85
|
+
content TEXT NOT NULL,
|
|
86
|
+
created_at TEXT NOT NULL
|
|
87
|
+
);
|
|
88
|
+
CREATE TABLE IF NOT EXISTS relations (
|
|
89
|
+
rel_id TEXT PRIMARY KEY,
|
|
90
|
+
from_entity TEXT NOT NULL,
|
|
91
|
+
relation_type TEXT NOT NULL,
|
|
92
|
+
to_entity TEXT NOT NULL,
|
|
93
|
+
created_at TEXT NOT NULL
|
|
94
|
+
);
|
|
95
|
+
`);
|
|
96
|
+
_save(_db);
|
|
97
|
+
return _db;
|
|
98
|
+
})();
|
|
99
|
+
return _dbReady;
|
|
100
|
+
}
|
|
101
|
+
function _save(db) {
|
|
102
|
+
const data = db.export();
|
|
103
|
+
const buffer = Buffer.from(data);
|
|
104
|
+
fs.writeFileSync(_db_path(), buffer);
|
|
105
|
+
}
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Public functions
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
async function store_entity(name, entity_type, observations) {
|
|
110
|
+
const db = await _getDb();
|
|
111
|
+
const now = new Date().toISOString();
|
|
112
|
+
// Upsert entity — look up by name+type
|
|
113
|
+
let entity_id;
|
|
114
|
+
const existing = db.exec("SELECT entity_id FROM entities WHERE name = ? AND entity_type = ?", [name, entity_type]);
|
|
115
|
+
if (existing.length > 0 && existing[0].values.length > 0) {
|
|
116
|
+
entity_id = existing[0].values[0][0];
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
entity_id = crypto.randomUUID();
|
|
120
|
+
db.run("INSERT INTO entities (entity_id, name, entity_type, created_at) VALUES (?, ?, ?, ?)", [entity_id, name, entity_type, now]);
|
|
121
|
+
}
|
|
122
|
+
// Fetch existing observation contents to deduplicate
|
|
123
|
+
const existingObs = db.exec("SELECT content FROM observations WHERE entity_id = ?", [entity_id]);
|
|
124
|
+
const existingContentSet = new Set();
|
|
125
|
+
if (existingObs.length > 0) {
|
|
126
|
+
for (const row of existingObs[0].values) {
|
|
127
|
+
existingContentSet.add(row[0]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
let added = 0;
|
|
131
|
+
for (const content of observations) {
|
|
132
|
+
if (!existingContentSet.has(content)) {
|
|
133
|
+
db.run("INSERT INTO observations (obs_id, entity_id, content, created_at) VALUES (?, ?, ?, ?)", [crypto.randomUUID(), entity_id, content, now]);
|
|
134
|
+
existingContentSet.add(content);
|
|
135
|
+
added++;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const countResult = db.exec("SELECT COUNT(*) AS n FROM observations WHERE entity_id = ?", [entity_id]);
|
|
139
|
+
const totalObservations = countResult.length > 0 ? countResult[0].values[0][0] : 0;
|
|
140
|
+
_save(db);
|
|
141
|
+
return {
|
|
142
|
+
entity_id,
|
|
143
|
+
name,
|
|
144
|
+
entity_type,
|
|
145
|
+
observations_added: added,
|
|
146
|
+
total_observations: totalObservations,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
async function retrieve_entities(query, entity_type = undefined, limit = 10) {
|
|
150
|
+
const db = await _getDb();
|
|
151
|
+
const pattern = `%${query}%`;
|
|
152
|
+
let rows;
|
|
153
|
+
if (entity_type) {
|
|
154
|
+
const result = db.exec("SELECT entity_id, name, entity_type, created_at FROM entities WHERE name LIKE ? AND entity_type = ?", [pattern, entity_type]);
|
|
155
|
+
rows = result.length > 0 ? result[0].values.map(r => ({ entity_id: r[0], name: r[1], entity_type: r[2], created_at: r[3] })) : [];
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const result = db.exec("SELECT entity_id, name, entity_type, created_at FROM entities WHERE name LIKE ?", [pattern]);
|
|
159
|
+
rows = result.length > 0 ? result[0].values.map(r => ({ entity_id: r[0], name: r[1], entity_type: r[2], created_at: r[3] })) : [];
|
|
160
|
+
}
|
|
161
|
+
const total = rows.length;
|
|
162
|
+
rows = rows.slice(0, Math.min(limit, 50));
|
|
163
|
+
const entities = [];
|
|
164
|
+
for (const row of rows) {
|
|
165
|
+
const obsResult = db.exec("SELECT content, created_at FROM observations WHERE entity_id = ? ORDER BY created_at", [row.entity_id]);
|
|
166
|
+
const obs = obsResult.length > 0 ? obsResult[0].values.map(o => ({ content: o[0], created_at: o[1] })) : [];
|
|
167
|
+
const relResult = db.exec("SELECT relation_type, to_entity FROM relations WHERE from_entity = ? ORDER BY created_at", [row.name]);
|
|
168
|
+
const rels = relResult.length > 0 ? relResult[0].values.map(r => ({ relation_type: r[0], to_entity: r[1] })) : [];
|
|
169
|
+
entities.push({
|
|
170
|
+
entity_id: row.entity_id,
|
|
171
|
+
name: row.name,
|
|
172
|
+
entity_type: row.entity_type,
|
|
173
|
+
created_at: row.created_at,
|
|
174
|
+
observations: obs,
|
|
175
|
+
relations: rels,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return { entities, total };
|
|
179
|
+
}
|
|
180
|
+
async function store_relation(from_entity, relation_type, to_entity) {
|
|
181
|
+
const db = await _getDb();
|
|
182
|
+
const rel_id = crypto.randomUUID();
|
|
183
|
+
const now = new Date().toISOString();
|
|
184
|
+
db.run("INSERT INTO relations (rel_id, from_entity, relation_type, to_entity, created_at) VALUES (?, ?, ?, ?, ?)", [rel_id, from_entity, relation_type, to_entity, now]);
|
|
185
|
+
_save(db);
|
|
186
|
+
return { rel_id, from_entity, relation_type, to_entity };
|
|
187
|
+
}
|