diff --git a/packages/bub-acp-server/tests/test_mcp.py b/packages/bub-acp-server/tests/test_mcp.py index 8b8390f..e931b2c 100644 --- a/packages/bub-acp-server/tests/test_mcp.py +++ b/packages/bub-acp-server/tests/test_mcp.py @@ -202,8 +202,8 @@ async def test_model_receives_only_current_session_mcp_tools( seen = [] - def run(self, *, tools, **kwargs): - seen.append({tool.name for tool in tools}) + def run(self, *, system_prompt, **kwargs): + seen.append(system_prompt or "") async def events(): yield StreamEvent("text", {"delta": "ok"}) @@ -227,7 +227,8 @@ async def events(): await agent.prompt( session_id=session.session_id, prompt=[TextContentBlock(text="hello")] ) - # Bub aliases dots for the model, but execution still uses each session's tool object. + # MCP tools are deferred, so the model sees their aliased names in the prompt; + # execution still uses each session's tool object. assert "mcp_first_echo" in seen[0] and "mcp_second_echo" not in seen[0] assert "mcp_second_echo" in seen[1] and "mcp_first_echo" not in seen[1] finally: diff --git a/packages/bub-mcp/README.md b/packages/bub-mcp/README.md index e1f8cab..4947062 100644 --- a/packages/bub-mcp/README.md +++ b/packages/bub-mcp/README.md @@ -32,6 +32,10 @@ The file must contain a top-level `mcpServers` mapping: } ``` +Discovered MCP tools are registered as Bub deferred tools by default: the model only sees their +names until it loads their definitions with `tool.describe`, which keeps large MCP tool sets out of +the prompt. Set `BUB_MCP_DEFERRED_TOOLS=false` to expose them directly instead. + ## CLI Usage Use the CLI to inspect and manage `mcp.json`: diff --git a/packages/bub-mcp/src/bub_mcp/config.py b/packages/bub-mcp/src/bub_mcp/config.py index 9288211..5ae504b 100644 --- a/packages/bub-mcp/src/bub_mcp/config.py +++ b/packages/bub-mcp/src/bub_mcp/config.py @@ -28,6 +28,7 @@ class MCPSettings(bub.Settings): config_path: Path = Field(default_factory=default_config_path) init_timeout_seconds: float | None = 20.0 + deferred_tools: bool = True def read_mcp_servers(self) -> dict[str, Any]: return read_config(self.config_path) diff --git a/packages/bub-mcp/src/bub_mcp/plugin.py b/packages/bub-mcp/src/bub_mcp/plugin.py index 1a15fd9..a6c42ee 100644 --- a/packages/bub-mcp/src/bub_mcp/plugin.py +++ b/packages/bub-mcp/src/bub_mcp/plugin.py @@ -440,6 +440,7 @@ def _build_tool(self, server_name: str, remote_tool: mcp.types.Tool) -> Tool | N handler=self._make_handler(server_name, remote_name, result_mode), renderer=_render_tool_value, output_schema=output_schema, + deferred=self.settings.deferred_tools, ) def _record_failed_server( diff --git a/packages/bub-mcp/tests/test_plugin.py b/packages/bub-mcp/tests/test_plugin.py index fbdd158..1ea4420 100644 --- a/packages/bub-mcp/tests/test_plugin.py +++ b/packages/bub-mcp/tests/test_plugin.py @@ -445,6 +445,22 @@ def test_build_tool_forwards_remote_output_schema() -> None: assert tool.render({"temperature": 21.5}) == '{\n "temperature": 21.5\n}' +def test_build_tool_defers_remote_tools_by_default(monkeypatch) -> None: + remote_tool = FakeRemoteTool( + "get_forecast", "Get forecast.", {"type": "object", "properties": {}} + ) + + channel = plugin.MCPChannel() + tool = channel._build_tool("weather", remote_tool) + assert tool is not None + assert tool.deferred is True + + monkeypatch.setattr(channel.settings, "deferred_tools", False) + tool = channel._build_tool("weather", remote_tool) + assert tool is not None + assert tool.deferred is False + + def test_fastmcp_wrapped_results_are_unwrapped() -> None: remote_tool = FakeRemoteTool( "describe", "Describe.", {"type": "object", "properties": {}} diff --git a/uv.lock b/uv.lock index 2cf6eed..3399e09 100644 --- a/uv.lock +++ b/uv.lock @@ -371,8 +371,8 @@ wheels = [ [[package]] name = "bub" -version = "0.5.0" -source = { git = "https://github.com/bubbuild/bub.git#8669780e7c6754f5d445b2f0c655ea7c01bf00c6" } +version = "0.5.1.dev11+gb4a61bf13" +source = { git = "https://github.com/bubbuild/bub.git#b4a61bf1326729a024161d22ba20019b8500f907" } dependencies = [ { name = "aiohttp" }, { name = "any-llm-sdk" },