Skip to content

feat(host): browse MCP tools and call history - #193

Merged
kitakkun merged 13 commits into
feature/ai-operation-indicatorfrom
feature/mcp-call-history
Jul 28, 2026
Merged

feat(host): browse MCP tools and call history#193
kitakkun merged 13 commits into
feature/ai-operation-indicatorfrom
feature/mcp-call-history

Conversation

@kitakkun

@kitakkun kitakkun commented Jul 28, 2026

Copy link
Copy Markdown
Owner

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

  • The browser is a screen (McpToolsNavKey, opened as a dialog) that subscribes to tools, activity, sessions, and plugin metadata through its own McpToolsScreenContext, 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.
  • McpActivityRepository records 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.
  • McpToolRegistrar is 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 with errorResult(...) — e.g. a click that found no element — was recorded as a success. Both paths now record a failure. structuredContent is 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.
  • Exercised against a running host: the dialog opens seeded from a badge, filters add and remove as chips, tool rows show plugin names and call counts, history records arguments and responses (including truncation), and the copy actions were confirmed by reading the clipboard back.

Notes

kitakkun added 2 commits July 28, 2026 21:39
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
kitakkun force-pushed the feature/mcp-call-history branch from d586e7c to ff7c43e Compare July 28, 2026 12:48
kitakkun added 11 commits July 28, 2026 21:56
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.
@kitakkun kitakkun changed the title feat(host): show MCP call history in the tool dialog feat(host): browse MCP tools and call history Jul 28, 2026
@kitakkun
kitakkun merged commit 2a98ac8 into feature/ai-operation-indicator Jul 28, 2026
2 checks passed
@kitakkun
kitakkun deleted the feature/mcp-call-history branch July 28, 2026 21:29
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.

1 participant