Skip to content

feat: replace work item relations with dedicated dependency and custom relation tools#148

Open
akhil-vamshi-konam wants to merge 8 commits into
mainfrom
chore-custom-relations
Open

feat: replace work item relations with dedicated dependency and custom relation tools#148
akhil-vamshi-konam wants to merge 8 commits into
mainfrom
chore-custom-relations

Conversation

@akhil-vamshi-konam

@akhil-vamshi-konam akhil-vamshi-konam commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Description:

Summary

Replaces the legacy work_item_relations tools with three purpose-built modules that map to the new dedicated API endpoints:

  • work_item_relation_definitions (4 tools) — CRUD for workspace-level relation definitions; these define the outward/inward labels used by custom relations
  • work_item_dependencies (3 tools) — list, create, remove across all six hardcoded directions (blocking / blocked_by / start_before / start_after / finish_before / finish_after); uses /relation-dependencies/
  • work_item_custom_relations (3 tools) — list, create, remove definition-based relations with directionality via relation_definition_type; uses /work-item-relations/

Removes work_item_relations.py — the legacy /relations/ endpoint it wrapped is broken for custom types and superseded by the above.

Type of Change

  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)

Test Scenarios

  • list_work_item_relation_definitions — returns all definitions (default + custom)
  • create_work_item_dependency with each of the six relation types
  • list_work_item_dependencies — verifies grouping by direction and reverse-side visibility
  • remove_work_item_dependency — confirms removal from both sides
  • create_work_item_custom_relation outward + inward directions
  • list_work_item_custom_relations — verifies dict keyed by definition labels
  • remove_work_item_custom_relation — confirms clearing from both sides

Summary by CodeRabbit

  • New Features

    • Added tools for managing workspace custom relation definitions (list, create, update, delete).
    • Consolidated work item relations interface supporting both built-in dependencies and custom workspace-defined relations.
  • Documentation

    • Added documentation for work item relation definitions CRUD operations.
  • Improvements

    • Clarified search functionality to match against work item name, sequence ID, and project identifier.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a9c4f06d-ac58-4ff0-98cf-01fed2f7d479

📥 Commits

Reviewing files that changed from the base of the PR and between bd920fd and 9fd7787.

📒 Files selected for processing (3)
  • README.md
  • plane_mcp/tools/work_items.py
  • tests/test_integration.py
✅ Files skipped from review due to trivial changes (2)
  • plane_mcp/tools/work_items.py
  • README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_integration.py

📝 Walkthrough

Walkthrough

Adds a new work_item_relation_definitions.py module exposing four MCP tools for workspace relation definition CRUD. Rewrites work_item_relations.py to split list/create/remove across built-in dependency and custom relation backends. Simplifies SERVER_INSTRUCTIONS to epics-only, updates a search docstring, and extends integration tests and README accordingly.

Changes

Work Item Relation Definitions and Dual-Mode Relation Tools

Layer / File(s) Summary
Tool registry wiring
plane_mcp/tools/__init__.py
Imports register_work_item_relation_definition_tools and calls it inside register_tools() alongside the existing relation tools registration.
Work item relation definition CRUD tools
plane_mcp/tools/work_item_relation_definitions.py, tests/test_integration.py, README.md
New module exposes four MCP tools: paginated list with optional is_default/is_active filters returning both built_in_dependencies and custom_definitions; create, update, and delete workspace relation definitions via client.work_item_relation_definitions. Integration tests add the four tool names to EXPECTED_TOOLS; README adds a "Work Item Relation Definitions" subsection.
Dual-mode work item relation tools rewrite
plane_mcp/tools/work_item_relations.py
list_work_item_relations now returns {"dependencies": ..., "custom": ...} from separate client calls. create_work_item_relation supports two mutually exclusive modes: relation_type (validated dependency) or relation_definition_id+relation_definition_label (custom), routing to the respective client endpoint and raising ValueError otherwise. remove_work_item_relation accepts related_work_item_id and is_dependency to choose the deletion endpoint.
SERVER_INSTRUCTIONS simplification and search docstring update
plane_mcp/instructions.py, plane_mcp/tools/work_items.py
SERVER_INSTRUCTIONS is rewritten as a single epics-only inline string, removing prior WORK_ITEM_TYPE_SCOPING_INSTRUCTIONS, EPIC_INSTRUCTIONS, and INITIATIVE_INSTRUCTIONS constants. The search_work_items docstring is updated to state that query matches name, sequence id, and project identifier only.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • makeplane/plane-mcp-server#150: Modifies plane_mcp/instructions.py by rewriting SERVER_INSTRUCTIONS to a single consolidated "Epics"-only instruction string, directly overlapping with this PR's changes to the same variable.
  • makeplane/plane-mcp-server#127: Touches the create_work_item_relation tool in plane_mcp/tools/work_item_relations.py, which this PR rewrites with a dual-mode routing implementation.

Suggested reviewers

  • Prashant-Surya

Poem

🐇 Hop, hop, relations defined!
Custom and built-in, neatly aligned,
Two modes for creating, one flag to remove,
Instructions trimmed down, epics approve.
The rabbit checks tools — all four are there,
A tidy PR handled with care! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: replacing a legacy work item relations system with dedicated dependency and custom relation tools, which is the core objective and the most significant architectural change across the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 chore-custom-relations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@akhil-vamshi-konam akhil-vamshi-konam marked this pull request as ready for review June 15, 2026 06:38
@akhil-vamshi-konam akhil-vamshi-konam marked this pull request as draft June 16, 2026 03:41
@akhil-vamshi-konam akhil-vamshi-konam marked this pull request as ready for review June 16, 2026 06:27

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plane_mcp/tools/work_item_relation_definitions.py (1)

138-142: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add missing Returns sections to tool docstrings.

Both modified tool docstrings include Args but omit Returns, which violates the tool docstring standard.

  • plane_mcp/tools/work_item_relation_definitions.py#L138-L142: add a Returns section (even if it is explicit None/ack behavior).
  • plane_mcp/tools/work_item_relations.py#L136-L150: add a Returns section documenting the remove tool’s output contract.
    As per coding guidelines, “Tool docstrings must include Args and Returns sections.”
🤖 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 `@plane_mcp/tools/work_item_relation_definitions.py` around lines 138 - 142,
Add missing `Returns` sections to both tool docstrings to comply with the tool
docstring standard. In `plane_mcp/tools/work_item_relation_definitions.py` lines
138-142, add a `Returns` section to the docstring for the delete method
documenting what it returns (e.g., None or acknowledgment behavior). In
`plane_mcp/tools/work_item_relations.py` lines 136-150, add a `Returns` section
to the docstring for the remove tool documenting its output contract. Both
sections should clearly describe the return value or behavior to ensure complete
documentation per coding guidelines.

Source: Coding guidelines

🤖 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 `@plane_mcp/tools/work_item_relations.py`:
- Around line 96-127: The function currently silently chooses dependency
creation if relation_type is provided, even if custom relation fields
(relation_definition_id and relation_definition_label) are also passed. Since
these modes are mutually exclusive per the API contract, add early validation
before the existing if statements to check if both relation_type and the custom
relation fields are provided together. If this ambiguous combination is
detected, raise a ValueError explaining that these parameters are mutually
exclusive and the caller must choose only one mode.

In `@tests/test_integration.py`:
- Around line 193-200: The extract_result call for the epics_result is
incorrectly assuming the payload is wrapped in a dict with a "results" key, but
list_work_items returns the item list directly. Remove the ["results"] indexing
from the epics assignment where extract_result(epics_result) is called, so that
epics is assigned the extracted list directly rather than attempting to access a
non-existent "results" key within it.
- Around line 142-158: Before creating a new workspace-level Epic type via the
create_work_item_type call, first check if an Epic type already exists in the
workspace_types list that was just retrieved. After extracting the
workspace_types from the result, search through that list for an existing type
with the name "Epic". If found, use that existing type for epic_type instead of
creating a duplicate. Only call create_work_item_type if no Epic type already
exists in the workspace_types. This prevents duplicate-name errors and avoids
unnecessary type creation when the type is already available.

---

Outside diff comments:
In `@plane_mcp/tools/work_item_relation_definitions.py`:
- Around line 138-142: Add missing `Returns` sections to both tool docstrings to
comply with the tool docstring standard. In
`plane_mcp/tools/work_item_relation_definitions.py` lines 138-142, add a
`Returns` section to the docstring for the delete method documenting what it
returns (e.g., None or acknowledgment behavior). In
`plane_mcp/tools/work_item_relations.py` lines 136-150, add a `Returns` section
to the docstring for the remove tool documenting its output contract. Both
sections should clearly describe the return value or behavior to ensure complete
documentation per coding guidelines.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: f03418e5-4715-4273-8d89-b804cb6dc997

📥 Commits

Reviewing files that changed from the base of the PR and between 3abec05 and bd920fd.

📒 Files selected for processing (9)
  • README.md
  • plane_mcp/instructions.py
  • plane_mcp/tools/__init__.py
  • plane_mcp/tools/initiatives.py
  • plane_mcp/tools/work_item_relation_definitions.py
  • plane_mcp/tools/work_item_relations.py
  • plane_mcp/tools/work_item_types.py
  • plane_mcp/tools/work_items.py
  • tests/test_integration.py
✅ Files skipped from review due to trivial changes (2)
  • plane_mcp/tools/work_item_types.py
  • README.md

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plane_mcp/tools/work_item_relation_definitions.py (1)

138-142: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add missing Returns sections to tool docstrings.

Both modified tool docstrings include Args but omit Returns, which violates the tool docstring standard.

  • plane_mcp/tools/work_item_relation_definitions.py#L138-L142: add a Returns section (even if it is explicit None/ack behavior).
  • plane_mcp/tools/work_item_relations.py#L136-L150: add a Returns section documenting the remove tool’s output contract.
    As per coding guidelines, “Tool docstrings must include Args and Returns sections.”
🤖 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 `@plane_mcp/tools/work_item_relation_definitions.py` around lines 138 - 142,
Add missing `Returns` sections to both tool docstrings to comply with the tool
docstring standard. In `plane_mcp/tools/work_item_relation_definitions.py` lines
138-142, add a `Returns` section to the docstring for the delete method
documenting what it returns (e.g., None or acknowledgment behavior). In
`plane_mcp/tools/work_item_relations.py` lines 136-150, add a `Returns` section
to the docstring for the remove tool documenting its output contract. Both
sections should clearly describe the return value or behavior to ensure complete
documentation per coding guidelines.

Source: Coding guidelines

🤖 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 `@plane_mcp/tools/work_item_relations.py`:
- Around line 96-127: The function currently silently chooses dependency
creation if relation_type is provided, even if custom relation fields
(relation_definition_id and relation_definition_label) are also passed. Since
these modes are mutually exclusive per the API contract, add early validation
before the existing if statements to check if both relation_type and the custom
relation fields are provided together. If this ambiguous combination is
detected, raise a ValueError explaining that these parameters are mutually
exclusive and the caller must choose only one mode.

In `@tests/test_integration.py`:
- Around line 193-200: The extract_result call for the epics_result is
incorrectly assuming the payload is wrapped in a dict with a "results" key, but
list_work_items returns the item list directly. Remove the ["results"] indexing
from the epics assignment where extract_result(epics_result) is called, so that
epics is assigned the extracted list directly rather than attempting to access a
non-existent "results" key within it.
- Around line 142-158: Before creating a new workspace-level Epic type via the
create_work_item_type call, first check if an Epic type already exists in the
workspace_types list that was just retrieved. After extracting the
workspace_types from the result, search through that list for an existing type
with the name "Epic". If found, use that existing type for epic_type instead of
creating a duplicate. Only call create_work_item_type if no Epic type already
exists in the workspace_types. This prevents duplicate-name errors and avoids
unnecessary type creation when the type is already available.

---

Outside diff comments:
In `@plane_mcp/tools/work_item_relation_definitions.py`:
- Around line 138-142: Add missing `Returns` sections to both tool docstrings to
comply with the tool docstring standard. In
`plane_mcp/tools/work_item_relation_definitions.py` lines 138-142, add a
`Returns` section to the docstring for the delete method documenting what it
returns (e.g., None or acknowledgment behavior). In
`plane_mcp/tools/work_item_relations.py` lines 136-150, add a `Returns` section
to the docstring for the remove tool documenting its output contract. Both
sections should clearly describe the return value or behavior to ensure complete
documentation per coding guidelines.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: f03418e5-4715-4273-8d89-b804cb6dc997

📥 Commits

Reviewing files that changed from the base of the PR and between 3abec05 and bd920fd.

📒 Files selected for processing (9)
  • README.md
  • plane_mcp/instructions.py
  • plane_mcp/tools/__init__.py
  • plane_mcp/tools/initiatives.py
  • plane_mcp/tools/work_item_relation_definitions.py
  • plane_mcp/tools/work_item_relations.py
  • plane_mcp/tools/work_item_types.py
  • plane_mcp/tools/work_items.py
  • tests/test_integration.py
✅ Files skipped from review due to trivial changes (2)
  • plane_mcp/tools/work_item_types.py
  • README.md
🛑 Comments failed to post (3)
plane_mcp/tools/work_item_relations.py (1)

96-127: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject ambiguous create mode inputs.

If callers pass relation_type and custom relation fields together, dependency creation is chosen silently. This should fail fast because the API contract is mutually exclusive.

Suggested fix
         client, workspace_slug = get_plane_client_context()
+        if relation_type and (relation_definition_id or relation_definition_label):
+            raise ValueError(
+                "Use either relation_type (dependency) or relation_definition_id + "
+                "relation_definition_label (custom), not both."
+            )
         if relation_type:
             if relation_type not in _DEPENDENCY_TYPES:
                 raise ValueError(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        if relation_type and (relation_definition_id or relation_definition_label):
            raise ValueError(
                "Use either relation_type (dependency) or relation_definition_id + "
                "relation_definition_label (custom), not both."
            )
        if relation_type:
            if relation_type not in _DEPENDENCY_TYPES:
                raise ValueError(
                    f"relation_type must be one of {list(_DEPENDENCY_TYPES)}. For any "
                    "other relationship, pass relation_definition_id + "
                    "relation_definition_label from list_work_item_relation_definitions."
                )
            return client.work_items.dependencies.create(
                workspace_slug=workspace_slug,
                project_id=project_id,
                work_item_id=work_item_id,
                data=CreateWorkItemDependency(
                    relation_type=relation_type,  # type: ignore[arg-type]
                    work_item_ids=work_item_ids,
                ),
            )
        if relation_definition_id and relation_definition_label:
            return client.work_items.custom_relations.create(
                workspace_slug=workspace_slug,
                project_id=project_id,
                work_item_id=work_item_id,
                data=CreateWorkItemCustomRelation(
                    relation_definition_id=relation_definition_id,
                    relation_definition_type=relation_definition_label,
                    work_item_ids=work_item_ids,
                ),
            )
        raise ValueError(
            "Provide relation_type for a built-in dependency, or "
            "relation_definition_id + relation_definition_label for a custom "
            "relation (call list_work_item_relation_definitions to find one)."
        )
🤖 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 `@plane_mcp/tools/work_item_relations.py` around lines 96 - 127, The function
currently silently chooses dependency creation if relation_type is provided,
even if custom relation fields (relation_definition_id and
relation_definition_label) are also passed. Since these modes are mutually
exclusive per the API contract, add early validation before the existing if
statements to check if both relation_type and the custom relation fields are
provided together. If this ambiguous combination is detected, raise a ValueError
explaining that these parameters are mutually exclusive and the caller must
choose only one mode.
tests/test_integration.py (2)

142-158: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid duplicate “Epic” type creation in workspace scope.

The fallback path creates a new workspace-level Epic whenever workspace_types is non-empty, without first checking whether Epic already exists there. That can trigger duplicate-name errors or unnecessary type churn.

Suggested fix
-            if workspace_types:
-                new_type_result = await client.call_tool("create_work_item_type", {"name": "Epic"})
-                epic_type = extract_result(new_type_result)
-                created_workspace_epic_type = True
-                await client.call_tool(
-                    "import_work_item_types_to_project",
-                    {"project_id": project_id, "work_item_type_ids": [epic_type["id"]]},
-                )
+            if workspace_types:
+                epic_type = next((t for t in workspace_types if t.get("name", "").lower() == "epic"), None)
+                if epic_type is None:
+                    new_type_result = await client.call_tool("create_work_item_type", {"name": "Epic"})
+                    epic_type = extract_result(new_type_result)
+                    created_workspace_epic_type = True
+                await client.call_tool(
+                    "import_work_item_types_to_project",
+                    {"project_id": project_id, "work_item_type_ids": [epic_type["id"]]},
+                )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

        if epic_type is None:
            workspace_types_result = await client.call_tool("list_work_item_types", {})
            workspace_types = extract_result(workspace_types_result)
            if workspace_types:
                epic_type = next((t for t in workspace_types if t.get("name", "").lower() == "epic"), None)
                if epic_type is None:
                    new_type_result = await client.call_tool("create_work_item_type", {"name": "Epic"})
                    epic_type = extract_result(new_type_result)
                    created_workspace_epic_type = True
                await client.call_tool(
                    "import_work_item_types_to_project",
                    {"project_id": project_id, "work_item_type_ids": [epic_type["id"]]},
                )
            else:
                new_type_result = await client.call_tool(
                    "create_work_item_type", {"name": "Epic", "project_id": project_id}
                )
                epic_type = extract_result(new_type_result)
🤖 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 `@tests/test_integration.py` around lines 142 - 158, Before creating a new
workspace-level Epic type via the create_work_item_type call, first check if an
Epic type already exists in the workspace_types list that was just retrieved.
After extracting the workspace_types from the result, search through that list
for an existing type with the name "Epic". If found, use that existing type for
epic_type instead of creating a duplicate. Only call create_work_item_type if no
Epic type already exists in the workspace_types. This prevents duplicate-name
errors and avoids unnecessary type creation when the type is already available.

193-200: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use list_work_items result as a list, not ["results"].

Line 199 assumes a wrapped payload shape and can fail with TypeError/KeyError when the tool returns the list directly.
Based on learnings, list endpoints should return only response.results (the item list), not pagination metadata wrappers.

Suggested fix
-        epics = extract_result(epics_result)["results"]
+        epics = extract_result(epics_result)
🤖 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 `@tests/test_integration.py` around lines 193 - 200, The extract_result call
for the epics_result is incorrectly assuming the payload is wrapped in a dict
with a "results" key, but list_work_items returns the item list directly. Remove
the ["results"] indexing from the epics assignment where
extract_result(epics_result) is called, so that epics is assigned the extracted
list directly rather than attempting to access a non-existent "results" key
within it.

Source: Learnings

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