BD-ClickUp Integration Architecture¶
Overview¶
This project provides bidirectional sync between local bd (beads) issues and ClickUp tasks, exposed as MCP tools for AI-assisted development in Cursor IDE.
┌─────────────────────────────────────────────────────────────────────────┐
│ CURSOR IDE / CLAUDE CODE │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ AI Assistant (Claude) │ │
│ │ "Create a task for fixing the login bug" │ │
│ └──────────────────────────┬────────────────────────────────────────┘ │
│ │ MCP Protocol │
│ v │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ BeadsMCPServer (beads_clickup/mcp_server.py) │ │
│ │ Tools: bd_create, bd_update, bd_close, bd_list, bd_show, │ │
│ │ bd_search, bd_sync, bd_assign_subtask, bd_lifecycle_graph,│ │
│ │ bd_switch_workspace, bd_clickup_setup │ │
│ └──────────────────────────┬────────────────────────────────────────┘ │
└─────────────────────────────┼────────────────────────────────────────── ┘
│
v
┌─────────────────────────────────────────────────────────────────────────┐
│ LOCAL MACHINE │
│ │
│ ┌──────────────┐ ┌──────────────────┐ ┌────────────────────────┐ │
│ │ bd CLI │ │ SyncEngine │ │ .beads/ │ │
│ │ (wrapper │───>│ (sync_engine.py)│───>│ ├── issues.jsonl │ │
│ │ script) │ │ │ │ └── integrations/ │ │
│ └──────────────┘ │ FieldMapper │ │ └── clickup/ │ │
│ │ CustomFieldMapper│ │ ├── config │ │
│ ┌──────────────┐ │ FieldInference │ │ ├── sync_ │ │
│ │ bd-clickup │ │ CommentSync │ │ │ state │ │
│ │ CLI │ └────────┬─────────┘ │ └── caches │ │
│ │ (cli.py) │ │ └────────────────────────┘ │
│ └──────────────┘ │ REST API (HTTPS) │
│ v │
└───────────────────────────────┼─────────────────────────────────────────┘
│
v
┌─────────────────────────────────────────────────────────────────────────┐
│ CLICKUP CLOUD │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ ClickUp REST API v2 (api.clickup.com) │ │
│ │ Tasks, Custom Fields, Comments, Spaces, Lists │ │
│ └───────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
Data Flow¶
| Direction | Trigger | Path |
|---|---|---|
| Beads -> ClickUp | bd_create, bd_update, bd_close via MCP or CLI |
MCP Server -> bd CLI -> SyncEngine -> FieldMapper -> ClickUpClient -> REST API |
| ClickUp -> Beads | bd_sync |
SyncEngine fetches ClickUp tasks via REST API -> FieldMapper -> updates issues.jsonl |
| Full Sync | bd_sync (full=true) |
SyncEngine reads all beads issues + all ClickUp tasks, reconciles both directions |
| Force Import | bd_sync (force_import=true) |
Imports ClickUp tasks that weren't created by beads (skips beads-automation tag filter) |
Project Structure¶
bd-clickup-integration/
├── beads_clickup/ # Main Python package
│ ├── __init__.py # Public API exports, version
│ ├── __main__.py # Entry: python -m beads_clickup -> MCP server
│ ├── mcp_server.py # MCP server (JSON-RPC over stdio)
│ ├── cli.py # CLI commands (bd-clickup)
│ ├── sync_engine.py # Core bidirectional sync logic
│ ├── clickup_client.py # ClickUp REST API v2 client
│ ├── field_mapper.py # Beads <-> ClickUp field mapping
│ ├── field_inference.py # Infer custom field values from content
│ ├── provisioning.py # ClickUp list/status/field provisioning
│ ├── comment_sync.py # Bidirectional comment sync
│ ├── lifecycle_graph.py # Task graph visualization (vis-network)
│ ├── lifecycle_stages.py # Stage definitions and colors
│ ├── stage_classifier.py # LLM-based lifecycle stage classification
│ ├── neighbor_classifier.py # LLM-based task neighbor inference
│ ├── issue_template.py # Issue description templates and validation
│ ├── models.py # Data models (Issue, ClickUpTask, SyncState, etc.)
│ ├── constants.py # Enums (IssueStatus, Priority, IssueType, etc.)
│ ├── exceptions.py # Exception hierarchy
│ ├── env_detection.py # Python environment detection
│ ├── mcp_client.py # Deprecated alias for clickup_client
│ └── py.typed # PEP 561 marker
├── tests/ # Pytest test suite
│ ├── test_sync_engine.py
│ ├── test_field_mapper.py
│ ├── test_field_inference.py
│ ├── test_mcp_server.py
│ ├── test_mcp_client.py
│ ├── test_comment_sync.py
│ ├── test_issue_template.py
│ ├── test_env_detection.py
│ └── integration/
│ └── test_full_sync.py
├── scripts/
│ └── setup.sh # Interactive project setup (venv, config, Cursor MCP, skills)
├── docs/ # Documentation
│ ├── architecture.md # This file
│ ├── quickstart.md
│ ├── setup.md
│ ├── mcp-setup.md
│ ├── deploy.md
│ ├── LIFECYCLE_STAGES.md
│ ├── issue-templates.md
│ └── ...
├── bd # Bash wrapper: intercepts bd commands, triggers sync
├── pyproject.toml # Package config (hatchling), deps, tool config
├── uv.lock # Dependency lock file (uv)
├── config.yaml.example # Example ClickUp config
├── templates.yaml.example # Example issue template config
├── cursor-mcp-config.json # Example Cursor MCP config
├── CLAUDE.md # Agent instructions (Claude Code)
├── AGENTS.md # Agent instructions (Cursor)
└── README.md
Module Architecture¶
Core Modules¶
┌──────────────────┐
│ BeadsMCPServer │ JSON-RPC over stdio
│ (mcp_server.py) │ 11 tools exposed
└────────┬─────────┘
│ delegates to
┌──────────────────┼──────────────────────┐
│ │ │
v v v
┌──────────────────┐ ┌──────────────┐ ┌────────────────────┐
│ SyncEngine │ │ Provisioner │ │ lifecycle_graph │
│ (sync_engine) │ │ (provision.) │ │ (lifecycle_graph) │
└────────┬─────────┘ └──────┬───────┘ └────────┬───────────┘
│ │ │
┌────────┼───────────┐ │ ┌────────┼───────────┐
│ │ │ │ │ │ │
v v v v v v v
┌───────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌───────────┐
│ClickUp│ │ Field │ │ Comment │ │ Stage │ │ Neighbor │
│Client │ │ Mapper │ │ Sync │ │Classifier │ │Classifier │
└───────┘ └────┬─────┘ └──────────┘ └───────────┘ └───────────┘
│
v
┌───────────┐
│ Field │
│ Inference │
└───────────┘
Module Responsibilities¶
| Module | Responsibility |
|---|---|
| mcp_server.py | MCP protocol handler. Receives JSON-RPC calls, dispatches to bd CLI and SyncEngine. Manages workspace discovery and switching. |
| cli.py | CLI entry point (bd-clickup). Subcommands: init, sync, push, pull, link, status, provision, setup, lifecycle-graph, mcp-config. |
| sync_engine.py | Core sync orchestrator. Reads issues.jsonl, manages sync_state.json, coordinates FieldMapper/ClickUpClient/CommentSync. Supports concurrent sync via max_workers. |
| clickup_client.py | HTTP client wrapping ClickUp REST API v2. Handles auth, rate limiting, error mapping. Supports tasks, custom fields, comments, spaces, lists, folders. |
| field_mapper.py | Two classes: FieldMapper (status/priority/labels mapping, full issue-to-task conversion) and CustomFieldMapper (dropdown/text/url/number/date type conversion with option ID resolution). |
| field_inference.py | Rule-based inference of custom field values (lifecycle stage, category, effort, business value) from issue content. Used as fallback when fields aren't explicitly set. |
| provisioning.py | ClickUpProvisioner creates/configures ClickUp lists with correct statuses and custom fields. Writes discovered field IDs back to config.yaml. |
| comment_sync.py | Bidirectional comment sync. Stores comments as JSONL per issue. Tracks sync state per comment. |
| lifecycle_graph.py | Builds interactive task dependency graph using vis-network. Coordinates LLM classifiers for stage/neighbor inference, exports standalone HTML. |
| stage_classifier.py | LLM-powered (OpenAI/Anthropic) lifecycle stage assignment. Batch classification with JSON cache and TTL. |
| neighbor_classifier.py | LLM-powered task relationship inference. Identifies related tasks, validates bidirectionality, caches results. |
| issue_template.py | Issue description template loaded from .beads/templates.yaml (or built-in default). Validates description structure, parses sections, tracks DoD completion. |
| models.py | Dataclass models: Issue, ClickUpTask, SyncState, SyncStats, Comment, CustomFieldConfig. All with from_dict()/to_dict() serialization. |
| constants.py | Enums: IssueStatus, Priority, ClickUpPriority, IssueType, CustomFieldType, SyncDirection, LifecycleStage, Effort, BusinessValue, Category. Includes conversion helpers. |
| exceptions.py | Hierarchy rooted at BeadsClickUpError with specific subtypes for API errors (401/403/404/429), sync conflicts, field mapping failures, and project errors. |
| env_detection.py | Finds a valid Python executable with beads_clickup importable. Checks venv, system, and sys.executable. |
Data Models¶
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ Issue (beads) │ │ ClickUpTask │
│ ───────────── │ │ ──────────── │
│ id: str │ │ id: str │
│ title: str │◄───►│ name: str │
│ status: IssueStatus │ │ status: str │
│ priority: Priority │ │ priority: int │
│ issue_type: IssueType │ │ description: str │
│ description: str │ │ tags: list[str] │
│ labels: list[str] │ │ assignees: list │
│ custom_fields: dict │ │ custom_fields: list[dict] │
│ external_ref: str │ │ parent_id: str │
│ created_at, updated_at │ │ list_id, space_id │
│ closed_at, close_reason │ │ start_date, due_date, url │
└──────────────┬───────────────┘ └──────────────┬───────────────┘
│ │
│ ┌──────────────────┐ │
└────────►│ SyncState │◄──────┘
│ ────────── │
│ issue_id │
│ clickup_task_id │
│ last_synced │
│ sync_direction │
└──────────────────┘
Local Storage¶
| File | Format | Purpose |
|---|---|---|
.beads/issues.jsonl |
JSONL | All beads issues (one JSON object per line) |
.beads/templates.yaml |
YAML | Issue description template (optional, customizable) |
.beads/integrations/clickup/config.yaml |
YAML | ClickUp workspace, list, field mappings, sync options |
.beads/integrations/clickup/sync_state.json |
JSON | Map of issue_id -> {clickup_task_id, last_synced, ...} |
.beads/integrations/clickup/comments/{id}.jsonl |
JSONL | Comments per issue |
.beads/integrations/clickup/stage_cache.json |
JSON | LLM stage classification cache |
.beads/integrations/clickup/neighbor_cache.json |
JSON | LLM neighbor inference cache |
Entry Points¶
| Entry Point | How to Run | What it Does |
|---|---|---|
| MCP Server | python -m beads_clickup |
Starts JSON-RPC stdio server for Cursor IDE |
| CLI | bd-clickup <command> |
Direct CLI for sync, setup, provisioning |
| bd Wrapper | ./bd <command> |
Intercepts bd CLI commands and triggers ClickUp sync |
MCP Tools¶
The MCP server exposes 11 tools to AI assistants:
| Tool | Description |
|---|---|
bd_create |
Create a new beads issue (auto-syncs to ClickUp) |
bd_update |
Update issue status, priority, or fields (auto-syncs) |
bd_close |
Close an issue with a reason (auto-syncs) |
bd_list |
List issues with filters (status, priority) |
bd_show |
Show full details of a specific issue |
bd_search |
Search issues by text query |
bd_assign_subtask |
Assign parent-child relationships between tasks |
bd_sync |
Bidirectional sync (full, force_import options) |
bd_lifecycle_graph |
Generate interactive task graph with LLM-inferred stages and neighbors |
bd_switch_workspace |
Switch active beads workspace (or list available) |
bd_clickup_setup |
One-command board setup: create list, provision statuses and custom fields |
Sync Mechanism¶
State Tracking¶
The sync_state.json file maintains a mapping for each synced issue:
{
"issue-abc": {
"clickup_task_id": "abc123",
"last_synced": "2025-01-15T10:30:00",
"last_beads_update": "2025-01-15T10:29:00",
"last_clickup_update": "2025-01-15T09:00:00",
"sync_direction": "bidirectional"
}
}
Sync Modes¶
| Mode | Description |
|---|---|
| Incremental | Only syncs issues modified since last sync (timestamp-based) |
| Full | Compares all beads issues against all ClickUp tasks |
| Force Import | Imports ClickUp tasks not created by beads (bypasses beads-automation tag filter) |
Conflict Resolution¶
When both sides have changed since last sync, the configured strategy applies:
- beads_wins: Local changes overwrite ClickUp
- clickup_wins: ClickUp changes overwrite local
- manual: Log conflict to conflicts.jsonl for human review
Custom Field Pipeline¶
When syncing issues to ClickUp, custom fields go through a three-tier pipeline:
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Config Defaults │────>│ Inferred Values │────>│ Explicit Values │
│ (lowest) │ │ (medium) │ │ (highest) │
│ │ │ │ │ │
│ config.yaml │ │ FieldInference │ │ issue.custom_ │
│ default: "..." │ │ Engine rules │ │ fields dict │
└──────────────────┘ └──────────────────┘ └──────────────────┘
│
v
┌──────────────────┐
│ CustomFieldMapper │
│ Converts to │
│ ClickUp format │
│ (dropdown IDs, │
│ timestamps, etc) │
└──────────────────┘
Supported Field Types¶
| Type | Beads Value | ClickUp API Value |
|---|---|---|
drop_down |
Option name (string) | Option ID (UUID or orderindex) |
text / short_text |
String | String |
url |
URL string | URL string |
number |
Number | Float |
date |
ISO string or Unix ms | Unix timestamp (ms) |
users |
User ID or list | List of user IDs |
labels |
String or list | List of label strings |
Lenient vs Strict Mode¶
field_mapping:
lenient_mode: true # Log warnings, skip bad fields (default)
# lenient_mode: false # Raise exceptions on mapping failures
Issue Templates¶
When creating issues, descriptions are populated from a configurable template. The template is a single YAML file at .beads/templates.yaml. If the file doesn't exist, a built-in default is used.
# .beads/templates.yaml
template:
objective: "[title]"
plan: |
1. Step one
2. Step two
3. Step three
definition_of_done: |
- [ ] Acceptance criterion 1
- [ ] Acceptance criterion 2
- [ ] Acceptance criterion 3
testing_plan: "Manual verification"
Each key becomes a **Section Name:** header in the issue description. The [title] placeholder is replaced with the issue title. Sections can be added, removed, or reordered freely -- any YAML key is accepted. A **Results:** TBD section is appended automatically if not specified.
See templates.yaml.example at the repo root for the full reference with comments.
LLM-Powered Features¶
Two optional features use LLM APIs (OpenAI or Anthropic) for intelligent task analysis:
Stage Classification (stage_classifier.py)¶
- Assigns lifecycle stages to tasks based on title, description, type, and status
- Supports batch classification for efficiency
- Results cached to JSON with configurable TTL
- Providers: OpenAI (
gpt-4o-mini) or Anthropic (claude-3-haiku)
Neighbor Inference (neighbor_classifier.py)¶
- Identifies related/dependent tasks from the full issue set
- Validates bidirectionality (if A relates to B, B relates to A)
- Limits max neighbors per issue (configurable)
- Results cached with TTL
Lifecycle Graph (lifecycle_graph.py)¶
- Combines stage + neighbor data into an interactive vis-network graph
- Groups tasks by lifecycle stage with color coding
- Exports standalone HTML file
- Optionally syncs inferred data back to ClickUp
Exception Hierarchy¶
BeadsClickUpError
├── ConfigurationError
│ ├── MissingConfigError
│ └── InvalidConfigError
├── ClickUpAPIError (status_code, endpoint)
│ ├── AuthenticationError (401)
│ ├── PermissionError (403)
│ ├── ResourceNotFoundError (404)
│ └── RateLimitError (429)
├── SyncError (issue_id, task_id, direction)
│ ├── IssueNotFoundError
│ ├── TaskNotFoundError
│ └── SyncConflictError
├── FieldMappingError (field_name, field_value)
│ ├── InvalidFieldValueError
│ ├── UnknownFieldError
│ └── MissingRequiredFieldError
└── ProjectError
├── ProjectNotFoundError
└── InvalidProjectError
ClickUpAPIError.from_response() factory auto-selects the correct subclass by HTTP status code.
Cursor / Claude Code MCP Setup¶
Add to ~/.cursor/mcp.json (Cursor) or configure via .claude/settings.json (Claude Code):
{
"mcpServers": {
"beads": {
"command": "beads-mcp-server",
"env": {
"BEADS_PROJECT_DIR": "/path/to/your/project",
"CLICKUP_API_TOKEN": "pk_..."
}
}
}
}
Run scripts/setup.sh to configure this automatically. See MCP Setup for details.
The BEADS_PROJECT_DIR environment variable sets the default workspace. The MCP server also supports runtime workspace switching via bd_switch_workspace.
Why Not ClickUp's Official MCP Server?¶
ClickUp has an official MCP server, but it requires OAuth authentication which doesn't work with personal API tokens. This project uses the REST API directly, giving you:
- Works with personal API tokens (no OAuth flow)
- Full control over sync logic and conflict resolution
- Custom field mapping with type-safe conversion
- Bidirectional sync with local issue storage
- LLM-powered task analysis features
- Offline-first: local
.beads/is the source of truth
Tech Stack¶
| Component | Technology |
|---|---|
| Language | Python 3.11+ |
| Build | Hatchling |
| Package Manager | uv |
| HTTP Client | requests |
| Config | PyYAML |
| Validation | Pydantic (LLM response parsing) |
| LLM | openai, anthropic SDKs |
| Environment | python-dotenv |
| Testing | pytest, pytest-cov |
| Linting | ruff, mypy (strict) |
| Dev Environment | Devbox |
Testing¶
pytest # Run full test suite with coverage
pytest tests/test_sync_engine.py # Run specific module tests
pytest tests/integration/ # Run integration tests (requires API token)
Test coverage spans: sync engine, field mapper, field inference, MCP server, ClickUp client, comment sync, issue templates, and environment detection.
Related Documentation¶
- QUICKSTART.md - Get started in 5 minutes
- SETUP.md - Detailed setup instructions
- MCP_SETUP.md - Cursor MCP configuration
- LIFECYCLE_STAGES.md - Lifecycle stage definitions
- ISSUE_TEMPLATES.md - Issue template reference
- config.yaml.example - All configuration options