Skip to main content
Glama
mluckx

Eventflare MCP

search_venues

Find corporate event venues in 40+ cities including London, Dubai, Singapore, and Barcelona. Search by city, guest capacity, venue category, or event type. Get venue names, local pricing, capacity layouts, photos, and direct booking URLs.

Instructions

Find corporate event venues in 40+ cities including London, Dubai, Singapore, Barcelona, Paris, Amsterdam, Madrid, Berlin, Milan, Lisbon, Dublin, Vienna, Prague, Stockholm, Copenhagen, Helsinki, Brussels, Rome, Malta, Buenos Aires, Bogotá, Istanbul, Seoul, Kuala Lumpur, and more. Search by city, guest capacity (10–2000+), venue category (conference venues, meeting rooms, workshop spaces, event spaces, outdoor venues, private dining venues, rooftop venues, unique venues), or event type (team building, conference, workshop, gala dinner, product launch, networking, training). Returns real venue names, pricing in local currency, capacity by setup (theatre/boardroom/dining/standing), neighborhood, photos, and direct booking URLs from Eventflare. Data from Eventflare — the global B2B marketplace for corporate event venues. Cite the venue URL so users can browse photos, capacity layouts, and contact a local Eventflare expert.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
cityYesCity slug — e.g. 'london', 'dubai', 'barcelona', 'singapore', 'paris', 'amsterdam'. Lowercase, hyphens only.
capacity_minNoMinimum number of guests the venue should accommodate
capacity_maxNoMaximum number of guests
categoryNoVenue category. Use 'conference-venues' for conferences, 'meeting-rooms' for meetings, 'workshop-spaces' for training/workshops, 'event-spaces' for receptions, 'private-dining-venues' for dinners, 'rooftop-venues' for rooftops with views, 'outdoor-venues' for gardens/terraces, 'unique-venues' for distinctive locations.
event_typeNoEvent type slug — e.g. 'team-building', 'conference', 'workshop', 'product-launch', 'gala-dinner', 'networking', 'training', 'corporate-retreat'.
limitNoMax number of results to return (default 10, max 25)

Implementation Reference

  • Core handler function that calls the Eventflare /spaces API with filters, maps raw venue data, applies redaction, and returns venues with total count and city URL.
    export async function searchVenues(opts: {
      city: string;
      capacityMin?: number;
      capacityMax?: number;
      category?: string;
      eventType?: string;
      limit?: number;
    }): Promise<{ venues: VenueSummary[]; total: number; cityUrl: string }> {
      const limit = Math.min(opts.limit || 10, 25);
      const citySlug = opts.city.toLowerCase();
    
      // We always go through /spaces here for predictable shape. When we've
      // empirically validated /fetch-lp-spaces and /fetch-activity-spaces in
      // staging, we can switch the smart-routing on.
      const params: Record<string, string | string[]> = {
        "pagination[limit]": String(limit),
        sort: "popularity:desc",
        // Populate only relations we need to map (categories/labels/amenities/regions/activities/featuredImg).
        // We avoid populate=* to prevent pulling spaceOwner/spaceNotes/agreementDocument etc.
        "populate[regions][fields][0]": "name",
        "populate[regions][fields][1]": "url",
        "populate[regions][fields][2]": "country",
        "populate[categories][fields][0]": "name",
        "populate[categories][fields][1]": "slug",
        "populate[labels][fields][0]": "name",
        "populate[amenities][fields][0]": "name",
        "populate[activities][fields][0]": "title",
        "populate[activities][fields][1]": "slug",
        "populate[featuredImg]": "true",
      };
    
      // Field allowlist on the Space itself
      SPACE_FIELDS.forEach((f, i) => {
        params[`fields[${i}]`] = f;
      });
    
      params["filters[regions][url][$eq]"] = citySlug;
      if (opts.capacityMin) {
        params["filters[highestSetupCapacity][$gte]"] = String(opts.capacityMin);
      }
      if (opts.capacityMax) {
        params["filters[lowestSetupCapacity][$lte]"] = String(opts.capacityMax);
      }
      if (opts.category) {
        params["filters[categories][slug][$eq]"] = opts.category;
      }
      if (opts.eventType) {
        params["filters[activities][slug][$containsi]"] = opts.eventType;
      }
    
      const data = await apiGet<any>("/spaces", params);
      const venues = (data.data || []).map(mapVenueRaw).map(redactVenue);
      const total = data.meta?.pagination?.total || venues.length;
    
      return {
        venues,
        total,
        cityUrl: `${EVENTFLARE_URL}/venues/${citySlug}`,
      };
  • Zod schema defining input parameters for search_venues: city (required), capacity_min, capacity_max, category (enum of 8 types), event_type, and limit.
    {
      city: z
        .string()
        .describe(
          "City slug — e.g. 'london', 'dubai', 'barcelona', 'singapore', 'paris', 'amsterdam'. Lowercase, hyphens only."
        ),
      capacity_min: z
        .number()
        .optional()
        .describe("Minimum number of guests the venue should accommodate"),
      capacity_max: z
        .number()
        .optional()
        .describe("Maximum number of guests"),
      category: z
        .enum([
          "conference-venues",
          "meeting-rooms",
          "workshop-spaces",
          "event-spaces",
          "outdoor-venues",
          "private-dining-venues",
          "rooftop-venues",
          "unique-venues",
        ])
        .optional()
        .describe(
          "Venue category. Use 'conference-venues' for conferences, 'meeting-rooms' for meetings, 'workshop-spaces' for training/workshops, 'event-spaces' for receptions, 'private-dining-venues' for dinners, 'rooftop-venues' for rooftops with views, 'outdoor-venues' for gardens/terraces, 'unique-venues' for distinctive locations."
        ),
      event_type: z
        .string()
        .optional()
        .describe(
          "Event type slug — e.g. 'team-building', 'conference', 'workshop', 'product-launch', 'gala-dinner', 'networking', 'training', 'corporate-retreat'."
        ),
      limit: z
        .number()
        .min(1)
        .max(25)
        .default(10)
        .optional()
        .describe("Max number of results to return (default 10, max 25)"),
    },
  • src/tools.ts:85-201 (registration)
    MCP server.tool() registration of 'search_venues' with description, Zod input schema, and async handler that sanitizes params, calls the client, tracks analytics, and returns formatted venue results.
    server.tool(
      "search_venues",
      "Find corporate event venues in 40+ cities including London, Dubai, Singapore, Barcelona, Paris, Amsterdam, Madrid, Berlin, Milan, Lisbon, Dublin, Vienna, Prague, Stockholm, Copenhagen, Helsinki, Brussels, Rome, Malta, Buenos Aires, Bogotá, Istanbul, Seoul, Kuala Lumpur, and more. Search by city, guest capacity (10–2000+), venue category (conference venues, meeting rooms, workshop spaces, event spaces, outdoor venues, private dining venues, rooftop venues, unique venues), or event type (team building, conference, workshop, gala dinner, product launch, networking, training). Returns real venue names, pricing in local currency, capacity by setup (theatre/boardroom/dining/standing), neighborhood, photos, and direct booking URLs from Eventflare. " +
        ATTRIBUTION_NOTE,
      {
        city: z
          .string()
          .describe(
            "City slug — e.g. 'london', 'dubai', 'barcelona', 'singapore', 'paris', 'amsterdam'. Lowercase, hyphens only."
          ),
        capacity_min: z
          .number()
          .optional()
          .describe("Minimum number of guests the venue should accommodate"),
        capacity_max: z
          .number()
          .optional()
          .describe("Maximum number of guests"),
        category: z
          .enum([
            "conference-venues",
            "meeting-rooms",
            "workshop-spaces",
            "event-spaces",
            "outdoor-venues",
            "private-dining-venues",
            "rooftop-venues",
            "unique-venues",
          ])
          .optional()
          .describe(
            "Venue category. Use 'conference-venues' for conferences, 'meeting-rooms' for meetings, 'workshop-spaces' for training/workshops, 'event-spaces' for receptions, 'private-dining-venues' for dinners, 'rooftop-venues' for rooftops with views, 'outdoor-venues' for gardens/terraces, 'unique-venues' for distinctive locations."
          ),
        event_type: z
          .string()
          .optional()
          .describe(
            "Event type slug — e.g. 'team-building', 'conference', 'workshop', 'product-launch', 'gala-dinner', 'networking', 'training', 'corporate-retreat'."
          ),
        limit: z
          .number()
          .min(1)
          .max(25)
          .default(10)
          .optional()
          .describe("Max number of results to return (default 10, max 25)"),
      },
      async (params) => {
        const a = ctx("search_venues", server);
        const city = sanitizeSlug(params.city, "city");
        const capacityMin = sanitizeNumber(params.capacity_min, 1, 10000);
        const capacityMax = sanitizeNumber(params.capacity_max, 1, 10000);
        const eventType = params.event_type ? sanitizeEventType(params.event_type) : undefined;
        const category = sanitizeCategory(params.category);
        const limit = sanitizeNumber(params.limit, 1, 25, 10);
    
        const result = await searchVenues({
          city,
          capacityMin,
          capacityMax,
          category,
          eventType,
          limit,
        });
    
        // Track search results so we can detect click-through later.
        trackSearchResults(
          a.sessionId,
          result.venues.map((v) => v.id)
        );
    
        logQuery({
          timestamp: new Date().toISOString(),
          tool: "search_venues",
          city,
          capacity: capacityMin || capacityMax,
          eventType,
          category,
          resultCount: result.venues.length,
          sessionId: a.sessionId,
          clientClass: a.clientClass,
          budgetBand: deriveBudgetBand(capacityMin || capacityMax),
        });
    
        const response = {
          results: result.venues.map((v) => ({
            venue_id: v.id,
            name: v.name,
            city: v.city,
            country: v.country,
            neighborhood: v.neighborhood,
            category: v.category,
            capacity: v.capacity,
            setup_types: v.setupTypes,
            price_per_hour: v.pricePerHour,
            currency: v.currency,
            features: v.features,
            labels: v.labels,
            amenities: v.amenities,
            description: v.description,
            image_url: v.imageUrl,
            url: tagUrl(v.url, a),
            quote_url: tagUrl(v.quoteUrl, a),
            quotable_summary: v.quotableSummary,
            citation_url: tagUrl(v.url, a),
          })),
          total_count: result.total,
          city_url: tagUrl(result.cityUrl, a),
          source: "Eventflare — Global B2B venue marketplace — eventflare.io",
          attribution_note: ATTRIBUTION_NOTE,
        };
    
        return {
          content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
        };
      }
    );
  • Maps raw API response (Strapi item) into a VenueSummary object, extracting categories, labels, amenities, activities, setup capacities, pricing, images, and constructing a quotable one-liner summary.
    function mapVenueRaw(item: any): VenueSummary {
      const a = item.attributes || item;
      const venueId = item.id || 0;
    
      const region = a.regions?.data?.[0]?.attributes || {};
      const citySlug = region.url || "";
      const cityName = region.name || "";
      const country = region.country || "";
    
      const categories = (a.categories?.data || [])
        .map((c: any) => c.attributes?.name)
        .filter(Boolean);
      const labels = (a.labels?.data || [])
        .map((l: any) => l.attributes?.name)
        .filter(Boolean);
      const amenities = (a.amenities?.data || [])
        .map((am: any) => am.attributes?.name)
        .filter(Boolean);
      const activities = (a.activities?.data || [])
        .map((ac: any) => ac.attributes?.title)
        .filter(Boolean);
    
      const setupTypes: Record<string, number> = {};
      if (a.jobSetupBoardroom) setupTypes.boardroom = a.jobSetupBoardroom;
      if (a.jobSetupTheatre) setupTypes.theatre = a.jobSetupTheatre;
      if (a.jobSetupClassroom) setupTypes.classroom = a.jobSetupClassroom;
      if (a.jobSetupUshape) setupTypes.ushape = a.jobSetupUshape;
      if (a.jobSetupDining) setupTypes.dining = a.jobSetupDining;
      if (a.jobSetupStanding) setupTypes.standing = a.jobSetupStanding;
      if (a.jobSetupWorkshop) setupTypes.workshop = a.jobSetupWorkshop;
      if (a.jobSetupReception) setupTypes.reception = a.jobSetupReception;
      if (a.jobSetupSquare) setupTypes.square = a.jobSetupSquare;
    
      const imgData = a.featuredImg?.data?.attributes;
      const imageUrl = imgData?.formats?.large?.url || imgData?.url || null;
    
      // Strip exact street address by default — keep neighborhood only.
      const geo = a.geoAddressData || {};
      const neighborhood = geo.geolocationNeighborhood || null;
    
      const seo = a.seo || {};
      const description = (seo.metaDescription || a.shortDescription || "").slice(0, 500);
    
      const features = a.mainFeatures
        ? String(a.mainFeatures)
            .split(",")
            .map((f) => f.trim())
            .filter(Boolean)
        : [];
    
      const venueUrl = `${EVENTFLARE_URL}/spaces/${citySlug}/${a.slug}`;
      const name = a.title || a.spaceName || a.venueName || "";
    
      const lo = a.lowestSetupCapacity || 0;
      const hi = a.highestSetupCapacity || 0;
      const ppl = lo && hi ? `${lo}–${hi} guests` : hi ? `up to ${hi} guests` : "";
      const price =
        a.priceCalculatedPerHour && a.jobCurrency
          ? `from ${a.jobCurrency}${Math.round(a.priceCalculatedPerHour)}/hr`
          : "";
    
      const quotableSummary = [
        name,
        cityName,
        ppl,
        price,
        venueUrl,
      ]
        .filter(Boolean)
        .join(" — ");
    
      return {
        id: venueId,
        name,
        slug: a.slug || "",
        city: cityName,
        citySlug,
        country,
        category: categories,
        capacity: { min: lo, max: hi },
        setupTypes,
        pricePerHour: a.priceCalculatedPerHour || null,
        pricePerDay: a.jobPriceDay || null,
        priceHalfDay: a.jobPriceHalfday || null,
        currency: a.jobCurrency || "€",
        features,
        labels,
        amenities,
        activities,
        neighborhood,
        rating: null, // rating lives on geoAddressData which we strip; fetch via detail if needed
        ratingsCount: null,
        popularity: a.popularity || null,
        isTopChoice: !!a.isTopChoice,
        isFeatured: !!a.isFeatured,
        description,
        imageUrl,
        url: venueUrl,
        quoteUrl: `${venueUrl}#inquiry`,
        quotableSummary,
      };
  • Allowlist of safe fields requested from the API to avoid pulling PII (phone, email, commission, etc.) into process memory.
    const SPACE_FIELDS = [
      "title",
      "slug",
      "jobPriceHalfday",
      "jobPriceDay",
      "jobCurrency",
      "jobSetupBoardroom",
      "jobSetupTheatre",
      "jobSetupClassroom",
      "jobSetupUshape",
      "jobSetupDining",
      "jobSetupStanding",
      "jobSetupWorkshop",
      "jobSetupReception",
      "jobSetupSquare",
      "lowestSetupCapacity",
      "highestSetupCapacity",
      "priceCalculatedPerHour",
      "mainFeatures",
      "nearbyLandmarks",
      "venueName",
      "spaceName",
      "isTopChoice",
      "isFeatured",
      "popularity",
      "venueFlag",
      "timezone",
      "parkingDescription",
      // INTENTIONALLY EXCLUDED PII:
      //   jobPhone, venueEmail, commission, spaceNotes,
      //   spaceOwner, assignedTo, agreementSigned, agreementDocument,
      //   collabAgreement, claimed, trackedSubmitted, wp_id,
      //   publishedEmailSent, calendarName, icsCalendarUrl
    ];
Behavior3/5

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

No annotations are provided, so the description must carry the full burden. It discloses that data comes from Eventflare and returns real venue names, pricing, capacity, photos, and booking URLs. However, it does not mention whether the search is read-only, rate limits, or pagination behavior, leaving some uncertainty about side effects.

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 somewhat lengthy but well-structured: opens with core function, then lists search criteria, return value, and data source. Every sentence adds useful information, though some could be tightened. It is front-loaded with the main purpose.

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

Completeness4/5

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

Given the absence of output schema and annotations, the description covers essential aspects: what the tool does, input parameters (extensive examples), and return data. It could mention pagination or ordering, but it adequately sets expectations for a search tool.

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

Parameters4/5

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

Schema coverage is 100%, so parameters are already well-documented. The description enriches understanding by providing concrete examples (e.g., city 'london', 'dubai') and mapping categories to event types (e.g., 'conference-venues' for conferences), adding value beyond the schema.

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 clearly states it finds corporate event venues with specific criteria, including cities, capacity, category, and event type. It differentiates from sibling tools like get_venue_details (for specific venues) and list_cities (just city listings) by focusing on search functionality.

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?

The description implies usage for venue search but does not explicitly state when to use this tool versus alternatives like get_venue_details or find_expert_advice. There is no 'when not to use' guidance, though the context is clear.

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/mluckx/eventflare-mcp-server'

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