Skip to content

Sign in to the Instagram connectors with Meta (Unified Login) - #1630

Merged
helpcodeai merged 12 commits into
mainfrom
feat/meta-oauth-instagram-login
Nov 11, 2026
Merged

helpcodeai merged 12 commits into
mainfrom
feat/meta-oauth-instagram-login

Conversation

@helpcodeai

@helpcodeai helpcodeai commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Connecting Instagram today means pasting a Graph API token and looking up an account id by hand. This PR adds Authorize with Provider for the Instagram connectors, so a user signs in with Instagram or Facebook and AnythingMCP fills in the token and the account id.

Meta apps created since autumn 2026 no longer get "Instagram Login": their dashboard only offers Instagram API with Unified Login, which is Facebook Login for Business with a configuration id. The instagram connector therefore signs in with Unified Login when its app has a configuration id, and with classic Facebook Login otherwise. A new instagram-login adapter covers apps that still have Instagram Login.

Draft until the sign-in has been completed end to end against a real account (see Testing).

Changes

  • Unified Login for the instagram connector (meta-login.util.ts, meta-login.service.ts, meta-auth.controller.ts):
    • the dialog takes config_id instead of scope, with override_default_response_type=true and prompt=login
    • the code is exchanged at Graph v27.0 and gives a Business Integration System User token, which is stored as is and never exchanged or renewed (systemUser flag in the bearer renewal)
    • accounts come from /me/assigned_instagram_accounts; when that is empty, from the client business portfolio (owned_instagram_accounts, instagram_accounts) and from the account ids in the token's granular scopes (debug_token)
    • a sign-in that returns no account keeps its token; the user can enter the account id (checked with the token) or sign in again
  • Facebook Login with a scope list for older apps (no configuration id); Instagram Login (instagram-login, graph.instagram.com) for apps that still have it.
  • Apps: the connector's own app (META_APP_ID / META_APP_SECRET, plus META_CONFIG_ID for Unified Login; or INSTAGRAM_APP_ID / INSTAGRAM_APP_SECRET), else for instagram only a server app (META_PLATFORM_APP_ID / _SECRET / META_PLATFORM_CONFIG_ID). There is no server app for Instagram Login, because new Meta apps cannot get it.
  • Adapters:
    • instagram: new instagram_list_assigned_accounts, six Direct Message tools (send message and private reply installed switched off), mention and comment tools, Unified Login instructions and variables, and the "Instagram DM" / "Instagram Direct" aliases moved here
    • new instagram-login adapter
  • UX: every outcome of the sign-in returns to the connector page with a banner and its next step: test the connection, pick the account, enter its id, or authorize again. The OAuth complete page reads its parameters once, so it no longer shows "Nothing to complete here" while the completion runs. Strings in de/en/it.
  • Platform data: deleting an instagram or instagram-login connector deletes its tool_invocations rows (scoped by organization and connector) in the same transaction (Meta Platform Terms §3.d). They already cascaded through the tools; the explicit delete keeps it so if the relation changes.
  • Logging: the callback and the completion log every outcome, and account discovery logs counts, error codes and scope names. A spec checks that neither the token nor the app secret is logged.
  • GET /api/meta/platform, GET /api/meta/connectors/:id/accounts, POST /api/meta/connectors/:id/account; OpenAPI tag Meta.
  • docker-compose.cloud.yml and docker-compose.yml pass META_PLATFORM_APP_ID, META_PLATFORM_APP_SECRET and META_PLATFORM_CONFIG_ID to the backend, next to the TikTok Shop platform variables. The Coolify and quickstart files list none of the platform variables, so they are unchanged.
  • Docs (docs/deployment.md, docs/tool-definition.md), .env.example, quoted adapter count 732, regenerated llms.txt / llms-full.txt; the validator accepts tokenRenewal.configIdVar.

Cloud vs self-hosted

  • Cloud: with META_PLATFORM_APP_ID, META_PLATFORM_APP_SECRET and META_PLATFORM_CONFIG_ID set (app 1785424892691435, configuration 2539939149839675, redirect https://cloud.anythingmcp.com/api/meta/callback), the setup page asks for no app: the user only signs in. Users without a role on the app need Meta App Review (Advanced Access) and Live mode. That review is in preparation and is not part of this PR.
  • Self-hosted: the same code. Without the server variables, users enter their own Meta app and register <SERVER_URL>/api/meta/callback. Pasting a token keeps working in both.

Type

  • Bug fix
  • New feature
  • Documentation
  • Refactoring
  • Performance improvement
  • Other (describe below)

Testing

  • Added/updated unit tests (meta-login util, service and controller, bearer renewal, connector removal, adapter static specs)
  • Tested manually (see below)
  • No tests needed (explain why)

Commands (worktree root unless noted):

  • node scripts/validate-adapters.mjs --warn: 748 passed, 0 failed, 1434 warnings, the same total as before the rebase.
  • node scripts/regenerate-catalog.mjs, node scripts/adapter-count.mjs --check (OK, 732 adapters), node scripts/generate-llms-txt.mjs, node --test scripts/validate-adapters.test.mjs (32/32).
  • In packages/backend:
    • npm run lint: 0 errors.
    • npx tsc --noEmit -p tsconfig.json: clean.
    • npx jest --maxWorkers=4 with DATABASE_URL set to a local test database: 1075 suites passed, 1 failed, 10 skipped. The failure is ee/cloud/onboarding-cron.service.spec.ts, and only because my local .env exports FRONTEND_URL=http://localhost:3000; run without that env it passes 89/89.
    • Without a database, auth/jwt-or-management-key.guard.spec.ts cannot run; it passes 14/14 against the test database.
  • In packages/frontend: npm run lint (0 errors), npx tsc --noEmit -p tsconfig.json (clean).

Manual, local stack (backend and frontend dev servers, a Cloudflare quick tunnel as SERVER_URL, the Cloud Meta app in development mode, a real Facebook profile):

  1. GET /api/meta/platform returns unified: true. Authorize returns https://www.facebook.com/dialog/oauth with client_id, config_id, override_default_response_type=true, prompt=login and no scope.

  2. First sign-in: the code exchange worked, but no account came back, and the old code discarded the token. That led to keeping the token, the banner on the connector page, and the logging.

  3. Second attempt: Meta never redirected to the callback (the attempt stayed pending). Logging for every callback and completion outcome was added.

  4. Third sign-in (portfolio helpcode.ai GmbH, Instagram account treppeo.de, which is owned by another portfolio and only shared into helpcode.ai): callback, exchange and storage all worked, and the token is kept. With the stored token:

    • debug_token: type: SYSTEM_USER, valid, expires_at: 0 (never), all five Instagram scopes granted, but the granular scopes carry no target_ids
    • /me?fields=client_business_id returns the helpcode.ai portfolio
    • /me/assigned_instagram_accounts is empty at v25.0 and at v27.0
    • the account's asset id returns (#10) Application does not have permission
    • the portfolio lists need business_management, which the configuration does not request

    So Meta granted the permissions but no Instagram account, because the chosen portfolio does not own it. The connector page now says exactly that (reason=no_assets), and the instructions tell users to pick the portfolio that owns the account.

  5. Catalog update on the installed connector applied through POST /api/connectors/:id/resync-catalog: the stored instructions now start with Unified Login, and the catalog diff is up to date.

  6. Fourth sign-in with the portfolio that owns the account (Treppeo, account treppeo.de):

    • log: Unified Login: 1 Instagram account(s) (assigned_instagram_accounts=1)
    • the token and INSTAGRAM_ACCOUNT_ID (17841448449522133) were stored automatically, and the user landed on the connector page with the "connected" banner
  7. Read-only tool checks against that account (nothing published, nothing sent):

    • instagram_get_account: OK (username, followers, media count)
    • instagram_list_assigned_accounts: OK (1 account)
    • instagram_list_media: OK (5 of 11 posts)
    • instagram_get_media_insights on a video post: OK (reach, views, likes)
    • instagram_get_account_insights (reach, day): OK
    • instagram_list_comments on a post with a comment: OK (1 comment)
    • instagram_list_conversations: 403, Meta code 200, "The account owner has disabled access to Instagram Direct Messaging Outside of Instagram App". The path and token are correct (the same answer at v25.0 and v27.0, with and without platform=instagram); the owner has to turn on Allow access to messages in the Instagram app. The instructions now quote this message so the assistant explains it instead of blaming the token.
  8. The banner title names the account ("Instagram is connected as @treppeo.de").

Checklist

  • My code follows the existing code style
  • I have tested my changes locally
  • I have updated documentation (if applicable)
  • All existing tests pass (npm test) (see Testing for the two environment-only cases)
  • This PR has a descriptive title

Related Issues

Instagram connector sign-in; Meta App Review for the Cloud app is tracked separately.

instagram-login covers the Instagram API with Instagram Login on
graph.instagram.com: no Facebook Page needed. Profile, media, stories,
tagged media, mention replies, account and media insights, comment
moderation, Direct Messages (conversations, messages, user profile)
and publishing. Sending, publishing and deleting install switched off.
The token is refreshed by the existing instagram_login renewal.

The Facebook Login adapter gains mentioned media and comments, mention
replies, top-level comments, comments on/off, recently searched
hashtags, and comment and media deletion (switched off).
Meta's logins issue no refresh token, so the Instagram connectors took
a pasted token. "Authorize with Facebook" (instagram) and "Authorize
with Instagram" (instagram-login) now get it by signing in: the code
is exchanged for a long-lived token, written to INSTAGRAM_ACCESS_TOKEN
with the account id, and handed to the bearer token renewal. A
Facebook user with several Instagram accounts picks one.

The app is the connector's own, else the server's when
META_PLATFORM_APP_* or INSTAGRAM_PLATFORM_APP_* are set, the same on
Cloud and self-hosted; the redirect URI is <SERVER_URL>/api/meta/callback.
Tokens from the server's Facebook app are renewed with that app.
Pasting a token keeps working.

A pasted Instagram Login token whose first refresh came too early was
never refreshed afterwards; it is now retried once the wait is over.
New Meta apps no longer get Instagram Login: they sign in through
Facebook Login for Business with a configuration id. The instagram
connector now uses that flow when its app (or the server app with
META_PLATFORM_CONFIG_ID) has one, stores the non-expiring system user
token without renewal, finds the shared accounts, and gains Direct
Message tools. Instagram Login stays for apps that still have it, with
no server app. Deleting an Instagram connector deletes its tool-call
logs in the same transaction.
A Unified Login that returns no Instagram account kept nothing. Now the
token is stored, the accounts are looked up through the client business
portfolio and the granted scopes as well, and every outcome of the
sign-in returns to the connector page with a banner and its next step:
test, pick an account, enter its id, or authorize again.
debug_token shows the account ids a Unified Login token was granted.
None at all means the client chose a business portfolio that does not
own the account, so say that on the connector page instead of guessing.
The portfolio lists are asked only with business_management, and the
account id hint no longer insists on a 1784 prefix.
@helpcodeai
helpcodeai marked this pull request as ready for review November 11, 2026 10:30
@helpcodeai
helpcodeai requested a review from D3nisty as a code owner November 11, 2026 10:30
@helpcodeai
helpcodeai enabled auto-merge (squash) November 11, 2026 11:41
@helpcodeai
helpcodeai merged commit 7e84ed5 into main Nov 11, 2026
16 checks passed
@helpcodeai
helpcodeai deleted the feat/meta-oauth-instagram-login branch November 11, 2026 12:12
@github-actions github-actions Bot locked and limited conversation to collaborators Nov 11, 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