How stdio Works in VS MCP Bridge

Stdio

Source of Truth: docs/ARCHITECTURE.md
Status: Canonical repo cleanup aligned to the current architecture as of 2026-05-16. Bracket-style tokens are intentional BlogEngine/GwnWikiExtension tokens.

How stdio Works in VS MCP Bridge

In the VS MCP Bridge architecture, stdio is the AI-facing MCP transport boundary. It is important, but it is not the whole bridge.

That distinction matters because “MCP over stdio” can sound as if the AI client is talking directly to Visual Studio. It is not. The AI client speaks MCP to a local server process over standard input and standard output. That server then uses a separate local named-pipe hop when a tool needs Visual Studio state.

This post explains the boundary, why stdout has to stay clean, and how the current implementation keeps diagnostics observable without corrupting the MCP protocol stream.

The Short Version

The runtime path for VS-backed MCP tools is:

AI client
  -> MCP over stdio
VsMcpBridge.McpServer
  -> JSON over named pipe
VsMcpBridge.Vsix
  -> Visual Studio services / DTE / editor state

So stdio gets the request into the local MCP server. The named pipe gets VS-backed work into the VSIX. The two transports are intentionally separate.

Where stdio Is Enabled

The stdio transport is configured in the MCP host bootstrap:

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithTools<VsTools>();

That configuration lives in the VsMcpBridge.McpServer project, inside McpServerHost.Configure(...). The important line is WithStdioServerTransport().

That line tells the MCP host to exchange protocol messages through standard input and standard output instead of through HTTP, a socket listener, or a custom public endpoint.

What stdio Means Here

Standard input and standard output are process streams.

  • stdin is how the AI client writes MCP requests into the server process.
  • stdout is how the server process writes MCP responses back to the client.

That makes stdio a good fit for local AI tooling. The AI client can launch the MCP server as a worker process, keep it alive, write protocol messages to stdin, and read responses from stdout. The MCP server does not need to expose a network port for this local path.

The boundary is still a protocol boundary. stdout is not a casual logging stream once MCP is running over it.

Why stdout Must Stay Clean

One practical consequence of MCP over stdio is that stdout must be reserved for protocol traffic. If the server writes arbitrary diagnostic lines to stdout, the AI client can receive those lines as if they were MCP messages. That can make a healthy server look broken.

For that reason, diagnostics belong somewhere else:

  • stderr when the host framework allows it safely,
  • file logs under the local app-data logging paths,
  • Visual Studio output panes and VSIX trace logs,
  • structured trace artifacts when validating a workflow.

The current architecture treats clean stdout as part of the transport contract. Operational detail is preserved, but it is kept off the response stream that the MCP client is parsing.

What the Entry Point Does

The program entry point is intentionally small:

var builder = Host.CreateApplicationBuilder(args);
McpServerHost.Configure(builder);

await builder.Build().RunAsync();

Startup has a narrow job:

  1. Create the host builder.
  2. Register logging, the pipe client, MCP server support, stdio transport, and the VS-backed tool container.
  3. Build and run the host.

Once the host is running, the MCP tool surface is visible to the AI client over stdio.

What stdio Does Not Do

stdio does not make the MCP server a Visual Studio extension. It does not grant direct DTE access, load inside the Visual Studio process, or apply edits in the editor.

Those responsibilities stay on the VSIX side. The MCP server process stays outside Visual Studio and acts as the local AI-facing adapter.

That separation is one of the core architecture choices in the project:

  • MCP protocol work lives in VsMcpBridge.McpServer.
  • Visual Studio API work lives in VsMcpBridge.Vsix.
  • Shared compiled bridge tools execute through BridgeToolExecutor when they use the shared tool catalog/executor path.

stdio is a transport. It is not the policy, approval, audit, or redaction boundary for compiled bridge tools. That boundary remains BridgeToolExecutor.

How VS-Backed Tool Calls Cross the Boundary

The MCP host exposes the VS-backed tool container registered with WithTools<VsTools>(). That class contains explicit MCP tools such as:

  • vs_get_active_document
  • vs_get_selected_text
  • vs_list_solution_projects
  • vs_get_error_list
  • vs_propose_text_edit
  • vs_propose_text_edits

From the AI client’s perspective, those are MCP tools. The request arrives over stdin, the MCP host resolves the method, and the method executes inside the VsMcpBridge.McpServer process.

For Visual Studio-backed operations, the method still does not call Visual Studio directly. It forwards a structured request through the pipe client.

The Named-Pipe Hop

Inside VsTools, the VS-backed methods use an injected IPipeClient. That client connects to the VSIX-hosted named-pipe side:

using var pipe = new NamedPipeClientStream(".", _pipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
await pipe.ConnectAsync(timeout: 5000, cancellationToken);

The full call path is layered:

  1. The AI client calls an MCP tool over stdio.
  2. The MCP host routes the call to a VsTools method.
  3. The method uses PipeClient to connect to the VSIX over the local named pipe.
  4. The VSIX dispatches the known pipe command and performs the Visual Studio-side operation.
  5. The VSIX returns a structured response through the pipe.
  6. The MCP server writes the MCP response back over stdout.

This is why stdio and the named pipe should be debugged separately. A stdio failure means the AI client and MCP server are not communicating correctly. A pipe failure means the MCP server could not reach the VSIX side.

The Activation Boundary

The named-pipe side is initialized by the Visual Studio extension. For live VS-backed tool calls, the operator must launch the Visual Studio Experimental Instance and open View -> Other Windows -> VS MCP Bridge so the VSIX/tool-window path initializes the local pipe server.

If that pipe side is inactive, the current MCP server returns an activation-focused diagnostic instead of leaving the operator with an opaque timeout. The diagnostic points to the activation steps: launch the Experimental Instance, open the VS MCP Bridge tool window, then retry the VS-backed tool.

That message is still returned as a structured tool failure. The transport does not change, and the server does not start adding retry loops or writing troubleshooting text to stdout outside the MCP response.

Correlation and Trace-Only Diagnostics

Because stdio needs clean protocol output, observability depends on structured diagnostics outside stdout. Current traces preserve request IDs, correlation IDs, operation names, timing, and success or failure outcomes across the relevant boundary.

For the inactive-pipe path, the useful evidence is not a random console line. It is the reconstructable chain:

MCP tool request received
PipeClient attempted named-pipe connection
named pipe was unavailable
activation diagnostic returned
correlation/request metadata preserved
no raw payload or secret values disclosed

That is the anti-black-box discipline used throughout the project. A failure should be explainable from durable logs, trace artifacts, and documented workflow boundaries, not from guessing which process happened to be awake.

Related Mermaid Trace Sources

The repo already has Mermaid sources that make this boundary easier to inspect:

Those .mmd files are the diagram source of truth. This post references them instead of embedding a derived image so the article stays aligned with the durable trace artifacts.

How This Relates to BridgeToolExecutor

The stdio server exposes VS-backed tools directly through the MCP tool container, and those tools cross into the VSIX over the named pipe. Separately, the shared bridge tool architecture has compiled tools, descriptors, capability metadata, approval requirements, secret-reference awareness, redaction, audit envelopes, and classification metadata.

Those shared compiled tools flow through BridgeToolExecutor. That executor is the policy, approval, execution, audit, correlation, and redaction boundary for that path.

The important distinction is:

  • stdio is how an AI client talks MCP to the local server process.
  • named pipes are how VS-backed MCP tools reach the VSIX.
  • BridgeToolExecutor is the shared execution/security boundary for compiled bridge tools.

Keeping those responsibilities separate is what lets the architecture grow without turning transport code, Visual Studio integration, and security policy into one indistinct layer.

What to Remember When Studying This Code

If you are learning the system, keep these files and roles in mind:

  • Program.cs starts the MCP server host.
  • McpServerHost.Configure(...) wires logging, stdio transport, the pipe client, and the MCP tool surface.
  • VsTools defines the VS-backed MCP tools exposed over stdio.
  • PipeClient bridges from the MCP server process into the VSIX.
  • The VSIX owns Visual Studio APIs, editor state, proposal application, and the named-pipe server side.
  • BridgeToolExecutor owns the shared compiled-tool policy and audit boundary.

Once those layers are clear, the implementation is much easier to reason about. The bridge is not one process doing everything. It is a set of local boundaries with explicit responsibilities.

Takeaway

In VS MCP Bridge, stdio is the process-to-process protocol transport that lets an AI client speak MCP to the local server host. The server then uses a separate local named-pipe boundary for Visual Studio-backed operations.

The cleanest mental model is:

stdio gets into the MCP server
named pipes get into Visual Studio
BridgeToolExecutor governs shared compiled tool execution

That separation keeps the bridge observable, debuggable, and easier to evolve. stdout stays clean for MCP. Diagnostics stay reconstructable. Visual Studio work stays in the VSIX. Shared tool execution keeps its own policy and audit boundary.

VS MCP Bridge Blog Series: Part 4

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 host correctness into compiled bridge tools, catalog/executor boundaries, and approval-aware execution.

VS MCP Bridge Blog Series: Part 4

Compiled Tools, Execution Boundaries, and Observable Results

Part 3 focused on Visual Studio host correctness: UI-thread-sensitive work, tool-window state, proposal lifecycle ownership, and why IProposalManager keeps approval state from becoming incidental UI behavior.

Part 4 moves one layer deeper into the shared tool execution architecture.

The bridge now has a compiled tool path where shared tools are described, discovered, selected, executed, logged, audited, and returned through a single boundary. That boundary is important because tools are where an AI-assisted system can easily become opaque. A tool call should not be a mystery box. It should have a descriptor, a request, a result, a correlation trail, and a predictable failure shape.

Why A Tool Boundary Exists

The MCP server exposes a small Visual Studio-backed tool surface over stdio, but the shared bridge also needs a place for reusable compiled tools that are not themselves Visual Studio commands.

The design goal is conservative:

callers ask for a bridge tool
catalog resolves the tool
executor owns policy, approval, logging, audit, redaction, and execution
tool returns a structured result

That shape keeps runtime behavior inspectable. A caller should not instantiate random tool classes and run them directly. If a tool matters enough to be part of the bridge, it should flow through IBridgeToolExecutor.

The Basic Tool Contract

The smallest unit is IBridgeTool. It has two responsibilities:

  • publish a BridgeToolDescriptor,
  • execute a BridgeToolRequest and return a BridgeToolResult.

The descriptor is the tool's contract surface. It gives the bridge enough metadata to explain what the tool is before it runs:

  • Id, Name, and Description,
  • Category, Source, and Host,
  • RequiredCapabilities for future capability-aware policy,
  • ApprovalRequirement for tools that must stop for an approval decision.

The request carries the execution identity:

  • ToolId,
  • RequestId,
  • OperationId,
  • structured arguments.

The result carries the same identity back out:

  • ToolId,
  • RequestId,
  • OperationId,
  • Success,
  • Message,
  • ErrorCode,
  • structured result data.

That identity round trip matters. It lets logs, audit envelopes, tests, and caller-visible results all point to the same operation.

Catalog First, Then Executor

IBridgeToolCatalog answers two questions:

  • what tools are available?
  • can this specific ToolId be resolved?

The current catalog implementation is CompiledBridgeToolCatalog. It builds an in-memory lookup from discovered IBridgeTool instances. Duplicate tool ids fail early, because ambiguous tool identity would make policy, logging, and audit evidence unreliable.

The catalog also tolerates an empty tool set. Empty catalog behavior matters in tests and host composition because it proves the bridge can represent “no tools are registered” without inventing hidden defaults.

Unknown tools fail through the executor as structured results. The caller receives ErrorCode = UnknownTool, and the request and operation ids are preserved. That is the anti-black-box pattern in small form: even a failure has a shape.

Compiled Discovery Is The Default Path

CompiledBridgeToolDiscovery adapts DI-registered compiled tools into the catalog. The default shared registration wires the bridge tool services so callers can resolve:

  • IBridgeToolCatalog,
  • IBridgeToolExecutor,
  • compiled tool implementations such as RegexTextSearchTool and Bm25TextSearchTool.

There is also a MEF discovery seam, but it is discovery-only and explicitly constrained. MEF does not own execution, policy, approval, audit, redaction, or transport. Discovered tools still have to run through BridgeToolExecutor.

BridgeToolExecutor Is The Boundary

BridgeToolExecutor is the important part of the design. It is not just a convenience wrapper around tool.ExecuteAsync. It is the shared execution boundary.

Today that boundary owns:

  • start and completion logging,
  • redacted request and result trace payloads,
  • catalog lookup,
  • unknown-tool failure,
  • IToolExecutionPolicy evaluation,
  • descriptor-declared required capability metadata,
  • approval evaluation when ApprovalRequirement = Required,
  • secret-reference resolution through the broker seam,
  • tool invocation,
  • structured cancellation and exception results,
  • BridgeAuditEnvelope emission,
  • classification metadata for terminal outcomes,
  • request and operation correlation preservation.

That is why callers should not bypass the executor. Bypassing it would also bypass the evidence that makes tool behavior reconstructable.

Approval-Aware Execution

The approval-aware execution seam is intentionally small. A tool descriptor can mark itself as requiring approval. If it does, BridgeToolExecutor asks IToolExecutionApprovalService for a decision after policy evaluation and before tool execution.

If approval is denied, the tool is not invoked. The result is a structured failure with ErrorCode = ApprovalDenied. The audit envelope records the approval requirement, decision, and redacted reason. Correlation metadata is preserved.

This is separate from the Visual Studio proposal approval workflow described in Part 3. Proposal approval is the host UI workflow for applying edits. Tool execution approval is a shared executor checkpoint for selected compiled tools.

The First Concrete Proof: Regex Text Search

RegexTextSearchTool is the first concrete proof of the compiled bridge tool path. It is deliberately small:

  • descriptor id: bridge.regexTextSearch,
  • source: compiled,
  • host: shared,
  • arguments: pattern or query, input text or entries, case sensitivity, max results,
  • result data: matches, match count, total match count, and whether results were limited.

The point is not that regex search is the final search story. The point is that it proves the path:

DI registration
catalog descriptor
executor lookup
policy check
tool invocation
structured result
correlated logs
audit envelope

Bm25TextSearchTool extends the same compiled path with request-scoped in-memory ranking. It does not add persistence, crawling, or a background search service. That restraint matters because the architecture is still proving the boundary before turning it into a broad plugin system.

Tests Make The Boundary Real

The shared tests cover the shape of the boundary rather than only the happy path. They verify that:

  • DI resolves the catalog and executor,
  • compiled tools appear in the catalog,
  • empty catalogs are allowed,
  • duplicate tool ids fail fast,
  • unknown tools return structured failure,
  • fake tools can be invoked through the executor,
  • request and operation ids survive execution,
  • policy denial prevents execution,
  • approval denial prevents execution,
  • normal tools skip approval by default,
  • audit metadata records policy, approval, capabilities, secrets, and classification data.

Those tests are not incidental. They are what stop the executor from becoming a label on top of unstructured tool calls.

Related Mermaid Trace Sources

The repo already has Mermaid sources that show the compiled tool boundary from several angles:

Those .mmd files are the diagram source of truth. This post references them directly rather than embedding generated images.

Takeaway

The compiled bridge tool architecture is valuable because it turns tool execution into an observable contract.

A tool is not just a method call. It has a descriptor, a request, a result, a catalog entry, a policy path, optional approval, redacted logs, an audit envelope, and correlation metadata. That structure gives future tools room to grow without making the runtime harder to understand.

The working rule is simple:

tools can be extensible
execution must stay centralized
evidence must stay reconstructable

That is how the bridge supports future extensibility without becoming black-box infrastructure.

Next In The Series

The next useful topic is how the bridge turns these execution boundaries into durable validation evidence: logs, metadata, diagrams, and handoffs that let future AI sessions reconstruct what actually happened instead of relying on chat history.