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.
- package/.eslintrc.json +20 -0
- package/LICENSE +24 -0
- package/README.md +76 -0
- package/auth.js +148 -0
- package/bin/config-helper.js +51 -0
- package/bin/mcp-salesforce.js +12 -0
- package/bin/setup.js +266 -0
- package/bin/status.js +134 -0
- package/docs/README.md +52 -0
- package/docs/step1.png +0 -0
- package/docs/step2.png +0 -0
- package/docs/step3.png +0 -0
- package/docs/step4.png +0 -0
- package/examples/README.md +35 -0
- package/package.json +16 -0
- package/scripts/README.md +30 -0
- package/src/auth/file-storage.js +447 -0
- package/src/auth/oauth.js +417 -0
- package/src/auth/token-manager.js +207 -0
- package/src/backup/manager.js +949 -0
- package/src/index.js +168 -0
- package/src/salesforce/client.js +388 -0
- package/src/sf-client.js +79 -0
- package/src/tools/auth.js +190 -0
- package/src/tools/backup.js +486 -0
- package/src/tools/create.js +109 -0
- package/src/tools/delegate-hygiene.js +268 -0
- package/src/tools/delegate-validate.js +212 -0
- package/src/tools/delegate-verify.js +143 -0
- package/src/tools/delete.js +72 -0
- package/src/tools/describe.js +132 -0
- package/src/tools/installation-info.js +656 -0
- package/src/tools/learn-context.js +1077 -0
- package/src/tools/learn.js +351 -0
- package/src/tools/query.js +82 -0
- package/src/tools/repair-credentials.js +77 -0
- package/src/tools/setup.js +120 -0
- package/src/tools/time_machine.js +347 -0
- package/src/tools/update.js +138 -0
- package/src/tools.js +214 -0
- package/src/utils/cache.js +120 -0
- package/src/utils/debug.js +52 -0
- package/src/utils/logger.js +19 -0
- package/tokens.json +8 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Salesforce Learn Tool
|
|
3
|
+
*
|
|
4
|
+
* This tool analyzes the complete Salesforce installation and creates
|
|
5
|
+
* a comprehensive local documentation cache of all objects, fields,
|
|
6
|
+
* relationships, and customizations.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import { debug as logger } from '../utils/debug.js';
|
|
13
|
+
import { getCacheFilePath, ensureCacheDirectory } from '../utils/cache.js';
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = path.dirname(__filename);
|
|
17
|
+
|
|
18
|
+
// Path for storing the learned installation data
|
|
19
|
+
const INSTALLATION_FILE = getCacheFilePath('salesforce-installation.json');
|
|
20
|
+
|
|
21
|
+
export const salesforceLearnTool = {
|
|
22
|
+
name: "salesforce_learn",
|
|
23
|
+
description: "Analyzes the complete Salesforce installation and creates local documentation of all objects, fields, and customizations. This should be run once after initial setup to enable intelligent assistance.",
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: "object",
|
|
26
|
+
properties: {
|
|
27
|
+
force_refresh: {
|
|
28
|
+
type: "boolean",
|
|
29
|
+
description: "Forces a complete re-analysis even if documentation already exists",
|
|
30
|
+
default: false
|
|
31
|
+
},
|
|
32
|
+
include_unused: {
|
|
33
|
+
type: "boolean",
|
|
34
|
+
description: "Includes unused/inactive fields and objects in the documentation",
|
|
35
|
+
default: false
|
|
36
|
+
},
|
|
37
|
+
detailed_relationships: {
|
|
38
|
+
type: "boolean",
|
|
39
|
+
description: "Analyzes detailed relationships between objects",
|
|
40
|
+
default: true
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export async function handleSalesforceLearn(args, salesforceClient) {
|
|
47
|
+
const { force_refresh = false, include_unused = false, detailed_relationships = true } = args;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Check if we need to create cache directory
|
|
51
|
+
await ensureCacheDirectory();
|
|
52
|
+
|
|
53
|
+
// Check if documentation already exists
|
|
54
|
+
const existingDoc = await getExistingDocumentation();
|
|
55
|
+
if (existingDoc && !force_refresh) {
|
|
56
|
+
return {
|
|
57
|
+
content: [{
|
|
58
|
+
type: "text",
|
|
59
|
+
text: `✅ Salesforce installation already documented (${existingDoc.metadata.learned_at})\n\n` +
|
|
60
|
+
`📊 **Overview:**\n` +
|
|
61
|
+
`- **${existingDoc.summary.total_objects}** total objects\n` +
|
|
62
|
+
`- **${existingDoc.summary.custom_objects}** custom objects\n` +
|
|
63
|
+
`- **${existingDoc.summary.total_fields}** total fields\n` +
|
|
64
|
+
`- **${existingDoc.summary.custom_fields}** custom fields\n\n` +
|
|
65
|
+
`💡 Use \`force_refresh: true\` to force re-analysis, or use the \`salesforce_installation_info\` tool to view details.`
|
|
66
|
+
}]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Start learning process
|
|
71
|
+
logger.log('🔍 Starting Salesforce installation learning process...');
|
|
72
|
+
|
|
73
|
+
// Step 1: Get all available objects
|
|
74
|
+
const globalDescribe = await salesforceClient.describeGlobal();
|
|
75
|
+
|
|
76
|
+
logger.log(`📋 Found: ${globalDescribe.length} objects`);
|
|
77
|
+
|
|
78
|
+
// Step 2: Filter and categorize objects
|
|
79
|
+
const standardObjects = [];
|
|
80
|
+
const customObjects = [];
|
|
81
|
+
|
|
82
|
+
for (const obj of globalDescribe) {
|
|
83
|
+
if (obj.custom) {
|
|
84
|
+
customObjects.push(obj);
|
|
85
|
+
} else if (obj.name.match(/^(Account|Contact|Lead|Opportunity|Case|Task|Event|Campaign|Product2|Pricebook2|User|Profile)$/)) {
|
|
86
|
+
// Include important standard objects
|
|
87
|
+
standardObjects.push(obj);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
logger.log(`📊 Standard objects: ${standardObjects.length}, Custom objects: ${customObjects.length}`);
|
|
92
|
+
|
|
93
|
+
// Step 3: Detailed analysis of selected objects
|
|
94
|
+
const documentation = {
|
|
95
|
+
metadata: {
|
|
96
|
+
learned_at: new Date().toISOString(),
|
|
97
|
+
salesforce_instance: salesforceClient.instanceUrl,
|
|
98
|
+
api_version: salesforceClient.version || 'v58.0',
|
|
99
|
+
learning_options: { include_unused, detailed_relationships }
|
|
100
|
+
},
|
|
101
|
+
summary: {
|
|
102
|
+
total_objects: standardObjects.length + customObjects.length,
|
|
103
|
+
standard_objects: standardObjects.length,
|
|
104
|
+
custom_objects: customObjects.length,
|
|
105
|
+
total_fields: 0,
|
|
106
|
+
custom_fields: 0
|
|
107
|
+
},
|
|
108
|
+
objects: {}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Analyze objects in batches to avoid rate limits
|
|
112
|
+
const objectsToAnalyze = [...standardObjects, ...customObjects];
|
|
113
|
+
const batchSize = 10;
|
|
114
|
+
|
|
115
|
+
for (let i = 0; i < objectsToAnalyze.length; i += batchSize) {
|
|
116
|
+
const batch = objectsToAnalyze.slice(i, i + batchSize);
|
|
117
|
+
logger.log(`🔬 Analyzing batch ${Math.floor(i/batchSize) + 1}/${Math.ceil(objectsToAnalyze.length/batchSize)} (${batch.length} objects)`);
|
|
118
|
+
|
|
119
|
+
await Promise.all(batch.map(async (obj) => {
|
|
120
|
+
try {
|
|
121
|
+
const objectDoc = await analyzeObject(obj, salesforceClient, { include_unused, detailed_relationships });
|
|
122
|
+
documentation.objects[obj.name] = objectDoc;
|
|
123
|
+
documentation.summary.total_fields += objectDoc.field_count;
|
|
124
|
+
documentation.summary.custom_fields += objectDoc.custom_field_count;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
logger.warn(`⚠️ Error analyzing ${obj.name}:`, error.message);
|
|
127
|
+
documentation.objects[obj.name] = {
|
|
128
|
+
error: `Analysis failed: ${error.message}`,
|
|
129
|
+
basic_info: {
|
|
130
|
+
name: obj.name,
|
|
131
|
+
label: obj.label,
|
|
132
|
+
custom: obj.custom
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
// Small delay between batches
|
|
139
|
+
if (i + batchSize < objectsToAnalyze.length) {
|
|
140
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Step 4: Save documentation
|
|
145
|
+
await fs.writeFile(INSTALLATION_FILE, JSON.stringify(documentation, null, 2), 'utf8');
|
|
146
|
+
|
|
147
|
+
logger.log('✅ Learning process completed');
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
content: [{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: `🎉 **Salesforce installation successfully learned!**\n\n` +
|
|
153
|
+
`📊 **Analyzed data:**\n` +
|
|
154
|
+
`- **${documentation.summary.total_objects}** total objects\n` +
|
|
155
|
+
`- **${documentation.summary.standard_objects}** standard objects\n` +
|
|
156
|
+
`- **${documentation.summary.custom_objects}** custom objects\n` +
|
|
157
|
+
`- **${documentation.summary.total_fields}** total fields\n` +
|
|
158
|
+
`- **${documentation.summary.custom_fields}** custom fields\n\n` +
|
|
159
|
+
`💾 **Documentation saved** to: \`${INSTALLATION_FILE}\`\n\n` +
|
|
160
|
+
`✨ **Next steps:**\n` +
|
|
161
|
+
`- Use \`salesforce_installation_info\` to get an overview\n` +
|
|
162
|
+
`- Ask about specific objects or fields\n` +
|
|
163
|
+
`- The AI can now work intelligently with your Salesforce installation!`
|
|
164
|
+
}]
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
} catch (error) {
|
|
168
|
+
logger.error('❌ Error during learning process:', error);
|
|
169
|
+
return {
|
|
170
|
+
content: [{
|
|
171
|
+
type: "text",
|
|
172
|
+
text: `❌ **Error learning Salesforce installation:**\n\n` +
|
|
173
|
+
`${error.message}\n\n` +
|
|
174
|
+
`🔧 **Possible solutions:**\n` +
|
|
175
|
+
`- Check your Salesforce connection\n` +
|
|
176
|
+
`- Ensure you have sufficient permissions\n` +
|
|
177
|
+
`- Try re-authenticating with \`salesforce_auth\``
|
|
178
|
+
}]
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function analyzeObject(obj, salesforceClient, options) {
|
|
184
|
+
const { include_unused, detailed_relationships } = options;
|
|
185
|
+
|
|
186
|
+
// Get detailed object description
|
|
187
|
+
const describe = await salesforceClient.describe(obj.name);
|
|
188
|
+
|
|
189
|
+
const objectDoc = {
|
|
190
|
+
basic_info: {
|
|
191
|
+
name: obj.name,
|
|
192
|
+
label: obj.label,
|
|
193
|
+
label_plural: obj.labelPlural,
|
|
194
|
+
custom: obj.custom,
|
|
195
|
+
api_name: obj.name
|
|
196
|
+
},
|
|
197
|
+
metadata: {
|
|
198
|
+
createable: describe.createable,
|
|
199
|
+
updateable: describe.updateable,
|
|
200
|
+
deletable: describe.deletable,
|
|
201
|
+
queryable: describe.queryable,
|
|
202
|
+
searchable: describe.searchable,
|
|
203
|
+
retrieveable: describe.retrieveable
|
|
204
|
+
},
|
|
205
|
+
field_count: describe.fields.length,
|
|
206
|
+
custom_field_count: describe.fields.filter(f => f.custom).length,
|
|
207
|
+
fields: {},
|
|
208
|
+
relationships: {
|
|
209
|
+
parent_relationships: [],
|
|
210
|
+
child_relationships: []
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// Analyze fields
|
|
215
|
+
for (const field of describe.fields) {
|
|
216
|
+
// Skip system fields if not including unused
|
|
217
|
+
if (!include_unused && isSystemField(field)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const fieldDoc = {
|
|
222
|
+
name: field.name,
|
|
223
|
+
label: field.label,
|
|
224
|
+
type: field.type,
|
|
225
|
+
custom: field.custom,
|
|
226
|
+
required: !field.nillable && !field.defaultedOnCreate,
|
|
227
|
+
updateable: field.updateable,
|
|
228
|
+
createable: field.createable,
|
|
229
|
+
// Enhanced writability information
|
|
230
|
+
writability: {
|
|
231
|
+
fully_writable: field.updateable && field.createable,
|
|
232
|
+
create_only: field.createable && !field.updateable,
|
|
233
|
+
read_only: !field.updateable && !field.createable,
|
|
234
|
+
system_managed: isSystemManagedField(field),
|
|
235
|
+
calculated: field.calculated || false,
|
|
236
|
+
auto_number: field.type === 'autonumber',
|
|
237
|
+
formula: field.type === 'formula' || field.calculated,
|
|
238
|
+
rollup_summary: field.type === 'summary'
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Add type-specific information
|
|
243
|
+
if (field.type === 'picklist' || field.type === 'multipicklist') {
|
|
244
|
+
fieldDoc.picklist_values = field.picklistValues?.map(v => ({
|
|
245
|
+
value: v.value,
|
|
246
|
+
label: v.label,
|
|
247
|
+
active: v.active
|
|
248
|
+
})) || [];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (field.type === 'reference') {
|
|
252
|
+
fieldDoc.reference_to = field.referenceTo;
|
|
253
|
+
if (detailed_relationships && field.referenceTo?.length > 0) {
|
|
254
|
+
objectDoc.relationships.parent_relationships.push({
|
|
255
|
+
field: field.name,
|
|
256
|
+
references: field.referenceTo
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (field.length) {
|
|
262
|
+
fieldDoc.max_length = field.length;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (field.precision) {
|
|
266
|
+
fieldDoc.precision = field.precision;
|
|
267
|
+
fieldDoc.scale = field.scale;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
objectDoc.fields[field.name] = fieldDoc;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Analyze child relationships
|
|
274
|
+
if (detailed_relationships && describe.childRelationships) {
|
|
275
|
+
for (const childRel of describe.childRelationships) {
|
|
276
|
+
if (childRel.relationshipName) {
|
|
277
|
+
objectDoc.relationships.child_relationships.push({
|
|
278
|
+
child_object: childRel.childSObject,
|
|
279
|
+
relationship_name: childRel.relationshipName,
|
|
280
|
+
field: childRel.field
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return objectDoc;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function isSystemField(field) {
|
|
290
|
+
const systemFields = [
|
|
291
|
+
'Id', 'CreatedDate', 'CreatedById', 'LastModifiedDate', 'LastModifiedById',
|
|
292
|
+
'SystemModstamp', 'IsDeleted', 'MasterRecordId', 'LastActivityDate',
|
|
293
|
+
'LastViewedDate', 'LastReferencedDate'
|
|
294
|
+
];
|
|
295
|
+
return systemFields.includes(field.name);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function isSystemManagedField(field) {
|
|
299
|
+
// System fields that are automatically managed by Salesforce
|
|
300
|
+
const systemManagedFields = [
|
|
301
|
+
'Id', 'CreatedDate', 'CreatedById', 'LastModifiedDate', 'LastModifiedById',
|
|
302
|
+
'SystemModstamp', 'IsDeleted', 'MasterRecordId', 'LastActivityDate',
|
|
303
|
+
'LastViewedDate', 'LastReferencedDate', 'RecordTypeId'
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
// Check if it's a system managed field by name
|
|
307
|
+
if (systemManagedFields.includes(field.name)) {
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Check if it's an auto-number field
|
|
312
|
+
if (field.type === 'autonumber') {
|
|
313
|
+
return true;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Check if it's a formula field
|
|
317
|
+
if (field.type === 'formula' || field.calculated) {
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Check if it's a rollup summary field
|
|
322
|
+
if (field.type === 'summary') {
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Check if field is marked as not updateable and not createable (fully read-only)
|
|
327
|
+
if (!field.updateable && !field.createable) {
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
async function getExistingDocumentation() {
|
|
336
|
+
try {
|
|
337
|
+
const content = await fs.readFile(INSTALLATION_FILE, 'utf8');
|
|
338
|
+
return JSON.parse(content);
|
|
339
|
+
} catch {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export async function getInstallationDocumentation() {
|
|
345
|
+
return await getExistingDocumentation();
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export async function hasInstallationDocumentation() {
|
|
349
|
+
const doc = await getExistingDocumentation();
|
|
350
|
+
return doc !== null;
|
|
351
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { hasInstallationDocumentation } from './learn.js';
|
|
2
|
+
|
|
3
|
+
export const queryTool = {
|
|
4
|
+
name: "salesforce_query",
|
|
5
|
+
description: "Execute SOQL queries against any Salesforce object. Supports SELECT, WHERE, ORDER BY, LIMIT, and other SOQL features.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
query: {
|
|
10
|
+
type: "string",
|
|
11
|
+
description: "SOQL query string (e.g., 'SELECT Id, Name FROM Account WHERE Industry = \\'Technology\\' LIMIT 10'). Use proper SOQL syntax with single quotes for string literals."
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
required: ["query"]
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export async function executeQuery(client, args) {
|
|
19
|
+
try {
|
|
20
|
+
const { query } = args;
|
|
21
|
+
|
|
22
|
+
// Check if installation has been learned
|
|
23
|
+
const hasDocumentation = await hasInstallationDocumentation();
|
|
24
|
+
if (!hasDocumentation) {
|
|
25
|
+
return {
|
|
26
|
+
content: [{
|
|
27
|
+
type: "text",
|
|
28
|
+
text: `⚠️ **Salesforce-Installation noch nicht gelernt**\n\n` +
|
|
29
|
+
`Bevor du SOQL-Abfragen ausführst, sollte die KI deine Salesforce-Installation kennenlernen.\n\n` +
|
|
30
|
+
`🚀 **Empfehlung:** Führe zuerst das \`salesforce_learn\` Tool aus, um alle verfügbaren Objekte und Felder zu analysieren.\n\n` +
|
|
31
|
+
`Danach kann ich dir bei intelligenten Abfragen helfen und die richtigen Feld- und Objektnamen vorschlagen.\n\n` +
|
|
32
|
+
`**Deine Abfrage wird trotzdem ausgeführt:**`
|
|
33
|
+
}]
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!query || typeof query !== 'string') {
|
|
38
|
+
throw new Error('Query parameter is required and must be a string');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Basic SOQL validation
|
|
42
|
+
if (!query.trim().toUpperCase().startsWith('SELECT')) {
|
|
43
|
+
throw new Error('Query must be a SELECT statement');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = await client.query(query);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
content: [
|
|
50
|
+
{
|
|
51
|
+
type: "text",
|
|
52
|
+
text: `SOQL Query Results:\n\n` +
|
|
53
|
+
`Query: ${query}\n` +
|
|
54
|
+
`Total Records: ${result.totalSize}\n` +
|
|
55
|
+
`Records Returned: ${result.records.length}\n` +
|
|
56
|
+
`More Records Available: ${!result.done}\n\n` +
|
|
57
|
+
`Results:\n${JSON.stringify(result.records, null, 2)}`
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
};
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: "text",
|
|
66
|
+
text: `❌ Query failed: ${error.message}\n\n` +
|
|
67
|
+
`Query: ${args.query || 'N/A'}\n\n` +
|
|
68
|
+
`Common issues:\n` +
|
|
69
|
+
`- Check field names (case-sensitive)\n` +
|
|
70
|
+
`- Verify object name exists\n` +
|
|
71
|
+
`- Ensure you have read permissions\n` +
|
|
72
|
+
`- Use single quotes for string literals\n\n` +
|
|
73
|
+
`SOQL Syntax Help:\n` +
|
|
74
|
+
`- SELECT Id, Name FROM Account LIMIT 10\n` +
|
|
75
|
+
`- SELECT Id, Name FROM Contact WHERE Email != null\n` +
|
|
76
|
+
`- SELECT Id, Name FROM Opportunity WHERE Amount > 1000`
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
isError: true
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { FileStorageManager } from '../auth/file-storage.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Repair credentials that were accidentally set to null
|
|
5
|
+
* This is a one-time fix for the bug where credentials were overwritten with null
|
|
6
|
+
*/
|
|
7
|
+
export async function repairCredentials() {
|
|
8
|
+
const fileStorage = new FileStorageManager();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const data = await fileStorage.getAllData();
|
|
12
|
+
|
|
13
|
+
console.error('Current data:', data);
|
|
14
|
+
|
|
15
|
+
if (data.access_token && data.refresh_token && (!data.clientId || !data.clientSecret)) {
|
|
16
|
+
console.error('🔍 Found tokens without credentials - this is the bug!');
|
|
17
|
+
|
|
18
|
+
// We need to ask the user for the credentials
|
|
19
|
+
console.error('❗ We need to restore the missing credentials.');
|
|
20
|
+
console.error('Please provide the following information:');
|
|
21
|
+
console.error('1. Client ID (Consumer Key from your Connected App)');
|
|
22
|
+
console.error('2. Client Secret (Consumer Secret from your Connected App)');
|
|
23
|
+
console.error('');
|
|
24
|
+
console.error('You can find these in your Salesforce Setup > App Manager > Your Connected App > View');
|
|
25
|
+
|
|
26
|
+
// For now, we'll create a temporary fix that preserves the tokens
|
|
27
|
+
// but allows the system to work
|
|
28
|
+
const repairedData = {
|
|
29
|
+
...data,
|
|
30
|
+
clientId: data.clientId || 'NEEDS_REPAIR',
|
|
31
|
+
clientSecret: data.clientSecret || 'NEEDS_REPAIR',
|
|
32
|
+
instanceUrl: data.instanceUrl || data.instance_url,
|
|
33
|
+
credentialsStoredAt: data.credentialsStoredAt || new Date().toISOString(),
|
|
34
|
+
repaired: true,
|
|
35
|
+
repairNote: 'Credentials were null due to bug - needs manual repair'
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
await fileStorage.storeCredentials({
|
|
39
|
+
clientId: 'NEEDS_REPAIR',
|
|
40
|
+
clientSecret: 'NEEDS_REPAIR',
|
|
41
|
+
instanceUrl: data.instanceUrl || data.instance_url
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
console.error('✅ Temporary repair applied. You can now use the system, but please update credentials.');
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
success: true,
|
|
48
|
+
message: 'Temporary repair applied',
|
|
49
|
+
nextSteps: [
|
|
50
|
+
'Get your Client ID and Client Secret from Salesforce Setup',
|
|
51
|
+
'Run the setup tool to properly configure credentials',
|
|
52
|
+
'Your existing tokens will be preserved'
|
|
53
|
+
]
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.error('✅ No repair needed - credentials look correct');
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
message: 'No repair needed'
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('❌ Repair failed:', error.message);
|
|
65
|
+
return {
|
|
66
|
+
success: false,
|
|
67
|
+
error: error.message
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// If run directly
|
|
73
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
74
|
+
repairCredentials().then(result => {
|
|
75
|
+
console.error(JSON.stringify(result, null, 2));
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { FileStorageManager } from '../auth/file-storage.js';
|
|
2
|
+
import { logger } from '../utils/debug.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Setup tool for collecting Salesforce credentials
|
|
6
|
+
*/
|
|
7
|
+
export const setupTool = {
|
|
8
|
+
name: 'salesforce_setup',
|
|
9
|
+
description: 'Configure Salesforce credentials (Client ID, Client Secret, Instance URL)',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
clientId: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'Salesforce Connected App Client ID (Consumer Key)'
|
|
16
|
+
},
|
|
17
|
+
clientSecret: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Salesforce Connected App Client Secret (Consumer Secret)'
|
|
20
|
+
},
|
|
21
|
+
instanceUrl: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Salesforce Instance URL (e.g., https://mycompany.salesforce.com)'
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
required: ['clientId', 'clientSecret', 'instanceUrl']
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Handle Salesforce setup
|
|
32
|
+
* @param {Object} args - Setup arguments
|
|
33
|
+
* @returns {Object} Setup result
|
|
34
|
+
*/
|
|
35
|
+
export async function handleSalesforceSetup(args) {
|
|
36
|
+
try {
|
|
37
|
+
logger.log('🔧 Setting up Salesforce credentials...');
|
|
38
|
+
|
|
39
|
+
const { clientId, clientSecret, instanceUrl } = args;
|
|
40
|
+
|
|
41
|
+
// Validate inputs
|
|
42
|
+
if (!clientId || !clientSecret || !instanceUrl) {
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
error: 'Missing required credentials. Please provide clientId, clientSecret, and instanceUrl.'
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Validate instance URL format
|
|
50
|
+
if (!instanceUrl.startsWith('https://') || !instanceUrl.includes('.salesforce.com')) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: 'Invalid instance URL format. Must be https://yourorg.salesforce.com'
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Clean up instance URL (remove trailing slash)
|
|
58
|
+
const cleanInstanceUrl = instanceUrl.replace(/\/$/, '');
|
|
59
|
+
|
|
60
|
+
// Store credentials
|
|
61
|
+
const fileStorage = new FileStorageManager();
|
|
62
|
+
await fileStorage.storeCredentials({
|
|
63
|
+
clientId,
|
|
64
|
+
clientSecret,
|
|
65
|
+
instanceUrl: cleanInstanceUrl
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
logger.log('✅ Salesforce credentials configured successfully');
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
success: true,
|
|
72
|
+
message: 'Salesforce credentials have been configured successfully. You can now use other Salesforce tools.',
|
|
73
|
+
details: {
|
|
74
|
+
instanceUrl: cleanInstanceUrl,
|
|
75
|
+
clientIdPreview: clientId.substring(0, 20) + '...'
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
} catch (error) {
|
|
80
|
+
logger.error('❌ Setup failed:', error);
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: `Setup failed: ${error.message}`
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Check if credentials are configured
|
|
90
|
+
* @returns {Object} Configuration status
|
|
91
|
+
*/
|
|
92
|
+
export async function checkCredentialsStatus() {
|
|
93
|
+
try {
|
|
94
|
+
const fileStorage = new FileStorageManager();
|
|
95
|
+
const credentials = await fileStorage.getCredentials();
|
|
96
|
+
|
|
97
|
+
if (!credentials) {
|
|
98
|
+
return {
|
|
99
|
+
configured: false,
|
|
100
|
+
message: 'No Salesforce credentials found. Please run the salesforce_setup tool first.'
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
configured: true,
|
|
106
|
+
message: 'Salesforce credentials are configured.',
|
|
107
|
+
details: {
|
|
108
|
+
instanceUrl: credentials.instanceUrl,
|
|
109
|
+
clientIdPreview: credentials.clientId.substring(0, 20) + '...',
|
|
110
|
+
configuredAt: credentials.credentialsStoredAt
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return {
|
|
116
|
+
configured: false,
|
|
117
|
+
error: `Error checking credentials: ${error.message}`
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|