Repository navigation
Conversation
MCPAgent.streamEvents() pushed the user query into conversationHistory before taking the history snapshot for the run, then appended the query again as the trailing HumanMessage of the inputs. With memory enabled and no external history the model received the current turn duplicated on every call. run()/stream() take the snapshot first and only record the query afterwards, so streamEvents() now does the same.
There was a problem hiding this comment.
1 issue found across 3 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="libraries/typescript/packages/agent/src/agents/mcp_agent_langchain.ts">
<violation number="1" location="libraries/typescript/packages/agent/src/agents/mcp_agent_langchain.ts:1872">
P3: The moved block still commits the user query to `conversationHistory` before the stream executes, so a stream that throws leaves an orphaned HumanMessage (no matching AI response) in memory, and the next turn feeds that unanswered query to the model. This diverges from the run()/stream() behavior the PR aims to mirror: there, all new messages are committed only after execution completes successfully (step 4, `accumulatedMessages.slice(langchainHistory.length)`). To fully mirror that and stay consistent on errors, defer the `addToHistory(new HumanMessage(...))` until after the event loop completes, alongside the existing `addToHistory(new AIMessage(finalResponse))` at the end of the function.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| // Add user message to history if memory enabled | ||
| if (this.memoryEnabled) { | ||
| logger.debug(`🔄 Adding user message to history: ${display_query}`); | ||
| this.addToHistory(new HumanMessage({ content: query })); |
There was a problem hiding this comment.
P3: The moved block still commits the user query to conversationHistory before the stream executes, so a stream that throws leaves an orphaned HumanMessage (no matching AI response) in memory, and the next turn feeds that unanswered query to the model. This diverges from the run()/stream() behavior the PR aims to mirror: there, all new messages are committed only after execution completes successfully (step 4, accumulatedMessages.slice(langchainHistory.length)). To fully mirror that and stay consistent on errors, defer the addToHistory(new HumanMessage(...)) until after the event loop completes, alongside the existing addToHistory(new AIMessage(finalResponse)) at the end of the function.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At libraries/typescript/packages/agent/src/agents/mcp_agent_langchain.ts, line 1872:
<comment>The moved block still commits the user query to `conversationHistory` before the stream executes, so a stream that throws leaves an orphaned HumanMessage (no matching AI response) in memory, and the next turn feeds that unanswered query to the model. This diverges from the run()/stream() behavior the PR aims to mirror: there, all new messages are committed only after execution completes successfully (step 4, `accumulatedMessages.slice(langchainHistory.length)`). To fully mirror that and stay consistent on errors, defer the `addToHistory(new HumanMessage(...))` until after the event loop completes, alongside the existing `addToHistory(new AIMessage(finalResponse))` at the end of the function.</comment>
<file context>
@@ -1872,6 +1866,12 @@ export class MCPAgent {
+ // Add user message to history if memory enabled
+ if (this.memoryEnabled) {
+ logger.debug(`🔄 Adding user message to history: ${display_query}`);
+ this.addToHistory(new HumanMessage({ content: query }));
+ }
+
</file context>
There was a problem hiding this comment.
Confirmed. Moving the history insertion below the input snapshot fixes the duplicate-query request, but the insertion still happens before streamEvents() completes. If the stream throws, memory retains the new HumanMessage without the corresponding AI message. Deferring the memory writes until successful completion, and covering the failing-stream path, would preserve both the single-query invariant and consistent history on errors. The current branch has not addressed this follow-up.
There was a problem hiding this comment.
Addressed in 2e0e2d5: the addToHistory(new HumanMessage(query)) in streamEvents() now runs only after the event loop completes, so a stream that throws leaves conversationHistory untouched, matching run()/stream() which commit new messages to memory only after successful execution. Added a regression test that drives a failing stream and asserts no orphaned HumanMessage remains (packages/agent/tests/stream_events_duplicate_query.test.ts). pnpm --filter @mcp-use/agent test:unit passes: 20 files, 150 passed, 1 pre-existing skip.
streamEvents() committed the user query to conversationHistory before the event loop started, so a stream that threw left an orphaned HumanMessage (with no matching AI response) that the next turn fed back to the model. Defer the addToHistory() call until after the event loop completes, mirroring run()/stream() which commit new messages to memory only after successful execution.
|
Hey, thanks for the PR. We're no longer supporting |
Where did you find this issue?
Found by code review while comparing how
run()/stream()andstreamEvents()assemble agent inputs in the LangChainMCPAgent(libraries/typescript/packages/agent/src/agents/mcp_agent_langchain.ts). WithmemoryEnabled: true(the default) and noexternalHistory,streamEvents()sends the current user query to the model twice on every call. I did not find an existing issue tracking it.Language / Project Scope
Changes
streamEvents()pushed the user query intoconversationHistorybefore taking the history snapshot for the run, then appended the query again as the trailingHumanMessageof the inputs — so the current turn reached the model twice. TheaddToHistory()call now runs after the inputs are assembled, soinputs = [...priorHistory, new HumanMessage(query)]and the query is stored once for the next turn.Implementation Details
packages/agent/src/agents/mcp_agent_langchain.ts(streamEvents()): moved theif (this.memoryEnabled) { this.addToHistory(new HumanMessage({ content: query })) }block from beforeconst historyToUse = history ?? this.conversationHistoryto after theinputsarray is built. Before: turn 1 sent[human("hello"), human("hello")], turn 2 sent[hello, ai(answer), world, world].run()/stream()in the same file already snapshot the history first and only record messages afterwards (accumulatedMessages = [...langchainHistory, new HumanMessage(query)], with history updated after execution);streamEvents()now matches that behavior.TypeScript Checklist
Packages Modified
tests@mcp-use/agent(packages/agent/src/agents/mcp_agent_langchain.ts; the template has no agent checkbox).Pre-commit Checklist
pnpm lint:fix— not needed:pnpm lintreports no issues in the changed files (the 41 problems it reports are pre-existing onmain, all inpackages/cli/src/**)pnpm format—pnpm format:checkpassespnpm buildand build succeeds without errorspnpm changesetto create a changesetpackages/agent/tests/stream_events_duplicate_query.test.tsdocs/— not needed (bugfix only)Example Usage (Before)
Example Usage (After)
Documentation Updates
None — bugfix only, no public API change.
Testing
packages/agent/tests/stream_events_duplicate_query.test.ts: drives two consecutivestreamEvents()turns against a mocked agent executor and asserts the executor receives the current query exactly once per turn (turn 1:[human("hello")]; turn 2:[human("hello"), ai("answer-1"), human("world")]).mainthe new test fails withAssertionError: expected 2 to be 1(query counted twice in turn 1 inputs); with the fix it passes.pnpm --filter @mcp-use/agent test:unit— 20 test files, 149 passed, 1 skipped (skip is pre-existing).pnpm format:check— passes.pnpm lint— no new issues; the 41 reported problems exist identically onmainand are all inpackages/cli/src/**.pnpm build— succeeds.Backwards Compatibility
Backwards compatible. The only observable change is that the current user query is no longer duplicated in the prompt sent to the model on each
streamEvents()call; conversation memory contents are unchanged. Callers passingexternalHistoryare unaffected (external history is used instead of memory and never received the query).Related Issues
No existing issue found in the tracker for this bug; happy to link one if a maintainer prefers an issue first.
Summary by cubic
Fixes
MCPAgent.streamEvents()sending the current user query to the model twice on every call when memory is enabled (the default) and noexternalHistoryis provided. The query was pushed intoconversationHistorybefore the history snapshot was taken and then appended again as the trailingHumanMessage; it is now recorded only after the inputs are assembled, matchingrun()andstream().The
addToHistory()call is now deferred until after the stream completes, so a stream that throws leaves no orphaned user message in conversation memory for the next turn.Bug Fixes
streamEvents()turns and a failing stream.Written for commit 2e0e2d5. Summary will update on new commits.