square-activity-46232
03/04/2026, 11:52 PMactivate_mcp. When this tool is called, it:
Activates an MCP server (e.g. google_contacts)
Retrieves its tools via server.list_tools()
Rebuilds the agent with the new mcp_servers
1. Calls context.session.update_agent(updated_agent)
This works correctly in terms of activation, but the LLM cannot immediately call the newly available tools in the same turn.
Example flow:
User request →
Agent calls activate_mcp(service="google_contacts") →
MCP server activates and exposes tools (e.g. list_contacts) →
Agent says something like "Give me a moment while I check", but never calls the new tool.
It seems like the tool schema snapshot used by the LLM was captured at the beginning of the turn, so newly injected tools are not visible until the next run.
My questions:
Does context.session.update_agent() update the tool registry immediately within the same run, or only for the next turn?
Is there a recommended way to trigger a replan / rerun of the agent after dynamically adding tools?
Is the recommended pattern instead to expose stable proxy tools that internally activate MCP servers if needed?
1. Are there examples in the LiveKit Agents repo of dynamic tool injection during a conversation?
The goal is to allow the agent to chain:
activate_mcp → new MCP tool (e.g. list_contacts)
within a single user request.tall-airline-16756
03/04/2026, 11:52 PMtall-airline-16756
03/04/2026, 11:52 PMcontext.session.update_agent(updated_agent) does update the agent in session state, but the LLM’s view of available tools is captured for the current reasoning cycle and will not see newly injected tools until the next turn.
• The reliable patterns are: (A) complete the activation turn and then trigger a new LLM pass where the new tools are visible, or (B) expose stable proxy/wrapper tools that activate MCP servers internally and call the remote tool for the same-turn behavior.
What’s happening (why you see the behavior)
• The LLM/planner must have a consistent tool schema during a decision cycle. Tool registration is effectively snapshotted at the start of the turn, so mid-turn additions are not available for that same pass. That’s why your agent activates the MCP server successfully but never calls the newly added tool during the same turn.
Direct answers to your questions 1) Does context.session.update_agent() update the tool registry immediately within the same run, or only for the next turn?
• It updates the session’s agent state, but the LLM will not see newly added tools in the current reasoning cycle. New tools are available starting on the next turn.
2) Is there a recommended way to trigger a replan / rerun of the agent after dynamically adding tools?
• There isn’t a single magic API that forces the current LLM pass to replan using newly injected tools. Recommended approaches:
a. Two-step flow (most straightforward):
• The tool activate_mcp completes and returns a message indicating activation succeeded.
• Immediately start a new LLM pass (for example, call context.session.generate_reply() or otherwise append a follow-up system/user instruction) telling the agent to use the newly available tool (e.g., “Now call list_contacts to fetch contacts”). The new pass will include the updated tool list.
a. Orchestrated follow-up from your controller:
• After the activation tool finishes, programmatically enqueue the follow-up prompt/call so the agent continues without waiting for the user to type again.
Both ensure the LLM evaluates available tools after the session update.
3) Is the recommended pattern instead to expose stable proxy tools that internally activate MCP servers if needed?
• Yes — this is a robust recommended pattern. Expose stable, long-lived function tools (e.g., search_google_contacts(query) or get_user_contacts()) that:
◦ Check/activate the MCP server internally if it isn’t already active.
◦ Discover and call the correct MCP tool behind the scenes.
◦ Return results to the agent in the same turn.
This hides dynamic loading from the LLM and keeps the tool schema stable during each reasoning cycle.
4) Are there examples in the LiveKit Agents repo of dynamic tool injection during a conversation?
• There are examples showing MCP usage, dynamic construction of function tools, and returning new agent instances (handoffs). However, there is no canonical example that demonstrates activating an MCP server mid-tool-call and then immediately calling the newly injected remote tool within the same turn. The practical options in the repo lean toward either multi-turn flows or using pre-defined wrapper tools.
Practical implementation patterns you can use right now
• Pattern A — Two-step activate-then-run (simple, reliable)
a. Tool activate_mcp(service="google_contacts") activates the server and calls context.session.update_agent(updated_agent).
b. The tool returns a message like: “Activated Google Contacts. I will fetch your contacts now.”
c. Your orchestration code or the agent calls context.session.generate_reply() with instructions to call list_contacts (or you add a follow-up user message). The new turn sees list_contacts and calls it.
• Pattern B — Stable wrapper tool (single-turn)
a. Register a stable tool fetch_contacts(query) at agent startup.
b. Implementation of `fetch_contacts`:
• If google_contacts MCP isn’t active, activate it and fetch the remote tool handle via server.list_tools().
• Call the remote list_contacts tool (directly invoke the MCP server or the tool implementation) and return the result.
a. The LLM calls fetch_contacts in the same turn; the wrapper handles all activation and delegation, so no replan is needed.
Which to pick?
• If you need immediate same-turn chaining, use wrapper/proxy tools (Pattern B).
• If you prefer to keep MCP servers as explicit agent-level tools, use Pattern A (activate then trigger a new turn).
Suggested code/flow sketch (wrapper tool idea)
• Register @function_tool or equivalent named fetch_google_contacts(query).
• Inside it:
◦ If the server is not active, create/activate the MCP server.
◦ Use server.list_tools() to discover list_contacts.
◦ Call list_contacts on the MCP server programmatically and return results to the agent.
Closing / Offer to help If you’d like, I can:
• Help you draft a concrete wrapper tool implementation sketch in your language of choice (Python/Node).
• Show an example flow that uses context.session.generate_reply() to trigger the second turn after activation.
Which would be most helpful — a quick Python example of the wrapper tool or a small orchestrator snippet that runs the two-step activate-then-run flow?
Sources: Tool definition and use | LiveKit Documentation | Agent session | LiveKit Documentation | Agents and handoffs | LiveKit Documentation | MCP Agent | LiveKit Documentationsquare-activity-46232
03/11/2026, 4:51 PMtall-airline-16756
03/11/2026, 4:51 PMgenerate_reply to refresh the tool snapshot is a clunky workaround. Here’s a clear explanation of why that’s necessary, plus the practical options you can use going forward.
Why you can’t update the tool registry mid‑loop
• The planner/LLM needs a consistent function/tool schema for each reasoning cycle. That schema is captured (snapshotted) at the start of the turn so the model can plan reliably.
• Because of that snapshotting, adding tools via context.session.update_agent() during the same reasoning pass does update the session state, but the LLM’s active view of tools does not change until a new pass/turn begins. You therefore must start a new pass for the model to “see” the new tools.
Why a forced generate_reply works
• Forcing a new LLM pass (e.g., generate_reply() or programmatically enqueuing a follow-up message) creates a fresh reasoning cycle in which the tool registry is reloaded — so newly injected tools become visible and callable in that new turn.
Recommended, practical solutions
• Wrap activation behind stable proxy tools (best for same-turn behavior)
◦ Register a persistent tool like fetch_google_contacts(query) up front.
◦ Have that tool internally activate the MCP server if needed, discover the remote tool via server.list_tools(), call it, and return results.
◦ The LLM calls the wrapper in one turn and the wrapper handles activation + delegation without changing the LLM’s tool schema mid-turn.
• Use a controlled two-step flow (explicit replan)
◦ Keep activate_mcp as a separate tool. After it runs, immediately start a new LLM pass (programmatically) that instructs the agent to call the newly available tool. This is reliable and simple to implement.
• Orchestrate an automated follow-up rather than waiting for the user
◦ After activation finishes, your orchestrator can append a follow-up user/system message or call generate_reply() so the agent continues seamlessly without manual user input.
When to prefer each pattern
• Need true single-turn chaining (activate → call new tool): use stable wrapper tools.
• Want explicit lifecycle separation and simpler reasoning: use activate-then-new-turn flows.
• Want best UX without exposing dynamic tool churn to the LLM: use wrappers as the primary interface.
If you’d like, I can:
• Sketch a short Python example of a wrapper tool that activates an MCP server and calls its list_contacts in the same function, or
• Show a minimal orchestrator snippet that runs activate_mcp and then programmatically triggers the follow-up generate_reply() so the agent uses the new tools.
Which would you prefer?