Skip to main content
Glama
grafana
by grafana

grafana_ui

Access Grafana UI components, documentation, themes, and metadata to build Grafana-compatible interfaces with React components, Storybook examples, and design tokens.

Instructions

Unified tool for accessing Grafana UI components, documentation, themes, and metadata

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
actionYesThe action to perform
componentNameNoName of the Grafana UI component (e.g., "Button", "Alert")
queryNoSearch query string (required for search action)
includeDescriptionNoWhether to search in documentation content (default: false)
categoryNoToken category to filter by (colors, typography, spacing, shadows, etc.)
deepNoWhether to analyze dependencies recursively (default: false)
pathNoPath within the repository (default: components directory)
ownerNoRepository owner (default: "grafana")
repoNoRepository name (default: "grafana")
branchNoBranch name (default: "main")

Implementation Reference

  • Primary MCP server.tool registration and handler implementation for the 'grafana_ui' tool. Dispatches to specific actions using axios utilities for fetching Grafana UI components, demos, metadata, etc.
    "grafana_ui", "Unified tool for accessing Grafana UI components, documentation, themes, and metadata", unifiedToolSchemaRaw, async (params) => { try { // Validate parameters based on action const validatedParams = unifiedToolSchema.parse(params); switch (validatedParams.action) { case "get_component": const sourceCode = await axios.getComponentSource( validatedParams.componentName!, ); return createSuccessResponse(sourceCode); case "get_demo": const demoCode = await axios.getComponentDemo( validatedParams.componentName!, ); return createSuccessResponse(demoCode); case "list_components": const components = await axios.getAvailableComponents(); return createSuccessResponse({ components: components.sort(), total: components.length, }); case "get_metadata": const metadata = await axios.getComponentMetadata( validatedParams.componentName!, ); if (!metadata) { throw new McpError( ErrorCode.InvalidRequest, `Metadata not found for component "${validatedParams.componentName}"`, ); } return createSuccessResponse(metadata); case "get_directory": const directoryTree = await axios.buildDirectoryTree( validatedParams.owner || axios.paths.REPO_OWNER, validatedParams.repo || axios.paths.REPO_NAME, validatedParams.path || axios.paths.COMPONENTS_PATH, validatedParams.branch || axios.paths.REPO_BRANCH, ); return createSuccessResponse(directoryTree); case "get_documentation": const mdxContent = await axios.getComponentDocumentation( validatedParams.componentName!, ); const parsedContent = parseMDXContent( validatedParams.componentName!, mdxContent, ); return createSuccessResponse({ title: parsedContent.title, sections: parsedContent.sections.map((section) => ({ title: section.title, level: section.level, content: section.content.substring(0, 500) + (section.content.length > 500 ? "..." : ""), examples: section.examples.length, })), totalExamples: parsedContent.examples.length, imports: parsedContent.imports, components: parsedContent.components, }); case "get_stories": const storyContent = await axios.getComponentDemo( validatedParams.componentName!, ); const storyMetadata = parseStoryMetadata( validatedParams.componentName!, storyContent, ); const examples = extractStoryExamples(storyContent); return createSuccessResponse({ component: storyMetadata.componentName, meta: storyMetadata.meta, totalStories: storyMetadata.totalStories, hasInteractiveStories: storyMetadata.hasInteractiveStories, examples: examples.slice(0, 5), rawStoryCode: storyContent.substring(0, 1000) + (storyContent.length > 1000 ? "..." : ""), }); case "get_tests": const testContent = await axios.getComponentTests( validatedParams.componentName!, ); const testDescriptions = []; const testRegex = /(describe|it|test)\s*\(\s*['`"]([^'`"]+)['`"]/g; let match; while ((match = testRegex.exec(testContent)) !== null) { testDescriptions.push({ type: match[1], description: match[2], }); } return createSuccessResponse({ component: validatedParams.componentName, testDescriptions: testDescriptions.slice(0, 10), totalTests: testDescriptions.filter( (t) => t.type === "it" || t.type === "test", ).length, testCode: testContent.substring(0, 2000) + (testContent.length > 2000 ? "..." : ""), }); case "search": const searchResults = await axios.searchComponents( validatedParams.query!, validatedParams.includeDescription || false, ); return createSuccessResponse({ query: validatedParams.query, includeDescription: validatedParams.includeDescription || false, results: searchResults, totalResults: searchResults.length, }); case "get_theme_tokens": const themeFiles = await axios.getThemeFiles( validatedParams.category, ); const processedThemes: any = {}; for (const [themeName, themeContent] of Object.entries( themeFiles.themes, )) { if (typeof themeContent === "string") { const tokens = extractThemeTokens(themeContent); const themeMetadata = extractThemeMetadata(themeContent); processedThemes[themeName] = { metadata: themeMetadata, tokens: validatedParams.category ? filterTokensByCategory(tokens, validatedParams.category) : tokens, }; } } return createSuccessResponse({ category: validatedParams.category || "all", themes: processedThemes, availableThemes: Object.keys(processedThemes), }); case "get_dependencies": const dependencies = await axios.getComponentDependencies( validatedParams.componentName!, validatedParams.deep || false, ); return createSuccessResponse(dependencies); default: throw new McpError( ErrorCode.InvalidParams, `Unknown action: ${validatedParams.action}`, ); } } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `Failed to execute action "${(params as any).action}": ${error instanceof Error ? error.message : String(error)}`, ); } }, );
  • Zod validation schema for 'grafana_ui' tool inputs, defining actions and conditional parameters.
    const unifiedToolSchema = z .object({ action: z.enum([ "get_component", "get_demo", "list_components", "get_metadata", "get_directory", "get_documentation", "get_stories", "get_tests", "search", "get_theme_tokens", "get_dependencies", ]), componentName: z.string().optional(), query: z.string().optional(), includeDescription: z.boolean().optional(), category: z.string().optional(), deep: z.boolean().optional(), path: z.string().optional(), owner: z.string().optional(), repo: z.string().optional(), branch: z.string().optional(), }) .refine( (data) => { // Validate required parameters based on action switch (data.action) { case "get_component": case "get_demo": case "get_metadata": case "get_documentation": case "get_stories": case "get_tests": case "get_dependencies": return !!data.componentName; case "search": return !!data.query; case "list_components": case "get_directory": case "get_theme_tokens": return true; default: return false; } }, { message: "Missing required parameters for the specified action", }, );
  • src/handler.ts:168-175 (registration)
    Tool schema lookup in request handler, specifically registers schema for 'grafana_ui'.
    function getToolSchema(toolName: string): z.ZodType | undefined { try { switch (toolName) { case "grafana_ui": return unifiedToolSchema; default: return undefined; }
  • src/handler.ts:42-44 (registration)
    Registers the list tools handler using exported tools from tools.ts which includes 'grafana_ui'.
    server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: Object.values(tools), }));
  • Grafana UI specific cache utility used for caching API responses in tool operations.
    export class GrafanaUICache { private cache: Cache; // Cache TTL configurations for different types of data private static readonly TTL = { COMPONENT_LIST: 24 * 60 * 60 * 1000, // 24 hours - component list changes rarely COMPONENT_SOURCE: 12 * 60 * 60 * 1000, // 12 hours - source code changes occasionally COMPONENT_METADATA: 6 * 60 * 60 * 1000, // 6 hours - metadata changes occasionally COMPONENT_STORIES: 6 * 60 * 60 * 1000, // 6 hours - stories change occasionally COMPONENT_DOCS: 6 * 60 * 60 * 1000, // 6 hours - docs change occasionally DIRECTORY_STRUCTURE: 24 * 60 * 60 * 1000, // 24 hours - directory structure changes rarely RATE_LIMIT: 5 * 60 * 1000, // 5 minutes - rate limit info changes frequently PARSED_METADATA: 12 * 60 * 60 * 1000, // 12 hours - parsed metadata is expensive to compute }; constructor(cache: Cache) { this.cache = cache; } /** * Generate cache key for component source code */ componentSourceKey(componentName: string): string { return `component:${componentName}:source`; } /** * Generate cache key for component metadata */ componentMetadataKey(componentName: string): string { return `component:${componentName}:metadata`; } /** * Generate cache key for component stories */ componentStoriesKey(componentName: string): string { return `component:${componentName}:stories`; } /** * Generate cache key for component documentation */ componentDocsKey(componentName: string): string { return `component:${componentName}:docs`; } /** * Generate cache key for component files */ componentFilesKey(componentName: string): string { return `component:${componentName}:files`; } /** * Generate cache key for parsed component metadata */ componentParsedMetadataKey(componentName: string): string { return `component:${componentName}:parsed-metadata`; } /** * Generate cache key for component list */ componentListKey(): string { return "components:list"; } /** * Generate cache key for directory structure */ directoryStructureKey(path?: string): string { return `directory:${path || "components"}:structure`; } /** * Generate cache key for rate limit info */ rateLimitKey(): string { return "github:rate-limit"; } /** * Cache component source code */ async getOrFetchComponentSource( componentName: string, fetchFn: () => Promise<string>, ): Promise<string> { return this.cache.getOrFetch( this.componentSourceKey(componentName), fetchFn, GrafanaUICache.TTL.COMPONENT_SOURCE, ); } /** * Cache component metadata */ async getOrFetchComponentMetadata( componentName: string, fetchFn: () => Promise<any>, ): Promise<any> { return this.cache.getOrFetch( this.componentMetadataKey(componentName), fetchFn, GrafanaUICache.TTL.COMPONENT_METADATA, ); } /** * Cache component stories */ async getOrFetchComponentStories( componentName: string, fetchFn: () => Promise<string>, ): Promise<string> { return this.cache.getOrFetch( this.componentStoriesKey(componentName), fetchFn, GrafanaUICache.TTL.COMPONENT_STORIES, ); } /** * Cache component documentation */ async getOrFetchComponentDocs( componentName: string, fetchFn: () => Promise<string>, ): Promise<string> { return this.cache.getOrFetch( this.componentDocsKey(componentName), fetchFn, GrafanaUICache.TTL.COMPONENT_DOCS, ); } /** * Cache component files */ async getOrFetchComponentFiles( componentName: string, fetchFn: () => Promise<any>, ): Promise<any> { return this.cache.getOrFetch( this.componentFilesKey(componentName), fetchFn, GrafanaUICache.TTL.COMPONENT_METADATA, ); } /** * Cache parsed component metadata */ async getOrFetchParsedMetadata( componentName: string, fetchFn: () => Promise<any>, ): Promise<any> { return this.cache.getOrFetch( this.componentParsedMetadataKey(componentName), fetchFn, GrafanaUICache.TTL.PARSED_METADATA, ); } /** * Cache component list */ async getOrFetchComponentList( fetchFn: () => Promise<string[]>, ): Promise<string[]> { return this.cache.getOrFetch( this.componentListKey(), fetchFn, GrafanaUICache.TTL.COMPONENT_LIST, ); } /** * Cache directory structure */ async getOrFetchDirectoryStructure( path: string, fetchFn: () => Promise<any>, ): Promise<any> { return this.cache.getOrFetch( this.directoryStructureKey(path), fetchFn, GrafanaUICache.TTL.DIRECTORY_STRUCTURE, ); } /** * Cache rate limit info */ async getOrFetchRateLimit(fetchFn: () => Promise<any>): Promise<any> { return this.cache.getOrFetch( this.rateLimitKey(), fetchFn, GrafanaUICache.TTL.RATE_LIMIT, ); } /** * Invalidate all cache entries for a specific component */ invalidateComponent(componentName: string): void { const prefixes = [`component:${componentName}:`]; prefixes.forEach((prefix) => { this.cache.deleteByPrefix(prefix); }); } /** * Invalidate all component-related cache entries */ invalidateAllComponents(): void { this.cache.deleteByPrefix("component:"); this.cache.delete(this.componentListKey()); } /** * Get cache statistics */ getStats(): { totalItems: number; componentSourceCached: number; componentMetadataCached: number; componentStoriesCached: number; componentDocsCached: number; expiredItems: number; } { const totalItems = this.cache.size(); // Count different types of cached items let componentSourceCached = 0; let componentMetadataCached = 0; let componentStoriesCached = 0; let componentDocsCached = 0; // This is a simple approximation - in a real implementation, // we'd iterate through the cache keys to count by pattern const estimatedItemsPerType = Math.floor(totalItems / 4); componentSourceCached = estimatedItemsPerType; componentMetadataCached = estimatedItemsPerType; componentStoriesCached = estimatedItemsPerType; componentDocsCached = estimatedItemsPerType; const expiredItems = this.cache.clearExpired(); return { totalItems, componentSourceCached, componentMetadataCached, componentStoriesCached, componentDocsCached, expiredItems, }; } /** * Warm up cache with commonly used components */ async warmUp( commonComponents: string[], fetchFunctions: { getComponentSource: (name: string) => Promise<string>; getComponentMetadata: (name: string) => Promise<any>; getComponentStories: (name: string) => Promise<string>; getComponentDocs: (name: string) => Promise<string>; }, ): Promise<void> { const promises = commonComponents.flatMap((componentName) => [ this.getOrFetchComponentSource(componentName, () => fetchFunctions.getComponentSource(componentName), ), this.getOrFetchComponentMetadata(componentName, () => fetchFunctions.getComponentMetadata(componentName), ), this.getOrFetchComponentStories(componentName, () => fetchFunctions.getComponentStories(componentName), ), this.getOrFetchComponentDocs(componentName, () => fetchFunctions.getComponentDocs(componentName), ), ]); // Execute all requests in parallel, but catch errors to prevent one failure from stopping others await Promise.allSettled(promises); } } // Export a singleton instance for Grafana UI cache export const grafanaUICache = new GrafanaUICache(cache);
Install Server

Other Tools

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

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