cortex-mcp-bridge 0.3.0
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/cortex-mcp-bridge.js +280 -0
- package/package.json +14 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Cortex MCP Bridge — lightweight MCP server that proxies to Cortex REST API.
|
|
4
|
+
* No Rust binary needed. Just Node.js.
|
|
5
|
+
*
|
|
6
|
+
* Usage in Claude Desktop config:
|
|
7
|
+
* {
|
|
8
|
+
* "mcpServers": {
|
|
9
|
+
* "cortex": {
|
|
10
|
+
* "command": "node",
|
|
11
|
+
* "args": ["/path/to/cortex-mcp-bridge.js"],
|
|
12
|
+
* "env": {
|
|
13
|
+
* "CORTEX_URL": "https://cortex.example.com",
|
|
14
|
+
* "CORTEX_AUTH_TOKEN": "your-token-here"
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* CORTEX_AUTH_TOKEN is optional — omit it for local servers with auth disabled.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const BASE = process.env.CORTEX_URL || "http://localhost:19091";
|
|
24
|
+
const TOKEN = process.env.CORTEX_AUTH_TOKEN || null;
|
|
25
|
+
const readline = require("readline");
|
|
26
|
+
|
|
27
|
+
const TOOLS = [
|
|
28
|
+
{
|
|
29
|
+
name: "cortex_store",
|
|
30
|
+
description: "Store knowledge in persistent graph memory. Use to remember facts, decisions, goals, events, patterns, observations.",
|
|
31
|
+
inputSchema: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
kind: { type: "string", description: "fact|decision|goal|event|pattern|observation", default: "fact" },
|
|
35
|
+
title: { type: "string", description: "Short summary" },
|
|
36
|
+
body: { type: "string", description: "Full content" },
|
|
37
|
+
tags: { type: "array", items: { type: "string" } },
|
|
38
|
+
importance: { type: "number", description: "0.0-1.0", default: 0.5 },
|
|
39
|
+
},
|
|
40
|
+
required: ["title"],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "cortex_search",
|
|
45
|
+
description: "Search graph memory by meaning. Returns nodes ranked by semantic similarity.",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: "object",
|
|
48
|
+
properties: {
|
|
49
|
+
query: { type: "string" },
|
|
50
|
+
limit: { type: "integer", default: 10 },
|
|
51
|
+
},
|
|
52
|
+
required: ["query"],
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "cortex_recall",
|
|
57
|
+
description: "Hybrid search combining semantic similarity and graph structure. More contextual than pure search.",
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
query: { type: "string" },
|
|
62
|
+
limit: { type: "integer", default: 10 },
|
|
63
|
+
},
|
|
64
|
+
required: ["query"],
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "cortex_briefing",
|
|
69
|
+
description: "Generate a context briefing — structured summary of goals, decisions, patterns, and recent context from graph memory.",
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
agent_id: { type: "string", default: "default" },
|
|
74
|
+
compact: { type: "boolean", default: false },
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: "cortex_traverse",
|
|
80
|
+
description: "Explore connections from a node in the knowledge graph.",
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
node_id: { type: "string" },
|
|
85
|
+
depth: { type: "integer", default: 2 },
|
|
86
|
+
direction: { type: "string", default: "both" },
|
|
87
|
+
},
|
|
88
|
+
required: ["node_id"],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "cortex_relate",
|
|
93
|
+
description: "Create a typed relationship between two nodes.",
|
|
94
|
+
inputSchema: {
|
|
95
|
+
type: "object",
|
|
96
|
+
properties: {
|
|
97
|
+
from_id: { type: "string" },
|
|
98
|
+
to_id: { type: "string" },
|
|
99
|
+
relation: { type: "string", default: "relates-to" },
|
|
100
|
+
},
|
|
101
|
+
required: ["from_id", "to_id"],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "cortex_observe",
|
|
106
|
+
description: "Record a performance observation after an agent interaction. Updates prompt variant weights via EMA for adaptive selection.",
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: {
|
|
110
|
+
agent: { type: "string", description: "Agent name (e.g. 'kai')" },
|
|
111
|
+
variant_id: { type: "string", description: "UUID of the prompt variant node used" },
|
|
112
|
+
variant_slug: { type: "string", description: "Slug/title of the prompt variant" },
|
|
113
|
+
sentiment_score: { type: "number", description: "User sentiment 0.0 (frustrated) to 1.0 (pleased)", default: 0.5 },
|
|
114
|
+
correction_count: { type: "integer", description: "Number of user corrections in this interaction", default: 0 },
|
|
115
|
+
task_outcome: { type: "string", description: "success | partial | failure | unknown", default: "unknown" },
|
|
116
|
+
token_cost: { type: "integer", description: "Total tokens consumed" },
|
|
117
|
+
response_time_ms: { type: "integer", description: "Time to generate response in milliseconds" },
|
|
118
|
+
user_satisfaction: { type: "number", description: "Explicit user satisfaction score 0.0-1.0 if available" },
|
|
119
|
+
task_type: { type: "string", description: "Task category: coding | planning | casual | crisis | reflection" },
|
|
120
|
+
topic: { type: "string", description: "Topic or domain of the interaction" },
|
|
121
|
+
session_length: { type: "integer", description: "Session length in minutes" },
|
|
122
|
+
message_count: { type: "integer", description: "Number of messages in this session" },
|
|
123
|
+
},
|
|
124
|
+
required: ["agent", "variant_id", "variant_slug"],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
async function http(method, path, body) {
|
|
130
|
+
const url = `${BASE}${path}`;
|
|
131
|
+
const headers = { "Content-Type": "application/json" };
|
|
132
|
+
if (TOKEN) headers["Authorization"] = `Bearer ${TOKEN}`;
|
|
133
|
+
const opts = { method, headers };
|
|
134
|
+
if (body) opts.body = JSON.stringify(body);
|
|
135
|
+
const res = await fetch(url, opts);
|
|
136
|
+
return res.json();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function handleTool(name, args) {
|
|
140
|
+
switch (name) {
|
|
141
|
+
case "cortex_store": {
|
|
142
|
+
const r = await http("POST", "/nodes", {
|
|
143
|
+
kind: args.kind || "fact",
|
|
144
|
+
title: args.title,
|
|
145
|
+
body: args.body || args.title,
|
|
146
|
+
tags: args.tags,
|
|
147
|
+
importance: args.importance,
|
|
148
|
+
source_agent: "mcp",
|
|
149
|
+
});
|
|
150
|
+
return `Stored: ${r.data?.title || args.title} (id: ${r.data?.id || "?"})`;
|
|
151
|
+
}
|
|
152
|
+
case "cortex_search": {
|
|
153
|
+
const r = await http("GET", `/search?q=${encodeURIComponent(args.query)}&limit=${args.limit || 10}`);
|
|
154
|
+
const items = (r.data || []).map((i) => `[${i.score?.toFixed(2)}] ${i.node?.title}: ${i.node?.body}`);
|
|
155
|
+
return items.length ? items.join("\n") : "No results found.";
|
|
156
|
+
}
|
|
157
|
+
case "cortex_recall": {
|
|
158
|
+
const r = await http("GET", `/search/hybrid?q=${encodeURIComponent(args.query)}&limit=${args.limit || 10}`);
|
|
159
|
+
const items = (r.data || []).map((i) => `[${i.score?.toFixed(2)}] ${i.title}: ${i.body || ""}`);
|
|
160
|
+
return items.length ? items.join("\n") : "No results found.";
|
|
161
|
+
}
|
|
162
|
+
case "cortex_briefing": {
|
|
163
|
+
const aid = args.agent_id || "default";
|
|
164
|
+
const compact = args.compact ? "true" : "false";
|
|
165
|
+
const r = await http("GET", `/briefing/${encodeURIComponent(aid)}?compact=${compact}`);
|
|
166
|
+
return r.data?.rendered || "No briefing available.";
|
|
167
|
+
}
|
|
168
|
+
case "cortex_traverse": {
|
|
169
|
+
const r = await http("GET", `/nodes/${args.node_id}/neighbors?depth=${args.depth || 2}&direction=${args.direction || "both"}`);
|
|
170
|
+
return JSON.stringify(r.data, null, 2);
|
|
171
|
+
}
|
|
172
|
+
case "cortex_relate": {
|
|
173
|
+
const r = await http("POST", "/edges", {
|
|
174
|
+
from_id: args.from_id,
|
|
175
|
+
to_id: args.to_id,
|
|
176
|
+
relation: args.relation || "relates-to",
|
|
177
|
+
});
|
|
178
|
+
return `Related: ${args.from_id} → [${args.relation || "relates-to"}] → ${args.to_id} (edge: ${r.data?.id || "?"})`;
|
|
179
|
+
}
|
|
180
|
+
case "cortex_observe": {
|
|
181
|
+
const body = {
|
|
182
|
+
variant_id: args.variant_id,
|
|
183
|
+
variant_slug: args.variant_slug,
|
|
184
|
+
sentiment_score: args.sentiment_score ?? 0.5,
|
|
185
|
+
correction_count: args.correction_count ?? 0,
|
|
186
|
+
task_outcome: args.task_outcome || "unknown",
|
|
187
|
+
};
|
|
188
|
+
if (args.token_cost != null) body.token_cost = args.token_cost;
|
|
189
|
+
if (args.response_time_ms != null) body.response_time_ms = args.response_time_ms;
|
|
190
|
+
if (args.user_satisfaction != null) body.user_satisfaction = args.user_satisfaction;
|
|
191
|
+
if (args.topic != null) body.topic = args.topic;
|
|
192
|
+
if (args.session_length != null) body.session_length = args.session_length;
|
|
193
|
+
if (args.message_count != null) body.message_count = args.message_count;
|
|
194
|
+
if (args.task_type) {
|
|
195
|
+
body.context_signals = { task_type: args.task_type, sentiment: args.sentiment_score ?? 0.5 };
|
|
196
|
+
}
|
|
197
|
+
const r = await http("POST", `/agents/${encodeURIComponent(args.agent)}/observe`, body);
|
|
198
|
+
if (r.error) return `Observe error: ${r.error}`;
|
|
199
|
+
const d = r.data || {};
|
|
200
|
+
return `Observed: score=${d.observation_score?.toFixed(3)}, weight ${d.old_edge_weight?.toFixed(3)} → ${d.new_edge_weight?.toFixed(3)} (obs: ${d.observation_id || "?"})`;
|
|
201
|
+
}
|
|
202
|
+
default:
|
|
203
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function respond(id, result) {
|
|
208
|
+
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, result }) + "\n");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function respondError(id, code, message) {
|
|
212
|
+
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } }) + "\n");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const rl = readline.createInterface({ input: process.stdin });
|
|
216
|
+
|
|
217
|
+
rl.on("line", async (line) => {
|
|
218
|
+
let req;
|
|
219
|
+
try { req = JSON.parse(line.trim()); } catch { return; }
|
|
220
|
+
|
|
221
|
+
const { id, method, params } = req;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
switch (method) {
|
|
225
|
+
case "initialize":
|
|
226
|
+
respond(id, {
|
|
227
|
+
protocolVersion: "2024-11-05",
|
|
228
|
+
capabilities: { tools: {} },
|
|
229
|
+
serverInfo: { name: "cortex", version: "0.1.0" },
|
|
230
|
+
});
|
|
231
|
+
break;
|
|
232
|
+
|
|
233
|
+
case "notifications/initialized":
|
|
234
|
+
break; // no response needed
|
|
235
|
+
|
|
236
|
+
case "tools/list":
|
|
237
|
+
respond(id, { tools: TOOLS });
|
|
238
|
+
break;
|
|
239
|
+
|
|
240
|
+
case "tools/call": {
|
|
241
|
+
const text = await handleTool(params.name, params.arguments || {});
|
|
242
|
+
respond(id, { content: [{ type: "text", text }] });
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
case "resources/list":
|
|
247
|
+
respond(id, {
|
|
248
|
+
resources: [
|
|
249
|
+
{ uri: "cortex://stats", name: "Graph Statistics", mimeType: "application/json" },
|
|
250
|
+
],
|
|
251
|
+
});
|
|
252
|
+
break;
|
|
253
|
+
|
|
254
|
+
case "resources/read": {
|
|
255
|
+
if (params.uri === "cortex://stats") {
|
|
256
|
+
const r = await http("GET", "/stats");
|
|
257
|
+
respond(id, { contents: [{ uri: params.uri, mimeType: "application/json", text: JSON.stringify(r.data, null, 2) }] });
|
|
258
|
+
} else if (params.uri?.startsWith("cortex://node/")) {
|
|
259
|
+
const nid = params.uri.replace("cortex://node/", "");
|
|
260
|
+
const r = await http("GET", `/nodes/${nid}`);
|
|
261
|
+
respond(id, { contents: [{ uri: params.uri, mimeType: "application/json", text: JSON.stringify(r.data, null, 2) }] });
|
|
262
|
+
} else {
|
|
263
|
+
respondError(id, -32000, `Unknown resource: ${params.uri}`);
|
|
264
|
+
}
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
case "ping":
|
|
269
|
+
respond(id, {});
|
|
270
|
+
break;
|
|
271
|
+
|
|
272
|
+
default:
|
|
273
|
+
respondError(id, -32601, `Unknown method: ${method}`);
|
|
274
|
+
}
|
|
275
|
+
} catch (e) {
|
|
276
|
+
respondError(id, -32000, e.message);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
process.stderr.write(`[cortex-mcp] Bridge ready → ${BASE}\n`);
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cortex-mcp-bridge",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "MCP bridge for Cortex graph memory (no Rust needed)",
|
|
5
|
+
"bin": {
|
|
6
|
+
"cortex-mcp-bridge": "./cortex-mcp-bridge.js"
|
|
7
|
+
},
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/MikeSquared-Agency/cortex"
|
|
12
|
+
},
|
|
13
|
+
"keywords": ["mcp", "cortex", "memory", "ai-agents", "model-context-protocol"]
|
|
14
|
+
}
|