Add live web search to a CrewAI agent
CrewAI builds multi-agent workflows from three pieces: an Agent (a role with a goal and a set of tools), a Task (a unit of work assigned to an agent), and a Crew (the orchestrator that runs the tasks). Any tool you hand an agent's tools= list becomes callable — either all five Superhighway tools at once via MCPServerAdapter, or individual @tool-decorated functions over the REST API.
The Agent / Task / Crew pattern
Every CrewAI program wires up the same three objects:
- Agent — a persona defined by
role,goal, andbackstory. Thetools=list is what the agent can actually do. Give it live web search and it stops hallucinating and starts citing. - Task — a single instruction (
description) assigned to an agent, with anexpected_outputthat shapes the result. - Crew — collects agents and tasks and runs them with
.kickoff(), returning aCrewOutput(.raw,.json,.pydantic,.token_usage).
Two paths
| Path | Best for | What you need |
|---|---|---|
| MCPServerAdapter | All five tools, fully autonomous — agent pays per call via x402 | A funded Base wallet + npx |
| @tool + API key | A curated set of tool functions, controlled spend, no wallet | A free API key from /pricing |
Use MCPServerAdapter when you want the agent to have the full toolbox and pay as it goes with no human in the loop. Use @tool when you want to pick exactly which capabilities the agent gets and bill against a single API key.
Path 1: MCPServerAdapter (recommended for autonomous agents)
pip install "crewai-tools[mcp]"
import os
from crewai import Agent, Task, Crew
from crewai_tools import MCPServerAdapter
# The dict config launches the MCP server as a stdio subprocess.
server_config = {
"superhighway": {
"command": "npx",
"args": ["-y", "superhighway-mcp"],
"env": {
"AGENT_PRIVATE_KEY": os.environ["AGENT_PRIVATE_KEY"],
"X402_NETWORK": "base",
},
"transport": "stdio",
}
}
# The context manager starts the server, discovers its tools, and stops
# it cleanly on exit. mcp_tools is an iterable of CrewAI-ready tools.
with MCPServerAdapter(server_config) as mcp_tools:
print("Available tools:", [tool.name for tool in mcp_tools])
researcher = Agent(
role="Web Researcher",
goal="Find accurate, up-to-date information from the live web",
backstory="A meticulous analyst who always verifies claims against current sources.",
tools=mcp_tools, # all five tools: web_search, news_search, image_search, scrape, research
verbose=True,
)
task = Task(
description="Find the top 3 AI web-search APIs and their per-call pricing.",
expected_output="A ranked list of 3 APIs with prices and source URLs.",
agent=researcher,
)
result = Crew(agents=[researcher], tasks=[task]).kickoff()
print(result.raw)
AGENT_PRIVATE_KEY is the private key of a wallet holding USDC on Base. Each tool call costs $0.001–$0.005 and settles automatically over the x402 protocol — no signup, no API key, no human approval. The with block is important: MCPServerAdapter manages the subprocess lifecycle, so the server is started on entry and torn down on exit. Build everything that uses mcp_tools — agent, task, crew, and kickoff() — inside the block. To restrict the agent to a subset, pass tool names: MCPServerAdapter(server_config, "web_search", "research").
Path 2: @tool functions with an API key
CrewAI's @tool decorator turns a plain Python function into an agent tool. The decorator argument is the tool's display name; the docstring is the description the model reads to decide when to call it — so write it for the model, not for humans. Get a free API key (1,000 calls/month, email only) and wrap exactly the endpoints you want.
pip install crewai crewai-tools requests
import os
import requests
from crewai import Agent, Task, Crew
from crewai.tools import tool
BASE = "https://superhighway.walls.sh"
HEADERS = {"X-Api-Key": os.environ["SUPERHIGHWAY_API_KEY"]}
@tool("Web Search")
def web_search(query: str) -> str:
"""Search the live web for current information and return ranked results
with titles, URLs, and descriptions. Use for general up-to-date facts."""
r = requests.get(f"{BASE}/search", params={"q": query, "limit": 5}, headers=HEADERS)
return str(r.json())
@tool("News Search")
def news_search(query: str) -> str:
"""Search recent news articles with published dates. Use when recency
matters — breaking events, latest releases, current headlines."""
r = requests.get(f"{BASE}/news", params={"q": query, "limit": 5}, headers=HEADERS)
return str(r.json())
@tool("Deep Research")
def research(query: str) -> str:
"""Search the web AND read the top pages in a single call, returning a
synthesized answer with sources. Use for questions needing real depth."""
r = requests.get(f"{BASE}/research", params={"q": query, "pages": 3}, headers=HEADERS)
return str(r.json())
analyst = Agent(
role="Market Analyst",
goal="Answer questions with current, well-sourced web information",
backstory="An analyst who never guesses — every claim is backed by a live source.",
tools=[web_search, news_search, research],
verbose=True,
)
task = Task(
description="What changed in the latest Python release? Cite the source.",
expected_output="A short summary of the headline changes with a source URL.",
agent=analyst,
)
result = Crew(agents=[analyst], tasks=[task]).kickoff()
print(result.raw)
The key CrewAI pattern: @tool("Display Name") from crewai.tools wraps any function, and its docstring becomes the description the model uses for tool selection — a vague docstring means the agent calls the wrong tool. Functions take typed arguments (query: str) so CrewAI can build the input schema. Pass the decorated functions directly in tools=[...]. You can also mix MCP tools and @tool functions in one list by concatenating them.
Which tools are available
| Tool / endpoint | What it returns | Price |
|---|---|---|
web_search / /search | Ranked web results — title, URL, description | $0.001 |
news_search / /news | Recent news with published dates | $0.001 |
image_search / /images | Image URLs, thumbnails, source pages | $0.001 |
scrape / /scrape | Any URL → clean markdown text | $0.002 |
research / /research | Search + read top pages in one call | $0.005 |
Full API reference: /openapi.json. Get a free API key or learn the x402 wallet flow.