Skip to main content
Glama

mcp-adr-analysis-server

by tosin2013
research-by-topic-resource.ts4.81 kB
/** * Research by Topic Resource - Topic-specific research documents * URI Pattern: adr://research/{topic} */ import { URLSearchParams } from 'url'; import * as path from 'path'; import * as fs from 'fs/promises'; import { McpAdrError } from '../types/index.js'; import { resourceCache, generateETag } from './resource-cache.js'; import { ResourceGenerationResult } from './index.js'; import { resourceRouter } from './resource-router.js'; export interface ResearchDocument { id: string; title: string; topic: string; path: string; content: string; lastModified: string; wordCount: number; size: number; } /** * Extract title from markdown content */ function extractTitle(content: string): string { const titleMatch = content.match(/^#\s+(.+)$/m); if (titleMatch && titleMatch[1]) { return titleMatch[1].trim(); } return 'Untitled'; } /** * Generate topic summary from documents */ function generateTopicSummary(documents: ResearchDocument[]): { totalWords: number; totalSize: number; lastUpdated: string; documentCount: number; } { const firstDoc = documents[0]; return { totalWords: documents.reduce((sum, doc) => sum + doc.wordCount, 0), totalSize: documents.reduce((sum, doc) => sum + doc.size, 0), lastUpdated: documents.length > 0 && firstDoc ? documents.reduce( (latest, doc) => (doc.lastModified > latest ? doc.lastModified : latest), firstDoc.lastModified ) : new Date().toISOString(), documentCount: documents.length, }; } /** * Check if a file matches the topic */ function matchesTopic(filename: string, content: string, topic: string): boolean { const normalizedTopic = topic.toLowerCase().replace(/-/g, '_'); const normalizedFilename = filename.toLowerCase(); const normalizedContent = content.toLowerCase(); // Check filename if ( normalizedFilename.includes(topic.toLowerCase()) || normalizedFilename.includes(normalizedTopic) ) { return true; } // Check content (first 1000 characters for keywords) const contentPreview = normalizedContent.substring(0, 1000); if (contentPreview.includes(topic.toLowerCase()) || contentPreview.includes(normalizedTopic)) { return true; } return false; } /** * Generate research by topic resource */ export async function generateResearchByTopicResource( params: Record<string, string>, _searchParams: URLSearchParams ): Promise<ResourceGenerationResult> { const topic = params['topic']; if (!topic) { throw new McpAdrError('Missing required parameter: topic', 'INVALID_PARAMS'); } const cacheKey = `research-topic:${topic}`; // Check cache const cached = await resourceCache.get<ResourceGenerationResult>(cacheKey); if (cached) { return cached; } const researchDirs = ['docs/research', 'custom/research']; const relatedDocs: ResearchDocument[] = []; // Scan research directories for (const dir of researchDirs) { const fullPath = path.resolve(process.cwd(), dir); try { 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'); // Check if file matches topic if (matchesTopic(file, content, topic)) { const stats = await fs.stat(filePath); relatedDocs.push({ id: file.replace('.md', ''), title: extractTitle(content), topic, path: path.join(dir, file), content, lastModified: stats.mtime.toISOString(), wordCount: content.split(/\s+/).length, size: stats.size, }); } } } } catch { // Directory doesn't exist or can't be read, skip silently continue; } } // Sort by last modified (newest first) relatedDocs.sort((a, b) => b.lastModified.localeCompare(a.lastModified)); if (relatedDocs.length === 0) { throw new McpAdrError(`No research documents found for topic: ${topic}`, 'RESOURCE_NOT_FOUND'); } const topicData = { topic, documentCount: relatedDocs.length, documents: relatedDocs, summary: generateTopicSummary(relatedDocs), }; const result: ResourceGenerationResult = { data: topicData, contentType: 'application/json', lastModified: new Date().toISOString(), cacheKey, ttl: 600, // 10 minutes cache etag: generateETag(topicData), }; // Cache result resourceCache.set(cacheKey, result, result.ttl); return result; } // Register route resourceRouter.register( '/research/{topic}', generateResearchByTopicResource, 'Research documents filtered by topic' );

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