"""MCP tools for Fable chat — create conversations and stream responses.""" from __future__ import annotations import json from typing import Any from fable_mcp.client import FableClient 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. Streams the SSE response and accumulates token chunks into a single string. 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 = conv["id"] # Build SSE stream URL with message as query param params: dict[str, Any] = {"message": message} if think: params["think"] = "true" tokens: list[str] = [] tool_calls: list[Any] = [] stream_path = f"/api/chat/conversations/{conversation_id}/stream" async for raw_line in client.stream_get(stream_path, params=params): if not raw_line.startswith("data: "): continue payload_str = raw_line[len("data: "):] 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 return { "conversation_id": conversation_id, "response": "".join(tokens), "tool_calls": tool_calls, }