Skip to main content
Glama

Salesforce MCP Server

index.ts15.2 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import * as dotenv from "dotenv"; import { createSalesforceConnection } from "./utils/connection.js"; import { SEARCH_OBJECTS, handleSearchObjects } from "./tools/search.js"; import { DESCRIBE_OBJECT, handleDescribeObject } from "./tools/describe.js"; import { QUERY_RECORDS, handleQueryRecords, QueryArgs } from "./tools/query.js"; import { AGGREGATE_QUERY, handleAggregateQuery, AggregateQueryArgs } from "./tools/aggregateQuery.js"; import { DML_RECORDS, handleDMLRecords, DMLArgs } from "./tools/dml.js"; import { MANAGE_OBJECT, handleManageObject, ManageObjectArgs } from "./tools/manageObject.js"; import { MANAGE_FIELD, handleManageField, ManageFieldArgs } from "./tools/manageField.js"; import { MANAGE_FIELD_PERMISSIONS, handleManageFieldPermissions, ManageFieldPermissionsArgs } from "./tools/manageFieldPermissions.js"; import { SEARCH_ALL, handleSearchAll, SearchAllArgs, WithClause } from "./tools/searchAll.js"; import { READ_APEX, handleReadApex, ReadApexArgs } from "./tools/readApex.js"; import { WRITE_APEX, handleWriteApex, WriteApexArgs } from "./tools/writeApex.js"; import { READ_APEX_TRIGGER, handleReadApexTrigger, ReadApexTriggerArgs } from "./tools/readApexTrigger.js"; import { WRITE_APEX_TRIGGER, handleWriteApexTrigger, WriteApexTriggerArgs } from "./tools/writeApexTrigger.js"; import { EXECUTE_ANONYMOUS, handleExecuteAnonymous, ExecuteAnonymousArgs } from "./tools/executeAnonymous.js"; import { MANAGE_DEBUG_LOGS, handleManageDebugLogs, ManageDebugLogsArgs } from "./tools/manageDebugLogs.js"; dotenv.config(); const server = new Server( { name: "salesforce-mcp-server", version: "1.0.0", }, { capabilities: { tools: {}, }, }, ); // Tool handlers server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ SEARCH_OBJECTS, DESCRIBE_OBJECT, QUERY_RECORDS, AGGREGATE_QUERY, DML_RECORDS, MANAGE_OBJECT, MANAGE_FIELD, MANAGE_FIELD_PERMISSIONS, SEARCH_ALL, READ_APEX, WRITE_APEX, READ_APEX_TRIGGER, WRITE_APEX_TRIGGER, EXECUTE_ANONYMOUS, MANAGE_DEBUG_LOGS ], })); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; if (!args) throw new Error('Arguments are required'); const conn = await createSalesforceConnection(); switch (name) { case "salesforce_search_objects": { const { searchPattern } = args as { searchPattern: string }; if (!searchPattern) throw new Error('searchPattern is required'); return await handleSearchObjects(conn, searchPattern); } case "salesforce_describe_object": { const { objectName } = args as { objectName: string }; if (!objectName) throw new Error('objectName is required'); return await handleDescribeObject(conn, objectName); } case "salesforce_query_records": { const queryArgs = args as Record<string, unknown>; if (!queryArgs.objectName || !Array.isArray(queryArgs.fields)) { throw new Error('objectName and fields array are required for query'); } // Type check and conversion const validatedArgs: QueryArgs = { objectName: queryArgs.objectName as string, fields: queryArgs.fields as string[], whereClause: queryArgs.whereClause as string | undefined, orderBy: queryArgs.orderBy as string | undefined, limit: queryArgs.limit as number | undefined }; return await handleQueryRecords(conn, validatedArgs); } case "salesforce_aggregate_query": { const aggregateArgs = args as Record<string, unknown>; if (!aggregateArgs.objectName || !Array.isArray(aggregateArgs.selectFields) || !Array.isArray(aggregateArgs.groupByFields)) { throw new Error('objectName, selectFields array, and groupByFields array are required for aggregate query'); } // Type check and conversion const validatedArgs: AggregateQueryArgs = { objectName: aggregateArgs.objectName as string, selectFields: aggregateArgs.selectFields as string[], groupByFields: aggregateArgs.groupByFields as string[], whereClause: aggregateArgs.whereClause as string | undefined, havingClause: aggregateArgs.havingClause as string | undefined, orderBy: aggregateArgs.orderBy as string | undefined, limit: aggregateArgs.limit as number | undefined }; return await handleAggregateQuery(conn, validatedArgs); } case "salesforce_dml_records": { const dmlArgs = args as Record<string, unknown>; if (!dmlArgs.operation || !dmlArgs.objectName || !Array.isArray(dmlArgs.records)) { throw new Error('operation, objectName, and records array are required for DML'); } const validatedArgs: DMLArgs = { operation: dmlArgs.operation as 'insert' | 'update' | 'delete' | 'upsert', objectName: dmlArgs.objectName as string, records: dmlArgs.records as Record<string, any>[], externalIdField: dmlArgs.externalIdField as string | undefined }; return await handleDMLRecords(conn, validatedArgs); } case "salesforce_manage_object": { const objectArgs = args as Record<string, unknown>; if (!objectArgs.operation || !objectArgs.objectName) { throw new Error('operation and objectName are required for object management'); } const validatedArgs: ManageObjectArgs = { operation: objectArgs.operation as 'create' | 'update', objectName: objectArgs.objectName as string, label: objectArgs.label as string | undefined, pluralLabel: objectArgs.pluralLabel as string | undefined, description: objectArgs.description as string | undefined, nameFieldLabel: objectArgs.nameFieldLabel as string | undefined, nameFieldType: objectArgs.nameFieldType as 'Text' | 'AutoNumber' | undefined, nameFieldFormat: objectArgs.nameFieldFormat as string | undefined, sharingModel: objectArgs.sharingModel as 'ReadWrite' | 'Read' | 'Private' | 'ControlledByParent' | undefined }; return await handleManageObject(conn, validatedArgs); } case "salesforce_manage_field": { const fieldArgs = args as Record<string, unknown>; if (!fieldArgs.operation || !fieldArgs.objectName || !fieldArgs.fieldName) { throw new Error('operation, objectName, and fieldName are required for field management'); } const validatedArgs: ManageFieldArgs = { operation: fieldArgs.operation as 'create' | 'update', objectName: fieldArgs.objectName as string, fieldName: fieldArgs.fieldName as string, label: fieldArgs.label as string | undefined, type: fieldArgs.type as string | undefined, required: fieldArgs.required as boolean | undefined, unique: fieldArgs.unique as boolean | undefined, externalId: fieldArgs.externalId as boolean | undefined, length: fieldArgs.length as number | undefined, precision: fieldArgs.precision as number | undefined, scale: fieldArgs.scale as number | undefined, referenceTo: fieldArgs.referenceTo as string | undefined, relationshipLabel: fieldArgs.relationshipLabel as string | undefined, relationshipName: fieldArgs.relationshipName as string | undefined, deleteConstraint: fieldArgs.deleteConstraint as 'Cascade' | 'Restrict' | 'SetNull' | undefined, picklistValues: fieldArgs.picklistValues as Array<{ label: string; isDefault?: boolean }> | undefined, description: fieldArgs.description as string | undefined, grantAccessTo: fieldArgs.grantAccessTo as string[] | undefined }; return await handleManageField(conn, validatedArgs); } case "salesforce_manage_field_permissions": { const permArgs = args as Record<string, unknown>; if (!permArgs.operation || !permArgs.objectName || !permArgs.fieldName) { throw new Error('operation, objectName, and fieldName are required for field permissions management'); } const validatedArgs: ManageFieldPermissionsArgs = { operation: permArgs.operation as 'grant' | 'revoke' | 'view', objectName: permArgs.objectName as string, fieldName: permArgs.fieldName as string, profileNames: permArgs.profileNames as string[] | undefined, readable: permArgs.readable as boolean | undefined, editable: permArgs.editable as boolean | undefined }; return await handleManageFieldPermissions(conn, validatedArgs); } case "salesforce_search_all": { const searchArgs = args as Record<string, unknown>; if (!searchArgs.searchTerm || !Array.isArray(searchArgs.objects)) { throw new Error('searchTerm and objects array are required for search'); } // Validate objects array const objects = searchArgs.objects as Array<Record<string, unknown>>; if (!objects.every(obj => obj.name && Array.isArray(obj.fields))) { throw new Error('Each object must specify name and fields array'); } // Type check and conversion const validatedArgs: SearchAllArgs = { searchTerm: searchArgs.searchTerm as string, searchIn: searchArgs.searchIn as "ALL FIELDS" | "NAME FIELDS" | "EMAIL FIELDS" | "PHONE FIELDS" | "SIDEBAR FIELDS" | undefined, objects: objects.map(obj => ({ name: obj.name as string, fields: obj.fields as string[], where: obj.where as string | undefined, orderBy: obj.orderBy as string | undefined, limit: obj.limit as number | undefined })), withClauses: searchArgs.withClauses as WithClause[] | undefined, updateable: searchArgs.updateable as boolean | undefined, viewable: searchArgs.viewable as boolean | undefined }; return await handleSearchAll(conn, validatedArgs); } case "salesforce_read_apex": { const apexArgs = args as Record<string, unknown>; // Type check and conversion const validatedArgs: ReadApexArgs = { className: apexArgs.className as string | undefined, namePattern: apexArgs.namePattern as string | undefined, includeMetadata: apexArgs.includeMetadata as boolean | undefined }; return await handleReadApex(conn, validatedArgs); } case "salesforce_write_apex": { const apexArgs = args as Record<string, unknown>; if (!apexArgs.operation || !apexArgs.className || !apexArgs.body) { throw new Error('operation, className, and body are required for writing Apex'); } // Type check and conversion const validatedArgs: WriteApexArgs = { operation: apexArgs.operation as 'create' | 'update', className: apexArgs.className as string, apiVersion: apexArgs.apiVersion as string | undefined, body: apexArgs.body as string }; return await handleWriteApex(conn, validatedArgs); } case "salesforce_read_apex_trigger": { const triggerArgs = args as Record<string, unknown>; // Type check and conversion const validatedArgs: ReadApexTriggerArgs = { triggerName: triggerArgs.triggerName as string | undefined, namePattern: triggerArgs.namePattern as string | undefined, includeMetadata: triggerArgs.includeMetadata as boolean | undefined }; return await handleReadApexTrigger(conn, validatedArgs); } case "salesforce_write_apex_trigger": { const triggerArgs = args as Record<string, unknown>; if (!triggerArgs.operation || !triggerArgs.triggerName || !triggerArgs.body) { throw new Error('operation, triggerName, and body are required for writing Apex trigger'); } // Type check and conversion const validatedArgs: WriteApexTriggerArgs = { operation: triggerArgs.operation as 'create' | 'update', triggerName: triggerArgs.triggerName as string, objectName: triggerArgs.objectName as string | undefined, apiVersion: triggerArgs.apiVersion as string | undefined, body: triggerArgs.body as string }; return await handleWriteApexTrigger(conn, validatedArgs); } case "salesforce_execute_anonymous": { const executeArgs = args as Record<string, unknown>; if (!executeArgs.apexCode) { throw new Error('apexCode is required for executing anonymous Apex'); } // Type check and conversion const validatedArgs: ExecuteAnonymousArgs = { apexCode: executeArgs.apexCode as string, logLevel: executeArgs.logLevel as 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'FINE' | 'FINER' | 'FINEST' | undefined }; return await handleExecuteAnonymous(conn, validatedArgs); } case "salesforce_manage_debug_logs": { const debugLogsArgs = args as Record<string, unknown>; if (!debugLogsArgs.operation || !debugLogsArgs.username) { throw new Error('operation and username are required for managing debug logs'); } // Type check and conversion const validatedArgs: ManageDebugLogsArgs = { operation: debugLogsArgs.operation as 'enable' | 'disable' | 'retrieve', username: debugLogsArgs.username as string, logLevel: debugLogsArgs.logLevel as 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'FINE' | 'FINER' | 'FINEST' | undefined, expirationTime: debugLogsArgs.expirationTime as number | undefined, limit: debugLogsArgs.limit as number | undefined, logId: debugLogsArgs.logId as string | undefined, includeBody: debugLogsArgs.includeBody as boolean | undefined }; return await handleManageDebugLogs(conn, validatedArgs); } default: return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true, }; } } catch (error) { return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}`, }], isError: true, }; } }); async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Salesforce MCP Server running on stdio"); } runServer().catch((error) => { console.error("Fatal error running server:", error); process.exit(1); });

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