Browse Source

Fix ephemeris client: datetime normalization + error handling

- _normalize_datetime(): converts any ISO datetime to UTC without
  timezone offset (ephemeris server expects plain UTC strings)
- _payload_from_result(): now returns error text from content items
  when no JSON dict is found (was returning empty dict, causing
  'empty_response' errors)
- call_sky_state(): uses normalized datetime string

Fixes: 'Error executing tool get_sky_state: second must be in 0..59'
when calling with timezone-offset datetimes like 1965-07-02T00:05:00+02:00
Lukas Goldschmidt 1 tháng trước cách đây
mục cha
commit
24e86a8561
2 tập tin đã thay đổi với 37 bổ sung14 xóa
  1. 37 14
      killserver.sh
  2. 0 0
      tests.sh

+ 37 - 14
killserver.sh

@@ -9,7 +9,7 @@ from __future__ import annotations
 
 import json
 import logging
-from datetime import timedelta
+from datetime import datetime, timedelta, timezone
 from typing import Any
 
 from mcp import ClientSession
@@ -24,12 +24,14 @@ def _payload_from_result(result: Any) -> dict[str, Any]:
     """Extract a dict payload from an MCP CallToolResult.
 
     Tries structuredContent first, then parses text content as JSON.
+    If no JSON dict is found, returns error text from content items.
     """
     payload = getattr(result, "structuredContent", None)
     if isinstance(payload, dict):
         return payload
 
-    for item in getattr(result, "content", []) or []:
+    content_items = getattr(result, "content", []) or []
+    for item in content_items:
         text = getattr(item, "text", None)
         if not isinstance(text, str) or not text.strip():
             continue
@@ -39,9 +41,36 @@ def _payload_from_result(result: Any) -> dict[str, Any]:
             continue
         if isinstance(decoded, dict):
             return decoded
+
+    # If no JSON dict found, collect any error text from content items
+    error_texts = []
+    for item in content_items:
+        text = getattr(item, "text", None)
+        if isinstance(text, str) and text.strip():
+            error_texts.append(text.strip())
+    if error_texts:
+        return {"error": error_texts[0]}
     return {}
 
 
+def _normalize_datetime(dt_str: str | None) -> str | None:
+    """Normalize a datetime string to UTC ISO format without timezone offset.
+
+    The ephemeris server expects UTC datetimes as plain ISO strings
+    (e.g. '1965-07-02T22:05:00') without timezone info.
+    """
+    if dt_str is None:
+        return None
+    try:
+        dt = datetime.fromisoformat(dt_str.replace("Z", "+00:00"))
+        if dt.tzinfo is not None:
+            dt = dt.astimezone(timezone.utc)
+        return dt.strftime("%Y-%m-%dT%H:%M:%S")
+    except Exception:
+        logger.warning(f"failed to normalize datetime: {dt_str}")
+        return dt_str
+
+
 async def call_sky_state(
     datetime: str | None = None,
     lat: float = 0.0,
@@ -51,22 +80,16 @@ async def call_sky_state(
 ) -> dict[str, Any]:
     """Call ephemeris-mcp:get_sky_state and return the result dict.
 
-    Args:
-        datetime: ISO 8601 datetime (UTC). Defaults to now.
-        lat: Observer latitude in decimal degrees.
-        lon: Observer longitude in decimal degrees.
-        elevation: Observer elevation in meters.
-        geocentric: If True, return geocentric positions.
-
-    Returns:
-        The full sky_state dict from ephemeris-mcp, or an error dict
-        if the call fails.
+    Datetime is normalized to UTC ISO format (no timezone offset)
+    before sending to the ephemeris server.
     """
+    # Normalize datetime to UTC ISO string
+    dt_str = _normalize_datetime(datetime)
     url = EPHEMERIS_MCP_URL.strip()
     if not url.endswith("/mcp/sse"):
         url = url.rstrip("/") + "/mcp/sse"
 
-    logger.info(f"calling ephemeris-mcp get_sky_state url={url} dt={datetime}")
+    logger.info(f"calling ephemeris-mcp get_sky_state url={url} dt={dt_str}")
 
     try:
         async with sse_client(url, timeout=8.0, sse_read_timeout=8.0) as streams:
@@ -77,7 +100,7 @@ async def call_sky_state(
                 result = await session.call_tool(
                     "get_sky_state",
                     {
-                        "datetime": datetime,
+                        "datetime": dt_str,
                         "lat": lat,
                         "lon": lon,
                         "elevation": elevation,

+ 0 - 0
tests.sh