Search code examples
c#roslynlanguage-server-protocolomnisharp

C# LSP "Message header must separate key and value using ':'" error during initialization with OmniSharp


I’m attempting to build a toy Language Server Protocol (LSP) server in C# using OmniSharp, but I keep encountering the following error:

Message header must separate key and value using ':'

This error typically occurs when the JSON-RPC messages are not properly formatted. However, I assumed all OmniSharp objects would be correctly formatted, so I'm confused about what’s going wrong. The error occurs immediately after initialization, so none of the subsequent handlers (like DidOpen, DidChange, etc.) get triggered.

namespace ModdingToolChain
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var loggerFactory = LoggerFactory.Create(builder =>
            {
                builder.AddConsole(); // Configure console logging
            });
            var logger = loggerFactory.CreateLogger<Program>();

            FileLogger.Initialize("C:\\Users\\Admin\\source\\repos\\ModdingLSP\\ModdingToolChain\\Logs\\debug.txt", "[Program]"); // For debugging
            FileLogger.Log("Initialized");

            logger.LogInformation("Starting LSP server...");

            try
            {
                var server = await LanguageServer.From(options =>
                    options
                        .WithInput(Console.OpenStandardInput())
                        .WithOutput(Console.OpenStandardOutput())
                        .WithHandler<TextDocumentHandler>()
                        .WithHandler<CompletionHandler>()
                        .OnInitialize((server, request, token) =>
                        {
                            logger.LogInformation("LSP Server Initialized with request: " + request);
                            if (request is InitializeParams initParams)
                            {
                                FileLogger.Log($"ProcessId: {initParams.ProcessId}");
                                FileLogger.Log($"ClientInfo: {initParams.ClientInfo}");
                                // Log other properties as needed
                            }

                            var response = new InitializeResult
                            {
                                Capabilities = new ServerCapabilities
                                {
                                    TextDocumentSync = TextDocumentSyncKind.Full,
                                    HoverProvider = true,
                                    CodeActionProvider = true,
                                },
                                ServerInfo = new ServerInfo
                                {
                                    Name = "ModdingLSP",
                                    Version = "1.0.0",
                                }
                            };

                            FileLogger.Log("Response: " + response);
                            return Task.FromResult(response);
                        })
                        .OnStarted((server, token) =>
                        {
                            FileLogger.Log("LSP Server Initialized");
                            logger.LogInformation("LSP Server Initialized");
                            return Task.CompletedTask;
                        })
                ).ConfigureAwait(false);

                await server.WaitForExit.ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                FileLogger.Log($"An error occurred while running the LSP server. {ex}");
                logger.LogError(ex, "An error occurred while running the LSP server.");
            }

            logger.LogInformation("LSP server stopping...");
        }
    }
}

I’m using Console.OpenStandardInput() and Console.OpenStandardOutput() for input and output streams. I have logging in place, and this is what shows up in my debug logs during initialization:

2024-10-22 21:32:40 [Program] Initialized
2024-10-22 21:32:40 [Program] ProcessId: 36396
2024-10-22 21:32:40 [Program] ClientInfo: Visual Studio Code (1.94.1)
2024-10-22 21:32:40 [Program] Response: InitializeResult { Capabilities = OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities.ServerCapabilities, ServerInfo = ModdingLSP (1.0.0) }
2024-10-22 21:32:42 [Program] Initialized
2024-10-22 21:32:43 [Program] ProcessId: 36396
2024-10-22 21:32:43 [Program] ClientInfo: Visual Studio Code (1.94.1)
2024-10-22 21:32:43 [Program] Response: InitializeResult { Capabilities = OmniSharp.Extensions.LanguageServer.Protocol.Server.Capabilities.ServerCapabilities, ServerInfo = ModdingLSP (1.0.0) }

It seems to initialize but fails right afterward with the typical JSON-RPC error. Here's the main question: Is there something wrong with how I'm configuring the input/output streams or handling the JSON-RPC protocol?

Any help or pointers would be appreciated!


Solution

  • One thing that jumps out: it looks like you're trying to use your console input/output for the LSP RPC, and you're also trying to use it for logging with this bit:

    var loggerFactory = LoggerFactory.Create(builder =>
    {
        builder.AddConsole(); // Configure console logging
    });
    

    So any time you write to that logger, you're going to be writing to the same stream as your RPC stream which will corrupt it.

    (Generally, be very careful using stdin/stdout as your LSP RPC channel -- it's easy for somebody to write to it by accident and corrupt it.)