Skip to content

Commit f33ecdc

Browse files
authored
Merge pull request #1 from mudler/memory
Add memory MCP
2 parents 115b50d + 7b56c6c commit f33ecdc

3 files changed

Lines changed: 275 additions & 1 deletion

File tree

.github/workflows/image.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
runs-on: ubuntu-latest
1818
strategy:
1919
matrix:
20-
mcp: [duckduckgo, weather]
20+
mcp: [duckduckgo, weather, memory]
2121
permissions:
2222
packages: write
2323
contents: read

README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,60 @@ mcp:
101101
}
102102
```
103103

104+
### 🧠 Memory Server
105+
106+
A persistent memory storage server that allows AI models to store, retrieve, and manage information across sessions.
107+
108+
**Features:**
109+
- Persistent JSON file storage
110+
- Add, list, and remove memory entries
111+
- Unique ID generation for each entry
112+
- Timestamp tracking for entries
113+
- Configurable storage location
114+
- JSON schema validation for inputs/outputs
115+
116+
**Tools:**
117+
- `add_memory` - Add a new entry to memory storage
118+
- `list_memory` - List all memory entries
119+
- `remove_memory` - Remove a memory entry by ID
120+
121+
**Configuration:**
122+
- `MEMORY_FILE_PATH` - Environment variable to set the memory file path (default: `/data/memory.json`)
123+
124+
**Memory Entry Format:**
125+
```json
126+
{
127+
"id": "1703123456789000000",
128+
"content": "User prefers coffee over tea",
129+
"created_at": "2023-12-21T10:30:56.789Z"
130+
}
131+
```
132+
133+
**Docker Image:**
134+
```bash
135+
docker run -e MEMORY_FILE_PATH=/custom/path/memory.json ghcr.io/mudler/mcps/memory:latest
136+
```
137+
138+
**LocalAI configuration ( to add to the model config):**
139+
```yaml
140+
mcp:
141+
stdio: |
142+
{
143+
"mcpServers": {
144+
"memory": {
145+
"command": "docker",
146+
"env": {
147+
"MEMORY_FILE_PATH": "/data/memory.json"
148+
},
149+
"args": [
150+
"run", "-i", "--rm", "-v", "/host/data:/data",
151+
"ghcr.io/mudler/mcps/memory:master"
152+
]
153+
}
154+
}
155+
}
156+
```
157+
104158
## Development
105159

106160
### Prerequisites
@@ -123,6 +177,7 @@ make dev
123177
# Build specific server
124178
make MCP_SERVER=duckduckgo build
125179
make MCP_SERVER=weather build
180+
make MCP_SERVER=memory build
126181
127182
# Run tests and checks
128183
make ci-local
@@ -178,6 +233,9 @@ Docker images are automatically built and pushed to GitHub Container Registry:
178233
- `ghcr.io/mudler/mcps/weather:latest` - Latest Weather server
179234
- `ghcr.io/mudler/mcps/weather:v1.0.0` - Tagged versions
180235
- `ghcr.io/mudler/mcps/weather:master` - Development versions
236+
- `ghcr.io/mudler/mcps/memory:latest` - Latest Memory server
237+
- `ghcr.io/mudler/mcps/memory:v1.0.0` - Tagged versions
238+
- `ghcr.io/mudler/mcps/memory:master` - Development versions
181239

182240
## Contributing
183241

memory/main.go

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"log"
8+
"os"
9+
"path/filepath"
10+
"time"
11+
12+
"github.com/modelcontextprotocol/go-sdk/mcp"
13+
)
14+
15+
// Memory entry structure
16+
type MemoryEntry struct {
17+
ID string `json:"id"`
18+
Content string `json:"content"`
19+
CreatedAt time.Time `json:"created_at"`
20+
}
21+
22+
// Memory storage structure
23+
type MemoryStorage struct {
24+
Entries []MemoryEntry `json:"entries"`
25+
}
26+
27+
// Input types for different operations
28+
type AddMemoryInput struct {
29+
Content string `json:"content" jsonschema:"the content to store in memory"`
30+
}
31+
32+
type RemoveMemoryInput struct {
33+
ID string `json:"id" jsonschema:"the ID of the memory entry to remove"`
34+
}
35+
36+
// Output types
37+
type AddMemoryOutput struct {
38+
ID string `json:"id" jsonschema:"the ID of the created memory entry"`
39+
Content string `json:"content" jsonschema:"the stored content"`
40+
CreatedAt time.Time `json:"created_at" jsonschema:"when the entry was created"`
41+
}
42+
43+
type ListMemoryOutput struct {
44+
Entries []MemoryEntry `json:"entries" jsonschema:"list of all memory entries"`
45+
}
46+
47+
type RemoveMemoryOutput struct {
48+
Success bool `json:"success" jsonschema:"whether the removal was successful"`
49+
Message string `json:"message" jsonschema:"status message"`
50+
}
51+
52+
// Global variable to store the memory file path
53+
var memoryFilePath string
54+
55+
// Load memory entries from JSON file
56+
func loadMemory() (*MemoryStorage, error) {
57+
if _, err := os.Stat(memoryFilePath); os.IsNotExist(err) {
58+
// File doesn't exist, return empty storage
59+
return &MemoryStorage{Entries: []MemoryEntry{}}, nil
60+
}
61+
62+
data, err := os.ReadFile(memoryFilePath)
63+
if err != nil {
64+
return nil, fmt.Errorf("failed to read memory file: %w", err)
65+
}
66+
67+
var storage MemoryStorage
68+
if err := json.Unmarshal(data, &storage); err != nil {
69+
return nil, fmt.Errorf("failed to parse memory file: %w", err)
70+
}
71+
72+
return &storage, nil
73+
}
74+
75+
// Save memory entries to JSON file
76+
func saveMemory(storage *MemoryStorage) error {
77+
data, err := json.MarshalIndent(storage, "", " ")
78+
if err != nil {
79+
return fmt.Errorf("failed to marshal memory data: %w", err)
80+
}
81+
82+
if err := os.WriteFile(memoryFilePath, data, 0644); err != nil {
83+
return fmt.Errorf("failed to write memory file: %w", err)
84+
}
85+
86+
return nil
87+
}
88+
89+
// Generate a unique ID for memory entries
90+
func generateID() string {
91+
return fmt.Sprintf("%d", time.Now().UnixNano())
92+
}
93+
94+
// Add memory entry
95+
func AddMemory(ctx context.Context, req *mcp.CallToolRequest, input AddMemoryInput) (
96+
*mcp.CallToolResult,
97+
AddMemoryOutput,
98+
error,
99+
) {
100+
storage, err := loadMemory()
101+
if err != nil {
102+
return nil, AddMemoryOutput{}, err
103+
}
104+
105+
entry := MemoryEntry{
106+
ID: generateID(),
107+
Content: input.Content,
108+
CreatedAt: time.Now(),
109+
}
110+
111+
storage.Entries = append(storage.Entries, entry)
112+
113+
if err := saveMemory(storage); err != nil {
114+
return nil, AddMemoryOutput{}, err
115+
}
116+
117+
output := AddMemoryOutput{
118+
ID: entry.ID,
119+
Content: entry.Content,
120+
CreatedAt: entry.CreatedAt,
121+
}
122+
123+
return nil, output, nil
124+
}
125+
126+
// List all memory entries
127+
func ListMemory(ctx context.Context, req *mcp.CallToolRequest, input struct{}) (
128+
*mcp.CallToolResult,
129+
ListMemoryOutput,
130+
error,
131+
) {
132+
storage, err := loadMemory()
133+
if err != nil {
134+
return nil, ListMemoryOutput{}, err
135+
}
136+
137+
output := ListMemoryOutput{
138+
Entries: storage.Entries,
139+
}
140+
141+
return nil, output, nil
142+
}
143+
144+
// Remove memory entry by ID
145+
func RemoveMemory(ctx context.Context, req *mcp.CallToolRequest, input RemoveMemoryInput) (
146+
*mcp.CallToolResult,
147+
RemoveMemoryOutput,
148+
error,
149+
) {
150+
storage, err := loadMemory()
151+
if err != nil {
152+
return nil, RemoveMemoryOutput{}, err
153+
}
154+
155+
// Find and remove the entry
156+
found := false
157+
for i, entry := range storage.Entries {
158+
if entry.ID == input.ID {
159+
storage.Entries = append(storage.Entries[:i], storage.Entries[i+1:]...)
160+
found = true
161+
break
162+
}
163+
}
164+
165+
if !found {
166+
output := RemoveMemoryOutput{
167+
Success: false,
168+
Message: fmt.Sprintf("Memory entry with ID '%s' not found", input.ID),
169+
}
170+
return nil, output, nil
171+
}
172+
173+
if err := saveMemory(storage); err != nil {
174+
return nil, RemoveMemoryOutput{}, err
175+
}
176+
177+
output := RemoveMemoryOutput{
178+
Success: true,
179+
Message: fmt.Sprintf("Memory entry with ID '%s' removed successfully", input.ID),
180+
}
181+
182+
return nil, output, nil
183+
}
184+
185+
func main() {
186+
// Get memory file path from environment variable, default to ./memory.json
187+
memoryFilePath = os.Getenv("MEMORY_FILE_PATH")
188+
if memoryFilePath == "" {
189+
memoryFilePath = "/data/memory.json"
190+
}
191+
192+
os.MkdirAll(filepath.Dir(memoryFilePath), 0755)
193+
194+
// Create a server with memory tools
195+
server := mcp.NewServer(&mcp.Implementation{Name: "memory", Version: "v1.0.0"}, nil)
196+
197+
// Register memory tools
198+
mcp.AddTool(server, &mcp.Tool{
199+
Name: "add_memory",
200+
Description: "Add a new entry to memory storage",
201+
}, AddMemory)
202+
203+
mcp.AddTool(server, &mcp.Tool{
204+
Name: "list_memory",
205+
Description: "List all memory entries",
206+
}, ListMemory)
207+
208+
mcp.AddTool(server, &mcp.Tool{
209+
Name: "remove_memory",
210+
Description: "Remove a memory entry by ID",
211+
}, RemoveMemory)
212+
213+
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
214+
log.Fatal(err)
215+
}
216+
}

0 commit comments

Comments
 (0)