Skip to main content
Glama

Salesforce MCP Server

import { Tool } from "@modelcontextprotocol/sdk/types.js"; import type { Connection } from "jsforce"; export const MANAGE_DEBUG_LOGS: Tool = { name: "salesforce_manage_debug_logs", description: `Manage debug logs for Salesforce users - enable, disable, or retrieve logs. Examples: 1. Enable debug logs for a user: { "operation": "enable", "username": "user@example.com", "logLevel": "DEBUG", "expirationTime": 30 } 2. Disable debug logs for a user: { "operation": "disable", "username": "user@example.com" } 3. Retrieve debug logs for a user: { "operation": "retrieve", "username": "user@example.com", "limit": 5 } 4. Retrieve a specific log with full content: { "operation": "retrieve", "username": "user@example.com", "logId": "07L1g000000XXXXEAA0", "includeBody": true } Notes: - The operation must be one of: 'enable', 'disable', or 'retrieve' - The username parameter is required for all operations - For 'enable' operation, logLevel is optional (defaults to 'DEBUG') - Log levels: NONE, ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST - expirationTime is optional for 'enable' operation (minutes until expiration, defaults to 30) - limit is optional for 'retrieve' operation (maximum number of logs to return, defaults to 10) - logId is optional for 'retrieve' operation (to get a specific log) - includeBody is optional for 'retrieve' operation (to include the full log content, defaults to false) - The tool validates that the specified user exists before performing operations - If logLevel is not specified when enabling logs, the tool will ask for clarification`, inputSchema: { type: "object", properties: { operation: { type: "string", enum: ["enable", "disable", "retrieve"], description: "Operation to perform on debug logs" }, username: { type: "string", description: "Username of the Salesforce user" }, logLevel: { type: "string", enum: ["NONE", "ERROR", "WARN", "INFO", "DEBUG", "FINE", "FINER", "FINEST"], description: "Log level for debug logs (required for 'enable' operation)" }, expirationTime: { type: "number", description: "Minutes until the debug log configuration expires (optional, defaults to 30)" }, limit: { type: "number", description: "Maximum number of logs to retrieve (optional, defaults to 10)" }, logId: { type: "string", description: "ID of a specific log to retrieve (optional)" }, includeBody: { type: "boolean", description: "Whether to include the full log content (optional, defaults to false)" } }, required: ["operation", "username"] } }; export interface ManageDebugLogsArgs { operation: 'enable' | 'disable' | 'retrieve'; username: string; logLevel?: 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'FINE' | 'FINER' | 'FINEST'; expirationTime?: number; limit?: number; logId?: string; includeBody?: boolean; } /** * Handles managing debug logs for Salesforce users * @param conn Active Salesforce connection * @param args Arguments for managing debug logs * @returns Tool response with operation results */ export async function handleManageDebugLogs(conn: any, args: ManageDebugLogsArgs) { try { // Validate inputs if (!args.username) { throw new Error('username is required'); } // Determine if the input is likely a username or a full name const isLikelyUsername = args.username.includes('@') || !args.username.includes(' '); // Build the query based on whether the input looks like a username or a full name let userQuery; if (isLikelyUsername) { // Query by username userQuery = await conn.query(` SELECT Id, Username, Name, IsActive FROM User WHERE Username = '${args.username}' `); } else { // Query by full name userQuery = await conn.query(` SELECT Id, Username, Name, IsActive FROM User WHERE Name LIKE '%${args.username}%' ORDER BY LastModifiedDate DESC LIMIT 5 `); } if (userQuery.records.length === 0) { // If no results with the initial query, try a more flexible search userQuery = await conn.query(` SELECT Id, Username, Name, IsActive FROM User WHERE Name LIKE '%${args.username}%' OR Username LIKE '%${args.username}%' ORDER BY LastModifiedDate DESC LIMIT 5 `); if (userQuery.records.length === 0) { return { content: [{ type: "text", text: `Error: No user found matching '${args.username}'. Please verify the username or full name and try again.` }], isError: true, }; } // If multiple users found, ask for clarification if (userQuery.records.length > 1) { let responseText = `Multiple users found matching '${args.username}'. Please specify which user by providing the exact username:\n\n`; userQuery.records.forEach((user: any) => { responseText += `- **${user.Name}** (${user.Username})\n`; }); return { content: [{ type: "text", text: responseText }] }; } } const user = userQuery.records[0]; if (!user.IsActive) { return { content: [{ type: "text", text: `Warning: User '${args.username}' exists but is inactive. Debug logs may not be generated for inactive users.` }] }; } // Handle operations switch (args.operation) { case 'enable': { // If logLevel is not provided, we need to ask for it if (!args.logLevel) { return { content: [{ type: "text", text: `Please specify a log level for enabling debug logs. Valid options are: NONE, ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST.` }], isError: true, }; } // Set default expiration time if not provided const expirationTime = args.expirationTime || 30; // Check if a trace flag already exists for this user const existingTraceFlag = await conn.tooling.query(` SELECT Id, DebugLevelId FROM TraceFlag WHERE TracedEntityId = '${user.Id}' AND ExpirationDate > ${new Date().toISOString()} `); let traceFlagId; let debugLevelId; let operation; // Calculate expiration date const expirationDate = new Date(); expirationDate.setMinutes(expirationDate.getMinutes() + expirationTime); if (existingTraceFlag.records.length > 0) { // Update existing trace flag traceFlagId = existingTraceFlag.records[0].Id; debugLevelId = existingTraceFlag.records[0].DebugLevelId; await conn.tooling.sobject('TraceFlag').update({ Id: traceFlagId, LogType: 'USER_DEBUG', StartDate: new Date().toISOString(), ExpirationDate: expirationDate.toISOString() }); operation = 'updated'; } else { // Create a new debug level with the correct field names const debugLevelResult = await conn.tooling.sobject('DebugLevel').create({ DeveloperName: `UserDebug_${Date.now()}`, MasterLabel: `User Debug ${user.Username}`, ApexCode: args.logLevel, ApexProfiling: args.logLevel, Callout: args.logLevel, Database: args.logLevel, System: args.logLevel, Validation: args.logLevel, Visualforce: args.logLevel, Workflow: args.logLevel }); debugLevelId = debugLevelResult.id; // Create a new trace flag const traceFlagResult = await conn.tooling.sobject('TraceFlag').create({ TracedEntityId: user.Id, DebugLevelId: debugLevelId, LogType: 'USER_DEBUG', StartDate: new Date().toISOString(), ExpirationDate: expirationDate.toISOString() }); traceFlagId = traceFlagResult.id; operation = 'enabled'; } return { content: [{ type: "text", text: `Successfully ${operation} debug logs for user '${args.username}'.\n\n` + `**Log Level:** ${args.logLevel}\n` + `**Expiration:** ${expirationDate.toLocaleString()} (${expirationTime} minutes from now)\n` + `**Trace Flag ID:** ${traceFlagId}` }] }; } case 'disable': { // Find all active trace flags for this user const traceFlags = await conn.tooling.query(` SELECT Id FROM TraceFlag WHERE TracedEntityId = '${user.Id}' AND ExpirationDate > ${new Date().toISOString()} `); if (traceFlags.records.length === 0) { return { content: [{ type: "text", text: `No active debug logs found for user '${args.username}'.` }] }; } try { // Delete trace flags instead of updating expiration date const traceFlagIds = traceFlags.records.map((tf: any) => tf.Id); const deleteResults = await Promise.all( traceFlagIds.map((id: string) => conn.tooling.sobject('TraceFlag').delete(id) ) ); return { content: [{ type: "text", text: `Successfully disabled ${traceFlagIds.length} debug log configuration(s) for user '${args.username}' by removing them.` }] }; } catch (deleteError) { console.error('Error deleting trace flags:', deleteError); // Fallback to setting a future expiration date if delete fails try { // Set expiration date to 5 minutes in the future to satisfy Salesforce's requirement const nearFutureExpiration = new Date(); nearFutureExpiration.setMinutes(nearFutureExpiration.getMinutes() + 5); const traceFlagIds = traceFlags.records.map((tf: any) => tf.Id); const updateResults = await Promise.all( traceFlagIds.map((id: string) => conn.tooling.sobject('TraceFlag').update({ Id: id, ExpirationDate: nearFutureExpiration.toISOString() }) ) ); return { content: [{ type: "text", text: `Successfully disabled ${traceFlagIds.length} debug log configuration(s) for user '${args.username}'. They will expire in 5 minutes.` }] }; } catch (updateError) { console.error('Error updating trace flags:', updateError); throw new Error(`Could not disable debug logs: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`); } } } case 'retrieve': { // Set default limit if not provided const limit = args.limit || 10; // If a specific log ID is provided, retrieve that log directly if (args.logId) { try { // First check if the log exists const logQuery = await conn.tooling.query(` SELECT Id, LogUserId, Operation, Application, Status, LogLength, LastModifiedDate, Request FROM ApexLog WHERE Id = '${args.logId}' `); if (logQuery.records.length === 0) { return { content: [{ type: "text", text: `No log found with ID '${args.logId}'.` }] }; } const log = logQuery.records[0]; // If includeBody is true, retrieve the log body if (args.includeBody) { try { // Retrieve the log body const logBody = await conn.tooling.request({ method: 'GET', url: `${conn.instanceUrl}/services/data/v58.0/tooling/sobjects/ApexLog/${log.Id}/Body` }); let responseText = `**Log Details:**\n\n`; responseText += `- **ID:** ${log.Id}\n`; responseText += `- **Operation:** ${log.Operation}\n`; responseText += `- **Application:** ${log.Application}\n`; responseText += `- **Status:** ${log.Status}\n`; responseText += `- **Size:** ${log.LogLength} bytes\n`; responseText += `- **Date:** ${new Date(log.LastModifiedDate).toLocaleString()}\n\n`; responseText += `**Log Body:**\n\`\`\`\n${logBody}\n\`\`\`\n`; return { content: [{ type: "text", text: responseText }] }; } catch (logError) { console.error('Error retrieving log body:', logError); return { content: [{ type: "text", text: `Error retrieving log body: ${logError instanceof Error ? logError.message : String(logError)}` }], isError: true }; } } else { // Just return the log metadata let responseText = `**Log Details:**\n\n`; responseText += `- **ID:** ${log.Id}\n`; responseText += `- **Operation:** ${log.Operation}\n`; responseText += `- **Application:** ${log.Application}\n`; responseText += `- **Status:** ${log.Status}\n`; responseText += `- **Size:** ${log.LogLength} bytes\n`; responseText += `- **Date:** ${new Date(log.LastModifiedDate).toLocaleString()}\n\n`; responseText += `To view the full log content, add "includeBody": true to your request.`; return { content: [{ type: "text", text: responseText }] }; } } catch (error) { console.error('Error retrieving log:', error); return { content: [{ type: "text", text: `Error retrieving log: ${error instanceof Error ? error.message : String(error)}` }], isError: true, }; } } // Query for logs const logs = await conn.tooling.query(` SELECT Id, LogUserId, Operation, Application, Status, LogLength, LastModifiedDate, Request FROM ApexLog WHERE LogUserId = '${user.Id}' ORDER BY LastModifiedDate DESC LIMIT ${limit} `); if (logs.records.length === 0) { return { content: [{ type: "text", text: `No debug logs found for user '${args.username}'.` }] }; } // Format log information let responseText = `Found ${logs.records.length} debug logs for user '${args.username}':\n\n`; for (let i = 0; i < logs.records.length; i++) { const log = logs.records[i]; responseText += `**Log ${i + 1}**\n`; responseText += `- **ID:** ${log.Id}\n`; responseText += `- **Operation:** ${log.Operation}\n`; responseText += `- **Application:** ${log.Application}\n`; responseText += `- **Status:** ${log.Status}\n`; responseText += `- **Size:** ${log.LogLength} bytes\n`; responseText += `- **Date:** ${new Date(log.LastModifiedDate).toLocaleString()}\n\n`; } // Add a note about viewing specific logs with full content responseText += `To view a specific log with full content, use:\n\`\`\`\n`; responseText += `{\n`; responseText += ` "operation": "retrieve",\n`; responseText += ` "username": "${args.username}",\n`; responseText += ` "logId": "<LOG_ID>",\n`; responseText += ` "includeBody": true\n`; responseText += `}\n\`\`\`\n`; return { content: [{ type: "text", text: responseText }] }; } default: throw new Error(`Invalid operation: ${args.operation}. Must be 'enable', 'disable', or 'retrieve'.`); } } catch (error) { console.error('Error managing debug logs:', error); return { content: [{ type: "text", text: `Error managing debug logs: ${error instanceof Error ? error.message : String(error)}` }], isError: true, }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tsmztech/mcp-server-salesforce'

If you have feedback or need assistance with the MCP directory API, please join our Discord server