Salesforce MCP Server

  • src
#!/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 { 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 { SEARCH_ALL, handleSearchAll, SearchAllArgs, WithClause } from "./tools/searchAll.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, DML_RECORDS, MANAGE_OBJECT, MANAGE_FIELD, SEARCH_ALL ], })); 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_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 }; return await handleManageField(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); } 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); });