split each MCP server into tools/resources/prompts modules

This commit is contained in:
2026-04-16 02:30:03 -03:00
parent 8645adb3d6
commit 6856d09986
12 changed files with 569 additions and 563 deletions

View File

@@ -4,13 +4,8 @@ Covers: passenger notification generation, flight manifest, and
notification prompt template (multi-tone).
"""
import json
from fastmcp import FastMCP
from mcp_servers.data.models import MPStatus
from mcp_servers.data.scenarios.manager import scenario_manager
mcp = FastMCP(
"stellar-ops-passenger",
instructions=(
@@ -19,151 +14,4 @@ mcp = FastMCP(
),
)
# ── Tools ──────────────────────────────────────────────────────────
@mcp.tool()
async def generate_notification(context: dict) -> str:
"""Synthesizes flight disruption context into an empathetic,
actionable passenger notification.
Uses Claude via Anthropic SDK (or Bedrock when USE_BEDROCK=true).
Output: clear, human, no jargon, includes gate/time/status.
Falls back to template if no API key is configured.
"""
try:
from mcp_servers.shared_llm import generate, _get_provider
system_prompt = (
"You are a passenger notification system for Stellar Air. "
"Write a clear, empathetic notification about this flight disruption. "
"Explain WHY the delay or cancellation happened using the operational data provided. "
"Tell the passenger what's happening next: new boarding time, gate, status. "
"Be human and reassuring. No aviation jargon. No speculation. "
"If data is missing for a section, omit it — don't make things up."
)
text = await generate(system_prompt, json.dumps(context, indent=2))
return json.dumps({"text": text, "provider": _get_provider()})
except Exception:
return json.dumps({"text": _template_notification(context), "provider": "template"})
def _template_notification(context: dict) -> str:
"""Structured template fallback when LLM is unavailable."""
flight_id = context.get("flight_id", "")
origin = context.get("origin", "")
destination = context.get("destination", "")
status = context.get("status", "DELAYED")
delay_minutes = context.get("delay_minutes", 0)
delay_cause = context.get("delay_cause", "")
gate = context.get("gate", "")
weather_summary = context.get("weather_summary", "")
crew_notes_summary = context.get("crew_notes_summary", "")
lines = [
f"{flight_id}{origin}{destination}",
f"Status: {status}" + (f" {delay_minutes} minutes" if delay_minutes else ""),
"",
]
if delay_cause:
cause_text = {
"WEATHER": "weather conditions along your route",
"MAINTENANCE": "a routine maintenance check on your aircraft",
"CREW": "ensuring your crew is fully rested for safe operation",
"ATC": "air traffic control restrictions",
"LATE_AIRCRAFT": "the late arrival of your inbound aircraft",
}.get(delay_cause, f"{delay_cause.lower()}")
lines.append(f"Your flight is delayed due to {cause_text}.")
if weather_summary:
lines.append(f"Current conditions: {weather_summary}.")
if gate:
lines.append(f"\nGate {gate} — no gate change expected.")
return "\n".join(lines)
# ── Resources ──────────────────────────────────────────────────────
@mcp.resource("ops://flights/{flight_id}/manifest")
def flight_manifest(flight_id: str) -> str:
"""Passenger manifest summary for a flight.
Returns: flight_id, passenger count, count by MP status tier,
special needs count, connection count.
Summary-level — no PII exposed through this resource.
"""
flight = None
for f in scenario_manager.flights:
if f.flight_id == flight_id:
flight = f
break
if not flight:
return json.dumps({"error": f"Flight {flight_id} not found"})
# Count passengers by status from scenario data
pax_on_flight = [
p for p in scenario_manager.passengers
if p.flight_id == flight_id
]
status_counts = {}
special_needs_count = 0
connection_count = 0
for p in pax_on_flight:
status = p.mileage_plus_status.value
status_counts[status] = status_counts.get(status, 0) + 1
if p.special_needs:
special_needs_count += 1
if p.connection_flight:
connection_count += 1
return json.dumps({
"flight_id": flight_id,
"total_passengers": flight.passenger_count,
"by_status": status_counts,
"special_needs_count": special_needs_count,
"connection_count": connection_count,
})
# ── Prompts ────────────────────────────────────────────────────────
TONE_INSTRUCTIONS = {
"empathetic": (
"Write a passenger notification about this flight disruption. "
"Be empathetic and human. Explain WHY the delay/cancellation happened "
"using the weather, maintenance, or operational data provided. "
"Tell the passenger what's happening next: new boarding time, gate, "
"rebooking options. End with reassurance. No jargon."
),
"factual": (
"Write a brief, factual notification. Include: flight number, "
"route, status, delay duration, cause (one phrase), new departure time, "
"gate. No editorial. No reassurance. Just facts."
),
"brief_sms": (
"Write an SMS-length notification (under 160 characters). "
"Format: UA{flight} {route}: {status}. {cause}. New dep: {time}. Gate {gate}."
),
}
@mcp.prompt()
def passenger_notification(tone: str = "empathetic") -> str:
"""Template for generating passenger delay/cancellation notifications.
tone: empathetic (default) | factual | brief_sms
"""
instruction = TONE_INSTRUCTIONS.get(tone, TONE_INSTRUCTIONS["empathetic"])
return (
f"{instruction}\n\n"
"Use the operational data provided below. Do not invent details. "
"If data is missing for a section, omit that section."
)
from mcp_servers.passenger import tools, resources, prompts # noqa: E402, F401