> This page is for Workforce.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.merge.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.merge.dev/_mcp/server.

# Custom MCP servers

> How to register a remote MCP server with Merge, from the Workforce console or the API, so its tools reach employees through the same Tool Packs and Groups as the built-in Connectors.

When the Connector catalog doesn't cover a system your employees need (an in-house service, a niche SaaS, a tool your team built), you can register a remote MCP server as a custom Connector. Merge discovers its tools and serves them through the same MCP URL your employees' AI clients already call, so the server gets Tool Packs, Groups, guardrails, and logs without any change on the employee's machine.

The trade-off: you operate the MCP server yourself. Merge doesn't host third-party code. You point it at your endpoint, and Merge proxies each call through.

You can register a server from the Workforce console or from the API. Use the console for a one-off server. Use the API when you provision Connectors from a script, per environment, or from version control, so a new environment gets the same servers without anyone clicking through a form.

## What's supported

A server qualifies when it is reachable at a public HTTPS URL and completes an MCP `initialize` handshake. Merge speaks MCP protocol revisions `2025-03-26` and `2024-11-05`, offering `2025-03-26` first and falling back to `2024-11-05` once for a server that won't negotiate it.

For authentication, a server can take no credentials or a single static bearer token or API key, configured once when you register it. Every employee who reaches the Connector does so with that one key, so the server sees Merge, not the individual employee. If the server has to know who is calling, use OAuth instead.

OAuth 2.0 with PKCE, when enabled for your organization. A Connector can then authenticate each user through the Link flow instead of a shared token, and Merge can register an OAuth client with your server automatically through dynamic client registration (RFC 7591). This is gated per organization, so contact the Merge team to turn it on. OAuth Connectors are registered from the dashboard only; the API accepts no authentication or a static header.

Local MCP servers are not supported. A server launched from the command line (`npx <package>`) runs on the employee's machine, and Merge runs server-side, so it can't reach one.

## Registering a server from the console

1. Open **Connectors** in the [Workforce console](https://wf-develop.merge.dev/connectors) and click **Add new**.
2. Fill in:
   * **Name.** Unique across your organization's Connectors, and not the name of a Connector Merge already ships. The check is global rather than per organization, so `salesforce` is taken even if you have never authorized it.
   * **Remote MCP server address.** Your server's HTTPS endpoint.
   * **Authorization token.** The bearer token your server expects. Check your server's docs for the format.
3. Save. Merge runs `initialize` against the server, then `tools/list`, and fills in the Connector's tool list.

If discovery fails, the console still creates the Connector, with zero tools, and shows the error. The usual causes are an endpoint that isn't reachable from Merge's network, a token in the wrong format, a server that never completes `initialize`, or one that negotiates a protocol revision Merge doesn't support.

## Registering a server with the API

`POST /api/v1/connectors/` registers a remote MCP server in one call. It needs an [API key](/merge-workforce/settings/api-keys) with the `management:all` scope, which every key generated in the console has.

The API is stricter than the console about failures. Merge completes the handshake and `tools/list` before it writes anything, so the call either returns a Connector with its tools already discovered or creates nothing. A provisioning script never has to clean up a half-registered Connector.

### A server with no authentication

```bash
curl -X POST https://ah-api.merge.dev/api/v1/connectors/ \
  -H "Authorization: Bearer $MERGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Wiki",
    "url": "https://mcp.acme.com/mcp"
  }'
```

`auth` defaults to `{"type": "none"}`, so you can leave it out.

### A server that expects an API key in a header

Send the key in `auth.header`. Merge stores the value encrypted at the organization level and never returns it.

```bash
curl -X POST https://ah-api.merge.dev/api/v1/connectors/ \
  -H "Authorization: Bearer $MERGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Wiki",
    "slug": "acme_wiki",
    "url": "https://mcp.acme.com/mcp",
    "auth": {
      "type": "secret",
      "header": {
        "key": "Authorization",
        "value": "Bearer acme_live_8f2c1e"
      }
    }
  }'
```

The same request in Python:

**`register_mcp_server.py`**

```python register_mcp_server.py
import os

import requests

response = requests.post(
    "https://ah-api.merge.dev/api/v1/connectors/",
    headers={"Authorization": f"Bearer {os.environ['MERGE_API_KEY']}"},
    json={
        "name": "Acme Wiki",
        "slug": "acme_wiki",
        "url": "https://mcp.acme.com/mcp",
        "auth": {
            "type": "secret",
            "header": {"key": "Authorization", "value": os.environ["ACME_MCP_TOKEN"]},
        },
    },
    timeout=30,
)
response.raise_for_status()
connector = response.json()
print(connector["slug"], [tool["name"] for tool in connector["tools"]])
```

A `201` returns the Connector with its discovered tools:

```json
{
  "id": "084fce8e-43d3-472a-8227-e28553e73aa0",
  "name": "Acme Wiki",
  "slug": "acme_wiki",
  "description": "",
  "source_url": "https://mcp.acme.com/mcp",
  "categories": [],
  "auth_options": [],
  "source": "external_mcp",
  "tools": [
    { "name": "search_pages", "description": "Search the Acme wiki.", "credit_type": "simple" },
    { "name": "get_page", "description": "Fetch a single wiki page by id.", "credit_type": "simple" }
  ]
}
```

### Request fields

| Field     | Required | Notes                                                                                                                                                                                                                                                 |
| --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`    | Yes      | Display name, up to 100 characters                                                                                                                                                                                                                    |
| `url`     | Yes      | Your server's HTTPS endpoint. It can't point at localhost, a private network, or a reserved IP address, and the API has no call to change it after registration                                                                                       |
| `slug`    | No       | The Connector's identifier: 1 to 255 lowercase letters, digits, or underscores. Derived from `name` when you leave it out, so `Acme Wiki` becomes `acme_wiki`. Send one when your code needs an identifier that won't change if you rename the server |
| `auth`    | No       | `{"type": "none"}` (the default) or `{"type": "secret", "header": {"key": ..., "value": ...}}`. `oauth` returns a `400`                                                                                                                               |
| `headers` | No       | Up to 20 extra non-secret headers sent on every request to your server. They're write-only, so keep your own record of them. Don't repeat the `auth.header` key here                                                                                  |

The slug can't match a Connector Merge already ships or a server in the [generic MCP catalog](/merge-workforce/connectors/generic/all-generic-mcp-servers). To use a catalog server, authorize it from **Connectors** instead.

### Retries and repeat calls

The endpoint is safe to retry after a timeout you can't confirm. An identical repeat call for a Connector with no authentication returns `200` and the existing Connector rather than creating a second one. Anything else that targets an existing slug returns `409`, including a repeat of a `secret` Connector, because Merge can't tell a retry from a key rotation and won't report a rotation that didn't happen.

| Status | Meaning                                                                                                                                                  | What to do                                                                            |
| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `201`  | Registered, tools discovered                                                                                                                             | Add the Connector to a Tool Pack                                                      |
| `200`  | An identical Connector already exists                                                                                                                    | Nothing, you already have it                                                          |
| `400`  | A field failed validation and the body names it, or the handshake with your server failed and the `error` field says why                                 | Fix the input or the server. Each request in the handshake times out after 10 seconds |
| `409`  | The slug is taken by a different configuration, or this server is already registered under another slug. The `connectors` field lists the slugs involved | Delete the existing Connector first, or reuse it                                      |

Registration is limited to 60 requests a minute per organization. Each successful registration is recorded in the [audit trail](/merge-workforce/logs/audit-trail) as `CONNECTOR_IMPORTED`. The full schema is in the API reference under [Register a remote MCP server](/merge-workforce/agent-handler/connectors/connectors-create).

## Giving employees access

A registered server reaches nobody yet, the same as a built-in Connector you have authorized. Grant it in the usual order: add it to a [Tool Pack](/merge-workforce/tool-packs/overview) with the tools you want exposed, then assign that Tool Pack to a [Group](/merge-workforce/provisioning/groups).

From there it behaves like any built-in Connector. [Description overrides](/merge-workforce/tool-packs/tool-description-overrides) and [input overrides](/merge-workforce/tool-packs/tool-input-overrides) apply per pack, every call passes through your [guardrails](/merge-workforce/security/overview), and calls show up in the [tool call log](/merge-workforce/logs/tool-calls). The one difference is that responses come from your server rather than from a third-party API Merge maintains.

## Tool discovery and refresh

Merge refreshes the tool list periodically, so tools you add to or remove from your server show up within a few minutes. A tool that appears on the server is not exposed to anyone until you add it to a Tool Pack, so a server-side change can't widen what employees reach on its own.

For an immediate refresh, open the Connector and click **Resync**. Merge re-queries `tools/list` right away.

## Rotating the auth token

When you rotate the static token on your server, update Merge's stored copy at the same time. In the console, open the Connector, go to **Application Credentials**, replace the token, and save.

The API has no update call for a registered server. Sending the same registration again with a new key returns `409` rather than replacing the old key, so rotating through the API means deleting the Connector and registering it again. Deleting takes the Connector out of its Tool Packs (see the next section), so you add it back afterward. For a Connector employees already use, the console is the shorter path. Keep the API path for servers your own provisioning code owns end to end.

Rotation isn't graceful. Calls in flight while Merge still holds the old token fail, so time a rotation for a low-traffic window.

## Deleting a server

`DELETE /api/v1/connectors/{slug}/` removes a custom Connector and the credentials stored for it, and returns `204`. Only Connectors your organization registered can be deleted this way. A Connector Merge ships, or one that belongs to another organization, returns `404`.

```bash
curl -X DELETE https://ah-api.merge.dev/api/v1/connectors/acme_wiki/ \
  -H "Authorization: Bearer $MERGE_API_KEY"
```

> **Deleting takes the Connector out of its Tool Packs**
>
> A successful delete also removes the Connector from every Tool Pack that included it, and employees in those packs' Groups lose its tools immediately. Check the Connector's **Tool Packs** tab before you delete it.

The exception is a Tool Pack that sets an auth scope on the Connector. Then the delete is refused with `409`, and the response names the packs holding it:

```json
{
  "error": "The connector acme_wiki is still in the tool packs Support team. Remove it with DELETE /api/v1/tool-packs/{tool_pack_id}/connectors/acme_wiki/ for each one, then delete the connector.",
  "tool_packs": [
    { "id": "5b1d6f0e-2c4a-4e8b-9a7d-3f6c2e1b8a90", "name": "Support team" }
  ]
}
```

Remove it from each listed pack, then repeat the delete. Each deletion is recorded in the audit trail as `CONNECTOR_DELETED`. The schema is in the API reference under [Delete a remote MCP server](/merge-workforce/agent-handler/connectors/connectors-destroy).

## When this isn't the right fit

Custom MCP servers suit internal tools that already speak MCP, and niche third parties where you can run a thin MCP shim in front of their API. For a widely used SaaS product, [request a Connector](https://www.merge.dev/get-in-touch) instead: Merge maintains it across the vendor's API changes, and you don't run a server.

## Next

Bundle the new Connector's tools for the employees who need them with [Tool Packs](/merge-workforce/tool-packs/overview).