asana-connector
Enables interaction with Asana projects and tasks via the Asana API, supporting listing projects/tasks, creating/updating tasks, and adding comments, with secure authentication and rate-limit handling.
README
Asana Connector
Project management integration console · DOO Builders League
Builder: Idrees Khaled · Provider: Asana · Version: v1.0.0
A production-oriented Asana connector: a reusable connector core with five typed actions, normalized errors, pagination and rate-limit handling, a thin MCP adapter, and a dark-first developer console over the top.
Quickstart
Runs with no credentials and no configuration. A fresh clone boots into clearly-labelled demo mode, so you can review the whole surface immediately.
git clone https://github.com/Idrees-02/doo-asana-connector.git
cd doo-asana-connector
npm install
npm run dev
- Console → http://localhost:5173
- API → http://localhost:8787
Requires Node ≥ 20.19 and nothing else. No Docker, no global installs, no hosted services.
Reviewing the engineering rather than the UI
npm test # connector suite: all five actions, errors, pagination, rate limits
npm run test:frontend # console tests
npm run verify # typecheck + lint + secret scan + tests
npm run mcp # MCP server over stdio
The five actions
These IDs are fixed by the assignment and are used verbatim throughout the manifest, schemas, OpenAPI spec, MCP tools and console.
| Action | Type | Asana endpoint |
|---|---|---|
asana.list_projects |
READ | GET /projects |
asana.list_project_tasks |
READ | GET /projects/{gid}/tasks |
asana.create_task |
WRITE | POST /tasks |
asana.update_task |
WRITE | PUT /tasks/{gid} |
asana.add_comment |
WRITE | POST /tasks/{gid}/stories |
There is deliberately no delete action — it is not part of the assignment,
and the connector never requests a :delete OAuth scope.
Getting your Asana credentials
Never paste a token into a chat window, an issue, a screenshot, or any file that git tracks. It goes into
.envon your machine and nowhere else..envis gitignored, and a pre-commit hook blocks it even if that is bypassed.
Option A — Personal Access Token (recommended to start)
A PAT is the quickest path and is all you need for local review.
-
Sign in at https://app.asana.com — a free account is enough.
-
Open https://app.asana.com/0/my-apps (or: profile photo → My Settings → Apps → Manage Developer Apps).
-
Click Create new token.
-
Name it something identifiable, e.g.
doo-asana-connector-dev. -
Accept the API terms, then click Create token.
-
Copy it immediately — Asana shows it exactly once.
-
Run the setup command and paste it when prompted:
npm run setupInput is hidden while you type, so the token never appears on screen, in a screen recording, or in your shell history. It is written to
.envwith permissions0600and verified against Asana immediately — without ever being displayed back to you.Prefer doing it by hand?
cp .env.example .env, then edit theASANA_ACCESS_TOKEN=line in your editor. -
Restart
npm run dev. The amber DEMO MODE banner disappears and Settings → Test Connection shows your real account and workspaces.
.env.exampleis tracked by git and is public. Only ever put placeholders there. Real values belong in.env, which is gitignored.
Tip: actions performed through a PAT are attributed to you in Asana's activity feed. For a shared or demo setup, create a dedicated bot user and generate the PAT from that account instead.
Option B — OAuth 2.0 (multi-user)
Needed only if you want the browser-based connect flow rather than a pre-shared token.
-
Same page: https://app.asana.com/0/my-apps → Create new app.
-
Name the app and accept the terms.
-
Under OAuth, add this exact redirect URL:
http://localhost:8787/api/auth/oauth/callback -
Copy the Client ID and Client Secret into
.env:ASANA_OAUTH_CLIENT_ID=... ASANA_OAUTH_CLIENT_SECRET=... ASANA_OAUTH_REDIRECT_URI=http://localhost:8787/api/auth/oauth/callback -
Restart, then use Settings → Connect with Asana.
Scopes requested (least privilege — nothing more than the five actions need):
projects:read tasks:read tasks:write stories:write users:read workspaces:read
Recommended: a sandbox workspace
Before running any write action, create a throwaway project (e.g. Connector Sandbox) and point the connector at it. Write tests then never touch anything that matters. The connector never deletes anything, but it does create tasks and comments.
Secrets policy
Every credential lives in .env and nowhere else. This is enforced
mechanically, not by convention:
src/config.tsis the only module permitted to readprocess.env. An ESLint rule (no-restricted-properties) fails the build if anything else touches it, so credentials cannot spread through the codebase.describeConfig()is the only way config reaches a log, an API response or the UI, and its return type has no field capable of carrying a secret value — presence booleans and an opaque fingerprint only.npm run secrets:scanpattern-scans tracked files (Asana PAT format, bearer tokens, private keys, credential-shaped assignments) and runs in CI.- A pre-commit hook blocks staged
.envfiles outright and re-scans staged content. - The test suite requires no credentials, so CI runs with no secrets configured at all.
Configuration
All configuration is environment-driven (12-factor), so the same build runs
locally and deployed with nothing changed but the environment. See
.env.example for every variable with inline documentation.
| Variable | Default | Purpose |
|---|---|---|
ASANA_MODE |
auto |
auto | live | demo. auto picks live when credentials exist |
ASANA_ACCESS_TOKEN |
— | Personal Access Token |
ASANA_RATE_LIMIT_RPM |
140 |
Client-side throttle, just under the 150/min free tier |
ASANA_TIMEOUT_MS |
15000 |
Per-request timeout |
ASANA_MAX_CONCURRENCY |
8 |
In-flight request cap (Asana allows 50 GET / 15 write) |
PORT |
8787 |
API port |
MCP_TRANSPORT |
stdio |
stdio locally, http for a deployed endpoint |
ASANA_MODE=live without credentials fails at startup on purpose — silently
serving synthetic data to someone who asked for real data would be the worst
possible failure mode.
Project structure
doo-asana-connector/
├── connector.yaml # generated manifest
├── openapi.yaml # generated from the same Zod schemas
├── src/
│ ├── connector.ts # DooConnector: manifest, testConnection, listActions, execute
│ ├── client.ts # Asana HTTP client: pagination, throttle, retry classification
│ ├── config.ts # the only reader of process.env
│ ├── auth/ # PAT + OAuth 2.0
│ ├── actions/ # the five actions
│ ├── schemas/ # Zod schemas — single source of truth
│ ├── errors/ # normalized error system
│ ├── runtime/ # shared execution pipeline
│ └── demo/ # demo provider
├── mcp/server.ts # thin MCP adapter
├── server/ # HTTP API consumed by the console
├── frontend/ # the console
├── tests/ · fixtures/ · examples/ · docs/
└── .env.example
Documentation
| Document | Contents |
|---|---|
docs/AUTHENTICATION.md |
Getting a PAT or OAuth app, and how credentials are handled |
docs/WRITE-SAFETY.md |
Why writes are never auto-retried, approval, idempotency, concurrency |
docs/LIMITATIONS.md |
What is not built, and what is not yet verified |
openapi.yaml |
Generated API contract |
connector.yaml |
Generated connector manifest |
In-app documentation is also available at /docs in the running console.
MCP
npm run mcp # stdio — Claude Desktop, MCP Inspector
npm run mcp:inspect # interactive tool explorer
Claude Desktop (claude_desktop_config.json), replacing the path with this
project's absolute location:
{
"mcpServers": {
"asana-connector": {
"command": "npx",
"args": ["tsx", "/ABSOLUTE/PATH/TO/doo-asana-connector/mcp/server.ts"]
}
}
}
The adapter iterates connector.listActions() and registers each as a tool. It
contains no Asana endpoint, no schema and no business logic — a test asserts the
exposed tool ids equal the connector's action ids, so it cannot drift.
Assignment checklist
Marked honestly. Anything not demonstrated is called out rather than assumed.
| Requirement | Status |
|---|---|
| Manifest exists | Yes — generated, connector.yaml, 35 actions |
| Asana authentication (PAT + OAuth 2.0) | PAT verified live; OAuth authorize step verified live against Asana (real client_id, PKCE, scopes accepted); interactive consent not clicked through — see below |
testConnection has no side effects |
Yes — asserted by test (no non-GET request) |
| All five required actions implemented | Yes — end-to-end tested, plus 30 extended actions (35 total) |
| Typed input/output schemas | Yes — Zod, single source of truth |
| Inputs validated | Yes — before any network call |
| Errors normalized | Yes — 19 ASANA_* codes |
| Request IDs | Yes — connector-generated; Asana returns none |
| Retry classification | Yes — including manual_with_idempotency_key |
| Pagination | Yes — cursor-based, all list actions |
| Rate limits handled | Yes — client-side pacing before sending |
| Approval / idempotency / duplicates documented | Yes — docs/WRITE-SAFETY.md, all 21 write actions require approval |
| No secrets committed | Yes — scanner + pre-commit hook + CI |
| Unit and fixture tests pass | Yes — 263 total (238 connector + 25 console) |
| OpenAPI exists | Yes — generated, 35 endpoints |
| MCP adapter exists, duplicates no logic | Yes — enforced by test, all 35 actions exposed as tools |
| Frontend connected to the real backend | Yes — no mocked UI data, all 35 actions surfaced |
| Frontend responsive and accessible | Yes — per-breakpoint layouts, 25 tests |
| Documentation and known limitations | Yes |
| Versioned v1.0.0 | Yes |
| Real sandbox/test-account flow | Verified — required 5 actions live end-to-end (9/9), 30 extended actions live-checked via asana.get_current_user |
| OAuth interactive consent | Not clicked through — requires a human login, which this assistant will not perform. Authorization request verified live; token exchange/refresh/revoke covered by 21 tests against a double matching Asana's real contract |
| HTTPS MCP endpoint deployed | Not met by design — local-first; HTTP transport implemented but undeployed |
The remaining gap is the HTTPS MCP endpoint, which is not met by design
(local-first). See docs/LIMITATIONS.md.
Scripts
| Command | Purpose |
|---|---|
npm run dev |
API + console together |
npm test |
Connector suite (141 tests) |
npm run test:frontend |
Console suite (25 tests) |
npm run verify |
typecheck + lint + secret scan + tests |
npm run generate |
Regenerate openapi.yaml and connector.yaml |
npm run setup |
Interactive .env setup — hidden token input, verifies the connection |
npm run smoke:live |
Read-only check against real Asana (needs a PAT) |
npm run smoke:live -- --writes |
Also exercises create/update/comment |
npx tsx examples/use-connector.ts |
Use the connector as a library |
License
MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。