# Register an MCP endpoint

POST

/v1/mcp/endpoints

```sh
curl --request POST \
  --url https://example.com/v1/mcp/endpoints \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{ "endpointId": "example", "name": "example", "transport": "stdio", "config": { "transport": "stdio", "command": "example", "args": [ "example" ], "env": { "additionalProperty": "example" } }, "secretRef": { "envName": "example", "name": "example" }, "auth": { "scheme": "basic", "username": "example", "secretRef": { "envName": "example", "name": "example" } }, "instructions": "example", "metadata": {}, "sendTraceparent": true, "scopeKind": "tenant", "scopeId": "example" }'
```

Body is a full `MCPEndpoint` plus the scope to register it in (`scopeKind` + `scopeId`); authorization checks that scope. Server validates the closed transport enum + the `config.transport` matches `transport` guardrail + per-variant required fields (`command` for stdio; `url` for http-sse / streamable-http), and refuses unknown fields. `secretRef` names a secret in the deployment’s store — plaintext secrets never cross the wire — and never a model provider’s key (`400 provider-key-refused`). Where each secret it names is read is recorded on it now (`secretScopes`): the first of its own scope, its project’s org and the tenant that has the secret and that the caller administers, else the broadest they administer; the runtime reads it there only. A deployment with `KINDGI_TENANT_HOST_ACCESS=deployed` (the default outside development) refuses a `stdio` endpoint, which would run a command on the server’s host: `403 host-access-denied`.

## Authorizations

- **[bearerAuth](https://docs.kindgi.com/v0.1.9/reference/api.md#bearerauth)**

## Parameters

### Header Parameters

**Idempotency-Key**

string

\>= 1 characters

Caller-supplied idempotency key, scoped to the caller: the same key from someone else in the tenant is their own request (from 0.1.5). Retries with the same key return the original response byte-identical (per `docs/API-ROUTE-CONVENTIONS.md` §3.1). A retry sent while the first request still runs is answered `409 idempotency-key-in-flight` with `Retry-After`, on a runtime whose store holds keys (from 0.1.5); retry after it, and you get the first request’s answer. An answer that carries a secret (a new API key or public run token, a session token, a generated signing secret) isn’t kept: a retry gets `409 idempotency-key-replay-withheld`, with the first request’s status and when it succeeded (from 0.1.5).

## Request Bodyrequired

application/json

object

**endpointId**

required

No `.`: an MCP tool’s id is `<endpointId>.<tool name>`, so it splits at its first `.`. An endpoint registered with one before keeps it.

string

\>= 1 characters /^\[^.]+$/

**name**

required

Human-readable display name.

string

\>= 1 characters

**transport**

required

MCP transport variant. `stdio` — local subprocess (spawn a command). `http-sse` — the older MCP HTTP+SSE transport (separate POST + SSE endpoints). `streamable-http` — the Streamable HTTP transport (single endpoint, session id via header).

string

Allowed values: stdio http-sse streamable-http

**config**

required

One of:

- **object**

  object

  **transport**

  required

  Allowed value: stdio

  **command**

  required

  string

  \>= 1 characters

  **args**

  Array\<string>

  **env**

  object

  **_key_**

  additional properties

  string

- **object**

  object

  **transport**

  required

  Allowed value: http-sse

  **url**

  required

  string format: uri

  **sseUrl**

  Optional distinct SSE endpoint if the server splits them.

  string format: uri

  **headers**

  Headers sent with every request, as given. Not for a credential: a header named `Authorization`, `Proxy-Authorization` or `Cookie`, or with a `-`/`_`-separated part `token`, `secret`, `password`, `passwd`, `apikey`, `credential`, `credentials`, `signature`, `session`, `auth`, `authorization` or `authentication`, or the parts `api-key`, `access-key`, `private-key`, `auth-key`, `subscription-key`, `functions-key`, `application-key`, `app-key`, `master-key`, `client-key` or `partner-key` (`X-Api-Key`, `X-Auth-Token`), is refused (`invalid-mcp-endpoint`, reason `credential-in-headers`). Send it by reference: `auth` with `scheme: header`, or `secretRef` for a bearer. An endpoint registered with one before keeps working, and its value reads back as `[redacted]`.

  object

  **_key_**

  additional properties

  string

- **object**

  object

  **transport**

  required

  Allowed value: streamable-http

  **url**

  required

  string format: uri

  **headers**

  Headers sent with every request, as given. Not for a credential: a header named `Authorization`, `Proxy-Authorization` or `Cookie`, or with a `-`/`_`-separated part `token`, `secret`, `password`, `passwd`, `apikey`, `credential`, `credentials`, `signature`, `session`, `auth`, `authorization` or `authentication`, or the parts `api-key`, `access-key`, `private-key`, `auth-key`, `subscription-key`, `functions-key`, `application-key`, `app-key`, `master-key`, `client-key` or `partner-key` (`X-Api-Key`, `X-Auth-Token`), is refused (`invalid-mcp-endpoint`, reason `credential-in-headers`). Send it by reference: `auth` with `scheme: header`, or `secretRef` for a bearer. An endpoint registered with one before keeps working, and its value reads back as `[redacted]`.

  object

  **_key_**

  additional properties

  string

**secretRef**

The secret an MCP endpoint authenticates with: a name in the deployment’s secrets store, resolved at the endpoint’s tenant scope by the runtime (the shape webhooks and providers use). As the endpoint’s own `secretRef`, it is sent as its bearer; inside `auth`, it is the scheme’s password, client secret or header value. The endpoint keeps only this reference.

object

**envName**

required

string

/^\[a-z]\[a-z0-9-]{0,62}$/

**name**

required

string

\>= 1 characters <= 256 characters

**auth**

One of: discriminator: scheme

- **object**

  HTTP Basic: the runtime sends `Authorization: Basic base64(<username>:<secret>)`, with the secret `secretRef` names (a WordPress Application Password, for one).

  object

  **scheme**

  required

  Allowed value: basic

  **username**

  required

  The user name. Not a secret, and without `:`.

  string

  \>= 1 characters <= 256 characters /^\[^:]+$/

  **secretRef**

  required

  The secret an MCP endpoint authenticates with: a name in the deployment’s secrets store, resolved at the endpoint’s tenant scope by the runtime (the shape webhooks and providers use). As the endpoint’s own `secretRef`, it is sent as its bearer; inside `auth`, it is the scheme’s password, client secret or header value. The endpoint keeps only this reference.

  object

  **envName**

  required

  string

  /^\[a-z]\[a-z0-9-]{0,62}$/

  **name**

  required

  string

  \>= 1 characters <= 256 characters

- **object**

  OAuth 2 client credentials: the runtime asks `tokenUrl` for an access token (`grant_type=client_credentials`) as `clientId`, with the client secret `secretRef` names, sends it as the endpoint’s bearer, and asks for a new one before it expires (Drupal’s Simple OAuth, for one). The token is never stored.

  object

  **scheme**

  required

  Allowed value: oauth2-client-credentials

  **tokenUrl**

  required

  The token endpoint: https, or http to a loopback host (`localhost`, `127.0.0.1`, `[::1]`).

  string format: uri

  <= 2048 characters

  **clientId**

  required

  string

  \>= 1 characters <= 512 characters

  **secretRef**

  required

  The secret an MCP endpoint authenticates with: a name in the deployment’s secrets store, resolved at the endpoint’s tenant scope by the runtime (the shape webhooks and providers use). As the endpoint’s own `secretRef`, it is sent as its bearer; inside `auth`, it is the scheme’s password, client secret or header value. The endpoint keeps only this reference.

  object

  **envName**

  required

  string

  /^\[a-z]\[a-z0-9-]{0,62}$/

  **name**

  required

  string

  \>= 1 characters <= 256 characters

  **scope**

  The scopes to ask for, space-separated, sent as-is. Absent: none asked for.

  string

  \>= 1 characters <= 1024 characters

  **audience**

  An `audience` parameter, for identity providers that need one. Absent: none sent.

  string

  \>= 1 characters <= 1024 characters

  **clientAuth**

  How the client authenticates to the token endpoint (RFC 6749 §2.3.1): `client_secret_basic` (an `Authorization: Basic` header, the default) or `client_secret_post` (`client_id` and `client_secret` in the form).

  string

  Allowed values: client_secret_basic client_secret_post

- **object**

  Headers of the server’s own, each with a secret: an API key in `X-Api-Key`, a key and a secret in two headers, or `Authorization` with a prefix other than `Bearer `(`Token …`). The runtime resolves each `secretRef` when it connects, and refuses to follow a redirect with them, as it does for every `auth`.

  object

  **scheme**

  required

  Allowed value: header

  **headers**

  required

  Array\<object>

  \>= 1 items <= 4 items

  One header a `header` auth sends: `<name>: <prefix><secret>`.

  object

  **name**

  required

  An HTTP header name, once per `auth` (case-insensitive). Not one the MCP transport or HTTP sets itself (`Host`, `Content-Type`, `Content-Length`, `Accept`, `Connection`, `Mcp-Session-Id`, `Mcp-Protocol-Version`, `Last-Event-ID`, `traceparent`, `tracestate`, …): reason `invalid-auth`.

  string

  \>= 1 characters <= 256 characters /^\[!#$%&'\*+.^\_\`|\~0-9A-Za-z-]+$/

  **secretRef**

  required

  The secret an MCP endpoint authenticates with: a name in the deployment’s secrets store, resolved at the endpoint’s tenant scope by the runtime (the shape webhooks and providers use). As the endpoint’s own `secretRef`, it is sent as its bearer; inside `auth`, it is the scheme’s password, client secret or header value. The endpoint keeps only this reference.

  object

  **envName**

  required

  string

  /^\[a-z]\[a-z0-9-]{0,62}$/

  **name**

  required

  string

  \>= 1 characters <= 256 characters

  **prefix**

  Text sent before the secret, such as `Token `or `ApiKey `, without control characters. Absent: none. Not a secret: it reads back as registered.

  string

  \>= 1 characters <= 64 characters

**instructions**

Optional pass-through to the MCP client `serverInfo.instructions`.

string

**metadata**

Optional caller-defined metadata bag.

object

**_key_**

additional properties

any

**sendTraceparent**

Send the W3C `traceparent` of the run calling a tool to this endpoint, as a request header, so the server’s logs can be matched to the run. Ids only, never content. Default `false`. HTTP transports only: `true` on a `stdio` endpoint is refused (`invalid-mcp-endpoint`, reason `invalid-send-traceparent`). An older runtime ignores it and sends none.

boolean

**scopeKind**

required

Discriminator for the ?scopeKind + ?scopeId + ?inherit triplet. Tenant carries no id (implicit from session); org/project require scopeId.

string

Allowed values: tenant org project

**scopeId**

Required when `scopeKind` is `org` or `project`; absent for `tenant` (implicit from the session).

string

\>= 1 characters

## Responses

### 201

MCP endpoint registered.

application/json

object

**endpointId**

required

string

##### Examplegenerated

```json
{
  "endpointId": "example"
}
```

### 400

Validation failed (see `details.reason`). Or `provider-key-refused`: `secretRef` names a model provider’s key (`details.secret`, `details.providerId`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 401

Missing / malformed / expired / revoked bearer token.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 403

`host-access-denied`: a `stdio` endpoint on a deployment that refuses commands on its host (`KINDGI_TENANT_HOST_ACCESS=deployed` or `isolated`); or `authz-denied`.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 409

MCP endpoint already registered at that id. Or `env-not-served`: this runtime keeps every value under the env it serves (whoami’s `servedEnv`, with `refusesOtherEnvs`), and the secret reference names another. Name it under `details.servedEnv`. Nothing is stored.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 413

The request body is over the API’s limit (`request-too-large`; `details.maxBytes` says how much).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 429

`rate-limit-exceeded` with `details.limit: "schema-compile-time"`, on a server with a compile budget (Kindgi Cloud): this tenant’s schemas spent `details.budgetMs` compiling in the last 60 seconds. Nothing was stored; `Retry-After` says when to send it again.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 500

Server error (unmapped domain code or framework crash).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

## See also

- [`GET /v1/mcp/endpoints`: List MCP endpoints](https://docs.kindgi.com/v0.1.9/reference/api/operations/mcpendpointslist.md)
- [`GET /v1/mcp/endpoints/{endpointId}`: Fetch an MCP endpoint](https://docs.kindgi.com/v0.1.9/reference/api/operations/mcpendpointsget.md)
- [`POST /v1/mcp/endpoints/{endpointId}/unregister`: Unregister an MCP endpoint](https://docs.kindgi.com/v0.1.9/reference/api/operations/mcpendpointsunregister.md)
- [`GET /v1/mcp/endpoints/{endpointId}/resources`: List resources at a remote MCP endpoint](https://docs.kindgi.com/v0.1.9/reference/api/operations/mcpendpointsresourceslist.md)
- [Kindgi 0.1.9](https://docs.kindgi.com/v0.1.9/releases/0.1.9.md)
- [Error code `authz-denied`](https://docs.kindgi.com/v0.1.9/reference/errors.md#authz-denied)
- [Error code `env-not-served`](https://docs.kindgi.com/v0.1.9/reference/errors.md#env-not-served)
- [Error code `host-access-denied`](https://docs.kindgi.com/v0.1.9/reference/errors.md#host-access-denied)
- [Error code `provider-key-refused`](https://docs.kindgi.com/v0.1.9/reference/errors.md#provider-key-refused)
- [Error code `rate-limit-exceeded`](https://docs.kindgi.com/v0.1.9/reference/errors.md#rate-limit-exceeded)
- [Error code `request-too-large`](https://docs.kindgi.com/v0.1.9/reference/errors.md#request-too-large)
- [Every error code](https://docs.kindgi.com/v0.1.9/reference/errors.md)
