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
- Choosing between API traffic and push traffic
- Available tools
- list_api_users
- search_api_requests
- get_api_request_body
- search_push_requests (HTTP application)
- search_integration_events (integrations application)
- search_internal_logs (internal application)
- search_logs (unified search)
- GDPR Note
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.
- Run
list_api_users(customer_code, company_name)to locate the company. - If
company.use_apiistrue, usesearch_api_requestsand then, only if needed,get_api_request_body. - If
company.use_apiisfalse, uselist_loggable_integrations,search_push_requestsandget_push_request_body. - If the flag doesn't match the case being investigated, it doesn't block an explicit
search: an
ApiUserbelonging 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:
ApiUserID 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_foundfield lists the sources present in the results, useful when searching without specifyingsource.
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"
}
]
}
search_logs (unified search)¶
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
integrationis 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"fromapplicationsto skip it, or provideintegrationto 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.