Skip to content

feat(labels): MCP list/update/delete + project-scope enforcement on attach - #205

Merged
getappz merged 3 commits into
masterfrom
feat/label-mcp-actions
Jul 16, 2026
Merged

feat(labels): MCP list/update/delete + project-scope enforcement on attach#205
getappz merged 3 commits into
masterfrom
feat/label-mcp-actions

Conversation

@getappz

@getappz getappz commented Jul 16, 2026

Copy link
Copy Markdown
Owner

What

Extends the label subsystem (Plane-modeled) in the task-management backend:

(a) MCP label tool — full CRUD

  • Adds list, update, delete actions (was create-only).
  • create now passes parent_id (nesting/groups) and sort_order through — previously hardcoded None, so those schema columns were unreachable via MCP.

(b) Attach-scope enforcement

  • item::add_label now rejects a label that isn't in the item's scope: a project-scoped label must share the item's project; a workspace-level label (project_id NULL) must share the item's workspace.
  • Enforced in the backend, so it guards both the add_label action and item.create's label_ids path (which routes through add_label).
  • New Error::Validation variant → MCP invalid_params.
  • Mirrors Plane's serializer-level project-membership check, which leanstack previously lacked (any label_id could be cross-attached).

(c) sort_order auto-append

  • New labels default to max(sort_order) + 10000 within their scope (65535 when empty), matching Plane's Label.save(). Explicit sort_order is still respected.

Tests

  • agentflare-backend: 67 lib tests pass (+3: cross-project reject, workspace-level accept, auto-append).
  • main-bin: 475 tests pass (+3 MCP: create/list/update/delete lifecycle, update-requires-id, cross-project add_label rejection).
  • clippy -D warnings clean on agentflare-backend.

Note

Pre-existing clippy -D warnings failures in ponytail and agentflare-caveman (newer clippy pedantic lints, untouched crates) are unrelated to this change.

Summary by CodeRabbit

  • New Features
    • Added full label management via the MCP label tool: create, list, update, and delete.
    • Label create/update now supports nesting (parent_id) and manual ordering (sort_order).
    • Auto-assigned label ordering now advances relative to existing labels in the same scope.
  • Bug Fixes
    • Enforced label scoping to prevent attaching or mutating labels across unrelated projects/workspaces.
    • Improved error handling by returning caller-fixable validation parameters for invalid transitions.
    • Added end-to-end test coverage for label CRUD and scope/ID validation.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e3cfe430-bb2a-4f26-ab69-b530f9a99854

📥 Commits

Reviewing files that changed from the base of the PR and between 53f9c9c and c4798bf.

📒 Files selected for processing (1)
  • crates/agentflare-backend/src/item.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/agentflare-backend/src/item.rs

📝 Walkthrough

Walkthrough

Adds backend label scope validation, per-scope automatic sort ordering, and MCP label create/list/update/delete operations. Tests cover ordering, CRUD roundtrips, missing identifiers, and cross-project or workspace rejection.

Changes

Label management

Layer / File(s) Summary
Label scope validation
crates/agentflare-backend/src/error.rs, crates/agentflare-backend/src/item.rs
Adds Error::Validation and prevents attaching labels from incompatible projects or workspaces, with coverage for accepted and rejected workspace-level labels.
Automatic label ordering
crates/agentflare-backend/src/label.rs
Automatically assigns omitted sort orders using the maximum active order in the applicable project or workspace scope, while preserving explicit values.
MCP label CRUD and scope checks
src/mcp_server.rs
Expands the label request and tool to support create, list, update, and delete, forwards nesting and ordering fields, validates label ownership, and maps invalid transitions and validation failures to MCP invalid-parameter responses. Tests cover CRUD, required identifiers, and foreign-scope rejection.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MCPClient
  participant mcp_server
  participant LabelBackend
  participant Database
  MCPClient->>mcp_server: Submit label action
  mcp_server->>LabelBackend: Validate scope and execute operation
  LabelBackend->>Database: Read or persist label state
  Database-->>LabelBackend: Return operation result
  LabelBackend-->>mcp_server: Return label or validation error
  mcp_server-->>MCPClient: Return MCP response
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main label CRUD and scope-enforcement changes.
Description check ✅ Passed The description covers the required summary, test results, and reviewer notes, even if the template headings are renamed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/label-mcp-actions

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/skills/pm/reference/rubric.md:
- Around line 11-12: Update the effort definition in the rubric so the value
used by the RICE calculation is 1 for tiny work and 5 for large work, either by
reversing the documented scale or by converting existing estimates before
division. Preserve the UNESTIMATED behavior for unknown effort.

In `@crates/agentflare-backend/src/label.rs`:
- Around line 68-90: Update create and next_sort_order so automatic sort-order
calculation and label insertion occur within a single SQLite write transaction,
preventing concurrent creates from observing the same maximum; preserve
caller-supplied sort_order behavior and commit the transaction before returning
the created Label. Add a regression test that performs concurrent label creation
and verifies automatic sort orders are uniquely and deterministically allocated.

In `@scripts/pm-fixtures.sh`:
- Around line 20-29: Update the fixture updates in the bd function and its
callers so they target exact seeded item IDs or an explicit fixture marker
rather than mutable names, while retaining the project constraint and adding
deleted_at IS NULL. Ensure each intended FIX fixture is updated without
modifying real or soft-deleted items.
- Around line 25-29: Update the fixture setup around the bd function to validate
that the expected active rows exist before applying updates, and fail the script
when any required fixture row is missing. Ensure zero-row updates cannot produce
a successful “backdated” result, while preserving the existing date assignments
for FIX-08, FIX-09, FIX-05, FIX-06, and FIX-03.

In `@src/mcp_server.rs`:
- Around line 2419-2432: The label update and delete branches in the MCP request
handler must enforce the resolved project scope before mutating by ID. Load each
label by the supplied ID, verify its project matches the resolved project used
by create/list, and only then call `agentflare_backend::label::update` or
`delete`; return the existing not-found or invalid-parameter error for
mismatches. Add tests covering cross-project update and delete attempts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f61cf783-3dce-4077-ba5b-e53cbb754a21

📥 Commits

Reviewing files that changed from the base of the PR and between bd67d87 and 3346608.

📒 Files selected for processing (8)
  • .claude/skills/pm/SKILL.md
  • .claude/skills/pm/reference/read-recipe.md
  • .claude/skills/pm/reference/rubric.md
  • crates/agentflare-backend/src/error.rs
  • crates/agentflare-backend/src/item.rs
  • crates/agentflare-backend/src/label.rs
  • scripts/pm-fixtures.sh
  • src/mcp_server.rs

Comment thread .claude/skills/pm/reference/rubric.md Outdated
Comment on lines +11 to +12
- Effort — size. From a `size:S|M|L` label or an estimate in the body.
1 = large/expensive … 5 = tiny. If unknown, mark UNESTIMATED (see below).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Invert the effort scale or formula.

RICE divides by Effort, but the stated scale gives large work 1 and tiny work 5; this ranks larger work above otherwise identical tiny work. Define effort as 1=tiny through 5=large, or convert the value before division.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/pm/reference/rubric.md around lines 11 - 12, Update the
effort definition in the rubric so the value used by the RICE calculation is 1
for tiny work and 5 for large work, either by reversing the documented scale or
by converting existing estimates before division. Preserve the UNESTIMATED
behavior for unknown effort.

Comment thread crates/agentflare-backend/src/label.rs Outdated
Comment thread scripts/pm-fixtures.sh Outdated
Comment on lines +20 to +29
# Guard: refuse if any FIX-* row lives outside this project (name collision).
STRAY=$(sqlite3 "$DB" "SELECT count(*) FROM items WHERE name LIKE 'FIX-%' AND project_id<>'$PID';")
[ "$STRAY" = "0" ] || { echo "refusing: $STRAY FIX-* items outside project $PID" >&2; exit 1; }

now=$(date +%s); day=86400
bd() { sqlite3 "$DB" "UPDATE items SET updated_at=$1 WHERE project_id='$PID' AND name IN ($2);"; }
bd $((now-25*day)) "'FIX-08','FIX-09'" # stale backlog >14d
bd $((now-3*day)) "'FIX-05'" # completed this week
bd $((now-10*day)) "'FIX-06'" # completed prior week
bd $((now-20*day)) "'FIX-03'" # started but stuck

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not target fixture rows by mutable names alone.

Line 25 updates every matching item in the project; the schema does not enforce unique item names, and the predicate also omits deleted_at IS NULL. A real item named FIX-05 or a soft-deleted fixture can therefore be modified, contradicting the script’s safety guarantee. Use exact seeded item IDs or an explicit fixture marker, and at minimum exclude deleted rows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/pm-fixtures.sh` around lines 20 - 29, Update the fixture updates in
the bd function and its callers so they target exact seeded item IDs or an
explicit fixture marker rather than mutable names, while retaining the project
constraint and adding deleted_at IS NULL. Ensure each intended FIX fixture is
updated without modifying real or soft-deleted items.

Comment thread scripts/pm-fixtures.sh Outdated
Comment on lines +25 to +29
bd() { sqlite3 "$DB" "UPDATE items SET updated_at=$1 WHERE project_id='$PID' AND name IN ($2);"; }
bd $((now-25*day)) "'FIX-08','FIX-09'" # stale backlog >14d
bd $((now-3*day)) "'FIX-05'" # completed this week
bd $((now-10*day)) "'FIX-06'" # completed prior week
bd $((now-20*day)) "'FIX-03'" # started but stuck

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fail when expected fixture rows are missing.

Each UPDATE can affect zero rows while the script still exits successfully and prints “backdated.” Preflight or validate the expected active-row counts so incomplete PM fixtures cannot be mistaken for a successful setup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/pm-fixtures.sh` around lines 25 - 29, Update the fixture setup around
the bd function to validate that the expected active rows exist before applying
updates, and fail the script when any required fixture row is missing. Ensure
zero-row updates cannot produce a successful “backdated” result, while
preserving the existing date assignments for FIX-08, FIX-09, FIX-05, FIX-06, and
FIX-03.

Comment thread src/mcp_server.rs
getappz added 2 commits July 16, 2026 13:39
…ct scope on attach

- label MCP tool: add list/update/delete actions; pass parent_id/sort_order on create
- item::add_label: reject labels outside the item's project/workspace scope,
  guarding both the add_label action and item.create's label_ids path;
  new Error::Validation maps to MCP invalid_params
- label::create: auto-append sort_order (max+10000 per scope) when unset, mirroring Plane
…cation

Address CodeRabbit review on #205:
- MCP label update/delete now verify the label belongs to the resolved
  project before mutating by ID (closes a cross-project mutation hole)
- label::create allocates auto-append sort_order and inserts in one
  transaction so concurrent creates can't observe the same MAX
@getappz
getappz force-pushed the feat/label-mcp-actions branch from 3346608 to 53f9c9c Compare July 16, 2026 08:12

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/agentflare-backend/src/item.rs (1)

791-831: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Test rejection of a workspace-level label from another workspace.

This only exercises project_id: None when workspace IDs match. Add a workspace-level label from test2, attach it to the test1 item, and assert Validation plus no persisted association.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/agentflare-backend/src/item.rs` around lines 791 - 831, The test
add_label_accepts_workspace_level_label_in_same_workspace only covers a valid
same-workspace label. Extend it by creating a workspace-level label associated
with test2, attempting to attach it to the existing test1 item, and asserting
the operation returns Validation; then verify list_labels still contains no
persisted association for that rejected label.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/agentflare-backend/src/item.rs`:
- Around line 372-375: Update the validation error in the !in_scope branch of
the item label-scope check to use scope-neutral wording, such as reporting that
the label is outside the item’s scope, so both project and workspace mismatches
are accurately described.

---

Nitpick comments:
In `@crates/agentflare-backend/src/item.rs`:
- Around line 791-831: The test
add_label_accepts_workspace_level_label_in_same_workspace only covers a valid
same-workspace label. Extend it by creating a workspace-level label associated
with test2, attempting to attach it to the existing test1 item, and asserting
the operation returns Validation; then verify list_labels still contains no
persisted association for that rejected label.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c2430ee-390e-482b-8c4a-9c4d1ed3cd63

📥 Commits

Reviewing files that changed from the base of the PR and between 3346608 and 53f9c9c.

📒 Files selected for processing (4)
  • crates/agentflare-backend/src/error.rs
  • crates/agentflare-backend/src/item.rs
  • crates/agentflare-backend/src/label.rs
  • src/mcp_server.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • crates/agentflare-backend/src/error.rs
  • crates/agentflare-backend/src/label.rs
  • src/mcp_server.rs

Comment thread crates/agentflare-backend/src/item.rs
…tral error msg

Address CodeRabbit re-review: make the add_label scope-mismatch message
scope-neutral (fires for workspace mismatches too) and cover the
workspace-level-label-from-another-workspace rejection path.
@getappz
getappz merged commit 0905ae4 into master Jul 16, 2026
21 of 23 checks passed
@getappz
getappz deleted the feat/label-mcp-actions branch July 16, 2026 08:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant