Skip to main content
Glama
modern-dashboard-service.ts8.07 kB
/** * Example of a modern dashboard service using the new architecture patterns */ import { z } from 'zod'; import { BaseHttpService } from '../core/base-service.js'; import { Tool, ToolService } from '../core/tool-system.js'; import { HandleErrors, ValidationError, NotFoundError } from '../core/error-handling.js'; import { GrafanaHttpClient } from '../http-client.js'; import { AsyncResult } from '../core/interfaces.js'; /** * Input schemas for tools */ const SearchDashboardsSchema = z.object({ query: z.string().optional(), tags: z.array(z.string()).optional(), starred: z.boolean().optional(), folderId: z.number().optional(), limit: z.number().min(1).max(5000).default(1000), }); const GetDashboardSchema = z.object({ uid: z.string().min(1), version: z.number().optional(), }); const CreateDashboardSchema = z.object({ dashboard: z.object({ title: z.string().min(1), tags: z.array(z.string()).optional(), folderId: z.number().optional(), panels: z.array(z.any()).default([]), }), folderId: z.number().optional(), overwrite: z.boolean().default(false), }); /** * Dashboard types */ interface Dashboard { id?: number; uid: string; title: string; tags: string[]; folderId?: number; folderTitle?: string; uri: string; url: string; type: string; starred: boolean; } interface DashboardDetail extends Dashboard { dashboard: any; meta: { type: string; canSave: boolean; canEdit: boolean; canAdmin: boolean; canStar: boolean; created: string; updated: string; version: number; }; } /** * Modern Dashboard Service using new patterns */ @ToolService('dashboards') export class ModernDashboardService extends BaseHttpService { constructor(httpClient: GrafanaHttpClient) { super('ModernDashboardService', httpClient, '2.0.0'); } /** * Search for dashboards with comprehensive error handling */ @Tool({ name: 'search_dashboards', description: 'Search for dashboards by title, tags, or other metadata with advanced filtering', category: 'dashboards', tags: ['search', 'filter'], schema: SearchDashboardsSchema, }) @HandleErrors('searchDashboards') async searchDashboards(request: any): Promise<any> { const params = SearchDashboardsSchema.parse(request.params.arguments); const result = await this.searchDashboardsResult(params); if (!result.success) { throw result.error; } return { content: [ { type: 'text', text: `Found ${result.data.length} dashboards:\n\n${ result.data.map(d => `• **${d.title}** (${d.uid})\n` + ` Tags: ${d.tags.join(', ') || 'none'}\n` + ` URL: ${d.url}`, ).join('\n\n')}`, }, ], }; } /** * Get dashboard by UID */ @Tool({ name: 'get_dashboard', description: 'Retrieve a specific dashboard by its UID', category: 'dashboards', tags: ['retrieve'], schema: GetDashboardSchema, }) @HandleErrors('getDashboard') async getDashboard(request: any): Promise<any> { const { uid, version } = GetDashboardSchema.parse(request.params.arguments); const result = await this.getDashboardResult(uid, version); if (!result.success) { throw result.error; } const dashboard = result.data; return { content: [ { type: 'text', text: `# Dashboard: ${dashboard.dashboard.title}\n\n` + `**UID:** ${dashboard.uid}\n` + `**ID:** ${dashboard.id || 'N/A'}\n` + `**Version:** ${dashboard.meta.version}\n` + `**Tags:** ${dashboard.dashboard.tags?.join(', ') || 'none'}\n` + `**Panels:** ${dashboard.dashboard.panels?.length || 0}\n` + `**Created:** ${dashboard.meta.created}\n` + `**Updated:** ${dashboard.meta.updated}\n\n` + '**Permissions:**\n' + `- Can Save: ${dashboard.meta.canSave}\n` + `- Can Edit: ${dashboard.meta.canEdit}\n` + `- Can Admin: ${dashboard.meta.canAdmin}`, }, ], }; } /** * Create a new dashboard */ @Tool({ name: 'create_dashboard', description: 'Create a new dashboard with the specified configuration', category: 'dashboards', tags: ['create'], schema: CreateDashboardSchema, }) @HandleErrors('createDashboard') async createDashboard(request: any): Promise<any> { const params = CreateDashboardSchema.parse(request.params.arguments); const result = await this.createDashboardResult(params); if (!result.success) { throw result.error; } const response = result.data; return { content: [ { type: 'text', text: '✅ Dashboard created successfully!\n\n' + `**Title:** ${response.dashboard.title}\n` + `**UID:** ${response.dashboard.uid}\n` + `**ID:** ${response.dashboard.id}\n` + `**Version:** ${response.version}\n` + `**URL:** ${response.url}\n` + `**Status:** ${response.status}`, }, ], }; } // Business logic methods with Result pattern /** * Search dashboards with Result wrapper */ async searchDashboardsResult(params: z.infer<typeof SearchDashboardsSchema>): AsyncResult<Dashboard[]> { return this.execute(async () => { const queryParams: Record<string, any> = { limit: params.limit, }; if (params.query) queryParams.query = params.query; if (params.tags?.length) queryParams.tag = params.tags; if (params.starred !== undefined) queryParams.starred = params.starred; if (params.folderId !== undefined) queryParams.folderId = params.folderId; const response = await this.httpClient.get('/api/search', { params: queryParams }); if (!Array.isArray(response.data)) { throw new ValidationError('Invalid response format from Grafana API'); } return response.data as Dashboard[]; }, 'searchDashboards'); } /** * Get dashboard by UID with Result wrapper */ async getDashboardResult(uid: string, version?: number): AsyncResult<DashboardDetail> { return this.execute(async () => { const url = version ? `/api/dashboards/uid/${uid}?version=${version}` : `/api/dashboards/uid/${uid}`; try { const response = await this.httpClient.get(url); return response.data as DashboardDetail; } catch (error: any) { if (error.status === 404) { throw new NotFoundError('Dashboard', uid); } throw error; } }, 'getDashboard'); } /** * Create dashboard with Result wrapper */ async createDashboardResult(params: z.infer<typeof CreateDashboardSchema>): AsyncResult<any> { return this.execute(async () => { const payload = { dashboard: { ...params.dashboard, id: null, // Ensure new dashboard }, folderId: params.folderId || 0, overwrite: params.overwrite, }; const response = await this.httpClient.post('/api/dashboards/db', payload); return response.data; }, 'createDashboard'); } // Service lifecycle methods protected async onInitialize(): Promise<void> { await super.onInitialize(); console.log(`📊 ${this.name} initialized successfully`); } protected async onCleanup(): Promise<void> { console.log(`🧹 ${this.name} cleanup completed`); } protected async onHealthCheck(): Promise<boolean> { try { // Test basic API connectivity await this.httpClient.get('/api/health'); // Test dashboard API access await this.httpClient.get('/api/search?limit=1'); return true; } catch (error) { console.warn(`${this.name} health check failed:`, error); return false; } } }

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/quanticsoul4772/grafana-mcp'

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