salesforce_search_all
Search across multiple Salesforce objects using SOSL with custom filters, field selection, and access controls to find relevant records efficiently.
Instructions
Search across multiple Salesforce objects using SOSL (Salesforce Object Search Language).
Examples:
Basic search across all objects: { "searchTerm": "John", "objects": [ { "name": "Account", "fields": ["Name"], "limit": 10 }, { "name": "Contact", "fields": ["FirstName", "LastName", "Email"] } ] }
Advanced search with filters: { "searchTerm": "Cloud*", "searchIn": "NAME FIELDS", "objects": [ { "name": "Account", "fields": ["Name", "Industry"], "orderBy": "Name DESC", "where": "Industry = 'Technology'" } ], "withClauses": [ { "type": "NETWORK", "value": "ALL NETWORKS" }, { "type": "SNIPPET", "fields": ["Description"] } ] }
Notes:
Use * and ? for wildcards in search terms
Each object can have its own WHERE, ORDER BY, and LIMIT clauses
Support for WITH clauses: DATA CATEGORY, DIVISION, METADATA, NETWORK, PRICEBOOKID, SNIPPET, SECURITY_ENFORCED
"updateable" and "viewable" options control record access filtering
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| searchTerm | Yes | Text to search for (supports wildcards * and ?) | |
| searchIn | No | Which fields to search in | |
| objects | Yes | List of objects to search and their return fields | |
| withClauses | No | Additional WITH clauses for the search | |
| updateable | No | Return only updateable records | |
| viewable | No | Return only viewable records |
Implementation Reference
- src/tools/searchAll.ts:170-288 (handler)The primary handler function that implements the core logic for the 'salesforce_search_all' tool. It constructs a SOSL query based on input parameters, executes the search via Salesforce connection, formats the results by object, and handles errors with enhanced messaging.export async function handleSearchAll(conn: any, args: SearchAllArgs) { const { searchTerm, searchIn = "ALL FIELDS", objects, withClauses, updateable, viewable } = args; try { // Validate the search term if (!searchTerm.trim()) { throw new Error('Search term cannot be empty'); } // Construct the RETURNING clause with object-specific clauses const returningClause = objects .map(obj => { let clause = `${obj.name}(${obj.fields.join(',')}` // Add object-specific clauses if present if (obj.where) clause += ` WHERE ${obj.where}`; if (obj.orderBy) clause += ` ORDER BY ${obj.orderBy}`; if (obj.limit) clause += ` LIMIT ${obj.limit}`; return clause + ')'; }) .join(', '); // Build WITH clauses if present const withClausesStr = withClauses ? withClauses.map(buildWithClause).join(' ') : ''; // Add updateable/viewable flags if specified const accessFlags = []; if (updateable) accessFlags.push('UPDATEABLE'); if (viewable) accessFlags.push('VIEWABLE'); const accessClause = accessFlags.length > 0 ? ` RETURNING ${accessFlags.join(',')}` : ''; // Construct complete SOSL query const soslQuery = `FIND {${searchTerm}} IN ${searchIn} ${withClausesStr} RETURNING ${returningClause} ${accessClause}`.trim(); // Execute search const result = await conn.search(soslQuery); // Format results by object let formattedResults = ''; objects.forEach((obj, index) => { const objectResults = result.searchRecords.filter((record: any) => record.attributes.type === obj.name ); formattedResults += `\n${obj.name} (${objectResults.length} records found):\n`; if (objectResults.length > 0) { objectResults.forEach((record: any, recordIndex: number) => { formattedResults += ` Record ${recordIndex + 1}:\n`; obj.fields.forEach(field => { const value = record[field]; formattedResults += ` ${field}: ${value !== null && value !== undefined ? value : 'null'}\n`; }); // Add metadata or snippet info if requested if (withClauses?.some(w => w.type === "METADATA")) { formattedResults += ` Metadata:\n Last Modified: ${record.attributes.lastModifiedDate}\n`; } if (withClauses?.some(w => w.type === "SNIPPET")) { formattedResults += ` Snippets:\n${record.snippets?.map((s: any) => ` ${s.field}: ${s.snippet}`).join('\n') || ' None'}\n`; } }); } if (index < objects.length - 1) { formattedResults += '\n'; } }); return { content: [{ type: "text", text: `Search Results:${formattedResults}` }], isError: false, }; } catch (error) { // Enhanced error handling for SOSL queries const errorMessage = error instanceof Error ? error.message : String(error); let enhancedError = errorMessage; if (errorMessage.includes('MALFORMED_SEARCH')) { enhancedError = `Invalid search query format. Common issues:\n` + `1. Search term contains invalid characters\n` + `2. Object or field names are incorrect\n` + `3. Missing required SOSL syntax elements\n` + `4. Invalid WITH clause combination\n\n` + `Original error: ${errorMessage}`; } else if (errorMessage.includes('INVALID_FIELD')) { enhancedError = `Invalid field specified in RETURNING clause. Please check:\n` + `1. Field names are correct\n` + `2. Fields exist on the specified objects\n` + `3. You have access to all specified fields\n` + `4. WITH SNIPPET fields are valid\n\n` + `Original error: ${errorMessage}`; } else if (errorMessage.includes('WITH_CLAUSE')) { enhancedError = `Error in WITH clause. Please check:\n` + `1. WITH clause type is supported\n` + `2. WITH clause value is valid\n` + `3. You have permission to use the specified WITH clause\n\n` + `Original error: ${errorMessage}`; } return { content: [{ type: "text", text: `Error executing search: ${enhancedError}` }], isError: true, }; } }
- src/tools/searchAll.ts:3-127 (schema)The Tool object definition including name, description, and detailed inputSchema for validating parameters like searchTerm, objects array, withClauses, etc.export const SEARCH_ALL: Tool = { name: "salesforce_search_all", description: `Search across multiple Salesforce objects using SOSL (Salesforce Object Search Language). Examples: 1. Basic search across all objects: { "searchTerm": "John", "objects": [ { "name": "Account", "fields": ["Name"], "limit": 10 }, { "name": "Contact", "fields": ["FirstName", "LastName", "Email"] } ] } 2. Advanced search with filters: { "searchTerm": "Cloud*", "searchIn": "NAME FIELDS", "objects": [ { "name": "Account", "fields": ["Name", "Industry"], "orderBy": "Name DESC", "where": "Industry = 'Technology'" } ], "withClauses": [ { "type": "NETWORK", "value": "ALL NETWORKS" }, { "type": "SNIPPET", "fields": ["Description"] } ] } Notes: - Use * and ? for wildcards in search terms - Each object can have its own WHERE, ORDER BY, and LIMIT clauses - Support for WITH clauses: DATA CATEGORY, DIVISION, METADATA, NETWORK, PRICEBOOKID, SNIPPET, SECURITY_ENFORCED - "updateable" and "viewable" options control record access filtering`, inputSchema: { type: "object", properties: { searchTerm: { type: "string", description: "Text to search for (supports wildcards * and ?)" }, searchIn: { type: "string", enum: ["ALL FIELDS", "NAME FIELDS", "EMAIL FIELDS", "PHONE FIELDS", "SIDEBAR FIELDS"], description: "Which fields to search in", optional: true }, objects: { type: "array", items: { type: "object", properties: { name: { type: "string", description: "API name of the object" }, fields: { type: "array", items: { type: "string" }, description: "Fields to return for this object" }, where: { type: "string", description: "WHERE clause for this object", optional: true }, orderBy: { type: "string", description: "ORDER BY clause for this object", optional: true }, limit: { type: "number", description: "Maximum number of records to return for this object", optional: true } }, required: ["name", "fields"] }, description: "List of objects to search and their return fields" }, withClauses: { type: "array", items: { type: "object", properties: { type: { type: "string", enum: ["DATA CATEGORY", "DIVISION", "METADATA", "NETWORK", "PRICEBOOKID", "SNIPPET", "SECURITY_ENFORCED"] }, value: { type: "string", description: "Value for the WITH clause", optional: true }, fields: { type: "array", items: { type: "string" }, description: "Fields for SNIPPET clause", optional: true } }, required: ["type"] }, description: "Additional WITH clauses for the search", optional: true }, updateable: { type: "boolean", description: "Return only updateable records", optional: true }, viewable: { type: "boolean", description: "Return only viewable records", optional: true } }, required: ["searchTerm", "objects"] } };
- src/index.ts:196-225 (registration)Switch case in the main CallToolRequestHandler that routes calls to 'salesforce_search_all', performs argument validation and type conversion, then invokes the handleSearchAll function.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); }
- src/index.ts:45-63 (registration)Registration of the SEARCH_ALL tool in the list returned by ListToolsRequestHandler, making it discoverable to MCP clients.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ SEARCH_OBJECTS, DESCRIBE_OBJECT, QUERY_RECORDS, AGGREGATE_QUERY, DML_RECORDS, MANAGE_OBJECT, MANAGE_FIELD, MANAGE_FIELD_PERMISSIONS, SEARCH_ALL, READ_APEX, WRITE_APEX, READ_APEX_TRIGGER, WRITE_APEX_TRIGGER, EXECUTE_ANONYMOUS, MANAGE_DEBUG_LOGS ], }));
- src/tools/searchAll.ts:153-168 (helper)Helper function to construct SOSL WITH clauses from input parameters, used in the handler.function buildWithClause(withClause: WithClause): string { switch (withClause.type) { case "SNIPPET": return `WITH SNIPPET (${withClause.fields?.join(', ')})`; case "DATA CATEGORY": case "DIVISION": case "NETWORK": case "PRICEBOOKID": return `WITH ${withClause.type} = ${withClause.value}`; case "METADATA": case "SECURITY_ENFORCED": return `WITH ${withClause.type}`; default: return ''; } }