Skip to main content
Glama
duytnb79
by duytnb79
README.md
# @duytnb79/asana-mcp

A read-only local MCP server for Asana that uses an Asana personal access token (PAT).

It runs over stdio and calls GET endpoints in the standard Asana REST API at `https://app.asana.com/api/1.0`. It is separate from Asana's hosted MCP server at `https://mcp.asana.com/v2/mcp`, which requires a registered MCP app and OAuth.

## Requirements

- Node.js 24+
- An Asana personal access token
- Access to the Asana workspaces, projects, and tasks you want to use

Create a PAT in the Asana developer console and treat it like a password. The server can only access data and perform actions allowed for the Asana user who owns the token.

## Installation

### Local clone

```bash
npm install
npm run build
cp .env.example .env
node dist/index.js
```

### Published package

After this package is published, it can be run with:

```bash
npx -y @duytnb79/asana-mcp
```

Or installed globally:

```bash
npm install -g @duytnb79/asana-mcp
asana-mcp
```

## Configuration

Create a `.env` file or provide environment variables through your MCP client:

```bash
ASANA_ACCESS_TOKEN="your_asana_personal_access_token"
ASANA_TIMEOUT_MS="10000"
ASANA_MAX_PAGE_SIZE="100"
ASANA_MAX_IMAGE_BYTES="10485760"
```

Required:

- `ASANA_ACCESS_TOKEN`

Optional:

- `ASANA_TIMEOUT_MS` — request timeout in milliseconds; defaults to `10000`
- `ASANA_MAX_PAGE_SIZE` — maximum page size exposed by list/search tools; defaults to `100` and must be between `1` and `100`
- `ASANA_MAX_IMAGE_BYTES` — maximum downloaded image size in bytes; defaults to `10485760` (10 MiB)

The server automatically loads `.env` when running locally.

## Test the Asana connection

Copy the example environment file, replace the placeholder with your real PAT, then run the read-only connection test:

```bash
cp .env.example .env
# Edit .env and set ASANA_ACCESS_TOKEN
npm run test:connection
```

A successful response starts with:

```text
Asana connection successful.
```

It then prints the authenticated user and accessible workspaces. This test calls only `GET /users/me`; it does not create or modify Asana data.

## MCP client configuration

### Local build

```json
{
  "mcpServers": {
    "asana": {
      "command": "node",
      "args": [
        "/absolute/path/to/asana-mcp-server/dist/index.js"
      ],
      "env": {
        "ASANA_ACCESS_TOKEN": "your_asana_personal_access_token"
      }
    }
  }
}
```

Alternatively, run through npm from the project directory:

```json
{
  "mcpServers": {
    "asana": {
      "command": "npm",
      "args": ["start"],
      "cwd": "/absolute/path/to/asana-mcp-server",
      "env": {
        "ASANA_ACCESS_TOKEN": "your_asana_personal_access_token"
      }
    }
  }
}
```

### Published package

```json
{
  "mcpServers": {
    "asana": {
      "command": "npx",
      "args": ["-y", "@duytnb79/asana-mcp"],
      "env": {
        "ASANA_ACCESS_TOKEN": "your_asana_personal_access_token"
      }
    }
  }
}
```

## Available tools

### Read tools

- `list_projects`
  - Lists projects in a workspace.
  - Supports `archived`, `limit`, `offset`, and `opt_fields`.
- `list_tasks`
  - Lists tasks in a project in project priority order.
  - Supports `completed_since`, `limit`, `offset`, and `opt_fields`.
- `get_task`
  - Gets one task by GID.
- `search_tasks`
  - Searches a workspace by assignee, completion state, modified time, project, or text.
  - Asana search is eventually consistent and may lag recent writes by 10–60 seconds.
  - The search endpoint does not support normal Asana offset pagination and returns at most 100 items.
- `list_sections`
  - Lists sections in a project.
  - Supports `limit`, `offset`, and `opt_fields`.
- `list_task_comments`
  - Returns the newest human comments from one task, excluding assignment, due-date, and other system activity stories.
  - Accepts `task_gid` and an optional `limit` from 1 to 100; the default is 20.
  - Scans the task's paginated story history, filters comments, and returns the requested number in newest-first order.
- `list_task_attachments`
  - Lists attachment metadata for a task with offset pagination; temporary download URLs are not exposed.
- `read_attachment_image`
  - Downloads one attachment by GID and returns MCP image content for visual analysis.
  - Supports PNG, JPEG, WebP, and GIF up to `ASANA_MAX_IMAGE_BYTES`; SVG and non-image files are rejected.

The server does not register task creation, task update, or comment-writing tools. Its Asana client issues GET requests only.

## Pagination

Asana list endpoints return an opaque `next_page.offset`. The MCP response exposes it as `meta.next_offset`.

Pass that value back as `offset` to retrieve the next page. Only use offsets returned by Asana; they can expire when underlying data changes.

## Input/output fields

Asana returns compact objects by default. Use `opt_fields` to request additional properties, for example:

```json
{
  "project_gid": "12345",
  "limit": 50,
  "opt_fields": [
    "name",
    "completed",
    "assignee.name",
    "due_on",
    "permalink_url"
  ]
}
```

Keep `opt_fields` focused. Very broad or deeply nested responses are more expensive and may be rate-limited.

## Rate limits and errors

- Asana returns HTTP `429` when a token is rate-limited.
- The server reports the `Retry-After` value when Asana provides it and does not retry requests automatically.
- Authentication, permission, validation, not-found, timeout, and server errors are converted into readable MCP errors.
- The PAT is sent only in the `Authorization: Bearer` header and is never placed in request URLs.

## Security

- Never commit `.env` or a PAT.
- Prefer a dedicated PAT with the minimum user permissions needed for this integration.
- Rotate the PAT if it is exposed.
- The PAT still inherits the permissions of its Asana account; read-only behavior is enforced by this server's GET-only client and exposed tools, not by changing the PAT itself.
- This server intentionally exposes specific read operations rather than a generic HTTP passthrough tool.
- Image downloads never receive the Asana authorization header, validate redirects, reject local/private IP literals, verify image signatures, and enforce a bounded in-memory size.

## Development

```bash
npm run dev
npm run typecheck
npm run build
npm start
```

TDQS

B3.4/5.0

Scored across 8 tools

Disambiguation4/5

Most tools target distinct resources (sections, comments, projects) or distinct actions (list vs. get vs. create vs. update vs. search). The main potential confusion is between list_tasks and search_tasks, but their descriptions clarify the scope (project-scoped vs. workspace-wide search).

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern in snake_case: list_sections, add_comment, list_tasks, get_task, create_task, update_task, search_tasks. The sole exception is 'add_comment' rather than 'create_comment', but it still adheres to the same grammatical style.

Tool Count5/5

Eight tools is well-scoped for an Asana integration, covering the essential operations on tasks, projects, sections, and comments without redundancy. This is within the typical 3-15 range and each tool earns its place.

Completeness4/5

The tool surface covers the core task lifecycle (create, get, update, list, search) plus project listing, section listing, and comment creation. Missing operations like task deletion or project creation are notable gaps, but agents can work around them for most workflows.

Maintenance

ActivitySlowing
ResponsivenessNo issues