Skip to content

feat: mcpToolSearch config - #597

Merged
ericdallo merged 2 commits into
editor-code-assistant:masterfrom
rschmukler:add-search-tools
Sep 11, 2026
Merged

feat: mcpToolSearch config#597
ericdallo merged 2 commits into
editor-code-assistant:masterfrom
rschmukler:add-search-tools

Conversation

@rschmukler

Copy link
Copy Markdown
Contributor

Every tool sent to the LLM costs context on every request: its description plus its full input schema. With a few MCP servers connected that is thousands of tokens the model rarely needs.

Add mcpToolSearch, which trades that upfront cost for an extra round trip:

"mcpToolSearch": {
  "includePattern": [".*"],
  "excludePattern": ["clojure-mcp"]
}

Matching MCP tools are deferred - their schemas are withheld and only a compact catalog of names and truncated descriptions is rendered into the system prompt. When the model needs one it calls the new eca__search_tools tool, which ranks the deferred catalog against a query, returns the matches with their input schemas, and records them on the chat so they are sent as regular tools from then on.

Deferred tools are only withheld from the request payload; they stay resolvable and callable throughout, so a tool call that arrives before the search still executes.

Only MCP tools can be deferred. Native ECA tools are the agent's baseline capabilities, so a catch-all pattern never takes them away - use disabledTools to remove one of those. eca__search_tools is likewise never deferred, and is only offered to the model when at least one tool is actually deferred.

Pattern matching reuses the existing disabledTools engine rather than introducing a third dialect alongside it and the exact-match approval selectors: anchored Java regex against the builtin tool name or the server__tool full name, or an exact server name for all of its tools. That matcher is extracted as tool-entry-matches? and its regex compilation is now memoized, which also lets it warn once when an entry fails to compile - * is not a valid regex and was previously matched literally in silence.

Configurable globally, per agent, and in agent markdown frontmatter, where the object may be abbreviated to a list or string when only includePattern is needed:

mcpToolSearch:
  - github__.*

The static prompt cache signature now tracks the deferrable set separately from the tool list, so changing the patterns mid-chat rebuilds the catalog.

  • I added a entry in changelog under unreleased section.
  • This is not an AI slop.

@rschmukler
rschmukler marked this pull request as ready for review September 10, 2026 19:31
Comment thread src/eca/llm_api.clj Outdated
tools (when (:tools model-capabilities) tools)
;; Deferred tools stay callable but their schemas are withheld until the
;; model loads them with eca__search_tools.
tools (when (:tools model-capabilities)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this will work only for the first request, when LLM enters the loop, and need to request again we will send all tools, cna you check that? worth a test for that too

Comment thread src/eca/config.clj Outdated
:skills []
:extraConfigs []
:disabledTools []
:mcpToolSearch {:includePattern []

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we defer all by default? I believe cursor and others are doing this no? as I believe it helps for most cases

@rschmukler
rschmukler force-pushed the add-search-tools branch 2 times, most recently from b162935 to 0d15c26 Compare September 11, 2026 19:40
Comment thread CHANGELOG.md Outdated
Comment on lines +5 to +6
- Add `mcpToolSearch` config to keep MCP tool schemas out of context until the LLM loads them with the new `eca__search_tools` tool.
- Fix deferred MCP tools being re-sent from the second request of a tool-call loop onward, undoing the context saving as soon as the model called any tool.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we just need one single line of changelog

Every tool sent to the LLM costs context on every request: its
description plus its full input schema. With a few MCP servers
connected that is thousands of tokens the model rarely needs.

Add `mcpToolSearch`, which trades that upfront cost for an extra round
trip:

    "mcpToolSearch": {
      "deferAllWhenTotalTokensExceedPercentOfContext": 10,
      "includePattern": [".*"],
      "excludePattern": ["clojure-mcp"]
    }

Matching MCP tools are deferred - their schemas are withheld and only a
compact catalog of names and truncated descriptions is rendered into the
system prompt. When the model needs one it calls the new
`eca__search_tools` tool, which ranks the deferred catalog against a
query, returns the matches with their input schemas, and records them on
the chat so they are sent as regular tools from then on.

A tool is deferred when the MCP definitions as a whole outgrow
`deferAllWhenTotalTokensExceedPercentOfContext` of the model context
window, or when it matches `includePattern`, and in both cases only if
it does not match `excludePattern`. That limit is a percentage rather
than a token count so one setting behaves sensibly on a 32k local model
and on a 1M one, and it is null by default: nothing is deferred until
asked for. Native tool definitions are left out of the total, so the
limit tracks what MCP actually adds.

Deferred tools are only withheld from the request payload; they stay
resolvable and callable throughout, so a tool call that arrives before
the search still executes.

Every provider rebuilds the next request of a tool-call loop from the
tool list its `on-tools-called` callback returns, shadowing the one the
initial payload filtered, so that list is filtered too. Without it
deferred schemas came back as soon as the model called any tool, which
is most of a turn. Wrapped in `sync-or-async-prompt!` rather than
`prompt!` because the sync path invokes the callback itself, without
going through it, and covered by an integration test asserting on the
continuation request.

Only MCP tools can be deferred. Native ECA tools are the agent's
baseline capabilities, so a catch-all pattern never takes them away -
use `disabledTools` to remove one of those. `eca__search_tools` is
likewise never deferred, and is only offered to the model when at least
one tool is actually deferred.

Pattern matching reuses the existing `disabledTools` engine rather than
introducing a third dialect alongside it and the exact-match approval
selectors: anchored Java regex against the builtin tool name or the
`server__tool` full name, or an exact server name for all of its tools.
That matcher is extracted as `tool-entry-matches?` and its regex
compilation is now memoized, which also lets it warn once when an entry
fails to compile - `*` is not a valid regex and was previously matched
literally in silence.

Configurable globally, per agent, and in agent markdown frontmatter,
where the object may be abbreviated to a list or string when only
`includePattern` is needed:

    mcpToolSearch:
      - github__.*

The static prompt cache signature now tracks the deferrable set
separately from the tool list, so changing the patterns mid-chat
rebuilds the catalog.
@ericdallo
ericdallo enabled auto-merge September 11, 2026 20:07
@ericdallo
ericdallo merged commit e300263 into editor-code-assistant:master Sep 11, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants