@rohithvemulapally/mcp-server-salesforce 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -86,6 +86,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
86
86
  };
87
87
  return await handleQueryRecords(conn, validatedArgs);
88
88
  }
89
+ case "salesforce_metadata_query": {
90
+ return await handleMetadataQuery(conn, args);
91
+ }
89
92
  case "salesforce_aggregate_query": {
90
93
  const aggregateArgs = args;
91
94
  if (!aggregateArgs.objectName || !Array.isArray(aggregateArgs.selectFields) || !Array.isArray(aggregateArgs.groupByFields)) {
@@ -252,9 +255,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
252
255
  };
253
256
  return await handleWriteApexTrigger(conn, validatedArgs);
254
257
  }
255
- case "salesforce_metadata_query": {
256
- return await handleMetadataQuery(conn, args);
257
- }
258
258
  case "salesforce_execute_anonymous": {
259
259
  const executeArgs = args;
260
260
  if (!executeArgs.apexCode) {
@@ -3,7 +3,7 @@ export declare const METADATA_QUERY: Tool;
3
3
  export declare function handleMetadataQuery(conn: any, args: any): Promise<{
4
4
  content: {
5
5
  type: string;
6
- text: string;
6
+ text: any;
7
7
  }[];
8
8
  isError: boolean;
9
9
  }>;
@@ -1,21 +1,12 @@
1
1
  export const METADATA_QUERY = {
2
2
  name: "salesforce_metadata_query",
3
- description: "Fetch Salesforce metadata such as validation rules and Lightning record pages",
3
+ description: "Fetch validation rules or lightning pages",
4
4
  inputSchema: {
5
5
  type: "object",
6
6
  properties: {
7
- metadataType: {
8
- type: "string",
9
- description: "Type of metadata: ValidationRule or FlexiPage"
10
- },
11
- objectName: {
12
- type: "string",
13
- description: "Object name (required for ValidationRule)"
14
- },
15
- name: {
16
- type: "string",
17
- description: "Metadata name (required for FlexiPage)"
18
- }
7
+ metadataType: { type: "string" },
8
+ objectName: { type: "string" },
9
+ name: { type: "string" }
19
10
  },
20
11
  required: ["metadataType"]
21
12
  }
@@ -24,42 +15,26 @@ export async function handleMetadataQuery(conn, args) {
24
15
  const { metadataType, objectName, name } = args;
25
16
  try {
26
17
  if (metadataType === "ValidationRule") {
27
- if (!objectName) {
28
- throw new Error("objectName is required for ValidationRule");
29
- }
30
18
  const result = await conn.tooling.query(`SELECT Id, ValidationName, ErrorMessage, Active
31
19
  FROM ValidationRule
32
20
  WHERE EntityDefinition.QualifiedApiName = '${objectName}'`);
33
21
  return {
34
- content: [{
35
- type: "text",
36
- text: JSON.stringify(result.records, null, 2)
37
- }],
22
+ content: [{ type: "text", text: JSON.stringify(result.records, null, 2) }],
38
23
  isError: false
39
24
  };
40
25
  }
41
26
  if (metadataType === "FlexiPage") {
42
- if (!name) {
43
- throw new Error("name is required for FlexiPage");
44
- }
45
27
  const result = await conn.metadata.read("FlexiPage", name);
46
28
  return {
47
- content: [{
48
- type: "text",
49
- text: JSON.stringify(result, null, 2)
50
- }],
29
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
51
30
  isError: false
52
31
  };
53
32
  }
54
33
  throw new Error("Unsupported metadata type");
55
34
  }
56
35
  catch (error) {
57
- const errorMessage = error instanceof Error ? error.message : String(error);
58
36
  return {
59
- content: [{
60
- type: "text",
61
- text: `Error fetching metadata: ${errorMessage}`
62
- }],
37
+ content: [{ type: "text", text: error.message }],
63
38
  isError: true
64
39
  };
65
40
  }
@@ -1,4 +1,11 @@
1
1
  import { Tool } from "@modelcontextprotocol/sdk/types.js";
2
+ /**
3
+ * Tool definition for querying Salesforce records using SOQL.
4
+ * Supports:
5
+ * - Standard queries
6
+ * - Relationship queries
7
+ * - Automatic switch to Bulk API for large datasets
8
+ */
2
9
  export declare const QUERY_RECORDS: Tool;
3
10
  export interface QueryArgs {
4
11
  objectName: string;
@@ -7,6 +14,11 @@ export interface QueryArgs {
7
14
  orderBy?: string;
8
15
  limit?: number;
9
16
  }
17
+ /**
18
+ * Executes query using either:
19
+ * - Standard API (for aggregates, limits)
20
+ * - Bulk API (for large datasets)
21
+ */
10
22
  export declare function handleQueryRecords(conn: any, args: QueryArgs): Promise<{
11
23
  content: {
12
24
  type: string;
@@ -1,16 +1,31 @@
1
- // Bulk API query for large datasets
2
- async function bulkQuery(conn, soql) {
3
- return new Promise((resolve, reject) => {
4
- const records = [];
5
- conn.bulk.query(soql)
6
- .on("record", (record) => records.push(record))
7
- .on("end", () => resolve(records))
8
- .on("error", (err) => reject(err));
9
- });
10
- }
1
+ /**
2
+ * Tool definition for querying Salesforce records using SOQL.
3
+ * Supports:
4
+ * - Standard queries
5
+ * - Relationship queries
6
+ * - Automatic switch to Bulk API for large datasets
7
+ */
11
8
  export const QUERY_RECORDS = {
12
9
  name: "salesforce_query_records",
13
- description: "Query records from any Salesforce object using SOQL",
10
+ description: `Query records from any Salesforce object using SOQL, including relationship queries.
11
+
12
+ NOTE:
13
+ - For aggregate queries (COUNT, SUM, GROUP BY), standard query is used.
14
+ - For large datasets, Bulk API is used automatically.
15
+
16
+ Examples:
17
+ 1. Parent-to-child query:
18
+ - objectName: "Account"
19
+ - fields: ["Name", "(SELECT Id, FirstName FROM Contacts)"]
20
+
21
+ 2. Child-to-parent query:
22
+ - objectName: "Contact"
23
+ - fields: ["FirstName", "Account.Name"]
24
+
25
+ 3. Multi-level relationship:
26
+ - objectName: "Contact"
27
+ - fields: ["Account.Owner.Name"]
28
+ `,
14
29
  inputSchema: {
15
30
  type: "object",
16
31
  properties: {
@@ -23,40 +38,52 @@ export const QUERY_RECORDS = {
23
38
  required: ["objectName", "fields"]
24
39
  }
25
40
  };
41
+ /**
42
+ * Validates relationship fields syntax.
43
+ */
26
44
  function validateRelationshipFields(fields) {
27
45
  for (const field of fields) {
28
- if (field.includes(".")) {
29
- const parts = field.split(".");
30
- if (parts.some(part => !part)) {
31
- return { isValid: false, error: `Invalid relationship field format: "${field}"` };
46
+ if (field.includes('.')) {
47
+ const parts = field.split('.');
48
+ if (parts.some(p => !p)) {
49
+ return { isValid: false, error: `Invalid field: ${field}` };
32
50
  }
33
51
  if (parts.length > 5) {
34
- return { isValid: false, error: `Relationship field "${field}" exceeds maximum depth` };
52
+ return { isValid: false, error: `Too deep relationship: ${field}` };
35
53
  }
36
54
  }
37
- if (field.includes("SELECT") && !field.match(/^\(SELECT.*FROM.*\)$/)) {
38
- return { isValid: false, error: `Invalid subquery format: "${field}"` };
55
+ if (field.includes('SELECT') && !field.match(/^\(SELECT.*FROM.*\)$/)) {
56
+ return { isValid: false, error: `Invalid subquery: ${field}` };
39
57
  }
40
58
  }
41
59
  return { isValid: true };
42
60
  }
43
- function formatRelationshipResults(record, field, prefix = "") {
44
- if (field.includes(".")) {
45
- const [relationship, ...rest] = field.split(".");
46
- const relatedRecord = record[relationship];
47
- if (relatedRecord === null)
61
+ /**
62
+ * Formats relationship fields for readable output.
63
+ */
64
+ function formatRelationshipResults(record, field, prefix = '') {
65
+ if (field.includes('.')) {
66
+ const [rel, ...rest] = field.split('.');
67
+ const related = record[rel];
68
+ if (!related)
48
69
  return `${prefix}${field}: null`;
49
- return formatRelationshipResults(relatedRecord, rest.join("."), `${prefix}${relationship}.`);
70
+ return formatRelationshipResults(related, rest.join('.'), `${prefix}${rel}.`);
50
71
  }
51
72
  const value = record[field];
52
73
  if (Array.isArray(value)) {
53
74
  return `${prefix}${field}: [${value.length} records]`;
54
75
  }
55
- return `${prefix}${field}: ${value ?? "null"}`;
76
+ return `${prefix}${field}: ${value ?? 'null'}`;
56
77
  }
78
+ /**
79
+ * Executes query using either:
80
+ * - Standard API (for aggregates, limits)
81
+ * - Bulk API (for large datasets)
82
+ */
57
83
  export async function handleQueryRecords(conn, args) {
58
84
  const { objectName, fields, whereClause, orderBy, limit } = args;
59
85
  try {
86
+ // Step 1: Validate fields
60
87
  const validation = validateRelationshipFields(fields);
61
88
  if (!validation.isValid) {
62
89
  return {
@@ -64,7 +91,8 @@ export async function handleQueryRecords(conn, args) {
64
91
  isError: true
65
92
  };
66
93
  }
67
- let soql = `SELECT ${fields.join(", ")} FROM ${objectName}`;
94
+ // Step 2: Build SOQL query
95
+ let soql = `SELECT ${fields.join(', ')} FROM ${objectName}`;
68
96
  if (whereClause)
69
97
  soql += ` WHERE ${whereClause}`;
70
98
  if (orderBy)
@@ -72,27 +100,38 @@ export async function handleQueryRecords(conn, args) {
72
100
  if (limit)
73
101
  soql += ` LIMIT ${limit}`;
74
102
  let records = [];
103
+ // Step 3: Decide API (Standard vs Bulk)
75
104
  const lower = soql.toLowerCase();
76
105
  if (lower.includes("limit") ||
77
106
  lower.includes("count") ||
78
107
  lower.includes("group by")) {
108
+ // Use standard REST query
79
109
  const result = await conn.query(soql);
80
110
  records = result.records;
81
111
  }
82
112
  else {
83
- records = await bulkQuery(conn, soql);
113
+ // Use Bulk API for large data
114
+ records = await new Promise((resolve, reject) => {
115
+ const recs = [];
116
+ conn.bulk.query(soql)
117
+ .on("record", (r) => recs.push(r))
118
+ .on("end", () => resolve(recs))
119
+ .on("error", reject);
120
+ });
84
121
  }
122
+ // Step 4: Format results
85
123
  const formattedRecords = records.map((record, index) => {
86
124
  const recordStr = fields.map(field => {
87
- if (field.startsWith("(SELECT")) {
88
- const relationshipName = field.match(/FROM\s+(\w+)/)?.[1];
89
- const childRecords = record[relationshipName];
90
- return ` ${relationshipName}: [${childRecords?.length || 0} records]`;
125
+ if (field.startsWith('(SELECT')) {
126
+ const relName = field.match(/FROM\s+(\w+)/)?.[1];
127
+ const child = record[relName];
128
+ return ` ${relName}: [${child?.length || 0} records]`;
91
129
  }
92
- return " " + formatRelationshipResults(record, field);
93
- }).join("\n");
130
+ return ' ' + formatRelationshipResults(record, field);
131
+ }).join('\n');
94
132
  return `Record ${index + 1}:\n${recordStr}`;
95
- }).join("\n\n");
133
+ }).join('\n\n');
134
+ // Step 5: Return response
96
135
  return {
97
136
  content: [{
98
137
  type: "text",
@@ -102,11 +141,11 @@ export async function handleQueryRecords(conn, args) {
102
141
  };
103
142
  }
104
143
  catch (error) {
105
- const errorMessage = error instanceof Error ? error.message : String(error);
144
+ const msg = error instanceof Error ? error.message : String(error);
106
145
  return {
107
146
  content: [{
108
147
  type: "text",
109
- text: `Error executing query: ${errorMessage}`
148
+ text: `Error executing query: ${msg}`
110
149
  }],
111
150
  isError: true
112
151
  };
package/package.json CHANGED
@@ -1,39 +1,38 @@
1
1
  {
2
2
  "name": "@rohithvemulapally/mcp-server-salesforce",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "A Salesforce connector MCP Server.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "type": "module",
8
8
  "bin": {
9
- "salesforce-connector": "dist/index.js"
9
+ "salesforce-connector": "dist/index.js"
10
10
  },
11
11
  "files": [
12
- "dist"
12
+ "dist"
13
13
  ],
14
14
  "scripts": {
15
- "build": "tsc && shx chmod +x dist/*.js",
16
- "prepare": "npm run build",
17
- "watch": "tsc --watch",
18
- "test": "node --test"
15
+ "build": "tsc && shx chmod +x dist/*.js",
16
+ "prepare": "npm run build",
17
+ "watch": "tsc --watch",
18
+ "test": "node --test"
19
19
  },
20
20
  "keywords": [
21
- "mcp",
22
- "salesforce",
23
- "claude",
24
- "ai"
21
+ "mcp",
22
+ "salesforce",
23
+ "claude",
24
+ "ai"
25
25
  ],
26
26
  "author": "rohithvemulapally",
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
- "@modelcontextprotocol/sdk": "1.22.0",
30
- "dotenv": "16.4.7",
31
- "jsforce": "^3.10.3"
29
+ "@modelcontextprotocol/sdk": "1.22.0",
30
+ "dotenv": "16.4.7",
31
+ "jsforce": "^3.10.3"
32
32
  },
33
33
  "devDependencies": {
34
- "@types/node": "^24.3.0",
35
- "typescript": "^5.7.2",
36
- "shx": "^0.4.0"
34
+ "@types/node": "^24.3.0",
35
+ "typescript": "^5.7.2",
36
+ "shx": "^0.4.0"
37
37
  }
38
- }
39
-
38
+ }