Source of Truth: docs/ARCHITECTURE.md
Status: Canonical repo cleanup aligned to the current architecture as of 2026-05-16. This post continues the core series from observable workflows into host correctness, UI state, and proposal lifecycle ownership.
VS MCP Bridge Blog Series: Part 3
Host Correctness, Proposal State, and UI Thread Safety
Part 2 explained that useful AI-assisted development is not a single prompt turning into a single command. It is a workflow: the AI may read context, propose an edit, wait for approval, and then report a result.
Part 3 focuses on what keeps that workflow correct inside Visual Studio.
The short version is that transport can be asynchronous, but Visual Studio access, UI state, and proposal lifecycle state must be owned deliberately. The bridge became easier to test and reason about when proposal state stopped being incidental UI behavior and became an explicit lifecycle managed through IProposalManager.
Transport Work Is Not UI Work
The local bridge has background work by design. The MCP server receives requests over stdio. VS-backed requests cross the named-pipe boundary. The pipe server accepts and dispatches requests without requiring the UI thread to wait for transport input.
That part is healthy:
MCP request arrives
-> named-pipe request is dispatched
-> host service handles the operation
-> result returns through the bridge
But Visual Studio API access is different. DTE, editor state, tool window state, and view-model updates have host threading rules. The bridge has to separate background request handling from UI-thread-sensitive work.
Visual Studio Host Correctness
The VSIX is the Visual Studio host. It owns Visual Studio APIs, editor state, proposal application, and tool-window interaction. The MCP server must not bypass that host boundary.
That gives the system a practical rule:
background transport can be asynchronous
Visual Studio work must happen through the host boundary
UI state must be updated through the UI orchestration path
This keeps the bridge from turning an AI tool call into arbitrary background mutation of Visual Studio state.
The Tool Window Is State, Not Just Display
The VS MCP Bridge tool window is not only a place to print logs. It is also the human review surface for proposal entry, pending approval, completed proposal previews, status messages, and reset/new-chat behavior.
That means the view model has meaningful workflow state:
ProposalFilePath for the current target file,
- selected proposal files for multi-file proposals,
- pending approval description and reviewed changes,
- included files for pending and completed proposal review,
- last completed proposal preview data,
- terminal status messages,
- reset and new-chat command state.
If that state is updated from the wrong place, the UI becomes hard to reason about. Worse, approval callbacks can outlive the proposal they were meant to control.
Why IProposalManager Matters
IProposalManager is the seam that keeps proposal lifecycle ownership out of the general presenter path.
The presenter still orchestrates the tool window, logs, and host-facing interaction. But proposal-specific lifecycle behavior belongs in the proposal manager:
- showing an approval prompt,
- recording pending approval state,
- capturing completed proposal preview state,
- clearing stale approval callbacks,
- resetting current request state,
- starting a new chat/session cleanup path,
- reacting to
ProposalFilePath changes.
That separation matters because proposal lifecycle is not just UI decoration. It defines when a human can approve, what proposal is being approved, what preview remains after completion, and what state must be cleared before a new request.
Approval Prompt State and Callbacks
An approval prompt carries more than a yes/no question. It carries the proposal description, original and updated content, reviewed ranges, included files, and callbacks for approve or reject.
The dangerous case is stale state. If an old callback remains available after a proposal completes, the UI can look idle while old approval behavior is still reachable. The current lifecycle avoids that by making terminal outcomes drive cleanup:
- A proposal reaches pending review.
- The view model shows the pending approval surface.
- The operator approves or rejects.
- The proposal manager captures the completed preview state.
- Pending approval state and callbacks are cleared.
- The proposal entry state is refreshed from the current file path when appropriate.
That is host correctness at the UI boundary. It prevents one proposal's approval surface from silently leaking into the next one.
Drafts, File Selection, and Manual Submission
The proposal entry path also has to support ordinary operator behavior. The user may type a file path manually, use a host-specific picker, load a file into the proposal panes, edit proposed text, and then submit.
The key design point is that there is a single authoritative load path for proposal entry. Picker selection flows through ProposalFilePath rather than creating a second hidden workflow. That keeps manual entry and picker-driven entry aligned.
When ProposalFilePath is valid, the proposal panes can populate. The original pane remains read-only. The proposed pane remains editable until submission. After submission, the review surface owns the pending decision.
Completed Proposal Preview Capture
Completed proposal preview state is intentionally retained after terminal outcomes. The operator should be able to see what just happened, even after approval, rejection, skip, drift failure, ambiguity failure, or generic failure.
That completed preview is separate from pending approval state. Pending approval answers “what can I approve now?” Completed preview answers “what just happened?”
Keeping those separate made the tool window easier to reason about. It also made terminal outcomes more diagnosable because the UI does not collapse all context at the moment the workflow completes.
Reset and New-Chat Cleanup
The reset and new-chat paths are part of proposal correctness.
A reset should clear current proposal/request state without pretending the whole application restarted. A new-chat cleanup should clear session-shaped state so the next interaction is not contaminated by stale prompt, approval, or preview context.
That is why reset/new-chat handlers are wired through the view model into the proposal manager. The UI can expose commands, but proposal lifecycle ownership stays centralized.
How This Improved Testability
Before the proposal manager seam, proposal behavior was easier to treat as presenter side effect. That made the workflow harder to test because UI orchestration, approval state, and lifecycle cleanup were tightly blended.
Moving proposal lifecycle behavior behind IProposalManager made tests more focused:
- pending approval state can be checked directly,
- completed preview capture can be verified separately,
- reset behavior can be exercised without re-running transport setup,
- file-path changes can be tested as proposal lifecycle events,
- callbacks can be checked for terminal cleanup.
That is the same anti-black-box theme from Part 2, applied to UI state. If a workflow is important enough to approve or apply code, it should be testable without guessing what the UI happened to remember.
Relationship to Approval-Aware Execution
There are now two approval concepts in the architecture, and they should stay distinct.
- The proposal approval workflow is the Visual Studio host workflow for reviewing and applying editor changes.
- The approval-aware tool execution seam is part of
BridgeToolExecutor for shared compiled tools whose descriptors require approval before execution.
They share a philosophy: selected actions should stop at an explicit decision boundary. But they are not the same implementation. Part 3 is primarily about the host/UI proposal lifecycle. The compiled-tool approval seam belongs to the shared executor boundary.
Logs and Traces Still Matter
Thread correctness and proposal lifecycle correctness are hard to validate from screenshots alone. Logs and trace artifacts help show where the workflow crossed from transport to host service to UI state.
For proposal work, the useful evidence is not just “the button was clicked.” It is the chain:
proposal request received
proposal loaded into review state
approval or rejection invoked
completed preview captured
pending callbacks cleared
terminal status recorded
That chain is what makes async UI behavior observable instead of mysterious.
Related Mermaid Trace Sources
The repo already has Mermaid sources that help explain the surrounding workflows:
Those .mmd files are the diagram source of truth. This post references them directly rather than embedding generated images.
Takeaway
For the current bridge, host correctness means more than switching to the UI thread at the right time. It means keeping transport, Visual Studio access, UI orchestration, and proposal lifecycle ownership separate.
The working model is:
transport can run asynchronously
host access stays behind the VSIX/service boundary
UI state changes through presenter/viewmodel orchestration
proposal lifecycle belongs to IProposalManager
approval callbacks are cleared at terminal outcomes
completed previews preserve what just happened
That separation reduced black-box behavior. It made proposal workflows easier to test, easier to reset, and easier to explain when an AI-assisted edit crosses from suggestion into human-reviewed action.
Next In The Series
The next useful topic is runtime validation: how to prove MCP tool calls, named-pipe dispatch, proposal creation, approval flow, and diagnostics work end to end in the real host environment.