> ## Documentation Index
> Fetch the complete documentation index at: https://rasa.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Servers

> Rasa supports native integration of MCP servers.

<Warning>
  **New Beta Feature in 3.14**

  Rasa supports native integration of MCP servers.

  This feature is in a beta (experimental) stage and may change in future Rasa versions.
  We welcome your feedback on this feature.
</Warning>

MCP servers allow your Rasa agent to connect to external services and APIs through the [Model Context Protocol](https://modelcontextprotocol.io/).
These servers expose tools that your agent can use [directly in flows](/docs/reference/primitives/flow-steps#calling-an-mcp-tool) or provide to [autonomous sub agents](/docs/reference/config/agents/overview-agents#how-to-use-sub-agents) for dynamic decision-making.

## Basic Configuration

MCP servers are configured in your `endpoints.yml` file.

```yaml theme={null}
mcp_servers:
  - name: trade_server
    url: http://localhost:8080
    type: http
```

The following fields are required:

* **`name`**: A unique identifier for the MCP server
* **`url`**: The URL where the MCP server is running
* **`type`**: The server type (currently supports `http` and `https`)

## Multiple MCP Servers

You can configure multiple MCP servers to connect to different services:

```yaml theme={null}
mcp_servers:
  - name: inventory_server
    url: http://localhost:8080
    type: http
  - name: payment_server
    url: https://api.payment-service.com
    type: https
  - name: customer_database
    url: http://localhost:9000
    type: http
```

Each server can expose different tools and be used independently in your flows or by different sub agents.
Server names must be unique across all MCP servers. If you attempt to configure multiple servers with the same name, Rasa will raise a validation error during startup.

## Authentication

MCP servers support multiple authentication methods for connecting to external services:

* **API Key**: Static key attached as `Authorization: Bearer <token>`
* **OAuth 2.0 (Client Credentials)**: Automatic token retrieval with client ID/secret
* **Pre-issued Token**: Direct token usage until expiry

Authentication settings are specified using additional parameters in your MCP server configuration.

<Tabs>
  <Tab title="API Key">
    ```yaml theme={null}
    mcp_servers:
      - name: secure_api_server
        url: https://api.example.com
        type: https
        api_key: "${API_KEY}"
    ```
  </Tab>

  <Tab title="API Key (Custom Header)">
    ```yaml theme={null}
    mcp_servers:
      - name: custom_header_server
        url: https://api.example.com
        type: https
        api_key: "${API_KEY}"
        header_name: "X-API-Key"
        header_format: "{key}"
    ```
  </Tab>

  <Tab title="OAuth 2.0">
    ```yaml theme={null}
    mcp_servers:
      - name: oauth_server
        url: https://api.example.com
        type: https
        oauth:
          client_id: "${CLIENT_ID}"
          client_secret: "${CLIENT_SECRET}"
          token_url: "https://auth.example.com/oauth/token"
          scope: "read:data write:data"
    ```
  </Tab>

  <Tab title="Pre-issued Token">
    ```yaml theme={null}
    mcp_servers:
      - name: token_server
        url: https://api.example.com
        type: https
        token: "${ACCESS_TOKEN}"
    ```
  </Tab>
</Tabs>

The `$` syntax is **required** for the following sensitive parameters:

* `api_key`
* `token`
* `client_secret`

This ensures that they are not stored in plain text in your configuration files.
For other parameters like `client_id`, using the `$` syntax is optional —
you can either reference an environment variable using the `$` syntax or provide the value directly in the configuration.

## Advanced Configuration

### Custom Headers

You can specify custom headers for API key authentication:

```yaml theme={null}
mcp_servers:
  - name: custom_auth_server
    url: https://api.example.com
    type: https
    api_key: "${API_KEY}"
    header_name: "X-Custom-Auth"
    header_format: "Bearer {key}"
```

### OAuth 2.0 Scopes

For OAuth 2.0 authentication, you can optionally pass in scope, audience, and timeout:

```yaml theme={null}
mcp_servers:
  - name: oauth_server_with_scopes
    url: https://api.example.com
    type: https
    oauth:
      client_id: "${CLIENT_ID}"
      client_secret: "${CLIENT_SECRET}"
      token_url: "https://auth.example.com/oauth/token"
      scope: "read:users write:orders admin:settings" # Optional: scopes for the OAuth 2.0 token
      audience: "https://api.example.com" # Optional: audience for the OAuth 2.0 token
      timeout: 10 # Optional: timeout for the OAuth 2.0 token
```

## Complete Example

Here's a comprehensive example showing multiple MCP servers with different authentication methods:

```yaml theme={null}
mcp_servers:
  # Public API with API key
  - name: weather_service
    url: https://api.weather.com
    type: https
    api_key: "${WEATHER_API_KEY}"
  
  # Internal service with OAuth
  - name: internal_database
    url: https://db.internal.com
    type: https
    oauth:
      client_id: "${DB_CLIENT_ID}"
      client_secret: "${DB_CLIENT_SECRET}"
      token_url: "https://auth.internal.com/oauth/token"
      scope: "database:read database:write"
  
  # Local development server
  - name: local_tools
    url: http://localhost:8080
    type: http
  
  # Service with custom header authentication
  - name: custom_auth_server
    url: https://api.example.com
    type: https
    api_key: "${API_KEY}"
    header_name: "X-API-Key"
    header_format: "{key}"
```

## Call-time credential hooks (`pre_call_hook`)

Call-time credential hooks resolve **per-conversation secrets** only when an outbound MCP tool call is about to be made. Rasa invokes your hook, merges the returned metadata into MCP `_meta`, and never writes secret values to slots, the tracker, Kafka, or the Inspector.

Use `pre_call_hook` when credentials are:

* **Per user or per conversation** (keyed by `sender_id`)
* **Stored encrypted** in your own vault or secret manager
* **Required on every tool call** to a specific MCP server

The hook applies to:

* **Direct MCP tool calls in Flows** — `call` steps that reference `mcp_server` in a flow (see [Calling an MCP Tool](/docs/reference/primitives/flow-steps#calling-an-mcp-tool))
* **ReAct external MCP tools** — tools from MCP servers listed in the sub agent's `connections` section

Flows and sub agents reference MCP servers by **name only**. There is no `pre_call_hook` on individual flow steps or per sub-agent connection entries — configure the hook in each MCP server entry in `endpoints.yml`.

```yaml title="endpoints.yml" theme={null}
mcp_servers:
  - name: appointment_booking
    url: http://appointment-booking:8000/mcp/
    type: http
    pre_call_hook: custom.call_time_credentials.resolve_appointment_booking_meta
    meta_map:
      static:
        api_version: "v2"
```

When both `meta_map` and `pre_call_hook` are set, static `meta_map` is merged first; hook `metadata` wins on key collision.

### Hook contract

Add `pre_call_hook` as a dotted import path to a module-level sync or async function. Use `async def` when the hook performs I/O (for example fetching from a secret store). Import types from `rasa.shared.agents.outbound_call_hook`:

```python theme={null}
from rasa.shared.agents.outbound_call_hook import (
    MCPOutboundCallContext,
    OutboundCallResult,
)
```

Rasa calls the hook as `hook(context)` and passes a frozen, read-only `MCPOutboundCallContext`:

| Field                | Description                                                              |
| -------------------- | ------------------------------------------------------------------------ |
| `sender_id`          | Conversation id — use as your secret-store lookup key                    |
| `tool_name`          | Tool being called (flow `call` step, MCP tool name, or custom tool name) |
| `server_name`        | MCP server name for flow / ReAct MCP calls; `None` for custom tools      |
| `flow_id`, `step_id` | Set for direct MCP tool calls in flows; otherwise `None`                 |
| `agent_id`           | Sub-agent id for ReAct MCP calls; otherwise `None`                       |

The hook must return an `OutboundCallResult` with a `metadata` dict. `OutboundCallResult` has a single `metadata` field; Rasa merges it onto the wire. For convenience, a plain `dict` is accepted and normalized to `OutboundCallResult(metadata=...)`. Never put secrets in flow `mapping.input` or tool argument mappings.

```python title="custom/call_time_credentials.py" theme={null}
async def resolve_appointment_booking_meta(
    context: MCPOutboundCallContext,
) -> OutboundCallResult:
    creds = await decrypt_credentials(context.sender_id)
    return OutboundCallResult(
        metadata={
            "x-customer-id": creds["customer_id"],
            "authorization": f"Bearer {creds['dealer_api_token']}",
        }
    )
```

<Tip>
  **AWS Secrets Manager or similar**

  Replace `decrypt_credentials` with your vault client. Use `context.sender_id` as the stable lookup key your connector assigns to each conversation. Fetch and decrypt inside the hook only — do not copy secrets into domain slots.
</Tip>

If a hook raises an exception or returns an invalid type, Rasa aborts the outbound call. For flow MCP, that routes to [`pattern_internal_error`](/docs/reference/primitives/patterns#handling-agent-and-mcp-tool-failures). For ReAct MCP tools, Rasa returns an error tool result without calling the remote endpoint. Rasa logs metadata **key names** at debug level, never values.

For ReAct **custom Python tools** (not MCP servers), override `build_custom_tool_call_metadata()` on your `MCPOpenAgent` or `MCPTaskAgent` subclass instead. See [Call-time credentials for custom tools](/docs/reference/config/agents/react-sub-agents#call-time-credentials-for-custom-tools).

## Passing metadata to tools (`meta_map`)

Remote MCP tool calls can include a protocol-level `_meta` object (when supported by your MCP client SDK) so the server receives context that does not appear in the tool arguments the LLM fills. In Rasa, this is configured per server with optional `meta_map` in `endpoints.yml`:

* **`static`**: Fixed string key-value pairs always merged into `_meta` (for example API version or environment labels). Use for **non-secret** context.
* **`from_slots`**: A list of `{ slot, param }` entries. For each entry, the current value of the named slot (from the tracker / agent input) is sent as `_meta[param]`. Prefer [`pre_call_hook`](/docs/reference/integrations/mcp-servers#call-time-credential-hooks-pre_call_hook) instead of slots when the value is a secret.

When both `meta_map` and `pre_call_hook` are set, static `meta_map` is merged first; hook `metadata` wins on key collision.

```yaml theme={null}
mcp_servers:
  - name: internal_api
    url: http://internal-api:8000/mcp/
    meta_map:
      from_slots:
        - slot: user_id
          param: user_id
        - slot: role
          param: user_role
      static:
        api_version: "v2"
        source: "rasa_agent"
```

If `meta_map` is omitted or has no `from_slots` entries, Rasa still sends any `static` entries. Slot values are read from the same agent input used for the ReAct turn; ensure the slot is set before the tool runs if you map it in `from_slots`.

Older MCP Python SDK releases that do not support a `meta` parameter on `call_tool` are still supported: Rasa detects this and falls back to calling without metadata so tools keep working; once the SDK supports `meta`, metadata is sent automatically.

For tools implemented in Python inside your ReAct sub-agent (not via an MCP server), use [custom tool executors](/docs/reference/config/agents/react-sub-agents#adding-custom-tools) and override `build_custom_tool_call_metadata()` to attach call-time credentials to `AgentToolContext.metadata`. See [Call-time credentials for custom tools](/docs/reference/config/agents/react-sub-agents#call-time-credentials-for-custom-tools).

### Reading metadata on the MCP server

On the wire, Rasa sends the resolved `meta_map` object as JSON-RPC `params._meta` on each `tools/call` request. Keys are exactly the `param` names defined in from `from_slots` and the those specified under `static` (for example `user_id`, `user_role`, `api_version`, `source` in the sample above).

With the [official MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk), that payload is available as `CallToolRequest.params.meta` (the field is serialized as `_meta`). If you build the server with **FastMCP**, inject `Context` and read the same values from the request metadata object (unknown keys are allowed on `meta` alongside standard fields like `progressToken`):

```python theme={null}
def get_request_meta(ctx: Context | None) -> dict:
    """Request _meta as a dict. Safe to call with None (returns {})."""
    if ctx is None:
        return {}
    meta = getattr(ctx.request_context, "meta", None)
    if meta is None:
        return {}
    return meta.model_dump(exclude_none=True)

@mcp.tool()
def sum(a: int, b: int, context: Context | None = None) -> str:
    meta_dict = get_request_meta(context)
    api_version = meta_dict.get("api_version", "v1")
    ...
```

If you use the low-level `Server` API and a custom `tools/call` handler, read the same fields from the incoming `CallToolRequest` (for example `req.params.meta`) before invoking your tool implementation.

**Privacy and observability:** Rasa does not attach this metadata to LLM tracing fields. At debug log level, only the **keys** of the resolved metadata are logged, not values.

## Validation

Rasa validates MCP server configurations to ensure:

* Server type is either `http` or `https`
* Name and URL are not empty
* Sensitive parameters (API keys, tokens, secrets) are properly referenced using environment variables
* OAuth configuration includes all required fields
* If `meta_map.from_slots` is set, every referenced `slot` exists in the domain (training / `rasa train` validation)
* If `pre_call_hook` is set, the import path resolves to a callable (training / `rasa train` validation)

If validation fails, Rasa will provide specific error messages to help you fix the configuration.


## Related topics

- [Integrating an MCP Server](/docs/pro/build/mcp-integration.md)
- [Rasa MCP Tools](/docs/pro/installation/rasa-mcp-tools.md)
- [Rasa MCP Tools API Reference](/docs/reference/api/rasa-mcp-tools.md)
- [ReAct Sub Agent](/docs/reference/config/agents/react-sub-agents.md)
- [Flow Steps](/docs/reference/primitives/flow-steps.md)
