aboutsummaryrefslogtreecommitdiff
path: root/mcp/senia/server.py
diff options
context:
space:
mode:
Diffstat (limited to 'mcp/senia/server.py')
-rw-r--r--mcp/senia/server.py144
1 files changed, 144 insertions, 0 deletions
diff --git a/mcp/senia/server.py b/mcp/senia/server.py
new file mode 100644
index 0000000..e009301
--- /dev/null
+++ b/mcp/senia/server.py
@@ -0,0 +1,144 @@
+#!/usr/bin/env python3
+"""Sénia MCP server: shared registration point for all senia tools.
+
+Tools are implemented in their respective submodules and re-exported via
+the package __init__. This file is intentionally thin — it only handles
+registration and startup.
+"""
+
+import functools
+
+from db import run_migrations
+from mcp.server.fastmcp import FastMCP
+import diary
+import tasks
+
+mcp = FastMCP("senia")
+
+
+@mcp.tool()
+def diary_log(content: str) -> dict:
+ """Record a brief observation, decision, or moment to your diary.
+
+ Use generously throughout a conversation: things the user mentioned, decisions
+ made, patterns you noticed, your own reactions. Keep entries short (one or two
+ sentences) — granular is fine, you can write many. These become the raw material
+ for daily reflection and longer-term memory.
+
+ Args:
+ content: The observation, in your own words. Be specific; "user mentioned X"
+ is more useful than "user is busy".
+
+ Returns:
+ The created entry.
+ """
+ return diary.log(content)
+
+
+@mcp.tool()
+def diary_today() -> list[dict]:
+ """Return all diary entries logged so far today (since 00:00 local time).
+
+ Use this when starting a new conversation to recall what you've already
+ noted today, so you don't rediscover the same conclusions.
+ """
+ return diary.today()
+
+
+@mcp.tool()
+def diary_recent(hours: int = 24) -> list[dict]:
+ """Return diary entries from the last N hours. Default 24."""
+ return diary.recent(hours)
+
+
+@mcp.tool()
+def diary_range(start_ts: str, end_ts: str) -> list[dict]:
+ """Return diary entries between two ISO 8601 timestamps (inclusive start,
+ exclusive end). Useful for inspecting specific past periods."""
+ return diary.range_query(start_ts, end_ts)
+
+
+@mcp.tool()
+def tasks_add(owner: str, content: str, due_at: str | None = None,
+ notes: str | None = None) -> dict:
+ """Create a live task.
+
+ owner determines handling:
+ - "agent": something you can work on autonomously — pick it up, do it, then complete it.
+ - "user": something only the user can do (appointments, errands, calls). You cannot
+ finish these yourself; your job is to gently remind the user until they confirm done.
+
+ Args:
+ owner: "agent" or "user".
+ content: What the task is, in natural language.
+ due_at: Optional ISO 8601 deadline. Drives reminder urgency for user tasks.
+ notes: Optional context.
+ """
+ return tasks.add_task(owner, content, due_at, notes)
+
+
+@mcp.tool()
+def tasks_list(owner: str | None = None, due_before: str | None = None) -> list[dict]:
+ """List live tasks. Every task here is open by definition (done/cancelled tasks
+ are deleted). Ordered by deadline, soonest first, undated last.
+
+ Args:
+ owner: Filter by "agent" or "user". Omit for all.
+ due_before: ISO 8601; only tasks due before this. Useful for finding what's urgent.
+ """
+ return tasks.list_tasks(owner, due_before)
+
+
+@mcp.tool()
+def tasks_update(task_id: int, content: str | None = None, due_at: str | None = None,
+ owner: str | None = None, notes: str | None = None) -> dict:
+ """Update a task's fields. Only provided fields change. Use for refining content,
+ adjusting the deadline, reassigning owner, or appending notes. To finish or drop a
+ task, use complete_task / cancel_task instead."""
+ return tasks.update_task(task_id, content, due_at, owner, notes)
+
+
+def _tasks_remove(task_id: int, context: str, state: str) -> dict:
+ result = tasks.remove_task(task_id)
+ if "error" in result:
+ return result
+ deleted = result["deleted"]
+ log_content = f"Task '{deleted['content']}' has been {state}. {context}"
+ log_entry = diary.log(log_content)
+ return {"deleted": deleted, "logged": log_entry}
+
+
+@mcp.tool()
+def tasks_complete(task_id: int, context: str) -> dict:
+ """Remove a finished task from the live list.
+
+ Args:
+ task_id: identifier of the task.
+ context: brief summary of the task completion.
+ """
+ return _tasks_remove(task_id, context, 'completed')
+
+
+@mcp.tool()
+def tasks_cancel(task_id: int, context: str) -> dict:
+ """Remove a no-longer-relevant task from the live list.
+
+ Args:
+ task_id: identifier of the task.
+ context: reason for the task cancellation.
+ """
+ return _tasks_remove(task_id, context, 'cancelled')
+
+
+@mcp.tool()
+def tasks_nudged(task_id: int) -> dict:
+ """Record that you've just reminded the user about a (user-owned) task, updating its
+ last_nudged_at. Call immediately after nudging, so you can throttle reminders. Check
+ last_nudged_at before nudging again — re-nudge more often as the deadline approaches,
+ sparingly for distant or undated tasks."""
+ return tasks.record_nudge(task_id)
+
+
+if __name__ == "__main__":
+ run_migrations()
+ mcp.run()