@recall_v3/mcp-server 0.1.0 → 3.0.0
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 +66 -0
- package/dist/api/client.d.ts +102 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +65 -16
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +297 -0
- package/dist/config/index.d.ts +10 -3
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +71 -4
- package/dist/crypto/index.d.ts +10 -0
- package/dist/crypto/index.d.ts.map +1 -1
- package/dist/crypto/index.js +29 -0
- package/dist/index.js +12 -12
- package/dist/tools/getContext.d.ts +2 -2
- package/dist/tools/getContext.d.ts.map +1 -1
- package/dist/tools/getContext.js +57 -20
- package/dist/tools/getHistory.d.ts +1 -1
- package/dist/tools/getHistory.d.ts.map +1 -1
- package/dist/tools/getHistory.js +93 -30
- package/dist/tools/getTranscripts.js +1 -1
- package/dist/tools/saveSession.d.ts +8 -3
- package/dist/tools/saveSession.d.ts.map +1 -1
- package/dist/tools/saveSession.js +186 -45
- package/package.json +1 -1
- package/src/api/client.ts +154 -17
- package/src/cli.ts +334 -0
- package/src/config/index.ts +84 -4
- package/src/crypto/index.ts +33 -0
- package/src/index.ts +12 -12
- package/src/tools/getContext.ts +102 -20
- package/src/tools/getHistory.ts +157 -31
- package/src/tools/getTranscripts.ts +1 -1
- package/src/tools/saveSession.ts +282 -50
package/src/cli.ts
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Recall v3 CLI
|
|
5
|
+
*
|
|
6
|
+
* Command-line interface for Recall MCP operations.
|
|
7
|
+
* Used by Claude Code hooks and for manual operations.
|
|
8
|
+
*
|
|
9
|
+
* Commands:
|
|
10
|
+
* recall-mcp context Load and display team context
|
|
11
|
+
* recall-mcp save Save current session
|
|
12
|
+
* recall-mcp status Check authentication status
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { getContext } from './tools/getContext.js';
|
|
16
|
+
import { saveSession } from './tools/saveSession.js';
|
|
17
|
+
import { getApiBaseUrl, getApiToken, getConfigPath } from './config/index.js';
|
|
18
|
+
import { RecallApiClient } from './api/client.js';
|
|
19
|
+
|
|
20
|
+
// Parse command line arguments
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
const command = args[0];
|
|
23
|
+
const flags = args.slice(1);
|
|
24
|
+
|
|
25
|
+
// Check for --quiet flag
|
|
26
|
+
const quiet = flags.includes('--quiet') || flags.includes('-q');
|
|
27
|
+
|
|
28
|
+
// Helper to log only if not quiet
|
|
29
|
+
function log(message: string): void {
|
|
30
|
+
if (!quiet) {
|
|
31
|
+
console.log(message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Helper to log errors (always shown)
|
|
36
|
+
function logError(message: string): void {
|
|
37
|
+
console.error(message);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Parse --project-path flag from args
|
|
42
|
+
*/
|
|
43
|
+
function getProjectPathFromFlags(flagList: string[]): string | undefined {
|
|
44
|
+
const projectPathIndex = flagList.findIndex(f => f === '--project-path' || f === '-p');
|
|
45
|
+
if (projectPathIndex !== -1 && flagList[projectPathIndex + 1]) {
|
|
46
|
+
return flagList[projectPathIndex + 1];
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Parse --project-path flag
|
|
52
|
+
const projectPath = getProjectPathFromFlags(flags);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Display usage information
|
|
56
|
+
*/
|
|
57
|
+
function showHelp(): void {
|
|
58
|
+
console.log(`
|
|
59
|
+
Recall v3 CLI - Team memory for AI coding assistants
|
|
60
|
+
|
|
61
|
+
Usage:
|
|
62
|
+
recall-mcp <command> [options]
|
|
63
|
+
|
|
64
|
+
Commands:
|
|
65
|
+
context Load team context for current repository
|
|
66
|
+
save Save current session (with AI summarization)
|
|
67
|
+
status Check authentication and connection status
|
|
68
|
+
help Show this help message
|
|
69
|
+
|
|
70
|
+
Options:
|
|
71
|
+
--quiet, -q Suppress non-essential output
|
|
72
|
+
--summary, -s Provide a manual summary (skips AI summarization)
|
|
73
|
+
--project-path, -p PATH Specify the git repository path (defaults to cwd)
|
|
74
|
+
|
|
75
|
+
Save Command:
|
|
76
|
+
The save command supports three modes:
|
|
77
|
+
|
|
78
|
+
1. AI Summarization (recommended):
|
|
79
|
+
echo "<conversation transcript>" | recall-mcp save
|
|
80
|
+
- Pipes conversation to API for AI-powered summarization
|
|
81
|
+
- Extracts decisions, mistakes, files changed automatically
|
|
82
|
+
|
|
83
|
+
2. Manual Summary:
|
|
84
|
+
recall-mcp save --summary "Fixed authentication bug"
|
|
85
|
+
- Use when you want to write your own summary
|
|
86
|
+
|
|
87
|
+
3. Placeholder (fallback):
|
|
88
|
+
recall-mcp save --quiet
|
|
89
|
+
- Saves minimal placeholder when no transcript available
|
|
90
|
+
- Used by Claude Code stop hooks as a backup
|
|
91
|
+
|
|
92
|
+
Examples:
|
|
93
|
+
recall-mcp context # Load context at session start
|
|
94
|
+
recall-mcp save --quiet # Save session quietly (for hooks)
|
|
95
|
+
recall-mcp save --summary "Fixed X" # Save with manual summary
|
|
96
|
+
cat transcript.txt | recall-mcp save # Save with AI summarization
|
|
97
|
+
recall-mcp save -p /path/to/repo --quiet # Save for specific repo (Claude Code hooks)
|
|
98
|
+
recall-mcp context --project-path /path/to/repo # Load context for specific repo
|
|
99
|
+
recall-mcp status # Check if properly configured
|
|
100
|
+
|
|
101
|
+
Environment Variables:
|
|
102
|
+
RECALL_API_URL API base URL (default: https://api-v3.recall.team)
|
|
103
|
+
RECALL_API_TOKEN Authentication token (required)
|
|
104
|
+
`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Load and display team context
|
|
109
|
+
*/
|
|
110
|
+
async function runContext(): Promise<void> {
|
|
111
|
+
try {
|
|
112
|
+
const result = await getContext({ projectPath });
|
|
113
|
+
|
|
114
|
+
if (result.isError) {
|
|
115
|
+
logError(`Error: ${result.content[0]?.text || 'Unknown error'}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Output the context (always, even in quiet mode - it's the primary output)
|
|
120
|
+
console.log(result.content[0]?.text || '');
|
|
121
|
+
|
|
122
|
+
} catch (error) {
|
|
123
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
124
|
+
logError(`Failed to load context: ${message}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Read from stdin if available (non-blocking)
|
|
131
|
+
* Handles large transcripts (Claude Code sessions can be 1MB+)
|
|
132
|
+
*/
|
|
133
|
+
async function readStdin(): Promise<string | null> {
|
|
134
|
+
return new Promise((resolve) => {
|
|
135
|
+
// Check if stdin has data (is a pipe, not TTY)
|
|
136
|
+
if (process.stdin.isTTY) {
|
|
137
|
+
resolve(null);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const chunks: string[] = [];
|
|
142
|
+
let resolved = false;
|
|
143
|
+
|
|
144
|
+
// Initial timeout - wait up to 500ms for first data
|
|
145
|
+
let timeout = setTimeout(() => {
|
|
146
|
+
if (!resolved) {
|
|
147
|
+
resolved = true;
|
|
148
|
+
resolve(chunks.length > 0 ? chunks.join('') : null);
|
|
149
|
+
}
|
|
150
|
+
}, 500);
|
|
151
|
+
|
|
152
|
+
process.stdin.setEncoding('utf8');
|
|
153
|
+
|
|
154
|
+
process.stdin.on('data', (chunk: string) => {
|
|
155
|
+
// Clear and reset timeout on each chunk (allows large files)
|
|
156
|
+
clearTimeout(timeout);
|
|
157
|
+
chunks.push(chunk);
|
|
158
|
+
|
|
159
|
+
// Set a shorter timeout between chunks (100ms idle = end of data)
|
|
160
|
+
timeout = setTimeout(() => {
|
|
161
|
+
if (!resolved) {
|
|
162
|
+
resolved = true;
|
|
163
|
+
resolve(chunks.join(''));
|
|
164
|
+
}
|
|
165
|
+
}, 100);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
process.stdin.on('end', () => {
|
|
169
|
+
clearTimeout(timeout);
|
|
170
|
+
if (!resolved) {
|
|
171
|
+
resolved = true;
|
|
172
|
+
resolve(chunks.length > 0 ? chunks.join('') : null);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
process.stdin.on('error', () => {
|
|
177
|
+
clearTimeout(timeout);
|
|
178
|
+
if (!resolved) {
|
|
179
|
+
resolved = true;
|
|
180
|
+
resolve(null);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
// Start reading
|
|
185
|
+
process.stdin.resume();
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Save current session
|
|
191
|
+
*
|
|
192
|
+
* Supports two modes:
|
|
193
|
+
* 1. With transcript via stdin: `echo "conversation..." | recall-mcp save`
|
|
194
|
+
* - Sends transcript to API for AI summarization
|
|
195
|
+
* 2. Without transcript: `recall-mcp save`
|
|
196
|
+
* - Saves a minimal placeholder (fallback)
|
|
197
|
+
*
|
|
198
|
+
* Usage with --summary flag: `recall-mcp save --summary "What we did"`
|
|
199
|
+
*/
|
|
200
|
+
async function runSave(): Promise<void> {
|
|
201
|
+
try {
|
|
202
|
+
// Check for --summary flag
|
|
203
|
+
const summaryIndex = flags.findIndex(f => f === '--summary' || f === '-s');
|
|
204
|
+
let providedSummary: string | undefined;
|
|
205
|
+
if (summaryIndex !== -1 && flags[summaryIndex + 1]) {
|
|
206
|
+
providedSummary = flags[summaryIndex + 1];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Try to read transcript from stdin
|
|
210
|
+
const transcript = await readStdin();
|
|
211
|
+
|
|
212
|
+
// Build saveSession args
|
|
213
|
+
const saveArgs: { summary?: string; transcript?: string; projectPath?: string } = {};
|
|
214
|
+
|
|
215
|
+
// Add project path if specified
|
|
216
|
+
if (projectPath) {
|
|
217
|
+
saveArgs.projectPath = projectPath;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (transcript && transcript.trim().length >= 50) {
|
|
221
|
+
// We have a transcript - send it for AI summarization
|
|
222
|
+
saveArgs.transcript = transcript;
|
|
223
|
+
log('Summarizing session with AI...');
|
|
224
|
+
} else if (providedSummary) {
|
|
225
|
+
// Use provided summary
|
|
226
|
+
saveArgs.summary = providedSummary;
|
|
227
|
+
} else {
|
|
228
|
+
// Fallback to placeholder - this is the minimal save case
|
|
229
|
+
saveArgs.summary = 'Session auto-saved by Recall hook';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const result = await saveSession(saveArgs);
|
|
233
|
+
|
|
234
|
+
if (result.isError) {
|
|
235
|
+
logError(`Error: ${result.content[0]?.text || 'Unknown error'}`);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
log(result.content[0]?.text || 'Session saved');
|
|
240
|
+
|
|
241
|
+
} catch (error) {
|
|
242
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
243
|
+
logError(`Failed to save session: ${message}`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Check authentication and connection status
|
|
250
|
+
*/
|
|
251
|
+
async function runStatus(): Promise<void> {
|
|
252
|
+
try {
|
|
253
|
+
const token = getApiToken();
|
|
254
|
+
const baseUrl = getApiBaseUrl();
|
|
255
|
+
|
|
256
|
+
console.log('Recall v3 Status');
|
|
257
|
+
console.log('================');
|
|
258
|
+
console.log(`Config: ${getConfigPath()}`);
|
|
259
|
+
console.log(`API URL: ${baseUrl}`);
|
|
260
|
+
console.log(`Token: ${token ? `${token.substring(0, 10)}...` : 'Not set'}`);
|
|
261
|
+
|
|
262
|
+
if (!token) {
|
|
263
|
+
logError('\nError: RECALL_API_TOKEN not set');
|
|
264
|
+
logError(`Set the environment variable or configure ${getConfigPath()}`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Test API connection
|
|
269
|
+
const client = new RecallApiClient({
|
|
270
|
+
baseUrl,
|
|
271
|
+
token,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
console.log('\nTesting API connection...');
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
const teams = await client.listTeams();
|
|
278
|
+
console.log(`✓ Connected successfully`);
|
|
279
|
+
console.log(`✓ Teams: ${teams.teams?.length || 0}`);
|
|
280
|
+
|
|
281
|
+
if (teams.teams && teams.teams.length > 0) {
|
|
282
|
+
for (const team of teams.teams) {
|
|
283
|
+
console.log(` - ${team.name} (${team.role})`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} catch (apiError) {
|
|
287
|
+
const message = apiError instanceof Error ? apiError.message : 'Unknown error';
|
|
288
|
+
logError(`✗ API connection failed: ${message}`);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
} catch (error) {
|
|
293
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
294
|
+
logError(`Status check failed: ${message}`);
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Main entry point
|
|
301
|
+
*/
|
|
302
|
+
async function main(): Promise<void> {
|
|
303
|
+
switch (command) {
|
|
304
|
+
case 'context':
|
|
305
|
+
await runContext();
|
|
306
|
+
break;
|
|
307
|
+
|
|
308
|
+
case 'save':
|
|
309
|
+
await runSave();
|
|
310
|
+
break;
|
|
311
|
+
|
|
312
|
+
case 'status':
|
|
313
|
+
await runStatus();
|
|
314
|
+
break;
|
|
315
|
+
|
|
316
|
+
case 'help':
|
|
317
|
+
case '--help':
|
|
318
|
+
case '-h':
|
|
319
|
+
case undefined:
|
|
320
|
+
showHelp();
|
|
321
|
+
break;
|
|
322
|
+
|
|
323
|
+
default:
|
|
324
|
+
logError(`Unknown command: ${command}`);
|
|
325
|
+
logError('Run "recall-mcp help" for usage information');
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Run CLI
|
|
331
|
+
main().catch((error) => {
|
|
332
|
+
logError(`Fatal error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
333
|
+
process.exit(1);
|
|
334
|
+
});
|
package/src/config/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Recall Configuration Management
|
|
2
|
+
* Recall v3 Configuration Management
|
|
3
3
|
*
|
|
4
|
-
* Manages the local config file at ~/.recall/config.json.
|
|
4
|
+
* Manages the local config file at ~/.config/recall/config.json.
|
|
5
|
+
* This is intentionally SEPARATE from v2's ~/.recall/ directory.
|
|
5
6
|
* Handles secure storage of API tokens and team keys.
|
|
6
7
|
*/
|
|
7
8
|
|
|
@@ -16,11 +17,26 @@ const CONFIG_VERSION = 1;
|
|
|
16
17
|
// Default API base URL (v3)
|
|
17
18
|
const DEFAULT_API_BASE_URL = 'https://api-v3.recall.team';
|
|
18
19
|
|
|
20
|
+
// v3 uses ~/.config/recall/ (XDG-compliant, separate from v2's ~/.recall/)
|
|
21
|
+
const V3_CONFIG_DIR = path.join(os.homedir(), '.config', 'recall');
|
|
22
|
+
|
|
23
|
+
// v2 used ~/.recall/ - we check this for migration
|
|
24
|
+
const V2_CONFIG_DIR = path.join(os.homedir(), '.recall');
|
|
25
|
+
|
|
19
26
|
/**
|
|
20
|
-
* Get the path to the Recall config directory
|
|
27
|
+
* Get the path to the Recall v3 config directory
|
|
28
|
+
* Uses ~/.config/recall/ (XDG-compliant, separate from v2)
|
|
21
29
|
*/
|
|
22
30
|
export function getConfigDir(): string {
|
|
23
|
-
return
|
|
31
|
+
return V3_CONFIG_DIR;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the path to the legacy v2 config directory
|
|
36
|
+
* (used only for migration checking)
|
|
37
|
+
*/
|
|
38
|
+
export function getLegacyConfigDir(): string {
|
|
39
|
+
return V2_CONFIG_DIR;
|
|
24
40
|
}
|
|
25
41
|
|
|
26
42
|
/**
|
|
@@ -68,11 +84,75 @@ function migrateConfig(config: Partial<RecallConfig>): RecallConfig {
|
|
|
68
84
|
return migrated;
|
|
69
85
|
}
|
|
70
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Check if a config file at a given path is a v3 config
|
|
89
|
+
* v3 configs have teamKeys and a version field (v2 didn't)
|
|
90
|
+
*/
|
|
91
|
+
function isV3Config(config: Record<string, unknown>): boolean {
|
|
92
|
+
// v3 configs always have version field
|
|
93
|
+
return typeof config.version === 'number' && config.version >= 1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Migrate v3 config from old v2 location (~/.recall/) to new v3 location (~/.config/recall/)
|
|
98
|
+
* This only migrates if:
|
|
99
|
+
* 1. No config exists at new v3 location
|
|
100
|
+
* 2. A v3 config exists at old v2 location (has version field)
|
|
101
|
+
*
|
|
102
|
+
* v2 configs (without version field) are left alone - they belong to v2
|
|
103
|
+
*/
|
|
104
|
+
function migrateFromV2Location(): void {
|
|
105
|
+
const v3ConfigPath = getConfigPath();
|
|
106
|
+
const v2ConfigPath = path.join(getLegacyConfigDir(), 'config.json');
|
|
107
|
+
|
|
108
|
+
// If v3 config already exists at new location, nothing to migrate
|
|
109
|
+
if (fs.existsSync(v3ConfigPath)) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// If no config exists at old v2 location, nothing to migrate
|
|
114
|
+
if (!fs.existsSync(v2ConfigPath)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const raw = fs.readFileSync(v2ConfigPath, 'utf-8');
|
|
120
|
+
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
|
121
|
+
|
|
122
|
+
// Only migrate if it's a v3 config (has version field)
|
|
123
|
+
// v2 configs don't have version field and belong to v2
|
|
124
|
+
if (!isV3Config(parsed)) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// This is a v3 config in the old location - migrate it
|
|
129
|
+
console.log(`[Recall v3] Migrating config from ~/.recall/ to ~/.config/recall/`);
|
|
130
|
+
|
|
131
|
+
// Ensure new directory exists
|
|
132
|
+
ensureConfigDir();
|
|
133
|
+
|
|
134
|
+
// Copy config to new location
|
|
135
|
+
fs.writeFileSync(v3ConfigPath, JSON.stringify(parsed, null, 2), { mode: 0o600 });
|
|
136
|
+
|
|
137
|
+
// Remove config from old location (but leave other v2 artifacts)
|
|
138
|
+
// Note: We only remove config.json, not the whole directory
|
|
139
|
+
fs.unlinkSync(v2ConfigPath);
|
|
140
|
+
|
|
141
|
+
console.log(`[Recall v3] Migration complete. v3 config is now at ~/.config/recall/config.json`);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
// Migration failed - not critical, user can manually migrate
|
|
144
|
+
console.warn(`[Recall v3] Could not migrate config from ~/.recall/ (non-critical)`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
71
148
|
/**
|
|
72
149
|
* Load configuration from disk
|
|
73
150
|
* Returns a default config if the file doesn't exist
|
|
74
151
|
*/
|
|
75
152
|
export function loadConfig(): RecallConfig {
|
|
153
|
+
// First, try to migrate from old v2 location if needed
|
|
154
|
+
migrateFromV2Location();
|
|
155
|
+
|
|
76
156
|
const configPath = getConfigPath();
|
|
77
157
|
|
|
78
158
|
if (!fs.existsSync(configPath)) {
|
package/src/crypto/index.ts
CHANGED
|
@@ -205,3 +205,36 @@ export function encryptContent(plaintext: string, keyBase64: string): string {
|
|
|
205
205
|
const payload = encrypt(plaintext, keyBase64);
|
|
206
206
|
return JSON.stringify(payload);
|
|
207
207
|
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Encrypt content and return in Recall format: RECALL_ENCRYPTED:v1:iv:tag:ciphertext
|
|
211
|
+
* This is the format expected by the v3 API.
|
|
212
|
+
*/
|
|
213
|
+
export function encryptForApi(plaintext: string, keyBase64: string): string {
|
|
214
|
+
const payload = encrypt(plaintext, keyBase64);
|
|
215
|
+
return `RECALL_ENCRYPTED:v1:${payload.iv}:${payload.tag}:${payload.ciphertext}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Decrypt content in Recall format: RECALL_ENCRYPTED:v1:iv:tag:ciphertext
|
|
220
|
+
* This is the format returned by the v3 API.
|
|
221
|
+
*/
|
|
222
|
+
export function decryptFromApi(encrypted: string, keyBase64: string): string {
|
|
223
|
+
if (!encrypted.startsWith('RECALL_ENCRYPTED:')) {
|
|
224
|
+
throw new Error('Invalid encrypted format: must start with RECALL_ENCRYPTED:');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const parts = encrypted.split(':');
|
|
228
|
+
if (parts.length !== 5) {
|
|
229
|
+
throw new Error('Invalid encrypted format: expected RECALL_ENCRYPTED:v1:iv:tag:ciphertext');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const [, version, iv, tag, ciphertext] = parts;
|
|
233
|
+
|
|
234
|
+
if (version !== 'v1') {
|
|
235
|
+
throw new Error(`Unsupported encryption version: ${version}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const payload: EncryptedPayload = { iv, tag, ciphertext };
|
|
239
|
+
return decrypt(payload, keyBase64);
|
|
240
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -33,9 +33,9 @@ import {
|
|
|
33
33
|
// Tool definitions
|
|
34
34
|
const TOOLS = [
|
|
35
35
|
{
|
|
36
|
-
name: '
|
|
36
|
+
name: 'recall3_get_context',
|
|
37
37
|
description:
|
|
38
|
-
"Get team brain (context.md) for the current repository. This is the distilled current state - loads automatically at every session start. Use
|
|
38
|
+
"Get team brain (context.md) for the current repository. This is the distilled current state - loads automatically at every session start. Use recall3_get_history for the full encyclopedia.",
|
|
39
39
|
inputSchema: {
|
|
40
40
|
type: 'object',
|
|
41
41
|
properties: {
|
|
@@ -48,9 +48,9 @@ const TOOLS = [
|
|
|
48
48
|
},
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
|
-
name: '
|
|
51
|
+
name: 'recall3_get_history',
|
|
52
52
|
description:
|
|
53
|
-
"Get detailed session history (context.md + recent sessions). This includes more context than
|
|
53
|
+
"Get detailed session history (context.md + recent sessions). This includes more context than recall3_get_context but uses more tokens.",
|
|
54
54
|
inputSchema: {
|
|
55
55
|
type: 'object',
|
|
56
56
|
properties: {
|
|
@@ -64,7 +64,7 @@ const TOOLS = [
|
|
|
64
64
|
},
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
|
-
name: '
|
|
67
|
+
name: 'recall3_get_transcripts',
|
|
68
68
|
description:
|
|
69
69
|
"Get full session transcripts (context.md + history.md). WARNING: This can be very large and use many tokens. Only use when you need complete historical details.",
|
|
70
70
|
inputSchema: {
|
|
@@ -80,7 +80,7 @@ const TOOLS = [
|
|
|
80
80
|
},
|
|
81
81
|
},
|
|
82
82
|
{
|
|
83
|
-
name: '
|
|
83
|
+
name: 'recall3_save_session',
|
|
84
84
|
description:
|
|
85
85
|
"Save a summary of what was accomplished in this coding session. This updates the team memory files.",
|
|
86
86
|
inputSchema: {
|
|
@@ -127,7 +127,7 @@ const TOOLS = [
|
|
|
127
127
|
},
|
|
128
128
|
},
|
|
129
129
|
{
|
|
130
|
-
name: '
|
|
130
|
+
name: 'recall3_log_decision',
|
|
131
131
|
description:
|
|
132
132
|
"Log an important decision made during coding. Quick way to capture why something was done.",
|
|
133
133
|
inputSchema: {
|
|
@@ -179,23 +179,23 @@ class RecallMCPServer {
|
|
|
179
179
|
try {
|
|
180
180
|
let result;
|
|
181
181
|
switch (name) {
|
|
182
|
-
case '
|
|
182
|
+
case 'recall3_get_context':
|
|
183
183
|
result = await getContext(args as unknown as GetContextArgs);
|
|
184
184
|
break;
|
|
185
185
|
|
|
186
|
-
case '
|
|
186
|
+
case 'recall3_get_history':
|
|
187
187
|
result = await getHistory(args as unknown as GetHistoryArgs);
|
|
188
188
|
break;
|
|
189
189
|
|
|
190
|
-
case '
|
|
190
|
+
case 'recall3_get_transcripts':
|
|
191
191
|
result = await getTranscripts(args as unknown as GetTranscriptsArgs);
|
|
192
192
|
break;
|
|
193
193
|
|
|
194
|
-
case '
|
|
194
|
+
case 'recall3_save_session':
|
|
195
195
|
result = await saveSession(args as unknown as SaveSessionArgs);
|
|
196
196
|
break;
|
|
197
197
|
|
|
198
|
-
case '
|
|
198
|
+
case 'recall3_log_decision':
|
|
199
199
|
result = await logDecision(args as unknown as LogDecisionArgs);
|
|
200
200
|
break;
|
|
201
201
|
|