# =clickup — ClickUp (community MCP: taazkareem/clickup-mcp-server, pinned @0.8.5)
#
# ┌──────────────────────────────────────────────────────────────────────────┐
# │ DISABLED until an admin completes setup. enabled:false.                    │
# │ This is NOT a hosted OAuth server. taazkareem/clickup-mcp-server is a      │
# │ self-hosted STDIO process the org must register in the MCP catalog under   │
# │ id `clickup`, authed by a PERSONAL ClickUp API token + team id (env), not  │
# │ a per-user OAuth popup. Before flipping enabled:true an admin MUST:        │
# │  1. Register the `clickup` MCP server (npx @taazkareem/clickup-mcp-server  │
# │     @0.8.5) in the org MCP catalog, exposing tools get_tasks + get_task.   │
# │  2. Set env CLICKUP_API_KEY and CLICKUP_TEAM_ID on that server (see ClickUp│
# │     Settings → Apps → API Token, and the team/workspace id in the app URL).│
# │  3. Fill the org-default list id below (search for "# ADMIN:" — there is   │
# │     one in the default scope and one in the search text-branch). get_tasks │
# │     is list-scoped and CANNOT run workspace-wide, so a default list id is   │
# │     mandatory for the chip to return anything with no query.               │
# │  4. Confirm the auth profile `clickup_user` matches how your catalog       │
# │     entry authenticates (token-env vs OAuth); adjust if your fork differs. │
# └──────────────────────────────────────────────────────────────────────────┘
#
# REAL CONTRACT (verified against taazkareem/clickup-mcp-server docs/tools/tasks.md
# + README @0.8.5, 2026-06-03):
#   get_tasks  — REQUIRES one of {listId | listName}; optional archived, page,
#                subtasks, statuses, order_by, reverse, include_closed.
#                Returns the ClickUp "Get Tasks" shape: { "tasks": [ <task>, ... ] }.
#   get_task   — REQUIRES one of {taskId | taskName}; optional listName
#                (disambiguation), customTaskId, subtasks. taskId auto-detects
#                both 9-char ClickUp ids and custom ids (e.g. DEV-1234).
#                Returns a single task object (NOT wrapped in {tasks:[]}).
#   Task object fields used below: id, name, url, status.status, status.color,
#                priority (number 1..4 or {priority,color}), assignees[]
#                ({username,email,profilePicture}), due_date, date_updated,
#                list.name, space.name, description.
schema_version: 1
id: clickup
chip: =clickup
title: ClickUp
icon: check-square
description: Your ClickUp tasks from the org default list; look up a task by id.
placeholder_examples:
  - "86b4bnnny"
  - "DEV-1234"
  - "auth refactor"
emits: Issue
# Token/credentials live under MCP id `clickup` (the catalog entry the admin
# registers). clickup_user already points provider:clickup in auth-profiles.yaml.
auth_profile_ref: clickup_user

requires:
  - mcp: clickup
    version: ">=0.8.5"
    tools:
      - get_tasks
      - get_task
    scopes:
      - task:read

# MCP/network backend → confirm dispatch (don't hammer ClickUp per keystroke).
# A bare ClickUp/custom task id auto-fires a direct lookup; free text searches
# the configured list on Enter.
dispatch:
  mode: confirm
  confirm_label: 'Search ClickUp for "{{query}}"'
  confirm_hint: Press Enter to search your ClickUp list
  auto_when:
    # 9-char ClickUp id (e.g. 86b4bnnny) or custom id (e.g. DEV-1234 / PROJ_456).
    - regex: '^([0-9a-z]{9}|[A-Za-z][A-Za-z0-9]*[-_.][0-9]+)$'
      reason: Exact task-id lookup is unambiguous and cheap.

scopes:
  # DEFAULT: open tasks in the org-default list.
  - id: list
    label: My list
    default: true
    flow:
      - call:
          kind: mcp
          mcp: clickup
          tool: get_tasks
          args:
            # get_tasks is list-scoped and requires a list. No query → use the
            # org default list id. Prefer org tenant config when present, else a
            # constant the admin edits.
            listId: '{{org.clickup_default_list_id | default:"REPLACE_ME"}}'  # ADMIN: default <listId> — the ClickUp list these chip rows come from; copy the numeric id from the list URL (app.clickup.com/.../li/<listId>) or run get_tasks once with listName. org.clickup_default_list_id wins if your tenant config exposes it.
            subtasks: false
            include_closed: false
        out: r
      - transform: &tasks_to_rows |
          ((.r.structured_content?.data? // .r.structured_content? // .r) // {}) as $p
          | ( ($p.tasks?)
              // ($p.items?)
              // ($p.data?)
              // ($p.nodes?)
              // ($p | if type == "array" then . else empty end)
              // ($p | if (type == "object" and (has("id") or has("name"))) then [.] else empty end)
              // [] ) as $nodes
          | ($nodes | map(
                . as $t
                | ($t.status // {}) as $st
                | (($t.priority) // null) as $prRaw
                | (if ($prRaw | type) == "object" then ($prRaw.priority // "")
                   elif $prRaw == null then ""
                   else ($prRaw | tostring) end) as $priorityName
                | (if ($prRaw | type) == "object" then ($prRaw.color // "") else "" end) as $priorityColor
                | (($t.assignees // [])) as $as
                | ($as | map(.username // .email // empty)) as $aNames
                | (($t.list // {})) as $list
                | (($t.space // {})) as $space
                | (($t.creator // {})) as $cr
                | (($as | length) > 0) as $hasAssignee
                | ($as[0] // {}) as $a0
                | ([ $t.id,
                     ($st.status // empty),
                     (if $priorityName == "" then empty else $priorityName end),
                     (if $hasAssignee then ($aNames | join(", ")) else "Unassigned" end) ]
                    | map(select(. != null and . != "")) | join("  ·  ")) as $subline
                | {
                    id: ($t.id // ""),
                    key: ($t.id // ""),
                    _url: ($t.url // ""),
                    name: ($t.name // "(no name)"),
                    title: ($t.name // "(no name)"),
                    subline: $subline,
                    statusName: ($st.status // "—"),
                    statusColor: ($st.color // ""),
                    statusType: ($st.type // ""),
                    priorityName: (if $priorityName == "" then "—" else $priorityName end),
                    priorityColor: $priorityColor,
                    assigneeName: (if $hasAssignee then ($aNames | join(", ")) else "Unassigned" end),
                    assigneeAvatar: ($a0.profilePicture // ""),
                    assigneeEmail: ($a0.email // "—"),
                    creatorName: ($cr.username // "—"),
                    due: ($t.due_date // "—"),
                    updated: ($t.date_updated // "—"),
                    created: ($t.date_created // "—"),
                    listName: ($list.name // "—"),
                    spaceName: ($space.name // "—"),
                    description: ((if ($t.description | type) == "string" then $t.description else "" end)
                                  | if length > 280 then (.[0:277] + "…") else . end
                                  | if . == "" then "No description" else . end)
                  }
              ))
        out: enriched
      - emit: enriched

  # Typing after the chip: a task id jumps straight to get_task; any other text
  # searches the org-default list (get_tasks is list-scoped, so we fetch the
  # list and the launcher refines client-side via search_fields).
  - id: search
    label: Search
    match:
      # 9-char ClickUp id OR custom id (DEV-1234 / PROJ_456 / TEAM.456).
      - regex: '^([0-9a-z]{9}|[A-Za-z][A-Za-z0-9]*[-_.][0-9]+)$'
        bind:
          taskId: '{{match}}'
        flow:
          - call:
              kind: mcp
              mcp: clickup
              tool: get_task
              args:
                # taskId auto-detects regular (9-char) and custom ids; no escape
                # filter needed (not a query DSL — a single id-typed scalar).
                taskId: '{{taskId}}'
                subtasks: false
            out: r
          - transform: *tasks_to_rows
            out: enriched
          - emit: enriched
      # Any other text → fetch the configured list, refine client-side. get_tasks
      # has no server-side free-text search; statuses/lists only. We pass the list
      # id and let presentation.search_fields narrow + highlight.
      - text: '{{query}}'
        flow:
          - call:
              kind: mcp
              mcp: clickup
              tool: get_tasks
              args:
                listId: '{{org.clickup_default_list_id | default:"REPLACE_ME"}}'  # ADMIN: default <listId> — same list as the default scope; copy from the list URL (.../li/<listId>). org.clickup_default_list_id wins if your tenant config exposes it.
                subtasks: false
                include_closed: false
            out: r
          - transform: *tasks_to_rows
            out: enriched
          - emit: enriched

actions:
  - id: open
    kind: url
    target: row
    label: Open in ClickUp
    icon: external_link
    # Each task row carries its app.clickup.com/t/<id> URL from the task's `url`.
    url_template: '{{row._url}}'

presentation:
  widget: list
  title_field: name
  subtitle_field: subline
  list_fields:
    - id
    - name
    - statusName
    - priorityName
    - assigneeName
  search_fields:
    - id
    - name
    - statusName
    - priorityName
    - assigneeName
    - listName
    - spaceName
    - description
  sort_field: updated
  sort_order: desc
  row_key_field: id
  searchable: true
  filterable: true
  right_widget:
    kind: entity_preview
    mode: selected_row
    bind:
      title: '{{row.name}}'
      subtitle: '{{row.id}} · {{row.statusName}}'
      image_url: '{{row.assigneeAvatar}}'
      fields:
        - { label: Status,      value: '{{row.statusName}}' }
        - { label: Priority,    value: '{{row.priorityName}}' }
        - { label: Assignee,    value: '{{row.assigneeName}}' }
        - { label: List,        value: '{{row.listName}}' }
        - { label: Space,       value: '{{row.spaceName}}' }
        - { label: Due,         value: '{{row.due}}' }
        - { label: Updated,     value: '{{row.updated}}' }
        - { label: Description, value: '{{row.description}}' }
      actions:
        - open

enabled: false
provisioning_mode: public
departments: []
roles: []
groups: []
