list_markers
Retrieve deployment markers for a Honeycomb environment with pagination, sorting, and search options. Fetch IDs, types, messages, URLs, and timestamps for efficient event tracking and analysis.
Instructions
Lists available markers (deployment events) for a specific dataset or environment with pagination, sorting, and search support. Returns IDs, messages, types, URLs, creation times, start times, and end times.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| environment | Yes | The Honeycomb environment | |
| limit | No | Number of items per page | |
| page | No | Page number (1-based) | |
| search | No | Search term to filter results | |
| search_fields | No | Fields to search in (string or array of strings) | |
| sort_by | No | Field to sort by | |
| sort_order | No | Sort direction |
Implementation Reference
- src/tools/list-markers.ts:32-199 (handler)The handler function that executes the list_markers tool. Fetches markers from Honeycomb API for the given environment, simplifies the response, and handles pagination, sorting, searching using cache or manual filtering.handler: async (params: z.infer<typeof ListMarkersSchema>) => { const { environment, page, limit, sort_by, sort_order, search, search_fields } = params; // Validate input parameters if (!environment) { return handleToolError(new Error("environment parameter is required"), "list_markers"); } try { // Fetch markers from the API const markers = await api.getMarkers(environment); // Create a simplified response const simplifiedMarkers = markers.map(marker => ({ id: marker.id, message: marker.message, type: marker.type, url: marker.url || '', created_at: marker.created_at, start_time: marker.start_time, end_time: marker.end_time || '', })); // If no pagination or filtering is requested, return all markers if (!page && !limit && !search && !sort_by) { return { content: [ { type: "text", text: JSON.stringify(simplifiedMarkers, null, 2), }, ], metadata: { count: simplifiedMarkers.length, environment } }; } // Otherwise, use the cache manager to handle pagination, sorting, and filtering const cache = getCache(); const cacheOptions = { page: page || 1, limit: limit || 10, // Configure sorting if requested ...(sort_by && { sort: { field: sort_by, order: sort_order || 'asc' } }), // Configure search if requested ...(search && { search: { field: search_fields || ['message', 'type'], term: search, caseInsensitive: true } }) }; // Access the collection with pagination and filtering const result = cache.accessCollection( environment, 'marker', undefined, cacheOptions ); // If the collection isn't in cache yet, apply the filtering manually if (!result) { // Basic implementation for non-cached data let filteredMarkers = [...simplifiedMarkers]; // Apply search if requested if (search) { const searchFields = Array.isArray(search_fields) ? search_fields : search_fields ? [search_fields] : ['message', 'type']; const searchTerm = search.toLowerCase(); filteredMarkers = filteredMarkers.filter(marker => { return searchFields.some(field => { const value = marker[field as keyof typeof marker]; return typeof value === 'string' && value.toLowerCase().includes(searchTerm); }); }); } // Apply sorting if requested if (sort_by) { const field = sort_by; const order = sort_order || 'asc'; filteredMarkers.sort((a, b) => { const aValue = a[field as keyof typeof a]; const bValue = b[field as keyof typeof b]; if (typeof aValue === 'string' && typeof bValue === 'string') { return order === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue); } return order === 'asc' ? (aValue > bValue ? 1 : -1) : (bValue > aValue ? 1 : -1); }); } // Apply pagination const itemLimit = limit || 10; const currentPage = page || 1; const total = filteredMarkers.length; const pages = Math.ceil(total / itemLimit); const offset = (currentPage - 1) * itemLimit; // Return formatted response const paginatedResponse: PaginatedResponse<typeof simplifiedMarkers[0]> = { data: filteredMarkers.slice(offset, offset + itemLimit), metadata: { total, page: currentPage, pages, limit: itemLimit } }; return { content: [ { type: "text", text: JSON.stringify(paginatedResponse, null, 2), }, ], }; } // Format the cached result and type-cast the unknown data const typedData = result.data as typeof simplifiedMarkers; const paginatedResponse: PaginatedResponse<typeof simplifiedMarkers[0]> = { data: typedData, metadata: { total: result.total, page: result.page || 1, pages: result.pages || 1, limit: limit || 10 } }; return { content: [ { type: "text", text: JSON.stringify(paginatedResponse, null, 2), }, ], }; } catch (error) { return handleToolError(error, "list_markers"); } }
- src/types/schema.ts:370-373 (schema)Zod schema defining the input parameters for the list_markers tool, including required 'environment' and optional pagination/search fields from PaginationSchema.export const ListMarkersSchema = z.object({ environment: z.string().min(1).trim().describe("The Honeycomb environment"), }).merge(PaginationSchema).describe("Parameters for listing Honeycomb markers. Markers represent significant events like deployments or incidents.");
- src/tools/list-markers.ts:14-201 (registration)Factory function that creates and configures the list_markers MCP tool object, including name, description, schema, and handler.export function createListMarkersTool(api: HoneycombAPI) { return { name: "list_markers", description: "Lists available markers (deployment events) for a specific dataset or environment with pagination, sorting, and search support. Returns IDs, messages, types, URLs, creation times, start times, and end times.", schema: ListMarkersSchema.shape, /** * Handler for the list_markers tool * * @param params - The parameters for the tool * @param params.environment - The Honeycomb environment * @param params.page - Optional page number for pagination * @param params.limit - Optional limit of items per page * @param params.sort_by - Optional field to sort by * @param params.sort_order - Optional sort direction (asc/desc) * @param params.search - Optional search term * @param params.search_fields - Optional fields to search in * @returns List of markers with relevant metadata, potentially paginated */ handler: async (params: z.infer<typeof ListMarkersSchema>) => { const { environment, page, limit, sort_by, sort_order, search, search_fields } = params; // Validate input parameters if (!environment) { return handleToolError(new Error("environment parameter is required"), "list_markers"); } try { // Fetch markers from the API const markers = await api.getMarkers(environment); // Create a simplified response const simplifiedMarkers = markers.map(marker => ({ id: marker.id, message: marker.message, type: marker.type, url: marker.url || '', created_at: marker.created_at, start_time: marker.start_time, end_time: marker.end_time || '', })); // If no pagination or filtering is requested, return all markers if (!page && !limit && !search && !sort_by) { return { content: [ { type: "text", text: JSON.stringify(simplifiedMarkers, null, 2), }, ], metadata: { count: simplifiedMarkers.length, environment } }; } // Otherwise, use the cache manager to handle pagination, sorting, and filtering const cache = getCache(); const cacheOptions = { page: page || 1, limit: limit || 10, // Configure sorting if requested ...(sort_by && { sort: { field: sort_by, order: sort_order || 'asc' } }), // Configure search if requested ...(search && { search: { field: search_fields || ['message', 'type'], term: search, caseInsensitive: true } }) }; // Access the collection with pagination and filtering const result = cache.accessCollection( environment, 'marker', undefined, cacheOptions ); // If the collection isn't in cache yet, apply the filtering manually if (!result) { // Basic implementation for non-cached data let filteredMarkers = [...simplifiedMarkers]; // Apply search if requested if (search) { const searchFields = Array.isArray(search_fields) ? search_fields : search_fields ? [search_fields] : ['message', 'type']; const searchTerm = search.toLowerCase(); filteredMarkers = filteredMarkers.filter(marker => { return searchFields.some(field => { const value = marker[field as keyof typeof marker]; return typeof value === 'string' && value.toLowerCase().includes(searchTerm); }); }); } // Apply sorting if requested if (sort_by) { const field = sort_by; const order = sort_order || 'asc'; filteredMarkers.sort((a, b) => { const aValue = a[field as keyof typeof a]; const bValue = b[field as keyof typeof b]; if (typeof aValue === 'string' && typeof bValue === 'string') { return order === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue); } return order === 'asc' ? (aValue > bValue ? 1 : -1) : (bValue > aValue ? 1 : -1); }); } // Apply pagination const itemLimit = limit || 10; const currentPage = page || 1; const total = filteredMarkers.length; const pages = Math.ceil(total / itemLimit); const offset = (currentPage - 1) * itemLimit; // Return formatted response const paginatedResponse: PaginatedResponse<typeof simplifiedMarkers[0]> = { data: filteredMarkers.slice(offset, offset + itemLimit), metadata: { total, page: currentPage, pages, limit: itemLimit } }; return { content: [ { type: "text", text: JSON.stringify(paginatedResponse, null, 2), }, ], }; } // Format the cached result and type-cast the unknown data const typedData = result.data as typeof simplifiedMarkers; const paginatedResponse: PaginatedResponse<typeof simplifiedMarkers[0]> = { data: typedData, metadata: { total: result.total, page: result.page || 1, pages: result.pages || 1, limit: limit || 10 } }; return { content: [ { type: "text", text: JSON.stringify(paginatedResponse, null, 2), }, ], }; } catch (error) { return handleToolError(error, "list_markers"); } } }; }
- src/tools/index.ts:40-40 (registration)Instantiation of the list_markers tool within the tools array in registerTools function.createListMarkersTool(api),
- src/tools/index.ts:61-107 (registration)Generic registration loop that registers all tools, including list_markers, with the MCP server.for (const tool of tools) { // Register the tool with the server using type assertion to bypass TypeScript's strict type checking (server as any).tool( tool.name, tool.description, tool.schema, async (args: Record<string, any>, extra: any) => { try { // Validate and ensure required fields are present before passing to handler if (tool.name.includes("analyze_columns") && (!args.environment || !args.dataset || !args.columns)) { throw new Error("Missing required fields: environment, dataset, and columns are required"); } else if (tool.name.includes("run_query") && (!args.environment || !args.dataset)) { throw new Error("Missing required fields: environment and dataset are required"); } // Use type assertion to satisfy TypeScript's type checking const result = await tool.handler(args as any); // If the result already has the expected format, return it directly if (result && typeof result === 'object' && 'content' in result) { return result as any; } // Otherwise, format the result as expected by the SDK return { content: [ { type: "text", text: typeof result === 'string' ? result : JSON.stringify(result, null, 2), }, ], } as any; } catch (error) { // Format errors to match the SDK's expected format return { content: [ { type: "text", text: error instanceof Error ? error.message : String(error), }, ], isError: true, } as any; } } ); }