UNPKG

maplestory-mcp-server

Version:

Official-style NEXON MapleStory MCP Server for Claude Desktop - Complete character info, union details, guild data, rankings, and game mechanics

279 lines 9.35 kB
"use strict"; /** * MCP Server Foundation for MapleStory API * Implements the core Model Context Protocol server using MCP TypeScript SDK */ Object.defineProperty(exports, "__esModule", { value: true }); exports.McpServer = void 0; const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js"); const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js"); const types_js_1 = require("@modelcontextprotocol/sdk/types.js"); const logger_1 = require("../utils/logger"); const errors_1 = require("../utils/errors"); const nexon_client_1 = require("../api/nexon-client"); class McpServer { server; transport = null; logger; nexonClient; tools = new Map(); config; isRunning = false; constructor(config) { if (!config.nexonApiKey || config.nexonApiKey.trim() === '') { throw new Error('nexonApiKey is required'); } this.config = { name: 'mcp-maple', version: '1.0.0', debug: false, ...config, }; this.logger = new logger_1.McpLogger('mcp-server'); this.nexonClient = new nexon_client_1.NexonApiClient({ apiKey: config.nexonApiKey, }); // Initialize MCP server this.server = new index_js_1.Server({ name: this.config.name, version: this.config.version, }, { capabilities: { tools: {}, }, }); this.setupHandlers(); this.logger.info('MCP server initialized', { name: this.config.name, version: this.config.version, }); } setupHandlers() { // Handle tool listing this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => { this.logger.logMcpOperation('list_tools', 'all', { toolCount: this.tools.size, }); const tools = Array.from(this.tools.values()).map((tool) => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema, // MCP SDK type compatibility })); return { tools }; }); // Handle tool execution this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; this.logger.logMcpOperation('call_tool', name, { arguments: args, }); try { const tool = this.tools.get(name); if (!tool) { throw new errors_1.McpMapleError(`Tool '${name}' not found`, 'TOOL_NOT_FOUND', 404, { toolName: name, availableTools: Array.from(this.tools.keys()), }); } // Execute the tool const result = await tool.execute(args || {}, { nexonClient: this.nexonClient, logger: this.logger, }); this.logger.logMcpOperation('tool_executed', name, { success: true, resultType: typeof result, }); // Format result as MCP response const content = [ { type: 'text', text: typeof result === 'string' ? result : JSON.stringify(result, null, 2), }, ]; return { content }; } catch (error) { this.logger.error('Tool execution failed', { operation: 'tool_execution_error', toolName: name, error: error instanceof Error ? error.message : String(error), }); // Return error as text content const errorMessage = error instanceof errors_1.McpMapleError ? `${error.message} (${error.code})` : error instanceof Error ? error.message : 'Unknown error occurred'; return { content: [ { type: 'text', text: `Error: ${errorMessage}`, }, ], isError: true, }; } }); } /** * Register a tool with the server */ registerTool(tool) { if (this.tools.has(tool.name)) { this.logger.warn('Tool already registered, overwriting', { toolName: tool.name, }); } this.tools.set(tool.name, tool); this.logger.info('Tool registered', { toolName: tool.name, description: tool.description, }); } /** * Unregister a tool from the server */ unregisterTool(toolName) { const removed = this.tools.delete(toolName); if (removed) { this.logger.info('Tool unregistered', { toolName }); } else { this.logger.warn('Attempted to unregister non-existent tool', { toolName }); } return removed; } /** * Get list of registered tools */ getRegisteredTools() { return Array.from(this.tools.keys()); } /** * Get tool by name */ getTool(name) { return this.tools.get(name); } /** * Get server information */ getServerInfo() { return { name: this.config.name, version: this.config.version, capabilities: { tools: {}, }, }; } /** * Check if server is running */ isServerRunning() { return this.isRunning; } /** * Start the MCP server */ async start() { if (this.isRunning) { throw new errors_1.McpMapleError('Server is already running', 'SERVER_ALREADY_RUNNING'); } try { this.transport = new stdio_js_1.StdioServerTransport(); await this.server.connect(this.transport); this.isRunning = true; this.logger.info('MCP server started successfully', { name: this.config.name, toolCount: this.tools.size, tools: Array.from(this.tools.keys()), }); // Test NEXON API connection try { const healthCheck = await this.nexonClient.healthCheck(); this.logger.info('NEXON API health check', healthCheck); } catch (error) { this.logger.warn('NEXON API health check failed', { error: error instanceof Error ? error.message : String(error), }); } } catch (error) { this.isRunning = false; this.logger.error('Failed to start MCP server', { error: error instanceof Error ? error.message : String(error), }); throw new errors_1.McpMapleError('Failed to start MCP server', 'SERVER_START_FAILED', undefined, { originalError: error, }); } } /** * Stop the MCP server */ async stop() { if (!this.isRunning) { this.logger.warn('Attempted to stop server that is not running'); return; } try { if (this.transport) { await this.transport.close(); this.transport = null; } this.isRunning = false; this.logger.info('MCP server stopped successfully'); } catch (error) { this.logger.error('Error stopping MCP server', { error: error instanceof Error ? error.message : String(error), }); throw new errors_1.McpMapleError('Failed to stop MCP server', 'SERVER_STOP_FAILED', undefined, { originalError: error, }); } } /** * Graceful shutdown with cleanup */ async shutdown() { this.logger.info('Initiating graceful shutdown'); try { await this.stop(); this.tools.clear(); this.logger.info('Graceful shutdown completed'); } catch (error) { this.logger.error('Error during graceful shutdown', { error: error instanceof Error ? error.message : String(error), }); throw error; } } /** * Health check for the server */ async healthCheck() { const serverStatus = this.isRunning ? 'healthy' : 'stopped'; let nexonApiStatus = 'unknown'; try { const healthCheck = await this.nexonClient.healthCheck(); nexonApiStatus = healthCheck.status; } catch (error) { nexonApiStatus = 'unhealthy'; } return { server: serverStatus, nexonApi: nexonApiStatus, toolCount: this.tools.size, uptime: process.uptime().toFixed(2) + 's', }; } } exports.McpServer = McpServer; //# sourceMappingURL=mcp-server.js.map