363 lines
8.9 KiB
TypeScript
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;
|
|
}
|
|
}
|