@rohithvemulapally/mcp-server-salesforce 0.0.6

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +357 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +312 -0
  5. package/dist/tools/aggregateQuery.d.ts +18 -0
  6. package/dist/tools/aggregateQuery.js +250 -0
  7. package/dist/tools/describe.d.ts +9 -0
  8. package/dist/tools/describe.js +33 -0
  9. package/dist/tools/dml.d.ts +15 -0
  10. package/dist/tools/dml.js +105 -0
  11. package/dist/tools/executeAnonymous.d.ts +25 -0
  12. package/dist/tools/executeAnonymous.js +130 -0
  13. package/dist/tools/manageDebugLogs.d.ts +30 -0
  14. package/dist/tools/manageDebugLogs.js +424 -0
  15. package/dist/tools/manageField.d.ts +32 -0
  16. package/dist/tools/manageField.js +349 -0
  17. package/dist/tools/manageFieldPermissions.d.ts +17 -0
  18. package/dist/tools/manageFieldPermissions.js +247 -0
  19. package/dist/tools/manageObject.d.ts +20 -0
  20. package/dist/tools/manageObject.js +138 -0
  21. package/dist/tools/metadata.d.ts +9 -0
  22. package/dist/tools/metadata.js +66 -0
  23. package/dist/tools/query.d.ts +16 -0
  24. package/dist/tools/query.js +114 -0
  25. package/dist/tools/readApex.d.ts +26 -0
  26. package/dist/tools/readApex.js +165 -0
  27. package/dist/tools/readApexTrigger.d.ts +26 -0
  28. package/dist/tools/readApexTrigger.js +165 -0
  29. package/dist/tools/search.d.ts +9 -0
  30. package/dist/tools/search.js +45 -0
  31. package/dist/tools/searchAll.d.ts +29 -0
  32. package/dist/tools/searchAll.js +250 -0
  33. package/dist/tools/writeApex.d.ts +27 -0
  34. package/dist/tools/writeApex.js +154 -0
  35. package/dist/tools/writeApexTrigger.d.ts +28 -0
  36. package/dist/tools/writeApexTrigger.js +178 -0
  37. package/dist/types/connection.d.ts +52 -0
  38. package/dist/types/connection.js +21 -0
  39. package/dist/types/metadata.d.ts +43 -0
  40. package/dist/types/metadata.js +1 -0
  41. package/dist/types/salesforce.d.ts +33 -0
  42. package/dist/types/salesforce.js +1 -0
  43. package/dist/utils/connection.d.ts +7 -0
  44. package/dist/utils/connection.js +172 -0
  45. package/dist/utils/errorHandler.d.ts +15 -0
  46. package/dist/utils/errorHandler.js +23 -0
  47. package/package.json +39 -0
@@ -0,0 +1,18 @@
1
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const AGGREGATE_QUERY: Tool;
3
+ export interface AggregateQueryArgs {
4
+ objectName: string;
5
+ selectFields: string[];
6
+ groupByFields: string[];
7
+ whereClause?: string;
8
+ havingClause?: string;
9
+ orderBy?: string;
10
+ limit?: number;
11
+ }
12
+ export declare function handleAggregateQuery(conn: any, args: AggregateQueryArgs): Promise<{
13
+ content: {
14
+ type: string;
15
+ text: string;
16
+ }[];
17
+ isError: boolean;
18
+ }>;
@@ -0,0 +1,250 @@
1
+ export const AGGREGATE_QUERY = {
2
+ name: "salesforce_aggregate_query",
3
+ description: `Execute SOQL queries with GROUP BY, aggregate functions, and statistical analysis. Use this tool for queries that summarize and group data rather than returning individual records.
4
+
5
+ NOTE: For regular queries without GROUP BY or aggregates, use salesforce_query_records instead.
6
+
7
+ This tool handles:
8
+ 1. GROUP BY queries (single/multiple fields, related objects, date functions)
9
+ 2. Aggregate functions: COUNT(), COUNT_DISTINCT(), SUM(), AVG(), MIN(), MAX()
10
+ 3. HAVING clauses for filtering grouped results
11
+ 4. Date/time grouping: CALENDAR_YEAR(), CALENDAR_MONTH(), CALENDAR_QUARTER(), FISCAL_YEAR(), FISCAL_QUARTER()
12
+
13
+ Examples:
14
+ 1. Count opportunities by stage:
15
+ - objectName: "Opportunity"
16
+ - selectFields: ["StageName", "COUNT(Id) OpportunityCount"]
17
+ - groupByFields: ["StageName"]
18
+
19
+ 2. Analyze cases by priority and status:
20
+ - objectName: "Case"
21
+ - selectFields: ["Priority", "Status", "COUNT(Id) CaseCount", "AVG(Days_Open__c) AvgDaysOpen"]
22
+ - groupByFields: ["Priority", "Status"]
23
+
24
+ 3. Count contacts by account industry:
25
+ - objectName: "Contact"
26
+ - selectFields: ["Account.Industry", "COUNT(Id) ContactCount"]
27
+ - groupByFields: ["Account.Industry"]
28
+
29
+ 4. Quarterly opportunity analysis:
30
+ - objectName: "Opportunity"
31
+ - selectFields: ["CALENDAR_YEAR(CloseDate) Year", "CALENDAR_QUARTER(CloseDate) Quarter", "SUM(Amount) Revenue"]
32
+ - groupByFields: ["CALENDAR_YEAR(CloseDate)", "CALENDAR_QUARTER(CloseDate)"]
33
+
34
+ 5. Find accounts with more than 10 opportunities:
35
+ - objectName: "Opportunity"
36
+ - selectFields: ["Account.Name", "COUNT(Id) OpportunityCount"]
37
+ - groupByFields: ["Account.Name"]
38
+ - havingClause: "COUNT(Id) > 10"
39
+
40
+ Important Rules:
41
+ - All non-aggregate fields in selectFields MUST be included in groupByFields
42
+ - Use whereClause to filter rows BEFORE grouping
43
+ - Use havingClause to filter AFTER grouping (for aggregate conditions)
44
+ - ORDER BY can only use fields from groupByFields or aggregate functions
45
+ - OFFSET is not supported with GROUP BY in Salesforce`,
46
+ inputSchema: {
47
+ type: "object",
48
+ properties: {
49
+ objectName: {
50
+ type: "string",
51
+ description: "API name of the object to query"
52
+ },
53
+ selectFields: {
54
+ type: "array",
55
+ items: { type: "string" },
56
+ description: "Fields to select - mix of group fields and aggregates. Format: 'FieldName' or 'COUNT(Id) AliasName'"
57
+ },
58
+ groupByFields: {
59
+ type: "array",
60
+ items: { type: "string" },
61
+ description: "Fields to group by - must include all non-aggregate fields from selectFields"
62
+ },
63
+ whereClause: {
64
+ type: "string",
65
+ description: "WHERE clause to filter rows BEFORE grouping (cannot contain aggregate functions)",
66
+ optional: true
67
+ },
68
+ havingClause: {
69
+ type: "string",
70
+ description: "HAVING clause to filter results AFTER grouping (use for aggregate conditions)",
71
+ optional: true
72
+ },
73
+ orderBy: {
74
+ type: "string",
75
+ description: "ORDER BY clause - can only use grouped fields or aggregate functions",
76
+ optional: true
77
+ },
78
+ limit: {
79
+ type: "number",
80
+ description: "Maximum number of grouped results to return",
81
+ optional: true
82
+ }
83
+ },
84
+ required: ["objectName", "selectFields", "groupByFields"]
85
+ }
86
+ };
87
+ // Aggregate functions that don't need to be in GROUP BY
88
+ const AGGREGATE_FUNCTIONS = ['COUNT', 'COUNT_DISTINCT', 'SUM', 'AVG', 'MIN', 'MAX'];
89
+ const DATE_FUNCTIONS = ['CALENDAR_YEAR', 'CALENDAR_MONTH', 'CALENDAR_QUARTER', 'FISCAL_YEAR', 'FISCAL_QUARTER'];
90
+ // Helper function to detect if a field contains an aggregate function
91
+ function isAggregateField(field) {
92
+ const upperField = field.toUpperCase();
93
+ return AGGREGATE_FUNCTIONS.some(func => upperField.includes(`${func}(`));
94
+ }
95
+ // Helper function to extract the base field from a select field (removing alias)
96
+ function extractBaseField(field) {
97
+ // Remove alias if present (e.g., "COUNT(Id) OpportunityCount" -> "COUNT(Id)")
98
+ const parts = field.trim().split(/\s+/);
99
+ return parts[0];
100
+ }
101
+ // Helper function to extract non-aggregate fields from select fields
102
+ function extractNonAggregateFields(selectFields) {
103
+ return selectFields
104
+ .filter(field => !isAggregateField(field))
105
+ .map(field => extractBaseField(field));
106
+ }
107
+ // Helper function to validate that all non-aggregate fields are in GROUP BY
108
+ function validateGroupByFields(selectFields, groupByFields) {
109
+ const nonAggregateFields = extractNonAggregateFields(selectFields);
110
+ const groupBySet = new Set(groupByFields.map(f => f.trim()));
111
+ const missingFields = nonAggregateFields.filter(field => !groupBySet.has(field));
112
+ return {
113
+ isValid: missingFields.length === 0,
114
+ missingFields
115
+ };
116
+ }
117
+ // Helper function to validate WHERE clause doesn't contain aggregates
118
+ function validateWhereClause(whereClause) {
119
+ if (!whereClause)
120
+ return { isValid: true };
121
+ const upperWhere = whereClause.toUpperCase();
122
+ for (const func of AGGREGATE_FUNCTIONS) {
123
+ if (upperWhere.includes(`${func}(`)) {
124
+ return {
125
+ isValid: false,
126
+ error: `WHERE clause cannot contain aggregate functions. Use HAVING clause instead for aggregate conditions like ${func}()`
127
+ };
128
+ }
129
+ }
130
+ return { isValid: true };
131
+ }
132
+ // Helper function to validate ORDER BY fields
133
+ function validateOrderBy(orderBy, groupByFields, selectFields) {
134
+ if (!orderBy)
135
+ return { isValid: true };
136
+ // Extract fields from ORDER BY (handling DESC/ASC)
137
+ const orderByParts = orderBy.split(',').map(part => {
138
+ return part.trim().replace(/ (DESC|ASC)$/i, '').trim();
139
+ });
140
+ const groupBySet = new Set(groupByFields);
141
+ const aggregateFields = selectFields.filter(field => isAggregateField(field)).map(field => extractBaseField(field));
142
+ for (const orderField of orderByParts) {
143
+ // Check if it's in GROUP BY or is an aggregate
144
+ if (!groupBySet.has(orderField) && !aggregateFields.some(agg => agg === orderField) && !isAggregateField(orderField)) {
145
+ return {
146
+ isValid: false,
147
+ error: `ORDER BY field '${orderField}' must be in GROUP BY clause or be an aggregate function`
148
+ };
149
+ }
150
+ }
151
+ return { isValid: true };
152
+ }
153
+ export async function handleAggregateQuery(conn, args) {
154
+ const { objectName, selectFields, groupByFields, whereClause, havingClause, orderBy, limit } = args;
155
+ try {
156
+ // Validate GROUP BY contains all non-aggregate fields
157
+ const groupByValidation = validateGroupByFields(selectFields, groupByFields);
158
+ if (!groupByValidation.isValid) {
159
+ return {
160
+ content: [{
161
+ type: "text",
162
+ text: `Error: The following non-aggregate fields must be included in GROUP BY clause: ${groupByValidation.missingFields.join(', ')}\n\n` +
163
+ `All fields in SELECT that are not aggregate functions (COUNT, SUM, AVG, etc.) must be included in GROUP BY.`
164
+ }],
165
+ isError: true,
166
+ };
167
+ }
168
+ // Validate WHERE clause doesn't contain aggregates
169
+ const whereValidation = validateWhereClause(whereClause);
170
+ if (!whereValidation.isValid) {
171
+ return {
172
+ content: [{
173
+ type: "text",
174
+ text: whereValidation.error
175
+ }],
176
+ isError: true,
177
+ };
178
+ }
179
+ // Validate ORDER BY fields
180
+ const orderByValidation = validateOrderBy(orderBy, groupByFields, selectFields);
181
+ if (!orderByValidation.isValid) {
182
+ return {
183
+ content: [{
184
+ type: "text",
185
+ text: orderByValidation.error
186
+ }],
187
+ isError: true,
188
+ };
189
+ }
190
+ // Construct SOQL query
191
+ let soql = `SELECT ${selectFields.join(', ')} FROM ${objectName}`;
192
+ if (whereClause)
193
+ soql += ` WHERE ${whereClause}`;
194
+ soql += ` GROUP BY ${groupByFields.join(', ')}`;
195
+ if (havingClause)
196
+ soql += ` HAVING ${havingClause}`;
197
+ if (orderBy)
198
+ soql += ` ORDER BY ${orderBy}`;
199
+ if (limit)
200
+ soql += ` LIMIT ${limit}`;
201
+ const result = await conn.query(soql);
202
+ // Format the output
203
+ const formattedRecords = result.records.map((record, index) => {
204
+ const recordStr = selectFields.map(field => {
205
+ const baseField = extractBaseField(field);
206
+ const fieldParts = field.trim().split(/\s+/);
207
+ const displayName = fieldParts.length > 1 ? fieldParts[fieldParts.length - 1] : baseField;
208
+ // Handle nested fields in results
209
+ if (baseField.includes('.')) {
210
+ const parts = baseField.split('.');
211
+ let value = record;
212
+ for (const part of parts) {
213
+ value = value?.[part];
214
+ }
215
+ return ` ${displayName}: ${value !== null && value !== undefined ? value : 'null'}`;
216
+ }
217
+ const value = record[baseField] || record[displayName];
218
+ return ` ${displayName}: ${value !== null && value !== undefined ? value : 'null'}`;
219
+ }).join('\n');
220
+ return `Group ${index + 1}:\n${recordStr}`;
221
+ }).join('\n\n');
222
+ return {
223
+ content: [{
224
+ type: "text",
225
+ text: `Aggregate query returned ${result.records.length} grouped results:\n\n${formattedRecords}`
226
+ }],
227
+ isError: false,
228
+ };
229
+ }
230
+ catch (error) {
231
+ const errorMessage = error instanceof Error ? error.message : String(error);
232
+ // Provide more helpful error messages for common issues
233
+ let enhancedError = errorMessage;
234
+ if (errorMessage.includes('MALFORMED_QUERY')) {
235
+ if (errorMessage.includes('GROUP BY')) {
236
+ enhancedError = `Query error: ${errorMessage}\n\nCommon issues:\n` +
237
+ `1. Ensure all non-aggregate fields in SELECT are in GROUP BY\n` +
238
+ `2. Check that date functions match exactly between SELECT and GROUP BY\n` +
239
+ `3. Verify field names and relationships are correct`;
240
+ }
241
+ }
242
+ return {
243
+ content: [{
244
+ type: "text",
245
+ text: `Error executing aggregate query: ${enhancedError}`
246
+ }],
247
+ isError: true,
248
+ };
249
+ }
250
+ }
@@ -0,0 +1,9 @@
1
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const DESCRIBE_OBJECT: Tool;
3
+ export declare function handleDescribeObject(conn: any, objectName: string): Promise<{
4
+ content: {
5
+ type: string;
6
+ text: string;
7
+ }[];
8
+ isError: boolean;
9
+ }>;
@@ -0,0 +1,33 @@
1
+ export const DESCRIBE_OBJECT = {
2
+ name: "salesforce_describe_object",
3
+ description: "Get detailed schema metadata including all fields, relationships, and field properties of any Salesforce object. Examples: 'Account' shows all Account fields including custom fields; 'Case' shows all Case fields including relationships to Account, Contact etc.",
4
+ inputSchema: {
5
+ type: "object",
6
+ properties: {
7
+ objectName: {
8
+ type: "string",
9
+ description: "API name of the object (e.g., 'Account', 'Contact', 'Custom_Object__c')"
10
+ }
11
+ },
12
+ required: ["objectName"]
13
+ }
14
+ };
15
+ export async function handleDescribeObject(conn, objectName) {
16
+ const describe = await conn.describe(objectName);
17
+ // Format the output
18
+ const formattedDescription = `
19
+ Object: ${describe.name} (${describe.label})${describe.custom ? ' (Custom Object)' : ''}
20
+ Fields:
21
+ ${describe.fields.map((field) => ` - ${field.name} (${field.label})
22
+ Type: ${field.type}${field.length ? `, Length: ${field.length}` : ''}
23
+ Required: ${!field.nillable}
24
+ ${field.referenceTo && field.referenceTo.length > 0 ? `References: ${field.referenceTo.join(', ')}` : ''}
25
+ ${field.picklistValues && field.picklistValues.length > 0 ? `Picklist Values: ${field.picklistValues.map((v) => v.value).join(', ')}` : ''}`).join('\n')}`;
26
+ return {
27
+ content: [{
28
+ type: "text",
29
+ text: formattedDescription
30
+ }],
31
+ isError: false,
32
+ };
33
+ }
@@ -0,0 +1,15 @@
1
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const DML_RECORDS: Tool;
3
+ export interface DMLArgs {
4
+ operation: 'insert' | 'update' | 'delete' | 'upsert';
5
+ objectName: string;
6
+ records: Record<string, any>[];
7
+ externalIdField?: string;
8
+ }
9
+ export declare function handleDMLRecords(conn: any, args: DMLArgs): Promise<{
10
+ content: {
11
+ type: string;
12
+ text: string;
13
+ }[];
14
+ isError: boolean;
15
+ }>;
@@ -0,0 +1,105 @@
1
+ export const DML_RECORDS = {
2
+ name: "salesforce_dml_records",
3
+ description: `Perform data manipulation operations on Salesforce records:
4
+ - insert: Create new records
5
+ - update: Modify existing records (requires Id)
6
+ - delete: Remove records (requires Id)
7
+ - upsert: Insert or update based on external ID field
8
+ Examples: Insert new Accounts, Update Case status, Delete old records, Upsert based on custom external ID`,
9
+ inputSchema: {
10
+ type: "object",
11
+ properties: {
12
+ operation: {
13
+ type: "string",
14
+ enum: ["insert", "update", "delete", "upsert"],
15
+ description: "Type of DML operation to perform"
16
+ },
17
+ objectName: {
18
+ type: "string",
19
+ description: "API name of the object"
20
+ },
21
+ records: {
22
+ type: "array",
23
+ items: { type: "object" },
24
+ description: "Array of records to process"
25
+ },
26
+ externalIdField: {
27
+ type: "string",
28
+ description: "External ID field name for upsert operations",
29
+ optional: true
30
+ }
31
+ },
32
+ required: ["operation", "objectName", "records"]
33
+ }
34
+ };
35
+ export async function handleDMLRecords(conn, args) {
36
+ const { operation, objectName, records, externalIdField } = args;
37
+ let result;
38
+ switch (operation) {
39
+ case 'insert':
40
+ result = await conn.sobject(objectName).create(records);
41
+ break;
42
+ case 'update':
43
+ result = await conn.sobject(objectName).update(records);
44
+ break;
45
+ case 'delete':
46
+ result = await conn.sobject(objectName).destroy(records.map(r => r.Id));
47
+ break;
48
+ case 'upsert':
49
+ if (!externalIdField) {
50
+ throw new Error('externalIdField is required for upsert operations');
51
+ }
52
+ result = await conn.sobject(objectName).upsert(records, externalIdField);
53
+ break;
54
+ default:
55
+ throw new Error(`Unsupported operation: ${operation}`);
56
+ }
57
+ // Format DML results
58
+ const results = Array.isArray(result) ? result : [result];
59
+ const successCount = results.filter(r => r.success).length;
60
+ const failureCount = results.length - successCount;
61
+ let responseText = `${operation.toUpperCase()} operation completed.\n`;
62
+ responseText += `Processed ${results.length} records:\n`;
63
+ responseText += `- Successful: ${successCount}\n`;
64
+ responseText += `- Failed: ${failureCount}\n\n`;
65
+ if (failureCount > 0) {
66
+ responseText += 'Errors:\n';
67
+ results.forEach((r, idx) => {
68
+ if (!r.success && r.errors) {
69
+ responseText += `Record ${idx + 1}:\n`;
70
+ if (Array.isArray(r.errors)) {
71
+ r.errors.forEach((error) => {
72
+ responseText += ` - ${error.message}`;
73
+ if (error.statusCode) {
74
+ responseText += ` [${error.statusCode}]`;
75
+ }
76
+ if (error.fields && error.fields.length > 0) {
77
+ responseText += `\n Fields: ${error.fields.join(', ')}`;
78
+ }
79
+ responseText += '\n';
80
+ });
81
+ }
82
+ else {
83
+ // Single error object
84
+ const error = r.errors;
85
+ responseText += ` - ${error.message}`;
86
+ if (error.statusCode) {
87
+ responseText += ` [${error.statusCode}]`;
88
+ }
89
+ if (error.fields) {
90
+ const fields = Array.isArray(error.fields) ? error.fields.join(', ') : error.fields;
91
+ responseText += `\n Fields: ${fields}`;
92
+ }
93
+ responseText += '\n';
94
+ }
95
+ }
96
+ });
97
+ }
98
+ return {
99
+ content: [{
100
+ type: "text",
101
+ text: responseText
102
+ }],
103
+ isError: false,
104
+ };
105
+ }
@@ -0,0 +1,25 @@
1
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const EXECUTE_ANONYMOUS: Tool;
3
+ export interface ExecuteAnonymousArgs {
4
+ apexCode: string;
5
+ logLevel?: 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'FINE' | 'FINER' | 'FINEST';
6
+ }
7
+ /**
8
+ * Handles executing anonymous Apex code in Salesforce
9
+ * @param conn Active Salesforce connection
10
+ * @param args Arguments for executing anonymous Apex
11
+ * @returns Tool response with execution results and debug logs
12
+ */
13
+ export declare function handleExecuteAnonymous(conn: any, args: ExecuteAnonymousArgs): Promise<{
14
+ content: {
15
+ type: string;
16
+ text: string;
17
+ }[];
18
+ isError?: undefined;
19
+ } | {
20
+ content: {
21
+ type: string;
22
+ text: string;
23
+ }[];
24
+ isError: boolean;
25
+ }>;
@@ -0,0 +1,130 @@
1
+ export const EXECUTE_ANONYMOUS = {
2
+ name: "salesforce_execute_anonymous",
3
+ description: `Execute anonymous Apex code in Salesforce.
4
+
5
+ Examples:
6
+ 1. Execute simple Apex code:
7
+ {
8
+ "apexCode": "System.debug('Hello World');"
9
+ }
10
+
11
+ 2. Execute Apex code with variables:
12
+ {
13
+ "apexCode": "List<Account> accounts = [SELECT Id, Name FROM Account LIMIT 5]; for(Account a : accounts) { System.debug(a.Name); }"
14
+ }
15
+
16
+ 3. Execute Apex with debug logs:
17
+ {
18
+ "apexCode": "System.debug(LoggingLevel.INFO, 'Processing accounts...'); List<Account> accounts = [SELECT Id FROM Account LIMIT 10]; System.debug(LoggingLevel.INFO, 'Found ' + accounts.size() + ' accounts');",
19
+ "logLevel": "DEBUG"
20
+ }
21
+
22
+ Notes:
23
+ - The apexCode parameter is required and must contain valid Apex code
24
+ - The code is executed in an anonymous context and does not persist
25
+ - The logLevel parameter is optional (defaults to 'DEBUG')
26
+ - Execution results include compilation success/failure, execution success/failure, and debug logs
27
+ - For security reasons, some operations may be restricted based on user permissions
28
+ - This tool can be used for data operations or updates when there are no other specific tools available
29
+ - When users request data queries or updates that aren't directly supported by other tools, this tool can be used if the operation is achievable using Apex code
30
+ `,
31
+ inputSchema: {
32
+ type: "object",
33
+ properties: {
34
+ apexCode: {
35
+ type: "string",
36
+ description: "Apex code to execute anonymously"
37
+ },
38
+ logLevel: {
39
+ type: "string",
40
+ enum: ["NONE", "ERROR", "WARN", "INFO", "DEBUG", "FINE", "FINER", "FINEST"],
41
+ description: "Log level for debug logs (optional, defaults to DEBUG)"
42
+ }
43
+ },
44
+ required: ["apexCode"]
45
+ }
46
+ };
47
+ /**
48
+ * Handles executing anonymous Apex code in Salesforce
49
+ * @param conn Active Salesforce connection
50
+ * @param args Arguments for executing anonymous Apex
51
+ * @returns Tool response with execution results and debug logs
52
+ */
53
+ export async function handleExecuteAnonymous(conn, args) {
54
+ try {
55
+ // Validate inputs
56
+ if (!args.apexCode || args.apexCode.trim() === '') {
57
+ throw new Error('apexCode is required and cannot be empty');
58
+ }
59
+ console.error(`Executing anonymous Apex code`);
60
+ // Set default log level if not provided
61
+ const logLevel = args.logLevel || 'DEBUG';
62
+ // Execute the anonymous Apex code
63
+ const result = await conn.tooling.executeAnonymous(args.apexCode);
64
+ // Format the response
65
+ let responseText = '';
66
+ // Add compilation and execution status
67
+ if (result.compiled) {
68
+ responseText += `**Compilation:** Success\n`;
69
+ }
70
+ else {
71
+ responseText += `**Compilation:** Failed\n`;
72
+ responseText += `**Line:** ${result.line}\n`;
73
+ responseText += `**Column:** ${result.column}\n`;
74
+ responseText += `**Error:** ${result.compileProblem}\n\n`;
75
+ }
76
+ if (result.compiled && result.success) {
77
+ responseText += `**Execution:** Success\n`;
78
+ }
79
+ else if (result.compiled) {
80
+ responseText += `**Execution:** Failed\n`;
81
+ responseText += `**Error:** ${result.exceptionMessage}\n`;
82
+ if (result.exceptionStackTrace) {
83
+ responseText += `**Stack Trace:**\n\`\`\`\n${result.exceptionStackTrace}\n\`\`\`\n\n`;
84
+ }
85
+ }
86
+ // Get debug logs if available
87
+ if (result.compiled) {
88
+ try {
89
+ // Query for the most recent debug log
90
+ const logs = await conn.query(`
91
+ SELECT Id, LogUserId, Operation, Application, Status, LogLength, LastModifiedDate, Request
92
+ FROM ApexLog
93
+ ORDER BY LastModifiedDate DESC
94
+ LIMIT 1
95
+ `);
96
+ if (logs.records.length > 0) {
97
+ const logId = logs.records[0].Id;
98
+ // Retrieve the log body
99
+ const logBody = await conn.tooling.request({
100
+ method: 'GET',
101
+ url: `${conn.instanceUrl}/services/data/v58.0/tooling/sobjects/ApexLog/${logId}/Body`
102
+ });
103
+ responseText += `\n**Debug Log:**\n\`\`\`\n${logBody}\n\`\`\``;
104
+ }
105
+ else {
106
+ responseText += `\n**Debug Log:** No logs available. Ensure debug logs are enabled for your user.`;
107
+ }
108
+ }
109
+ catch (logError) {
110
+ responseText += `\n**Debug Log:** Unable to retrieve debug logs: ${logError instanceof Error ? logError.message : String(logError)}`;
111
+ }
112
+ }
113
+ return {
114
+ content: [{
115
+ type: "text",
116
+ text: responseText
117
+ }]
118
+ };
119
+ }
120
+ catch (error) {
121
+ console.error('Error executing anonymous Apex:', error);
122
+ return {
123
+ content: [{
124
+ type: "text",
125
+ text: `Error executing anonymous Apex: ${error instanceof Error ? error.message : String(error)}`
126
+ }],
127
+ isError: true,
128
+ };
129
+ }
130
+ }
@@ -0,0 +1,30 @@
1
+ import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ export declare const MANAGE_DEBUG_LOGS: Tool;
3
+ export interface ManageDebugLogsArgs {
4
+ operation: 'enable' | 'disable' | 'retrieve';
5
+ username: string;
6
+ logLevel?: 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'FINE' | 'FINER' | 'FINEST';
7
+ expirationTime?: number;
8
+ limit?: number;
9
+ logId?: string;
10
+ includeBody?: boolean;
11
+ }
12
+ /**
13
+ * Handles managing debug logs for Salesforce users
14
+ * @param conn Active Salesforce connection
15
+ * @param args Arguments for managing debug logs
16
+ * @returns Tool response with operation results
17
+ */
18
+ export declare function handleManageDebugLogs(conn: any, args: ManageDebugLogsArgs): Promise<{
19
+ content: {
20
+ type: string;
21
+ text: string;
22
+ }[];
23
+ isError: boolean;
24
+ } | {
25
+ content: {
26
+ type: string;
27
+ text: string;
28
+ }[];
29
+ isError?: undefined;
30
+ }>;