@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/getContext.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* getContext Tool Implementation
|
|
3
3
|
*
|
|
4
|
-
* Fetches the team brain (context
|
|
4
|
+
* Fetches the team brain (context) for the current repository from Recall v3 API.
|
|
5
5
|
* This is the distilled current state - loaded at every session start.
|
|
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 { decryptFromApi } from '../crypto/index.js';
|
|
11
11
|
import { successResponse, errorResponse, formattedResponse, type ToolResponse } from './types.js';
|
|
12
12
|
import { resolveProjectPath, getRepoInfo } from './utils.js';
|
|
13
13
|
|
|
@@ -15,11 +15,100 @@ export interface GetContextArgs {
|
|
|
15
15
|
projectPath?: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
interface SessionFromApi {
|
|
19
|
+
id: string;
|
|
20
|
+
encrypted_content: string;
|
|
21
|
+
started_at: string;
|
|
22
|
+
ended_at: string;
|
|
23
|
+
status: string;
|
|
24
|
+
tldr_summary: string | null;
|
|
25
|
+
user: {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string | null;
|
|
28
|
+
github_username: string | null;
|
|
29
|
+
avatar_url: string | null;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ContextApiResponse {
|
|
34
|
+
sessions: SessionFromApi[];
|
|
35
|
+
cached_context: string;
|
|
36
|
+
updated_at: string;
|
|
37
|
+
session_count: number;
|
|
38
|
+
tier: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build context markdown from sessions
|
|
43
|
+
*/
|
|
44
|
+
function buildContextFromSessions(
|
|
45
|
+
sessions: SessionFromApi[],
|
|
46
|
+
teamKey: string,
|
|
47
|
+
repoName: string
|
|
48
|
+
): string {
|
|
49
|
+
const parts: string[] = [];
|
|
50
|
+
|
|
51
|
+
parts.push(`# Team Memory - ${repoName}`);
|
|
52
|
+
parts.push('');
|
|
53
|
+
parts.push(`Last updated: ${new Date().toISOString().split('T')[0]}`);
|
|
54
|
+
parts.push('');
|
|
55
|
+
|
|
56
|
+
if (sessions.length === 0) {
|
|
57
|
+
parts.push('No sessions recorded yet. Use `recall save` to save your first session.');
|
|
58
|
+
return parts.join('\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
parts.push('## Recent Sessions');
|
|
62
|
+
parts.push('');
|
|
63
|
+
|
|
64
|
+
// Show last 10 sessions for context
|
|
65
|
+
const recentSessions = sessions.slice(0, 10);
|
|
66
|
+
|
|
67
|
+
for (const session of recentSessions) {
|
|
68
|
+
const date = session.started_at.split('T')[0];
|
|
69
|
+
const developer = session.user.github_username || session.user.name || 'Unknown';
|
|
70
|
+
|
|
71
|
+
parts.push(`### ${date} - ${developer}`);
|
|
72
|
+
parts.push('');
|
|
73
|
+
|
|
74
|
+
// Use TLDR summary for quick context
|
|
75
|
+
if (session.tldr_summary) {
|
|
76
|
+
parts.push(session.tldr_summary);
|
|
77
|
+
parts.push('');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Try to decrypt and include full content if available
|
|
81
|
+
if (session.encrypted_content && session.encrypted_content.startsWith('RECALL_ENCRYPTED:')) {
|
|
82
|
+
try {
|
|
83
|
+
const decrypted = decryptFromApi(session.encrypted_content, teamKey);
|
|
84
|
+
parts.push(decrypted);
|
|
85
|
+
parts.push('');
|
|
86
|
+
} catch (error) {
|
|
87
|
+
// If decryption fails, just use the TLDR
|
|
88
|
+
if (!session.tldr_summary) {
|
|
89
|
+
parts.push('(Session content encrypted)');
|
|
90
|
+
parts.push('');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
parts.push('---');
|
|
96
|
+
parts.push('');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (sessions.length > 10) {
|
|
100
|
+
parts.push(`*${sessions.length - 10} more sessions available. Use recall_get_history for full history.*`);
|
|
101
|
+
parts.push('');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return parts.join('\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
18
107
|
/**
|
|
19
108
|
* Execute the getContext tool
|
|
20
109
|
*
|
|
21
110
|
* @param args - Tool arguments (projectPath is optional)
|
|
22
|
-
* @returns MCP tool response with context
|
|
111
|
+
* @returns MCP tool response with context content
|
|
23
112
|
*/
|
|
24
113
|
export async function getContext(args: GetContextArgs): Promise<ToolResponse> {
|
|
25
114
|
try {
|
|
@@ -61,25 +150,18 @@ export async function getContext(args: GetContextArgs): Promise<ToolResponse> {
|
|
|
61
150
|
}
|
|
62
151
|
|
|
63
152
|
// Fetch context from API
|
|
64
|
-
const response = await client.
|
|
65
|
-
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
} else {
|
|
73
|
-
// Already plaintext (shouldn't happen, but handle gracefully)
|
|
74
|
-
contextMd = response.contextMd;
|
|
75
|
-
}
|
|
76
|
-
} catch (decryptError) {
|
|
77
|
-
// If decryption fails, it might already be plaintext
|
|
78
|
-
contextMd = response.contextMd;
|
|
79
|
-
}
|
|
153
|
+
const response = await client.getContextV3(repoId) as ContextApiResponse;
|
|
154
|
+
|
|
155
|
+
// Build context markdown from sessions
|
|
156
|
+
const contextMd = buildContextFromSessions(
|
|
157
|
+
response.sessions,
|
|
158
|
+
teamKey,
|
|
159
|
+
repoInfo.fullName
|
|
160
|
+
);
|
|
80
161
|
|
|
81
162
|
// Format output with metadata
|
|
82
|
-
const header =
|
|
163
|
+
const header = `# Team Memory (Recall)\n\nThe following is your team's shared context for this repository.\nUse this to understand what has been built, why decisions were made,\nand what mistakes to avoid.\n\n---`;
|
|
164
|
+
|
|
83
165
|
return formattedResponse(header, contextMd);
|
|
84
166
|
|
|
85
167
|
} catch (error) {
|
package/src/tools/getHistory.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* getHistory Tool Implementation
|
|
3
3
|
*
|
|
4
|
-
* Fetches context
|
|
4
|
+
* Fetches context + recent session history for the repository from Recall v3 API.
|
|
5
5
|
* This provides more detail than getContext but uses more tokens.
|
|
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 { decryptFromApi } from '../crypto/index.js';
|
|
11
11
|
import { successResponse, errorResponse, formattedResponse, type ToolResponse } from './types.js';
|
|
12
12
|
import { resolveProjectPath, getRepoInfo } from './utils.js';
|
|
13
13
|
|
|
@@ -15,6 +15,151 @@ export interface GetHistoryArgs {
|
|
|
15
15
|
projectPath: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
interface SessionFromApi {
|
|
19
|
+
id: string;
|
|
20
|
+
encrypted_content: string;
|
|
21
|
+
started_at: string;
|
|
22
|
+
ended_at: string;
|
|
23
|
+
status: string;
|
|
24
|
+
tldr_summary: string | null;
|
|
25
|
+
user: {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string | null;
|
|
28
|
+
github_username: string | null;
|
|
29
|
+
avatar_url: string | null;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface DecisionFromApi {
|
|
34
|
+
id: string;
|
|
35
|
+
title: string;
|
|
36
|
+
encrypted_content: string;
|
|
37
|
+
created_at: string;
|
|
38
|
+
user: {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string | null;
|
|
41
|
+
github_username: string | null;
|
|
42
|
+
avatar_url: string | null;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface MistakeFromApi {
|
|
47
|
+
id: string;
|
|
48
|
+
title: string;
|
|
49
|
+
encrypted_content: string;
|
|
50
|
+
created_at: string;
|
|
51
|
+
user: {
|
|
52
|
+
id: string;
|
|
53
|
+
name: string | null;
|
|
54
|
+
github_username: string | null;
|
|
55
|
+
avatar_url: string | null;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface HistoryApiResponse {
|
|
60
|
+
sessions: SessionFromApi[];
|
|
61
|
+
decisions: DecisionFromApi[];
|
|
62
|
+
mistakes: MistakeFromApi[];
|
|
63
|
+
token_warning?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build history markdown from API response
|
|
68
|
+
*/
|
|
69
|
+
function buildHistoryMarkdown(
|
|
70
|
+
response: HistoryApiResponse,
|
|
71
|
+
teamKey: string,
|
|
72
|
+
repoName: string
|
|
73
|
+
): string {
|
|
74
|
+
const parts: string[] = [];
|
|
75
|
+
|
|
76
|
+
parts.push(`# Team Memory History - ${repoName}`);
|
|
77
|
+
parts.push('');
|
|
78
|
+
parts.push(`Generated: ${new Date().toISOString()}`);
|
|
79
|
+
parts.push('');
|
|
80
|
+
|
|
81
|
+
// Decisions section
|
|
82
|
+
if (response.decisions.length > 0) {
|
|
83
|
+
parts.push('## Key Decisions');
|
|
84
|
+
parts.push('');
|
|
85
|
+
for (const decision of response.decisions) {
|
|
86
|
+
const date = decision.created_at.split('T')[0];
|
|
87
|
+
const author = decision.user.github_username || decision.user.name || 'Unknown';
|
|
88
|
+
parts.push(`### ${date} - ${author}`);
|
|
89
|
+
parts.push('');
|
|
90
|
+
parts.push(`**${decision.title}**`);
|
|
91
|
+
parts.push('');
|
|
92
|
+
|
|
93
|
+
// Try to decrypt full content
|
|
94
|
+
if (decision.encrypted_content && decision.encrypted_content.startsWith('RECALL_ENCRYPTED:')) {
|
|
95
|
+
try {
|
|
96
|
+
const decrypted = decryptFromApi(decision.encrypted_content, teamKey);
|
|
97
|
+
parts.push(decrypted);
|
|
98
|
+
} catch {
|
|
99
|
+
// Use title only
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
parts.push('');
|
|
103
|
+
}
|
|
104
|
+
parts.push('---');
|
|
105
|
+
parts.push('');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Mistakes section
|
|
109
|
+
if (response.mistakes.length > 0) {
|
|
110
|
+
parts.push('## Mistakes & Gotchas');
|
|
111
|
+
parts.push('');
|
|
112
|
+
parts.push('*Things the team learned the hard way - avoid repeating these.*');
|
|
113
|
+
parts.push('');
|
|
114
|
+
for (const mistake of response.mistakes) {
|
|
115
|
+
const date = mistake.created_at.split('T')[0];
|
|
116
|
+
const author = mistake.user.github_username || mistake.user.name || 'Unknown';
|
|
117
|
+
parts.push(`- **${mistake.title}** (${date}, ${author})`);
|
|
118
|
+
}
|
|
119
|
+
parts.push('');
|
|
120
|
+
parts.push('---');
|
|
121
|
+
parts.push('');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Sessions section
|
|
125
|
+
parts.push('## Session History');
|
|
126
|
+
parts.push('');
|
|
127
|
+
|
|
128
|
+
if (response.sessions.length === 0) {
|
|
129
|
+
parts.push('No sessions recorded yet.');
|
|
130
|
+
} else {
|
|
131
|
+
for (const session of response.sessions) {
|
|
132
|
+
const date = session.started_at.split('T')[0];
|
|
133
|
+
const developer = session.user.github_username || session.user.name || 'Unknown';
|
|
134
|
+
|
|
135
|
+
parts.push(`### ${date} - ${developer}`);
|
|
136
|
+
parts.push('');
|
|
137
|
+
|
|
138
|
+
// TLDR summary
|
|
139
|
+
if (session.tldr_summary) {
|
|
140
|
+
parts.push(`**Summary:** ${session.tldr_summary}`);
|
|
141
|
+
parts.push('');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Full content if available
|
|
145
|
+
if (session.encrypted_content && session.encrypted_content.startsWith('RECALL_ENCRYPTED:')) {
|
|
146
|
+
try {
|
|
147
|
+
const decrypted = decryptFromApi(session.encrypted_content, teamKey);
|
|
148
|
+
parts.push(decrypted);
|
|
149
|
+
} catch {
|
|
150
|
+
// Use TLDR only
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
parts.push('');
|
|
155
|
+
parts.push('---');
|
|
156
|
+
parts.push('');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return parts.join('\n');
|
|
161
|
+
}
|
|
162
|
+
|
|
18
163
|
/**
|
|
19
164
|
* Execute the getHistory tool
|
|
20
165
|
*
|
|
@@ -61,38 +206,19 @@ export async function getHistory(args: GetHistoryArgs): Promise<ToolResponse> {
|
|
|
61
206
|
}
|
|
62
207
|
|
|
63
208
|
// Fetch history from API
|
|
64
|
-
const response = await client.
|
|
65
|
-
|
|
66
|
-
// Decrypt content
|
|
67
|
-
let contextMd: string;
|
|
68
|
-
let historyMd: string;
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
// Decrypt context
|
|
72
|
-
if (response.contextMd.startsWith('{') || response.contextMd.includes(':')) {
|
|
73
|
-
contextMd = decryptContent(response.contextMd, teamKey);
|
|
74
|
-
} else {
|
|
75
|
-
contextMd = response.contextMd;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Decrypt history
|
|
79
|
-
if (response.historyMd.startsWith('{') || response.historyMd.includes(':')) {
|
|
80
|
-
historyMd = decryptContent(response.historyMd, teamKey);
|
|
81
|
-
} else {
|
|
82
|
-
historyMd = response.historyMd;
|
|
83
|
-
}
|
|
84
|
-
} catch (decryptError) {
|
|
85
|
-
// If decryption fails, use as-is
|
|
86
|
-
contextMd = response.contextMd;
|
|
87
|
-
historyMd = response.historyMd;
|
|
88
|
-
}
|
|
209
|
+
const response = await client.getHistoryV3(repoId) as HistoryApiResponse;
|
|
89
210
|
|
|
90
|
-
//
|
|
91
|
-
const
|
|
211
|
+
// Build history markdown
|
|
212
|
+
const historyMd = buildHistoryMarkdown(response, teamKey, repoInfo.fullName);
|
|
92
213
|
|
|
93
214
|
// Format output with metadata
|
|
94
|
-
const
|
|
95
|
-
|
|
215
|
+
const sessionCount = response.sessions.length;
|
|
216
|
+
const decisionCount = response.decisions.length;
|
|
217
|
+
const mistakeCount = response.mistakes.length;
|
|
218
|
+
|
|
219
|
+
const header = `# Team Memory (Recall)\n\nRepository: ${repoInfo.fullName}\nSessions: ${sessionCount} | Decisions: ${decisionCount} | Mistakes: ${mistakeCount}\n\n---`;
|
|
220
|
+
|
|
221
|
+
return formattedResponse(header, historyMd);
|
|
96
222
|
|
|
97
223
|
} catch (error) {
|
|
98
224
|
if (error instanceof AuthenticationError) {
|
|
@@ -123,7 +123,7 @@ export async function getTranscripts(args: GetTranscriptsArgs): Promise<ToolResp
|
|
|
123
123
|
].join('\n');
|
|
124
124
|
|
|
125
125
|
// Format output
|
|
126
|
-
const header = `
|
|
126
|
+
const header = `Full session transcripts for: ${repoInfo.fullName}`;
|
|
127
127
|
return formattedResponse(header, combinedContent);
|
|
128
128
|
|
|
129
129
|
} catch (error) {
|