> ## Documentation Index
> Fetch the complete documentation index at: https://docs.modulex.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Installing & using integrations

> How ModuleX integrations are discovered through Python entry points, how to install the modulex-integrations package and its tool SDK dependencies, and the difference between managed and bring-your-own-key tool usage.

export const MediaEmbed = ({id, type = 'screenshot', caption = '', ext, ratio = '16 / 9'}) => {
  const isVideo = type === 'video' || type === 'app_video';
  const resolvedExt = ext || (isVideo ? 'mp4' : type === 'screenshot' ? 'webp' : 'svg');
  const src = 'https://media.modulex.dev/' + id + '.' + resolvedExt;
  const [status, setStatus] = useState('loading');
  const [isDev, setIsDev] = useState(false);
  const [inView, setInView] = useState(false);
  const boxRef = useRef(null);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const h = window.location.hostname;
    setIsDev(h === 'localhost' || h === '127.0.0.1' || h.endsWith('.mintlify.app'));
  }, []);
  useEffect(() => {
    if (inView) return;
    if (typeof IntersectionObserver === 'undefined') {
      setInView(true);
      return;
    }
    const el = boxRef.current;
    if (!el) return;
    const io = new IntersectionObserver(entries => {
      if (entries.some(e => e.isIntersecting)) {
        setInView(true);
        io.disconnect();
      }
    }, {
      rootMargin: '300px'
    });
    io.observe(el);
    return () => io.disconnect();
  }, [inView]);
  if (status === 'missing') {
    if (!isDev) return null;
    return <div style={{
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      gap: '0.4rem',
      padding: '1rem 1.25rem',
      margin: '1.25rem 0',
      width: '100%',
      aspectRatio: ratio,
      boxSizing: 'border-box',
      border: '1px dashed rgba(128,128,128,0.45)',
      borderRadius: '0.75rem',
      background: 'rgba(128,128,128,0.06)',
      color: 'currentColor',
      fontSize: '0.85rem',
      lineHeight: 1.45
    }}>
        <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '0.5rem',
      opacity: 0.75
    }}>
          <span aria-hidden="true">🎬</span>
          <code style={{
      fontSize: '0.75rem'
    }}>{id}</code>
          <span style={{
      fontSize: '0.65rem',
      textTransform: 'uppercase',
      letterSpacing: '0.04em',
      padding: '0.1rem 0.4rem',
      borderRadius: '0.4rem',
      background: 'rgba(128,128,128,0.18)'
    }}>
            {type}
          </span>
        </div>
        <div style={{
      opacity: 0.9
    }}>{caption || 'Media not uploaded yet.'}</div>
        <div style={{
      fontSize: '0.7rem',
      opacity: 0.5
    }}>
          Upload to R2 as <code>{id}.{resolvedExt}</code> — preview only, hidden in production.
        </div>
      </div>;
  }
  const mediaStyle = {
    display: status === 'loaded' ? 'block' : 'none',
    width: '100%',
    height: 'auto',
    borderRadius: '0.75rem'
  };
  const media = isVideo ? <video src={inView ? src : undefined} autoPlay loop muted playsInline preload="metadata" onLoadedData={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} /> : <img src={inView ? src : undefined} alt={caption} onLoad={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} />;
  return <figure style={{
    margin: '1.25rem 0'
  }}>
      <div ref={boxRef} style={status === 'loaded' ? {
    width: '100%'
  } : {
    width: '100%',
    aspectRatio: ratio,
    borderRadius: '0.75rem',
    background: 'rgba(128,128,128,0.06)'
  }}>
        {media}
      </div>
      {status === 'loaded' && caption ? <figcaption style={{
    marginTop: '0.5rem',
    textAlign: 'center',
    fontSize: '0.85rem',
    opacity: 0.7
  }}>
          {caption}
        </figcaption> : null}
    </figure>;
};

ModuleX integrations are connectors to external services. Each **integration** (for example `github`) ships a manifest plus a set of **tools** — the callable actions you run from a workflow's [Tool node](/workflow-builder/nodes/tool) or from the Assistant. The full set of integrations lives in one installable Python distribution, **`modulex-integrations`**, which the ModuleX backend loads at startup.

This page is for people running or extending the ModuleX backend: it covers how integrations are discovered at runtime, how to install the package and the per-tool SDK dependencies you actually need, and how a tool call is authenticated either with ModuleX-managed credentials or your own key. If you only want to connect a service in the app, see [Authentication & credentials](/integrations/authentication) and [Connect an integration](/guides/connect-an-integration) instead — you do not install anything for that.

<Note>
  The hosted ModuleX service already runs `modulex-integrations` for you. You install the package yourself only when you self-host the backend or develop integrations locally. To author a new integration, start at [Build an integration](/integrations/building/overview).
</Note>

## How integrations load at runtime

Integrations are not configured by hand or read from a directory you point at. They are discovered through standard Python **entry points** in the group `modulex.tools`. Every integration in the package declares one entry point that maps its catalog name to its module:

```toml pyproject.toml theme={null}
[project.entry-points."modulex.tools"]
aws = "modulex_integrations.tools.aws"
github = "modulex_integrations.tools.github"
slack = "modulex_integrations.tools.slack"
# ... 175 entries total
```

At startup the backend enumerates this group, imports each module, and reads two required symbols from it:

<ResponseField name="manifest" type="IntegrationManifest" required>
  The integration's manifest — name, display name, categories, actions, and auth schemas. Defined in `manifest.py` and re-exported from the package `__init__.py`. See the [manifest & schema contract](/integrations/building/manifest-schema).
</ResponseField>

<ResponseField name="TOOLS" type="tuple[StructuredTool, ...]" required>
  A tuple of LangChain tool objects, one per action. See the [`@tool` function contract](/integrations/building/tool-contract).
</ResponseField>

The mapping key (for example `github`) is the integration's catalog and credential name. The mapping value (for example `modulex_integrations.tools.github`) is the **package module**, not the tools module — the runtime appends `.tools` to it when it needs to import the callable (resolving to `modulex_integrations.tools.github.tools`).

<Steps>
  <Step title="Enumerate">
    The backend calls `entry_points(group="modulex.tools")` to list every registered integration.
  </Step>

  <Step title="Load">
    For each entry point it imports the module and reads `manifest` and `TOOLS`.
  </Step>

  <Step title="Cache">
    The resolved catalog is cached for the lifetime of the worker process. A newly installed package version is not picked up until you restart the worker.
  </Step>
</Steps>

<Warning>
  Because the catalog is cached per process, upgrading `modulex-integrations` while a worker is running has no effect until that worker restarts. Restart all backend workers after installing or upgrading the package.
</Warning>

### Two catalog sources

The runtime resolves tools from two places, and entry points are not the only one:

* **Package entry points** — the `modulex.tools` group described above. This is the primary source and **wins on a name collision**.
* **Legacy on-disk JSON** — a small set of integrations not yet migrated to the package, loaded from `*_integration.json` files in the backend. The Model Context Protocol connector (`mcp_server`) is handled this way rather than as a package entry point. See [Custom MCP servers](/integrations/building/custom-mcp).

So "every tool comes from `modulex-integrations`" is almost true — treat the legacy JSON integrations as documented exceptions.

<Note>
  There are **two distinct discovery mechanisms** in this system, and they are not interchangeable. The runtime uses **entry points** (above). The documentation and CI pipeline that generates the [integration catalog](/integrations/catalog) instead walks the filesystem for `tools/*/manifest.py`, using the entry-point table only as a cross-check. If you are reading manifests for tooling, match the mechanism to your goal.
</Note>

## Install the package

`modulex-integrations` requires **Python 3.12 or newer**. The base install pulls in only three core dependencies — `pydantic`, `httpx`, and `langchain-core` — which is everything most integrations need.

<CodeGroup>
  ```bash pip theme={null}
  pip install modulex-integrations
  ```

  ```bash uv theme={null}
  uv pip install modulex-integrations
  ```
</CodeGroup>

The backend pins an exact version of this package (it does not float with `>=`), because the manifest schema can change between minor versions and the runtime rejects unknown manifest fields at import time. When self-hosting, install the version that matches the backend you are running rather than the latest release.

### Do not rely on install extras

The package declares an `all` extra and the README advertises per-tool extras such as `[github,slack]`. **Do not use them.** They do not work as written today:

<Warning>
  The `all` extra is an **empty list** in the package, and there are **no per-tool extras** (`[github]`, `[slack]`, `[aws]`, and so on are not defined). Installing them does not add any tool SDKs:

  * `pip install "modulex-integrations[all]"` installs only the three core dependencies — the same as the base install.
  * `pip install "modulex-integrations[github,slack]"` emits pip warnings like `WARNING: modulex-integrations does not provide the extra 'github'` and installs only the base package.

  A script to auto-assemble these extras from each tool's declared dependencies is planned but **not implemented**. Until it lands, treat `[all]` and per-tool extras as non-functional and install tool SDKs manually (below).
</Warning>

This limitation is also recorded in [Known limitations](/reference/known-limitations) and the [Help known-limitations page](/help/known-limitations).

### Install tool SDK dependencies manually

Because extras are inert, the working path is: install the base package, then install whatever client SDK each integration you intend to use needs. Many integrations need **nothing extra** — they call their service over plain HTTP through the core `httpx` dependency. For example, both `github` and `slack` declare no additional dependencies.

Others do need a vendor SDK. Each integration declares what it needs in a `dependencies.toml` file in its source directory:

```toml src/modulex_integrations/tools/aws/dependencies.toml theme={null}
dependencies = ["boto3>=1.34.0"]
```

Install those yourself. For an AWS-backed workflow, for example:

```bash theme={null}
pip install modulex-integrations boto3
```

<Note>
  There is **no REST endpoint that runs a tool directly**. Tools execute only inside workflow runs and Assistant/Composer turns. Installing the package makes the catalog available to the backend; you call individual tools through the [Tool node](/workflow-builder/nodes/tool), the Assistant, or the AI Composer.
</Note>

## Managed usage vs bring-your-own-key

Whether a tool needs an SDK installed is separate from **how it is authenticated and billed**. Every tool call resolves a [credential](/integrations/managing-credentials) for the integration in your organization, and that credential is one of two kinds.

<CardGroup cols={2}>
  <Card title="ModuleX-managed" icon="building">
    The call runs through a ModuleX-provisioned key from a managed pool. Usage is **metered in credits** and is subject to the billing gate. The managed providers appear on the wire as `modulexai` (models) and `modulexdb` (knowledge); managed tool calls draw from your credit balance.
  </Card>

  <Card title="Bring your own key (BYOK)" icon="key">
    You connect your own provider account. Usage is billed **directly by that provider with no ModuleX markup**, and BYOK calls are **not** charged in ModuleX credits — they are tracked for analytics only. BYOK is available on every plan; there is no entitlement that gates it.
  </Card>
</CardGroup>

The runtime injects the resolved credential into the tool call internally, so the model and the tool function never see raw secrets. The behavior differs by credential kind:

<ResponseField name="ModuleX-managed credential" type="metered in credits">
  Before the call runs, the runtime checks the organization's credit limit (without recording yet). If the monthly budget is exhausted and no wallet overage is available, the call is denied. On success it records the per-call tool cost. See [Credits & metering](/billing/credits).
</ResponseField>

<ResponseField name="BYOK credential" type="not credit-limited">
  The runtime decrypts your stored credential, refreshes the OAuth2 token if it is close to expiry, and runs the call. There is **no credit check or credit charge** — only usage tracking.
</ResponseField>

<Warning>
  ModuleX-managed tool calls are part of the live billing gate. When the credit limit is exhausted, a managed call is denied rather than running. Plan for this in any workflow that depends on managed tool usage. BYOK calls are never credit-gated.
</Warning>

For how a tool resolves credentials, refreshes OAuth2 tokens, and maps auth into the call, see [Credentials & OAuth2](/concepts/credentials-oauth) and [Authentication & credentials](/integrations/authentication). For model and knowledge providers specifically, see [LLM providers](/integrations/llm-providers/overview) and [Knowledge providers](/integrations/knowledge-providers/overview).

## Verify the catalog loaded

After installing the package and restarting your workers, confirm the integrations you expect are present. The catalog the backend exposes reflects the entry points found at startup.

<MediaEmbed id="MX-MEDIA-4010" type="screenshot" caption={"The integrations catalog in the ModuleX app after the package has loaded, showing connected and available integrations."} />

If an integration you expect is missing, check, in order:

<AccordionGroup>
  <Accordion title="The worker was not restarted">
    The catalog is cached per process. Restart all backend workers after installing or upgrading `modulex-integrations`.
  </Accordion>

  <Accordion title="The package version is wrong">
    The backend pins an exact version. A mismatched install can fail to import a manifest with an unknown field, because the schema rejects unknown fields at import time. Install the version that matches your backend.
  </Accordion>

  <Accordion title="A tool SDK is not installed">
    An integration whose `dependencies.toml` lists a vendor SDK will fail to load its tools if that SDK is missing, because extras do not install it for you. Install the SDK manually (see above). The integration's manifest may still appear while individual actions fail to load.
  </Accordion>

  <Accordion title="It is a legacy JSON integration">
    A few integrations (such as `mcp_server`) load from legacy on-disk JSON, not from the package entry points. See [Custom MCP servers](/integrations/building/custom-mcp).
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Authentication & credentials" icon="lock" href="/integrations/authentication">
    Connect a service with an API key or OAuth2 and store the credential.
  </Card>

  <Card title="Browse the catalog" icon="grid" href="/integrations/catalog">
    See every integration ModuleX can connect to, grouped by category.
  </Card>

  <Card title="Use a tool in a workflow" icon="wrench" href="/workflow-builder/nodes/tool">
    Call an integration's action from the Tool node.
  </Card>

  <Card title="Build an integration" icon="hammer" href="/integrations/building/overview">
    Author your own integration and expose its tools.
  </Card>
</CardGroup>
