server-memory-enhanced 2.3.0 → 2.3.2
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/README.md +245 -44
- package/dist/index.js +27 -1
- package/dist/lib/analysis/analytics-service.js +111 -0
- package/dist/lib/analysis/conflict-detector.js +48 -0
- package/dist/lib/analysis/context-builder.js +31 -0
- package/dist/lib/analysis/memory-stats.js +63 -0
- package/dist/lib/analysis/path-finder.js +77 -0
- package/dist/lib/collaboration/conversation-service.js +43 -0
- package/dist/lib/collaboration/flag-manager.js +38 -0
- package/dist/lib/jsonl-storage-adapter.js +8 -4
- package/dist/lib/knowledge-graph-manager.js +94 -663
- package/dist/lib/maintenance/bulk-updater.js +44 -0
- package/dist/lib/maintenance/memory-pruner.js +48 -0
- package/dist/lib/operations/entity-operations.js +27 -0
- package/dist/lib/operations/observation-operations.js +105 -0
- package/dist/lib/operations/relation-operations.js +45 -0
- package/dist/lib/queries/entity-queries.js +68 -0
- package/dist/lib/queries/graph-reader.js +9 -0
- package/dist/lib/queries/search-service.js +99 -0
- package/dist/lib/schemas.js +15 -0
- package/dist/lib/utils/entity-finder.js +31 -0
- package/dist/lib/utils/negation-detector.js +23 -0
- package/dist/lib/utils/observation-validator.js +43 -0
- package/dist/lib/utils/relation-key.js +16 -0
- package/dist/lib/validation/entity-type-validator.js +32 -0
- package/dist/lib/validation/observation-validator.js +61 -0
- package/dist/lib/validation/quality-scorer.js +27 -0
- package/dist/lib/validation/relation-validator.js +36 -0
- package/dist/lib/validation/request-validator.js +91 -0
- package/dist/lib/validation.js +8 -200
- package/dist/lib/versioning/observation-history.js +54 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,8 +15,10 @@ An enhanced version of the Memory MCP server that provides persistent knowledge
|
|
|
15
15
|
### Entities
|
|
16
16
|
Each entity now includes:
|
|
17
17
|
- `name`: Entity identifier
|
|
18
|
-
- `entityType`: Type of entity
|
|
19
|
-
- `observations`: Array of
|
|
18
|
+
- `entityType`: Type of entity (free-form, any domain-specific type allowed)
|
|
19
|
+
- `observations`: Array of **versioned Observation objects** (not strings) - **BREAKING CHANGE**
|
|
20
|
+
- Each observation has: `id`, `content`, `timestamp`, `version`, `supersedes`, `superseded_by`
|
|
21
|
+
- Supports full version history tracking
|
|
20
22
|
- `agentThreadId`: Unique identifier for the agent thread
|
|
21
23
|
- `timestamp`: ISO 8601 timestamp of creation
|
|
22
24
|
- `confidence`: Confidence score (0.0 to 1.0)
|
|
@@ -52,39 +54,64 @@ The server stores data in separate JSONL files per agent thread:
|
|
|
52
54
|
|
|
53
55
|
## Available Tools
|
|
54
56
|
|
|
57
|
+
### ⭐ Recommended Tool (New)
|
|
58
|
+
1. **save_memory**: **[RECOMMENDED]** Unified tool for creating entities and relations atomically with server-side validation
|
|
59
|
+
- Enforces observation limits (max 300 chars, 3 sentences per observation, ignoring periods in version numbers)
|
|
60
|
+
- Requires at least 1 relation per entity (prevents orphaned nodes)
|
|
61
|
+
- Free-form entity types with soft normalization
|
|
62
|
+
- Atomic transactions (all-or-nothing)
|
|
63
|
+
- Bidirectional relation tracking
|
|
64
|
+
- Quality score calculation
|
|
65
|
+
- Clear, actionable error messages
|
|
66
|
+
|
|
55
67
|
### Core Operations
|
|
56
|
-
|
|
57
|
-
|
|
68
|
+
> ⚠️ **Note**: `create_entities` and `create_relations` are **deprecated**. New code should use `save_memory` for better reliability and validation.
|
|
69
|
+
|
|
70
|
+
1. **create_entities**: Create new entities with metadata (including importance) - **[DEPRECATED - Use save_memory]**
|
|
71
|
+
2. **create_relations**: Create relationships between entities with metadata (including importance) - **[DEPRECATED - Use save_memory]**
|
|
58
72
|
3. **add_observations**: Add observations to existing entities with metadata (including importance)
|
|
59
|
-
4. **
|
|
60
|
-
5. **
|
|
61
|
-
6. **
|
|
62
|
-
7. **
|
|
63
|
-
8. **
|
|
64
|
-
9. **
|
|
65
|
-
10. **
|
|
73
|
+
4. **update_observation**: **[NEW]** Update an existing observation by creating a new version with updated content, maintaining version history
|
|
74
|
+
5. **delete_entities**: Remove entities and cascading relations
|
|
75
|
+
6. **delete_observations**: Remove specific observations
|
|
76
|
+
7. **delete_relations**: Delete relationships
|
|
77
|
+
8. **read_graph**: Read the entire knowledge graph
|
|
78
|
+
9. **search_nodes**: Search entities by name, type, or observation content
|
|
79
|
+
10. **open_nodes**: Retrieve specific entities by name
|
|
80
|
+
11. **query_nodes**: Advanced querying with range-based filtering by timestamp, confidence, and importance
|
|
81
|
+
12. **list_entities**: List entities with optional filtering by type and name pattern for quick discovery
|
|
82
|
+
13. **validate_memory**: Validate entities without saving (dry-run) - check for errors before attempting save_memory
|
|
66
83
|
|
|
67
84
|
### Memory Management & Insights
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
14. **get_analytics**: **[NEW]** Get simple, LLM-friendly analytics about your knowledge graph
|
|
86
|
+
- Recent changes (last 10 entities)
|
|
87
|
+
- Top important entities (by importance score)
|
|
88
|
+
- Most connected entities (by relation count)
|
|
89
|
+
- Orphaned entities (quality check)
|
|
90
|
+
15. **get_observation_history**: **[NEW]** Retrieve version history for observations
|
|
91
|
+
- Track how observations evolve over time
|
|
92
|
+
- View complete version chains
|
|
93
|
+
- Supports rollback by viewing previous versions
|
|
94
|
+
16. **get_memory_stats**: Get comprehensive statistics (entity counts, thread activity, avg confidence/importance, recent activity)
|
|
95
|
+
17. **get_recent_changes**: Retrieve entities and relations created/modified since a specific timestamp
|
|
96
|
+
18. **prune_memory**: Remove old or low-importance entities to manage memory size
|
|
97
|
+
19. **bulk_update**: Efficiently update multiple entities at once (confidence, importance, observations)
|
|
98
|
+
20. **list_conversations**: List all available agent threads (conversations) with metadata including entity counts, relation counts, and activity timestamps
|
|
72
99
|
|
|
73
100
|
### Relationship Intelligence
|
|
74
|
-
|
|
75
|
-
|
|
101
|
+
21. **find_relation_path**: Find the shortest path of relationships between two entities (useful for "how are they connected?")
|
|
102
|
+
22. **get_context**: Retrieve entities and relations related to specified entities up to a certain depth
|
|
76
103
|
|
|
77
104
|
### Quality & Review
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
105
|
+
23. **detect_conflicts**: Detect potentially conflicting observations using pattern matching and negation detection
|
|
106
|
+
24. **flag_for_review**: Mark entities for human review with a specific reason (Human-in-the-Loop)
|
|
107
|
+
25. **get_flagged_entities**: Retrieve all entities flagged for review
|
|
81
108
|
|
|
82
109
|
## Usage
|
|
83
110
|
|
|
84
111
|
### Installation
|
|
85
112
|
|
|
86
113
|
```bash
|
|
87
|
-
npm install
|
|
114
|
+
npm install server-memory-enhanced
|
|
88
115
|
```
|
|
89
116
|
|
|
90
117
|
### Running the Server
|
|
@@ -95,44 +122,150 @@ npx mcp-server-memory-enhanced
|
|
|
95
122
|
|
|
96
123
|
### Configuration
|
|
97
124
|
|
|
125
|
+
#### File Storage (Default)
|
|
126
|
+
|
|
98
127
|
Set the `MEMORY_DIR_PATH` environment variable to customize the storage location:
|
|
99
128
|
|
|
100
129
|
```bash
|
|
101
130
|
MEMORY_DIR_PATH=/path/to/memory/directory npx mcp-server-memory-enhanced
|
|
102
131
|
```
|
|
103
132
|
|
|
104
|
-
|
|
133
|
+
#### Neo4j Storage (Optional)
|
|
134
|
+
|
|
135
|
+
The server supports Neo4j as an alternative storage backend. If Neo4j environment variables are set, the server will attempt to connect to Neo4j. If the connection fails or variables are not set, it will automatically fall back to file-based JSONL storage.
|
|
136
|
+
|
|
137
|
+
**Environment Variables:**
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Neo4j connection settings
|
|
141
|
+
export NEO4J_URI=neo4j://localhost:7687
|
|
142
|
+
export NEO4J_USERNAME=neo4j
|
|
143
|
+
export NEO4J_PASSWORD=your_password
|
|
144
|
+
export NEO4J_DATABASE=neo4j # Optional, defaults to 'neo4j'
|
|
145
|
+
|
|
146
|
+
# Run the server
|
|
147
|
+
npx mcp-server-memory-enhanced
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Using Docker Compose:**
|
|
151
|
+
|
|
152
|
+
A `docker-compose.yml` file is provided for local development with Neo4j:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Start Neo4j and the MCP server
|
|
156
|
+
docker-compose up
|
|
157
|
+
|
|
158
|
+
# The Neo4j browser will be available at http://localhost:7474
|
|
159
|
+
# Username: neo4j, Password: testpassword
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Using with Claude Desktop:**
|
|
163
|
+
|
|
164
|
+
Configure the server in your Claude Desktop configuration with Neo4j:
|
|
165
|
+
|
|
166
|
+
```json
|
|
167
|
+
{
|
|
168
|
+
"mcpServers": {
|
|
169
|
+
"memory-enhanced": {
|
|
170
|
+
"command": "npx",
|
|
171
|
+
"args": ["-y", "mcp-server-memory-enhanced"],
|
|
172
|
+
"env": {
|
|
173
|
+
"NEO4J_URI": "neo4j://localhost:7687",
|
|
174
|
+
"NEO4J_USERNAME": "neo4j",
|
|
175
|
+
"NEO4J_PASSWORD": "your_password"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Benefits of Neo4j Storage:**
|
|
183
|
+
|
|
184
|
+
- **Graph-native queries**: Faster relationship traversals and path finding
|
|
185
|
+
- **Scalability**: Better performance with large knowledge graphs
|
|
186
|
+
- **Advanced queries**: Native support for graph algorithms
|
|
187
|
+
- **Visualization**: Use Neo4j Browser to visualize your knowledge graph
|
|
188
|
+
- **Automatic fallback**: If Neo4j is not available, automatically uses file storage
|
|
189
|
+
|
|
190
|
+
## User Guide
|
|
191
|
+
|
|
192
|
+
### ✨ Using save_memory (Recommended)
|
|
193
|
+
|
|
194
|
+
The `save_memory` tool is the recommended way to create entities and relations. It provides atomic transactions and server-side validation to ensure high-quality knowledge graphs.
|
|
195
|
+
|
|
196
|
+
#### Key Principles
|
|
197
|
+
|
|
198
|
+
1. **Atomic Observations**: Each observation should be a single, atomic fact
|
|
199
|
+
- ✅ Good: `"Works at Google"`, `"Lives in San Francisco"`
|
|
200
|
+
- ❌ Bad: `"Works at Google and lives in San Francisco and has a PhD in Computer Science"`
|
|
201
|
+
- **Max length**: 300 characters per observation
|
|
202
|
+
- **Max sentences**: 3 sentences per observation (technical content with version numbers supported)
|
|
203
|
+
|
|
204
|
+
2. **Mandatory Relations**: Every entity must connect to at least one other entity
|
|
205
|
+
- ✅ Good: `{ targetEntity: "Google", relationType: "works at" }`
|
|
206
|
+
- ❌ Bad: Empty relations array `[]`
|
|
207
|
+
- This prevents orphaned nodes and ensures a well-connected knowledge graph
|
|
208
|
+
|
|
209
|
+
3. **Free Entity Types**: Use any entity type that makes sense for your domain
|
|
210
|
+
- ✅ Good: `"Person"`, `"Company"`, `"Document"`, `"Recipe"`, `"Patient"`, `"API"`
|
|
211
|
+
- Soft normalization: `"person"` → `"Person"` (warning, not error)
|
|
212
|
+
- Space warning: `"API Key"` → suggests `"APIKey"`
|
|
213
|
+
|
|
214
|
+
4. **Error Messages**: The tool provides clear, actionable error messages
|
|
215
|
+
- Too long: `"Observation too long (350 chars). Max 300. Suggestion: Split into multiple observations."`
|
|
216
|
+
- No relations: `"Entity 'X' must have at least 1 relation. Suggestion: Add relations to show connections."`
|
|
217
|
+
- Too many sentences: `"Too many sentences (4). Max 3. Suggestion: Split this into 4 separate observations."`
|
|
218
|
+
|
|
219
|
+
### Example Usage
|
|
105
220
|
|
|
106
221
|
```typescript
|
|
107
|
-
//
|
|
108
|
-
await
|
|
222
|
+
// ✅ RECOMMENDED: Use save_memory for atomic entity and relation creation
|
|
223
|
+
await save_memory({
|
|
109
224
|
entities: [
|
|
110
225
|
{
|
|
111
226
|
name: "Alice",
|
|
112
|
-
entityType: "
|
|
113
|
-
observations: ["
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
227
|
+
entityType: "Person",
|
|
228
|
+
observations: ["Works at Google", "Lives in SF"], // Atomic facts, under 300 chars
|
|
229
|
+
relations: [{ targetEntity: "Bob", relationType: "knows" }] // At least 1 relation required
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
name: "Bob",
|
|
233
|
+
entityType: "Person",
|
|
234
|
+
observations: ["Works at Microsoft"],
|
|
235
|
+
relations: [{ targetEntity: "Alice", relationType: "knows" }]
|
|
118
236
|
}
|
|
119
|
-
]
|
|
237
|
+
],
|
|
238
|
+
threadId: "conversation-001"
|
|
120
239
|
});
|
|
121
240
|
|
|
122
|
-
//
|
|
123
|
-
await
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
241
|
+
// Get analytics about your knowledge graph
|
|
242
|
+
await get_analytics({
|
|
243
|
+
threadId: "conversation-001"
|
|
244
|
+
});
|
|
245
|
+
// Returns: {
|
|
246
|
+
// recent_changes: [...], // Last 10 entities
|
|
247
|
+
// top_important: [...], // Top 10 by importance
|
|
248
|
+
// most_connected: [...], // Top 10 by relation count
|
|
249
|
+
// orphaned_entities: [...] // Quality check
|
|
250
|
+
// }
|
|
251
|
+
|
|
252
|
+
// Get observation version history
|
|
253
|
+
await get_observation_history({
|
|
254
|
+
entityName: "Python Scripts",
|
|
255
|
+
observationId: "obs_abc123"
|
|
135
256
|
});
|
|
257
|
+
// Returns: { history: [{ id, content, version, timestamp, supersedes, superseded_by }, ...] }
|
|
258
|
+
|
|
259
|
+
// Update an existing observation (creates a new version)
|
|
260
|
+
await update_observation({
|
|
261
|
+
entityName: "Alice",
|
|
262
|
+
observationId: "obs_abc123",
|
|
263
|
+
newContent: "Works at Google (Senior Engineer)",
|
|
264
|
+
agentThreadId: "conversation-001",
|
|
265
|
+
timestamp: "2024-01-20T12:00:00Z",
|
|
266
|
+
confidence: 0.95 // Optional: update confidence
|
|
267
|
+
});
|
|
268
|
+
// Returns: { success: true, updatedObservation: { id, content, version: 2, supersedes, ... }, message: "..." }
|
|
136
269
|
|
|
137
270
|
// Query nodes with range-based filtering
|
|
138
271
|
await queryNodes({
|
|
@@ -146,6 +279,10 @@ await queryNodes({
|
|
|
146
279
|
await getMemoryStats();
|
|
147
280
|
// Returns: { entityCount, relationCount, threadCount, entityTypes, avgConfidence, avgImportance, recentActivity }
|
|
148
281
|
|
|
282
|
+
// List all conversations (agent threads)
|
|
283
|
+
await listConversations();
|
|
284
|
+
// Returns: { conversations: [{ agentThreadId, entityCount, relationCount, firstCreated, lastUpdated }, ...] }
|
|
285
|
+
|
|
149
286
|
// Get recent changes since last interaction
|
|
150
287
|
await getRecentChanges({ since: "2024-01-20T10:00:00Z" });
|
|
151
288
|
|
|
@@ -173,6 +310,49 @@ await bulkUpdate({
|
|
|
173
310
|
await pruneMemory({ olderThan: "2024-01-01T00:00:00Z", importanceLessThan: 0.3, keepMinEntities: 100 });
|
|
174
311
|
```
|
|
175
312
|
|
|
313
|
+
### 🔄 Migration Guide
|
|
314
|
+
|
|
315
|
+
For users of the old `create_entities` and `create_relations` tools:
|
|
316
|
+
|
|
317
|
+
#### What Changed
|
|
318
|
+
- **Old approach**: Two separate tools that could be used independently
|
|
319
|
+
- `create_entities` → creates entities
|
|
320
|
+
- `create_relations` → creates relations (optional, often skipped by LLMs)
|
|
321
|
+
- **New approach**: Single `save_memory` tool with atomic transactions
|
|
322
|
+
- Creates entities and relations together
|
|
323
|
+
- Enforces mandatory relations (at least 1 per entity)
|
|
324
|
+
- Validates observation length and atomicity
|
|
325
|
+
|
|
326
|
+
#### Migrating Your Code
|
|
327
|
+
```typescript
|
|
328
|
+
// ❌ OLD WAY (deprecated but still works)
|
|
329
|
+
await create_entities({
|
|
330
|
+
entities: [{ name: "Alice", entityType: "person", observations: ["works at Google and lives in SF"] }]
|
|
331
|
+
});
|
|
332
|
+
await create_relations({ // Often forgotten!
|
|
333
|
+
relations: [{ from: "Alice", to: "Bob", relationType: "knows" }]
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// ✅ NEW WAY (recommended)
|
|
337
|
+
await save_memory({
|
|
338
|
+
entities: [
|
|
339
|
+
{
|
|
340
|
+
name: "Alice",
|
|
341
|
+
entityType: "Person",
|
|
342
|
+
observations: ["Works at Google", "Lives in SF"], // Split into atomic facts
|
|
343
|
+
relations: [{ targetEntity: "Bob", relationType: "knows" }] // Required!
|
|
344
|
+
}
|
|
345
|
+
],
|
|
346
|
+
threadId: "conversation-001"
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### Migration Strategy
|
|
351
|
+
1. **Old tools remain available**: `create_entities` and `create_relations` are deprecated but not removed
|
|
352
|
+
2. **No forced migration**: Update your code gradually at your own pace
|
|
353
|
+
3. **New code should use `save_memory`**: Benefits from validation and atomic transactions
|
|
354
|
+
4. **Observation versioning**: New installations use versioned observations (breaking change for data model)
|
|
355
|
+
|
|
176
356
|
## Development
|
|
177
357
|
|
|
178
358
|
### Build
|
|
@@ -196,3 +376,24 @@ npm run watch
|
|
|
196
376
|
## License
|
|
197
377
|
|
|
198
378
|
MIT
|
|
379
|
+
|
|
380
|
+
## 🤝 Contributing
|
|
381
|
+
|
|
382
|
+
Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
383
|
+
|
|
384
|
+
## 🔒 Security
|
|
385
|
+
|
|
386
|
+
See [SECURITY.MD](SECURITY.md) for reporting security vulnerabilities.
|
|
387
|
+
|
|
388
|
+
## 📜 License
|
|
389
|
+
|
|
390
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
391
|
+
|
|
392
|
+
## 💬 Community
|
|
393
|
+
|
|
394
|
+
- [GitHub Discussions](https://github.com/modelcontextprotocol/servers/discussions)
|
|
395
|
+
- [Model Context Protocol Documentation](https://modelcontextprotocol.io)
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
Part of the [Model Context Protocol](https://modelcontextprotocol.io) project.
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { promises as fs } from 'fs';
|
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
import { KnowledgeGraphManager } from './lib/knowledge-graph-manager.js';
|
|
9
|
-
import { EntitySchema, RelationSchema, SaveMemoryInputSchema, SaveMemoryOutputSchema, GetAnalyticsInputSchema, GetAnalyticsOutputSchema, GetObservationHistoryInputSchema, GetObservationHistoryOutputSchema, ListEntitiesInputSchema, ListEntitiesOutputSchema, ValidateMemoryInputSchema, ValidateMemoryOutputSchema } from './lib/schemas.js';
|
|
9
|
+
import { EntitySchema, RelationSchema, SaveMemoryInputSchema, SaveMemoryOutputSchema, GetAnalyticsInputSchema, GetAnalyticsOutputSchema, GetObservationHistoryInputSchema, GetObservationHistoryOutputSchema, ListEntitiesInputSchema, ListEntitiesOutputSchema, ValidateMemoryInputSchema, ValidateMemoryOutputSchema, UpdateObservationInputSchema, UpdateObservationOutputSchema } from './lib/schemas.js';
|
|
10
10
|
import { handleSaveMemory } from './lib/save-memory-handler.js';
|
|
11
11
|
import { validateSaveMemoryRequest } from './lib/validation.js';
|
|
12
12
|
import { JsonlStorageAdapter } from './lib/jsonl-storage-adapter.js';
|
|
@@ -267,6 +267,32 @@ server.registerTool("delete_observations", {
|
|
|
267
267
|
structuredContent: { success: true, message: "Observations deleted successfully" }
|
|
268
268
|
};
|
|
269
269
|
});
|
|
270
|
+
// Register update_observation tool
|
|
271
|
+
server.registerTool("update_observation", {
|
|
272
|
+
title: "Update Observation",
|
|
273
|
+
description: "Update an existing observation by creating a new version with updated content. This maintains version history through the supersedes/superseded_by chain.",
|
|
274
|
+
inputSchema: UpdateObservationInputSchema.shape,
|
|
275
|
+
outputSchema: UpdateObservationOutputSchema.shape
|
|
276
|
+
}, async ({ entityName, observationId, newContent, agentThreadId, timestamp, confidence, importance }) => {
|
|
277
|
+
const updatedObservation = await knowledgeGraphManager.updateObservation({
|
|
278
|
+
entityName,
|
|
279
|
+
observationId,
|
|
280
|
+
newContent,
|
|
281
|
+
agentThreadId,
|
|
282
|
+
timestamp,
|
|
283
|
+
confidence,
|
|
284
|
+
importance
|
|
285
|
+
});
|
|
286
|
+
const message = `Observation updated successfully. New version: ${updatedObservation.id} (v${updatedObservation.version})`;
|
|
287
|
+
return {
|
|
288
|
+
content: [{ type: "text", text: message }],
|
|
289
|
+
structuredContent: {
|
|
290
|
+
success: true,
|
|
291
|
+
updatedObservation,
|
|
292
|
+
message
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
});
|
|
270
296
|
// Register delete_relations tool
|
|
271
297
|
server.registerTool("delete_relations", {
|
|
272
298
|
title: "Delete Relations",
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics service for thread-specific metrics
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Calculate recent changes for a thread
|
|
6
|
+
*/
|
|
7
|
+
function calculateRecentChanges(threadEntities) {
|
|
8
|
+
return threadEntities
|
|
9
|
+
.map(e => ({
|
|
10
|
+
entityName: e.name,
|
|
11
|
+
entityType: e.entityType,
|
|
12
|
+
lastModified: e.timestamp,
|
|
13
|
+
changeType: 'created' // Simplified: all are 'created' for now
|
|
14
|
+
}))
|
|
15
|
+
.sort((a, b) => b.lastModified.localeCompare(a.lastModified))
|
|
16
|
+
.slice(0, 10);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Calculate top important entities for a thread
|
|
20
|
+
*/
|
|
21
|
+
function calculateTopImportant(threadEntities) {
|
|
22
|
+
return threadEntities
|
|
23
|
+
.map(e => ({
|
|
24
|
+
entityName: e.name,
|
|
25
|
+
entityType: e.entityType,
|
|
26
|
+
importance: e.importance,
|
|
27
|
+
observationCount: e.observations.length
|
|
28
|
+
}))
|
|
29
|
+
.sort((a, b) => b.importance - a.importance)
|
|
30
|
+
.slice(0, 10);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Calculate most connected entities for a thread
|
|
34
|
+
*/
|
|
35
|
+
function calculateMostConnected(threadEntities, entityRelationCounts) {
|
|
36
|
+
return Array.from(entityRelationCounts.entries())
|
|
37
|
+
.map(([entityName, connectedSet]) => {
|
|
38
|
+
const entity = threadEntities.find(e => e.name === entityName);
|
|
39
|
+
return {
|
|
40
|
+
entityName,
|
|
41
|
+
entityType: entity.entityType,
|
|
42
|
+
relationCount: connectedSet.size,
|
|
43
|
+
connectedTo: Array.from(connectedSet)
|
|
44
|
+
};
|
|
45
|
+
})
|
|
46
|
+
.sort((a, b) => b.relationCount - a.relationCount)
|
|
47
|
+
.slice(0, 10);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Calculate orphaned entities for a thread
|
|
51
|
+
*/
|
|
52
|
+
function calculateOrphanedEntities(threadEntities, threadRelations, entityRelationCounts) {
|
|
53
|
+
const orphaned_entities = [];
|
|
54
|
+
const allEntityNames = new Set(threadEntities.map(e => e.name));
|
|
55
|
+
for (const entity of threadEntities) {
|
|
56
|
+
const relationCount = entityRelationCounts.get(entity.name)?.size || 0;
|
|
57
|
+
if (relationCount === 0) {
|
|
58
|
+
orphaned_entities.push({
|
|
59
|
+
entityName: entity.name,
|
|
60
|
+
entityType: entity.entityType,
|
|
61
|
+
reason: 'no_relations'
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
// Check for broken relations (pointing to non-existent entities)
|
|
66
|
+
const entityRelations = threadRelations.filter(r => r.from === entity.name || r.to === entity.name);
|
|
67
|
+
const hasBrokenRelation = entityRelations.some(r => !allEntityNames.has(r.from) || !allEntityNames.has(r.to));
|
|
68
|
+
if (hasBrokenRelation) {
|
|
69
|
+
orphaned_entities.push({
|
|
70
|
+
entityName: entity.name,
|
|
71
|
+
entityType: entity.entityType,
|
|
72
|
+
reason: 'broken_relation'
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return orphaned_entities;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get analytics for a specific thread (limited to 4 core metrics)
|
|
81
|
+
*/
|
|
82
|
+
export async function getAnalytics(storage, threadId) {
|
|
83
|
+
const graph = await storage.loadGraph();
|
|
84
|
+
// Filter to thread-specific data
|
|
85
|
+
const threadEntities = graph.entities.filter(e => e.agentThreadId === threadId);
|
|
86
|
+
const threadRelations = graph.relations.filter(r => r.agentThreadId === threadId);
|
|
87
|
+
// Calculate all metrics
|
|
88
|
+
const recent_changes = calculateRecentChanges(threadEntities);
|
|
89
|
+
const top_important = calculateTopImportant(threadEntities);
|
|
90
|
+
// Build the relation counts map once for both most_connected and orphaned_entities
|
|
91
|
+
const entityRelationCounts = new Map();
|
|
92
|
+
for (const entity of threadEntities) {
|
|
93
|
+
entityRelationCounts.set(entity.name, new Set());
|
|
94
|
+
}
|
|
95
|
+
for (const relation of threadRelations) {
|
|
96
|
+
if (entityRelationCounts.has(relation.from)) {
|
|
97
|
+
entityRelationCounts.get(relation.from).add(relation.to);
|
|
98
|
+
}
|
|
99
|
+
if (entityRelationCounts.has(relation.to)) {
|
|
100
|
+
entityRelationCounts.get(relation.to).add(relation.from);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const most_connected = calculateMostConnected(threadEntities, entityRelationCounts);
|
|
104
|
+
const orphaned_entities = calculateOrphanedEntities(threadEntities, threadRelations, entityRelationCounts);
|
|
105
|
+
return {
|
|
106
|
+
recent_changes,
|
|
107
|
+
top_important,
|
|
108
|
+
most_connected,
|
|
109
|
+
orphaned_entities
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conflict detection service
|
|
3
|
+
*/
|
|
4
|
+
import { hasNegation, NEGATION_WORDS } from '../utils/negation-detector.js';
|
|
5
|
+
/**
|
|
6
|
+
* Detect conflicting observations within entities
|
|
7
|
+
* Identifies potential contradictions by checking for negation patterns
|
|
8
|
+
*/
|
|
9
|
+
export async function detectConflicts(storage) {
|
|
10
|
+
const graph = await storage.loadGraph();
|
|
11
|
+
const conflicts = [];
|
|
12
|
+
for (const entity of graph.entities) {
|
|
13
|
+
const entityConflicts = [];
|
|
14
|
+
for (let i = 0; i < entity.observations.length; i++) {
|
|
15
|
+
for (let j = i + 1; j < entity.observations.length; j++) {
|
|
16
|
+
const obs1Content = entity.observations[i].content.toLowerCase();
|
|
17
|
+
const obs2Content = entity.observations[j].content.toLowerCase();
|
|
18
|
+
// Skip if observations are in the same version chain
|
|
19
|
+
if (entity.observations[i].supersedes === entity.observations[j].id ||
|
|
20
|
+
entity.observations[j].supersedes === entity.observations[i].id ||
|
|
21
|
+
entity.observations[i].superseded_by === entity.observations[j].id ||
|
|
22
|
+
entity.observations[j].superseded_by === entity.observations[i].id) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
// Check for negation patterns
|
|
26
|
+
const obs1HasNegation = hasNegation(obs1Content);
|
|
27
|
+
const obs2HasNegation = hasNegation(obs2Content);
|
|
28
|
+
// If one has negation and they share key words, might be a conflict
|
|
29
|
+
if (obs1HasNegation !== obs2HasNegation) {
|
|
30
|
+
const words1 = obs1Content.split(/\s+/).filter(w => w.length > 3);
|
|
31
|
+
const words2Set = new Set(obs2Content.split(/\s+/).filter(w => w.length > 3));
|
|
32
|
+
const commonWords = words1.filter(w => words2Set.has(w) && !NEGATION_WORDS.has(w));
|
|
33
|
+
if (commonWords.length >= 2) {
|
|
34
|
+
entityConflicts.push({
|
|
35
|
+
obs1: entity.observations[i].content,
|
|
36
|
+
obs2: entity.observations[j].content,
|
|
37
|
+
reason: 'Potential contradiction with negation'
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (entityConflicts.length > 0) {
|
|
44
|
+
conflicts.push({ entityName: entity.name, conflicts: entityConflicts });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return conflicts;
|
|
48
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context builder service
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get context (entities related to specified entities up to a certain depth)
|
|
6
|
+
* Expands to include related entities up to specified depth
|
|
7
|
+
*/
|
|
8
|
+
export async function getContext(storage, entityNames, depth = 1) {
|
|
9
|
+
const graph = await storage.loadGraph();
|
|
10
|
+
const contextEntityNames = new Set(entityNames);
|
|
11
|
+
// Expand to include related entities up to specified depth
|
|
12
|
+
for (let d = 0; d < depth; d++) {
|
|
13
|
+
const currentEntities = Array.from(contextEntityNames);
|
|
14
|
+
for (const entityName of currentEntities) {
|
|
15
|
+
// Find all relations involving this entity
|
|
16
|
+
const relatedRelations = graph.relations.filter(r => r.from === entityName || r.to === entityName);
|
|
17
|
+
// Add related entities
|
|
18
|
+
relatedRelations.forEach(r => {
|
|
19
|
+
contextEntityNames.add(r.from);
|
|
20
|
+
contextEntityNames.add(r.to);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// Get all entities and relations in context
|
|
25
|
+
const contextEntities = graph.entities.filter(e => contextEntityNames.has(e.name));
|
|
26
|
+
const contextRelations = graph.relations.filter(r => contextEntityNames.has(r.from) && contextEntityNames.has(r.to));
|
|
27
|
+
return {
|
|
28
|
+
entities: contextEntities,
|
|
29
|
+
relations: contextRelations
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory statistics service
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get comprehensive memory statistics
|
|
6
|
+
*/
|
|
7
|
+
export async function getMemoryStats(storage) {
|
|
8
|
+
const graph = await storage.loadGraph();
|
|
9
|
+
// Count entity types
|
|
10
|
+
const entityTypes = {};
|
|
11
|
+
graph.entities.forEach(e => {
|
|
12
|
+
entityTypes[e.entityType] = (entityTypes[e.entityType] || 0) + 1;
|
|
13
|
+
});
|
|
14
|
+
// Calculate averages
|
|
15
|
+
const avgConfidence = graph.entities.length > 0
|
|
16
|
+
? graph.entities.reduce((sum, e) => sum + e.confidence, 0) / graph.entities.length
|
|
17
|
+
: 0;
|
|
18
|
+
const avgImportance = graph.entities.length > 0
|
|
19
|
+
? graph.entities.reduce((sum, e) => sum + e.importance, 0) / graph.entities.length
|
|
20
|
+
: 0;
|
|
21
|
+
// Count unique threads
|
|
22
|
+
const threads = new Set([
|
|
23
|
+
...graph.entities.map(e => e.agentThreadId),
|
|
24
|
+
...graph.relations.map(r => r.agentThreadId)
|
|
25
|
+
]);
|
|
26
|
+
// Recent activity (last 7 days, grouped by day)
|
|
27
|
+
const now = new Date();
|
|
28
|
+
const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
29
|
+
const recentEntities = graph.entities.filter(e => new Date(e.timestamp) >= sevenDaysAgo);
|
|
30
|
+
// Group by day
|
|
31
|
+
const activityByDay = {};
|
|
32
|
+
recentEntities.forEach(e => {
|
|
33
|
+
const day = e.timestamp.substring(0, 10); // YYYY-MM-DD
|
|
34
|
+
activityByDay[day] = (activityByDay[day] || 0) + 1;
|
|
35
|
+
});
|
|
36
|
+
const recentActivity = Object.entries(activityByDay)
|
|
37
|
+
.map(([timestamp, entityCount]) => ({ timestamp, entityCount }))
|
|
38
|
+
.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
39
|
+
return {
|
|
40
|
+
entityCount: graph.entities.length,
|
|
41
|
+
relationCount: graph.relations.length,
|
|
42
|
+
threadCount: threads.size,
|
|
43
|
+
entityTypes,
|
|
44
|
+
avgConfidence,
|
|
45
|
+
avgImportance,
|
|
46
|
+
recentActivity
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get recent changes since a specific timestamp
|
|
51
|
+
*/
|
|
52
|
+
export async function getRecentChanges(storage, since) {
|
|
53
|
+
const graph = await storage.loadGraph();
|
|
54
|
+
const sinceDate = new Date(since);
|
|
55
|
+
// Only return entities and relations that were actually modified since the specified time
|
|
56
|
+
const recentEntities = graph.entities.filter(e => new Date(e.timestamp) >= sinceDate);
|
|
57
|
+
// Only include relations that are recent themselves
|
|
58
|
+
const recentRelations = graph.relations.filter(r => new Date(r.timestamp) >= sinceDate);
|
|
59
|
+
return {
|
|
60
|
+
entities: recentEntities,
|
|
61
|
+
relations: recentRelations
|
|
62
|
+
};
|
|
63
|
+
}
|