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
search_staysUp to 10 priced, available stays, each with a bookingUrl
view_propertyOne villa: amenities, bedrooms, images, cancellation policy, price
ask_about_propertiesAnswers about villas by id: facts, prices, open dates

Three tools, two of them UI-bearing, mirroring the shape Booking.com's connector publishes. It is a deliberate ceiling rather than a starting point: every extra tool is another entry in the host's permission list and another chance for the model to pick the wrong one. Destination discovery folded into search_stays (an unrecognised destination comes back naming the ones we do sell), and pricing and calendar folded into view_property and ask_about_properties, which is where a guest was always going to ask for them.

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)

Two 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 search_stays to show villas and ask_about_properties for follow-ups rather than searching again, never to invent a price, and to hand over the bookingUrl when the guest wants to proceed.