honeycomb-mcp-server

by kajirita2002
Verified
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequest, CallToolRequestSchema, ListToolsRequestSchema, Tool, } from "@modelcontextprotocol/sdk/types.js"; // Honeycomb APIのインターフェース定義 interface AuthArgs {} interface DatasetListArgs {} interface DatasetGetArgs { datasetSlug: string; } interface ColumnListArgs { datasetSlug: string; } interface QueryCreateArgs { datasetSlug: string; query: any; } interface QueryGetArgs { datasetSlug: string; queryId: string; } interface QueryResultCreateArgs { datasetSlug: string; queryId: string; } interface QueryResultGetArgs { datasetSlug: string; queryResultId: string; } interface EventCreateArgs { datasetSlug: string; event: any; } // P2のエンドポイント用インターフェース interface BoardListArgs {} interface BoardCreateArgs { name: string; description?: string; query_ids?: string[]; } interface BoardGetArgs { boardId: string; } interface BoardUpdateArgs { boardId: string; name?: string; description?: string; query_ids?: string[]; } interface MarkerListArgs { datasetSlug: string; } interface MarkerCreateArgs { datasetSlug: string; message: string; type: string; start_time: string; end_time?: string; url?: string; } // P1のツール定義 const authTool: Tool = { name: "honeycomb_auth", description: "Get authentication information and validate API key", inputSchema: { type: "object", properties: {}, }, }; const datasetsListTool: Tool = { name: "honeycomb_datasets_list", description: "List all datasets in the environment", inputSchema: { type: "object", properties: {}, }, }; const datasetGetTool: Tool = { name: "honeycomb_dataset_get", description: "Get information about a specific dataset", inputSchema: { type: "object", properties: { datasetSlug: { type: "string", description: "Dataset slug to retrieve", }, }, required: ["datasetSlug"], }, }; const columnsListTool: Tool = { name: "honeycomb_columns_list", description: "List all columns in a dataset", inputSchema: { type: "object", properties: { datasetSlug: { type: "string", description: "Dataset slug to list columns for", }, }, required: ["datasetSlug"], }, }; const queryCreateTool: Tool = { name: "honeycomb_query_create", description: "Create a new query for a dataset", inputSchema: { type: "object", properties: { datasetSlug: { type: "string", description: "Dataset slug to create query for", }, query: { type: "object", description: "Query object with calculation, time range, and filters", }, }, required: ["datasetSlug", "query"], }, }; const queryGetTool: Tool = { name: "honeycomb_query_get", description: "Get information about a specific query", inputSchema: { type: "object", properties: { datasetSlug: { type: "string", description: "Dataset slug the query belongs to", }, queryId: { type: "string", description: "Query ID to retrieve", }, }, required: ["datasetSlug", "queryId"], }, }; const queryResultCreateTool: Tool = { name: "honeycomb_query_result_create", description: "Create a new query result (run a query)", inputSchema: { type: "object", properties: { datasetSlug: { type: "string", description: "Dataset slug to create query result for", }, queryId: { type: "string", description: "Query ID to run", }, }, required: ["datasetSlug", "queryId"], }, }; const queryResultGetTool: Tool = { name: "honeycomb_query_result_get", description: "Get results of a specific query execution", inputSchema: { type: "object", properties: { datasetSlug: { type: "string", description: "Dataset slug the query result belongs to", }, queryResultId: { type: "string", description: "Query result ID to retrieve", }, }, required: ["datasetSlug", "queryResultId"], }, }; const eventCreateTool: Tool = { name: "honeycomb_event_create", description: "Create a new event in a dataset", inputSchema: { type: "object", properties: { datasetSlug: { type: "string", description: "Dataset slug to create event in", }, event: { type: "object", description: "Event data to send", }, }, required: ["datasetSlug", "event"], }, }; // P2のツール定義 const boardsListTool: Tool = { name: "honeycomb_boards_list", description: "List all boards", inputSchema: { type: "object", properties: {}, }, }; const boardCreateTool: Tool = { name: "honeycomb_board_create", description: "Create a new board", inputSchema: { type: "object", properties: { name: { type: "string", description: "Name of the board", }, description: { type: "string", description: "Description of the board", }, query_ids: { type: "array", items: { type: "string", }, description: "Query IDs to include in the board", }, }, required: ["name"], }, }; const boardGetTool: Tool = { name: "honeycomb_board_get", description: "Get information about a specific board", inputSchema: { type: "object", properties: { boardId: { type: "string", description: "Board ID to retrieve", }, }, required: ["boardId"], }, }; const boardUpdateTool: Tool = { name: "honeycomb_board_update", description: "Update an existing board", inputSchema: { type: "object", properties: { boardId: { type: "string", description: "Board ID to update", }, name: { type: "string", description: "New name for the board", }, description: { type: "string", description: "New description for the board", }, query_ids: { type: "array", items: { type: "string", }, description: "New query IDs to include in the board", }, }, required: ["boardId"], }, }; const markersListTool: Tool = { name: "honeycomb_markers_list", description: "List all markers for a dataset", inputSchema: { type: "object", properties: { datasetSlug: { type: "string", description: "Dataset slug to list markers for, or 'all' for all datasets", }, }, required: ["datasetSlug"], }, }; const markerCreateTool: Tool = { name: "honeycomb_marker_create", description: "Create a new marker for a dataset", inputSchema: { type: "object", properties: { datasetSlug: { type: "string", description: "Dataset slug to create marker for, or 'all' for all datasets", }, message: { type: "string", description: "Message for the marker", }, type: { type: "string", description: "Type of marker", }, start_time: { type: "string", description: "Start time for the marker (ISO format)", }, end_time: { type: "string", description: "End time for the marker (ISO format), optional for point-in-time markers", }, url: { type: "string", description: "URL associated with the marker", }, }, required: ["datasetSlug", "message", "type", "start_time"], }, }; // P2機能用のツールの追加定義 const datasetsCreateTool: Tool = { name: "honeycomb_datasets_create", description: "Create a new dataset", inputSchema: { type: "object", properties: { name: { type: "string", description: "Name of the dataset", }, description: { type: "string", description: "Description of the dataset", }, }, required: ["name"], }, }; class HoneycombClient { private baseUrl: string; private headers: Record<string, string>; constructor(apiKey: string) { this.baseUrl = "https://api.honeycomb.io/1"; this.headers = { "X-Honeycomb-Team": apiKey, "Content-Type": "application/json", }; } // Dataset operations async listDatasets(): Promise<any> { const response = await fetch(`${this.baseUrl}/datasets`, { method: "GET", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to list datasets: ${response.statusText}`); } return await response.json(); } async getDataset(slug: string): Promise<any> { const response = await fetch(`${this.baseUrl}/datasets/${slug}`, { method: "GET", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to get dataset: ${response.statusText}`); } return await response.json(); } async createDataset(name: string, description?: string): Promise<any> { const response = await fetch(`${this.baseUrl}/datasets`, { method: "POST", headers: this.headers, body: JSON.stringify({ name, description, }), }); if (!response.ok) { throw new Error(`Failed to create dataset: ${response.statusText}`); } return await response.json(); } async updateDataset(slug: string, name?: string, description?: string): Promise<any> { const response = await fetch(`${this.baseUrl}/datasets/${slug}`, { method: "PUT", headers: this.headers, body: JSON.stringify({ name, description, }), }); if (!response.ok) { throw new Error(`Failed to update dataset: ${response.statusText}`); } return await response.json(); } // Column operations async listColumns(datasetSlug: string): Promise<any> { const response = await fetch(`${this.baseUrl}/datasets/${datasetSlug}/columns`, { method: "GET", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to list columns: ${response.statusText}`); } return await response.json(); } // Query operations async createQuerySpec(datasetSlug: string, querySpec: any): Promise<any> { const response = await fetch(`${this.baseUrl}/datasets/${datasetSlug}/queries`, { method: "POST", headers: this.headers, body: JSON.stringify(querySpec), }); if (!response.ok) { throw new Error(`Failed to create query spec: ${response.statusText}`); } return await response.json(); } async getQueryResult(datasetSlug: string, queryId: string): Promise<any> { const response = await fetch(`${this.baseUrl}/datasets/${datasetSlug}/queries/${queryId}/results`, { method: "GET", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to get query results: ${response.statusText}`); } return await response.json(); } // Board operations async listBoards(): Promise<any> { const response = await fetch(`${this.baseUrl}/boards`, { method: "GET", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to list boards: ${response.statusText}`); } return await response.json(); } async getBoard(boardId: string): Promise<any> { const response = await fetch(`${this.baseUrl}/boards/${boardId}`, { method: "GET", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to get board: ${response.statusText}`); } return await response.json(); } async createBoard(name: string, description?: string): Promise<any> { const response = await fetch(`${this.baseUrl}/boards`, { method: "POST", headers: this.headers, body: JSON.stringify({ name, description, }), }); if (!response.ok) { throw new Error(`Failed to create board: ${response.statusText}`); } return await response.json(); } async updateBoard(boardId: string, name?: string, description?: string): Promise<any> { const response = await fetch(`${this.baseUrl}/boards/${boardId}`, { method: "PUT", headers: this.headers, body: JSON.stringify({ name, description, }), }); if (!response.ok) { throw new Error(`Failed to update board: ${response.statusText}`); } return await response.json(); } async deleteBoard(boardId: string): Promise<void> { const response = await fetch(`${this.baseUrl}/boards/${boardId}`, { method: "DELETE", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to delete board: ${response.statusText}`); } } // SLO operations async listSLOs(datasetSlug: string): Promise<any> { const response = await fetch(`${this.baseUrl}/slos?dataset=${datasetSlug}`, { method: "GET", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to list SLOs: ${response.statusText}`); } return await response.json(); } async getSLO(sloId: string): Promise<any> { const response = await fetch(`${this.baseUrl}/slos/${sloId}`, { method: "GET", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to get SLO: ${response.statusText}`); } return await response.json(); } async createSLO(datasetSlug: string, sloData: any): Promise<any> { const response = await fetch(`${this.baseUrl}/slos`, { method: "POST", headers: this.headers, body: JSON.stringify({ ...sloData, dataset: datasetSlug, }), }); if (!response.ok) { throw new Error(`Failed to create SLO: ${response.statusText}`); } return await response.json(); } async updateSLO(sloId: string, sloData: any): Promise<any> { const response = await fetch(`${this.baseUrl}/slos/${sloId}`, { method: "PUT", headers: this.headers, body: JSON.stringify(sloData), }); if (!response.ok) { throw new Error(`Failed to update SLO: ${response.statusText}`); } return await response.json(); } // Trigger operations async listTriggers(datasetSlug: string): Promise<any> { const response = await fetch(`${this.baseUrl}/triggers?dataset=${datasetSlug}`, { method: "GET", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to list triggers: ${response.statusText}`); } return await response.json(); } async getTrigger(datasetSlug: string, triggerId: string): Promise<any> { const response = await fetch(`${this.baseUrl}/triggers/${triggerId}?dataset=${datasetSlug}`, { method: "GET", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to get trigger: ${response.statusText}`); } return await response.json(); } async createTrigger(datasetSlug: string, triggerData: any): Promise<any> { const response = await fetch(`${this.baseUrl}/triggers`, { method: "POST", headers: this.headers, body: JSON.stringify({ ...triggerData, dataset: datasetSlug, }), }); if (!response.ok) { throw new Error(`Failed to create trigger: ${response.statusText}`); } return await response.json(); } async updateTrigger(datasetSlug: string, triggerId: string, triggerData: any): Promise<any> { const response = await fetch(`${this.baseUrl}/triggers/${triggerId}`, { method: "PUT", headers: this.headers, body: JSON.stringify({ ...triggerData, dataset: datasetSlug, }), }); if (!response.ok) { throw new Error(`Failed to update trigger: ${response.statusText}`); } return await response.json(); } async deleteTrigger(datasetSlug: string, triggerId: string): Promise<void> { const response = await fetch(`${this.baseUrl}/triggers/${triggerId}?dataset=${datasetSlug}`, { method: "DELETE", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to delete trigger: ${response.statusText}`); } } // Marker operations async listMarkers(datasetSlug: string): Promise<any> { const endpoint = datasetSlug === 'all' ? `${this.baseUrl}/markers` : `${this.baseUrl}/markers?dataset=${datasetSlug}`; const response = await fetch(endpoint, { method: "GET", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to list markers: ${response.statusText}`); } return await response.json(); } async getMarker(datasetSlug: string, markerId: string): Promise<any> { const endpoint = datasetSlug === 'all' ? `${this.baseUrl}/markers/${markerId}` : `${this.baseUrl}/markers/${markerId}?dataset=${datasetSlug}`; const response = await fetch(endpoint, { method: "GET", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to get marker: ${response.statusText}`); } return await response.json(); } async createMarker(markerData: MarkerCreateArgs): Promise<any> { const endpoint = markerData.datasetSlug === 'all' ? `${this.baseUrl}/markers` : `${this.baseUrl}/markers?dataset=${markerData.datasetSlug}`; const response = await fetch(endpoint, { method: "POST", headers: this.headers, body: JSON.stringify({ message: markerData.message, type: markerData.type, start_time: markerData.start_time, end_time: markerData.end_time, url: markerData.url }), }); if (!response.ok) { throw new Error(`Failed to create marker: ${response.statusText}`); } return await response.json(); } async updateMarker(datasetSlug: string, markerId: string, markerData: Partial<MarkerCreateArgs>): Promise<any> { const endpoint = datasetSlug === 'all' ? `${this.baseUrl}/markers/${markerId}` : `${this.baseUrl}/markers/${markerId}?dataset=${datasetSlug}`; const response = await fetch(endpoint, { method: "PUT", headers: this.headers, body: JSON.stringify({ message: markerData.message, type: markerData.type, start_time: markerData.start_time, end_time: markerData.end_time, url: markerData.url }), }); if (!response.ok) { throw new Error(`Failed to update marker: ${response.statusText}`); } return await response.json(); } async deleteMarker(datasetSlug: string, markerId: string): Promise<void> { const endpoint = datasetSlug === 'all' ? `${this.baseUrl}/markers/${markerId}` : `${this.baseUrl}/markers/${markerId}?dataset=${datasetSlug}`; const response = await fetch(endpoint, { method: "DELETE", headers: this.headers, }); if (!response.ok) { throw new Error(`Failed to delete marker: ${response.statusText}`); } } } async function main() { const apiKey = process.env.HONEYCOMB_API_KEY; if (!apiKey) { console.error("Error: HONEYCOMB_API_KEY environment variable is required."); process.exit(1); } console.error("Starting Honeycomb MCP Server..."); const server = new Server( { name: "Honeycomb MCP Server", version: "1.0.0", }, { capabilities: { tools: {}, }, }, ); const client = new HoneycombClient(apiKey); server.setRequestHandler( CallToolRequestSchema, async (request: CallToolRequest) => { console.error("CallToolRequest received:", request); try { if (!request.params.arguments) { throw new Error("No arguments provided"); } switch (request.params.name) { // Auth case "honeycomb_auth": { // 認証情報の確認のみ return { content: [{ type: "text", text: JSON.stringify({ authenticated: true }) }], }; } // Dataset management case "honeycomb_datasets_list": { const response = await client.listDatasets(); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "honeycomb_dataset_get": { const args = request.params.arguments as unknown as DatasetGetArgs; if (!args.datasetSlug) { throw new Error("datasetSlug is required"); } const response = await client.getDataset(args.datasetSlug); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "honeycomb_datasets_create": { const args = request.params.arguments as unknown as any; if (!args.name) { throw new Error("name is required"); } const response = await client.createDataset(args.name, args.description); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "honeycomb_datasets_update": { const args = request.params.arguments as unknown as any; if (!args.datasetSlug) { throw new Error("datasetSlug is required"); } const response = await client.updateDataset(args.datasetSlug, args.name, args.description); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } // Column management case "honeycomb_columns_list": { const args = request.params.arguments as unknown as ColumnListArgs; if (!args.datasetSlug) { throw new Error("datasetSlug is required"); } const response = await client.listColumns(args.datasetSlug); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } // Query management case "honeycomb_query_create": { const args = request.params.arguments as unknown as QueryCreateArgs; if (!args.datasetSlug || !args.query) { throw new Error("datasetSlug and query are required"); } const response = await client.createQuerySpec(args.datasetSlug, args.query); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "honeycomb_query_result_create": { const args = request.params.arguments as unknown as QueryResultCreateArgs; if (!args.datasetSlug || !args.queryId) { throw new Error("datasetSlug and queryId are required"); } const response = await client.getQueryResult(args.datasetSlug, args.queryId); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "honeycomb_query_result_get": { const args = request.params.arguments as unknown as QueryResultGetArgs; if (!args.datasetSlug || !args.queryResultId) { throw new Error("datasetSlug and queryResultId are required"); } const response = await client.getQueryResult(args.datasetSlug, args.queryResultId); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } // Event management case "honeycomb_event_create": { const args = request.params.arguments as unknown as EventCreateArgs; if (!args.datasetSlug || !args.event) { throw new Error("datasetSlug and event are required"); } // イベント送信メソッドの実装が必要 // const response = await client.createEvent(args.datasetSlug, args.event); return { content: [{ type: "text", text: JSON.stringify({ success: true }) }], }; } // P2 - Board management case "honeycomb_boards_list": { const response = await client.listBoards(); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "honeycomb_board_get": { const args = request.params.arguments as unknown as BoardGetArgs; if (!args.boardId) { throw new Error("boardId is required"); } const response = await client.getBoard(args.boardId); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "honeycomb_board_create": { const args = request.params.arguments as unknown as BoardCreateArgs; if (!args.name) { throw new Error("name is required"); } const response = await client.createBoard(args.name, args.description); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "honeycomb_board_update": { const args = request.params.arguments as unknown as BoardUpdateArgs; if (!args.boardId) { throw new Error("boardId is required"); } const response = await client.updateBoard(args.boardId, args.name, args.description); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } // P2 - Marker management case "honeycomb_markers_list": { const args = request.params.arguments as unknown as MarkerListArgs; if (!args.datasetSlug) { throw new Error("datasetSlug is required"); } const response = await client.listMarkers(args.datasetSlug); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } case "honeycomb_marker_create": { const args = request.params.arguments as unknown as MarkerCreateArgs; if (!args.datasetSlug || !args.message || !args.type || !args.start_time) { throw new Error("datasetSlug, message, type, and start_time are required"); } const response = await client.createMarker(args); return { content: [{ type: "text", text: JSON.stringify(response) }], }; } default: throw new Error(`Unknown tool: ${request.params.name}`); } } catch (error: any) { console.error("Error handling request:", error); return { content: [{ type: "text", text: JSON.stringify({ error: error.message }) }], }; } } ); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // P1 Tools authTool, datasetsListTool, datasetGetTool, datasetsCreateTool, columnsListTool, queryCreateTool, queryGetTool, queryResultCreateTool, queryResultGetTool, eventCreateTool, // P2 Tools boardsListTool, boardGetTool, boardCreateTool, boardUpdateTool, markersListTool, markerCreateTool, ], }; }); const transport = new StdioServerTransport(); console.error("Connecting server to transport..."); await server.connect(transport); console.error("Honeycomb MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error in main():", error); process.exit(1); });