Skip to main content
Glama
appian-design

Design System MCP Server

refresh-sources

Initiate a refresh of the documentation sources to update available design system components, layouts, and patterns.

Instructions

Trigger manual refresh of documentation sources

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The refreshSources() method on SourceManager clears the internal cache and updates the lastRefresh timestamp. This is the core execution logic triggered when the 'refresh-sources' tool is called.
    async refreshSources(): Promise<void> {
      this.cache = {};
      this.lastRefresh = Date.now();
    }
  • The MCP tool registration for 'refresh-sources'. The handler function calls sourceManager.refreshSources() and returns a success message.
    // Tool 2: Refresh documentation sources
    server.tool(
      "refresh-sources",
      "Trigger manual refresh of documentation sources",
      {},
      async () => {
        await sourceManager.refreshSources();
        
        return {
          content: [
            {
              type: "text",
              text: "Documentation sources refreshed successfully. Cache cleared and sources will be re-synced on next request.",
            },
          ],
        };
      },
    );
  • src/index.ts:146-163 (registration)
    The tool is registered via server.tool() on an McpServer instance with the name 'refresh-sources'. No input schema is defined (empty object {}), and the description states it triggers a manual refresh.
    // Tool 2: Refresh documentation sources
    server.tool(
      "refresh-sources",
      "Trigger manual refresh of documentation sources",
      {},
      async () => {
        await sourceManager.refreshSources();
        
        return {
          content: [
            {
              type: "text",
              text: "Documentation sources refreshed successfully. Cache cleared and sources will be re-synced on next request.",
            },
          ],
        };
      },
    );
  • The tool accepts no input parameters (empty schema {}). No Zod schema is used for validation since there are no inputs.
    // Tool 2: Refresh documentation sources
    server.tool(
      "refresh-sources",
      "Trigger manual refresh of documentation sources",
      {},
  • The SourceManager class encapsulates the cache and refresh logic. It has a `cache` field (ContentCache dict) and `lastRefresh` timestamp that are cleared/reset by refreshSources().
    export class SourceManager {
      private config: Config;
      private cache: ContentCache = {};
      private lastRefresh: number = 0;
    
      constructor() {
        this.config = loadConfig();
      }
    
      /**
       * Get content from the appropriate source(s) with merging logic
       */
      async getContent(filePath: string): Promise<SourcedContent | null> {
        const cacheKey = filePath;
        
        // Check cache first
        if (this.config.cache_enabled && this.cache[cacheKey]) {
          const cached = this.cache[cacheKey];
          const age = Date.now() - cached.timestamp;
          if (age < this.config.refresh_interval * 1000) {
            return cached.content;
          }
        }
    
        // Get content from all enabled sources
        const sources = this.getEnabledSources();
        const contentResults: Array<{ content: ParsedMarkdown; source: DocumentationSource; sourceKey: 'public' | 'internal' }> = [];
    
        for (const [sourceKey, source] of sources) {
          try {
            const content = await this.fetchFromSource(source, filePath);
            if (content) {
              contentResults.push({ 
                content: { ...content, source: sourceKey }, 
                source, 
                sourceKey 
              });
            }
          } catch (error) {
            console.error(`[SOURCE-MANAGER] Failed to fetch from ${sourceKey}:`, error);
          }
        }
    
        if (contentResults.length === 0) {
          return null;
        }
    
        // Merge content based on priority
        const mergedContent = this.mergeContent(contentResults, filePath);
        
        // Cache the result
        if (this.config.cache_enabled) {
          this.cache[cacheKey] = {
            content: mergedContent,
            timestamp: Date.now()
          };
        }
    
        return mergedContent;
      }
    
      /**
       * Get list of available sources and their status
       */
      getSourceStatus(): Array<{
        name: 'public' | 'internal';
        enabled: boolean;
        priority: number;
        auth_required: boolean;
        last_sync?: string;
      }> {
        const sources = Object.entries(this.config.documentation.sources)
          .filter(([_, source]) => source !== undefined) as Array<['public' | 'internal', DocumentationSource]>;
    
        return sources.map(([name, source]) => ({
          name,
          enabled: source.enabled,
          priority: source.priority,
          auth_required: source.auth_required,
          last_sync: this.lastRefresh ? new Date(this.lastRefresh).toISOString() : undefined
        }));
      }
    
      /**
       * Clear cache and force refresh
       */
      async refreshSources(): Promise<void> {
        this.cache = {};
        this.lastRefresh = Date.now();
      }
    
      /**
       * Get enabled sources sorted by priority
       */
      private getEnabledSources(): Array<['public' | 'internal', DocumentationSource]> {
        const sources = Object.entries(this.config.documentation.sources)
          .filter(([_, source]) => source?.enabled) as Array<['public' | 'internal', DocumentationSource]>;
        
        // Sort by priority (lower numbers first, higher priority sources last for override logic)
        return sources.sort(([, a], [, b]) => a.priority - b.priority);
      }
    
      /**
       * Fetch content from a specific source
       */
      private async fetchFromSource(source: DocumentationSource, filePath: string): Promise<ParsedMarkdown | null> {
        try {
          // For now, use GitHub API (in future this could support local files, git clones, etc.)
          const repoUrl = source.repo;
          const match = repoUrl.match(/github\.com\/([^\/]+)\/([^\/]+?)(?:\.git)?$/);
          
          if (!match) {
            throw new Error(`Invalid GitHub repository URL: ${repoUrl}`);
          }
    
          const [, owner, repo] = match;
          
          // Include submodule_path if specified
          const basePath = source.submodule_path ? `${source.submodule_path}/` : '';
          const url = `https://api.github.com/repos/${owner}/${repo}/contents/${basePath}${filePath}`;
          
          const headers: Record<string, string> = {
            'Accept': 'application/vnd.github.v3+json',
            'User-Agent': 'design-system-mcp-server'
          };
    
          const token = getAuthToken(source);
          if (token) {
            headers['Authorization'] = `Bearer ${token}`;
          }
    
          const response = await fetch(url, { headers });
          
          if (!response.ok) {
            if (response.status === 404) {
              return null; // File not found in this source
            }
            throw new Error(`HTTP error! status: ${response.status}`);
          }
          
          const data = await response.json();
          const content = atob(data.content);
          
          return this.parseFrontmatter(content);
        } catch (error) {
          console.error(`[SOURCE-MANAGER] Error fetching from source:`, error);
          return null;
        }
      }
    
      /**
       * Parse frontmatter from markdown content
       */
      private parseFrontmatter(content: string): ParsedMarkdown {
        const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
        const match = content.match(frontmatterRegex);
        
        let frontmatter = {};
        let markdownContent = content;
        
        if (match) {
          const frontmatterText = match[1];
          markdownContent = match[2];
          
          // Parse YAML-like frontmatter
          frontmatterText.split('\n').forEach(line => {
            const colonIndex = line.indexOf(':');
            if (colonIndex > 0) {
              const key = line.substring(0, colonIndex).trim();
              let value = line.substring(colonIndex + 1).trim();
              
              // Remove quotes if present
              if ((value.startsWith('"') && value.endsWith('"')) || 
                  (value.startsWith("'") && value.endsWith("'"))) {
                value = value.slice(1, -1);
              }
              
              (frontmatter as any)[key] = value;
            }
          });
        }
        
        // Extract Design and Development sections
        const designMatch = markdownContent.match(/## Design\n([\s\S]*?)(?=## Development|$)/);
        const developmentMatch = markdownContent.match(/## Development\n([\s\S]*?)$/);
        
        return {
          frontmatter,
          content: markdownContent,
          designSection: designMatch ? designMatch[1].trim() : '',
          developmentSection: developmentMatch ? developmentMatch[1].trim() : '',
          source: 'public', // Will be overridden by caller
          last_updated: (frontmatter as any).last_updated
        };
      }
    
      /**
       * Merge content from multiple sources based on priority
       */
      private mergeContent(
        contentResults: Array<{ content: ParsedMarkdown; source: DocumentationSource; sourceKey: 'public' | 'internal' }>,
        filePath: string
      ): SourcedContent {
        // Sort by priority (higher priority last to override)
        contentResults.sort((a, b) => a.source.priority - b.source.priority);
        
        // Use the highest priority content as base
        const primaryResult = contentResults[contentResults.length - 1];
        const primaryContent = primaryResult.content;
        
        // Determine if this is an override situation
        let overrides: 'public' | 'internal' | undefined;
        if (contentResults.length > 1) {
          const lowerPrioritySource = contentResults[0].sourceKey;
          overrides = lowerPrioritySource;
        }
    
        // Merge frontmatter from all sources (higher priority wins for conflicts)
        const mergedFrontmatter = {};
        for (const result of contentResults) {
          Object.assign(mergedFrontmatter, result.content.frontmatter);
        }
    
        return {
          content: primaryContent.content,
          frontmatter: mergedFrontmatter,
          source: primaryResult.sourceKey,
          overrides,
          last_updated: primaryContent.last_updated || new Date().toISOString(),
          file_path: filePath
        };
      }
    }
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It only states that it triggers a refresh, omitting details about side effects, required permissions, rate limits, or whether the operation is destructive.

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

Conciseness5/5

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

The description is a single, front-loaded sentence with no unnecessary words. It conveys the core purpose efficiently.

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

Completeness3/5

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

Given the tool has no parameters and no output schema, the description provides the minimal information about its purpose. However, it lacks behavioral context such as expected execution time, whether it is asynchronous, or any side effects, making it less than fully complete.

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

Parameters4/5

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

There are no parameters, so the input schema is fully covered. Per guidelines, the baseline score is 4 for zero parameters, and the description adds no additional parameter information because none is needed.

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

Purpose5/5

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

The description uses a specific verb ('Trigger manual refresh') and clearly identifies the resource ('documentation sources'). It effectively distinguishes the tool from its read-only siblings like 'get-component-details' and 'list-categories'.

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

Usage Guidelines3/5

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

The description implies that this tool is for manually refreshing sources, but it does not specify when to use it versus alternatives, nor does it mention any prerequisites or conditions for use.

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/appian-design/aurora-mcp'

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