# MCP tools

CKEditor AI can call tools on external MCP servers, such as the servers published by your wiki, your ticketing system, or your product catalog. Connect one, and the model looks up current information in that system, or acts in it, during a conversation.

This page covers the servers you define in the service configuration. You give each server a URL, a way to authenticate, and, if you want, a list of the tools the model must not call. For what users see in the editor, and for registering a server through the admin REST API instead, see [MCP tools](../../guides/ckeditor-ai/extensions/mcp-tools.md).

A connected server offers **tools** and **resources**. CKEditor AI calls the tools during a request. An administrator attaches the resources to a context as files. One connection serves both.

> **Note**
>
> The two sets of server names share one namespace, and the service configuration has priority. The REST API rejects a name that the configuration already uses. If the configuration later takes a name that the API added first, the configuration entry replaces the API entry.

<a id="two-ways-to-connect-a-server">

## Two ways to connect a server

* **Admin API** – an administrator registers a server for one environment at runtime, with no redeploy. The endpoints are on the [MCP tools](../../guides/ckeditor-ai/extensions/mcp-tools.md) guide page.
* **Service configuration** – the `mcp_servers` object described on this page. It applies to every environment, or to the ones you list, and changing it means redeploying.

Chat and Document Processing use servers connected either way. Reviews and actions do not use MCP tools. Document Processing runs with no signed-in user, so it skips servers that need OAuth.

<a id="configuration">

## Configuration

Each server is an entry in the `mcp_servers` object, keyed by its name. The key is the name you use for the server afterwards, and it prefixes the names of the server’s tools. Only `url` is required:

* `url` (required) – the URL of the MCP endpoint of the server. The service opens the connection over the [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http), so this URL must be reachable from the network your containers run in.

<a id="basic-setup">

### Basic setup

**JSON**

```json
{
	"mcp_servers": {
		"<MCP SERVER NAME>": {
			"url": "<MCP SERVER URL>"
		}
	}
}
```

**Environment variable**

Pass the same object as a JSON string in the `MCP_SERVERS` variable:

```bash
docker run --init -p 8000:8000 \
	# ... other environment variables ...
	-e MCP_SERVERS='{"<MCP SERVER NAME>":{"url":"<MCP SERVER URL>"}}' \
	docker.cke-cs.com/ai-service:[version]
```

The same configuration split over several lines with a shell variable:

```bash
export MCP_SERVERS='{
	"<MCP SERVER NAME>": {
		"url": "<MCP SERVER URL>"
	}
}'

docker run --init -p 8000:8000 \
	# ... other environment variables ...
	-e MCP_SERVERS="$MCP_SERVERS" \
	docker.cke-cs.com/ai-service:[version]
```

<a id="http-headers">

### HTTP headers

Use `headers` to send extra HTTP headers, such as an authorization token, with every request to the MCP server:

**JSON**

```json
{
	"mcp_servers": {
		"<MCP SERVER NAME>": {
			"url": "<MCP SERVER URL>",
			"headers": {
				"Authorization": "Bearer QUkgc2VydmljZUFJIHNlcnZpY2VBSSBzZXJ2aWNlQUkgc2VydmljZQ==",
				"<HEADER NAME>": "<HEADER VALUE>"
			}
		}
	}
}
```

**Environment variable**

```bash
MCP_SERVERS='{"<MCP SERVER NAME>":{"url":"<MCP SERVER URL>","headers":{"Authorization":"Bearer QUkgc2VydmljZUFJIHNlcnZpY2VBSSBzZXJ2aWNlQUkgc2VydmljZQ==","<HEADER NAME>":"<HEADER VALUE>"}}}'
```

<a id="tools">

### Tools

To stop the model from calling a tool, add the name of the tool to the `tools.disabled` array:

**JSON**

```json
{
	"mcp_servers": {
		"<MCP SERVER NAME>": {
			"url": "<MCP SERVER URL>",
			"headers": {
				"Authorization": "Bearer QUkgc2VydmljZUFJIHNlcnZpY2VBSSBzZXJ2aWNlQUkgc2VydmljZQ==",
				"<HEADER NAME>": "<HEADER VALUE>"
			},
			"tools": {
				"disabled": ["<NAME OF THE TOOL TO DISABLE>"]
			}
		}
	}
}
```

**Environment variable**

```bash
MCP_SERVERS='{"<MCP SERVER NAME>":{"url":"<MCP SERVER URL>","headers":{"Authorization":"Bearer ...","<HEADER NAME>":"<HEADER VALUE>"},"tools":{"disabled":["<NAME OF THE TOOL TO DISABLE>"]}}}'
```

<a id="additional-options">

### Additional options

The `options` object holds settings for the MCP client:

* `callToolTimeout` (optional, default: `60`) – the timeout for a single tool call, in seconds.

**JSON**

```json
{
	"mcp_servers": {
		"<MCP SERVER NAME>": {
			"url": "<MCP SERVER URL>",
			"options": {
			   "callToolTimeout": 300
			}
		}
	}
}
```

**Environment variable**

```bash
MCP_SERVERS='{"<MCP SERVER NAME>":{"url":"<MCP SERVER URL>","options":{"callToolTimeout":300}}}'
```

<a id="environment-allowlist">

### Environment allowlist

By default, every environment can use a server you configure here. Use `allowedEnvironments` to limit it to the environments you list:

**JSON**

```json
{
	"mcp_servers": {
		"<MCP SERVER NAME>": {
			"url": "<MCP SERVER URL>",
			"allowedEnvironments": ["<ENVIRONMENT ID>"]
		}
	}
}
```

**Environment variable**

```bash
MCP_SERVERS='{"<MCP SERVER NAME>":{"url":"<MCP SERVER URL>","allowedEnvironments":["<ENVIRONMENT ID>"]}}'
```

* `allowedEnvironments` (optional) – the IDs of the environments that may use this server. IDs are matched exactly, and there is no wildcard value.

The service does not offer the tools of the server in an environment that is not on the list. A request from that environment behaves as if you had not configured the server.

<a id="connection-pooling">

### Connection pooling

The service keeps MCP connections open and reuses them for later tool calls. A server authenticated with static [HTTP headers](#http-headers) uses one shared connection. An [OAuth-enabled](#oauth-authentication) server holds one connection per user and environment, because every user connects with their own token. The `pool` object limits those connections per server:

| Option                | Default                | Description                                                                                                                                                                                                                                                                                                                                     |
| --------------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `maxClients`          | `10`                   | The maximum number of open connections for this server. At the limit, the service closes the connection that was idle the longest. If every connection is busy, the request fails and does not wait.                                                                                                                                            |
| `maxIdleTime`         | `300000` (5 minutes)   | How long an unused connection is kept, in milliseconds.                                                                                                                                                                                                                                                                                         |
| `maxLifetime`         | `1800000` (30 minutes) | The maximum age of a connection, in milliseconds. An older connection is closed once it is no longer in use, and the next request opens a new one.                                                                                                                                                                                              |
| `healthCheckInterval` | `10000`                | How often the service checks the pooled connections, in milliseconds. The check pings the idle connections. It closes a connection that does not respond, and a connection that reached `maxIdleTime` or `maxLifetime`. To stop the check, set `healthCheckInterval` to `0`. Without the check, `maxIdleTime` and `maxLifetime` have no effect. |

All four options are optional. This example sets them together:

**JSON**

```json
{
	"mcp_servers": {
		"<MCP SERVER NAME>": {
			"url": "<MCP SERVER URL>",
			"pool": {
				"maxClients": 25,
				"maxIdleTime": 600000,
				"maxLifetime": 1800000,
				"healthCheckInterval": 10000
			}
		}
	}
}
```

**Environment variable**

```bash
MCP_SERVERS='{"<MCP SERVER NAME>":{"url":"<MCP SERVER URL>","pool":{"maxClients":25,"maxIdleTime":600000,"maxLifetime":1800000,"healthCheckInterval":10000}}}'
```

> **Note**
>
> The `pool` values are in milliseconds. `options.callToolTimeout` is in seconds.

<a id="oauth-authentication">

## OAuth authentication

Some MCP servers require OAuth 2.0 authorization and do not accept a static token in the [`Authorization` header](#http-headers). For those, configure CKEditor AI as an OAuth client. It implements the [OAuth 2.0 Authorization Code flow with PKCE](https://datatracker.ietf.org/doc/html/rfc7636) and supports [Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591) (RFC 7591) for servers that allow it.

An OAuth server works in chat only. Each user authorizes the connection in their own browser, and until they do, the service does not offer that server’s tools to them. Document Processing runs with no signed-in user, so it uses only servers that authenticate with a shared header or no credential.

The service stores the access token and sends it on that user’s later requests. When the token expires, the service refreshes it.

<a id="configuration-2">

### Configuration

To enable OAuth for an MCP server, make two changes to the configuration:

1. A top-level `mcp_oauth_callback_url` – the URL of the OAuth callback page in your application. Unless the `oauth.callbackUrl` of a server overrides this URL, CKEditor AI sends it as the `redirect_uri` for every OAuth-enabled MCP server.
2. An `oauth` block in the server’s entry under `mcp_servers`.

**JSON**

```json
{
	"mcp_oauth_callback_url": "https://your-app.example.com/oauth/callback",
	"mcp_servers": {
		"<MCP SERVER NAME>": {
			"url": "<MCP SERVER URL>",
			"oauth": {
				"clientId": "<OAUTH CLIENT ID>",
				"clientSecret": "<OAUTH CLIENT SECRET>",
				"scopes": ["<OAUTH SCOPE>"]
			}
		}
	}
}
```

**Environment variable**

```bash
MCP_SERVERS='{"<MCP SERVER NAME>":{"url":"<MCP SERVER URL>","oauth":{"clientId":"<OAUTH CLIENT ID>","clientSecret":"<OAUTH CLIENT SECRET>","scopes":["<OAUTH SCOPE>"]}}}'
```

All fields inside `oauth` are optional, because `mcp_oauth_callback_url` supplies the callback URL:

* `clientId` – OAuth 2.0 client identifier issued by the MCP server. Omit to perform [Dynamic Client Registration](#dynamic-client-registration).
* `clientSecret` – OAuth 2.0 client secret. Used together with `clientId` for confidential clients.
* `scopes` – the OAuth scopes to request from the authorization server. Omit to use the server’s defaults.
* `callbackUrl` – per-server override of the top-level `mcp_oauth_callback_url`. Use it when servers need different callback pages in your application.

<a id="dynamic-client-registration">

### Dynamic Client Registration

If the MCP server supports [RFC 7591 Dynamic Client Registration](https://datatracker.ietf.org/doc/html/rfc7591), omit `clientId` and `clientSecret` from the `oauth` block. CKEditor AI registers itself with the server on the first authorization attempt and reuses that registration afterwards.

<a id="authorization-flow">

### Authorization flow

Your application drives the OAuth flow. The service exposes the REST endpoints listed below, and your application handles the redirects and forwards the authorization code.

1. **Initialize.** Your application calls `POST /v1/mcp/oauth/{serverName}/initialize` for the signed-in user. The response is one of:

   * `{ "connected": true }` – the user already has a valid token, and nothing more is needed.
   * `{ "connected": false, "authorizationUrl": "..." }` – redirect the user’s browser to `authorizationUrl`.

2. **User consents.** The MCP server’s authorization page opens in the user’s browser. After the user approves, the server redirects the browser to the callback URL, `mcp_oauth_callback_url` or the server’s `oauth.callbackUrl`, with `code` and `state` query parameters.

3. **Complete.** Your callback page reads `code` and `state` from the query string and forwards them to `POST /v1/mcp/oauth/{serverName}/complete` with body `{ "code": "...", "state": "..." }`. On success, the connection is active, and the user’s later conversations can use the server’s tools.

CKEditor AI does not host the callback page. Your application does. In the developer console of the MCP server, register `mcp_oauth_callback_url`, or each per-server `oauth.callbackUrl`, as an authorized redirect URI. With Dynamic Client Registration, this happens automatically.

> **Note**
>
> The authorization session expires ten minutes after `initialize`. If the user takes longer, `complete` returns a `mcp-oauth-invalid-state` error, and the flow starts again from `initialize`.

<a id="rest-endpoints">

### REST endpoints

The endpoints below take the same token as the other endpoints of the service. The token identifies the user, and each user has a separate connection state for each MCP server.

| Method   | Path                                    | Description                                                                                                                                    |
| -------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET`    | `/v1/mcp/oauth/status`                  | Returns the connection status for every OAuth-enabled MCP server, with `connected` and an optional `expiresAt` timestamp for the calling user. |
| `POST`   | `/v1/mcp/oauth/{serverName}/initialize` | Starts the OAuth flow. Returns either `{ "connected": true }` or `{ "connected": false, "authorizationUrl": "..." }`.                          |
| `POST`   | `/v1/mcp/oauth/{serverName}/complete`   | Finishes the OAuth flow. Body: `{ "code": "...", "state": "..." }`. On success returns `{ "connected": true }`.                                |
| `DELETE` | `/v1/mcp/oauth/{serverName}`            | Removes the stored token (and any in-progress authorization state) for the calling user.                                                       |

The endpoints work after you configure OAuth for at least one MCP server.

<a id="connection-scope-and-revocation">

### Connection scope and revocation

An OAuth connection belongs to one user in one environment.

To revoke the calling user’s connection, send `DELETE /v1/mcp/oauth/{serverName}`.

<a id="usage">

## Usage

After you configure a server, the service connects to it and reads its tool list. The tools are then available in chat and in [Document Processing](../../guides/ckeditor-ai/document-processing.md), where the model decides when to call them. Reviews and actions do not use MCP tools.

For an OAuth-enabled server, the connection opens only after the user completes the [authorization flow](#authorization-flow). Until then, the service does not offer the server’s tools to that user. Document Processing runs with no user present to authorize, so it uses only servers that authenticate with a shared credential.

The service does not offer the server’s resources to the model as tools. An administrator attaches the server to a context. The documents of the server then become files of that context, and the model reads them there. See [Load files from an MCP server](../../guides/ckeditor-ai/extensions/context-library.md#load-files-from-an-mcp-server).

---

Full index of the Cloud Services documentation: [llms.txt](../../../llms.txt)
