Skip to main content
Glama
MaxwellCalkin

N2YO Satellite Tracker MCP Server

query_satellites_natural

Answer natural language questions about satellite positions and passes, including location-based queries and category filtering.

Instructions

Answer natural language questions about satellites like 'What satellites will be over France at 6:00 tonight?'

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesNatural language query about satellites (e.g., 'What satellites will be over France at 6:00 tonight?', 'Show me military satellites above Germany now')
categoryFilterNoOptional filter for satellite categoryall

Implementation Reference

  • Main handler function that executes the tool logic: parses natural language query for location and time, fetches satellites above the location via N2YOClient, applies category filter, and returns structured JSON response.
    private async querySatellitesNatural(
      query: string,
      categoryFilter: string = "all"
    ): Promise<CallToolResult> {
      try {
        // Parse the natural language query
        const parsed = LocationTimeParser.extractLocationAndTime(query);
    
        if (!parsed.location) {
          return {
            content: [
              {
                type: "text",
                text: "Could not identify a location in your query. Please specify a location like 'France', 'New York', or 'Germany'.",
              },
            ],
            isError: true,
          };
        }
    
        // Default to current time if no time specified
        const targetTime =
          parsed.time?.timestamp || Math.floor(Date.now() / 1000);
        const timeDescription = parsed.time?.description || "right now";
    
        // Determine which satellites to get based on the query context
        let satellites;
        const categoryId =
          categoryFilter !== "all"
            ? this.n2yoClient.getCategoryId(categoryFilter)
            : 0;
    
        // Check if query is asking about future time (for predictions) or current/past time (for current position)
        const timeDiff = targetTime - Math.floor(Date.now() / 1000);
    
        if (timeDiff > 300) {
          // More than 5 minutes in the future
          // This is a future prediction query - but N2YO "above" endpoint only shows current satellites
          // We'll get current satellites and note the limitation
          satellites = await this.n2yoClient.getSatellitesAbove(
            parsed.location.latitude,
            parsed.location.longitude,
            0, // altitude
            85, // search radius
            categoryId
          );
    
          const filteredSatellites =
            categoryFilter !== "all"
              ? satellites.filter((sat) =>
                  this.matchesCategoryFilter(sat, categoryFilter)
                )
              : satellites;
    
          const response = {
            query: query,
            location: {
              name: parsed.location.name,
              coordinates: {
                latitude: parsed.location.latitude,
                longitude: parsed.location.longitude,
              },
            },
            time: {
              requested: timeDescription,
              note: "Showing satellites currently above the location. N2YO API provides current positions, not future predictions for overhead satellites.",
            },
            categoryFilter: categoryFilter,
            satellites: filteredSatellites.map((sat) => ({
              noradId: sat.satid,
              name: sat.satname,
              position: {
                latitude: sat.satlat,
                longitude: sat.satlng,
                altitude: sat.satalt,
              },
              launchDate: sat.launchDate,
              internationalDesignator: sat.intDesignator,
            })),
            count: filteredSatellites.length,
            summary: `Found ${filteredSatellites.length} ${
              categoryFilter === "all" ? "" : categoryFilter + " "
            }satellites currently above ${parsed.location.name}`,
          };
    
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(response, null, 2),
              },
            ],
          };
        } else {
          // This is a current or near-current time query
          satellites = await this.n2yoClient.getSatellitesAbove(
            parsed.location.latitude,
            parsed.location.longitude,
            0, // altitude
            85, // search radius
            categoryId
          );
    
          const filteredSatellites =
            categoryFilter !== "all"
              ? satellites.filter((sat) =>
                  this.matchesCategoryFilter(sat, categoryFilter)
                )
              : satellites;
    
          const response = {
            query: query,
            location: {
              name: parsed.location.name,
              coordinates: {
                latitude: parsed.location.latitude,
                longitude: parsed.location.longitude,
              },
            },
            time: {
              description: timeDescription,
              timestamp: targetTime,
            },
            categoryFilter: categoryFilter,
            satellites: filteredSatellites.map((sat) => ({
              noradId: sat.satid,
              name: sat.satname,
              position: {
                latitude: sat.satlat,
                longitude: sat.satlng,
                altitude: sat.satalt,
              },
              launchDate: sat.launchDate,
              internationalDesignator: sat.intDesignator,
            })),
            count: filteredSatellites.length,
            summary: `Found ${filteredSatellites.length} ${
              categoryFilter === "all" ? "" : categoryFilter + " "
            }satellites above ${parsed.location.name} ${timeDescription}`,
          };
    
          return {
            content: [
              {
                type: "text",
                text: JSON.stringify(response, null, 2),
              },
            ],
          };
        }
      } catch (error) {
        return {
          content: [
            {
              type: "text",
              text: `Error processing natural language query: ${
                error instanceof Error ? error.message : String(error)
              }`,
            },
          ],
          isError: true,
        };
      }
    }
  • src/server.ts:34-63 (registration)
    Tool registration in the getTools() method, defining name, description, and input schema.
    {
      name: "query_satellites_natural",
      description:
        "Answer natural language questions about satellites like 'What satellites will be over France at 6:00 tonight?'",
      inputSchema: {
        type: "object",
        properties: {
          query: {
            type: "string",
            description:
              "Natural language query about satellites (e.g., 'What satellites will be over France at 6:00 tonight?', 'Show me military satellites above Germany now')",
          },
          categoryFilter: {
            type: "string",
            enum: [
              "all",
              "military",
              "weather",
              "gps",
              "amateur",
              "starlink",
              "space-stations",
            ],
            default: "all",
            description: "Optional filter for satellite category",
          },
        },
        required: ["query"],
      },
    },
  • Input schema defining the expected parameters: query (required string) and optional categoryFilter.
    inputSchema: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description:
            "Natural language query about satellites (e.g., 'What satellites will be over France at 6:00 tonight?', 'Show me military satellites above Germany now')",
        },
        categoryFilter: {
          type: "string",
          enum: [
            "all",
            "military",
            "weather",
            "gps",
            "amateur",
            "starlink",
            "space-stations",
          ],
          default: "all",
          description: "Optional filter for satellite category",
        },
      },
      required: ["query"],
    },
  • Dispatch case in callTool method that routes the tool call to the specific handler.
    case "query_satellites_natural":
      return await this.querySatellitesNatural(args.query, args.categoryFilter);
  • Helper function to filter satellites by category based on name keywords.
    private matchesCategoryFilter(
      satellite: any,
      categoryFilter: string
    ): boolean {
      const satName = satellite.satname?.toLowerCase() || "";
    
      switch (categoryFilter) {
        case "military":
          return (
            satName.includes("military") ||
            satName.includes("defense") ||
            satName.includes("nrol") ||
            (satName.includes("usa") && satName.includes("classified"))
          );
    
        case "weather":
          return (
            satName.includes("weather") ||
            satName.includes("noaa") ||
            satName.includes("goes") ||
            satName.includes("meteosat")
          );
    
        case "gps":
          return (
            satName.includes("gps") ||
            satName.includes("navstar") ||
            satName.includes("navigation")
          );
    
        case "amateur":
          return (
            satName.includes("amateur") ||
            satName.includes("amsat") ||
            satName.includes("cubesat")
          );
    
        case "starlink":
          return satName.includes("starlink");
    
        case "space-stations":
          return (
            satName.includes("space station") ||
            satName.includes("iss") ||
            satName.includes("tiangong")
          );
    
        default:
          return true;
      }
    }

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/MaxwellCalkin/N2YO-MCP'

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