# SPDX-FileCopyrightText: 2026 James Harton
#
# SPDX-License-Identifier: Apache-2.0
defmodule BB.MCP.Tools.QueryEvents do
@moduledoc """
Query pubsub events captured by the current MCP session.
Each session subscribes to all configured robots' pubsub trees on connect
and records messages in a ring buffer (capacity configurable via
`:bb_mcp, :event_buffer_size`, default 1000). This tool returns the most
recent events, optionally filtered.
Events arrive in real time so the agent can use this tool after invoking
a command to inspect motion lifecycle, joint-state changes, or other
pubsub traffic without polling the robot state.
"""
use Anubis.Server.Component, type: :tool
alias Anubis.Server.Response
alias BB.MCP.EventBuffer
alias BB.MCP.Tools
schema do
field(:robot, :string, description: "Filter to events from one robot")
field(:message_type, :string,
description:
"Substring match against the payload module name (e.g. \"JointState\", " <>
"\"BB.Command.Event\")"
)
field(:path_prefix, :string,
description: "Dotted path prefix, e.g. \"sensor\" or \"command.move_to_pose\""
)
field(:since_ms, :integer,
description: "Only return events received within the last N milliseconds"
)
field(:limit, :integer, description: "Maximum events to return (default 50)")
end
@impl true
def execute(params, frame) do
filters = build_filters(params)
buffer = Map.get(frame.assigns, :event_buffer, EventBuffer.new())
events = EventBuffer.query(buffer, filters)
payload = %{
"count" => length(events),
"capacity" => buffer.capacity,
"buffered" => length(buffer.events),
"events" => events
}
{:reply, Response.json(Response.tool(), payload), frame}
end
defp build_filters(params) do
%{
robot: Tools.get_arg(params, :robot),
message_type: Tools.get_arg(params, :message_type),
path_prefix: Tools.get_arg(params, :path_prefix),
since_ms: Tools.get_arg(params, :since_ms),
limit: Tools.get_arg(params, :limit) || 50
}
end
end