import express from 'express';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { createSalesforceClient, SalesforceClient } from './salesforce.js';
import 'dotenv/config';
const app = express();
app.use(express.json());
const API_KEY = process.env.API_KEY;
// Auth middleware
function authenticate(req: express.Request, res: express.Response, next: express.NextFunction) {
if (!API_KEY) {
return next();
}
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing or invalid authorization header' });
}
const token = authHeader.substring(7);
if (token !== API_KEY) {
return res.status(403).json({ error: 'Invalid API key' });
}
next();
}
// Health check
app.get('/health', (_req, res) => {
res.json({ status: 'ok', service: 'salesforce-mcp-server' });
});
// SSE endpoint for MCP
app.get('/sse', authenticate, async (req, res) => {
let sfClient: SalesforceClient;
const server = new Server(
{
name: 'salesforce-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'query',
description: 'Execute a SOQL query against Salesforce.',
inputSchema: {
type: 'object',
properties: {
soql: { type: 'string', description: 'The SOQL query to execute' }
},
required: ['soql']
}
},
{
name: 'describe_object',
description: 'Get metadata about a Salesforce object.',
inputSchema: {
type: 'object',
properties: {
object_name: { type: 'string', description: 'API name of the Salesforce object' }
},
required: ['object_name']
}
},
{
name: 'create',
description: 'Create a new record in Salesforce.',
inputSchema: {
type: 'object',
properties: {
object_name: { type: 'string' },
data: { type: 'object' }
},
required: ['object_name', 'data']
}
},
{
name: 'update',
description: 'Update an existing record in Salesforce.',
inputSchema: {
type: 'object',
properties: {
object_name: { type: 'string' },
data: { type: 'object' }
},
required: ['object_name', 'data']
}
},
{
name: 'delete',
description: 'Delete a record from Salesforce.',
inputSchema: {
type: 'object',
properties: {
object_name: { type: 'string' },
id: { type: 'string' }
},
required: ['object_name', 'id']
}
}
]
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (!sfClient) {
sfClient = createSalesforceClient();
}
switch (name) {
case 'query': {
const { soql } = args as { soql: string };
const result = await sfClient.query(soql);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
};
}
case 'describe_object': {
const { object_name } = args as { object_name: string };
const result = await sfClient.describeObject(object_name);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
};
}
case 'create': {
const { object_name, data } = args as { object_name: string; data: Record<string, unknown> };
const result = await sfClient.create(object_name, data);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
};
}
case 'update': {
const { object_name, data } = args as { object_name: string; data: Record<string, unknown> };
const result = await sfClient.update(object_name, data);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
};
}
case 'delete': {
const { object_name, id } = args as { object_name: string; id: string };
const result = await sfClient.delete(object_name, id);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
});
const transport = new SSEServerTransport('/messages', res);
await server.connect(transport);
});
app.post('/messages', authenticate, async (req, res) => {
// Handle POST messages for SSE transport
res.status(200).json({ received: true });
});
const PORT = process.env.PORT || 8000;
app.listen(PORT, () => {
console.log(`Salesforce MCP server listening on port ${PORT}`);
});