Add a custom MCP server
Connect any MCP server your agents need that is not in the catalog: a remote URL, an npm or PyPI package, a GitHub release, or a package store artifact. Covers credentials, private network routing, agent access, the API, and every error you can hit.
Use a custom MCP server when the tool your agents need is not in the catalog. You give SchemaBounce the server's location and its credentials once, then grant it to the agents that should use it. Agents call its tools the same way they call any catalog server.
| What | Detail |
|---|---|
| Where | Agents > Tools, then Add custom server |
| Permission | adl:manage on the workspace. Without it the button is disabled. |
| Plans | Starter and above. Free and Personal workspaces connect catalog servers only. |
| Sources | Remote URL, npm, PyPI, GitHub release, package store |
| Where it runs | A remote server runs where you host it. A package server runs in your workspace's gateway. |
| Secrets | Encrypted at rest, write-only, never shown again after you save |
Before you start
- Know where the server comes from. You need its URL, or its package name and an exact version.
- Have its credentials ready. API keys and tokens go in the Credentials rows of the form, never in a URL, a package name or a command.
- Check your plan. On Free and Personal, saving a custom server is refused with "The free plan can connect curated marketplace MCP servers. Upgrade to connect custom or community servers." Every plan also limits how many MCP servers a workspace can connect, and custom servers count toward it. See Pricing.
Choose a source
| Source | Use it when | You enter | Pinning |
|---|---|---|---|
| Remote URL | You or a vendor already host the server | HTTPS URL, optional auth header name | None needed. You control the server. |
| npm | The server is published to npm | Package name and exact version | Exact version required (1.4.2) |
| PyPI | The server is published to PyPI | Package name and exact version | Exact version required (0.6.2) |
| GitHub release | The server ships as a binary on a GitHub release | Repository, release tag, asset name | Release tag required, checksum verified |
| Package store | The server is published as a versioned artifact in the registry | Repository path, version or digest | Resolved to a sha256 digest on save |
A remote server is the fastest path when the vendor already runs one. A package server is the right choice for a server you would otherwise run on your own computer with npx or uvx.
Add the server
-
Open Agents > Tools.
-
Select Add custom server. The form opens with the Remote URL tab selected.
-
Choose who owns the connection:
- Personal: your own account. Used only for your messages and runs. Available when personal connections are turned on for the workspace.
- Team: a shared account for one team. Only the team's head can choose it.
- Workspace: a company service or shared account. Used for anyone's messages and scheduled runs, wherever it is granted to an agent.
- Agent: a dedicated identity for one agent. It is assigned from the agent's setup, not from this form.
The owner cannot be changed after you save. See MCP ownership and sharing.
-
Enter a Display name, for example
Internal Scraper. The form shows the server ref it derives,custom/internal-scraper. Two custom servers cannot share a ref, so pick a different name if the form says the ref is taken. The name cannot be changed later. -
Pick the source tab and fill in its fields. Each tab is described below. If the vendor gives you a setup snippet, select Paste setup snippet instead and paste it; the form fills itself in. See Bring a server from Claude Desktop, Claude Code or Cursor.
-
Check the Launch preview. It shows exactly what will run or which URL will be called.
-
Add each secret the server needs under Credentials, one name and value per row.
-
Select Add to workspace (or Add personal connection or Add team connection, matching the owner).
What happens next depends on the source:
- Remote URL: SchemaBounce checks the server right away. The form shows the result, or a "Checking connection" notice while the check finishes in the background.
- npm and PyPI: the button reads Preparing server while the gateway installs that exact version. This can take a few minutes. When it is done you see "Server prepared", and a server check starts.
- GitHub release and package store: the release or artifact is fetched and verified the first time the server starts.
You can also add a custom server from an agent: open the agent, go to Data & Tools > Live tools, select Add a tool, then Add a custom server. A server added this way is granted to that agent as soon as it is saved.
Remote URL
Your team or a vendor operates the server. SchemaBounce connects to it over Streamable HTTP and sends your credential as a request header.
| Field | What to enter |
|---|---|
| Server URL | The server's MCP endpoint, for example https://mcp.example.com/mcp. If you leave out the scheme, the form adds https:// and uses port 443. |
| Auth header | Which row under Credentials to send as a request header. The options are None and the names you entered under Credentials. The row's name is the header name, and its value is sent exactly as written on every request. |
Example: a server that expects Authorization: Bearer <token>.
- Server URL:
https://mcp.example.com/mcp - Credentials: name
Authorization, valueBearer sk-your-token - Auth header:
Authorization
The Auth header is always a credential's name, never its value, so the secret stays encrypted. If you leave it at None, SchemaBounce picks the header from the credential's name:
| Credential name | Header sent |
|---|---|
Authorization (any case) | Authorization: <value>, exactly as written. Use this for Basic ... credentials. |
Contains TOKEN or BEARER, such as API_TOKEN | Authorization: Bearer <value>. A value that already starts with Basic, Bearer, Token or Digest is sent as written. |
Contains API_KEY or APIKEY | X-Api-Key: <value> |
Rules for the URL:
- HTTPS only.
http://is refused, because the credential would travel in plain text. - No secrets in the URL. A query parameter named
token,api_key,apikey,access_token,password,secretorkeyis refused. Put the value in Credentials instead. - No private addresses. A URL pointing at a private, link-local or internal IP is refused. To reach a server inside your own network, route it over the private network.
npm
SchemaBounce installs this exact package version in your workspace's MCP gateway before any agent uses it, then runs its installed entry point. Agent sessions never download packages.
| Field | Example | Rule |
|---|---|---|
| npm package | @my-org/my-mcp-server | A valid npm name, scoped or not |
| Exact version | 1.0.0 | 1.2.3, v1.2.3 or 1.2.3-beta.1. No ranges, no latest. |
The launch preview shows the command, for example npx -y @my-org/[email protected]. Credentials reach the server as environment variables.
PyPI
SchemaBounce builds a prepared virtual environment for this exact version. Agent sessions run it without calling uvx.
| Field | Example | Rule |
|---|---|---|
| PyPI package | my-mcp-server | A valid PyPI name |
| Exact version | 0.6.2 | An exact release such as 1.2.3 or 1.2.3rc1 |
The launch preview shows uvx [email protected]. Credentials reach the server as environment variables.
GitHub release
SchemaBounce downloads a release asset, checks its SHA-256 against the release's published checksums, and runs it in your workspace's gateway.
| Field | Example | Rule |
|---|---|---|
| Repository | my-org/my-mcp | Any owner/name. With GitHub connected to the workspace, your repos are suggested as you type. |
| Release tag | v1.2.3 | A fixed tag. latest, main, master and head are refused. |
| Release asset | my-mcp-linux-amd64 | The file name of the Linux x86-64 binary, or of a .tar.gz, .tgz or .zip holding it |
What the release needs:
- A checksum file that covers the asset. SchemaBounce looks for
checksums.txt,checksums,<asset>.sha256, or goreleaser's<repo>_<version>_checksums.txt. Without one the server does not start. - For an archive, a findable binary. The archive's top level must hold a file named exactly like the repository (
my-mcpformy-org/my-mcp), or exactly one file. - For a private repository, a token. Connect GitHub to the workspace, or add a
GITHUB_TOKENcredential with read access to the repository.
SchemaBounce runs the repository saved on the connection and nothing else. A request for any other repository under that connection is refused.
Package store
SchemaBounce pulls a versioned artifact from the registry, checks it against a pinned sha256 digest, and runs it in your workspace's gateway. The artifact is the published package for an MCP server (a tarball or a single binary), not a source repository.
| Field | Example | Rule |
|---|---|---|
| Repository | org/bots/my-mcp | Lowercase path with at least two parts |
| Version | v1.2.3 | Resolved to a digest when you save. Enter a version, a digest, or both. |
| Pinned digest | sha256: + 64 hex characters | Optional when a version is set |
| Layer selector | my-mcp-linux-amd64 | Only for an artifact that ships builds for several platforms |
For a private artifact, add an OCI_TOKEN credential with pull access. As with GitHub releases, SchemaBounce runs only the repository saved on the connection.
Credentials
Each row under Credentials is a name and a secret value.
- A package server (npm, PyPI, GitHub release, package store) receives every row as an environment variable, for example
MY_API_TOKEN. - A remote server receives credentials as request headers. See Remote URL for which row becomes which header.
- Values are write-only. After you save, the form shows only the names. Leave a value blank when editing to keep the stored secret, or type a new one to replace it.
- Values are encrypted at rest with per-workspace envelope encryption, and the API never returns them.
- Secrets never go anywhere else. The form refuses a package name that contains a space, a colon, or a credential label such as "Client Secret", and the API refuses a command or URL that carries one (
--token=,--api-key=,--password=,Authorization:,Bearer). The launch recipe is visible to workspace members and recorded in the audit log, so a secret there would be exposed.
Never paste a token into a chat with an agent. If one was pasted, revoke it where it was issued and create a new one.
Reach a server on your private network
A remote server inside your own network (a VPC, a data center, a tailnet) is reached through the workspace's private network connection.
- Connect a VPN first, in Settings > Agent Platform > Private network. Without one, the form warns that the server will not reach private hosts.
- On the Remote URL tab, turn on Route this server over the private network (VPN).
- Under Allowed domains, list each host the server may reach, one per line, as
hostorhost:port. For examplemetrics.internal:443. At least one entry is required, and only these hosts are reachable through the tunnel.
Private network routing is available on the Team plan and above, and only for remote URL servers. It is refused for npm and PyPI servers. See Private network.
Give agents access
A saved server does nothing until an agent is granted it.
- From the server: on Agents > Tools, open the connection and use Agents with access.
- For several agents at once: use the Access matrix tab on the same page.
- From the agent: open the agent, go to Data & Tools > Live tools, and select Add a tool.
A grant can be narrowed to a list of the server's tools. Through the API or the SchemaBounce MCP, allowedTools set to a list allows only those tools, an empty list allows none, and leaving it out allows all of them.
Calls to a custom server's tools follow the same approval rules as any connected server. Set them for one agent under the agent's Governance > Approvals, or for every agent under Settings > Agent Platform.
Check that it works
The connection card on Agents > Tools shows the server's state.
| State | Meaning | What to do |
|---|---|---|
| Saved, not yet verified | Saved, but nothing has proven it works yet | Select Test connection, or let an agent use it |
| Checking | A check or a package preparation is running | Wait a few seconds; npm and PyPI preparation can take minutes |
| Connected | The server answered and a read-only tool call succeeded | Grant it to agents |
| Reachable, not authenticated or Reachable (handshake accepted) | The server answered and listed its tools, but none can run without arguments, so the credential is unproven | Agents can use it. If calls fail, check the credential and the Auth header choice |
| Needs setup | A credential or a sign-in is missing | Select Edit credentials and add it |
| Needs attention | The server could not be reached, or it rejected the credential | Open the card to see the error, then fix the URL or credential |
| At session limit | The workspace is running as many MCP servers at once as its plan allows | Stop idle agents, grant fewer servers per agent, or upgrade |
A check starts the server, lists its tools, and calls one read-only tool that needs no arguments, when the server has one. That call is what proves the credential. A server whose tools all need arguments shows Reachable (handshake accepted), and agents can still use it. An agent sees a server's tools only after a check lists them.
Bring a server from Claude Desktop, Claude Code or Cursor
The fastest way: select Paste setup snippet at the top of the Add custom MCP server form and paste what the vendor gave you. It accepts:
- a JSON block from
claude_desktop_config.json,~/.claude.jsonor.cursor/mcp.json(anmcpServersorserversobject, one server object, or a bare"name": { ... }fragment) - a
claude mcp add ...orclaude mcp add-json ...command
The form fills in the name, the tab, the URL or the package and version. Header and environment values go into the masked Credentials rows, Auth header is set (to Authorization when there is one), and the paste box clears itself because the snippet can hold a secret. It lists anything you still need to fix, such as a placeholder value or a missing version pin. An mcp-remote bridge becomes a direct Remote URL. A Docker or local program is refused with the reason. A snippet with several servers asks you to pick one.
To map a config by hand instead, here is how each field lands. That file configures your own computer only; it does nothing in a SchemaBounce workspace.
{
"mcpServers": {
"weather": {
"command": "npx",
"env": { "WEATHER_API_KEY": "..." }
},
"docs": {
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "Bearer ..." }
}
}
}
| In the config file | In SchemaBounce |
|---|---|
"command": "npx" with a package in args | npm tab. Enter the package and pin an exact version. |
"command": "uvx" with a package | PyPI tab. Pin an exact version. |
"url" | Remote URL tab |
Each "env" entry | A row under Credentials with the same name |
A header such as "Authorization" | A row under Credentials with the header's name and full value, chosen as Auth header |
"command": "docker" or a local binary | Not supported. Publish the server to npm or PyPI, or host it remotely. |
Add a server with the API or the SchemaBounce MCP
Everything the form does is one API call, so you can script it. Authenticate with a service account that has adl:manage.
POST https://api.schemabounce.com/api/v1/workspaces/{workspaceId}/mcp/connections
A remote server:
{
"serverRef": "custom/docs-search",
"displayName": "Docs Search",
"credentials": { "Authorization": "Bearer sk-your-token" },
"transportConfig": {
"transportType": "streamable-http",
"packageType": "remote",
"url": "https://mcp.example.com/mcp",
"authHeaderName": "Authorization"
}
}
An npm server:
{
"serverRef": "custom/weather",
"displayName": "Weather",
"credentials": { "WEATHER_API_KEY": "your-key" },
"transportConfig": {
"transportType": "stdio",
"packageType": "npm",
"command": "npx",
}
}
Field rules:
serverRefmust start withcustom/, andtransportConfigis then required. Sending the sameserverRefagain updates that connection.transportTypeisstreamable-httporssefor a remote server,stdiofor a package server.packageTypeisremote,npm,pypi,githuboroci(the package store).- GitHub release uses
repo,refandasset. Package store usesociRepositorywithociVersion,ociDigest, or both. - Private network routing adds
"network": { "scope": "private-network", "allowedDomains": ["metrics.internal:443"] }totransportConfig. - Non-secret settings such as a base URL can go in
variablesinstead ofcredentials. A name cannot be in both. authHeaderNamemust be the name of one of the connection's credentials (for exampleAuthorization), never a value. A value is refused with a 400.
The response is the saved connection. Its credentialKeys lists the credential names, never the values. Check it with POST .../mcp/connections/{connectionId}/validate. That call returns 202 while the check runs in the background.
From an MCP client connected to the SchemaBounce MCP, call sb_access with action: connection_create and pass server_ref, display_name, credentials and transport_config, using the same fields as above. Then grant it with action: grant.
Change or remove a server
- Edit: on Agents > Tools, open the connection and select Edit credentials. You can change the source fields, the pinned version and the credentials. The display name and the owner stay fixed.
- Move to a new version: edit the server and enter the new exact version, tag or digest. Custom servers do not take the catalog's one-click upgrade.
- Remove: select Remove on the connection card. This deletes the connection and its stored credentials, and agents that were granted it can no longer call its tools.
Troubleshooting
| You see | Cause | Fix |
|---|---|---|
| "The free plan can connect curated marketplace MCP servers. Upgrade to connect custom or community servers." | The workspace is on Free or Personal | Upgrade to Starter or above |
| "Plan limit reached: your plan allows N connected MCP servers." | The workspace is at its plan's server limit | Remove a server you no longer use, or upgrade |
| "Personal MCP connections are not enabled for this workspace." | Personal ownership is off for this workspace | Choose Workspace or Team |
transportConfig.authHeaderName must be the name of one of the connection's credentials | A header value was sent in authHeaderName | Put the value in credentials and send only its name |
transportConfig.url must use https | The URL starts with http:// | Use the server's HTTPS address |
transportConfig.url must not point at an internal/private address | The URL is a private IP | Route the server over the private network |
... must not contain an inline credential | A token is in the URL, command or arguments | Move it to Credentials |
"An exact version is required." or is not an exact pin | The version is empty, a range, or a tag such as latest | Enter one exact version |
| "This looks like a credential." | The package field holds a secret | Put the package name there and the secret under Credentials |
ref "main" is not pinned | The GitHub tag is a branch or latest | Use a release tag such as v1.2.3 |
repo "owner/name" is not on the allowlist | The repository differs from the one saved on the connection, or the workspace is on Free or Personal | Edit the server to the right repository. Custom servers need Starter or above |
no checksums asset in release or no checksum entry | The GitHub release has no checksum file, or it does not list the asset | Publish checksums.txt (goreleaser does this by default) with the release |
ambiguous archive: no top-level file named ... | The archive holds several files and none is named like the repository | Name the binary after the repository, or ship it alone in the archive |
| "A custom server already uses the ref ..." | Another custom server has the same derived ref | Choose a different display name, or edit the existing server |
scope "private-network" is not allowed for packageType "npm" | Private network routing was set on a package server | Host the server remotely, or turn routing off |
| At session limit on the card | Too many MCP servers running at once for the plan | Stop idle agents, grant fewer servers per agent, or upgrade |
Limits
- No Docker images and no local binaries. A server comes from a URL or a published package. SchemaBounce does not run a container per session.
- No floating versions. Every package server runs one exact version, so every run uses the same code.
- No plain HTTP. Remote servers use HTTPS.
- Plan limits apply. Custom servers count toward the plan's connected MCP server limit and its limit on servers running at once.
Common questions
Can I add an MCP server that is not in the catalog?
Yes. On Starter and above, open Agents > Tools, select Add custom server, and enter its URL or package. Free and Personal workspaces connect catalog servers only.
Where does my custom MCP server run?
A remote server runs wherever you or the vendor host it; SchemaBounce only connects to it. A package server (npm, PyPI, GitHub release, package store) runs as a process inside your workspace's MCP gateway.
Can SchemaBounce run my server from a Docker image?
No. Publish it to npm or PyPI, or host it and add it by URL.
How do I use a server from my Claude Desktop config?
Copy each field into the matching tab: npx packages into npm, uvx packages into PyPI, a url into Remote URL, and each env entry or header into Credentials. See Bring a server from Claude Desktop, Claude Code or Cursor.
Who can see my credentials?
Nobody, after you save. Values are encrypted and write-only; the console and the API show only their names.
Can an agent add a custom server by itself?
An agent inside the workspace cannot add a server or enter its credentials. An MCP client you connect to the SchemaBounce MCP can, through sb_access with action: connection_create, using your permissions.
Related
Related Documentation
Continue exploring