forked from zereight/gitlab-mcp
-
Notifications
You must be signed in to change notification settings - Fork 1
refactor(labels): CQRS consolidation - 5 tools → 2 tools #8
Copy link
Copy link
Labels
refactorCode restructuring without behavior changeCode restructuring without behavior change
Description
Summary
Consolidate 5 label tools into 2 CQRS-aligned tools for better MCP client compatibility and cleaner read/write separation.
Current State (5 tools)
| Tool | Type | Description |
|---|---|---|
list_labels |
READ | List labels with filtering |
get_label |
READ | Get single label details |
create_label |
WRITE | Create new label |
update_label |
WRITE | Modify existing label |
delete_label |
WRITE | Remove label |
Target State (2 tools)
browse_labels (Query)
Consolidates all READ operations:
{
action: "list" | "get",
// Common
projectId?: string,
groupId?: string,
// For "list" action
search?: string,
include_ancestor_groups?: boolean,
per_page?: number,
page?: number,
// For "get" action
labelId?: number,
name?: string
}Read-only mode: Always allowed
manage_label (Command)
Consolidates all WRITE operations:
{
action: "create" | "update" | "delete",
// Common
projectId?: string,
groupId?: string,
// For "update" and "delete"
labelId?: number,
name?: string, // Can identify by name
// For "create" and "update"
new_name?: string, // For rename in update
color?: string, // Hex format #RRGGBB
description?: string,
priority?: number
}Read-only mode: Blocked entirely
Implementation Tasks
- Create new
browse_labelshandler with action dispatch - Create new
manage_labelhandler with action dispatch - Update Zod schemas with flat object pattern (see Schema Pattern below)
- Update registry to export new tool names
- Update read-only tools list (only
browse_labels) - Add deprecation warnings for old tool names (optional migration period)
- Remove old handlers after migration
- Update unit tests
- Update integration tests
Schema Pattern (AI-Compatible)
CRITICAL: Per #29, do NOT use
z.discriminatedUnion()- it generatesoneOfat JSON Schema root level which is incompatible with Claude API.
Use flat z.object() with .refine() for conditional validation:
// browse_labels - READ operations
const BrowseLabelsSchema = z.object({
action: z.enum(["list", "get"]),
projectId: z.string().optional(),
groupId: z.string().optional(),
// list-specific (optional)
search: z.string().optional(),
include_ancestor_groups: z.boolean().optional(),
per_page: z.number().optional(),
page: z.number().optional(),
// get-specific (optional but required for "get")
labelId: z.number().optional(),
name: z.string().optional(),
}).refine(
(data) => data.action !== "get" || data.labelId !== undefined || data.name !== undefined,
{ message: "labelId or name is required for 'get' action", path: ["labelId"] }
).refine(
(data) => data.projectId !== undefined || data.groupId !== undefined,
{ message: "Either projectId or groupId is required", path: ["projectId"] }
);
// manage_label - WRITE operations
const ManageLabelSchema = z.object({
action: z.enum(["create", "update", "delete"]),
projectId: z.string().optional(),
groupId: z.string().optional(),
// identification (for update/delete)
labelId: z.number().optional(),
name: z.string().optional(),
// create/update fields
new_name: z.string().optional(),
color: z.string().optional(),
description: z.string().optional(),
priority: z.number().optional(),
}).refine(
(data) => data.action === "create" || data.labelId !== undefined || data.name !== undefined,
{ message: "labelId or name is required for update/delete actions", path: ["labelId"] }
).refine(
(data) => data.action !== "create" || data.name !== undefined,
{ message: "name is required for 'create' action", path: ["name"] }
).refine(
(data) => data.action !== "create" || data.color !== undefined,
{ message: "color is required for 'create' action", path: ["color"] }
);Breaking Changes
| Old Tool | Migration Path |
|---|---|
list_labels |
browse_labels with action: "list" |
get_label |
browse_labels with action: "get" |
create_label |
manage_label with action: "create" |
update_label |
manage_label with action: "update" |
delete_label |
manage_label with action: "delete" |
Testing Requirements
- Unit tests for flat schema validation with refine
- Test each action variant
- Test read-only mode blocks
manage_labelentirely - Integration tests for all operations
- Verify JSON Schema output has NO
oneOf/allOf/anyOfat top level
Acceptance Criteria
- Total tools reduced from 5 to 2
- All existing functionality preserved
- Read-only mode correctly gates write operations
- Clear error messages for invalid action/parameter combinations
- Tool descriptions are agent-friendly
- JSON Schema compatible with Claude API (no oneOf/allOf/anyOf at root)
Related
- refactor(schemas): Replace discriminated unions with flat schemas for Claude API compatibility #29 - AI-compatible schema requirement
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
refactorCode restructuring without behavior changeCode restructuring without behavior change