Skip to main content

MCP connector

The Elivaas stay discovery connector: a remote MCP server that lets an AI assistant search our villas and show them as interactive cards in the conversation, then hand the guest to elivaas.com to book.

One URL serves every host — Claude, ChatGPT, Cursor, VS Code — because it speaks Streamable HTTP.

Module:mcp (com.elivaas.mcp)
EndpointPOST /mcp
Container appbard-mcp
Imagebard-mcp
Authenticationnone — search is public, exactly like the Booking.com connector
EnvironmentResource groupManaged envEndpoint
non-proddevelivaas-non-prodhttps://bard-mcp.blackriver-831938f9.centralindia.azurecontainerapps.io/mcp
prodbard-prod-rgbard-prod-envhttps://bard-mcp.nicewave-abbaa2f1.centralindia.azurecontainerapps.io/mcp

The sales channel is ELIVAAS, chnl_mX5N2zqWsGAZJx — the same channel the public site sells on, so a price quoted in chat matches the price on the page the guest is handed to.

Provisioned; deep-link routes still placeholders

Both apps carry the bootstrap image; the first deploy-azure.yml run pushes the real one. The app will refuse to start until MCP_STAY_URL_TEMPLATE and MCP_SEARCH_URL_TEMPLATE hold the site's real routes — see Why the app refuses to start.

Curation is done in dev only. ELIVAAS had no channel_listing rows at all, which meant the "not yet curated" branch made all 305 active listings visible on it anyway; all 305 are now enabled explicitly, so the catalogue is unchanged but stated rather than inferred. Confirm the same in prod before the connector goes public — that is the difference between selling the catalogue and leaking it.

Scope

Read-only. The connector searches and links out; bookings and payments never happen in the chat. There is no cart, payment or authentication code in this module and none should be added — that is a deliberate product boundary, not a staging step.

Tools

ToolPurposeUI card
list_destinationsDestinations we sell in, with property counts
search_staysUp to 10 priced, available stays, each with a bookingUrl
get_propertyAmenities, bedrooms, images, cancellation policy
availability_calendarOpen dates and nightly rates for one villa
quote_stayItemised price: nightly rates, taxes, offers, total

Every response is {status, results, meta, guidance}. An empty result always says which kind of empty it is (NO_AVAILABILITY / UNKNOWN_DESTINATION / …) and what to do next — a model handed a bare empty list decides for itself and tells the guest there are no villas in Goa.

Interactive cards (MCP Apps)

Three ui:// templates are published as MCP Apps resources with mime type text/html;profile=mcp-app, referenced from their tools via _meta.ui.resourceUri. They live in mcp/src/main/resources/ui/ as static, self-contained HTML.

Design. The cards carry the Elivaas guest palette taken from crs templates/cart/timeline.ftlh — the shared-cart page a guest already sees — so a card in chat and the page it links to read as the same brand: midnight #0f1220, raised card #1c2036, and the periwinkle→violet accent #6a8bff#b07cff. They commit to that surface rather than following the host theme: a branded object in the conversation, not a themed one.

The gradient is spent once per surface, on the thing that matters most — the grand total on a quote, the total on a detail card, the Book action on the search grid. Webfonts are CSP-blocked, so the type personality comes from treatment (a 0.22em-tracked uppercase eyebrow against tight -0.02em headings, tabular numerals on every amount), exactly as the brand's own page does it.

Templates announce readiness with the ui/initializeui/notifications/initialized handshake. Hosts that gate delivery of the tool result on it would otherwise leave the frame blank.

Every UI-bearing tool result also carries full text and structuredContent, so a host that does not implement the extension still gets a complete answer.

Not yet verified in a real host — the cards have been rendered headlessly against real tool payloads, but never inside Claude, because the connector is not registered as a connector yet.

The host builds a CSP from _meta.ui.csp. We declare no connectDomains (the templates make no network calls) and one resourceDomain (the CloudFront image host). Anything undeclared is blocked, so a template that reached for a CDN would simply fail to render.

Configuration

VariableNotes
MCP_CHANNEL_IDRequired. The sales channel the connector quotes on.
MCP_STAY_URL_TEMPLATERequired. e.g. https://www.elivaas.com/villa/{listingId}
MCP_SEARCH_URL_TEMPLATERequired. e.g. https://www.elivaas.com/search?city={destination}
MCP_UTM_SOURCEAttribution tag, default ai-assistant
MCP_RATE_LIMIT_PER_MINUTE / _BURSTPer client IP, default 120 / 30
SPRING_DATASOURCE_*, REDIS_HOST, REDIS_PORTAs for every other app

Why the app refuses to start

Three startup guards, each of which fails the boot rather than degrading quietly:

  1. Blank MCP_CHANNEL_ID. ListingSearchRepository applies its channel-visibility clause only when the channel is non-blank, and a dateless search never reaches AvailabilityService's fail-closed validator. Browse-without-dates is exactly what a discovery connector does — so a blank channel would publish the entire catalogue, silently. An empty-string env var is enough to trigger it.
  2. Uncurated channel. A channel with no rows in channel_listing matches every listing (the "not yet curated" branch). Harmless internally; a catalogue leak on a connector in a public directory.
  3. Missing link template. Booking happens on elivaas.com, so a result without a working link cannot convert. A connector that emits broken links fails silently in front of guests, which is worse than one that will not start.

Do not "fix" a boot failure by relaxing these. Fix the configuration:

az containerapp update -g dev -n bard-mcp --set-env-vars \
MCP_CHANNEL_ID=<the website's channel id> \
MCP_STAY_URL_TEMPLATE='https://www.elivaas.com/<real-route>/{listingId}' \
MCP_SEARCH_URL_TEMPLATE='https://www.elivaas.com/<real-route>?city={destination}'

The same command against -g bard-prod-rg for production.

Deployment

Standard Azure Container Apps path, same as every other app:

  • non-prod: push to qadeploy-qa.ymldeploy-azure.yml (tag: qa), RG dev.
  • prod: push to maindeploy-main-azure.ymldeploy-azure.yml (tag: prod), RG bard-prod-rg.
  • manual: deploy-azure.ymlRun workflow → service mcp.

Path filters live in .github/app-paths.yml under the mcp key. The connector tracks listing/ and the shared modules but deliberately not commerce-core — it has no booking code and must not acquire any.

bard-mcp is registered in .github/scripts/prod-drift.mjs, so the nightly drift check monitors it like the rest.

Directory listing

To publish in the claude.ai connector directory, supply: connector name, one-line description, icon, privacy-policy URL and terms URL. The server-level model instructions are already set in application.properties (spring.ai.mcp.server.instructions) — they tell the model to call list_destinations before filtering, to call quote_stay before stating any price, never to invent a price, and to hand over the bookingUrl when the guest wants to proceed.