Skip to content

Breaking: keep Strands TypeScript MCP connections per StrandsPlugin - #371

Merged
campoy merged 1 commit into
mainfrom
fix/strands-mcp-connections-shared
Nov 2, 2026
Merged

campoy merged 1 commit into
mainfrom
fix/strands-mcp-connections-shared

Conversation

@campoy

@campoy campoy commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

What was changed

@temporalio/strands-agents keeps its MCP connections in each StrandsPlugin instance instead of one module-level map keyed by server name. Before, in a process with several Workers, the first plugin to connect to a server name served every Worker that registered that name, with its own server and credentials, and any Worker that stopped closed the connection under the others' calls.

  • temporal-mcp-client.ts: a new McpConnections class holds one plugin's connections by server name. Its use() runs one Worker and counts the Workers using the plugin; the last one to stop closes the connections. A connection that a call is still using when it is evicted is closed when that call finishes, like one retired after a failed call. buildListToolsActivity and buildCallToolActivity take the plugin's McpConnections instead of the idle timeout, which moves into it. _evictConnection and the module-level map are gone. None of these are exported from the package index.
  • plugin.ts: each StrandsPlugin builds its own McpConnections, passes it to its MCP Activities, and its runContext is connections.use(next).
  • README: the MCP section says connections belong to each plugin instance, and when they close.
  • Tests: two Workers with their own plugin and one server name reach their own servers, and stopping one does not close the other's connection under its call; stopping one of two Workers that share a plugin keeps the other's connection open until the last Worker stops; and the last Worker to stop closes a connection only after its in-flight call finishes. The existing eviction tests use the new class. Comments that gave tests distinct server names to avoid the process-wide cache are removed.

This matches the Python plugin after #190, whose temporalio-mcp pool belongs to each StrandsPlugin and closes when the last Worker or Replayer using it ends.

Breaking change (callers): two Workers in one process with separate StrandsPlugin instances that map one server name to the same stateful MCP server share one connection and session today, so a run on one Worker can read session values a run on the other wrote. After this change each instance opens its own connection and session. Before upgrading, give such Workers the same StrandsPlugin instance (for example on the NativeConnection). Code that imported buildListToolsActivity, buildCallToolActivity or _evictConnection from lib/temporal-mcp-client must change; they are not exported from the package index.

Also: an application that builds a new StrandsPlugin from the same options for each Worker now opens one connection per Worker for each server (for a stdio server, one process each) instead of one per process. A call that is still running after a forced Worker shutdown keeps its connection until the call ends; before, the Worker's stop closed it.

Replay is not affected: Workflow code, Activity names, inputs and results are unchanged, and the change adds no events or commands to the history. Upgrade and rollback need no extra steps beyond the caller change above.

Why?

Closes #370. Two Workers in one process with their own StrandsPlugin sent MCP calls to another Worker's server or with its credentials, with no error, and stopping one Worker failed another Worker's in-flight MCP call, which then ran the tool twice.

Checklist

  1. Closes [Bug] strands-agents: MCP connections are shared across StrandsPlugin instances and Workers in one process #370

  2. How was this tested:
    Supplemental local runs in Docker (Node 24, @temporalio/* 1.24.0, @strands-agents/sdk 1.19.0, @modelcontextprotocol/sdk 1.32.0 from the lock, Temporal CLI 1.9.1), on main at 88745dc:

    • make lint, make test (90 tests passed), make build with scripts/ci/check_npm_package.py, and scripts/ci/check_conventions.py pass. Prettier with the SDK's settings reports no changes in the touched files.
    • The two new Worker tests fail on main (only test-strands.ts changed) on their intended assertions: two plugins' calls are handled by ['A', 'A'] instead of ['A', 'B'], and Worker A's stop disconnects the shared plugin's connection (['S']) while Worker B's call is in flight. Both pass with the change. The new eviction unit test uses the new class, so it cannot run on main.
    • [Bug] strands-agents: MCP connections are shared across StrandsPlugin instances and Workers in one process #370's script prints server A for plugin B's call and A disconnected during a call on main; with the change each plugin reaches its own server and B's connection stays open.
    • With real Workers and MCP servers, scripts that showed this on main (another Worker's credentials in both start orders, a held call while the other Worker stops, with separate plugins and with one shared plugin, a retry on the surviving Worker, and both Workers shut down together with a 6-second grace time) now show each Worker using its own server and key, and the call finishing on its first attempt with one tool run.
    • Replay: no Workflow code, Activity name, input or result changes, so no replay check was needed.
    • CI's other platforms and Node versions were not run locally.
  3. Any docs updates needed?
    The plugin README's MCP section. Release notes should name the caller change above.

Not covered:

Open questions: none.

The MCP connection cache was one module-level map keyed by server name,
so in a process with several Workers the first plugin to connect served
every plugin that registered that name, with its server and credentials,
and any Worker that stopped closed the connection under the others'
calls.

Each StrandsPlugin now keeps its own connections. Its runContext counts
the Workers using the plugin, and the last one to stop closes them; a
connection still in use by a call is closed when that call finishes.
@campoy
campoy marked this pull request as ready for review November 2, 2026 00:43
@campoy
campoy requested a review from a team as a code owner November 2, 2026 00:43
@campoy
campoy merged commit 37c69b7 into main Nov 2, 2026
15 checks passed
@campoy
campoy deleted the fix/strands-mcp-connections-shared branch November 2, 2026 23:14
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.

[Bug] strands-agents: MCP connections are shared across StrandsPlugin instances and Workers in one process

2 participants