---
title: "Add a tool server"
method: POST
path: "/client/mcp/upstreams"
tags: ["Tool gateway"]
---

# Add a tool server

`POST /client/mcp/upstreams`

## Request

- Base URL: `https://api.roar-ai.com`
- URL: `https://api.roar-ai.com/client/mcp/upstreams`
- Auth: HTTP bearer

Registers an MCP server the gateway will forward tool calls to. Its tools arrive switched off until approved — refresh it next. Up to 20 per organisation. Owner or admin, or a key with `mcp:write`.

## Request body

- CreateMcpUpstreamRequest
  - `name` string, required: Lower-case letters, digits and single hyphens, 1–20 characters. Unique in your organisation
  - `url` string, uri, required: The server’s Streamable HTTP endpoint. https only, no username or password in it
  - `auth` McpUpstreamAuth: `{"type":"none"}`, `{"type":"bearer","token":"…"}`, `{"type":"header","header":"X-Api-Key","value":"…"}` or `{"type":"oauth"}` (sign in after saving — nothing is pasted). Stored encrypted; never returned
    - `type` 'none' | 'bearer' | 'header' | 'oauth', required
    - `token` string: For `bearer`
    - `header` string: For `header`: a header name the connection does not itself use
    - `value` string: For `header`
  - `argsPolicy` 'pass' | 'redact' | 'block'

## Response `201`

The added tool server

- McpUpstream
  - `id` string, uuid, required
  - `clientId` string, uuid, required
  - `name` string, required: The prefix agents see on its tools: `<name>__<tool>`
  - `url` string, uri, nullable, required: Null unless the caller may change tool servers: an owner or admin session, or an org-level key with mcp:write. An address can carry a token in its path.
  - `authType` 'none' | 'bearer' | 'header' | 'oauth', required: `oauth`: an owner or admin SIGNS IN to it; its state is `GET /client/mcp/upstreams/{id}/oauth`
  - `authHeader` string, nullable, required: The header NAME for `header` auth — never its value. Null on the same terms as `url`
  - `status` 'active' | 'disabled', required
  - `argsPolicy` 'pass' | 'redact' | 'block', required: What happens to personal data in a call’s ARGUMENTS. Results are always masked
  - `toolsFetchedAt` string, date-time, nullable, required: Null until the first successful refresh
  - `lastError` string, nullable, required: Why the last refresh failed, in words safe to show. Null after a success
  - `createdAt` string, date-time, required
  - `updatedAt` string, date-time, required
  - `hasCredential` boolean, required: Whether a credential is stored. The credential itself is never returned

## Other responses

- `400`: Invalid address or credential, or the organisation is at its limit
- `403`: Not an owner or admin, or key lacks mcp:write
- `409`: A tool server with that name exists

---

[API](https://skmtc.dev/roar-ai/apis/roar-ai-api.md) · [All operations](https://skmtc.dev/roar-ai/apis/roar-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/roar-ai/apis/roar-ai-api/revisions/37e4d0d5a7fe?raw)
