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

# Add a custom MCP server

> Connect any remote MCP server to an agent by its URL from the Tools page or the admin dashboard, with no code: sign-in, tool choice, sharing, health, and limits

After this guide, an agent can call the tools of any remote [MCP server](/concepts/mcp-servers) you add by its URL, from the **Tools** page in the Lua app or from the agent in the admin dashboard. Nothing goes into a lua-cli project and nothing needs a push: Lua tests the server, signs in to it, and the agent has its tools on its next turn.

## Which way to add an MCP server

| Way | Use it when | Managed from |
| - | - | - |
| [Integration](/integrations/connect) | The product is in the integration catalog (`lua integrations available`). Sign-in, scopes, and events are handled for you | The **Tools** page or `lua integrations` |
| Custom MCP server (this page) | The server isn't in the catalog, it signs in with OAuth, or you manage the agent from the Lua app rather than from a project | The **Tools** page or the admin dashboard |
| [`LuaMCPServer`](/build/use-an-mcp-server) in code | The server belongs in your project's source, its headers come from the agent's environment through `env()`, or it only speaks SSE | `lua push mcp` and `lua mcp` |

A custom server connects over Streamable HTTP, the current MCP standard. A server added this way is not part of your project: `lua push` never changes it, `lua sync` never pulls it into your code, and agent versions don't include it.

**Before you begin**

* The server's address. It must start with `https://`.
* What it needs to sign in, if anything: an API key or token to send as a header, or an account with the provider for OAuth. Lua detects which one the server uses.
* To add a server to a shared agent, permission to manage that agent's integrations.

## Add a server from the Tools page

<Steps>
  <Step title="Open the card">
    In the Lua app (desktop or web), open **Tools** and choose **Custom MCP server** under **Add your own**. On a shared agent, Lua asks you to confirm first: everyone who uses the agent will be able to use the server's tools, with the credentials or sign-in you add.
  </Step>

  <Step title="Enter the address">
    Enter the **Server URL** and, if you like, a **Name**; the name defaults to the server's host. Select **Continue**. Lua contacts the server and detects how it signs in.
  </Step>

  <Step title="Choose how it signs in">
    **Sign-in method** shows what Lua detected, for example `Detected: Sign in with OAuth · auth.example.com`. Change it if the detection is wrong.

    * **No sign-in**: there is nothing to enter.
    * **Headers (API key or token)**: add each header the server expects, for example `Authorization` with the value `Bearer <token>`. Values are hidden as you type.
    * **Sign in with OAuth**: select **Sign in**, then finish on the provider's page in your browser.

    Select **Continue** or **Sign in**. Lua tests the connection with what you entered.
  </Step>

  <Step title="Choose the tools">
    The dialog lists the server's tools. Keep **Allow all tools** on, or turn it off and switch tools on one by one. Select **Add server**; the server is saved and its tools reach the agent on its next turn.

    After an OAuth sign-in the server is already saved, with every tool allowed, by the time the list appears. Narrow the list if you want, then select **Done**; closing the dialog instead keeps every tool allowed.
  </Step>
</Steps>

### Connecting is the test

Nothing is saved until the test passes. When it fails, the dialog lists the steps it passed and the one that failed, such as **Reach the server**, **Read the sign-in provider's details**, or **List the server's tools**, with a plain message and a **Ref** code. Select the Ref to copy it and quote it to support. Fix what the message says and select **Try again**. If an OAuth sign-in finished but a later step failed, **Try again** reuses that sign-in for up to 15 minutes; after that, sign in again.

If the connection drops before Lua answers, the dialog says it couldn't confirm whether the server was added and offers **Check again**. Check before adding it a second time: two servers of one owner can't share a name.

## Add a server from the admin dashboard

In the admin dashboard (`lua admin`), open the agent's **MCP Servers** page and select **Add server**, or select **+** next to **MCP Servers** on the agent's overview. The **Add an MCP server** dialog walks the same steps as the Tools page.

A server added in the dashboard always belongs to the agent, so everyone who uses the agent gets its tools. With OAuth the dialog says so before you sign in: you sign in with your own account, and everyone who uses the agent will use the server as you. Adding and editing servers in the dashboard needs permission to manage the agent's integrations (`integrations:write`).

## How sign-in works

* **No sign-in.** Lua connects with no credentials.
* **Headers.** Lua sends the headers you entered with every request to the server. Once saved, Lua shows each header's name and never its value.
* **OAuth.** When the server supports OAuth, Lua finds the server's sign-in provider, registers itself with the provider where the provider allows that, and opens the provider's sign-in page. Lua asks for the scopes the server names when it asks you to sign in; when it names none, the scopes its metadata lists; plus `offline_access` when the provider offers it, so Lua can renew the sign-in. Lua renews the sign-in by itself while the provider allows it. When the provider refuses to renew it, the server shows **Signed out**. If the provider gives Lua no way to renew a sign-in, the server's tools stop when the sign-in expires; select **Sign in again**.
* **OAuth with your own app.** Some providers don't let apps register themselves, and the dialog then says the server needs you to register Lua as an app first. Register an app with the provider using the callback URL the dialog shows (**Copy** copies it), then enter the app's **Client ID** and, if it has one, its **Client secret**. You can also choose **Use my own OAuth app instead** before you sign in.

Lua detects the sign-in method when you test the address, and you can change it before you add the server. A server that answers without sign-in can still take headers, for example an API key that raises its rate limit: select **Add headers to this server instead** on the tools step.

The sign-in method is fixed once the server is saved. To change it, disconnect the server and add it again.

## Who can use it

Ownership follows the same rule as personal and agent [integration](/concepts/integrations) connections. The connect dialog states it under its title: **Just you · your private agents**, or **Everyone who uses** followed by the agent's name.

| Added on | The server belongs to | Who gets its tools |
| - | - | - |
| A private agent, from the Tools page | You. It's added to all your private agents | Only you, with your own credentials or sign-in. Someone else talking to one of those agents doesn't get them |
| A shared agent, from the Tools page | The agent, after you confirm | Everyone who uses the agent, with the credentials or sign-in you added |
| Any agent, from the admin dashboard | The agent | Everyone who uses the agent, with the credentials or sign-in you added |

In the admin dashboard, a personal server shows as **Personal · managed from Tools** and is read-only there; only its owner changes it, from the Tools page.

### When the agent uses the tools

Tools from a custom server work like other connected tools: the model sees them on its next turn and calls them when the persona and the conversation call for it. A server that fails contributes no tools for that turn, and the rest of the agent carries on. Workflow steps that run as the agent can use the same tools, with the same choices. A personal server is used only in runs started by its owner. `lua logs --type mcp` shows calls to a custom server like calls to any other MCP server.

In a shared conversation (a room), a participant's personal servers can be used under the same approval rules as their other personal tools.

There are no per-tool approval levels for custom servers. Your organization's approval rules still apply to their tools.

## Choose the tools

**Allow all tools** on means every tool the server lists, including tools it adds later. Turn it off to choose tools one by one; tools the server adds later then start off. The choice applies on every agent the server is on.

**Refresh** in the server's panel reads the server's tool list again, so new tools appear and removed ones go.

## Manage a server

Open the server under **Tools** to see its address (the host, followed by `/…` when the address has a path), its sign-in method, its workflow key, and its tools. From there:

* **Pause** removes its tools from every agent without forgetting anything; **Resume** brings them back. In the admin dashboard the **Active** switch does the same on a server that belongs to the agent.
* **Edit** changes the name, the address, or the header values. See [Edit a server](#edit-a-server).
* **Sign in again** renews an OAuth sign-in.
* **Disconnect** removes the server and its saved credentials from every agent it's on. For an OAuth server, Lua also asks the provider to revoke its sign-in where the provider supports that.

A server that belongs to an agent is removed, its credentials deleted and its OAuth sign-in revoked, when the agent is permanently deleted. While a deleted agent can still be restored, the server is kept.

### Health states

| State | What it means | What to do |
| - | - | - |
| **Active** | The server answers and the agent gets its tools | Nothing |
| **Paused** | Someone paused it, so the agent gets none of its tools | **Resume**, or turn **Active** back on in the admin dashboard |
| **Can't reach server** | Lua's attempts to reach it kept failing for at least 10 minutes | Check the server is running, then select **Check again** |
| **Signed out** | The provider refused to renew the sign-in, for example because access was revoked | Select **Sign in again** |
| **Credentials rejected** | The server refuses the saved headers, or a server added with no sign-in started asking for credentials | Select **Update credentials** and enter the header values again; for a server added with no sign-in, disconnect it and add it again with headers |

Older versions of the Lua app show **Needs attention** for the last three states; open the server in a current version to fix it.

### Edit a server

**Edit** tests a new address or new header values against the server before saving them; a new name is saved directly.

* **Name**: up to 120 characters. Names that differ only in case, spaces or punctuation count as the same, so `Acme CRM` and `acme-crm` can't both be one owner's servers. Renaming a server changes its [workflow key](#use-it-in-a-workflow).
* **Server URL**: Lua never shows the saved address, so the field is empty. Leave it empty to keep the address, or enter a new one. When the new address needs new credentials, the dialog says so. For an OAuth server, a new address always means signing in again. For a server with headers, a new address on a different server (another scheme, host or port) means entering every header value again.
* **Headers**: saved headers show their names with **Unchanged** in place of the value. Type a new value to replace one, remove a header, or select **Add header**.

## Security and privacy

* **Only `https://` servers.** Lua refuses any other scheme, and an address with a user name or password in it: put credentials in headers instead.
* **No private addresses.** Lua refuses addresses that are private, internal, or cloud metadata, or that resolve to one, when you add the server and every time it connects, sign-in addresses included.
* **The address is a secret.** Some servers put a token in their URL, so Lua treats the whole address as a credential. After saving, it is never shown again: the Tools page and the admin dashboard show the host, followed by `/…` when the address has a path, and the CLI shows only the host.
* **Header values are write-only.** Lists and edit forms show header names, never values.
* **Credentials are encrypted at rest.** The address, header values, OAuth tokens, and your OAuth app's client secret are stored encrypted and used only to connect to the server. They are not part of the agent's configuration, its versions, or your project, and the agent's code can't read them.

## From the CLI

lua-cli 3.46.0 and later shows custom servers and leaves them alone. Earlier versions list a custom server under its internal `mcp_…` name with only its host as the address; the server still refuses the changes described below.

* `lua mcp list` groups the agent's servers into **From your project (lua-cli)**, **From integrations (Tools page)**, and **Custom servers (Tools page)**, each row labelled with who manages it (`Managed by lua-cli`, `Managed from Tools`, or `Personal · managed from Tools`). A custom server shows its name and host, never its address.
* `lua mcp activate`, `deactivate`, and `delete` change only servers from your project. On a custom server they stop before changing anything and point to the Tools page; see [`lua mcp`](/reference/cli/mcp#examples).
* `lua integrations list` labels a custom server `Custom MCP server (managed from Tools)`, or `Custom MCP server (personal · managed from Tools)` for one that serves only you, with its host, its state, and how many of its tools are on. `lua integrations update` and `lua integrations mcp activate|deactivate` refuse it; `lua integrations disconnect`, `pause`, and `resume` act on it like on any connection. See [`lua integrations`](/reference/cli/integrations#custom-mcp-servers).
* `lua push` never overwrites or takes over a custom server. A project server can't use the name Lua gives a custom server's tools on that agent (for a server named `Acme CRM`, `mcp_acme_crm`); that push fails. `lua sync` doesn't pull custom servers into `lua.skill.yaml` or report them as drift, and `lua version create` doesn't record them.

## Use it in a workflow

A custom server answers to a **workflow key**, shown in its panel on the Tools page and in the admin dashboard. The key is the server's name in lowercase, with each run of other characters turned into `-`: `Acme CRM` becomes `acme-crm`, and a name that doesn't start with a letter gets `s-` in front. Declare it in a [workflow's connections](/build/workflows/connections) with the type `custom_mcp`:

```ts theme={null}
connections: [{ key: 'acme-crm', integrationType: 'custom_mcp' }],
```

Only a server with that exact key is picked, so renaming the server breaks the declaration until you update the key.

## Limits

| Item | Value |
| - | - |
| Address | `https://` only, up to 2,048 characters, no user name or password |
| Private, internal, and cloud metadata addresses | Refused, on save and on every connection |
| Transport | Streamable HTTP |
| Saved address | Never shown again; the host, plus `/…` when there is a path |
| Header values | Write-only; up to 20 headers |
| Headers on an OAuth server | None |
| Name | Up to 120 characters; unique among one owner's servers, ignoring case, spaces and punctuation |
| Header names | Not ones Lua sets itself, such as `Host`, `Content-Type` and `Accept` |
| Tests | Up to 20 connection tests and 10 sign-ins a minute per person |
| OAuth sign-in | Must finish within 15 minutes |
| Sign-in method | Fixed; disconnect and add again to change it |
| Per-tool approval levels | None; your organization's approval rules apply |

## If it isn't working

<AccordionGroup>
  <Accordion title="Lua only connects to https:// servers.">
    The address uses `http://` or another scheme. Use the server's `https://` address; Lua has no option for plain HTTP.
  </Accordion>

  <Accordion title="Put credentials in headers, not in the address.">
    The address has a user name or password before the host (`https://user:pass@…`). Remove it and choose **Headers (API key or token)** as the sign-in method.
  </Accordion>

  <Accordion title="The test fails at Check the address">
    The address is private, internal, or cloud metadata, or resolves to one. Lua connects only to servers on the public internet; host the server at a public `https://` address.
  </Accordion>

  <Accordion title="This server needs you to register Lua as an app first.">
    The provider doesn't let apps register themselves. Register an app with the provider using the callback URL the dialog shows, then enter its **Client ID** and **Client secret**.
  </Accordion>

  <Accordion title="Your browser blocked the sign-in window.">
    The browser stopped the provider's sign-in window from opening. Allow pop-ups for Lua and try again.
  </Accordion>

  <Accordion title="The sign-in expired before the server was added. Sign in again.">
    More than 15 minutes passed between the sign-in and a successful test. Select **Sign in** again.
  </Accordion>

  <Accordion title="Atlassian (Jira, Confluence) says your organization admin must authorize access">
    Atlassian's MCP server accepts a sign-in only from addresses an Atlassian organization admin has allowed, and only for a site that has both Jira and Confluence.

    1. An organization admin opens **Atlassian Administration** > **Rovo** > **Rovo MCP server** > **Domain settings** and adds Lua's sign-in address, `https://api.heylua.ai/mcp/oauth/callback`.
    2. Select **Sign in** again and pick a site with both Jira and Confluence.

    If Atlassian still shows the message after the address is added, the admin removes the entry, adds it again, and saves; then sign in from a new private browser window.
  </Accordion>

  <Accordion title="lua mcp says it can't change the server">
    The server was added from the Tools page or the admin dashboard, so lua-cli doesn't manage it. Pause, edit, or disconnect it there.
  </Accordion>
</AccordionGroup>

## Next steps

<Columns cols={2}>
  <Card title="About MCP servers" href="/concepts/mcp-servers">The three ways a server reaches an agent, and when to pick each.</Card>
  <Card title="Use an MCP server in code" href="/build/use-an-mcp-server">Declare a `LuaMCPServer`, push it, and activate it.</Card>
  <Card title="About integrations" href="/concepts/integrations">Catalog connections, personal and agent ownership.</Card>
  <Card title="lua mcp" href="/reference/cli/mcp">What the CLI shows and changes.</Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.