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