diff --git a/fable-mcp/fable_mcp/tools/chat.py b/fable-mcp/fable_mcp/tools/chat.py index 740fda4..119803b 100644 --- a/fable-mcp/fable_mcp/tools/chat.py +++ b/fable-mcp/fable_mcp/tools/chat.py @@ -1,10 +1,11 @@ """MCP tools for Fable chat — create conversations and stream responses.""" from __future__ import annotations +import asyncio import json from typing import Any -from fable_mcp.client import FableClient +from fable_mcp.client import FableClient, FableAPIError async def list_conversations( @@ -28,8 +29,12 @@ async def send_message( """Send a message to Fable and return the full assistant response. Creates a new MCP conversation if conversation_id is None. - Posts the user message to start generation, then streams the SSE buffer - and accumulates token chunks into a single string. + Posts the user message to start generation, then streams the SSE buffer. + + SSE event format: + id: + event: # chunk | done | tool_call | status | ... + data: Returns: Dict with: @@ -54,21 +59,34 @@ async def send_message( tool_calls: list[Any] = [] stream_path = f"/api/chat/conversations/{conversation_id}/generation/stream" - async for raw_line in client.stream_get(stream_path): - if not raw_line.startswith("data: "): - continue - payload_str = raw_line[len("data: "):] + + # Retry connecting to the stream briefly — the background task may not have + # created the generation buffer by the time we issue the GET. + for attempt in range(10): try: - event = json.loads(payload_str) - except json.JSONDecodeError: - continue - event_type = event.get("type") - if event_type == "token": - tokens.append(event.get("content", "")) - elif event_type == "tool_call": - tool_calls.append(event) - elif event_type == "done": - break + event_type: str | None = None + async for raw_line in client.stream_get(stream_path): + if raw_line.startswith("event: "): + event_type = raw_line[len("event: "):].strip() + elif raw_line.startswith("data: "): + payload_str = raw_line[len("data: "):] + try: + data = json.loads(payload_str) + except json.JSONDecodeError: + continue + if event_type == "chunk": + tokens.append(data.get("chunk", "")) + elif event_type == "tool_call": + tool_calls.append(data.get("tool_call", data)) + elif event_type == "done": + break + event_type = None # reset after consuming data line + break # stream completed successfully + except FableAPIError as exc: + if exc.status_code == 404 and attempt < 9: + await asyncio.sleep(0.3) + continue + raise return { "conversation_id": conversation_id, diff --git a/fable-mcp/pyproject.toml b/fable-mcp/pyproject.toml index 14e3020..26250f9 100644 --- a/fable-mcp/pyproject.toml +++ b/fable-mcp/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "fable-mcp" -version = "0.2.2" +version = "0.2.3" description = "MCP server for Fabled Assistant" requires-python = ">=3.12" dependencies = [