diff --git a/src/fabledassistant/mcp/__init__.py b/src/fabledassistant/mcp/__init__.py new file mode 100644 index 0000000..c00ca03 --- /dev/null +++ b/src/fabledassistant/mcp/__init__.py @@ -0,0 +1,8 @@ +"""In-app MCP server. Exposes Fabled Scribe data via the MCP streamable-HTTP transport. + +Auth uses the existing `api_keys` table: a Bearer token in the Authorization +header resolves to a user, and every tool call acts on that user's data only. +""" +from fabledassistant.mcp.server import build_mcp_server, mount_mcp + +__all__ = ["build_mcp_server", "mount_mcp"] diff --git a/src/fabledassistant/mcp/server.py b/src/fabledassistant/mcp/server.py new file mode 100644 index 0000000..4856655 --- /dev/null +++ b/src/fabledassistant/mcp/server.py @@ -0,0 +1,38 @@ +"""FastMCP instance + Quart mount-point. Tools are registered in mcp/tools/.""" +from __future__ import annotations + +from mcp.server.fastmcp import FastMCP +from quart import Quart + +_INSTRUCTIONS = """ +Fabled Scribe is the user's self-hosted second-brain and project-management +data store. You (Claude) are the assistant. + +Hierarchy: Project -> Milestone -> Task/Note. + +- Notes and Tasks share a model; tasks are notes with is_task=True. +- Use fable_*_note tools for notes, fable_*_task tools for tasks. +- Typed entities (person, place, list) are notes with a type field plus + type-specific columns; use the dedicated tools for those. +- Tags are plain strings (no `#` prefix). Empty list clears tags; omit to leave + unchanged on updates. +- For optional integer FKs (project_id, milestone_id, parent_id), use 0 to mean + "not set". +""" + + +def build_mcp_server() -> FastMCP: + """Build the FastMCP instance with all tools registered.""" + mcp = FastMCP("fable", instructions=_INSTRUCTIONS.strip()) + # Tools will be registered here in Phase 2/3. + return mcp + + +def mount_mcp(app: Quart) -> None: + """Mount the FastMCP streamable-HTTP ASGI sub-app at /mcp on the Quart app. + + Phase 1.2 scaffold: just builds and exposes the instance for tests. The + actual ASGI wiring with bearer-auth happens in Task 1.4. + """ + mcp = build_mcp_server() + app.mcp_instance = mcp