Subagent with an MCP Tool — A Subagent Is Just an LLMAgent¶
This notebook builds websearch_subagent, wired to an MCP-discovered
websearch tool, and registers it alongside the framework's general
and explore default recipes. Nothing about SubAgentSpec restricts
what a dispatched agent can be equipped with: builder is an ordinary
LLMAgentBuilder, the same one used for a top-level agent.
Setting the backbone LLM of your agent¶
These notebooks run on Ollama by default, the setup the book teaches.
If you do nothing, nothing changes: make_llm() starts a local Ollama
service when one isn't already running.
To use OpenAI or Anthropic instead:
- Install the extra:
uv sync --extra openaioruv sync --extra anthropic. - Export
OPENAI_API_KEYorANTHROPIC_API_KEYbefore launching Jupyter. - Set
LLM_PROVIDER=openai(oranthropic), or passprovider="openai"tomake_llm(). A key on its own never switches providers, so one exported for unrelated work cannot reroute you off the Ollama path.
The switch applies wherever a notebook builds its LLM with make_llm().
A notebook that constructs OllamaLLM directly stays on Ollama regardless
of these settings.
If you opted in but forgot to export the key, you will be prompted for it.
That is the safe path on hosted kernels, and it keeps the key out of the
saved notebook. Setting OLLAMA_API_KEY alone routes Ollama to Ollama
Cloud.
Caveat: the examples are tuned for qwen3. Output on gpt-5 or Claude will differ from what is printed in the book, and prompt-sensitive examples (the ch09 evaluator pattern, ch08 supervised trajectories) may behave noticeably differently.
# Uncomment the line below to install `llm-agents-from-scratch` from PyPI
# !pip install llm-agents-from-scratch
Running an Ollama service¶
To execute the code provided in this notebook, you'll need to have
Ollama installed on your local machine and have its LLM hosting
service running. To download Ollama, follow the instructions found on
this page: https://ollama.com/download. After downloading and
installing Ollama, you can start a service by opening a terminal and
running ollama serve.
from llm_agents_from_scratch.notebook_utils import make_llm
Connecting to a Websearch MCP Server¶
duckduckgo-mcp-server
exposes search and fetch_content tools over stdio. Unlike the
GitHub and GoodNews MCP servers from Chapter 5, it needs no API key
and no local clone; uvx fetches and runs it on demand, requiring
only that uv is installed on your machine. The browser extra
below is required: DuckDuckGo's search endpoint blocks the server's
default HTTP client, and the fallback that handles this only works
with that extra installed.
from mcp import StdioServerParameters
from llm_agents_from_scratch.tools.mcp import MCPToolProvider
websearch_mcp_provider = MCPToolProvider(
name="websearch_mcp",
stdio_params=StdioServerParameters(
command="uvx",
args=[
"--with",
"duckduckgo-mcp-server[browser]",
"duckduckgo-mcp-server",
],
),
)
Defining the Roster¶
websearch_subagent's builder is nothing special: an
LLMAgentBuilder pointed at the MCP provider above, the same builder
class every other subagent in this book uses. Registering it alongside
general_subagent and explore_subagent shows the coordinator's
subagent registry is an open list, not a fixed pair of defaults.
from llm_agents_from_scratch import LLMAgent, LLMAgentBuilder
from llm_agents_from_scratch.data_structures import Task
from llm_agents_from_scratch.subagents import SubAgentSpec
from llm_agents_from_scratch.subagents.recipes import (
explore_subagent,
general_subagent,
)
llm = make_llm()
websearch_subagent = SubAgentSpec(
name="websearch_subagent",
description=(
"Searches the live web and fetches page content for current info."
),
builder=LLMAgentBuilder(llm=llm, mcp_providers=[websearch_mcp_provider]),
max_steps=5,
)
coordinator = LLMAgent(
llm=llm,
subagents=[
general_subagent(llm),
explore_subagent(llm),
websearch_subagent,
],
)
✓ Using Ollama Cloud (kimi-k2.7-code:cloud)
Example — Routing to the Websearch Specialist¶
The task below asks for today's actual, current release of Python.
websearch_subagent is the only specialist built with a dedicated
search tool, so it's the intended routing target, even though
general_subagent's Python interpreter could technically improvise
a network call of its own.
task = Task(
instruction=(
"Ask websearch_subagent to search the web for the current "
"official version number of the Python programming language "
"and report it back with the source URL. Report exactly what "
"it returns."
),
)
handler = coordinator.run(task, max_steps=10)
result = await handler
print(result.content)
The websearch_subagent returned: > The current official Python version shown on python.org is **Python 3.14.7**. > > This is confirmed by the official Python downloads page at https://www.python.org/downloads/, which lists **Python 3.14.7** as the latest release (dated Aug. 5, 2026) and also states "Latest: Python 3.14.7" on the homepage.
handler.rollout shows the coordinator dispatching straight to
websearch_subagent, the only registered specialist actually equipped
to answer; general_subagent and explore_subagent stay registered
but unused, exactly as a real open registry should behave.
print(handler.rollout)
=== Task Step Start ===
💬 assistant: My current instruction is 'Ask websearch_subagent to search the web for the current official version number of the Python programming language and report it back with the source URL. Report exactly what it returns.'
💬 assistant: I need to make the following tool call(s):
{
"id_": "3c567290-f94f-4cfb-8f8f-ecc494e16b2e",
"tool_name": "from_scratch__use_subagent",
"arguments": {
"name": "websearch_subagent",
"task": "Search the web for the current official version number of the Python programming language. Report the version number and include the source URL where you found it. Be specific and use authoritative sources such as python.org."
}
}.
🔧 tool: {
"tool_call_id": "3c567290-f94f-4cfb-8f8f-ecc494e16b2e",
"content": "The current official Python version shown on python.org is **Python 3.14.7**.\n\nThis is confirmed by the official Python downloads page at https://www.python.org/downloads/, which lists **Python 3.14.7** as the latest release (dated Aug. 5, 2026) and also states \"Latest: Python 3.14.7\" on the homepage.",
"error": false
}
💬 assistant: The websearch_subagent returned:
> The current official Python version shown on python.org is **Python 3.14.7**.
>
> This is confirmed by the official Python downloads page at https://www.python.org/downloads/, which lists **Python 3.14.7** as the latest release (dated Aug. 5, 2026) and also states "Latest: Python 3.14.7" on the homepage.
=== Task Step End ===
The MCP provider keeps its uvx subprocess and session alive for
reuse across dispatches. In a notebook the kernel can stay up long
after this cell runs, so close it explicitly rather than relying on
process exit to clean it up.
await websearch_mcp_provider.close()