Skip to main content
Glama

Article Manager MCP Server

by joelmnz
api.ts•7.95 kB
import { requireAuth } from '../middleware/auth'; import { listArticles, searchArticles, readArticle, createArticle, updateArticle, deleteArticle } from '../services/articles'; import { semanticSearch, hybridSearch, getDetailedIndexStats, rebuildIndex, indexUnindexedArticles } from '../services/vectorIndex'; const SEMANTIC_SEARCH_ENABLED = process.env.SEMANTIC_SEARCH_ENABLED?.toLowerCase() === 'true'; export async function handleApiRequest(request: Request): Promise<Response> { const url = new URL(request.url); const path = url.pathname; // Health check endpoint (no auth required) if (path === '/health') { return new Response(JSON.stringify({ status: 'ok' }), { headers: { 'Content-Type': 'application/json' } }); } // All other API endpoints require authentication const authError = requireAuth(request); if (authError) return authError; try { // GET /api/search - Semantic or Hybrid search if (path === '/api/search' && request.method === 'GET') { if (!SEMANTIC_SEARCH_ENABLED) { return new Response(JSON.stringify({ error: 'Semantic search is not enabled' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } const query = url.searchParams.get('query'); const k = parseInt(url.searchParams.get('k') || '5', 10); const mode = url.searchParams.get('mode') || 'hybrid'; // 'semantic' or 'hybrid' if (!query) { return new Response(JSON.stringify({ error: 'Query parameter is required' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } const results = mode === 'semantic' ? await semanticSearch(query, k) : await hybridSearch(query, k); return new Response(JSON.stringify(results), { headers: { 'Content-Type': 'application/json' } }); } // GET /api/rag/status - Get RAG index status if (path === '/api/rag/status' && request.method === 'GET') { if (!SEMANTIC_SEARCH_ENABLED) { return new Response(JSON.stringify({ enabled: false, message: 'Semantic search is not enabled' }), { headers: { 'Content-Type': 'application/json' } }); } const stats = await getDetailedIndexStats(); return new Response(JSON.stringify({ enabled: true, ...stats }), { headers: { 'Content-Type': 'application/json' } }); } // POST /api/rag/reindex - Rebuild entire index if (path === '/api/rag/reindex' && request.method === 'POST') { if (!SEMANTIC_SEARCH_ENABLED) { return new Response(JSON.stringify({ error: 'Semantic search is not enabled' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } try { await rebuildIndex(); const stats = await getDetailedIndexStats(); return new Response(JSON.stringify({ success: true, message: 'Index rebuilt successfully', ...stats }), { headers: { 'Content-Type': 'application/json' } }); } catch (error) { return new Response(JSON.stringify({ success: false, error: error instanceof Error ? error.message : 'Failed to rebuild index' }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } } // POST /api/rag/index-unindexed - Index only unindexed articles if (path === '/api/rag/index-unindexed' && request.method === 'POST') { if (!SEMANTIC_SEARCH_ENABLED) { return new Response(JSON.stringify({ error: 'Semantic search is not enabled' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } try { const result = await indexUnindexedArticles(); const stats = await getDetailedIndexStats(); return new Response(JSON.stringify({ success: true, indexed: result.indexed, failed: result.failed, ...stats }), { headers: { 'Content-Type': 'application/json' } }); } catch (error) { return new Response(JSON.stringify({ success: false, error: error instanceof Error ? error.message : 'Failed to index articles' }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } } // GET /api/articles - List all articles if (path === '/api/articles' && request.method === 'GET') { const query = url.searchParams.get('q'); if (query) { // Search articles const results = await searchArticles(query); return new Response(JSON.stringify(results), { headers: { 'Content-Type': 'application/json' } }); } else { // List all articles const articles = await listArticles(); return new Response(JSON.stringify(articles), { headers: { 'Content-Type': 'application/json' } }); } } // GET /api/articles/:filename - Read single article if (path.startsWith('/api/articles/') && request.method === 'GET') { const filename = path.replace('/api/articles/', ''); const article = await readArticle(filename); if (!article) { return new Response(JSON.stringify({ error: 'Article not found' }), { status: 404, headers: { 'Content-Type': 'application/json' } }); } return new Response(JSON.stringify(article), { headers: { 'Content-Type': 'application/json' } }); } // POST /api/articles - Create new article if (path === '/api/articles' && request.method === 'POST') { const body = await request.json(); const { title, content } = body; if (!title || !content) { return new Response(JSON.stringify({ error: 'Title and content are required' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } const article = await createArticle(title, content); return new Response(JSON.stringify(article), { status: 201, headers: { 'Content-Type': 'application/json' } }); } // PUT /api/articles/:filename - Update article if (path.startsWith('/api/articles/') && request.method === 'PUT') { const filename = path.replace('/api/articles/', ''); const body = await request.json(); const { title, content } = body; if (!title || !content) { return new Response(JSON.stringify({ error: 'Title and content are required' }), { status: 400, headers: { 'Content-Type': 'application/json' } }); } const article = await updateArticle(filename, title, content); return new Response(JSON.stringify(article), { headers: { 'Content-Type': 'application/json' } }); } // DELETE /api/articles/:filename - Delete article if (path.startsWith('/api/articles/') && request.method === 'DELETE') { const filename = path.replace('/api/articles/', ''); await deleteArticle(filename); return new Response(JSON.stringify({ success: true }), { headers: { 'Content-Type': 'application/json' } }); } // Route not found return new Response(JSON.stringify({ error: 'Not found' }), { status: 404, headers: { 'Content-Type': 'application/json' } }); } catch (error) { console.error('API Error:', error); return new Response(JSON.stringify({ error: error instanceof Error ? error.message : 'Internal server error' }), { status: 500, headers: { 'Content-Type': 'application/json' } }); } }

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/joelmnz/mcp-markdown-manager'

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