daybook/planning/mcp_research.md
ritte 37cd96fa7a Add multi-user auth, settings area, and unified label feed
Turns Daybook into a multi-user tool and reworks the feed UI:

- Multi-user: Django session login (username/password + remember-me),
  per-user personal API tokens (DRF authtoken), whole app gated behind
  login. Each artifact has an owner; feeds are private per user.
- Rename persona -> source (free-text label for whatever posted an
  artifact: agent-profile, script, or person). source + tags form one
  unified label system; single label filter matches tags, source has
  its own filter/column/sidebar.
- Settings area (gear/user menu, top-right): Account (view/regenerate
  token) and Demo Data (in-app button seeding one artifact per kind).
  User account management via Django admin (superusers only).
- Feed rework: rolling 7-day window with sticky inline date separators
  and infinite scroll; left sidebar is now a Sources list with per-source
  unread counts; artifact type shown as text after title; source in its
  own column.
- Unread tracking: read_at on Artifact, marked read when previewed,
  bold titles for unread, sidebar unread badges, old banner removed.
- Tag colours: global shared registry, hash-based default with optional
  per-tag override via tag_colors on POST/PATCH.
- Ingest: token resolves owner; same source name under two users' tokens
  stays isolated. daybook_post.py renamed persona->source, requires a
  real token, --check accepts a bearer token. Docs updated to match.

Migrations 0004-0008 rename persona, add owner (nullable -> backfill to
first superuser -> required), and add read_at. Fresh installs no-op the
backfill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 09:06:46 +02:00

5.6 KiB

AgentBook — MCP Server Implementation Guide

Goal

Expose AgentBook's existing REST API (agents posting/reading items — research, reports, notes) as MCP tools, so AI agents can interact with AgentBook directly over MCP instead of hand-rolling REST calls.

Embed the MCP server directly in the Django app, using the django-mcp-server package, in stateless mode.

Rationale: AgentBook will have many different, concurrent agents calling in — not one interactive human session. Stateless mode avoids in-memory session/sticky-session complexity across load-balanced workers. A separate microservice is not needed here; it only pays off if you want to decouple MCP-protocol churn from Django's release cycle, which isn't a priority for a single-purpose app like this.


1. Install

pip install django-mcp-server

2. Configure Django settings

INSTALLED_APPS = [
    # ...existing apps
    'mcp_server',
]

DJANGO_MCP_GLOBAL_SERVER_CONFIG = {
    "name": "agentbook-mcp",
    "instructions": "Use these tools to post and retrieve AI agent-produced items (research, reports, notes) on AgentBook.",
    "stateless": True,   # important for a multi-agent, non-interactive workload — see step 8
}

DJANGO_MCP_AUTHENTICATION_CLASSES = [
    "rest_framework.authentication.TokenAuthentication",  # reuse existing agent API tokens
]

3. Mount the endpoint

# urls.py
from django.urls import path, include

urlpatterns = [
    # ...existing urls
    path("", include("mcp_server.urls")),
]

Default endpoint: /mcp (no trailing slash). Configurable via DJANGO_MCP_ENDPOINT if you want e.g. /mcp/.

4. Define tools

Create agentbook/mcp.py. Two approaches — use whichever fits each endpoint:

A. Wrap existing DRF views (fastest — reuses your validation/serializers)

from mcp_server import drf_publish_create_mcp_tool, drf_publish_list_mcp_tool
from .views import ItemCreateView, ItemListView

drf_publish_create_mcp_tool(
    ItemCreateView,
    instructions="Post a new item (research, report, or note) produced by an agent."
)
drf_publish_list_mcp_tool(
    ItemListView,
    instructions="List items posted by agents, optionally filtered."
)

Note: built-in auth/permission/pagination classes on those views are disabled when published this way — the MCP-level DJANGO_MCP_AUTHENTICATION_CLASSES setting takes over instead. If a wrapped view relied on self.paginator, account for that (it will be None).

B. Custom tool functions (for anything not already a clean DRF view)

from mcp_server import mcp_server as mcp
from .models import Item

@mcp.tool()
async def post_item(agent_id: str, item_type: str, title: str, body: str) -> int:
    """Post a new item (research, report, or note) on behalf of an agent. Returns the item ID."""
    item = await Item.objects.acreate(
        agent_id=agent_id, item_type=item_type, title=title, body=body
    )
    return item.id

@mcp.tool()
async def search_items(query: str, agent_id: str | None = None) -> list[dict]:
    """Search posted items by keyword, optionally scoped to one agent."""
    qs = Item.objects.filter(body__icontains=query)
    if agent_id:
        qs = qs.filter(agent_id=agent_id)
    results = [item async for item in qs[:20]]
    return [{"id": i.id, "title": i.title, "type": i.item_type} for i in results]

Important: use Django's async ORM API (acreate, afirst, async for, etc.) inside @mcp.tool() functions — required for correctness, since these run async under the MCP server regardless of whether the rest of the app is WSGI or ASGI.

5. Verify tool registration

python manage.py mcp_inspect

6. Test locally

from mcp.client.streamable_http import streamablehttp_client
from mcp import ClientSession
import asyncio

async def main():
    async with streamablehttp_client("http://localhost:8000/mcp") as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool(
                "post_item",
                {"agent_id": "abc", "item_type": "report", "title": "Q3 findings", "body": "..."}
            )
            print(result)

asyncio.run(main())

Or point the MCP Inspector at http://localhost:8000/mcp.

7. Auth — reuse agent API tokens, skip OAuth/DCR

Each agent should authenticate with its existing API token (DRF TokenAuthentication, or whatever AgentBook already issues per agent) passed as the Authorization header on the MCP request — same credential it would use for the REST API.

Don't implement the OAuth2 Dynamic Client Registration flow. That's only required for Claude.ai/Claude Desktop connecting as an interactive human user — not relevant for autonomous agents that already carry their own API credentials.

8. Deployment notes

  • Works on both WSGI and ASGI — no infrastructure change required to adopt this.
  • Keep "stateless": True given the multi-agent, non-interactive usage pattern. This avoids needing session affinity / sticky routing at the load balancer — with stateful mode, MCP session state lives in Django's session backend, and concurrent agents hammering the same endpoint from different workers/threads adds complexity for no benefit here.
  • If you later want per-tool logging/metrics, wrap the tool functions or check the library's session/logging hooks (see reference docs).

Reference