MCP
Use MCP to connect agents to external tools and data.
The Model Context Protocol (MCP) is an open standard for connecting agents to external tools, resources, and prompts. Better Agent includes an MCP client that speaks HTTP and SSE transports.
Two ways to use it:
- Convert remote MCP tools into Better Agent server tools and add them to an agent
- Use the raw client for direct access to tools, resources, and prompts
Add MCP Tools to an Agent
Use createMCPClient() to connect, listTools() to discover tools, and convertMCPTools() to turn them into Better Agent server tools.
import { defineAgent } from "@better-agent/core";
import { createMCPClient, convertMCPTools } from "@better-agent/core/mcp";
import { openai } from "./openai";
const docsAgent = defineAgent({
name: "docs",
model: openai.model("gpt-4o"),
tools: async () => {
const client = await createMCPClient({
transport: { type: "http", url: "https://mcp.context7.com/mcp" },
});
const listed = await client.listTools();
return convertMCPTools(client, listed.tools, { prefix: "docs" });
},
});convertMCPTools takes the MCP client, the tool list from listTools(), and an optional prefix. Each MCP tool becomes a server tool that proxies execution back to the MCP server.
Use prefix to namespace tool names and avoid collisions when combining tools from multiple sources.
Reuse a Client with lazyTools
The example above creates a new client on every run. Use lazyTools() to cache a client across runs and control its lifecycle manually.
import { lazyTools } from "@better-agent/core";
import { createMCPClient, convertMCPTools } from "@better-agent/core/mcp";
export const mcpTools = lazyTools(async () => {
const client = await createMCPClient({
transport: {
type: "http",
url: "https://mcp.context7.com/mcp",
headers: {
"X-API-Version": "2024-01",
},
},
});
const listed = await client.listTools();
return {
tools: convertMCPTools(client, listed.tools, { prefix: "mcp" }),
dispose: async () => {
await client.close();
},
};
});Then pass it directly to an agent.
const docsAgent = defineAgent({
name: "docs",
model: openai.model("gpt-4o"),
tools: mcpTools,
});lazyTools() caches the first successful load, shares concurrent resolves, and retries after failures. Call await mcpTools.dispose() when your app shuts down.
Per-run cleanup does not dispose the cached client. You must call dispose() yourself when the app exits.
Transport Configuration
The client supports two transport types: http and sse.
HTTP
transport: {
type: "http",
url: "https://mcp.example.com/mcp",
headers: { "X-API-Key": "sk-abc123" },
redirect: "follow",
sessionId: "session-xyz",
}SSE
transport: {
type: "sse",
url: "https://mcp.example.com/sse",
headers: { "X-API-Key": "sk-abc123" },
redirect: "follow",
}Both transports accept the same base fields:
| Field | Type | Description |
|---|---|---|
url | string | MCP server URL. Required. |
headers | Record<string, string> | Additional HTTP headers sent with every request. |
redirect | "follow" | "error" | How to handle HTTP redirects. |
sessionId | string | Session id for resumable connections (HTTP only). |
advanced | object | Reconnect options for the inbound SSE channel. |
Reconnect Options
The inbound SSE channel reconnects automatically on disconnect. Configure the backoff behavior with advanced.
transport: {
type: "http",
url: "https://mcp.example.com/mcp",
advanced: {
reconnectInitialDelayMs: 1000,
reconnectMaxDelayMs: 30_000,
reconnectBackoffFactor: 1.5,
reconnectMaxRetries: 2,
},
}Custom Transport
Pass any object that implements the MCPTransport interface (start, send, close) directly as the transport field.
const client = await createMCPClient({
transport: myCustomTransport,
});Client Configuration
const client = await createMCPClient({
transport: { type: "http", url: "https://mcp.example.com/mcp" },
name: "my-app",
version: "1.0.0",
capabilities: {
roots: { listChanged: true },
},
onUncaughtError: (error) => {
console.error("MCP error:", error.message);
},
});| Field | Type | Description |
|---|---|---|
transport | MCPTransportConfig | MCPTransport | Transport config or custom transport instance. Required. |
name | string | Client name sent during the MCP handshake. Defaults to "better-agent-mcp-client". |
version | string | Client version sent during the handshake. Defaults to "1.0.0". |
capabilities | ClientCapabilities | Client capabilities to advertise to the server. |
onUncaughtError | (error: MCPClientError) => void | Callback for errors that occur outside a request, such as SSE channel failures. |
During initialization, the client performs the MCP handshake automatically. It sends initialize with the latest supported protocol version, validates the server's version, and sends notifications/initialized.
Raw Client API
Use the client directly when you need access to resources, prompts, or fine-grained tool control.
Tools
const client = await createMCPClient({
transport: { type: "http", url: "https://mcp.example.com/mcp" },
});
const { tools } = await client.listTools();
const result = await client.callTool({
name: "search",
arguments: { query: "agent frameworks" },
});Resources
const { resources } = await client.listResources();
const doc = await client.readResource({ uri: resources[0].uri });
const { resourceTemplates } = await client.listResourceTemplates();Prompts
const { prompts } = await client.listPrompts();
const result = await client.getPrompt({
name: "summarize",
arguments: { topic: "climate change" },
});Request Options
Every client method accepts RequestOptions for cancellation and timeouts.
const controller = new AbortController();
const { tools } = await client.listTools({
options: {
signal: controller.signal,
timeout: 5000,
},
});| Field | Type | Description |
|---|---|---|
signal | AbortSignal | Abort signal for cancellation. |
timeout | number | Timeout in milliseconds for a single request. |
Close
Always close the client when you're done.
await client.close();Multiple MCP Servers
Combine tools from multiple servers with lazyTools() so the clients are reused across runs and cleaned up explicitly.
import { lazyTools } from "@better-agent/core";
import { createMCPClient, convertMCPTools } from "@better-agent/core/mcp";
export const multiMCPTools = lazyTools(async () => {
const docsClient = await createMCPClient({
transport: { type: "http", url: "https://docs.example.com/mcp" },
});
const analyticsClient = await createMCPClient({
transport: { type: "http", url: "https://analytics.example.com/mcp" },
});
const docsListed = await docsClient.listTools();
const analyticsListed = await analyticsClient.listTools();
return {
tools: [
...convertMCPTools(docsClient, docsListed.tools, { prefix: "docs" }),
...convertMCPTools(analyticsClient, analyticsListed.tools, { prefix: "analytics" }),
],
dispose: async () => {
await Promise.all([docsClient.close(), analyticsClient.close()]);
},
};
});const multiAgent = defineAgent({
name: "multi",
model: openai.model("gpt-4o"),
tools: multiMCPTools,
});Error Handling
MCP operations throw MCPClientError on failure.
import { MCPClientError } from "@better-agent/core/mcp";
try {
const client = await createMCPClient({
transport: { type: "http", url: "https://mcp.example.com/mcp" },
});
const { tools } = await client.listTools();
} catch (error) {
if (error instanceof MCPClientError) {
console.error("MCP failed:", error.message);
}
}Common failure modes:
- Server unavailable: transport cannot connect or server returns an error status
- Capability not supported: calling
listTools()when the server doesn't advertise tools capability - Protocol version mismatch: server uses an unsupported MCP protocol version
- Timeout: request exceeds the configured timeout
- Connection closed: server or network dropped the connection mid-request