Skip to content

refactor(pipelines): CQRS consolidation - 12 tools → 3 tools #14

@polaz

Description

@polaz

Summary

Consolidate 12 pipeline tools into 3 CQRS-aligned tools for better MCP client compatibility and cleaner read/write separation.

Current State (12 tools)

Tool Type Description
list_pipelines READ List pipelines with filtering
get_pipeline READ Get pipeline details
list_pipeline_jobs READ List jobs in pipeline
list_pipeline_trigger_jobs READ List bridge/trigger jobs
get_pipeline_job READ Get single job details
get_pipeline_job_output READ Get job logs
create_pipeline WRITE Trigger new pipeline
retry_pipeline WRITE Retry failed pipeline
cancel_pipeline WRITE Cancel running pipeline
play_pipeline_job WRITE Trigger manual job
retry_pipeline_job WRITE Retry single job
cancel_pipeline_job WRITE Cancel single job

Target State (3 tools)

browse_pipelines (Query)

Consolidates all READ operations:

{
  action: "list" | "get" | "jobs" | "triggers" | "job" | "logs",
  
  // Common
  projectId: string,
  
  // For "list" action
  status?: "created" | "waiting_for_resource" | "preparing" | "pending" |
           "running" | "success" | "failed" | "canceled" | "skipped" | "manual" | "scheduled",
  ref?: string,
  sha?: string,
  username?: string,
  yaml_errors?: boolean,
  updated_before?: string,
  updated_after?: string,
  name?: string,
  order_by?: "id" | "status" | "ref" | "updated_at" | "user_id",
  sort?: "asc" | "desc",
  source?: string,
  
  // For "get", "jobs", "triggers"
  pipelineId?: number,
  
  // For "jobs" action
  scope?: "created" | "pending" | "running" | "failed" | "success" |
          "canceled" | "skipped" | "waiting_for_resource" | "manual",
  include_retried?: boolean,
  
  // For "job", "logs"
  jobId?: number,
  
  // Pagination
  per_page?: number,
  page?: number
}

Read-only mode: Always allowed

manage_pipeline (Command)

Pipeline-level operations:

{
  action: "create" | "retry" | "cancel",
  
  projectId: string,
  
  // For "create"
  ref?: string,              // Branch or tag
  variables?: Array<{
    key: string,
    value: string,
    variable_type?: "env_var" | "file"
  }>,
  
  // For "retry" and "cancel"
  pipelineId?: number
}

Read-only mode: Blocked entirely

manage_pipeline_job (Command)

Job-level operations:

{
  action: "play" | "retry" | "cancel",
  
  projectId: string,
  jobId: number,
  
  // For "play" action (manual jobs)
  job_variables_attributes?: Array<{
    key: string,
    value: string
  }>
}

Read-only mode: Blocked entirely

Implementation Tasks

  • Create new browse_pipelines handler with action dispatch
  • Create new manage_pipeline handler with action dispatch
  • Create new manage_pipeline_job handler with action dispatch
  • Update Zod schemas with discriminated union pattern
  • Update registry to export new tool names
  • Update read-only tools list (only browse_pipelines)
  • Remove old handlers after migration
  • Update unit tests
  • Update integration tests

Schema Pattern

const BrowsePipelinesSchema = z.discriminatedUnion("action", [
  z.object({
    action: z.literal("list"),
    projectId: z.string(),
    status: PipelineStatusSchema.optional(),
    ref: z.string().optional(),
    // ...
  }),
  z.object({
    action: z.literal("get"),
    projectId: z.string(),
    pipelineId: z.number(),
  }),
  z.object({
    action: z.literal("jobs"),
    projectId: z.string(),
    pipelineId: z.number(),
    scope: JobScopeSchema.optional(),
    include_retried: z.boolean().optional(),
  }),
  z.object({
    action: z.literal("triggers"),
    projectId: z.string(),
    pipelineId: z.number(),
  }),
  z.object({
    action: z.literal("job"),
    projectId: z.string(),
    jobId: z.number(),
  }),
  z.object({
    action: z.literal("logs"),
    projectId: z.string(),
    jobId: z.number(),
  }),
]);

Breaking Changes

Old Tool Migration Path
list_pipelines browse_pipelines with action: "list"
get_pipeline browse_pipelines with action: "get"
list_pipeline_jobs browse_pipelines with action: "jobs"
list_pipeline_trigger_jobs browse_pipelines with action: "triggers"
get_pipeline_job browse_pipelines with action: "job"
get_pipeline_job_output browse_pipelines with action: "logs"
create_pipeline manage_pipeline with action: "create"
retry_pipeline manage_pipeline with action: "retry"
cancel_pipeline manage_pipeline with action: "cancel"
play_pipeline_job manage_pipeline_job with action: "play"
retry_pipeline_job manage_pipeline_job with action: "retry"
cancel_pipeline_job manage_pipeline_job with action: "cancel"

Why 3 Tools Instead of 2?

Pipeline-level and job-level operations have distinct targets:

  • Pipeline operations target pipelineId
  • Job operations target jobId

Combining them would create confusing parameter requirements. Keeping them separate maintains clarity while still achieving significant consolidation (12 → 3).

Feature Flag

Existing USE_PIPELINE flag continues to control entire pipelines entity visibility.

Testing Requirements

  • Unit tests for discriminated union schema validation
  • Test pipeline listing with various filters
  • Test job listing and logs retrieval
  • Test pipeline create/retry/cancel
  • Test manual job play with variables
  • Test job retry/cancel
  • Test read-only mode blocks command tools
  • Integration tests for all operations

Acceptance Criteria

  • Total tools reduced from 12 to 3
  • All existing functionality preserved
  • Clear separation between pipeline and job operations
  • Variable passing works for create and play
  • 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