⏺ MCP Server Implementation Prompt for Brainspread Django App
Overview
Build a Model Context Protocol (MCP) server as a Django app within the Brainspread
project. The server will enable full agentic control of the hierarchical block-based
knowledge management system, allowing AI chat to create, move, edit, and organize
blocks and pages programmatically while leveraging existing Django infrastructure.
Architecture Understanding
Brainspread uses:
- Pages: Containers for blocks (daily notes, regular pages, templates)
- Blocks: Hierarchical content units with parent/child relationships
- Tags: Hashtag-based organization (#tag)
- Links: Page references ([[Page Title]]) and block references (((block-uuid)))
- Properties: Key-value metadata (key:: value)
- Commands Pattern: All business logic in command classes
Django App Structure
Create new Django app at packages/django-app/app/mcp_server/:
mcp_server/
├── init.py
├── apps.py
├── server.py # Main MCP server implementation
├── tools/ # MCP tool implementations
│ ├── init.py
│ ├── page_tools.py # Page management tools
│ ├── block_tools.py # Block manipulation tools
│ ├── content_tools.py # Search, tags, backlinks
│ └── batch_tools.py # Bulk operations
├── utils/ # MCP-specific utilities
│ ├── init.py
│ ├── auth.py # User context handling
│ ├── serializers.py # Data formatting
│ └── validators.py # Input validation
├── management/ # Django commands
│ └── commands/
│ └── run_mcp_server.py
└── tests/ # MCP server tests
├── init.py
├── test_page_tools.py
├── test_block_tools.py
└── test_integration.py
Command Integration
The MCP server will directly use existing Commands with their updated signatures:
Command Signature Reference:
Updated command signatures (from system reminders)
CreateBlockCommand(user, page, content="", content_type="text", block_type="bullet",
order=0, parent=None, media_url="", media_metadata=None, properties=None)
CreatePageCommand(form, user)
UpdateBlockCommand(user, block_id, **updates)
ToggleBlockTodoCommand(user, block_id)
GetTagContentCommand(user, tag_name)
GetHistoricalDataCommand(form) # Still uses form
Required MCP Tools
- Page Management Tools
brainspread_create_page
-
Purpose: Create new pages or daily notes
-
Parameters:
- title (string): Page title
- page_type (enum): "page", "daily", "template"
- date (string, optional): For daily notes (YYYY-MM-DD)
- content (string, optional): Initial page content
-
Implementation:
@server.call_tool()
async def brainspread_create_page(title: str, page_type: str = "page", date: str =
None, content: str = "") -> dict:
try:
# Create form data
form_data = {"title": title, "content": content, "page_type": page_type}
if date:
form_data["date"] = date
form = CreatePageForm(form_data)
command = CreatePageCommand(form, current_user)
page = command.execute()
return {"success": True, "page_uuid": str(page.uuid), "slug": page.slug}
except Exception as e:
return {"success": False, "error": str(e)}
brainspread_get_page
- Purpose: Retrieve page with all nested blocks
- Parameters:
- page_slug (string, optional): Page slug
- page_uuid (string, optional): Page UUID
- date (string, optional): For daily notes
- Returns: Full page structure with nested blocks
- Implementation: Use PageRepository.get_by_slug() or daily note methods
brainspread_update_page
- Purpose: Update page metadata and content
- Parameters:
- page_uuid (string): Page identifier
- title (string, optional): New title
- content (string, optional): New content
- Implementation: Use UpdatePageCommand with form
brainspread_list_pages
- Purpose: List user's pages with filtering
- Parameters:
- page_type (enum, optional): Filter by type
- limit (integer, default 50): Max results
- search (string, optional): Search term
- Returns: List of pages with metadata
- Implementation: Use GetUserPagesCommand with form
- Block Management Tools
brainspread_create_block
-
Purpose: Create new blocks with hierarchy support
-
Parameters:
- page_uuid (string): Target page
- content (string): Block content
- parent_uuid (string, optional): Parent block for nesting
- block_type (enum, optional): "bullet", "todo", "done", "heading", "quote", "code",
"divider"
- content_type (enum, optional): "text", "markdown", "code", etc.
- order (integer, optional): Position within parent/page
-
Implementation:
@server.call_tool()
async def brainspread_create_block(page_uuid: str, content: str, parent_uuid: str =
None, block_type: str = "bullet", content_type: str = "text", order: int = 0) -> dict:
try:
# Get page object
page = Page.objects.get(uuid=page_uuid, user=current_user)
# Get parent if specified
parent = None
if parent_uuid:
parent = Block.objects.get(uuid=parent_uuid, user=current_user, page=page)
# Use command directly with new signature
command = CreateBlockCommand(
user=current_user,
page=page,
content=content,
content_type=content_type,
block_type=block_type,
order=order,
parent=parent
)
block = command.execute()
return {"success": True, "block_uuid": str(block.uuid)}
except Exception as e:
return {"success": False, "error": str(e)}
brainspread_update_block
- Purpose: Edit block content and properties
- Parameters:
- block_uuid (string): Block identifier
- content (string, optional): New content
- block_type (enum, optional): New block type
- properties (object, optional): Key-value properties
- Implementation:
@server.call_tool()
async def brainspread_update_block(block_uuid: str, **updates) -> dict:
try:
command = UpdateBlockCommand(current_user, block_uuid, **updates)
block = command.execute()
return {"success": True, "block_uuid": str(block.uuid)}
except Exception as e:
return {"success": False, "error": str(e)}
brainspread_move_block
- Purpose: Move blocks between pages or change hierarchy
- Parameters:
- block_uuid (string): Block to move
- target_page_uuid (string, optional): New page
- new_parent_uuid (string, optional): New parent (null for top-level)
- new_order (integer, optional): Position in new location
- Implementation: Use UpdateBlockCommand with page and parent_id updates
brainspread_delete_block
- Purpose: Delete blocks and their children
- Parameters:
- block_uuid (string): Block to delete
- Implementation: Use DeleteBlockCommand(user, block_id)
brainspread_toggle_todo
- Purpose: Toggle block between todo/done states
- Parameters:
- block_uuid (string): Block identifier
- Implementation:
@server.call_tool()
async def brainspread_toggle_todo(block_uuid: str) -> dict:
try:
command = ToggleBlockTodoCommand(current_user, block_uuid)
block = command.execute()
return {"success": True, "block_uuid": str(block.uuid), "new_type":
block.block_type}
except Exception as e:
return {"success": False, "error": str(e)}
- Content Organization Tools
brainspread_search_content
- Purpose: Search across pages and blocks
- Parameters:
- query (string): Search term
- content_type (enum, optional): Filter by content type
- tags (array, optional): Filter by tags
- date_range (object, optional): {start: "YYYY-MM-DD", end: "YYYY-MM-DD"}
- Returns: Matching pages and blocks with context
- Implementation: Use repository search methods and queryset filtering
brainspread_get_tag_content
- Purpose: Retrieve all content with specific tags
- Parameters:
- tag_name (string): Tag to search for
- limit (integer, default 50): Max results
- Returns: Tagged pages and blocks
- Implementation:
@server.call_tool()
async def brainspread_get_tag_content(tag_name: str, limit: int = 50) -> dict:
try:
command = GetTagContentCommand(current_user, tag_name)
result = command.execute()
return {"success": True, "data": result}
except Exception as e:
return {"success": False, "error": str(e)}
brainspread_get_backlinks
- Purpose: Find all references to a page or block
- Parameters:
- target_uuid (string): Page or block UUID
- type (enum): "page" or "block"
- Returns: List of linking blocks and pages
- Implementation: Use model get_backlinks() methods
- Historical and Date-based Tools
brainspread_get_daily_note
- Purpose: Get or create daily note for specific date
- Parameters:
- date (string): Date in YYYY-MM-DD format
- create_if_missing (boolean, default true): Auto-create if not exists
- Returns: Daily note page with blocks
- Implementation: Use PageRepository daily note methods
brainspread_get_historical_data
- Purpose: Retrieve content from specific time periods
- Parameters:
- days_back (integer, default 30): Days to look back
- limit (integer, default 50): Max results
- Returns: Historical pages and blocks
- Implementation:
@server.call_tool()
async def brainspread_get_historical_data(days_back: int = 30, limit: int = 50) ->
dict:
try:
form_data = {"user": current_user.pk, "days_back": days_back, "limit": limit}
form = GetHistoricalDataForm(form_data)
command = GetHistoricalDataCommand(form)
result = command.execute()
return {"success": True, "data": result}
except Exception as e:
return {"success": False, "error": str(e)}
brainspread_move_blocks_by_date
- Purpose: Move blocks from one date to another (key use case)
- Parameters:
- source_date (string): Source date (YYYY-MM-DD)
- target_date (string): Target date (YYYY-MM-DD)
- block_uuids (array): Specific blocks to move
- preserve_hierarchy (boolean, default true): Keep parent/child relationships
- Implementation: Complex operation using multiple UpdateBlockCommand calls
- Batch Operations Tools
brainspread_bulk_create_blocks
- Purpose: Create multiple blocks efficiently (for TODO lists)
- Parameters:
- page_uuid (string): Target page
- blocks (array): Array of block definitions with content, type, hierarchy
- Implementation: Multiple CreateBlockCommand calls in transaction
brainspread_bulk_move_blocks
- Purpose: Move multiple blocks while preserving relationships
- Parameters:
- operations (array): Array of move operations
- preserve_hierarchy (boolean, default true): Maintain parent/child links
- Implementation: Coordinated UpdateBlockCommand calls
Django App Implementation
- App Configuration (apps.py)
from django.apps import AppConfig
class McpServerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'mcp_server'
verbose_name = 'MCP Server'
- Main Server (server.py)
import asyncio
from mcp import Server
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp.types import TextContent, Tool
from django.contrib.auth import get_user_model
from django.core.management import setup_environ
from django.conf import settings
from .tools.page_tools import PageTools
from .tools.block_tools import BlockTools
from .tools.content_tools import ContentTools
from .tools.batch_tools import BatchTools
from .utils.auth import get_current_user
User = get_user_model()
class BrainspreadMCPServer:
def init(self):
self.server = Server("brainspread")
self.setup_tools()
def setup_tools(self):
# Register all tool categories
page_tools = PageTools(self.server)
block_tools = BlockTools(self.server)
content_tools = ContentTools(self.server)
batch_tools = BatchTools(self.server)
# Register tools with server
page_tools.register_tools()
block_tools.register_tools()
content_tools.register_tools()
batch_tools.register_tools()
async def run(self):
async with stdio_server() as (read_stream, write_stream):
await self.server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="brainspread",
server_version="1.0.0",
capabilities=self.server.get_capabilities(
notification_options=None,
experimental_capabilities=None,
),
),
)
- Management Command (management/commands/run_mcp_server.py)
from django.core.management.base import BaseCommand
from mcp_server.server import BrainspreadMCPServer
import asyncio
class Command(BaseCommand):
help = 'Run the Brainspread MCP server'
def handle(self, *args, **options):
server = BrainspreadMCPServer()
asyncio.run(server.run())
- User Authentication (utils/auth.py)
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
User = get_user_model()
For now, use a simple approach - in production you'd want proper auth
def get_current_user():
"""Get the current user for MCP operations"""
# This could be enhanced to support multiple users via session/token
try:
return User.objects.get(email="admin@email.com") # Default admin user
except ObjectDoesNotExist:
raise Exception("Default user not found. Please create admin user first.")
Future enhancement: support user switching
def set_current_user(user_id: str):
"""Switch to different user context"""
pass
Integration with Django Project
- Add to INSTALLED_APPS in settings.py:
INSTALLED_APPS = [
# ... existing apps
'mcp_server',
]
- Run MCP Server:
just django-admin run_mcp_server
- Connect from Claude Code:
Add MCP server configuration to connect to the running Django MCP server.
Key Use Cases to Support
- Daily TODO Management
AI: "Move all incomplete TODOs from yesterday to today"
Tools: brainspread_get_daily_note(yesterday) → brainspread_bulk_move_blocks →
brainspread_get_daily_note(today)
- Content Reorganization
AI: "Create a 'Project Planning' page and move all blocks tagged #project there"
Tools: brainspread_create_page → brainspread_get_tag_content →
brainspread_bulk_move_blocks
- Nested Structure Creation
AI: "Create a nested outline for the meeting notes with action items"
Tools: brainspread_bulk_create_blocks with hierarchical structure
Implementation Requirements
- Error Handling
- Validate UUIDs and object existence
- Handle circular reference prevention
- Provide clear error messages
- Handle concurrent modification conflicts
- Transaction Management
- Wrap complex operations in database transactions
- Ensure consistency for multi-block operations
- Rollback on validation failures
- Data Validation
- Validate block hierarchy (no circular references)
- Respect content type constraints
- Enforce user permissions and data isolation
- Performance Considerations
- Use select_related/prefetch_related for nested queries
- Implement pagination for large result sets
- Cache frequently accessed data
- Testing Strategy
- Unit tests for each MCP tool
- Integration tests for complex workflows
- Test user isolation and permissions
- Test concurrent access scenarios
This Django app approach provides seamless integration with existing Brainspread
infrastructure while enabling powerful agentic control through the MCP protocol.
⏺ MCP Server Implementation Prompt for Brainspread Django App
Overview
Build a Model Context Protocol (MCP) server as a Django app within the Brainspread
project. The server will enable full agentic control of the hierarchical block-based
knowledge management system, allowing AI chat to create, move, edit, and organize
blocks and pages programmatically while leveraging existing Django infrastructure.
Architecture Understanding
Brainspread uses:
Django App Structure
Create new Django app at packages/django-app/app/mcp_server/:
mcp_server/
├── init.py
├── apps.py
├── server.py # Main MCP server implementation
├── tools/ # MCP tool implementations
│ ├── init.py
│ ├── page_tools.py # Page management tools
│ ├── block_tools.py # Block manipulation tools
│ ├── content_tools.py # Search, tags, backlinks
│ └── batch_tools.py # Bulk operations
├── utils/ # MCP-specific utilities
│ ├── init.py
│ ├── auth.py # User context handling
│ ├── serializers.py # Data formatting
│ └── validators.py # Input validation
├── management/ # Django commands
│ └── commands/
│ └── run_mcp_server.py
└── tests/ # MCP server tests
├── init.py
├── test_page_tools.py
├── test_block_tools.py
└── test_integration.py
Command Integration
The MCP server will directly use existing Commands with their updated signatures:
Command Signature Reference:
Updated command signatures (from system reminders)
CreateBlockCommand(user, page, content="", content_type="text", block_type="bullet",
order=0, parent=None, media_url="", media_metadata=None, properties=None)
CreatePageCommand(form, user)
UpdateBlockCommand(user, block_id, **updates)
ToggleBlockTodoCommand(user, block_id)
GetTagContentCommand(user, tag_name)
GetHistoricalDataCommand(form) # Still uses form
Required MCP Tools
brainspread_create_page
Purpose: Create new pages or daily notes
Parameters:
Implementation:
@server.call_tool()
async def brainspread_create_page(title: str, page_type: str = "page", date: str =
None, content: str = "") -> dict:
try:
# Create form data
form_data = {"title": title, "content": content, "page_type": page_type}
if date:
form_data["date"] = date
except Exception as e:
return {"success": False, "error": str(e)}
brainspread_get_page
brainspread_update_page
brainspread_list_pages
brainspread_create_block
Purpose: Create new blocks with hierarchy support
Parameters:
"divider"
Implementation:
@server.call_tool()
async def brainspread_create_block(page_uuid: str, content: str, parent_uuid: str =
None, block_type: str = "bullet", content_type: str = "text", order: int = 0) -> dict:
try:
# Get page object
page = Page.objects.get(uuid=page_uuid, user=current_user)
except Exception as e:
return {"success": False, "error": str(e)}
brainspread_update_block
@server.call_tool()
async def brainspread_update_block(block_uuid: str, **updates) -> dict:
try:
command = UpdateBlockCommand(current_user, block_uuid, **updates)
block = command.execute()
return {"success": True, "block_uuid": str(block.uuid)}
except Exception as e:
return {"success": False, "error": str(e)}
brainspread_move_block
brainspread_delete_block
brainspread_toggle_todo
@server.call_tool()
async def brainspread_toggle_todo(block_uuid: str) -> dict:
try:
command = ToggleBlockTodoCommand(current_user, block_uuid)
block = command.execute()
return {"success": True, "block_uuid": str(block.uuid), "new_type":
block.block_type}
except Exception as e:
return {"success": False, "error": str(e)}
brainspread_search_content
brainspread_get_tag_content
@server.call_tool()
async def brainspread_get_tag_content(tag_name: str, limit: int = 50) -> dict:
try:
command = GetTagContentCommand(current_user, tag_name)
result = command.execute()
return {"success": True, "data": result}
except Exception as e:
return {"success": False, "error": str(e)}
brainspread_get_backlinks
brainspread_get_daily_note
brainspread_get_historical_data
@server.call_tool()
async def brainspread_get_historical_data(days_back: int = 30, limit: int = 50) ->
dict:
try:
form_data = {"user": current_user.pk, "days_back": days_back, "limit": limit}
form = GetHistoricalDataForm(form_data)
command = GetHistoricalDataCommand(form)
result = command.execute()
return {"success": True, "data": result}
except Exception as e:
return {"success": False, "error": str(e)}
brainspread_move_blocks_by_date
brainspread_bulk_create_blocks
brainspread_bulk_move_blocks
Django App Implementation
from django.apps import AppConfig
class McpServerConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'mcp_server'
verbose_name = 'MCP Server'
import asyncio
from mcp import Server
from mcp.server.models import InitializationOptions
from mcp.server.stdio import stdio_server
from mcp.types import TextContent, Tool
from django.contrib.auth import get_user_model
from django.core.management import setup_environ
from django.conf import settings
from .tools.page_tools import PageTools
from .tools.block_tools import BlockTools
from .tools.content_tools import ContentTools
from .tools.batch_tools import BatchTools
from .utils.auth import get_current_user
User = get_user_model()
class BrainspreadMCPServer:
def init(self):
self.server = Server("brainspread")
self.setup_tools()
from django.core.management.base import BaseCommand
from mcp_server.server import BrainspreadMCPServer
import asyncio
class Command(BaseCommand):
help = 'Run the Brainspread MCP server'
from django.contrib.auth import get_user_model
from django.core.exceptions import ObjectDoesNotExist
User = get_user_model()
For now, use a simple approach - in production you'd want proper auth
def get_current_user():
"""Get the current user for MCP operations"""
# This could be enhanced to support multiple users via session/token
try:
return User.objects.get(email="admin@email.com") # Default admin user
except ObjectDoesNotExist:
raise Exception("Default user not found. Please create admin user first.")
Future enhancement: support user switching
def set_current_user(user_id: str):
"""Switch to different user context"""
pass
Integration with Django Project
INSTALLED_APPS = [
# ... existing apps
'mcp_server',
]
just django-admin run_mcp_server
Add MCP server configuration to connect to the running Django MCP server.
Key Use Cases to Support
AI: "Move all incomplete TODOs from yesterday to today"
Tools: brainspread_get_daily_note(yesterday) → brainspread_bulk_move_blocks →
brainspread_get_daily_note(today)
AI: "Create a 'Project Planning' page and move all blocks tagged #project there"
Tools: brainspread_create_page → brainspread_get_tag_content →
brainspread_bulk_move_blocks
AI: "Create a nested outline for the meeting notes with action items"
Tools: brainspread_bulk_create_blocks with hierarchical structure
Implementation Requirements
This Django app approach provides seamless integration with existing Brainspread
infrastructure while enabling powerful agentic control through the MCP protocol.