MCP Server Development Guide for Mnemoverse Docs β
Version: 1.1.0
Date: 2025-07-30
Status: Implementation complete - production ready
β IMPLEMENTATION COMPLETE
This document covers the technical implementation of MCP servers for Mnemoverse documentation. We have successfully published two production-ready packages:@mnemoverse/mcp-docs-server
(production server for Mnemoverse docs) and@mcp-x/mcp-docs-server
(universal template for any documentation project). The architecture leverages VitePress's search capabilities and provides standardized MCP protocol access. Both servers are live, tested, and ready for use in IDEs and AI tools.
Document Purpose β
This document provides technical specifications and implementation details for MCP (Model Context Protocol) servers in the Mnemoverse ecosystem. It covers both the production server powering Mnemoverse documentation and the universal template for community use.
Available Packages β
@mnemoverse/mcp-docs-server β
Production server - Powers Mnemoverse's live documentation with research library access, optimized for our specific content structure.
@mcp-x/mcp-docs-server β
Universal template - Community-friendly template for creating MCP servers for any documentation project. Features include markdown parsing, frontmatter support, and configurable search.
Architectural Overview β
Existing System β
- VitePress documentation with search
- Document structure:
/docs/research/
,/docs/guides/
,/docs/vision/
- Search index: MiniSearch with fuzzy search settings
- Static hosting: GitHub Pages
- Published packages: Both production and template servers on npm
Target MCP Server Architecture β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MCP Client β
β (AI Agent/Claude) β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ
β JSON-RPC 2.0 / HTTP
βββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββββ
β βΌ β
β MCP Server β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββ β
β β Tools API β β Resources API β β Search API β β
β β β β β β β β
β β β’ search_docs β β β’ get_document β β VitePress β β
β β β’ list_docs β β β’ get_section β β Search β β
β β β’ validate_refs β β β’ get_metadata β β Integration β β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββ β
β β β
β βββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββ β
β β File System βΌ β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β /docs/ β β β
β β β βββ research/ β β β
β β β β βββ mcp-x-protocol-spec.md β β β
β β β β βββ memory-solutions-landscape.md β β β
β β β β βββ ... β β β
β β β βββ guides/ β β β
β β β βββ vision/ β β β
β β β βββ .vitepress/config.mjs β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Quick Start Guide β
π Try the Production Server (2 minutes) β
# Install the Mnemoverse docs server
npm install -g @mnemoverse/mcp-docs-server
# Test it works
@mnemoverse/mcp-docs-server --version
Configure Claude Desktop:
{
"mcpServers": {
"mnemoverse-docs": {
"command": "node",
"args": ["/usr/local/lib/node_modules/@mnemoverse/mcp-docs-server/index.js"]
}
}
}
π οΈ Create Your Own MCP Server (5 minutes) β
# Use the universal template
npm install -g @mcp-x/mcp-docs-server
# Clone for customization
git clone https://github.com/mnemoverse/mcp-docs-server.git
cd mcp-docs-server
npm install
npm run build
Test your server:
# Start in development mode
npm run dev
# Test with MCP Inspector
npx @modelcontextprotocol/inspector node dist/index.js
π§ͺ Development Workflow β
# 1. Fork the template
git clone https://github.com/mnemoverse/mcp-docs-server.git my-docs-server
cd my-docs-server
# 2. Install dependencies
npm install
# 3. Configure for your docs
export DOCS_BASE_URL="https://your-site.com"
export PROJECT_NAME="Your Project"
# 4. Start development
npm run dev
# 5. Test integration
npx @modelcontextprotocol/inspector node dist/index.js
Implementation Status β
Production Deployment β β
Both MCP servers are live and production-ready:
@mnemoverse/mcp-docs-server
- π¦ npm: Published and maintained
- π GitHub: mnemoverse/mnemoverse-docs
- π― Purpose: Production server for Mnemoverse documentation
- π§ Tools:
mnemoverseDocs
,mnemoverseResearch
,mnemoverseSearch
@mcp-x/mcp-docs-server
- π¦ npm: Published as universal template
- π GitHub: mnemoverse/mcp-docs-server
- π― Purpose: Universal template for any documentation project
- π§ Tools:
list_documents
,get_document
,search_docs
Usage Examples β
# Use Mnemoverse production server
npx @mnemoverse/mcp-docs-server
# Use universal template for your docs
npx @mcp-x/mcp-docs-server /path/to/your/docs
Technical Requirements β
SDK Selection Analysis β
Current Implementation: TypeScript SDK
Production advantages realized:
- VitePress Integration: Direct integration with existing Node.js ecosystem β
- Fast Development: Rapid deployment and iteration β
- npm Distribution: Easy installation via npx β
- Cross-platform: Works on macOS, Linux, Windows β
Alternative for Enterprise/Scale: Java SDK
Java SDK advantages:
- π₯ Official Maturity: 2.1k stars, active development, enterprise-ready
- β‘ Performance: JVM optimizations for high loads
- π οΈ Rich Features: Full MCP support + Spring Boot integration
- π¦ Multiple Transports: STDIO, HTTP SSE, WebFlux, WebMVC
- π§ͺ Testing Suite: Dedicated
mcp-test
utilities
When to choose Java SDK:
- Phase 2/3: Standalone MCP server deployment
- High-load production environment (>1000 concurrent requests)
- Microservices architecture
- Enterprise integration requirements
Alternatives also considered:
- Python SDK: Good for ML integrations, but requires additional infrastructure
- Go SDK: High performance, but harder to integrate with VitePress
- Rust SDK: Maximum performance, but overly complex for this task
Migration Path to Java SDK (Phase 2/3) β
Architecture for Java SDK deployment:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Load Balancer β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββΌββββββββββββββββββββββββββββββββ
β βΌ β
β Spring Boot MCP Server β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β Spring WebFlux β β
β β βββββββββββββββ βββββββββββββββββββββββ β β
β β β MCP Tools β β Document Service β β
β β β Controller β β (Cache Layer) β β β
β β βββββββββββββββ βββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β File Watcher β β
β β (Spring Boot Actuator) β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββ¬ββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββΌββββββββββββββββββββββββββββββββ
β File System β
β (VitePress docs via NFS/Volume) β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Java SDK Implementation Highlights:
@RestController
@RequestMapping("/mcp")
public class McpController {
@Autowired
private McpServer mcpServer;
@PostMapping
public Mono<McpResponse> handleMcpRequest(@RequestBody McpRequest request) {
return mcpServer.handleRequest(request)
.timeout(Duration.ofSeconds(30))
.onErrorResume(this::handleError);
}
}
@Service
public class DocumentSearchService {
@Cacheable("document-search")
public List<SearchResult> searchDocuments(String query, SearchOptions options) {
// High-performance search with Lucene/Elasticsearch integration
return luceneSearchEngine.search(query, options);
}
}
Migration Benefits:
- π 10x Performance: JVM optimizations for high-load scenarios
- π Spring Metrics: Built-in monitoring with Micrometer/Prometheus
- π Auto-scaling: Container orchestration ready (K8s/Docker Swarm)
- π‘οΈ Security: Spring Security integration for enterprise auth
- π Observability: Distributed tracing with Zipkin/Jaeger
Migration Timeline:
- Phase 1: TypeScript MVP (current) - proof of concept
- Phase 2: Performance evaluation - if >1000 concurrent users
- Phase 3: Java SDK migration - enterprise deployment
Core Dependencies β
Production Dependencies (Current Implementation):
{
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"gray-matter": "^4.0.3",
"markdown-it": "^14.1.0",
"path": "^0.12.7",
"fs": "^0.0.1-security"
},
"devDependencies": {
"@types/node": "^20.11.30",
"typescript": "^5.4.3",
"tsx": "^4.7.1"
},
"peerDependencies": {
"node": ">=18.0.0"
}
}
Published Packages:
- [mnemoverse/mcp-docs-server] (citation not found)(https://www.npmjs.com/package/@mnemoverse/mcp-docs-server) - Production ready, Mnemoverse-specific
- [mcp-x/mcp-docs-server] (citation not found)(https://www.npmjs.com/package/@mcp-x/mcp-docs-server) - Universal template for any project
Installation and Usage:
# Install production server
npm install -g @mnemoverse/mcp-docs-server
# Or use universal template
npm install -g @mcp-x/mcp-docs-server
# Test local installation
node -e "console.log(require('@mnemoverse/mcp-docs-server/package.json').version)"
Detailed MCP Server Specification β
1. Server Configuration β
// src/config/mcpServerConfig.ts
export interface McpServerConfig {
name: string;
version: string;
description: string;
// Transport configuration
transport: {
type: 'streamable-http' | 'stdio';
host: string;
port: number;
cors: {
origin: string[] | string;
allowedHeaders: string[];
exposedHeaders: string[];
};
};
// Documentation paths
docs: {
basePath: string;
searchIndexPath: string;
cacheEnabled: boolean;
watchForChanges: boolean;
};
// Feature flags
features: {
semanticSearch: boolean;
documentValidation: boolean;
citationExtraction: boolean;
fullTextIndexing: boolean;
};
}
export const defaultConfig: McpServerConfig = {
name: "mnemoverse-docs-server",
version: "1.0.0",
description: "MCP Server for Mnemoverse Research Documentation",
transport: {
type: 'streamable-http',
host: 'localhost',
port: 3001,
cors: {
origin: ['http://localhost:3000', 'https://docs.mnemoverse.org'],
allowedHeaders: ['Content-Type', 'mcp-session-id'],
exposedHeaders: ['mcp-session-id']
}
},
docs: {
basePath: '../docs', // Relative path from mcp-server/
searchIndexPath: '../docs/.vitepress/cache', // Use existing VitePress cache
cacheEnabled: true,
watchForChanges: true
},
features: {
semanticSearch: false, // Phase 2
documentValidation: true,
citationExtraction: true,
fullTextIndexing: true
}
};
2. Tools Implementation β
2.1 search_docs Tool β
// src/tools/searchDocs.ts
import { z } from 'zod';
import { Tool } from '@modelcontextprotocol/sdk/types.js';
const SearchDocsInputSchema = z.object({
query: z.string().min(1).describe("Search query for documentation"),
filters: z.object({
sections: z.array(z.enum(['research', 'guides', 'vision', 'legal'])).optional()
.describe("Restrict search to specific documentation sections"),
fileTypes: z.array(z.enum(['markdown', 'md'])).optional()
.describe("File types to search (default: markdown)"),
dateRange: z.object({
from: z.string().optional().describe("ISO date string (YYYY-MM-DD)"),
to: z.string().optional().describe("ISO date string (YYYY-MM-DD)")
}).optional(),
maxResults: z.number().min(1).max(50).default(10)
.describe("Maximum number of results to return")
}).optional(),
options: z.object({
fuzzy: z.boolean().default(true).describe("Enable fuzzy search"),
prefix: z.boolean().default(true).describe("Enable prefix matching"),
includeContent: z.boolean().default(false)
.describe("Include document content in results"),
highlightMatches: z.boolean().default(true)
.describe("Highlight search matches in results")
}).optional()
});
export const searchDocsTool: Tool = {
name: "search_docs",
description: "Search through Mnemoverse documentation using advanced search capabilities",
inputSchema: SearchDocsInputSchema,
};
export interface SearchResult {
title: string;
uri: string;
section: string;
excerpt: string;
score: number;
lastModified: string;
highlights?: string[];
metadata?: DocumentMetadata;
}
export interface DocumentMetadata {
tags: string[];
authors: string[];
dateCreated: string;
wordCount: number;
readingTime: number;
citations?: Citation[];
}
export interface Citation {
key: string;
title: string;
authors: string[];
year: number;
doi?: string;
url?: string;
}
2.2 list_documents Tool β
// src/tools/listDocuments.ts
const ListDocumentsInputSchema = z.object({
section: z.enum(['research', 'guides', 'vision', 'legal', 'all'])
.default('all').describe("Documentation section to list"),
sortBy: z.enum(['title', 'date', 'size', 'relevance'])
.default('title').describe("Sort criteria for documents"),
order: z.enum(['asc', 'desc']).default('asc').describe("Sort order"),
includeMetadata: z.boolean().default(true)
.describe("Include document metadata in results"),
pagination: z.object({
page: z.number().min(1).default(1),
limit: z.number().min(1).max(100).default(20)
}).optional()
});
export const listDocumentsTool: Tool = {
name: "list_documents",
description: "List all available documents in the Mnemoverse documentation",
inputSchema: ListDocumentsInputSchema,
};
2.3 validate_citations Tool β
// src/tools/validateCitations.ts
const ValidateCitationsInputSchema = z.object({
documentPath: z.string().describe("Path to document to validate"),
checkExternalLinks: z.boolean().default(false)
.describe("Check if external citation links are accessible"),
fixBrokenRefs: z.boolean().default(false)
.describe("Attempt to fix broken citation references")
});
export const validateCitationsTool: Tool = {
name: "validate_citations",
description: "Validate citations and references in documentation",
inputSchema: ValidateCitationsInputSchema,
};
3. Resources Implementation β
3.1 Document Resource β
// src/resources/documentResource.ts
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
export const DocumentResourceTemplate = new ResourceTemplate(
"mnemoverse://docs/{section}/{path...}",
{
list: undefined, // Will be handled by list_documents tool
complete: {
section: (value: string) => ['research', 'guides', 'vision', 'legal']
.filter(s => s.startsWith(value)),
path: async (value: string, context?: any) => {
// Auto-complete file paths based on filesystem
return await getDocumentPaths(context?.arguments?.section, value);
}
}
}
);
export interface DocumentContent {
uri: string;
title: string;
content: string;
metadata: DocumentMetadata;
sections: DocumentSection[];
citations: Citation[];
lastModified: string;
}
export interface DocumentSection {
id: string;
title: string;
level: number;
content: string;
startLine: number;
endLine: number;
}
3.2 Search Index Resource β
// src/resources/searchIndexResource.ts
export const SearchIndexResourceTemplate = new ResourceTemplate(
"mnemoverse://search-index/{type}",
{
list: undefined,
complete: {
type: () => ['statistics', 'terms', 'documents', 'configuration']
}
}
);
export interface SearchIndexInfo {
totalDocuments: number;
totalTerms: number;
lastUpdated: string;
indexSize: string;
averageDocumentSize: number;
mostSearchedTerms: Array<{
term: string;
frequency: number;
}>;
}
4. Core Implementation Classes β
4.1 Document Parser β
// src/core/DocumentParser.ts
import matter from 'gray-matter';
import MarkdownIt from 'markdown-it';
export class DocumentParser {
private markdown: MarkdownIt;
constructor() {
this.markdown = new MarkdownIt({
html: true,
linkify: true,
typographer: true
});
}
async parseDocument(filePath: string): Promise<DocumentContent> {
const content = await fs.readFile(filePath, 'utf-8');
const parsed = matter(content);
return {
uri: this.pathToUri(filePath),
title: this.extractTitle(parsed),
content: parsed.content,
metadata: this.extractMetadata(parsed.data, filePath),
sections: this.extractSections(parsed.content),
citations: this.extractCitations(parsed.content),
lastModified: await this.getLastModified(filePath)
};
}
private extractSections(content: string): DocumentSection[] {
const lines = content.split('\n');
const sections: DocumentSection[] = [];
lines.forEach((line, index) => {
const match = line.match(/^(#{1,6})\s+(.+)$/);
if (match) {
sections.push({
id: this.generateSectionId(match[2]),
title: match[2],
level: match[1].length,
content: '', // Will be filled in post-processing
startLine: index + 1,
endLine: -1 // Will be calculated
});
}
});
return this.fillSectionContent(sections, lines);
}
private extractCitations(content: string): Citation[] {
// Extract citations from various formats:
// **[key]** *(citation not found)*, \cite{key}, [^key], etc.
const citationRegex = /\**[([^\]** *(citation not found)*]+)\]|\\\w*cite\{([^}]+)\}|\[\^([^\]]+)\]/g;
const citations: Set<string> = new Set();
let match;
while ((match = citationRegex.exec(content)) !== null) {
const key = match[1] || match[2] || match[3];
if (key) citations.add(key);
}
return Array.from(citations).map(key => ({
key,
title: '', // Will be resolved from bibliography
authors: [],
year: 0
}));
}
}
4.2 Document Structure Discovery β
// src/core/DocumentStructureDiscovery.ts
import fs from 'fs-extra';
import path from 'path';
export interface DocumentStructure {
sections: string[];
paths: Map<string, string[]>;
metadata: Map<string, any>;
}
export class DocumentStructureDiscovery {
constructor(private docsBasePath: string = '../docs') {} // Relative path from mcp-server/
async discoverStructure(): Promise<DocumentStructure> {
const structure: DocumentStructure = {
sections: [],
paths: new Map(),
metadata: new Map()
};
// First, try to load VitePress config from existing docs
const vitePressStructure = await this.loadVitePressStructure();
if (vitePressStructure) {
return vitePressStructure;
}
// Fallback: discover from filesystem
return await this.discoverFromFilesystem();
}
private async loadVitePressStructure(): Promise<DocumentStructure | null> {
try {
// Path to existing VitePress config
const configPath = path.resolve(__dirname, this.docsBasePath, '.vitepress/config.mjs');
if (!await fs.pathExists(configPath)) {
console.warn(`VitePress config not found at: ${configPath}`);
return null;
}
// Import VitePress config dynamically
const configModule = await import(`file://${configPath}`);
const config = configModule.default;
console.log('β
Loaded VitePress config successfully');
return this.parseVitePressConfig(config);
} catch (error) {
console.warn('Failed to load VitePress config:', error);
return null;
}
}
private parseVitePressConfig(config: any): DocumentStructure {
const structure: DocumentStructure = {
sections: [],
paths: new Map(),
metadata: new Map()
};
// Parse sidebar structure
if (config.themeConfig?.sidebar) {
for (const item of config.themeConfig.sidebar) {
if (item.text && item.items) {
const sectionName = item.text.toLowerCase().replace(/\s+/g, '-');
structure.sections.push(sectionName);
const sectionPaths: string[] = [];
for (const subItem of item.items) {
if (subItem.link) {
sectionPaths.push(subItem.link);
structure.metadata.set(subItem.link, {
title: subItem.text,
section: sectionName,
order: sectionPaths.length
});
}
}
structure.paths.set(sectionName, sectionPaths);
}
}
}
return structure;
}
private async discoverFromFilesystem(): Promise<DocumentStructure> {
const structure: DocumentStructure = {
sections: [],
paths: new Map(),
metadata: new Map()
};
const docsDir = path.join(process.cwd(), this.docsBasePath);
const entries = await fs.readdir(docsDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory() && !entry.name.startsWith('.')) {
const sectionName = entry.name;
structure.sections.push(sectionName);
const sectionPath = path.join(docsDir, sectionName);
const files = await this.findMarkdownFiles(sectionPath);
structure.paths.set(sectionName, files);
// Extract metadata from each file
for (const filePath of files) {
const metadata = await this.extractFileMetadata(path.join(sectionPath, filePath));
structure.metadata.set(`/${sectionName}/${filePath}`, metadata);
}
}
}
return structure;
}
private async findMarkdownFiles(dirPath: string): Promise<string[]> {
const files: string[] = [];
try {
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile() && entry.name.endsWith('.md')) {
files.push(entry.name);
} else if (entry.isDirectory()) {
const subFiles = await this.findMarkdownFiles(path.join(dirPath, entry.name));
files.push(...subFiles.map(f => `${entry.name}/${f}`));
}
}
} catch (error) {
console.warn(`Failed to read directory ${dirPath}:`, error);
}
return files;
}
private async extractFileMetadata(filePath: string): Promise<any> {
try {
const content = await fs.readFile(filePath, 'utf-8');
const matter = require('gray-matter');
const parsed = matter(content);
return {
title: parsed.data.title || this.extractTitleFromContent(parsed.content),
description: parsed.data.description,
tags: parsed.data.tags || [],
date: parsed.data.date,
wordCount: parsed.content.split(/\s+/).length,
readingTime: Math.ceil(parsed.content.split(/\s+/).length / 200)
};
} catch (error) {
console.warn(`Failed to extract metadata from ${filePath}:`, error);
return {};
}
}
private extractTitleFromContent(content: string): string {
const match = content.match(/^#\s+(.+)$/m);
return match ? match[1] : 'Untitled';
}
}
4.3 Search Engine Integration β
// src/core/SearchEngine.ts
import { createRequire } from 'module';
import { DocumentStructureDiscovery } from './DocumentStructureDiscovery.js';
export class SearchEngine {
private searchIndex: any; // VitePress MiniSearch index
private documentsCache: Map<string, DocumentContent> = new Map();
private structureDiscovery: DocumentStructureDiscovery;
private documentStructure: DocumentStructure | null = null;
constructor(private config: McpServerConfig) {
this.structureDiscovery = new DocumentStructureDiscovery(config.docs.basePath);
}
async initialize(): Promise<void> {
// Discover document structure first
this.documentStructure = await this.structureDiscovery.discoverStructure();
await this.loadVitePressIndex();
await this.buildDocumentCache();
if (this.config.docs.watchForChanges) {
this.setupFileWatcher();
}
}
getSections(): string[] {
return this.documentStructure?.sections || [];
}
getDocumentsInSection(section: string): string[] {
return this.documentStructure?.paths.get(section) || [];
}
async search(query: string, options: SearchOptions = {}): Promise<SearchResult[]> {
const searchOptions = {
combineWith: 'AND',
fuzzy: options.fuzzy ? 0.2 : false,
prefix: options.prefix,
boost: {
title: 4,
text: 2,
headers: 3
},
...options
};
const results = this.searchIndex.search(query, searchOptions);
return results.map((result: any) => ({
title: result.title || this.extractTitleFromPath(result.id),
uri: this.pathToUri(result.id),
section: this.extractSection(result.id),
excerpt: this.generateExcerpt(result, query),
score: result.score,
lastModified: this.getDocumentModified(result.id),
highlights: options.highlightMatches ?
this.generateHighlights(result, query) : undefined,
metadata: this.getDocumentMetadata(result.id)
}));
}
private async loadVitePressIndex(): Promise<void> {
try {
// Load VitePress search index
const indexPath = path.join(
process.cwd(),
this.config.docs.basePath,
'.vitepress/cache/search-index.js'
);
if (await fs.pathExists(indexPath)) {
const indexData = await import(indexPath);
this.searchIndex = indexData.default || indexData;
} else {
// Fallback: build our own index
await this.buildCustomIndex();
}
} catch (error) {
console.warn('Failed to load VitePress index, building custom index:', error);
await this.buildCustomIndex();
}
}
private async buildCustomIndex(): Promise<void> {
const MiniSearch = (await import('minisearch')).default;
this.searchIndex = new MiniSearch({
fields: ['title', 'text', 'headers'],
storeFields: ['title', 'url', 'text'],
searchOptions: {
boost: { title: 2 },
fuzzy: 0.2,
prefix: true
}
});
const documents = await this.indexAllDocuments();
this.searchIndex.addAll(documents);
}
}
4.3 HTTP Server β
// src/server/httpServer.ts
import express from 'express';
import cors from 'cors';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
export class MnemoverseMcpHttpServer {
private app: express.Application;
private mcpServer: McpServer;
private transports: Map<string, StreamableHTTPServerTransport> = new Map();
constructor(private config: McpServerConfig) {
this.app = express();
this.mcpServer = new McpServer({
name: config.name,
version: config.version
});
this.setupMiddleware();
this.setupRoutes();
this.registerMcpHandlers();
}
private setupMiddleware(): void {
this.app.use(express.json());
this.app.use(cors(this.config.transport.cors));
// Request logging
this.app.use((req, res, next) => {
console.log(`${new Date().toISOString()} ${req.method} ${req.path}`);
next();
});
}
private setupRoutes(): void {
// Health check
this.app.get('/health', (req, res) => {
res.json({
status: 'healthy',
version: this.config.version,
timestamp: new Date().toISOString()
});
});
// MCP endpoint
this.app.all('/mcp', async (req, res) => {
await this.handleMcpRequest(req, res);
});
// MCP Discovery endpoints (.well-known)
this.setupMcpDiscoveryEndpoints();
// Documentation endpoint for debugging
this.app.get('/docs', (req, res) => {
res.json({
tools: this.mcpServer.listTools(),
resources: this.mcpServer.listResources(),
endpoints: ['/mcp', '/health', '/docs', '/.well-known/mcp']
});
});
}
private setupMcpDiscoveryEndpoints(): void {
// MCP Server Discovery - RFC compatible
this.app.get('/.well-known/mcp', (req, res) => {
res.json({
version: "2025-03-26",
name: this.config.name,
description: this.config.description,
version: this.config.version,
vendor: "Mnemoverse",
capabilities: {
tools: true,
resources: true,
prompts: false,
experimental: {
streaming: false
}
},
transports: {
"streamable-http": {
endpoint: "/mcp",
methods: ["GET", "POST", "DELETE"]
}
},
documentation: {
openapi: "/openapi.json",
examples: "/examples"
}
});
});
// OpenAPI 3.1 Specification
this.app.get('/openapi.json', (req, res) => {
res.json(this.generateOpenApiSpec());
});
// Usage examples
this.app.get('/examples', (req, res) => {
res.json(this.generateUsageExamples());
});
}
private generateOpenApiSpec(): any {
return {
openapi: "3.1.0",
info: {
title: this.config.name,
description: this.config.description,
version: this.config.version,
contact: {
name: "Mnemoverse Team",
url: "https://github.com/mnemoverse"
},
license: {
name: "MIT",
url: "https://opensource.org/licenses/MIT"
}
},
servers: [
{
url: `http://${this.config.transport.host}:${this.config.transport.port}`,
description: "Development server"
}
],
paths: {
"/mcp": {
post: {
summary: "MCP JSON-RPC Endpoint",
description: "Main endpoint for MCP communication using JSON-RPC 2.0",
requestBody: {
required: true,
content: {
"application/json": {
schema: {
type: "object",
properties: {
jsonrpc: { type: "string", enum: ["2.0"] },
id: { type: ["string", "number", "null"] },
method: { type: "string" },
params: { type: "object" }
},
required: ["jsonrpc", "method"]
},
examples: {
search_docs: {
summary: "Search documentation",
value: {
jsonrpc: "2.0",
id: 1,
method: "tools/call",
params: {
name: "search_docs",
arguments: {
query: "MCP protocol",
filters: { maxResults: 5 }
}
}
}
},
list_tools: {
summary: "List available tools",
value: {
jsonrpc: "2.0",
id: 2,
method: "tools/list"
}
}
}
}
}
},
responses: {
"200": {
description: "Successful response",
content: {
"application/json": {
schema: {
type: "object",
properties: {
jsonrpc: { type: "string", enum: ["2.0"] },
id: { type: ["string", "number", "null"] },
result: { type: "object" },
error: {
type: "object",
properties: {
code: { type: "number" },
message: { type: "string" },
data: { type: "object" }
}
}
}
}
}
}
}
}
}
},
"/health": {
get: {
summary: "Health check endpoint",
responses: {
"200": {
description: "Server is healthy",
content: {
"application/json": {
schema: {
type: "object",
properties: {
status: { type: "string", enum: ["healthy"] },
version: { type: "string" },
timestamp: { type: "string", format: "date-time" }
}
}
}
}
}
}
}
}
},
components: {
schemas: {
MCPTool: {
type: "object",
properties: {
name: { type: "string" },
description: { type: "string" },
inputSchema: { type: "object" }
},
required: ["name", "description"]
},
SearchResult: {
type: "object",
properties: {
title: { type: "string" },
uri: { type: "string" },
section: { type: "string" },
excerpt: { type: "string" },
score: { type: "number" },
lastModified: { type: "string", format: "date-time" }
}
}
}
}
};
}
private generateUsageExamples(): any {
return {
"curl_examples": {
"search_docs": {
description: "Search for documents containing 'memory'",
command: `curl -X POST ${this.getBaseUrl()}/mcp \\
-H "Content-Type: application/json" \\
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search_docs",
"arguments": {
"query": "memory",
"filters": {"maxResults": 3}
}
}
}'`
},
"list_documents": {
description: "List all research documents",
command: `curl -X POST ${this.getBaseUrl()}/mcp \\
-H "Content-Type: application/json" \\
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "list_documents",
"arguments": {
"section": "research",
"includeMetadata": true
}
}
}'`
}
},
"javascript_examples": {
"mcp_client": `
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
const client = new Client({
name: 'mnemoverse-client',
version: '1.0.0'
});
const transport = new StreamableHTTPClientTransport('${this.getBaseUrl()}/mcp');
await client.connect(transport);
// Search documents
const searchResult = await client.callTool('search_docs', {
query: 'artificial intelligence',
filters: { sections: ['research'], maxResults: 5 }
});
console.log(searchResult);
`,
"simple_fetch": `
const response = await fetch('${this.getBaseUrl()}/mcp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: Date.now(),
method: 'tools/call',
params: {
name: 'search_docs',
arguments: { query: 'MCP protocol' }
}
})
});
const result = await response.json();
console.log(result);
`
}
};
}
private getBaseUrl(): string {
return `http://${this.config.transport.host}:${this.config.transport.port}`;
}
}
private async handleMcpRequest(req: express.Request, res: express.Response): Promise<void> {
const sessionId = req.headers['mcp-session-id'] as string;
try {
let transport = this.transports.get(sessionId);
if (!transport) {
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => require('crypto').randomUUID()
});
transport.onclose = () => {
if (transport?.sessionId) {
this.transports.delete(transport.sessionId);
}
};
await this.mcpServer.connect(transport);
this.transports.set(transport.sessionId!, transport);
}
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error('MCP request error:', error);
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error'
},
id: null
});
}
}
async start(): Promise<void> {
return new Promise((resolve) => {
this.app.listen(this.config.transport.port, this.config.transport.host, () => {
console.log(`MCP Server running on ${this.config.transport.host}:${this.config.transport.port}`);
resolve();
});
});
}
}
5. Implementation Plan β
Phase 1: Core Foundation (Week 1) β
Setup project structure
- Initialize TypeScript project
- Install MCP SDK and dependencies
- Setup build and dev scripts
Basic MCP Server
- Implement HTTP server with Express
- Setup MCP server with StreamableHTTP transport
- Add health check and documentation endpoints
Document Parser
- Implement markdown parsing with frontmatter
- Extract sections, titles, metadata
- Basic citation extraction
Phase 2: Search Integration (Week 2) β
VitePress Search Integration
- Load existing VitePress search index
- Fallback to custom MiniSearch implementation
- File watching for index updates
Tools Implementation
search_docs
with full search optionslist_documents
with filtering and pagination- Error handling and validation
Resources Implementation
- Document resource with URI template
- Search index statistics resource
- Auto-completion support
Phase 3: Advanced Features (Week 3) β
Citation Validation
validate_citations
tool- Link checking for external references
- Bibliography integration
Performance Optimization
- Document caching strategy
- Search result caching
- Memory usage optimization
Testing & Documentation
- Unit tests for all components
- Integration tests with real MCP clients
- API documentation generation
Phase 4 (Optional): Enterprise Migration to Java SDK β
When to Consider Java SDK Migration:
- Production load >1000 concurrent requests
- Microservices architecture requirements
- Enterprise Spring Boot integration needs
- Standalone deployment separate from VitePress
Java SDK Migration Benefits:
// Example Java MCP Server with Spring Boot
@Component
public class MnemoverseMcpServer {
@McpTool
public SearchResult searchDocs(
@McpToolParam("query") String query,
@McpToolParam("filters") SearchFilters filters) {
return documentSearchService.search(query, filters);
}
@McpResource("mnemoverse://docs/{section}/{path}")
public DocumentContent getDocument(String section, String path) {
return documentService.getDocument(section, path);
}
}
Migration Path:
- Keep TypeScript server for VitePress integration
- Add Java MCP server for external API access
- Bridge pattern: Java server calls TypeScript for VitePress data
- Gradual migration based on performance needs
6. Testing Strategy β
6.1 Test Environment Setup β
// tests/setup/testEnvironment.ts
import { McpServerConfig } from '../../src/config/mcpServerConfig.js';
import { MnemoverseMcpHttpServer } from '../../src/server/httpServer.js';
import path from 'path';
export const testConfig: McpServerConfig = {
name: "mnemoverse-docs-server-test",
version: "1.0.0-test",
description: "Test MCP Server for Mnemoverse Documentation",
transport: {
type: 'streamable-http',
host: 'localhost',
port: 0, // Let the system assign a port
cors: {
origin: '*',
allowedHeaders: ['Content-Type', 'mcp-session-id'],
exposedHeaders: ['mcp-session-id']
}
},
docs: {
basePath: path.join(__dirname, '../fixtures/docs'),
searchIndexPath: '/.vitepress/cache',
cacheEnabled: false, // Disable caching in tests
watchForChanges: false
},
features: {
semanticSearch: false,
documentValidation: true,
citationExtraction: true,
fullTextIndexing: true
}
};
export class TestServer {
private server: MnemoverseMcpHttpServer;
private port: number = 0;
async start(): Promise<string> {
this.server = new MnemoverseMcpHttpServer(testConfig);
await this.server.start();
this.port = this.server.getPort();
return `http://localhost:${this.port}`;
}
async stop(): Promise<void> {
await this.server.stop();
}
getBaseUrl(): string {
return `http://localhost:${this.port}`;
}
}
// Test fixtures setup
export async function setupTestFixtures(): Promise<void> {
const fixturesPath = path.join(__dirname, '../fixtures/docs');
// Create test documents
const testDocs = {
'research/test-paper.md': `---
title: "Test Research Paper"
authors: ["Test Author"]
date: "2025-01-15"
tags: ["AI", "testing"]
---
# Test Research Paper
This is a test document for MCP server testing.
## Introduction
Testing the search and parsing capabilities.
## Methodology
We use automated tests **[testref2025]** *(citation not found)*.
## Conclusion
The tests should pass.
`,
'guides/test-guide.md': `---
title: "Test Guide"
description: "A guide for testing"
---
# Test Guide
This guide explains how to test the MCP server.
## Setup
Follow these steps...
## Running Tests
Execute the test suite...
`,
'vision/test-vision.md': `---
title: "Test Vision Document"
---
# Test Vision
Our vision for testing.
`
};
for (const [filePath, content] of Object.entries(testDocs)) {
const fullPath = path.join(fixturesPath, filePath);
await fs.ensureDir(path.dirname(fullPath));
await fs.writeFile(fullPath, content);
}
}
6.2 Unit Tests β
// tests/tools/searchDocs.test.ts
import { SearchEngine } from '../../src/core/SearchEngine.js';
import { testConfig, setupTestFixtures } from '../setup/testEnvironment.js';
describe('SearchEngine', () => {
let searchEngine: SearchEngine;
beforeAll(async () => {
await setupTestFixtures();
});
beforeEach(async () => {
searchEngine = new SearchEngine(testConfig);
await searchEngine.initialize();
});
describe('Document Discovery', () => {
test('should discover document structure automatically', async () => {
const sections = searchEngine.getSections();
expect(sections).toContain('research');
expect(sections).toContain('guides');
expect(sections).toContain('vision');
});
test('should list documents in each section', async () => {
const researchDocs = searchEngine.getDocumentsInSection('research');
expect(researchDocs).toContain('test-paper.md');
expect(researchDocs.length).toBeGreaterThan(0);
});
});
describe('Search Functionality', () => {
test('should return relevant results for query', async () => {
const results = await searchEngine.search('test research');
expect(results).toHaveLength(greaterThan(0));
expect(results[0]).toMatchObject({
title: expect.stringMatching(/test/i),
score: expect.any(Number),
uri: expect.stringMatching(/^mnemoverse:\/\/docs\//),
section: expect.any(String)
});
});
test('should filter by sections correctly', async () => {
const results = await searchEngine.search('test', {
filters: { sections: ['research'] }
});
results.forEach(result => {
expect(result.section).toBe('research');
});
});
test('should respect maxResults limit', async () => {
const results = await searchEngine.search('test', {
filters: { maxResults: 2 }
});
expect(results).toHaveLength(lessThanOrEqualTo(2));
});
test('should include highlights when requested', async () => {
const results = await searchEngine.search('methodology', {
options: { highlightMatches: true }
});
expect(results[0].highlights).toBeDefined();
expect(results[0].highlights).toContain(expect.stringMatching(/methodology/i));
});
test('should handle fuzzy search', async () => {
const results = await searchEngine.search('methdology', { // Intentional typo
options: { fuzzy: true }
});
expect(results).toHaveLength(greaterThan(0));
});
test('should include content when requested', async () => {
const results = await searchEngine.search('test', {
options: { includeContent: true }
});
expect(results[0]).toHaveProperty('content');
expect(results[0].content).toContain('test');
});
});
describe('Error Handling', () => {
test('should handle empty queries gracefully', async () => {
await expect(searchEngine.search('')).rejects.toThrow();
});
test('should handle non-existent sections', async () => {
const results = await searchEngine.search('test', {
filters: { sections: ['nonexistent'] }
});
expect(results).toHaveLength(0);
});
});
});
// tests/tools/documentParser.test.ts
import { DocumentParser } from '../../src/core/DocumentParser.js';
import path from 'path';
describe('DocumentParser', () => {
let parser: DocumentParser;
beforeEach(() => {
parser = new DocumentParser();
});
test('should parse frontmatter correctly', async () => {
const testFile = path.join(__dirname, '../fixtures/docs/research/test-paper.md');
const result = await parser.parseDocument(testFile);
expect(result.metadata.title).toBe('Test Research Paper');
expect(result.metadata.authors).toContain('Test Author');
expect(result.metadata.tags).toContain('AI');
});
test('should extract sections correctly', async () => {
const testFile = path.join(__dirname, '../fixtures/docs/research/test-paper.md');
const result = await parser.parseDocument(testFile);
expect(result.sections).toHaveLength(greaterThan(2));
expect(result.sections[0].title).toBe('Introduction');
expect(result.sections[0].level).toBe(2);
});
test('should extract citations', async () => {
const testFile = path.join(__dirname, '../fixtures/docs/research/test-paper.md');
const result = await parser.parseDocument(testFile);
expect(result.citations).toContain(
expect.objectContaining({ key: 'testref2025' })
);
});
test('should calculate reading time', async () => {
const testFile = path.join(__dirname, '../fixtures/docs/research/test-paper.md');
const result = await parser.parseDocument(testFile);
expect(result.metadata.readingTime).toBeGreaterThan(0);
expect(result.metadata.wordCount).toBeGreaterThan(0);
});
});
6.3 Integration Tests β
// tests/integration/mcpServer.test.ts
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { TestServer, setupTestFixtures } from '../setup/testEnvironment.js';
describe('MCP Server Integration', () => {
let testServer: TestServer;
let client: Client;
let baseUrl: string;
beforeAll(async () => {
await setupTestFixtures();
testServer = new TestServer();
baseUrl = await testServer.start();
client = new Client({
name: 'test-client',
version: '1.0.0'
});
const transport = new StreamableHTTPClientTransport(`${baseUrl}/mcp`);
await client.connect(transport);
});
afterAll(async () => {
if (client) {
await client.close();
}
if (testServer) {
await testServer.stop();
}
});
describe('MCP Protocol Compliance', () => {
test('should handle initialize request', async () => {
const result = await client.request({
method: 'initialize',
params: {
protocolVersion: '2025-03-26',
capabilities: {},
clientInfo: { name: 'test-client', version: '1.0.0' }
}
});
expect(result.capabilities).toBeDefined();
expect(result.serverInfo.name).toBe('mnemoverse-docs-server-test');
});
test('should list available tools', async () => {
const tools = await client.listTools();
expect(tools.tools).toEqual(
expect.arrayContaining([
expect.objectContaining({ name: 'search_docs' }),
expect.objectContaining({ name: 'list_documents' }),
expect.objectContaining({ name: 'validate_citations' })
])
);
});
test('should list available resources', async () => {
const resources = await client.listResources();
expect(resources.resources).toBeDefined();
});
});
describe('Tool Execution', () => {
test('should execute search_docs tool successfully', async () => {
const result = await client.callTool('search_docs', {
query: 'test research',
filters: { maxResults: 3 }
});
expect(result.content).toHaveLength(1);
expect(result.content[0].type).toBe('text');
const searchResults = JSON.parse(result.content[0].text);
expect(searchResults.results).toBeDefined();
expect(searchResults.results).toHaveLength(lessThanOrEqualTo(3));
});
test('should execute list_documents tool successfully', async () => {
const result = await client.callTool('list_documents', {
section: 'research',
includeMetadata: true
});
expect(result.content).toHaveLength(1);
const docs = JSON.parse(result.content[0].text);
expect(docs.documents).toBeDefined();
expect(docs.documents[0]).toHaveProperty('metadata');
});
test('should validate tool input schemas', async () => {
await expect(
client.callTool('search_docs', { /* empty args */ })
).rejects.toThrow();
});
test('should handle tool errors gracefully', async () => {
const result = await client.callTool('search_docs', {
query: 'test',
filters: { sections: ['nonexistent'] }
});
expect(result.isError).toBeFalsy();
const searchResults = JSON.parse(result.content[0].text);
expect(searchResults.results).toHaveLength(0);
});
});
describe('Performance Tests', () => {
test('should respond within acceptable time limits', async () => {
const startTime = Date.now();
await client.callTool('search_docs', {
query: 'test',
filters: { maxResults: 10 }
});
const duration = Date.now() - startTime;
expect(duration).toBeLessThan(500); // 500ms max
});
test('should handle concurrent requests', async () => {
const promises = Array(10).fill(0).map((_, i) =>
client.callTool('search_docs', {
query: `test ${i}`,
filters: { maxResults: 5 }
})
);
const results = await Promise.all(promises);
results.forEach(result => {
expect(result.isError).toBeFalsy();
});
});
});
});
6.4 End-to-End Tests β
// tests/e2e/discoveryEndpoints.test.ts
import fetch from 'node-fetch';
import { TestServer, setupTestFixtures } from '../setup/testEnvironment.js';
describe('MCP Discovery Endpoints E2E', () => {
let testServer: TestServer;
let baseUrl: string;
beforeAll(async () => {
await setupTestFixtures();
testServer = new TestServer();
baseUrl = await testServer.start();
});
afterAll(async () => {
await testServer.stop();
});
test('should serve .well-known/mcp discovery endpoint', async () => {
const response = await fetch(`${baseUrl}/.well-known/mcp`);
expect(response.status).toBe(200);
const data = await response.json();
expect(data).toMatchObject({
version: "2025-03-26",
name: "mnemoverse-docs-server-test",
capabilities: {
tools: true,
resources: true
},
transports: {
"streamable-http": {
endpoint: "/mcp"
}
}
});
});
test('should serve OpenAPI specification', async () => {
const response = await fetch(`${baseUrl}/openapi.json`);
expect(response.status).toBe(200);
const spec = await response.json();
expect(spec.openapi).toBe("3.1.0");
expect(spec.info.title).toBe("mnemoverse-docs-server-test");
expect(spec.paths['/mcp']).toBeDefined();
});
test('should serve usage examples', async () => {
const response = await fetch(`${baseUrl}/examples`);
expect(response.status).toBe(200);
const examples = await response.json();
expect(examples.curl_examples).toBeDefined();
expect(examples.javascript_examples).toBeDefined();
});
test('should handle CORS preflight requests', async () => {
const response = await fetch(`${baseUrl}/mcp`, {
method: 'OPTIONS',
headers: {
'Origin': 'https://example.com',
'Access-Control-Request-Method': 'POST',
'Access-Control-Request-Headers': 'Content-Type'
}
});
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('*');
expect(response.headers.get('Access-Control-Allow-Methods')).toContain('POST');
});
});
6.5 Load Testing β
// tests/load/loadTest.ts
import { performance } from 'perf_hooks';
import { TestServer } from '../setup/testEnvironment.js';
describe('Load Testing', () => {
let testServer: TestServer;
let baseUrl: string;
beforeAll(async () => {
testServer = new TestServer();
baseUrl = await testServer.start();
});
afterAll(async () => {
await testServer.stop();
});
test('should handle sustained load', async () => {
const concurrency = 20;
const requestsPerClient = 10;
const totalRequests = concurrency * requestsPerClient;
const startTime = performance.now();
const clients = Array(concurrency).fill(0).map(async (_, clientId) => {
const results = [];
for (let i = 0; i < requestsPerClient; i++) {
const requestStart = performance.now();
const response = await fetch(`${baseUrl}/mcp`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: `${clientId}-${i}`,
method: 'tools/call',
params: {
name: 'search_docs',
arguments: { query: `test query ${i}` }
}
})
});
const requestEnd = performance.now();
results.push({
success: response.ok,
duration: requestEnd - requestStart,
status: response.status
});
}
return results;
});
const allResults = await Promise.all(clients);
const flatResults = allResults.flat();
const endTime = performance.now();
const totalDuration = endTime - startTime;
// Analyze results
const successCount = flatResults.filter(r => r.success).length;
const avgDuration = flatResults.reduce((sum, r) => sum + r.duration, 0) / flatResults.length;
const p95Duration = flatResults
.map(r => r.duration)
.sort((a, b) => a - b)[Math.floor(flatResults.length * 0.95)];
console.log(`Load Test Results:
Total Requests: ${totalRequests}
Success Rate: ${(successCount / totalRequests * 100).toFixed(2)}%
Total Duration: ${totalDuration.toFixed(2)}ms
Avg Request Duration: ${avgDuration.toFixed(2)}ms
P95 Request Duration: ${p95Duration.toFixed(2)}ms
Throughput: ${(totalRequests / (totalDuration / 1000)).toFixed(2)} RPS
`);
// Assertions
expect(successCount / totalRequests).toBeGreaterThan(0.95); // 95% success rate
expect(avgDuration).toBeLessThan(200); // Average < 200ms
expect(p95Duration).toBeLessThan(500); // P95 < 500ms
});
});
6.6 Test Configuration β
// jest.config.js
{
"preset": "ts-jest",
"testEnvironment": "node",
"roots": ["<rootDir>/tests"],
"testMatch": ["**/*.test.ts"],
"collectCoverageFrom": [
"src/**/*.ts",
"!src/**/*.d.ts",
"!src/index.ts"
],
"coverageDirectory": "coverage",
"coverageReporters": ["text", "lcov", "html"],
"setupFilesAfterEnv": ["<rootDir>/tests/setup/jest.setup.ts"],
"testTimeout": 30000,
"maxWorkers": 4
}
// tests/setup/jest.setup.ts
import 'jest-extended';
// Global test setup
beforeAll(async () => {
// Setup global test environment
});
afterAll(async () => {
// Cleanup global test environment
});
// Custom matchers
expect.extend({
toBeValidMcpResponse(received) {
const pass =
received.jsonrpc === '2.0' &&
(received.result !== undefined || received.error !== undefined) &&
received.id !== undefined;
return {
message: () => `expected ${received} to be a valid MCP response`,
pass
};
}
});
7. Deployment Configuration β
7.1 TypeScript Deployment (Phase 1) β
Docker Setup
# Dockerfile
FROM node:20-alpine
WORKDIR /app
# Copy package files
COPY mcp-server/package*.json ./
RUN npm ci --only=production
# Copy source code
COPY mcp-server/dist/ ./dist/
COPY docs/ ./docs/
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S mcpserver -u 1001
USER mcpserver
EXPOSE 3001
CMD ["node", "dist/index.js"]
Docker Compose (Development)
# docker-compose.yml
version: '3.8'
services:
vitepress-docs:
build:
context: .
dockerfile: docs/Dockerfile
ports:
- "3000:3000"
volumes:
- ./docs:/app/docs
environment:
- NODE_ENV=development
mcp-server:
build:
context: .
dockerfile: mcp-server/Dockerfile
ports:
- "3001:3001"
volumes:
- ./docs:/app/docs:ro # Read-only access to docs
environment:
- NODE_ENV=development
- MCP_DOCS_PATH=/app/docs
depends_on:
- vitepress-docs
7.2 Java SDK Deployment (Phase 2/3) β
Spring Boot Configuration
# application.yml
server:
port: 8080
servlet:
context-path: /api
spring:
application:
name: mnemoverse-mcp-server
cache:
type: caffeine
caffeine:
spec: maximumSize=1000,expireAfterWrite=10m
jackson:
property-naming-strategy: SNAKE_CASE
mcp:
server:
name: "mnemoverse-docs-server-java"
version: "2.0.0"
docs:
base-path: "/data/docs"
cache-enabled: true
watch-for-changes: true
cors:
allowed-origins:
- "https://docs.mnemoverse.com"
- "https://app.mnemoverse.com"
allowed-methods: ["GET", "POST", "OPTIONS"]
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
Kubernetes Deployment
# k8s-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-server-java
labels:
app: mcp-server
version: java
spec:
replicas: 3
selector:
matchLabels:
app: mcp-server
template:
metadata:
labels:
app: mcp-server
spec:
containers:
- name: mcp-server
image: mnemoverse/mcp-server-java:2.0.0
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "production"
- name: MCP_DOCS_PATH
value: "/data/docs"
volumeMounts:
- name: docs-volume
mountPath: /data/docs
readOnly: true
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: docs-volume
persistentVolumeClaim:
claimName: docs-pvc
---
apiVersion: v1
kind: Service
metadata:
name: mcp-server-service
spec:
selector:
app: mcp-server
ports:
- port: 80
targetPort: 8080
type: LoadBalancer
7.3 Environment Configuration β
TypeScript Environment
# .env.production
NODE_ENV=production
MCP_SERVER_HOST=0.0.0.0
MCP_SERVER_PORT=3001
MCP_DOCS_PATH=/app/docs
MCP_CACHE_ENABLED=true
MCP_WATCH_FILES=false
MCP_LOG_LEVEL=info
# CORS settings
MCP_CORS_ORIGIN=https://docs.mnemoverse.org,https://app.mnemoverse.org
Java Environment
# application-production.properties
server.port=8080
logging.level.com.mnemoverse.mcp=INFO
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
mcp.server.docs.base-path=/data/docs
mcp.server.cache.enabled=true
mcp.server.performance.max-concurrent-requests=1000
mcp.server.security.rate-limit.requests-per-minute=600
spring.datasource.url=jdbc:h2:mem:mcp
spring.jpa.hibernate.ddl-auto=create-drop
8. Monitoring & Logging β
8.1 Structured Logging β
// src/utils/logger.ts
import winston from 'winston';
export const logger = winston.createLogger({
level: process.env.MCP_LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'mnemoverse-mcp-server' },
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' })
]
});
8.2 Health Monitoring β
// src/monitoring/healthCheck.ts
export interface HealthStatus {
status: 'healthy' | 'degraded' | 'unhealthy';
timestamp: string;
version: string;
uptime: number;
checks: {
filesystem: boolean;
searchIndex: boolean;
memoryUsage: number;
documentCache: number;
};
}
export class HealthMonitor {
async getHealthStatus(): Promise<HealthStatus> {
const checks = await Promise.all([
this.checkFilesystem(),
this.checkSearchIndex(),
this.checkMemoryUsage(),
this.checkDocumentCache()
]);
return {
status: checks.every(c => c.healthy) ? 'healthy' : 'degraded',
timestamp: new Date().toISOString(),
version: process.env.npm_package_version || '1.0.0',
uptime: process.uptime(),
checks: {
filesystem: checks[0].healthy,
searchIndex: checks[1].healthy,
memoryUsage: checks[2].value,
documentCache: checks[3].value
}
};
}
}
Readiness Criteria β
Phase 1: TypeScript MVP - Functional Requirements β
- [ ] All three core tools are implemented and tested
- [ ] Search works with the VitePress index
- [ ] Documents are parsed correctly with metadata
- [ ] HTTP API responds to all MCP requests
- [ ] CORS is configured for production domains
Phase 1: Performance (TypeScript) β
- [ ] Search responds in < 500ms for basic queries
- [ ] Server handles 50+ concurrent requests
- [ ] Memory usage < 256MB with full document load
- [ ] Documents are cached efficiently
Phase 1: Reliability β
- [ ] Graceful handling of all errors
- [ ] Automatic fallback if VitePress index is unavailable
- [ ] Health check endpoint is operational
- [ ] Logging of all operations
Phase 1: Security β
- [ ] Validation of all input parameters
- [ ] CORS restricts access to allowed domains
- [ ] No path traversal vulnerabilities
- [ ] Basic rate limiting to prevent abuse
Phase 2/3: Java SDK - Enterprise Readiness β
- [ ] Performance: < 200ms P95, 1000+ concurrent requests
- [ ] Scalability: Horizontal scaling with load balancer
- [ ] Monitoring: Prometheus metrics, distributed tracing
- [ ] Security: Spring Security integration, advanced rate limiting
- [ ] Resilience: Circuit breakers, retry mechanisms, health checks
- [ ] Caching: Redis/Hazelcast for distributed caching
- [ ] Testing: 95%+ test coverage, performance benchmarks
Migration Trigger Points (TypeScript β Java) β
- [ ] Load: > 500 concurrent users consistently
- [ ] Performance: Response times consistently > 500ms
- [ ] Scale: Need for horizontal scaling across multiple instances
- [ ] Enterprise: Need for advanced monitoring, security, governance
- [ ] Integration: Need for microservices architecture
ποΈ Project Structure in mnemoverse-docs β
Integration into existing repository:
mnemoverse-docs/
βββ docs/ # Existing VitePress documentation
β βββ .vitepress/config.mjs # Configuration for MCP server
β βββ research/ # Documents for indexing
β βββ guides/ # Including this guide
βββ mcp-server/ # π NEW folder for MCP server
β βββ src/
β β βββ config/
β β β βββ mcpServerConfig.ts
β β βββ tools/
β β β βββ searchDocs.ts
β β β βββ listDocuments.ts
β β β βββ validateCitations.ts
β β βββ core/
β β β βββ DocumentParser.ts
β β β βββ SearchEngine.ts
β β β βββ DocumentStructureDiscovery.ts
β β βββ server/
β β β βββ httpServer.ts
β β βββ index.ts
β βββ tests/
β β βββ setup/
β β βββ tools/
β β βββ integration/
β β βββ e2e/
β βββ package.json # MCP server dependencies
β βββ tsconfig.json
β βββ Dockerfile
βββ package.json # ΠΠΎΡΠ½Π΅Π²ΠΎΠΉ Π΄Π»Ρ VitePress docs
βββ scripts/ # ΠΠ±ΡΠΈΠ΅ ΡΠΊΡΠΈΠΏΡΡ + MCP utilities
βββ README.md
Advantages of this structure:
- β Single repository - everything in one place
- β VitePress config reuse - automatic structure discovery
- β Shared CI/CD - one pipeline for docs + MCP server
- β Data proximity - MCP server next to documentation
- β Simple deployment - one Docker compose for everything
Next steps for implementation:
Create MCP server structure in mnemoverse-docs
bashcd /Users/eduardizgorodin/Projects/mnemoverse/mnemoverse-docs mkdir -p mcp-server/src/{config,tools,core,server} mkdir -p mcp-server/tests/{setup,tools,integration,e2e}
Configure package.json for MCP server
bashcd mcp-server npm init -y npm install @modelcontextprotocol/sdk express cors typescript
Integrate with existing VitePress configuration
- Use
docs/.vitepress/config.mjs
for auto-discovery - Reuse existing search index
- Integrate with existing scripts
- Use
Add MCP server commands to root package.json
json{ "scripts": { "mcp:dev": "cd mcp-server && npm run dev", "mcp:build": "cd mcp-server && npm run build", "mcp:test": "cd mcp-server && npm test" } }
Create unified Docker setup
- VitePress docs on port 3000
- MCP server on port 3001
- Shared volumes for documentation
Each step should be independently testable and integrate with the existing structure.
Troubleshooting Guide β
π§ Common Installation Issues β
npm Installation Fails β
# Clear npm cache
npm cache clean --force
# Update npm to latest version
npm install -g npm@latest
# Install with verbose logging
npm install -g @mnemoverse/mcp-docs-server --verbose
Permission Errors on macOS/Linux β
# Fix npm permissions
mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
# Alternative: use npx instead of global install
npx @mnemoverse/mcp-docs-server
Windows Installation Issues β
# Run as Administrator
npm install -g @mnemoverse/mcp-docs-server
# Or use Windows-specific path
npm config set prefix %APPDATA%\npm
π Runtime Debugging β
Server Won't Start β
# Check Node.js version (requires 18+)
node --version
# Test direct execution
node /usr/local/lib/node_modules/@mnemoverse/mcp-docs-server/index.js
# Check for missing dependencies
npm list -g @mnemoverse/mcp-docs-server
Claude Desktop Connection Issues β
# Verify config file location:
# macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
# Windows: %APPDATA%/Claude/claude_desktop_config.json
# Validate JSON syntax
cat ~/Library/Application\ Support/Claude/claude_desktop_config.json | python -m json.tool
# Check server process
ps aux | grep mcp-docs-server
MCP Inspector Testing β
# Install MCP Inspector
npm install -g @modelcontextprotocol/inspector
# Test your server
npx @modelcontextprotocol/inspector node /path/to/your/server/index.js
# Test with specific args
npx @modelcontextprotocol/inspector node dist/index.js --docs-path ./docs
π Performance Issues β
Slow Response Times β
# Enable debug logging
DEBUG=mcp:* node /path/to/server/index.js
# Monitor memory usage
node --inspect /path/to/server/index.js
Memory Leaks β
# Run with memory monitoring
node --max-old-space-size=4096 --inspect /path/to/server/index.js
# Profile memory usage
node --prof /path/to/server/index.js
node --prof-process isolate-*.log > prof.txt
π Development Debugging β
TypeScript Build Errors β
# Clean build artifacts
rm -rf dist/
npm run clean
# Rebuild with verbose output
npm run build -- --verbose
# Check TypeScript config
npx tsc --showConfig
Test Failures β
# Run tests with verbose output
npm test -- --verbose
# Run specific test file
npm test -- --testNamePattern="document search"
# Debug tests
node --inspect-brk ./node_modules/.bin/jest --runInBand
π‘ Custom Development Setup β
Local Development Server β
// dev-server.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
const server = new Server({
name: "dev-docs-server",
version: "1.0.0-dev"
}, {
capabilities: {
tools: {},
resources: {}
}
});
// Enable hot reload
process.on('SIGTERM', () => server.close());
Custom Environment Variables β
# .env.development
DEBUG=mcp:*
DOCS_BASE_PATH=/custom/docs/path
CACHE_ENABLED=true
LOG_LEVEL=debug
PORT=3001
# Load in development
npm run dev -- --env-file .env.development
π Getting Help β
Error Reporting Checklist β
When reporting issues, include:
Environment info:
bashnode --version npm --version cat /etc/os-release || sw_vers # Linux || macOS
Installation method:
bashnpm list -g @mnemoverse/mcp-docs-server which @mnemoverse/mcp-docs-server
Error logs:
bashDEBUG=mcp:* your-command 2>&1 | tee debug.log
Configuration:
bash# Sanitized config (remove sensitive data) cat claude_desktop_config.json
Support Channels β
- π Documentation: MCP Server Guide
- π Bug Reports: GitHub Issues
- π¬ Discussions: GitHub Discussions
- π§ Email: support@mnemoverse.com
Contributing Fixes β
Found a bug? Help improve the server:
# Fork and clone
git clone https://github.com/mnemoverse/mcp-docs-server.git
cd mcp-docs-server
# Create feature branch
git checkout -b fix/your-bug-description
# Install dependencies
npm install
# Make changes and test
npm test
npm run build
# Submit PR
git push origin fix/your-bug-description
Ready to build your own MCP server? Start with the Quick Start or explore the main MCP Server Guide for user-focused documentation.
Related Links β
Explore related documentation:
- Getting Started with Mnemoverse - π Getting Started with Mnemoverse | Quick start guide for Mnemoverse AI memory engine. Set up spatial memory systems in 5 minutes!
- How to Contribute to Mnemoverse - π How to Contribute to Mnemoverse | Step-by-step tutorial for Mnemoverse. Learn practical implementation with code examples.
- MCP Quick Start - π MCP Quick Start | Step-by-step tutorial for Mnemoverse AI memory engine. Learn spatial memory concepts with practical examples.
- MCP Server - π MCP Server | Step-by-step tutorial for Mnemoverse AI memory engine. Learn spatial memory concepts with practical examples.
- π Advanced Research Search - π π Advanced Research Search | Step-by-step tutorial for Mnemoverse AI memory engine. Learn spatial memory concepts with practical examples.