In the previous NotebookEditTool chapter, we learned how to manipulate local data science files. We gave claudeCode the ability to be a careful editor.
However, a good developer doesn't just look at their own code. They look at documentation, StackOverflow, and GitHub issues.
By default, an AI model is like an encyclopedia printed last year. It knows everything up to that date, but it doesn't know about the JavaScript library released yesterday.
Enter the WebSearchTool. This tool gives the agent a live connection to the internet, allowing it to fetch up-to-date information.
The WebSearchTool acts as a research assistant. When claudeCode realizes it doesn't know the answer (or needs to check the latest version of a package), it uses this tool to "Google it."
Imagine you ask: "Why is my code failing with Error: useHistory is not exported from react-router-dom?"
The AI might remember an old version of React Router. But with the WebSearchTool, it can:
useHistory was replaced by useNavigate."Just like you type keywords into a search bar, the AI generates a Query String. It tries to be specific to get the best technical results.
Searching the entire web can be noisy. This tool supports:
developer.mozilla.org."reddit.com."This keeps the information high-quality and relevant.
The tool doesn't just return a screenshot. It returns structured data:
The AI uses this tool automatically when it lacks information. However, you can see how it requests a search by looking at its input structure.
The AI sends a JSON object requesting a search.
{
"query": "python 3.12 release notes new features",
"allowed_domains": ["python.org", "docs.python.org"]
}
The tool replies with a summary found on the web.
{
"query": "python 3.12 release notes new features",
"results": [
{
"title": "What's New In Python 3.12",
"url": "https://docs.python.org/3/whatsnew/3.12.html"
}
],
"durationSeconds": 0.8
}
Explanation: The AI reads this results array to formulate its final answer to you.
Unlike the FileEditTool which touches your hard drive, this tool reaches out to an external API.
Interestingly, claudeCode often delegates this task. It asks a specialized version of the Claude model (or a specific API endpoint) to perform the search and summarize the findings.
Here is the flow:
The implementation is located in tools/WebSearchTool/WebSearchTool.ts. Let's break down the key parts.
We define the tool using buildTool, setting its name and description so the AI knows when to use it.
// tools/WebSearchTool/WebSearchTool.ts
export const WebSearchTool = buildTool({
name: 'web_search',
// Helps the AI understand the tool's purpose
description: (input) => `Claude wants to search for: ${input.query}`,
// The shape of data the AI must provide
inputSchema: inputSchema(),
// Is it safe to run alongside other tools? Yes.
isConcurrencySafe: () => true,
// ... implementation details
});
Before searching, we check if the request makes sense.
async validateInput(input) {
const { query, allowed_domains, blocked_domains } = input;
// 1. We need a query to search!
if (!query.length) {
return { result: false, message: 'Error: Missing query' };
}
// 2. You can't both Allow AND Block domains (logic conflict)
if (allowed_domains?.length && blocked_domains?.length) {
return {
result: false,
message: 'Cannot specify both allowed and blocked domains'
};
}
return { result: true };
}
Explanation: This ensures we don't send empty or contradictory requests to the search API.
The call function is where the magic happens. We actually stream the request to a model capable of browsing.
// Inside async call(...)
const userMessage = createUserMessage({
content: 'Perform a web search for: ' + query,
});
// We create a stream to the API
const queryStream = queryModelWithStreaming({
messages: [userMessage],
tools: [], // We might pass specific browser tools here
// ... options
});
// We wait for the stream to finish and collect results
for await (const event of queryStream) {
// Logic to capture search hits as they arrive...
}
Explanation: We use queryModelWithStreaming (discussed in Query Engine) to delegate the heavy lifting of searching and summarizing to the backend API.
Once we have the data, we need to turn it into a string the AI can read.
mapToolResultToToolResultBlockParam(output, toolUseID) {
const { query, results } = output;
let formatted = `Web search results for: "${query}"\n\n`;
// Loop through results and add links
results.forEach(result => {
if (result.content) {
formatted += `Links: ${jsonStringify(result.content)}\n\n`;
}
});
return {
type: 'tool_result',
content: formatted.trim(),
};
}
Explanation: This converts the raw JSON into a readable text block. The AI reads this text to learn the information.
Because this tool sends data (your query) to the outside world, it interacts with the Permission & Security System.
Usually, WebSearchTool is flagged as "ReadOnly" (it doesn't change your files), but some users might want to block it to prevent data leaks. The checkPermissions function handles this.
async checkPermissions(_input) {
return {
behavior: 'passthrough', // Usually safe
message: 'WebSearchTool requires permission.',
// Suggest adding a rule to allow it permanently
suggestions: [{ type: 'addRules', rules: [{ toolName: 'web_search' }] }]
};
}
The WebSearchTool is a bridge to the outside world.
You have learned that the WebSearchTool is claudeCode's window to the present. By validating queries and connecting to a search API, it allows the AI to answer questions about topics that didn't exist when the model was trained.
Now that we have a powerful set of tools (File Editing, Notebooks, Web Search), we need a way to turn them on or off depending on who is using the application.
Generated by Code IQ