Cursor Local Asana works (Connected via ~/.cursor/mcp.json with a real CLIENT_ID). Cloud Authenticate and the Grok Bot connect card both open Asana OAuth with client_id=${env:ASANA_CLIENT_ID} literally, so Asana returns invalid_request: The required parameter client_id is missing or invalid. Mac env vars are set; restarting Grok Bot does not help. Please interpolate those env vars on the Grok Bot / Cloud MCP OAuth path, or allow a static CLIENT_ID there like Local.
Steps to Reproduce
Install the Asana plugin and set ASANA_CLIENT_ID / ASANA_CLIENT_SECRET (also in ~/.cursor/mcp.json with a real CLIENT_ID).
In Cursor Customize → MCPs → Asana, Local Authenticate succeeds (Connected).
Click Cloud Authenticate (or the Grok Bot Asana connect card).
Browser opens Asana OAuth with client_id=${env:ASANA_CLIENT_ID} literally. Asana returns: invalid_request: The required parameter client_id is missing or invalid.
Expected Behavior
Cloud / Grok Bot OAuth should send the real CLIENT_ID, same as Local.
Hey, thanks for the detailed report. This comes down to the difference between local and cloud paths.
Local Authenticate works because ${env:ASANA_CLIENT_ID} gets resolved on your machine. The IDE reads that env var from your local environment when it loads the config. But Cloud Authenticate and the “connect card” flow in Grok Bot run the OAuth handshake on Cursor’s backend, which can’t access env vars from your Mac. So the ${env:...} placeholder never gets expanded and is sent into the authorize URL as-is, which is why Asana returns invalid_request. Restarting Grok Bot won’t help here, this is expected for the cloud path.
How to make cloud work right now:
In the cloud MCP config, set the actual CLIENT_ID value directly, not an env-var reference. Cloud configs are encrypted at rest, and CLIENT_SECRET can’t be read back after saving, so storing values there is safe.
In your Asana OAuth app, add Cursor’s cloud callback to the allowed redirect URIs. You can copy the exact URL from the redirect_uri parameter in the cloud authorize request (we’ve seen https://www.cursor.com/agents/mcp/oauth/callback).
This is basically what you asked for in option 2, using a static CLIENT_ID for cloud just like local. Resolving local shell env vars on the cloud path isn’t possible by design, but static values are supported.
I also passed feedback to the team that the backend should explicitly warn about an unresolved ${...} placeholder instead of building a known-bad authorize URL.
Let me know how it goes after you set a static CLIENT_ID.
What you’re describing, a static CLIENT_ID set explicitly in Cursor Cloud but the connection still fails, is a separate issue from the original env var symptom. It looks like the saved static override isn’t being applied on the cloud path right now. This is a known issue we’re tracking, but I can’t share an ETA yet.
To confirm you’re hitting that exact issue and to help raise the signal to the team, a couple questions:
Where exactly did you set CLIENT_ID, in the plugin variable settings or directly in the cloud MCP config?
When you click Cloud / Grok Bot authenticate, what does the client_id parameter show in the URL on the Asana page, your real ID or still the literal placeholder? A screenshot of the authorize URL would really help.
Is the callback https://www.cursor.com/agents/mcp/oauth/callback added to the allowed redirect URIs in your Asana OAuth app?
Thanks, this helps. What you’re seeing, the client_id in the authorize URL staying as the literal ${env:ASANA_CLIENT_ID} even after you set a static ID and secret in the dashboard, is exactly the issue we’re tracking. The saved static override isn’t being applied on the cloud path yet. I can’t share an ETA right now, but I’ll reply in the thread when there’s an update.
One detail in your URL to double check is redirect_uri=http://localhost:8787/callback. That’s the local IDE auth flow, not the Cloud or Grok Bot path. For Cloud, the callback would be https://www.cursor.com/agents/mcp/oauth/callback. On the local path, client_id comes from your local config, not from the dashboard value, so the dashboard value won’t be picked up there.
To get the local path working right now:
In your local config ~/.cursor/mcp.json or in plugin variable settings, set the actual CLIENT_ID value directly instead of ${env:ASANA_CLIENT_ID}.
If you want to keep using an env var, note that on macOS GUI apps don’t inherit variables from ~/.zshrc. Cursor will only see ASANA_CLIENT_ID if you launch it from a terminal where the variable is exported, or if you set it via launchctl setenv. Otherwise ${env:...} will stay unresolved.
Also, can you confirm where you clicked Authenticate, in the IDE local flow or in the dashboard Grok Bot connect card? Based on the localhost callback, it looks like the local flow, but I want to make sure we’re talking about the same setup.
Thanks, that clears things up. Look at the URL you sent. It has redirect_uri=http://localhost:8787/callback. That’s the local IDE auth flow, even if you click the button in the plugins section. On the local path, client_id isn’t taken from the dashboard, it’s taken from your local config ~/.cursor/mcp.json.
Since the URL still shows the literal ${env:ASANA_CLIENT_ID}, your local config still points to an env var and it isn’t being expanded. On macOS, GUI apps don’t inherit variables from ~/.zshrc, so ${env:...} stays as-is.
To get the connection working right now via the local path:
In ~/.cursor/mcp.json or in the plugin variable settings, set the real CLIENT_ID value directly, not ${env:ASANA_CLIENT_ID}.
If you want to keep using an env var, export it via launchctl setenv ASANA_CLIENT_ID <value> or start Cursor from a terminal where the variable is already set.
Then re-check the authorize URL. client_id should show the real ID, not a placeholder.
For the Cloud / Grok Bot connect card flow with callback https://www.cursor.com/agents/mcp/oauth/callback, if a static ID/secret is set in the dashboard but it’s not being applied on the cloud path, that’s a separate issue we’re tracking. I don’t have an ETA yet, I’ll update the thread when I have news.
Let me know what the URL shows after you set a static value in your local config.