Skip to main content
Glama

salesforce_search_all

Search across multiple Salesforce objects using SOSL queries with customizable filters, field selection, and access controls to find relevant records.

Instructions

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

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
searchTermYesText to search for (supports wildcards * and ?)
searchInNoWhich fields to search in
objectsYesList of objects to search and their return fields
withClausesNoAdditional WITH clauses for the search
updateableNoReturn only updateable records
viewableNoReturn only viewable records

Implementation Reference

  • Core handler function that constructs and executes SOSL search queries across multiple Salesforce objects, formats results, and handles errors.
    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,
        };
      }
    }
  • Tool definition including name, description, and detailed inputSchema for validating salesforce_search_all parameters.
    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:35-45 (registration)
    Registers the salesforce_search_all tool in the MCP server's list of available tools.
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        SEARCH_OBJECTS, 
        DESCRIBE_OBJECT, 
        QUERY_RECORDS, 
        DML_RECORDS,
        MANAGE_OBJECT,
        MANAGE_FIELD,
        SEARCH_ALL
      ],
    }));
  • src/index.ts:143-172 (registration)
    Switch case in tool dispatcher that validates arguments and invokes the handleSearchAll handler for salesforce_search_all.
    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);
    }
  • Helper function to construct SOSL WITH clauses from input parameters.
    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 '';
      }
    }

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observed

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the transparency burden. It discloses important behavioral details: SOSL syntax, wildcard support, per-object WHERE/ORDER BY/LIMIT, WITH clause types, and access filtering via updateable/viewable. It stops short of stating read-only nature explicitly, but 'search' strongly implies it. Overall, it provides above-average transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is longer than a minimal one, but the length is justified by the tool's complexity. It is front-loaded with a clear purpose, followed by structured examples and a concise bullet list of notes. Every section adds value, though some redundancy exists (e.g., wildcards are mentioned twice).

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is complex with nested object parameters and optional WITH clauses. The description covers the multidimensional usage thoroughly with examples, notes, and feature lists, providing enough context for an agent to construct valid invocations. Given no output schema, the description is sufficiently complete for this search tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, providing a solid baseline. The description goes beyond the schema by including two detailed examples that show how objects, withClauses, and wildcards are used together, and it clarifies the meaning of updateable/viewable. This significantly enriches parameter understanding.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise statement: 'Search across multiple Salesforce objects using SOSL (Salesforce Object Search Language).' This clearly identifies the tool's action, resource, and scope, and distinguishes it from sibling tools like salesforce_search_objects and salesforce_query_records by emphasizing multi-object SOSL search.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context that this tool is for searching across multiple objects, with examples showing the expected structure. However, it does not explicitly reference alternatives or state when not to use it, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.