Add manual cleanup block for unpublish - #477
Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
There was a problem hiding this comment.
Pull request overview
This PR updates the develop mcp unpublish flow to consume the platform’s new ManualCleanupRequired response block and (best-effort) delete Entra app registrations that the platform cannot remove on its own.
Changes:
- Change
UnpublishServerAsyncto return a parsedUnpublishMcpServerResponse?(instead ofbool) so the command layer can act on returned cleanup artifacts. - Add response/cleanup models (
UnpublishMcpServerResponse,McpServerManualCleanup,McpServerAppEntry) mirroring the platform contract. - Add Graph support to resolve an app object id from an app (client) id and use it to delete returned apps after a successful unpublish.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Commands/DevelopMcpCommandRegressionTests.cs | Updates mocks for new unpublish return type; adds regression tests for null response and no-Graph cleanup behavior. |
| src/Microsoft.Agents.A365.DevTools.Cli/Services/IAgent365ToolingService.cs | Updates unpublish service contract to return UnpublishMcpServerResponse?. |
| src/Microsoft.Agents.A365.DevTools.Cli/Services/GraphApiService.cs | Adds appId→objectId lookup helper for later deletion. |
| src/Microsoft.Agents.A365.DevTools.Cli/Services/Agent365ToolingService.cs | Parses unpublish response body (when present) and returns cleanup info to the command layer. |
| src/Microsoft.Agents.A365.DevTools.Cli/Models/UnpublishMcpServerResponse.cs | New unpublish response model (Status/Message/ManualCleanupRequired + IsSuccess). |
| src/Microsoft.Agents.A365.DevTools.Cli/Models/McpServerManualCleanup.cs | New model for the ManualCleanupRequired block. |
| src/Microsoft.Agents.A365.DevTools.Cli/Models/McpServerAppEntry.cs | New model for Entra app registrations returned for cleanup. |
| src/Microsoft.Agents.A365.DevTools.Cli/Commands/DevelopMcpCommand.cs | Wires unpublish to consume cleanup block and invoke best-effort Entra app deletion. |
Comments suppressed due to low confidence (1)
src/Microsoft.Agents.A365.DevTools.Cli/Commands/DevelopMcpCommand.cs:528
- The unpublish handler logs an error and returns on failure, but it never sets a non-zero exit code. As a result,
a365 develop mcp unpublishcan fail (null/!IsSuccess response) while still exiting 0, which breaks scripting/CI usage and conflicts with the repo’s command exit-code rule for failure branches.
var response = await toolingService.UnpublishServerAsync(envId, serverName);
if (response is null || !response.IsSuccess)
{
logger.LogError("Failed to unpublish MCP server {ServerName} from environment {EnvId}", serverName, envId);
return;
}
Rick Brighenti (rbrighenti)
left a comment
There was a problem hiding this comment.
Copilot has a few relevant comments that I think should be addressed. Please take a look at them
The platform now returns the Public Clients Entra app registration(s) it cannot delete in the customer tenant (AppIdsToCleanup) from the unpublish response. Change UnpublishServerAsync to return the parsed UnpublishMcpServerResponse instead of bool, and have the develop-mcp unpublish subcommand delete each returned app via Graph (resolving the object id from the app id), mirroring the publish rollback pattern. Degrades gracefully with manual-cleanup guidance when Graph is unavailable or the tenant can't be detected. - New models UnpublishMcpServerResponse + McpServerAppEntry. - New GraphApiService.GetAppObjectIdByAppIdAsync (appId -> application object id). - Regression tests updated for the new return type; added null-response and no-graph-service cleanup coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Follow the platform's self-describing unpublish contract: read the Entra apps the platform cannot delete from response.ManualCleanupRequired.Apps instead of the removed AppIdsToCleanup list. Adds the mirroring McpServerManualCleanup model and updates the cleanup regression test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
61aa166 to
d49f91a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/Microsoft.Agents.A365.DevTools.Cli/Commands/DevelopMcpCommand.cs:546
- Manual cleanup guidance from the service response is partially dropped:
ManualCleanupRequired.Reasonis never passed intoCleanupEntraAppsAsync, so when Graph/tenant detection is unavailable the CLI warning cannot include the platform-provided reason/instructions. Preserve that reason in the user-facing warning to make the fallback cleanup instructions self-describing.
// The platform removes the tenant publication but cannot delete Entra app registrations in the
// customer tenant, so it returns any it created for this server (in ManualCleanupRequired) for
// the CLI to clean up here.
await CleanupEntraAppsAsync(logger, graphApiService, response.ManualCleanupRequired?.Apps, serverName);
});
Summary
When you unpublish a custom MCP server, the platform removes the tenant publication (MOS title + MCC record) but cannot delete the Entra app registration it created for the server (the *-PublicClients app), because the platform's identity has no rights to delete app registrations in the customer tenant. Previously that app was left dangling after unpublish, with nothing telling the user it existed.
The platform now returns those apps on the unpublish response under a self-describing ManualCleanupRequired block. This PR teaches the CLI to consume that block and delete the apps on the user's behalf — mirroring the rollback the publish flow already does when a publish fails.
What changed
UnpublishServerAsync now returns the parsed response (Task<UnpublishMcpServerResponse?> instead of Task), so the command layer can act on the returned apps. Returns null on failure.
New models mirroring the platform contract:
Testing
CLI + test projects build clean (warnings-as-errors).
Full suite: 1923 passed / 0 failed / 12 skipped.
Added coverage: null-response path (unpublish failed → no throw) and the no-Graph-service cleanup path (degrades gracefully).