feat(host): browse MCP tools and call history - #193
Merged
kitakkun merged 13 commits intoJul 28, 2026
Conversation
Keeps a bounded, newest-first record of completed MCP tool calls in McpActivity so the MCP dialog can show what an AI agent actually did with a plugin, not just what it could do. - McpCallRecord captures tool name, plugin, session, success and the epoch-millis completion time; McpActivity.recentCalls caps at 100 entries and drops the oldest. - toolInvocationFinished now takes whether the handler failed, and McpToolRegistrar reports a throwing handler as a failed call. - The MCP dialog gains Tools/History tabs. History lists the calls attributed to that plugin in the selected session, with a success or failure marker and the wall-clock time, and an empty state before any call is made. Search and the parameter pane keep their state across tab switches.
Grow the dialog with the window up to a readable maximum instead of a fixed 760x500, so tool descriptions and parameter lists fit without scrolling. In the tool list, show how many recorded calls each tool has and a pulsing accent dot on the tool an agent is running right now. Counts come from the retained history, so they describe the same calls the History tab lists.
kitakkun
force-pushed
the
feature/mcp-call-history
branch
from
July 28, 2026 12:48
d586e7c to
ff7c43e
Compare
Right-align the per-tool call count as a badge instead of a bare number, so it reads clearly against the tool name, and take the accent fill and rotating ring while that tool is running. Repeat the badge next to the History tab so the number of recorded calls is visible without switching.
Every MCP tool call now carries the arguments it was made with into the call history, so the History tab can explain what an agent actually did rather than only which tool it reached for. Arguments are rendered to strings and shortened to `McpCallArgument.MAX_VALUE_LENGTH` characters at record time, so a call carrying a large body does not keep that body alive for as long as it stays in history. History rows expand on click to list the recorded `name = value` pairs, and a right-click context menu copies the full tool name, the arguments, or the whole call.
Match the Tools tab: a selectable list of calls on the left, and the selected call's full detail on the right — tool name, status, time, and each argument on its own labelled line. Replaces the inline expansion, which cramped long argument values into the row. The right pane also carries copy buttons alongside the existing row context menu.
PluginDrawerItemView had grown to hold the drawer item, the MCP badge, the tool browser, the call history, and their context menus. Move everything from the dialog down into McpToolsDialog.kt so each file covers one screen concern. Code is unchanged apart from the visibility needed across the file boundary and the import split.
Call history showed what an agent asked for but not what it got back. The tool registrar now renders every CallToolResult to text -- text blocks verbatim, binary blocks (image, audio, resource) named rather than inlined so history does not fill with base64 -- and hands it to the repository along with the failure flag. A throwing handler records its message instead, so a failed call is still explainable. Responses are truncated at record time like arguments, but against their own McpCallRecord.MAX_RESPONSE_LENGTH: the response is the payload the user opens history to read, whereas an argument only has to be recognisable. The detail pane gains a Response section rendered in monospace inside a bounded, self-scrolling box, so a long response stays readable without pushing the copy actions out of the pane. Copy response joins the button row and the row context menu, and Copy details carries the response too.
Groundwork for opening the MCP browser as a screen rather than a dialog scoped to one plugin: a nav key whose null plugin/session mean "all", and a screen context that subscribes to the tools, activity, sessions, and plugin metadata itself.
The MCP browser was a dialog handed one plugin's slice of tools and history, so it could not answer "when was this tool last called?" across plugins. Make it a window-backed screen that subscribes to the MCP, session and plugin data itself through `McpToolsScreenContext`, and narrow it with two filters seeded from `McpToolsNavKey`: a null plugin or session id means "All", and both can be changed once the screen is open. Tool rows now name the plugin that publishes them, since the short name alone is ambiguous once plugins are mixed. History keeps calls that named no session visible under a specific session too, because those tools target none. The per-section copy actions move inline next to the heading they copy, leaving "Copy details" as the pane's only labelled button.
The drawer built a per-plugin tool list and call history purely to hand them to a dialog it owned. Now that the screen subscribes to that data itself, the badge only needs to know whether the plugin publishes tools at all, so `DrawerPluginItemUiState` carries `exposesMcpTools` instead of the tools, the history and the running tool name. Clicking the badge navigates to the browser scoped to that plugin and the selected session. A drawer button beside the settings icon opens it unscoped, so it is reachable without a plugin badge.
MCP lets a tool report its own failure by returning a result flagged with `isError` instead of throwing, which is how the built-in tools signal a rejected click or drag. The registrar only watched for a thrown handler, so those calls landed in history marked as successes. Also fold `structuredContent` into the recorded response so a tool that answers with a machine-readable payload is not shown as an empty result.
The plugin and session filters accepted a single value, so two plugins could not be looked at side by side. Both now hold a set of ids, and an empty set means every value passes. Each picked value gets its own removable `InputChip` with a close icon; the group falls back to an "All" chip that opens the picker while its set is empty. The picker keeps a check beside the entries already chosen and stays open across picks so several can be added in one go.
A separate window did not inherit the app theme, leaving the browser unreadable on a white background. Register it with the dialog strategy used by the other dialogs, disable the platform default width so the browser can size itself, and give it the same window-fraction sizing the tool dialog used before it became a screen.
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.
What
Turns the per-plugin MCP badge into a browser for everything an AI agent can do and has done, so the user can answer "what tools exist?" and "what did the agent actually call?" without opening each plugin in turn.
Tools tab — every tool the selected scope publishes, searchable, each row showing its plugin, how many recorded calls it has, and a pulsing accent while an agent is running it. The detail pane shows the tool's description and its parameters (name, type, required, description).
History tab — the calls already made, newest first. The detail pane shows the arguments the agent passed and the response it got back, with copy actions inline next to each section and a right-click menu on the rows.
Filters — plugin and session, both multi-select. Opening from a plugin's badge seeds the filters to that plugin and session; the user can then widen or add to them. Selected values appear as chips and are removed individually with the chip's ×; an empty group reads "All".
How
McpToolsNavKey, opened as a dialog) that subscribes to tools, activity, sessions, and plugin metadata through its ownMcpToolsScreenContext, rather than being handed one plugin's slice. That is what lets it show more than the plugin it was opened from. The drawer correspondingly stops computing per-plugin tool and history lists — its badge only needs two booleans now.McpActivityRepositoryrecords each completed call: tool, plugin, session, success, time, arguments, and response. Values are truncated at record time (80 chars for an argument, 2000 for a response) so the retained history cannot grow into a memory problem. History is capped at 100 entries, newest-first.McpToolRegistraris still the single choke point every tool call passes through, so nothing has to opt in to being recorded.Bug fixed along the way
MCP reports a tool failure two ways: the handler throws, or it returns normally with
isError = true. Only the first was treated as a failure, so a built-in tool answering witherrorResult(...)— e.g. a click that found no element — was recorded as a success. Both paths now record a failure.structuredContentis also included in the recorded response, so a tool that answers there no longer shows an empty response.Verification
spotlessCheck,:jetwhale-host:app:build, and the mcp/data module tests pass.Notes