MadgicxMCP Docs

Agentic Frameworks

Integrate your backend applications and AI agents with Madgicx MCP using OAuth 2.0 client credentials.

Agentic Frameworks

Agentic frameworks are server-side applications that can securely store credentials. Unlike browser-based OAuth flows, these integrations authenticate directly using a Client ID and Client Secret — no user interaction required after initial setup.

This is ideal for backend services, automated pipelines, and AI agent frameworks that need programmatic access to Madgicx MCP.

Two MCP servers, one set of credentials

Both Madgicx MCP servers (Facebook Ads and Google Ads) authorize against the same Madgicx OAuth provider. A single Client ID / Client Secret pair works for both — point your MCP client at one or both URLs.

Server URLs

ServerURL
Facebook Ads MCPhttps://mcp.madgicx.com/mcp
Google Ads MCPhttps://mcp-google-ads.madgicx.com/mcp

The framework guides on this page use MADGICX_MCP_URL for the Facebook Ads server by default. To target Google Ads, swap the URL — or run two MCP clients in parallel, one per server.

Getting Your Credentials

Copy your Client ID

Your Client ID is displayed in the Credentials section. Use the copy button to copy it.

Generate a Client Secret

Click the rotate icon next to the Client ID to generate a new Client Secret. Copy and store it securely — it will only be shown once.

Store your secret securely

The client secret is only displayed once at creation time. Store it in a secrets manager or environment variable — never commit it to source control.

Rotating the Client Secret

You can rotate your client secret at any time from the MCP Integration settings:

  1. Click the rotate icon next to your Client ID
  2. Confirm the rotation in the dialog — this will invalidate your current secret immediately
  3. Copy and deploy the new secret to all services that use it

Secret rotation is immediate

Rotating the secret invalidates the previous one instantly. Any services using the old secret will need to be updated, or they will fail to authenticate.

Environment Variables

All framework integrations below use the same environment variables. Create a .env file or set them in your environment:

MADGICX_CLIENT_ID=your_client_id_here
MADGICX_CLIENT_SECRET=your_client_secret_here
MADGICX_TOKEN_URL=https://app.madgicx.com/o/token/
MADGICX_MCP_URL=https://mcp.madgicx.com/mcp

Token URL is shared

Note that MADGICX_TOKEN_URL is the same for both servers — https://app.madgicx.com/o/token/. Only the MCP endpoint URL differs.

To run agents against both MCP servers simultaneously, configure two MCP clients in your application using the same credentials but different URLs (see each framework guide for the multi-server pattern).

Auth Helper

All framework integrations use a shared MadgicxConfidentialClientAuth helper that handles the OAuth 2.0 client credentials flow, including automatic token refresh. Place this in your project as auth.py:

"""OAuth2 Confidential Client token manager for Madgicx MCP server."""
 
from __future__ import annotations
 
import logging
import os
import threading
import time
 
import requests
 
log = logging.getLogger("mcp_auth")
 
 
class MadgicxConfidentialClientAuth:
    """Thread-safe OAuth2 token manager using client_credentials grant.
 
    Token lifecycle:
      1. Valid access token  -> use it
      2. Access token expired + valid refresh token -> refresh
      3. Refresh token expired/rejected -> re-authenticate
    """
 
    def __init__(
        self,
        client_id: str | None = None,
        client_secret: str | None = None,
        token_url: str | None = None,
        scopes: str = "mcp:read mcp:write",
        expiry_buffer_seconds: int = 60,
    ):
        self.client_id = client_id or os.getenv("MADGICX_CLIENT_ID", "")
        self.client_secret = client_secret or os.getenv("MADGICX_CLIENT_SECRET", "")
        self.token_url = token_url or os.getenv("MADGICX_TOKEN_URL", "")
        self.scopes = scopes
        self.expiry_buffer = expiry_buffer_seconds
 
        self._access_token: str | None = None
        self._refresh_token: str | None = None
        self._access_token_expiry: float = 0
        self._refresh_token_expiry: float = 0
        self._lock = threading.Lock()
 
    @property
    def access_token(self) -> str:
        """Always returns a valid access token. Thread-safe."""
        with self._lock:
            now = time.time()
 
            if self._access_token and now < self._access_token_expiry - self.expiry_buffer:
                return self._access_token
 
            if self._refresh_token and now < self._refresh_token_expiry - self.expiry_buffer:
                self._do_refresh()
                return self._access_token
 
            self._do_client_credentials()
            return self._access_token
 
    @property
    def headers(self) -> dict[str, str]:
        return {"Authorization": f"Bearer {self.access_token}"}
 
    def _do_client_credentials(self) -> None:
        resp = requests.post(
            self.token_url,
            data={
                "grant_type": "client_credentials",
                "client_id": self.client_id,
                "client_secret": self.client_secret,
                "scope": self.scopes,
            },
        )
        resp.raise_for_status()
        self._store_tokens(resp.json())
 
    def _do_refresh(self) -> None:
        try:
            resp = requests.post(
                self.token_url,
                data={
                    "grant_type": "refresh_token",
                    "refresh_token": self._refresh_token,
                    "client_id": self.client_id,
                    "client_secret": self.client_secret,
                },
            )
            resp.raise_for_status()
            self._store_tokens(resp.json())
        except requests.HTTPError:
            self._do_client_credentials()
 
    def _store_tokens(self, token_response: dict) -> None:
        now = time.time()
        self._access_token = token_response["access_token"]
        self._access_token_expiry = now + token_response.get("expires_in", 3600)
 
        if "refresh_token" in token_response:
            self._refresh_token = token_response["refresh_token"]
            self._refresh_token_expiry = now + token_response.get(
                "refresh_token_expires_in", 1_209_600
            )

This class:

  • Reads credentials from environment variables (or accepts them as constructor arguments)
  • Automatically obtains tokens via the client_credentials grant
  • Refreshes expired access tokens using the refresh token
  • Falls back to a full re-authentication if the refresh token is also expired
  • Is thread-safe for use in concurrent applications
  • Works for both MCP servers — the same access token is accepted by Facebook Ads and Google Ads

Framework Integrations

On this page