Skip to main content
Glama

mcp-metabase-server P

by easecloudio
server.ts6.5 kB
/** * Main Metabase MCP Server class */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { ListResourcesRequestSchema, ReadResourceRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import axios from "axios"; import { MetabaseClient } from "./client/metabase-client.js"; import { ResourceHandlers } from "./handlers/resource-handlers.js"; import { ToolRegistry } from "./handlers/tool-registry.js"; import { MetabaseConfig } from "./types/metabase.js"; import { ErrorCode, McpError } from "./types/errors.js"; import { loadConfig, validateConfig } from "./utils/config.js"; // Schema definitions const ListResourceTemplatesRequestSchema = z.object({ method: z.literal("resources/list_templates"), }); const ListToolsRequestSchema = z.object({ method: z.literal("tools/list"), }); export class MetabaseServer { private server: Server; private metabaseClient: MetabaseClient; private resourceHandlers: ResourceHandlers; private toolRegistry: ToolRegistry; constructor(config?: MetabaseConfig) { // Load and validate configuration const serverConfig = config || loadConfig(); validateConfig(serverConfig); // Initialize server this.server = new Server( { name: "metabase-server", version: "0.1.0", }, { capabilities: { resources: {}, tools: {}, }, } ); // Initialize Metabase client this.metabaseClient = new MetabaseClient(serverConfig); // Initialize handlers this.resourceHandlers = new ResourceHandlers(this.metabaseClient); this.toolRegistry = new ToolRegistry(this.metabaseClient); // Setup request handlers this.setupResourceHandlers(); this.setupToolHandlers(); this.setupErrorHandling(); } private setupResourceHandlers(): void { // List resources this.server.setRequestHandler( ListResourcesRequestSchema, async (request) => { this.logInfo("Listing resources..."); try { return await this.resourceHandlers.handleListResources(); } catch (error) { this.logError("Failed to list resources", error); throw new McpError( ErrorCode.InternalError, "Failed to list Metabase resources" ); } } ); // List resource templates this.server.setRequestHandler( ListResourceTemplatesRequestSchema, async () => { return await this.resourceHandlers.handleListResourceTemplates(); } ); // Read resource this.server.setRequestHandler( ReadResourceRequestSchema, async (request) => { this.logInfo("Reading resource...", { uri: request.params?.uri }); const uri = request.params?.uri; if (!uri) { throw new McpError(ErrorCode.InvalidParams, "URI is required"); } try { return await this.resourceHandlers.handleReadResource(uri); } catch (error) { this.logError("Failed to read resource", error); if (error instanceof McpError) { throw error; } if (axios.isAxiosError(error)) { throw new McpError( ErrorCode.InternalError, `Metabase API error: ${ error.response?.data?.message || error.message }` ); } throw new McpError( ErrorCode.InternalError, "Failed to read resource" ); } } ); } private setupToolHandlers(): void { // List tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: this.toolRegistry.getAllToolSchemas(), }; }); // Call tool this.server.setRequestHandler(CallToolRequestSchema, async (request) => { this.logInfo("Calling tool...", { tool: request.params?.name }); const toolName = request.params?.name; const args = request.params?.arguments || {}; if (!toolName) { throw new McpError(ErrorCode.InvalidParams, "Tool name is required"); } try { return await this.toolRegistry.handleTool(toolName, args); } catch (error) { this.logError("Tool execution failed", error); if (error instanceof McpError) { throw error; } if (axios.isAxiosError(error)) { return { content: [ { type: "text", text: `Metabase API error: ${ error.response?.data?.message || error.message }`, }, ], isError: true, }; } throw new McpError( ErrorCode.InternalError, `Tool execution failed: ${ error instanceof Error ? error.message : "Unknown error" }` ); } }); } private setupErrorHandling(): void { // Enhanced error handling with logging this.server.onerror = (error: Error) => { this.logError("Server Error", error); }; // Graceful shutdown process.on("SIGINT", async () => { this.logInfo("Shutting down server..."); await this.server.close(); process.exit(0); }); } private logInfo(message: string, data?: unknown): void { const logMessage = { timestamp: new Date().toISOString(), level: "info", message, data, }; console.error(JSON.stringify(logMessage)); console.error(`INFO: ${message}`); } private logError(message: string, error: unknown): void { const errorObj = error as Error; const logMessage = { timestamp: new Date().toISOString(), level: "error", message, error: errorObj.message || "Unknown error", stack: errorObj.stack, }; console.error(JSON.stringify(logMessage)); console.error(`ERROR: ${message} - ${errorObj.message || "Unknown error"}`); } /** * Start the server */ async run(): Promise<void> { try { this.logInfo("Starting Metabase MCP server..."); const transport = new StdioServerTransport(); await this.server.connect(transport); this.logInfo("Metabase MCP server running on stdio"); } catch (error) { this.logError("Failed to start server", error); throw error; } } }

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/easecloudio/mcp-metabase-server'

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