What does asana-gate do?
asana-gate is a policy gateway in front of the Asana REST API.
A YAML policy says, per scope (workspace, team, portfolio, project, section, task), which actions an agent may take and how its requests are shaped; the gate enforces it on every request, whether it arrives as an MCP tool call or as an asana-gate command.
Everything you can see or do through it is decided by that policy, not by Asana: objects the policy hides do not exist for you, and refusals come back as HTTP-style errors whose message starts with asana-gate:.
Read the gate's answers first
Every object the gate returns carries allowed_actions: the <resource>:<verb> actions the policy allows on it right now, such as task:update or story:create.
Check it before writing, because an action missing from it will be refused: plan around what is listed rather than trying and failing.
| Answer | Meaning | What to do |
|---|---|---|
404 <type>: Not a recognized ID: <gid> | The object does not exist, or the policy hides it; the gate deliberately makes the two indistinguishable (the same 404 Asana sends) | Do not retry or try other gids. Say the object is not visible under the policy; its owner can run asana-gate explain <gid> to see which rule hides it |
403 asana-gate: <action> is not permitted on <type> <gid> | The object is visible, but the policy does not allow this action there | Report it and offer an action from allowed_actions; widening the policy is the owner's call |
403 asana-gate: updating <resource>.<member> is not permitted here (allowed fields: …) | The update touched a member outside the rule's fields allowlist | Resend with only the allowed members |
400 asana-gate: the <resource> does not satisfy the policy: … | A constraint of the policy: a required member is missing or cleared, a prefix or format is wrong, a value is not allowed, a member is forbidden | Fix the request exactly as the message says and resend |
428 asana-gate: <action> needs a person's confirmation | The policy wants a human to approve this action | Over MCP, answer the elicitation the client shows; from a shell, rerun with --yes |
429 asana-gate: <action> is limited to N per <period> | A rate cap the policy sets on this action in this scope | Wait for the period to pass; do not loop on it |
Lists (projects, tasks, search, typeahead, events) are filtered silently: hidden objects are simply absent, nothing says how many were removed, and a page carries next_page.offset to continue with offset.
Asana's own errors pass through unchanged, without the asana-gate: prefix: a 402 means the workspace's plan lacks the feature (search, custom fields, portfolios and goals need a paid plan), a plain 400 is Asana rejecting the body, a plain 403 is the token's own permission (audit log events, workspace events and organization exports take a service account on an Enterprise tier; the early-access Agents endpoints take the ai_teammate_actors flag in the settings' asana.enable).
Look for the prefix: with it the policy decided, without it Asana did.
Working through MCP
The server exposes the curated tools the policy could ever allow, plus three generic ones:
asana_create_tasksandasana_update_taskstake up to 50 tasks at once and answer with onestatus_codeandbodyper task, in order; some may fail while the others succeed.asana_requestcalls any Asana REST operation (method,pathbelow/api/1.0,query,bodyas thedataobject) through the same policy; aPOST /batchthrough it takes up to 50 actions.asana_operationslists the operations with the policy action each needs;asana_allowed_actionstells what you may do with one object.
| Tool | Command line | What it does |
|---|---|---|
asana_search_tasks | asana-gate task search | Full-text and filtered task search in the workspace (Asana Search API; paid plans only). Results are limited to tasks the policy makes visible. |
asana_list_tasks | asana-gate task list | Lists tasks in a project, a section, with a tag, or assigned to a user (one of project, section, tag or assignee is required). Each task carries allowed_actions. |
asana_get_task | asana-gate task get <task_gid> | Reads one task with its full fields and allowed_actions. |
asana_create_task | asana-gate task create | Creates a task in one or more projects (optionally in a section), under a parent task, or unhomed in the workspace. |
asana_update_task | asana-gate task update <task_gid> | Updates fields of a task. Completing needs task:complete, assigning needs task:assign; other fields need task:update (possibly limited to an allowlist). |
asana_create_tasks | asana-gate task create-many | Creates up to 50 tasks in one call, each as asana_create_task would. The result holds one status_code and body per task, in order; Asana may fail some while the others succeed. A policy refusal of any task refuses the whole call before anything runs, and its message names the task (actions[i]). |
asana_update_tasks | asana-gate task update-many | Updates up to 50 tasks in one call, each as asana_update_task would. The result holds one status_code and body per task, in order; Asana may fail some while the others succeed. A policy refusal of any task refuses the whole call before anything runs, and its message names the task (actions[i]). |
asana_delete_task | asana-gate task delete <task_gid> | Deletes a task (it moves to the trash in Asana). |
asana_list_subtasks | asana-gate task subtasks <task_gid> | Lists the direct subtasks of a task. |
asana_create_subtask | — | Creates a subtask under a task; it inherits the parent's scope unless projects are given. |
asana_set_task_parent | asana-gate task set-parent <task_gid> | Makes a task a subtask of another (or a top-level task with parent null). |
asana_add_task_to_project | asana-gate task add-project <task_gid> | Multi-homes a task into a project, optionally into a section. |
asana_remove_task_from_project | asana-gate task remove-project <task_gid> | Removes a task from one of its projects. |
asana_move_task_to_section | asana-gate task move <task_gid> | Moves a task into a section of a project it belongs to (needs task:move at both ends). |
asana_add_tag_to_task | asana-gate task tag <task_gid> | Applies a tag to a task (needs tag:apply in the task's scope). |
asana_remove_tag_from_task | asana-gate task untag <task_gid> | Removes a tag from a task (needs tag:apply in the task's scope). |
asana_add_task_dependencies | asana-gate task add-dependencies <task_gid> | Marks tasks this task depends on; every referenced task must be visible. |
asana_remove_task_dependencies | asana-gate task remove-dependencies <task_gid> | Removes dependencies of a task. |
asana_list_task_stories | asana-gate task comments <task_gid> | Lists the comments and activity (stories) of a task. |
asana_create_comment | asana-gate task comment <task_gid> | Adds a comment (story) to a task. |
asana_update_story | asana-gate comment update <story_gid> | Edits a comment. |
asana_delete_story | asana-gate comment delete <story_gid> | Deletes a comment. |
asana_list_attachments | asana-gate attachment list | Lists the attachments of a task or project brief. |
asana_get_attachment | asana-gate attachment get <attachment_gid> | Reads an attachment, including its download URL. |
asana_create_attachment | asana-gate attachment create | Attaches an external URL, or uploads a small file given as base64, to a task or project brief. |
asana_delete_attachment | asana-gate attachment delete <attachment_gid> | Deletes an attachment. |
asana_list_projects | asana-gate project list | Lists projects of the workspace or of a team. Each project carries allowed_actions. |
asana_get_project | asana-gate project get <project_gid> | Reads one project. |
asana_create_project | asana-gate project create | Creates a project in the workspace (and a team, in organizations). |
asana_update_project | asana-gate project update <project_gid> | Updates fields of a project; archiving needs project:archive. |
asana_delete_project | asana-gate project delete <project_gid> | Deletes a project. |
asana_list_sections | asana-gate section list | Lists the sections of a project. |
asana_create_section | asana-gate section create | Creates a section in a project. |
asana_update_section | asana-gate section update <section_gid> | Renames a section. |
asana_delete_section | asana-gate section delete <section_gid> | Deletes an empty section. |
asana_list_status_updates | asana-gate status list | Lists the status updates of a project, portfolio or goal. |
asana_create_status_update | asana-gate status create | Posts a status update on a project, portfolio or goal. |
asana_get_workspace | asana-gate workspace get | Reads the workspace the policy governs. |
asana_get_me | asana-gate user me | Reads the user the access token belongs to. |
asana_list_users | asana-gate user list | Lists the users of the workspace or of a team. |
asana_get_user | asana-gate user get <user> | Reads a user by gid or email. |
asana_list_teams | asana-gate team list | Lists the teams of the workspace (organizations only). |
asana_get_team | asana-gate team get <team_gid> | Reads one team. |
asana_list_tags | asana-gate tag list | Lists the tags of the workspace. |
asana_create_tag | asana-gate tag create | Creates a tag in the workspace. |
asana_list_portfolios | asana-gate portfolio list | Lists the portfolios of the workspace owned by a user (the token owner by default). |
asana_get_portfolio | asana-gate portfolio get <portfolio_gid> | Reads one portfolio. |
asana_list_portfolio_items | asana-gate portfolio items <portfolio_gid> | Lists the projects and portfolios inside a portfolio. |
asana_list_custom_fields | asana-gate custom-field list | Lists the custom field definitions of the workspace. |
asana_typeahead | asana-gate find <query> | Finds objects by name prefix (tasks, projects, users, tags, teams, portfolios, goals, custom fields, project templates). |
To read many objects at once, send one POST /batch through asana_request with up to ten actions ({"actions": [{"method": "get", "relative_path": "/tasks/123"}, …]}): the gate checks each action and Asana runs them as one request, which beats ten sequential calls.
When a tool call comes back as an elicitation (a confirmation request) the policy wants a person to approve the action: present it to the user and resend only when they accept.
The full tool reference, with every input member and its command-line form, is in references/tools.md.
Working from a shell
Every curated tool is also a command with the same inputs and the same policy:
asana-gate task list --project 1200000000000123 --completed-since now # open tasks
asana-gate task create --name "Write docs" --projects 1200000000000123 --due-on 2026-12-01
asana-gate task update 1200000000000456 --assignee me --completed
asana-gate task move 1200000000000456 --section 1200000000000789
asana-gate task comment 1200000000000456 --text "Done, see the attached diff."
asana-gate task delete 1200000000000456 --yes # confirms non-interactively
asana-gate task create-many --tasks @tasks.json # up to 50 at once; also inline JSON or - for stdin
asana-gate find design --type project
asana-gate api GET /tasks/1200000000000456 -q opt_fields=name,completed
asana-gate api POST /tasks -d '{"name": "Write docs", "projects": ["1200000000000123"]}'
- The object a verb acts on is a positional argument; everything else is a flag named after the API member (
--due-onfordue_on). Booleans are switches (--completed,--no-completed), lists take comma-separated values,--custom-fieldsand--taskstake JSON (inline,@fileor-), andnullclears a member that allows it. --jsonprints the response as Asana returns it (theapicommand always does); otherwise lists print as tables and objects askey: valuelines.- Exit codes: 0 success, 1 the policy or Asana refused (the message is on stderr), 2 usage error.
asana-gate <resource> --helplists the verbs,asana-gate <resource> <verb> --helpthe flags.asana-gate api <METHOD> <path> [-q key=value] [-d json|@file|-]reaches every operation;asana-gate explain <gid>shows how the policy treats one object (it reads Asana unrestricted, so it is for policy owners, not agents).
A project keeps its policy and settings in .asana-gate/ (policy.yaml, settings.yaml, personal settings.local.yaml, and the git-ignored token); ~/.asana-gate/ holds your defaults, token, cache and logs; an administrator's managed settings override everything.
Commands find .asana-gate by walking up from the working directory; -C <dir> runs a command as if started in <dir>.
The token comes from ASANA_ACCESS_TOKEN, else the token.command of the user's settings, else .asana-gate/token in the project or ~/.asana-gate/token; it is never printed, and asana-gate token status says which source is in use.
asana-gate init creates the project directory (--agent claude-code also wires Claude Code), asana-gate check validates the settings, the token and every object the policy names against Asana (exit code 1 on a problem), asana-gate settings shows the values in force and where each comes from, and asana-gate cache shows or clears the on-disk cache of workspace names.
In a host without MCP (Claude Cowork, for example) work from the folder that holds .asana-gate with these commands, and run asana-gate check first: it tells in one go whether Node, the network, the token and the policy are all in place.
Details: references/cli.md.
Writing or changing a policy
A policy names one workspace and lists rules; their order does not matter.
version: 1
workspace: "1200000000000001" # a gid or a name
rules:
- allow: [read] # no `where`: the whole workspace
- where: { project: "Mobile App" }
allow: ["task:*", story:create, tag:apply]
- where: { project: "Mobile App", section: "Done" }
deny: [modify] # frozen but still visible; deny: ["*"] would hide it
- where: { project: "Mobile App" } # constraints: what requests must look like
require: { task: { due_on: true } }
apply: { task: { name: { prefix: "[bot] " } } }
confirm: [task:delete]
limit: { "task:create": { max: 20, per: 1h } }
How a request is decided:
- Scope.
wherekeys:team,portfolio,project,section(named inside itsproject),subtree(a task gid and all its subtasks),task(a gid),unhomed: true(tasks in no project); the filterstag,assignee(me, a gid or an email) andcustom_field: { Name: value }; and the state filterscreated_by,completed,has_parent,name: { prefix | suffix | contains | matches },overdue,due_within,created_within. Keys in onewhereare ANDed, and a name is resolved against Asana when the policy loads, so an unknown name is an error rather than a silent no-match. - Actions.
<resource>:<verb>such astask:update; the groupsread,modify(every verb but read),write(read plus every creating and modifying verb) andadmin(everything); the wildcardstask:*and*:delete.readis the prerequisite of every verb: an object whosereadis not allowed is invisible, whatever else a rule says. Updatingcompletedneedstask:complete,assigneeneedstask:assign, a project'sarchivedneedsproject:archive; other members needupdate. Explicit-only actions never come from a group or a wildcard and must be written out to be allowed:webhook:create,webhook:update,export:create,organization_export:create,organization_export:read,audit_log:read; indeny,confirmandlimita group or wildcard does cover them. - Which rule wins. The most specific scope decides (task, then subtree, section, project, then team, portfolio or unhomed, then workspace), then the rule with more filters; at equal specificity
denywins; no matching rule means deny. Filters never make an object visible on their own: they widen or narrow what may be done with objects another rule already lets the agent read, and they can hide (deny: [task:read]). A rule takes part only for the actions it names, so a read-only scope isallow: [read]plusdeny: [modify]: the deny keeps a broaderallowelsewhere from reaching it, whiledeny: ["*"]would also deny read and hide the objects. A task in several projects must be allowed in every one of them, and a subtask takes the placement of its nearest ancestor that has one. - Constraints shape the requests made in a rule's scope and may stand alone, without
allow:require(per resource and member,truefor "must be present", or operatorsprefix,suffix,contains,matches,not_matches,in,not_in,min_length,max_length,min,max),apply(a bare default value, ordefault,value,prefix,suffix,add),forbid(members that may not be set),redact(members removed from responses),limit(maxperper, such as1h),confirm(actions a person must approve).applyruns beforerequire, so a prefix one rule adds satisfies the prefix another requires; every matching rule'srequireholds, the most specific rule'sapplywins per member, andforbid,redactandconfirmadd up. Custom fields nest by name:require: { task: { custom_fields: { Owner: { in: [AI] } } } }. fieldson anallowrule limits updates to the listed members:allow: [read]withfields: { task: [completed, due_on] }lets the agent update only those two (and complete, whichcompletedstands for).
Workflow: asana-gate init writes .asana-gate/policy.yaml from the workspace (its teams and projects listed as comments to start from), then edit it, then asana-gate check (resolves every name, warns about rules that cannot work, confirms the token), then asana-gate explain <gid> to see which rule decides each action on one object and which constraints are in force.
The full reference, every key and value included, is in references/policy.md; the MCP server's transports and confirmation flow are in references/mcp.md.
Resources and verbs
| Resource | Verbs |
|---|---|
task | read create update delete complete move add_project remove_project set_parent assign |
story project_brief custom_field goal goal_relationship membership webhook allocation budget rate role record table ooo_entry time_tracking_category time_tracking_entry | read create update delete |
attachment status_update project_template custom_field_setting | read create delete |
section | read create update delete reorder |
project | read create update delete archive manage_members |
project_membership custom_type portfolio_membership team_membership workspace_membership user_task_list job event audit_log ai_studio_usage agent time_period reaction | read |
task_template | read delete |
tag | read create update delete apply |
portfolio | read create update delete manage_members |
project_portfolio_setting user notification | read update |
team | read create update manage_members |
workspace | read update manage_members |
export | create |
organization_export | read create |
access_request timesheet_approval_status | read create update |
rule | trigger |