Fetching latest headlines…
Gateway Talents in Solon AI: OpenAPI, Tool, and MCP Surfaces That Scale Without Context Blow-Up
NORTH AMERICA
🇺🇸 United StatesJuly 19, 2026

Gateway Talents in Solon AI: OpenAPI, Tool, and MCP Surfaces That Scale Without Context Blow-Up

6 views0 likes0 comments
Originally published byDev.to

When an agent can see 80 tools at once, the model does not get smarter. It gets noisier. Token bills climb, tool choice drifts, and a simple “check order status” prompt suddenly carries half of your company’s OpenAPI surface.

Solon AI (v4.0.3) answers that with the Gateway Talent trio in solon-ai-talent-gateway:

Talent Source of tools Unit of management
OpenApiGatewayTalent OpenAPI / Swagger docs one API source
ToolGatewayTalent local / MCP FunctionTools one tool, runtime add/remove
McpGatewayTalent MCP client connections one MCP server, plus allow/deny lists

All three share the same four-stage adaptive discovery model. Small catalogs stay fully expanded. Large catalogs fold into summary → name list → search, so the LLM only pays for what it needs.

This article sticks to official APIs from article/1353, article/1389, article/1335, and article/1293.

Why a gateway is a Talent, not “just more tools”

In Solon AI, a Tool is an atomic function. A Talent packages tools with instruction, activation, and SOP-style constraints.

Gateways need that packaging:

  • too many raw schemas blow up context
  • tools from different systems need grouping and lifecycle
  • the model should discover capability step by step, not swallow everything on turn 1

That is why you hang gateways with defaultTalentAdd(...) (or request-scoped talentAdd), not by dumping every OpenAPI operation into defaultToolAdd.

<dependency>
    <groupId>org.noear</groupId>
    <artifactId>solon-ai-talent-gateway</artifactId>
</dependency>

MCP features also need solon-ai-mcp. OpenAPI parsing pulls in swagger-parser (v2 and v3); exclude it only if you never load docs.

The four stages (shared by all three gateways)

Stage Trigger (defaults) What the model sees Proxy tools exposed
FULL count <= dynamicThreshold (8) full tool schemas original tools (OpenAPI collapses to call_api)
SUMMARY 8 < count <= listThreshold (30 OpenAPI / 40 others) name + description (+ endpoint) get_*_detail + call_*
LIST listThreshold < count <= searchThreshold (100) grouped names only search_* + get_*_detail + call_*
SEARCH count > 100 no catalog dump forced search path: search_* → detail → call

Design rule from the docs: when the catalog is small, embed full schemas in the instruction so the model can call in one step; when it grows, fold information and force progressive discovery.

Shared knobs:

Method Default Notes
dynamicThreshold(n) 8 FULL ceiling
listThreshold(n) 30 (OpenAPI) / 40 (others) SUMMARY ceiling
searchThreshold(n) 100 LIST ceiling
retryConfig(maxRetries) 3 shared
retryConfig(maxRetries, retryDelayMs) 3, 1000 McpGatewayTalent delay form
maxContextLength(n) 8000 OpenApiGatewayTalent response truncate

1. OpenApiGatewayTalent — turn Swagger into an agent surface

Use this when tools already exist as OpenAPI / Swagger documents (remote http:// or local classpath:).

Capabilities called out officially:

  • Swagger 2.0 + OpenAPI 3.0 auto detection
  • multi-source load with tag-based grouping
  • $ref expansion and circular-ref markers
  • path placeholder replacement with URL encoding
  • response truncation (maxContextLength)
  • per-source allowedTools / disallowedTools
  • ApiAuthenticator (bearer / apiKey / custom)
  • skip @Deprecated operations
import org.noear.solon.ai.agent.react.ReActAgent;
import org.noear.solon.ai.talents.gateway.OpenApiGatewayTalent;
import org.noear.solon.ai.talents.gateway.openapi.ApiAuthenticator;
import org.noear.solon.ai.talents.gateway.openapi.ApiSource;

import java.time.Duration;
import java.util.Arrays;

OpenApiGatewayTalent apiTalent = new OpenApiGatewayTalent()
        .dynamicThreshold(5)
        .listThreshold(30)
        .searchThreshold(100)
        .maxContextLength(10_000)
        .defaultTimeout(Duration.ofSeconds(30))
        .defaultAuthenticator(ApiAuthenticator.bearer("your-token"));

// simple multi-source mount
apiTalent.addApi("http://order-service:8081/v3/api-docs", "http://order-service:8081");
apiTalent.addApi("classpath:swagger/user-api.json", "http://user-service:8082");

// fine-grained source
ApiSource pay = new ApiSource();
pay.setDocUrl("http://pay-service:8083/v3/api-docs");
pay.setApiBaseUrl("http://pay-service:8083");
pay.setAllowedTools(Arrays.asList("createPayment", "queryPayment"));
pay.setAuthenticator(ApiAuthenticator.bearer("pay-service-token"));
pay.setTimeout(Duration.ofSeconds(60));
apiTalent.addApi(pay);

ReActAgent agent = ReActAgent.of(chatModel)
        .role("Senior business assistant")
        .instruction("Prefer the provided business APIs. Do not invent path or body fields.")
        .defaultTalentAdd(apiTalent)
        .build();

// runtime lifecycle
apiTalent.removeApi("http://order-service:8081/v3/api-docs");
apiTalent.refreshApi("http://pay-service:8083/v3/api-docs");
apiTalent.refreshApiAll();

Built-in OpenAPI proxy tools by stage:

Tool Modes Role
call_api all execute REST call (path/query/body/multipart)
get_api_detail SUMMARY / LIST / SEARCH full parameter schema
search_apis LIST / SEARCH multi-keyword AND search

Auth priority: source authenticator > defaultAuthenticator.

Runtime permission edits happen on the ApiSourceClient copy, then refreshApi(...):

var client = apiTalent.getApiSource("http://pay-service:8083/v3/api-docs");
client.setAllowedTools(Arrays.asList("createPayment"));
client.getDisallowedTools().add("queryRefund");
apiTalent.refreshApi("http://pay-service:8083/v3/api-docs");

2. ToolGatewayTalent — govern local (and mixed) FunctionTools

Use this when tools live in code — AbsToolProvider, single FunctionTool, or already-fetched MCP tools — and you need runtime, imperative add/remove at tool granularity.

import org.noear.solon.ai.talents.gateway.ToolGatewayTalent;

ToolGatewayTalent toolGateway = new ToolGatewayTalent()
        .dynamicThreshold(8)
        .listThreshold(40)
        .searchThreshold(100);

toolGateway.addTool("Finance", financeToolProvider);
toolGateway.addTool("HR", hrToolProvider);
toolGateway.addTool("General", singleFunctionTool);

ReActAgent agent = ReActAgent.of(chatModel)
        .role("Enterprise ops assistant")
        .defaultTalentAdd(toolGateway)
        .build();

Built-in proxy tools:

Tool Modes Role
call_tool SUMMARY / LIST / SEARCH proxy execute by tool_name + tool_args
get_tool_detail SUMMARY / LIST / SEARCH full JSON Schema
search_tools LIST / SEARCH keyword search

Important FULL-mode note from the docs: original business tools are exposed directly to the LLM; the proxy trio appears only after the catalog leaves FULL.

3. McpGatewayTalent — many MCP servers, one agent

Use this when tools arrive as MCP services and you prefer managing connections (with optional allow/deny lists) over hand-picking every tool at runtime.

import org.noear.solon.ai.mcp.client.McpServerParameters;
import org.noear.solon.ai.talents.gateway.McpGatewayTalent;

import java.util.Arrays;

McpGatewayTalent mcpGateway = new McpGatewayTalent()
        .dynamicThreshold(8)
        .listThreshold(40)
        .searchThreshold(100)
        .retryConfig(3, 1000);

mcpGateway.addMcpServer("weather", new McpServerParameters().then(e -> {
    e.setTransport("stdio");
    e.setCommand("npx");
    e.addArgVar("-y");
    e.addArgVar("@modelcontextprotocol/server-weather");
}));

mcpGateway.addMcpServer("database", new McpServerParameters().then(e -> {
    e.setTransport("stdio");
    e.setCommand("npx");
    e.setArgs(Arrays.asList("-y", "@modelcontextprotocol/server-database"));
    e.setAllowedTools(Arrays.asList("query", "list_tables"));
    e.setDisallowedTools(Arrays.asList("drop_table", "execute_raw"));
}));

// alternative: pass an existing McpClientProvider
// mcpGateway.addMcpServer("database", provider);

ReActAgent agent = ReActAgent.of(chatModel)
        .role("Ops agent with remote MCP tools")
        .defaultTalentAdd(mcpGateway)
        .build();

mcpGateway.removeMcpServer("weather");      // remove + close connection
mcpGateway.refreshMcpServer("database");    // after allow/deny changes
mcpGateway.refreshMcpServerAll();

Official lifecycle details worth keeping:

  • refresh uses a shadow swap (add new tools, then remove old ones) so in-flight calls do not see a blank tool table
  • removeMcpServer closes the underlying connection
  • disable via McpClientProvider.setEnabled(false) only drops the tool index; the provider stays alive until re-enabled + refresh

Proxy tool names match ToolGatewayTalent: call_tool / get_tool_detail / search_tools.

Choosing the right gateway (and the common MCP confusion)

Pick by ingress shape and mutation unit:

If your tools… Prefer
are OpenAPI / Swagger documents OpenApiGatewayTalent
are code-side FunctionTools you add/remove at runtime ToolGatewayTalent
come from MCP servers you want to attach as wholes McpGatewayTalent

Common mistake: “I need per-tool MCP control, so I must use ToolGatewayTalent.”

Not required. McpGatewayTalent already supports tool-level exposure through allowedTools / disallowedTools on McpServerParameters. After changing lists, call refreshMcpServer(...).

Real split:

  • ToolGatewayTalent = runtime, imperative, single-tool mutations
  • McpGatewayTalent = connection lifecycle + declarative allow/deny at attach/refresh time

You can also hang more than one gateway Talent on the same agent when sources differ.

Production checklist from the official notes

  1. Unique tool/operation names across sources — stored lower-case, case-insensitive at call time.
  2. Path params must use {name} — gateway URL-encodes replacements; leftover {xxx} fails loudly.
  3. Deprecated OpenAPI ops are skipped automatically.
  4. Disabled sources (ApiSource.setEnabled(false) / provider setEnabled(false)) stay registered for management but leave the tool index until re-enabled + refresh.
  5. Permission edits need refresh — changing allow/deny on a client copy is not enough by itself.
  6. Thresholds are product decisions — lower dynamicThreshold earlier if FULL schemas already dominate context; raise it only when the catalog is tiny and high-precision one-shot calls matter.

Minimal “when do I leave plain tools?” rule

Stay on AbsToolProvider + @ToolMapping + defaultToolAdd while the set is small and always relevant.

Move to a Gateway Talent when any of these becomes true:

  • dozens of OpenAPI operations from multiple services
  • MCP servers that should not dump full schemas every turn
  • runtime plugin-style tool catalogs that grow and shrink
  • you need progressive discovery instead of “show everything”

Gateways do not replace ReAct planning, HITL, or session memory. They solve a narrower, painful problem: keep tool surfaces usable as they scale.

Official references

Solon version used for this write-up: v4.0.3.

Comments (0)

Sign in to join the discussion

Be the first to comment!