Skip to main content
Glama
station-services-tools.ts38.7 kB
/** * Station Services Information tools for EVE Online */ import { z } from "zod"; import { ESIClient, ESIStationInfo, ESIStructureInfo } from "./esi-client.js"; import { SDEClient, SDEAgentInfo, SDEAgentTypeInfo } from "./sde-client.js"; const esiClient = new ESIClient(); const sdeClient = new SDEClient(); /** * Station service type mapping */ const STATION_SERVICES = { 'bounty-missions': 'Bounty Missions', 'assasination-missions': 'Assassination Missions', 'courier-missions': 'Courier Missions', 'interbus': 'Interbus', 'reprocessing-plant': 'Reprocessing Plant', 'refinery': 'Refinery', 'market': 'Market', 'black-market': 'Black Market', 'stock-exchange': 'Stock Exchange', 'cloning': 'Cloning', 'surgery': 'Surgery', 'dna-therapy': 'DNA Therapy', 'repair-facilities': 'Repair Facilities', 'factory': 'Factory', 'labratory': 'Laboratory', 'gambling': 'Gambling', 'fitting': 'Fitting', 'paintshop': 'Paintshop', 'news': 'News', 'storage': 'Storage', 'insurance': 'Insurance', 'docking': 'Docking', 'office-rental': 'Office Rental', 'jump-clone-facility': 'Jump Clone Facility', 'loyalty-point-store': 'Loyalty Point Store', 'navy-offices': 'Navy Offices', 'security-offices': 'Security Offices' } as const; export interface CombinedStationInfo { station_id: number; name: string; system_id: number; system_name?: string; region_id?: number; region_name?: string; type_id: number; type_name?: string; position: { x: number; y: number; z: number; }; owner_id: number; owner_name?: string; race_id?: number; race_name?: string; services: Array<{ service_key: string; service_name: string; available: boolean; }>; docking_info: { max_dockable_ship_volume: number; office_rental_cost: number; }; reprocessing_info: { efficiency: number; stations_take: number; }; security_status?: number; security_class?: string; agents?: Array<{ agent_id: number; agent_name?: string; agent_type?: string; corporation_name?: string; division_id?: number; level?: number; quality?: number; is_locator?: boolean; is_research_agent?: boolean; }>; } /** * Get station agents information */ export const getStationAgentsTool = { annotations: { openWorldHint: true, // This tool interacts with external APIs readOnlyHint: true, // This tool doesn't modify anything title: "Get Station Agents", }, description: "Get information about agents located at specific stations, including their types, levels, and specializations", execute: async (args: { stations: (string | number)[]; includeResearchAgents?: boolean; }) => { try { if (args.stations.length === 0) { return JSON.stringify({ success: false, message: "At least one station must be provided", stations: [] }); } if (args.stations.length > 20) { return JSON.stringify({ success: false, message: "Maximum 20 stations allowed per request", stations: [] }); } const results: Array<{ station_id: number; station_name?: string; system_name?: string; agents: Array<{ agent_id: number; agent_name?: string; agent_type?: string; corporation_name?: string; division_id?: number; level?: number; quality?: number; is_locator?: boolean; is_research_agent?: boolean; }>; }> = []; const errors: string[] = []; // Convert station names to IDs if needed const stationIds: number[] = []; const stringStations = args.stations.filter(s => typeof s === 'string') as string[]; const numericStations = args.stations.filter(s => typeof s === 'number') as number[]; stationIds.push(...numericStations); if (stringStations.length > 0) { try { const stationIdResults = await esiClient.getStationIds(stringStations); stationIds.push(...stationIdResults.map(s => s.id)); // Check for stations that weren't found const foundNames = stationIdResults.map(s => s.name.toLowerCase()); const notFound = stringStations.filter(name => !foundNames.includes(name.toLowerCase()) ); notFound.forEach(name => { errors.push(`Station '${name}' not found`); }); } catch (error) { errors.push(`Error converting station names to IDs: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Get research agent IDs if requested let researchAgentIds: Set<number> = new Set(); if (args.includeResearchAgents) { try { const researchIds = await sdeClient.getAllResearchAgentIds(); researchAgentIds = new Set(researchIds); } catch (error) { console.warn('Failed to get research agent IDs:', error); } } // Process each station for (const stationId of stationIds) { try { // Get station basic info let stationName: string | undefined; let systemName: string | undefined; try { const stationInfo = await esiClient.getStationInfo(stationId); stationName = stationInfo.name; const systemInfo = await esiClient.getSolarSystemInfo(stationInfo.system_id); systemName = systemInfo.name; } catch (error) { console.warn(`Failed to get station info for ${stationId}:`, error); } // Get agents at this station const agents = await sdeClient.getAgentsByLocation(stationId); const agentInfos: Array<{ agent_id: number; agent_name?: string; agent_type?: string; corporation_name?: string; division_id?: number; level?: number; quality?: number; is_locator?: boolean; is_research_agent?: boolean; }> = []; // Process each agent for (const agent of agents) { try { // Get agent name and corporation info const idsToResolve: number[] = [agent.characterID]; if (agent.corporationID) idsToResolve.push(agent.corporationID); let nameMap = new Map<number, string>(); try { const nameResults = await esiClient.idsToNames(idsToResolve); nameMap = new Map(nameResults.map(result => [result.id, result.name])); } catch (error) { console.warn('Failed to resolve agent names:', error); } // Get agent type info let agentTypeName: string | undefined; if (agent.agentTypeID) { try { const agentTypeInfo = await sdeClient.getAgentTypeInfo(agent.agentTypeID); agentTypeName = agentTypeInfo.agentType; } catch (error) { console.warn(`Failed to get agent type ${agent.agentTypeID}:`, error); } } agentInfos.push({ agent_id: agent.characterID, agent_name: nameMap.get(agent.characterID), agent_type: agentTypeName, corporation_name: agent.corporationID ? nameMap.get(agent.corporationID) : undefined, division_id: agent.divisionID, level: agent.level, quality: agent.quality, is_locator: agent.isLocator || false, is_research_agent: researchAgentIds.has(agent.characterID) }); } catch (error) { console.warn(`Failed to process agent ${agent.characterID}:`, error); } } results.push({ station_id: stationId, station_name: stationName, system_name: systemName, agents: agentInfos }); } catch (error) { errors.push(`Error processing station ${stationId}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } const totalAgents = results.reduce((sum, station) => sum + station.agents.length, 0); return JSON.stringify({ success: results.length > 0, message: `Found ${totalAgents} agent(s) across ${results.length} station(s)`, stations: results, errors: errors.length > 0 ? errors : undefined, summary: { total_stations_requested: args.stations.length, successful_stations: results.length, failed_stations: errors.length, total_agents_found: totalAgents, research_agents_included: args.includeResearchAgents || false } }); } catch (error) { return JSON.stringify({ success: false, message: `Error getting station agents: ${error instanceof Error ? error.message : 'Unknown error'}`, stations: [] }); } }, name: "get_station_agents", parameters: z.object({ stations: z.array(z.union([z.string(), z.number()])).min(1).max(20).describe("Array of station names (English proper nouns like 'Jita IV - Moon 4 - Caldari Navy Assembly Plant') or IDs to get agent information for (max 20)"), includeResearchAgents: z.boolean().optional().default(false).describe("Whether to identify research agents (may be slower)") }), }; /** * Get comprehensive station information including services */ export const getStationServicesTool = { annotations: { openWorldHint: true, // This tool interacts with external ESI API readOnlyHint: true, // This tool doesn't modify anything title: "Get Station Services", }, description: "Get comprehensive station information including available services, docking capabilities, and reprocessing facilities. Supports both station IDs and names.", execute: async (args: { stations: (string | number)[]; includeSystemInfo?: boolean; includeAgents?: boolean; }) => { try { if (args.stations.length === 0) { return JSON.stringify({ success: false, message: "At least one station must be provided", stations: [] }); } if (args.stations.length > 50) { return JSON.stringify({ success: false, message: "Maximum 50 stations allowed per request", stations: [] }); } const results: CombinedStationInfo[] = []; const errors: string[] = []; // Convert station names to IDs if needed const stationIds: number[] = []; const stringStations = args.stations.filter(s => typeof s === 'string') as string[]; const numericStations = args.stations.filter(s => typeof s === 'number') as number[]; stationIds.push(...numericStations); if (stringStations.length > 0) { try { const stationIdResults = await esiClient.getStationIds(stringStations); stationIds.push(...stationIdResults.map(s => s.id)); // Check for stations that weren't found const foundNames = stationIdResults.map(s => s.name.toLowerCase()); const notFound = stringStations.filter(name => !foundNames.includes(name.toLowerCase()) ); notFound.forEach(name => { errors.push(`Station '${name}' not found`); }); } catch (error) { errors.push(`Error converting station names to IDs: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Get station information for (const stationId of stationIds) { try { const stationInfo = await esiClient.getStationInfo(stationId); // Collect IDs for name resolution const idsToResolve: number[] = [ stationInfo.system_id, stationInfo.type_id, stationInfo.owner ]; if (stationInfo.race_id) { idsToResolve.push(stationInfo.race_id); } // Get system info for region and security status let systemInfo; let regionId: number | undefined; let securityStatus: number | undefined; let securityClass: string | undefined; if (args.includeSystemInfo !== false) { try { systemInfo = await esiClient.getSolarSystemInfo(stationInfo.system_id); regionId = systemInfo.constellation_id; // We'll need to get region from constellation securityStatus = systemInfo.security_status; securityClass = systemInfo.security_class; // Get constellation info to get region try { const constellationInfo = await esiClient.getConstellationInfo(systemInfo.constellation_id); regionId = constellationInfo.region_id; idsToResolve.push(regionId); } catch (error) { console.warn(`Failed to get constellation info for ${systemInfo.constellation_id}:`, error); } } catch (error) { console.warn(`Failed to get system info for ${stationInfo.system_id}:`, error); } } // Resolve names let nameMap = new Map<number, string>(); try { const nameResults = await esiClient.idsToNames(idsToResolve); nameMap = new Map(nameResults.map(result => [result.id, result.name])); } catch (error) { console.warn('Failed to fetch names for station data:', error); } // Process services const services = stationInfo.services.map(serviceKey => ({ service_key: serviceKey, service_name: STATION_SERVICES[serviceKey as keyof typeof STATION_SERVICES] || serviceKey, available: true })); // Get agents if requested let agentInfos: Array<{ agent_id: number; agent_name?: string; agent_type?: string; corporation_name?: string; division_id?: number; level?: number; quality?: number; is_locator?: boolean; is_research_agent?: boolean; }> | undefined; if (args.includeAgents) { try { const agents = await sdeClient.getAgentsByLocation(stationInfo.station_id); agentInfos = []; for (const agent of agents) { try { // Get agent name and corporation info const agentIdsToResolve: number[] = [agent.characterID]; if (agent.corporationID) agentIdsToResolve.push(agent.corporationID); let agentNameMap = new Map<number, string>(); try { const agentNameResults = await esiClient.idsToNames(agentIdsToResolve); agentNameMap = new Map(agentNameResults.map(result => [result.id, result.name])); } catch (error) { console.warn('Failed to resolve agent names:', error); } // Get agent type info let agentTypeName: string | undefined; if (agent.agentTypeID) { try { const agentTypeInfo = await sdeClient.getAgentTypeInfo(agent.agentTypeID); agentTypeName = agentTypeInfo.agentType; } catch (error) { console.warn(`Failed to get agent type ${agent.agentTypeID}:`, error); } } agentInfos.push({ agent_id: agent.characterID, agent_name: agentNameMap.get(agent.characterID), agent_type: agentTypeName, corporation_name: agent.corporationID ? agentNameMap.get(agent.corporationID) : undefined, division_id: agent.divisionID, level: agent.level, quality: agent.quality, is_locator: agent.isLocator || false, is_research_agent: false // We don't check research agents here for performance }); } catch (error) { console.warn(`Failed to process agent ${agent.characterID}:`, error); } } } catch (error) { console.warn(`Failed to get agents for station ${stationInfo.station_id}:`, error); } } const combinedInfo: CombinedStationInfo = { station_id: stationInfo.station_id, name: stationInfo.name, system_id: stationInfo.system_id, system_name: nameMap.get(stationInfo.system_id), region_id: regionId, region_name: regionId ? nameMap.get(regionId) : undefined, type_id: stationInfo.type_id, type_name: nameMap.get(stationInfo.type_id), position: stationInfo.position, owner_id: stationInfo.owner, owner_name: nameMap.get(stationInfo.owner), race_id: stationInfo.race_id, race_name: stationInfo.race_id ? nameMap.get(stationInfo.race_id) : undefined, services, docking_info: { max_dockable_ship_volume: stationInfo.max_dockable_ship_volume, office_rental_cost: stationInfo.office_rental_cost }, reprocessing_info: { efficiency: stationInfo.reprocessing_efficiency, stations_take: stationInfo.reprocessing_stations_take }, security_status: securityStatus, security_class: securityClass, agents: agentInfos }; results.push(combinedInfo); } catch (error) { errors.push(`Error fetching station ${stationId}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } return JSON.stringify({ success: results.length > 0, message: `Found information for ${results.length} station(s)`, stations: results, errors: errors.length > 0 ? errors : undefined, summary: { total_stations_requested: args.stations.length, successful_stations: results.length, failed_stations: errors.length, service_summary: { most_common_services: getMostCommonServices(results), unique_services: getUniqueServices(results) } } }); } catch (error) { return JSON.stringify({ success: false, message: `Error getting station services: ${error instanceof Error ? error.message : 'Unknown error'}`, stations: [] }); } }, name: "get_station_services", parameters: z.object({ stations: z.array(z.union([z.string(), z.number()])).min(1).max(50).describe("Array of station names (English proper nouns like 'Jita IV - Moon 4 - Caldari Navy Assembly Plant') or IDs to get service information for (max 50)"), includeSystemInfo: z.boolean().optional().default(true).describe("Whether to include system security status and region information"), includeAgents: z.boolean().optional().default(false).describe("Whether to include agent information (may be slower)") }), }; /** * Get stations in a solar system with their services */ export const getSystemStationsTool = { annotations: { openWorldHint: true, // This tool interacts with external ESI API readOnlyHint: true, // This tool doesn't modify anything title: "Get System Stations", }, description: "Get all stations in specified solar systems with their available services and facilities information.", execute: async (args: { systems: (string | number)[]; serviceFilter?: string[]; }) => { try { if (args.systems.length === 0) { return JSON.stringify({ success: false, message: "At least one system must be provided", systems: [] }); } if (args.systems.length > 20) { return JSON.stringify({ success: false, message: "Maximum 20 systems allowed per request", systems: [] }); } const results: Array<{ system_id: number; system_name: string; security_status: number; security_class?: string; region_name?: string; stations: CombinedStationInfo[]; }> = []; const errors: string[] = []; // Convert system names to IDs if needed const systemIds: number[] = []; const stringSystems = args.systems.filter(s => typeof s === 'string') as string[]; const numericSystems = args.systems.filter(s => typeof s === 'number') as number[]; systemIds.push(...numericSystems); if (stringSystems.length > 0) { try { const systemIdResults = await esiClient.getSolarSystemIds(stringSystems); systemIds.push(...systemIdResults.map(s => s.id)); // Check for systems that weren't found const foundNames = systemIdResults.map(s => s.name.toLowerCase()); const notFound = stringSystems.filter(name => !foundNames.includes(name.toLowerCase()) ); notFound.forEach(name => { errors.push(`System '${name}' not found`); }); } catch (error) { errors.push(`Error converting system names to IDs: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Process each system for (const systemId of systemIds) { try { // Get system information const systemInfo = await esiClient.getSolarSystemInfo(systemId); // Get region information let regionName: string | undefined; try { const constellationInfo = await esiClient.getConstellationInfo(systemInfo.constellation_id); const regionInfo = await esiClient.getRegionInfo(constellationInfo.region_id); regionName = regionInfo.name; } catch (error) { console.warn(`Failed to get region info for system ${systemId}:`, error); } // Get stations in the system const stations = await esiClient.getSystemStations(systemId); const stationInfos: CombinedStationInfo[] = []; for (const station of stations) { // Get additional names const idsToResolve = [station.type_id, station.owner]; if (station.race_id) idsToResolve.push(station.race_id); let nameMap = new Map<number, string>(); try { const nameResults = await esiClient.idsToNames(idsToResolve); nameMap = new Map(nameResults.map(result => [result.id, result.name])); } catch (error) { console.warn('Failed to fetch names for station data:', error); } // Process services const services = station.services.map(serviceKey => ({ service_key: serviceKey, service_name: STATION_SERVICES[serviceKey as keyof typeof STATION_SERVICES] || serviceKey, available: true })); // Apply service filter if provided if (args.serviceFilter && args.serviceFilter.length > 0) { const hasRequiredService = services.some(service => args.serviceFilter!.some(filter => service.service_key.toLowerCase().includes(filter.toLowerCase()) || service.service_name.toLowerCase().includes(filter.toLowerCase()) ) ); if (!hasRequiredService) { continue; // Skip this station if it doesn't have required services } } const stationInfo: CombinedStationInfo = { station_id: station.station_id, name: station.name, system_id: station.system_id, system_name: systemInfo.name, region_id: undefined, // Will be set if we got region info region_name: regionName, type_id: station.type_id, type_name: nameMap.get(station.type_id), position: station.position, owner_id: station.owner, owner_name: nameMap.get(station.owner), race_id: station.race_id, race_name: station.race_id ? nameMap.get(station.race_id) : undefined, services, docking_info: { max_dockable_ship_volume: station.max_dockable_ship_volume, office_rental_cost: station.office_rental_cost }, reprocessing_info: { efficiency: station.reprocessing_efficiency, stations_take: station.reprocessing_stations_take }, security_status: systemInfo.security_status, security_class: systemInfo.security_class }; stationInfos.push(stationInfo); } results.push({ system_id: systemId, system_name: systemInfo.name, security_status: systemInfo.security_status, security_class: systemInfo.security_class, region_name: regionName, stations: stationInfos }); } catch (error) { errors.push(`Error processing system ${systemId}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } const totalStations = results.reduce((sum, system) => sum + system.stations.length, 0); return JSON.stringify({ success: results.length > 0, message: `Found ${totalStations} station(s) across ${results.length} system(s)`, systems: results, errors: errors.length > 0 ? errors : undefined, summary: { total_systems_requested: args.systems.length, successful_systems: results.length, failed_systems: errors.length, total_stations_found: totalStations, service_filter_applied: args.serviceFilter && args.serviceFilter.length > 0, filtered_services: args.serviceFilter } }); } catch (error) { return JSON.stringify({ success: false, message: `Error getting system stations: ${error instanceof Error ? error.message : 'Unknown error'}`, systems: [] }); } }, name: "get_system_stations", parameters: z.object({ systems: z.array(z.union([z.string(), z.number()])).min(1).max(20).describe("Array of solar system names (English proper nouns like 'Jita', 'Amarr') or IDs to get station information for (max 20)"), serviceFilter: z.array(z.string()).optional().describe("Optional array of service names or keywords to filter stations by (e.g., ['market', 'reprocessing', 'cloning'])") }), }; /** * Find stations with specific services across multiple systems or regions */ export const findStationsWithServicesTool = { annotations: { openWorldHint: true, // This tool interacts with external ESI API readOnlyHint: true, // This tool doesn't modify anything title: "Find Stations with Services", }, description: "Find stations that offer specific services within specified systems or regions. Useful for finding trading hubs, reprocessing facilities, or other specialized services.", execute: async (args: { requiredServices: string[]; searchArea: { systems?: (string | number)[]; regions?: (string | number)[]; }; securityFilter?: { minSecurity?: number; maxSecurity?: number; }; limit?: number; }) => { try { if (args.requiredServices.length === 0) { return JSON.stringify({ success: false, message: "At least one required service must be specified", stations: [] }); } const limit = args.limit || 50; if (limit > 100) { return JSON.stringify({ success: false, message: "Maximum limit is 100 stations", stations: [] }); } const results: CombinedStationInfo[] = []; const errors: string[] = []; let systemsToSearch: number[] = []; // Determine systems to search if (args.searchArea.systems && args.searchArea.systems.length > 0) { // Convert system names to IDs if needed const stringSystems = args.searchArea.systems.filter(s => typeof s === 'string') as string[]; const numericSystems = args.searchArea.systems.filter(s => typeof s === 'number') as number[]; systemsToSearch.push(...numericSystems); if (stringSystems.length > 0) { try { const systemIdResults = await esiClient.getSolarSystemIds(stringSystems); systemsToSearch.push(...systemIdResults.map(s => s.id)); } catch (error) { errors.push(`Error converting system names to IDs: ${error instanceof Error ? error.message : 'Unknown error'}`); } } } if (args.searchArea.regions && args.searchArea.regions.length > 0) { // Get systems from regions const stringRegions = args.searchArea.regions.filter(r => typeof r === 'string') as string[]; const numericRegions = args.searchArea.regions.filter(r => typeof r === 'number') as number[]; let regionIds = [...numericRegions]; if (stringRegions.length > 0) { try { const regionIdResults = await esiClient.getRegionIds(stringRegions); regionIds.push(...regionIdResults.map(r => r.id)); } catch (error) { errors.push(`Error converting region names to IDs: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Get systems from regions for (const regionId of regionIds) { try { const regionInfo = await esiClient.getRegionInfo(regionId); for (const constellationId of regionInfo.constellations) { try { const constellationInfo = await esiClient.getConstellationInfo(constellationId); systemsToSearch.push(...constellationInfo.systems); } catch (error) { console.warn(`Failed to get constellation ${constellationId}:`, error); } } } catch (error) { errors.push(`Error processing region ${regionId}: ${error instanceof Error ? error.message : 'Unknown error'}`); } } } // If no search area specified, use a sample of systems if (systemsToSearch.length === 0) { try { const allSystems = await esiClient.getAllSolarSystemIds(); systemsToSearch = allSystems.slice(0, 100); // Sample first 100 systems } catch (error) { return JSON.stringify({ success: false, message: `Error getting systems to search: ${error instanceof Error ? error.message : 'Unknown error'}`, stations: [] }); } } // Remove duplicates and limit search scope systemsToSearch = [...new Set(systemsToSearch)].slice(0, 200); // Limit to 200 systems max // Search stations in systems let stationsFound = 0; for (const systemId of systemsToSearch) { if (stationsFound >= limit) break; try { const systemInfo = await esiClient.getSolarSystemInfo(systemId); // Apply security filter if (args.securityFilter) { if (args.securityFilter.minSecurity !== undefined && systemInfo.security_status < args.securityFilter.minSecurity) { continue; } if (args.securityFilter.maxSecurity !== undefined && systemInfo.security_status > args.securityFilter.maxSecurity) { continue; } } const stations = await esiClient.getSystemStations(systemId); for (const station of stations) { if (stationsFound >= limit) break; // Check if station has required services const hasAllServices = args.requiredServices.every(requiredService => station.services.some(stationService => stationService.toLowerCase().includes(requiredService.toLowerCase()) || (STATION_SERVICES[stationService as keyof typeof STATION_SERVICES] || stationService) .toLowerCase().includes(requiredService.toLowerCase()) ) ); if (!hasAllServices) continue; // Get additional information const idsToResolve = [station.type_id, station.owner]; if (station.race_id) idsToResolve.push(station.race_id); let nameMap = new Map<number, string>(); try { const nameResults = await esiClient.idsToNames(idsToResolve); nameMap = new Map(nameResults.map(result => [result.id, result.name])); } catch (error) { console.warn('Failed to fetch names for station data:', error); } // Get region information let regionName: string | undefined; try { const constellationInfo = await esiClient.getConstellationInfo(systemInfo.constellation_id); const regionInfo = await esiClient.getRegionInfo(constellationInfo.region_id); regionName = regionInfo.name; } catch (error) { console.warn(`Failed to get region info for system ${systemId}:`, error); } const services = station.services.map(serviceKey => ({ service_key: serviceKey, service_name: STATION_SERVICES[serviceKey as keyof typeof STATION_SERVICES] || serviceKey, available: true })); const stationInfo: CombinedStationInfo = { station_id: station.station_id, name: station.name, system_id: station.system_id, system_name: systemInfo.name, region_id: undefined, region_name: regionName, type_id: station.type_id, type_name: nameMap.get(station.type_id), position: station.position, owner_id: station.owner, owner_name: nameMap.get(station.owner), race_id: station.race_id, race_name: station.race_id ? nameMap.get(station.race_id) : undefined, services, docking_info: { max_dockable_ship_volume: station.max_dockable_ship_volume, office_rental_cost: station.office_rental_cost }, reprocessing_info: { efficiency: station.reprocessing_efficiency, stations_take: station.reprocessing_stations_take }, security_status: systemInfo.security_status, security_class: systemInfo.security_class }; results.push(stationInfo); stationsFound++; } } catch (error) { // Continue with other systems if one fails continue; } } return JSON.stringify({ success: results.length > 0, message: `Found ${results.length} station(s) with required services`, stations: results, errors: errors.length > 0 ? errors : undefined, summary: { required_services: args.requiredServices, systems_searched: systemsToSearch.length, stations_found: results.length, security_filter: args.securityFilter, search_area: args.searchArea } }); } catch (error) { return JSON.stringify({ success: false, message: `Error finding stations with services: ${error instanceof Error ? error.message : 'Unknown error'}`, stations: [] }); } }, name: "find_stations_with_services", parameters: z.object({ requiredServices: z.array(z.string()).min(1).describe("Array of required service names or keywords (e.g., ['market', 'reprocessing', 'cloning'])"), searchArea: z.object({ systems: z.array(z.union([z.string(), z.number()])).optional().describe("Optional array of solar system names (English proper nouns like 'Jita', 'Amarr') or IDs to search in"), regions: z.array(z.union([z.string(), z.number()])).optional().describe("Optional array of region names (English proper nouns like 'The Forge', 'Domain') or IDs to search in") }).describe("Area to search for stations (specify either systems or regions or both)"), securityFilter: z.object({ minSecurity: z.number().min(-1).max(1).optional().describe("Minimum security status (e.g., 0.5 for high-sec only)"), maxSecurity: z.number().min(-1).max(1).optional().describe("Maximum security status (e.g., 0.0 for low-sec and below)") }).optional().describe("Optional security status filter"), limit: z.number().min(1).max(100).optional().default(50).describe("Maximum number of stations to return (default: 50, max: 100)") }), }; /** * Helper function to get most common services across stations */ function getMostCommonServices(stations: CombinedStationInfo[]): Array<{ service: string; count: number }> { const serviceCounts = new Map<string, number>(); stations.forEach(station => { station.services.forEach(service => { serviceCounts.set(service.service_name, (serviceCounts.get(service.service_name) || 0) + 1); }); }); return Array.from(serviceCounts.entries()) .map(([service, count]) => ({ service, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); } /** * Helper function to get unique services across stations */ function getUniqueServices(stations: CombinedStationInfo[]): string[] { const uniqueServices = new Set<string>(); stations.forEach(station => { station.services.forEach(service => { uniqueServices.add(service.service_name); }); }); return Array.from(uniqueServices).sort(); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kongyo2/eve-online-traffic-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server