LangChain.js MCP Adapters: Connect Agents to MCP Servers
Introduction
To connect a LangChain.js agent to a Model Context Protocol (MCP) server from JavaScript or TypeScript, you use the @langchain/mcp-adapters package.
Its MCPAdapter class starts or connects to each MCP server, and its listTools() method turns the server's tools into LangChain tools that you pass to createAgent.
The agent's Large Language Model (LLM) can then call those tools to read real-time data and take actions in response to the user's requests.
In this post, I'll show you how to connect a JavaScript application to an MCP server using LangChain.js, over both the stdio and Streamable HTTP transports, and how to build an agent that interacts with a Kubernetes cluster using a local model.
I tested every step in October 2026 with langchain 1.5.16, @langchain/mcp-adapters 2.0.1, @langchain/ollama 1.3.0, the Kubernetes MCP Server v0.0.67, and the qwen3:4b-instruct model running on Ollama, so you can run every example without an API key.
If your code uses MultiServerMCPClient from version 1.x, see what changed in 2.0.
What you need
To follow along, you need:
- Node.js 22 or later (
@langchain/mcp-adapters2.x itself needs 20.10 or later, but Node.js 20 is no longer supported). - Ollama with a model that supports tool calling.
I used
qwen3:4b-instruct(about 2.5 GB), which you can download withollama pull qwen3:4b-instruct. Make sure the Ollama server is running (the desktop app starts it; otherwise runollama serve). - Access to a Kubernetes cluster through your kubeconfig file. Any local cluster, like the ones kind or minikube create, works.
Create a new project and install the packages:
npm init -y
npm install langchain @langchain/core @langchain/langgraph @langchain/mcp-adapters @langchain/ollama@langchain/core and @langchain/langgraph are peer dependencies of the adapters, and @langchain/ollama provides the chat model for Ollama.
Configuring the MCP servers
Before you can use MCP tools with LangChain.js, you need MCP clients that can communicate with MCP servers.
With @langchain/mcp-adapters 2.x, you don't create those clients yourself: you describe each server in an MCPAdapter, and the adapter creates and manages the clients for you.
It picks the transport from each server's configuration: a command means stdio, and a url means Streamable HTTP.
Connecting to a local server over stdio
The stdio transport is ideal for command-line tools and direct integrations. It allows communication with an MCP server running as a subprocess of your application. This approach is particularly useful for local execution, which is convenient for agents that perform tasks on the same machine where the server is running.
The following code snippet configures an MCPAdapter with a single stdio server:
import {MCPAdapter} from '@langchain/mcp-adapters';
const adapter = new MCPAdapter({
servers: {
kubernetes: {
command: 'npx',
args: ['-y', 'kubernetes-mcp-server@latest']
}
}
});In this example, I'm creating an adapter with one server named kubernetes that runs the Kubernetes MCP Server package using npx.
The adapter spawns the command as a child process and communicates with it through its standard input and output streams.
These are the key components of the stdio server configuration:
- The key in the
serversmap (kubernetes) is the server name. The adapter uses it to identify the server and, by default, as a prefix for its tool names. command: The command to execute the MCP server.args: The array of arguments to pass to the command.
The adapter doesn't connect when you create it; it connects the first time you ask it for tools.
Note
The stdio transport doesn't pass your whole environment to the server process.
On Linux and macOS it only passes HOME, LOGNAME, PATH, SHELL, TERM and USER (Windows has its own list).
If you select your cluster with the KUBECONFIG environment variable, add env: {KUBECONFIG: process.env.KUBECONFIG} to the server's configuration, or pass the server's --kubeconfig flag in args.
Connecting to a remote server over Streamable HTTP
The Streamable HTTP transport is suitable for MCP servers running in a remote environment. It establishes a connection with the MCP server over HTTP using a single endpoint for bidirectional messaging.
Note
Prior to MCP protocol version 2025-03-26, the HTTP+SSE (Server-Sent Events) transport was used for remote connections.
It has been deprecated since then in favor of Streamable HTTP.
The adapter still connects to servers that only support SSE: with a plain url it falls back to SSE when the server answers 404 or 405, and transport: 'sse' skips the Streamable HTTP attempt.
To try it locally, start the Kubernetes MCP Server in HTTP mode in a separate terminal:
npx -y kubernetes-mcp-server@latest --port 8080 --bind-address 127.0.0.1The server then listens for Streamable HTTP requests at http://localhost:8080/mcp.
By default, it binds to all network interfaces (0.0.0.0), which would expose your cluster to anyone who can reach your machine; --bind-address 127.0.0.1 keeps it local.
The following code snippet configures an MCPAdapter that connects to it:
import {MCPAdapter} from '@langchain/mcp-adapters';
const adapter = new MCPAdapter({
servers: {
kubernetes: {
url: 'http://localhost:8080/mcp'
}
}
});The only difference from the stdio configuration is the server entry: a url instead of a command.
Streamable HTTP is the default for URL-based servers, so you don't need to set transport: 'http'.
For a server that requires authentication, the same entry takes headers or an authProvider for OAuth.
A single adapter can also combine both kinds of servers: add one entry per server to the servers map, and the agent gets the tools of all of them.
Loading MCP tools as LangChain.js tools
Now that the adapter knows about your MCP server, you can load its tools and use them with LangChain.js.
Continuing from the previous snippet, the listTools() method connects to every configured server, discovers its tools, and wraps each one as a LangChain tool:
const tools = await adapter.listTools();
console.log(tools.map(tool => tool.name));
// [ 'kubernetes_configuration_view', 'kubernetes_events_list', ... ]Behind the scenes, the adapter negotiates the protocol version with each server, lists its tools, and creates a LangChain tool with the same name, description and input schema for each one. With the default configuration, the Kubernetes MCP Server v0.0.67 exposed 20 tools.
Notice the kubernetes_ prefix: since version 2.0, the adapter prefixes every tool name with its server name and an underscore, even when there is only one server.
This avoids collisions when two servers expose a tool with the same name.
You can set prefixToolNameWithServerName: false in the adapter's configuration to keep the original names.
The adapter keeps the connections open while your agent uses the tools.
When you're done, call await adapter.close() to close them and stop any server processes it started.
Using your own MCP client with loadMcpTools
If your application already manages its own MCP client, the standalone loadMcpTools function adapts that client's tools without handing the connection over to an MCPAdapter.
Since @langchain/mcp-adapters 2.0, it expects a client from version 2 of the MCP TypeScript SDK, which you install with npm install @modelcontextprotocol/client:
import {Client} from '@modelcontextprotocol/client';
import {StdioClientTransport} from '@modelcontextprotocol/client/stdio';
import {loadMcpTools} from '@langchain/mcp-adapters';
const client = new Client(
{name: 'blog.marcnuri.com', version: '1.0.0'},
{versionNegotiation: {mode: 'auto'}}
);
await client.connect(new StdioClientTransport({
command: 'npx',
args: ['-y', 'kubernetes-mcp-server@latest']
}));
const tools = await loadMcpTools('kubernetes', client);For a remote server, import StreamableHTTPClientTransport from @modelcontextprotocol/client and pass it new URL('http://localhost:8080/mcp') instead.
These are the key components of the example:
Client: The MCP client, with a name and version that identify it to the server.versionNegotiation: {mode: 'auto'}lets it use the latest protocol revision (2026-07-28) when the server supports it, and fall back to the previous one when it doesn't; without it, the SDK only speaks the older revisions. In my tests, the client negotiated 2026-07-28 with the Kubernetes MCP Server over stdio, and 2025-11-25 over Streamable HTTP.StdioClientTransport: The stdio transport, which spawns the server as a child process.loadMcpTools: Lists the client's tools and returns them as LangChain tools. The first argument is the server name; unlikeMCPAdapter,loadMcpToolsdoesn't prefix the tool names unless you ask it to.
You're responsible for this client: close it with await client.close() when you're done.
Building a LangChain.js agent with createAgent and LangGraph.js
You now have all the pieces together to create an agent that can use the MCP tools.
Since LangChain.js 1.0 (October 2025), createAgent from the langchain package is the standard way to build one.
It runs on LangGraph.js and replaces LangGraph's prebuilt createReactAgent, which is now deprecated.
Here's a complete example that creates an agent that can interact with a Kubernetes cluster using the Kubernetes MCP Server and a local model running on Ollama:
import {createAgent} from 'langchain';
import {ChatOllama} from '@langchain/ollama';
import {MCPAdapter} from '@langchain/mcp-adapters';
const adapter = new MCPAdapter({
servers: {
kubernetes: {
command: 'npx',
args: ['-y', 'kubernetes-mcp-server@latest']
}
}
});
try {
const tools = await adapter.listTools();
const agent = createAgent({
model: new ChatOllama({model: 'qwen3:4b-instruct'}),
tools
});
const listPods = await agent.invoke({
messages: [{
role: 'user',
content: 'List all pods in my cluster and output their name and namespace as a Markdown table'
}]
});
console.log(listPods.messages.at(-1).text);
} finally {
await adapter.close();
}You can run it with node kubernetes-agent.mjs.
In this example, I create a very simple assistant that connects the Kubernetes MCP Server tools with the qwen3:4b-instruct model running on your machine.
These are the key components of the agent example:
ChatOllama:
A LangChain chat model that talks to the local Ollama server (http://127.0.0.1:11434by default).model: The name of the Ollama model to use.
adapter.listTools():
Loads the MCP tools from the Kubernetes MCP Server as described in the previous section.createAgent:
Creates a LangChain agent using the model and the tools you loaded.invoke:
Invokes the agent with a user request to list all pods in the Kubernetes cluster. The agent will use the tools to perform the action and return the result.listPods.messages:
The agent's state after the run. The example prints the text of the last message, which contains the model's final answer.adapter.close():
Closes the connection and stops the server process, even if the agent fails.
Tip
Any LangChain chat model that supports tool calling works in place of ChatOllama.
For a hosted model like Google's Gemini, install @langchain/google-genai, add import {ChatGoogleGenerativeAI} from '@langchain/google-genai', and use new ChatGoogleGenerativeAI({model: 'gemini-2.5-flash'}) with your GOOGLE_API_KEY.
The agent follows a ReAct (Reasoning and Acting) pattern, alternating between reasoning about what to do next and taking action using the available tools. In this case, the agent can use the tools provided by the Kubernetes MCP Server to interact with your Kubernetes cluster.
I ran the example against a fake local test cluster whose only Pods were an nginx deployment of two replicas, and the output was (on a kind or minikube cluster, the list also includes the kube-system Pods):
| NAME | NAMESPACE |
|--------------------------------------|-----------|
| nginx-7f8fbb96d-kjc5p | default |
| nginx-7f8fbb96d-l5svd | default |The listPods.messages array contains the complete list of messages exchanged during the run.
In this case, there were four: the user's request, the model's call to the kubernetes_pods_list tool, the tool's result, and the model's final answer.
The beauty of this approach is that the agent decides which tools to use based on the user's request. For example, if the user asks to list pods, the agent identifies that it needs the Kubernetes tool that fetches pod information, and then formats the result as a Markdown table.
Small models need some help, though.
qwen3:4b-instruct called the right tool in every one of my runs, but it usually added a status.phase=Running filter nobody asked for, which would hide pods in any other state, and the table's columns changed from run to run.
Other small models I tried sent empty or null values for optional arguments, which the adapter rejects because they don't match the tool's input schema.
The agent gets the error back as the tool's result, so the model can retry, but the small models rarely recovered: granite4 usually repeated the same call and then answered with a placeholder table, and llama3.2:3b gave up.
I covered how to get the most out of small models in Giving Superpowers to Small Language Models with MCP.
The Kubernetes MCP Server also exposes tools that delete or modify resources in your cluster.
To experiment safely, add --read-only to its args, so it only exposes the tools that don't change anything.
Read-only prevents changes, not reads: tools like resources_get can still return Secrets, which then reach the model (and its provider, if you use a hosted one); the server's denied_resources setting can keep them out.
Upgrading from @langchain/mcp-adapters 1.x
Anthropic introduced MCP in November 2024, and it quickly became the standard way to expose tools to artificial intelligence (AI) applications.
In March 2025, the LangChain community released the MCP adapters for LangChain.js, a JavaScript library for building applications powered by LLMs; the adapters started as a separate project and now live in the main LangChain.js repository.
@langchain/mcp-adapters 2.0 (October 2026) moved to version 2 of the MCP TypeScript SDK, which adds the 2026-07-28 revision of the MCP specification while still talking to older servers, with MCPAdapter as its new entry point.
I first wrote this post in April 2025, with hand-built MCP clients, the SSE transport, LangGraph's createReactAgent and OpenAI's GPT-4o mini through GitHub Models.
In January 2026, I moved it to Streamable HTTP, createAgent and Google's Gemini.
If you followed an earlier version of this post, or any tutorial that uses MultiServerMCPClient, this is what changed in @langchain/mcp-adapters 2.0:
| 1.x | 2.x |
|---|---|
MultiServerMCPClient | MCPAdapter |
mcpServers: {...} | servers: {...} |
getTools() | listTools() |
Unprefixed tool names (pods_list) | Prefixed tool names (kubernetes_pods_list) |
@modelcontextprotocol/sdk client | @modelcontextprotocol/client client |
The old class and method names still work but are deprecated.
Passing a client from the old @modelcontextprotocol/sdk package to loadMcpTools doesn't: it fails with TypeError: client.getProtocolEra is not a function.
Switch the imports to @modelcontextprotocol/client (and @modelcontextprotocol/client/stdio for the stdio transport), as shown above.
The prefixed tool names also affect anything that refers to a tool by name, like prompts or human-in-the-loop approval rules.
The official migration guide covers the rest of the changes.
Conclusion
In this post, I've shown you how to connect your JavaScript applications to an MCP server using LangChain.js.
I've covered how to configure stdio and Streamable HTTP servers with MCPAdapter, how to load their tools (or adapt your own MCP client's tools with loadMcpTools), and how to bind everything together with createAgent and a local model.
With the MCP adapters, any of the many available MCP servers becomes a set of tools your LangChain.js agents can use with a few lines of configuration.
You can find the source code for this post on GitHub.
You Might Also Like
- AI SDK MCP Client: Connect JavaScript to an MCP Server
- LangChain4j MCP Client: Connect Java to an MCP Server
- Introduction to the Model Context Protocol (MCP): The Future of AI Integration
- Giving Superpowers to Small Language Models with Model Context Protocol (MCP)
- MCP Tool Annotations: Every Hint and Its Default Value
