UNPKG

earth2-mcp-server

Version:

Earth2 MCP Server for Claude integration - Access your Earth2 account data through Claude

758 lines (693 loc) 24.6 kB
#!/usr/bin/env node /** * Earth2 MCP Server for Claude Integration * Provides access to Earth2 account data via Claude */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js"; import { Earth2Client } from "./src/earth2-client.mjs"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; import dotenv from "dotenv"; // Load environment variables const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); dotenv.config({ path: join(__dirname, ".env") }); class Earth2MCPServer { constructor() { this.server = new Server( { name: "earth2", version: "1.3.0", description: "Earth2 Integration - Access your Earth2 account data through Claude.\n\n**IMPORTANT - Link Formatting:**\nWhen mentioning properties or users, ALWAYS include clickable links:\n- Property links: https://app.earth2.io/#propertyInfo/{landfield_id} (use ID from response)\n- User profile links: https://app.earth2.io/#profile/{user_id} (use ID from response)\nExample: 'Check out this [property](https://app.earth2.io/#propertyInfo/5e1b65f2-15b0-4eb6-bf01-063bd0a843fc)'\nAlways use actual IDs from API responses, never make them up.", }, { capabilities: { tools: {}, }, } ); this.earth2Client = null; this.setupToolHandlers(); this.setupErrorHandling(); } setupErrorHandling() { this.server.onerror = (error) => { console.error("[MCP Error]", error); }; process.on("SIGINT", async () => { await this.server.close(); process.exit(0); }); } /** * Initialize Earth2 client with credentials from environment * Supports both user credentials and fallback to Earthie credentials */ initializeClient() { // Try user credentials first let xsrfToken = process.env.EARTH2_XSRF_TOKEN; let cookie = process.env.EARTH2_COOKIE; // Fallback to Earthie/Platform credentials if user credentials not available if (!xsrfToken || !cookie) { xsrfToken = process.env.EARTHIE_XSRF_TOKEN || process.env.HARDCODED_CSRF; cookie = process.env.EARTHIE_COOKIE || process.env.HARDCODED_COOKIE; } if (!xsrfToken || !cookie) { throw new McpError( ErrorCode.InvalidRequest, "Earth2 credentials not found. Please set either (EARTH2_XSRF_TOKEN and EARTH2_COOKIE) for user credentials, or (EARTHIE_XSRF_TOKEN and EARTHIE_COOKIE) for platform credentials." ); } this.earth2Client = new Earth2Client(xsrfToken, cookie); // Log which credentials are being used (only in debug mode) if (process.env.MCP_DEBUG) { const credType = process.env.EARTH2_XSRF_TOKEN ? "user" : "earthie/platform"; console.error(`[MCP] Using ${credType} credentials`); } } setupToolHandlers() { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // User Profile & Info { name: "get_user_info", description: "Get Earth2 user profile information for any user", inputSchema: { type: "object", properties: { user_id: { type: "string", description: "User ID (optional, defaults to current user)", }, }, }, }, { name: "get_my_networth", description: "Get your current Earth2 net worth", inputSchema: { type: "object", properties: {}, }, }, { name: "search_users", description: "Search for Earth2 users by username", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query", }, }, required: ["query"], }, }, // Properties / Landfields { name: "get_landfields", description: "Get all your Earth2 properties/landfields with optional filters", inputSchema: { type: "object", properties: { country: { type: "string", description: "Filter by country code", }, minTiles: { type: "number", description: "Minimum number of tiles", }, hasHolobuilding: { type: "boolean", description: "Filter properties with holobuildings", }, }, }, }, { name: "get_landfield_details", description: "Get detailed information about a specific property", inputSchema: { type: "object", properties: { landfield_id: { type: "string", description: "The landfield/property ID", }, }, required: ["landfield_id"], }, }, { name: "get_landfield_resources", description: "Get resource balances for a specific property", inputSchema: { type: "object", properties: { landfield_id: { type: "string", description: "The landfield/property ID", }, }, required: ["landfield_id"], }, }, // Marketplace { name: "get_marketplace", description: "Browse Earth2 marketplace with advanced filtering", inputSchema: { type: "object", properties: { country: { type: "string", description: "Filter by country code (e.g., 'US', 'JP')", }, landfieldTier: { type: "number", description: "Filter by tier (1-3)", }, sorting: { type: "string", description: "Sort by: price, price_per_tile, date, etc.", }, items: { type: "number", description: "Number of items to retrieve", }, }, }, }, // Resources { name: "get_my_resources", description: "Get all your resource balances (ether, essence, jewels, etc.)", inputSchema: { type: "object", properties: {}, }, }, { name: "get_resource_balance", description: "Get balance for a specific resource", inputSchema: { type: "object", properties: { ticker: { type: "string", description: "Resource ticker (e.g., 'ETHER', 'ESS', 'SHARD')", }, }, required: ["ticker"], }, }, { name: "get_claimable_ether", description: "Get your claimable ether amount", inputSchema: { type: "object", properties: {}, }, }, { name: "get_resource_stakes", description: "Get your staked resources", inputSchema: { type: "object", properties: {}, }, }, // Jewels { name: "get_my_jewels", description: "Get your jewels with advanced filtering", inputSchema: { type: "object", properties: { colorName: { type: "string", description: "Filter by color (e.g., 'BLUE', 'RUBY', 'EMERALD')", }, tier: { type: "number", description: "Filter by tier (1-3)", }, qualityLevel: { type: "string", description: "Filter by quality (COMMON, UNCOMMON, RARE, EPIC, LEGENDARY)", }, stacked: { type: "boolean", description: "Show only stacked jewels", }, }, }, }, { name: "get_bazaar_jewels", description: "Browse jewel offers on the bazaar", inputSchema: { type: "object", properties: { colorName: { type: "string", description: "Filter by color", }, tier: { type: "number", description: "Filter by tier", }, sortBy: { type: "string", description: "Sort field", }, }, }, }, // Droids { name: "get_my_droids", description: "Get your droids with filters", inputSchema: { type: "object", properties: { rarity: { type: "string", description: "Filter by rarity", }, sortBy: { type: "string", description: "Sort field", }, }, }, }, { name: "get_bazaar_droids", description: "Browse droid offers on the bazaar", inputSchema: { type: "object", properties: { droidRarity: { type: "string", description: "Filter by rarity (common, uncommon, rare, epic, legendary)", }, sortBy: { type: "string", description: "Sort by: price, rarity, date", }, sortDir: { type: "string", description: "Sort direction: asc or desc", }, }, }, }, // Civilians { name: "get_my_civilians", description: "Get your civilians with filters", inputSchema: { type: "object", properties: { faction: { type: "string", description: "Filter by faction", }, level: { type: "number", description: "Filter by level", }, }, }, }, { name: "get_bazaar_civilians", description: "Browse civilian offers on the bazaar", inputSchema: { type: "object", properties: { civilianFaction: { type: "string", description: "Filter by faction (e.g., 'space_force')", }, civilianLevel: { type: "number", description: "Filter by level", }, civilianOccupation: { type: "string", description: "Filter by occupation", }, }, }, }, // Blueprints { name: "get_my_blueprints", description: "Get your holobuilding blueprints", inputSchema: { type: "object", properties: {}, }, }, { name: "get_bazaar_blueprints", description: "Browse blueprint offers on the bazaar", inputSchema: { type: "object", properties: { sort: { type: "string", description: "Sort by: trending, recent, price", }, hideOwn: { type: "boolean", description: "Hide your own listings", }, }, }, }, // Transactions & Trading { name: "get_balance_transactions", description: "Get your balance change transactions", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Number of transactions to retrieve", }, }, }, }, { name: "get_ongoing_trades", description: "Get your active trades", inputSchema: { type: "object", properties: {}, }, }, // Leaderboards { name: "get_players_leaderboard", description: "Get top players leaderboard with flexible filtering", inputSchema: { type: "object", properties: { sort_by: { type: "string", description: "Sort by: credits_sum, tiles_count, holobuildings_count, hq_jewels_count", }, country: { type: "string", description: "Filter by country code (e.g., 'US', 'BR', 'JP', 'UK')", }, continent: { type: "string", description: "Filter by continent: africa, asia, europe, north_america, south_america, oceania, antarctica", }, jewel_quality: { type: "string", description: "For hq_jewels sort (BRILLIANT, LUMINOUS, etc.)", }, }, }, }, { name: "get_player_countries_leaderboard", description: "Get player leaderboard by country", inputSchema: { type: "object", properties: { sort_by: { type: "string", description: "Sort by: tiles_count, essence_claimed_in_24_hours, essence_claimed_in_7_days, max_property_purchased_cost", }, }, }, }, { name: "get_player_continents_leaderboard", description: "Get player leaderboard by continent", inputSchema: { type: "object", properties: { sort_by: { type: "string", description: "Sort by: tiles_count, etc.", }, }, }, }, { name: "get_landfield_countries_leaderboard", description: "Get landfield leaderboard by country", inputSchema: { type: "object", properties: { sort_by: { type: "string", description: "Sort by: tiles_count, essence_claimed_in_24_hours, essence_claimed_in_7_days, max_property_purchased_cost", }, }, }, }, { name: "get_landfield_continents_leaderboard", description: "Get landfield leaderboard by continent", inputSchema: { type: "object", properties: { sort_by: { type: "string", description: "Sort by: tiles_count, etc.", }, }, }, }, // Essence & Crypto { name: "get_essence_token_info", description: "Get ESS token information", inputSchema: { type: "object", properties: {}, }, }, { name: "get_ess_usd_rate", description: "Get current ESS/USD exchange rate", inputSchema: { type: "object", properties: {}, }, }, { name: "get_crypto_payouts", description: "Get your crypto withdrawal/payout history", inputSchema: { type: "object", properties: {}, }, }, // Activities & Quests { name: "get_quests", description: "Get available quests", inputSchema: { type: "object", properties: {}, }, }, { name: "get_my_quests", description: "Get your active and completed quests", inputSchema: { type: "object", properties: {}, }, }, // Tiles & Pricing { name: "get_tile_prices", description: "Get all tile prices by country", inputSchema: { type: "object", properties: {}, }, }, ], }; }); // Handle tool calls this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { // Initialize client if not already done if (!this.earth2Client) { this.initializeClient(); } const { name, arguments: args } = request.params; let result; switch (name) { // User Profile & Info case "get_user_info": result = await this.earth2Client.getUserInfo(args?.user_id); break; case "get_my_networth": result = await this.earth2Client.getMyNetworth(); break; case "search_users": if (!args?.query) { throw new McpError(ErrorCode.InvalidParams, "query is required"); } result = await this.earth2Client.searchUsers(args.query); break; // Properties / Landfields case "get_landfields": result = await this.earth2Client.getLandfields(args || {}); break; case "get_landfield_details": if (!args?.landfield_id) { throw new McpError(ErrorCode.InvalidParams, "landfield_id is required"); } result = await this.earth2Client.getLandfieldDetails(args.landfield_id); break; case "get_landfield_resources": if (!args?.landfield_id) { throw new McpError(ErrorCode.InvalidParams, "landfield_id is required"); } result = await this.earth2Client.getLandfieldBalanceResources(args.landfield_id); break; // Marketplace case "get_marketplace": result = await this.earth2Client.getMarketplace(args || {}); break; // Resources case "get_my_resources": result = await this.earth2Client.getMyBalanceResources(); break; case "get_resource_balance": if (!args?.ticker) { throw new McpError(ErrorCode.InvalidParams, "ticker is required"); } result = await this.earth2Client.getMyResourceBalance(args.ticker); break; case "get_claimable_ether": result = await this.earth2Client.getClaimableEtherAmount(); break; case "get_resource_stakes": result = await this.earth2Client.getMyResourceStakes(); break; // Jewels case "get_my_jewels": result = await this.earth2Client.getMyJewels(args || {}); break; case "get_bazaar_jewels": result = await this.earth2Client.getBazaarJewelOffers(args || {}); break; // Droids case "get_my_droids": result = await this.earth2Client.getDroids(args || {}); break; case "get_bazaar_droids": result = await this.earth2Client.getBazaarDroidOffers(args || {}); break; // Civilians case "get_my_civilians": result = await this.earth2Client.getCivilians(args || {}); break; case "get_bazaar_civilians": result = await this.earth2Client.getBazaarCivilianOffers(args || {}); break; // Blueprints case "get_my_blueprints": result = await this.earth2Client.getMyBlueprints(); break; case "get_bazaar_blueprints": result = await this.earth2Client.getBazaarBlueprintOffers(args || {}); break; // Transactions & Trading case "get_balance_transactions": result = await this.earth2Client.getBalanceChangeTransactions(args || {}); break; case "get_ongoing_trades": result = await this.earth2Client.getMyOngoingTrades(); break; // Leaderboards case "get_players_leaderboard": result = await this.earth2Client.getPlayersLeaderboard(args || {}); break; case "get_player_countries_leaderboard": result = await this.earth2Client.getPlayerCountriesLeaderboard(args || {}); break; case "get_player_continents_leaderboard": result = await this.earth2Client.getPlayerContinentsLeaderboard(args || {}); break; case "get_landfield_countries_leaderboard": result = await this.earth2Client.getLandfieldCountriesLeaderboard(args || {}); break; case "get_landfield_continents_leaderboard": result = await this.earth2Client.getLandfieldContinentsLeaderboard(args || {}); break; // Essence & Crypto case "get_essence_token_info": result = await this.earth2Client.getEssenceTokenInfo(); break; case "get_ess_usd_rate": result = await this.earth2Client.getEssUsdRate(); break; case "get_crypto_payouts": result = await this.earth2Client.getMyCryptoPayouts(); break; // Activities & Quests case "get_quests": result = await this.earth2Client.getQuests(); break; case "get_my_quests": result = await this.earth2Client.getMyQuests(); break; // Tiles & Pricing case "get_tile_prices": result = await this.earth2Client.getTilePricesAll(); break; default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError(ErrorCode.InternalError, error.message); } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error("Earth2 MCP server running on stdio"); } } const server = new Earth2MCPServer(); server.run().catch(console.error);