fix: add missing tool result to AppsRenderer#1075
fix: add missing tool result to AppsRenderer#1075cliffhall merged 8 commits intomodelcontextprotocol:mainfrom
Conversation
|
|
||
| const runTool = async () => { | ||
| try { | ||
| const result = await mcpClient.request( |
There was a problem hiding this comment.
While calling the tool from here works.
I wonder if the tool result is already present and can be passed as a prop to this component instead of making a brand new tool request.
let me know if that's preferable
There was a problem hiding this comment.
I tested this PR and it works. However, I would rather this part operate more like the ToolsTab tool calling scheme. To test the theory, I removed these changes and passed down toolResult from App -> AppsTab -> AppRenderer and it works, but only if I run the tool in the tool tab first.
To complete the scenario, we would need to pass down callTool from the App, and in AppsTab.handleSelectTool, where we set the app to be open if there is no form to fill first, call the tool. Also in AppsTab.handleOpenApp which handles the Open App button if there was a form to fill, call the tool. Probably there can be a common function that is called from both those places.
|
@cliffhall this should also fix the pdf server and the other examples that were failing |
cliffhall
left a comment
There was a problem hiding this comment.
Agree that using toolResult and the existing callTool function is better. Following the pattern with ToolsTab I see it can be done.
|
|
||
| const runTool = async () => { | ||
| try { | ||
| const result = await mcpClient.request( |
There was a problem hiding this comment.
I tested this PR and it works. However, I would rather this part operate more like the ToolsTab tool calling scheme. To test the theory, I removed these changes and passed down toolResult from App -> AppsTab -> AppRenderer and it works, but only if I run the tool in the tool tab first.
To complete the scenario, we would need to pass down callTool from the App, and in AppsTab.handleSelectTool, where we set the app to be open if there is no form to fill first, call the tool. Also in AppsTab.handleOpenApp which handles the Open App button if there was a form to fill, call the tool. Probably there can be a common function that is called from both those places.
@cliffhall I have now implemented a flow with the following:
Hope that covers everything give it a test and let me know |
There was a problem hiding this comment.
@infoxicator Code looks good, but I see one lint error. (Clearly we need lint to happen in our CI)

There was a problem hiding this comment.
Pull request overview
This PR fixes a bug where the Apps tab was missing tool result data, causing apps like the Budget Allocator Server example to not display correctly. The fix implements a complete Tools → Apps handoff mechanism that passes both tool input parameters and execution results to the AppRenderer.
Changes:
- Added cross-tab prefill state management to pass tool call data (params + result) from Tools tab to Apps tab
- Modified Apps tab to execute tools before opening app renderer, with loading states and race condition guards
- Updated AppRenderer to accept and normalize tool results before passing to MCP UI renderer
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
client/src/App.tsx |
Adds PrefilledAppsToolCall state and logic to capture/clear tool results for app tools when executed from Tools tab |
client/src/components/AppsTab.tsx |
Implements executeToolAndOpenApp flow with loading states, race condition guards via run IDs, and automatic prefilled app launching |
client/src/components/AppRenderer.tsx |
Adds toolResult prop with normalization logic to handle both direct and wrapped CompatibilityCallToolResult formats |
client/src/components/ToolsTab.tsx |
Changes callTool return type from Promise to Promise |
client/src/components/__tests__/AppsTab.test.tsx |
Comprehensive tests for async app launching, prefilled calls, param snapshot preservation, error recovery, and race condition handling |
client/src/components/__tests__/AppRenderer.test.tsx |
Tests for tool result prop passing, normalization of wrapped results, and bridge event delivery |
client/src/__tests__/App.toolsAppsPrefill.test.tsx |
Integration test verifying end-to-end Tools → Apps prefill handoff |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| try { | ||
| return structuredClone(source); | ||
| } catch { | ||
| return { ...source }; | ||
| } |
There was a problem hiding this comment.
The cloneToolParams function is duplicated between App.tsx and AppsTab.tsx with identical implementations. Consider extracting this utility function to a shared location like client/src/utils/paramUtils.ts to follow the DRY principle and maintain consistency.
| try { | |
| return structuredClone(source); | |
| } catch { | |
| return { ...source }; | |
| } | |
| if (typeof structuredClone === "function") { | |
| try { | |
| return structuredClone(source); | |
| } catch { | |
| // Fall through to the shallow-clone fallback below. | |
| } | |
| } | |
| // Fallback for environments without structuredClone or if it fails. | |
| return { ...source }; |
Summary
Fixes Apps tab missing tool result data causing Budget Allocator Server example not to display correctly.
This was to do with missing tool result not passed to the AppsRenderer component
Type of Change
Changes Made
In Apps tab, apps received
ui/notifications/tool-inputbut notui/notifications/tool-result. Apps that initialize from ontoolresult (for example, budget allocator) could render empty due to missing tool dataHere’s a PR-ready summary (non-test changes only):
Summary
This PR wires the Tools -> Apps result handoff so app UIs can open with both the submitted input and latest tool result, while also improving app-launch execution flow and state handling.
What changed
Appto store the latest app tool invocation payload (toolName, params, and result) when run from the Tools tab.getToolUiResourceUri(...), so both_meta.ui.resourceUriand_meta["ui/resourceUri"]metadata are supported.callTool(...)to returnCompatibilityCallToolResultso callers can immediately reuse the result.AppsTabnow accepts:callTool,prefilledToolCall,onPrefilledToolCallConsumed.AppsTabnow executes tools before opening the app renderer and manages explicit launch state:Opening App...) + disabled open button,submittedParams,submittedToolResult) to avoid mid-flight form edits leaking into rendered app input,App.AppsTabto prevent stale async completions from older launches overriding the current app state.AppsTabfailure behavior: rejected tool calls now reset opening state and keep user in input view instead of leaking unhandled promise errors.AppRenderernow acceptstoolResultand normalizes compatibility result shapes (including wrapped{ toolResult: ... }) to validCallToolResultbefore passing to the MCP UI renderer.Related Issues
Testing
Added comprehensive unit tests (integration)
Test Results and/or Instructions
Run the budget allocation server example: https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/budget-allocator-server
Broken version:

Fixed version:
Checklist
npm run prettier-fix)Breaking Changes
Additional Context