Skip to main content
Glama

mcp-adr-analysis-server

by tosin2013
research-index-resource.ts7.17 kB
/** * Research Index Resource * Provides access to research documents and findings */ import * as fs from 'fs/promises'; import * as path from 'path'; import { McpAdrError } from '../types/index.js'; import { resourceCache, generateETag } from './resource-cache.js'; import { ResourceGenerationResult } from './index.js'; export interface ResearchDocument { id: string; title: string; topic: string; path: string; lastModified: string; wordCount: number; size: number; } /** * Extract title from markdown content */ function extractTitle(content: string): string { const lines = content.split('\n'); for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith('# ')) { return trimmed.substring(2).trim(); } } return 'Untitled'; } /** * Extract topic from filename */ function extractTopic(filename: string): string { // Remove extension and parse filename // Example: perform_research_test_research_001.md -> research const baseName = filename.replace(/\.md$/, ''); const parts = baseName.split('_'); // Try to find meaningful topic if (parts.includes('research')) { const researchIndex = parts.indexOf('research'); if (researchIndex > 0) { return parts.slice(0, researchIndex + 1).join('_'); } return 'research'; } return parts[0] || 'general'; } /** * Group documents by topic */ function groupByTopic(docs: ResearchDocument[]): Record<string, number> { const grouped: Record<string, number> = {}; for (const doc of docs) { grouped[doc.topic] = (grouped[doc.topic] || 0) + 1; } return grouped; } /** * Generate comprehensive research document index resource with metadata and categorization. * * Scans project research directories and builds an index of all research documents with * metadata including titles, topics, word counts, file sizes, and last modified dates. * Supports multiple research directories for organizational flexibility. * * **Scanned Directories:** * - `docs/research/` - Primary research documentation * - `custom/research/` - Custom user research notes * * **Document Extraction:** * - Title: Extracted from first H1 heading or filename * - Topic: Derived from filename prefix or directory structure * - Metadata: Word count, file size, last modified timestamp * * @returns Promise resolving to resource generation result containing: * - data: Complete research index with documents array and summary statistics * - contentType: "application/json" * - lastModified: ISO timestamp of generation * - cacheKey: "research-index:current" * - ttl: Cache duration (300 seconds / 5 minutes) * - etag: Entity tag for cache validation * * @throws {McpAdrError} When research index generation fails due to: * - RESOURCE_GENERATION_ERROR: File system access errors or markdown parsing failures * - Cache operation failures * * @example * ```typescript * const researchIndex = await generateResearchIndexResource(); * * console.log(`Total research documents: ${researchIndex.data.summary.total}`); * console.log(`Topics covered: ${researchIndex.data.summary.byTopic.length}`); * console.log(`Total words: ${researchIndex.data.summary.totalWords}`); * * // Find research by topic * const architectureDocs = researchIndex.data.documents.filter( * doc => doc.topic === 'architecture' * ); * console.log(`Architecture research docs: ${architectureDocs.length}`); * * // Sort by recency * const recentDocs = researchIndex.data.documents * .sort((a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime()) * .slice(0, 5); * * // Expected output structure: * { * data: { * version: "1.0.0", * timestamp: "2025-10-12T17:00:00.000Z", * summary: { * total: 55, * byTopic: { * "performance": 12, * "security": 8, * "architecture": 15 * }, * totalWords: 45000, * averageWordCount: 818 * }, * documents: [ * { * id: "perform_research_research_001", * title: "TypeScript Performance Optimization", * topic: "performance", * path: "docs/research/perform_research_research_001.md", * lastModified: "2025-10-10T12:00:00.000Z", * wordCount: 1250, * size: 8192 * } * ] * }, * contentType: "application/json", * cacheKey: "research-index:current", * ttl: 300 * } * ``` * * @since v2.0.0 * @see {@link extractTitle} for title extraction logic * @see {@link extractTopic} for topic categorization */ export async function generateResearchIndexResource(): Promise<ResourceGenerationResult> { try { const cacheKey = 'research-index:current'; // Check cache const cached = await resourceCache.get<ResourceGenerationResult>(cacheKey); if (cached) { return cached; } const researchDirs = ['docs/research', 'custom/research']; const researchDocs: ResearchDocument[] = []; for (const dir of researchDirs) { const fullPath = path.resolve(process.cwd(), dir); try { await fs.access(fullPath); const files = await fs.readdir(fullPath); for (const file of files) { if (file.endsWith('.md')) { const filePath = path.join(fullPath, file); const content = await fs.readFile(filePath, 'utf-8'); const stats = await fs.stat(filePath); researchDocs.push({ id: file.replace(/\.md$/, ''), title: extractTitle(content), topic: extractTopic(file), path: path.join(dir, file), lastModified: stats.mtime.toISOString(), wordCount: content.split(/\s+/).filter(w => w.length > 0).length, size: stats.size, }); } } } catch { // Directory doesn't exist or can't be accessed, skip console.warn(`[ResearchIndexResource] Cannot access directory: ${fullPath}`); } } // Sort by last modified (newest first) researchDocs.sort( (a, b) => new Date(b.lastModified).getTime() - new Date(a.lastModified).getTime() ); const researchIndexData = { version: '1.0.0', timestamp: new Date().toISOString(), summary: { total: researchDocs.length, byTopic: groupByTopic(researchDocs), totalWordCount: researchDocs.reduce((sum, doc) => sum + doc.wordCount, 0), totalSize: researchDocs.reduce((sum, doc) => sum + doc.size, 0), }, documents: researchDocs, }; const result: ResourceGenerationResult = { data: researchIndexData, contentType: 'application/json', lastModified: new Date().toISOString(), cacheKey, ttl: 300, // 5 minutes cache etag: generateETag(researchIndexData), }; // Cache result resourceCache.set(cacheKey, result, result.ttl); return result; } catch (error) { throw new McpAdrError( `Failed to generate research index resource: ${error instanceof Error ? error.message : String(error)}`, 'RESOURCE_GENERATION_ERROR' ); } }

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/tosin2013/mcp-adr-analysis-server'

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