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
Tool call template
This tool declares no required parameters. Add optional parameters from the input schema as needed.
{
"id": 1,
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"arguments": {},
"name": "refresh-sources"
}
}Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/source-manager.ts:117-120 (handler)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(); } - src/index.ts:146-163 (handler)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.", }, ], }; }, ); - src/index.ts:146-150 (schema)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", {}, - src/source-manager.ts:31-263 (helper)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 }; } }