Skip to main content
Glama
idea-base-mcp-server

idea-base-mcp-server

Official

@idea-base/mcp-server

MCP (Model Context Protocol) server for IDEA Base — AI-powered project management. Manage projects, tasks, and time tracking directly from Claude Code, Cursor, or any MCP-compatible AI tool.

Quick Setup

1. Get your API key

Sign in to IDEA Base, go to Settings > API Keys, and create a key.

2. Add to your MCP config

Claude Code (~/.claude/claude_desktop_config.json):

{
  "mcpServers": {
    "idea-base": {
      "command": "npx",
      "args": ["-y", "@idea-base/mcp-server"],
      "env": {
        "IDEA_BASE_API_KEY": "ib_your_api_key_here"
      }
    }
  }
}

Cursor (.cursor/mcp.json in your project):

{
  "mcpServers": {
    "idea-base": {
      "command": "npx",
      "args": ["-y", "@idea-base/mcp-server"],
      "env": {
        "IDEA_BASE_API_KEY": "ib_your_api_key_here"
      }
    }
  }
}

Or via Claude Code CLI:

claude mcp add idea-base -- npx -y @idea-base/mcp-server \
  --env IDEA_BASE_API_KEY=ib_your_api_key_here

3. Start using it

Ask Claude to manage your projects:

  • "List my projects"

  • "Create a task in project 1: Implement login page"

  • "Log 2 hours on task 42 — built the auth flow"

  • "What tasks are in progress?"

Related MCP server: wootech-jira-mcp

Available Tools

Projects

Tool

Description

list_projects

List all projects with task counts and progress

get_project

Get project details and statistics

create_project

Create a new project or sub-project

update_project

Update a project name, description, status, parent (re-parent, or null to detach) or product

list_sprints

List a product's sprints with real task counts and its cadence

get_sprint

One sprint: tasks, counts, commitment ledger; optionally candidates + blocked work

create_sprint

Open a sprint — next on the cadence, or an explicit window

add_tasks_to_sprint

Commit tasks (refuses a task already in another sprint)

remove_tasks_from_sprint

Take tasks out of a sprint

close_sprint

Close it; unfinished work carries forward. Irreversible

get_sprint_cadence

Read a product's sprint rhythm

set_sprint_cadence

Set length, start weekday, carry-over, auto-open

Slash commands (MCP prompts)

Typed directly in Claude Code. The idea-base part is whatever you named the server in your MCP config.

Command

What it gives you

/idea-base:start_sprint [id]

A sprint laid out to work: goal, window, days left, and items split into assigned-to-you / unassigned / blocked-with-reason. Omit the id for the sprint covering today.

/idea-base:sprint_report [id]

Committed vs completed, what carried and how often, from the commitment ledger written at close.

/idea-base:plan_sprint <product_id>

Cadence, the next window, the eligible pool, and what is held back as blocked.

/idea-base:setup

Whether this project emits session tracking, every missing piece with the literal text to add, and a live check that a heartbeat actually lands. Writes nothing.

/idea-base:features

What is wired on this machine — each capability on, off, configured-but-not-firing, or undetermined with the reason. Read-only.

/idea-base:update

The installed version, the latest published one, and the one command that updates both the package and what it installed here. Reports; the command does the work. See Updating.

Tracker text in these results is fenced as data, and item lists are capped with the true total printed.

Session tracking

With it installed, a long-running agent session appears on the dashboard's Agents Working Now board while it works and leaves within seconds of ending. Without it the board is empty, and there is no way to tell that from "no agents are running" — which is the failure this exists to prevent.

npx @idea-base/mcp-server install          # DRY RUN — prints the plan, writes nothing
npx @idea-base/mcp-server install --apply --actor <agent-user-id>
npx @idea-base/mcp-server doctor           # does a heartbeat actually land?
npx @idea-base/mcp-server features         # the inventory, read-only
npx @idea-base/mcp-server@latest update    # move an existing install forward

Already installed? Updating is the one you want — install leaves an existing heartbeat.sh alone on purpose, so it will not move an older one forward.

install is safe to run as often as you like. It merges rather than overwrites: your other tools' hooks, your permissions, your env and everything else in .claude/settings.json are preserved, and a second run leaves the file byte-identical. If it finds the same heartbeat hook registered twice it collapses it to one — a duplicate heartbeat double-writes. A settings.json it cannot parse is refused, never rewritten: a file that will not parse is not an empty file, and rebuilding it would delete every hook you have.

It installs three hooks — SessionStart, PostToolUse, SessionEnd — plus the script they call, at .claude/hooks/heartbeat.sh. The script is written before the hooks that reference it, because a hook pointing at a missing script fails closed and can deny Bash and Edit, including the edit needed to repair it.

--actor is not optional in practice, and is never guessed

The API key belongs to a person. Without env.IDEABASE_ACTOR every heartbeat an agent posts is attributed to that person, and because the board filters to agents, the session is filtered off it entirely.

It is never defaulted, deliberately:

IDEABASE_ACTOR

Result

unset

a row is written, attributed to the key's human owner. Mislabelled, but visible and fixable.

an id that does not exist

403 — no row at all.

an id belonging to a human

403 — no row.

A wrong guess lands in the invisible row. So install asks for it rather than inventing one.

Verifying, and what "verified" means

doctor posts one labelled probe and reports the server's own verdict, because POST /api/heartbeat answers HTTP 202 with {"recorded":false} when it discards a ping — a success status for "I threw this away". Exit status:

Exit

Meaning

0

recorded (or heartbeats are deliberately switched off)

1

the endpoint refused or skipped it

2

could not get as far as asking

1 and 2 are kept apart on purpose: "I asked and was told no" and "I never managed to ask" need different fixes.

A session with no task is untracked, by design. The hook sends nothing unless the session has declared what it is working on — normally by calling start_working. Nothing about your branch names or directory layout affects this. A hub on main, an exploratory session, anything not working a task: all correctly absent, and doctor says which.

What this does not do

It never changes a task's status. A session ending marks the session finished; deciding a task is done is a judgment with a verification gate in front of it.

These three hooks are Claude Code mechanisms. Nothing here transfers to another agent harness — POST /api/heartbeat is not Claude-Code-specific, so you can wire it to any session lifecycle hook yourself, but there is no packaged install for another harness today.

Requires curl on PATH, a write-scoped API key, and Node 20+. IDEABASE_HEARTBEAT=off disables emission entirely.

Tasks

Tool

Description

list_tasks

List tasks for a project (filter by status). Compact rows by default (verbose:true for full rows)

get_task

Get task details, acceptance criteria, and time entries

create_task

Create a task with title, description, estimate, priority, start/due dates, assignee; parent_task_id makes it a subtask

update_task

Update task details, priority, start/due dates, assignee; blocked_by: [ids] replaces its dependencies

update_task_status

Change task status (todo/in_progress/blocked/done)

search_tasks

Search tasks across all projects, ranked title-first then description then recency. Compact rows by default; filter by project_id/product_id/customer_id/status; verbose:true for full rows

quick_log

Create + complete + log MEASURED time in one step; estimated_minutes is separate from minutes so the estimate and the actual need not be the same number

Time Tracking

Tool

Description

log_time

Hand-log measured time against a task. Rarely what you want — see below. Refuses a negative entry, and refuses an agent entirely on a task that already has measured time

start_working

Open a timed work session on a task, and mark yourself actively working (surfaces the saved resume context + recent work notes so a cold session re-orients). Calling it twice never opens a second session — it resumes or reports the one you have

pause_working

Pause the open session without ending it, with a required reason

resume_working

Close the pause and carry on in the same session

stop_working

Close the session with a UTC end instant, write the task's time entry from the measured duration, and drop the active flag (optionally capture a note and/or resume_context on the way out). Fails if you have no open session

stop_working logs your time. You type no number.

An agent has no clock, so a duration it fills in by hand is a guess about how long the work felt. Closing a session writes the task's time entry from the session's measured worked duration — computed by the database from the recorded instants — and the response tells you the entry it wrote. Each entry records how its number was arrived at:

source

Meaning

measured

written by stop_working from a closed session. Nobody typed it

manual

somebody supplied the number by hand

(unset)

unknown — the entry predates provenance being recorded. Unknown is not measured

So: start_working, work, stop_working. Reach for log_time only for work no session measured, and say in the notes why there is no measurement. Two refusals you will meet if you ignore that:

  • an agent hand-logging on a task that already has measured time is refused whatever the amount — the measurement exists, so there is nothing to supply;

  • negative minutes are refused on every path. Correct the original entry rather than filing an offsetting one: a correction stored as a second row makes the total a chain, and a reader entering it at the wrong row gets a wrong number with nothing to signal it.

An estimate is a different field — estimated_minutes on the task. An estimate is a plan and is allowed to be wrong; logged time is a measurement and is not.

A session under half a minute rounds to zero and logs nothing, rather than being rounded up to a minute. That is deliberate — do not then log it by hand.

The pause reason decides the number. start_working records a UTC start instant, stop_working a UTC end instant, and each pause in between records both its boundaries plus why work stopped:

reason

Effect on worked time

waiting_on_human

Excluded. A person has to act before you can continue

waiting_on_agent

Counted. A sub-agent blocked on another active sub-agent is still working

other

Counted

Only waiting on the human is not working. Use pause_working rather than stop_working whenever you intend to carry on — stop_working ends the session and the reason is lost.

Activity & Audit Trail

For AI agents, these leave a durable trail of what was done and why on each task — so a future session (or a human reviewer) can see the reasoning, not just the final state.

Tool

Description

add_work_note

Append a timestamped progress note to a task's activity log (append-only journal)

add_comment

Add a comment to a task's discussion thread (customer-visible by default)

set_resume_context

Overwrite the task's pinned "where I left off" block, read first on start_working

Products

Tool

Description

list_products

List products (top-level containers)

get_product

Get product details with linked projects

create_product

Create a new product

link_project_to_product

Link a project to a product

Support

Tool

Description

report_bug

File a bug report against IDEA Base itself through the existing Help Desk intake (POST /api/tickets, category bug) — not a second intake path. Staff see it in the Help Desk queue. A read-only API key is refused.

Environment Variables

Variable

Required

Description

IDEA_BASE_API_KEY

Yes

Your API key from Settings > API Keys

IDEA_BASE_API_URL

No

Custom API URL (default: https://app.idea-base.us/api)

Real-time Notifications

When you use the MCP server to update tasks or log time, changes are broadcast to all connected users. Team members viewing the project in their browser see live toast notifications — when Claude updates a task, everyone sees it immediately.

Security

  • All data access is scoped to your account via API key

  • API keys support read/write permissions

  • No data is stored locally — all operations go through the IDEA Base API

  • Cross-account access is blocked server-side

  • Rate limited per API key

Development

# Run the server directly
IDEA_BASE_API_KEY=your_key npm start

# Watch mode
IDEA_BASE_API_KEY=your_key npm run dev

# Live integration test — spawns a real server (this checkout, or a
# published version via --cmd) and drives it against the real API.
# Creates and deletes its own scratch tasks; see test/live-mcp.test.mjs.
IDEA_BASE_API_KEY=your_key node test/live-mcp.test.mjs
IDEA_BASE_API_KEY=your_key node test/live-mcp.test.mjs --cmd "npx -y @idea-base/mcp-server@2.5.0"

Updating

One command, run in the repository you want tracked:

npx @idea-base/mcp-server@latest update

@latest is the part that does the npm work — npx resolves and fetches the current version in order to run it. The command then:

  1. Refreshes the files the package installed here. Today that is .claude/hooks/heartbeat.sh. install deliberately skips a script that is already present (an installer that clobbers on every run is worse), which means an install made at an older version stayed at that version with nothing saying so. This is the command that moves it forward.

  2. Re-asserts the three hook registrations, merging exactly as install does — your other tools' hooks, your permissions, your env and everything else in .claude/settings.json are preserved, and a second run leaves both files byte-identical.

  3. Runs doctor and prints its verdict, so you finish knowing whether tracking actually works rather than whether files were copied.

A script you edited yourself is never overwritten. The decision is made by sha256 against the list of bodies this package has ever published (src/assets/heartbeat.provenance.json, known_shas):

Your heartbeat.sh

What happens

the current version

nothing is written

an older version we shipped

refreshed, with both sha256s printed

absent

written

anything else

left exactly as it is, and reported

--dry-run prints all of the above and writes nothing.

Then restart. A running MCP server keeps the code it started with, so none of this reaches the session you ran it from — start a new session, or restart your editor. The command says so on every run; it does not pretend to restart anything for you.

Knowing you are behind

A running server tells you once, on the first tool call of the session, when a newer version is published — and tells you the same command. /idea-base:update reports the installed and latest versions on demand.

The check is one unauthenticated GET to the npm registry for this package's latest dist-tag: no credential, no request body, and nothing about you, your project or your data. It is fired once at startup and never awaited on the tool path, so an unreachable registry costs no tool call any time at all — it simply says nothing, because "I could not ask" and "you are up to date" must not look the same. Set IDEA_BASE_UPDATE_CHECK=off to disable it entirely.

Releasing

Merging to main does not ship anything. Every consumer runs whatever npm view @idea-base/mcp-server version currently answers — not whatever is on main. A global npm install -g (several deployments run this way) is frozen at that version until someone explicitly re-installs it; nothing about publishing a new version moves it. GH #410 is seventeen days of merged work (update_task(parent_task_id), update_task(github_issue_id)) that sat unreleased because nothing said the registry had fallen behind main.

To actually ship a change:

  1. Bump version in package.json, npm install --package-lock-only.

  2. Commit, push, gh release create vX.Y.Z — publish.yml (Trusted Publisher, Node 24, no tokens) publishes to npm on the release event.

  3. Then check who is still on the old version. A global install has no version spec to resolve and will not move on its own — that upgrade is a deliberate decision on each box, not a side effect of publishing.

The .github/workflows/version-drift.yml workflow runs on every push to main and reports (as a non-blocking warning) when main has commits beyond the published gitHead — so "merged but unpublished" is visible instead of being rediscovered by whoever trips over it next.

License

MIT - IDEA Management LLC

Available Tools

36 tools
add_assigneeA

Assign a user to a task WITHOUT disturbing anyone already assigned. task_assignments holds one row per (task_id, user_id) — POST /api/tasks/:id/assignments upserts exactly that one row and leaves every other assignee alone, unlike update_task's assignee_user_id (which REPLACES the whole set — use that only when you actually want a single owner). This is the fix for the defect where a second MCP assignment silently dropped the first.

Idempotent and is_active-safe: this checks the task's current assignees first, and if the user already holds a row (assigned OR actively working) it does nothing and says so, rather than re-POSTing. That matters because the REST endpoint's upsert OVERWRITES is_active on conflict — a blind re-assign of someone with a running timer (is_active=1, set by start_working) would silently stop their clock. Skipping the no-op re-POST is what keeps assignment and active-work presence independent.

Rejects a user who is not a member of this account (404, surfaced as an error) rather than creating a dangling assignment.

Inputs: task_id (required), user_id (required).

add_commentA

Add a comment to a task's discussion thread. Unlike work notes (progress journal), comments are for communication and are customer-visible by default.

Inputs: comment (required), task_id (required), is_internal.

add_tasks_to_sprintA

Commit tasks to a sprint. Each task is checked on its own and the result says which were added and which were refused and why — a task already in another sprint is REFUSED, not moved, because a task belongs to at most one. Tasks must belong to a project under the sprint's product.

Inputs: task_ids (required), sprint_id (required).

add_work_noteA

Append a timestamped work note (status update) to a task's activity log. Use this to record progress so a future session can pick up where you left off (e.g. "finished auth handler, tests green, next: wire the callback"). Notes are append-only and never overwrite each other.

Inputs: note (required), task_id (required), is_internal.

close_sprintA

Close a sprint. Unfinished work moves into the next sprint and its carry count increments (unless the product's cadence has carry-over off, in which case it is released); finished work stays attached. Every task held is written to the commitment ledger first, so what the sprint committed to survives the carry. This is an action, not a status edit — closing is irreversible.

Inputs: sprint_id (required), carry_to_sprint_id.

create_productA

Create a new product. Products are top-level containers for projects. Every product belongs to a customer (customer_id is required) — use an internal/own-company customer for internal work. Find customer ids via list_products (each shows its customer_id).

Inputs: name (required), customer_id (required), description, github_repo_url.

create_projectA

Create a new project. A TOP-LEVEL project requires product_id; a sub-project (parent_project_id set) inherits its parent's product. Returns the created project with its ID.

Inputs: name (required), product_id, description, parent_project_id.

create_sprintA

Open a sprint on a product. With no dates it opens the NEXT one on the product's cadence, which is the normal case; pass starts_on/ends_on only to set an explicit window. One sprint per product per period — a second for the same period is refused rather than duplicated.

Inputs: product_id (required), goal, name, state, +2 more.

create_taskC

Create a new task in a project. Returns the created task with its ID.

Inputs: title (required), project_id (required), due_date, priority, +13 more.

get_productA

Get details of a specific product including linked projects, team members, and statistics.

Inputs: product_id (required).

get_projectA

Get details of a specific project including name, description, status, and task statistics.

Inputs: project_id (required).

get_sprintA

Get one sprint: its tasks with derived status, counts taken from real task rows, and — for a CLOSED sprint — the commitment ledger saying what it committed to and what became of each item. Pass candidates:true to also get the open work that could be pulled in, and the work being held back as blocked (with the reason).

Inputs: sprint_id (required), candidates.

get_sprint_cadenceA

Get a product's sprint rhythm: length in days, which weekday it starts, whether unfinished work carries over, and whether sprints open unattended. A product with no cadence row still reports the weekly-Monday default, flagged as not configured.

Inputs: product_id (required).

get_taskA

Get detailed information about a specific task including description, acceptance criteria, time entries, and status.

Inputs: task_id (required).

list_productsA

List all products accessible to the authenticated user. Products are top-level containers for organizing related projects.

Inputs: status.

list_projectsA

List all projects accessible to the authenticated user. Returns project names, IDs, status, and task counts.

Inputs: status, parent_id.

list_sprintsB

List a product's sprints, newest first, with real task counts and its cadence. Sprints are owned by the PRODUCT: the product sets the rhythm and owns the lifecycle, while projects add and remove the work. A sprint can hold tasks from any project under that product, sub-projects included.

Inputs: product_id (required).

list_tasksA

List all tasks for a specific project. Returns compact rows (id, title, status, priority, estimated_minutes, time_spent, due_date, snippet) by default to keep payloads small; pass verbose:true for full rows including description, acceptance_criteria, tags, and assignments.

Inputs: project_id (required), status, verbose.

log_timeA

Log MEASURED time on a task — time you actually observed, never an estimate and never a guess.

IN ALMOST EVERY CASE YOU SHOULD NOT CALL THIS. Use start_working and stop_working instead: stop_working writes the task's time entry itself, from the work session's measured duration, read back out of the database. You have no clock, so a number you type here is a guess about how long something felt.

An estimate goes in the task's estimated_minutes (create_task / update_task), NOT here. Logged time is a measurement; an estimate is a plan. They are different fields and must not be confused — writing an estimate into logged time is what this tool's description used to invite, and an agent did it: 75 minutes logged for work its own sessions measured at 15.7, 14.5 and 8.1.

Use it ONLY for work no session measured — something done away from the keyboard, or work from before you started a session — and say in the notes why there is no measurement. An AGENT logging by hand on a task that already has measured time is REFUSED, whatever the amount. Negative minutes are refused too: correct the original entry rather than filing an offsetting one.

Inputs: minutes (required), task_id (required), date, notes.

pause_workingA

Pause your open work session without ending it, and record WHY. Use this whenever work stops but is not finished — you asked the human a question, you are waiting on another agent, you are blocked on a build.

THE REASON IS THE POINT, and it decides the number: waiting_on_human — you are blocked on a person and CANNOT proceed. The paused interval is EXCLUDED from worked time. waiting_on_agent — you are blocked on another agent or an automated run that is itself active. The interval IS COUNTED as worked time: a sub-agent blocked on another active sub-agent is still working. other — anything else. Counted as worked.

Only waiting on the human is not working. Choose waiting_on_human ONLY when a person has to act before you can continue; if a machine or another agent is doing the work, it is waiting_on_agent.

Resume with resume_working. Do NOT use stop_working for a pause — that ends the session and the reason is lost.

Inputs: reason (required), task_id (required), detail.

quick_logA

Quick workflow for recording work that is ALREADY FINISHED: creates a task, marks it done, and logs time in one call.

minutes must be MEASURED time, not an estimate. This is a hand-entry path — there is no work session behind it — so only use it for work you actually observed the duration of, and prefer start_working / stop_working whenever the work is still ahead of you, because stop_working writes the time entry from the measured session and you type no number at all.

An estimate goes in estimated_minutes, which is a separate optional argument here precisely so the two cannot be the same number by accident. Omit it and it defaults to minutes, as before.

Inputs: title (required), minutes (required), project_id (required), notes, +3 more.

record_verification_feedbackA

Record whether the most recent verify_task verdict on this task was accurate — calibration data, not a correction. Attaches to the latest ai_generations row of type completion_review for this task, so verify_task must have run at least once first. Does NOT change the stored ai_completion_score or re-run any model — it costs nothing (no Anthropic call, just an audit-log row) and is safe to call as often as useful.

Inputs: agreed (required), task_id (required), notes, actual_score.

remove_assigneeA

Remove exactly one user's assignment from a task, leaving every other assignee untouched. DELETE /api/tasks/:id/assignments?user_id=... removes that one task_assignments row outright (both the assignment and any is_active flag on it) — this is a full unassign, not stop_working's "still assigned, no longer active" (use stop_working to end a work session without unassigning).

Inputs: task_id (required), user_id (required).

remove_tasks_from_sprintA

Take tasks out of a sprint. The tasks themselves are untouched — the sprint is the time box, not the owner of the work.

Inputs: task_ids (required), sprint_id (required).

report_bugA

File a bug report against IDEA Base itself as a support ticket, through the existing Help Desk intake (POST /api/tickets, category "bug") — not a second intake path. Use this when something in IDEA Base is broken, not for a customer project's own bugs. Staff see it in the Help Desk queue.

Inputs: subject (required), priority, project_id, description.

resume_workingA

Resume your paused work session — closes the pause by recording the UTC instant you came back, so the paused interval has both boundaries and can be measured. The session itself was never closed and keeps its original start.

Fails if the session is not paused, rather than pretending to resume something. start_working also resumes a paused session, so use whichever reads better; this one does not re-fetch the orientation block.

Inputs: task_id (required).

search_tasksA

Search for tasks across all projects by title or description. Returns compact rows (id, title, status, priority, project_id, project_name, product_name, customer_name, estimated_minutes, time_logged, due_date, snippet) ranked with title matches first, then description matches, then recency. Pass verbose:true for full rows.

Inputs: query (required), limit, status, verbose, +3 more.

set_resume_contextA

Set (overwrite) the task's resume context — a single pinned block describing the current state, what is done, what is next, and where to look. This is read first when a work session restarts cold (e.g. via start_working) so you can re-orient immediately. Update it at the end of a work session.

Inputs: context (required), task_id (required).

set_sprint_cadenceA

Set a product's sprint rhythm. "Mon-Sun weekly" is length_days 7 + start_weekday 1; a Wednesday fortnight is 14 + 3. Changing it does not move sprints that already exist — it applies from the next sprint opened. Setting a cadence is also what opts a product into having sprints opened and closed on the schedule.

Inputs: product_id (required), enabled, auto_create, length_days, +2 more.

start_workingA

Start work on a task. Two things happen: you are shown to the team as actively working on it (and a todo task moves to in_progress), and a TIMED WORK SESSION is opened with a UTC start instant, so how long the task was actually worked becomes measurable instead of guessed.

Calling it a second time on the same task NEVER opens a second session. If your session is paused it is resumed; if it is already running you are told so, with how long it has been going. The response always says which of the three happened.

It also orients a cold session: the response carries the task's resume context and its recent work notes, so read the whole thing before you start. Pair it with stop_working — which is what LOGS the time, from the measured session, with no number typed by you — and use pause_working when you have to wait on something.

Inputs: task_id (required).

stop_workingA

Stop working on a task: close your open work session with a UTC end instant, and drop the "actively working" flag (you stay assigned).

THIS IS WHAT LOGS YOUR TIME, and you supply no number. Closing the session writes the task's time entry from the session's measured worked duration — computed by the database from the recorded start and end instants, minus any waiting_on_human pause — and the response tells you the entry it wrote. Do NOT call log_time afterwards: the time is already recorded, and a hand entry on a task that already has measured time is refused. A session shorter than half a minute rounds to zero and logs nothing rather than being rounded up, and the response says so.

Requires an open session — if you have none this FAILS and writes nothing, rather than inventing a session with no start. Call start_working first.

This ENDS the session. If you are only waiting on something and intend to carry on afterwards, use pause_working instead: a pause keeps the session open and records why you stopped, which is what decides whether the interval counts as worked time.

Optionally capture your state on the way out: pass note to append a work note and/or resume_context to overwrite the resume block, so the next session can pick up where you left off.

Inputs: task_id (required), note, resume_context.

update_projectA

Update a project: its name, description, status, or where it sits in the hierarchy. Omitted fields are left exactly as they are. Set parent_project_id to re-parent the project, or to null to DETACH it into a top-level project; a detached project keeps the product it had inherited, so product_id is only needed to move it to a DIFFERENT product. A sub-project inherits its parent's product, so re-parenting under a parent in a different product moves the project there too. The API refuses a move that is circular, crosses accounts, or exceeds the plan's nesting depth or sub-project count, and says which.

Inputs: project_id (required), name, status, product_id, +2 more.

update_taskC

Update task details like title, description, estimates, or acceptance criteria.

Inputs: task_id (required), title, due_date, priority, +15 more.

update_task_statusA

Change the status of a task (todo, in_progress, blocked, done). This triggers a real-time notification to users viewing the project. Note that a task with subtasks or dependencies also has a DERIVED status which can differ from the one set here — see effective_status / status_reason on get_task.

SETTING "done" IS GATED (GH #387). A task holding any subtask that is not done is REFUSED with 403 OPEN_SUBTASKS, naming the blocking children. The bar is "done": a subtask that is todo, in progress or blocked all refuse it for the same reason. This is not advisory and there is no automated way around it — the override is reserved for a human account owner or admin, and an automated identity is refused it explicitly (403 FORCE_NOT_PERMITTED, reason actor_is_agent). So finish the subtasks; do not attempt to force the parent.

Inputs: status (required), task_id (required), blocked_reason.

verify_taskA

Run AI verification: grades this task's acceptance_criteria against the diff of its linked PR (a Haiku relevance pre-screen, then a Sonnet deep review) and PERSISTS the result to ai_completion_score / ai_completion_notes. This is exactly what update_task_status checks when the task's verification_mode is "ai_review" or "all" — call this to satisfy that gate yourself rather than repeatedly hitting 403 VERIFICATION_REQUIRED with no way through it.

COSTS MONEY — DO NOT LOOP THIS. Every call is metered via recordAIUsage and recorded to ai_generations (Sonnet, large PR-diff context) for audit. It runs FREE while your account is within its plan's included monthly verification cap; once over that cap each call is billed as AI credit overage — 2 credits per call on this account. Calling this repeatedly hoping for a higher score spends real credits for no guaranteed gain; if a genuine score lands below the 0.8 gate, that is a finding about the work, not a reason to retry.

GRADES THE DIFF, NOT THE RUNNING SYSTEM. A high score means the PR's code changes look like they satisfy the criteria on paper — it is NOT proof the deployed/running behavior actually works. Never report this score as end-to-end verification; that is a human's job or an independent task-verify sub-agent's, not this tool's.

Requires the task to already have non-empty acceptance_criteria (set via update_task) and a way to see the work: either the task's own github_pr_url (set via update_task) or a pr_url/work_description passed here. Fails with a clear message naming what is missing rather than grading an empty contract — refusal is free, no credits spent.

Inputs: task_id (required), pr_url, work_description.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.7/5.0

    Scored across 36 tools

    Disambiguation4/5

    Most tools map to a distinct resource+action, and the descriptions go out of their way to separate easily-confused pairs (add_comment vs add_work_note, log_time vs stop_working, add_assignee vs update_task's assignee_user_id). The remaining overlaps are mild: resume_working duplicates start_working's resume behavior by the author's own admission, and log_time / quick_log / stop_working all touch time recording, requiring careful reading to pick correctly.

    Naming Consistency5/5

    Nearly every name is snake_case verb_noun with a predictable verb vocabulary (create_/get_/list_/update_/add_/remove_/set_/start_/stop_/pause_/resume_/close_), making resource and action readable at a glance. Deviations like quick_log, report_bug, and update_task_status are still idiomatic and unambiguous within the same convention.

    Tool Count3/5

    36 tools is heavy and sits above the comfortable band, but the domain genuinely spans products, projects, tasks, sprints, work sessions, assignments, verification, and support, so most tools earn a place. There is still consolidation headroom (log_time vs stop_working, resume_working vs start_working, record_verification_feedback), which pushes it to borderline rather than well-scoped.

    Completeness3/5

    Core workflows are covered end to end: product/project/task CRUD-ish surfaces, sprint lifecycle with cadence, measured work sessions, assignments, and AI verification. Notable gaps remain, though: no delete anywhere (tasks, projects, products, sprints), no update_product, no list_comments or way to read a task's comment thread, and no way to list or correct existing time entries other than the initial write.

    Maintenance

    ActivityMaintained
    ResponsivenessMixed handling

    Related MCP Connectors

    Related MCP Servers