"""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, FableAPIError async def list_conversations( client: FableClient, *, limit: int = 20, offset: int = 0, ) -> dict[str, Any]: """List MCP chat conversations.""" params: dict[str, Any] = {"limit": limit, "offset": offset, "type": "mcp"} return await client.get("/api/chat/conversations", params=params) async def send_message( client: FableClient, *, message: str, conversation_id: str | None = None, think: bool = False, ) -> dict[str, Any]: """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. SSE event format: id: event: # chunk | done | tool_call | status | ... data: Returns: Dict with: - "conversation_id": str - "response": str (full assistant message) - "tool_calls": list of any tool_call events observed """ if conversation_id is None: conv = await client.post( "/api/chat/conversations", json={"conversation_type": "mcp"}, ) conversation_id = str(conv["id"]) # Start generation await client.post( f"/api/chat/conversations/{conversation_id}/messages", json={"content": message, "think": think}, ) tokens: list[str] = [] tool_calls: list[Any] = [] stream_path = f"/api/chat/conversations/{conversation_id}/generation/stream" # 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_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, "response": "".join(tokens), "tool_calls": tool_calls, }