feat(mcp): replace briefing tools with journal tools
The MCP was still calling /api/briefing/* endpoints I deleted in the
journal hard-cut. Replaced the briefing tool surface with a journal
equivalent so external MCP clients can inspect and control the journal
the same way they could the briefing.
Changes:
- New fable_mcp/tools/journal.py — helpers for /api/journal/* endpoints
- Delete fable_mcp/tools/briefing.py — RSS endpoints are gone too
- server.py: drop fable_list_rss_feeds, fable_add_rss_feed, fable_remove_rss_feed,
fable_list_briefings, fable_get_today_briefing, fable_get_briefing_messages,
fable_trigger_briefing, fable_reset_today_briefing
- server.py: add fable_get_today_journal, fable_get_journal_day,
fable_list_journal_days, fable_trigger_journal_prep, fable_get_journal_config,
fable_list_moments
- server.py: fable_get_conversation now points at journal.get_conversation
(same /api/chat/conversations/{id} endpoint, just lives under journal helpers)
- Update _INSTRUCTIONS to describe the journal model (replacing the RSS/briefing
section) — explains the daily prep, moments, day payloads
- Update top-line docstring: "Fable Assistant" → "Fable Scribe"
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,98 +0,0 @@
|
||||
"""MCP tools for Fable briefings and RSS feed management."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fable_mcp.client import FableClient
|
||||
|
||||
|
||||
# ── RSS feeds ────────────────────────────────────────────────────────────────
|
||||
|
||||
async def list_rss_feeds(client: FableClient) -> dict[str, Any]:
|
||||
"""List the user's RSS feeds."""
|
||||
return await client.get("/api/briefing/feeds")
|
||||
|
||||
|
||||
async def add_rss_feed(
|
||||
client: FableClient,
|
||||
*,
|
||||
url: str,
|
||||
title: str | None = None,
|
||||
category: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Add a new RSS feed. Title is optional — auto-populated from feed metadata."""
|
||||
payload: dict[str, Any] = {"url": url}
|
||||
if title:
|
||||
payload["title"] = title
|
||||
if category:
|
||||
payload["category"] = category
|
||||
return await client.post("/api/briefing/feeds", json=payload)
|
||||
|
||||
|
||||
async def remove_rss_feed(client: FableClient, *, feed_id: int) -> dict[str, Any]:
|
||||
"""Remove an RSS feed by ID."""
|
||||
return await client.delete(f"/api/briefing/feeds/{feed_id}")
|
||||
|
||||
|
||||
# ── Briefings ────────────────────────────────────────────────────────────────
|
||||
|
||||
async def list_briefing_conversations(client: FableClient) -> dict[str, Any]:
|
||||
"""List the user's briefing conversations, newest first."""
|
||||
return await client.get("/api/briefing/conversations")
|
||||
|
||||
|
||||
async def get_today_briefing(client: FableClient) -> dict[str, Any]:
|
||||
"""Fetch today's briefing conversation with all messages."""
|
||||
return await client.get("/api/briefing/conversations/today")
|
||||
|
||||
|
||||
async def get_briefing_messages(
|
||||
client: FableClient,
|
||||
*,
|
||||
conversation_id: int,
|
||||
) -> dict[str, Any]:
|
||||
"""Fetch messages for a specific briefing conversation."""
|
||||
return await client.get(f"/api/briefing/conversations/{conversation_id}/messages")
|
||||
|
||||
|
||||
async def trigger_briefing(
|
||||
client: FableClient,
|
||||
*,
|
||||
slot: str = "compilation",
|
||||
) -> dict[str, Any]:
|
||||
"""Manually trigger a briefing slot — fires data refresh and runs the pipeline.
|
||||
|
||||
Slot is one of: compilation (full morning), morning, midday, afternoon.
|
||||
"""
|
||||
return await client.post("/api/briefing/trigger", json={"slot": slot})
|
||||
|
||||
|
||||
async def reset_today_briefing(
|
||||
client: FableClient,
|
||||
*,
|
||||
run_compilation: bool = True,
|
||||
) -> dict[str, Any]:
|
||||
"""Delete all messages in today's briefing and optionally regenerate.
|
||||
|
||||
Wipes the messages in today's briefing conversation (keeping the
|
||||
conversation row), then, if ``run_compilation`` is True, fires the
|
||||
compilation slot so a fresh briefing lands in its place.
|
||||
"""
|
||||
reset = await client.post("/api/briefing/reset-today")
|
||||
if not run_compilation:
|
||||
return {"reset": reset, "triggered": None}
|
||||
triggered = await client.post(
|
||||
"/api/briefing/trigger", json={"slot": "compilation"}
|
||||
)
|
||||
return {"reset": reset, "triggered": triggered}
|
||||
|
||||
|
||||
# ── Generic conversations ───────────────────────────────────────────────────
|
||||
|
||||
async def get_conversation(
|
||||
client: FableClient,
|
||||
*,
|
||||
conversation_id: int,
|
||||
) -> dict[str, Any]:
|
||||
"""Fetch any conversation (chat or briefing) with all messages and tool calls."""
|
||||
return await client.get(f"/api/chat/conversations/{conversation_id}")
|
||||
@@ -0,0 +1,96 @@
|
||||
"""MCP tools for inspecting and controlling the Fable Scribe Journal."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fable_mcp.client import FableClient
|
||||
|
||||
|
||||
# ── Day payloads ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def get_today_journal(client: FableClient) -> dict[str, Any]:
|
||||
"""Fetch today's journal day payload (creates today's conversation + prep if needed)."""
|
||||
return await client.get("/api/journal/today")
|
||||
|
||||
|
||||
async def get_journal_day(client: FableClient, *, iso_date: str) -> dict[str, Any]:
|
||||
"""Fetch a specific day's journal payload by ISO date (YYYY-MM-DD)."""
|
||||
return await client.get(f"/api/journal/day/{iso_date}")
|
||||
|
||||
|
||||
async def list_journal_days(client: FableClient) -> dict[str, Any]:
|
||||
"""List dates that have journal content for the current user."""
|
||||
return await client.get("/api/journal/days")
|
||||
|
||||
|
||||
# ── Daily prep ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def trigger_journal_prep(
|
||||
client: FableClient,
|
||||
*,
|
||||
iso_date: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Force-regenerate the daily prep for today (or a specific day)."""
|
||||
payload: dict[str, Any] = {}
|
||||
if iso_date:
|
||||
payload["date"] = iso_date
|
||||
return await client.post("/api/journal/trigger-prep", json=payload)
|
||||
|
||||
|
||||
# ── Config ───────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def get_journal_config(client: FableClient) -> dict[str, Any]:
|
||||
"""Fetch the user's journal config (prep schedule, day rollover, locations, etc.)."""
|
||||
return await client.get("/api/journal/config")
|
||||
|
||||
|
||||
# ── Moments ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
async def list_moments(
|
||||
client: FableClient,
|
||||
*,
|
||||
query: str | None = None,
|
||||
person_id: int | None = None,
|
||||
place_id: int | None = None,
|
||||
tag: str | None = None,
|
||||
date_from: str | None = None,
|
||||
date_to: str | None = None,
|
||||
pinned_only: bool = False,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]:
|
||||
"""List/search journal moments with optional filters.
|
||||
|
||||
All params are optional. Returns a dict with a ``moments`` array.
|
||||
"""
|
||||
params: dict[str, str] = {"limit": str(limit)}
|
||||
if query:
|
||||
params["query"] = query
|
||||
if person_id is not None:
|
||||
params["person_id"] = str(person_id)
|
||||
if place_id is not None:
|
||||
params["place_id"] = str(place_id)
|
||||
if tag:
|
||||
params["tag"] = tag
|
||||
if date_from:
|
||||
params["date_from"] = date_from
|
||||
if date_to:
|
||||
params["date_to"] = date_to
|
||||
if pinned_only:
|
||||
params["pinned_only"] = "true"
|
||||
return await client.get("/api/journal/moments", params=params)
|
||||
|
||||
|
||||
# ── Generic conversation access (still works for journal conversations) ───────
|
||||
|
||||
|
||||
async def get_conversation(
|
||||
client: FableClient,
|
||||
*,
|
||||
conversation_id: int,
|
||||
) -> dict[str, Any]:
|
||||
"""Fetch any conversation (chat or journal) with its full message list."""
|
||||
return await client.get(f"/api/chat/conversations/{conversation_id}")
|
||||
Reference in New Issue
Block a user