@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/tools/saveSession.ts
CHANGED
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
* saveSession Tool Implementation
|
|
3
3
|
*
|
|
4
4
|
* Saves a summary of what was accomplished in this coding session.
|
|
5
|
-
*
|
|
5
|
+
* Encrypts content with team key and sends to Recall v3 API.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { RecallApiClient, AuthenticationError, RecallApiError } from '../api/client.js';
|
|
9
9
|
import { getApiBaseUrl, getApiToken, getTeamKey, setTeamKey } from '../config/index.js';
|
|
10
|
-
import {
|
|
10
|
+
import { encryptForApi } from '../crypto/index.js';
|
|
11
11
|
import { successResponse, errorResponse, type ToolResponse } from './types.js';
|
|
12
12
|
import { resolveProjectPath, getRepoInfo } from './utils.js';
|
|
13
|
-
import type { SaveSessionRequest } from '@recall_v3/shared';
|
|
14
13
|
|
|
15
14
|
export interface SaveSessionArgs {
|
|
16
|
-
summary
|
|
15
|
+
/** Pre-written summary (if transcript not provided) */
|
|
16
|
+
summary?: string;
|
|
17
|
+
/** Raw conversation transcript - will be summarized by AI */
|
|
18
|
+
transcript?: string;
|
|
19
|
+
/** Git repository path (defaults to cwd if not provided) */
|
|
20
|
+
projectPath?: string;
|
|
17
21
|
decisions?: Array<{
|
|
18
22
|
what: string;
|
|
19
23
|
why: string;
|
|
@@ -24,17 +28,172 @@ export interface SaveSessionArgs {
|
|
|
24
28
|
blockers?: string;
|
|
25
29
|
}
|
|
26
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Request body for the summarize API (matches actual API, not shared types)
|
|
33
|
+
*/
|
|
34
|
+
interface SummarizeApiRequest {
|
|
35
|
+
transcript: string;
|
|
36
|
+
repo_name?: string;
|
|
37
|
+
developer?: string;
|
|
38
|
+
tool?: 'claude-code' | 'cursor' | 'copilot' | 'other';
|
|
39
|
+
tier?: 'team' | 'pro' | 'enterprise';
|
|
40
|
+
started_at?: string;
|
|
41
|
+
ended_at?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Response from the summarize API (matches actual API response)
|
|
46
|
+
*/
|
|
47
|
+
interface SummarizeApiResponse {
|
|
48
|
+
session_title: string;
|
|
49
|
+
summary: string;
|
|
50
|
+
detailed_summary?: string;
|
|
51
|
+
status: string;
|
|
52
|
+
tldr: {
|
|
53
|
+
summary: string;
|
|
54
|
+
status: string;
|
|
55
|
+
decisions: string[];
|
|
56
|
+
mistakes: string[];
|
|
57
|
+
tags: string[];
|
|
58
|
+
files_changed: string[];
|
|
59
|
+
};
|
|
60
|
+
decisions: Array<{
|
|
61
|
+
title: string;
|
|
62
|
+
what: string;
|
|
63
|
+
why: string;
|
|
64
|
+
alternatives: Array<{ option: string; rejected_because: string }>;
|
|
65
|
+
confidence: string;
|
|
66
|
+
}>;
|
|
67
|
+
failures: Array<{
|
|
68
|
+
title: string;
|
|
69
|
+
what_tried: string;
|
|
70
|
+
what_happened: string;
|
|
71
|
+
root_cause: string;
|
|
72
|
+
resolution: string;
|
|
73
|
+
}>;
|
|
74
|
+
lessons: Array<{
|
|
75
|
+
title: string;
|
|
76
|
+
lesson: string;
|
|
77
|
+
when_applies: string;
|
|
78
|
+
}>;
|
|
79
|
+
quality_metadata: {
|
|
80
|
+
understandingScore: number;
|
|
81
|
+
accuracyScore: number;
|
|
82
|
+
attempts: number;
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Build the full session content to encrypt
|
|
88
|
+
*/
|
|
89
|
+
function buildSessionContent(args: SaveSessionArgs): string {
|
|
90
|
+
const parts: string[] = [];
|
|
91
|
+
|
|
92
|
+
parts.push('## Summary');
|
|
93
|
+
parts.push((args.summary || 'Session saved').trim());
|
|
94
|
+
parts.push('');
|
|
95
|
+
|
|
96
|
+
if (args.decisions && args.decisions.length > 0) {
|
|
97
|
+
parts.push('## Decisions');
|
|
98
|
+
for (const decision of args.decisions) {
|
|
99
|
+
parts.push(`- **${decision.what}**: ${decision.why}`);
|
|
100
|
+
}
|
|
101
|
+
parts.push('');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (args.mistakes && args.mistakes.length > 0) {
|
|
105
|
+
parts.push('## Mistakes / Gotchas');
|
|
106
|
+
for (const mistake of args.mistakes) {
|
|
107
|
+
parts.push(`- ${mistake}`);
|
|
108
|
+
}
|
|
109
|
+
parts.push('');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (args.filesChanged && args.filesChanged.length > 0) {
|
|
113
|
+
parts.push('## Files Changed');
|
|
114
|
+
for (const file of args.filesChanged) {
|
|
115
|
+
parts.push(`- ${file}`);
|
|
116
|
+
}
|
|
117
|
+
parts.push('');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (args.nextSteps) {
|
|
121
|
+
parts.push('## Next Steps');
|
|
122
|
+
parts.push(args.nextSteps.trim());
|
|
123
|
+
parts.push('');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (args.blockers) {
|
|
127
|
+
parts.push('## Blockers');
|
|
128
|
+
parts.push(args.blockers.trim());
|
|
129
|
+
parts.push('');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return parts.join('\n');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Build TLDR metadata for the session
|
|
137
|
+
*/
|
|
138
|
+
function buildTldr(args: SaveSessionArgs): {
|
|
139
|
+
summary: string;
|
|
140
|
+
status: 'complete' | 'in-progress' | 'blocked';
|
|
141
|
+
decisions?: string[];
|
|
142
|
+
mistakes?: string[];
|
|
143
|
+
tags?: string[];
|
|
144
|
+
files_changed?: string[];
|
|
145
|
+
blockers?: string | null;
|
|
146
|
+
next_steps?: string | null;
|
|
147
|
+
} {
|
|
148
|
+
const tldr: {
|
|
149
|
+
summary: string;
|
|
150
|
+
status: 'complete' | 'in-progress' | 'blocked';
|
|
151
|
+
decisions?: string[];
|
|
152
|
+
mistakes?: string[];
|
|
153
|
+
tags?: string[];
|
|
154
|
+
files_changed?: string[];
|
|
155
|
+
blockers?: string | null;
|
|
156
|
+
next_steps?: string | null;
|
|
157
|
+
} = {
|
|
158
|
+
summary: (args.summary || 'Session saved').trim().substring(0, 200),
|
|
159
|
+
status: args.blockers ? 'blocked' : 'complete',
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
if (args.decisions && args.decisions.length > 0) {
|
|
163
|
+
tldr.decisions = args.decisions.map(d => d.what);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (args.mistakes && args.mistakes.length > 0) {
|
|
167
|
+
tldr.mistakes = args.mistakes;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (args.filesChanged && args.filesChanged.length > 0) {
|
|
171
|
+
tldr.files_changed = args.filesChanged;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (args.nextSteps) {
|
|
175
|
+
tldr.next_steps = args.nextSteps.trim();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (args.blockers) {
|
|
179
|
+
tldr.blockers = args.blockers.trim();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return tldr;
|
|
183
|
+
}
|
|
184
|
+
|
|
27
185
|
/**
|
|
28
186
|
* Execute the saveSession tool
|
|
29
187
|
*
|
|
30
|
-
* @param args - Tool arguments with session summary and metadata
|
|
188
|
+
* @param args - Tool arguments with session summary/transcript and metadata
|
|
31
189
|
* @returns MCP tool response with save confirmation
|
|
32
190
|
*/
|
|
33
191
|
export async function saveSession(args: SaveSessionArgs): Promise<ToolResponse> {
|
|
34
192
|
try {
|
|
35
|
-
// Validate required fields
|
|
36
|
-
if (!args.summary || args.summary.trim().length === 0)
|
|
37
|
-
|
|
193
|
+
// Validate required fields - need either summary or transcript
|
|
194
|
+
if ((!args.summary || args.summary.trim().length === 0) &&
|
|
195
|
+
(!args.transcript || args.transcript.trim().length === 0)) {
|
|
196
|
+
return errorResponse('Either summary or transcript is required. Please provide a description of what was accomplished or the session transcript.');
|
|
38
197
|
}
|
|
39
198
|
|
|
40
199
|
// Get API token
|
|
@@ -45,15 +204,16 @@ export async function saveSession(args: SaveSessionArgs): Promise<ToolResponse>
|
|
|
45
204
|
);
|
|
46
205
|
}
|
|
47
206
|
|
|
48
|
-
// Resolve project path (use
|
|
49
|
-
const projectPath = resolveProjectPath(
|
|
207
|
+
// Resolve project path (use args.projectPath if provided, otherwise cwd)
|
|
208
|
+
const projectPath = resolveProjectPath(args.projectPath);
|
|
50
209
|
|
|
51
210
|
// Get repo info from git
|
|
52
211
|
const repoInfo = await getRepoInfo(projectPath);
|
|
53
212
|
if (!repoInfo) {
|
|
54
213
|
return errorResponse(
|
|
55
214
|
`Could not determine repository info for: ${projectPath}\n` +
|
|
56
|
-
'Make sure this is a git repository with a remote origin
|
|
215
|
+
'Make sure this is a git repository with a remote origin.\n' +
|
|
216
|
+
'If running from Claude Code hooks, use --project-path to specify the repo path.'
|
|
57
217
|
);
|
|
58
218
|
}
|
|
59
219
|
|
|
@@ -74,64 +234,136 @@ export async function saveSession(args: SaveSessionArgs): Promise<ToolResponse>
|
|
|
74
234
|
setTeamKey(teamId, teamKey);
|
|
75
235
|
}
|
|
76
236
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
};
|
|
237
|
+
// If transcript provided, call summarize API to generate structured summary
|
|
238
|
+
let summarizedArgs = { ...args };
|
|
239
|
+
let aiSummary: SummarizeApiResponse | null = null;
|
|
81
240
|
|
|
82
|
-
if (args.
|
|
83
|
-
|
|
84
|
-
|
|
241
|
+
if (args.transcript && args.transcript.trim().length >= 50) {
|
|
242
|
+
try {
|
|
243
|
+
// Build the API request
|
|
244
|
+
const now = new Date().toISOString();
|
|
245
|
+
const summarizeRequest: SummarizeApiRequest = {
|
|
246
|
+
transcript: args.transcript,
|
|
247
|
+
repo_name: repoInfo.fullName,
|
|
248
|
+
tool: 'claude-code',
|
|
249
|
+
started_at: now,
|
|
250
|
+
ended_at: now,
|
|
251
|
+
};
|
|
85
252
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
253
|
+
// Call the summarize API using the raw method to avoid type mismatch
|
|
254
|
+
aiSummary = await client.summarizeRaw<SummarizeApiRequest, SummarizeApiResponse>(summarizeRequest);
|
|
89
255
|
|
|
90
|
-
|
|
91
|
-
|
|
256
|
+
// Update args with AI-generated content
|
|
257
|
+
summarizedArgs = {
|
|
258
|
+
...args,
|
|
259
|
+
summary: aiSummary.tldr.summary || aiSummary.summary,
|
|
260
|
+
decisions: aiSummary.decisions.map(d => ({
|
|
261
|
+
what: d.what,
|
|
262
|
+
why: d.why,
|
|
263
|
+
})),
|
|
264
|
+
mistakes: aiSummary.tldr.mistakes,
|
|
265
|
+
filesChanged: aiSummary.tldr.files_changed,
|
|
266
|
+
nextSteps: undefined, // Could extract from lessons
|
|
267
|
+
blockers: aiSummary.tldr.status === 'blocked' ? 'Session blocked' : undefined,
|
|
268
|
+
};
|
|
269
|
+
} catch (error) {
|
|
270
|
+
// If summarization fails, fall back to provided summary or placeholder
|
|
271
|
+
console.error('[Recall] AI summarization failed, using fallback:', error);
|
|
272
|
+
if (!args.summary || args.summary.trim().length === 0) {
|
|
273
|
+
summarizedArgs.summary = 'Session saved (AI summarization unavailable)';
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
} else if (!args.summary || args.summary.trim().length === 0) {
|
|
277
|
+
// No transcript and no summary - this shouldn't happen due to validation
|
|
278
|
+
summarizedArgs.summary = 'Session saved';
|
|
92
279
|
}
|
|
93
280
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
281
|
+
// Build the session content to encrypt
|
|
282
|
+
const sessionContent = buildSessionContent(summarizedArgs);
|
|
97
283
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
284
|
+
// Encrypt the content with team key
|
|
285
|
+
const encryptedContent = encryptForApi(sessionContent, teamKey);
|
|
101
286
|
|
|
102
|
-
//
|
|
103
|
-
const
|
|
287
|
+
// Build TLDR metadata (use summarized args if available)
|
|
288
|
+
const tldr = buildTldr(summarizedArgs);
|
|
104
289
|
|
|
105
|
-
//
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
290
|
+
// Create timestamps (session is "now")
|
|
291
|
+
const now = new Date().toISOString();
|
|
292
|
+
|
|
293
|
+
// Build the API request body (matches CreateSessionRequestBody in API)
|
|
294
|
+
const requestBody = {
|
|
295
|
+
encrypted_content: encryptedContent,
|
|
296
|
+
tldr,
|
|
297
|
+
started_at: now,
|
|
298
|
+
ended_at: now,
|
|
299
|
+
tool: 'claude-code' as const,
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Save session via API
|
|
303
|
+
const response = await client.saveSessionV3(repoId, requestBody);
|
|
112
304
|
|
|
113
|
-
|
|
114
|
-
|
|
305
|
+
// Build a clean, informative success message
|
|
306
|
+
const parts: string[] = [];
|
|
307
|
+
parts.push(`Session saved to ${repoInfo.fullName}`);
|
|
308
|
+
if (aiSummary) {
|
|
309
|
+
parts.push(` (AI summarized)`);
|
|
115
310
|
}
|
|
311
|
+
parts.push(``);
|
|
312
|
+
parts.push(` Saved:`);
|
|
313
|
+
|
|
314
|
+
// Summary (truncate if too long)
|
|
315
|
+
const summaryText = summarizedArgs.summary || 'Session saved';
|
|
316
|
+
const summaryPreview = summaryText.trim().length > 80
|
|
317
|
+
? summaryText.trim().substring(0, 77) + '...'
|
|
318
|
+
: summaryText.trim();
|
|
319
|
+
parts.push(` - Summary: ${summaryPreview}`);
|
|
116
320
|
|
|
117
|
-
|
|
118
|
-
|
|
321
|
+
// Key decisions (show first one if any)
|
|
322
|
+
if (summarizedArgs.decisions && summarizedArgs.decisions.length > 0) {
|
|
323
|
+
const firstDecision = summarizedArgs.decisions[0].what;
|
|
324
|
+
if (summarizedArgs.decisions.length === 1) {
|
|
325
|
+
parts.push(` - Decision: ${firstDecision}`);
|
|
326
|
+
} else {
|
|
327
|
+
parts.push(` - Decisions: ${firstDecision} (+${summarizedArgs.decisions.length - 1} more)`);
|
|
328
|
+
}
|
|
119
329
|
}
|
|
120
330
|
|
|
121
|
-
|
|
122
|
-
|
|
331
|
+
// Blockers (if any)
|
|
332
|
+
if (summarizedArgs.blockers) {
|
|
333
|
+
const blockerPreview = summarizedArgs.blockers.trim().length > 60
|
|
334
|
+
? summarizedArgs.blockers.trim().substring(0, 57) + '...'
|
|
335
|
+
: summarizedArgs.blockers.trim();
|
|
336
|
+
parts.push(` - Blocker: ${blockerPreview}`);
|
|
123
337
|
}
|
|
124
338
|
|
|
125
|
-
if
|
|
126
|
-
|
|
339
|
+
// Next steps (if any)
|
|
340
|
+
if (summarizedArgs.nextSteps) {
|
|
341
|
+
const nextPreview = summarizedArgs.nextSteps.trim().length > 60
|
|
342
|
+
? summarizedArgs.nextSteps.trim().substring(0, 57) + '...'
|
|
343
|
+
: summarizedArgs.nextSteps.trim();
|
|
344
|
+
parts.push(` - Next: ${nextPreview}`);
|
|
127
345
|
}
|
|
128
346
|
|
|
129
|
-
if
|
|
130
|
-
|
|
347
|
+
// Files changed (count only if many)
|
|
348
|
+
if (summarizedArgs.filesChanged && summarizedArgs.filesChanged.length > 0) {
|
|
349
|
+
if (summarizedArgs.filesChanged.length <= 3) {
|
|
350
|
+
parts.push(` - Files: ${summarizedArgs.filesChanged.join(', ')}`);
|
|
351
|
+
} else {
|
|
352
|
+
parts.push(` - Files: ${summarizedArgs.filesChanged.length} files changed`);
|
|
353
|
+
}
|
|
131
354
|
}
|
|
132
355
|
|
|
133
|
-
|
|
134
|
-
parts.push(`
|
|
356
|
+
// Footer
|
|
357
|
+
parts.push(` - Updated team memory for next session`);
|
|
358
|
+
|
|
359
|
+
// Show warnings if any (non-critical issues that didn't prevent save)
|
|
360
|
+
if (response.warnings && response.warnings.length > 0) {
|
|
361
|
+
parts.push(``);
|
|
362
|
+
parts.push(` Warnings:`);
|
|
363
|
+
for (const warning of response.warnings) {
|
|
364
|
+
parts.push(` ⚠️ ${warning}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
135
367
|
|
|
136
368
|
return successResponse(parts.join('\n'));
|
|
137
369
|
|