Wikimedia MCP Server
by privetin
- src
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import path from 'path';
import { tools } from './tools/index.js';
type ToolHandler = (args: Record<string, unknown>) => Promise<{
content: Array<{
type: string;
text: string;
}>;
isError?: boolean;
}>;
/**
* Main MCP server class for Neurolora functionality
*/
const ENV = {
isLocal: process.env.MCP_ENV === 'local',
storagePath: process.env.STORAGE_PATH || 'data',
maxTokens: parseInt(process.env.MAX_TOKENS || '190000'),
timeoutMs: parseInt(process.env.TIMEOUT_MS || '300000'),
};
/**
* Server configuration and state management
*/
class ServerConfig {
public readonly isLocal: boolean;
public readonly name: string;
public readonly baseDir: string;
constructor() {
this.isLocal = ENV.isLocal;
this.name = this.isLocal ? 'local-mcp-neurolora' : '@aindreyway/mcp-neurolora';
this.baseDir = process.argv[1] ? path.dirname(process.argv[1]) : process.cwd();
}
}
/**
* Error handler for MCP server
*/
class ErrorHandler {
constructor(private readonly config: ServerConfig) {}
public formatError(error: Error): string {
return this.config.isLocal
? `${error.message}\n${error.stack}`
: 'An error occurred while processing the request';
}
public logError(error: unknown, context?: string): void {
const prefix = this.config.isLocal ? '[LOCAL VERSION][MCP Error]' : '[MCP Error]';
console.error(prefix, context ? `[${context}]` : '', error);
}
}
/**
* Connection manager for MCP server
*/
class ConnectionManager {
private currentTransport: any;
constructor(
private readonly server: Server,
private readonly errorHandler: ErrorHandler
) {}
public async connect(transport: any): Promise<void> {
this.currentTransport = transport;
await this.reconnect();
}
private async reconnect(retryCount = 0, maxRetries = 3): Promise<void> {
try {
await this.server.connect(this.currentTransport);
console.error('✅ Neurolora MCP server connected successfully');
} catch (error) {
this.errorHandler.logError(error, 'reconnect');
if (retryCount < maxRetries) {
console.error(`Retrying connection (${retryCount + 1}/${maxRetries})...`);
await new Promise(resolve => setTimeout(resolve, 1000));
await this.reconnect(retryCount + 1, maxRetries);
} else {
throw error;
}
}
}
public async disconnect(): Promise<void> {
await this.server.close();
console.error('Server closed successfully');
}
}
export class NeuroloraServer {
private readonly server: Server;
private readonly config: ServerConfig;
private readonly errorHandler: ErrorHandler;
private readonly connectionManager: ConnectionManager;
constructor() {
this.config = new ServerConfig();
this.errorHandler = new ErrorHandler(this.config);
// Initialize server
this.server = new Server({
name: this.config.name,
version: '1.4.0',
capabilities: {
tools: {},
},
timeout: ENV.timeoutMs,
});
this.connectionManager = new ConnectionManager(this.server, this.errorHandler);
// Show debug info in local mode
if (this.config.isLocal) {
console.error('[LOCAL VERSION] Running in development mode');
console.error(`[LOCAL VERSION] Storage path: ${ENV.storagePath}`);
console.error(`[LOCAL VERSION] Max tokens: ${ENV.maxTokens}`);
console.error(`[LOCAL VERSION] Timeout: ${ENV.timeoutMs}ms`);
}
this.initializeHandlers();
}
private initializeHandlers(): void {
// Register tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async request => {
const tool = tools.find(t => t.name === request.params.name);
if (!tool) {
return {
content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }],
isError: true,
};
}
try {
const handler = tool?.handler as ToolHandler;
return handler(request.params.arguments || {});
} catch (error) {
this.errorHandler.logError(error, 'tool execution');
return {
content: [{ type: 'text', text: this.errorHandler.formatError(error as Error) }],
isError: true,
};
}
});
// Error handling
this.server.onerror = async error => {
this.errorHandler.logError(error);
if (error instanceof Error && error.message.includes('connection')) {
await this.handleConnectionError();
}
};
}
private async handleConnectionError(): Promise<void> {
console.error('Connection error detected, attempting to reconnect...');
try {
await this.server.close();
await new Promise(resolve => setTimeout(resolve, 1000));
await this.connectionManager.connect(this.connectionManager['currentTransport']);
} catch (error) {
this.errorHandler.logError(error, 'reconnection');
}
}
private async handleShutdown(signal: string): Promise<void> {
console.error(`\nReceived ${signal}, shutting down...`);
try {
await this.connectionManager.disconnect();
// Allow async operations to complete before exiting
setTimeout(() => process.exit(0), 1000);
} catch (error) {
this.errorHandler.logError(error, 'shutdown');
process.exit(1);
}
}
async run(transport: any): Promise<void> {
console.error('Neurolora MCP server running from:', this.config.baseDir);
try {
await this.connectionManager.connect(transport);
// Setup signal handlers
const signals = ['SIGINT', 'SIGTERM', 'SIGHUP'];
signals.forEach(signal => {
process.on(signal, () => this.handleShutdown(signal));
});
} catch (error) {
this.errorHandler.logError(error, 'startup');
process.exit(1);
}
}
}