Orbit C++ → Rust · Post 23

A Profiler an Agent Can Drive

External agents can drive a live Orbit profile end to end. orbit-service mcp is a Rust Model Context Protocol server: list processes, attach, search functions, arm Frida hooks with no rebuild, start and stop a capture, and read the hotspots, the call stacks, and the per-function timings back.

12MCP tools, JSON-RPC 2.0
0Model calls the server makes. Keys stay with the agent
16Functions armed at once, the service’s own cap
2Transports: stdin, and loopback HTTP

The viewer is one client of orbit-service. An agent is another. It should be able to do what the Record button does — pick a process, hook a function that is already running, capture, read the numbers, unhook, capture again — without a human clicking, and without a dump of the event ring in the tool result.

What shipped

orbit-service mcp is a subcommand of the same binary that serves the viewer. The default speaks JSON-RPC on stdin, which is the config Claude Code and Cursor paste. --http 127.0.0.1:44767 is the same tools as streamable HTTP. Both are clients of the service’s HTTP API. The capture keeps running in orbit-service when the agent disconnects.

A separate Python server, orbit-optimize-loop, exists on the loop branch. It builds a project, benches it, and can apply a patch. That is a different product. It does not arm hooks, and applying a patch is not profiling. It was not merged. An agent that wants to edit source already has its editor. This server will not do it.

The twelve tools

orbit_capabilities comes first. It says whether the service is up, and which features this operating system actually has. Then orbit_list_processes, orbit_attach, orbit_search_functions, orbit_arm_hooks, orbit_start_capture, orbit_stop_capture. After the stop: orbit_function_stats for hooked functions and manual scopes, and on Linux orbit_sampling_report and orbit_callstacks. orbit_unhook edits the set the next capture will install. A capture that is already running keeps the hooks it started with.

Results are pages, not dumps. Twenty processes, fifteen functions, fifteen hotspots, eight stacks. Times in the function table are integer nanoseconds. A search that asks to skip past 200 hits is refused: narrow the query. At most sixteen functions are armed, which is the cap the service already enforces, said out loud instead of silently truncated.

Linux, and macOS for what macOS does

Frida hooks and manual scopes work on both. Sampling, the scheduler, kernel uprobes, and auto-profiling are Linux. On macOS those tools return supported: false and a sentence that says to use orbit_function_stats. Asking to start a capture with sampling turned on does not start the capture. An empty hotspot list would look like a quiet program. It is not that.

On Linux a report can still come back with zero samples, when perf_event_paranoid is too high. That result says supported: true and samples: 0. The distinction is the point.

What it will not do

The HTTP API has no token. The MCP server does not add one. It refuses a service URL that is not loopback unless --allow-remote is set, and that flag is the warning: anyone who can reach the service can attach to processes as the user running it. --http refuses a bind that is not loopback, and a browser Origin that is not loopback. Nothing in the tool list deletes a file, rewrites source, or kills the target. The destructive operation is the one Orbit already had: a hook in a running process, removed when the capture stops.

What is proven

Unit tests drive the twelve tools against a fake service: paging, an ambiguous symbol that is not armed, a start body that contains the hook, an unhook, and a second start whose body does not. A macOS platform refuses sampling without posting a capture. An end-to-end test in the Frida workflow starts the real service, runs the Frida sample target, and goes through attach, hook, capture, stats, unhook, and a second capture where that function is gone. The manual has the Claude Code and Cursor configs to paste.