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

254 lines 9.49 kB
"use strict"; /** * NEXON MapleStory Open API Client * Provides methods to interact with NEXON's official MapleStory API */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.NexonApiClient = void 0; const axios_1 = __importDefault(require("axios")); const winston_1 = require("winston"); const constants_1 = require("./constants"); class NexonApiClient { client; logger; apiKey; constructor(config) { this.apiKey = config.apiKey; // Check if in MCP mode (no port specified) const isMcpMode = !process.env.MCP_PORT && !process.argv.includes('--port'); this.logger = (0, winston_1.createLogger)({ level: 'info', format: require('winston').format.combine(require('winston').format.timestamp(), require('winston').format.json()), silent: isMcpMode, transports: isMcpMode ? [] : [new (require('winston').transports.Console)()], }); // Create axios instance with default configuration this.client = axios_1.default.create({ baseURL: config.baseURL || constants_1.API_CONFIG.BASE_URL, timeout: config.timeout || constants_1.API_CONFIG.TIMEOUT, headers: { [constants_1.HEADERS.AUTHORIZATION]: this.apiKey, 'Content-Type': constants_1.HEADERS.CONTENT_TYPE, 'User-Agent': constants_1.HEADERS.USER_AGENT, }, }); this.setupInterceptors(); } setupInterceptors() { // Request interceptor this.client.interceptors.request.use((config) => { this.logger.info('API Request', { method: config.method?.toUpperCase(), url: config.url, params: config.params, }); return config; }, (error) => { this.logger.error('Request Error', { error: error.message }); return Promise.reject(error); }); // Response interceptor this.client.interceptors.response.use((response) => { this.logger.info('API Response', { status: response.status, url: response.config.url, dataSize: JSON.stringify(response.data).length, }); return response; }, (error) => { this.logger.error('Response Error', { status: error.response?.status, url: error.config?.url, message: error.message, data: error.response?.data, }); // Transform error to our standard format const apiError = { error: { name: this.getErrorName(error.response?.status), message: this.getErrorMessage(error.response?.status, error.response?.data), }, }; return Promise.reject(apiError); }); } getErrorName(status) { switch (status) { case constants_1.HTTP_STATUS.UNAUTHORIZED: return 'UNAUTHORIZED'; case constants_1.HTTP_STATUS.FORBIDDEN: return 'FORBIDDEN'; case constants_1.HTTP_STATUS.NOT_FOUND: return 'NOT_FOUND'; case constants_1.HTTP_STATUS.TOO_MANY_REQUESTS: return 'RATE_LIMITED'; case constants_1.HTTP_STATUS.INTERNAL_SERVER_ERROR: return 'INTERNAL_ERROR'; case constants_1.HTTP_STATUS.SERVICE_UNAVAILABLE: return 'SERVICE_UNAVAILABLE'; default: return 'UNKNOWN_ERROR'; } } getErrorMessage(status, data) { if (data?.message) { return data.message; } switch (status) { case constants_1.HTTP_STATUS.UNAUTHORIZED: return constants_1.ERROR_MESSAGES.INVALID_API_KEY; case constants_1.HTTP_STATUS.TOO_MANY_REQUESTS: return constants_1.ERROR_MESSAGES.RATE_LIMIT_EXCEEDED; case constants_1.HTTP_STATUS.NOT_FOUND: return constants_1.ERROR_MESSAGES.CHARACTER_NOT_FOUND; default: return constants_1.ERROR_MESSAGES.UNKNOWN_ERROR; } } async request(endpoint, params) { const response = await this.client.get(endpoint, { params }); return response.data; } // Character API methods async getCharacterOcid(characterName) { return this.request(constants_1.ENDPOINTS.CHARACTER.OCID, { character_name: characterName }); } async getCharacterBasic(ocid, date) { return this.request(constants_1.ENDPOINTS.CHARACTER.BASIC, { ocid, date }); } async getCharacterStat(ocid, date) { return this.request(constants_1.ENDPOINTS.CHARACTER.STAT, { ocid, date }); } async getCharacterHyperStat(ocid, date) { return this.request(constants_1.ENDPOINTS.CHARACTER.HYPER_STAT, { ocid, date }); } async getCharacterPropensity(ocid, date) { return this.request(constants_1.ENDPOINTS.CHARACTER.PROPENSITY, { ocid, date }); } async getCharacterAbility(ocid, date) { return this.request(constants_1.ENDPOINTS.CHARACTER.ABILITY, { ocid, date }); } async getCharacterItemEquipment(ocid, date) { return this.request(constants_1.ENDPOINTS.CHARACTER.ITEM_EQUIPMENT, { ocid, date }); } // Union API methods async getUnionInfo(ocid, date) { return this.request(constants_1.ENDPOINTS.UNION.BASIC, { ocid, date }); } async getUnionRaider(ocid, date) { return this.request(constants_1.ENDPOINTS.UNION.RAIDER, { ocid, date }); } // Guild API methods async getGuildId(guildName, worldName) { return this.request(constants_1.ENDPOINTS.GUILD.ID, { guild_name: guildName, world_name: worldName, }); } async getGuildBasic(oguildId, date) { return this.request(constants_1.ENDPOINTS.GUILD.BASIC, { oguild_id: oguildId, date }); } // Ranking API methods async getOverallRanking(worldName, worldType, className, ocid, page, date) { return this.request(constants_1.ENDPOINTS.RANKING.OVERALL, { world_name: worldName, world_type: worldType, class: className, ocid, page, date, }); } async getUnionRanking(worldName, ocid, page, date) { return this.request(constants_1.ENDPOINTS.RANKING.UNION, { world_name: worldName, ocid, page, date, }); } async getGuildRanking(worldName, rankingType, guildName, page, date) { return this.request(constants_1.ENDPOINTS.RANKING.GUILD, { world_name: worldName, ranking_type: rankingType, guild_name: guildName, page, date, }); } // Convenience methods async getCharacterFullInfo(characterName, date) { try { // First get OCID const { ocid } = await this.getCharacterOcid(characterName); // Then fetch all character information in parallel const [basic, stat, hyperStat, propensity, ability, equipment] = await Promise.all([ this.getCharacterBasic(ocid, date), this.getCharacterStat(ocid, date), this.getCharacterHyperStat(ocid, date), this.getCharacterPropensity(ocid, date), this.getCharacterAbility(ocid, date), this.getCharacterItemEquipment(ocid, date), ]); return { ocid, basic, stat, hyperStat, propensity, ability, equipment, }; } catch (error) { this.logger.error('Error fetching character full info', { characterName, error, }); throw error; } } async getGuildFullInfo(guildName, worldName, date) { try { // First get guild ID const { oguild_id } = await this.getGuildId(guildName, worldName); // Then fetch guild information const basic = await this.getGuildBasic(oguild_id, date); return { oguild_id, basic, }; } catch (error) { this.logger.error('Error fetching guild full info', { guildName, worldName, error, }); throw error; } } // Health check method async healthCheck() { try { // Try to get ranking data as a health check await this.getOverallRanking(undefined, undefined, undefined, undefined, 1); return { status: 'healthy', timestamp: new Date().toISOString(), }; } catch (error) { this.logger.error('Health check failed', { error }); return { status: 'unhealthy', timestamp: new Date().toISOString(), }; } } } exports.NexonApiClient = NexonApiClient; //# sourceMappingURL=nexon-client.js.map