| name | affine-mcp-server-integration |
| description | Connect AI assistants to AFFiNE workspaces, documents, databases, and collaboration APIs using the Model Context Protocol. |
| triggers | ["connect to my AFFiNE workspace","set up AFFiNE MCP server","configure MCP for AFFiNE Cloud","create and manage AFFiNE documents with AI","integrate Claude with AFFiNE","access my AFFiNE knowledge base","use AFFiNE database with MCP","deploy AFFiNE MCP server"] |
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
npm i -g affine-mcp-server
affine-mcp --version
Ad-hoc Execution
npx -y -p affine-mcp-server affine-mcp -- --version
Docker Deployment
docker pull ghcr.io/dawncr0w/affine-mcp-server:latest
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)
affine-mcp login
Environment Variables
export AFFINE_BASE_URL=https://app.affine.pro
export AFFINE_API_TOKEN=ut_your_token_here
export AFFINE_BASE_URL=https://your-affine-instance.com
export AFFINE_EMAIL=user@example.com
export AFFINE_PASSWORD=${AFFINE_PASSWORD}
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
codex mcp add affine -- affine-mcp
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
affine-mcp show-config
affine-mcp config-path
affine-mcp snippet claude
affine-mcp snippet cursor
affine-mcp snippet codex
affine-mcp snippet all
affine-mcp snippet claude --env
affine-mcp logout
Health Checks
affine-mcp status
affine-mcp status --json
affine-mcp doctor
Running the Server
affine-mcp
MCP_TRANSPORT=http MCP_HTTP_PORT=3000 affine-mcp
LOG_LEVEL=debug affine-mcp
Tool Surface Overview
The server exposes 85 tools organized by domain:
Workspace Tools
list_workspaces()
get_workspace({ workspaceId: "workspace-id" })
create_workspace({
name: "My New Workspace",
description: "Project workspace"
})
update_workspace({
workspaceId: "workspace-id",
name: "Updated Name"
})
delete_workspace({ workspaceId: "workspace-id" })
Document Tools
search_docs({
workspaceId: "workspace-id",
query: "meeting notes",
limit: 10
})
get_doc_by_title({
workspaceId: "workspace-id",
title: "Project Roadmap"
})
read_doc({
workspaceId: "workspace-id",
docId: "doc-id"
})
create_doc({
workspaceId: "workspace-id",
title: "New Document",
text: "Initial content",
folderId: "folder-id"
})
update_doc({
workspaceId: "workspace-id",
docId: "doc-id",
text: "Updated content"
})
delete_doc({
workspaceId: "workspace-id",
docId: "doc-id"
})
trash_doc({
workspaceId: "workspace-id",
docId: "doc-id"
})
restore_doc({
workspaceId: "workspace-id",
docId: "doc-id"
})
Template Tools
list_templates({ workspaceId: "workspace-id" })
inspect_template({
workspaceId: "workspace-id",
templateId: "template-id"
})
create_doc_from_template({
workspaceId: "workspace-id",
templateId: "template-id",
title: "Q1 Report",
folderId: "folder-id"
})
Database Tools
create_database({
workspaceId: "workspace-id",
docId: "doc-id",
blockId: "block-id",
name: "Project Tracker"
})
add_database_column({
workspaceId: "workspace-id",
docId: "doc-id",
databaseId: "database-id",
name: "Status",
type: "select",
options: ["Todo", "In Progress", "Done"]
})
add_database_row({
workspaceId: "workspace-id",
docId: "doc-id",
databaseId: "database-id",
values: {
"Task": "Implement feature",
"Status": "In Progress"
}
})
update_database_row({
workspaceId: "workspace-id",
docId: "doc-id",
databaseId: "database-id",
rowId: "row-id",
values: {
"Status": "Done"
}
})
inspect_database_schema({
workspaceId: "workspace-id",
docId: "doc-id",
databaseId: "database-id"
})
Collection Tools
list_collections({ workspaceId: "workspace-id" })
create_collection({
workspaceId: "workspace-id",
name: "Project Docs"
})
add_doc_to_collection({
workspaceId: "workspace-id",
collectionId: "collection-id",
docId: "doc-id"
})
remove_doc_from_collection({
workspaceId: "workspace-id",
collectionId: "collection-id",
docId: "doc-id"
})
Comment Tools
list_comments({
workspaceId: "workspace-id",
docId: "doc-id"
})
create_comment({
workspaceId: "workspace-id",
docId: "doc-id",
text: "Great point!",
quote: "original text"
})
update_comment({
workspaceId: "workspace-id",
docId: "doc-id",
commentId: "comment-id",
text: "Updated comment"
})
delete_comment({
workspaceId: "workspace-id",
docId: "doc-id",
commentId: "comment-id"
})
Common Workflow Patterns
Creating a Project Structure
const workspace = await create_workspace({
name: "Q1 Project",
description: "Project tracking workspace"
});
const mainDoc = await create_doc({
workspaceId: workspace.id,
title: "Project Overview",
text: "# Project Overview\n\nKey objectives..."
});
const database = await create_database({
workspaceId: workspace.id,
docId: mainDoc.id,
name: "Task Tracker"
});
await add_database_column({
workspaceId: workspace.id,
docId: mainDoc.id,
databaseId: database.id,
name: "Priority",
type: "select",
options: ["High", "Medium", "Low"]
});
await add_database_row({
workspaceId: workspace.id,
docId: mainDoc.id,
databaseId: database.id,
values: {
"Task": "Define requirements",
"Priority": "High"
}
});
Document Search and Update
const results = await search_docs({
workspaceId: "workspace-id",
query: "meeting notes",
limit: 5
});
const doc = await get_doc_by_title({
workspaceId: "workspace-id",
title: "Weekly Standup - 2024-01-15"
});
const content = await read_doc({
workspaceId: "workspace-id",
docId: doc.id
});
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
const templates = await list_templates({
workspaceId: "workspace-id"
});
const template = await inspect_template({
workspaceId: "workspace-id",
templateId: templates[0].id
});
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
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" }
]
}
}
]
};
const result = await compose_semantic_page({
workspaceId: "workspace-id",
intent: JSON.stringify(pageIntent)
});
Database Intent Composition
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
export AFFINE_TOOL_PROFILE=read_only
export AFFINE_TOOL_PROFILE=core
export AFFINE_TOOL_PROFILE=authoring
export AFFINE_TOOL_PROFILE=full
Fine-Grained Tool Control
export AFFINE_DISABLED_GROUPS=destructive,admin,docs.database
export AFFINE_DISABLED_TOOLS=delete_workspace,delete_doc,trash_doc
HTTP Server Security
export AFFINE_MCP_AUTH_MODE=bearer
export AFFINE_MCP_HTTP_TOKEN=${MCP_HTTP_TOKEN}
export MCP_HTTP_PORT=3000
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
curl http://localhost:3000/healthz
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
affine-mcp doctor
affine-mcp status --json
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_PROFILE and AFFINE_DISABLED_GROUPS settings
- 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 ps or check process
- Check firewall rules allow port access
- Verify
MCP_HTTP_PORT matches client configuration
Debug Mode
LOG_LEVEL=debug affine-mcp
export LOG_LEVEL=debug
affine-mcp
Verify Tool Availability
affine-mcp status --json | jq '.tools'
affine-mcp status --json | jq '.tools[] | select(.name == "create_doc")'
Advanced Patterns
Capability and Fidelity Reporting
const capabilities = await report_doc_capabilities({
workspaceId: "workspace-id",
docId: "doc-id"
});
const fidelity = await report_content_fidelity({
workspaceId: "workspace-id",
docId: "doc-id"
});
Block-Level Mutations
await append_block({
workspaceId: "workspace-id",
docId: "doc-id",
blockType: "paragraph",
content: "New paragraph text"
});
await insert_block({
workspaceId: "workspace-id",
docId: "doc-id",
afterBlockId: "block-id",
blockType: "heading",
content: "New Section",
level: 2
});
Workspace Blueprints
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
await create_collection({
workspaceId: "workspace-id",
name: "Meeting Notes",
rules: {
tagFilter: ["meeting"],
autoSync: true
}
});
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
/healthz and /readyz for containerized deployments
- Restrict tool profiles - Use
AFFINE_TOOL_PROFILE=authoring in 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