Skip to content

feat(agents) Add MCP toolset support - #2492

Open
paulheinrichs-jb wants to merge 1 commit into
livekit:mainfrom
paulheinrichs-jb:feat/mcp-toolset
Open

paulheinrichs-jb wants to merge 1 commit into
livekit:mainfrom
paulheinrichs-jb:feat/mcp-toolset

Conversation

@paulheinrichs-jb

@paulheinrichs-jb paulheinrichs-jb commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

• ## Summary

Adds Model Context Protocol (MCP) tool support to Agents JS through the current MCPToolset lifecycle.

  • Adds MCPServerHTTP, MCPServerStdio, and MCPToolset.
  • Supports SSE and streamable HTTP, stdio, request timeouts, headers, allowed-tool filtering, custom result resolution, per-tool flags/duplicate policy, and progress reporting.
  • Uses an optional @modelcontextprotocol/sdk peer dependency, loaded only when MCP is used.
  • Refreshes dynamic tool definitions after notifications/tools/list_changed.
  • Detects closed MCP connections, clears stale state, and reports a framework ToolError.
  • Updates the MCP example, changeset, lockfile, and public API report.

Why this API

This intentionally uses MCPToolset through tools: [...], rather than adding direct mcpServers options to Agent or AgentSession.

That matches the current Python direction:

  • Python deprecated direct mcp_servers configuration in favor of MCPToolset: livekit/agents#5667
  • Python’s MCP implementation uses a dedicated async MCPToolset with per-tool options: mcp.py, MCPToolset
  • Progress-capable MCP tools forward server progress through the Agent run context, allowing non-blocking replies: mcp.py

The optional peer dependency preserves the original JS MCP PR’s goal: applications that do not use MCP do not need to install the MCP SDK. agents-js#1451

Parity and reliability choices

  • Empty successful MCP tool results raise ToolError. This is exact Python parity: default resolver.
  • Per-tool defaults are blocking, non-cancellable, and duplicate-allowed, matching Python’s _DEFAULT_TOOL_OPTIONS: mcp.py.
  • Cancellation is forwarded through the MCP SDK request signal, so cancellable tools can stop underlying work rather than only detaching the Agent task.
  • Closed transports clear cached client/tool state and return a readable framework error. This follows the intent of the upstream unreleased dead-connection fix: livekit/agents#6298.
  • Tool-list-change handling is a deliberate JS enhancement using the existing dynamic Toolset lifecycle. Upstream Python tracks this as an open capability request: livekit/agents#5378.

Verification

Pre-Review Checklist

  • Build passes: All builds (lint, typecheck, tests) pass locally
  • AI-generated code reviewed: Removed unnecessary comments and ensured code quality
  • Changes explained: All changes are properly documented and justified above
  • Scope appropriate: All changes relate to the PR title, or explanations provided for why they're included
  • Video demo: A small video demo showing changes works as expected and did not break any existing functionality using Agent Playground (if applicable)

Testing

  • Added regression tests for:
    • Empty MCP results raising ToolError
    • AbortSignal propagation to MCP SDK calls
    • Tool-list-change cache invalidation
  • pnpm exec vitest run agents/src/llm/mcp.test.ts
  • pnpm --filter @livekit/agents typecheck
  • pnpm --filter @livekit/agents build
  • pnpm --filter @livekit/agents api:update
  • git diff --check

Additional Notes


Note to reviewers: Please ensure the pre-review checklist is completed before starting your review.

@changeset-bot

changeset-bot Bot commented Sep 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9565a9f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 39 packages
Name Type
@livekit/agents Patch
@livekit/agents-plugin-anam Patch
@livekit/agents-plugin-anthropic Patch
@livekit/agents-plugin-assemblyai Patch
@livekit/agents-plugin-azure Patch
@livekit/agents-plugin-baseten Patch
@livekit/agents-plugin-bey Patch
@livekit/agents-plugin-cartesia Patch
@livekit/agents-plugin-cerebras Patch
@livekit/agents-plugin-deepgram Patch
@livekit/agents-plugin-did Patch
@livekit/agents-plugin-elevenlabs Patch
@livekit/agents-plugin-fishaudio Patch
@livekit/agents-plugin-google Patch
@livekit/agents-plugin-hume Patch
@livekit/agents-plugin-inworld Patch
@livekit/agents-plugin-krisp Patch
@livekit/agents-plugin-lemonslice Patch
@livekit/agents-plugin-liveavatar Patch
@livekit/agents-plugin-livekit Patch
@livekit/agents-plugin-meta Patch
@livekit/agents-plugin-minimax Patch
@livekit/agents-plugin-mistral Patch
@livekit/agents-plugin-mistralai Patch
@livekit/agents-plugin-neuphonic Patch
@livekit/agents-plugin-openai Patch
@livekit/agents-plugin-perplexity Patch
@livekit/agents-plugin-phonic Patch
@livekit/agents-plugin-protoface Patch
@livekit/agents-plugin-resemble Patch
@livekit/agents-plugin-rime Patch
@livekit/agents-plugin-runway Patch
@livekit/agents-plugin-sarvam Patch
@livekit/agents-plugin-silero Patch
@livekit/agents-plugin-soniox Patch
@livekit/agents-plugin-tavus Patch
@livekit/agents-plugins-test Patch
@livekit/agents-plugin-trugen Patch
@livekit/agents-plugin-xai Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@CLAassistant

CLAassistant commented Sep 14, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@paulheinrichs-jb
paulheinrichs-jb force-pushed the feat/mcp-toolset branch 2 times, most recently from f90155e to 3da834b Compare September 14, 2026 18:28
@paulheinrichs-jb

Copy link
Copy Markdown
Contributor Author

@chenghao-mou - Noticed you were assigned to the old one but noticed it was quite outdated. I took a pass at it, i was a bit conscious of the "ongoing" work in the agents repo about some concepts i'd like to see supported (toolset changing).

Please let me know if you have any thoughts / opinions or would prefer to take it on yourself. The mcp_agent works as anticipated connecting with livekits mcp server.

@paulheinrichs-jb
paulheinrichs-jb marked this pull request as ready for review September 14, 2026 18:46
@paulheinrichs-jb
paulheinrichs-jb requested a review from a team as a code owner September 14, 2026 18:46
@paulheinrichs-jb paulheinrichs-jb changed the title Feat/mcp toolset feat(mcp) Add MCP toolset support Sep 14, 2026
@paulheinrichs-jb paulheinrichs-jb changed the title feat(mcp) Add MCP toolset support feat(agents) Add MCP toolset support Sep 14, 2026
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@paulheinrichs-jb
paulheinrichs-jb force-pushed the feat/mcp-toolset branch 2 times, most recently from 13a3f10 to 48d8002 Compare September 15, 2026 12:54

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 4 new potential issues.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment thread agents/src/llm/mcp.ts
Comment thread agents/src/llm/mcp.ts Outdated
)
.join('\n'),
);
return await this.toolResultResolver({ toolName: name, arguments: args, result });

@devin-ai-integration devin-ai-integration Bot Sep 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Pending resolver blocks MCP shutdown

When toolResultResolver never settles, the default non-cancellable MCP tool remains active after its request completes. MCPToolset.aclose() then waits forever while draining it.

Learn more

clientSessionTimeout bounds only SDK requests. The custom result resolver runs afterward and receives no abort signal or deadline. MCP tools default to non-cancellable, so drain awaits the resolver's raw promise without a deadline. The bounded-request shutdown branch therefore relies on a bound that does not cover the complete tool execution.

Example: A resolver awaits a database lookup that never returns. The MCP server responds within one second, but the resolver stays pending. Closing the session calls MCPToolset.aclose(), and executor drain never reaches server cleanup.

Recommended fix: Make resolver completion bounded or cancellable as part of MCP tool execution. Pass an abort signal to the resolver and abort it during teardown, or enforce a resolver timeout aligned with clientSessionTimeout. Ensure shutdown does not await an arbitrary resolver promise indefinitely.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is an MCP-specific instance of the broader hung-tool behavior tracked in #2428. We’re avoiding an MCP-only timeout so the eventual framework-level solution can consistently cover the complete tool execution, including custom result resolvers.

Comment thread agents/src/llm/mcp.ts
Comment thread agents/src/llm/mcp.ts Outdated

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment thread agents/src/llm/mcp.ts Outdated
Comment thread agents/src/llm/mcp.ts
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