Skip to main content
Glama

Kibana MCP Server

by TocharianOU
base-tools.ts11.3 kB
import { z } from "zod"; import type { ServerBase, KibanaClient, ToolResponse } from "./types"; // Import API index and search logic import fs from 'fs'; import path from 'path'; import yaml from 'js-yaml'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); interface ApiEndpoint { path: string; method: string; description?: string; summary?: string; parameters?: any[]; requestBody?: any; responses?: any; deprecated?: boolean; tags?: string[]; } let apiEndpointIndex: ApiEndpoint[] = []; let isIndexBuilt = false; let YAML_FILE_PATH = ''; let openApiDoc: any = null; async function buildApiIndex(): Promise<void> { if (isIndexBuilt) return; // Enhanced path resolution for both compiled JS and direct TS execution const possiblePaths = [ // Environment variable takes highest priority process.env.KIBANA_OPENAPI_YAML_PATH, // Current working directory path.join(process.cwd(), 'kibana-openapi-source.yaml'), // Relative to the source file path.join(__dirname, 'kibana-openapi-source.yaml'), // One level up from source file (for ts-node execution) path.resolve(__dirname, '..', 'kibana-openapi-source.yaml'), // dist directory for compiled JS path.join(process.cwd(), 'dist', 'src', 'kibana-openapi-source.yaml') ].filter((p): p is string => typeof p === 'string' && p.length > 0); for (const p of possiblePaths) { if (fs.existsSync(p)) { YAML_FILE_PATH = p; console.warn(`Using YAML file from: ${p}`); break; } } if (!YAML_FILE_PATH) { console.error('Could not find kibana-openapi-source.yaml file'); isIndexBuilt = true; return; } try { const yamlContent = fs.readFileSync(YAML_FILE_PATH, 'utf8'); openApiDoc = yaml.load(yamlContent); if (!openApiDoc || !openApiDoc.paths) { throw new Error('Invalid YAML file structure: missing paths'); } for (const [pathStr, pathObj] of Object.entries(openApiDoc.paths)) { for (const [method, methodObj] of Object.entries(pathObj as Record<string, any>)) { if (["get", "post", "put", "delete", "patch"].includes(method)) { apiEndpointIndex.push({ path: pathStr as string, method: method.toUpperCase(), description: (methodObj as any).description, summary: (methodObj as any).summary, parameters: (methodObj as any).parameters, requestBody: (methodObj as any).requestBody, responses: (methodObj as any).responses, deprecated: (methodObj as any).deprecated, tags: (methodObj as any).tags }); } } } isIndexBuilt = true; } catch (error) { console.error('Error loading or parsing YAML file:', error); throw error; } } function searchApiEndpoints(query: string): ApiEndpoint[] { if (!isIndexBuilt) throw new Error('API index not built yet'); const q = query.toLowerCase(); return apiEndpointIndex.filter(e => e.path.toLowerCase().includes(q) || (e.description && e.description.toLowerCase().includes(q)) || (e.summary && e.summary.toLowerCase().includes(q)) || (e.tags && e.tags.some(tag => tag.toLowerCase().includes(q))) ); } // Recursively resolve $ref references function resolveRef(obj: any, doc: any, seen = new Set()): any { if (!obj || typeof obj !== 'object') return obj; if (Array.isArray(obj)) return obj.map(item => resolveRef(item, doc, seen)); if (obj.$ref) { const ref = obj.$ref; if (seen.has(ref)) return { circularRef: ref }; seen.add(ref); const parts = ref.replace(/^#\//, '').split('/'); let target = doc; for (const p of parts) { if (target && typeof target === 'object') target = target[p]; else return { invalidRef: ref }; } return resolveRef(target, doc, seen); } const result: any = {}; for (const k of Object.keys(obj)) { result[k] = resolveRef(obj[k], doc, seen); } return result; } export function registerBaseTools(server: ServerBase, kibanaClient: KibanaClient, defaultSpace: string) { // Tool: Get Kibana server status server.tool( "get_status", `Get Kibana server status with multi-space support`, z.object({ space: z.string().optional().describe("Target Kibana space (optional, defaults to configured space)") }), async ({ space }): Promise<ToolResponse> => { try { const targetSpace = space || defaultSpace; const response = await kibanaClient.get('/api/status', { space }); return { content: [ { type: "text", text: `[Space: ${targetSpace}] Kibana server status: ${JSON.stringify(response, null, 2)}` } ] }; } catch (error) { console.error(`Failed to get server status: ${error}`); return { content: [ { type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Tool: Execute custom API request server.tool( "execute_kb_api", `Execute a custom API request for Kibana with multi-space support`, z.object({ method: z.enum(['GET', 'POST', 'PUT', 'DELETE']), path: z.string(), body: z.any().optional(), params: z.record(z.union([z.string(), z.number(), z.boolean()])).optional(), space: z.string().optional().describe("Target Kibana space (optional, defaults to configured space)") }), async ({ method, path, body, params, space }): Promise<ToolResponse> => { try { const targetSpace = space || defaultSpace; let url = path; if (params) { const queryString = new URLSearchParams( Object.entries(params).map(([key, value]) => [key, String(value)]) ).toString(); url += `?${queryString}`; } let response; switch (method.toLowerCase()) { case 'get': response = await kibanaClient.get(url, { space }); break; case 'post': response = await kibanaClient.post(url, body, { space }); break; case 'put': response = await kibanaClient.put(url, body, { space }); break; case 'delete': response = await kibanaClient.delete(url, { space }); break; default: throw new Error(`Unsupported HTTP method: ${method}`); } return { content: [ { type: "text", text: `[Space: ${targetSpace}] API response: ${JSON.stringify(response, null, 2)}` } ] }; } catch (error) { console.error(`API request failed: ${error}`); return { content: [ { type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); // Tool: Search Kibana API endpoints (by keyword) server.tool( "search_kibana_api_paths", `Search Kibana API endpoints by keyword`, z.object({ search: z.string().describe('Search keyword for filtering API endpoints') }), async ({ search }): Promise<ToolResponse> => { await buildApiIndex(); const endpoints = searchApiEndpoints(search); return { content: [ { type: "text", text: `Found ${endpoints.length} API endpoints: ${JSON.stringify(endpoints.map(e => ({ method: e.method, path: e.path, summary: e.summary, description: e.description })), null, 2)}` } ] }; } ); // Tool: List all Kibana API endpoints (no filter, full list, resource style) server.tool( "list_all_kibana_api_paths", `List all Kibana API endpoints as a resource list`, z.object({}), async (): Promise<ToolResponse> => { await buildApiIndex(); const endpoints = apiEndpointIndex.map(e => ({ method: e.method, path: e.path, summary: e.summary, description: e.description })); return { content: [ { type: "text", text: JSON.stringify({ contents: [ { uri: "kibana-api://paths", mimeType: "application/json", text: JSON.stringify(endpoints, null, 2) } ] }, null, 2) } ] }; } ); // Tool: Get details for a specific Kibana API endpoint server.tool( "get_kibana_api_detail", `Get details for a specific Kibana API endpoint`, z.object({ method: z.string().describe("HTTP method, e.g. GET, POST, PUT, DELETE"), path: z.string().describe("API path, e.g. /api/actions/connector_types") }), async ({ method, path }): Promise<ToolResponse> => { await buildApiIndex(); const endpoint = apiEndpointIndex.find( e => e.method === method.toUpperCase() && e.path === path ); if (!endpoint) { return { content: [ { type: "text", text: `API endpoint not found: ${method} ${path}` } ], isError: true }; } // Recursively resolve parameters, requestBody, and responses references const detailed = { ...endpoint, parameters: resolveRef(endpoint.parameters, openApiDoc), requestBody: resolveRef(endpoint.requestBody, openApiDoc), responses: resolveRef(endpoint.responses, openApiDoc) }; return { content: [ { type: "text", text: `API endpoint details: ${JSON.stringify(detailed, null, 2)}` } ] }; } ); server.tool( "get_available_spaces", "Get all available Kibana spaces with current context", z.object({ include_details: z.boolean().optional().default(true).describe("Include detailed space information (name, description, etc.)") }), async ({ include_details = true }): Promise<ToolResponse> => { try { const response = await kibanaClient.get('/api/spaces/space'); const result = { current_default_space: defaultSpace, total_count: response.length, available_spaces: include_details ? response : response.map((space: any) => ({ id: space.id, name: space.name })) }; return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } catch (error) { console.error(`Failed to get available spaces: ${error}`); return { content: [ { type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } ); }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/TocharianOU/mcp-server-kibana'

If you have feedback or need assistance with the MCP directory API, please join our Discord server