From 2dfbabc998ec6f2bdbbd164192ddfde4b057400a Mon Sep 17 00:00:00 2001 From: Alexander Rymdeko-Harvey <8761706+killswitch-GUI@users.noreply.github.com> Date: Fri, 2 Oct 2026 08:08:06 -0400 Subject: [PATCH 1/2] feat(outlook-classic): add authenticated user-session HTTP companion --- plugins/outlook-classic/README.md | 13 + plugins/outlook-classic/src/HttpCompanion.cs | 109 +++++++ .../src/OutlookClassicMcp.csproj | 3 +- plugins/outlook-classic/src/Program.cs | 9 +- .../outlook-classic/src/packages.lock.json | 299 +----------------- .../tests/http-companion-smoke.mjs | 90 ++++++ .../outlook-classic/tests/packages.lock.json | 282 +++-------------- scripts/Build-OutlookClassic.ps1 | 2 + 8 files changed, 270 insertions(+), 537 deletions(-) create mode 100644 plugins/outlook-classic/src/HttpCompanion.cs create mode 100644 plugins/outlook-classic/tests/http-companion-smoke.mjs diff --git a/plugins/outlook-classic/README.md b/plugins/outlook-classic/README.md index 4e0abca..f26c15c 100644 --- a/plugins/outlook-classic/README.md +++ b/plugins/outlook-classic/README.md @@ -38,6 +38,19 @@ Consequently direct stdio inside that boundary is not currently a supported deployment. Do not disable isolation as an installation workaround. See the [build report](../../docs/local-build.md) for test scope and the bridge follow-up. +The binary now also contains a parent-owned `--http-companion` transport for the +interactive Outlook user's session. Its parent supplies a 32-byte random bearer +token as a 43-character base64url line on a private stdin pipe and keeps that pipe +open. The helper binds one ephemeral `127.0.0.1` endpoint, reports its `/mcp` URL +on stdout, and stops when the pipe closes. It rejects incorrect Host or Origin +headers and missing or incorrect bearer credentials before MCP dispatch. The +build checks verify those denials and tool discovery without opening a mailbox. +Colossus Desktop does not yet launch or supervise this mode, provision its token, +or bind it to an installed plugin digest. The packaged `mcp.json` still declares +stdio, so this mode is not an AppContainer compatibility claim or a supported +one-step installation path. The managed lifecycle is tracked in +[Colossus issue #246](https://github.com/obscuritylabs/Colossus/issues/246). + Main-branch CI publishes signed OCI previews to GHCR and adds verified digests to the catalog. The Windows executable is not Authenticode-signed. All 24 local live checks passed on 2026-10-02 for alpha.4, including write tests confined to diff --git a/plugins/outlook-classic/src/HttpCompanion.cs b/plugins/outlook-classic/src/HttpCompanion.cs new file mode 100644 index 0000000..a3c1fe4 --- /dev/null +++ b/plugins/outlook-classic/src/HttpCompanion.cs @@ -0,0 +1,109 @@ +using System.Net; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.AspNetCore.Hosting.Server.Features; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using ModelContextProtocol.AspNetCore; +using ModelContextProtocol.Server; + +namespace Colossus.OutlookClassic; + +/// +/// A parent-owned, loopback-only MCP transport. Its bearer token arrives on stdin; +/// closing that inherited channel revokes the endpoint and shuts down the helper. +/// +internal static class HttpCompanion +{ + internal static async Task RunAsync(StaDispatcher dispatcher, OutlookReader outlook) + { + var token = await Console.In.ReadLineAsync(); + if (token is null || !ValidToken(token)) + { + Console.Error.WriteLine("Companion bootstrap token is missing or invalid."); + return 2; + } + + var builder = WebApplication.CreateSlimBuilder(new WebApplicationOptions + { + Args = [], + ContentRootPath = AppContext.BaseDirectory + }); + builder.Configuration.Sources.Clear(); + builder.Configuration.AddInMemoryCollection(); + builder.Logging.ClearProviders(); + builder.WebHost.UseUrls("http://127.0.0.1:0"); + builder.Services.AddSingleton(dispatcher); + builder.Services.AddSingleton(outlook); + builder.Services.AddMcpServer() + .WithHttpTransport(options => options.SessionMode = HttpServerSessionMode.Stateless) + .WithTools(); + + await using var app = builder.Build(); + var boundPort = 0; + app.Use(async (context, next) => + { + if (!IPAddress.IsLoopback(context.Connection.RemoteIpAddress ?? IPAddress.None) + || context.Request.Host.Host != "127.0.0.1" + || context.Request.Host.Port != boundPort + || context.Request.Headers.ContainsKey("Origin")) + { + context.Response.StatusCode = StatusCodes.Status403Forbidden; + return; + } + if (!Authorized(context.Request, token)) + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + return; + } + await next(context); + }); + app.MapMcp("/mcp"); + await app.StartAsync(); + var server = app.Services.GetRequiredService(); + var addresses = server.Features.Get()?.Addresses; + if (addresses is null || addresses.Count != 1 || + !Uri.TryCreate(addresses.Single(), UriKind.Absolute, out var address) || + address.Host != "127.0.0.1" || address.Port == 0) + { + await app.StopAsync(); + Console.Error.WriteLine("Companion did not bind one IPv4 loopback endpoint."); + return 1; + } + boundPort = address.Port; + Console.WriteLine(JsonSerializer.Serialize(new + { + endpoint = $"http://127.0.0.1:{boundPort}/mcp", + processId = Environment.ProcessId + })); + await Console.Out.FlushAsync(); + + // The parent keeps stdin open for the lifetime of this exact process. + // EOF is a narrow and reliable revocation signal, including parent crashes. + await Console.In.ReadToEndAsync(); + await app.StopAsync(); + return 0; + } + + private static bool ValidToken(string? token) => + token is { Length: 43 } && token.All(character => + character is >= 'A' and <= 'Z' or >= 'a' and <= 'z' or >= '0' and <= '9' or '-' or '_'); + + private static bool Authorized(HttpRequest request, string token) + { + if (request.Headers.Authorization.Count != 1) return false; + var header = request.Headers.Authorization.ToString(); + if (!header.StartsWith("Bearer ", StringComparison.Ordinal)) return false; + var presented = header.AsSpan(7); + if (presented.Length != token.Length) return false; + var presentedBytes = Encoding.ASCII.GetBytes(presented.ToString()); + var expectedBytes = Encoding.ASCII.GetBytes(token); + return CryptographicOperations.FixedTimeEquals(presentedBytes, expectedBytes); + } +} diff --git a/plugins/outlook-classic/src/OutlookClassicMcp.csproj b/plugins/outlook-classic/src/OutlookClassicMcp.csproj index 99cbaa5..68b8f37 100644 --- a/plugins/outlook-classic/src/OutlookClassicMcp.csproj +++ b/plugins/outlook-classic/src/OutlookClassicMcp.csproj @@ -16,7 +16,8 @@ false + - + diff --git a/plugins/outlook-classic/src/Program.cs b/plugins/outlook-classic/src/Program.cs index 78c5839..8dc4444 100644 --- a/plugins/outlook-classic/src/Program.cs +++ b/plugins/outlook-classic/src/Program.cs @@ -11,9 +11,9 @@ internal static class Program { public static async Task Main(string[] args) { - if (args is not ["--stdio"] and not ["--probe"] and not ["--version"]) + if (args is not ["--stdio"] and not ["--probe"] and not ["--version"] and not ["--http-companion"]) { - Console.Error.WriteLine("Usage: outlook-classic-mcp --stdio | --probe | --version"); + Console.Error.WriteLine("Usage: outlook-classic-mcp --stdio | --http-companion | --probe | --version"); return 2; } if (args[0] == "--version") @@ -40,6 +40,11 @@ public static async Task Main(string[] args) } } + if (args[0] == "--http-companion") + { + return await HttpCompanion.RunAsync(dispatcher, outlook); + } + // Do not load ambient appsettings or command-line configuration into this plugin. var builder = Host.CreateApplicationBuilder(new HostApplicationBuilderSettings { diff --git a/plugins/outlook-classic/src/packages.lock.json b/plugins/outlook-classic/src/packages.lock.json index 982bb2d..2ad078d 100644 --- a/plugins/outlook-classic/src/packages.lock.json +++ b/plugins/outlook-classic/src/packages.lock.json @@ -2,45 +2,22 @@ "version": 1, "dependencies": { "net10.0-windows7.0": { - "Microsoft.Extensions.Hosting": { + "ModelContextProtocol": { "type": "Direct", - "requested": "[10.0.12, )", - "resolved": "10.0.12", - "contentHash": "dzPj59oALFLu9TA4YUrfxqXEcbJVen/D2kOoitgM1lXBeSvPwPR8rOrrV0NKilCV3K0l4o+TLJJ3a1wmTu40Uw==", + "requested": "[2.2.0, )", + "resolved": "2.2.0", + "contentHash": "4Pb9u02Nwsp0poueDsqNdyGRojFxOYpljB7zDBsq+aHL+Afou3OgxlBc3GWFVnsRMRJrUtWqDh3s6k2JgPzmrQ==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.Configuration.Binder": "10.0.12", - "Microsoft.Extensions.Configuration.CommandLine": "10.0.12", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.12", - "Microsoft.Extensions.Configuration.FileExtensions": "10.0.12", - "Microsoft.Extensions.Configuration.Json": "10.0.12", - "Microsoft.Extensions.Configuration.UserSecrets": "10.0.12", - "Microsoft.Extensions.DependencyInjection": "10.0.12", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Diagnostics": "10.0.12", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.12", - "Microsoft.Extensions.FileProviders.Physical": "10.0.12", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging.Configuration": "10.0.12", - "Microsoft.Extensions.Logging.Console": "10.0.12", - "Microsoft.Extensions.Logging.Debug": "10.0.12", - "Microsoft.Extensions.Logging.EventLog": "10.0.12", - "Microsoft.Extensions.Logging.EventSource": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12" + "ModelContextProtocol.Core": "[2.2.0]" } }, - "ModelContextProtocol": { + "ModelContextProtocol.AspNetCore": { "type": "Direct", "requested": "[2.2.0, )", "resolved": "2.2.0", - "contentHash": "4Pb9u02Nwsp0poueDsqNdyGRojFxOYpljB7zDBsq+aHL+Afou3OgxlBc3GWFVnsRMRJrUtWqDh3s6k2JgPzmrQ==", + "contentHash": "3JelDMuFIwFzXybsh6K30G6wXu5gmqKnuRnieqgBMuR0FkqqXRv4B+NYZuPgthSyNq5UwRyvv2U2VJBE3RD3PQ==", "dependencies": { - "Microsoft.Extensions.Caching.Abstractions": "10.0.10", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.10", - "ModelContextProtocol.Core": "[2.2.0]" + "ModelContextProtocol": "[2.2.0]" } }, "Microsoft.Extensions.AI.Abstractions": { @@ -48,270 +25,12 @@ "resolved": "10.8.3", "contentHash": "K0B05oApxmviWalNHPMBBcRC7erKiDATz3ENNR/jqTR9JwIwLRefgDhj2jCRwL1aca99pXUe0qyQC73/xIuZig==" }, - "Microsoft.Extensions.Caching.Abstractions": { - "type": "Transitive", - "resolved": "10.0.10", - "contentHash": "4ZFBNE+jzR+CrWWlhOesnmywCW7pYKT0dxyAQRdL11yJwxe4jvcAu31eorFtEkoFeCDcUTeNssgPv2yaRRptaQ==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.10" - } - }, - "Microsoft.Extensions.Configuration": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "e3IPP32CRNL031VZJAUTlCTG0YN7WFh4mN3fsSHTDQCJB+3+f0jGycv4fXk3rrftaY3B85XrQaj7sRthrOsavg==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, - "Microsoft.Extensions.Configuration.Abstractions": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "8xaGcvS/qZ1otoxPQCEJkNva389CVL/plNcvIETZhQTETYdRkYDPEYhUMoAGONo4FU45ufdfE0j29AfWVVj0wA==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, - "Microsoft.Extensions.Configuration.Binder": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "dAgIf1TOr8KLs+aBRIbXUZBjHoSH4rDG8+XkX/Q6AZwkQdMA0+yPDKTHsieeXdZDfpOpZVHzOnuAb5Z2nX3KsA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Configuration.CommandLine": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "hWWJzJpmg161rYJ2H6vxqCuFfh6zL2Tp9emOrfu1M4gsmW5BOlW8GldthyFwm2L+beW+0axmkMKRw3HyO1qceQ==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Configuration.EnvironmentVariables": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "OIFhLxKAdvSrZuecX177ZkN4AcAB6OLlbS8y0MQYL68512aMOZI8cJLb8OeuYe2X+gu5uMqbbCxhzAmSp+Q2ow==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Configuration.FileExtensions": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "PNPS7yYH9U2M0dExn0tMkQFVtE53xTlCs/dgTfsWTTFyCF6qwGT3VrTgm2nnnkSRlSmvM9/J+n9xCpyzd9xTpQ==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.12", - "Microsoft.Extensions.FileProviders.Physical": "10.0.12", - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, - "Microsoft.Extensions.Configuration.Json": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "H4u2ZeIhjmRQvbHj7S7U4MK1kXb0khx3UtOWcir32VHczdFyIVF0lvHew/VVHmCF7Uk0QseAUaO7HyFKOCSiKA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.Configuration.FileExtensions": "10.0.12", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Configuration.UserSecrets": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "2OtyTIt0/tkChr3inq9Y0c34xogxXW//68gvPzIWgKwyVR4x82RSGVSZfV+ZgG13zrKBzqjaI23wpUCfKpY2yQ==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.Configuration.Json": "10.0.12", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.12", - "Microsoft.Extensions.FileProviders.Physical": "10.0.12" - } - }, - "Microsoft.Extensions.DependencyInjection": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "lXyK2O5GoYvfxW8eCFcD16JFbcoSTM1sJkAM0UHS1jZyl9NYMW64Tqm6OQFT0IDBjZi+xHt95/Zg+nxZhGFhZg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "9/qymSh7hVDMGTGwrLz8MRp5zRyXy9adGDOs4HwRdnLil3oZGYuWeZjbmHgCQ9BL1qBroVfgUK3U/nb61617Cw==" - }, - "Microsoft.Extensions.Diagnostics": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "amx0O/R4dReqKlC3T740+CHpX8D8tAIV0f5kAPfFi4XK9jYo9BSepGt9/rWfvvCoSMaZ1+XvnqPp/Vv2fmTdAg==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.12", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.12" - } - }, - "Microsoft.Extensions.Diagnostics.Abstractions": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "fMp91qlRlMOuHfMyp7LuAnOS+MOeWTF1obJmyFcs4nZCXu6pr+QB1AbFJcl2qpBltBrqF/Y7+Rn9IuNcSuctig==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12" - } - }, - "Microsoft.Extensions.FileProviders.Abstractions": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "iQseV6HixWzEYQH4i++84ToIroXnM5jC1TJF4EqBz3lWNG1HBTMYDLkRb6Wm3wk5V6WqSfl7NukO/LYsloGyfg==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, - "Microsoft.Extensions.FileProviders.Physical": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "3cY+wo2+Qe65BVwShhr2fFqZv5Ns73h3kX4mpW3WvSgfT3AWCk0hKArKW4TF7Dg3mvbJdA6RhWPW7FR+PS5/1w==", - "dependencies": { - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.12", - "Microsoft.Extensions.FileSystemGlobbing": "10.0.12", - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, - "Microsoft.Extensions.FileSystemGlobbing": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "45cV+UeI0chPAIXrx9Wv7K4PJXuuIR2pnOur5MOAo6kOFclBHSLLDrvGAxMfWRMtUvpv/kQZkxlgaUN56jwzSQ==" - }, - "Microsoft.Extensions.Hosting.Abstractions": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "ZlIxIJtWmrph0Ikj0svSaTkZ+d+ZYpTAhazpf1b3aoFRmlNnNWwkcvqnqT9yKHjKvfBRthvUrkq11OxfItOhpg==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.12", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Logging": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "6I46fTPfgYkrjRYfRXbho9WOvOelTnNjWuZws/hzGHDASH1LEJeA4VKK9k3wJvido8o7jJSB5WkMTonX7HM1bA==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12" - } - }, - "Microsoft.Extensions.Logging.Abstractions": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "+24lC4plfbEDNfLAdTV/SWKS7dW+16X4HdydO3R++134kSNTzcbYA4KpR1Hdh6uWisB8Za3AzwyOn+K+NxWIug==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Logging.Configuration": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "CLHWfKQZWFE6fSzRXfjd4UlGveckkfPtwx/p5yF/o3hEkMSzGsl7MSxNAkI19hjw0rprBqrQC09Dwy6Lh6kweA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.Configuration.Binder": "10.0.12", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.12" - } - }, - "Microsoft.Extensions.Logging.Console": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "nHvaR8gO0CdEfG324KdUsP3Hqgdf1licZb+UlNh5pFYw7+o8QYTmiMgFmwayE/LPSpaqND/Qn3xHa1cI9b8x0w==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging.Configuration": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12" - } - }, - "Microsoft.Extensions.Logging.Debug": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "dEvvABq9Qgfu/SgTncXDjCshcJmgO97bLOnMAuqBDSonlk4+VXlKJMHPak+S78TlOxJ47+gY7vPgVukJsr69gg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Logging.EventLog": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "Qn0FWPMiQzM4GQEEXxrJbh3vp3I9wrAgpMcsVH36+6FTRGXaqtwHDgf/2030FUJ4OuNSpOeIRh+9kCZGDDsfGg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12" - } - }, - "Microsoft.Extensions.Logging.EventSource": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "nKpYoiUdBu8fdLwDxj/fjjGn//7/VwRLzujtMq99/pExrswMXOz+80hbB98QxrD8qPt9oWlFpEVrN9OBKOP7RQ==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12", - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, - "Microsoft.Extensions.Options": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "TDYD33TSRpXKZWlmTXNlj5kCihxatmv2Ec1u6C+bMYLphCS7PoSLE9Pjd/nunDoE7yETk+LLKjVJX78HYtWjpA==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, - "Microsoft.Extensions.Options.ConfigurationExtensions": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "rqpu4qj5WE9x1IHGXSIgHBKi7IUlQaHyp4aXCYIanG2OghlUMFZpZTgExaXwcvmLAJHsxKQWMPpc7D2WIbCVtA==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.Configuration.Binder": "10.0.12", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12", - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, - "Microsoft.Extensions.Primitives": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "dYfCLR52UA+3DL7C4I/pvSaRPkNqxrUAQmbFL2u0zvYKKzqgrFCJl08Df+F1aYc8leu9JvpC9bsURUdpExcBXQ==" - }, "ModelContextProtocol.Core": { "type": "Transitive", "resolved": "2.2.0", "contentHash": "FeBfXU6T8k+jw4afg4sfxdEX2rL/e5oKOk9ROOGztu9k47+7Bz08sdaToYt2XvMY1opNbwxYQOFMj6wH9TInhA==", "dependencies": { - "Microsoft.Extensions.AI.Abstractions": "10.8.3", - "Microsoft.Extensions.Logging.Abstractions": "10.0.10" + "Microsoft.Extensions.AI.Abstractions": "10.8.3" } } }, diff --git a/plugins/outlook-classic/tests/http-companion-smoke.mjs b/plugins/outlook-classic/tests/http-companion-smoke.mjs new file mode 100644 index 0000000..e8b41f4 --- /dev/null +++ b/plugins/outlook-classic/tests/http-companion-smoke.mjs @@ -0,0 +1,90 @@ +import assert from 'node:assert/strict'; +import { randomBytes } from 'node:crypto'; +import { spawn } from 'node:child_process'; +import { request as httpRequest } from 'node:http'; +import { createInterface } from 'node:readline'; + +const executable = process.argv[2]; +assert.ok(executable, 'Usage: node http-companion-smoke.mjs '); +const token = randomBytes(32).toString('base64url'); +const child = spawn(executable, ['--http-companion'], { + windowsHide: true, + stdio: ['pipe', 'pipe', 'pipe'], +}); +let stderr = ''; +child.stderr.setEncoding('utf8'); +child.stderr.on('data', chunk => { stderr = (stderr + chunk).slice(-4000); }); +const lines = createInterface({ input: child.stdout }); +const ready = new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error(`Companion startup timed out: ${stderr}`)), 15000); + lines.once('line', line => { clearTimeout(timeout); resolve(JSON.parse(line)); }); + child.once('error', error => { clearTimeout(timeout); reject(error); }); + child.once('exit', code => { clearTimeout(timeout); reject(new Error(`Companion exited ${code}: ${stderr}`)); }); +}); + +function post(endpoint, body, headers = {}) { + return new Promise((resolve, reject) => { + const request = httpRequest(endpoint, { + method: 'POST', + timeout: 10000, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json, text/event-stream', + 'MCP-Protocol-Version': '2025-11-25', + ...headers, + }, + }, response => { + let text = ''; + response.setEncoding('utf8'); + response.on('data', chunk => { + text += chunk; + if (text.length > 1024 * 1024) request.destroy(new Error('Oversized MCP response')); + }); + response.on('end', () => resolve({ status: response.statusCode, body: text })); + }); + request.on('timeout', () => request.destroy(new Error('MCP request timed out'))); + request.on('error', reject); + request.end(JSON.stringify(body)); + }); +} + +const initialize = { + jsonrpc: '2.0', id: 1, method: 'initialize', params: { + protocolVersion: '2025-11-25', capabilities: {}, + clientInfo: { name: 'companion-smoke', version: '1' }, + }, +}; + +let completed = false; +try { + child.stdin.write(`${token}\n`); + const { endpoint } = await ready; + const parsed = new URL(endpoint); + assert.equal(parsed.hostname, '127.0.0.1'); + assert.equal(parsed.pathname, '/mcp'); + assert.equal((await post(endpoint, initialize)).status, 401); + assert.equal((await post(endpoint, initialize, { Authorization: 'Bearer wrong' })).status, 401); + const authorization = { Authorization: `Bearer ${token}` }; + assert.equal((await post(endpoint, initialize, { ...authorization, Host: 'evil.example' })).status, 403); + assert.equal((await post(endpoint, initialize, { ...authorization, Origin: 'https://evil.example' })).status, 403); + const accepted = await post(endpoint, initialize, authorization); + assert.equal(accepted.status, 200); + assert.match(accepted.body, /"tools"/); + const listed = await post(endpoint, { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} }, authorization); + assert.equal(listed.status, 200); + assert.match(listed.body, /"get_status"/); + assert.match(listed.body, /"list_messages"/); + assert.equal(stderr, ''); + completed = true; + console.log('PASS companion loopback, bearer/Host/Origin denial, MCP discovery. No mailbox was opened.'); +} finally { + child.stdin.end(); + const stopped = child.exitCode === null + ? new Promise(resolve => child.once('exit', resolve)) + : Promise.resolve(child.exitCode); + const killTimer = setTimeout(() => child.kill(), 5000); + const code = await stopped; + clearTimeout(killTimer); + lines.close(); + if (completed) assert.equal(code, 0, `Companion shutdown failed: ${stderr}`); +} diff --git a/plugins/outlook-classic/tests/packages.lock.json b/plugins/outlook-classic/tests/packages.lock.json index b0ca9a1..2608264 100644 --- a/plugins/outlook-classic/tests/packages.lock.json +++ b/plugins/outlook-classic/tests/packages.lock.json @@ -15,283 +15,69 @@ "Microsoft.Extensions.Primitives": "10.0.10" } }, - "Microsoft.Extensions.Configuration": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "e3IPP32CRNL031VZJAUTlCTG0YN7WFh4mN3fsSHTDQCJB+3+f0jGycv4fXk3rrftaY3B85XrQaj7sRthrOsavg==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, "Microsoft.Extensions.Configuration.Abstractions": { "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "8xaGcvS/qZ1otoxPQCEJkNva389CVL/plNcvIETZhQTETYdRkYDPEYhUMoAGONo4FU45ufdfE0j29AfWVVj0wA==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, - "Microsoft.Extensions.Configuration.Binder": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "dAgIf1TOr8KLs+aBRIbXUZBjHoSH4rDG8+XkX/Q6AZwkQdMA0+yPDKTHsieeXdZDfpOpZVHzOnuAb5Z2nX3KsA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Configuration.CommandLine": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "hWWJzJpmg161rYJ2H6vxqCuFfh6zL2Tp9emOrfu1M4gsmW5BOlW8GldthyFwm2L+beW+0axmkMKRw3HyO1qceQ==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Configuration.EnvironmentVariables": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "OIFhLxKAdvSrZuecX177ZkN4AcAB6OLlbS8y0MQYL68512aMOZI8cJLb8OeuYe2X+gu5uMqbbCxhzAmSp+Q2ow==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Configuration.FileExtensions": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "PNPS7yYH9U2M0dExn0tMkQFVtE53xTlCs/dgTfsWTTFyCF6qwGT3VrTgm2nnnkSRlSmvM9/J+n9xCpyzd9xTpQ==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.12", - "Microsoft.Extensions.FileProviders.Physical": "10.0.12", - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, - "Microsoft.Extensions.Configuration.Json": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "H4u2ZeIhjmRQvbHj7S7U4MK1kXb0khx3UtOWcir32VHczdFyIVF0lvHew/VVHmCF7Uk0QseAUaO7HyFKOCSiKA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.Configuration.FileExtensions": "10.0.12", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Configuration.UserSecrets": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "2OtyTIt0/tkChr3inq9Y0c34xogxXW//68gvPzIWgKwyVR4x82RSGVSZfV+ZgG13zrKBzqjaI23wpUCfKpY2yQ==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.Configuration.Json": "10.0.12", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.12", - "Microsoft.Extensions.FileProviders.Physical": "10.0.12" - } - }, - "Microsoft.Extensions.DependencyInjection": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "lXyK2O5GoYvfxW8eCFcD16JFbcoSTM1sJkAM0UHS1jZyl9NYMW64Tqm6OQFT0IDBjZi+xHt95/Zg+nxZhGFhZg==", + "resolved": "10.0.10", + "contentHash": "5Vnd2I75DmZCVEjSynIdJ/0EGafgnLQwgR3t2C2/fkjx/nRG+cLwxLLdInoHeCEpkD5K4Ov/g9ZCRYrl4TRsaA==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12" + "Microsoft.Extensions.Primitives": "10.0.10" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "9/qymSh7hVDMGTGwrLz8MRp5zRyXy9adGDOs4HwRdnLil3oZGYuWeZjbmHgCQ9BL1qBroVfgUK3U/nb61617Cw==" - }, - "Microsoft.Extensions.Diagnostics": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "amx0O/R4dReqKlC3T740+CHpX8D8tAIV0f5kAPfFi4XK9jYo9BSepGt9/rWfvvCoSMaZ1+XvnqPp/Vv2fmTdAg==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.12", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.12" - } + "resolved": "10.0.10", + "contentHash": "z/2xXlFw2aLGjHyEm6E0tQ+In6VfzQzTrtArbQ2c0TQE16ZbyDCMGPvaUT9I0s8rgy9sRWlU2P9waW37qV04qA==" }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "fMp91qlRlMOuHfMyp7LuAnOS+MOeWTF1obJmyFcs4nZCXu6pr+QB1AbFJcl2qpBltBrqF/Y7+Rn9IuNcSuctig==", + "resolved": "10.0.10", + "contentHash": "9uWiKpeOVac355STyChWR/pliFX/5CeLqChW9kKsaxyDH4EUTZxMkT4Jwp/J/peLm0GBFmSX5c0WCse3yCnq1Q==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Options": "10.0.10" } }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "iQseV6HixWzEYQH4i++84ToIroXnM5jC1TJF4EqBz3lWNG1HBTMYDLkRb6Wm3wk5V6WqSfl7NukO/LYsloGyfg==", - "dependencies": { - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, - "Microsoft.Extensions.FileProviders.Physical": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "3cY+wo2+Qe65BVwShhr2fFqZv5Ns73h3kX4mpW3WvSgfT3AWCk0hKArKW4TF7Dg3mvbJdA6RhWPW7FR+PS5/1w==", - "dependencies": { - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.12", - "Microsoft.Extensions.FileSystemGlobbing": "10.0.12", - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, - "Microsoft.Extensions.FileSystemGlobbing": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "45cV+UeI0chPAIXrx9Wv7K4PJXuuIR2pnOur5MOAo6kOFclBHSLLDrvGAxMfWRMtUvpv/kQZkxlgaUN56jwzSQ==" - }, - "Microsoft.Extensions.Hosting": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "dzPj59oALFLu9TA4YUrfxqXEcbJVen/D2kOoitgM1lXBeSvPwPR8rOrrV0NKilCV3K0l4o+TLJJ3a1wmTu40Uw==", + "resolved": "10.0.10", + "contentHash": "c5zqFCY9DiIpMovLd7/d/CTiEtrMOuQ639dhv3PABtKQIKNQikSHwQt8+N679uii9q+B55lgK28Uv64FOwEu8w==", "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.Configuration.Binder": "10.0.12", - "Microsoft.Extensions.Configuration.CommandLine": "10.0.12", - "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.12", - "Microsoft.Extensions.Configuration.FileExtensions": "10.0.12", - "Microsoft.Extensions.Configuration.Json": "10.0.12", - "Microsoft.Extensions.Configuration.UserSecrets": "10.0.12", - "Microsoft.Extensions.DependencyInjection": "10.0.12", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Diagnostics": "10.0.12", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.12", - "Microsoft.Extensions.FileProviders.Physical": "10.0.12", - "Microsoft.Extensions.Hosting.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging.Configuration": "10.0.12", - "Microsoft.Extensions.Logging.Console": "10.0.12", - "Microsoft.Extensions.Logging.Debug": "10.0.12", - "Microsoft.Extensions.Logging.EventLog": "10.0.12", - "Microsoft.Extensions.Logging.EventSource": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12" + "Microsoft.Extensions.Primitives": "10.0.10" } }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "ZlIxIJtWmrph0Ikj0svSaTkZ+d+ZYpTAhazpf1b3aoFRmlNnNWwkcvqnqT9yKHjKvfBRthvUrkq11OxfItOhpg==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.12", - "Microsoft.Extensions.FileProviders.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Logging": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "6I46fTPfgYkrjRYfRXbho9WOvOelTnNjWuZws/hzGHDASH1LEJeA4VKK9k3wJvido8o7jJSB5WkMTonX7HM1bA==", + "resolved": "10.0.10", + "contentHash": "5LugpYGHk+mkn0a8IZgcyfBca8PCTAU9RQFoMrTdtOOidq88M2SI5f3px6ugnzgxC+eTkvYYJi8pzlUnG5xdAQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12" + "Microsoft.Extensions.Configuration.Abstractions": "10.0.10", + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.10", + "Microsoft.Extensions.FileProviders.Abstractions": "10.0.10", + "Microsoft.Extensions.Logging.Abstractions": "10.0.10" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "+24lC4plfbEDNfLAdTV/SWKS7dW+16X4HdydO3R++134kSNTzcbYA4KpR1Hdh6uWisB8Za3AzwyOn+K+NxWIug==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Logging.Configuration": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "CLHWfKQZWFE6fSzRXfjd4UlGveckkfPtwx/p5yF/o3hEkMSzGsl7MSxNAkI19hjw0rprBqrQC09Dwy6Lh6kweA==", - "dependencies": { - "Microsoft.Extensions.Configuration": "10.0.12", - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.Configuration.Binder": "10.0.12", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12", - "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.12" - } - }, - "Microsoft.Extensions.Logging.Console": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "nHvaR8gO0CdEfG324KdUsP3Hqgdf1licZb+UlNh5pFYw7+o8QYTmiMgFmwayE/LPSpaqND/Qn3xHa1cI9b8x0w==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging.Configuration": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12" - } - }, - "Microsoft.Extensions.Logging.Debug": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "dEvvABq9Qgfu/SgTncXDjCshcJmgO97bLOnMAuqBDSonlk4+VXlKJMHPak+S78TlOxJ47+gY7vPgVukJsr69gg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12" - } - }, - "Microsoft.Extensions.Logging.EventLog": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "Qn0FWPMiQzM4GQEEXxrJbh3vp3I9wrAgpMcsVH36+6FTRGXaqtwHDgf/2030FUJ4OuNSpOeIRh+9kCZGDDsfGg==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12" - } - }, - "Microsoft.Extensions.Logging.EventSource": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "nKpYoiUdBu8fdLwDxj/fjjGn//7/VwRLzujtMq99/pExrswMXOz+80hbB98QxrD8qPt9oWlFpEVrN9OBKOP7RQ==", + "resolved": "10.0.10", + "contentHash": "zkFxGYUvdxAvIKTyXHrmW+Sux53D4SezD9dMyZ6hrwwzPQJNuwCRy1f5W7AvYTqacEGhWF2XderRQG1OvbV8og==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Logging": "10.0.12", - "Microsoft.Extensions.Logging.Abstractions": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12", - "Microsoft.Extensions.Primitives": "10.0.12" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "TDYD33TSRpXKZWlmTXNlj5kCihxatmv2Ec1u6C+bMYLphCS7PoSLE9Pjd/nunDoE7yETk+LLKjVJX78HYtWjpA==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Primitives": "10.0.12" - } - }, - "Microsoft.Extensions.Options.ConfigurationExtensions": { - "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "rqpu4qj5WE9x1IHGXSIgHBKi7IUlQaHyp4aXCYIanG2OghlUMFZpZTgExaXwcvmLAJHsxKQWMPpc7D2WIbCVtA==", + "resolved": "10.0.10", + "contentHash": "srnhnk7nE8krBiIXp71LvBmKBtraBONWSRzdjJgRv1Ko9Mp8IVNqv4vIS9hGeVteBig8aQkva9ZG+sC+o5sVcA==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "10.0.12", - "Microsoft.Extensions.Configuration.Binder": "10.0.12", - "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.12", - "Microsoft.Extensions.Options": "10.0.12", - "Microsoft.Extensions.Primitives": "10.0.12" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.10", + "Microsoft.Extensions.Primitives": "10.0.10" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "10.0.12", - "contentHash": "dYfCLR52UA+3DL7C4I/pvSaRPkNqxrUAQmbFL2u0zvYKKzqgrFCJl08Df+F1aYc8leu9JvpC9bsURUdpExcBXQ==" + "resolved": "10.0.10", + "contentHash": "5wu/GrYVd8mG2DVUw3vFJzF+O336TyTGg/Kmcgw9bfwYhCoFiV5lR5QeEmKecJyrW4W54nMfD3p3589E8a7czQ==" }, "ModelContextProtocol": { "type": "Transitive", @@ -303,6 +89,14 @@ "ModelContextProtocol.Core": "[2.2.0]" } }, + "ModelContextProtocol.AspNetCore": { + "type": "Transitive", + "resolved": "2.2.0", + "contentHash": "3JelDMuFIwFzXybsh6K30G6wXu5gmqKnuRnieqgBMuR0FkqqXRv4B+NYZuPgthSyNq5UwRyvv2U2VJBE3RD3PQ==", + "dependencies": { + "ModelContextProtocol": "[2.2.0]" + } + }, "ModelContextProtocol.Core": { "type": "Transitive", "resolved": "2.2.0", @@ -315,8 +109,8 @@ "outlook-classic-mcp": { "type": "Project", "dependencies": { - "Microsoft.Extensions.Hosting": "[10.0.12, )", - "ModelContextProtocol": "[2.2.0, )" + "ModelContextProtocol": "[2.2.0, )", + "ModelContextProtocol.AspNetCore": "[2.2.0, )" } } } diff --git a/scripts/Build-OutlookClassic.ps1 b/scripts/Build-OutlookClassic.ps1 index 282fb75..c6011ed 100644 --- a/scripts/Build-OutlookClassic.ps1 +++ b/scripts/Build-OutlookClassic.ps1 @@ -49,6 +49,8 @@ try { if (-not $SkipChecks) { & node (Join-Path $pluginRoot 'tests\mcp-smoke.mjs') (Join-Path $packageRoot 'bin\outlook-classic-mcp.exe') if ($LASTEXITCODE -ne 0) { throw 'MCP protocol checks failed.' } + & node (Join-Path $pluginRoot 'tests\http-companion-smoke.mjs') (Join-Path $packageRoot 'bin\outlook-classic-mcp.exe') + if ($LASTEXITCODE -ne 0) { throw 'HTTP companion checks failed.' } } $portableZip = Join-Path $buildRoot 'outlook-classic-windows-amd64.zip' Compress-Archive -LiteralPath $packageRoot -DestinationPath $portableZip -CompressionLevel Optimal From ae09c5d741a639131d7cc1e978392eef4c7698a0 Mon Sep 17 00:00:00 2001 From: Alexander Rymdeko-Harvey <8761706+killswitch-GUI@users.noreply.github.com> Date: Fri, 2 Oct 2026 09:40:23 -0400 Subject: [PATCH 2/2] test(outlook-classic): record companion live acceptance --- validation/outlook-classic-live.json | 52 ++++++++++++++-------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/validation/outlook-classic-live.json b/validation/outlook-classic-live.json index 8282976..2ea6165 100644 --- a/validation/outlook-classic-live.json +++ b/validation/outlook-classic-live.json @@ -1,121 +1,121 @@ { "schemaVersion": 1, - "sourceFingerprint": "cc2bfd5bb80d30393d5ad15bbeb90dd99a3c5ffd4c668fe1765c863bc8755b2a", + "sourceFingerprint": "354c278403c17aa5f5be981d37003a499d22ecbaa96cb2486efad01f6ca33eeb", "baseVersion": "0.1.0-alpha.4", "testedVersion": "0.1.0-alpha.4", "scope": "Classic Outlook in the interactive user session; AppContainer and new Outlook are unsupported.", "report": { - "startedAt": "2026-10-02T10:19:15.903Z", + "startedAt": "2026-10-02T13:37:32.548Z", "checks": [ { "name": "live COM status uses STA and reports classic Outlook", "status": "passed", - "elapsedMs": 167 + "elapsedMs": 197 }, { "name": "store pagination discovers both synthetic PSTs without duplicates", "status": "passed", - "elapsedMs": 688 + "elapsedMs": 716 }, { "name": "folder enumeration and paging preserve Unicode and nested folders", "status": "passed", - "elapsedMs": 229 + "elapsedMs": 274 }, { "name": "store default-folder discovery exposes the synthetic Inbox, Drafts, and Deleted Items", "status": "passed", - "elapsedMs": 89 + "elapsedMs": 101 }, { "name": "message paging returns only mail items with no duplicates", "status": "passed", - "elapsedMs": 363 + "elapsedMs": 370 }, { "name": "literal Unicode and apostrophe subject filters do not become queries", "status": "passed", - "elapsedMs": 171 + "elapsedMs": 186 }, { "name": "unread-only filter returns the expected unread fixtures", "status": "passed", - "elapsedMs": 78 + "elapsedMs": 89 }, { "name": "empty folders and offsets past the end return empty terminal pages", "status": "passed", - "elapsedMs": 73 + "elapsedMs": 79 }, { "name": "large-folder scan stops at 500 and resumes on the next page", "status": "passed", - "elapsedMs": 1980 + "elapsedMs": 2145 }, { "name": "body omission, content bounds, and untrusted-data marker work on live items", "status": "passed", - "elapsedMs": 342 + "elapsedMs": 385 }, { "name": "Unicode truncation does not split an emoji surrogate pair", "status": "passed", - "elapsedMs": 64 + "elapsedMs": 67 }, { "name": "attachment metadata and pagination preserve names without exporting files", "status": "passed", - "elapsedMs": 269 + "elapsedMs": 309 }, { "name": "non-mail and nonexistent COM item handles fail safely", "status": "passed", - "elapsedMs": 100 + "elapsedMs": 114 }, { "name": "handles from a second PST resolve within the correct store", "status": "passed", - "elapsedMs": 110 + "elapsedMs": 119 }, { "name": "create and update an unsent draft only in the selected synthetic PST", "status": "passed", - "elapsedMs": 524 + "elapsedMs": 596 }, { "name": "read-state changes require the current synthetic source folder", "status": "passed", - "elapsedMs": 176 + "elapsedMs": 190 }, { "name": "move and archive stay inside the selected synthetic store", "status": "passed", - "elapsedMs": 329 + "elapsedMs": 353 }, { "name": "delete moves to Deleted Items and refuses permanent deletion", "status": "passed", - "elapsedMs": 246 + "elapsedMs": 272 }, { "name": "default Inbox bounded live reads preserve unread flags (no content logged)", "status": "passed", - "elapsedMs": 2846 + "elapsedMs": 2946 }, { "name": "MCP shutdown leaves Outlook available and handles survive a new process", "status": "passed", - "elapsedMs": 793 + "elapsedMs": 890 }, { "name": "all synthetic unread flags still match after the live tool suite", "status": "passed", - "elapsedMs": 159 + "elapsedMs": 192 }, { "name": "MCP exits cleanly with no diagnostic output", "status": "passed", - "elapsedMs": 38 + "elapsedMs": 41 }, { "name": "only the test-created synthetic draft was removed after Deleted Items verification", @@ -135,10 +135,10 @@ "realUnreadMessagesChecked": 2 }, "personalContentLogged": false, - "completedAt": "2026-10-02T10:19:26.127Z", + "completedAt": "2026-10-02T13:37:43.675Z", "passed": true, "executableSha256": "000e1507bd4dc2065cde48c58c061c30cbbd71f3caf5df94597fcbe3c1cd8072", - "runtimeSha256": "5dc0ebe7557f05a79bc1b28a6d2015ecf34cb3df1f06aabc0e9efbff81173c0e", + "runtimeSha256": "2f577f895de1d41676b91b1a4a57e7bc8dfb0b46c079d175d9647734b7b5aa4e", "osVersion": "10.0.22000.0", "executionMode": "interactive Windows user session; not AppContainer", "syntheticStoresDetached": true