{"openapi":"3.1.0","info":{"title":"Kibana MCP Service for ThinkOwl","description":"<CENTER><img src=\"/static/kibana-mcp.png\"></CENTER><BR>\n                    This service provides access to Kibana over a REST interface and,\n                    via mcpservice.py, over MCP so that an LLM can use it as a tool.<BR>\n                    It provides automatically several endpoints like metrics, docs, redoc and\n                    a working root /.<BR>\n                    It is important to note that the predefined prefix \"kibana-mcp\" needs to be the\n                    exact same endpoint your service will be proxied to by nginx or another webserver.\n                    ","contact":{"url":"http://www.thinkowl.com/"},"license":{"name":"ThinkOwl Usage License","url":"http://www.thinkowl.com/"},"version":"v1"},"paths":{"/metrics":{"get":{"summary":"Combined Metrics","description":"Expose combined metrics from the local instrumentator and the external server.","operationId":"combined_metrics_metrics_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1/test/":{"post":{"summary":"Classify Text","description":"This is a test endpoint that returns the body of the request as a response.\nThe prometheus.autoscaling decorator is used to monitor the endpoint and\nprovide metrics for the autoscaling of the service. It should be used on\nheavy load endpoints only.\nA structured (JSON) request body is used instead of a raw text/plain body,\nbecause mcpservice.py derives its MCP tools from these endpoints and a raw\nbody cannot be expressed as an MCP tool parameter.\nArgs:\n    body (TestRequest): The request body containing the text to classify.\n\nReturns:\n    TestResponse: A pydantic model that is returned as a JSON response.","operationId":"classify_text_v1_test__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TestResponse"}}}},"404":{"description":"Not Found","detail":"Not found"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/valid":{"get":{"summary":"Is Valid","description":"This is an authorization test endpoint that returns the body of the request as a response\nand depends on a valid authorization token that is checked by the authorization module.","operationId":"is_valid_v1_valid_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedMessage"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/indices/":{"post":{"summary":"List Indices","description":"List the Elasticsearch names you can query, with their document counts.\n\nStart here. Data streams and aliases are returned instead of their concrete backing\nindices, because the stream or alias name is what you query: an ILM managed cluster\nhas thousands of \"logs-app-2026.08.30-000042\" style backing indices. System indices\nare never listed. If the result says it was truncated, narrow it with \"pattern\".\n\nArgs:\n    body (IndicesRequest): The pattern to list and the maximum number of entries.\n    claim (dict): The validated claim of the bearer token.\n\nReturns:\n    IndicesResponse: The queryable names with type, document count and status.","operationId":"list_indices_v1_indices__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndicesRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndicesResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedMessage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/mapping/":{"post":{"summary":"Get Index Mapping","description":"Read the schema of an index, alias, data stream or pattern as a field path to type map.\n\nCall this before you construct any query, and pass the same index expression you are\ngoing to search. Do not guess field names. Dynamic templates, analyzer configuration\nand _meta are stripped; only the queryable fields and their types are returned. An ECS\nmapping has more than a thousand fields, so the result is capped: if it says it was\ntruncated, narrow it with \"field_filter\" instead of raising \"max_fields\".\n\nArgs:\n    body (MappingRequest): The index expression, an optional field filter and the field cap.\n    claim (dict): The validated claim of the bearer token.\n\nReturns:\n    MappingResponse: The flattened field path to Elasticsearch type map.","operationId":"get_index_mapping_v1_mapping__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MappingRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MappingResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedMessage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/sample/":{"post":{"summary":"Sample Documents","description":"Return a few whole documents of an index so you can see the real value formats.\n\nUse this after get_index_mapping and before search_query_dsl: the mapping tells you\nthat a field is a keyword, this tells you what its values actually look like. At most\nfive documents are returned, whatever size you ask for.\n\nArgs:\n    body (SampleRequest): The index expression and the number of documents.\n    claim (dict): The validated claim of the bearer token.\n\nReturns:\n    SampleResponse: The sampled documents.","operationId":"sample_documents_v1_sample__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SampleRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SampleResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedMessage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/search/":{"post":{"summary":"Search Query Dsl","description":"Search documents with an Elasticsearch Query DSL query.\n\nCall get_index_mapping for this index expression before you construct the query. Do\nnot guess field names.\n\nPass only the contents of the \"query\" key, for example {\"term\": {\"request_id\": \"abc\"}};\nthe server assembles the rest of the search body and applies its own caps.\n\nTo reconstruct one request as it moved across services, filter on \"request_id\" and sort\nby \"@timestamp\" ascending, which is the default sort. The response reports \"total\", so\nyou can see how much you did not get, and \"last_sort\": pass that value back as\n\"search_after\" with the same sort to page through a trace that is longer than the\nresult cap, without gaps or duplicates.\n\nArgs:\n    body (SearchRequest): Index expression, query, size, source fields, sort and search_after.\n    claim (dict): The validated claim of the bearer token.\n\nReturns:\n    SearchResponse: The matching documents plus the total and the caps that were applied.","operationId":"search_query_dsl_v1_search__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedMessage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/esql/":{"post":{"summary":"Search Esql","description":"Run an ES|QL query, which filters, sorts, projects and aggregates in one round trip.\n\nThis is the most efficient tool for reconstructing a trace, because SORT, KEEP and\nSTATS happen in the cluster. Call get_index_mapping for the FROM target first; do not\nguess field names.\n\nExample: FROM logs-* | WHERE request_id == \"abc\" | SORT @timestamp ASC\n| KEEP @timestamp, servicename, message\n\nEvery query is capped: a query without a LIMIT gets the configured maximum appended,\nand a query whose LIMIT exceeds that maximum is rejected rather than silently\ntruncated. Narrow the query instead of raising the LIMIT.\n\nArgs:\n    body (EsqlRequest): The ES|QL query.\n    claim (dict): The validated claim of the bearer token.\n\nReturns:\n    EsqlResponse: The result columns and rows plus the query as it was executed.","operationId":"search_esql_v1_esql__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EsqlRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EsqlResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedMessage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/v1/aggregate/":{"post":{"summary":"Aggregate Terms","description":"Group documents by a keyword field and count them, without returning the documents.\n\nUse this to explore before you search: which services appear in a session, which log\nlevels occur, how many documents a filter would match. It is far cheaper than\nbrute forcing the same answer out of sampled searches.\n\nCall get_index_mapping first: a \"text\" field is not aggregatable, use its \".keyword\"\nsub field.\n\nArgs:\n    body (AggregateRequest): Index expression, field, optional filter query and bucket limit.\n    claim (dict): The validated claim of the bearer token.\n\nReturns:\n    AggregateResponse: The buckets in descending count order.","operationId":"aggregate_terms_v1_aggregate__post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AggregateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AggregateResponse"}}}},"401":{"description":"Unauthorized","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnauthorizedMessage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}}},"components":{"schemas":{"AggregateRequest":{"properties":{"index":{"type":"string","title":"Index","description":"Index name, alias, data stream or pattern to aggregate over."},"field":{"type":"string","title":"Field","description":"The keyword field to group by, for example 'servicename'. Text fields are not aggregatable, use their '.keyword' sub field."},"query":{"additionalProperties":true,"type":"object","title":"Query","description":"Optional contents of the Elasticsearch 'query' key to filter the documents before grouping. Empty aggregates over every document."},"limit":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Limit","description":"Highest number of buckets to return.","default":20}},"type":"object","required":["index","field"],"title":"AggregateRequest","description":"Request of aggregate_terms."},"AggregateResponse":{"properties":{"index":{"type":"string","title":"Index","description":"The index expression that was aggregated over."},"field":{"type":"string","title":"Field","description":"The field that was grouped by."},"buckets":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Buckets","description":"The buckets, each with 'key' and 'doc_count', in descending count order."},"other_count":{"type":"integer","title":"Other Count","description":"Number of documents in the buckets that did not fit into the limit."},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Warnings about the result."}},"type":"object","required":["index","field","buckets","other_count"],"title":"AggregateResponse","description":"Response of aggregate_terms."},"EsqlRequest":{"properties":{"query":{"type":"string","title":"Query","description":"The ES|QL query, for example 'FROM logs-* | WHERE request_id == \"abc\" | SORT @timestamp ASC | KEEP @timestamp, servicename, message'."}},"type":"object","required":["query"],"title":"EsqlRequest","description":"Request of search_esql."},"EsqlResponse":{"properties":{"columns":{"items":{"additionalProperties":{"type":"string"},"type":"object"},"type":"array","title":"Columns","description":"The result columns, each with 'name' and 'type'."},"rows":{"items":{"items":{},"type":"array"},"type":"array","title":"Rows","description":"The result rows, in the order of the columns."},"row_count":{"type":"integer","title":"Row Count","description":"Number of returned rows."},"applied_query":{"type":"string","title":"Applied Query","description":"The query as it was actually executed, including the LIMIT and the tenant filter this service enforces."},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Warnings about the result, for example that a LIMIT was added."}},"type":"object","required":["columns","rows","row_count","applied_query"],"title":"EsqlResponse","description":"Response of search_esql."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"IndicesRequest":{"properties":{"pattern":{"type":"string","title":"Pattern","description":"Index pattern to list, for example 'logs-*'. Defaults to every non system index.","default":"*"},"limit":{"type":"integer","maximum":200.0,"minimum":1.0,"title":"Limit","description":"Highest number of entries to return. Hard maximum 200.","default":50}},"type":"object","title":"IndicesRequest","description":"Request of list_indices."},"IndicesResponse":{"properties":{"indices":{"items":{"additionalProperties":{"type":"string"},"type":"object"},"type":"array","title":"Indices","description":"One entry per queryable name, with 'index', 'type' (data_stream, alias or index), 'docs.count' and 'status'."},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Warnings about the result, for example that the list was truncated."}},"type":"object","required":["indices"],"title":"IndicesResponse","description":"Response of list_indices."},"MappingRequest":{"properties":{"index":{"type":"string","title":"Index","description":"Index name, alias, data stream or pattern to read the schema of, for example 'logs-*'."},"field_filter":{"type":"string","title":"Field Filter","description":"Optional glob or substring that the field path must match, for example 'request' or '*.keyword'. Empty returns every field.","default":""},"max_fields":{"type":"integer","maximum":2000.0,"minimum":1.0,"title":"Max Fields","description":"Highest number of fields to return. An ECS mapping has more than a thousand.","default":200}},"type":"object","required":["index"],"title":"MappingRequest","description":"Request of get_index_mapping."},"MappingResponse":{"properties":{"index":{"type":"string","title":"Index","description":"The index expression the schema was read for."},"fields":{"additionalProperties":{"type":"string"},"type":"object","title":"Fields","description":"Field path to Elasticsearch type, for example {'@timestamp': 'date', 'message': 'text'}."},"field_count":{"type":"integer","title":"Field Count","description":"Number of returned fields."},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Warnings about the result, for example that the field list was truncated."}},"type":"object","required":["index","fields","field_count"],"title":"MappingResponse","description":"Response of get_index_mapping."},"SampleRequest":{"properties":{"index":{"type":"string","title":"Index","description":"Index name, alias, data stream or pattern to sample from."},"size":{"type":"integer","minimum":1.0,"title":"Size","description":"Number of documents to return. Clamped to 5.","default":3}},"type":"object","required":["index"],"title":"SampleRequest","description":"Request of sample_documents."},"SampleResponse":{"properties":{"index":{"type":"string","title":"Index","description":"The index expression the documents were read from."},"documents":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Documents","description":"The sampled documents."},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Warnings about the result, for example that the size was clamped."}},"type":"object","required":["index","documents"],"title":"SampleResponse","description":"Response of sample_documents."},"SearchRequest":{"properties":{"index":{"type":"string","title":"Index","description":"Index name, alias, data stream or pattern to search, for example 'logs-*'."},"query":{"additionalProperties":true,"type":"object","title":"Query","description":"The contents of the Elasticsearch 'query' key only, for example {'term': {'request_id': 'abc'}}. The server assembles the rest of the search body."},"size":{"type":"integer","minimum":1.0,"title":"Size","description":"Number of hits to return. Clamped to the configured maximum, by default 50.","default":10},"source_fields":{"items":{"type":"string"},"type":"array","title":"Source Fields","description":"Document fields to return, mapped to the Elasticsearch '_source' parameter. Empty returns the whole document."},"sort":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Sort","description":"Sort order, for example [{'@timestamp': 'asc'}]. Defaults to ascending @timestamp."},"search_after":{"items":{},"type":"array","title":"Search After","description":"The 'last_sort' value of the previous response, to fetch the next page of the same sorted result set."}},"type":"object","required":["index","query"],"title":"SearchRequest","description":"Request of search_query_dsl."},"SearchResponse":{"properties":{"total":{"type":"integer","title":"Total","description":"Number of documents that matched, which can be far more than were returned."},"total_relation":{"type":"string","title":"Total Relation","description":"'eq' if total is exact, 'gte' if it is a lower bound."},"returned":{"type":"integer","title":"Returned","description":"Number of hits in this response."},"applied_size":{"type":"integer","title":"Applied Size","description":"The size that was actually applied after clamping."},"hits":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Hits","description":"The matching documents, each with '_index', '_id', '_source' and 'sort'."},"last_sort":{"items":{},"type":"array","title":"Last Sort","description":"Sort value of the last hit. Pass it as 'search_after' to get the next page."},"notes":{"items":{"type":"string"},"type":"array","title":"Notes","description":"Warnings about the result, for example that the size was clamped."}},"type":"object","required":["total","total_relation","returned","applied_size","hits"],"title":"SearchResponse","description":"Response of search_query_dsl."},"TestRequest":{"properties":{"text":{"type":"string","title":"Text"}},"type":"object","required":["text"],"title":"TestRequest","description":"This is a structured example request that is accepted by the endpoint /v1/test/"},"TestResponse":{"properties":{"value":{"type":"string","title":"Value"}},"type":"object","required":["value"],"title":"TestResponse","description":"This is a structured example response that is returned by the endpoint /v1/test/"},"UnauthorizedMessage":{"properties":{"detail":{"type":"string","title":"Detail","default":"Bearer token missing or unknown"}},"type":"object","title":"UnauthorizedMessage","description":"A message to be returned when the user is unauthorized."},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}