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>
145 lines
No EOL
5.6 KiB
Markdown
145 lines
No EOL
5.6 KiB
Markdown
# 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 |