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
JavaScript
"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