APP-2527 Phase 2: MCP data pipeline#12828
Conversation
cephalonaut
left a comment
There was a problem hiding this comment.
Phase 2 Review — APP-2527
Verdict: MINOR — all blocking Phase 2 requirements are met; four minor items must be resolved before Phase 3 launches.
✅ Requirements satisfied
McpRequest { name: String, args: serde_json::Value }struct defined and used.RequestedCommandViewcarriesmcp_request: Option<McpRequest>andmcp_tree_state: JsonTreeState.ToggleJsonNode { path: Vec<PathSegment> }andToggleJsonString { path: Vec<PathSegment> }action variants are present and fully handled inhandle_actionwith the correcttoggle/toggle_string+ctx.notify()calls.JsonTreeStateandPathSegmentare imported from Phase 1 (crate::ui_components::json_tree), not redefined.handle_mcp_tool_stream_updatesignature extended correctly; thecommand_textstring is built identically to before.mcp_result_to_renderablelogic is correct across all five cases: structured_content → JSON text → String fallback → Error → Cancelled.- Five unit tests for
mcp_result_to_renderableare present and meaningful. - The old
should_render_mcp_content/content_textrender path is untouched. - Code comments are free of spec/process references and explain the "why" locally.
🔶 Minor items (must fix before Phase 3)
M1 — Carry-over from Phase 1: file-level doc comment in json_tree_tests.rs
app/src/ui_components/json_tree_tests.rs line 1:
//! Pure-logic unit tests for the `json_tree` component (Phase 1, APP-2527).
Remove the phase/ticket reference. Reword to something like:
//! Pure-logic unit tests for the `json_tree` component.
M2 — Carry-over from Phase 1: no tests for toggle_string / is_string_expanded
JsonTreeState::toggle_string and is_string_expanded are exercised in the Phase 2 action handler but have no unit tests. Add at least one test each to json_tree_tests.rs (e.g., verify collapsed-by-default, toggling to true, toggling back to false).
M3 — Depth calculation in ToggleJsonNode will be wrong if Phase 3 uses synthetic namespace segments
RequestedCommandViewAction::ToggleJsonNode { path } => {
let depth = path.len(); // ← inferred from path length
self.mcp_tree_state.toggle(path.clone(), depth);The spec says mcp_tree_state uses a synthetic prefix segment (PathSegment::Key("__request__") / PathSegment::Key("__response__")) to namespace request and response paths. If Phase 3 follows that scheme, every path will include this extra leading segment, making path.len() equal to render_depth + 1. That shifts the root node from depth 0 (expanded by default) to depth 1 (collapsed by default), breaking the initial-open UX.
Fix before Phase 3 by one of:
- Add
depth: usizeto theToggleJsonNodeaction and have the Phase 3 toggle callback supply the render-time depth directly. - Or commit to Phase 3 using separate
JsonTreeStateinstances for request and response (no namespace segments), in which casepath.len() == render_depthis guaranteed and no action change is needed.
Either approach is valid; the spec should be updated to record the decision.
M4 — Unnecessary .clone() in both action handlers
path is moved out of the enum variant by destructuring, so the clones below are redundant:
// ToggleJsonNode
let depth = path.len();
self.mcp_tree_state.toggle(path.clone(), depth); // .clone() not needed
// ToggleJsonString
self.mcp_tree_state.toggle_string(path.clone()); // .clone() not neededChange to pass path directly (the .len() call does not consume it):
let depth = path.len();
self.mcp_tree_state.toggle(path, depth);self.mcp_tree_state.toggle_string(path);Clippy (-D warnings) will catch this — it must be clean before the Phase 3 PR is opened.
cephalonaut
left a comment
There was a problem hiding this comment.
Phase 2 Re-Review — APP-2527
Verdict: MINOR — M1, M2, M3 fully resolved. M4 still open (redundant clone was relocated, not removed).
✅ M1 — Fixed
File-level doc comment in json_tree_tests.rs now reads:
//! Pure-logic unit tests for the `json_tree` component.
No phase/ticket reference remains.
✅ M2 — Fixed
Three well-designed tests added (string_collapsed_by_default, toggle_string_expands_then_collapses, toggle_string_is_independent_of_node_expansion). Cover the default state, round-trip toggle, and independence from node-expansion state.
✅ M3 — Fixed
Split into mcp_request_tree_state + mcp_response_tree_state. McpTree { Request, Response } enum correctly added. Both action variants carry tree: McpTree and dispatch to the correct state. depth = path.len() is now accurate because no synthetic namespace segments are in the paths. The PartialEq + Eq derives were correctly removed from RequestedCommandViewAction (the Action trait only requires Any + Debug + Send + Sync; no application code compares actions with ==).
🔶 M4 — Still open
The clone was moved from the call site to a let rebinding, but not eliminated:
// ToggleJsonNode handler
RequestedCommandViewAction::ToggleJsonNode { path, tree } => {
let depth = path.len();
let path = path.clone(); // ← still a redundant clone
match tree {
McpTree::Request => self.mcp_request_tree_state.toggle(path, depth),
McpTree::Response => self.mcp_response_tree_state.toggle(path, depth),
}
ctx.notify();
}
// ToggleJsonString handler
RequestedCommandViewAction::ToggleJsonString { path, tree } => {
let path = path.clone(); // ← still a redundant clone
match tree {
McpTree::Request => self.mcp_request_tree_state.toggle_string(path),
McpTree::Response => self.mcp_response_tree_state.toggle_string(path),
}
ctx.notify();
}The original path is not used after the clone is made, so clippy::redundant_clone still fires. Because Rust's borrow checker allows the same variable to appear in multiple match arms (only one arm executes, so there is no double-move), path can be moved directly without cloning:
// ToggleJsonNode — fixed
RequestedCommandViewAction::ToggleJsonNode { path, tree } => {
let depth = path.len();
match tree {
McpTree::Request => self.mcp_request_tree_state.toggle(path, depth),
McpTree::Response => self.mcp_response_tree_state.toggle(path, depth),
}
ctx.notify();
}
// ToggleJsonString — fixed
RequestedCommandViewAction::ToggleJsonString { path, tree } => {
match tree {
McpTree::Request => self.mcp_request_tree_state.toggle_string(path),
McpTree::Response => self.mcp_response_tree_state.toggle_string(path),
}
ctx.notify();
}Presubmit runs cargo clippy --workspace --all-targets --all-features --tests -- -D warnings, so this will fail CI before Phase 3 merges.
cephalonaut
left a comment
There was a problem hiding this comment.
Phase 2 Final Review — APP-2527
Verdict: PASS — all four minors are resolved; no new issues introduced by the API change.
✅ M4 — Fixed (API changed to &[PathSegment])
toggle and toggle_string now accept &[PathSegment], allocating with .to_vec() internally only at write time. The action handlers pass the destructured path: Vec<PathSegment> directly via auto-deref — no clone, no redundant allocation:
RequestedCommandViewAction::ToggleJsonNode { path, tree } => {
let depth = path.len();
match tree {
McpTree::Request => self.mcp_request_tree_state.toggle(path, depth),
McpTree::Response => self.mcp_response_tree_state.toggle(path, depth),
}
ctx.notify();
}
RequestedCommandViewAction::ToggleJsonString { path, tree } => {
match tree {
McpTree::Request => self.mcp_request_tree_state.toggle_string(path),
McpTree::Response => self.mcp_response_tree_state.toggle_string(path),
}
ctx.notify();
}All existing test call sites in json_tree_tests.rs were updated from path.clone() to &path. No other call sites for JsonTreeState::toggle / toggle_string exist in the codebase (other .toggle() calls in the repo are on unrelated types). The API change is complete and consistent.
All minors resolved
| Item | Status |
|---|---|
M1 — doc comment (Phase 1, APP-2527) |
✅ Fixed |
M2 — toggle_string / is_string_expanded tests |
✅ Fixed |
M3 — separate request/response states, McpTree enum |
✅ Fixed |
M4 — redundant .clone() |
✅ Fixed |
Phase 2 is clean. Ready to merge and proceed to Phase 3.
|
I'm starting a first review of this pull request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR threads structured MCP request data into the requested-command view and adds normalization helpers/tests for future JSON tree rendering of MCP results. The attached spec context reports that no approved or repository spec context was found, so there is no material spec drift to validate.
Concerns
- The new Phase 3-only data-layer items are not referenced by non-test code in this diff, while Warp CI runs Clippy with
-D warnings; this is likely to fail before the future rendering work lands. - The new MCP result normalizer silently drops non-text MCP content, which can make image/resource-only tool results render as an empty string once Phase 3 consumes it.
Verdict
Found: 0 critical, 1 important, 1 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| /// Prefers `structured_content` when present; otherwise tries to parse joined | ||
| /// text content as JSON; falls back to wrapping the raw text as a JSON | ||
| /// string value so the tree renderer always receives a `serde_json::Value`. | ||
| pub(crate) fn mcp_result_to_renderable(result: &CallMCPToolResult) -> McpRenderable { |
There was a problem hiding this comment.
-D warnings, so non-test builds are likely to fail on dead_code. Wire these items into the render path in this PR or add a narrowly scoped allow(dead_code) with a removal TODO.
| let text = result | ||
| .content | ||
| .iter() | ||
| .filter_map(|c| { |
There was a problem hiding this comment.
💡 [SUGGESTION] Non-text MCP content is silently discarded here; tools can return images/resources, so a content-only non-text result would become an empty JSON string when this normalizer is used. Preserve unsupported content explicitly or return a renderable fallback instead of dropping it.
|
Response to oz-for-oss review:
|
| /// Structured representation of an MCP tool call request, held so the | ||
| /// detail view can render it as a JSON tree rather than a flat string. | ||
| pub struct McpRequest { | ||
| pub name: String, |
There was a problem hiding this comment.
Why does request need a name but response doesn't? What is the name, is that the name of the MCP server + name of the tool call?
There was a problem hiding this comment.
Good catch — the name field (the tool call name, not the server name) was included for potential future use but turned out to be unused by the tree renderer. The name is already available via command_text for the collapsed header title and doesn't need to be duplicated in McpRequest. Removed in the latest commit.
dc10cf6 to
66d3a57
Compare
76dffa8 to
2007fbe
Compare
Thread structured serde_json::Value request through to RequestedCommandView and normalize CallMCPToolResult into a renderable form. - Add McpRequest struct to hold the tool name and args for tree rendering - Add mcp_request: Option<McpRequest> and mcp_tree_state: JsonTreeState fields to RequestedCommandView - Add ToggleJsonNode and ToggleJsonString action variants; handle them in handle_action by delegating to mcp_tree_state - Add McpRenderable enum and mcp_result_to_renderable() helper: prefers structured_content, falls back to parsing text as JSON, then wraps plain text as a JSON String value - Add update_mcp_request() method on RequestedCommandView - Extend handle_mcp_tool_stream_update() in block.rs to accept and propagate the coerced display_input and name, calling update_mcp_request on the view - Add unit tests for mcp_result_to_renderable in json_tree_tests.rs No user-visible change; the old Text render path stays active. Co-Authored-By: Oz <oz-agent@warp.dev>
M1: Remove phase/spec reference from json_tree_tests.rs doc comment
M2: Add unit tests for toggle_string and is_string_expanded
M3: Split mcp_tree_state into mcp_request_tree_state and
mcp_response_tree_state; add McpTree enum (Request|Response);
add tree: McpTree field to ToggleJsonNode/ToggleJsonString so
handle_action dispatches to the correct state — fixes depth-0
default expansion for both trees
M4: Eliminate extra .clone() calls in handlers by cloning path once
then moving it into the match arm
Co-Authored-By: Oz <oz-agent@warp.dev>
Change toggle() and toggle_string() to accept &[PathSegment] instead of Vec<PathSegment>. The HashMap internally allocates the vec via to_vec(), but call sites no longer clone. Action handlers now pass path (a &Vec<PathSegment>) directly — it auto-coerces to &[PathSegment]. Update all test call sites accordingly. Co-Authored-By: Oz <oz-agent@warp.dev>
2007fbe to
d63aab6
Compare
What
Thread the structured
serde_json::ValueMCP tool call request through toRequestedCommandViewand normalizeCallMCPToolResultinto a renderable form. No visible UI change — the oldTextrender path stays active.McpRequeststruct: holdsname: Stringandargs: serde_json::Valuefor tree renderingRequestedCommandView:mcp_request: Option<McpRequest>andmcp_tree_state: JsonTreeState(path-keyed expansion state for both request and response trees)ToggleJsonNode { path }andToggleJsonString { path }onRequestedCommandViewAction, handled by delegating tomcp_tree_stateMcpRenderableenum andmcp_result_to_renderable(): normalizesCallMCPToolResult— prefersstructured_content, falls back to parsing text content as JSON, then wraps plain text as a JSONStringvalue;ErrorandCancelledmap directlyupdate_mcp_request(): setsmcp_requeston the view from stream updatesblock.rs: extendshandle_mcp_tool_stream_updateto accept and propagate the coerceddisplay_inputandname, callingupdate_mcp_requeston the viewWhy
Phase 2 of APP-2527. Establishes the data pipeline so Phase 3 can wire
render_json_treeinto the MCP tool call detail body without any additional data-layer work.Agent Mode
Testing
Added 5 unit tests for
mcp_result_to_renderableinjson_tree_tests.rs:Successwithstructured_content→Tree(value)Successwith JSON text content →Tree(parsed_value)Successwith non-JSON text →Tree(String(text))Error→Error(msg)Cancelled→CancelledAll 31 tests in the
json_tree_testssuite pass. No existing tests regressed.Conversation: https://staging.warp.dev/conversation/ebf3f6a3-c6f6-4fff-9aca-5e4582527167
Run: https://oz.staging.warp.dev/runs/019ede47-c45b-78a5-ac6e-7fd91b3c6404
This PR was generated with Oz.