MCP: Log Search

MCP server tools for querying the three types of logs HDH stores in Elasticsearch.

HotelDataHub logs to Elasticsearch across three distinct index families, each with a different purpose and content. The MCP server (POST /mcp) exposes a specialized tool for each one, plus a unifying tool for when you don't know which index the data is in. All of them require the logs:read scope and return only data for the specified customer.

Table of contents

The three Elasticsearch applications

Application Indices What it contains When to use it
HTTP backend_api_{year}_{month}_{prefix} Raw HTTP requests/responses received by HDH, both from the authenticated API and from push integrations. See the literal HTTP traffic: path, method, status code and, on demand, body.
Integrations integration_log_{year}_{month}_{source} Each entry received from a PMS/BE integration (booking, guest, event…) exactly as it arrived, with its original payload in data, before normalization. "What did PMS X send us?", auditing what was received, debugging a booking.
Internal hdh_log_{year}_{month} HDH's own internal events (failed movement locks, contact not found, processing warnings/errors). Not business data. Diagnosing why something wasn't processed.

Customer scoping differs by application: authenticated API traffic is scoped by apiuser_id and cross-checked against customer_id; push traffic, which may be unauthenticated, is scoped by the request's URL. The integrations and internal applications are scoped by the document's customer_id field. Global internal events (without customer_id) are never returned via MCP.

Choosing between API traffic and push traffic

Company.use_api indicates the recommended flow, but it's only a guiding signal: the existence of the ApiUser and of documents in Elasticsearch is the source of truth.

  1. Run list_api_users(customer_code, company_name) to locate the company.
  2. If company.use_api is true, use search_api_requests and then, only if needed, get_api_request_body.
  3. If company.use_api is false, use list_loggable_integrations, search_push_requests and get_push_request_body.
  4. If the flag doesn't match the case being investigated, it doesn't block an explicit search: an ApiUser belonging to the customer can still be queried by its ID.

Example for investigating "requests received by the HDH API":

list_api_users(customer_code="CHAIN01")
search_api_requests(customer_code="CHAIN01", apiuser_id=123, days=7)
get_api_request_body(customer_code="CHAIN01", apiuser_id=123,
                     request_id="abc123", index="backend_api_2026_8_contacts")

Example for a "Paraty integration" when it's not yet known how it sends data:

list_api_users(customer_code="CHAIN01", company_name="Paraty")

If the result contains a user and company.use_api=true, continue with search_api_requests. If there's no user, or use_api=false, check the push integrations with list_loggable_integrations and search_push_requests.

Available tools

Common date range parameters across all of them: if from_date/to_date aren't provided (format YYYY-MM-DD, inclusive), the last days days are queried (7 by default). The maximum result size is 50 (size, 20 by default), always from most recent to oldest.

list_api_users

Lists the customer's ApiUsers. Accepts company_name as a case-insensitive partial filter and active_only (true by default). Returns:

  • ApiUser ID and username.
  • Status, last connection and names of its permissions.
  • Customer ID, code and name.
  • Company ID, name and use_api.

Does not return passwords, hashes, tokens or internal normalization flags.

search_api_requests

Searches an ApiUser's authenticated traffic across all monthly backend_api_* indices in the range. Before querying, it validates that the user belongs to the customer_code. The path filter only narrows documents; it isn't used to infer the index, so paths like /api/v1/contacts/ are searched correctly.

Parameter Description
customer_code Chain code. Required.
apiuser_id ID obtained from list_api_users. Required.
path_contains Optional path fragment.
method Optional HTTP method, e.g. GET or POST.
status_code Optional HTTP status, e.g. 200, 400 or 500.
days, from_date, to_date, size Inclusive range and size; last 7 days and max 50 results.

Each result includes path, method, status_code, @timestamp, host, customer_id, company_id, apiuser_id, _id and _index. It doesn't include bodies, responses or headers, to avoid exposing credentials or personal data in listings.

get_api_request_body

Retrieves the body of a single request located with search_api_requests. Requires the exact request_id and index returned by the search. Only accepts backend_api_* indices and re-validates that the document belongs to the given ApiUser, customer and company. If the body is JSON, it's returned indented; it also includes the path, method, status and timestamp.

search_push_requests (HTTP application)

Searches push requests (webhooks) received from a PMS/Booking Engine for the customer's hotels. Returns only metadata; the full body is obtained with get_push_request_body. Requires the integration name (its Elastic configuration lives in the frontmatter of each integration's documentation). Use list_loggable_integrations to discover which integrations are queryable and which hotels they correspond to.

search_integration_events (integrations application)

Searches the raw entries recorded by each integration for a customer.

Parameter Description
customer_code Chain code in Fideltour (e.g. CHAIN01). Required.
source Short name of the integration (index suffix), e.g. sihot, opera, cloudbeds, winhotelv2. If omitted, searches across all of the customer's sources.
company_id, connection_id, place_id Optional filters by company, connection or hotel.
event_type Event type (booking, guest, event, roomstay, loyalty_member…).
status received, processed or error.
days, from_date, to_date, size Range and size (see above).

Response:

{
  "customer_code": "CHAIN01",
  "count": 2,
  "sources_found": ["sihot"],
  "events": [
    {
      "@timestamp": "2026-06-22T08:14:03Z",
      "source": "sihot",
      "status": "received",
      "event_type": "booking",
      "company_id": 15,
      "company": "Sihot",
      "customer_id": 314,
      "customer": "Cadena Demo",
      "place_id": 42,
      "place": "Hotel Demo",
      "connection_id": 1234,
      "data": "{...integration's original payload...}",
      "_id": "abc123",
      "_index": "integration_log_2026_06_sihot"
    }
  ]
}

The sources_found field lists the sources present in the results, useful when searching without specifying source.

search_internal_logs (internal application)

Searches internal HDH system events for a customer.

Parameter Description
customer_code Chain code. Required.
log_type Event identifier, e.g. movement_lock_failed, contact_not_found.
level warning, error or info.
company_id, connection_id Optional filters.
days, from_date, to_date, size Range and size.

Response:

{
  "customer_code": "CHAIN01",
  "count": 1,
  "logs": [
    {
      "@timestamp": "2026-06-22T08:10:00Z",
      "log_type": "movement_lock_failed",
      "level": "warning",
      "company_id": 15,
      "company": "Sihot",
      "customer_id": 314,
      "connection_id": 1234,
      "place_id": 42,
      "data": "{...event details...}",
      "_id": "def456",
      "_index": "hdh_log_2026_06"
    }
  ]
}

When you don't know which application the data is in, search_logs runs the query against all three and combines the results, tagging each one with the application field (http, integration or internal).

Parameter Description
customer_code Chain code. Required.
applications Subset of ["http", "integration", "internal"]. All three by default.
source Integrations app filter (short name).
integration Integration name for the HTTP app (e.g. Sihot).
event_type, status Integrations app filters.
level, log_type Internal app filters.
status_code, method HTTP app filters.
days, from_date, to_date, size Range and size of the combined result.

Cost of the HTTP app: resolving the HTTP application requires knowing the integration. If integration is provided, it only searches that one; otherwise, it searches all of the customer's queryable integrations with a reduced limit per integration, which can be slow. Exclude "http" from applications to skip it, or provide integration to narrow it down.

Response:

{
  "customer_code": "CHAIN01",
  "counts": {"http": 0, "integration": 2, "internal": 1},
  "count": 3,
  "results": [
    {"application": "integration", "@timestamp": "2026-06-22T08:14:03Z", "...": "..."},
    {"application": "internal", "@timestamp": "2026-06-22T08:10:00Z", "...": "..."}
  ],
  "notes": ["http: ..."]
}

The notes field collects diagnostic warnings (for example, why an application was skipped or narrowed down).

GDPR Note

The original integration payload (data in search_integration_events and in the integration results of search_logs) contains personal guest data, just like the bodies from get_push_request_body and get_api_request_body. Only fetch bodies individually, when needed, and don't expose them beyond what was requested.