When writing a UI MCP client that doesn't have a console and targeting netfx, can't connect to MCP servers because of this line:
|
Console.InputEncoding = StreamClientSessionTransport.NoBomUtf8Encoding; |
A workaround would be a try/catch or AllocConsole() + hide window.
An earlier fix was attempted here, but wasn't minimal:
#694
Repro:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net472</TargetFramework>
<UseWPF>true</UseWPF>
<LangVersion>latest</LangVersion>
<AssemblyName>McpWpfRepro</AssemblyName>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ModelContextProtocol" Version="1.2.0" />
</ItemGroup>
</Project>
// Minimal repro for: ModelContextProtocol C# SDK stdio transport throws on
// .NET Framework WPF (Windows-subsystem) apps because StdioClientTransport
// sets Console.InputEncoding = new UTF8Encoding(false) around Process.Start
// (no StandardInputEncoding on net472). With no console attached, the
// encoding setter calls SetConsoleCP on an invalid handle and throws,
// failing the connect before the child process is even spawned.
//
// Run as-is (ApplyWorkaround = false) -> connect fails with an IOException
// (or NotSupportedException, depending on the platform path), surfaced in
// the window's text area.
//
// Flip ApplyWorkaround to true -> EnsureHiddenConsole calls AllocConsole
// once at startup and hides the window. The encoding setter then has a
// valid console handle to write to and the SDK proceeds to spawn the child.
//
// Background:
// https://github.com/modelcontextprotocol/csharp-sdk/pull/694
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using ModelContextProtocol.Client;
namespace McpWpfRepro;
public static class Program
{
// Flip to true to enable the AllocConsole + hide workaround.
private const bool ApplyWorkaround = true;
[STAThread]
public static int Main()
{
if (ApplyWorkaround)
{
EnsureHiddenConsole();
}
var output = new TextBox
{
IsReadOnly = true,
FontFamily = new FontFamily("Consolas"),
FontSize = 12,
TextWrapping = TextWrapping.Wrap,
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
AcceptsReturn = true,
};
var connectButton = new Button { Content = "Connect to stdio MCP server (cmd /c exit)", Padding = new Thickness(8, 4, 8, 4), Margin = new Thickness(0, 0, 0, 6) };
var panel = new DockPanel { LastChildFill = true, Margin = new Thickness(8) };
DockPanel.SetDock(connectButton, Dock.Top);
panel.Children.Add(connectButton);
panel.Children.Add(output);
var window = new Window
{
Title = "MCP stdio + WPF net472 repro (ApplyWorkaround = " + ApplyWorkaround + ")",
Width = 820,
Height = 460,
Content = panel,
};
Append(output, "ApplyWorkaround = " + ApplyWorkaround);
Append(output, "Click the button to attempt a stdio MCP connect.");
Append(output, "");
connectButton.Click += async (_, __) =>
{
connectButton.IsEnabled = false;
try
{
await TryConnect(output).ConfigureAwait(true);
}
finally
{
connectButton.IsEnabled = true;
}
};
var app = new Application();
return app.Run(window);
}
private static async Task TryConnect(TextBox output)
{
Append(output, "--- connecting ---");
try
{
var options = new StdioClientTransportOptions
{
Command = "cmd.exe",
Arguments = new List<string> { "/c", "exit" },
Name = "repro-server",
};
var transport = new StdioClientTransport(options);
// Connect will fail BEFORE the child process is started because the SDK's
// encoding setter throws on a Windows-subsystem net472 host.
var client = await McpClient.CreateAsync(transport).ConfigureAwait(true);
Append(output, "UNEXPECTED: connect returned without throwing. Client = " + client);
await client.DisposeAsync().ConfigureAwait(true);
}
catch (Exception ex)
{
Append(output, "FAILED: " + ex.GetType().FullName + ": " + ex.Message);
Append(output, "");
Append(output, ex.ToString());
}
}
private static void Append(TextBox output, string line)
{
output.AppendText(line + Environment.NewLine);
output.ScrollToEnd();
}
// ---- AllocConsole workaround (off by default; enable via ApplyWorkaround) ----
private static int s_consoleEnsured;
private static void EnsureHiddenConsole()
{
if (Interlocked.CompareExchange(ref s_consoleEnsured, 1, 0) != 0)
{
return;
}
if (GetConsoleWindow() != IntPtr.Zero)
{
return;
}
if (!AllocConsole())
{
return;
}
var hwnd = GetConsoleWindow();
if (hwnd != IntPtr.Zero)
{
ShowWindow(hwnd, SW_HIDE);
}
}
private const int SW_HIDE = 0;
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool AllocConsole();
[DllImport("kernel32.dll")]
private static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
}
When writing a UI MCP client that doesn't have a console and targeting netfx, can't connect to MCP servers because of this line:
csharp-sdk/src/ModelContextProtocol.Core/Client/StdioClientTransport.cs
Line 192 in ed07fdb
A workaround would be a try/catch or AllocConsole() + hide window.
An earlier fix was attempted here, but wasn't minimal:
#694
Repro: