| title | description | type | prerequisites | |
|---|---|---|---|---|
MCP (Model Context Protocol) |
Discover and invoke tools from remote MCP servers directly from your chat bot handlers. |
guide |
|
Chat SDK can connect to remote MCP servers to discover and invoke tools. This lets your bot call APIs like Sentry, Linear, Notion, and Figma through a standard protocol.
The MCP SDK is an optional peer dependency:
pnpm add @modelcontextprotocol/sdkPass mcpServers when creating your Chat instance:
import { Chat } from "chat";
const bot = new Chat({
userName: "mybot",
adapters: { slack },
state,
mcpServers: [
{
name: "sentry",
transport: {
type: "http",
url: "https://mcp.sentry.dev/mcp",
headers: {
Authorization: `Bearer ${process.env.SENTRY_AUTH_TOKEN}`,
},
},
},
],
});MCP servers are connected lazily on the first webhook request.
Discover all tools available across connected servers:
const tools = await bot.mcp.listTools();
for (const tool of tools) {
console.log(`${tool.serverName}/${tool.name}: ${tool.description}`);
}Each tool includes name, description, inputSchema (JSON Schema), and serverName.
Invoke a tool by name from any handler:
bot.onNewMention(async (thread, message) => {
const result = await bot.mcp.callTool("search_issues", {
query: message.text,
});
const text = result.content
.filter((c) => c.type === "text")
.map((c) => c.text)
.join("\n");
await thread.post(text);
});| Type | Config value | Description |
|---|---|---|
| Streamable HTTP | "http" |
Default. Modern bidirectional transport over HTTP. |
| Server-Sent Events | "sse" |
Legacy SSE-based transport. Use if the server doesn't support Streamable HTTP. |
The bot uses a shared API key for all requests. Configure headers directly in the transport:
mcpServers: [
{
name: "sentry",
transport: {
type: "http",
url: "https://mcp.sentry.dev/mcp",
headers: { Authorization: `Bearer ${process.env.SENTRY_AUTH_TOKEN}` },
},
},
];Look up the tenant's token at call time and pass it via the headers option. A temporary connection is created with those headers for the duration of the call:
bot.onNewMention(async (thread, message) => {
const tenantToken = await lookupTenantToken(thread.id);
const result = await bot.mcp.callTool(
"search_issues",
{ query: message.text },
{ headers: { Authorization: `Bearer ${tenantToken}` } }
);
await thread.post(result.content[0].text);
});Same pattern — resolve the user's OAuth token and pass it per-call:
bot.onNewMention(async (thread, message) => {
const userToken = await getUserOAuthToken(message.author.id);
const result = await bot.mcp.callTool(
"create_issue",
{ title: message.text },
{ headers: { Authorization: `Bearer ${userToken}` } }
);
await thread.post(`Created: ${result.content[0].text}`);
});Tools from all configured servers are merged. The SDK routes each callTool to the correct server automatically:
mcpServers: [
{
name: "sentry",
transport: { type: "http", url: "https://mcp.sentry.dev/mcp" },
},
{
name: "linear",
transport: {
type: "http",
url: "https://mcp.linear.app/mcp",
headers: { Authorization: `Bearer ${process.env.LINEAR_API_KEY}` },
},
},
];If a server's tool list changes at runtime, re-fetch it:
await bot.mcp.refresh();If a server fails to connect during initialization, a warning is logged and the remaining servers continue normally. Your bot won't crash because one MCP server is down.
| Code | When |
|---|---|
MCP_TOOL_NOT_FOUND |
callTool is called with a tool name that doesn't exist on any connected server. |
MCP_NOT_CONFIGURED |
callTool is called but no mcpServers were configured. |
MCP_SDK_NOT_INSTALLED |
@modelcontextprotocol/sdk is not installed. |