Skip to content

feat: Dynamic action filtering with multi-level description customization #32

@polaz

Description

@polaz

Summary

Add comprehensive customization system for CQRS tools with discriminated union schema architecture: filter specific actions (removing their exclusive parameters), customize descriptions at all levels, and dynamically transform schemas for AI context optimization.

Motivation

Current limitations:

  1. GITLAB_DENIED_TOOLS_REGEX can only disable entire tools
  2. GITLAB_TOOL_{NAME} only overrides top-level tool description
  3. Flat schemas don't track which parameters belong to which action - can't remove action-specific params
  4. Tool descriptions contain hardcoded action lists that become stale when filtering
  5. Long parameter descriptions consume AI context unnecessarily

With CQRS consolidation (61 tools), we need finer control to:

  • Disable specific actions AND their exclusive parameters (token savings!)
  • Customize descriptions at every level for specific use cases
  • Keep AI context clean by removing unnecessary information

Architectural Decision: Discriminated Union Schemas

Problem with Current Flat Schemas

// Current: flat schema - can't know which params belong to which action
const ManageMilestoneSchema = z.object({
  action: z.enum(['create', 'update', 'delete', 'promote']),
  namespace: z.string(),  // shared by all
  milestone_id: z.string().optional(),  // update/delete/promote only
  title: z.string().optional(),  // create/update only
  state_event: z.enum(['close', 'activate']).optional(),  // update only
}).refine(...);

If we disable delete action, we still export milestone_id because it's also used by update and promote. But if we disable ALL of update, delete, promote - we should remove milestone_id entirely. Flat schemas can't express this.

Solution: Discriminated Union + Runtime Flattening

// New: discriminated union - each action has its own parameter set
const ManageMilestoneSchema = z.discriminatedUnion('action', [
  z.object({
    action: z.literal('create'),
    namespace: z.string(),
    title: z.string(),
    description: z.string().optional(),
    due_date: z.string().optional(),
  }),
  z.object({
    action: z.literal('update'),
    namespace: z.string(),
    milestone_id: z.string(),
    title: z.string().optional(),
    state_event: z.enum(['close', 'activate']).optional(),
  }),
  z.object({
    action: z.literal('delete'),
    namespace: z.string(),
    milestone_id: z.string(),
  }),
  z.object({
    action: z.literal('promote'),
    namespace: z.string(),
    milestone_id: z.string(),
  }),
]);

Benefits:

  1. Action filtering = branch removal → exclusive params automatically disappear
  2. Type-safe - TypeScript knows exactly which params each action needs
  3. Future-proof - when Claude API supports oneOf/anyOf, we can send native schema
  4. Cleaner handlers - discriminated union provides better type narrowing

Schema Pipeline

Source Schema (discriminated union)
        ↓
   Filter denied actions (remove branches)
        ↓
   Apply description overrides
        ↓
   Transform to flat (for current AI clients)
        ↓
Output Schema (flat, minimal context)

Proposed Solution

Three-Level Customization System

Level 1: Tool Description (existing)

# Already implemented - override entire tool description
GITLAB_TOOL_BROWSE_MILESTONES="Milestone operations"

Level 2: Action Filtering & Description

# Filter out specific actions (removes from schema + their exclusive params!)
GITLAB_DENIED_ACTIONS="browse_milestones:burndown,manage_milestone:promote,manage_milestone:delete"

# Override action field description (optional)
GITLAB_ACTION_MANAGE_MILESTONE_CREATE="Create new milestone"

Level 3: Parameter Description

# Override parameter descriptions to shorten context
GITLAB_PARAM_BROWSE_MILESTONES_NAMESPACE="Project/group path"
GITLAB_PARAM_MANAGE_MILESTONE_TITLE="Title"

Schema Output Mode (Future)

# When AI clients support discriminated unions natively
GITLAB_SCHEMA_MODE="discriminated"  # or "flat" (default)

Implementation Plan

Phase 1: Infrastructure ✅ (Done)

  • Add GITLAB_DENIED_ACTIONS parsing to config.ts
  • Add getActionDescriptionOverrides() function
  • Add getParamDescriptionOverrides() function
  • Add isActionDenied() and getAllowedActions() helpers
  • Unit tests for config parsing

Phase 2: Schema Architecture Migration 🔄 (Current)

  • Create src/utils/schema-utils.ts with:
    • filterDiscriminatedUnion(schema, allowedActions) - remove branches
    • flattenDiscriminatedUnion(schema) - convert to flat for AI clients
    • applyDescriptionOverrides(schema, overrides) - apply custom descriptions
  • Migrate ONE schema as pilot (e.g., manage_milestone)
  • Verify handler still works with discriminated union
  • Verify flat output matches current behavior

Phase 3: Full Schema Migration

  • Migrate all CQRS command schemas (manage_*)
  • Migrate all CQRS query schemas (browse_*, list_*)
  • Update unit tests for new schema format

Phase 4: Integration

  • Update registry-manager.ts to apply full pipeline:
    1. Filter denied actions from discriminated union
    2. Apply description overrides
    3. Flatten to current format
  • Add runtime validation in handlers (reject denied actions)
  • Integration tests

Phase 5: Documentation & Release

  • Update CONFIGURATION.md with new env vars
  • Add examples for common use cases
  • Update README with customization section

Example: Token Savings

Before (all actions enabled):

{
  "action": {"enum": ["create", "update", "delete", "promote"]},
  "namespace": {...},
  "milestone_id": {..., "description": "Required for update/delete/promote"},
  "title": {...},
  "description": {...},
  "due_date": {...},
  "start_date": {...},
  "state_event": {...}
}

~400 tokens for schema

After (only create enabled):

{
  "action": {"enum": ["create"]},
  "namespace": {...},
  "title": {...},
  "description": {...},
  "due_date": {...},
  "start_date": {...}
}

~200 tokens - 50% reduction!

Files to Modify

  1. src/utils/schema-utils.ts - NEW: schema transformation utilities
  2. src/entities/*/schema.ts - Migrate to discriminated union
  3. src/registry-manager.ts - Apply transformation pipeline
  4. src/config.ts - Already done ✅
  5. docs/CONFIGURATION.md - Document new env vars

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature, new MCP tool, new capability

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions