Skip to main content
Glama
by ghrud92
index.ts7 kB
#!/usr/bin/env node import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { JsonRpcErrorCode, createJsonRpcError, createToolErrorResponse, } from "./utils/errors.js"; import { createLogger } from "./utils/logger.js"; import { LokiClient } from "./utils/loki-client.js"; import { readFileSync } from "fs"; import { fileURLToPath } from "url"; import { dirname, resolve } from "path"; import { DEFAULT_LIMIT } from "./utils/loki-query-options.js"; // Create logger const logger = createLogger("MCPServer"); // Get version from package.json const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const packageJsonPath = resolve(__dirname, "../package.json"); const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")); // Handle server-level errors (transport, connection, etc.) function handleServerError(errorType: string, error: Error | unknown): void { // Create a standard JSON-RPC error object const jsonRpcError = createJsonRpcError( JsonRpcErrorCode.ServerError, error instanceof Error ? error.message : String(error), { name: error instanceof Error ? error.name : undefined, stack: error instanceof Error ? error.stack : undefined, errorType, } ); logger.error(`MCP ${errorType} error`, { error, jsonRpcError, }); } // Create server const server = new McpServer({ name: "loki-query-server", version: packageJson.version, description: "MCP server for querying Loki logs via logcli", }); const lokiClient = new LokiClient(); logger.info("Server initialization complete"); // Loki query tool server.tool( "query_loki", { query: z.string().describe("Loki query string"), from: z .string() .optional() .describe( "Start timestamp in UTC (e.g. '2023-01-01T12:00:00Z'). Relative time expressions like '1h ago' are not supported. Use ISO 8601 format." ), to: z .string() .optional() .describe( "End timestamp in UTC (e.g. '2023-01-01T13:00:00Z'). Relative time expressions like 'now' are not supported. Use ISO 8601 format." ), limit: z .number() .optional() .refine((val) => val === undefined || val <= DEFAULT_LIMIT, { message: `Maximum limit value is ${DEFAULT_LIMIT}`, }) .describe( `Maximum number of logs to return. Maximum value is ${DEFAULT_LIMIT}` ), batch: z.number().optional().describe("Batch size for query results"), output: z .enum(["default", "raw", "jsonl"]) .optional() .describe( "Output format - valid values are 'default' (formatted log lines), 'raw' (unprocessed log lines), or 'jsonl' (JSON Lines format). Note: values like 'text' or 'json' are not supported." ), quiet: z.boolean().optional().describe("Suppress query metadata"), forward: z .boolean() .optional() .describe("Display results in chronological order"), }, async ({ query, from, to, ...restOptions }, extra) => { logger.debug("Loki query tool execution", { query, from, to, restOptions, extra, }); try { // Convert string dates to Date objects const options = { ...restOptions, ...(from ? { from: new Date(from) } : {}), ...(to ? { to: new Date(to) } : {}), }; const result = await lokiClient.queryLoki(query, options); return { content: [ { type: "text", text: result, }, ], }; } catch (error) { logger.debug("Loki query tool execution error details", { error, errorType: typeof error, errorName: error instanceof Error ? error.name : undefined, errorMessage: error instanceof Error ? error.message : String(error), errorStack: error instanceof Error ? error.stack : undefined, }); logger.error("Loki query tool execution error", { query, error }); // Log the error logger.error( "Loki query error:", error instanceof Error ? error.message : String(error) ); // Create standardized error response return createToolErrorResponse(error, "Error running Loki query", { query, options: { from, to, ...restOptions }, }); } } ); // Label values query tool server.tool( "get_label_values", { label: z.string().describe("Label name to get values for"), }, async ({ label }, extra) => { logger.debug("Label values query tool execution", { label, extra }); try { const valuesJson = await lokiClient.getLabelValues(label); return { content: [ { type: "text", text: valuesJson, }, ], }; } catch (error) { logger.error("Label values query tool execution error", { label, error }); // Create standardized error response return createToolErrorResponse(error, "Error getting label values", { label, }); } } ); // Get all labels tool server.tool("get_labels", {}, async (_args, extra) => { logger.debug("All labels query tool execution", { extra }); try { const labelsJson = await lokiClient.getLabels(); return { content: [ { type: "text", text: labelsJson, }, ], }; } catch (error) { logger.error("Labels query tool execution error", { error }); // Create standardized error response return createToolErrorResponse(error, "Error getting labels"); } }); // Initialize error handling for message processing // This function sets up appropriate event handlers for the server function setupErrorHandlers(server: McpServer): void { try { // Set up handlers for process-level errors process.on("uncaughtException", (error) => { handleServerError("transport", error); // Don't exit the process, just log the error }); process.on("unhandledRejection", (reason) => { handleServerError( "transport", reason instanceof Error ? reason : new Error(String(reason)) ); // Don't exit the process, just log the error }); // Add a handler for when the server is initialized // eslint-disable-next-line @typescript-eslint/no-explicit-any (server.server as any).oninitialized = () => { logger.info("Server fully initialized"); }; logger.info("Error handlers initialized"); } catch (error) { logger.warn("Could not initialize all error handlers", error); } } // Apply error handlers before connecting setupErrorHandlers(server); // Start server const transport = new StdioServerTransport(); server .connect(transport) .then(() => { logger.info("Loki MCP server has started"); }) .catch((err) => { handleServerError("connection", err); });

Latest Blog Posts

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/ghrud92/loki-mcp'

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