TaskBoard MCP
A persistent task layer that syncs across Claude Code sessions and Claude.ai chats. Tasks live in Supabase. Any Claude instance with the MCP connected can read and write the board. You manage it from a mobile-friendly PWA.
What it does
- One board, visible to every Claude session you run
- Claude adds tasks during work, completes them as they get done
- You add and manage sections from the PWA on your phone
- Sections you create in the app are protected from Claude overwriting them
- Pinned reference docs show up under each section header, tappable from anywhere
- Ask Claude about any task using your own Anthropic API key
Stack
- PWA -- dark-mode mobile app hosted on Vercel, no app store required
- Supabase -- single-row JSON blob, free tier
- MCP server --
api/mcp.js, same Vercel deployment as the PWA
- Anthropic -- each user brings their own API key; no shared billing
Deploy your own
1. Set up Supabase
Create a free project at supabase.com. Then run supabase-setup.sql in the SQL Editor (Supabase dashboard > SQL Editor > New query):
create table if not exists taskboard (
id integer primary key default 1,
data jsonb not null default '{}'::jsonb,
updated_at timestamptz default now()
);
alter table taskboard enable row level security;
create policy "allow all" on taskboard
for all
using (true)
with check (true);
Copy your Project URL and anon/public key from Project Settings > API.
2. Deploy to Vercel

Set these three environment variables when prompted:
SUPABASE_URL | Supabase > Project Settings > API > Project URL |
SUPABASE_ANON_KEY | Supabase > Project Settings > API > anon/public key |
ANTHROPIC_API_KEY | console.anthropic.com > API Keys (optional -- enables Ask Claude) |
ANTHROPIC_API_KEY is optional. The board works without it -- you just won't have the Ask Claude feature in the PWA or the ask_claude MCP tool.
3. Connect to Claude Code
Run the interactive setup script:
curl -fsSL https://raw.githubusercontent.com/RefugeSwordPublishing/taskboard/master/setup.sh | bash
Or connect manually:
claude mcp add taskboard --transport http https://your-deployment.vercel.app/api/mcp
4. Install the PWA (optional)
Android (Chrome): Open your Vercel URL in Chrome. A banner will appear at the top prompting you to install. Tap Install, or use the three-dot menu > Add to Home Screen.
iOS (Safari): Open your Vercel URL in Safari. Tap the Share button (box with arrow) > Add to Home Screen. The install banner does not appear on iOS -- Safari requires the manual share menu flow.
MCP tools
get_board
Read all sections, tasks, and pinned refs. Call this at the start of every session.
get_board()
Returns the full board JSON including section IDs, task IDs, and ref IDs you'll need for other calls.
add_task
Add a task to a section. Use the section_id from get_board.
add_task(
section_id: "your-section-id",
text: "Description of the task"
)
With a blocking tag:
add_task(
section_id: "your-section-id",
text: "Critical: fix this before deploying",
tag: "blocking"
)
complete_task
Mark a task done and remove it from the board. Requires both the task ID and section ID.
complete_task(
task_id: "task-id-from-get-board",
section_id: "your-section-id"
)
For a user-owned section (requires explicit instruction from the user):
complete_task(
task_id: "task-id-from-get-board",
section_id: "user-created-section-id",
override: true
)
remove_task
Remove a task without marking it complete -- use for cancelled or duplicate items.
remove_task(
task_id: "task-id-from-get-board",
section_id: "your-section-id"
)
add_section
Create a new section. Always tagged source: "claude" so it can be freely managed.
add_section(
title: "Legacy Renovations - Marketing",
color: "#b45309"
)
Returns the new section ID for use in follow-up calls.
remove_section
Delete a section and all its tasks.
remove_section(section_id: "user-created-section-id")
For user-owned sections, override: true is required and should only be passed when the user explicitly named the section:
remove_section(
section_id: "user-created-section-id",
override: true
)
pin_ref
Pin a reference document to a section's ref shelf. GitHub blob URLs are preferred so the link opens on mobile.
pin_ref(
section_id: "your-section-id",
label: "Sprite & Art Conventions",
url: "https://github.com/org/repo/blob/main/docs/sprite-conventions.md"
)
Without a URL (label-only ref):
pin_ref(
section_id: "your-section-id",
label: "Brand Guide v2 -- ask Dustin for the file"
)
remove_ref
Remove a pinned ref by its ID (from get_board output).
remove_ref(
section_id: "your-section-id",
ref_id: "ref-id-from-get-board"
)
ask_claude
Ask Claude a question about a task using your own Anthropic API key. Requires ANTHROPIC_API_KEY set in your Vercel env.
ask_claude(
question: "What's the fastest way to unblock this?",
context: "Section: JobShot. Task: Fix multi-shot camera crash - needs logcat output to diagnose."
)
Without context:
ask_claude(question: "What does RLS mean and why does it matter for Supabase?")
Section protection
Sections you create in the PWA are tagged source: "user". Claude will not delete or replace them during a sync. Claude can still add or complete tasks inside them when you explicitly ask -- "mark off all my Legacy tasks" triggers an override for that operation only.
Sections Claude creates via add_section are tagged source: "claude" and have no protection.
CLAUDE.md integration
The setup script writes this automatically when it runs. If you skipped that step or want to add it manually, append this to your project's CLAUDE.md:
## TaskBoard
MCP server connected at https://your-deployment.vercel.app/api/mcp
Session start: call get_board, note any tasks completed since last session.
Session wrap: call complete_task for anything finished. Add new tasks with
add_task. Never call remove_section on sections with source:"user" unless
I name them explicitly.
For developers
Built with:
@modelcontextprotocol/sdk -- MCP server, Streamable HTTP transport
- Vercel serverless functions -- stateless; all state lives in Supabase
- Vanilla JS PWA -- no framework, installs offline
To add a tool, extend createServer() in api/mcp.js. The pattern is:
server.tool('tool_name', 'description', { param: z.string() }, async ({ param }) => {
return { content: [{ type: 'text', text: 'result' }] };
});
PRs welcome. Open an issue before adding new tools so the schema stays coordinated.