AFFiNE MCP Server Integration
Skill by ara.so — MCP Skills collection.
Overview
AFFiNE MCP Server is a Model Context Protocol server that exposes AFFiNE workspaces, documents, databases, and collaboration features to AI assistants. It supports both AFFiNE Cloud and self-hosted deployments, offering 85+ tools across workspace management, document operations, database manipulation, and organizational features.
Key capabilities:
- Connect AI assistants (Claude Code, Cursor, Codex CLI, Claude Desktop) to AFFiNE
- Read, create, update, and delete documents programmatically
- Manage databases, collections, and organizational structures
- Access via stdio (local) or HTTP (remote) transports
- Semantic page composition and template instantiation
- Block-level document mutation and structured data handling
Installation
Global CLI Installation
# Install globally via npm
npm i -g affine-mcp-server
# Verify installation
affine-mcp --version
Ad-hoc Execution
# Run without installing
npx -y -p affine-mcp-server affine-mcp -- --version
Docker Deployment
# Pull the official image
docker pull ghcr.io/dawncr0w/affine-mcp-server:latest
# Run with environment variables
docker run -d \
-p 3000:3000 \
-e MCP_TRANSPORT=http \
-e AFFINE_BASE_URL=https://app.affine.pro \
-e AFFINE_API_TOKEN=${AFFINE_API_TOKEN} \
-e AFFINE_MCP_AUTH_MODE=bearer \
-e AFFINE_MCP_HTTP_TOKEN=${MCP_HTTP_TOKEN} \
ghcr.io/dawncr0w/affine-mcp-server:latest
Authentication Setup
Interactive Login (Recommended for Local Use)
# Store credentials securely (~/.config/affine-mcp/config with mode 600)
affine-mcp login
# Interactive prompts will ask for:
# - AFFiNE base URL (default: https://app.affine.pro)
# - Authentication method (token, cookie, or email/password)
# - Credentials
Environment Variables
# For AFFiNE Cloud (token required)
export AFFINE_BASE_URL=https://app.affine.pro
export AFFINE_API_TOKEN=ut_your_token_here
# For self-hosted with email/password
export AFFINE_BASE_URL=https://your-affine-instance.com
export AFFINE_EMAIL=user@example.com
export AFFINE_PASSWORD=${AFFINE_PASSWORD}
# For self-hosted with cookie
export AFFINE_BASE_URL=https://your-affine-instance.com
export AFFINE_COOKIE=${AFFINE_COOKIE}
Getting an API Token
AFFiNE Cloud:
- Sign in to https://app.affine.pro
- Go to Settings → Integrations → MCP Server
- Generate an API token
Self-hosted:
- Sign in to your AFFiNE instance
- Navigate to Settings → Account → Personal Access Tokens
- Create a new token with appropriate scopes
Client Configuration
Claude Code
Add to your .claude/project_config.json:
{
"mcpServers": {
"affine": {
"command": "affine-mcp"
}
}
}
Or with explicit environment variables:
{
"mcpServers": {
"affine": {
"command": "affine-mcp",
"env": {
"AFFINE_BASE_URL": "https://app.affine.pro",
"AFFINE_API_TOKEN": "${AFFINE_API_TOKEN}"
}
}
}
}
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):
{
"mcpServers": {
"affine": {
"command": "affine-mcp"
}
}
}
Cursor
Add to ~/.cursor/mcp_config.json:
{
"mcpServers": {
"affine": {
"command": "affine-mcp"
}
}
}
Codex CLI
# Add the server
codex mcp add affine -- affine-mcp
# Verify
codex mcp list
HTTP Mode (Remote Deployment)
Client configuration:
{
"mcpServers": {
"affine": {
"type": "http",
"url": "https://your-mcp-server.com/mcp",
"headers": {
"Authorization": "Bearer ${MCP_HTTP_TOKEN}"
}
}
}
}
Server environment:
export MCP_TRANSPORT=http
export MCP_HTTP_PORT=3000
export AFFINE_MCP_AUTH_MODE=bearer
export AFFINE_MCP_HTTP_TOKEN=${MCP_HTTP_TOKEN}
CLI Commands
Configuration Management
# Show current configuration (secrets redacted)
affine-mcp show-config
# Get config file path
affine-mcp config-path
# Generate client configuration snippets
affine-mcp snippet claude
affine-mcp snippet cursor
affine-mcp snippet codex
affine-mcp snippet all
# Generate with environment variables
affine-mcp snippet claude --env
# Logout (remove stored credentials)
affine-mcp logout
Health Checks
# Test effective configuration
affine-mcp status
# Machine-readable status
affine-mcp status --json
# Diagnose configuration and connectivity
affine-mcp doctor
Running the Server
# Start stdio server (default)
affine-mcp
# Start HTTP server
MCP_TRANSPORT=http MCP_HTTP_PORT=3000 affine-mcp
# With custom log level
LOG_LEVEL=debug affine-mcp
Tool Surface Overview
The server exposes 85 tools organized by domain:
Workspace Tools
// List all workspaces
list_workspaces()
// Get workspace details
get_workspace({ workspaceId: "workspace-id" })
// Create workspace
create_workspace({
name: "My New Workspace",
description: "Project workspace"
})
// Update workspace
update_workspace({
workspaceId: "workspace-id",
name: "Updated Name"
})
// Delete workspace
delete_workspace({ workspaceId: "workspace-id" })
Document Tools
// Search documents
search_docs({
workspaceId: "workspace-id",
query: "meeting notes",
limit: 10
})
// Get document by exact title
get_doc_by_title({
workspaceId: "workspace-id",
title: "Project Roadmap"
})
// Read document content
read_doc({
workspaceId: "workspace-id",
docId: "doc-id"
})
// Create document
create_doc({
workspaceId: "workspace-id",
title: "New Document",
text: "Initial content",
folderId: "folder-id" // Optional, new in v2.1.0
})
// Update document
update_doc({
workspaceId: "workspace-id",
docId: "doc-id",
text: "Updated content"
})
// Delete document
delete_doc({
workspaceId: "workspace-id",
docId: "doc-id"
})
// Move document to trash
trash_doc({
workspaceId: "workspace-id",
docId: "doc-id"
})
// Restore from trash
restore_doc({
workspaceId: "workspace-id",
docId: "doc-id"
})
Template Tools
// List available templates
list_templates({ workspaceId: "workspace-id" })
// Inspect template structure
inspect_template({
workspaceId: "workspace-id",
templateId: "template-id"
})
// Create document from template
create_doc_from_template({
workspaceId: "workspace-id",
templateId: "template-id",
title: "Q1 Report",
folderId: "folder-id" // Optional
})
Database Tools
// Create database
create_database({
workspaceId: "workspace-id",
docId: "doc-id",
blockId: "block-id", // Optional
name: "Project Tracker"
})
// Add database column
add_database_column({
workspaceId: "workspace-id",
docId: "doc-id",
databaseId: "database-id",
name: "Status",
type: "select",
options: ["Todo", "In Progress", "Done"]
})
// Add database row
add_database_row({
workspaceId: "workspace-id",
docId: "doc-id",
databaseId: "database-id",
values: {
"Task": "Implement feature",
"Status": "In Progress"
}
})
// Update database row
update_database_row({
workspaceId: "workspace-id",
docId: "doc-id",
databaseId: "database-id",
rowId: "row-id",
values: {
"Status": "Done"
}
})
// Inspect database schema
inspect_database_schema({
workspaceId: "workspace-id",
docId: "doc-id",
databaseId: "database-id"
})
Collection Tools
// List collections
list_collections({ workspaceId: "workspace-id" })
// Create collection
create_collection({
workspaceId: "workspace-id",
name: "Project Docs"
})
// Add document to collection
add_doc_to_collection({
workspaceId: "workspace-id",
collectionId: "collection-id",
docId: "doc-id"
})
// Remove document from collection
remove_doc_from_collection({
workspaceId: "workspace-id",
collectionId: "collection-id",
docId: "doc-id"
})
Comment Tools
// List comments on document
list_comments({
workspaceId: "workspace-id",
docId: "doc-id"
})
// Create comment
create_comment({
workspaceId: "workspace-id",
docId: "doc-id",
text: "Great point!",
quote: "original text" // Optional
})
// Update comment
update_comment({
workspaceId: "workspace-id",
docId: "doc-id",
commentId: "comment-id",
text: "Updated comment"
})
// Delete comment
delete_comment({
workspaceId: "workspace-id",
docId: "doc-id",
commentId: "comment-id"
})
Common Workflow Patterns
Creating a Project Structure
// 1. Create workspace
const workspace = await create_workspace({
name: "Q1 Project",
description: "Project tracking workspace"
});
// 2. Create main document
const mainDoc = await create_doc({
workspaceId: workspace.id,
title: "Project Overview",
text: "# Project Overview\n\nKey objectives..."
});
// 3. Create database for task tracking
const database = await create_database({
workspaceId: workspace.id,
docId: mainDoc.id,
name: "Task Tracker"
});
// 4. Add columns
await add_database_column({
workspaceId: workspace.id,
docId: mainDoc.id,
databaseId: database.id,
name: "Priority",
type: "select",
options: ["High", "Medium", "Low"]
});
// 5. Add initial tasks
await add_database_row({
workspaceId: workspace.id,
docId: mainDoc.id,
databaseId: database.id,
values: {
"Task": "Define requirements",
"Priority": "High"
}
});
Document Search and Update
// 1. Search for documents
const results = await search_docs({
workspaceId: "workspace-id",
query: "meeting notes",
limit: 5
});
// 2. Find specific document by exact title
const doc = await get_doc_by_title({
workspaceId: "workspace-id",
title: "Weekly Standup - 2024-01-15"
});
// 3. Read current content
const content = await read_doc({
workspaceId: "workspace-id",
docId: doc.id
});
// 4. Append new content
const updatedText = content.text + "\n\n## Action Items\n- Follow up on design";
await update_doc({
workspaceId: "workspace-id",
docId: doc.id,
text: updatedText
});
Using Templates
// 1. List available templates
const templates = await list_templates({
workspaceId: "workspace-id"
});
// 2. Inspect template to understand structure
const template = await inspect_template({
workspaceId: "workspace-id",
templateId: templates[0].id
});
// 3. Create document from template with placement
const newDoc = await create_doc_from_template({
workspaceId: "workspace-id",
templateId: templates[0].id,
title: "Q1 Planning Doc",
folderId: "planning-folder-id"
});
Semantic Page Composition
// Compose a structured document with intent
const pageIntent = {
pageTitle: "Product Launch Plan",
sections: [
{
heading: "Executive Summary",
content: "Overview of product launch strategy..."
},
{
heading: "Timeline",
content: "Key milestones and dates",
database: {
name: "Launch Milestones",
columns: [
{ name: "Milestone", type: "text" },
{ name: "Date", type: "date" },
{ name: "Owner", type: "text" }
]
}
}
]
};
// Use semantic composition tool
const result = await compose_semantic_page({
workspaceId: "workspace-id",
intent: JSON.stringify(pageIntent)
});
Database Intent Composition
// Create complex database structure from high-level intent
const dbIntent = {
name: "Bug Tracker",
columns: [
{ name: "Title", type: "text" },
{ name: "Severity", type: "select", options: ["Critical", "High", "Medium", "Low"] },
{ name: "Status", type: "select", options: ["Open", "In Progress", "Fixed", "Closed"] },
{ name: "Assigned To", type: "text" },
{ name: "Due Date", type: "date" }
],
initialRows: [
{
"Title": "Login page crash",
"Severity": "Critical",
"Status": "Open"
}
]
};
const result = await compose_database_from_intent({
workspaceId: "workspace-id",
docId: "doc-id",
intent: JSON.stringify(dbIntent)
});
Security Configuration
Tool Profile Restriction
# Limit to read-only operations
export AFFINE_TOOL_PROFILE=read_only
# Core tools only (no experimental features)
export AFFINE_TOOL_PROFILE=core
# Authoring tools (read + write, no admin)
export AFFINE_TOOL_PROFILE=authoring
# Full access (default)
export AFFINE_TOOL_PROFILE=full
Fine-Grained Tool Control
# Disable entire tool groups
export AFFINE_DISABLED_GROUPS=destructive,admin,docs.database
# Disable specific tools
export AFFINE_DISABLED_TOOLS=delete_workspace,delete_doc,trash_doc
HTTP Server Security
# Enable bearer token authentication
export AFFINE_MCP_AUTH_MODE=bearer
export AFFINE_MCP_HTTP_TOKEN=${MCP_HTTP_TOKEN}
# Use HTTPS in production
export MCP_HTTP_PORT=3000
# Then reverse proxy with nginx/caddy for TLS
Docker Deployment
Basic Docker Run
docker run -d \
--name affine-mcp \
-p 3000:3000 \
-e MCP_TRANSPORT=http \
-e AFFINE_BASE_URL=${AFFINE_BASE_URL} \
-e AFFINE_API_TOKEN=${AFFINE_API_TOKEN} \
-e AFFINE_MCP_AUTH_MODE=bearer \
-e AFFINE_MCP_HTTP_TOKEN=${MCP_HTTP_TOKEN} \
ghcr.io/dawncr0w/affine-mcp-server:latest
Docker Compose
version: '3.8'
services:
affine-mcp:
image: ghcr.io/dawncr0w/affine-mcp-server:latest
ports:
- "3000:3000"
environment:
MCP_TRANSPORT: http
MCP_HTTP_PORT: 3000
AFFINE_BASE_URL: ${AFFINE_BASE_URL}
AFFINE_API_TOKEN: ${AFFINE_API_TOKEN}
AFFINE_MCP_AUTH_MODE: bearer
AFFINE_MCP_HTTP_TOKEN: ${MCP_HTTP_TOKEN}
AFFINE_TOOL_PROFILE: authoring
LOG_LEVEL: info
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3000/healthz"]
interval: 30s
timeout: 10s
retries: 3
restart: unless-stopped
Health Endpoints
# Liveness check
curl http://localhost:3000/healthz
# Readiness check
curl http://localhost:3000/readyz
Environment Variables Reference
Required
| Variable | Description | Example |
|---|---|---|
AFFINE_BASE_URL |
AFFiNE instance URL | https://app.affine.pro |
AFFINE_API_TOKEN |
API token (recommended) | ut_... |
Authentication (Choose One)
| Variable | Description |
|---|---|
AFFINE_API_TOKEN |
API token (recommended) |
AFFINE_EMAIL + AFFINE_PASSWORD |
Email/password (self-hosted only) |
AFFINE_COOKIE |
Session cookie |
Transport
| Variable | Default | Description |
|---|---|---|
MCP_TRANSPORT |
stdio |
stdio or http |
MCP_HTTP_PORT |
3000 |
HTTP server port |
Security
| Variable | Default | Description |
|---|---|---|
AFFINE_MCP_AUTH_MODE |
none |
bearer, oauth, or none |
AFFINE_MCP_HTTP_TOKEN |
- | Bearer token for HTTP auth |
AFFINE_TOOL_PROFILE |
full |
read_only, core, authoring, full |
AFFINE_DISABLED_GROUPS |
- | Comma-separated groups to disable |
AFFINE_DISABLED_TOOLS |
- | Comma-separated tools to disable |
Logging
| Variable | Default | Description |
|---|---|---|
LOG_LEVEL |
info |
debug, info, warn, error |
Troubleshooting
Connection Issues
# Diagnose configuration
affine-mcp doctor
# Check status with verbose output
affine-mcp status --json
# Verify config file exists and is readable
affine-mcp config-path
ls -la $(affine-mcp config-path)
Common Errors
"Authentication failed"
- Verify API token is correct and not expired
- For AFFiNE Cloud, use API token (not email/password)
- Check token has appropriate scopes
"Workspace not found"
- Ensure workspace exists and is server-backed (not browser-local)
- Verify user has access to workspace
- Check workspace ID is correct
"Tool not found"
- Check
AFFINE_TOOL_PROFILEandAFFINE_DISABLED_GROUPSsettings - Verify tool name matches manifest exactly
- Update to latest version:
npm i -g affine-mcp-server@latest
"Connection refused" (HTTP mode)
- Verify server is running:
docker psor check process - Check firewall rules allow port access
- Verify
MCP_HTTP_PORTmatches client configuration
Debug Mode
# Enable debug logging
LOG_LEVEL=debug affine-mcp
# Or with environment variable
export LOG_LEVEL=debug
affine-mcp
Verify Tool Availability
# List all available tools
affine-mcp status --json | jq '.tools'
# Check specific tool
affine-mcp status --json | jq '.tools[] | select(.name == "create_doc")'
Advanced Patterns
Capability and Fidelity Reporting
// Check document capabilities before operations
const capabilities = await report_doc_capabilities({
workspaceId: "workspace-id",
docId: "doc-id"
});
// Returns: { canEdit, canComment, canShare, exportFormats, ... }
// Check content fidelity (what blocks/features are supported)
const fidelity = await report_content_fidelity({
workspaceId: "workspace-id",
docId: "doc-id"
});
Block-Level Mutations
// Append block to document
await append_block({
workspaceId: "workspace-id",
docId: "doc-id",
blockType: "paragraph",
content: "New paragraph text"
});
// Insert block at specific position
await insert_block({
workspaceId: "workspace-id",
docId: "doc-id",
afterBlockId: "block-id",
blockType: "heading",
content: "New Section",
level: 2
});
Workspace Blueprints
// Create workspace from blueprint
const blueprint = {
name: "Team Sprint",
folders: ["Planning", "Design", "Development"],
templates: ["standup", "retrospective"],
databases: ["task-tracker"]
};
const workspace = await create_workspace_from_blueprint({
blueprint: JSON.stringify(blueprint)
});
Collection Rules and Sync
// Create collection with rules
await create_collection({
workspaceId: "workspace-id",
name: "Meeting Notes",
rules: {
tagFilter: ["meeting"],
autoSync: true
}
});
// Sync collection (apply rules)
await sync_collection_rules({
workspaceId: "workspace-id",
collectionId: "collection-id"
});
Best Practices
- Use API tokens - More secure and reliable than email/password
- Enable health checks - Use
/healthzand/readyzfor containerized deployments - Restrict tool profiles - Use
AFFINE_TOOL_PROFILE=authoringin production to prevent accidental deletions - Rotate tokens regularly - Generate new API tokens periodically
- Use HTTPS - Always use TLS for remote deployments
- Handle errors gracefully - Check tool responses for error fields
- Batch operations - Use semantic composition and database intent tools for complex structures
- Verify before destructive ops - Use read tools to confirm before delete/trash operations
Resources
- Documentation:
/docsdirectory in repositorygetting-started.md- First-run setupclient-setup.md- Client-specific configurationconfiguration-and-deployment.md- Advanced deploymenttool-reference.md- Complete tool catalogworkflow-recipes.md- End-to-end examplesedgeless-canvas-cookbook.md- Canvas layout helpers
- Tool Manifest:
tool-manifest.json- Canonical tool definitions - GitHub: https://github.com/dawncr0w/affine-mcp-server
- Issues: https://github.com/dawncr0w/affine-mcp-server/issues
- AFFiNE Docs: https://docs.affine.pro