@recall_v3/mcp-server 0.1.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/dist/api/client.d.ts +111 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +244 -0
- package/dist/config/index.d.ts +89 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +256 -0
- package/dist/crypto/index.d.ts +56 -0
- package/dist/crypto/index.d.ts.map +1 -0
- package/dist/crypto/index.js +224 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +189 -0
- package/dist/tools/getContext.d.ts +18 -0
- package/dist/tools/getContext.d.ts.map +1 -0
- package/dist/tools/getContext.js +87 -0
- package/dist/tools/getHistory.d.ts +18 -0
- package/dist/tools/getHistory.d.ts.map +1 -0
- package/dist/tools/getHistory.js +97 -0
- package/dist/tools/getTranscripts.d.ts +19 -0
- package/dist/tools/getTranscripts.d.ts.map +1 -0
- package/dist/tools/getTranscripts.js +129 -0
- package/dist/tools/index.d.ts +13 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +37 -0
- package/dist/tools/logDecision.d.ts +19 -0
- package/dist/tools/logDecision.d.ts.map +1 -0
- package/dist/tools/logDecision.js +92 -0
- package/dist/tools/saveSession.d.ts +26 -0
- package/dist/tools/saveSession.d.ts.map +1 -0
- package/dist/tools/saveSession.js +115 -0
- package/dist/tools/types.d.ts +32 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +33 -0
- package/dist/tools/utils.d.ts +52 -0
- package/dist/tools/utils.d.ts.map +1 -0
- package/dist/tools/utils.js +238 -0
- package/package.json +46 -0
- package/src/api/client.ts +295 -0
- package/src/config/index.ts +247 -0
- package/src/crypto/index.ts +207 -0
- package/src/index.ts +232 -0
- package/src/tools/getContext.ts +106 -0
- package/src/tools/getHistory.ts +118 -0
- package/src/tools/getTranscripts.ts +150 -0
- package/src/tools/index.ts +13 -0
- package/src/tools/logDecision.ts +118 -0
- package/src/tools/saveSession.ts +159 -0
- package/src/tools/types.ts +47 -0
- package/src/tools/utils.ts +226 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Recall Configuration Management
|
|
4
|
+
*
|
|
5
|
+
* Manages the local config file at ~/.recall/config.json.
|
|
6
|
+
* Handles secure storage of API tokens and team keys.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.getConfigDir = getConfigDir;
|
|
43
|
+
exports.getConfigPath = getConfigPath;
|
|
44
|
+
exports.loadConfig = loadConfig;
|
|
45
|
+
exports.saveConfig = saveConfig;
|
|
46
|
+
exports.getApiBaseUrl = getApiBaseUrl;
|
|
47
|
+
exports.getApiToken = getApiToken;
|
|
48
|
+
exports.setApiToken = setApiToken;
|
|
49
|
+
exports.getActiveTeamId = getActiveTeamId;
|
|
50
|
+
exports.setActiveTeamId = setActiveTeamId;
|
|
51
|
+
exports.setDefaultTeamId = setDefaultTeamId;
|
|
52
|
+
exports.getTeamKey = getTeamKey;
|
|
53
|
+
exports.setTeamKey = setTeamKey;
|
|
54
|
+
exports.setUserInfo = setUserInfo;
|
|
55
|
+
exports.setLastSyncAt = setLastSyncAt;
|
|
56
|
+
exports.isAuthenticated = isAuthenticated;
|
|
57
|
+
exports.clearAuth = clearAuth;
|
|
58
|
+
exports.getExtendedConfig = getExtendedConfig;
|
|
59
|
+
const fs = __importStar(require("node:fs"));
|
|
60
|
+
const path = __importStar(require("node:path"));
|
|
61
|
+
const os = __importStar(require("node:os"));
|
|
62
|
+
// Current config file version
|
|
63
|
+
const CONFIG_VERSION = 1;
|
|
64
|
+
// Default API base URL (v3)
|
|
65
|
+
const DEFAULT_API_BASE_URL = 'https://api-v3.recall.team';
|
|
66
|
+
/**
|
|
67
|
+
* Get the path to the Recall config directory
|
|
68
|
+
*/
|
|
69
|
+
function getConfigDir() {
|
|
70
|
+
return path.join(os.homedir(), '.recall');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get the path to the config file
|
|
74
|
+
*/
|
|
75
|
+
function getConfigPath() {
|
|
76
|
+
return path.join(getConfigDir(), 'config.json');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Ensure the config directory exists with secure permissions
|
|
80
|
+
*/
|
|
81
|
+
function ensureConfigDir() {
|
|
82
|
+
const configDir = getConfigDir();
|
|
83
|
+
if (!fs.existsSync(configDir)) {
|
|
84
|
+
fs.mkdirSync(configDir, { mode: 0o700, recursive: true });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Create a default empty config
|
|
89
|
+
*/
|
|
90
|
+
function createDefaultConfig() {
|
|
91
|
+
return {
|
|
92
|
+
token: '',
|
|
93
|
+
defaultTeamId: null,
|
|
94
|
+
activeTeamId: null,
|
|
95
|
+
teamKeys: {},
|
|
96
|
+
user: null,
|
|
97
|
+
lastSyncAt: null,
|
|
98
|
+
version: CONFIG_VERSION,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Migrate old config format to new if necessary
|
|
103
|
+
*/
|
|
104
|
+
function migrateConfig(config) {
|
|
105
|
+
const migrated = { ...createDefaultConfig(), ...config };
|
|
106
|
+
// Future migration logic would go here
|
|
107
|
+
// For now, just ensure version is set
|
|
108
|
+
migrated.version = CONFIG_VERSION;
|
|
109
|
+
return migrated;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Load configuration from disk
|
|
113
|
+
* Returns a default config if the file doesn't exist
|
|
114
|
+
*/
|
|
115
|
+
function loadConfig() {
|
|
116
|
+
const configPath = getConfigPath();
|
|
117
|
+
if (!fs.existsSync(configPath)) {
|
|
118
|
+
return createDefaultConfig();
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const raw = fs.readFileSync(configPath, 'utf-8');
|
|
122
|
+
const parsed = JSON.parse(raw);
|
|
123
|
+
// Migrate if necessary
|
|
124
|
+
if (!parsed.version || parsed.version < CONFIG_VERSION) {
|
|
125
|
+
const migrated = migrateConfig(parsed);
|
|
126
|
+
saveConfig(migrated);
|
|
127
|
+
return migrated;
|
|
128
|
+
}
|
|
129
|
+
return migrateConfig(parsed);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
// If config is corrupted, return default
|
|
133
|
+
console.error('Warning: Could not load Recall config, using defaults');
|
|
134
|
+
return createDefaultConfig();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Save configuration to disk with secure permissions (0600)
|
|
139
|
+
*/
|
|
140
|
+
function saveConfig(config) {
|
|
141
|
+
ensureConfigDir();
|
|
142
|
+
const current = loadConfig();
|
|
143
|
+
const merged = {
|
|
144
|
+
...current,
|
|
145
|
+
...config,
|
|
146
|
+
version: CONFIG_VERSION,
|
|
147
|
+
};
|
|
148
|
+
const configPath = getConfigPath();
|
|
149
|
+
const json = JSON.stringify(merged, null, 2);
|
|
150
|
+
// Write with secure permissions (0600 = owner read/write only)
|
|
151
|
+
fs.writeFileSync(configPath, json, { mode: 0o600 });
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get the API base URL from config or environment
|
|
155
|
+
*/
|
|
156
|
+
function getApiBaseUrl() {
|
|
157
|
+
// Environment variable takes precedence (for development)
|
|
158
|
+
const envUrl = process.env.RECALL_API_URL;
|
|
159
|
+
if (envUrl) {
|
|
160
|
+
return envUrl;
|
|
161
|
+
}
|
|
162
|
+
return DEFAULT_API_BASE_URL;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Get the API token from config or environment
|
|
166
|
+
*/
|
|
167
|
+
function getApiToken() {
|
|
168
|
+
// Environment variable takes precedence
|
|
169
|
+
const envToken = process.env.RECALL_API_TOKEN;
|
|
170
|
+
if (envToken) {
|
|
171
|
+
return envToken;
|
|
172
|
+
}
|
|
173
|
+
const config = loadConfig();
|
|
174
|
+
return config.token || null;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Store the API token securely
|
|
178
|
+
*/
|
|
179
|
+
function setApiToken(token) {
|
|
180
|
+
saveConfig({ token });
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get the active team ID
|
|
184
|
+
*/
|
|
185
|
+
function getActiveTeamId() {
|
|
186
|
+
const config = loadConfig();
|
|
187
|
+
return config.activeTeamId ?? config.defaultTeamId ?? null;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Set the active team ID for this session
|
|
191
|
+
*/
|
|
192
|
+
function setActiveTeamId(teamId) {
|
|
193
|
+
saveConfig({ activeTeamId: teamId });
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Set the default team ID
|
|
197
|
+
*/
|
|
198
|
+
function setDefaultTeamId(teamId) {
|
|
199
|
+
saveConfig({ defaultTeamId: teamId, activeTeamId: teamId });
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get a cached team encryption key
|
|
203
|
+
*/
|
|
204
|
+
function getTeamKey(teamId) {
|
|
205
|
+
const config = loadConfig();
|
|
206
|
+
return config.teamKeys[teamId] ?? null;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Cache a team encryption key
|
|
210
|
+
*/
|
|
211
|
+
function setTeamKey(teamId, key) {
|
|
212
|
+
const config = loadConfig();
|
|
213
|
+
const teamKeys = { ...config.teamKeys, [teamId]: key };
|
|
214
|
+
saveConfig({ teamKeys });
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Update cached user info
|
|
218
|
+
*/
|
|
219
|
+
function setUserInfo(user) {
|
|
220
|
+
saveConfig({ user });
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Update last sync timestamp
|
|
224
|
+
*/
|
|
225
|
+
function setLastSyncAt(timestamp) {
|
|
226
|
+
saveConfig({ lastSyncAt: timestamp });
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Check if the user is authenticated (has a token)
|
|
230
|
+
*/
|
|
231
|
+
function isAuthenticated() {
|
|
232
|
+
return !!getApiToken();
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Clear all authentication data (logout)
|
|
236
|
+
*/
|
|
237
|
+
function clearAuth() {
|
|
238
|
+
saveConfig({
|
|
239
|
+
token: '',
|
|
240
|
+
activeTeamId: null,
|
|
241
|
+
teamKeys: {},
|
|
242
|
+
user: null,
|
|
243
|
+
lastSyncAt: null,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get the full extended config
|
|
248
|
+
*/
|
|
249
|
+
function getExtendedConfig() {
|
|
250
|
+
const config = loadConfig();
|
|
251
|
+
return {
|
|
252
|
+
...config,
|
|
253
|
+
apiBaseUrl: getApiBaseUrl(),
|
|
254
|
+
isAuthenticated: isAuthenticated(),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall Encryption Utilities
|
|
3
|
+
*
|
|
4
|
+
* AES-256-GCM encryption/decryption compatible with Web Crypto API.
|
|
5
|
+
* Uses Node.js crypto module for server-side operations.
|
|
6
|
+
*/
|
|
7
|
+
import type { EncryptedPayload } from '@recall_v3/shared';
|
|
8
|
+
/**
|
|
9
|
+
* Generate a cryptographically secure random IV
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateIV(): Uint8Array;
|
|
12
|
+
/**
|
|
13
|
+
* Generate a new AES-256 encryption key
|
|
14
|
+
* Returns base64-encoded key
|
|
15
|
+
*/
|
|
16
|
+
export declare function generateKey(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Encrypt plaintext using AES-256-GCM
|
|
19
|
+
*
|
|
20
|
+
* @param plaintext - The string to encrypt
|
|
21
|
+
* @param keyBase64 - Base64-encoded 256-bit key
|
|
22
|
+
* @returns EncryptedPayload with base64-encoded ciphertext, iv, and tag
|
|
23
|
+
*/
|
|
24
|
+
export declare function encrypt(plaintext: string, keyBase64: string): EncryptedPayload;
|
|
25
|
+
/**
|
|
26
|
+
* Decrypt an encrypted payload using AES-256-GCM
|
|
27
|
+
*
|
|
28
|
+
* @param payload - EncryptedPayload with base64-encoded values
|
|
29
|
+
* @param keyBase64 - Base64-encoded 256-bit key
|
|
30
|
+
* @returns Decrypted plaintext string
|
|
31
|
+
*/
|
|
32
|
+
export declare function decrypt(payload: EncryptedPayload, keyBase64: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Parse an encrypted string into an EncryptedPayload
|
|
35
|
+
* The format is: base64(iv):base64(tag):base64(ciphertext)
|
|
36
|
+
* This is an alternative compact format for storage
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseEncryptedString(encrypted: string): EncryptedPayload;
|
|
39
|
+
/**
|
|
40
|
+
* Serialize an EncryptedPayload to a compact string
|
|
41
|
+
* Format: base64(iv):base64(tag):base64(ciphertext)
|
|
42
|
+
*/
|
|
43
|
+
export declare function serializeEncrypted(payload: EncryptedPayload): string;
|
|
44
|
+
/**
|
|
45
|
+
* Check if a string looks like an encrypted payload
|
|
46
|
+
*/
|
|
47
|
+
export declare function isEncryptedString(str: string): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Decrypt content that might be in either format (JSON or compact string)
|
|
50
|
+
*/
|
|
51
|
+
export declare function decryptContent(encrypted: string, keyBase64: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Encrypt content and return as JSON string
|
|
54
|
+
*/
|
|
55
|
+
export declare function encryptContent(plaintext: string, keyBase64: string): string;
|
|
56
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/crypto/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAO1D;;GAEG;AACH,wBAAgB,UAAU,IAAI,UAAU,CAEvC;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAGpC;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,gBAAgB,CA+B9E;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CA0C5E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAWxE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAEpE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CA8BtD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAY3E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAG3E"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Recall Encryption Utilities
|
|
4
|
+
*
|
|
5
|
+
* AES-256-GCM encryption/decryption compatible with Web Crypto API.
|
|
6
|
+
* Uses Node.js crypto module for server-side operations.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.generateIV = generateIV;
|
|
43
|
+
exports.generateKey = generateKey;
|
|
44
|
+
exports.encrypt = encrypt;
|
|
45
|
+
exports.decrypt = decrypt;
|
|
46
|
+
exports.parseEncryptedString = parseEncryptedString;
|
|
47
|
+
exports.serializeEncrypted = serializeEncrypted;
|
|
48
|
+
exports.isEncryptedString = isEncryptedString;
|
|
49
|
+
exports.decryptContent = decryptContent;
|
|
50
|
+
exports.encryptContent = encryptContent;
|
|
51
|
+
const crypto = __importStar(require("node:crypto"));
|
|
52
|
+
// AES-256-GCM constants
|
|
53
|
+
const ALGORITHM = 'aes-256-gcm';
|
|
54
|
+
const IV_LENGTH = 12; // 96 bits for GCM
|
|
55
|
+
const TAG_LENGTH = 16; // 128 bits for GCM auth tag
|
|
56
|
+
/**
|
|
57
|
+
* Generate a cryptographically secure random IV
|
|
58
|
+
*/
|
|
59
|
+
function generateIV() {
|
|
60
|
+
return crypto.randomBytes(IV_LENGTH);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Generate a new AES-256 encryption key
|
|
64
|
+
* Returns base64-encoded key
|
|
65
|
+
*/
|
|
66
|
+
function generateKey() {
|
|
67
|
+
const key = crypto.randomBytes(32); // 256 bits
|
|
68
|
+
return key.toString('base64');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Encrypt plaintext using AES-256-GCM
|
|
72
|
+
*
|
|
73
|
+
* @param plaintext - The string to encrypt
|
|
74
|
+
* @param keyBase64 - Base64-encoded 256-bit key
|
|
75
|
+
* @returns EncryptedPayload with base64-encoded ciphertext, iv, and tag
|
|
76
|
+
*/
|
|
77
|
+
function encrypt(plaintext, keyBase64) {
|
|
78
|
+
// Decode the key
|
|
79
|
+
const key = Buffer.from(keyBase64, 'base64');
|
|
80
|
+
if (key.length !== 32) {
|
|
81
|
+
throw new Error(`Invalid key length: expected 32 bytes, got ${key.length}`);
|
|
82
|
+
}
|
|
83
|
+
// Generate a random IV
|
|
84
|
+
const iv = generateIV();
|
|
85
|
+
// Create cipher
|
|
86
|
+
const cipher = crypto.createCipheriv(ALGORITHM, key, iv, {
|
|
87
|
+
authTagLength: TAG_LENGTH,
|
|
88
|
+
});
|
|
89
|
+
// Encrypt
|
|
90
|
+
const encrypted = Buffer.concat([
|
|
91
|
+
cipher.update(plaintext, 'utf8'),
|
|
92
|
+
cipher.final(),
|
|
93
|
+
]);
|
|
94
|
+
// Get auth tag
|
|
95
|
+
const tag = cipher.getAuthTag();
|
|
96
|
+
// Return base64-encoded values
|
|
97
|
+
return {
|
|
98
|
+
ciphertext: encrypted.toString('base64'),
|
|
99
|
+
iv: Buffer.from(iv).toString('base64'),
|
|
100
|
+
tag: tag.toString('base64'),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Decrypt an encrypted payload using AES-256-GCM
|
|
105
|
+
*
|
|
106
|
+
* @param payload - EncryptedPayload with base64-encoded values
|
|
107
|
+
* @param keyBase64 - Base64-encoded 256-bit key
|
|
108
|
+
* @returns Decrypted plaintext string
|
|
109
|
+
*/
|
|
110
|
+
function decrypt(payload, keyBase64) {
|
|
111
|
+
// Decode all components
|
|
112
|
+
const key = Buffer.from(keyBase64, 'base64');
|
|
113
|
+
const iv = Buffer.from(payload.iv, 'base64');
|
|
114
|
+
const ciphertext = Buffer.from(payload.ciphertext, 'base64');
|
|
115
|
+
const tag = Buffer.from(payload.tag, 'base64');
|
|
116
|
+
if (key.length !== 32) {
|
|
117
|
+
throw new Error(`Invalid key length: expected 32 bytes, got ${key.length}`);
|
|
118
|
+
}
|
|
119
|
+
if (iv.length !== IV_LENGTH) {
|
|
120
|
+
throw new Error(`Invalid IV length: expected ${IV_LENGTH} bytes, got ${iv.length}`);
|
|
121
|
+
}
|
|
122
|
+
if (tag.length !== TAG_LENGTH) {
|
|
123
|
+
throw new Error(`Invalid tag length: expected ${TAG_LENGTH} bytes, got ${tag.length}`);
|
|
124
|
+
}
|
|
125
|
+
// Create decipher
|
|
126
|
+
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, {
|
|
127
|
+
authTagLength: TAG_LENGTH,
|
|
128
|
+
});
|
|
129
|
+
// Set auth tag
|
|
130
|
+
decipher.setAuthTag(tag);
|
|
131
|
+
// Decrypt
|
|
132
|
+
try {
|
|
133
|
+
const decrypted = Buffer.concat([
|
|
134
|
+
decipher.update(ciphertext),
|
|
135
|
+
decipher.final(),
|
|
136
|
+
]);
|
|
137
|
+
return decrypted.toString('utf8');
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
// GCM authentication failure
|
|
141
|
+
if (error instanceof Error && error.message.includes('Unsupported state')) {
|
|
142
|
+
throw new Error('Decryption failed: authentication tag mismatch');
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Parse an encrypted string into an EncryptedPayload
|
|
149
|
+
* The format is: base64(iv):base64(tag):base64(ciphertext)
|
|
150
|
+
* This is an alternative compact format for storage
|
|
151
|
+
*/
|
|
152
|
+
function parseEncryptedString(encrypted) {
|
|
153
|
+
const parts = encrypted.split(':');
|
|
154
|
+
if (parts.length !== 3) {
|
|
155
|
+
throw new Error('Invalid encrypted string format');
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
iv: parts[0],
|
|
159
|
+
tag: parts[1],
|
|
160
|
+
ciphertext: parts[2],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Serialize an EncryptedPayload to a compact string
|
|
165
|
+
* Format: base64(iv):base64(tag):base64(ciphertext)
|
|
166
|
+
*/
|
|
167
|
+
function serializeEncrypted(payload) {
|
|
168
|
+
return `${payload.iv}:${payload.tag}:${payload.ciphertext}`;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Check if a string looks like an encrypted payload
|
|
172
|
+
*/
|
|
173
|
+
function isEncryptedString(str) {
|
|
174
|
+
// Check for compact format (iv:tag:ciphertext)
|
|
175
|
+
if (str.includes(':')) {
|
|
176
|
+
const parts = str.split(':');
|
|
177
|
+
if (parts.length === 3) {
|
|
178
|
+
// Each part should be valid base64
|
|
179
|
+
return parts.every(part => {
|
|
180
|
+
try {
|
|
181
|
+
Buffer.from(part, 'base64');
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Check for JSON format
|
|
191
|
+
try {
|
|
192
|
+
const parsed = JSON.parse(str);
|
|
193
|
+
return (typeof parsed === 'object' &&
|
|
194
|
+
parsed !== null &&
|
|
195
|
+
'ciphertext' in parsed &&
|
|
196
|
+
'iv' in parsed &&
|
|
197
|
+
'tag' in parsed);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Decrypt content that might be in either format (JSON or compact string)
|
|
205
|
+
*/
|
|
206
|
+
function decryptContent(encrypted, keyBase64) {
|
|
207
|
+
let payload;
|
|
208
|
+
// Try JSON format first
|
|
209
|
+
try {
|
|
210
|
+
payload = JSON.parse(encrypted);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Try compact string format
|
|
214
|
+
payload = parseEncryptedString(encrypted);
|
|
215
|
+
}
|
|
216
|
+
return decrypt(payload, keyBase64);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Encrypt content and return as JSON string
|
|
220
|
+
*/
|
|
221
|
+
function encryptContent(plaintext, keyBase64) {
|
|
222
|
+
const payload = encrypt(plaintext, keyBase64);
|
|
223
|
+
return JSON.stringify(payload);
|
|
224
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Recall MCP Server v3
|
|
4
|
+
* Local MCP server for AI coding assistants
|
|
5
|
+
*
|
|
6
|
+
* This package provides the local MCP server that connects
|
|
7
|
+
* to the Recall API for team memory synchronization.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG"}
|