MCP Bridge
The agentensemble-mcp module bridges the Model Context Protocol (MCP)
ecosystem into AgentEnsemble. It adapts MCP server tools to the AgentTool interface so
they can be used alongside Java-native tools in any agent’s tool list.
Installation
Section titled “Installation”implementation("net.agentensemble:agentensemble-mcp:VERSION")<dependency> <groupId>net.agentensemble</groupId> <artifactId>agentensemble-mcp</artifactId> <version>VERSION</version></dependency>Connecting to Any MCP Server
Section titled “Connecting to Any MCP Server”Use McpToolFactory.fromServer() to connect to any MCP-compatible server and obtain
its tools as AgentTool instances. Wrap the transport in try-with-resources to ensure
the subprocess is cleaned up:
import dev.langchain4j.mcp.client.transport.stdio.StdioMcpTransport;import net.agentensemble.mcp.McpToolFactory;
try (StdioMcpTransport transport = new StdioMcpTransport.Builder() .command(List.of("npx", "--yes", "@modelcontextprotocol/server-filesystem", "/workspace")) .build()) {
// Get all tools from the server List<AgentTool> tools = McpToolFactory.fromServer(transport);
// Or filter to specific tools by name List<AgentTool> filtered = McpToolFactory.fromServer(transport, "read_file", "write_file");}For managed lifecycle (recommended), prefer McpServerLifecycle via
McpToolFactory.filesystem() or McpToolFactory.git() — see below.
Each tool’s parameter schema is passed through directly from the MCP server to the LLM —
the LLM sees properly typed, named parameters just as with TypedAgentTool records.
Convenience: Filesystem and Git Servers
Section titled “Convenience: Filesystem and Git Servers”McpToolFactory provides convenience methods for the well-known MCP reference servers:
import net.agentensemble.mcp.McpToolFactory;import net.agentensemble.mcp.McpServerLifecycle;
// Filesystem server: read, write, search, list filestry (McpServerLifecycle fs = McpToolFactory.filesystem(Path.of("/workspace"))) { fs.start(); List<AgentTool> fsTools = fs.tools();
var agent = Agent.builder() .role("File Manager") .tools(fsTools) .llm(chatModel) .build();}
// Git server: status, diff, log, commit, branchtry (McpServerLifecycle git = McpToolFactory.git(Path.of("/repo"))) { git.start(); List<AgentTool> gitTools = git.tools(); // ...}These methods require npx (Node.js) to be available on the system PATH. They spawn
the MCP reference servers as subprocesses:
filesystem(Path)runsnpx @modelcontextprotocol/server-filesystem <dir>git(Path)runsnpx @modelcontextprotocol/server-git --repository <dir>
Lifecycle Management
Section titled “Lifecycle Management”McpServerLifecycle manages the MCP server subprocess lifecycle. It implements
AutoCloseable for use with try-with-resources:
try (McpServerLifecycle server = McpToolFactory.filesystem(projectDir)) { server.start(); // Spawn subprocess, initialize MCP protocol List<AgentTool> tools = server.tools(); // List and cache tools boolean alive = server.isAlive(); // Check health
// ... use tools ...
} // Automatically closes the server on exitState machine:
+-- close() ----+ v |CREATED --start()--> STARTED --close()--> CLOSED | ^ | | +------- start() ----+ +-- close() -> CLOSED (close before start is safe)start()spawns the server subprocess and performs a health check; idempotent when already running, and revivable — calling it afterclose()spawns a fresh subprocess and rebuilds the connection.tools()lists available tools (cached within a session). The cache is dropped insideclose(), so aclose()->start()->tools()sequence relists from the fresh connection.close()shuts down the server; idempotent and safe beforestart()(an unstarted-then-closed lifecycle stays inCLOSED; callingstart()afterwards spawns a fresh subprocess).isAlive()/isRunning()return true when started and not yet closed.
Tool instances returned by tools() survive a close/restart cycle: they look up the
active McpClient through a supplier on every call, so an Agent built once with
fs.tools() keeps working after fs.close() followed by fs.start().
Long-running processes
Section titled “Long-running processes”For loops and request handlers that build an ensemble per iteration, do not wrap the
inner run() in try-with-resources around the lifecycle — closing per iteration would
spawn a new npx subprocess every time. Instead, keep the lifecycle at the process
scope, or bind it to the ensemble (next section).
Binding the Lifecycle to an Ensemble
Section titled “Binding the Lifecycle to an Ensemble”Long-running ensembles can take ownership of an MCP server lifecycle so it is started
the moment .managedResource(fs) runs in the builder (a synchronous side effect of the
builder method, not deferred to .build() or start(int)) and closed during
Ensemble.stop():
McpServerLifecycle fs = McpToolFactory.filesystem(projectDir);
Ensemble kitchen = Ensemble.builder() .chatLanguageModel(model) .webDashboard(WebDashboard.onPort(7329)) .managedResource(fs) // started immediately; closed on stop() .scheduledTask(ScheduledTask.builder() .name("hourly-report") .task(Task.builder() .description("Summarize today's filesystem changes") .tools(fs.tools()) // tools() works because managedResource() started fs .build()) .schedule(Schedule.every(Duration.ofHours(1))) .build()) .build();
kitchen.start(7329); // fs is already running; scheduled tasks fire against it// ... fs stays alive across every scheduled firing ...kitchen.stop(); // fs is closed (ensemble owns the lifecycle)Ownership rule (mirrors webDashboard()): a resource that is already running
when registered is treated as caller-owned and is never closed by the ensemble. A
not-yet-running resource is owned by the ensemble and is closed during stop().
McpServerLifecycle implements ManagedResource; any other start()/close()-style
resource can be wired the same way.
Mixing MCP Tools with Java Tools
Section titled “Mixing MCP Tools with Java Tools”MCP tools and Java tools produce standard AgentTool instances. They can be freely
mixed in a single agent’s tool list:
// MCP tools for filesystem operationstry (McpServerLifecycle fs = McpToolFactory.filesystem(workDir)) { fs.start();
// Combine MCP tools with Java tools by name List<Object> allTools = new ArrayList<>(fs.tools()); allTools.add(new CalculatorTool()); allTools.add(WebSearchTool.ofTavily(apiKey));
var agent = Agent.builder() .role("Developer") .tools(allTools) .llm(chatModel) .build();}Coding Agents with MCP
Section titled “Coding Agents with MCP”MCP tools integrate naturally with the agentensemble-coding module. Start the
filesystem and git servers, then combine their tools into a coding agent:
import net.agentensemble.coding.CodingTask;import net.agentensemble.mcp.McpToolFactory;import net.agentensemble.mcp.McpServerLifecycle;
try (McpServerLifecycle fs = McpToolFactory.filesystem(projectDir); McpServerLifecycle git = McpToolFactory.git(projectDir)) { fs.start(); git.start();
List<Object> mcpTools = new ArrayList<>(); mcpTools.addAll(fs.tools()); mcpTools.addAll(git.tools());
Agent agent = Agent.builder() .role("Senior Software Engineer") .goal("Implement, debug, and refactor code with precision") .tools(mcpTools) .llm(model) .maxIterations(75) .build();
Task task = CodingTask.fix("Fix the failing authentication test") .toBuilder().agent(agent).build();
EnsembleOutput output = Ensemble.run(model, task);}For the full walkthrough, see the MCP Coding Example and the Coding Agents Guide.
Complete Example
Section titled “Complete Example”import net.agentensemble.Agent;import net.agentensemble.Ensemble;import net.agentensemble.Task;import net.agentensemble.mcp.McpToolFactory;import net.agentensemble.mcp.McpServerLifecycle;
// Start MCP filesystem server scoped to the project directorytry (McpServerLifecycle fs = McpToolFactory.filesystem(Path.of("/my/project"))) { fs.start();
Agent coder = Agent.builder() .role("Software Engineer") .goal("Read code and answer questions about it") .tools(fs.tools()) .llm(chatModel) .maxIterations(20) .build();
Task task = Task.builder() .description("Find the main entry point of the application and explain what it does") .build();
var result = Ensemble.builder() .agent(coder) .task(task) .build() .run();
System.out.println(result.output());}