From aa4c8f5e451a75e4d9ef3ed2fdb5df5337928149 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Thu, 26 Mar 2026 21:21:57 -0500 Subject: [PATCH 1/8] Add Update commands for MCP --- doc/ReleaseNotes.md | 7 + .../Response/PackageResponse.cs | 88 +++++++++++ src/WinGetMCPServer/WingetPackageTools.cs | 144 ++++++++++++++++++ 3 files changed, 239 insertions(+) diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index b325c92b48..36edf5ba3f 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -32,6 +32,13 @@ match criteria that factor into the result ordering. This will prevent them from Added a new `--no-progress` command-line flag that disables all progress reporting (progress bars and spinners). This flag is universally available on all commands and takes precedence over the `visual.progressBar` setting. Useful for automation scenarios or when running WinGet in environments where progress output is undesirable. +### MCP `upgrade` support + +The WinGet MCP server now exposes two new tools for upgrading packages: + +- **`get-upgradeable-winget-packages`** — Lists all installed packages that have available upgrades. Accepts an optional `query` parameter to filter by a specific package identifier, name, or moniker. AI agents can use this to answer requests like "What apps can I update with WinGet?" +- **`upgrade-winget-package`** — Upgrades a specific installed package to its latest available version. Returns a clear error if the package is not already installed (pointing to `install-winget-package` instead). Marked destructive, so MCP clients will prompt for confirmation before proceeding. AI agents can use this to answer requests like "Update WinGetCreate" or, in combination with `get-upgradeable-winget-packages`, "Update all my apps." + ### Authenticated GitHub API requests in PowerShell module The PowerShell module now automatically uses `GH_TOKEN` or `GITHUB_TOKEN` environment variables to authenticate GitHub API requests. This significantly increases the GitHub API rate limit, preventing failures in CI/CD pipelines. Use `-Verbose` to see which token is being used. diff --git a/src/WinGetMCPServer/Response/PackageResponse.cs b/src/WinGetMCPServer/Response/PackageResponse.cs index 49d5691c7b..520a83a33a 100644 --- a/src/WinGetMCPServer/Response/PackageResponse.cs +++ b/src/WinGetMCPServer/Response/PackageResponse.cs @@ -95,6 +95,94 @@ public static CallToolResult ForMultiFind(string identifer, string? source, Find return ToolResponse.FromObject(result); } + /// + /// Creates a response for a package that is not installed. + /// + /// The identifier used when searching. + /// The source that was searched. + /// The response. + public static CallToolResult ForNotInstalled(string identifier, string? source) + { + PackageIdentityErrorResult result = new() + { + Message = "The package is not installed; use install-winget-package to install it", + Identifier = identifier, + Source = source, + }; + + return ToolResponse.FromObject(result, isError: true); + } + + /// + /// Creates a response for an upgrade operation. + /// + /// The upgrade operation result. + /// The post-upgrade package data. + /// The response. + public static CallToolResult ForUpgradeOperation(InstallResult installResult, FindPackagesResult? findResult) + { + InstallOperationResult result = new InstallOperationResult(); + + switch (installResult.Status) + { + case InstallResultStatus.Ok: + result.Message = "Upgrade completed successfully"; + break; + case InstallResultStatus.BlockedByPolicy: + result.Message = "Upgrade was blocked by policy"; + break; + case InstallResultStatus.CatalogError: + result.Message = "An error occurred with the source"; + break; + case InstallResultStatus.InternalError: + result.Message = "An internal WinGet error occurred"; + break; + case InstallResultStatus.InvalidOptions: + result.Message = "The upgrade options were invalid"; + break; + case InstallResultStatus.DownloadError: + result.Message = "An error occurred while downloading the package installer"; + break; + case InstallResultStatus.InstallError: + result.Message = "The package installer failed during the upgrade"; + break; + case InstallResultStatus.ManifestError: + result.Message = "The package manifest was invalid"; + break; + case InstallResultStatus.NoApplicableInstallers: + result.Message = "No applicable package installers were available for this system"; + break; + case InstallResultStatus.NoApplicableUpgrade: + result.Message = "No applicable upgrade was available for this system"; + break; + case InstallResultStatus.PackageAgreementsNotAccepted: + result.Message = "The package requires accepting agreements; please upgrade manually"; + break; + default: + result.Message = "Unknown upgrade status"; + break; + } + + if (installResult.RebootRequired) + { + result.RebootRequired = true; + } + + result.ErrorCode = installResult.ExtendedErrorCode?.HResult; + + if (installResult.Status == InstallResultStatus.InstallError) + { + result.InstallerErrorCode = installResult.InstallerErrorCode; + } + + if (findResult != null && findResult.Status == FindPackagesResultStatus.Ok && findResult.Matches?.Count == 1) + { + result.InstalledPackageInformation = PackageListExtensions.FindPackageResultFromCatalogPackage(findResult.Matches[0].CatalogPackage); + } + + return ToolResponse.FromObject(result, installResult.Status != InstallResultStatus.Ok); + } + /// /// Creates a response for an install operation. /// diff --git a/src/WinGetMCPServer/WingetPackageTools.cs b/src/WinGetMCPServer/WingetPackageTools.cs index 4a99c2bba4..1b83048053 100644 --- a/src/WinGetMCPServer/WingetPackageTools.cs +++ b/src/WinGetMCPServer/WingetPackageTools.cs @@ -161,6 +161,144 @@ public async Task InstallPackage( } } + [McpServerTool( + Name = "get-upgradeable-winget-packages", + Title = "Get Upgradeable WinGet Packages", + ReadOnly = true, + OpenWorld = false)] + [Description("Get installed packages that have available upgrades using WinGet")] + public CallToolResult GetUpgradeablePackages( + [Description("Optionally filter by a package identifier, name, or moniker")] string? query = null) + { + try + { + ToolResponse.CheckGroupPolicy(); + + var catalog = ConnectCatalog(); + + FindPackagesResult findResult; + if (string.IsNullOrEmpty(query)) + { + findResult = FindAllPackages(catalog); + } + else + { + // First attempt a more exact match + findResult = FindForQuery(catalog, query, fullStringMatch: true); + + // If nothing is found, expand to a looser search + if ((findResult.Matches?.Count ?? 0) == 0) + { + findResult = FindForQuery(catalog, query, fullStringMatch: false); + } + } + + if (findResult.Status != FindPackagesResultStatus.Ok) + { + return PackageResponse.ForFindError(findResult); + } + + List contents = new List(); + for (int i = 0; i < findResult.Matches?.Count; ++i) + { + var package = findResult.Matches[i].CatalogPackage; + if (package.IsUpdateAvailable) + { + contents.Add(PackageListExtensions.FindPackageResultFromCatalogPackage(package)); + } + } + + return ToolResponse.FromObject(contents); + } + catch (ToolResponseException e) + { + return e.Response; + } + } + + [McpServerTool( + Name = "upgrade-winget-package", + Title = "Upgrade WinGet Package", + ReadOnly = false, + Destructive = true, + Idempotent = false, + OpenWorld = false)] + [Description("Upgrade an installed package to the latest available version using WinGet")] + public async Task UpgradePackage( + [Description("The identifier of the WinGet package to upgrade")] string identifier, + IProgress progress, + CancellationToken cancellationToken, + [Description("The source containing the package")] string? source = null) + { + try + { + ToolResponse.CheckGroupPolicy(); + + var packageCatalog = ConnectCatalog(source); + + if (cancellationToken.IsCancellationRequested) + { + return PackageResponse.ForCancelBeforeSystemChange(); + } + + // First attempt a more exact match + var findResult = FindForIdentifier(packageCatalog, identifier, expandedFields: false); + + if (cancellationToken.IsCancellationRequested) + { + return PackageResponse.ForCancelBeforeSystemChange(); + } + + // If nothing is found, expand to a looser search + if ((findResult.Matches?.Count ?? 0) == 0) + { + findResult = FindForIdentifier(packageCatalog, identifier, expandedFields: true); + } + + if (findResult.Status != FindPackagesResultStatus.Ok) + { + return PackageResponse.ForFindError(findResult); + } + + if (findResult.Matches?.Count == 0) + { + return PackageResponse.ForEmptyFind(identifier, source); + } + else if (findResult.Matches?.Count > 1) + { + return PackageResponse.ForMultiFind(identifier, source, findResult); + } + + CatalogPackage catalogPackage = findResult.Matches![0].CatalogPackage; + + if (catalogPackage.InstalledVersion == null) + { + return PackageResponse.ForNotInstalled(identifier, source); + } + + InstallOptions options = new InstallOptions(); + var operation = packageManager.UpgradePackageAsync(catalogPackage, options); + + operation.Progress = (asyncInfo, progressInfo) => progress.Report(CreateInstallProgressNotification(ref progressInfo)); + using CancellationTokenRegistration registration = cancellationToken.Register(() => operation.Cancel()); + + var installResult = await operation; + findResult = null; + + if (installResult.Status == InstallResultStatus.Ok) + { + progress.Report(CreateInstallProgressNotification(PackageInstallProgressState.Finished, 1.0, 1.0)); + findResult = ReFindForPackage(catalogPackage.DefaultInstallVersion); + } + + return PackageResponse.ForUpgradeOperation(installResult, findResult); + } + catch (ToolResponseException e) + { + return e.Response; + } + } + private ConnectResult ConnectCatalogWithResult(string? catalog = null) { CreateCompositePackageCatalogOptions createCompositePackageCatalogOptions = new CreateCompositePackageCatalogOptions(); @@ -217,6 +355,12 @@ private FindPackagesResult FindForIdentifier(PackageCatalog catalog, string quer return catalog!.FindPackages(findPackageOptions); } + private FindPackagesResult FindAllPackages(PackageCatalog catalog) + { + FindPackagesOptions findPackageOptions = new(); + return catalog!.FindPackages(findPackageOptions); + } + private FindPackagesResult? ReFindForPackage(PackageVersionInfo packageVersionInfo) { var connectResult = ConnectCatalogWithResult(packageVersionInfo.PackageCatalog.Info.Id); From f7eb0e28a25bc1943b02699d88b094b6803da56c Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 30 Mar 2026 11:35:46 -0500 Subject: [PATCH 2/8] Use correct catalog --- src/WinGetMCPServer/WingetPackageTools.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/WinGetMCPServer/WingetPackageTools.cs b/src/WinGetMCPServer/WingetPackageTools.cs index 1b83048053..b1b4c3af52 100644 --- a/src/WinGetMCPServer/WingetPackageTools.cs +++ b/src/WinGetMCPServer/WingetPackageTools.cs @@ -174,7 +174,10 @@ public CallToolResult GetUpgradeablePackages( { ToolResponse.CheckGroupPolicy(); - var catalog = ConnectCatalog(); + // Use LocalCatalogs behavior to only enumerate installed packages, consistent + // with `winget upgrade`. Remote catalogs are still included in the composite + // so IsUpdateAvailable remains accurate. + var catalog = ConnectCatalog(searchBehavior: CompositeSearchBehavior.LocalCatalogs); FindPackagesResult findResult; if (string.IsNullOrEmpty(query)) @@ -299,7 +302,7 @@ public async Task UpgradePackage( } } - private ConnectResult ConnectCatalogWithResult(string? catalog = null) + private ConnectResult ConnectCatalogWithResult(string? catalog = null, CompositeSearchBehavior searchBehavior = CompositeSearchBehavior.AllCatalogs) { CreateCompositePackageCatalogOptions createCompositePackageCatalogOptions = new CreateCompositePackageCatalogOptions(); @@ -313,15 +316,15 @@ private ConnectResult ConnectCatalogWithResult(string? catalog = null) createCompositePackageCatalogOptions.Catalogs.Add(catalogRef); } } - createCompositePackageCatalogOptions.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; + createCompositePackageCatalogOptions.CompositeSearchBehavior = searchBehavior; var compositeRef = packageManager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions); return compositeRef.Connect(); } - private PackageCatalog ConnectCatalog(string? catalog = null) + private PackageCatalog ConnectCatalog(string? catalog = null, CompositeSearchBehavior searchBehavior = CompositeSearchBehavior.AllCatalogs) { - var result = ConnectCatalogWithResult(catalog); + var result = ConnectCatalogWithResult(catalog, searchBehavior); if (result.Status != ConnectResultStatus.Ok) { throw new ToolResponseException(PackageResponse.ForConnectError(result)); From bf5edbc9312460f911711e9966e50c9291147a67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 30 Mar 2026 16:49:49 +0000 Subject: [PATCH 3/8] Fix spelling: upgradeable -> upgradable Agent-Logs-Url: https://github.com/Trenly/winget-cli/sessions/735d843d-a46d-4d20-b2a8-ed5f79405fd7 Co-authored-by: Trenly <12611259+Trenly@users.noreply.github.com> --- doc/ReleaseNotes.md | 4 ++-- src/WinGetMCPServer/WingetPackageTools.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index 36edf5ba3f..2edc503c27 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -36,8 +36,8 @@ Added a new `--no-progress` command-line flag that disables all progress reporti The WinGet MCP server now exposes two new tools for upgrading packages: -- **`get-upgradeable-winget-packages`** — Lists all installed packages that have available upgrades. Accepts an optional `query` parameter to filter by a specific package identifier, name, or moniker. AI agents can use this to answer requests like "What apps can I update with WinGet?" -- **`upgrade-winget-package`** — Upgrades a specific installed package to its latest available version. Returns a clear error if the package is not already installed (pointing to `install-winget-package` instead). Marked destructive, so MCP clients will prompt for confirmation before proceeding. AI agents can use this to answer requests like "Update WinGetCreate" or, in combination with `get-upgradeable-winget-packages`, "Update all my apps." +- **`get-upgradable-winget-packages`** — Lists all installed packages that have available upgrades. Accepts an optional `query` parameter to filter by a specific package identifier, name, or moniker. AI agents can use this to answer requests like "What apps can I update with WinGet?" +- **`upgrade-winget-package`** — Upgrades a specific installed package to its latest available version. Returns a clear error if the package is not already installed (pointing to `install-winget-package` instead). Marked destructive, so MCP clients will prompt for confirmation before proceeding. AI agents can use this to answer requests like "Update WinGetCreate" or, in combination with `get-upgradable-winget-packages`, "Update all my apps." ### Authenticated GitHub API requests in PowerShell module diff --git a/src/WinGetMCPServer/WingetPackageTools.cs b/src/WinGetMCPServer/WingetPackageTools.cs index b1b4c3af52..70d812d55a 100644 --- a/src/WinGetMCPServer/WingetPackageTools.cs +++ b/src/WinGetMCPServer/WingetPackageTools.cs @@ -162,12 +162,12 @@ public async Task InstallPackage( } [McpServerTool( - Name = "get-upgradeable-winget-packages", - Title = "Get Upgradeable WinGet Packages", + Name = "get-upgradable-winget-packages", + Title = "Get Upgradable WinGet Packages", ReadOnly = true, OpenWorld = false)] [Description("Get installed packages that have available upgrades using WinGet")] - public CallToolResult GetUpgradeablePackages( + public CallToolResult GetUpgradablePackages( [Description("Optionally filter by a package identifier, name, or moniker")] string? query = null) { try From 10074abb4bfce92d0d8af54daa1f7b8db8dc08a7 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 30 Mar 2026 13:48:35 -0500 Subject: [PATCH 4/8] Move List upgradeable into find --- src/WinGetMCPServer/WingetPackageTools.cs | 115 +++++++++------------- 1 file changed, 48 insertions(+), 67 deletions(-) diff --git a/src/WinGetMCPServer/WingetPackageTools.cs b/src/WinGetMCPServer/WingetPackageTools.cs index 70d812d55a..3b6219758b 100644 --- a/src/WinGetMCPServer/WingetPackageTools.cs +++ b/src/WinGetMCPServer/WingetPackageTools.cs @@ -34,23 +34,48 @@ public WingetPackageTools() Title = "Find WinGet Packages", ReadOnly = true, OpenWorld = false)] - [Description("Find installed and available packages using WinGet")] + [Description("Find installed and available packages using WinGet. When upgradeable is true, returns only installed packages that have available upgrades (query is optional). When upgradeable is false, a query is required to search for packages.")] public CallToolResult FindPackages( - [Description("Find packages identified by this value")] string query) + [Description("Find packages identified by this value. Required when upgradeable is false; optionally filters results when upgradeable is true.")] string? query = null, + [Description("When true, only return installed packages that have available upgrades")] bool upgradeable = false) { try { ToolResponse.CheckGroupPolicy(); - var catalog = ConnectCatalog(); + if (!upgradeable && string.IsNullOrEmpty(query)) + { + return new CallToolResult() + { + IsError = true, + Content = [new TextContentBlock() { Text = "A query is required when upgradeable is false" }], + }; + } - // First attempt a more exact match - var findResult = FindForQuery(catalog, query, fullStringMatch: true); + // Use LocalCatalogs when listing upgrades to enumerate only installed packages, + // consistent with `winget upgrade`. Remote catalogs are still included in the + // composite so IsUpdateAvailable remains accurate. + var catalog = ConnectCatalog(searchBehavior: upgradeable + ? CompositeSearchBehavior.LocalCatalogs + : CompositeSearchBehavior.AllCatalogs); - // If nothing is found, expand to a looser search - if ((findResult.Matches?.Count ?? 0) == 0) + FindPackagesResult findResult; + if (string.IsNullOrEmpty(query)) + { + // This can only happen in the case that upgradeable is true, in which case this + // won't accidentally list all packages from all catalogs + findResult = FindAllPackages(catalog); + } + else { - findResult = FindForQuery(catalog, query, fullStringMatch: false); + // First attempt a more exact match + findResult = FindForQuery(catalog, query, fullStringMatch: true); + + // If nothing is found, expand to a looser search + if ((findResult.Matches?.Count ?? 0) == 0) + { + findResult = FindForQuery(catalog, query, fullStringMatch: false); + } } if (findResult.Status != FindPackagesResultStatus.Ok) @@ -59,7 +84,21 @@ public CallToolResult FindPackages( } List contents = new List(); - contents.AddPackages(findResult); + if (upgradeable) + { + for (int i = 0; i < findResult.Matches?.Count; ++i) + { + var package = findResult.Matches[i].CatalogPackage; + if (package.IsUpdateAvailable) + { + contents.Add(PackageListExtensions.FindPackageResultFromCatalogPackage(package)); + } + } + } + else + { + contents.AddPackages(findResult); + } return ToolResponse.FromObject(contents); } @@ -161,64 +200,6 @@ public async Task InstallPackage( } } - [McpServerTool( - Name = "get-upgradable-winget-packages", - Title = "Get Upgradable WinGet Packages", - ReadOnly = true, - OpenWorld = false)] - [Description("Get installed packages that have available upgrades using WinGet")] - public CallToolResult GetUpgradablePackages( - [Description("Optionally filter by a package identifier, name, or moniker")] string? query = null) - { - try - { - ToolResponse.CheckGroupPolicy(); - - // Use LocalCatalogs behavior to only enumerate installed packages, consistent - // with `winget upgrade`. Remote catalogs are still included in the composite - // so IsUpdateAvailable remains accurate. - var catalog = ConnectCatalog(searchBehavior: CompositeSearchBehavior.LocalCatalogs); - - FindPackagesResult findResult; - if (string.IsNullOrEmpty(query)) - { - findResult = FindAllPackages(catalog); - } - else - { - // First attempt a more exact match - findResult = FindForQuery(catalog, query, fullStringMatch: true); - - // If nothing is found, expand to a looser search - if ((findResult.Matches?.Count ?? 0) == 0) - { - findResult = FindForQuery(catalog, query, fullStringMatch: false); - } - } - - if (findResult.Status != FindPackagesResultStatus.Ok) - { - return PackageResponse.ForFindError(findResult); - } - - List contents = new List(); - for (int i = 0; i < findResult.Matches?.Count; ++i) - { - var package = findResult.Matches[i].CatalogPackage; - if (package.IsUpdateAvailable) - { - contents.Add(PackageListExtensions.FindPackageResultFromCatalogPackage(package)); - } - } - - return ToolResponse.FromObject(contents); - } - catch (ToolResponseException e) - { - return e.Response; - } - } - [McpServerTool( Name = "upgrade-winget-package", Title = "Upgrade WinGet Package", From e71efb150fe10813a4269ec9498136226c1f86c7 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 30 Mar 2026 13:53:14 -0500 Subject: [PATCH 5/8] Move Upgrade into Install --- src/WinGetMCPServer/WingetPackageTools.cs | 98 +++-------------------- 1 file changed, 12 insertions(+), 86 deletions(-) diff --git a/src/WinGetMCPServer/WingetPackageTools.cs b/src/WinGetMCPServer/WingetPackageTools.cs index 3b6219758b..b8ec710142 100644 --- a/src/WinGetMCPServer/WingetPackageTools.cs +++ b/src/WinGetMCPServer/WingetPackageTools.cs @@ -115,12 +115,13 @@ public CallToolResult FindPackages( Destructive = true, Idempotent = false, OpenWorld = false)] - [Description("Install or update a package using WinGet")] + [Description("Install or upgrade a package using WinGet. When upgradeOnly is true, only upgrades an already-installed package and returns an error if it is not installed. When upgradeOnly is false (default), installs the package if not present or upgrades it if already installed.")] public async Task InstallPackage( [Description("The identifier of the WinGet package")] string identifier, IProgress progress, CancellationToken cancellationToken, - [Description("The source containing the package")] string? source = null) + [Description("The source containing the package")] string? source = null, + [Description("When true, only upgrade an already-installed package; returns an error if the package is not installed")] bool upgradeOnly = false) { try { @@ -162,6 +163,12 @@ public async Task InstallPackage( } CatalogPackage catalogPackage = findResult.Matches![0].CatalogPackage; + + if (upgradeOnly && catalogPackage.InstalledVersion == null) + { + return PackageResponse.ForNotInstalled(identifier, source); + } + InstallOptions options = new InstallOptions(); IAsyncOperationWithProgress? operation = null; @@ -192,90 +199,9 @@ public async Task InstallPackage( findResult = ReFindForPackage(catalogPackage.DefaultInstallVersion); } - return PackageResponse.ForInstallOperation(installResult, findResult); - } - catch (ToolResponseException e) - { - return e.Response; - } - } - - [McpServerTool( - Name = "upgrade-winget-package", - Title = "Upgrade WinGet Package", - ReadOnly = false, - Destructive = true, - Idempotent = false, - OpenWorld = false)] - [Description("Upgrade an installed package to the latest available version using WinGet")] - public async Task UpgradePackage( - [Description("The identifier of the WinGet package to upgrade")] string identifier, - IProgress progress, - CancellationToken cancellationToken, - [Description("The source containing the package")] string? source = null) - { - try - { - ToolResponse.CheckGroupPolicy(); - - var packageCatalog = ConnectCatalog(source); - - if (cancellationToken.IsCancellationRequested) - { - return PackageResponse.ForCancelBeforeSystemChange(); - } - - // First attempt a more exact match - var findResult = FindForIdentifier(packageCatalog, identifier, expandedFields: false); - - if (cancellationToken.IsCancellationRequested) - { - return PackageResponse.ForCancelBeforeSystemChange(); - } - - // If nothing is found, expand to a looser search - if ((findResult.Matches?.Count ?? 0) == 0) - { - findResult = FindForIdentifier(packageCatalog, identifier, expandedFields: true); - } - - if (findResult.Status != FindPackagesResultStatus.Ok) - { - return PackageResponse.ForFindError(findResult); - } - - if (findResult.Matches?.Count == 0) - { - return PackageResponse.ForEmptyFind(identifier, source); - } - else if (findResult.Matches?.Count > 1) - { - return PackageResponse.ForMultiFind(identifier, source, findResult); - } - - CatalogPackage catalogPackage = findResult.Matches![0].CatalogPackage; - - if (catalogPackage.InstalledVersion == null) - { - return PackageResponse.ForNotInstalled(identifier, source); - } - - InstallOptions options = new InstallOptions(); - var operation = packageManager.UpgradePackageAsync(catalogPackage, options); - - operation.Progress = (asyncInfo, progressInfo) => progress.Report(CreateInstallProgressNotification(ref progressInfo)); - using CancellationTokenRegistration registration = cancellationToken.Register(() => operation.Cancel()); - - var installResult = await operation; - findResult = null; - - if (installResult.Status == InstallResultStatus.Ok) - { - progress.Report(CreateInstallProgressNotification(PackageInstallProgressState.Finished, 1.0, 1.0)); - findResult = ReFindForPackage(catalogPackage.DefaultInstallVersion); - } - - return PackageResponse.ForUpgradeOperation(installResult, findResult); + return upgradeOnly + ? PackageResponse.ForUpgradeOperation(installResult, findResult) + : PackageResponse.ForInstallOperation(installResult, findResult); } catch (ToolResponseException e) { From b75748a4f597208b256f324bf6b3eee60a9724f1 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 30 Mar 2026 14:57:58 -0500 Subject: [PATCH 6/8] Make description more consumable by agents --- src/WinGetMCPServer/WingetPackageTools.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WinGetMCPServer/WingetPackageTools.cs b/src/WinGetMCPServer/WingetPackageTools.cs index b8ec710142..24fbbe1243 100644 --- a/src/WinGetMCPServer/WingetPackageTools.cs +++ b/src/WinGetMCPServer/WingetPackageTools.cs @@ -34,7 +34,7 @@ public WingetPackageTools() Title = "Find WinGet Packages", ReadOnly = true, OpenWorld = false)] - [Description("Find installed and available packages using WinGet. When upgradeable is true, returns only installed packages that have available upgrades (query is optional). When upgradeable is false, a query is required to search for packages.")] + [Description("Find installed and available packages using WinGet. To list all installed packages that have available upgrades (equivalent to 'winget upgrade'), call with upgradeable=true and no query. To filter upgradeable packages by name, call with upgradeable=true and a query. To search for packages to install, call with upgradeable=false and a required query.")] public CallToolResult FindPackages( [Description("Find packages identified by this value. Required when upgradeable is false; optionally filters results when upgradeable is true.")] string? query = null, [Description("When true, only return installed packages that have available upgrades")] bool upgradeable = false) From 59f05a0a1aa6c8dc86db5f03ada7de4a72023575 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 30 Mar 2026 15:05:54 -0500 Subject: [PATCH 7/8] Update release notes --- doc/ReleaseNotes.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index 2edc503c27..2d905ac666 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -34,10 +34,10 @@ Added a new `--no-progress` command-line flag that disables all progress reporti ### MCP `upgrade` support -The WinGet MCP server now exposes two new tools for upgrading packages: +The WinGet MCP server's existing tools have been extended with new parameters to support upgrade scenarios: -- **`get-upgradable-winget-packages`** — Lists all installed packages that have available upgrades. Accepts an optional `query` parameter to filter by a specific package identifier, name, or moniker. AI agents can use this to answer requests like "What apps can I update with WinGet?" -- **`upgrade-winget-package`** — Upgrades a specific installed package to its latest available version. Returns a clear error if the package is not already installed (pointing to `install-winget-package` instead). Marked destructive, so MCP clients will prompt for confirmation before proceeding. AI agents can use this to answer requests like "Update WinGetCreate" or, in combination with `get-upgradable-winget-packages`, "Update all my apps." +- **`find-winget-packages`** now accepts an `upgradeable` parameter (default: `false`). When set to `true`, it lists only installed packages that have available upgrades — equivalent to `winget upgrade`. The `query` parameter becomes optional in this mode, allowing it to filter results or be omitted to list all upgradeable packages. AI agents can use this to answer requests like "What apps can I update with WinGet?" +- **`install-winget-package`** now accepts an `upgradeOnly` parameter (default: `false`). When set to `true`, it only upgrades an already-installed package and returns a clear error if the package is not installed (pointing to `install-winget-package` without `upgradeOnly` instead). AI agents can use this to answer requests like "Update WinGetCreate" or, in combination with `find-winget-packages` with `upgradeable=true`, "Update all my apps." ### Authenticated GitHub API requests in PowerShell module From 679ba7a36d16ff3fdbc80c6abd41b0924324106d Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 30 Mar 2026 15:44:05 -0500 Subject: [PATCH 8/8] Spelling * Allow upgradeable and upgradable as alternate spellings --- .github/actions/spelling/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index dde4d3c712..b2e80df60a 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -607,6 +607,7 @@ Unregisters unvirtualized UParse upgradable +upgradeable upgradecode URLZONE USEDEFAULT