Repository navigation
Architecture
RoseMCP is several processes, and which of them exist depends on whether a tray is running. This page shows both arrangements, then follows one live-app request from the agent to the running app and back.
| Process | What it does | Started by |
|---|---|---|
| RoseMcp.Server | The executable an MCP client registers. Over stdio it either relays to a tray or runs a broker of its own; with --transport http it is a long-lived broker. |
Your MCP client, or you |
| RoseMcp.Tray | The same broker, hosted in-process behind http://127.0.0.1:5077, with a window that shows every workspace and debug session. |
You, or Windows at sign-in |
| RoseMcp.Worker | Owns one MSBuildWorkspace for one solution. All Roslyn work happens here. |
The broker, one per solution |
| RoseMcp.LiveApp | The live-app host: one ICorDebug session and one XAML diagnostics session for one debugged process, built per architecture (x64, x86, arm64) to match the target. | The broker, one per debug session |
| XAML tap |
RoseMcp.Xaml.Uwp.Tap.dll or RoseMcp.Xaml.WinUi.Tap.dll, a native provider loaded into the target app. Walks the visual tree, applies live edits, and draws the Rose panel. |
Injected by the live-app host |
| RoseMcp.Inspector | A window for one debugged process. Owns nothing; reads and drives a session over the broker's operator API. | The tray's Inspect, or you |
Every child talks MCP over stdio to the process that started it, and every one exits when that stdin closes. Nothing is orphaned when a broker goes away.
With a tray running, every stdio session on the machine relays to it. The tray holds the workers and the debug sessions, so they are shared by every agent and visible in one window.
flowchart LR
subgraph clients["Agents"]
a1(["MCP client<br/>repo A"])
a2(["MCP client<br/>repo B"])
end
s1["RoseMcp.Server<br/>stdio · TrayRelay"]
s2["RoseMcp.Server<br/>stdio · TrayRelay"]
subgraph tray["RoseMcp.Tray · http://127.0.0.1:5077"]
broker["Broker<br/>WorkspaceManager<br/>LiveAppSessionManager"]
window["Tray window"]
operator["/operator API<br/>bearer token, minted per run"]
end
wa["RoseMcp.Worker<br/>A.sln"]
wb["RoseMcp.Worker<br/>B.slnx"]
host["RoseMcp.LiveApp<br/>win-x64"]
inspector["RoseMcp.Inspector"]
subgraph app["Debugged app"]
target["App code"]
tap["XAML tap<br/>+ Rose panel"]
end
a1 -- stdio --> s1
a2 -- stdio --> s2
s1 -- "MCP over http<br/>+ origin directory" --> broker
s2 -- "MCP over http<br/>+ origin directory" --> broker
window -. reads in-process .-> broker
broker -- "MCP over stdio" --> wa
broker -- "MCP over stdio" --> wb
broker -- "MCP over stdio" --> host
inspector -- "http + token" --> operator
operator --> broker
host -- ICorDebug --> target
host -- "injects, then<br/>named pipe" --> tap
-
The relay adds one fact. A stdio process knows the directory its client started in, and an
http broker serving every repository does not.
TrayRelaysends that directory along with each call and changes nothing else, so a bare call still resolves to the right solution. - The tray launches the inspector. It hands over the port and its per-run token, so you never see the token unless you ask for it with Copy inspector command.
- One warm worker per solution, whichever agent asked first.
With no tray, a stdio server does everything itself. Its workers and debug sessions belong to that one client and end with it.
flowchart LR
client(["MCP client"])
subgraph server["RoseMcp.Server · stdio"]
broker["Broker"]
end
worker["RoseMcp.Worker<br/>A.sln"]
host["RoseMcp.LiveApp<br/>win-x64"]
subgraph app["Debugged app"]
target["App code"]
tap["XAML tap<br/>+ Rose panel"]
end
client -- stdio --> broker
broker -- "MCP over stdio" --> worker
broker -- "MCP over stdio" --> host
host -- ICorDebug --> target
host -- "injects, then<br/>named pipe" --> tap
There is no listener in this arrangement, so there is no inspector. The Rose panel still works: it lives in the app, not in a window of ours.
RoseMcp.Server --transport http is the third option. It is the tray without the window: a
long-lived broker with the same MCP endpoint and operator API, gated by ROSEMCP_TOKEN when that is
set. If the token is minted instead, it is written to the server's log once.
An agent asks for the visual tree of an app it launched under the debugger:
sequenceDiagram
autonumber
participant Agent as MCP client
participant Broker
participant Host as RoseMcp.LiveApp
participant Tap as XAML tap (in the app)
Agent->>Broker: rose_xaml_tree(sessionId)
Broker->>Host: forwards over stdio
alt no provider resident yet
Host->>Host: create named pipe rosemcp-xaml-{pid}-{guid}
Host->>Tap: InitializeXamlDiagnosticsEx loads the tap
Tap->>Tap: walk the tree, put the Rose panel up
Tap->>Host: connect to the pipe
end
Host->>Tap: request frame
Tap->>Tap: work on the app's UI thread
Tap->>Host: reply frame
Host->>Broker: LiveXamlTree
Broker->>Agent: result, attributed to the session
- Injection happens once per session. Every later request, including the inspector's, is a length-prefixed frame on the same pipe. The tap stays loaded until the app exits.
- One request at a time. The tap does its work on the app's UI thread, so requests from the agent and from the inspector queue behind each other rather than interleaving.
- A stopped app cannot answer XAML requests. While the debugger holds the target at a breakpoint, the UI thread that would serve the request is frozen too. Both the tool and the inspector report that rather than waiting.
-
The panel and the agent share a selection. An element picked by clicking in the app is what
rose_xaml_selectionreturns, and an element the agent selects is outlined in the app.
Getting started
Working with C#
Debugging and XAML
About