Skip to content

refactor(workitems): CQRS consolidation - 5 tools → 2 tools #11

@polaz

Description

@polaz

Summary

Consolidate 5 work items tools into 2 CQRS-aligned tools for better MCP client compatibility and cleaner read/write separation.

Current State (5 tools)

Tool Type Description
list_work_items READ List work items with filtering
get_work_item READ Get single work item with all widgets
create_work_item WRITE Create new work item
update_work_item WRITE Modify work item properties/widgets
delete_work_item WRITE Remove work item

Target State (2 tools)

browse_work_items (Query)

Consolidates all READ operations:

{
  action: "list" | "get",
  
  // Common
  namespacePath: string,     // Group or project path
  
  // For "list" action
  types?: WorkItemTypeEnum[],  // EPIC, ISSUE, TASK, etc.
  state?: "opened" | "closed" | "all",
  search?: string,
  authorUsername?: string,
  assigneeUsernames?: string[],
  labelNames?: string[],
  milestoneTitle?: string,
  iids?: string[],
  sort?: string,
  first?: number,
  after?: string,
  
  // For "get" action
  id: string,                // Work item global ID or IID
  iid?: string               // Alternative: use IID with namespacePath
}

Read-only mode: Always allowed

manage_work_item (Command)

Consolidates all WRITE operations:

{
  action: "create" | "update" | "delete",
  
  // Common
  namespacePath: string,
  
  // For "update" and "delete"
  id?: string,               // Work item ID
  
  // For "create"
  workItemType?: WorkItemTypeEnum,
  
  // For "create" and "update" - core fields
  title?: string,
  confidential?: boolean,
  
  // Widget updates
  descriptionWidget?: { description: string },
  assigneesWidget?: { assigneeIds: string[] },
  labelsWidget?: { 
    addLabelIds?: string[],
    removeLabelIds?: string[] 
  },
  milestoneWidget?: { milestoneId: string | null },
  startAndDueDateWidget?: {
    startDate?: string,
    dueDate?: string
  },
  hierarchyWidget?: {
    parentId?: string | null,
    childrenIds?: string[]
  },
  
  // State change
  stateEvent?: "CLOSE" | "REOPEN"
}

Read-only mode: Blocked entirely

Implementation Tasks

  • Create new browse_work_items handler with action dispatch
  • Create new manage_work_item handler with action dispatch
  • Update Zod schemas with discriminated union pattern
  • Preserve GraphQL-based implementation for work items
  • Handle dynamic work item type discovery
  • Update registry to export new tool names
  • Update read-only tools list (only browse_work_items)
  • Remove old handlers after migration
  • Update unit tests
  • Update integration tests

Schema Pattern

const BrowseWorkItemsSchema = z.discriminatedUnion("action", [
  z.object({
    action: z.literal("list"),
    namespacePath: z.string(),
    types: z.array(WorkItemTypeEnumSchema).optional(),
    state: z.enum(["opened", "closed", "all"]).optional(),
    // ... list-specific params
  }),
  z.object({
    action: z.literal("get"),
    namespacePath: z.string(),
    id: z.string().optional(),
    iid: z.string().optional(),
  }).refine(
    (data) => data.id || data.iid,
    { message: "Either id or iid is required" }
  ),
]);

Breaking Changes

Old Tool Migration Path
list_work_items browse_work_items with action: "list"
get_work_item browse_work_items with action: "get"
create_work_item manage_work_item with action: "create"
update_work_item manage_work_item with action: "update"
delete_work_item manage_work_item with action: "delete"

GraphQL Considerations

Work Items use GraphQL API exclusively. The refactored tools must:

  • Preserve dynamic widget inclusion based on tier
  • Maintain work item type discovery
  • Handle GROUP vs PROJECT level restrictions (Epics vs Issues)
  • Support all 26 widget types

Feature Flag

Existing USE_WORKITEMS flag continues to control entire work items entity visibility.

Testing Requirements

  • Unit tests for discriminated union schema validation
  • Test all work item types (Epic, Issue, Task, etc.)
  • Test widget operations (assignees, labels, milestones)
  • Test hierarchy operations (parent/child)
  • Test read-only mode blocks manage_work_item entirely
  • Integration tests against real GitLab instance

Acceptance Criteria

  • Total tools reduced from 5 to 2
  • All existing functionality preserved
  • All work item types supported
  • All widgets properly handled
  • GROUP/PROJECT level restrictions respected
  • Read-only mode correctly gates write operations
  • Clear error messages for invalid action/parameter combinations

Metadata

Metadata

Assignees

No one assigned

    Labels

    refactorCode restructuring without behavior change

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions