claude-recall 0.2.2 → 0.2.3
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
CHANGED
|
@@ -43,13 +43,26 @@ Launch Claude Code and your memories will be captured automatically. Claude Reca
|
|
|
43
43
|
|
|
44
44
|
## How It Works
|
|
45
45
|
|
|
46
|
-
Claude Recall uses the Model Context Protocol to integrate directly with Claude Code.
|
|
47
|
-
- Use tools or run commands - it captures the patterns
|
|
48
|
-
- Express preferences - it remembers them
|
|
49
|
-
- Make decisions - it stores the context
|
|
50
|
-
- Start new conversations - it provides relevant memories
|
|
46
|
+
Claude Recall uses the Model Context Protocol to integrate directly with Claude Code.
|
|
51
47
|
|
|
52
|
-
|
|
48
|
+
### Automatic Memory Capture (v0.2.3+)
|
|
49
|
+
|
|
50
|
+
**Any message containing "remember" will be automatically captured with highest priority.** For example:
|
|
51
|
+
- "Remember that I prefer tabs over spaces"
|
|
52
|
+
- "Remember to use PostgreSQL for the database"
|
|
53
|
+
- "Please remember our API uses port 8080"
|
|
54
|
+
|
|
55
|
+
Additionally, Claude Recall automatically detects and captures:
|
|
56
|
+
- **Preferences**: "I prefer X over Y", "Always use X", "From now on, use Y"
|
|
57
|
+
- **Decisions**: "We decided to...", "Let's go with...", "We'll use..."
|
|
58
|
+
- **Instructions**: "Make sure to...", "Ensure that...", "Files should be in..."
|
|
59
|
+
|
|
60
|
+
### Manual Memory Storage
|
|
61
|
+
|
|
62
|
+
You can also explicitly ask Claude to store memories using the MCP tools:
|
|
63
|
+
- Use the `mcp__claude-recall__store_memory` tool
|
|
64
|
+
- Search with `mcp__claude-recall__search`
|
|
65
|
+
- Get stats with `mcp__claude-recall__get_stats`
|
|
53
66
|
|
|
54
67
|
## Real-World Example
|
|
55
68
|
|
|
@@ -162,6 +175,18 @@ const memories = await mcp__claude-recall__search({
|
|
|
162
175
|
})
|
|
163
176
|
```
|
|
164
177
|
|
|
178
|
+
### Customizing Memory Patterns
|
|
179
|
+
|
|
180
|
+
Claude Recall uses configurable patterns for automatic memory capture. You can customize these by setting the `CLAUDE_RECALL_PATTERNS_CONFIG` environment variable to point to your own JSON configuration file.
|
|
181
|
+
|
|
182
|
+
Default patterns include:
|
|
183
|
+
- **Explicit remember**: Any sentence with "remember" (confidence: 1.0)
|
|
184
|
+
- **Preferences**: "I prefer", "Always use", "From now on" (confidence: 0.85-0.9)
|
|
185
|
+
- **Locations**: "Should be in X directory" (confidence: 0.8)
|
|
186
|
+
- **Instructions**: "Make sure", "Ensure that" (confidence: 0.7)
|
|
187
|
+
|
|
188
|
+
See `src/config/memory-patterns.json` for the full configuration format.
|
|
189
|
+
|
|
165
190
|
## Troubleshooting
|
|
166
191
|
|
|
167
192
|
### MCP Server Not Found
|
|
@@ -178,6 +203,59 @@ If Claude Code can't find the MCP server:
|
|
|
178
203
|
2. Check stats: `claude-recall stats`
|
|
179
204
|
3. Ensure MCP tools are being used in Claude Code
|
|
180
205
|
|
|
206
|
+
### Clearing npm Cache (Version Issues)
|
|
207
|
+
|
|
208
|
+
If you're seeing an old version after installing `@latest`, clear the npm cache:
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# Complete cache clear and reinstall
|
|
212
|
+
npm uninstall -g claude-recall
|
|
213
|
+
npm cache clean --force
|
|
214
|
+
npm cache verify
|
|
215
|
+
npm install -g claude-recall@latest
|
|
216
|
+
|
|
217
|
+
# Verify the installation
|
|
218
|
+
claude-recall --version
|
|
219
|
+
claude-recall mcp test
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Diagnostic Commands for Support
|
|
223
|
+
|
|
224
|
+
If you need help troubleshooting, run these commands and share the output:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
"""# 1. Check Installation
|
|
228
|
+
claude-recall --version
|
|
229
|
+
which claude-recall
|
|
230
|
+
npm list -g claude-recall
|
|
231
|
+
|
|
232
|
+
# 2. Check MCP Configuration
|
|
233
|
+
cat ~/.claude.json | grep -A10 "claude-recall"
|
|
234
|
+
claude-recall mcp test
|
|
235
|
+
|
|
236
|
+
# 3. Check Database
|
|
237
|
+
ls -la ~/.claude-recall/
|
|
238
|
+
ls -lh ~/.claude-recall/claude-recall.db 2>/dev/null || echo "Database not found"
|
|
239
|
+
|
|
240
|
+
# 4. Check System Status
|
|
241
|
+
claude-recall status
|
|
242
|
+
claude-recall stats
|
|
243
|
+
|
|
244
|
+
# 5. Test Basic Functionality
|
|
245
|
+
claude-recall search "test"
|
|
246
|
+
claude-recall stats | grep "Total memories"
|
|
247
|
+
|
|
248
|
+
# 6. Check for Errors
|
|
249
|
+
ls -la *.log 2>/dev/null || echo "No log files"
|
|
250
|
+
tail -20 info.log 2>/dev/null || echo "No info log"
|
|
251
|
+
|
|
252
|
+
# 7. Environment Info
|
|
253
|
+
node --version
|
|
254
|
+
npm --version
|
|
255
|
+
echo "OS: $(uname -s)"
|
|
256
|
+
echo "Home: $HOME""""
|
|
257
|
+
```
|
|
258
|
+
|
|
181
259
|
## Contributing
|
|
182
260
|
|
|
183
261
|
We welcome contributions! Claude Recall is designed to be hackable:
|
|
@@ -251,7 +251,7 @@ async function main() {
|
|
|
251
251
|
program
|
|
252
252
|
.name('claude-recall')
|
|
253
253
|
.description('Memory-enhanced Claude Code via MCP')
|
|
254
|
-
.version('0.2.
|
|
254
|
+
.version('0.2.3')
|
|
255
255
|
.option('--verbose', 'Enable verbose logging')
|
|
256
256
|
.option('--config <path>', 'Path to custom config file');
|
|
257
257
|
// MCP command
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"preferencePatterns": [
|
|
3
|
+
{
|
|
4
|
+
"pattern": "(?:I prefer|I'd prefer|prefer|preferring)\\s+([\\w\\s]+)\\s+(?:over|instead of|rather than)\\s+([\\w\\s]+)",
|
|
5
|
+
"type": "preference",
|
|
6
|
+
"confidence": 0.9,
|
|
7
|
+
"extractionMode": "comparison"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"pattern": "(?:always use|always using|we always use)\\s+([\\w\\s\\-\\.]+)",
|
|
11
|
+
"type": "preference",
|
|
12
|
+
"confidence": 0.85,
|
|
13
|
+
"extractionMode": "directive"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"pattern": "(?:from now on|moving forward|going forward)(?:,)?\\s+(?:use|we'll use|let's use)\\s+([\\w\\s\\-\\.]+)",
|
|
17
|
+
"type": "decision",
|
|
18
|
+
"confidence": 0.9,
|
|
19
|
+
"extractionMode": "future_directive"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"pattern": "(?:should be|must be|needs to be)\\s+(?:in|at|under)\\s+(?:the)?\\s+([\\w\\/\\-\\.]+)\\s+(?:directory|folder|dir)",
|
|
23
|
+
"type": "location_preference",
|
|
24
|
+
"confidence": 0.8,
|
|
25
|
+
"extractionMode": "location"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"pattern": "(?:make sure|ensure)\\s+(?:to|that)\\s+([^.!?]+)",
|
|
29
|
+
"type": "instruction",
|
|
30
|
+
"confidence": 0.7,
|
|
31
|
+
"extractionMode": "requirement"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"pattern": "(?:remember|Remember)\\s+(?:that\\s+)?(.+?)(?:[.!?]|$)",
|
|
35
|
+
"type": "explicit_memory",
|
|
36
|
+
"confidence": 1.0,
|
|
37
|
+
"extractionMode": "direct_capture"
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"actionPatterns": [
|
|
41
|
+
{
|
|
42
|
+
"pattern": "(?:creating|created|adding|added)\\s+(?:a|an|the)?\\s*([\\w\\s]+)\\s+(?:in|at|to)\\s+([\\w\\/\\-\\.]+)",
|
|
43
|
+
"type": "file_creation",
|
|
44
|
+
"confidence": 0.75
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"pattern": "(?:using|used|chose|chosen)\\s+([\\w\\s\\-\\.]+)\\s+(?:for|as)\\s+(?:the)?\\s*([\\w\\s]+)",
|
|
48
|
+
"type": "tool_choice",
|
|
49
|
+
"confidence": 0.8
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"contextTriggers": {
|
|
53
|
+
"highConfidenceWords": ["always", "never", "must", "should", "prefer", "requirement", "important"],
|
|
54
|
+
"decisionIndicators": ["decided", "choosing", "selected", "going with", "settled on"],
|
|
55
|
+
"futureTense": ["will", "going to", "from now on", "moving forward", "next time"]
|
|
56
|
+
},
|
|
57
|
+
"captureSettings": {
|
|
58
|
+
"minConfidence": 0.7,
|
|
59
|
+
"requireExplicitConfirmation": false,
|
|
60
|
+
"batchProcessingDelay": 1000,
|
|
61
|
+
"maxMemoriesPerSession": 50,
|
|
62
|
+
"deduplicationWindow": 3600000
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.MemoryCaptureMiddleware = void 0;
|
|
37
|
+
const preference_extractor_1 = require("../services/preference-extractor");
|
|
38
|
+
const action_pattern_detector_1 = require("../services/action-pattern-detector");
|
|
39
|
+
const memory_1 = require("../services/memory");
|
|
40
|
+
const logging_1 = require("../services/logging");
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
class MemoryCaptureMiddleware {
|
|
44
|
+
constructor() {
|
|
45
|
+
this.recentCaptures = new Map();
|
|
46
|
+
this.sessionMemoryCount = new Map();
|
|
47
|
+
this.preferenceExtractor = new preference_extractor_1.PreferenceExtractor();
|
|
48
|
+
this.actionDetector = new action_pattern_detector_1.ActionPatternDetector();
|
|
49
|
+
this.memoryService = memory_1.MemoryService.getInstance();
|
|
50
|
+
this.logger = logging_1.LoggingService.getInstance();
|
|
51
|
+
this.loadConfig();
|
|
52
|
+
}
|
|
53
|
+
loadConfig() {
|
|
54
|
+
try {
|
|
55
|
+
// First try custom config location
|
|
56
|
+
const customConfigPath = process.env.CLAUDE_RECALL_PATTERNS_CONFIG;
|
|
57
|
+
const defaultConfigPath = path.join(__dirname, '../../config/memory-patterns.json');
|
|
58
|
+
const configPath = customConfigPath && fs.existsSync(customConfigPath)
|
|
59
|
+
? customConfigPath
|
|
60
|
+
: defaultConfigPath;
|
|
61
|
+
if (fs.existsSync(configPath)) {
|
|
62
|
+
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
63
|
+
this.config = JSON.parse(configContent);
|
|
64
|
+
this.logger.info('MemoryCaptureMiddleware', 'Loaded memory patterns config', { configPath });
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Use default config if file doesn't exist
|
|
68
|
+
this.config = this.getDefaultConfig();
|
|
69
|
+
this.logger.warn('MemoryCaptureMiddleware', 'Using default config, no config file found');
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
this.logger.error('MemoryCaptureMiddleware', 'Failed to load config, using defaults', error);
|
|
74
|
+
this.config = this.getDefaultConfig();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
getDefaultConfig() {
|
|
78
|
+
return {
|
|
79
|
+
preferencePatterns: [
|
|
80
|
+
{
|
|
81
|
+
pattern: "(?:I prefer|prefer)\\s+([\\w\\s]+)\\s+(?:over|instead of)\\s+([\\w\\s]+)",
|
|
82
|
+
type: "preference",
|
|
83
|
+
confidence: 0.9,
|
|
84
|
+
extractionMode: "comparison"
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
actionPatterns: [],
|
|
88
|
+
contextTriggers: {
|
|
89
|
+
highConfidenceWords: ["always", "never", "must", "prefer"],
|
|
90
|
+
decisionIndicators: ["decided", "choosing", "selected"],
|
|
91
|
+
futureTense: ["will", "going to", "from now on"]
|
|
92
|
+
},
|
|
93
|
+
captureSettings: {
|
|
94
|
+
minConfidence: 0.7,
|
|
95
|
+
requireExplicitConfirmation: false,
|
|
96
|
+
batchProcessingDelay: 1000,
|
|
97
|
+
maxMemoriesPerSession: 50,
|
|
98
|
+
deduplicationWindow: 3600000
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Process a request/response pair for automatic memory capture
|
|
104
|
+
*/
|
|
105
|
+
async processForMemoryCapture(request, response, sessionId) {
|
|
106
|
+
try {
|
|
107
|
+
// Don't capture from memory-related tools to avoid loops
|
|
108
|
+
if (request.method === 'tools/call' &&
|
|
109
|
+
request.params?.name?.includes('memory')) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Extract content to analyze
|
|
113
|
+
const contentToAnalyze = this.extractContent(request, response);
|
|
114
|
+
if (!contentToAnalyze)
|
|
115
|
+
return;
|
|
116
|
+
// Check session memory limit
|
|
117
|
+
const sessionCount = this.sessionMemoryCount.get(sessionId) || 0;
|
|
118
|
+
if (sessionCount >= this.config.captureSettings.maxMemoriesPerSession) {
|
|
119
|
+
this.logger.debug('MemoryCaptureMiddleware', 'Session memory limit reached', { sessionId, count: sessionCount });
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Analyze for patterns
|
|
123
|
+
const detectedMemories = await this.analyzeContent(contentToAnalyze, sessionId);
|
|
124
|
+
// Store unique memories
|
|
125
|
+
for (const memory of detectedMemories) {
|
|
126
|
+
if (this.shouldCapture(memory, sessionId)) {
|
|
127
|
+
await this.captureMemory(memory, sessionId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
this.logger.error('MemoryCaptureMiddleware', 'Error in memory capture', error);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
extractContent(request, response) {
|
|
136
|
+
let content = '';
|
|
137
|
+
// Extract from request
|
|
138
|
+
if (request.params?.arguments?.content) {
|
|
139
|
+
content += request.params.arguments.content + '\n';
|
|
140
|
+
}
|
|
141
|
+
// Extract from response
|
|
142
|
+
if (response.result?.content) {
|
|
143
|
+
if (Array.isArray(response.result.content)) {
|
|
144
|
+
response.result.content.forEach((item) => {
|
|
145
|
+
if (item.text)
|
|
146
|
+
content += item.text + '\n';
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
else if (typeof response.result.content === 'string') {
|
|
150
|
+
content += response.result.content + '\n';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return content.trim() || null;
|
|
154
|
+
}
|
|
155
|
+
async analyzeContent(content, sessionId) {
|
|
156
|
+
const memories = [];
|
|
157
|
+
// PRIORITY 1: Check for explicit "remember" commands
|
|
158
|
+
const rememberRegex = /(?:remember|Remember)\s+(?:that\s+)?(.+?)(?:[.!?]|$)/gi;
|
|
159
|
+
const rememberMatches = content.matchAll(rememberRegex);
|
|
160
|
+
for (const match of rememberMatches) {
|
|
161
|
+
const memoryContent = match[1].trim();
|
|
162
|
+
if (memoryContent) {
|
|
163
|
+
memories.push({
|
|
164
|
+
type: 'explicit_memory',
|
|
165
|
+
content: memoryContent,
|
|
166
|
+
data: {
|
|
167
|
+
raw: memoryContent,
|
|
168
|
+
source: 'explicit_remember_command',
|
|
169
|
+
confidence: 1.0
|
|
170
|
+
},
|
|
171
|
+
confidence: 1.0, // Always highest confidence
|
|
172
|
+
priority: 1 // Highest priority
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// PRIORITY 2: Use configured preference patterns
|
|
177
|
+
for (const pattern of this.config.preferencePatterns) {
|
|
178
|
+
const regex = new RegExp(pattern.pattern, 'gi');
|
|
179
|
+
const matches = content.matchAll(regex);
|
|
180
|
+
for (const match of matches) {
|
|
181
|
+
// Skip if this was already captured as explicit memory
|
|
182
|
+
if (match[0].toLowerCase().includes('remember'))
|
|
183
|
+
continue;
|
|
184
|
+
memories.push({
|
|
185
|
+
type: pattern.type,
|
|
186
|
+
content: match[0],
|
|
187
|
+
data: {
|
|
188
|
+
pattern: pattern.type,
|
|
189
|
+
captured: match.slice(1),
|
|
190
|
+
confidence: pattern.confidence,
|
|
191
|
+
extractionMode: pattern.extractionMode
|
|
192
|
+
},
|
|
193
|
+
confidence: pattern.confidence,
|
|
194
|
+
priority: 2
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// PRIORITY 3: Use existing PreferenceExtractor
|
|
199
|
+
const preferences = this.preferenceExtractor.extractPreferences(content);
|
|
200
|
+
for (const pref of preferences) {
|
|
201
|
+
if (pref.confidence >= this.config.captureSettings.minConfidence) {
|
|
202
|
+
// Skip if already captured as explicit memory
|
|
203
|
+
if (pref.raw.toLowerCase().includes('remember'))
|
|
204
|
+
continue;
|
|
205
|
+
memories.push({
|
|
206
|
+
type: 'preference',
|
|
207
|
+
content: pref.raw,
|
|
208
|
+
data: {
|
|
209
|
+
key: pref.key,
|
|
210
|
+
value: pref.value,
|
|
211
|
+
confidence: pref.confidence
|
|
212
|
+
},
|
|
213
|
+
confidence: pref.confidence,
|
|
214
|
+
priority: 3
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Check for action patterns using configured patterns
|
|
219
|
+
for (const pattern of this.config.actionPatterns) {
|
|
220
|
+
const regex = new RegExp(pattern.pattern, 'gi');
|
|
221
|
+
const matches = content.matchAll(regex);
|
|
222
|
+
for (const match of matches) {
|
|
223
|
+
memories.push({
|
|
224
|
+
type: pattern.type,
|
|
225
|
+
content: match[0],
|
|
226
|
+
data: {
|
|
227
|
+
pattern: pattern.type,
|
|
228
|
+
captured: match.slice(1),
|
|
229
|
+
confidence: pattern.confidence
|
|
230
|
+
},
|
|
231
|
+
confidence: pattern.confidence
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Check for high-confidence context triggers
|
|
236
|
+
const hasHighConfidenceContext = this.config.contextTriggers.highConfidenceWords.some(word => content.toLowerCase().includes(word.toLowerCase()));
|
|
237
|
+
if (hasHighConfidenceContext) {
|
|
238
|
+
// Boost confidence for all found patterns
|
|
239
|
+
memories.forEach(m => m.confidence = Math.min(1.0, m.confidence * 1.1));
|
|
240
|
+
}
|
|
241
|
+
return memories;
|
|
242
|
+
}
|
|
243
|
+
shouldCapture(memory, sessionId) {
|
|
244
|
+
// ALWAYS capture explicit "remember" commands
|
|
245
|
+
if (memory.type === 'explicit_memory' || memory.priority === 1) {
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
// Check confidence threshold for other types
|
|
249
|
+
if (memory.confidence < this.config.captureSettings.minConfidence) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
// Check for duplicates within deduplication window
|
|
253
|
+
const memoryKey = `${memory.type}:${memory.content}`;
|
|
254
|
+
const lastCapture = this.recentCaptures.get(memoryKey);
|
|
255
|
+
const now = Date.now();
|
|
256
|
+
if (lastCapture && (now - lastCapture) < this.config.captureSettings.deduplicationWindow) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
async captureMemory(memory, sessionId) {
|
|
262
|
+
try {
|
|
263
|
+
// Store using MemoryService
|
|
264
|
+
const stored = this.memoryService.store({
|
|
265
|
+
key: `auto_${memory.type}_${Date.now()}`,
|
|
266
|
+
value: memory.data,
|
|
267
|
+
type: memory.type,
|
|
268
|
+
context: {
|
|
269
|
+
projectId: sessionId,
|
|
270
|
+
type: 'auto_capture',
|
|
271
|
+
timestamp: Date.now()
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
// Update tracking
|
|
275
|
+
const memoryKey = `${memory.type}:${memory.content}`;
|
|
276
|
+
this.recentCaptures.set(memoryKey, Date.now());
|
|
277
|
+
const currentCount = this.sessionMemoryCount.get(sessionId) || 0;
|
|
278
|
+
this.sessionMemoryCount.set(sessionId, currentCount + 1);
|
|
279
|
+
this.logger.info('MemoryCaptureMiddleware', 'Auto-captured memory', {
|
|
280
|
+
type: memory.type,
|
|
281
|
+
confidence: memory.confidence,
|
|
282
|
+
sessionId
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
this.logger.error('MemoryCaptureMiddleware', 'Failed to capture memory', error);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Clean up old session data
|
|
291
|
+
*/
|
|
292
|
+
cleanupSessions() {
|
|
293
|
+
// Clean up old captures
|
|
294
|
+
const now = Date.now();
|
|
295
|
+
const cutoff = now - this.config.captureSettings.deduplicationWindow;
|
|
296
|
+
for (const [key, timestamp] of this.recentCaptures) {
|
|
297
|
+
if (timestamp < cutoff) {
|
|
298
|
+
this.recentCaptures.delete(key);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
// Reset session counts periodically
|
|
302
|
+
if (this.sessionMemoryCount.size > 100) {
|
|
303
|
+
this.sessionMemoryCount.clear();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Reload configuration (useful for hot-reloading)
|
|
308
|
+
*/
|
|
309
|
+
reloadConfig() {
|
|
310
|
+
this.loadConfig();
|
|
311
|
+
this.logger.info('MemoryCaptureMiddleware', 'Configuration reloaded');
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
exports.MemoryCaptureMiddleware = MemoryCaptureMiddleware;
|
package/dist/mcp/server.js
CHANGED
|
@@ -7,6 +7,7 @@ const memory_1 = require("../services/memory");
|
|
|
7
7
|
const logging_1 = require("../services/logging");
|
|
8
8
|
const session_manager_1 = require("./session-manager");
|
|
9
9
|
const rate_limiter_1 = require("./rate-limiter");
|
|
10
|
+
const memory_capture_middleware_1 = require("./memory-capture-middleware");
|
|
10
11
|
class MCPServer {
|
|
11
12
|
constructor() {
|
|
12
13
|
this.tools = new Map();
|
|
@@ -21,31 +22,45 @@ class MCPServer {
|
|
|
21
22
|
maxRequests: 100, // 100 requests per minute
|
|
22
23
|
skipSuccessfulRequests: false
|
|
23
24
|
});
|
|
25
|
+
this.memoryCaptureMiddleware = new memory_capture_middleware_1.MemoryCaptureMiddleware();
|
|
24
26
|
this.setupRequestHandlers();
|
|
25
27
|
this.registerTools();
|
|
26
28
|
}
|
|
27
29
|
setupRequestHandlers() {
|
|
28
30
|
this.transport.onRequest(async (request) => {
|
|
31
|
+
let response;
|
|
29
32
|
try {
|
|
30
33
|
switch (request.method) {
|
|
31
34
|
case 'initialize':
|
|
32
|
-
|
|
35
|
+
response = await this.handleInitialize(request);
|
|
36
|
+
break;
|
|
33
37
|
case 'tools/list':
|
|
34
|
-
|
|
38
|
+
response = await this.handleToolsList(request);
|
|
39
|
+
break;
|
|
35
40
|
case 'tools/call':
|
|
36
|
-
|
|
41
|
+
response = await this.handleToolCall(request);
|
|
42
|
+
break;
|
|
37
43
|
case 'notifications/initialized':
|
|
38
|
-
|
|
44
|
+
response = await this.handleInitialized(request);
|
|
45
|
+
break;
|
|
39
46
|
case 'health/check':
|
|
40
|
-
|
|
47
|
+
response = await this.handleHealthCheck(request);
|
|
48
|
+
break;
|
|
41
49
|
default:
|
|
42
|
-
|
|
50
|
+
response = this.createErrorResponse(request.id, -32601, `Method not found: ${request.method}`);
|
|
43
51
|
}
|
|
44
52
|
}
|
|
45
53
|
catch (error) {
|
|
46
54
|
this.logger.logServiceError('MCPServer', 'handleRequest', error, { method: request.method });
|
|
47
|
-
|
|
55
|
+
response = this.createErrorResponse(request.id, -32603, 'Internal error', { message: error.message });
|
|
48
56
|
}
|
|
57
|
+
// Process for automatic memory capture (non-blocking)
|
|
58
|
+
if (this.isInitialized && request.method === 'tools/call') {
|
|
59
|
+
const sessionId = request.params?.arguments?.sessionId || this.generateSessionId();
|
|
60
|
+
this.memoryCaptureMiddleware.processForMemoryCapture(request, response, sessionId)
|
|
61
|
+
.catch(err => this.logger.error('MCPServer', 'Memory capture failed', err));
|
|
62
|
+
}
|
|
63
|
+
return response;
|
|
49
64
|
});
|
|
50
65
|
}
|
|
51
66
|
registerTools() {
|
|
@@ -257,6 +272,8 @@ class MCPServer {
|
|
|
257
272
|
this.sessionManager.shutdown();
|
|
258
273
|
// Shutdown rate limiter
|
|
259
274
|
this.rateLimiter.shutdown();
|
|
275
|
+
// Clean up memory capture middleware
|
|
276
|
+
this.memoryCaptureMiddleware.cleanupSessions();
|
|
260
277
|
await this.transport.stop();
|
|
261
278
|
this.memoryService.close();
|
|
262
279
|
this.logger.info('MCPServer', 'MCP server stopped');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-recall",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "MCP server for persistent memory in Claude Code conversations",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"postinstall": "node scripts/postinstall.js || true",
|
|
22
22
|
"test": "jest --passWithNoTests",
|
|
23
23
|
"test:watch": "jest --watch",
|
|
24
|
-
"build": "tsc && mkdir -p dist/memory && cp src/memory/schema.sql dist/memory/ && chmod +x dist/cli/claude-recall-cli.js",
|
|
24
|
+
"build": "tsc && mkdir -p dist/memory dist/config && cp src/memory/schema.sql dist/memory/ && cp -r src/config/* dist/config/ 2>/dev/null || true && chmod +x dist/cli/claude-recall-cli.js",
|
|
25
25
|
"build:cli": "tsc && chmod +x dist/cli/claude-recall-cli.js",
|
|
26
26
|
"dev": "ts-node",
|
|
27
27
|
"start": "ts-node src/server.ts",
|