363 lines
8.9 KiB
TypeScript

/**
* Salesforce MCP Server
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { SalesforceClient } from './clients/salesforce.js';
import { z } from 'zod';
export interface SalesforceServerConfig {
accessToken: string;
instanceUrl: string;
apiVersion?: string;
}
export class SalesforceServer {
private server: Server;
private client: SalesforceClient;
constructor(config: SalesforceServerConfig) {
this.server = new Server(
{
name: 'salesforce-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
resources: {},
},
}
);
this.client = new SalesforceClient({
accessToken: config.accessToken,
instanceUrl: config.instanceUrl,
apiVersion: config.apiVersion,
});
this.setupHandlers();
}
/**
* Setup MCP handlers
*/
private setupHandlers(): void {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
// Foundation tools - additional tools can be added via tool modules later
{
name: 'salesforce_query',
description: 'Execute a SOQL query against Salesforce',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'SOQL query string',
},
},
required: ['query'],
},
},
{
name: 'salesforce_create_record',
description: 'Create a new Salesforce record',
inputSchema: {
type: 'object',
properties: {
objectType: {
type: 'string',
description: 'Salesforce object type (e.g., Account, Contact)',
},
data: {
type: 'object',
description: 'Record data',
},
},
required: ['objectType', 'data'],
},
},
{
name: 'salesforce_update_record',
description: 'Update an existing Salesforce record',
inputSchema: {
type: 'object',
properties: {
objectType: {
type: 'string',
description: 'Salesforce object type',
},
id: {
type: 'string',
description: 'Record ID',
},
data: {
type: 'object',
description: 'Fields to update',
},
},
required: ['objectType', 'id', 'data'],
},
},
{
name: 'salesforce_delete_record',
description: 'Delete a Salesforce record',
inputSchema: {
type: 'object',
properties: {
objectType: {
type: 'string',
description: 'Salesforce object type',
},
id: {
type: 'string',
description: 'Record ID',
},
},
required: ['objectType', 'id'],
},
},
{
name: 'salesforce_describe_object',
description: 'Get metadata for a Salesforce object',
inputSchema: {
type: 'object',
properties: {
objectType: {
type: 'string',
description: 'Salesforce object type',
},
},
required: ['objectType'],
},
},
],
};
});
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
// Route to appropriate handler
switch (name) {
case 'salesforce_query':
return await this.handleQuery(args as { query: string });
case 'salesforce_create_record':
return await this.handleCreateRecord(
args as { objectType: string; data: Record<string, unknown> }
);
case 'salesforce_update_record':
return await this.handleUpdateRecord(
args as { objectType: string; id: string; data: Record<string, unknown> }
);
case 'salesforce_delete_record':
return await this.handleDeleteRecord(
args as { objectType: string; id: string }
);
case 'salesforce_describe_object':
return await this.handleDescribeObject(
args as { objectType: string }
);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return this.formatError(error);
}
});
// List resources (for UI apps)
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
resources: [
{
uri: 'salesforce://dashboard',
name: 'Salesforce Dashboard',
description: 'Overview of Salesforce data and metrics',
mimeType: 'application/json',
},
],
};
});
// Read resource
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri === 'salesforce://dashboard') {
const limits = this.client.getApiLimits();
return {
contents: [
{
uri,
mimeType: 'application/json',
text: JSON.stringify(
{
apiLimits: limits,
instanceUrl: this.client['instanceUrl'],
},
null,
2
),
},
],
};
}
throw new Error(`Unknown resource: ${uri}`);
});
}
/**
* Handle SOQL query
*/
private async handleQuery(args: { query: string }) {
const result = await this.client.query(args.query);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
/**
* Handle create record
*/
private async handleCreateRecord(args: {
objectType: string;
data: Record<string, unknown>;
}) {
const result = await this.client.createRecord(args.objectType, args.data);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
/**
* Handle update record
*/
private async handleUpdateRecord(args: {
objectType: string;
id: string;
data: Record<string, unknown>;
}) {
await this.client.updateRecord(args.objectType, args.id, args.data);
return {
content: [
{
type: 'text',
text: 'Record updated successfully',
},
],
};
}
/**
* Handle delete record
*/
private async handleDeleteRecord(args: { objectType: string; id: string }) {
await this.client.deleteRecord(args.objectType, args.id);
return {
content: [
{
type: 'text',
text: 'Record deleted successfully',
},
],
};
}
/**
* Handle describe object
*/
private async handleDescribeObject(args: { objectType: string }) {
const result = await this.client.describe(args.objectType);
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
/**
* Format error response
*/
private formatError(error: unknown) {
const message = error instanceof Error ? error.message : 'Unknown error';
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
error: true,
message,
},
null,
2
),
},
],
isError: true,
};
}
/**
* Start the server
*/
public async start(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Salesforce MCP Server running on stdio');
}
/**
* Graceful shutdown
*/
public async close(): Promise<void> {
await this.server.close();
}
/**
* Get the underlying Server instance
*/
public getServer(): Server {
return this.server;
}
/**
* Get the Salesforce client
*/
public getClient(): SalesforceClient {
return this.client;
}
}