Skip to main content
Glama
steffensbola

Salesforce MCP Server

by steffensbola
mcp-server.ts18.4 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { CallToolRequestSchema, CallToolResult, ListToolsRequestSchema, TextContent, Tool, } from '@modelcontextprotocol/sdk/types.js'; import { SalesforceClient } from '../client/salesforce-client.js'; import { ConfigManager } from '../config/config.js'; import { ToolAuthenticatePasswordArgs, ToolSoqlQueryArgs, ToolSoslSearchArgs, ToolGetObjectFieldsArgs, ToolGetRecordArgs, ToolCreateRecordArgs, ToolUpdateRecordArgs, ToolDeleteRecordArgs, ToolToolingExecuteArgs, ToolApexExecuteArgs, ToolRestfulArgs, } from '../types/salesforce.js'; import { debugLog, errorLog } from '../utils/debug-log.js'; /** * MCP Server for Salesforce integration */ export class SalesforceMCPServer { private readonly server: Server; private salesforceClient: SalesforceClient; constructor() { this.server = new Server( { name: 'salesforce-mcp-ts', version: '0.2.0', }, { capabilities: { tools: {}, }, } ); // Initialize Salesforce client with configuration const config = ConfigManager.getSalesforceConfig(); this.salesforceClient = new SalesforceClient(config); this.setupToolHandlers(); } /** * Ensures the client is authenticated before proceeding * @throws {Error} If not authenticated */ private ensureAuthenticated(): void { if (!this.salesforceClient.isAuthenticated()) { throw new Error( 'Not authenticated. Please authenticate first using the authenticate_password tool.' ); } } /** * Setup MCP tool handlers */ private setupToolHandlers(): void { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: this.getToolDefinitions(), }; }); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async request => { const { name, arguments: args } = request.params; try { switch (name) { case 'authenticate_password': return await this.handleAuthenticatePassword( args as unknown as ToolAuthenticatePasswordArgs ); case 'run_soql_query': return await this.handleSoqlQuery(args as unknown as ToolSoqlQueryArgs); case 'run_sosl_search': return await this.handleSoslSearch(args as unknown as ToolSoslSearchArgs); case 'get_object_fields': return await this.handleGetObjectFields(args as unknown as ToolGetObjectFieldsArgs); case 'get_record': return await this.handleGetRecord(args as unknown as ToolGetRecordArgs); case 'create_record': return await this.handleCreateRecord(args as unknown as ToolCreateRecordArgs); case 'update_record': return await this.handleUpdateRecord(args as unknown as ToolUpdateRecordArgs); case 'delete_record': return await this.handleDeleteRecord(args as unknown as ToolDeleteRecordArgs); case 'tooling_execute': return await this.handleToolingExecute(args as unknown as ToolToolingExecuteArgs); case 'apex_execute': return await this.handleApexExecute(args as unknown as ToolApexExecuteArgs); case 'restful': return await this.handleRestful(args as unknown as ToolRestfulArgs); default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return { content: [ { type: 'text', text: `❌ Error: ${errorMessage}`, } as TextContent, ], }; } }); } /** * Define available tools */ private getToolDefinitions(): Tool[] { return [ this.getAuthenticationTool(), ...this.getQueryTools(), ...this.getRecordTools(), ...this.getApiTools(), ]; } /** * Authentication tool definition */ private getAuthenticationTool(): Tool { return { name: 'authenticate_password', description: 'Authenticate using username/password with OAuth Resource Owner Password Credentials Flow', inputSchema: { type: 'object', properties: { username: { type: 'string', description: 'Salesforce username', }, password: { type: 'string', description: 'Salesforce password', }, security_token: { type: 'string', description: 'Salesforce security token (if required)', }, sandbox: { type: 'boolean', description: 'Whether to authenticate against Salesforce Sandbox (default: false)', }, }, required: ['username', 'password'], }, }; } /** * Query and search tools definitions */ private getQueryTools(): Tool[] { return [ { name: 'run_soql_query', description: 'Executes a SOQL query against Salesforce', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'The SOQL query to execute', }, }, required: ['query'], }, }, { name: 'run_sosl_search', description: 'Executes a SOSL search against Salesforce', inputSchema: { type: 'object', properties: { search: { type: 'string', description: 'The SOSL search to execute (e.g., "FIND {John Smith} IN ALL FIELDS")', }, }, required: ['search'], }, }, { name: 'get_object_fields', description: 'Retrieves field Names, labels and types for a specific Salesforce object', inputSchema: { type: 'object', properties: { object_name: { type: 'string', description: 'The name of the Salesforce object (e.g., "Account", "Contact")', }, }, required: ['object_name'], }, }, ]; } /** * Record CRUD operations tools definitions */ private getRecordTools(): Tool[] { return [ { name: 'get_record', description: 'Retrieves a specific record by ID', inputSchema: { type: 'object', properties: { object_name: { type: 'string', description: 'The name of the Salesforce object (e.g., "Account", "Contact")', }, record_id: { type: 'string', description: 'The ID of the record to retrieve', }, }, required: ['object_name', 'record_id'], }, }, { name: 'create_record', description: 'Creates a new record', inputSchema: { type: 'object', properties: { object_name: { type: 'string', description: 'The name of the Salesforce object (e.g., "Account", "Contact")', }, data: { type: 'object', description: 'The data for the new record', additionalProperties: true, }, }, required: ['object_name', 'data'], }, }, { name: 'update_record', description: 'Updates an existing record', inputSchema: { type: 'object', properties: { object_name: { type: 'string', description: 'The name of the Salesforce object (e.g., "Account", "Contact")', }, record_id: { type: 'string', description: 'The ID of the record to update', }, data: { type: 'object', description: 'The updated data for the record', additionalProperties: true, }, }, required: ['object_name', 'record_id', 'data'], }, }, { name: 'delete_record', description: 'Deletes a record', inputSchema: { type: 'object', properties: { object_name: { type: 'string', description: 'The name of the Salesforce object (e.g., "Account", "Contact")', }, record_id: { type: 'string', description: 'The ID of the record to delete', }, }, required: ['object_name', 'record_id'], }, }, ]; } /** * API and advanced tools definitions */ private getApiTools(): Tool[] { return [ { name: 'tooling_execute', description: 'Executes a Tooling API request', inputSchema: { type: 'object', properties: { action: { type: 'string', description: 'The Tooling API endpoint to call (e.g., "sobjects/ApexClass")', }, method: { type: 'string', description: 'The HTTP method (default: "GET")', enum: ['GET', 'POST', 'PATCH', 'DELETE'], }, data: { type: 'object', description: 'Data for POST/PATCH requests', additionalProperties: true, }, }, required: ['action'], }, }, { name: 'apex_execute', description: 'Executes an Apex REST request', inputSchema: { type: 'object', properties: { action: { type: 'string', description: 'The Apex REST endpoint to call (e.g., "/MyApexClass")', }, method: { type: 'string', description: 'The HTTP method (default: "GET")', enum: ['GET', 'POST', 'PATCH', 'DELETE'], }, data: { type: 'object', description: 'Data for POST/PATCH requests', additionalProperties: true, }, }, required: ['action'], }, }, { name: 'restful', description: 'Makes a direct REST API call to Salesforce', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'The path of the REST API endpoint (e.g., "sobjects/Account/describe")', }, method: { type: 'string', description: 'The HTTP method (default: "GET")', enum: ['GET', 'POST', 'PATCH', 'DELETE'], }, params: { type: 'object', description: 'Query parameters for the request', additionalProperties: true, }, data: { type: 'object', description: 'Data for POST/PATCH requests', additionalProperties: true, }, }, required: ['path'], }, }, ]; } /** * Handle password authentication */ private async handleAuthenticatePassword( args: ToolAuthenticatePasswordArgs ): Promise<CallToolResult> { const { username, password, security_token = '', sandbox = false } = args; if (!username || !password) { throw new Error('Password authentication requires username and password parameters.'); } // Get OAuth credentials from environment const config = ConfigManager.getSalesforceConfig(); if (!config.clientId || !config.clientSecret) { throw new Error( 'Password authentication requires SALESFORCE_CLIENT_ID and SALESFORCE_CLIENT_SECRET environment variables to be set.' ); } // Update the client configuration with provided credentials this.salesforceClient = new SalesforceClient({ ...config, username, password, securityToken: security_token, sandbox, }); const success = await this.salesforceClient.connect(); if (success) { return { content: [ { type: 'text', text: `✅ Password authentication successful!\nConnected to: ${this.salesforceClient.getInstanceUrl()}\nYou can now use other Salesforce tools.`, } as TextContent, ], }; } throw new Error('Password authentication failed. Please check your credentials and try again.'); } /** * Handle SOQL query */ private async handleSoqlQuery(args: ToolSoqlQueryArgs): Promise<CallToolResult> { const { query } = args; this.ensureAuthenticated(); const results = await this.salesforceClient.queryAll(query); return { content: [ { type: 'text', text: `SOQL Query Results (JSON):\n${JSON.stringify(results, null, 2)}`, } as TextContent, ], }; } /** * Handle SOSL search */ private async handleSoslSearch(args: ToolSoslSearchArgs): Promise<CallToolResult> { const { search } = args; this.ensureAuthenticated(); const results = await this.salesforceClient.search(search); return { content: [ { type: 'text', text: `SOSL Search Results (JSON):\n${JSON.stringify(results, null, 2)}`, } as TextContent, ], }; } /** * Handle get object fields */ private async handleGetObjectFields(args: ToolGetObjectFieldsArgs): Promise<CallToolResult> { const { object_name } = args; this.ensureAuthenticated(); const fields = await this.salesforceClient.getObjectFields(object_name); return { content: [ { type: 'text', text: `${object_name} Metadata (JSON):\n${JSON.stringify(fields, null, 2)}`, } as TextContent, ], }; } /** * Handle get record */ private async handleGetRecord(args: ToolGetRecordArgs): Promise<CallToolResult> { const { object_name, record_id } = args; this.ensureAuthenticated(); const record = await this.salesforceClient.getRecord(object_name, record_id); return { content: [ { type: 'text', text: `${object_name} Record (JSON):\n${JSON.stringify(record, null, 2)}`, } as TextContent, ], }; } /** * Handle create record */ private async handleCreateRecord(args: ToolCreateRecordArgs): Promise<CallToolResult> { const { object_name, data } = args; this.ensureAuthenticated(); const result = await this.salesforceClient.createRecord(object_name, data); return { content: [ { type: 'text', text: `Create ${object_name} Record Result (JSON):\n${JSON.stringify(result, null, 2)}`, } as TextContent, ], }; } /** * Handle update record */ private async handleUpdateRecord(args: ToolUpdateRecordArgs): Promise<CallToolResult> { const { object_name, record_id, data } = args; this.ensureAuthenticated(); const success = await this.salesforceClient.updateRecord(object_name, record_id, data); return { content: [ { type: 'text', text: `Update ${object_name} Record Result: ${success ? 'Success' : 'Failed'}`, } as TextContent, ], }; } /** * Handle delete record */ private async handleDeleteRecord(args: ToolDeleteRecordArgs): Promise<CallToolResult> { const { object_name, record_id } = args; this.ensureAuthenticated(); const success = await this.salesforceClient.deleteRecord(object_name, record_id); return { content: [ { type: 'text', text: `Delete ${object_name} Record Result: ${success ? 'Success' : 'Failed'}`, } as TextContent, ], }; } /** * Handle tooling execute */ private async handleToolingExecute(args: ToolToolingExecuteArgs): Promise<CallToolResult> { const { action, method = 'GET', data } = args; this.ensureAuthenticated(); const result = await this.salesforceClient.toolingExecute(action, method, data); return { content: [ { type: 'text', text: `Tooling Execute Result (JSON):\n${JSON.stringify(result, null, 2)}`, } as TextContent, ], }; } /** * Handle apex execute */ private async handleApexExecute(args: ToolApexExecuteArgs): Promise<CallToolResult> { const { action, method = 'GET', data } = args; this.ensureAuthenticated(); const result = await this.salesforceClient.apexExecute(action, method, data); return { content: [ { type: 'text', text: `Apex Execute Result (JSON):\n${JSON.stringify(result, null, 2)}`, } as TextContent, ], }; } /** * Handle restful API call */ private async handleRestful(args: ToolRestfulArgs): Promise<CallToolResult> { const { path, method = 'GET', params, data } = args; this.ensureAuthenticated(); const result = await this.salesforceClient.restful(path, method, params, data); return { content: [ { type: 'text', text: `RESTful API Call Result (JSON):\n${JSON.stringify(result, null, 2)}`, } as TextContent, ], }; } /** * Get the server instance */ getServer(): Server { return this.server; } /** * Initialize the Salesforce connection if possible */ async initialize(): Promise<void> { try { ConfigManager.logConfigStatus(); // Try to connect with existing configuration if (await this.salesforceClient.connect()) { debugLog('✅ Salesforce connection established on startup'); } else { errorLog( 'ℹ️ Salesforce connection not established. Use authenticate_password tool to connect.' ); } } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; errorLog(`ℹ️ Salesforce connection will be established when needed. (${errorMessage})`); } } }

Latest Blog Posts

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/steffensbola/salesforce-mcp-ts'

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