Skip to content

refactor(variables): CQRS consolidation - 5 tools → 2 tools #9

@polaz

Description

@polaz

Summary

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

Current State (5 tools)

Tool Type Description
list_variables READ List CI/CD variables
get_variable READ Get single variable details
create_variable WRITE Create new variable
update_variable WRITE Modify existing variable
delete_variable WRITE Remove variable

Target State (2 tools)

browse_variables (Query)

Consolidates all READ operations:

{
  action: "list" | "get",
  
  // Common - scope selection
  scope: "project" | "group",
  projectId?: string,        // Required if scope="project"
  groupId?: string,          // Required if scope="group"
  
  // For "get" action
  key: string,               // Variable key name
  filter?: {
    environment_scope?: string  // e.g., "production", "*"
  }
}

Read-only mode: Always allowed

manage_variable (Command)

Consolidates all WRITE operations:

{
  action: "create" | "update" | "delete",
  
  // Common - scope selection
  scope: "project" | "group",
  projectId?: string,
  groupId?: string,
  
  // Variable identification
  key: string,
  
  // For "create" and "update"
  value?: string,
  variable_type?: "env_var" | "file",
  protected?: boolean,
  masked?: boolean,
  raw?: boolean,
  environment_scope?: string,
  description?: string,
  
  // For "delete" with environment scope
  filter?: {
    environment_scope?: string
  }
}

Read-only mode: Blocked entirely

Implementation Tasks

  • Create new browse_variables handler with action dispatch
  • Create new manage_variable handler with action dispatch
  • Update Zod schemas with flat object pattern (see Schema Pattern below)
  • Handle both project and group variable APIs
  • Update registry to export new tool names
  • Update read-only tools list (only browse_variables)
  • Remove old handlers after migration
  • Update unit tests
  • Update integration tests

Schema Pattern (AI-Compatible)

CRITICAL: Per #29, do NOT use z.discriminatedUnion() - it generates oneOf at JSON Schema root level which is incompatible with Claude API.

Use flat z.object() with .refine() for conditional validation:

// browse_variables - READ operations
const BrowseVariablesSchema = z.object({
  action: z.enum(["list", "get"]),
  scope: z.enum(["project", "group"]),
  projectId: z.string().optional(),
  groupId: z.string().optional(),
  // get-specific
  key: z.string().optional(),
  environment_scope: z.string().optional(),
}).refine(
  (data) => data.scope !== "project" || data.projectId !== undefined,
  { message: "projectId is required when scope is 'project'", path: ["projectId"] }
).refine(
  (data) => data.scope !== "group" || data.groupId !== undefined,
  { message: "groupId is required when scope is 'group'", path: ["groupId"] }
).refine(
  (data) => data.action !== "get" || data.key !== undefined,
  { message: "key is required for 'get' action", path: ["key"] }
);

// manage_variable - WRITE operations
const ManageVariableSchema = z.object({
  action: z.enum(["create", "update", "delete"]),
  scope: z.enum(["project", "group"]),
  projectId: z.string().optional(),
  groupId: z.string().optional(),
  key: z.string(),
  // create/update fields
  value: z.string().optional(),
  variable_type: z.enum(["env_var", "file"]).optional(),
  protected: z.boolean().optional(),
  masked: z.boolean().optional(),
  raw: z.boolean().optional(),
  environment_scope: z.string().optional(),
  description: z.string().optional(),
}).refine(
  (data) => data.scope !== "project" || data.projectId !== undefined,
  { message: "projectId is required when scope is 'project'", path: ["projectId"] }
).refine(
  (data) => data.scope !== "group" || data.groupId !== undefined,
  { message: "groupId is required when scope is 'group'", path: ["groupId"] }
).refine(
  (data) => data.action !== "create" || data.value !== undefined,
  { message: "value is required for 'create' action", path: ["value"] }
);

Breaking Changes

Old Tool Migration Path
list_variables browse_variables with action: "list"
get_variable browse_variables with action: "get"
create_variable manage_variable with action: "create"
update_variable manage_variable with action: "update"
delete_variable manage_variable with action: "delete"

Security Considerations

  • Variable values may be masked - handle appropriately in responses
  • Protected variables require specific branch permissions
  • Environment-scoped variables need proper scope handling

Testing Requirements

  • Unit tests for flat schema validation with refine
  • Test project-level variables
  • Test group-level variables
  • Test environment-scoped variables
  • Test read-only mode blocks manage_variable entirely
  • Integration tests for all operations
  • Verify JSON Schema output has NO oneOf/allOf/anyOf at top level

Acceptance Criteria

  • Total tools reduced from 5 to 2
  • All existing functionality preserved
  • Both project and group variables supported
  • Environment scope filtering works correctly
  • Read-only mode correctly gates write operations
  • Clear error messages for invalid action/parameter combinations
  • JSON Schema compatible with Claude API (no oneOf/allOf/anyOf at root)

Related

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