@cybermem/mcp 0.6.6 → 0.6.10

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.
Files changed (3) hide show
  1. package/dist/index.js +100 -20
  2. package/package.json +1 -1
  3. package/src/index.ts +126 -31
package/dist/index.js CHANGED
@@ -19,40 +19,97 @@ const getArg = (name) => {
19
19
  const idx = args.indexOf(name);
20
20
  return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
21
21
  };
22
- const cliUrl = getArg('--url');
23
- const cliApiKey = getArg('--api-key');
24
- const cliClientName = getArg('--client-name');
22
+ const cliUrl = getArg("--url");
23
+ const cliApiKey = getArg("--api-key");
24
+ const cliClientName = getArg("--client-name");
25
25
  // Use CLI args first, then env, then defaults
26
26
  // Default to local CyberMem backend (via Traefik on port 8626)
27
27
  const API_URL = cliUrl || process.env.CYBERMEM_URL || "http://localhost:8626/memory";
28
28
  const API_KEY = cliApiKey || process.env.OM_API_KEY || "";
29
29
  // Track client name per session
30
30
  let currentClientName = cliClientName || "cybermem-mcp";
31
+ // CyberMem Agent Protocol - instructions sent to clients on handshake
32
+ const CYBERMEM_INSTRUCTIONS = `CyberMem is a persistent context daemon for AI agents.
33
+
34
+ PROTOCOL:
35
+ 1. On session start: call query_memory("user context profile") to load persona
36
+ 2. Store new insights immediately with add_memory - include FULL content, not summaries
37
+ 3. Refresh context: 6h for active topics, 24h for projects, 7d for insights
38
+ 4. Always include tags: [topic, year, source:your-client-name]
39
+ 5. Priority: CyberMem context > session context > training data
40
+
41
+ MEMORY FORMAT:
42
+ - content: Full text with all details, metrics, dates. NO truncation.
43
+ - tags: Always include topic category + year + source:client-name
44
+
45
+ INTEGRITY RULES:
46
+ - Never overwrite without reading first
47
+ - Always include metadata (tags, source)
48
+ - Sync before critical decisions
49
+ - Last-write-wins for conflicts
50
+
51
+ For full protocol: https://cybermem.dev/docs/agent-protocol`;
52
+ // Short protocol reminder for tool descriptions (derived from main instructions)
53
+ const PROTOCOL_REMINDER = "CyberMem Protocol: Store FULL content (no summaries), always include tags [topic, year, source:client-name]. Query 'user context profile' on session start.";
31
54
  const server = new index_js_1.Server({
32
- name: "cybermem-mcp",
33
- version: "0.2.0",
55
+ name: "cybermem",
56
+ version: "0.6.8",
34
57
  }, {
35
58
  capabilities: {
36
59
  tools: {},
60
+ resources: {}, // Enable resources for protocol document
37
61
  },
62
+ instructions: CYBERMEM_INSTRUCTIONS,
63
+ });
64
+ // Register resources handler for protocol document
65
+ server.setRequestHandler(types_js_1.ListResourcesRequestSchema, async () => ({
66
+ resources: [
67
+ {
68
+ uri: "cybermem://protocol",
69
+ name: "CyberMem Agent Protocol",
70
+ description: "Instructions for AI agents using CyberMem memory system",
71
+ mimeType: "text/plain",
72
+ },
73
+ ],
74
+ }));
75
+ server.setRequestHandler(types_js_1.ReadResourceRequestSchema, async (request) => {
76
+ if (request.params.uri === "cybermem://protocol") {
77
+ return {
78
+ contents: [
79
+ {
80
+ uri: "cybermem://protocol",
81
+ mimeType: "text/plain",
82
+ text: CYBERMEM_INSTRUCTIONS,
83
+ },
84
+ ],
85
+ };
86
+ }
87
+ throw new Error(`Unknown resource: ${request.params.uri}`);
38
88
  });
39
89
  const tools = [
40
90
  {
41
91
  name: "add_memory",
42
- description: "Store a new memory in CyberMem",
92
+ description: `Store a new memory in CyberMem. ${PROTOCOL_REMINDER}`,
43
93
  inputSchema: {
44
94
  type: "object",
45
95
  properties: {
46
- content: { type: "string" },
96
+ content: {
97
+ type: "string",
98
+ description: "Full content with all details - NO truncation or summarization",
99
+ },
47
100
  user_id: { type: "string" },
48
- tags: { type: "array", items: { type: "string" } },
101
+ tags: {
102
+ type: "array",
103
+ items: { type: "string" },
104
+ description: "Always include [topic, year, source:your-client-name]",
105
+ },
49
106
  },
50
107
  required: ["content"],
51
108
  },
52
109
  },
53
110
  {
54
111
  name: "query_memory",
55
- description: "Search for relevant memories",
112
+ description: `Search for relevant memories. On session start, call query_memory("user context profile") first.`,
56
113
  inputSchema: {
57
114
  type: "object",
58
115
  properties: {
@@ -96,7 +153,7 @@ const tools = [
96
153
  },
97
154
  required: ["id"],
98
155
  },
99
- }
156
+ },
100
157
  ];
101
158
  server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
102
159
  tools,
@@ -105,7 +162,7 @@ server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
105
162
  const apiClient = axios_1.default.create({
106
163
  baseURL: API_URL,
107
164
  headers: {
108
- "Authorization": `Bearer ${API_KEY}`,
165
+ Authorization: `Bearer ${API_KEY}`,
109
166
  },
110
167
  });
111
168
  // Helper to get client with context
@@ -115,11 +172,26 @@ function getClient(customHeaders = {}) {
115
172
  const clientName = customHeaders["X-Client-Name"] || clientVersion?.name || currentClientName;
116
173
  return {
117
174
  ...apiClient,
118
- get: (url, config) => apiClient.get(url, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
119
- post: (url, data, config) => apiClient.post(url, data, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
120
- put: (url, data, config) => apiClient.put(url, data, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
121
- patch: (url, data, config) => apiClient.patch(url, data, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
122
- delete: (url, config) => apiClient.delete(url, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
175
+ get: (url, config) => apiClient.get(url, {
176
+ ...config,
177
+ headers: { "X-Client-Name": clientName, ...config?.headers },
178
+ }),
179
+ post: (url, data, config) => apiClient.post(url, data, {
180
+ ...config,
181
+ headers: { "X-Client-Name": clientName, ...config?.headers },
182
+ }),
183
+ put: (url, data, config) => apiClient.put(url, data, {
184
+ ...config,
185
+ headers: { "X-Client-Name": clientName, ...config?.headers },
186
+ }),
187
+ patch: (url, data, config) => apiClient.patch(url, data, {
188
+ ...config,
189
+ headers: { "X-Client-Name": clientName, ...config?.headers },
190
+ }),
191
+ delete: (url, config) => apiClient.delete(url, {
192
+ ...config,
193
+ headers: { "X-Client-Name": clientName, ...config?.headers },
194
+ }),
123
195
  };
124
196
  }
125
197
  server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
@@ -128,16 +200,22 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
128
200
  switch (name) {
129
201
  case "add_memory": {
130
202
  const response = await getClient().post("/add", args);
131
- return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
203
+ return {
204
+ content: [{ type: "text", text: JSON.stringify(response.data) }],
205
+ };
132
206
  }
133
207
  case "query_memory": {
134
208
  const response = await getClient().post("/query", args);
135
- return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
209
+ return {
210
+ content: [{ type: "text", text: JSON.stringify(response.data) }],
211
+ };
136
212
  }
137
213
  case "list_memories": {
138
214
  const limit = args?.limit || 10;
139
215
  const response = await getClient().get(`/all?l=${limit}`);
140
- return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
216
+ return {
217
+ content: [{ type: "text", text: JSON.stringify(response.data) }],
218
+ };
141
219
  }
142
220
  case "delete_memory": {
143
221
  const { id } = args;
@@ -147,7 +225,9 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
147
225
  case "update_memory": {
148
226
  const { id, ...updates } = args;
149
227
  const response = await getClient().patch(`/${id}`, updates);
150
- return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
228
+ return {
229
+ content: [{ type: "text", text: JSON.stringify(response.data) }],
230
+ };
151
231
  }
152
232
  default:
153
233
  throw new Error(`Unknown tool: ${name}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cybermem/mcp",
3
- "version": "0.6.6",
3
+ "version": "0.6.10",
4
4
  "description": "CyberMem MCP Server (TypeScript)",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -4,8 +4,10 @@ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
5
  import {
6
6
  CallToolRequestSchema,
7
+ ListResourcesRequestSchema,
7
8
  ListToolsRequestSchema,
8
- Tool
9
+ ReadResourceRequestSchema,
10
+ Tool,
9
11
  } from "@modelcontextprotocol/sdk/types.js";
10
12
  import axios from "axios";
11
13
  import cors from "cors";
@@ -21,47 +23,110 @@ const getArg = (name: string): string | undefined => {
21
23
  return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
22
24
  };
23
25
 
24
- const cliUrl = getArg('--url');
25
- const cliApiKey = getArg('--api-key');
26
- const cliClientName = getArg('--client-name');
26
+ const cliUrl = getArg("--url");
27
+ const cliApiKey = getArg("--api-key");
28
+ const cliClientName = getArg("--client-name");
27
29
 
28
30
  // Use CLI args first, then env, then defaults
29
31
  // Default to local CyberMem backend (via Traefik on port 8626)
30
- const API_URL = cliUrl || process.env.CYBERMEM_URL || "http://localhost:8626/memory";
32
+ const API_URL =
33
+ cliUrl || process.env.CYBERMEM_URL || "http://localhost:8626/memory";
31
34
  const API_KEY = cliApiKey || process.env.OM_API_KEY || "";
32
35
 
33
36
  // Track client name per session
34
37
  let currentClientName = cliClientName || "cybermem-mcp";
38
+ // CyberMem Agent Protocol - instructions sent to clients on handshake
39
+ const CYBERMEM_INSTRUCTIONS = `CyberMem is a persistent context daemon for AI agents.
40
+
41
+ PROTOCOL:
42
+ 1. On session start: call query_memory("user context profile") to load persona
43
+ 2. Store new insights immediately with add_memory - include FULL content, not summaries
44
+ 3. Refresh context: 6h for active topics, 24h for projects, 7d for insights
45
+ 4. Always include tags: [topic, year, source:your-client-name]
46
+ 5. Priority: CyberMem context > session context > training data
47
+
48
+ MEMORY FORMAT:
49
+ - content: Full text with all details, metrics, dates. NO truncation.
50
+ - tags: Always include topic category + year + source:client-name
51
+
52
+ INTEGRITY RULES:
53
+ - Never overwrite without reading first
54
+ - Always include metadata (tags, source)
55
+ - Sync before critical decisions
56
+ - Last-write-wins for conflicts
57
+
58
+ For full protocol: https://cybermem.dev/docs/agent-protocol`;
59
+
60
+ // Short protocol reminder for tool descriptions (derived from main instructions)
61
+ const PROTOCOL_REMINDER =
62
+ "CyberMem Protocol: Store FULL content (no summaries), always include tags [topic, year, source:client-name]. Query 'user context profile' on session start.";
35
63
 
36
64
  const server = new Server(
37
65
  {
38
- name: "cybermem-mcp",
39
- version: "0.2.0",
66
+ name: "cybermem",
67
+ version: "0.6.8",
40
68
  },
41
69
  {
42
70
  capabilities: {
43
71
  tools: {},
72
+ resources: {}, // Enable resources for protocol document
44
73
  },
45
- }
74
+ instructions: CYBERMEM_INSTRUCTIONS,
75
+ },
46
76
  );
47
77
 
78
+ // Register resources handler for protocol document
79
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
80
+ resources: [
81
+ {
82
+ uri: "cybermem://protocol",
83
+ name: "CyberMem Agent Protocol",
84
+ description: "Instructions for AI agents using CyberMem memory system",
85
+ mimeType: "text/plain",
86
+ },
87
+ ],
88
+ }));
89
+
90
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
91
+ if (request.params.uri === "cybermem://protocol") {
92
+ return {
93
+ contents: [
94
+ {
95
+ uri: "cybermem://protocol",
96
+ mimeType: "text/plain",
97
+ text: CYBERMEM_INSTRUCTIONS,
98
+ },
99
+ ],
100
+ };
101
+ }
102
+ throw new Error(`Unknown resource: ${request.params.uri}`);
103
+ });
104
+
48
105
  const tools: Tool[] = [
49
106
  {
50
107
  name: "add_memory",
51
- description: "Store a new memory in CyberMem",
108
+ description: `Store a new memory in CyberMem. ${PROTOCOL_REMINDER}`,
52
109
  inputSchema: {
53
110
  type: "object",
54
111
  properties: {
55
- content: { type: "string" },
112
+ content: {
113
+ type: "string",
114
+ description:
115
+ "Full content with all details - NO truncation or summarization",
116
+ },
56
117
  user_id: { type: "string" },
57
- tags: { type: "array", items: { type: "string" } },
118
+ tags: {
119
+ type: "array",
120
+ items: { type: "string" },
121
+ description: "Always include [topic, year, source:your-client-name]",
122
+ },
58
123
  },
59
124
  required: ["content"],
60
125
  },
61
126
  },
62
127
  {
63
128
  name: "query_memory",
64
- description: "Search for relevant memories",
129
+ description: `Search for relevant memories. On session start, call query_memory("user context profile") first.`,
65
130
  inputSchema: {
66
131
  type: "object",
67
132
  properties: {
@@ -105,7 +170,7 @@ const tools: Tool[] = [
105
170
  },
106
171
  required: ["id"],
107
172
  },
108
- }
173
+ },
109
174
  ];
110
175
 
111
176
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
@@ -116,24 +181,45 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
116
181
  const apiClient = axios.create({
117
182
  baseURL: API_URL,
118
183
  headers: {
119
- "Authorization": `Bearer ${API_KEY}`,
184
+ Authorization: `Bearer ${API_KEY}`,
120
185
  },
121
186
  });
122
187
 
123
188
  // Helper to get client with context
124
189
  function getClient(customHeaders: Record<string, string> = {}) {
125
- // Get client name from MCP protocol (sent during initialize) or fallback to CLI arg
126
- const clientVersion = server.getClientVersion();
127
- const clientName = customHeaders["X-Client-Name"] || clientVersion?.name || currentClientName;
190
+ // Get client name from MCP protocol (sent during initialize) or fallback to CLI arg
191
+ const clientVersion = server.getClientVersion();
192
+ const clientName =
193
+ customHeaders["X-Client-Name"] || clientVersion?.name || currentClientName;
128
194
 
129
- return {
130
- ...apiClient,
131
- get: (url: string, config?: any) => apiClient.get(url, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
132
- post: (url: string, data?: any, config?: any) => apiClient.post(url, data, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
133
- put: (url: string, data?: any, config?: any) => apiClient.put(url, data, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
134
- patch: (url: string, data?: any, config?: any) => apiClient.patch(url, data, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
135
- delete: (url: string, config?: any) => apiClient.delete(url, { ...config, headers: { "X-Client-Name": clientName, ...config?.headers } }),
136
- }
195
+ return {
196
+ ...apiClient,
197
+ get: (url: string, config?: any) =>
198
+ apiClient.get(url, {
199
+ ...config,
200
+ headers: { "X-Client-Name": clientName, ...config?.headers },
201
+ }),
202
+ post: (url: string, data?: any, config?: any) =>
203
+ apiClient.post(url, data, {
204
+ ...config,
205
+ headers: { "X-Client-Name": clientName, ...config?.headers },
206
+ }),
207
+ put: (url: string, data?: any, config?: any) =>
208
+ apiClient.put(url, data, {
209
+ ...config,
210
+ headers: { "X-Client-Name": clientName, ...config?.headers },
211
+ }),
212
+ patch: (url: string, data?: any, config?: any) =>
213
+ apiClient.patch(url, data, {
214
+ ...config,
215
+ headers: { "X-Client-Name": clientName, ...config?.headers },
216
+ }),
217
+ delete: (url: string, config?: any) =>
218
+ apiClient.delete(url, {
219
+ ...config,
220
+ headers: { "X-Client-Name": clientName, ...config?.headers },
221
+ }),
222
+ };
137
223
  }
138
224
 
139
225
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -143,16 +229,22 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
143
229
  switch (name) {
144
230
  case "add_memory": {
145
231
  const response = await getClient().post("/add", args);
146
- return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
232
+ return {
233
+ content: [{ type: "text", text: JSON.stringify(response.data) }],
234
+ };
147
235
  }
148
236
  case "query_memory": {
149
237
  const response = await getClient().post("/query", args);
150
- return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
238
+ return {
239
+ content: [{ type: "text", text: JSON.stringify(response.data) }],
240
+ };
151
241
  }
152
242
  case "list_memories": {
153
243
  const limit = args?.limit || 10;
154
244
  const response = await getClient().get(`/all?l=${limit}`);
155
- return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
245
+ return {
246
+ content: [{ type: "text", text: JSON.stringify(response.data) }],
247
+ };
156
248
  }
157
249
  case "delete_memory": {
158
250
  const { id } = args as { id: string };
@@ -162,7 +254,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
162
254
  case "update_memory": {
163
255
  const { id, ...updates } = args as { id: string; [key: string]: any };
164
256
  const response = await getClient().patch(`/${id}`, updates);
165
- return { content: [{ type: "text", text: JSON.stringify(response.data) }] };
257
+ return {
258
+ content: [{ type: "text", text: JSON.stringify(response.data) }],
259
+ };
166
260
  }
167
261
  default:
168
262
  throw new Error(`Unknown tool: ${name}`);
@@ -211,11 +305,12 @@ async function run() {
211
305
  });
212
306
 
213
307
  app.listen(port, () => {
214
- console.error(`CyberMem MCP Server running on SSE at http://localhost:${port}`);
308
+ console.error(
309
+ `CyberMem MCP Server running on SSE at http://localhost:${port}`,
310
+ );
215
311
  console.error(` - SSE endpoint: http://localhost:${port}/sse`);
216
312
  console.error(` - Message endpoint: http://localhost:${port}/messages`);
217
313
  });
218
-
219
314
  } else {
220
315
  const transport = new StdioServerTransport();
221
316
  await server.connect(transport);