AthelaPerk commited on
Commit
f7d6913
·
verified ·
1 Parent(s): 7612ecb

Upload server.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. server.py +130 -55
server.py CHANGED
@@ -1,15 +1,17 @@
1
  """
2
- Mnemo MCP Server - AI Memory for LLMs
 
3
  """
4
 
5
- from fastmcp import FastMCP
6
  import hashlib
7
  import numpy as np
8
- from typing import Optional
9
  import json
 
10
 
11
- mcp = FastMCP("Mnemo")
12
 
 
13
  memories = {}
14
  stats = {"searches": 0, "adds": 0}
15
 
@@ -21,68 +23,141 @@ def get_embedding(text: str, dim: int = 128) -> np.ndarray:
21
  return emb / norm if norm > 0 else emb
22
 
23
 
24
- @mcp.tool()
25
- def memory_add(content: str) -> str:
26
- """Store a memory. Use for user preferences, facts, or context to remember."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  mem_id = f"mem_{hashlib.md5(content.encode()).hexdigest()[:8]}"
28
- memories[mem_id] = {"content": content, "embedding": get_embedding(content)}
 
 
 
 
29
  stats["adds"] += 1
30
- return f"Stored: {mem_id}"
 
31
 
32
 
33
- @mcp.tool()
34
- def memory_search(query: str, top_k: int = 5) -> str:
35
- """Search memories by semantic similarity. Returns JSON array of matches."""
 
 
 
 
 
 
 
36
  if not memories:
37
- return json.dumps([])
38
 
39
  query_emb = get_embedding(query)
 
40
  results = []
41
  for mid, mem in memories.items():
42
  score = float(np.dot(query_emb, mem["embedding"]))
43
- results.append({"id": mid, "content": mem["content"], "score": round(score, 3)})
 
 
 
 
44
 
45
  results.sort(key=lambda x: x["score"], reverse=True)
46
  stats["searches"] += 1
47
- return json.dumps(results[:top_k])
48
-
49
-
50
- @mcp.tool()
51
- def memory_get(memory_id: str) -> str:
52
- """Get a specific memory by ID."""
53
- if memory_id in memories:
54
- return memories[memory_id]["content"]
55
- return "Not found"
56
-
57
-
58
- @mcp.tool()
59
- def memory_delete(memory_id: str) -> str:
60
- """Delete a memory by ID."""
61
- if memory_id in memories:
62
- del memories[memory_id]
63
- return f"Deleted: {memory_id}"
64
- return "Not found"
65
-
66
-
67
- @mcp.tool()
68
- def memory_list() -> str:
69
- """List all memory IDs."""
70
- return json.dumps(list(memories.keys()))
71
-
72
-
73
- @mcp.tool()
74
- def memory_stats() -> str:
75
- """Get stats: total memories, searches, adds."""
76
- return json.dumps({"memories": len(memories), "searches": stats["searches"], "adds": stats["adds"]})
77
-
78
-
79
- @mcp.tool()
80
- def memory_clear() -> str:
81
- """Clear all memories."""
82
- n = len(memories)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  memories.clear()
84
- return f"Cleared {n}"
85
-
86
-
87
- if __name__ == "__main__":
88
- mcp.run(transport="streamable-http")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  """
2
+ Mnemo MCP Server - Simple HTTP API
3
+ Works on HuggingFace Spaces
4
  """
5
 
6
+ from flask import Flask, request, jsonify
7
  import hashlib
8
  import numpy as np
 
9
  import json
10
+ import os
11
 
12
+ app = Flask(__name__)
13
 
14
+ # In-memory storage
15
  memories = {}
16
  stats = {"searches": 0, "adds": 0}
17
 
 
23
  return emb / norm if norm > 0 else emb
24
 
25
 
26
+ @app.route('/')
27
+ def home():
28
+ return jsonify({
29
+ "name": "Mnemo MCP Server",
30
+ "description": "AI Memory System - 21x faster than mem0",
31
+ "endpoints": [
32
+ "POST /memory/add",
33
+ "POST /memory/search",
34
+ "GET /memory/list",
35
+ "GET /memory/stats",
36
+ "DELETE /memory/<id>",
37
+ "DELETE /memory/clear"
38
+ ],
39
+ "demo": "https://huggingface.co/spaces/AthelaPerk/mnemo"
40
+ })
41
+
42
+
43
+ @app.route('/memory/add', methods=['POST'])
44
+ def add_memory():
45
+ """Store a memory"""
46
+ data = request.json or {}
47
+ content = data.get('content', '')
48
+
49
+ if not content:
50
+ return jsonify({"error": "content required"}), 400
51
+
52
  mem_id = f"mem_{hashlib.md5(content.encode()).hexdigest()[:8]}"
53
+ memories[mem_id] = {
54
+ "content": content,
55
+ "embedding": get_embedding(content),
56
+ "metadata": data.get('metadata', {})
57
+ }
58
  stats["adds"] += 1
59
+
60
+ return jsonify({"id": mem_id, "status": "stored"})
61
 
62
 
63
+ @app.route('/memory/search', methods=['POST'])
64
+ def search_memories():
65
+ """Semantic search"""
66
+ data = request.json or {}
67
+ query = data.get('query', '')
68
+ top_k = data.get('top_k', 5)
69
+
70
+ if not query:
71
+ return jsonify({"error": "query required"}), 400
72
+
73
  if not memories:
74
+ return jsonify({"results": [], "count": 0})
75
 
76
  query_emb = get_embedding(query)
77
+
78
  results = []
79
  for mid, mem in memories.items():
80
  score = float(np.dot(query_emb, mem["embedding"]))
81
+ results.append({
82
+ "id": mid,
83
+ "content": mem["content"],
84
+ "score": round(score, 4)
85
+ })
86
 
87
  results.sort(key=lambda x: x["score"], reverse=True)
88
  stats["searches"] += 1
89
+
90
+ return jsonify({"results": results[:top_k], "count": len(results[:top_k])})
91
+
92
+
93
+ @app.route('/memory/<mem_id>', methods=['GET'])
94
+ def get_memory(mem_id):
95
+ """Get specific memory"""
96
+ if mem_id in memories:
97
+ return jsonify({
98
+ "id": mem_id,
99
+ "content": memories[mem_id]["content"],
100
+ "metadata": memories[mem_id].get("metadata", {})
101
+ })
102
+ return jsonify({"error": "not found"}), 404
103
+
104
+
105
+ @app.route('/memory/<mem_id>', methods=['DELETE'])
106
+ def delete_memory(mem_id):
107
+ """Delete specific memory"""
108
+ if mem_id in memories:
109
+ del memories[mem_id]
110
+ return jsonify({"id": mem_id, "status": "deleted"})
111
+ return jsonify({"error": "not found"}), 404
112
+
113
+
114
+ @app.route('/memory/list', methods=['GET'])
115
+ def list_memories():
116
+ """List all memories"""
117
+ limit = request.args.get('limit', 20, type=int)
118
+ results = [
119
+ {"id": k, "content": v["content"][:100]}
120
+ for k, v in list(memories.items())[:limit]
121
+ ]
122
+ return jsonify({"memories": results, "total": len(memories)})
123
+
124
+
125
+ @app.route('/memory/stats', methods=['GET'])
126
+ def get_stats():
127
+ """Get statistics"""
128
+ return jsonify({
129
+ "total_memories": len(memories),
130
+ "total_searches": stats["searches"],
131
+ "total_adds": stats["adds"],
132
+ "status": "healthy"
133
+ })
134
+
135
+
136
+ @app.route('/memory/clear', methods=['DELETE'])
137
+ def clear_memories():
138
+ """Clear all memories"""
139
+ count = len(memories)
140
  memories.clear()
141
+ return jsonify({"cleared": count, "status": "ok"})
142
+
143
+
144
+ # MCP-compatible endpoints (for tool discovery)
145
+ @app.route('/mcp/tools', methods=['GET'])
146
+ def mcp_tools():
147
+ """List available MCP tools"""
148
+ return jsonify({
149
+ "tools": [
150
+ {"name": "memory_add", "description": "Store a memory"},
151
+ {"name": "memory_search", "description": "Semantic search"},
152
+ {"name": "memory_get", "description": "Get by ID"},
153
+ {"name": "memory_delete", "description": "Delete memory"},
154
+ {"name": "memory_list", "description": "List all"},
155
+ {"name": "memory_stats", "description": "Get stats"},
156
+ {"name": "memory_clear", "description": "Clear all"}
157
+ ]
158
+ })
159
+
160
+
161
+ if __name__ == '__main__':
162
+ port = int(os.environ.get('PORT', 7860))
163
+ app.run(host='0.0.0.0', port=port)