Skip to main content
Glama
dannwaneri

MCP Knowledge Base Server

by dannwaneri
index.ts12.6 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from "@modelcontextprotocol/sdk/types.js"; // Type definitions interface KnowledgeEntry { id: string; content: string; category: string; metadata: { source: string; date: string; }; } // Simulated knowledge base (we'll replace this with Vectorize later) const knowledgeBase: KnowledgeEntry[] = [ { id: "1", content: "FPL Hub handles 500,000+ API calls daily with 99.9% uptime using Cloudflare Workers", category: "product", metadata: { source: "production-metrics", date: "2024" }, }, { id: "2", content: "Cloudflare Workers AI provides access to LLMs like Llama, Mistral, and embedding models at the edge", category: "ai", metadata: { source: "cloudflare-docs", date: "2024" }, }, { id: "3", content: "Vectorize supports vector dimensions up to 1536 and uses HNSW indexing for fast similarity search", category: "vectorize", metadata: { source: "technical-specs", date: "2024" }, }, { id: "4", content: "MCP (Model Context Protocol) enables LLMs to securely access external data sources and tools", category: "mcp", metadata: { source: "anthropic-docs", date: "2024" }, }, { id: "5", content: "TypeScript MCP SDK provides server and client implementations with full type safety", category: "mcp", metadata: { source: "mcp-sdk", date: "2024" }, }, { id: "6", content: "D1 database queries return results in under 10ms within the same region using bound statements", category: "database", metadata: { source: "performance-tests", date: "2024" }, }, { id: "7", content: "RAG systems typically use chunk sizes of 500-1000 tokens with 10-20% overlap for optimal retrieval", category: "ai", metadata: { source: "rag-best-practices", date: "2024" }, }, { id: "8", content: "Workers AI embedding model 'bge-small-en-v1.5' produces 384-dimensional vectors optimized for English text", category: "ai", metadata: { source: "workers-ai-docs", date: "2024" }, }, ]; // Simple cache implementation interface CacheEntry { data: string; timestamp: number; } const cache = new Map<string, CacheEntry>(); const CACHE_TTL = 60000; // 60 seconds function getCacheKey(toolName: string, args: any): string { return `${toolName}:${JSON.stringify(args)}`; } function getFromCache(key: string): string | null { const entry = cache.get(key); if (!entry) return null; // Check if expired if (Date.now() - entry.timestamp > CACHE_TTL) { cache.delete(key); return null; } return entry.data; } function setCache(key: string, data: string): void { cache.set(key, { data, timestamp: Date.now(), }); } // Simple keyword search (we'll upgrade to vector search) function searchKnowledge(query: string, limit: number = 5): KnowledgeEntry[] { const lowerQuery = query.toLowerCase(); return knowledgeBase .filter((item) => item.content.toLowerCase().includes(lowerQuery) || item.category.toLowerCase().includes(lowerQuery) ) .slice(0, limit); } // Initialize MCP server const server = new Server( { name: "knowledge-base-server", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); // Define available tools server.setRequestHandler(ListToolsRequestSchema, async () => { const tools: Tool[] = [ { name: "search_knowledge", description: "Search the knowledge base for relevant information. Use this when you need to find specific facts or documentation.", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query or keywords", }, limit: { type: "number", description: "Maximum number of results to return", default: 5, }, }, required: ["query"], }, }, { name: "list_categories", description: "List all available knowledge base categories", inputSchema: { type: "object", properties: {}, }, }, { name: "get_by_category", description: "Get all knowledge base entries for a specific category", inputSchema: { type: "object", properties: { category: { type: "string", description: "Category to filter by (e.g., 'ai', 'technology', 'product')", }, }, required: ["category"], }, }, { name: "get_by_id", description: "Get a specific knowledge base entry by its ID", inputSchema: { type: "object", properties: { id: { type: "string", description: "The ID of the entry to retrieve", }, }, required: ["id"], }, }, { name: "advanced_search", description: "Search with additional filters like category and result limit", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query", }, category: { type: "string", description: "Optional: filter by category", }, limit: { type: "number", description: "Maximum results to return", default: 5, }, }, required: ["query"], }, }, ]; return { tools }; }); // Handle tool execution server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; // Generate cache key const cacheKey = getCacheKey(name, args); // Check cache first const cached = getFromCache(cacheKey); if (cached) { console.error(`[CACHE HIT] ${name}`, args); return { content: [ { type: "text", text: cached, }, ], }; } console.error(`[CACHE MISS] ${name}`, args); try { if (name === "search_knowledge") { const query = args?.query as string; const limit = (args?.limit as number) || 5; if (!query) { throw new Error("Query parameter is required"); } const results = searchKnowledge(query, limit); const responseText = JSON.stringify( { query, resultsCount: results.length, results: results.map((r) => ({ id: r.id, content: r.content, category: r.category, })), }, null, 2 ); // Cache the response setCache(cacheKey, responseText); return { content: [ { type: "text", text: responseText, }, ], }; } if (name === "list_categories") { const categories = [...new Set(knowledgeBase.map((item) => item.category))]; const responseText = JSON.stringify( { categories, count: categories.length, }, null, 2 ); // Cache the response setCache(cacheKey, responseText); return { content: [ { type: "text", text: responseText, }, ], }; } if (name === "get_by_category") { const category = args?.category as string; if (!category) { throw new Error("Category parameter is required"); } const results = knowledgeBase.filter( (item) => item.category.toLowerCase() === category.toLowerCase() ); if (results.length === 0) { const responseText = JSON.stringify({ category, message: "No entries found for this category", availableCategories: [ ...new Set(knowledgeBase.map((item) => item.category)), ], }); // Don't cache errors return { content: [ { type: "text", text: responseText, }, ], }; } const responseText = JSON.stringify( { category, count: results.length, entries: results.map((r) => ({ id: r.id, content: r.content, metadata: r.metadata, })), }, null, 2 ); // Cache the response setCache(cacheKey, responseText); return { content: [ { type: "text", text: responseText, }, ], }; } if (name === "get_by_id") { const id = args?.id as string; if (!id) { throw new Error("ID parameter is required"); } const entry = knowledgeBase.find((item) => item.id === id); if (!entry) { const responseText = JSON.stringify({ id, error: "Entry not found", availableIds: knowledgeBase.map((item) => item.id), }); // Don't cache errors return { content: [ { type: "text", text: responseText, }, ], isError: true, }; } const responseText = JSON.stringify( { id: entry.id, content: entry.content, category: entry.category, metadata: entry.metadata, }, null, 2 ); // Cache the response setCache(cacheKey, responseText); return { content: [ { type: "text", text: responseText, }, ], }; } if (name === "advanced_search") { const query = args?.query as string; const category = args?.category as string | undefined; const limit = (args?.limit as number) || 5; if (!query) { throw new Error("Query parameter is required"); } let results = searchKnowledge(query, knowledgeBase.length); // Apply category filter if provided if (category) { results = results.filter( (item) => item.category.toLowerCase() === category.toLowerCase() ); } // Apply limit results = results.slice(0, limit); const responseText = JSON.stringify( { query, filters: { category: category || "none", limit, }, resultsCount: results.length, results: results.map((r) => ({ id: r.id, content: r.content, category: r.category, metadata: r.metadata, })), }, null, 2 ); // Cache the response setCache(cacheKey, responseText); return { content: [ { type: "text", text: responseText, }, ], }; } throw new Error(`Unknown tool: ${name}`); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; return { content: [ { type: "text", text: JSON.stringify({ error: errorMessage, }), }, ], isError: true, }; } }); // Error handling server.onerror = (error) => { console.error("[MCP Error]", error); }; process.on("SIGINT", async () => { await server.close(); process.exit(0); }); // Start the server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Knowledge Base MCP server running on stdio"); } main().catch((error) => { console.error("Fatal error:", error); process.exit(1); });

Implementation Reference

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/dannwaneri/mcp-knowledge-base-server'

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