Skip to main content
Glama

search_businesses

Search businesses by category and location. Use filters for country, language, subcategory, and minimum rating to get ranked results with names, ratings, and match scores.

Instructions

Search businesses by category and location. Returns ranked hits with name, city, rating, and matchScore. Filters: countryCode, language, subcategory, minRating, maxResults.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
categoryYesVertical to search. One of: realtor, insurance_agent, medical_practitioner, dentist, home_health, medical_transport, home_services. Or a free-text category like 'plumber'.
locationYesLocation string — city, region, postal code, or 'Dallas, TX' style. Geocoded server-side.
countryCodeNoISO-3166 alpha-2 (US, CA, GB, AU). Restricts results to that country.
languageNoISO-639-1 (en, fr, es, ...). Boosts businesses speaking this language.
subcategoryNoOptional sub-tag, e.g. 'buyer-agent', 'auto-insurance', 'family-medicine'.
maxResultsNo
minRatingNo

Implementation Reference

  • Main handler that loads all businesses, optionally filters by countryCode/minRating/category/subcategory, scores each against the input context, sorts by score, and returns the top results.
    export async function searchBusinesses(input: SearchBusinessesInput): Promise<SearchHit[]> {
      const businesses = await getAllBusinesses();
      const origin = input.location ? await geocode(input.location) : null;
    
      let candidates = businesses;
      if (input.countryCode) {
        candidates = candidates.filter((b) => b.address.countryCode === input.countryCode);
      }
      if (input.minRating != null) {
        candidates = candidates.filter((b) => {
          const top = b.publicReviews?.[0];
          return top ? top.rating >= input.minRating! : false;
        });
      }
    
      // Treat the input.category as either a known Vertical or a subcategory.
      const KNOWN: Vertical[] = [
        "realtor",
        "insurance_agent",
        "medical_practitioner",
        "dentist",
        "home_health",
        "medical_transport",
        "home_services"
      ];
      const isVertical = (KNOWN as string[]).includes(input.category);
      if (isVertical) {
        candidates = candidates.filter((b) => b.vertical === input.category);
      } else {
        const cat = input.category.toLowerCase();
        candidates = candidates.filter(
          (b) =>
            b.subcategories.some((s) => s.toLowerCase().includes(cat)) ||
            b.servicesOffered.some((s) => s.toLowerCase().includes(cat))
        );
      }
      if (input.subcategory) {
        const sub = input.subcategory.toLowerCase();
        candidates = candidates.filter((b) =>
          b.subcategories.some((s) => s.toLowerCase().includes(sub))
        );
      }
      const ctx = {
        origin: origin ?? undefined,
        vertical: isVertical ? input.category : undefined,
        subcategory: input.subcategory ?? (!isVertical ? input.category : undefined),
        language: input.language
      };
    
      const scored: SearchHit[] = candidates.map((b) => ({
        id: b.id,
        name: b.name,
        vertical: b.vertical,
        shortDescription: b.shortDescription,
        city: b.address.city,
        countryCode: b.address.countryCode,
        rating: b.publicReviews?.[0]?.rating,
        reviewCount: b.publicReviews?.[0]?.count,
        matchScore: scoreBusiness(b, ctx),
        tier: b.tier
      }));
    
      return sortByScore(scored).slice(0, input.maxResults);
    }
  • Zod schema defining the input parameters: category, location, countryCode, language, subcategory, maxResults, minRating.
    export const searchBusinessesSchema = z.object({
      category: z
        .string()
        .describe(
          "Vertical to search. One of: realtor, insurance_agent, medical_practitioner, dentist, home_health, medical_transport, home_services. Or a free-text category like 'plumber'."
        ),
      location: z
        .string()
        .describe(
          "Location string — city, region, postal code, or 'Dallas, TX' style. Geocoded server-side."
        ),
      countryCode: z
        .string()
        .length(2)
        .optional()
        .describe("ISO-3166 alpha-2 (US, CA, GB, AU). Restricts results to that country."),
      language: z.string().optional().describe("ISO-639-1 (en, fr, es, ...). Boosts businesses speaking this language."),
      subcategory: z.string().optional().describe("Optional sub-tag, e.g. 'buyer-agent', 'auto-insurance', 'family-medicine'."),
      maxResults: z.number().int().min(1).max(25).default(10),
      minRating: z.number().min(0).max(5).optional()
    });
  • src/server.ts:25-33 (registration)
    Registers the search_businesses tool on the MCP server with a description, schema, and handler callback.
    server.tool(
      "search_businesses",
      "Search businesses by category and location. Returns ranked hits with name, city, rating, and matchScore. Filters: countryCode, language, subcategory, minRating, maxResults.",
      searchBusinessesSchema.shape,
      async (args) => {
        const hits = await searchBusinesses(searchBusinessesSchema.parse(args));
        return { content: [{ type: "text", text: JSON.stringify(hits, null, 2) }] };
      }
    );
  • HTTP preview endpoint that wraps the searchBusinesses handler for testing without an MCP client.
    // Convenience preview endpoint — lets you test ranking without the MCP client.
    // e.g. GET /preview/search?category=realtor&location=Dallas
    app.get("/preview/search", async (req, res) => {
      try {
        const hits = await searchBusinesses({
          category: String(req.query.category ?? "realtor"),
          location: String(req.query.location ?? "Dallas"),
          countryCode: req.query.countryCode ? String(req.query.countryCode) : undefined,
          language: req.query.language ? String(req.query.language) : undefined,
          subcategory: req.query.subcategory ? String(req.query.subcategory) : undefined,
          maxResults: req.query.maxResults ? Number(req.query.maxResults) : 10,
          minRating: req.query.minRating ? Number(req.query.minRating) : undefined
        });
        res.json({ count: hits.length, hits });
      } catch (err) {
        res.status(400).json({ error: String(err) });
      }
    });
    
    // Stripe webhook stub — wire in Phase 2.
    app.post("/webhooks/stripe", (_req, res) => {
      res.status(202).json({ received: true, note: "stub — wire in Phase 2" });
    });
Behavior2/5

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

No annotations provided. Description only states basic behavior (returns ranked hits) and lists filters. Lacks disclosure of side effects, read-only nature, rate limits, or pagination.

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

Conciseness5/5

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

Two sentences: first states main action and return fields, second lists filters. No filler, front-loaded, efficient.

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

Completeness3/5

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

Description covers basic purpose and filters but lacks details on error handling, default ordering, geocoding behavior, and interpretation of 'ranked'. Adequate but with clear gaps.

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

Parameters3/5

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

Schema coverage is high (71%), so baseline 3. Description adds no extra semantic value beyond listing filter names already in schema. No explanation of parameter behavior like minRating or free-text categories.

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

Purpose4/5

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

Description clearly states it searches businesses by category and location and returns ranked hits with specific fields. However, it does not explicitly differentiate from the sibling 'search_by_query' tool.

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

Usage Guidelines3/5

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

Description implies usage for category/location searches with optional filters but provides no explicit when-to-use or when-not-to-use guidance, nor alternatives.

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

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mutamiri-sudo/ailistmybusiness'

If you have feedback or need assistance with the MCP directory API, please join our Discord server