delegate-sf-mcp 0.2.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.
Files changed (44) hide show
  1. package/.eslintrc.json +20 -0
  2. package/LICENSE +24 -0
  3. package/README.md +76 -0
  4. package/auth.js +148 -0
  5. package/bin/config-helper.js +51 -0
  6. package/bin/mcp-salesforce.js +12 -0
  7. package/bin/setup.js +266 -0
  8. package/bin/status.js +134 -0
  9. package/docs/README.md +52 -0
  10. package/docs/step1.png +0 -0
  11. package/docs/step2.png +0 -0
  12. package/docs/step3.png +0 -0
  13. package/docs/step4.png +0 -0
  14. package/examples/README.md +35 -0
  15. package/package.json +16 -0
  16. package/scripts/README.md +30 -0
  17. package/src/auth/file-storage.js +447 -0
  18. package/src/auth/oauth.js +417 -0
  19. package/src/auth/token-manager.js +207 -0
  20. package/src/backup/manager.js +949 -0
  21. package/src/index.js +168 -0
  22. package/src/salesforce/client.js +388 -0
  23. package/src/sf-client.js +79 -0
  24. package/src/tools/auth.js +190 -0
  25. package/src/tools/backup.js +486 -0
  26. package/src/tools/create.js +109 -0
  27. package/src/tools/delegate-hygiene.js +268 -0
  28. package/src/tools/delegate-validate.js +212 -0
  29. package/src/tools/delegate-verify.js +143 -0
  30. package/src/tools/delete.js +72 -0
  31. package/src/tools/describe.js +132 -0
  32. package/src/tools/installation-info.js +656 -0
  33. package/src/tools/learn-context.js +1077 -0
  34. package/src/tools/learn.js +351 -0
  35. package/src/tools/query.js +82 -0
  36. package/src/tools/repair-credentials.js +77 -0
  37. package/src/tools/setup.js +120 -0
  38. package/src/tools/time_machine.js +347 -0
  39. package/src/tools/update.js +138 -0
  40. package/src/tools.js +214 -0
  41. package/src/utils/cache.js +120 -0
  42. package/src/utils/debug.js +52 -0
  43. package/src/utils/logger.js +19 -0
  44. package/tokens.json +8 -0
@@ -0,0 +1,190 @@
1
+ import { TokenManager } from '../auth/token-manager.js';
2
+ import { FileStorageManager } from '../auth/file-storage.js';
3
+
4
+ export const reauth = {
5
+ name: 'salesforce_auth',
6
+ description: 'Authenticate with Salesforce. Automatically detects if authentication is needed and handles OAuth flow. Call this tool if any Salesforce operations fail due to authentication issues.',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ force: {
11
+ type: 'boolean',
12
+ description: 'Force re-authentication even if current tokens appear valid',
13
+ default: false
14
+ }
15
+ },
16
+ additionalProperties: false
17
+ }
18
+ };
19
+
20
+ export async function handleReauth(args) {
21
+ const { force = false } = args;
22
+
23
+ try {
24
+ // Get stored credentials
25
+ const fileStorage = new FileStorageManager();
26
+ const credentials = await fileStorage.getCredentials();
27
+
28
+ // Check if we have existing tokens even without credentials
29
+ if (!credentials) {
30
+ const tokens = await fileStorage.getTokens();
31
+ if (tokens && tokens.access_token && tokens.refresh_token) {
32
+ // We have tokens but no credentials, try to use tokens directly
33
+ const tokenManager = new TokenManager(null, null, tokens.instance_url);
34
+ tokenManager.currentTokens = tokens;
35
+
36
+ // Test if tokens are still valid
37
+ const testResult = await tokenManager.testTokens();
38
+ if (testResult.valid) {
39
+ return {
40
+ success: true,
41
+ message: 'Already authenticated with valid tokens',
42
+ details: {
43
+ authenticated: true,
44
+ instanceUrl: tokens.instance_url,
45
+ storedAt: tokens.stored_at
46
+ },
47
+ nextSteps: [
48
+ 'Your MCP Salesforce tools should now work normally',
49
+ 'Tokens are stored securely in your home directory with 600 permissions',
50
+ 'They will auto-refresh before expiration'
51
+ ]
52
+ };
53
+ }
54
+ }
55
+
56
+ return {
57
+ success: false,
58
+ error: 'No Salesforce credentials found. Please run the salesforce_setup tool first to configure your credentials.',
59
+ details: {
60
+ setupRequired: true,
61
+ requiredCredentials: ['clientId', 'clientSecret', 'instanceUrl']
62
+ }
63
+ };
64
+ }
65
+
66
+ const tokenManager = new TokenManager(
67
+ credentials.clientId,
68
+ credentials.clientSecret,
69
+ credentials.instanceUrl
70
+ );
71
+
72
+ // Check current token status if not forcing
73
+ if (!force) {
74
+ await tokenManager.initialize();
75
+ const tokenInfo = tokenManager.getTokenInfo();
76
+
77
+ if (tokenInfo.authenticated) {
78
+ // Test if tokens are actually working
79
+ const testResult = await tokenManager.testTokens();
80
+ if (testResult.valid) {
81
+ return {
82
+ success: true,
83
+ message: 'Current tokens are still valid. Use force=true to re-authenticate anyway.',
84
+ tokenInfo: {
85
+ authenticated: true,
86
+ expiresInMinutes: tokenInfo.expires_in_minutes,
87
+ instanceUrl: tokenInfo.instance_url
88
+ }
89
+ };
90
+ }
91
+ }
92
+ }
93
+
94
+ // Clear existing tokens if forcing or if they're invalid
95
+ await tokenManager.clearTokens();
96
+
97
+ // Start OAuth flow
98
+ const tokens = await tokenManager.authenticateWithOAuth();
99
+
100
+ // Verify the new tokens work
101
+ const testResult = await tokenManager.testTokens();
102
+
103
+ if (!testResult.valid) {
104
+ return {
105
+ success: false,
106
+ error: 'Authentication completed but token validation failed',
107
+ details: testResult
108
+ };
109
+ }
110
+
111
+ const tokenInfo = tokenManager.getTokenInfo();
112
+
113
+ return {
114
+ success: true,
115
+ message: 'Successfully authenticated with Salesforce! New tokens have been stored securely.',
116
+ authenticationFlow: 'OAuth completed successfully',
117
+ tokenInfo: {
118
+ authenticated: true,
119
+ instanceUrl: tokenInfo.instance_url,
120
+ expiresInMinutes: tokenInfo.expires_in_minutes,
121
+ storedAt: tokenInfo.stored_at
122
+ },
123
+ nextSteps: [
124
+ 'Your MCP Salesforce tools should now work normally',
125
+ 'Tokens are stored securely in your home directory with 600 permissions',
126
+ 'They will auto-refresh before expiration'
127
+ ]
128
+ };
129
+
130
+ } catch (error) {
131
+ // Handle specific error types
132
+ if (error.message.includes('OAuth flow timed out')) {
133
+ return {
134
+ success: false,
135
+ error: 'Authentication timed out after 5 minutes',
136
+ details: {
137
+ reason: 'User did not complete OAuth flow in browser',
138
+ suggestion: 'Please try again and complete the authentication in your browser quickly'
139
+ }
140
+ };
141
+ }
142
+
143
+ if (error.message.includes('User denied')) {
144
+ return {
145
+ success: false,
146
+ error: 'Authentication was denied by user',
147
+ details: {
148
+ reason: 'User cancelled or denied the OAuth authorization',
149
+ suggestion: 'Please try again and accept the OAuth authorization'
150
+ }
151
+ };
152
+ }
153
+
154
+ if (error.message.includes('invalid_client')) {
155
+ return {
156
+ success: false,
157
+ error: 'Invalid Salesforce Connected App credentials',
158
+ details: {
159
+ reason: 'Client ID or Client Secret is incorrect',
160
+ suggestion: 'Please verify your Client ID and Client Secret in ~/.mcp-salesforce.json configuration'
161
+ }
162
+ };
163
+ }
164
+
165
+ if (error.message.includes('EADDRINUSE')) {
166
+ return {
167
+ success: false,
168
+ error: 'Cannot start OAuth callback server - port conflict',
169
+ details: {
170
+ reason: 'OAuth callback port is already in use',
171
+ suggestion: 'Please close any other applications using ports 8080-9000 and try again'
172
+ }
173
+ };
174
+ }
175
+
176
+ return {
177
+ success: false,
178
+ error: `Authentication failed: ${error.message}`,
179
+ details: {
180
+ errorType: error.constructor.name,
181
+ troubleshooting: [
182
+ 'Verify your Salesforce Connected App configuration',
183
+ 'Check that callback URLs include http://localhost:8080/callback',
184
+ 'Ensure your Salesforce credentials are correct in MCP config',
185
+ 'Try running with force=true parameter'
186
+ ]
187
+ }
188
+ };
189
+ }
190
+ }
@@ -0,0 +1,486 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { SalesforceBackupManager } from '../backup/manager.js';
5
+ import { debug as logger } from '../utils/debug.js';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
+ /**
11
+ * Salesforce Backup MCP Tool
12
+ *
13
+ * Creates comprehensive backups of Salesforce data including all file attachments
14
+ * from ContentDocument/ContentVersion, Attachments, and Documents
15
+ */
16
+ export async function handleSalesforceBackup(args, client) {
17
+ const {
18
+ backup_type = 'incremental',
19
+ include_files = true,
20
+ include_attachments = true,
21
+ include_documents = true,
22
+ objects_filter = [],
23
+ since_date = null,
24
+ compression = false,
25
+ parallel_downloads = 5
26
+ } = args;
27
+
28
+ // Resolve backup directory relative to project root, not current working directory
29
+ const projectRoot = path.resolve(__dirname, '../..');
30
+ const output_directory = path.join(projectRoot, 'backups');
31
+
32
+ try {
33
+ // Ensure backup directory exists
34
+ await fs.mkdir(output_directory, { recursive: true });
35
+
36
+ logger.log(`đŸ—„ī¸ Starting Salesforce ${backup_type} backup...`);
37
+
38
+ // Validate parameters
39
+ const validBackupTypes = ['full', 'incremental', 'files_only'];
40
+ if (!validBackupTypes.includes(backup_type)) {
41
+ throw new Error(`Invalid backup_type. Must be one of: ${validBackupTypes.join(', ')}`);
42
+ }
43
+
44
+ if (parallel_downloads < 1 || parallel_downloads > 10) {
45
+ throw new Error('parallel_downloads must be between 1 and 10');
46
+ }
47
+
48
+ // Parse since_date if provided
49
+ let parsedSinceDate = null;
50
+ if (since_date) {
51
+ try {
52
+ parsedSinceDate = new Date(since_date).toISOString();
53
+ } catch (error) {
54
+ throw new Error(`Invalid since_date format. Use ISO format: YYYY-MM-DDTHH:mm:ss.sssZ`);
55
+ }
56
+ }
57
+
58
+ // Create backup manager with options
59
+ const backupManager = new SalesforceBackupManager(client, {
60
+ outputDirectory: output_directory,
61
+ includeFiles: include_files,
62
+ includeAttachments: include_attachments,
63
+ includeDocuments: include_documents,
64
+ compression: compression,
65
+ parallelDownloads: parallel_downloads,
66
+ objectsFilter: objects_filter
67
+ });
68
+
69
+ // Execute async backup - returns immediately
70
+ const jobResult = await backupManager.startAsyncBackup(backup_type, parsedSinceDate);
71
+
72
+ // Return immediate response with job information
73
+ let successMessage = `🚀 **Salesforce ${backup_type} backup started successfully!**\n\n`;
74
+ successMessage += `📋 **Job ID**: \`${jobResult.jobId}\`\n`;
75
+ successMessage += `📁 **Backup Location**: \`${jobResult.backupDirectory}\`\n`;
76
+ successMessage += `⚡ **Status**: ${jobResult.status} - Running in background\n\n`;
77
+ successMessage += `â„šī¸ **Monitor Progress**: Use the \`salesforce_backup_status\` tool to check job progress\n`;
78
+ successMessage += `📊 **View All Jobs**: Use \`salesforce_backup_status\` without parameters to see all jobs\n\n`;
79
+
80
+ successMessage += `🔄 **What happens next?**\n`;
81
+ successMessage += `- The backup is now running in the background\n`;
82
+ successMessage += `- You can continue using other tools immediately\n`;
83
+ successMessage += `- Check progress anytime with \`salesforce_backup_status\`\n`;
84
+ successMessage += `- You'll find the completed backup in the specified directory\n\n`;
85
+
86
+ successMessage += `📁 **Expected Directory Structure**:\n`;
87
+ successMessage += `\`\`\`\n`;
88
+ successMessage += `${path.basename(jobResult.backupDirectory)}/\n`;
89
+ successMessage += `├── metadata/ # Schemas and manifest\n`;
90
+ successMessage += `├── data/ # Object records (JSON)\n`;
91
+ successMessage += `├── files/\n`;
92
+ successMessage += `│ ├── content-versions/ # Modern files\n`;
93
+ successMessage += `│ ├── attachments/ # Legacy attachments\n`;
94
+ successMessage += `│ └── documents/ # Document objects\n`;
95
+ successMessage += `└── logs/ # Backup logs\n`;
96
+ successMessage += `\`\`\`\n\n`;
97
+
98
+ // Add next steps suggestions
99
+ successMessage += `🔄 **Next Steps**:\n`;
100
+ successMessage += `- Monitor progress: Use \`salesforce_backup_status ${jobResult.jobId}\` for detailed status\n`;
101
+ successMessage += `- Check all jobs: Use \`salesforce_backup_status\` to see all running/completed jobs\n`;
102
+
103
+ if (backup_type === 'full') {
104
+ successMessage += `- Schedule incremental backups using \`since_date\` parameter\n`;
105
+ }
106
+
107
+ if (compression) {
108
+ successMessage += `- Archive will be created with compression enabled\n`;
109
+ }
110
+
111
+ return {
112
+ content: [
113
+ {
114
+ type: "text",
115
+ text: successMessage
116
+ }
117
+ ]
118
+ };
119
+
120
+ } catch (error) {
121
+ let errorMessage = `❌ **Salesforce backup failed**\n\n`;
122
+ errorMessage += `**Error**: ${error.message}\n\n`;
123
+
124
+ errorMessage += `🔧 **Troubleshooting**:\n`;
125
+
126
+ if (error.message.includes('Authentication')) {
127
+ errorMessage += `- Run \`salesforce_auth\` to refresh your authentication\n`;
128
+ }
129
+
130
+ if (error.message.includes('permission') || error.message.includes('access')) {
131
+ errorMessage += `- Check your Salesforce user permissions for objects and files\n`;
132
+ errorMessage += `- Ensure you have \`View All Data\` or appropriate object permissions\n`;
133
+ }
134
+
135
+ if (error.message.includes('API')) {
136
+ errorMessage += `- Check your Salesforce API limits in Setup > System Overview\n`;
137
+ errorMessage += `- Consider reducing \`parallel_downloads\` parameter\n`;
138
+ }
139
+
140
+ if (error.message.includes('since_date')) {
141
+ errorMessage += `- Use ISO date format: \`2024-01-15T10:30:00.000Z\`\n`;
142
+ errorMessage += `- Check that the date is not in the future\n`;
143
+ }
144
+
145
+ errorMessage += `\n💡 **Suggestions**:\n`;
146
+ errorMessage += `- Try a smaller backup first with \`backup_type: "files_only"\`\n`;
147
+ errorMessage += `- Use \`objects_filter\` to backup specific objects only\n`;
148
+ errorMessage += `- Check available disk space\n`;
149
+
150
+ return {
151
+ content: [
152
+ {
153
+ type: "text",
154
+ text: errorMessage
155
+ }
156
+ ]
157
+ };
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Salesforce Backup List MCP Tool
163
+ *
164
+ * Lists available backups and their information
165
+ */
166
+ export async function handleSalesforceBackupList(args) {
167
+ // Resolve backup directory relative to project root, not current working directory
168
+ const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
169
+ const backup_directory = path.join(projectRoot, 'backups');
170
+
171
+ try {
172
+ // Check if backup directory exists
173
+ try {
174
+ await fs.access(backup_directory);
175
+ } catch (error) {
176
+ return {
177
+ content: [
178
+ {
179
+ type: "text",
180
+ text: `📁 No backups found.\n\nCreate your first backup using the \`salesforce_backup\` tool.`
181
+ }
182
+ ]
183
+ };
184
+ }
185
+
186
+ // Read backup directory
187
+ const entries = await fs.readdir(backup_directory, { withFileTypes: true });
188
+ const backupDirs = entries
189
+ .filter(entry => entry.isDirectory() && entry.name.startsWith('salesforce-backup-'))
190
+ .sort((a, b) => b.name.localeCompare(a.name)); // Most recent first
191
+
192
+ if (backupDirs.length === 0) {
193
+ return {
194
+ content: [
195
+ {
196
+ type: "text",
197
+ text: `📁 No Salesforce backups found.\n\nCreate your first backup using the \`salesforce_backup\` tool.`
198
+ }
199
+ ]
200
+ };
201
+ }
202
+
203
+ let response = `📋 **Available Salesforce Backups** (${backupDirs.length} found)\n\n`;
204
+
205
+ // Analyze each backup
206
+ for (const [index, backupDir] of backupDirs.slice(0, 10).entries()) { // Show max 10
207
+ const backupPath = path.join(backup_directory, backupDir.name);
208
+ const manifestPath = path.join(backupPath, 'backup-manifest.json');
209
+
210
+ try {
211
+ const manifestData = await fs.readFile(manifestPath, 'utf-8');
212
+ const manifest = JSON.parse(manifestData);
213
+
214
+ const backupInfo = manifest.backupInfo;
215
+ const stats = manifest.downloadStats || {};
216
+ const totalFiles = (stats.contentVersions || 0) + (stats.attachments || 0) + (stats.documents || 0);
217
+ const sizeMB = Math.round((stats.totalBytes || 0) / (1024 * 1024) * 100) / 100;
218
+
219
+ response += `**${index + 1}. ${backupDir.name}**\n`;
220
+ response += ` 📅 Date: ${new Date(backupInfo.timestamp).toLocaleString()}\n`;
221
+ response += ` âąī¸ Duration: ${backupInfo.duration} seconds\n`;
222
+ response += ` đŸ“Ļ Files: ${totalFiles} (${sizeMB} MB)\n`;
223
+ response += ` đŸĸ Instance: ${backupInfo.salesforceInstance}\n`;
224
+ response += ` 📁 Path: \`${backupPath}\`\n\n`;
225
+
226
+ } catch (error) {
227
+ response += `**${index + 1}. ${backupDir.name}**\n`;
228
+ response += ` âš ī¸ Manifest not found or corrupted\n`;
229
+ response += ` 📁 Path: \`${backupPath}\`\n\n`;
230
+ }
231
+ }
232
+
233
+ if (backupDirs.length > 10) {
234
+ response += `... and ${backupDirs.length - 10} more backups\n\n`;
235
+ }
236
+
237
+ response += `🔧 **Management**:\n`;
238
+ response += `- View backup details: Check \`backup-manifest.json\` in each directory\n`;
239
+ response += `- Clean old backups: Manually delete directories you no longer need\n`;
240
+ response += `- Restore from backup: Use the data files to recreate records\n`;
241
+
242
+ return {
243
+ content: [
244
+ {
245
+ type: "text",
246
+ text: response
247
+ }
248
+ ]
249
+ };
250
+
251
+ } catch (error) {
252
+ return {
253
+ content: [
254
+ {
255
+ type: "text",
256
+ text: `❌ **Failed to list backups**: ${error.message}\n\nPlease check your backup directory access.`
257
+ }
258
+ ]
259
+ };
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Salesforce Backup Status MCP Tool
265
+ *
266
+ * Check the status of backup jobs (running, completed, failed)
267
+ */
268
+ export async function handleSalesforceBackupStatus(args, client) {
269
+ const { job_id = null } = args;
270
+
271
+ // Resolve backup directory relative to project root
272
+ const projectRoot = path.resolve(__dirname, '../..');
273
+ const backupDirectory = path.join(projectRoot, 'backups');
274
+
275
+ try {
276
+ // Ensure backup directory exists
277
+ await fs.mkdir(backupDirectory, { recursive: true });
278
+
279
+ // Create backup manager to access status methods
280
+ const backupManager = new SalesforceBackupManager(client, {
281
+ outputDirectory: backupDirectory
282
+ });
283
+
284
+ if (job_id) {
285
+ // Get status of specific job
286
+ const jobStatus = await backupManager.getBackupJobStatus(job_id);
287
+
288
+ if (!jobStatus) {
289
+ return {
290
+ content: [{
291
+ type: "text",
292
+ text: `❌ **Job not found**: No backup job found with ID \`${job_id}\`\n\nUse \`salesforce_backup_status\` without parameters to see all jobs.`
293
+ }]
294
+ };
295
+ }
296
+
297
+ // Format single job status
298
+ let statusMessage = `📋 **Backup Job Status**\n\n`;
299
+ statusMessage += `🆔 **Job ID**: \`${jobStatus.jobId}\`\n`;
300
+ statusMessage += `⚡ **Status**: ${getStatusEmoji(jobStatus.status)} ${jobStatus.status.toUpperCase()}\n`;
301
+ statusMessage += `📅 **Started**: ${new Date(jobStatus.startTime).toLocaleString()}\n`;
302
+
303
+ if (jobStatus.endTime) {
304
+ statusMessage += `🏁 **Completed**: ${new Date(jobStatus.endTime).toLocaleString()}\n`;
305
+ }
306
+
307
+ statusMessage += `📁 **Backup Directory**: \`${jobStatus.backupDirectory}\`\n`;
308
+ statusMessage += `📊 **Progress**: ${jobStatus.progress}%\n`;
309
+ statusMessage += `đŸ’Ŧ **Message**: ${jobStatus.message}\n`;
310
+
311
+ if (jobStatus.result) {
312
+ const stats = jobStatus.result.stats;
313
+ const totalFiles = stats.contentVersions + stats.attachments + stats.documents;
314
+ const sizeMB = Math.round(stats.totalBytes / (1024 * 1024) * 100) / 100;
315
+
316
+ statusMessage += `\n📊 **Backup Results**:\n`;
317
+ statusMessage += `- âąī¸ **Duration**: ${jobStatus.result.duration} seconds\n`;
318
+ statusMessage += `- 📄 **ContentVersion files**: ${stats.contentVersions}\n`;
319
+ statusMessage += `- 📎 **Attachment files**: ${stats.attachments}\n`;
320
+ statusMessage += `- 📋 **Document files**: ${stats.documents}\n`;
321
+ statusMessage += `- đŸ“Ļ **Total files**: ${totalFiles}\n`;
322
+ statusMessage += `- 💾 **Total size**: ${sizeMB} MB\n`;
323
+
324
+ if (stats.errors > 0) {
325
+ statusMessage += `- âš ī¸ **Errors**: ${stats.errors} failed downloads\n`;
326
+ }
327
+ }
328
+
329
+ if (jobStatus.error) {
330
+ statusMessage += `\n❌ **Error**: ${jobStatus.error}\n`;
331
+ }
332
+
333
+ return {
334
+ content: [{
335
+ type: "text",
336
+ text: statusMessage
337
+ }]
338
+ };
339
+
340
+ } else {
341
+ // Get status of all jobs
342
+ const allJobs = await backupManager.getBackupJobStatuses();
343
+
344
+ if (allJobs.length === 0) {
345
+ return {
346
+ content: [{
347
+ type: "text",
348
+ text: `📋 **No backup jobs found**\n\nNo backup jobs have been started yet. Use the \`salesforce_backup\` tool to start a backup.`
349
+ }]
350
+ };
351
+ }
352
+
353
+ // Format all jobs status
354
+ let statusMessage = `📋 **All Backup Jobs** (${allJobs.length} total)\n\n`;
355
+
356
+ allJobs.forEach((job, index) => {
357
+ statusMessage += `**${index + 1}. ${job.jobId}**\n`;
358
+ statusMessage += ` ${getStatusEmoji(job.status)} Status: ${job.status.toUpperCase()}\n`;
359
+ statusMessage += ` 📅 Started: ${new Date(job.startTime).toLocaleString()}\n`;
360
+ statusMessage += ` 📊 Progress: ${job.progress}%\n`;
361
+ statusMessage += ` đŸ’Ŧ ${job.message}\n`;
362
+
363
+ if (job.endTime) {
364
+ statusMessage += ` 🏁 Completed: ${new Date(job.endTime).toLocaleString()}\n`;
365
+ }
366
+
367
+ if (job.error) {
368
+ statusMessage += ` ❌ Error: ${job.error}\n`;
369
+ }
370
+
371
+ statusMessage += `\n`;
372
+ });
373
+
374
+ statusMessage += `â„šī¸ **Tip**: Use \`salesforce_backup_status\` with a specific \`job_id\` to get detailed information about a single job.`;
375
+
376
+ return {
377
+ content: [{
378
+ type: "text",
379
+ text: statusMessage
380
+ }]
381
+ };
382
+ }
383
+
384
+ } catch (error) {
385
+ logger.log('Backup status error:', error);
386
+
387
+ return {
388
+ content: [{
389
+ type: "text",
390
+ text: `❌ **Error checking backup status**: ${error.message}\n\nPlease check that the backup system is properly configured.`
391
+ }]
392
+ };
393
+ }
394
+ }
395
+
396
+ /**
397
+ * Helper function to get status emoji
398
+ */
399
+ function getStatusEmoji(status) {
400
+ const emojis = {
401
+ 'starting': '🚀',
402
+ 'running': '⚡',
403
+ 'completed': '✅',
404
+ 'failed': '❌'
405
+ };
406
+ return emojis[status] || '❓';
407
+ }
408
+
409
+ export const BACKUP_TOOLS = {
410
+ salesforce_backup: {
411
+ name: "salesforce_backup",
412
+ description: "Start comprehensive backups of Salesforce data including all file attachments. Runs asynchronously in background - returns immediately with job ID. Check progress with salesforce_backup_status.",
413
+ inputSchema: {
414
+ type: "object",
415
+ properties: {
416
+ backup_type: {
417
+ type: "string",
418
+ enum: ["full", "incremental", "files_only"],
419
+ description: "Type of backup to perform: 'full' backs up everything, 'incremental' backs up changes since a date, 'files_only' backs up just attachments",
420
+ default: "incremental"
421
+ },
422
+ include_files: {
423
+ type: "boolean",
424
+ description: "Include modern Files (ContentDocument/ContentVersion) in backup",
425
+ default: true
426
+ },
427
+ include_attachments: {
428
+ type: "boolean",
429
+ description: "Include legacy Attachment files in backup",
430
+ default: true
431
+ },
432
+ include_documents: {
433
+ type: "boolean",
434
+ description: "Include Document object files in backup",
435
+ default: true
436
+ },
437
+ objects_filter: {
438
+ type: "array",
439
+ items: { type: "string" },
440
+ description: "Specific objects to backup (empty array = all objects). Example: ['Account', 'Contact', 'CustomObject__c']",
441
+ default: []
442
+ },
443
+ since_date: {
444
+ type: "string",
445
+ description: "ISO date for incremental backup - only backup records modified after this date. Format: YYYY-MM-DDTHH:mm:ss.sssZ",
446
+ default: null
447
+ },
448
+ compression: {
449
+ type: "boolean",
450
+ description: "Compress backup files with gzip to save space",
451
+ default: false
452
+ },
453
+ parallel_downloads: {
454
+ type: "number",
455
+ description: "Number of parallel file downloads (1-10). Higher values = faster but more API usage",
456
+ default: 5,
457
+ minimum: 1,
458
+ maximum: 10
459
+ }
460
+ }
461
+ }
462
+ },
463
+
464
+ salesforce_backup_list: {
465
+ name: "salesforce_backup_list",
466
+ description: "List all available Salesforce backups with their details including timestamp, duration, file counts, and sizes",
467
+ inputSchema: {
468
+ type: "object",
469
+ properties: {}
470
+ }
471
+ },
472
+
473
+ salesforce_backup_status: {
474
+ name: "salesforce_backup_status",
475
+ description: "Check the status of Salesforce backup jobs. Monitor running, completed, or failed backup operations. Use without parameters to see all jobs, or specify a job_id to get detailed status of a specific backup job.",
476
+ inputSchema: {
477
+ type: "object",
478
+ properties: {
479
+ job_id: {
480
+ type: "string",
481
+ description: "Optional: Specific backup job ID to check status for. If not provided, shows status of all backup jobs."
482
+ }
483
+ }
484
+ }
485
+ }
486
+ };