Skip to content

Check Atlassian API tokens at setup and give the model the cloudId - #1442

Merged
helpcodeai merged 1 commit into
mainfrom
fix/atlassian-api-token-setup
Nov 2, 2026
Merged

helpcodeai merged 1 commit into
mainfrom
fix/atlassian-api-token-setup

Conversation

@helpcodeai

@helpcodeai helpcodeai commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Merge order: #1437, then #1443, then this PR. This branch is built on fix/stuck-connector-errors (#1437), so until #1437 is merged the diff below also shows its three commits. The commit of this PR is the last one. The MCP argument-filtering fix that this PR used to carry is now its own PR, #1443 ("Send MCP bridge tools only the variables their schema declares"), so it can ship first. Until #1443 is merged, the Atlassian setup probe and calls also send the connector's variables (email and token) to Atlassian's server as tool arguments, as every MCP bridge does today.

The Atlassian (Jira, Confluence) adapter bridges Atlassian's Rovo MCP server with a personal API token. No call through it has ever succeeded on Cloud. Read-only checks showed two setup faults that the setup form accepted:

  1. The token was pasted twice. initialize answers 401 {"error":"invalid_token"}.
  2. The token was created without scopes. initialize and tools/list work, but discover returns searchableProducts: []. Every Jira and Confluence tool then answers "Unable to resolve user scopes … missing the scope claim".

The bridge also had no setup check at all, because MCP adapters had no probe. And the instructions told the model to "start with getAccessibleAtlassianResources … every call needs a cloudId". Atlassian says API tokens are not bound to a cloudId, and we could not confirm that this tool works with an API token.

This PR does the following:

  • Checks the token before saving.
  • Looks up the site's cloudId from a value the user knows (mycompany.atlassian.net).
  • Tells the model which cloudId to use.
  • Explains both faults when they happen at call time.

Changes

Setup check for MCP bridges (generic)

  • verifyCredentials runs the adapter's probe for an MCP adapter. Without a probe it lists the server's tools, which is what the install does anyway. Before this change every MCP bridge was skipped (no_probe).
  • The check fails when the probe answers with an MCP isError: true result, or when the answer matches a new probe.failWhen rule (a regex over the result text with a hint, an optional field and an optional kind).
  • The HTTP status that the MCP SDK puts in data.status (or in an explained error's cause) is now read, so a 401 is classified as auth_failed.
  • A verifyHints entry can name a field, and its message is then shown under that field on the setup form.

Atlassian adapter

  • Probe: the read-only discover with query: "get issue details".
  • A 401 goes under the token field: "Atlassian refused the email and token together: check the email is the token owner's, the token is pasted once, not revoked, and that API-token authentication is enabled for the Rovo MCP server".
  • An empty searchableProducts or "missing the scope claim" fails the check with: "This API token has no scopes. Create an 'API token with scopes' … agent-interface scopes …".
  • The token pattern is now capped at 300 characters (scoped tokens are about 192). The pattern message says to paste the token only once. The "pasted more than once" check from Explain or refuse the errors that kept connectors stuck #1437 already catches the exact double paste.
  • New optional ATLASSIAN_SITE (non-secret, labelled "strongly recommended"). The pattern accepts a host under atlassian.net, with or without https:// and a trailing slash. It is stored as a bare lower-case host.
  • Instructions and all 189 cloudId parameter descriptions are rewritten:
    • Use the cloudId that the workspace guide names.
    • If there is none, try getAccessibleAtlassianResources.
    • If that fails, ask the user for their site, try that host as the cloudId, and suggest setting the site on the connector.
    • getAccessibleAtlassianResources no longer says "Call it first".

Derived settings (derivedEnvVars, generic, derived-settings.ts)

  • These are non-secret settings that AnythingMCP looks up from a host the user typed. For Atlassian, ATLASSIAN_CLOUD_ID comes from the public https://<site>/_edge/tenant_info, using one unauthenticated GET through the SSRF-guarded client.
  • The lookup runs at the setup check, at install and in both variable editors (PUT :id/env-vars, PUT :id), only when the site changes.
  • If the host answers without a cloudId, the site field shows an error. If the host does not answer at all (timeout or 5xx), the setup continues without a cloudId. A stale cloudId is dropped.
  • There was no existing equivalent: the Odoo database lookup (Name the right Odoo database when the entered one does not exist #931) only names the database in a message and stores nothing.

Instruction facts (instructionFacts, generic, instruction-facts.ts)

  • Stored instructions never resolve {{VAR}}, and writing a fact into them at install would count as a user edit and block "Apply update".
  • Instead, a catalog connector's instructions get the adapter's fact sentences appended when they are served (initialize, per-server resources, the shared workspace guide). Each sentence is added only when all its variables are set.
  • For Atlassian the sentence is: "This workspace's Atlassian site is acme.atlassian.net, cloudId …: pass it as cloudId on every call".
  • Secret variables are never rendered. Values are flattened to one line.

Call-time hints

  • error-hints.ts handles the atlassian adapter only (a hand-built OAuth connector to the same server keeps the OAuth advice):
  • MCP tool results with isError: true now go through deriveErrorHint too. Before, they were returned without any hint. Any hint is appended after the server's own words.
  • extractErrorDetail keeps the MCP SDK's data.status.

Validator and docs

  • validate-adapters.mjs now checks probe.failWhen, derivedEnvVars, instructionFacts (non-secret variables only) and verifyHints.*.field.
  • docs/tool-definition.md and docs/connectors/mcp-bridge.md are updated, and llms-full.txt is regenerated.

Catalog: structural vs read at call time

Installed Atlassian connectors need Apply update for:

  • the new instructions text
  • the cloudId / getAccessibleAtlassianResources descriptions in the catalog snapshot (tools installed from tools/list keep the server's own descriptions)
  • the ATLASSIAN_SITE field in the setup form

These are read from the catalog at check, call or serve time and reach installed connectors without "Apply update":

  • probe / failWhen / verifyHints (the next setup check)
  • derivedEnvVars (the next variable edit)
  • instructionFacts
  • the call-time hints

An installed connector can therefore be fixed by setting the site in its variable editor and replacing the token. "Apply update" is only needed for the new instructions text.

Cloud vs self-hosted

Both deployments behave the same. Nothing here depends on DEPLOYMENT_MODE. The tenant_info lookup and the MCP calls go out from the AnythingMCP server in both deployments, through the same SSRF guard.

Type

  • Bug fix
  • New feature
  • Documentation

Testing

  • Added/updated unit tests
  • Tested manually: no Atlassian credentials were used. No customer credentials were used or fetched.

New and updated specs:

  • adapters.service.atlassian-verify.spec.ts: 401 on the token field, token pasted twice, token too long, unscoped token (empty searchableProducts and the scope-claim isError), scoped token OK, site → cloudId with normalisation, 404 site on its field, non-atlassian.net site refused with no lookup, timeout continues, install stores site and cloudId, "Save anyway" without cloudId, MCP bridge without probe → tools/list, and SDK status read from cause.data.
  • derived-settings.spec.ts, instruction-facts.spec.ts, probe.util.spec.ts
  • dynamic-mcp-tools.atlassian-hints.spec.ts: the unscoped-token hint on an isError result, invalid_token hint with status 401, and other MCP refusals are left untouched.
  • error-hints.stuck-connectors.spec.ts: Atlassian rules, and the OAuth missing-scope advice is kept elsewhere.
  • mcp-servers.instructions.spec.ts: facts in initialize instructions, visible content and the shared guide, with no secret.
  • connectors.controller.spec.ts: the variable editors store, refuse and drop the cloudId.
  • intl/atlassian.live.spec.ts: static checks always run (site pattern, token length, derived and facts, probe and rule, instructions). The live part is gated behind RUN_ATLASSIAN_LIVE=1 and reads ATLASSIAN_EMAIL / ATLASSIAN_API_TOKEN / ATLASSIAN_SITE from the environment only. It runs tenant_info, discover and one JQL search, all read-only. It was not run here.

Commands, all run from the repository root unless noted, on the current #1437 head (03e91544), after splitting out #1443:

Command Directory Result
node scripts/validate-adapters.mjs --warn root exit 0, no warning for intl/atlassian.json (existing warnings in other adapters unchanged)
node scripts/validate-adapters.mjs root exit 0
node scripts/regenerate-catalog.mjs root 705 adapters; catalog.ts changes only in the type declarations (in sync after regeneration)
node scripts/adapter-count.mjs --check root OK: 691 adapters (34 keyless, 12254 tools)
node scripts/generate-llms-txt.mjs --check root exit 0 after regenerating llms-full.txt
node --test scripts/validate-adapters.test.mjs scripts/adapter-new.test.mjs scripts/generate-llms-txt.test.mjs root 58 pass, 0 fail
node --test scripts/satellites/satellites.test.mjs root 18 pass, 0 fail
node --test scripts/check-runtime-deps.test.mjs && node scripts/check-runtime-deps.mjs root 2 pass, exit 0
npx prisma generate packages/backend generated
npx tsc --noEmit -p tsconfig.json packages/backend exit 0
npm run lint:check packages/backend 0 errors, 11 warnings (all existing, none in changed files)
npx jest --maxWorkers=4 src/adapters src/mcp-server/dynamic-mcp-tools src/mcp-server/error-hints src/connectors/connectors src/connectors/catalog-resync src/connectors/engines/mcp-client src/mcp-servers src/mcp-server/shared src/mcp-server/mcp-resources packages/backend 722 suites passed, 1 skipped; 24978 tests passed, 1786 skipped (gated live specs)

The full backend npm test was not run: only the targeted suites above. Before the rebase, src/adapters/mcp-adapter.spec.ts "prefixes the tools of a bridge that sets a tool prefix (linear)" failed on #1437's own pattern enforcement. #1437 fixed this in 03e91544, and it passes after the rebase.

Checklist

  • My code follows the existing code style
  • I have tested my changes locally (targeted suites, see above)
  • I have updated documentation (if applicable)
  • All existing tests pass (npm test): targeted suites only, see above
  • This PR has a descriptive title

Related Issues

Depends on #1437. Merge after #1437 and after #1443.

Open points

  • Not checked against a real Atlassian site: RUN_ATLASSIAN_LIVE=1 needs a scoped test token. The discover result shape for unscoped and scoped tokens comes from the read-only production checks described above.
  • I could not confirm that the Rovo MCP server accepts a site host as cloudId. A third-party listing suggests it does. The instructions say "try", and the reliable path is the stored cloudId.
  • A model that ignores the instructions still omits cloudId. Filling it in automatically from ATLASSIAN_CLOUD_ID (for example a parameter default, or naming the derived variable cloudId, which the schema-based injection of Send MCP bridge tools only the variables their schema declares #1443 would then fill) is possible but left out of this PR.
  • The PUT :id / PUT :id/env-vars editors show ATLASSIAN_CLOUD_ID as an ordinary variable. A user can edit it by hand, and it is only looked up again when the site changes.
  • Generic hints now also apply to MCP isError results. For example, a missing-scope text from an OAuth MCP server now gets the existing "Authorize with Provider" advice.

The Atlassian Rovo MCP bridge had no setup check, so a token pasted
twice and a token created without scopes were both saved as ready and
failed every call.

- Setup check for MCP bridges: the adapter's probe, or tools/list when
  it declares none. An isError tool result and probe.failWhen rules
  fail the check; a hint can name the field it belongs under; the
  status the MCP SDK keeps in data.status is read.
- Atlassian probes the read-only discover: a 401 is put under the token
  with what to check, an empty searchableProducts or "missing the scope
  claim" asks for an API token with scopes. Token pattern capped at 300.
- Optional ATLASSIAN_SITE: the cloudId is looked up from the site's
  public tenant_info (derivedEnvVars) at the check, at install and when
  the site changes in the variable editor, and stored as
  ATLASSIAN_CLOUD_ID.
- instructionFacts: per-workspace sentences added to a catalog
  connector's instructions when served, so the model is told the site
  and cloudId. Only non-secret variables are rendered.
- Instructions and cloudId descriptions use that cloudId and fall back
  to getAccessibleAtlassianResources, then to asking for the site.
- Call-time hints for invalid_token and "missing the scope claim",
  also on MCP isError results, linking the connector page.
- Regenerate llms-full.txt.
@helpcodeai
helpcodeai force-pushed the fix/atlassian-api-token-setup branch from f1ee308 to c75a1b4 Compare November 2, 2026 10:03
@helpcodeai
helpcodeai enabled auto-merge (squash) November 2, 2026 10:04
@helpcodeai
helpcodeai merged commit df2aaab into main Nov 2, 2026
14 checks passed
@helpcodeai
helpcodeai deleted the fix/atlassian-api-token-setup branch November 2, 2026 10:18
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 2, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant