asana-connector
Provides tools for interacting with Asana, allowing users to list projects and tasks, create and update tasks, and add comments to tasks.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@asana-connectorlist my projects"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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.
Live deployment
Console | https://doo-asana-connectorfrontend-production-80e4.up.railway.app/overview |
MCP endpoint |
|
MCP liveness | https://doo-asana-connectorfrontend-production-80e4.up.railway.app/mcp/health — open, so platform probes work |
The MCP endpoint answers 401 without Authorization: Bearer <MCP_AUTH_TOKEN>.
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 devConsole → 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: 757 tests
npm run test:frontend # console suite: 29 tests
npm run verify # typecheck + lint + secret scan + licence audit + both suites
npm run mcp # MCP server over stdioThe suite includes a five-action acceptance scenario
(tests/integration/acceptance.test.ts) that walks the required actions in
order and asserts approval, idempotency, pagination, request ids, the
stale-read guard and error normalization at the point each applies.
Related MCP server: Asana MCP Server
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 |
| READ |
|
| READ |
|
| WRITE |
|
| WRITE |
|
| WRITE |
|
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 → Redirect URLs, add this exact redirect URL:
http://localhost:8787/api/auth/oauth/callbackCharacter for character —
httpnothttps, port8787, no trailing slash. Asana only checks this after you log in, so getting it wrong shows a normal login page and then fails at the very end withinvalid_request: The redirect_uri parameter does not match a valid url for the application.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/callbackConnect. Either use Settings → Connect with Asana in the console, or run the guided flow:
npm run oauth:connectIt prints the URL to open, waits for Asana's redirect, then proves the resulting token works by calling
testConnectionwith it.A PAT takes precedence over OAuth. If
ASANA_ACCESS_TOKENis set, the connector keeps using it and a successful OAuth connection is silently ignored.npm run oauth:connectchecks for this and refuses rather than letting you complete a flow whose result is discarded — comment the PAT out first to exercise the OAuth path.To check everything before the consent click, against the real Asana authorization endpoint:
npm run verify:oauth
Scopes requested (least privilege — nothing more than the five actions need):
projects:read tasks:read tasks:write stories:write users:read workspaces:readIf you get
forbidden_scopes: Asana's granular scopes have to be enabled per-app in the developer console, and apps do not have them by default. Either enable them on your app at https://app.asana.com/0/my-apps, or setASANA_OAUTH_SCOPES=(blank) in.env— blank omits thescopeparameter entirely and asks for the app's default permissions, which is Asana's documented fallback.The fallback is not least privilege. Asana grants
default identity, i.e. full permissions for the authorizing user — as broad as a PAT. The connector requests least privilege and never asks for a delete scope, but that only applies if your app has granular scopes enabled. Seedocs/LIMITATIONS.md.
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.
A repository-wide privacy scan (
tests/integration/privacy.test.ts) fails the build if a real Asana identifier — a workspace gid, a project name, a non-reserved email domain — appears anywhere in tracked files.A dependency licence audit (
npm run licenses:check) fails on an unknown or copyleft licence.
Full detail: docs/SECURITY.md.
The /mcp endpoint fails closed
/mcp runs real actions with this server's own Asana credential, so the
connector refuses to start rather than expose it unauthenticated:
Configuration | Result |
| Startup error |
Non-loopback | Startup error |
| Startup error — refused, not ignored |
Local development with no token | Starts with a token minted for the process and printed to stderr |
Local development with | Starts unauthenticated — explicit, loopback only |
approved: true is not authentication. It is write consent inside an
already-authenticated request body, and the two controls are asserted
independently.
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 |
|
|
|
| — | Personal Access Token |
|
| Client-side throttle, just under the 150/min free tier |
|
| Per-request timeout |
|
| In-flight request cap (Asana allows 50 GET / 15 write) |
|
| API port |
| loopback in dev, | Bind interface. Security-relevant — a non-loopback bind makes |
|
|
|
| — | Bearer token for |
|
| Run the local |
|
|
|
| — | This deployment's public origin. The console prints |
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.exampleDocumentation
Document | Contents |
The live walkthrough — every claim, the command that produced it, and the captured output | |
The threat model, the fail-closed MCP policy, token handling, and every automated gate | |
Getting a PAT or OAuth app, and how credentials are handled | |
Why writes are never auto-retried, approval, idempotency, concurrency | |
What is not built, and what is not yet verified | |
Generated API contract | |
Generated connector manifest | |
Generated dependency licence inventory (516 packages) |
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 explorerClaude 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.
Over HTTPS
The API server also mounts the same adapter at /mcp, so a deployment exposes
both surfaces on one origin and one process:
https://doo-asana-connectorfrontend-production-80e4.up.railway.app/mcp # Streamable HTTP endpoint
https://doo-asana-connectorfrontend-production-80e4.up.railway.app/mcp/health # liveness, unauthenticatedMCP_AUTH_TOKEN is mandatory in production — the server will not start
without it. The endpoint executes real actions using the server's own Asana
credential, so without a token anyone who learns the URL can drive the
workspace. Generate one:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Clients send it as a bearer token:
Authorization: Bearer <MCP_AUTH_TOKEN>Running locally with no token configured is fine: the server mints one for the process and prints it to stderr at startup.
The standalone process (npm run mcp with MCP_TRANSPORT=http) remains
available for running MCP on a port of its own.
Assistant
The console includes an assistant: plain language in, connector actions out. It is a third adapter over the same core, and it holds one rule —
The assistant never writes. Reads run immediately; a write is returned as a
proposal, rendered with its duplicate-behaviour warning, and executed only after
the user approves it — through the same action route with approved: true.
That matters because Asana text flows back into the model, which is the shape of a prompt-injection attack, and because this connector cannot delete what it creates.
Set GROQ_API_KEY to enable it. Without a key the console runs unchanged and
hides the assistant.
Assignment checklist
Marked honestly. Anything not demonstrated is called out rather than assumed.
Live results below are from 6 September 2026; see
docs/LIMITATIONS.md for the full record.
Requirement | Status |
Manifest exists | Yes — generated, |
Asana authentication (PAT + OAuth 2.0) | PAT verified live; OAuth authorize step verified live (real |
| Yes — asserted by test (no non-GET request), and it returns a |
All five required actions implemented | Yes — verified live, 9/9, plus 30 extended actions (35 total) |
Captured provider fixtures for all five | Yes — including real |
Typed input/output schemas | Yes — Zod, single source of truth |
Inputs validated | Yes — before any network call |
Errors normalized | Yes — 19 |
Request IDs | Yes — connector-generated on every result and every error; Asana returns none |
Retry classification | Yes — including |
Pagination | Yes — cursor-based, all list actions, asserted to advance |
Rate limits handled | Yes — client-side pacing before sending, |
Approval / idempotency / duplicates documented | Yes — |
Idempotency | Replay, key-conflict detection, concurrent collapse, optional durable store — not distributed, and stated as such |
No secrets committed | Yes — scanner + privacy scan + pre-commit hook + CI |
Unit and fixture tests pass | Yes — 786 total (757 connector + 29 console), executed |
OpenAPI exists | Yes — generated, 3.1.0, validated by a real OpenAPI parser in CI |
JSON Schema | Draft 2020-12, compiled by Ajv in CI; conversion is fail-closed |
MCP adapter exists, duplicates no logic | Yes — enforced by test, all 35 actions exposed as tools |
MCP endpoint security | Fails closed — production or external bind without |
Frontend connected to the real backend | Yes — no mocked UI data, all 35 actions surfaced |
Frontend responsive and accessible | Yes — per-breakpoint layouts, 29 tests |
Documentation and known limitations | Yes — including what is not verified |
Licensing | Root |
Versioned v1.0.0 | Yes |
Real sandbox/test-account flow | Verified 2026-09-06 — required 5 actions live end-to-end (9/9), including idempotency replay creating no duplicate |
MCP endpoint driving live Asana | Verified 2026-09-06 — authenticated Streamable HTTP session, 35 tools listed, |
OAuth 2.0, end to end | Verified live 2026-09-06, 11/11 — consent with a real login, code exchange, encrypted persistence, decryption by a fresh process, token refresh, and revocation confirmed by Asana rejecting the revoked token |
HTTPS MCP endpoint deployed | Deployed — |
See docs/LIMITATIONS.md for what remains
externally unverified.
Scripts
Command | Purpose |
| API + console together |
| Connector suite (757 tests) |
| Console suite (29 tests) |
| typecheck + lint + secret scan + licence audit + both suites |
| Regenerate |
| Fail if the committed contracts are stale |
| Regenerate |
| Fail on an unknown/copyleft licence or stale notices |
| Interactive .env setup — hidden token input, verifies the connection |
| Live-check the OAuth flow up to the consent click (PKCE, scopes, state) |
| Guided consent flow, then verifies the resulting token |
| Read-only check against real Asana (needs a PAT) |
| Also exercises create/update/comment |
| Use the connector as a library |
License
MIT — see LICENSE.
Dependency licences are inventoried in
THIRD-PARTY-NOTICES.md, regenerated by
npm run licenses and verified in CI. All 516 packages carry permissive
licences; the repository vendors no third-party source.
This server cannot be deployed
Maintenance
Related MCP Connectors
Task management for people and AI agents, with scoped OAuth access to issues, projects, and docs.
Task management for people and AI agents, with scoped OAuth access to issues, projects, and docs.
Create and manage MeisterTask projects, tasks, and notes from your AI assistant.
Read teams, spaces, lists and tasks; create, update and comment on tasks and track time.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables Claude to interact with Asana workspaces, tasks and projects through the Asana API, allowing users to search, create, update, and manage Asana tasks and projects using natural language.2,237 npmMIT
- AlicenseNot gradedqualityNot gradedmaintenanceEnables comprehensive Asana workspace management through natural language, supporting task tracking, project planning, team collaboration, and workflow automation across workspaces, projects, sections, and tasks.1-
- AlicenseNot gradedqualityDmaintenanceProvides a standardized interface for interacting with Asana's tools and services through a unified API, enabling AI assistants to manage tasks, projects, and workflows.MIT
- AlicenseNot gradedqualityCmaintenanceEnables to interact with Asana through Claude, with read and write tools for tasks, projects, tags, and custom fields, with no destructive operations.19 npmMIT