Skip to main content
Glama
grafana

Grafana UI MCP Server

Official
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);
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions 'accessing' resources but doesn't describe whether operations are read-only, require authentication, have rate limits, or what the output format might be. For a tool with 10 parameters and multiple actions, this lack of behavioral context is a significant gap, leaving the agent uncertain about safety and performance implications.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that lists the key resource types (components, documentation, themes, metadata) without unnecessary details. It's appropriately sized for a high-level overview, though it could be more front-loaded with a clearer verb. There's no wasted text, making it concise, but the structure lacks prioritization of the most common or critical actions.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (10 parameters, 11 possible actions) and lack of annotations or output schema, the description is incomplete. It doesn't address behavioral traits, usage scenarios, or output expectations, leaving the agent with insufficient context to invoke the tool effectively. The high schema coverage helps with parameters, but overall completeness is poor due to missing operational and safety information.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the input schema fully documents all 10 parameters with descriptions and enums. The description adds no additional meaning beyond the schema, such as explaining parameter interactions or providing examples. With high schema coverage, the baseline score is 3, as the description doesn't compensate but also doesn't detract from the schema's completeness.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states the tool provides access to Grafana UI components, documentation, themes, and metadata, which gives a general purpose. However, it's vague about what 'accessing' entails (e.g., retrieving, listing, searching) and doesn't specify the scope of 'unified' beyond listing multiple resource types. With no sibling tools, differentiation isn't needed, but the purpose lacks specificity in verbs and operational details.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives, prerequisites, or contextual constraints. It lists resource types but doesn't indicate scenarios for choosing specific actions (e.g., 'get_component' vs 'list_components'). With no sibling tools, the absence of usage guidelines is a missed opportunity to clarify its role in a broader context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

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