MCP and Agent Integration in Practice (End-to-End)
This page is not "weather assistant demo copy"; it breaks open the execution chain after MCP actually enters the Agent runtime.
There is only one question to answer:
How does a third-party MCP service travel from the config file into the Agent reasoning loop and ultimately get called by the model?
1. First, the full execution chain
In AI4J, wiring MCP into an Agent passes through at least 7 layers:
mcp-servers-config.jsonMcpGateway.initialize(...)McpClient.connect()McpGatewayToolRegistry.refresh(...)ToolUtil.getAllTools(functionList, mcpServerIds)- The Agent runtime exposes the tool schema to the model
ToolUtil.invoke(...)then routes the call back to the gateway/client
Without breaking these 7 layers apart, many docs reduce "the Agent can call MCP" to an empty phrase.
2. Scenario setup
Suppose we wire in a third-party weather service:
- serviceId:
weather-http - transport:
streamable_http - tool:
query_weather - Agent runtime: ReAct
- Goal: the model decides whether to call the weather tool based on the user's question
Configuration:
{
"mcpServers": {
"weather-http": {
"type": "streamable_http",
"url": "http://127.0.0.1:8000/mcp",
"protocolProfile": "AUTO",
"enabled": true
}
}
}
Here weather-http is not a comment but the real serviceId the Agent allowlist will use later. protocolProfile is read by the Gateway configuration model; the default AUTO only performs a limited compatibility probe via server/discover against Streamable HTTP and will not automatically switch the endpoint to HTTP+SSE.
3. Segment 1: Wiring the third-party service into the host
McpGateway gateway = McpGateway.getInstance();
gateway.initialize("mcp-servers-config.json").join();
After this step, what actually happens is not as simple as "the gateway is ready"; rather:
- The gateway reads the configuration
McpGatewayClientFactorycreates the transport based ontype- An
McpClientis created forweather-http client.connect()runs the lifecycle corresponding to the profile: AUTO discovers a modern peer or enters the legacy initialization handshaketoolRegistry.refresh(...)pulls the tool list
It is recommended to perform an explicit check right at this step:
List<Tool.Function> gatewayTools = gateway.getAvailableTools().join();
System.out.println(gatewayTools);
If no tools are obtained here, no amount of calling by the Agent later will succeed.
4. Segment 2: Projecting MCP tools onto the Agent's visible surface
The Agent does not read the gateway's internal state directly; it obtains the tool set for the current request via ToolUtil.getAllTools(...).
Building an Agent typically looks like:
Agent agent = Agents.react()
.modelClient(modelClient)
.model("doubao-seed-1-8-251228")
.systemPrompt("你是天气助手,必要时必须调用工具后再回答。")
.toolRegistry(Collections.<String>emptyList(), Arrays.asList("weather-http"))
.options(AgentOptions.builder().maxSteps(4).build())
.build();
The most important thing here is not the syntax but the allowlist semantics:
functionListis empty, meaning no local Function tools are exposed this timemcpServices = ["weather-http"], meaning only this one MCP service's tools are exposed
AI4J does not automatically expose every service registered in the gateway to the model just because many are connected.
5. Segment 3: The actual path when the model issues a tool call
After the model decides to call a tool during reasoning, it does not touch McpClient directly; it first returns to ToolUtil.invoke(...).
In the MCP scenario, the key branches are:
ToolUtil.invoke(functionName, argument)- If it matches a user tool prefix, try
gateway.callUserTool(...)first - Otherwise, enter local MCP / Function / global gateway tool dispatch
- Remote third-party MCP eventually lands at
gateway.callTool(...) - The gateway finds
weather-httpvia thetool -> clientmapping McpClient.callTool(...)issuestools/call
Therefore, what the Agent sees is the tool schema, but actual execution is still backed by the Core SDK's MCP runtime.
6. Segment 4: How results return to the model
After McpClient.callTool(...) receives the MCP response, it will:
- Parse the return content of
tools/call - Convert it to a string result
- Return it to
ToolUtil - Hand it back to the Agent runtime
- The Agent puts the tool result back into the model context
- The model continues generating the final answer
In other words, the MCP tool plays two roles inside the Agent:
- First, exposed to the model as a schema
- Then, fed back to the model as a tool result
This aligns with the role of local Function tools inside the Agent, except there is an extra MCP protocol link in between.
7. A minimal runnable example
McpGateway gateway = McpGateway.getInstance();
gateway.initialize("mcp-servers-config.json").join();
Agent agent = Agents.react()
.modelClient(modelClient)
.model("doubao-seed-1-8-251228")
.systemPrompt("你是天气助手,必须先调用工具再回答。")
.toolRegistry(Collections.<String>emptyList(), Arrays.asList("weather-http"))
.options(AgentOptions.builder().maxSteps(4).build())
.build();
AgentResult result = agent.run(AgentRequest.builder()
.input("请查询北京今天天气,并给出穿衣建议")
.build());
System.out.println(result.getOutputText());
This code validates the entire closed loop, not some local API.
8. Why the gateway sometimes has tools but the Agent can't see them
This is the most common integration misjudgment.
The troubleshooting order should be:
- whether
gateway.isInitialized()istrue - whether
gateway.getAvailableTools()can list tools - whether
toolRegistry(..., mcpServices)was passed the correctserviceId - whether serviceId and toolName were conflated
mcpServices takes service IDs, not tool names.
9. Why the model sometimes doesn't trigger a tool
If gateway and Agent registration are both fine but the model still doesn't use the tool, the troubleshooting order should be:
- whether the tool description lets the model understand what it can do
- whether the
systemPromptexplicitly requires calling the tool when necessary - whether
maxStepsis too small - whether the user's question actually needs the tool
This is no longer an MCP connection issue but a runtime prompt and reasoning strategy issue.
10. How the multi-tenant scenario enters this chain
If the same Agent host must serve multiple users, and each user binds a different third-party MCP, wire it like this:
gateway.addUserMcpClient("u1001", "weather-http", userClient).join();
The call chain then becomes:
- The Agent session binds a
userId ToolUtiltries user-level tools first- The gateway looks up
user_{userId}_tool_{toolName} - On a hit, it goes through the user's dedicated client
- On a miss, it falls back to global tools
You must think through the permission boundary yourself here:
- The default implementation allows fallback
- Strong-isolation scenarios usually should not fall back
11. Where to add tracing and diagnostics
MCP + Agent problems are often not "wrong" but "don't know where it's stuck".
It is recommended to observe at least these points:
- whether gateway initialization succeeded
- whether the tool appears in the Agent's exposed list
- whether the model actually issued a tool call
- whether the tool call returned successfully
If the Agent runtime has tracing enabled, focus on:
- RUN
- MODEL
- TOOL
This lets you quickly distinguish:
- whether the model didn't decide to call
- or the MCP tool execution failed
12. A minimal assertion that resembles a regression test
@Test
public void test_mcp_agent_e2e() {
McpGateway gateway = McpGateway.getInstance();
gateway.initialize("mcp-servers-config.json").join();
List<Tool.Function> tools = gateway.getAvailableTools().join();
Assert.assertFalse(tools.isEmpty());
Agent agent = Agents.react()
.modelClient(modelClient)
.model("doubao-seed-1-8-251228")
.toolRegistry(Collections.<String>emptyList(), Arrays.asList("weather-http"))
.options(AgentOptions.builder().maxSteps(4).build())
.build();
AgentResult result = agent.run(AgentRequest.builder().input("北京天气").build());
Assert.assertNotNull(result);
Assert.assertNotNull(result.getOutputText());
}
The point is not to assert specific output text, but to assert:
- the gateway has tools
- the Agent can execute
- the final answer is non-empty
13. The takeaway to remember from this page
MCP entering the Agent is not "sticking a third-party tool name at the model".
It is in fact a chain spanning 3 layers:
- The Core SDK's MCP connection and governance layer
- The ToolUtil tool projection and call dispatch layer
- The Agent runtime's reasoning and tool consumption layer
Understanding these 3 layers separately makes end-to-end issues much easier to locate.