Agent Executor + Parallel Search MCP¶
Example how to give an agent executor web access through the Parallel Search MCP server.
The server is reachable over Streamable HTTP at https://search.parallel.ai/mcp and publishes two tools:
web_search- run one or more search queries and get back LLM friendly excerptsweb_fetch- read the content of specific URLs
It accepts anonymous requests, so this notebook needs no API key. Anonymous traffic is rate limited
and meant for evaluation; add an API key through the x-api-key header for anything heavier.
Using the server is subject to the Parallel customer terms and privacy policy.
Disclosure: this how-to was contributed by George Pickett, who works at Parallel.
var userHomeDir = System.getProperty("user.home");
var localRespoUrl = "file://" + userHomeDir + "/.m2/repository/";
var langchain4jVersion = "1.22.0";
var langchain4jbeta = "1.22.0-beta32";
var langgraph4jVersion = "1.9.6";
Remove installed package from Jupiter cache
%%bash
rm -rf \{userHomeDir}/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/langgraph4j
add local maven repo and install dependencies
%dependency /add-repo local \{localRespoUrl} release|never snapshot|always
// %dependency /list-repos
%dependency /add org.slf4j:slf4j-jdk14:2.0.9
%dependency /add org.bsc.langgraph4j:langgraph4j-core:\{langgraph4jVersion}
%dependency /add org.bsc.langgraph4j:langgraph4j-langchain4j:\{langgraph4jVersion}
%dependency /add org.bsc.langgraph4j:langgraph4j-agent-executor:\{langgraph4jVersion}
%dependency /add dev.langchain4j:langchain4j:\{langchain4jVersion}
%dependency /add dev.langchain4j:langchain4j-mcp:\{langchain4jbeta}
%dependency /add dev.langchain4j:langchain4j-ollama:\{langchain4jVersion}
%dependency /resolve
Repository local url: file:///Users/bsorrentino/.m2/repository/ added. Adding dependency org.slf4j:slf4j-jdk14:2.0.9 Adding dependency org.bsc.langgraph4j:langgraph4j-core:1.9.4 Adding dependency org.bsc.langgraph4j:langgraph4j-langchain4j:1.9.4 Adding dependency org.bsc.langgraph4j:langgraph4j-agent-executor:1.9.4 Adding dependency dev.langchain4j:langchain4j:1.22.0 Adding dependency dev.langchain4j:langchain4j-mcp:1.22.0-beta32 Adding dependency dev.langchain4j:langchain4j-ollama:1.22.0 Solving dependencies Resolved artifacts count: 21 Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/slf4j/slf4j-jdk14/2.0.9/slf4j-jdk14-2.0.9.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/slf4j/slf4j-api/2.0.9/slf4j-api-2.0.9.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/langgraph4j/langgraph4j-core/1.9.4/langgraph4j-core-1.9.4.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/async/async-generator/5.1.0/async-generator-5.1.0.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/langgraph4j/langgraph4j-langchain4j/1.9.4/langgraph4j-langchain4j-1.9.4.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-skills/1.22.0-beta32/langchain4j-skills-1.22.0-beta32.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/commonmark/commonmark/0.28.0/commonmark-0.28.0.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/commonmark/commonmark-ext-yaml-front-matter/0.28.0/commonmark-ext-yaml-front-matter-0.28.0.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/bsc/langgraph4j/langgraph4j-langchain4j-agentexecutor/1.9.4/langgraph4j-langchain4j-agentexecutor-1.9.4.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j/1.22.0/langchain4j-1.22.0.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-core/1.22.0/langchain4j-core-1.22.0.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/jspecify/jspecify/1.0.1/jspecify-1.0.1.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/fasterxml/jackson/core/jackson-annotations/2.22/jackson-annotations-2.22.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/fasterxml/jackson/core/jackson-core/2.22.1/jackson-core-2.22.1.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/com/fasterxml/jackson/core/jackson-databind/2.22.1/jackson-databind-2.22.1.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/org/apache/opennlp/opennlp-tools/2.5.11/opennlp-tools-2.5.11.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/io/smallrye/reactive/mutiny-zero/1.3.1/mutiny-zero-1.3.1.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-mcp/1.22.0-beta32/langchain4j-mcp-1.22.0-beta32.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-http-client/1.22.0/langchain4j-http-client-1.22.0.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-http-client-jdk/1.22.0/langchain4j-http-client-jdk-1.22.0.jar Add to classpath: /Users/bsorrentino/Library/Jupyter/kernels/rapaio-jupyter-kernel/mima_cache/dev/langchain4j/langchain4j-ollama/1.22.0/langchain4j-ollama-1.22.0.jar
Initialize Logger
try( var file = new java.io.FileInputStream("./logging.properties")) {
java.util.logging.LogManager.getLogManager().readConfiguration( file );
}
var log = org.slf4j.LoggerFactory.getLogger("ParallelSearch");
Setup MCP client¶
StreamableHttpMcpTransport talks to the remote server over plain HTTP, so unlike the
mcp/postgres example there is nothing to install and no Docker container to start.
No Authorization or x-api-key header is sent: the client connects anonymously.
import dev.langchain4j.mcp.client.DefaultMcpClient;
import dev.langchain4j.mcp.client.transport.http.StreamableHttpMcpTransport;
import java.time.Duration;
import java.util.List;
import java.util.Map;
var transport = new StreamableHttpMcpTransport.Builder()
.url( "https://search.parallel.ai/mcp" )
.timeout( Duration.ofSeconds(60) )
.logRequests(false) // set to true if you want to see the traffic in the log
.logResponses(false)
.build();
var mcpClient = new DefaultMcpClient.Builder()
.transport( transport )
.clientName( "langgraph4j-how-tos" )
.clientVersion( "1.0" )
.initializationTimeout( Duration.ofSeconds(30) )
.toolExecutionTimeout( Duration.ofMinutes(2) )
.build();
// the tools published by the server
mcpClient.listTools()
.stream()
.map( tool -> "%s - %s".formatted( tool.name(), tool.description().lines().findFirst().orElse("") ) )
.toList();
Setup Agent Executor¶
Only web_search and web_fetch are registered, so the agent keeps the same tool set even if
the server starts publishing more of them.
A failing tool call is reported back to the model as text instead of breaking the graph: the model can then retry with a different query or answer without the tool.
import org.bsc.langgraph4j.agentexecutor.AgentExecutor;
import dev.langchain4j.model.ollama.OllamaChatModel;
var webTools = List.of( "web_search", "web_fetch" );
final var model = OllamaChatModel.builder()
.baseUrl( "http://localhost:11434" )
.temperature(0.0)
.logRequests(true)
.logResponses(true)
.timeout( Duration.ofMinutes(5) )
.modelName("qwen2.5:7b")
.build();
var agentBuilder = AgentExecutor.builder()
.chatModel(model);
for( var toolSpecification : mcpClient.listTools() ) {
if( !webTools.contains( toolSpecification.name() ) ) continue;
agentBuilder.tool( toolSpecification, ( request, memoryId) -> {
var result = mcpClient.executeTool( request );
if( result.isError() ) {
log.warn( "{} failed: {}", request.name(), result.resultText() );
}
return result.resultText();
});
}
var agent = agentBuilder.build().compile();
Ask a question that needs the web¶
The model decides when to search and when to fetch a page. The answer is built from the tool results, so it can quote sources instead of relying on what the model memorized.
import dev.langchain4j.data.message.ChatMessage;
import dev.langchain4j.data.message.SystemMessage;
import dev.langchain4j.data.message.UserMessage;
import org.bsc.langgraph4j.GraphInput;
import org.bsc.langgraph4j.RunnableConfig;
var messages = List.<ChatMessage>of(
SystemMessage.from("""
You are a web research assistant.
Use web_search to look things up and web_fetch only when you need the full text of a page.
Answer using the tool results and cite the source URLs.
"""),
UserMessage.from( "What is langgraph4j and which checkpoint savers does it provide?" )
);
agent.invoke( GraphInput.args( Map.of( "messages", messages ) ), RunnableConfig.empty() )
.flatMap(AgentExecutor.State::finalResponse)
.orElse("no response");
START callAgent executeTools execute: [web_search] callAgent executeTools
Release the HTTP connection when the agent is no longer needed
mcpClient.close();