busy-lunch-15701
04/21/2026, 5:27 AMtall-airline-16756
04/21/2026, 5:27 AMtall-airline-16756
04/21/2026, 5:28 AMis_in_progress flag. You can infer or track tool-call activity using the RunContext and execution patterns:
• Use the RunContext passed into tools (it contains the current function_call). Track when a tool is invoked and when it finishes by setting/clearing a custom in-memory or persistent flag tied to that function_call.
• For long-running work, use the speech_handle methods (for example, run_ctx.speech_handle.wait_if_not_interrupted()) and cancellation hooks to detect interruptions or completion.
• Treat background tasks (e.g., asyncio.ensure_future started for a tool) as indicators of an active call — keep references and check task status (done(), cancelled()).
• Optionally surface user-facing indicators (thinking sounds or short verbal status updates) when a tool starts and stop them when it completes to provide a visible/hearing cue.
Could you tell me which tool or runtime pattern you’re using (server SDK, agent tools, or a specific function implementation)? If you share the relevant code snippet or where you want the check performed, I’ll give concrete code examples for tracking the in-progress state.
Sources: https://docs.livekit.io/agents/logic/tools/definition.md| | External data and RAG | LiveKit Documentation | Function tool definition | LiveKit Documentation, with interactive elementsbusy-lunch-15701
04/21/2026, 5:31 AMtall-airline-16756
04/21/2026, 5:31 AMBackgroundAudioPlayer but disable automatic thinking sounds.
2. At the very start of your tool function, call background_audio.play("/path/to/sound.mp3") and keep the returned handle.
3. When the tool finishes (in finally/cleanup), stop the handle (handle.stop() or await it) so audio ends immediately.
Example pattern:
@function_tool()
async def my_tool(context: RunContext):
handle = background_audio.play("/sounds/tool_thinking.mp3")
try:
result = await do_long_running_work()
finally:
handle.stop()
return result
If you want, tell me which SDK/runtime and how you currently trigger tools (code snippet). I’ll give a tailored code example for your environment.
Sources: External data and RAG | LiveKit Documentation | Background audio | LiveKit Documentation, with interactive elementsbusy-lunch-15701
04/21/2026, 5:39 AMtall-airline-16756
04/21/2026, 5:39 AMbackground_audio = BackgroundAudioPlayer()
await background_audio.start(room=room, agent_session=session)
1. In your MCP tool handler, play the audio at start and stop it in a finally block:
@function_tool()
async def my_mcp_tool(context: RunContext, ...):
handle = background_audio.play("/path/to/tool_thinking.mp3", loop=True)
try:
result = await do_mcp_work(...) # call external service / long-running task
return result
finally:
# ensure audio stops when tool finishes or is interrupted
if not handle.done():
handle.stop()
Notes and tips:
• Use loop=True for continuous background sound during long tasks.
• Ensure background_audio is started for the correct room and agent_session.
• If your MCP server runs outside the agent process, implement the same play/stop calls in the MCP handler that executes the tool logic (so playback is tied to the tool lifecycle).
• Clean up handles to avoid overlapping/orphaned audio; handle cancellations/interruption similarly.
If you want, share a short snippet of your MCP tool handler (or tell me if MCP runs in a separate process), and I’ll give a tailored code example for that exact setup.
Sources: Background audio | LiveKit Documentation | Model Context Protocol (MCP) | LiveKit Documentation | Audio customization | LiveKit Documentation, with interactive elementsbusy-lunch-15701
04/21/2026, 5:48 AMtall-airline-16756
04/21/2026, 5:48 AMbackground_audio.play(...) when it receives "start" and handle.stop() on "end".
◦ Keeps audio playback under the agent’s LiveKit session and avoids granting LiveKit access to your MCP host.
• Agent-initiated control (polling / RPC)
◦ Agent asks MCP to run a tool (HTTP/gRPC). Before calling the MCP endpoint, agent starts playback; when the MCP response returns (or an error), agent stops playback.
◦ Works if the agent initiates every tool call and waits for completion.
• Two-process LiveKit control (only if MCP must control audio directly)
◦ Give the MCP process a minimal control channel to the agent (secure RPC or websocket) so MCP can request the agent play/stop audio. MCP itself still shouldn’t instantiate a full LiveKit audio session — keep LiveKit client logic in the agent.
Implementation notes:
• Use loop=True for continuous background audio and always stop in a finally/cancel handler to handle interruptions.
• Ensure the agent exposes a secure, authenticated control endpoint (only the MCP server can call it).
• If tool execution is long-running and asynchronous, use an id/token per invocation so start/stop signals map correctly to the right audio handle.
• If agent starts playback before calling MCP, consider timeouts or heartbeat messages from MCP so the agent won’t play indefinitely if MCP fails.
If you tell me which signalling method you prefer (webhook, agent-initiated HTTP call, gRPC, or message queue), I’ll provide a concise code sketch for the agent and a matching MCP-side snippet.
, with interactive elementsbusy-lunch-15701
04/21/2026, 5:52 AMtall-airline-16756
04/21/2026, 5:52 AMhandle = background_audio.play(..., loop=True).
• When the HTTP response returns (or on exception), agent calls handle.stop() in a finally block.
• No extra server endpoints needed on the agent; works if agent initiates the tool call.
Option B — MCP signals agent (one small webhook)
• MCP hits a single secure agent endpoint /control/audio with JSON {id, action: "start"|"stop"}.
• Agent starts/stops the audio handle for that id.
• MCP sends start when the tool begins and stop when it ends.
• Only one tiny endpoint to implement on the agent.
If you tell me whether the agent or MCP initiates the tool call, I’ll give a 6–8 line code snippet for that option. Which one do you want?
, with interactive elementsbusy-lunch-15701
04/21/2026, 5:53 AMtall-airline-16756
04/21/2026, 5:53 AMbefore_mcp hook to trigger background audio for MCP tools.
Short answer: No — there isn’t a confirmed before_mcp lifecycle hook. MCP tool calls don’t expose a dedicated pre-call hook you can rely on.
What to do instead (simple, minimal options):
• If the agent initiates the MCP call (best): have the agent start playback immediately before calling MCP and stop it after the call returns (use a finally block). No new endpoints or LiveKit access required in MCP.
• If MCP initiates or runs externally: add one tiny signal from MCP to the agent (a single secure webhook endpoint or short RPC) that sends "start" when the tool begins and "stop" when it ends. Agent starts/stops the audio handle accordingly.
If you tell me which of the two fits your setup (agent calls MCP, or MCP calls agent), I’ll give a 6–8 line Python snippet for that exact flow.
Sources: Pipeline nodes and hooks | LiveKit Documentation | Model Context Protocol (MCP) | LiveKit Documentation | The ReAct Pattern for Voice Agents and How AI Agents Think, Act, and Respon... | Audio customization | LiveKit Documentation, with interactive elementsbusy-lunch-15701
04/21/2026, 5:54 AMtall-airline-16756
04/21/2026, 5:54 AM# agent_side.py
from livekit.agents import BackgroundAudioPlayer
import httpx
import asyncio
background_audio = BackgroundAudioPlayer()
async def start_agent(room, session):
await background_audio.start(room=room, agent_session=session)
async def call_mcp_tool(tool_url: str, payload: dict):
# start looping background audio for the duration of the MCP call
handle = background_audio.play("/sounds/tool_thinking.mp3", loop=True)
try:
async with httpx.AsyncClient(timeout=60.0) as client:
resp = await client.post(tool_url, json=payload)
resp.raise_for_status()
return resp.json()
finally:
# ensure audio stops even on error or cancellation
if not handle.done():
handle.stop()
# example usage
async def run():
# await start_agent(room, session) # ensure player started earlier
result = await call_mcp_tool("https://mcp.example.com/run_tool", {"arg": "value"})
print(result)
if __name__ == "__main__":
asyncio.run(run())
Notes:
• Ensure await background_audio.start(...) runs once before tool calls (e.g., during agent startup).
• Use loop=True for continuous audio and handle.stop() in the finally block so audio always stops.
• This requires no LiveKit client or extra hooks on the MCP server — the agent remains the only process interacting with LiveKit.
If you want, paste the code you use to invoke MCP and I’ll adapt this snippet to match your exact call pattern (httpx, gRPC, sync, etc.).
Sources: Model Context Protocol (MCP) | LiveKit Documentation | Tool definition and use | LiveKit Documentation, with interactive elementsbusy-lunch-15701
04/21/2026, 5:56 AMagent = Agent(
# ... other arguments ...
mcp_servers=[
mcp.MCPServerHTTP("<https://your-mcp-server.com>")
]
)
this is the correct codetall-airline-16756
04/21/2026, 5:56 AMmcp_servers on the Agent does not create a built‑in pre/post MCP hook for audio. You need to wire audio control into the code path that actually triggers MCP tool execution. Two minimal ways to do that that work with your Agent setup:
Option 1 — Wrap MCP calls with a function_tool the agent calls
• Create a local @function_tool that:
a. Starts background_audio.play(..., loop=True) and keeps the handle.
b. Invokes the MCP tool via the agent’s existing MCP plumbing (i.e., call the tool by name or let the LLM choose it but route execution through this wrapper).
c. Stops the handle in a finally block.
• This keeps LiveKit audio in the agent process and requires no changes to the MCP server.
Option 2 — Intercept the agent’s MCP client request path (lightweight wrapper)
• If the agent code uses an MCP client call internally, wrap that call:
a. Start audio before sending the MCP request.
b. Await the MCP response.
c. Stop audio in finally.
• This is effectively the same pattern as Option 1 but applied where the agent actually performs the HTTP/gRPC call to the MCP server.
If you want a concrete snippet that fits your exact Agent init and call flow, tell me:
• Do you call MCP tools via a local function_tool wrapper, or do you want me to show how to wrap the agent’s internal MCP HTTP call path?
Tell me which and I’ll return a 8–10 line Python example matching your setup.
, with interactive elementsbusy-lunch-15701
04/21/2026, 6:04 AMtall-airline-16756
04/21/2026, 6:04 AMfrom livekit.agents import BackgroundAudioPlayer, AudioConfig, BuiltinAudioClip
background_audio = BackgroundAudioPlayer(
thinking_sound=AudioConfig(BuiltinAudioClip.KEYBOARD_TYPING, volume=0.8)
)
# after you create/start your Agent and AgentSession:
await background_audio.start(room=room, agent_session=session)
This will play during MCP tool calls without modifying your MCP tools. If you prefer manual control for a specific tool, I can show the simple start/play/stop wrapper — which is useful only if you need a different sound or custom timing. Which would you like?
Sources: Background audio | LiveKit Documentation | External data and RAG | LiveKit Documentation | Model Context Protocol (MCP) | LiveKit Documentation | The ReAct Pattern for Voice Agents and How AI Agents Think, Act, and Respon..., with interactive elementsbusy-lunch-15701
04/21/2026, 6:05 AMtall-airline-16756
04/21/2026, 6:06 AMagent.tools or tool.origin exist. There isn’t a guaranteed public API name for the loaded tools across setups, so I need one small piece of info to give an exact patch.
Could you paste the short snippet where you create the Agent (the lines around your Agent(...) call and anything that shows how you access tools or MCP server config)? I’ll return a minimal, safe wrapper you can apply to all MCP-loaded tools for playing audio only during tool execution.
, with interactive elementsbusy-lunch-15701
04/21/2026, 6:10 AMagent = Agent(
# ... other arguments ...
mcp_servers=[
mcp.MCPServerHTTP("<https://your-mcp-server.com>")
]
)tall-airline-16756
04/21/2026, 6:11 AMfrom livekit.agents import BackgroundAudioPlayer
background_audio = BackgroundAudioPlayer(thinking_sound=None)
await background_audio.start(room=room, agent_session=session)
2) Small helper to play audio for the duration of a call:
def play_while(coro_fn):
async def wrapped(*args, **kwargs):
handle = background_audio.play("/sounds/tool_thinking.mp3", loop=True)
try:
return await coro_fn(*args, **kwargs)
finally:
if not handle.done():
handle.stop()
return wrapped
3) After Agent creation, wrap MCP-provided tools in-place:
# assume `agent` is already created with mcp_servers
for name, tool in list(agent.tools.items()):
# detect MCP tools by any marker you have (e.g., tool.origin or module)
# if you don't have a marker, restrict to tools you expect from MCP
if getattr(tool, "origin", None) == "mcp" or name.startswith("mcp_"):
agent.tools[name] = play_while(tool)
Notes:
• If your MCP tools don’t expose an origin marker, use whatever identifier you have (naming convention, tool registry, or check tool.__module__) to select only MCP tools.
• This wraps tools centrally — no need to edit MCP server code or enumerate tools by hand.
• If your agent loads tools lazily, run the wrapping step after tools are loaded (e.g., right after Agent init or when session starts).
If you paste the actual runtime attribute you can use to identify MCP tools (e.g., tool.origin, name prefix, or tool.__module__), I’ll adapt the wrapper line to detect them precisely.
, with interactive elementsbusy-lunch-15701
04/21/2026, 7:04 AMtall-airline-16756
04/21/2026, 7:05 AMtool.origin == "mcp", a naming prefix, or tool.__module__) and replace them with the wrapped version in agent.tools.
• If tools are loaded lazily, run the wrapping step after tools are available.
Minimal code sketch to hand off
• Disable automatic thinking and start player:
background_audio = BackgroundAudioPlayer(thinking_sound=None)
await background_audio.start(room=room, agent_session=session)
• Wrapper helper:
def play_while(coro_fn):
async def wrapped(*args, **kwargs):
handle = background_audio.play("/sounds/tool_thinking.mp3", loop=True)
try:
return await coro_fn(*args, **kwargs)
finally:
if not handle.done():
handle.stop()
return wrapped
• Wrap MCP tools after Agent init:
for name, tool in list(agent.tools.items()):
if getattr(tool, "origin", None) == "mcp" or name.startswith("mcp_"):
agent.tools[name] = play_while(tool)
Notes / caveats
• Need a reliable way to identify MCP tools in your environment. If no origin exists, use naming conventions or tool.__module__.
• Ensure background_audio.start(...) runs once before tool calls.
• If MCP tools run in a different lifecycle (lazy load), perform wrapping after tools are registered.
• This keeps LiveKit audio logic inside the agent — MCP server remains unchanged.
What I need from your colleague (if anything)
• The runtime attribute used to identify MCP tools (e.g., tool.origin, name prefix, or module) so the wrapper detection can be made exact.
• Confirm whether tools are loaded immediately at Agent init or lazily so we know when to run the wrapping.
If you want, I can produce a ready-to-drop patch matching your exact tool-identification pattern.
, with interactive elements