# MCP servers and secrets API

> Your MCP servers and the tools they offer, and the secrets Bash uses but never shows.

Send `Authorization: Bearer vk_test_...` on every call.

## `GET /v1/mcp-servers`

Its own page: [GET /v1/mcp-servers](https://vestra.ai/developers/reference/api/mcp-servers-and-secrets/get-v1-mcp-servers.md)

Your registered MCP servers.

Your API key, scope `setup` or `all`. On the card: [`deploy`](https://vestra.ai/developers/deploy.md). Over HTTP it is made from the contract: fill in every value.

### Request

Query:

- `limit` (`integer`): How many items per page, 1 to 100. Default 20.
- `cursor` (`string`): Where to go on from: the `next_cursor` of the page before, unchanged. Absent: the first page.

### Answers 200

- `data` (`object[]`): The items on this page.
  - `object` (`"mcp_server"`): Always `mcp_server`.
  - `id` (`string`): The MCP server's id. Example: `mcp_8f2k`.
  - `slug` (`string`): The name Bash calls it by.
  - `name` (`string`): Its name, as people read it.
  - `url` (`string`): The server's https URL.
  - `auth` (`object`): How Bash signs in to it.
    - `type` (`"none" | "headers"`): `none`, or `headers` sent with every call.
    - `headers` (`map of string`, may be absent): The headers, as written: a credential shows as its `${secret:NAME}`, never its value.
  - `tools` (`object`): Which of its tools Bash never calls (`deny`), and which wait for the person's yes (`require_approval`), in every deployment that uses the server. A deployment may add to either list, never take from it.
    - `deny` (`string[]`): Tools of the server Bash never calls, by name, at most 200. Every deployment that names the server runs under it.
    - `require_approval` (`string[]`): Tools of the server that always wait for the person's yes, by name, at most 200.
  - `secrets` (`string[]`): The company secrets it needs (`${secret:NAME}`), by name: store each with PUT /v1/secrets/{name}.
  - `user_secrets` (`string[]`): The secrets each user needs (`${user_secret:NAME}`), by name: store each user's with PUT /v1/secrets/{name} and their `user_id`.
  - `created_at` (`string`): When it was made, as an ISO 8601 date and time in UTC.
  - `updated_at` (`string`): When it last changed, as an ISO 8601 date and time in UTC.
- `next_cursor` (`string | null`): Send it as `cursor` for the next page; null on the last page.

### Code

**HTTP**

```bash
curl "$VESTRA_BASE_URL/v1/mcp-servers" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `POST /v1/mcp-servers`

Its own page: [POST /v1/mcp-servers](https://vestra.ai/developers/reference/api/mcp-servers-and-secrets/post-v1-mcp-servers.md)

Register one: URL, auth (none, or headers using ${secret:NAME} or ${user_secret:NAME}), and its tool policy floor: tools never run and tools that always need approval, for every deployment in both modes. A deployment version's own tool policy adds to the floor and never removes from it.

Your API key, scope `setup` or `all`. On the card: [`deploy`](https://vestra.ai/developers/deploy.md). Over HTTP it is made from the contract: fill in every value.

### Request

Headers:

- `Idempotency-Key` (`string`): Send the same key to retry this call safely for 24 hours.

Body:

- `name` (`string`, required): Its name, as people read it, 1 to 80 characters. Limits: 1 to 80 characters. Example: `Travel desk`.
- `slug` (`string`, required): The name Bash calls it by: lowercase letters, digits and hyphens, starting with a letter, up to 40. Example: `travel-desk`.
- `url` (`string (URL)`, required): The server's public https URL, up to 2,048 characters. Limits: at most 2048 characters. Example: `https://mcp.example.com/travel`.
- `auth` (`object`): How Bash signs in to it. Default `{ "type": "none" }`. Default: `{"type":"none"}`. One of: `{ type }`, `{ type, headers }`, `{ type }`.
- `tools` (`object`): Which of its tools Bash never calls (`deny`), and which wait for the person's yes (`require_approval`), in every deployment that uses the server. A deployment may add to either list, never take from it. Default: no rule. Default: `{"deny":[],"require_approval":[]}`.
  - `deny` (`string[]`): Tools of the server Bash never calls, by name, at most 200. Every deployment that names the server runs under it. Default: `[]`. Limits: at most 200 items.
  - `require_approval` (`string[]`): Tools of the server that always wait for the person's yes, by name, at most 200. Default: `[]`. Limits: at most 200 items.

### Answers 201

- `object` (`"mcp_server"`): Always `mcp_server`.
- `id` (`string`): The MCP server's id. Example: `mcp_8f2k`.
- `slug` (`string`): The name Bash calls it by.
- `name` (`string`): Its name, as people read it.
- `url` (`string`): The server's https URL.
- `auth` (`object`): How Bash signs in to it.
  - `type` (`"none" | "headers"`): `none`, or `headers` sent with every call.
  - `headers` (`map of string`, may be absent): The headers, as written: a credential shows as its `${secret:NAME}`, never its value.
- `tools` (`object`): Which of its tools Bash never calls (`deny`), and which wait for the person's yes (`require_approval`), in every deployment that uses the server. A deployment may add to either list, never take from it.
  - `deny` (`string[]`): Tools of the server Bash never calls, by name, at most 200. Every deployment that names the server runs under it.
  - `require_approval` (`string[]`): Tools of the server that always wait for the person's yes, by name, at most 200.
- `secrets` (`string[]`): The company secrets it needs (`${secret:NAME}`), by name: store each with PUT /v1/secrets/{name}.
- `user_secrets` (`string[]`): The secrets each user needs (`${user_secret:NAME}`), by name: store each user's with PUT /v1/secrets/{name} and their `user_id`.
- `created_at` (`string`): When it was made, as an ISO 8601 date and time in UTC.
- `updated_at` (`string`): When it last changed, as an ISO 8601 date and time in UTC.

### Code

**HTTP**

```bash
curl -X POST "$VESTRA_BASE_URL/v1/mcp-servers" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Travel desk",
    "slug": "travel-desk",
    "url": "https://mcp.example.com/travel"
  }'
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `GET /v1/mcp-servers/{server_id}`

Its own page: [GET /v1/mcp-servers/{server_id}](https://vestra.ai/developers/reference/api/mcp-servers-and-secrets/get-v1-mcp-servers-server-id.md)

One server and its settings.

Your API key, scope `setup` or `all`. On the card: [`deploy`](https://vestra.ai/developers/deploy.md). Over HTTP it is made from the contract: fill in each {…} and every value.

### Request

Path:

- `server_id` (`string`, required): The MCP server's id (`mcp_...`).

### Answers 200

- `object` (`"mcp_server"`): Always `mcp_server`.
- `id` (`string`): The MCP server's id. Example: `mcp_8f2k`.
- `slug` (`string`): The name Bash calls it by.
- `name` (`string`): Its name, as people read it.
- `url` (`string`): The server's https URL.
- `auth` (`object`): How Bash signs in to it.
  - `type` (`"none" | "headers"`): `none`, or `headers` sent with every call.
  - `headers` (`map of string`, may be absent): The headers, as written: a credential shows as its `${secret:NAME}`, never its value.
- `tools` (`object`): Which of its tools Bash never calls (`deny`), and which wait for the person's yes (`require_approval`), in every deployment that uses the server. A deployment may add to either list, never take from it.
  - `deny` (`string[]`): Tools of the server Bash never calls, by name, at most 200. Every deployment that names the server runs under it.
  - `require_approval` (`string[]`): Tools of the server that always wait for the person's yes, by name, at most 200.
- `secrets` (`string[]`): The company secrets it needs (`${secret:NAME}`), by name: store each with PUT /v1/secrets/{name}.
- `user_secrets` (`string[]`): The secrets each user needs (`${user_secret:NAME}`), by name: store each user's with PUT /v1/secrets/{name} and their `user_id`.
- `created_at` (`string`): When it was made, as an ISO 8601 date and time in UTC.
- `updated_at` (`string`): When it last changed, as an ISO 8601 date and time in UTC.

### Code

**HTTP**

```bash
curl "$VESTRA_BASE_URL/v1/mcp-servers/{server_id}" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `PATCH /v1/mcp-servers/{server_id}`

Its own page: [PATCH /v1/mcp-servers/{server_id}](https://vestra.ai/developers/reference/api/mcp-servers-and-secrets/patch-v1-mcp-servers-server-id.md)

Change URL, auth or the tool policy floor, for every deployment in both modes. Users pick it up at their next task. While a deployment's live version uses the server, a test key's change answers 409 mcp_server_live: change it with a live key, or as an owner or admin.

Your API key, scope `setup` or `all`. On the card: [`deploy`](https://vestra.ai/developers/deploy.md). Over HTTP it is made from the contract: fill in each {…} and every value.

### Request

Path:

- `server_id` (`string`, required): The MCP server's id (`mcp_...`).

Body:

- `name` (`string`): A new name, 1 to 80 characters. Limits: 1 to 80 characters.
- `url` (`string (URL)`): A new public https URL. Limits: at most 2048 characters.
- `auth` (`object`): How Bash signs in to it, from now on. One of: `{ type }`, `{ type, headers }`, `{ type }`.
- `tools` (`object`): New rules for its tools (`deny`, `require_approval`), for every deployment that uses the server; a list left out stays as it is.
  - `deny` (`string[]`): Tools of the server Bash never calls, by name, at most 200. Every deployment that names the server runs under it. It replaces the list; left out, the list stays. Limits: at most 200 items.
  - `require_approval` (`string[]`): Tools of the server that always wait for the person's yes, by name, at most 200. It replaces the list; left out, the list stays. Limits: at most 200 items.

### Answers 200

- `object` (`"mcp_server"`): Always `mcp_server`.
- `id` (`string`): The MCP server's id. Example: `mcp_8f2k`.
- `slug` (`string`): The name Bash calls it by.
- `name` (`string`): Its name, as people read it.
- `url` (`string`): The server's https URL.
- `auth` (`object`): How Bash signs in to it.
  - `type` (`"none" | "headers"`): `none`, or `headers` sent with every call.
  - `headers` (`map of string`, may be absent): The headers, as written: a credential shows as its `${secret:NAME}`, never its value.
- `tools` (`object`): Which of its tools Bash never calls (`deny`), and which wait for the person's yes (`require_approval`), in every deployment that uses the server. A deployment may add to either list, never take from it.
  - `deny` (`string[]`): Tools of the server Bash never calls, by name, at most 200. Every deployment that names the server runs under it.
  - `require_approval` (`string[]`): Tools of the server that always wait for the person's yes, by name, at most 200.
- `secrets` (`string[]`): The company secrets it needs (`${secret:NAME}`), by name: store each with PUT /v1/secrets/{name}.
- `user_secrets` (`string[]`): The secrets each user needs (`${user_secret:NAME}`), by name: store each user's with PUT /v1/secrets/{name} and their `user_id`.
- `created_at` (`string`): When it was made, as an ISO 8601 date and time in UTC.
- `updated_at` (`string`): When it last changed, as an ISO 8601 date and time in UTC.

### Code

**HTTP**

```bash
curl -X PATCH "$VESTRA_BASE_URL/v1/mcp-servers/{server_id}" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `DELETE /v1/mcp-servers/{server_id}`

Its own page: [DELETE /v1/mcp-servers/{server_id}](https://vestra.ai/developers/reference/api/mcp-servers-and-secrets/delete-v1-mcp-servers-server-id.md)

Remove it from every user. While a deployment's live version uses it, a test key answers 409 mcp_server_live.

Your API key, scope `setup` or `all`. On the card: [`deploy`](https://vestra.ai/developers/deploy.md). Over HTTP it is made from the contract: fill in each {…} and every value.

### Request

Path:

- `server_id` (`string`, required): The MCP server's id (`mcp_...`).

### Answers 200

- `object` (`"mcp_server"`): Always `mcp_server`.
- `id` (`string`): The MCP server's id. Example: `mcp_8f2k`.
- `deleted` (`true`): Always true: it is gone.

### Code

**HTTP**

```bash
curl -X DELETE "$VESTRA_BASE_URL/v1/mcp-servers/{server_id}" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `POST /v1/mcp-servers/{server_id}/check`

Its own page: [POST /v1/mcp-servers/{server_id}/check](https://vestra.ai/developers/reference/api/mcp-servers-and-secrets/post-v1-mcp-servers-server-id-check.md)

Connect now and list its tools, to prove it works.

Your API key, scope `setup` or `all`. On the card: [`deploy`](https://vestra.ai/developers/deploy.md). Over HTTP it is made from the contract: fill in each {…} and every value.

### Request

Path:

- `server_id` (`string`, required): The MCP server's id (`mcp_...`).

Body, if you send one:

- `user_id` (`string`): Your id for a user, to fill in their `${user_secret:NAME}` headers. Absent: the company's own secrets only. Limits: 1 to 256 characters. Example: `traveller-42`.

### Answers 200

- `object` (`"mcp_check"`): Always `mcp_check`.
- `ok` (`boolean`): Whether Bash reached the server and listed its tools.
- `tools` (`string[]`): The tools it offers, by name; empty when it could not be reached.
- `reason` (`"missing_secret" | "auth_required" | "url_not_public" | "unreachable"`, may be absent): Why not, when `ok` is false: `missing_secret` (see `missing`), `auth_required` (it asked for credentials), `url_not_public`, or `unreachable`.
- `missing` (`string[]`, may be absent): With `missing_secret`, the secrets to store first, by name.

### Code

**HTTP**

```bash
curl -X POST "$VESTRA_BASE_URL/v1/mcp-servers/{server_id}/check" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `GET /v1/mcp-servers/{server_id}/tools`

Its own page: [GET /v1/mcp-servers/{server_id}/tools](https://vestra.ai/developers/reference/api/mcp-servers-and-secrets/get-v1-mcp-servers-server-id-tools.md)

The tools we found, each with its setting in the server's tool policy floor. A deployment version's own policy can add to it.

Your API key, scope `setup` or `all`. On the card: [`deploy`](https://vestra.ai/developers/deploy.md). Over HTTP it is made from the contract: fill in each {…} and every value.

### Request

Path:

- `server_id` (`string`, required): The MCP server's id (`mcp_...`).

Query:

- `user_id` (`string`): Your id for a user, to fill in their `${user_secret:NAME}` headers. Absent: the company's own secrets only.

### Answers 200

- `data` (`object[]`): The items on this page.
  - `object` (`"mcp_tool"`): Always `mcp_tool`.
  - `name` (`string`): The tool's name, as the server gives it.
  - `denied` (`boolean`): true when the floor says Bash never calls it.
  - `requires_approval` (`boolean`): true when the floor says it waits for the person's yes.
- `next_cursor` (`string | null`): Send it as `cursor` for the next page; null on the last page.

### Code

**HTTP**

```bash
curl "$VESTRA_BASE_URL/v1/mcp-servers/{server_id}/tools" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**TypeScript, Python, CLI, MCP**: No sample in your language for this route, so here it is over HTTP.

## `PUT /v1/secrets/{name}`

Its own page: [PUT /v1/secrets/{name}](https://vestra.ai/developers/reference/api/mcp-servers-and-secrets/put-v1-secrets-name.md)

Store a secret, write-only: the company's (refer to it as ${secret:NAME}), or with user_id one user's own (${user_secret:NAME}), for example their travel account token. Storing it again replaces it.

Your API key, scope `setup` or `all`. On the card: [`secrets`](https://vestra.ai/developers/secrets.md). Every call here is a checked sample.

### Request

Path:

- `name` (`string`, required): The secret's name: capitals, digits and underscores, starting with a capital.

Body:

- `user_id` (`string`): Whose secret: your id for the user, for their own (read as `${user_secret:NAME}`). Absent: your company's (read as `${secret:NAME}`). Example: `traveller-42`.
- `value` (`string`, required): The secret itself, up to 8,192 characters. It is write-only: no call ever returns it. Limits: 1 to 8192 characters.

### Answers 200

- `object` (`"secret"`): Always `secret`.
- `name` (`string`): The secret's name. Example: `PNR_API_KEY`.
- `user_id` (`string | null`): Your id for the user whose secret it is; null for your company's.
- `mode` (`"live" | "test"`): The mode it is kept in: a live key reads live secrets, a test key test ones.

### Code

**TypeScript**

```ts
await vestra.secrets.set('TRIPNEST_MCP_TOKEN', process.env.TRIPNEST_MCP_TOKEN!);
```

**Python**

```python
vestra.secrets.set("TRIPNEST_MCP_TOKEN", os.environ["TRIPNEST_MCP_TOKEN"])
```

**CLI**

```bash
vestra secrets set TRIPNEST_MCP_TOKEN
```

**HTTP**

```bash
# Store a company secret. Your MCP servers refer to it as ${secret:TRIPNEST_MCP_TOKEN}.
curl -X PUT "$VESTRA_BASE_URL/v1/secrets/TRIPNEST_MCP_TOKEN" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "value": "your-token" }'

# Store a user's secret. It is for their own account, as ${user_secret:IRCTC_TOKEN}.
curl -X PUT "$VESTRA_BASE_URL/v1/secrets/IRCTC_TOKEN" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_id": "traveller-42", "value": "token-from-their-account" }'

# List and delete secrets. The list shows names only, never values.
curl "$VESTRA_BASE_URL/v1/secrets" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

curl -X DELETE "$VESTRA_BASE_URL/v1/secrets/TRIPNEST_MCP_TOKEN" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**MCP**: No sample in your language for this route, so here it is over HTTP.

## `DELETE /v1/secrets/{name}`

Its own page: [DELETE /v1/secrets/{name}](https://vestra.ai/developers/reference/api/mcp-servers-and-secrets/delete-v1-secrets-name.md)

Remove one: the company's, or one user's (user_id).

Your API key, scope `setup` or `all`. On the card: [`secrets`](https://vestra.ai/developers/secrets.md). Every call here is a checked sample.

### Request

Path:

- `name` (`string`, required): The secret's name: capitals, digits and underscores, starting with a capital.

Query:

- `user_id` (`string`): Whose secret: your id for the user, for their own (read as `${user_secret:NAME}`). Absent: your company's (read as `${secret:NAME}`).

### Answers 200

- `object` (`"secret"`): Always `secret`.
- `name` (`string`): The secret's name. Example: `PNR_API_KEY`.
- `user_id` (`string | null`): Your id for the user whose secret it is; null for your company's.
- `deleted` (`true`): Always true: it is gone.

### Code

**TypeScript**

```ts
await vestra.secrets.delete('TRIPNEST_MCP_TOKEN');
```

**Python**

```python
vestra.secrets.delete("TRIPNEST_MCP_TOKEN")
```

**CLI**

```bash
vestra secrets delete TRIPNEST_MCP_TOKEN
```

**HTTP**

```bash
# Store a company secret. Your MCP servers refer to it as ${secret:TRIPNEST_MCP_TOKEN}.
curl -X PUT "$VESTRA_BASE_URL/v1/secrets/TRIPNEST_MCP_TOKEN" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "value": "your-token" }'

# Store a user's secret. It is for their own account, as ${user_secret:IRCTC_TOKEN}.
curl -X PUT "$VESTRA_BASE_URL/v1/secrets/IRCTC_TOKEN" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_id": "traveller-42", "value": "token-from-their-account" }'

# List and delete secrets. The list shows names only, never values.
curl "$VESTRA_BASE_URL/v1/secrets" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

curl -X DELETE "$VESTRA_BASE_URL/v1/secrets/TRIPNEST_MCP_TOKEN" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**MCP**: No sample in your language for this route, so here it is over HTTP.

## `GET /v1/secrets`

Its own page: [GET /v1/secrets](https://vestra.ai/developers/reference/api/mcp-servers-and-secrets/get-v1-secrets.md)

Secret names, never the values: the company's, or one user's (user_id).

Your API key, scope `setup` or `all`. On the card: [`secrets`](https://vestra.ai/developers/secrets.md). Every call here is a checked sample.

### Request

Query:

- `limit` (`integer`): How many items per page, 1 to 100. Default 20.
- `cursor` (`string`): Where to go on from: the `next_cursor` of the page before, unchanged. Absent: the first page.
- `user_id` (`string`): Whose secret: your id for the user, for their own (read as `${user_secret:NAME}`). Absent: your company's (read as `${secret:NAME}`).

### Answers 200

- `data` (`object[]`): The items on this page.
  - `object` (`"secret"`): Always `secret`.
  - `name` (`string`): The secret's name. Example: `PNR_API_KEY`.
  - `user_id` (`string | null`): Your id for the user whose secret it is; null for your company's.
  - `mode` (`"live" | "test"`): The mode it is kept in: a live key reads live secrets, a test key test ones.
- `next_cursor` (`string | null`): Send it as `cursor` for the next page; null on the last page.

### Code

**TypeScript**

```ts
const secrets = await vestra.secrets.list();
```

**Python**

```python
secrets = vestra.secrets.list()
```

**CLI**

```bash
vestra secrets
```

**HTTP**

```bash
# Store a company secret. Your MCP servers refer to it as ${secret:TRIPNEST_MCP_TOKEN}.
curl -X PUT "$VESTRA_BASE_URL/v1/secrets/TRIPNEST_MCP_TOKEN" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "value": "your-token" }'

# Store a user's secret. It is for their own account, as ${user_secret:IRCTC_TOKEN}.
curl -X PUT "$VESTRA_BASE_URL/v1/secrets/IRCTC_TOKEN" \
  -H "Authorization: Bearer $VESTRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "user_id": "traveller-42", "value": "token-from-their-account" }'

# List and delete secrets. The list shows names only, never values.
curl "$VESTRA_BASE_URL/v1/secrets" \
  -H "Authorization: Bearer $VESTRA_API_KEY"

curl -X DELETE "$VESTRA_BASE_URL/v1/secrets/TRIPNEST_MCP_TOKEN" \
  -H "Authorization: Bearer $VESTRA_API_KEY"
```

**MCP**: No sample in your language for this route, so here it is over HTTP.
