Skip to content

feat(apps): Pass toolInfo to MCP Apps via hostContext#7506

Merged
aharvard merged 2 commits intomainfrom
aharvard/pass-toolinfo-to-mcp-apps
Mar 23, 2026
Merged

feat(apps): Pass toolInfo to MCP Apps via hostContext#7506
aharvard merged 2 commits intomainfrom
aharvard/pass-toolinfo-to-mcp-apps

Conversation

@aharvard
Copy link
Copy Markdown
Collaborator

@aharvard aharvard commented Feb 25, 2026

Summary

Implement the toolInfo field in McpUiHostContext per the MCP Apps spec (ext-apps 2026-01-26). MCP Apps can now access the tool definition (name, description, inputSchema) that triggered their instantiation.

To test: add https://mcp-app-bench.onrender.com/mcp as an extension and prompt "inspect host info" (refer hostContext.toolInfo in below screenshot)

image

What changed

Backend (Rust):

  • Added input_schema field to ToolInfo struct
  • Populated it from rmcp::model::Tool in the get_tools handler

Frontend (TypeScript):

  • Added optional toolName prop to McpAppRenderer
  • On mount, fetches the tool definition from /agent/tools and constructs an MCP SDK Tool object
  • Populates hostContext.toolInfo when available; gracefully omitted if fetch fails
  • Extracts and passes toolName from McpAppWrapper

Generated: OpenAPI spec + client types updated to include input_schema.

Notes

  • toolInfo.id (the JSON-RPC request id of the tools/call request) is not yet populated. That id is generated internally by rmcp's transport layer and isn't surfaced through the extension manager or message stream to the frontend. A comment in the code documents the path to plumb it if needed.
  • toolName is optional — StandaloneAppView renders apps without a tool call context, so toolInfo is simply omitted (valid per spec).

Test plan

  • Verified Rust builds, clippy, and fmt pass
  • Verified TypeScript compiles, ESLint passes
  • lint-staged hooks pass on commit

@aharvard aharvard force-pushed the aharvard/pass-toolinfo-to-mcp-apps branch from c878f0d to d622225 Compare February 25, 2026 15:52
@aharvard aharvard changed the title Pass toolInfo to MCP Apps via hostContext feat(mcp apps): Pass toolInfo to MCP Apps via hostContext Feb 25, 2026
@aharvard aharvard changed the title feat(mcp apps): Pass toolInfo to MCP Apps via hostContext feat(apps): Pass toolInfo to MCP Apps via hostContext Feb 25, 2026
@aharvard aharvard requested a review from Copilot February 25, 2026 17:32
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds MCP Apps spec support for exposing the triggering tool definition to apps via hostContext.toolInfo, wiring the data end-to-end from the Rust /agent/tools endpoint through the desktop renderer.

Changes:

  • Backend: extend ToolInfo with input_schema and populate it in the /agent/tools handler.
  • Frontend: plumb toolName into McpAppRenderer, fetch the tool definition via getTools, and set hostContext.toolInfo when available.
  • Generated: update OpenAPI + generated TS client types to include input_schema.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
ui/desktop/src/components/ToolCallWithResponse.tsx Extracts toolName from the prefixed tool call name and passes it to the app renderer.
ui/desktop/src/components/McpApps/McpAppRenderer.tsx Fetches the tool definition and injects it into hostContext.toolInfo for MCP Apps.
ui/desktop/src/api/types.gen.ts Updates generated API types to include ToolInfo.input_schema.
ui/desktop/openapi.json Updates OpenAPI schema for ToolInfo to include input_schema.
crates/goose/src/agents/extension.rs Extends backend ToolInfo model with optional input_schema and a builder helper.
crates/goose-server/src/routes/agent.rs Populates input_schema when returning tools from /agent/tools.

Comment on lines +500 to +510
@@ -506,6 +507,7 @@ async fn get_tools(
get_parameter_names(&tool),
permission,
)
.with_input_schema(input_schema.unwrap_or_default())
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_tools converts tool.input_schema via serde_json::to_value(...).ok().unwrap_or_default(), which adds unnecessary serialization work and silently falls back to null on the (unlikely) error path; since input_schema is already a JSON object, prefer constructing a serde_json::Value::Object(...) directly (or propagate the error / omit the field instead of defaulting).

Copilot uses AI. Check for mistakes.
Comment on lines +261 to +273
const response = await getTools({
query: { session_id: sessionId, extension_name: extensionName || undefined },
});
if (cancelled || !response.data) return;

const prefixedName = extensionName ? `${extensionName}__${toolName}` : toolName;
const match = response.data.find((t) => t.name === prefixedName);
if (match) {
const tool: Tool = {
name: toolName,
description: match.description || undefined,
inputSchema: (match.input_schema as Tool['inputSchema']) ?? { type: 'object' as const },
};
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fetching /agent/tools on every McpAppRenderer mount pulls the full tool list even though only a single tool definition is needed, which can become an N+1 request pattern when multiple apps render in a session; consider caching the tools list per (sessionId, extensionName) in the UI layer or adding a server endpoint to fetch a single tool by name.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @aharvard this seems to be a valid comment. Would it be helpful if we can cache the tool list?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, we can cache! Will work on that.

Implement the toolInfo field in McpUiHostContext per the MCP Apps spec
(ext-apps 2026-01-26). MCP Apps can now access the tool definition
(name, description, inputSchema) that triggered their instantiation.

Backend:
- Add input_schema field to ToolInfo struct
- Populate it from rmcp::model::Tool in the get_tools handler

Frontend:
- Add toolName prop to McpAppRenderer
- Fetch tool definition from /agent/tools and construct MCP SDK Tool
- Populate hostContext.toolInfo when available
- Extract and pass toolName from McpAppWrapper
@aharvard aharvard force-pushed the aharvard/pass-toolinfo-to-mcp-apps branch from d622225 to 8870afc Compare February 25, 2026 18:09
@aharvard
Copy link
Copy Markdown
Collaborator Author

Addressing Copilot review feedback:

Comment 1 (serde_json::to_value round-trip): Good catch — input_schema is already an Arc<JsonObject> (Map<String, Value>), so the serde_json::to_value round-trip was unnecessary. Fixed to use Value::Object(tool.input_schema.as_ref().clone()) directly.

Comment 2 (N+1 fetch pattern): The fetch is already scoped by extension_name so it's not the full tool list, and toolDefRef prevents re-fetching within the same component instance. In practice MCP Apps are rare in a conversation (typically 1-2). A shared cache per (sessionId, extensionName) would be a nice optimization but adds complexity for minimal gain right now — leaving as a future improvement.

if (cancelled || !response.data) return;

const prefixedName = extensionName ? `${extensionName}__${toolName}` : toolName;
const match = response.data.find((t) => t.name === prefixedName);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused by this. Only a single tool is relevant?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah. the goal is to send metadata of the tool call that instantiated the view. it's a one-to-one relationship.

- Resolve merge conflicts in McpAppRenderer.tsx (imports, useMemo deps)
- Add toolsCache.ts module to deduplicate /agent/tools fetches across
  multiple McpAppRenderer instances in the same session (N+1 fix)
- Keep mcpTool in hostContext useMemo deps from PR branch
- Adopt main's display mode changes (activeDisplayMode, effectiveDisplayModes, etc.)
- Drop appToolResult (removed on main; toolResult passed directly to AppRenderer)

Co-authored-by: Goose <[email protected]>
Ai-assisted: true
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e13411599e

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +288 to +292
const tools = await getCachedTools(sessionId, extensionName || undefined);
if (cancelled || !tools) return;

const prefixedName = extensionName ? `${extensionName}__${toolName}` : toolName;
const match = tools.find((t) => t.name === prefixedName);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retry tool lookups when the extension is still loading

/agent/tools is not a safe one-shot lookup here. ExtensionManager::fetch_all_tools turns per-extension list_tools errors into an empty list instead of failing (crates/goose/src/agents/extension_manager.rs:982-990), and this component already documents transient extension startup failures in the resource-fetch effect below. If the first tools request lands during that window, match never resolves and this effect never retries, so the app can render permanently without hostContext.toolInfo even though the resource itself eventually loads.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a practical concern. McpAppRenderer only mounts inside ToolCallWithResponse — i.e., after the agent has already called the tool and received a result with a resourceUri. For that to happen, the extension must have been fully loaded and its tools listed. So by the time this useEffect fires, the extension's tools are guaranteed to be available from /agent/tools.

The only other mount point (StandaloneAppView) doesn't pass toolName, so the effect early-returns and mcpTool stays null — which is correct since standalone apps aren't tool invocations.

The race condition described here can't happen in the actual call path.

Comment on lines +27 to +29
const key = cacheKey(sessionId, extensionName);
const existing = cache.get(key);
if (existing) return existing;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate cached tool definitions after session extension changes

This cache is keyed only by (sessionId, extensionName) and is reused forever, but the desktop UI can add/remove extensions on a live session (ui/desktop/src/components/bottom_menu/BottomMenuExtensionSelection.tsx:133-149). Because clearToolsCache is never called anywhere in ui/desktop/src, re-enabling or reconfiguring an extension in the same chat will keep serving the old /agent/tools snapshot, so later apps can receive a stale inputSchema or no toolInfo at all for newly added tools.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True that clearToolsCache is exported but not wired up — but low impact in practice:

  1. The cache is keyed by (sessionId, extensionName). Adding a different extension uses a different key — no staleness.
  2. Removing and re-adding the same extension serves the cached tools list, but tool definitions (name, description, inputSchema) for a given extension don't change between disable/enable cycles.
  3. toolInfo is optional per spec — worst case the app gets a slightly stale schema, not a crash.

Not worth adding the wiring complexity for this edge case right now. Could revisit if extensions start having mutable schemas mid-session.

@aharvard
Copy link
Copy Markdown
Collaborator Author

Would it be helpful if we can cache the tool list?

@lifeizhou-ap, done, PTAL. Thanks!

@aharvard aharvard requested a review from lifeizhou-ap March 19, 2026 14:05
@aharvard aharvard added this pull request to the merge queue Mar 23, 2026
Merged via the queue into main with commit a43cbe5 Mar 23, 2026
24 checks passed
@aharvard aharvard deleted the aharvard/pass-toolinfo-to-mcp-apps branch March 23, 2026 13:19
wpfleger96 added a commit that referenced this pull request Mar 23, 2026
* origin/main: (62 commits)
  Tweak the release process: no more merge to main (#7994)
  fix: gemini models via databricks (#8042)
  feat(apps): Pass toolInfo to MCP Apps via hostContext (#7506)
  fix: remove configured marker when deleting oauth provider configuration (#7887)
  docs: add vmware-aiops MCP extension documentation (#8055)
  Show setup instructions for ACP providers in settings modal (#8065)
  deps: replace sigstore-verification with sigstore-verify to kill vulns (#8064)
  feat(acp): add session/set_config and stabilize list, delete and close (#7984)
  docs: Correct `gosoe` typo to `goose` (#8062)
  fix: use default provider and model when provider in session no longer exists (#8035)
  feat: add GOOSE_SHELL env var to configure preferred shell (#7909)
  fix(desktop): fullscreen header bar + always-visible close controls (#8033)
  docs: add Claude Code approve mode permission routing documentation (#7949)
  chatgpt_codex: Support reasoning and gpt-5.4 (#7941)
  refactor(anthropic): fix N+1 thinking message storage issue (#7958)
  fix: handle mid-stream error events in OpenAI SSE streaming (#8031)
  Fix apps extension: coerce string arguments from inner LLM responses (#8030)
  feat: ability to expand sidebar to see chats names  (#7816)
  Fix config for GOOSE_MAX_BACKGROUND_TASKS (#7940)
  set MACOSX_DEPLOYMENT_TARGET=12.0 (#7947)
  ...
wpfleger96 added a commit that referenced this pull request Mar 23, 2026
…pstream

* wpfleger/socket-support: (62 commits)
  Tweak the release process: no more merge to main (#7994)
  fix: gemini models via databricks (#8042)
  feat(apps): Pass toolInfo to MCP Apps via hostContext (#7506)
  fix: remove configured marker when deleting oauth provider configuration (#7887)
  docs: add vmware-aiops MCP extension documentation (#8055)
  Show setup instructions for ACP providers in settings modal (#8065)
  deps: replace sigstore-verification with sigstore-verify to kill vulns (#8064)
  feat(acp): add session/set_config and stabilize list, delete and close (#7984)
  docs: Correct `gosoe` typo to `goose` (#8062)
  fix: use default provider and model when provider in session no longer exists (#8035)
  feat: add GOOSE_SHELL env var to configure preferred shell (#7909)
  fix(desktop): fullscreen header bar + always-visible close controls (#8033)
  docs: add Claude Code approve mode permission routing documentation (#7949)
  chatgpt_codex: Support reasoning and gpt-5.4 (#7941)
  refactor(anthropic): fix N+1 thinking message storage issue (#7958)
  fix: handle mid-stream error events in OpenAI SSE streaming (#8031)
  Fix apps extension: coerce string arguments from inner LLM responses (#8030)
  feat: ability to expand sidebar to see chats names  (#7816)
  Fix config for GOOSE_MAX_BACKGROUND_TASKS (#7940)
  set MACOSX_DEPLOYMENT_TARGET=12.0 (#7947)
  ...
lifeizhou-ap added a commit that referenced this pull request Mar 24, 2026
* main: (37 commits)
  fix: handle reasoning content blocks in OpenAI-compat streaming parser (#8078)
  chore(acp): build native packages on latest mac (#8075)
  Display delegate sub agents logs in UI (#7519)
  Update tar version to avoid CVE-2026-33056 (#8073)
  refactor: consolidate duplicated dependencies into workspace (#8041)
  tui: set up for publishing via github actions (#8020)
  feat: feature-gate local inference dependencies (#7976)
  feat: ability to manage sub recipes in desktop ui (#6360)
  Tweak the release process: no more merge to main (#7994)
  fix: gemini models via databricks (#8042)
  feat(apps): Pass toolInfo to MCP Apps via hostContext (#7506)
  fix: remove configured marker when deleting oauth provider configuration (#7887)
  docs: add vmware-aiops MCP extension documentation (#8055)
  Show setup instructions for ACP providers in settings modal (#8065)
  deps: replace sigstore-verification with sigstore-verify to kill vulns (#8064)
  feat(acp): add session/set_config and stabilize list, delete and close (#7984)
  docs: Correct `gosoe` typo to `goose` (#8062)
  fix: use default provider and model when provider in session no longer exists (#8035)
  feat: add GOOSE_SHELL env var to configure preferred shell (#7909)
  fix(desktop): fullscreen header bar + always-visible close controls (#8033)
  ...
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.

4 participants