Appearance
Features → code
A complete map of what the platform does and where it lives in the code. Every capability is exposed two ways — as MCP tools (for AI clients) and as REST /v1 endpoints (for the webapp and HTTP clients) — over the single backend in apps/mcp-server, sharing the same repositories, domain schemas, and scopes.
For the exhaustive call-by-call lists see MCP tools and the REST API. This page is the conceptual tour.
Where things live
- Domain schemas (single source of truth):
packages/shared/src/*.ts(Data models) - MCP tool registration:
apps/mcp-server/src/tools/register*.ts - REST routes:
apps/mcp-server/src/rest/router.ts - Persistence (Kysely repositories):
apps/mcp-server/src/db/*.ts
Listings & search
The core: vehicle listings (cars, motorcycles, scooters), owned by a dealer and optionally attributed to an organization.
| Capability | MCP tool | REST | Code |
|---|---|---|---|
| Search + filter + facets + sort + pagination | search_vehicles | GET /v1/listings | db/repository.ts (search) |
| Fetch one (by id or VIN) | get_vehicle | GET /v1/listings/:id | db/repository.ts |
| Create / update / delete | create_listing · update_listing · delete_listing | POST · PATCH · DELETE /v1/listings/:id | db/repository.ts |
| Bulk create / delete (≤50) | bulk_create_listings · bulk_delete_listings | POST /v1/listings/bulk · /bulk-delete | db/repository.ts |
| The caller's own listings (all statuses) | — | GET /v1/me/listings | db/repository.ts |
Search filters on type, make, model, year/price/mileage ranges (price CHF-normalized for cross-currency comparison), fuel, transmission, condition, drive, energy class, and radius. Writes are owner-scoped — a dealer can only mutate their own listings (or those of an org they belong to) — enforced by writeAuthFilter in db/repository.ts. Registered in tools/registerTools.ts.
Natural-language search
Parse free text like "cheap electric SUV after 2020" into a structured query, then run it.
- Tool:
search_vehicles_nl· REST:POST /v1/search/nl - Code:
apps/mcp-server/src/search/nlSearch.ts—NlSearchProviderinterface with a deterministicHeuristicNlSearchProvider(make aliases, fuel/type keywords, price/year extraction); pluggable for an LLM provider later.
Analytics: price rating, similar, compare
- Price rating — fair-price label (
great/good/fair/high) vs. the median of comparable active listings. Toolget_price_rating·GET /v1/listings/:id/price-rating. - Similar — ranks by closeness in price/year/mileage (bonus for same model). Tool
get_similar_vehicles·GET /v1/listings/:id/similar. - Compare — fetch 2–5 listings side-by-side. Tool
compare_vehicles·GET /v1/listings/compare?ids=…. - Code:
apps/mcp-server/src/analytics/analytics.ts(priceRating,similarListings,compareListings).
Seller profiles & locations
A sellers row is keyed by the Keycloak subject and auto-provisioned on first write.
- Profile read/update:
get_my_profile·update_my_profile·GET/PUT /v1/me. - Public profile:
GET /v1/dealers/:idand/v1/dealers/slug/:slug. - Locations (branches with address, hours, geocoded):
add_location·update_location·remove_location·GET/POST/PATCH/DELETE /v1/me/locations. - Code:
apps/mcp-server/src/db/sellerRepository.ts; registered intools/registerSellerTools.ts.
Organizations (shared storefronts)
A company many users belong to; a listing can be attributed to an org and any member can manage it, while the creator stays the dealerId for audit.
- Create/update, membership (owner/member roles), public org page, org inventory.
- Tools in
tools/registerOrganizationTools.ts:create_organization,update_organization,list_my_organizations,get_organization,list_organization_members,add_organization_member,remove_organization_member. - REST under
/v1/organizations/*and/v1/me/organizations. - Code:
apps/mcp-server/src/db/organizationRepository.ts.
Buyer accounts: favorites, saved searches, alerts
- Account profile + notification prefs:
get_account·update_account·GET/PUT /v1/me/account. - Favorites (watchlist):
list_favorites·add_favorite·remove_favorite·GET/POST/DELETE /v1/me/favorites. - Saved searches (+ new-match alerts):
list_saved_searches·save_search·delete_saved_search·GET/POST/DELETE /v1/me/saved-searches. - Alert delivery:
apps/mcp-server/src/notify/savedSearchAlerts.ts(runSavedSearchAlerts, cron-friendly, triggerable viaPOST /v1/admin/run-alerts) →notify/notifier.ts(email/push interface). - Code:
apps/mcp-server/src/db/accountRepository.ts; tools intools/registerAccountTools.ts.
Account deletion
The GDPR / nDSG "right to erasure": a user can permanently delete their own account.
- REST:
DELETE /v1/me/account(self-scoped,listings:read). One Postgres transaction hard-deletes every row keyed to the user — listings, seller profile + locations, buyer profile, favorites (theirs and others' favorites of their listings, FK-safe), saved searches, reviews (authored and received), org memberships and audit entries — then the Keycloak login is deleted so the account can no longer sign in. (Organizations the user created are intentionally left intact; only their membership is removed.) - Graceful degradation: the data erasure is the transaction; the Keycloak deletion is best-effort and runs after it. With no service account configured it's a no-op (the login is left for an operator); a failure is logged + sent to Sentry but never fails the request. With no deletion repository wired the endpoint returns
501. - Keycloak login removal uses a least-privilege confidential service-account client (
realm-management:manage-usersonly), provisioned bykeycloak/provision-account-admin.shand configured viaKEYCLOAK_ADMIN_CLIENT_ID/KEYCLOAK_ADMIN_CLIENT_SECRET. - Webapp: the account page's "Danger zone" → Delete my account (confirm) calls the endpoint and signs the user out.
- Code:
apps/mcp-server/src/db/accountDeletion.ts(Postgres transaction + in-memory purge),apps/mcp-server/src/auth/keycloakAdmin.ts(Keycloak user deleter); route inrest/router.ts.
Seller reviews & ratings
1–5 star buyer reviews of a seller, aggregated into ratingAvg/ratingCount; carry a moderation status so abusive content can be hidden.
list_seller_reviews·review_seller·moderate_review;GET/POST /v1/dealers/:id/reviews,PATCH /v1/admin/reviews/:id/moderation.- Code:
apps/mcp-server/src/db/reviewRepository.ts; tools intools/registerEngagementTools.ts.
Media
- Flows: signed HMAC upload tickets (
request_upload), inline base64 (upload_media), and authenticated binary upload (POST /v1/media). Served publicly at/media/:key(immutable cache). - Limits/types: ≤25 MiB (
MEDIA_MAX_BYTES); JPEG/PNG/WebP/GIF/AVIF + MP4/WebM/MOV. - Code:
apps/mcp-server/src/media/{store.ts,uploadTicket.ts,uploadPage.ts}; tools intools/registerMediaTools.ts.
Geocoding
Forward geocoding for location pickers, region-routed (Swisstopo GeoAdmin by default, CH-accurate).
- REST:
GET /v1/geo/suggest?q=…. Code:apps/mcp-server/src/geo/{geocoder.ts,geoAdmin.ts}. - Config:
GEOCODER_PROVIDER,GEOCODER_DEFAULT_COUNTRY,GEOCODER_TIMEOUT_MS.
Moderation, soft-delete & audit
- Moderation status (
approved/pending/rejected) hides listings from public search; soft-delete (deleted_at) keeps inventory recoverable. Bothlistings:moderate-only. - Tools:
moderate_listing,restore_listing,list_moderation_queue,list_audit_log. REST under/v1/admin/*. - Append-only audit log records every listing write (create/update/delete/restore/moderate).
- Code: moderation/soft-delete in
db/repository.ts;db/auditRepository.ts.
SEO & structured data
Server-rendered listing/dealer/org pages with schema.org JSON-LD (AutoDealer, AggregateRating) for crawlers and social graphs. The edge Caddy proxies crawler paths (/listings/*, /dealers/*, sitemap.xml, robots.txt, llms.txt) to the mcp-server.
- Code:
apps/mcp-server/src/seo/{render.ts,router.ts}.
The webapp
apps/webapp — a React 19 + Vite SPA that drives the REST API: browse/search, listing detail with price-rating and similar rails, favorites, saved searches, a map radius search, a dealer dashboard (create/edit/delete + profile + locations), organizations, an account hub, and seller reviews. Auth is a Keycloak OIDC redirect (token in localStorage).
Transports & auth
The same tool/route logic runs over two transports — see Architecture:
- stdio (
transport/stdio.ts) — local-trust, no token, fixed dev principal. - HTTP Streamable (
transport/http.ts) — OAuth 2.1 bearer at/mcp, RFC 8707 audience. - Auth:
auth/{bearer.ts,principal.ts,resourceMetadata.ts}; Keycloak realm inkeycloak/realm-export.json. Scopes:listings:read·listings:write·listings:moderate.