# AI Bot – Bring Your Own MCP Server

**URL:** https://meta.discourse.org/t/ai-bot-bring-your-own-mcp-server/399667
**Category:** Site Management
**Tags:** ai, ai-bot
**Created:** [March 31, 2026, 11:40pm UTC](https://meta.discourse.org/t/ai-bot-bring-your-own-mcp-server/399667 "2026-03-31T23:40:29Z")
**Posts on this page:** 1
**Showing post:** 1

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [March 31, 2026, 11:40pm UTC](https://meta.discourse.org/t/ai-bot-bring-your-own-mcp-server/399667/1 "2026-03-31T23:40:29Z")

</div>

> 🔖 This guide explains how to connect external **Model Context Protocol (MCP)** servers to Discourse AI agents, allowing any MCP-compatible tool provider to be used directly inside the AI bot.
> 
> 🙋 Required user level: **Administrator**

* * *

## What is “Bring Your Own MCP”?

[Model Context Protocol](https://spec.modelcontextprotocol.io/) is an open standard (originally proposed by Anthropic) that lets AI agents communicate with external tool servers over a standard HTTP/JSON-RPC interface. An MCP server exposes a menu of “tools” — functions with typed inputs — that an LLM can call to get things done.

Discourse AI now acts as an **MCP client**. You register any HTTPS MCP server in the admin panel, Discourse discovers its available tools, and those tools become first-class citizens inside any AI agent you choose. There is no JavaScript to write; the tools are defined by the remote server.

This differs from [custom script tools](https://meta.discourse.org/t/ai-bot-custom-tools/314103) which require writing JavaScript that runs inside Discourse. With MCP you bring an already-running external server.

* * *

## Summary

- Register an MCP server (URL + optional auth)
- Discourse auto-discovers and caches the server’s tools
- Assign the server to one or more AI agents
- Optionally limit which tools each agent can use
- Tools are called by the LLM at runtime, routed through Discourse to the external server

* * *

## Registering an MCP Server

Navigate to:

> **Admin → Plugins → Discourse AI → Tools** → **MCP servers** tab → **New MCP server**

Fill in the following fields:

| Field | Description |
| --- | --- |
| **Name** | A human-readable label (shown in the agent editor and logs) |
| **Description** | What this server is for (admin reference) |
| **URL** | The HTTPS endpoint of the MCP server — must be a public HTTPS URL, no private IPs |
| **Authentication** | One of: _No authentication_, _Header credential_, or _OAuth_ |
| **Timeout (seconds)** | How long Discourse waits per request — default 30, maximum 300 |
| **Enabled** | Toggle to temporarily disable the server without deleting it |

> ⚠ URLs must use HTTPS. `localhost` and RFC-1918 private addresses are blocked (SSRF protection is enforced server-side).

* * *

## Authentication Options

### No Authentication

The server is publicly reachable. No credentials are sent.

### Header Credential

A secret value is injected as an HTTP header on every request.

1. First create a **Credential** under Admin → AI → Credentials
2. Select it as the **Credential** on the MCP server form
3. Set the **Auth header** name (default: `Authorization`)
4. Set the optional **Auth scheme** prefix (default: `Bearer`)

The request header sent to the MCP server will be:

```plaintext
Authorization: Bearer <your-secret-value>

```

You can change both the header name and scheme to match any auth style your server requires (e.g., `X-Api-Key: <value>` by leaving scheme blank).

### OAuth

Discourse implements a full OAuth 2.0 + PKCE flow as the MCP client. This supports MCP servers that protect their tools behind OAuth.

**Setup steps:**

1. Set **Authentication** to `OAuth`
2. Choose **Client registration** :
  - **Client metadata document** (default) — Discourse publishes its own OAuth client metadata at `https://your-site.com/discourse-ai/mcp/oauth/client-metadata`. If the MCP server supports [RFC 7591 Dynamic Client Registration](https://www.rfc-editor.org/rfc/rfc7591), Discourse will register automatically
  - **Manual client credentials** — enter your pre-registered **OAuth client ID** and pick an **OAuth client secret** credential

3. Optionally set **OAuth scopes** (space-separated)
4. Save the server
5. Click **Connect** — Discourse will redirect you through the provider’s authorization page
6. After authorizing, you are returned to the admin and the status shows **Connected**

**Advanced OAuth options** (toggle “Show advanced options”):

| Option | Purpose |
| --- | --- |
| OAuth authorization params | JSON object merged into the authorization request (e.g. `{"access_type":"offline"}`) |
| OAuth token params | JSON object merged into token exchange requests |
| Require refresh token | Fail the connection if the provider doesn’t return a refresh token |

Discourse automatically refreshes access tokens before expiry and retries on `401` if a refresh token is available.

* * *

## Testing the Connection

Before assigning a server to an agent, use the **Test connection** button on the editor form. This immediately initializes an MCP session, calls `tools/list`, and returns:

- MCP **protocol version** negotiated
- **Number of tools** discovered
- **Names of all tools**

If the test fails, the error message from the server (or a timeout indicator) is shown inline.

* * *

## How Discourse Discovers Tools

Discourse follows the [MCP specification `2025-03-26`](https://spec.modelcontextprotocol.io/). The connection sequence is:

```plaintext
Discourse → POST / { method: "initialize", params: { protocolVersion, capabilities, clientInfo } }
Server → { result: { protocolVersion, capabilities } } + Mcp-Session-Id header
Discourse → POST / { method: "notifications/initialized" } (session handshake complete)
Discourse → POST / { method: "tools/list", session_id: … }
Server → { result: { tools: [{ name, description, inputSchema } …] } }

```

Tool definitions are **cached for 1 hour** per server. After the cache expires, a background job transparently refreshes it. The cache key is per-site/per-server, so multi-site installs are isolated.

You can also manually trigger a refresh by clicking **Test connection** — this always fetches live data.

The health status of the server (`healthy` / `error`) is updated on every cache refresh.

* * *

## Assigning MCP Servers to AI Agents

Once a server is registered, assign it to an agent:

1. Go to **Admin → Plugins → Discourse AI → Agents** and edit or create an agent
2. Scroll to the **MCP servers** section (below the regular Tools section)
3. The list shows all enabled MCP servers with their tool count and estimated token cost
4. Toggle a server on — it’s now available to that agent

> **Tip:** By default, _all_ tools from a server are made available to the agent. The locale string says it well: _“Selected MCP servers expose all of their tools by default. Narrow them per agent to reduce token usage and keep tool choice focused.”_

* * *

## Selecting Specific Tools per Agent

Having dozens of tools available increases the token cost of every message (each tool definition is sent to the model in the system prompt). To keep things lean:

1. In the agent editor, next to an assigned MCP server, click **Choose tools**
2. A modal shows every tool the server currently exposes, with its description and parameter list
3. Check only the tools this agent needs
4. Click Save — the agent will now only see the selected subset

The `ai_agent_mcp_servers` join table stores `selected_tool_names` as a JSONB array. An empty array means “all tools enabled”.

* * *

## Tool Naming and Conflict Resolution

MCP tool names must be unique within a single agent’s tool list (including the agent’s built-in and custom script tools). Discourse handles conflicts automatically:

- If two different MCP servers expose a tool with the **same name** , Discourse namespaces them: `servername__toolname`
- If a built-in Discourse tool and an MCP tool share a name, the MCP tool is also namespaced
- If there are still collisions after namespacing, a numeric suffix is appended (`_2`, `_3`, …)

The `function_name` used in the LLM call may therefore differ from the raw `tool_name` on the MCP server. This is handled transparently — the MCP tool class always stores the original `tool_name_value` separately and uses it when calling the server.

* * *

## How Tool Calls Work at Runtime

When the LLM decides to use an MCP tool:

1. **Session reuse** : Discourse looks up a cached MCP session ID for this server in the current bot context (`context.mcp_state`). Sessions are created per bot reply chain and reused across tool calls to the same server.
2. **Initialize if needed** : If no session exists, a fresh `initialize` + `notifications/initialized` handshake runs.
3. **Call** : `POST /` with `{ method: "tools/call", params: { name: tool_name, arguments: params } }`
4. **Session expiry** : If the server returns `404` (expired session), Discourse automatically re-initializes and retries once.
5. **Response normalization** : MCP content items of type `text` are concatenated. Non-text items are JSON-serialized. `structuredContent` is pretty-printed JSON.
6. **Error result** : If the server returns `isError: true`, the bot receives an error message rather than a result.

The server response can be plain JSON or an SSE stream — Discourse handles both.

* * *

## Token Cost Display

In the agent editor, each assigned MCP server shows an estimated token count. This is calculated using the OpenAI `cl100k_base` tokenizer applied to each tool’s full JSON signature (name + description + input schema). This is an approximation — actual cost depends on your LLM’s tokenizer.

The token cost breakdown is shown to help you make informed decisions about which servers and tools to assign to a given agent.

* * *

## Troubleshooting

| Symptom | What to check |
| --- | --- |
| **Test connection fails with timeout** | Increase **Timeout (seconds)**. Default is 30. If the server is slow to initialize, try 60–120. |
| **Test succeeds but tools don’t appear in agent** | Make sure the server is **Enabled** and that the agent has it toggled on in its MCP servers section. |
| **OAuth status shows “Needs attention”** | The last OAuth error is shown on the editor form. Common causes: refresh token expired (click Reconnect), server returned unexpected scopes, or the client metadata URL is not accessible from your server. |
| **Tool names look like `myserver__sometool`** | Normal — another tool (built-in or from another server) had the same name. The LLM sees and uses this namespaced name automatically. |
| **Health shows “error” after a period** | The background refresh job couldn’t reach the server. Check `/logs` on your site and verify the MCP server is reachable from Discourse’s host. |
| **Tools stopped working mid-conversation** | Session expiry. Discourse auto-retries once per tool call, but if the server is consistently restarting, shorten its session TTL or investigate the server-side logs. |

For deeper debugging, enable access to AI transcripts via `ai_bot_debugging_allowed_groups` and inspect the full conversation log.

* * *

## Technical Reference

### MCP Protocol Details

| Property | Value |
| --- | --- |
| Protocol version | `2025-03-26` |
| Transport | HTTP POST (JSON-RPC 2.0) |
| SSE support | Yes (for streaming `tools/call` responses) |
| Session management | `Mcp-Session-Id` HTTP header |
| Max response body | 5 MB |
| Client User-Agent | `Discourse AI MCP Client / <version>` |

### JSON Schema Support

Discourse resolves the following JSON Schema constructs in tool `inputSchema` definitions before passing them to the LLM:

- `$ref` — resolved against the root schema’s `$defs` / `definitions`
- `allOf` — merged (properties and required arrays are union-merged)
- `anyOf` / `oneOf` — the first non-`null` variant is used

### Relevant Source Files

- [`plugins/discourse-ai/lib/mcp/client.rb`](https://github.com/discourse/discourse/blob/main/plugins/discourse-ai/lib/mcp/client.rb) — MCP HTTP client (session, call, OAuth retry)
- [`plugins/discourse-ai/lib/mcp/tool_registry.rb`](https://github.com/discourse/discourse/blob/main/plugins/discourse-ai/lib/mcp/tool_registry.rb) — tool caching, conflict resolution
- [`plugins/discourse-ai/lib/agents/tools/mcp.rb`](https://github.com/discourse/discourse/blob/main/plugins/discourse-ai/lib/agents/tools/mcp.rb) — Tool class wrapping MCP calls for the agent runtime
- [`plugins/discourse-ai/app/models/ai_mcp_server.rb`](https://github.com/discourse/discourse/blob/main/plugins/discourse-ai/app/models/ai_mcp_server.rb) — server model, OAuth token management
- [`plugins/discourse-ai/app/models/ai_agent_mcp_server.rb`](https://github.com/discourse/discourse/blob/main/plugins/discourse-ai/app/models/ai_agent_mcp_server.rb) — per-agent tool selection
- [`plugins/discourse-ai/lib/mcp/oauth_flow.rb`](https://github.com/discourse/discourse/blob/main/plugins/discourse-ai/lib/mcp/oauth_flow.rb) — OAuth 2.0 + PKCE flow

* * *

## Related Topics

- [AI Bot – Custom Tools (script-based)](https://meta.discourse.org/t/ai-bot-custom-tools/314103)
- [Discourse AI persona / agent guide](https://meta.discourse.org/t/discourse-ai-persona-guide/306099)
- [Discourse MCP is here! (Discourse as an MCP _server_)](https://meta.discourse.org/t/discourse-mcp-is-here/386983)

> 💡 The Discourse-as-MCP-server feature ([discourse/discourse-mcp](https://github.com/discourse/discourse-mcp)) is the complement to this: it lets external AI clients (Claude Code, Cursor, etc.) read and write your Discourse site. This guide is the inverse — it lets your Discourse AI agents call external MCP servers.

---

_[View the full topic](https://meta.discourse.org/t/ai-bot-bring-your-own-mcp-server/399667)._
