Repository navigation
Conversation
A 200 response body like {"result": "..."} has no error key, so
result.error is undefined and the result.error !== null check flagged
every such success response as a failure; only bodies carrying an
explicit error: null passed. Compare with != null so an absent error
field counts as no error, while real error values and status: "error"
bodies still reject. Also stringify the error value so object errors
render as JSON instead of [object Object].
Contributor
There was a problem hiding this comment.
All reported issues were addressed across 3 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
JSON.stringify(result.error) wrapped primitive string errors in quotes and produced undefined for status-only error responses. Render primitive error values with String(), keep JSON.stringify for object errors, and fall back to serializing the whole response body when no error payload exists.
Build the message in one expression and let the catch add the "Remote agent execution failed:" prefix once, instead of twice. Remove the "failed to initialize" check: String() of a response object is always "[object Object]", so it could never match (mcp-use#2851). Claude-Session: https://claude.ai/code/session_01MEJoDUZb843zNMtq3jGMXu
@mcp-use/agent
@mcp-use/cli
@mcp-use/client
create-mcp-use-app
@mcp-use/inspector
mcp-use
@mcp-use/tunnel
commit: |
khandrew1
approved these changes
Oct 19, 2026
21 tasks
khandrew1
added a commit
that referenced
this pull request
Oct 19, 2026
* fix(cli): quote client SDK install args on Windows (#2853) * fix(cli): quote client SDK install args on Windows On Windows spawnAndWait ran npm/pnpm through cmd.exe with an args array. cmd.exe strips ^ from @mcp-use/client@^2.0.0-alpha.0, so the package manager asked for the unpublished 2.0.0-alpha.0 and mcp-use client / mcp-use screenshot auto-install always failed. Quote each argument and pass one command string, matching create-mcp-use-app's runPackageManager (#2696); this also drops the DEP0190 warning. * chore(cli): add changeset for Windows client install fix * test(cli): keep only the argument-quoting cases in the spawn test * test(cli): clarify that only Windows parses the spawn arguments through a shell * fix(cli): read the client peer range from @mcp-use/cli readClientPeerRange() still looked for a package.json named mcp-use, so after the move into @mcp-use/cli it never found the declared peer range and every auto-install fell back to ^2.0.0-alpha.0. Check for @mcp-use/cli, test that the declared range is returned, and use ^2.0.1 as the fallback. * chore: enter canary prerelease mode * chore(typescript): version packages (canary) * fix(inspector): surface streamed chat failures (#2877) * chore(typescript): version packages (canary) * fix(agent): emit OpenAI Responses tool-call args events keyed by item_id (#2551) * fix(agent): emit OpenAI Responses tool-call args by item_id `response.function_call_arguments.delta`/`.done` carry `item_id`, not `call_id`, so the callBuffers lookup always fell back to "" and never matched the buffer registered by `response.output_item.added`. Neither `tool-call-args-delta` nor `tool-call-ready` was emitted on the Responses path, so `streamNativeAgentSteps` saw no ready calls and `MCPAgent.stream()`/`streamEvents()` reported no AgentStep for tool calls made through OpenAIResponsesDriver. * fix(agent): keep the call_id lookup for argument events Index the buffer under both the output item id and the protocol call id, so a producer that identifies `response.function_call_arguments.{delta,done}` by `call_id` keeps matching. Spec-conformant producers are unaffected: the item id is tried first and the fallback only runs when it misses. Adds a regression test that feeds the same SSE sequence with the argument events correlated by `call_id`; it fails on the previous commit and passes here, while the existing item_id test keeps passing. * fix(agent): key the tool-call buffers by identifier namespace Storing every buffer under both the item id and the call_id in one flat Map let one call's item id collide with another call's call_id: the second call's output_item.added overwrote the key the first call was stored under, so the first call's argument events were emitted against the second call's tool name and call id. The two identifiers come from the producer and share no namespace, so they are prefixed with item: and call: and can no longer overlap. The call_id-only producer test is also parameterized over a producer that omits the output item's id, and a two-call case covers the collision. * fix(agent): correlate Responses argument events by item_id only The Responses API sends function_call_arguments delta/done with item_id, and this provider only serves the official OpenAI API, so the call_id fallback key had no producer. Drop it and its tests. Claude-Session: https://claude.ai/code/session_01MEJoDUZb843zNMtq3jGMXu --------- Co-authored-by: Andrew Khadder <[email protected]> * test(inspector): expect tool drawer args now that Responses emits tool-call-ready #2551 made the OpenAI Responses provider emit tool-call-ready, so chat tool-invocation parts carry their arguments again and the drawer test passes. Closes #2769. Claude-Session: https://claude.ai/code/session_01MEJoDUZb843zNMtq3jGMXu * chore(typescript): version packages (canary) * fix(agent): return only the final turn's text from MCPAgent.stream() (#2723) * fix(agent): return only the final turn's text from MCPAgent.stream() streamNativeAgentSteps appended every text-delta to the return value, so text the model wrote before a tool call was glued onto the final answer and saved to memory. Reset it after each tool result, matching runNativeAgent, which keeps only the turn without tool calls. * test(agent): cover maxSteps ending the run after tool results Both stream() and run() return an empty string in that case. Also reset the collected text only for tool results that match a reported tool call, so ignored events have no effect. * fix(agent): drop a tool-call turn's text when the stream is aborted before the tool runs Decide per turn instead of on tool results: a turn that requests tools is intermediate, so an abort between the tool call and its dispatch returns an empty answer, as runNativeAgent does. * fix(agent): reset stream() turn text on every tool result The reset ran only after a pending step was found, and steps exist only after tool-call-ready, so on the OpenAI Responses provider (which never emitted tool-call-ready) the preamble text was still glued onto the answer. Reset on every tool-result and mark a tool-calling turn on tool-call-start, which every provider emits. Adds a regression test with real Responses event shapes. Claude-Session: https://claude.ai/code/session_01MEJoDUZb843zNMtq3jGMXu --------- Co-authored-by: Andrew Khadder <[email protected]> * chore(typescript): version packages (canary) * fix(client): close OAuth loopback connections on teardown (#2766) * chore(typescript): version packages (canary) * fix(agent): treat missing error field as success in RemoteAgent.run (#2651) * fix(agent): treat missing error field as success in RemoteAgent.run A 200 response body like {"result": "..."} has no error key, so result.error is undefined and the result.error !== null check flagged every such success response as a failure; only bodies carrying an explicit error: null passed. Compare with != null so an absent error field counts as no error, while real error values and status: "error" bodies still reject. Also stringify the error value so object errors render as JSON instead of [object Object]. * fix(agent): keep string error messages unquoted in remote error path JSON.stringify(result.error) wrapped primitive string errors in quotes and produced undefined for status-only error responses. Render primitive error values with String(), keep JSON.stringify for object errors, and fall back to serializing the whole response body when no error payload exists. * fix(agent): simplify RemoteAgent error message and drop dead init check Build the message in one expression and let the catch add the "Remote agent execution failed:" prefix once, instead of twice. Remove the "failed to initialize" check: String() of a response object is always "[object Object]", so it could never match (#2851). Claude-Session: https://claude.ai/code/session_01MEJoDUZb843zNMtq3jGMXu --------- Co-authored-by: Andrew Khadder <[email protected]> * chore(typescript): version packages (canary) * fix(cli): join inspector paths through one basePath rule (#2555) * fix(cli): join inspector paths through one basePath rule views/document.ts special-cased basePath "/" while inspector-route.ts and dev-api.ts interpolated directly, so the root produced "//inspector". That made `mcp-use start --with-inspector` advertise /inspector while the mount guard matched //inspector, and left the three dev-API routes unreachable. The rule moves to src/base-path.ts and every consumer joins through it. The two URL sites in dev.ts move with the predicate: dev was self-consistent in the doubled form, so changing only the guard would have broken it. * fix(cli): return basePath for an empty child and correct the inspector docs Addresses review on #2555. pathUnderBase("/mcp", "") returned "/mcp/", contradicting the documented "never containing an empty segment". An empty child now yields basePath. The Inspector integration guide told readers to set basePath: '' to serve the Inspector at /inspector, but assertServerConfig rejects an empty string because basePath must start with "/". The working value is '/', which is exactly the case this PR repairs. Documented that, and replaced the naive `${basePath}/inspector` in the options table and the v2 CLI reference with the normalized rule. * docs(inspector): scope the basePath root note to server config My previous commit claimed an empty basePath is rejected, which is wrong in the mountInspector section it sits in: registerInspectorShell defaults basePath to "" and builds "/inspector" from it, so "" is the inspector's own root value. Only MCPServer config rejects it, via assertServerConfig. Restores the original `''` guidance for mountInspector and adds the MCPServer caveat separately. * docs: drop unrelated doc edits from this fix Claude-Session: https://claude.ai/code/session_01MEJoDUZb843zNMtq3jGMXu --------- Co-authored-by: Aishwary Dongre <[email protected]> Co-authored-by: Andrew Khadder <[email protected]> * fix(client): pin OAuth token expiry when tokens are saved (#2879) getOAuthTokenExpiry fell back to Date.now() + expires_in for opaque tokens on every read, so useMcp().authTokens.expires_at moved later on each reconnect. Save the absolute expiry with the tokens and prefer it over expires_in. JWT exp still wins. Supersedes #2674. Claude-Session: https://claude.ai/code/session_01MEJoDUZb843zNMtq3jGMXu Co-authored-by: Arpitm544 <[email protected]> * chore(typescript): version packages (canary) * feat: customize MCP landing page from landing.tsx (#2648) Claude-Session: https://claude.ai/code/session_018JTsXB5q2SkN8rNAfS7vn4 * chore(typescript): version packages (canary) * fix: landing page dark mode, page-owned Tailwind, and dev styles before hydration (#2880) * feat(server): follow the OS color scheme on landing pages; link dev page CSS The managed Tailwind `dark:` variant keys off `data-theme`, which nothing set on a landing page outside a host. Mirror `prefers-color-scheme` into it from an inline head script before the first paint, until the page sets its own value. Dev loads can also pass the stylesheets the page imports, linked in the head and marked for the dev entry to remove once its imports insert them. Claude-Session: https://claude.ai/code/session_018JTsXB5q2SkN8rNAfS7vn4 * fix(cli): let landing pages own Tailwind; style dev markup before hydration A landing stylesheet with its own `@import "tailwindcss"` now replaces the managed copy instead of compiling beside it, so the page's `dark` variant is the only one. The landing page gets its own managed stylesheet id, separate from the views' copy. In dev, the head links the stylesheets in the page's module graph, and only those extra URLs pass the dev server's Vite route. Claude-Session: https://claude.ai/code/session_018JTsXB5q2SkN8rNAfS7vn4 * docs(server): document landing dark mode and page-owned Tailwind Claude-Session: https://claude.ai/code/session_018JTsXB5q2SkN8rNAfS7vn4 * chore(typescript): version packages (canary) * fix(inspector): start connection timeout fields blank (#2881) The Connect form pre-filled 10000 / 60000. Since #2656 saves those values and tool calls honor them, every server added from the dashboard capped tool calls at 10s per request and 60s overall. Blank means the 10-minute default and no overall limit, as the help text says. Claude-Session: https://claude.ai/code/session_01MEJoDUZb843zNMtq3jGMXu * chore(typescript): version packages (canary) * test(inspector): wait for the settings form before editing the URL (#2882) The dashboard Connect form renders the same connection-form-url-input and stays mounted until the view transition to Connection Settings finishes, so waiting for visibility let fills land in the dashboard form. Save then kept the old URL and the toast still fired. Wait for the connection's URL instead, as the other settings tests already do. Claude-Session: https://claude.ai/code/session_01MEJoDUZb843zNMtq3jGMXu * fix(inspector): JSON-only OAuth metadata responses; per-caller proxy limits (#2855) * fix(inspector): JSON-only OAuth metadata responses; per-caller proxy limits The OAuth BFF relayed /oauth/metadata bodies with the upstream Content-Type, including on error responses, and accepts top-level navigations (no Origin). A link to /inspector/api/oauth/metadata?serverUrl=<attacker> therefore rendered attacker HTML on the host's origin (manufact.com for Manufact Cloud, inspector.manufact.com for the hosted Inspector). Metadata responses are now always application/json with nosniff; /oauth/proxy already answered with a JSON envelope. The proxy and OAuth BFF consumed one limiter key for every request (the stringified Hono request), so one busy caller exhausted the 120/min budget for everyone on the process. Each caller (client IP) now gets that budget, under a process-wide ceiling of 1,200/min for rotating or spoofed keys. The two copies of the limiter middleware became one helper. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(inspector): key proxy budgets on the proxy-added X-Forwarded-For entry X-Real-IP and the leftmost X-Forwarded-For entry can be set by the client, so each spoofed value got a fresh budget and only the process ceiling held. The rightmost entry is the one the platform proxy adds (Vercel overwrites the header, Railway appends), so a client behind it cannot choose its budget. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(inspector): skip the per-caller budget without X-Forwarded-For A local Inspector or mcp-use dev has no platform proxy adding X-Forwarded-For, so every request fell into one "unknown" bucket of 120/min, the same shared budget as before. The e2e suite drains it and gets 429s. Without the header callers can't be told apart, so apply only the process-wide ceiling. Claude-Session: https://claude.ai/code/session_01MEJoDUZb843zNMtq3jGMXu --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: Andrew Khadder <[email protected]> * chore(typescript): version packages (canary) * docs: changelog for mcp-use v2.11.0 and inspector v20.3.22 Claude-Session: https://claude.ai/code/session_01MEJoDUZb843zNMtq3jGMXu --------- Co-authored-by: Vansh Bhardwaj <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: 哈基米 <[email protected]> Co-authored-by: Freetrip <[email protected]> Co-authored-by: aishwary-dongre <[email protected]> Co-authored-by: Aishwary Dongre <[email protected]> Co-authored-by: Arpitm544 <[email protected]> Co-authored-by: Enrico Toniato <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
This was referenced Oct 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request Description
Where did you find this issue?
Reported in #2637, which includes a stubbed-
fetchreproduction: a 200 body like{"result": "The answer"}(noerrorkey) maderun()throwRemote agent execution failed: [object Object], while the same body with an expliciterror: nullresolved.Language / Project Scope
Check all that apply:
Changes
RemoteAgent.runno longer treats a 200 response as a failure just because the body omits theerrorkey, and rejection messages now render object error values as JSON instead of[object Object].Implementation Details
libraries/typescript/packages/agent/src/agents/remote.ts:347— the guardresult.status === "error" || result.error !== nullrejected every body whoseerrorkey is absent, becauseundefined !== null. Changed toresult.error != null, so an absenterrorfield counts as no error while realerrorvalues andstatus: "error"bodies still reject. This matches the comment above the guard ("Check for actual error conditions (not just presence of error field)") and the success handling ten lines below ("result" in result→return result.result).remote.ts:348—result.error ?? String(result)interpolated an object error value as[object Object];JSON.stringify(result.error)keeps the payload readable.String(result).includes("failed to initialize")check is removed. It was dead code (String({})is"[object Object]"), so it could never match. The error check now throws the bare message and the existing catch adds the "Remote agent execution failed:" prefix once (it used to be added twice). A test pins that a success body quoting "failed to initialize" still resolves.tests/agents/remote_run.test.ts(7 cases, stubbedglobalThis.fetch, no network) and a changeset. No dependencies added or modified.TypeScript Checklist
Packages Modified
Check all packages that were modified:
docstestsclicreate-mcp-use-appmcp-use(server)mcp-use(client)inspector(
@mcp-use/agentsource and tests — that package is not in the template's list.)Pre-commit Checklist
pnpm lint— clean, sopnpm lint:fixchanges nothingpnpm format:check— clean, sopnpm formatchanges nothingpnpm buildand build succeeds without errors.changeset/smart-lions-rest.md,"@mcp-use/agent": patch)docs/folder if needed — not needed (internal error handling, no API change)Python Checklist
Not applicable.
Example Usage (Before)
Example Usage (After)
Documentation Updates
None — internal error-handling fix with no API change.
Testing
tests/agents/remote_run.test.ts, 7 cases stubbingglobalThis.fetch(no network): absenterrorfield resolves;status: "success"resolves; expliciterror: nullresolves; stringerrorrejects;status: "error"rejects; objecterrorrenders as JSON in the message; a success body whose result text mentions "failed to initialize" still resolves.mainthe file fails 4 of 7 cases (the three success-shape cases plus the object-error message); with this change 7/7 pass.pnpm --filter @mcp-use/agent test:unit— 20 files, 155 passed, 1 skipped.pnpm format:check,pnpm lint,pnpm build— all clean.pnpm knipreports findings onmainalready (create-mcp-use-app / server / inspector); verified identical with this change stashed, and none referencepackages/agent.Backwards Compatibility
The behavior change is the fix itself: 200 bodies without an
errorkey now succeed instead of throwing. Bodies with a realerrorvalue orstatus: "error"still throw; the thrown message for object error values changes from[object Object]to the JSON payload.Related Issues
Closes #2637
Closes #2851 (the unreachable "failed to initialize" check is removed).
Summary by cubic
Fixes
RemoteAgent.runrejecting successful 200 responses whose body omits theerrorfield, sinceundefined !== nullflagged every such body as a failure. An absenterrorfield now counts as no error; real error values andstatus: "error"bodies still reject, object error values render as JSON instead of[object Object], string errors stay unquoted, and status-only error bodies fall back to the serialized response. Also removes the dead "failed to initialize" check, which could never match, and builds the error message in one expression so the "Remote agent execution failed:" prefix is added once.error,error: null,status: "success"), error shapes, JSON and plain-string error rendering, and status-only error bodies.Written for commit f4b0ddc. Summary will update on new commits.