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>
This commit is contained in:
ritte 2026-07-24 09:06:46 +02:00
parent 203f9b578e
commit 37cd96fa7a
33 changed files with 1614 additions and 367 deletions

145
planning/mcp_research.md Normal file
View file

@ -0,0 +1,145 @@
# 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.
## Recommended approach
**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
```bash
pip install django-mcp-server
```
## 2. Configure Django settings
```python
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
```python
# 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)
```python
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)
```python
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
```bash
python manage.py mcp_inspect
```
## 6. Test locally
```python
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](https://github.com/modelcontextprotocol/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
- `django-mcp-server` docs: https://github.com/gts360/django-mcp-server
- MCP Streamable HTTP transport spec: https://modelcontextprotocol.io/specification/2025-03-26/basic/transports