---
search:
  tags:
    - Actions
    - POST
seo:
  description: >-
    Answers 404 where the mcp-upstreams feature is off. Registers a remote MCP
    server… Reference for the POST /api/v2/mcp-upstreams endpoint in the Anlyon
    API.
sidebar:
  label: Register an upstream MCP server
  badge: POST
title: Register an upstream MCP server
type: openapi-operation
---
Answers 404 where the `mcp-upstreams` feature is off. Registers a remote MCP server (Streamable HTTP, https) and lists its tools: `initialize` and `tools/list`, reads only. Each tool is classified and hashed by the risk engine anlyon.dev runs. Nothing is adopted, so no agent can call anything yet. Headers carry credentials only as `{{secret:NAME}}` references, checked against the secret's allowed hosts and placements, and naming one needs `secrets:read`. A server whose tools cannot be listed is not registered (400), and one that asks for an OAuth sign-in without OAuth settings is refused with that reason. From a credential it applies at once, except that a stored secret, or a server that signs people in with OAuth, answers 202 and waits for a person's sign-off.

`POST /api/v2/mcp-upstreams`

**Request body** (`application/json`, required)

- `name` (string, required)
- `url` (string<uri>, required)
- `headers` (object)
- `registryName` (string | null)
- `oauth` (object | null) — For a server that signs people in with OAuth (ADR 0026). With \`clientId\`, the client registered with the vendor, whose secret must have allowed hosts. Without it, this deployment's own client metadata document on a verified vendor's server whose authorization server accepts one, else a client registered with it dynamically (RFC 7591).

Request body example:

```json
{
  "name": "string",
  "url": "http://example.com",
  "headers": {
    "property1": "string",
    "property2": "string"
  },
  "registryName": "string",
  "oauth": {
    "clientId": "string",
    "clientSecret": "{{secret:MCP_CLIENT_SECRET}}",
    "scopes": [
      "string"
    ],
    "redirectUri": "http://example.com",
    "authorizationServer": "http://example.com"
  }
}
```

**Responses**

- `201` — Registered, with its tools listed.
- `202` — Accepted, not applied: the change loosens a guardrail, so a workspace admin has to approve it. \`Location\` is the request to poll.
- `400` — Bad Request - Invalid input data
- `401` — Unauthorized - Invalid or missing API key
- `403` — Authenticated credential lacks the required scope or workspace access.
- `404` — Not Found - Resource does not exist
- `409` — The request conflicts with current resource state or a concurrent decision.
- `429` — Too Many Requests - Rate limit exceeded. A workspace's API requests are limited per minute and per UTC clock hour by plan; \`Retry-After\` says when to try again, and an hourly refusal names the limit and when the count resets. Successful responses carry the same limits in \`X-RateLimit-Limit\`, \`X-RateLimit-Remaining\`, \`X-RateLimit-Reset\` (the minute) and \`X-RateLimit-Limit-Hour\`, \`X-RateLimit-Remaining-Hour\`, \`X-RateLimit-Reset-Hour\` (the hour).

Response example, 201:

```json
{
  "success": true,
  "data": {
    "id": "mcpu_01JABCDEF",
    "name": "mailer",
    "url": "http://example.com",
    "headers": {
      "property1": "string",
      "property2": "string"
    },
    "registryName": "string",
    "protocolVersion": "string",
    "transport": "streamable-http",
    "serverInfo": {},
    "tools": [
      {
        "name": "string",
        "hash": "string",
        "definition": {},
        "suggestedClass": "read",
        "reasons": [
          "string"
        ],
        "schemaCheck": "full",
        "schemaCheckReason": "remote_ref"
      }
    ],
    "toolsHash": "string",
    "listedAt": "2019-08-24T14:15:22Z",
    "lastError": {},
    "listedWith": "anonymous",
    "check": {
      "status": "ok",
      "checkedAt": "2019-08-24T14:15:22Z",
      "nextCheckAt": "2019-08-24T14:15:22Z",
      "failures": 0
    },
    "changes": [
      {
        "tool": "string",
        "kind": "changed",
        "action": "string",
        "actionId": "string",
        "pinnedHash": "string",
        "currentHash": "string",
        "suggestedClass": "read",
        "before": {},
        "detectedAt": "2019-08-24T14:15:22Z"
      }
    ],
    "oauth": {
      "resource": "http://example.com",
      "issuer": "http://example.com",
      "authorizationServers": [
        "string"
      ],
      "authorizationEndpoint": "http://example.com",
      "tokenEndpoint": "http://example.com",
      "scopesSupported": [
        "string"
      ],
      "scopes": [
        "string"
      ],
      "clientId": "string",
      "clientRegistration": "pre_registered",
      "advertised": {
        "dynamicRegistration": true,
        "clientIdMetadataDocuments": true,
        "pkceS256": true
      },
      "publicClient": true,
      "redirectUri": "http://example.com",
      "operatorConnected": true
    },
    "adopted": [
      {
        "tool": "string",
        "action": "string",
        "actionId": "string",
        "enabled": true,
        "state": "current",
        "toolHash": "string",
        "requiresApproval": true,
        "schemaCheck": "full",
        "schemaCheckReason": "remote_ref"
      }
    ],
    "archivedAt": "2019-08-24T14:15:22Z",
    "createdAt": "2019-08-24T14:15:22Z",
    "updatedAt": "2019-08-24T14:15:22Z"
  }
}
```

Response example, 400:

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request data",
    "details": {
      "url": "Invalid URL format"
    }
  }
}
```

Response example, 401:

```json
{
  "success": false,
  "error": {
    "code": "AUTHENTICATION_ERROR",
    "message": "Invalid or missing API key"
  }
}
```

Response example, 404:

```json
{
  "success": false,
  "error": {
    "code": "NOT_FOUND",
    "message": "Resource not found"
  }
}
```

Response example, 429:

```json
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Please try again later."
  }
}
```
