MCP and Callable Goals

ReActMCP combines structured model tool calls with remote MCP tools and ordinary ASTRA callable goals. It extends ReActTools, which shares ReActCore turn control with the textual ReAct agent, and is provided by astra-langchain4j-tools.

Local goal registration

Define a callable achievement goal and explicitly register its contract with the tool catalogue:

import astra.langchain4j.reasoning.*;

agent MainMCP extends ReActMCP {
    @Callable(description="Double an integer", inputs=["value"], outputs=["result"])
    rule +!double(int value, int result) { result = value * 2; }

    rule +!configure() {
        tools.registerCallables();
        mcp.connectHttp("math", "http://127.0.0.1:8090/mcp", tools.catalog());
    }
}

Configure model using the usual provider configuration, adopt !solve(question, string answer), and call mcp.stop() when retiring the integration. The complete runnable MCP examples include a deterministic model and a small local server, so they need no API key. They also include a standalone MainGoals agent with no MCP interaction.

The model sees local double and remote math__add. If it selects double with {"value":7}, the local branch in ReActMCP performs:

GoalInvocation call = tools.invocation(session);
invoke(call);
tools.result(session, tools.resultJson(call), false);

The returned output is {"result":14}. Java prepares and reads the invocation; ASTRA performs the native subgoal execution and applies ordinary guards and failure handling. There is no wrapper rule to write in the application.

Callable JSON contracts support scalar inputs and outputs (string, char, int, long, float, double, boolean) and lists containing JSON scalar values or nested lists. Every input is required. Nulls, extra inputs, duplicate JSON fields, incompatible types and out-of-range numbers are rejected. Other ASTRA parameter types can exist in general callable metadata, but cannot be imported into this JSON tool catalogue. Overloads that produce the same model tool name are rejected rather than selected ambiguously. Catalogue registration is atomic; register once after the agent is configured.

Remote tools and conversation control

mcp.connectHttp(alias, url, tools.catalog()) uses MCP Streamable HTTP. mcp.connectStdio(alias, commandList, tools.catalog()) starts a stdio server. Remote tool names are advertised as alias__tool; execution sends the original name to the server. Each solve takes a catalogue snapshot and its own message history. ReActMCP executes every requested call sequentially, preserving model call IDs, and records all results before the next model turn.

Network I/O uses a bounded executor and delivers native $mcp.completed events. The intention waits for its correlated result without blocking the agent’s other intentions. Tool failures become error observations; model failures propagate through normal goal failure. The inherited maxSteps belief bounds model turns.

Execution has a 30-second deadline. mcp.cancel(call), mcp.disconnect(alias) and mcp.stop() complete pending calls once and discard late replies. Local cancellation cannot guarantee reversal or interruption of an already executing remote tool. Disconnecting and reconnecting an alias does not retarget an existing conversation to its replacement connection.

This integration supports MCP tools, not MCP resources or prompts. It currently passes structured content as JSON text and otherwise uses the SDK’s extracted text content; full multimodal content is not exposed. Existing manually registered tools can still use handwritten !invoke handlers for compatibility.