Skip to main content
Glama
gregario

astronomy-oracle

plan_session

Generate an observing session plan for your location and date, showing the best celestial objects grouped by time window with observability scores.

Instructions

Generate an observing session plan for a given location and date. Returns the best celestial objects to observe grouped by time window (evening, midnight, pre-dawn), scored by observability.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
latitudeYesObserver latitude in degrees (-90 to 90)
longitudeYesObserver longitude in degrees (-180 to 180)
dateNoDate in ISO 8601 format (defaults to today)
minAltitudeNoMinimum peak altitude in degrees (default 15)
maxMagnitudeNoMaximum (faintest) visual magnitude to include
typesNoObject type codes to include (e.g. ["G", "PN", "OCl"])

Implementation Reference

  • The handler function that executes the 'plan_session' tool logic by calculating visibility, scoring objects, and formatting the session plan.
    async (params) => {
      const store = await getCatalog();
      const {
        latitude,
        longitude,
        date: dateStr,
        minAltitude: minAltParam,
        maxMagnitude,
        types,
      } = params as {
        latitude: number;
        longitude: number;
        date?: string;
        minAltitude?: number;
        maxMagnitude?: number;
        types?: string[];
      };
    
      const obsDate = dateStr ? new Date(dateStr) : new Date();
      const minAltitude = minAltParam ?? 15;
    
      // Filter candidates
      let candidates = [...store.all.values()];
    
      // Only include objects with known magnitude
      candidates = candidates.filter((obj) => obj.magnitude !== null);
    
      if (maxMagnitude !== undefined) {
        candidates = candidates.filter(
          (obj) => obj.magnitude! <= maxMagnitude,
        );
      }
    
      if (types && types.length > 0) {
        const typeSet = new Set(types);
        candidates = candidates.filter((obj) => typeSet.has(obj.type));
      }
    
      // Compute visibility and score each candidate
      const sessionObjects: SessionObject[] = [];
    
      for (const obj of candidates) {
        const vis = riseTransitSet(obj.ra, obj.dec, latitude, longitude, obsDate);
    
        // Skip objects that never rise
        if (vis.neverRises) continue;
    
        // Compute peak altitude: 90 - |lat - dec| (simple approximation)
        const peakAltitude = 90 - Math.abs(latitude - obj.dec);
    
        // Skip objects that don't reach minAltitude
        if (peakAltitude < minAltitude) continue;
    
        // Need a transit time for window classification
        const transitTime = vis.transitTime;
        if (!transitTime) continue;
    
        const window = classifyWindow(transitTime);
    
        // Score = peakAltitude + max(0, (12 - magnitude) * 5) + min(majorAxis or 0, 30)
        const magBonus = Math.max(0, (12 - (obj.magnitude ?? 12)) * 5);
        const sizeBonus = Math.min(obj.majorAxis ?? 0, 30);
        const score = Math.round(peakAltitude + magBonus + sizeBonus);
    
        sessionObjects.push({
          object: obj,
          peakAltitude,
          transitTime,
          window,
          score,
        });
      }
    
      // Group by window, sort by score desc, take top 15 per window
      const windows = new Map<string, SessionObject[]>();
      for (const w of ["evening", "midnight", "predawn"] as const) {
        const inWindow = sessionObjects
          .filter((so) => so.window === w)
          .sort((a, b) => b.score - a.score)
          .slice(0, 15);
        windows.set(w, inWindow);
      }
    
      return {
        content: [
          {
            type: "text" as const,
            text: formatSessionPlan(windows, { lat: latitude, lon: longitude }, obsDate),
          },
        ],
      };
    },
  • Zod schema definition for the 'plan_session' tool input parameters.
    {
      latitude: z
        .number()
        .min(-90)
        .max(90)
        .describe("Observer latitude in degrees (-90 to 90)"),
      longitude: z
        .number()
        .min(-180)
        .max(180)
        .describe("Observer longitude in degrees (-180 to 180)"),
      date: z
        .string()
        .optional()
        .describe("Date in ISO 8601 format (defaults to today)"),
      minAltitude: z
        .number()
        .optional()
        .describe("Minimum peak altitude in degrees (default 15)"),
      maxMagnitude: z
        .number()
        .optional()
        .describe("Maximum (faintest) visual magnitude to include"),
      types: z
        .array(z.enum(typeKeys))
        .optional()
        .describe("Object type codes to include (e.g. [\"G\", \"PN\", \"OCl\"])"),
    },
  • Function to register the 'plan_session' tool with the MCP server.
    export function registerPlanSession(server: McpServer): void {
      server.tool(
        "plan_session",
        "Generate an observing session plan for a given location and date. Returns the best celestial objects to observe grouped by time window (evening, midnight, pre-dawn), scored by observability.",
        {
          latitude: z
            .number()
            .min(-90)
            .max(90)
            .describe("Observer latitude in degrees (-90 to 90)"),
          longitude: z
            .number()
            .min(-180)
            .max(180)
            .describe("Observer longitude in degrees (-180 to 180)"),
          date: z
            .string()
            .optional()
            .describe("Date in ISO 8601 format (defaults to today)"),
          minAltitude: z
            .number()
            .optional()
            .describe("Minimum peak altitude in degrees (default 15)"),
          maxMagnitude: z
            .number()
            .optional()
            .describe("Maximum (faintest) visual magnitude to include"),
          types: z
            .array(z.enum(typeKeys))
            .optional()
            .describe("Object type codes to include (e.g. [\"G\", \"PN\", \"OCl\"])"),
        },
        async (params) => {
          const store = await getCatalog();
          const {
            latitude,
            longitude,
            date: dateStr,
            minAltitude: minAltParam,
            maxMagnitude,
            types,
          } = params as {
            latitude: number;
            longitude: number;
            date?: string;
            minAltitude?: number;
            maxMagnitude?: number;
            types?: string[];
          };
    
          const obsDate = dateStr ? new Date(dateStr) : new Date();
          const minAltitude = minAltParam ?? 15;
    
          // Filter candidates
          let candidates = [...store.all.values()];
    
          // Only include objects with known magnitude
          candidates = candidates.filter((obj) => obj.magnitude !== null);
    
          if (maxMagnitude !== undefined) {
            candidates = candidates.filter(
              (obj) => obj.magnitude! <= maxMagnitude,
            );
          }
    
          if (types && types.length > 0) {
            const typeSet = new Set(types);
            candidates = candidates.filter((obj) => typeSet.has(obj.type));
          }
    
          // Compute visibility and score each candidate
          const sessionObjects: SessionObject[] = [];
    
          for (const obj of candidates) {
            const vis = riseTransitSet(obj.ra, obj.dec, latitude, longitude, obsDate);
    
            // Skip objects that never rise
            if (vis.neverRises) continue;
    
            // Compute peak altitude: 90 - |lat - dec| (simple approximation)
            const peakAltitude = 90 - Math.abs(latitude - obj.dec);
    
            // Skip objects that don't reach minAltitude
            if (peakAltitude < minAltitude) continue;
    
            // Need a transit time for window classification
            const transitTime = vis.transitTime;
            if (!transitTime) continue;
    
            const window = classifyWindow(transitTime);
    
            // Score = peakAltitude + max(0, (12 - magnitude) * 5) + min(majorAxis or 0, 30)
            const magBonus = Math.max(0, (12 - (obj.magnitude ?? 12)) * 5);
            const sizeBonus = Math.min(obj.majorAxis ?? 0, 30);
            const score = Math.round(peakAltitude + magBonus + sizeBonus);
    
            sessionObjects.push({
              object: obj,
              peakAltitude,
              transitTime,
              window,
              score,
            });
          }
    
          // Group by window, sort by score desc, take top 15 per window
          const windows = new Map<string, SessionObject[]>();
          for (const w of ["evening", "midnight", "predawn"] as const) {
            const inWindow = sessionObjects
              .filter((so) => so.window === w)
              .sort((a, b) => b.score - a.score)
              .slice(0, 15);
            windows.set(w, inWindow);
          }
    
          return {
            content: [
              {
                type: "text" as const,
                text: formatSessionPlan(windows, { lat: latitude, lon: longitude }, obsDate),
              },
            ],
          };
        },
      );
    }
  • Helper function to classify transit times into observation windows (evening, midnight, predawn).
    function classifyWindow(transitTime: Date): SessionWindow {
      const hour = transitTime.getUTCHours();
      if (hour >= 18 && hour < 22) return "evening";
      if (hour >= 22 || hour < 2) return "midnight";
      if (hour >= 2 && hour < 6) return "predawn";
      // Default: pick closest window
      if (hour >= 6 && hour < 15) return "predawn"; // morning → predawn is closest
      return "evening"; // afternoon → evening is closest
    }
Install Server

Other Tools

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/gregario/astronomy-oracle'

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