aios-core
Version:
Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework
535 lines (481 loc) • 17.8 kB
YAML
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": "AIOS 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