How do you give a CrewAI agent tools from an MCP server, and what lifecycle must you manage?
answer
- one adapter class does the bridging
- stdio subprocess or url plus transport
- the connection is the thing you own
- kickoff belongs inside the block
- take a subset, not the whole catalogue
basics
~20 sUse MCPServerAdapter from crewai_tools: construct it with the server's connection parameters, and it exposes the server's tools as CrewAI tools you pass into Agent(tools=...). It holds a live connection, so use it as a context manager or call stop() in a finally block.
solid answer
~50 s`crewai_tools` ships `MCPServerAdapter`, which connects to an MCP server and surfaces its tools as ordinary CrewAI tool objects. You construct it with the server's connection parameters — a local process launched over stdio, or a dict naming a `url` and a `transport` such as `"streamable-http"` or `"sse"` for a remote one — and then pass the adapter's tools straight into `Agent(tools=...)`. You can restrict what you take by naming tools in the constructor, which matters because the server decides its own catalogue and you do not want fifty tools in every prompt. The lifecycle is the part people get wrong: the adapter owns a live connection or a child process, so the idiomatic form is `with MCPServerAdapter(params) as tools:` with the crew's `kickoff()` **inside** the block; if you build it without the context manager, wrap the run in `try`/`finally` and call `adapter.stop()`. Using the tools after the block has exited fails, because the transport is closed.
code
python · 21 linesfrom crewai import Agent, Crew, Task
from crewai_tools import MCPServerAdapter
server_params = {
"url": "http://localhost:8000/mcp",
"transport": "streamable-http",
}
with MCPServerAdapter(server_params) as tools:
analyst = Agent(
role="Analyst",
goal="Answer questions using the connected server's tools",
backstory="Careful with external data.",
tools=tools,
)
task = Task(
description="Report yesterday's error rate.",
expected_output="One sentence with the figure.",
agent=analyst,
)
Crew(agents=[analyst], tasks=[task]).kickoff()go deeper
Know that crewai_tools provides MCPServerAdapter, that it turns an MCP server's tools into CrewAI tools, and that you pass them into Agent(tools=...).
Explain the two connection shapes — a local subprocess over stdio, or a mapping with url and transport for a remote server — and why the adapter is used as a context manager or paired with stop().
Show the operational picture: kickoff inside the connection's lifetime, one long-lived adapter for a service rather than one per run, a filtered subset of tools for prompt budget and selection accuracy, and the extra latency of a round trip per call.
Own the dependency. A server you do not operate can change its catalogue and its prompt text without a diff in your repo, and both its descriptions and its results are untrusted input — decide what gets wrapped, what gets pinned, and which capability classes may never share a task with it.
## What the adapter is for CrewAI's agents take tool objects. An MCP server publishes tools over its own protocol. `MCPServerAdapter`, from the `crewai_tools` package, is the bridge: it connects, discovers what the server offers, and wraps each one in a CrewAI-shaped tool with a name, a description and an argument schema derived from the server's declaration. From the agent's point of view they are ordinary tools. The attraction is that you stop writing integration code. A team that already runs an MCP server for its internal systems can hand a CrewAI crew that server's address instead of a Python package. ## Connecting Two shapes cover most cases: - **A local server run as a subprocess** — the adapter launches the command and speaks to it over stdio. Good for developer machines and for servers you ship alongside your crew. - **A remote server over HTTP** — you pass a mapping with the server's `url` and the `transport` to use (`"streamable-http"` or `"sse"`). Good for shared, centrally operated servers. Either way, the result is the same: `adapter.tools` is a list of CrewAI tools ready to hand to an agent. ## Filtering the catalogue Take only what the crew needs. `MCPServerAdapter` accepts tool names in the constructor so you can pull a subset, and you can also slice the resulting list. Two reasons this is not optional at scale: 1. **Prompt budget.** Every tool's name, description and schema is re-sent on every model call in that agent's loop. A generous server catalogue can quietly dominate the prompt. 2. **Selection accuracy.** More near-identical tools means more wrong picks. A server exposing twelve variants of "search" will make your agent worse, not better. ## The lifecycle trap This is the question behind the question. The adapter is not a plain object — it holds a transport, and for stdio it holds a child process. Two correct forms: ``` with MCPServerAdapter(params) as tools: ...build the agent and crew... crew.kickoff() ``` or ``` adapter = MCPServerAdapter(params) try: ... finally: adapter.stop() ``` The failure mode people hit is building the agent inside the `with` block and calling `kickoff()` after it, on the assumption that the tools were "copied". They were not; they are handles onto a connection that has been closed, and the first tool call fails. On the other side, forgetting `stop()` on a long-lived process leaks subprocesses or sockets across runs — visible as a slow climb in file descriptors on a service that kicks off crews repeatedly. For a long-running service, the practical pattern is to hold one adapter for the process lifetime, wire it into agents as they are constructed, and shut it down on application shutdown, rather than opening and closing a connection per kickoff. ## What you do not control The tool names, descriptions and schemas come from the server. That has direct consequences: - **You cannot tune the prompt text of a remote tool.** If descriptions are vague, agent behaviour suffers and the fix lives in someone else's repo. When it matters, wrap the adapter's tool in your own `BaseTool` with a description you control and delegate in `_run`. - **The catalogue can change under you.** A server can add, rename or alter tools between deployments; your agent's behaviour changes with no diff in your repo. Pin what you can by naming the tools you take, and treat a catalogue change as a change to your system. - **Trust.** Tool results are text going into your agent's context, and descriptions are text going into your prompt. A server you do not operate is an untrusted input on both channels. Do not pair an unaudited MCP toolset with write, publish or code-execution tools in the same task's tool list. ## Failure and latency Every MCP tool call is a network or IPC round trip on top of the model round trip, so an agent loop that calls three MCP tools per iteration has a latency floor you should measure before promising response times. Server errors arrive as tool errors and follow CrewAI's normal path — they become observations the agent reads — so the same discipline applies: keep the surfaced text short and actionable, and make sure a failure is not cached for the rest of the run. ## Interview shape Name the class, name the two connection shapes, then go straight to lifecycle and filtering. Those are the answers that show you have actually run it: the `with` block containing the kickoff, `stop()` in a `finally`, and taking a subset because the server's catalogue is not designed for your prompt budget.
- Why does building the agent inside the with block but calling kickoff() after it fail?The tools are handles onto the adapter's live transport, not detached copies. Leaving the block closes the connection — and for a stdio server, terminates the child process — so the first tool call at kickoff time has nothing to talk to. Either keep `kickoff()` inside the block or manage the adapter explicitly and call `stop()` only after the run completes.
- You cannot edit a remote MCP tool's description, but your agent keeps mis-selecting it. What can you do on the CrewAI side?Wrap it. Write your own `BaseTool` subclass with a name, description and `args_schema` you control, and have `_run` delegate to the adapter's tool. The agent then reads your prompt text while the execution still goes to the server. It also gives you a place to trim oversized results and shape error strings before they enter context.
- What is the risk of pointing a crew at an MCP server your team does not operate?Both channels are untrusted: the tool descriptions land in your prompt and the tool results land in your agent's context, so either can carry instructions. The catalogue can also change without a diff in your repo. Pin the tool names you take, wrap results you do not trust, and never give the same task a write, publish or code-execution tool alongside them.
saying these in an interview costs you the question
- Calls kickoff() after the adapter's context manager has exited
- Never calls stop(), leaking subprocesses across repeated runs
- Loads the server's entire tool catalogue into every agent
- Assumes the adapter's tools work offline once constructed
- Treats a third-party server's descriptions and results as trusted text