From 8ebb6f9482eb99e19375e4f3b204e3b7a2541de1 Mon Sep 17 00:00:00 2001 From: ddemeyer Date: Tue, 18 Aug 2026 15:01:06 +0200 Subject: [PATCH 1/4] #245 Augment Get-IshPublicationOutputData with protocol OpenApiWithOpenIdConnect implementation --- Doc/ReleaseNotes-ISHRemote-8.3.md | 1 + .../GetIshPublicationOutputData.cs | 80 +++++++++++++------ 2 files changed, 57 insertions(+), 24 deletions(-) diff --git a/Doc/ReleaseNotes-ISHRemote-8.3.md b/Doc/ReleaseNotes-ISHRemote-8.3.md index ef876f5..7153728 100644 --- a/Doc/ReleaseNotes-ISHRemote-8.3.md +++ b/Doc/ReleaseNotes-ISHRemote-8.3.md @@ -25,6 +25,7 @@ The below text describes the delta compared to fielded release ISHRemote v8.2. ## Implementation Details +* Augmented `Get-IshPublicationOutputData` with an `OpenApiWithOpenIdConnect` protocol implementation on Tridion Docs 15.1 and higher. Instead of a SOAP byte-level chunk loop (many sequential HTTP calls), a single streaming REST `GET /v3/Publications/ByLanguageCardId/{languageCardId}/Content` request is used, making large publication output downloads significantly faster especially over high-latency connections. Sessions using `OpenApiWithOpenIdConnect` on servers older than 15.1 transparently fall back to the existing SOAP chunk loop. See #245. Thanks @ddemeyer * Fixed `Start-IshRemoteMcpServer` failing to connect on Windows with newer MCP clients (e.g. OpenCode 1.18.11, protocol `2025-11-25`) with errors `MCP error -32001: Request timed out` and `Failed to get tools`. Three root causes: (1) `initialize` requests with `"id":0` were silently dropped because PowerShell treats `0` as falsy; (2) `[Console]::InputEncoding` defaults to OEM code page (`ibm437`) when `pwsh.exe` is spawned with redirected stdio on Windows, causing `ReadLine()` to block forever on UTF-8 JSON — fixed by explicitly setting UTF-8 encoding and replacing `Console.Out` with an auto-flushing `StreamWriter` via `[Console]::SetOut()`; (3) `Register-IshRemoteMcpTool` emitted an invalid `type: "object"` field in `ToolAnnotations` and used string `"true"`/`"false"` instead of boolean `$true`/`$false` for hint values, causing strict MCP schema validation to reject the tools list. Server name updated from `"PowerShell MCP Server (Template)"` to `"ISHRemote MCP Server"` and version bumped to `0.3.0`. Also fixed the server looping forever on stdin EOF (orphaned `pwsh` processes) by breaking the while loop when `ReadLine()` returns `$null`. See #243 and #261. Thanks @ddemeyer diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputData.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputData.cs index fd036f5..6109dc7 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputData.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputData.cs @@ -33,7 +33,15 @@ namespace Trisoft.ISHRemote.Cmdlets.PublicationOutput /// /// /// - /// $ishSession = New-IshSession -WsBaseUrl "https://example.com/InfoShareWS/" -IshUserName "username" -IshUserPassword "userpassword" + /// $ishSession = New-IshSession -WsBaseUrl "https://example.com/InfoShareWS/" + /// Get-IshPublicationOutput -LogicalId GUID-03081B9A-11E4-4862-845B-27339E0C400D | + /// Get-IshPublicationOutputData -FolderPath C:\TEMP\20260818\ + /// + /// Retrieves all PublicationOutputs of the given logical id and downloads them. + /// + /// + /// + /// $ishSession = New-IshSession -WsBaseUrl "https://example.com/InfoShareWS/" /// $requestedMetadataRetrieve = Set-IshRequestedMetadataField -IshSession $ishSession -Name 'FISHOUTPUTFORMATREF' -Level "Lng" | /// Set-IshRequestedMetadataField -IshSession $ishSession -Name 'FISHPUBLNGCOMBINATION' -Level "Lng" | /// Set-IshRequestedMetadataField -IshSession $ishSession -Name 'FISHPUBSTATUS' -Level "Lng" | @@ -109,9 +117,10 @@ protected override void ProcessRecord() { // Get language ref long lngRef = Convert.ToInt64(ishObject.ObjectRef[Enumerations.ReferenceType.Lng]); - string xmlIshDataObject = IshSession.PublicationOutput25.GetDataObjectInfoByIshLngRef(lngRef); - // Put the xml in a dataobject + // File extension is always sourced from GetDataObjectInfoByIshLngRef to guarantee + // an identical output filename regardless of the download protocol used. + string xmlIshDataObject = IshSession.PublicationOutput25.GetDataObjectInfoByIshLngRef(lngRef); XmlDocument xmlIshDataObjectDocument = new XmlDocument(); xmlIshDataObjectDocument.LoadXml(xmlIshDataObject); XmlElement ishDataObjectElement = @@ -120,28 +129,51 @@ protected override void ProcessRecord() string tempFilePath = FileNameHelper.GetDefaultPublicationOutputFileName(tempLocation, ishObject, ishDataObject.FileExtension); - WriteDebug($"Writing lngRef[{lngRef}] to [{tempFilePath}] {++current}/{ishObjects.Length}"); - - //Create the file. - using (FileStream fs = File.Create(tempFilePath)) + switch (IshSession.Protocol) { - for (int offset = 0; offset < ishDataObject.Size; offset += IshSession.ChunkSize) - { - int size = IshSession.ChunkSize; - long offsetCount = offset; - byte[] byteArray = new byte[IshSession.ChunkSize]; - var response = - IshSession.PublicationOutput25.GetNextDataObjectChunkByIshLngRef( - new PublicationOutput25ServiceReference.GetNextDataObjectChunkByIshLngRefRequest( - lngRef, - ishDataObject.Ed, - offsetCount, - size)); - offsetCount = response.offSet; - size = response.size; - byteArray = response.bytes; - fs.Write(byteArray, 0, size); - } + case Enumerations.Protocol.OpenApiWithOpenIdConnect: + if (IshSession.ServerIshVersion.MajorVersion > 15 || + (IshSession.ServerIshVersion.MajorVersion == 15 && IshSession.ServerIshVersion.MinorVersion >= 1)) + { + // Single streaming HTTP GET via OpenAPI — replaces the entire SOAP chunk loop. + // IshSession.ChunkSize is reused as the CopyTo buffer size to keep memory usage + // bounded while avoiding unnecessary syscall overhead. + WriteDebug($"Writing lngRef[{lngRef}] via OpenAPI stream to [{tempFilePath}] {++current}/{ishObjects.Length}"); + using (var fileResponse = IshSession.OpenApiISH30Client + .GetPublicationContentByLanguageCardIdAsync(lngRef) + .GetAwaiter().GetResult()) + using (FileStream fs = File.Create(tempFilePath)) + { + fileResponse.Stream.CopyTo(fs, IshSession.ChunkSize); + } + break; + } + // Server < 15.1 does not expose the REST endpoint; fall back to SOAP chunk loop. + goto case Enumerations.Protocol.WcfSoapWithOpenIdConnect; + case Enumerations.Protocol.WcfSoapWithWsTrust: + case Enumerations.Protocol.WcfSoapWithOpenIdConnect: + WriteDebug($"Writing lngRef[{lngRef}] to [{tempFilePath}] {++current}/{ishObjects.Length}"); + using (FileStream fs = File.Create(tempFilePath)) + { + for (int offset = 0; offset < ishDataObject.Size; offset += IshSession.ChunkSize) + { + int size = IshSession.ChunkSize; + long offsetCount = offset; + byte[] byteArray = new byte[IshSession.ChunkSize]; + var response = + IshSession.PublicationOutput25.GetNextDataObjectChunkByIshLngRef( + new PublicationOutput25ServiceReference.GetNextDataObjectChunkByIshLngRefRequest( + lngRef, + ishDataObject.Ed, + offsetCount, + size)); + offsetCount = response.offSet; + size = response.size; + byteArray = response.bytes; + fs.Write(byteArray, 0, size); + } + } + break; } // Append file info list From 2a9513c2b814d96aa209a709be5c0c0d5e2a5f93 Mon Sep 17 00:00:00 2001 From: ddemeyer Date: Thu, 19 Mar 2026 17:01:31 +0100 Subject: [PATCH 2/4] #232 HttpClient request and response compression handling --- .../InfoShareWcfSoapWithOpenIdConnectConnection.cs | 13 +++++++++++++ .../InfoShareWcfSoapWithWsTrustConnection.cs | 13 +++++++++++++ .../Trisoft.ISHRemote/Objects/Public/IshSession.cs | 14 ++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapWithOpenIdConnectConnection.cs b/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapWithOpenIdConnectConnection.cs index b2818e2..731fee5 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapWithOpenIdConnectConnection.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapWithOpenIdConnectConnection.cs @@ -1726,7 +1726,20 @@ private XDocument LoadConnectionConfiguration() handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; } handler.SslProtocols = (System.Security.Authentication.SslProtocols)(SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13); +#if NET48 + handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; +#else + handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli; +#endif var httpClient = new HttpClient(handler); +#if NET48 + httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip"); + httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("deflate"); +#else + httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip"); + httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("deflate"); + httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("br"); +#endif httpClient.Timeout = _connectionParameters.Timeout; var connectionConfigurationUri = new Uri(InfoShareWSBaseUri, "connectionconfiguration.xml"); _logger.WriteDebug($"LoadConnectionConfiguration uri[{connectionConfigurationUri}] timeout[{httpClient.Timeout}]"); diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapWithWsTrustConnection.cs b/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapWithWsTrustConnection.cs index d2aa2ec..9d49633 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapWithWsTrustConnection.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Connection/InfoShareWcfSoapWithWsTrustConnection.cs @@ -1368,7 +1368,20 @@ private XDocument LoadConnectionConfiguration() handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; } handler.SslProtocols = (System.Security.Authentication.SslProtocols)(SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13); +#if NET48 + handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; +#else + handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli; +#endif var httpClient = new HttpClient(handler); +#if NET48 + httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip"); + httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("deflate"); +#else + httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip"); + httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("deflate"); + httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("br"); +#endif httpClient.Timeout = _connectionParameters.Timeout; var connectionConfigurationUri = new Uri(InfoShareWSBaseUri, "connectionconfiguration.xml"); _logger.WriteDebug($"LoadConnectionConfiguration uri[{connectionConfigurationUri}] timeout[{httpClient.Timeout}]"); diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Objects/Public/IshSession.cs b/Source/ISHRemote/Trisoft.ISHRemote/Objects/Public/IshSession.cs index 42c9fbb..d1780a7 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Objects/Public/IshSession.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Objects/Public/IshSession.cs @@ -138,10 +138,24 @@ public IshSession(ILogger logger, string webServicesBaseUrl, string ishUserName, handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; } handler.SslProtocols = (System.Security.Authentication.SslProtocols)(SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13); +#if NET48 + handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; +#else + handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli; +#endif + _httpClient = new HttpClient(handler) { Timeout = _timeout }; +#if NET48 + _httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip"); + _httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("deflate"); +#else + _httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip"); + _httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("deflate"); + _httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("br"); +#endif // webServicesBaseUrl should have trailing slash, otherwise .NET throws unhandy "Reference to undeclared entity 'raquo'." error _webServicesBaseUri = (webServicesBaseUrl.EndsWith("/")) ? new Uri(webServicesBaseUrl) : new Uri(webServicesBaseUrl + "/"); _ishUserName = ishUserName == null ? Environment.UserName : ishUserName; From d80054cb0e424df326f5bd7e8f49ba9ff614f72b Mon Sep 17 00:00:00 2001 From: ddemeyer Date: Tue, 18 Aug 2026 17:06:39 +0200 Subject: [PATCH 3/4] #245 Better error handling and tweaked instructions to match that. --- .../source-cmdlets--csharp.instructions.md | 12 +++++++----- .../source-codereview-csharp.instructions.md | 8 +++++++- .../GetIshPublicationOutputData.cs | 13 +++++++++++++ 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.github/instructions/source-cmdlets--csharp.instructions.md b/.github/instructions/source-cmdlets--csharp.instructions.md index da7a2b8..55f4be0 100644 --- a/.github/instructions/source-cmdlets--csharp.instructions.md +++ b/.github/instructions/source-cmdlets--csharp.instructions.md @@ -127,13 +127,15 @@ Close `ProcessRecord`/`EndProcessing` with this exact catch ladder (copy from a and the per-type `ErrorCategory` matter; each ends in `ThrowTerminatingError`: ```csharp catch (TrisoftAutomationException e) { ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, ErrorCategory.InvalidOperation, null)); } +catch (OpenApiISH30.OpenApiISH30Exception e) {if (e.Result != null) { WriteWarning($"Status[{e.Result.Status}] Title[{e.Result.Title}] EventName[{e.Result.EventName}] Detail[{e.Result.Detail}]"); foreach (var error in e.Result.Errors) { WriteWarning($"ErrorEventName[{error.EventName}] ErrorDetail[{error.Detail}]"); } } ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, ErrorCategory.InvalidOperation, null)); } catch (AggregateException e) { var f = e.Flatten(); WriteWarning(f.ToString()); ThrowTerminatingError(new ErrorRecord(f, base.GetType().Name, ErrorCategory.NotSpecified, null)); } -catch (TimeoutException e) { WriteVerbose(...); ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, ErrorCategory.OperationTimeout, null)); } -catch (CommunicationException e) { WriteVerbose(...); ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, ErrorCategory.OperationStopped, null)); } -catch (Exception e) { ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, ErrorCategory.NotSpecified, null)); } +catch (TimeoutException e) { WriteVerbose("TimeoutException Message[" + e.Message + "] StackTrace[" + e.StackTrace + "]"); ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, ErrorCategory.OperationTimeout, null)); } +catch (CommunicationException e) { WriteVerbose("CommunicationException Message[" + e.Message + "] StackTrace[" + e.StackTrace + "]"); ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, ErrorCategory.OperationStopped, null)); } +catch (Exception e) { if (e.InnerException != null) { WriteWarning(e.InnerException.ToString()); } ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, ErrorCategory.NotSpecified, null)); } ``` -Don't reorder, collapse, or silently swallow these. If you believe the handling can genuinely be -improved, **challenge it explicitly with the implementer** before changing it. +The `OpenApiISH30Exception` catch is only required when the cmdlet makes OpenAPI calls; SOAP-only +cmdlets may omit it. Don't reorder, collapse, or silently swallow these. If you believe the +handling can genuinely be improved, **challenge it explicitly with the implementer** before changing it. ## 8. Diagnostic logging density A `-Debug` or `-Verbose` transcript must contain enough context to reconstruct what happened and diff --git a/.github/instructions/source-codereview-csharp.instructions.md b/.github/instructions/source-codereview-csharp.instructions.md index a4960b3..c817062 100644 --- a/.github/instructions/source-codereview-csharp.instructions.md +++ b/.github/instructions/source-codereview-csharp.instructions.md @@ -75,11 +75,17 @@ Authoring detail for each topic lives in the companion instruction files injecte ## 6. Exception handling - [ ] Catch ladder is in this exact order: - `TrisoftAutomationException` → `AggregateException` → `TimeoutException` → + `TrisoftAutomationException` → *(OpenAPI cmdlets only)* `OpenApiISH30Exception` → `AggregateException` → `TimeoutException` → `CommunicationException` → `Exception`. +- [ ] `OpenApiISH30Exception` catch is present whenever the cmdlet makes + OpenAPI calls; SOAP-only cmdlets may omit it. When present it calls `WriteWarning` for each + structured error field (`Status`, `Title`, `EventName`, `Detail`, per-error `ErrorEventName`/`ErrorDetail`) before `ThrowTerminatingError`. - [ ] Every catch calls `ThrowTerminatingError(new ErrorRecord(e, base.GetType().Name, ErrorCategory.XXX, null))` with the correct `ErrorCategory` — no silent swallows, no plain `throw`. +- [ ] `catch (Exception)` calls `WriteWarning(e.InnerException.ToString())` when `InnerException` + is non-null, before `ThrowTerminatingError` — surfaces the root cause of wrapped exceptions such + as `HttpRequestException`. - [ ] The order is not reordered, collapsed, or partially removed without explicit sign-off from the implementer. diff --git a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputData.cs b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputData.cs index 6109dc7..425ed99 100644 --- a/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputData.cs +++ b/Source/ISHRemote/Trisoft.ISHRemote/Cmdlets/PublicationOutput/GetIshPublicationOutputData.cs @@ -187,6 +187,18 @@ protected override void ProcessRecord() { ThrowTerminatingError(new ErrorRecord(trisoftAutomationException, base.GetType().Name, ErrorCategory.InvalidOperation, null)); } + catch (OpenApiISH30.OpenApiISH30Exception openApiISH30Exception) + { + if (openApiISH30Exception.Result != null) + { + WriteWarning($"Status[{openApiISH30Exception.Result.Status}] Title[{openApiISH30Exception.Result.Title}] EventName[{openApiISH30Exception.Result.EventName}] Detail[{openApiISH30Exception.Result.Detail}]"); + foreach (var error in openApiISH30Exception.Result.Errors) + { + WriteWarning($"ErrorEventName[{error.EventName}] ErrorDetail[{error.Detail}]"); + } + } + ThrowTerminatingError(new ErrorRecord(openApiISH30Exception, base.GetType().Name, ErrorCategory.InvalidOperation, null)); + } catch (AggregateException aggregateException) { var flattenedAggregateException = aggregateException.Flatten(); @@ -205,6 +217,7 @@ protected override void ProcessRecord() } catch (Exception exception) { + if (exception.InnerException != null) { WriteWarning(exception.InnerException.ToString()); } ThrowTerminatingError(new ErrorRecord(exception, base.GetType().Name, ErrorCategory.NotSpecified, null)); } } From 4c47ec7367ea0ac0e62d8b39396fb2ab6386b92a Mon Sep 17 00:00:00 2001 From: ddemeyer Date: Tue, 18 Aug 2026 19:10:10 +0200 Subject: [PATCH 4/4] #245 Updated release notes --- Doc/ReleaseNotes-ISHRemote-8.3.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Doc/ReleaseNotes-ISHRemote-8.3.md b/Doc/ReleaseNotes-ISHRemote-8.3.md index 7153728..cc6b191 100644 --- a/Doc/ReleaseNotes-ISHRemote-8.3.md +++ b/Doc/ReleaseNotes-ISHRemote-8.3.md @@ -9,7 +9,7 @@ High level release notes are on [Github](https://github.com/rws/ISHRemote/releas This release inherits the v0.1 to v0.14 up to v8.2 development branch and features. All cmdlets and business logic are fully compatible even around authentication. In short, we expect it all to work still :) -The one that is +The one that respects the details of Model Context Protocol (MCP) enabling usage in other toolings like Claude Code or OpenCode/OpenChamber. All `OpenApiWithOpenIdConnect` is now using in-flight http compression which benefits `Get-IshPublicationOutputData` offering faster downloads on 15.1.0+ environments. ### Remember @@ -25,7 +25,8 @@ The below text describes the delta compared to fielded release ISHRemote v8.2. ## Implementation Details -* Augmented `Get-IshPublicationOutputData` with an `OpenApiWithOpenIdConnect` protocol implementation on Tridion Docs 15.1 and higher. Instead of a SOAP byte-level chunk loop (many sequential HTTP calls), a single streaming REST `GET /v3/Publications/ByLanguageCardId/{languageCardId}/Content` request is used, making large publication output downloads significantly faster especially over high-latency connections. Sessions using `OpenApiWithOpenIdConnect` on servers older than 15.1 transparently fall back to the existing SOAP chunk loop. See #245. Thanks @ddemeyer +* Enabled HTTP response compression (`gzip`, `deflate`, `brotli`) on the `HttpClient` for non-SOAP usage across protocols — `WcfSoapWithWsTrust`, `WcfSoapWithOpenIdConnect`, and `OpenApiWithOpenIdConnect`. Both `AutomaticDecompression` on the handler and the matching `Accept-Encoding` request headers are set, so the server can compress response bodies. On .NET 4.8 `brotli` is not available; `gzip` and `deflate` are used instead. See #232. Thanks @ddemeyer +* Augmented `Get-IshPublicationOutputData` with an `OpenApiWithOpenIdConnect` protocol implementation on Tridion Docs 15.1 and higher. Instead of a SOAP byte-level chunk loop (many sequential HTTP calls), a single streaming REST `GET /v3/Publications/ByLanguageCardId/{languageCardId}/Content` request is used, making large publication output downloads significantly faster especially over high-latency connections. Sessions using `OpenApiWithOpenIdConnect` on servers older than 15.1 transparently fall back to the existing SOAP chunk loop. Downloads are in essence network restricted, still streaming shows improvements between 20% and 40% compared to the SOAP variation on the same environment. See #245. Thanks @ddemeyer * Fixed `Start-IshRemoteMcpServer` failing to connect on Windows with newer MCP clients (e.g. OpenCode 1.18.11, protocol `2025-11-25`) with errors `MCP error -32001: Request timed out` and `Failed to get tools`. Three root causes: (1) `initialize` requests with `"id":0` were silently dropped because PowerShell treats `0` as falsy; (2) `[Console]::InputEncoding` defaults to OEM code page (`ibm437`) when `pwsh.exe` is spawned with redirected stdio on Windows, causing `ReadLine()` to block forever on UTF-8 JSON — fixed by explicitly setting UTF-8 encoding and replacing `Console.Out` with an auto-flushing `StreamWriter` via `[Console]::SetOut()`; (3) `Register-IshRemoteMcpTool` emitted an invalid `type: "object"` field in `ToolAnnotations` and used string `"true"`/`"false"` instead of boolean `$true`/`$false` for hint values, causing strict MCP schema validation to reject the tools list. Server name updated from `"PowerShell MCP Server (Template)"` to `"ISHRemote MCP Server"` and version bumped to `0.3.0`. Also fixed the server looping forever on stdin EOF (orphaned `pwsh` processes) by breaking the while loop when `ReadLine()` returns `$null`. See #243 and #261. Thanks @ddemeyer