Skip to content

Lazy mode

By default (expose: all) the gateway mounts every downstream tool as server__tool, and every agent session loads the full catalog — each tool's name, description, and input schema — into context before a single call is made. With a dozen servers behind the gateway, that's hundreds of tool definitions paid for on every session, whether or not they get used.

expose: lazy flips the trade: the gateway advertises only its eight meta-tools (plus anything you pin). The downstream catalog stays fully available, but agents reach it on demand — route their current task to a capability, inspect the one tool they need, and invoke it through the gateway. A compact capability summary and per-server use_when hints make unpinned tools discoverable without loading their full schemas. The context cost of connecting N servers stays bounded and far below mounting every tool.

Turning it on

Set the top-level expose key in mcphub.yaml:

yaml
version: 1

# all  (default) — mount every downstream tool as 'server__tool'
# lazy           — advertise only mcphub's meta-tools; agents resolve or search
#                  capabilities and invoke through call_tool
expose: lazy

You can also flip exposure with x in the Studio TUI.

The setting is read when the gateway starts, so restart your agents (each one runs its own mcphub mcp serve) to pick it up. No mcphub sync is needed — the agent's harness entry is unchanged; only what the gateway advertises changes.

Lazy mode is a gateway feature

expose only affects agents in gateway mode. An agent in direct mode talks to each server itself, so it always loads every enabled server's full tool list. See Concepts for the two modes.

A lean profile for small local models

You can keep the full in-scope lazy catalog while exposing a minimal advertised surface to one gateway agent:

yaml
expose: lazy

agents:
  local-agent:
    type: local-agent
    path: ~/.config/local-agent/config.yaml
    mode: gateway
    pin:
      - bob__context
    tool_schema_budget: "8KB"

pin overrides any global pins for this agent. tool_schema_budget: "8KB" admits complete pinned definitions up to that serialized-byte budget. Neither setting removes downstream call authority: after resolving or searching, the agent can still use call_tool for every tool permitted by its servers and tools scope. The gateway never truncates a schema to fit a budget. Use pin: [] and tool_schema_budget: "0" only when you want the agent to load zero downstream definitions — that combination advertises only the eight management tools.

Legacy tool names

When a downstream server self-prefixes its tools (Bob reports bob_context, Hitspec reports hitspec_fetch), the gateway strips that redundant prefix for the advertised name (bob__context, hitspec__fetch). The legacy stutter form (bob__bob_context) remains accepted as an alias for one release.

Run mcphub sync to preview the generated harness entry and mcphub sync --write to apply it. Restart the harness afterward so it launches mcphub mcp serve --agent local-agent with the new policy.

The eight meta-tools

In lazy mode this is the entire advertised surface:

ToolWhat it does
list_serversList configured downstream servers with enabled/connected state and tool counts.
search_toolsRanked natural-language search across tool and server metadata; returns up to 20 matching server__tool names by default.
describe_toolReturn one tool's description and full JSON input schema.
resolve_toolRoute the current goal or activity to the best tool in one call, with match evidence, required fields, an argument template, alternatives, and an ambiguity flag.
call_toolInvoke a downstream tool by name — how everything gets called in lazy mode. detach: true runs a long-running tool in the background and returns a callId at once.
get_resultPage through an oversized result the gateway stored locally (see below).
poll_resultCheck a detached call by callId and collect its result when done.
statsLocal usage intelligence: calls, errors, estimated token cost, per-server breakdown.

Hosts that prefix the configured server name expose these as mcphub__list_servers. The older mcphub_* wire names remain accepted.

The gateway's MCP instructions tell the connecting model it is in lazy mode, include a compact summary of its in-scope capability families, and ask it to resolve context at the start of a non-trivial task and whenever work changes phase (research → planning → implementation → verification). Harnesses that pass MCP server instructions to their model can therefore discover and call unpinned tools proactively.

Give each server one or more concise routing hints when its name or tool names do not communicate the user outcome:

yaml
servers:
  hitspec:
    command: hitspec
    args: [mcp, serve, --workspace, /absolute/api-workspace]
    enabled: true
    description: Bounded HTTP fetches and saved-request validation
    tags: [http, api, markdown]
    use_when:
      - fetch a public HTTP URL as raw, text, Markdown, or JSON
      - list or validate saved .http and .hitspec requests

These hints do not mount or pin anything. They are lightweight vocabulary for the resolver and search index.

Catalog changes and reconnects

The gateway watches unavailable downstreams and reconnects them in the background. It also refreshes the catalog when a connected downstream reports that its tool list changed. A successful refresh updates the mounted surface and emits the MCP tool-list-change notification, so clients that cache a listing or resolver result should obtain a fresh one when the catalog revision changes.

A failed call is different from a successful refresh: its outcome is unknown. mcphub does not replay that request, because the downstream may already have performed a mutation before the transport failed. Reconnect restores future calls; it is not evidence about the failed one.

The discovery loop

A lazy-mode agent works the catalog in three steps.

1. Route or browse. Prefer the contextual resolver when the agent knows what it is doing but not which server owns the capability:

json
// resolve_tool
{ "query": "fetch this public URL as Markdown for research" }

It tokenizes the full sentence and ranks the connected, in-scope catalog across tool names, titles, descriptions, bounded top-level input field names, plus server names, descriptions, tags, and use_when hints. For browsing, use the same natural-language query with mcphub_search_tools:

json
// mcphub_search_tools
{ "query": "semantic search across this repository", "max_hits": 10 }

The response reports total count, bounded returned, truncated, and ranked matches with their namespaced (server__tool) name, server/tool metadata, score, and matched terms. max_hits defaults to 20 and is capped at 100. A 2,048-byte query cap and 12 KiB compact match-array budget prevent discovery itself from becoming a context spike; byte_limited and metadata_truncated report those bounds.

2. Inspect. Two options, depending on how much the agent already knows:

  • describe_tool takes {server, tool} — or just tool in the combined server__tool form — and returns the tool's description and full JSON input_schema, enough to construct a valid call.
  • resolve_tool already collapses route + describe into one round trip: give it a natural-language query (and optionally max_hits, default 5) and it returns one recommendation with required_fields and a ready-to-fill argument_template, a list of alternatives, and an ambiguous flag when several tools ranked equally. If argument_template_truncated is true, use describe_tool for the complete schema before the call.

3. Invoke. Call through the gateway:

json
// call_tool
{ "server": "vecgrep", "tool": "vecgrep_search", "arguments": { "query": "auth middleware" } }

tool may also be the combined form ("tool": "vecgrep__search"), with or without server set — the gateway routes it either way. Every call is recorded to the intelligence store, same as in expose: all.

Oversized results

If a downstream result exceeds the response budget, call_tool returns a compact receipt with a callId instead of flooding the context. The exact serialized result is stored locally for 24 hours, and the agent recovers it in bounded base64 pages with get_result: start at cursor: 0 and follow nextCursor until done is true. Small results pass through unchanged.

Pinning: keep hot tools mounted

Discovery costs a round trip or two. For the tools you call constantly, skip it: pins stay mounted directly on the gateway even under expose: lazy, so agents call them by their server__tool name automatically.

sh
mcphub pin codemap vecgrep              # whole servers (all their tools)
mcphub pin codemap__*                   # same, explicit wildcard
mcphub pin codemap__semantic    # one tool
mcphub pin --top 8                      # auto-pin your 8 most-called tools (from stats)
mcphub pin                              # list current pins

pin --top N reads the local intelligence store and pins your N most-called tools — run mcphub stats --tools first to see what it would choose. Pins land in mcphub.yaml under a top-level pin: list, so they survive in version control like the rest of your config; saving validates them, so a pin naming an unknown server is rejected.

Removing pins mirrors adding them:

sh
mcphub unpin codemap__semantic  # remove one exact pin
mcphub unpin codemap                    # remove every pin resolving to that server

Pins apply on the next gateway start

In gateway mode no sync is needed — restart your agents so their gateway processes reload the config.

Scoped agents

If an agent has per-agent routing (servers: / tools: lists), a pin outside that agent's scope is silently skipped for it. An agent-specific pin replaces the global list for that harness; pin: [] publishes only the eight meta-tools while retaining lazy access to every in-scope hidden tool. tool_schema_budget can instead keep complete pinned definitions up to a byte budget. The global pin still applies to agents that omit the override.

When to prefer expose: all

Lazy mode is a trade, not a strict upgrade:

  • Small catalogs. With one or two servers exposing a handful of tools, the eight meta-tools plus the indirection can cost as much as just mounting everything.
  • Extra round trips. Each first use of a tool costs a search/resolve call before the real one. Pinning removes this for hot paths, but a workload that touches many different tools once each pays it repeatedly.
  • Weaker instruction-following. Lazy mode relies on the model reading the gateway's instructions and discovering tools proactively. Agents that only use tools listed up front will underuse the catalog; give those expose: all (or pin generously).

A good middle ground: expose: lazy plus mcphub pin --top N, revisited occasionally as mcphub stats shows your usage shifting.

See also

Released under the MIT License.