@shodh/memory-mcp 0.1.0 → 0.1.1

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/README.md +1 -0
  2. package/dist/index.js +237 -29
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -49,6 +49,7 @@ docker run -p 3030:3030 shodh/memory
49
49
  |------|-------------|
50
50
  | `remember` | Store a memory with optional type and tags |
51
51
  | `recall` | Semantic search to find relevant memories |
52
+ | `context_summary` | Get categorized context for session bootstrap |
52
53
  | `list_memories` | List all stored memories |
53
54
  | `forget` | Delete a specific memory by ID |
54
55
  | `memory_stats` | Get statistics about stored memories |
package/dist/index.js CHANGED
@@ -4881,26 +4881,71 @@ class StdioServerTransport {
4881
4881
  var API_URL = process.env.SHODH_API_URL || "http://127.0.0.1:3030";
4882
4882
  var API_KEY = process.env.SHODH_API_KEY || "shodh-dev-key-change-in-production";
4883
4883
  var USER_ID = process.env.SHODH_USER_ID || "claude-code";
4884
+ var RETRY_ATTEMPTS = 3;
4885
+ var RETRY_DELAY_MS = 1000;
4886
+ var REQUEST_TIMEOUT_MS = 1e4;
4887
+ function getContent(m) {
4888
+ return m.experience?.content || "";
4889
+ }
4890
+ function getType(m) {
4891
+ return m.experience?.experience_type || "Observation";
4892
+ }
4893
+ function sleep(ms) {
4894
+ return new Promise((resolve) => setTimeout(resolve, ms));
4895
+ }
4884
4896
  async function apiCall(endpoint, method = "GET", body) {
4885
- const options = {
4886
- method,
4887
- headers: {
4888
- "Content-Type": "application/json",
4889
- "X-API-Key": API_KEY
4897
+ let lastError = null;
4898
+ for (let attempt = 1;attempt <= RETRY_ATTEMPTS; attempt++) {
4899
+ try {
4900
+ const controller = new AbortController;
4901
+ const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
4902
+ const options = {
4903
+ method,
4904
+ headers: {
4905
+ "Content-Type": "application/json",
4906
+ "X-API-Key": API_KEY
4907
+ },
4908
+ signal: controller.signal
4909
+ };
4910
+ if (body) {
4911
+ options.body = JSON.stringify(body);
4912
+ }
4913
+ const response = await fetch(`${API_URL}${endpoint}`, options);
4914
+ clearTimeout(timeoutId);
4915
+ if (!response.ok) {
4916
+ const errorText = await response.text().catch(() => "Unknown error");
4917
+ throw new Error(`API error ${response.status}: ${errorText}`);
4918
+ }
4919
+ return await response.json();
4920
+ } catch (error) {
4921
+ lastError = error instanceof Error ? error : new Error(String(error));
4922
+ if (lastError.message.includes("API error 4")) {
4923
+ throw lastError;
4924
+ }
4925
+ if (attempt < RETRY_ATTEMPTS) {
4926
+ console.error(`Attempt ${attempt} failed: ${lastError.message}. Retrying in ${RETRY_DELAY_MS}ms...`);
4927
+ await sleep(RETRY_DELAY_MS * attempt);
4928
+ }
4890
4929
  }
4891
- };
4892
- if (body) {
4893
- options.body = JSON.stringify(body);
4894
4930
  }
4895
- const response = await fetch(`${API_URL}${endpoint}`, options);
4896
- if (!response.ok) {
4897
- throw new Error(`API error: ${response.status} ${response.statusText}`);
4931
+ throw new Error(`Failed after ${RETRY_ATTEMPTS} attempts: ${lastError?.message || "Unknown error"}`);
4932
+ }
4933
+ async function isServerAvailable() {
4934
+ try {
4935
+ const controller = new AbortController;
4936
+ const timeoutId = setTimeout(() => controller.abort(), 2000);
4937
+ const response = await fetch(`${API_URL}/health`, {
4938
+ signal: controller.signal
4939
+ });
4940
+ clearTimeout(timeoutId);
4941
+ return response.ok;
4942
+ } catch {
4943
+ return false;
4898
4944
  }
4899
- return response.json();
4900
4945
  }
4901
4946
  var server = new Server({
4902
4947
  name: "shodh-memory",
4903
- version: "0.1.0"
4948
+ version: "0.1.1"
4904
4949
  }, {
4905
4950
  capabilities: {
4906
4951
  tools: {},
@@ -4954,6 +4999,35 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
4954
4999
  required: ["query"]
4955
5000
  }
4956
5001
  },
5002
+ {
5003
+ name: "context_summary",
5004
+ description: "Get a condensed summary of recent learnings, decisions, and context. Use this at the start of a session to quickly understand what you've learned before.",
5005
+ inputSchema: {
5006
+ type: "object",
5007
+ properties: {
5008
+ include_decisions: {
5009
+ type: "boolean",
5010
+ description: "Include recent decisions (default: true)",
5011
+ default: true
5012
+ },
5013
+ include_learnings: {
5014
+ type: "boolean",
5015
+ description: "Include recent learnings (default: true)",
5016
+ default: true
5017
+ },
5018
+ include_context: {
5019
+ type: "boolean",
5020
+ description: "Include project context (default: true)",
5021
+ default: true
5022
+ },
5023
+ max_items: {
5024
+ type: "number",
5025
+ description: "Maximum items per category (default: 5)",
5026
+ default: 5
5027
+ }
5028
+ }
5029
+ }
5030
+ },
4957
5031
  {
4958
5032
  name: "list_memories",
4959
5033
  description: "List all stored memories",
@@ -4995,6 +5069,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
4995
5069
  });
4996
5070
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
4997
5071
  const { name, arguments: args } = request.params;
5072
+ const serverUp = await isServerAvailable();
5073
+ if (!serverUp) {
5074
+ return {
5075
+ content: [
5076
+ {
5077
+ type: "text",
5078
+ text: `Memory server unavailable at ${API_URL}. Please ensure shodh-memory-server is running.
5079
+
5080
+ To start: cd shodh-memory && cargo run`
5081
+ }
5082
+ ],
5083
+ isError: true
5084
+ };
5085
+ }
4998
5086
  try {
4999
5087
  switch (name) {
5000
5088
  case "remember": {
@@ -5035,8 +5123,12 @@ Memory ID: ${result.memory_id}`
5035
5123
  ]
5036
5124
  };
5037
5125
  }
5038
- const formatted = memories.map((m, i) => `${i + 1}. [${((m.score || 0) * 100).toFixed(0)}%] ${m.content}
5039
- ID: ${m.memory_id.slice(0, 8)}...`).join(`
5126
+ const formatted = memories.map((m, i) => {
5127
+ const content = getContent(m);
5128
+ const score = ((m.score || 0) * 100).toFixed(0);
5129
+ return `${i + 1}. [${score}% match] ${content}
5130
+ Type: ${getType(m)} | ID: ${m.id.slice(0, 8)}...`;
5131
+ }).join(`
5040
5132
 
5041
5133
  `);
5042
5134
  return {
@@ -5050,6 +5142,98 @@ ${formatted}`
5050
5142
  ]
5051
5143
  };
5052
5144
  }
5145
+ case "context_summary": {
5146
+ const {
5147
+ include_decisions = true,
5148
+ include_learnings = true,
5149
+ include_context = true,
5150
+ max_items = 5
5151
+ } = args;
5152
+ const result = await apiCall("/api/memories", "POST", {
5153
+ user_id: USER_ID
5154
+ });
5155
+ const memories = result.memories || [];
5156
+ if (memories.length === 0) {
5157
+ return {
5158
+ content: [
5159
+ {
5160
+ type: "text",
5161
+ text: "No memories stored yet. Start remembering things to build context!"
5162
+ }
5163
+ ]
5164
+ };
5165
+ }
5166
+ const decisions = [];
5167
+ const learnings = [];
5168
+ const context = [];
5169
+ const patterns = [];
5170
+ const errors2 = [];
5171
+ for (const m of memories) {
5172
+ const type = getType(m);
5173
+ switch (type) {
5174
+ case "Decision":
5175
+ decisions.push(m);
5176
+ break;
5177
+ case "Learning":
5178
+ learnings.push(m);
5179
+ break;
5180
+ case "Context":
5181
+ context.push(m);
5182
+ break;
5183
+ case "Pattern":
5184
+ patterns.push(m);
5185
+ break;
5186
+ case "Error":
5187
+ errors2.push(m);
5188
+ break;
5189
+ }
5190
+ }
5191
+ const sections = [];
5192
+ if (include_context && context.length > 0) {
5193
+ const items = context.slice(0, max_items).map((m) => ` - ${getContent(m).slice(0, 100)}`);
5194
+ sections.push(`PROJECT CONTEXT:
5195
+ ${items.join(`
5196
+ `)}`);
5197
+ }
5198
+ if (include_decisions && decisions.length > 0) {
5199
+ const items = decisions.slice(0, max_items).map((m) => ` - ${getContent(m).slice(0, 100)}`);
5200
+ sections.push(`DECISIONS MADE:
5201
+ ${items.join(`
5202
+ `)}`);
5203
+ }
5204
+ if (include_learnings && learnings.length > 0) {
5205
+ const items = learnings.slice(0, max_items).map((m) => ` - ${getContent(m).slice(0, 100)}`);
5206
+ sections.push(`LEARNINGS:
5207
+ ${items.join(`
5208
+ `)}`);
5209
+ }
5210
+ if (patterns.length > 0) {
5211
+ const items = patterns.slice(0, max_items).map((m) => ` - ${getContent(m).slice(0, 100)}`);
5212
+ sections.push(`PATTERNS NOTICED:
5213
+ ${items.join(`
5214
+ `)}`);
5215
+ }
5216
+ if (errors2.length > 0) {
5217
+ const items = errors2.slice(0, Math.min(3, max_items)).map((m) => ` - ${getContent(m).slice(0, 100)}`);
5218
+ sections.push(`ERRORS TO AVOID:
5219
+ ${items.join(`
5220
+ `)}`);
5221
+ }
5222
+ const summary = sections.length > 0 ? sections.join(`
5223
+
5224
+ `) : `${memories.length} memories stored, but none categorized as decisions, learnings, or context. Consider using those types when remembering.`;
5225
+ return {
5226
+ content: [
5227
+ {
5228
+ type: "text",
5229
+ text: `CONTEXT SUMMARY (${memories.length} total memories)
5230
+ ${"=".repeat(40)}
5231
+
5232
+ ${summary}`
5233
+ }
5234
+ ]
5235
+ };
5236
+ }
5053
5237
  case "list_memories": {
5054
5238
  const { limit = 20 } = args;
5055
5239
  const result = await apiCall("/api/memories", "POST", {
@@ -5066,8 +5250,11 @@ ${formatted}`
5066
5250
  ]
5067
5251
  };
5068
5252
  }
5069
- const formatted = memories.map((m, i) => `${i + 1}. ${m.content.slice(0, 80)}${m.content.length > 80 ? "..." : ""}
5070
- Type: ${m.experience_type || "unknown"} | ID: ${m.memory_id.slice(0, 8)}...`).join(`
5253
+ const formatted = memories.map((m, i) => {
5254
+ const content = getContent(m);
5255
+ return `${i + 1}. ${content.slice(0, 80)}${content.length > 80 ? "..." : ""}
5256
+ Type: ${getType(m)} | ID: ${m.id.slice(0, 8)}...`;
5257
+ }).join(`
5071
5258
 
5072
5259
  `);
5073
5260
  return {
@@ -5110,11 +5297,26 @@ ${JSON.stringify(result, null, 2)}`
5110
5297
  }
5111
5298
  } catch (error) {
5112
5299
  const message = error instanceof Error ? error.message : String(error);
5300
+ let helpText = "";
5301
+ if (message.includes("ECONNREFUSED") || message.includes("fetch failed")) {
5302
+ helpText = `
5303
+
5304
+ The memory server appears to be offline. Start it with:
5305
+ cd shodh-memory && cargo run`;
5306
+ } else if (message.includes("API error 401")) {
5307
+ helpText = `
5308
+
5309
+ Authentication failed. Check your SHODH_API_KEY.`;
5310
+ } else if (message.includes("API error 404")) {
5311
+ helpText = `
5312
+
5313
+ Endpoint not found. The server may be running an older version.`;
5314
+ }
5113
5315
  return {
5114
5316
  content: [
5115
5317
  {
5116
5318
  type: "text",
5117
- text: `Error: ${message}`
5319
+ text: `Error: ${message}${helpText}`
5118
5320
  }
5119
5321
  ],
5120
5322
  isError: true
@@ -5128,12 +5330,15 @@ server.setRequestHandler(ListResourcesRequestSchema, async () => {
5128
5330
  });
5129
5331
  const memories = result.memories || [];
5130
5332
  return {
5131
- resources: memories.slice(0, 50).map((m) => ({
5132
- uri: `memory://${m.memory_id}`,
5133
- name: m.content.slice(0, 50) + (m.content.length > 50 ? "..." : ""),
5134
- mimeType: "text/plain",
5135
- description: `Type: ${m.experience_type || "unknown"}`
5136
- }))
5333
+ resources: memories.slice(0, 50).map((m) => {
5334
+ const content = getContent(m);
5335
+ return {
5336
+ uri: `memory://${m.id}`,
5337
+ name: content.slice(0, 50) + (content.length > 50 ? "..." : ""),
5338
+ mimeType: "text/plain",
5339
+ description: `Type: ${getType(m)}`
5340
+ };
5341
+ })
5137
5342
  };
5138
5343
  } catch {
5139
5344
  return { resources: [] };
@@ -5146,20 +5351,21 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
5146
5351
  const result = await apiCall("/api/memories", "POST", {
5147
5352
  user_id: USER_ID
5148
5353
  });
5149
- const memory = (result.memories || []).find((m) => m.memory_id === memoryId);
5354
+ const memory = (result.memories || []).find((m) => m.id === memoryId);
5150
5355
  if (!memory) {
5151
5356
  throw new Error(`Memory not found: ${memoryId}`);
5152
5357
  }
5358
+ const content = getContent(memory);
5153
5359
  return {
5154
5360
  contents: [
5155
5361
  {
5156
5362
  uri,
5157
5363
  mimeType: "text/plain",
5158
- text: `Content: ${memory.content}
5364
+ text: `Content: ${content}
5159
5365
 
5160
- Type: ${memory.experience_type || "unknown"}
5366
+ Type: ${getType(memory)}
5161
5367
  Created: ${memory.created_at || "unknown"}
5162
- ID: ${memory.memory_id}`
5368
+ ID: ${memory.id}`
5163
5369
  }
5164
5370
  ]
5165
5371
  };
@@ -5171,6 +5377,8 @@ ID: ${memory.memory_id}`
5171
5377
  async function main() {
5172
5378
  const transport = new StdioServerTransport;
5173
5379
  await server.connect(transport);
5174
- console.error("Shodh-Memory MCP server running");
5380
+ console.error("Shodh-Memory MCP server v0.1.1 running");
5381
+ console.error(`Connecting to: ${API_URL}`);
5382
+ console.error(`User ID: ${USER_ID}`);
5175
5383
  }
5176
5384
  main().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shodh/memory-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "mcpName": "io.github.varun29ankuS/shodh-memory",
5
5
  "description": "MCP server for persistent AI memory - store and recall context across sessions",
6
6
  "type": "module",