# chathome API & MCP tool reference

MCP endpoint: https://chathome.lu/api/mcp (Streamable HTTP, MCP spec 2025-11-25)
Auth: OAuth 2.1 + PKCE (dynamic client registration) or `X-API-Key: cht_...`
Health / server info (no credential): https://chathome.lu/api/mcp/health
Discovery methods (initialize, ping, tools/list, prompts/list, resources/list)
need no credential, and `search_vibe` + `get_market_context` also execute
anonymously under a per-IP cap; every other tools/call needs a credential.
Public REST spec (no credential): https://chathome.lu/openapi.json
Discovery: https://chathome.lu/.well-known/mcp.json · OAuth metadata:
https://chathome.lu/api/mcp/oauth/.well-known/oauth-authorization-server

## Tool parameters

### search_vibe (scope: listings:read)
- query (string, required): natural-language description, e.g. "quiet 2-bed
  near Kirchberg with a balcony under €2000"
- intent (string, optional): "rent" | "buy" (default "rent")
- limit (number, optional): 1-50, default 20
- locale (string, optional): en | fr | de | lu | pt ("lb" accepted as deprecated
  alias for "lu")
- includePhotos (boolean, optional, default true): attach thumbnail photos of
  the top results as MCP image content blocks

### search_rentals / search_for_sale (scope: listings:read)
- commune (string, optional): commune name or slug
- minPrice / maxPrice (number, optional): EUR (monthly rent, or sale price)
- minBeds (number, optional)
- minAreaSqm (number, optional)
- propertyType (string, optional): apartment | house | studio | room
- limit (number, optional): 1-50, default 20
- locale (string, optional)
- includePhotos (boolean, optional, default true)

### render_listing_cards (scope: listings:read)
- Call only after a property search.
- listings (array, required, 1-8): exact listing IDs in display order, with
  optional exact score and matchReasons copied from search_vibe.
- heading (string, optional): short heading grounded in the user's request.
- locale (string, optional): en | fr | de | lu | pt.
- Returns canonical listing facts again and links them to an interactive MCP
  Apps carousel. Clients without MCP Apps still receive structured data.

### get_listing (scope: listings:read)
- id (string, required) · locale (string, optional) ·
  includePhotos (boolean, optional, default true)

### get_listing_truth (scope: listings:read)
- id (string, required). Returns known facts with sources and freshness,
  unknown-but-important facts, and a privacy-safe summary of user-reported
  corrections.

### get_market_context (scope: market:read)
- commune (string, required): slug, e.g. "luxembourg", "esch-sur-alzette"
- locale (string, optional)

### get_town_agencies (scope: market:read)
- commune (string, required): one of the supported town names or slugs
- locale (string, optional)
- Returns a dated active-listing count and buy/rent split, plus every agency's
  current town stock and asking-price ranges. Its figures match the localized
  `/estate-agents/{slug}` page because both read the same service.

### get_price_estimate (scope: market:read)
AI valuation model trained and backtested on historical Luxembourg listing
asking prices, not deed-level sales. Provide location, property type, area,
and specs; comparable statistics in the response are listing comparables.

### get_affordability (scope: market:read)
- Base inputs: `annualIncome`, `monthlyDebts`, `savingsAvailable`,
  `householdType`, `employmentType`, and `residencyType`.
- To apply Bëllegen Akt, all three facts must be explicit:
  `residenceUse: "primary"`; `bellegenAktEligibleBuyers` (confirmed
  eligible acquirers); and `bellegenAktCreditAvailable` (their confirmed
  aggregate unused balance). Omit confirmation fields or pass
  `residenceUse: "unknown"` to preserve the safe unconfirmed state; pass zero
  only when a zero count or balance has actually been confirmed.
- `isPersonalResidence` is an optional deprecated compatibility flag; prefer
  `residenceUse`.
- `isFirstTimeBuyer` is an optional deprecated compatibility signal (default
  false). It may
  support profile recommendations, but never confirms Bëllegen eligibility or
  balance.
- The response includes `maxPropertyPriceCostBasis` as `complete` or
  `known_costs_only`. Never use a known-cost-only figure as a hard search
  ceiling or a definitive affordability verdict; disclose
  `acquisitionCosts.unknownComponents`.
- Uses current Luxembourg mortgage rates and evidence-based acquisition costs.

### get_commute_times (scope: market:read)
- commune (string, required)
- destination: "luxembourg-city" | "kirchberg" | "belval"
- Four transport modes returned (car, transit, bike, walk).

### analyze_lease (scope: documents:write)
- Lease PDF as base64, or raw text. No URL input. Requires OAuth user auth.
  Returns a compact risk summary; full analysis is stored in the user's
  chathome account.

### submit_inquiry (scope: inquiries:write)
- listingId (string, required) · message (string, 10-5000 chars, required)
- contactEmail, agentName (optional)
- chathome-hosted listing → creates a conversation thread; external agency
  listing → returns agency contact details for a clean hand-off.

### get_inquiry_status (scope: inquiries:read)
- conversationId (string, required). Status: awaiting_reply | replied.

## Response shapes

Search tools return `{ listings: AgentListing[], total, truncated? }`:

```typescript
type AgentListing = {
  id: string; url: string; title: string;
  price: number | null; currency: "EUR";
  listingType: "rent" | "sale"; propertyType: string | null;
  city: string | null; district: string | null;
  beds: number | null; baths: number | null; areaSqm: number | null;
  energyClass: string | null; imageUrl: string | null;
  photoUrls?: string[];            // up to 5, cover image first
  description?: string | null;     // short plain-text summary
  pricePerSqm?: number | null; charges?: number | null;
  features?: string[];             // amenity tags, e.g. "balcony"
  agencyName: string | null; latitude: number | null; longitude: number | null;
};
```

Vibe results additionally carry `score` (0-100) and `matchReasons`
(up to 3 strings).

Unless `includePhotos: false`, search and detail responses append thumbnail
photos of the top results as MCP `image` content blocks (each preceded by a
text label naming the listing) — display them next to the matching listing
when presenting results to a human.

## MCP prompts (built-in)

- find-family-home — guided family-friendly rental search
  (args: commune?, budget?, beds?)
- compare-communes — side-by-side commune comparison (commune_a, commune_b)
- match-properties — affordability + vibe search composed; only applies a
  calculated hard ceiling and definitive within / stretch / over labels when
  `maxPropertyPriceCostBasis` is `complete`; known-cost-only comparisons
  remain provisional

## Public no-auth endpoints

- https://chathome.lu/listings/feed.json — bulk feed of quality-gated active listings
- https://chathome.lu/listings/{id}/md — full listing record, Markdown
- https://chathome.lu/api/listings/public/{id} — full listing record, JSON
- https://chathome.lu/en/prices/md — Luxembourg price index, Markdown
- https://chathome.lu/en/insights/{slug}/md — localized Insights article, Markdown
- https://chathome.lu/en/frontalier/{town}/md — localized cross-border town guide, Markdown
- https://chathome.lu/openapi.json — OpenAPI 3.1 spec for the public read API
  (mirrored at https://chathome.lu/.well-known/openapi.json)
- https://chathome.lu/llms.txt — MCP developer reference
- https://chathome.lu/llms-full.txt — content index (listings, commune guides, blog)
- https://chathome.lu/.well-known/agent-card.json — A2A agent card
  (task endpoint https://chathome.lu/api/a2a)
