tool:
  schema_version: 2.0
  id: clickup
  type: mcp
  name: ClickUp
  version: 1.0.0
  description: ClickUp project management with pre-execution validation and API complexity handling
  knowledge_strategy: executable

  executable_knowledge:
    validators:
      # Combined validator for create_task (all checks in one function)
      - id: validate-create-task
        validates: create_task
        language: javascript
        checks:
          - required_fields: [name, list_id]
        function: |
          (function() {
            const errors = [];

            // 1. Required fields
            if (!args.args.name) {
              errors.push("name is required");
            }
            if (!args.args.list_id && !args.args.listId) {
              errors.push("list_id is required");
            }

            // 2. Assignee format (must be array for create)
            if (args.args.assignees && typeof args.args.assignees === 'object' && !Array.isArray(args.args.assignees)) {
              errors.push("assignees must be array for create_task, got object");
            }

            // 3. Custom fields structure
            const customFields = args.args.custom_fields;
            if (customFields) {
              if (!Array.isArray(customFields)) {
                errors.push("custom_fields must be an array");
              } else {
                customFields.forEach((field, index) => {
                  if (!field.id) {
                    errors.push(`custom_fields[${index}] missing required 'id' field`);
                  }
                  if (!field.hasOwnProperty('value')) {
                    errors.push(`custom_fields[${index}] missing required 'value' field`);
                  }
                });
              }
            }

            // 4. List ID format (numeric string)
            const listId = args.args.list_id || args.args.listId;
            if (listId && !/^\d+$/.test(String(listId))) {
              errors.push("list_id must be a numeric string");
            }

            // 5. Priority range (1-4)
            const priority = args.args.priority;
            if (priority !== undefined && priority !== null) {
              const p = Number(priority);
              if (isNaN(p) || p < 1 || p > 4) {
                errors.push("priority must be between 1 (urgent) and 4 (low)");
              }
            }

            // 6. Time estimate (positive number)
            const timeEstimate = args.args.time_estimate;
            if (timeEstimate !== undefined) {
              const estimate = Number(timeEstimate);
              if (isNaN(estimate) || estimate < 0) {
                errors.push("time_estimate must be a positive number (milliseconds)");
              }
            }

            return {
              valid: errors.length === 0,
              errors: errors
            };
          })();

      # Combined validator for update_task
      - id: validate-update-task
        validates: update_task
        language: javascript
        function: |
          (function() {
            const errors = [];
            const assignees = args.args.assignees;

            // Assignees format (must be object with add/rem for update)
            if (assignees) {
              if (Array.isArray(assignees)) {
                errors.push("assignees must be object {add: [], rem: []} for update_task, got array");
              } else if (typeof assignees === 'object') {
                if (assignees.add && !Array.isArray(assignees.add)) {
                  errors.push("assignees.add must be an array");
                }
                if (assignees.rem && !Array.isArray(assignees.rem)) {
                  errors.push("assignees.rem must be an array");
                }
              }
            }

            return {
              valid: errors.length === 0,
              errors: errors
            };
          })();

      # Validator for parse_webhook
      - id: validate-webhook-payload
        validates: parse_webhook
        language: javascript
        function: |
          (function() {
            const errors = [];
            const payload = args.args;

            if (!payload) {
              errors.push("payload is required");
            } else {
              // Check for required webhook identification fields
              if (!payload.event && !payload.webhook_id && !payload.history_items) {
                errors.push("Invalid webhook payload: missing event, webhook_id, or history_items");
              }
            }

            return {
              valid: errors.length === 0,
              errors: errors
            };
          })();

    helpers:
      - id: extract-custom-field
        language: javascript
        runtime: isolated_vm
        description: "Extract specific custom field value from task response"
        function: |
          (function() {
            const { response, fieldName } = args;
            if (!response || !response.custom_fields) {
              return null;
            }
            const field = response.custom_fields.find(f => f.name === fieldName);
            return field || null;
          })();

      - id: format-assignee-for-create
        language: javascript
        runtime: isolated_vm
        description: "Convert assignee IDs to create_task format (array)"
        function: |
          (function() {
            const { assignees } = args;
            if (!assignees) return [];
            if (Array.isArray(assignees)) return assignees;
            if (typeof assignees === 'number') return [assignees];
            return [];
          })();

      - id: format-assignee-for-update
        language: javascript
        runtime: isolated_vm
        description: "Convert assignee IDs to update_task format (object)"
        function: |
          (function() {
            const { add, remove } = args;
            const result = {};
            if (add) result.add = Array.isArray(add) ? add : [add];
            if (remove) result.rem = Array.isArray(remove) ? remove : [remove];
            return result;
          })();

      - id: parse-webhook-type
        language: javascript
        runtime: isolated_vm
        description: "Detect webhook payload type from structure"
        function: |
          (function() {
            const { payload } = args;
            if (!payload) return 'unknown';

            if (payload.event) return 'standard';
            if (payload.webhook_id) return 'webhook_variant';
            if (payload.history_items) return 'history_items';

            return 'unknown';
          })();

      - id: extract-webhook-payload
        language: javascript
        runtime: isolated_vm
        description: "Extract actual payload based on webhook type"
        function: |
          (function() {
            const { webhook } = args;
            if (!webhook) return null;

            // Standard format
            if (webhook.event && webhook.payload) {
              return webhook.payload;
            }

            // Webhook variant format
            if (webhook.webhook_id && webhook.data && webhook.data.webhook) {
              return webhook.data.webhook.payload;
            }

            // History items format
            if (webhook.history_items) {
              return webhook;
            }

            return null;
          })();

      - id: calculate-time-tracking-total
        language: javascript
        runtime: isolated_vm
        description: "Sum time tracking entries for a task"
        function: |
          (function() {
            const { timeEntries } = args;
            if (!Array.isArray(timeEntries)) return 0;

            return timeEntries.reduce((total, entry) => {
              return total + (entry.duration || 0);
            }, 0);
          })();

      - id: format-custom-field-update
        language: javascript
        runtime: isolated_vm
        description: "Format custom field for API update"
        function: |
          (function() {
            const { fieldId, value } = args;
            return {
              id: fieldId,
              value: value
            };
          })();

  api_complexity:
    payload_schemas:
      - type: standard
        detection: "event field exists in root"
        payload_path: "payload"
        example: |
          {
            "event": "taskCreated",
            "payload": { "task": {...} }
          }

      - type: webhook_variant
        detection: "webhook_id field exists"
        payload_path: "data.webhook.payload"
        example: |
          {
            "webhook_id": "abc123",
            "data": {
              "webhook": {
                "payload": { "task": {...} }
              }
            }
          }

      - type: history_items
        detection: "history_items array exists"
        payload_path: "history_items"
        example: |
          {
            "history_items": [
              { "field": "status", "before": "todo", "after": "in progress" }
            ]
          }

    field_mappings:
      assignees:
        create_format:
          structure: "[user_id1, user_id2]"
          type: "array of integers"
          example: "[123, 456]"
        update_format:
          structure: "{add: [user_id], rem: [user_id]}"
          type: "object with add/rem arrays"
          example: "{add: [789], rem: [456]}"
        note: "Different formats for create vs update - common error source"

      custom_fields:
        structure: "[{id: field_id, value: field_value}]"
        type: "array of objects"
        validation: "Each object must have 'id' and 'value' keys"
        example: "[{id: 'field-uuid', value: 'text value'}]"

      list_id:
        format: "numeric string"
        validation: "Must match /^\\d+$/"
        note: "Often called 'listId' in some API responses but 'list_id' in requests"

    api_quirks:
      - quirk: assignee_format_mismatch
        description: "Create API expects [123], Update expects {add: [123], rem: []}"
        impact: "Runtime errors if wrong format used"
        mitigation: "Use command-specific validators (validate-assignee-format, validate-task-update-assignees)"

      - quirk: rate_limit_headers_missing
        description: "Rate limit info only in X-RateLimit-* headers, not in error response body"
        impact: "Difficult to implement retry logic without header access"
        mitigation: "Always capture response headers for rate limit tracking"

      - quirk: custom_field_id_instability
        description: "Custom field IDs can change when field is renamed or recreated"
        impact: "Stored field IDs may become invalid"
        mitigation: "Use field names to lookup IDs via get_workspace_hierarchy before updates"

      - quirk: inconsistent_list_id_naming
        description: "API uses 'list_id' in requests but 'listId' in responses"
        impact: "Field mapping confusion in response processing"
        mitigation: "Normalize to 'list_id' in all internal processing"

  anti_patterns:
    - pattern: wrong_assignee_format
      description: "Using update format in create API"
      category: api_usage
      severity: high
      wrong: |
        create_task({
          name: "Task",
          list_id: "123",
          assignees: {add: [456]}  // ❌ Wrong - object format
        })
      correct: |
        create_task({
          name: "Task",
          list_id: "123",
          assignees: [456]  // ✅ Correct - array format
        })
      rationale: "Create API expects array, not object. Use format-assignee-for-create helper."

    - pattern: missing_custom_field_validation
      description: "Setting custom fields without validating structure"
      category: data_validation
      severity: medium
      wrong: |
        create_task({
          name: "Task",
          list_id: "123",
          custom_fields: "field-value"  // ❌ Wrong - string instead of array
        })
      correct: |
        create_task({
          name: "Task",
          list_id: "123",
          custom_fields: [  // ✅ Correct - array of objects
            {id: "field-uuid", value: "field-value"}
          ]
        })
      rationale: "Custom fields must be array of {id, value} objects. Use validate-custom-field-structure."

    - pattern: undocumented_rate_limit_handling
      description: "Not checking rate limit headers before subsequent requests"
      category: api_reliability
      severity: medium
      wrong: |
        // Make 100 requests in rapid succession
        for (let i = 0; i < 100; i++) {
          await create_task({...});  // ❌ No rate limit checking
        }
      correct: |
        for (let i = 0; i < 100; i++) {
          const response = await create_task({...});
          const remaining = response.headers['X-RateLimit-Remaining'];

          if (remaining < 10) {
            // Wait for reset
            await sleep(60000);  // ✅ Proper rate limit handling
          }
        }
      rationale: "ClickUp API has strict rate limits. Always monitor X-RateLimit-* headers."

  examples:
    # WORKFLOW EXAMPLE: Complete Story Creation Flow
    story_creation_workflow:
      - step: 1
        action: get_workspace_hierarchy
        description: "First, get workspace structure to find list IDs"
        command: get_workspace_hierarchy
        input: {}
        output_extract: |
          // From the response, find the Backlog list:
          {
            "spaces": [{
              "name": "AIOX Project",
              "lists": [
                {
                  "name": "Backlog",
                  "id": "901317181013"  // ← This is the list_id you need
                }
              ]
            }]
          }
        notes: "CRITICAL: You must use the numeric list_id from this response in create_task"

      - step: 2
        action: create_task
        description: "Create story task with discovered list_id"
        command: create_task
        input:
          list_id: "901317181013"  # ← From step 1 (numeric string)
          name: "Story 5.2: Implement Feature X"
          markdown_description: "Complete story content here..."
          parent: "86acfeqeq"  # Epic task ID (if creating as subtask)
          tags: ["story", "epic-5", "story-5.2"]
          custom_fields:
            - id: "epic_number"
              value: 5
            - id: "story_number"
              value: "5.2"
        output:
          id: "86acfetr9"
          name: "Story 5.2: Implement Feature X"
          url: "https://app.clickup.com/t/86acfetr9"
        notes: "list_id MUST be numeric string from get_workspace_hierarchy. Using 'listName' or non-numeric values will fail validation."

    create_task:
      - scenario: success
        description: "Standard task creation with assignees"
        input:
          name: "Implement user authentication"
          list_id: "123456789"
          assignees: [456, 789]
          priority: 2
        output:
          id: "task_abc123"
          status: "created"
          name: "Implement user authentication"

      - scenario: failure_invalid_param
        description: "Wrong assignee format (object instead of array)"
        input:
          name: "Fix login bug"
          list_id: "123456789"
          assignees: {add: [456]}
        error:
          code: VALIDATION_ERROR
          message: "assignees must be array for create_task, got object"
          validator: validate-assignee-format

      - scenario: failure_api_error
        description: "Invalid list_id"
        input:
          name: "Task"
          list_id: "invalid"
        error:
          code: API_ERROR
          message: "List not found"
          http_status: 404

      - scenario: edge_case
        description: "Task with custom fields"
        input:
          name: "Research task"
          list_id: "123456789"
          custom_fields:
            - id: "field-uuid-1"
              value: "High"
            - id: "field-uuid-2"
              value: 1000
        output:
          id: "task_xyz789"
          status: "created"
          custom_fields:
            - name: "Complexity"
              value: "High"
            - name: "Estimate"
              value: 1000

    update_task:
      - scenario: success
        description: "Update task assignees"
        input:
          task_id: "task_abc123"
          assignees:
            add: [999]
            rem: [456]
        output:
          id: "task_abc123"
          status: "updated"

      - scenario: failure_invalid_param
        description: "Wrong assignee format (array instead of object)"
        input:
          task_id: "task_abc123"
          assignees: [999]
        error:
          code: VALIDATION_ERROR
          message: "assignees must be object {add: [], rem: []} for update_task, got array"
          validator: validate-task-update-assignees

      - scenario: failure_api_error
        description: "Task not found"
        input:
          task_id: "nonexistent"
          name: "Updated name"
        error:
          code: API_ERROR
          message: "Task not found"
          http_status: 404

      - scenario: edge_case
        description: "Update with partial assignee change"
        input:
          task_id: "task_abc123"
          assignees:
            add: [111, 222]
        output:
          id: "task_abc123"
          assignees: [456, 789, 111, 222]

  mcp_specific:
    server_command: "npx -y @modelcontextprotocol/server-clickup"
    transport: stdio
    environment_variables:
      - name: CLICKUP_API_KEY
        required: true
        description: "ClickUp API key for authentication"
    health_check:
      method: tool_call
      command: get_workspace_hierarchy
      expected_response: "Object with 'spaces' array"
      timeout_ms: 5000
