# Custom MCP server

> Connecting a data source the catalog does not cover (a local command over stdio, or a remote HTTP endpoint) and what the catalog gives you that this does not.

Source: https://triagic.com/docs/integrations/custom

Anything speaking the Model Context Protocol can be configured by hand. In the **Add
shared data source** dialog, switch from **Catalog integration** to **Custom MCP
server** and pick a transport.

> **Note:** Prefer a catalog entry where one exists
>
> A catalog entry derives its package, arguments, transport, read-only flags and health
> check server-side, so upgrades and new flags reach existing configurations on their
> next restart. A custom row is a frozen snapshot of whatever you typed: nothing updates
> it, nothing pins its read-only mode, and nothing proves the credential beyond the
> server starting.

## Command (stdio) [#command-stdio]

For a server distributed as a package you run locally.

| Field                     | Notes                                                                                                                                                        |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Command**               | The runner: `npx`, `uvx`, `docker`, or an absolute path to a binary. It must exist on **every member's** machine, the same prerequisite rule as the catalog. |
| **Arguments**             | Space-separated. `-y some-mcp-server@1.4.0`. Pin a version: an unpinned `npx` follows silent major bumps and will break the row without warning.             |
| **Environment variables** | Key/value pairs passed to the spawned process. This is where connection details go.                                                                          |

Arguments support `${VAR}` interpolation from the environment map, and an argument
that interpolates to an empty string is **dropped**, which is how an optional flag can
be present in the template and vanish when its variable is unset.

## URL (HTTP) [#url-http]

For a hosted or self-hosted server you reach over HTTPS. Nothing is installed locally,
so members need outbound network access to that host instead of a runtime.

| Field          | Notes                                                                  |
| -------------- | ---------------------------------------------------------------------- |
| **Server URL** | The streamable-HTTP endpoint, e.g. `https://mcp.example.com/mcp`.      |
| **Headers**    | Key/value pairs sent on every request, e.g. `Authorization: Bearer …`. |

Servers that require an interactive OAuth authorization flow cannot be configured
here: there is no browser and no callback URL in this path. Only endpoints that accept
a static header work.

## Where secrets can and cannot go [#where-secrets-can-and-cannot-go]

> **Warning:** A shared custom server has no encrypted path for a secret
>
> For **catalog** integrations, credential fields are encrypted at rest and returned
> masked. For a **custom** row, the spawned process is built from its environment
> variables (stdio) or headers (HTTP), and org-side those two are stored **unencrypted**.
> The separate Credentials box on the custom form is stored encrypted but is not read
> when the server is started, so a token placed there does not reach the process at all.
>
> So: do not put a production secret on a shared custom server. Either add a catalog
> entry for that system, or have each member configure the custom server in their own
> desktop app, where its environment is encrypted locally.

## What you give up [#what-you-give-up]

|                       | Catalog entry                               | Custom server         |
| --------------------- | ------------------------------------------- | --------------------- |
| Package and flags     | Derived server-side, update on restart      | Frozen as typed       |
| Read-only mode        | Pinned on, not exposed as a switch          | Yours to get right    |
| Credential validation | Per-field, before saving                    | Shape only            |
| Health check          | A real read-only call proves the credential | Start-check only      |
| Failure diagnostics   | Mapped to the field that fixes them         | Raw error text        |
| Secret storage        | Encrypted, masked                           | See the warning above |

## Keys and tool names [#keys-and-tool-names]

A custom row gets a key like any other, `org-` prefixed when shared, and that key
prefixes its tool names, so a playbook can scope to it exactly as it would to a
catalog integration. See
[Anatomy of a playbook](/docs/playbooks) for how `allowedServers` uses keys.

## If it will not start [#if-it-will-not-start]

The portal cannot test-connect anything, so the first real signal is the status a
desktop reports back. The common causes, in order:

| Symptom                         | Cause                                                                                                                         |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Degraded, naming the runner     | `Command` isn't installed on that machine, or isn't on the PATH the app resolves.                                             |
| Degraded immediately on stdio   | Wrong arguments. Run the same command by hand in a terminal on that machine to see the real error.                            |
| Degraded with a 401 on HTTP     | The header is missing, malformed, or the endpoint wants OAuth.                                                                |
| Starts, but the tool count is 0 | It connected to something that is not an MCP server, or to one whose tools are all gated behind configuration it did not get. |
