You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Provision A365 proxy app on develop-mcp publish - #499
Wire develop-mcp publish to provision the A365 proxy Entra app (confidential app + secret) and forward its credentials to the platform, so custom (non-Dataverse) MCP servers actually get a Power Platform connector created at publish time. Previously publish created only the PublicClients app, so the platform's connector-creation step logged A365ProxyConnectorCreation=SkippedNoCredentials and skipped. The register flow already provisions this proxy app; this mirrors it in publish. - CreateEntraAppsAsync now creates {server}-A365Proxy (confidential, with secret) first, failing the publish if it can't be created; CreateProxyAppAsync self-cleans its own orphan on partial failure. - PublishMcpServerRequest carries a365ProxyClientId / a365ProxyClientSecret; PublishMcpServerResponse reads A365ProxyRedirectUri (+ A365ProxyConnectorId). - After publish, the proxy app's redirect URIs are set from A365ProxyRedirectUri (tc/non-tc list), mirroring register; warns if absent. - The McpServer required-resource-access grant is added onto both the A365 proxy app and the PublicClients app. The platform wires the connector with the proxy app as its OAuth client and McpServerAppId as the resource, so the proxy app must hold this grant or Entra rejects the connector token request with AADSTS650057. - a365ProxyClientSecret is added to RedactSecretFields so the new secret is masked as ***REDACTED*** in verbose request-payload logging (alongside the other client secrets). - RollbackEntraAppsAsync now deletes both apps. Paired MCP-Platform change (connector create at publish, tenant-publish at approve) is already merged. Tests: new PublishCommandExecutorEntraAppTests (incl. grant-on-both-apps assertion), RedactSecretsFromPayload_RedactsA365ProxyClientSecret; updated regression + dry-run tests. Full suite green (2019 passed).
Before installing the version with this change(correct error message)
After installing the latest version of a365 cli with this change
Publish now creates the confidential A365 proxy Entra app (app + secret) alongside the PublicClients app and forwards its credentials to the platform, so custom (non-Dataverse) MCP servers get a Power Platform connector created at publish time instead of the platform logging A365ProxyConnectorCreation=SkippedNoCredentials. Mirrors the register flow: proxy app created first (fatal on failure, with self-cleanup), request carries the proxy clientId/secret, proxy redirect URIs are updated post-publish, and rollback deletes both apps.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The platform wires the A365 proxy connector with the proxy app as its OAuth client and McpServerAppId as the resource, so the proxy app must hold the McpServerScope required-resource-access grant or Entra rejects the connector's token request with AADSTS650057. ConfigureEntraAppsAsync previously granted this only on the PublicClients app; now it grants on both the proxy app and the PublicClients app, mirroring register.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The publish request now carries the newly created A365 proxy Entra app secret as a365ProxyClientSecret. RedactSecretFields only masked clientApp1Secret/clientApp2Secret/clientSecret, so verbose request-payload logging wrote the live client secret in plaintext. Add a365ProxyClientSecret to the redaction key set with a regression test.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The real publish path now also grants the MCP server permission to both apps and, when returned, writes the proxy redirect URIs, but this dry-run output still says it only back-fills the PPMI scope. That makes --dry-run under-report the changes users are previewing; update this message and its assertion to describe the new proxy configuration as well.
Roll back proxy app when public client creation fails
The proxy app is now created before CreatePublicClientsAppAsync, but an exception from that call can escape CreateEntraAppsAsync: its Graph app-creation call is outside the provisioner's catch, and ExecuteAsync has no rollback around app creation. A transient Graph/network failure here therefore leaves the newly created A365 proxy registration and secret orphaned. Catch this failure (while preserving cancellation), delete the proxy app, and return a failed publish; add a regression test for this partial-creation path.
The reason will be displayed to describe this comment to others. Learn more.
Requesting changes. The main concern is credential hygiene: publish now mints a confidential app with a secret on every run, and it can be left behind on partial failure. Details inline.
…cleanup
Publish still forwards the A365 proxy app credentials on every call (the
CLI can't classify custom vs first-party before the platform does), but
now reconciles after publish: when the response shows no connector was
created, the unused proxy app is deleted so no orphaned credential lingers.
- Post-publish cleanup of the unused proxy app for first-party/Dataverse
servers, gated on the platform returning a connector id / redirect URI.
- Redirect-URI warning now fires only when a connector was actually
created but no URI came back, not on every first-party publish.
- Proxy required-resource-access grant applied only when a connector
exists; Public Clients grant unchanged.
- New --service-tree-id and --secret-lifetime-months options on publish,
threaded to both created Entra apps, mirroring register.
- Orphaned proxy app is deleted if Public Clients creation throws after
the proxy app was created.
- Dry-run output now describes proxy creation, permission/redirect config,
and cleanup.
- CHANGELOG entry references (microsoft#499).
Tests: proxy grant on both apps only when a connector exists, unused-proxy
deletion, orphan-cleanup-on-throw, option flow-through, and updated publish
option/dry-run assertions with documented requirement changes.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
The core connector-credential wiring is useful, but I recommend changes before
merging. First-party publishing now unnecessarily depends on client-secret
creation, and two partial-failure paths can leave the new proxy application
behind. Three isolated regression reproductions confirmed these behaviors.
The release notes also omit the new provisioning behavior and publish options.
Validation: 84 existing targeted tests passed. Three temporary tests asserting
the missing requirements failed as expected; these were run only in an isolated
PR worktree, not added to the branch. The project targets net8.0, but tests ran
on the installed .NET 10 runtime using DOTNET_ROLL_FORWARD=Major. NuGetAudit was
disabled only for the local restore because its endpoint was unreachable.
No live Graph/Power Platform integration test was performed.
The reason will be displayed to describe this comment to others. Learn more.
[P1] Do not require an unused client secret for first-party publishing
This call is mandatory for every server, and a failure to create the proxy
app or password aborts before the platform is called. A tenant that permits
public-client registrations but prohibits client secrets can therefore no
longer publish msdyn_DataverseMCPServer, although first-party/Dataverse
publishing does not create a proxy connector. The post-publish deletion
cannot address this case because execution never reaches it, and a shorter
--secret-lifetime-months value does not fix a password-creation prohibition.
Before this PR, publishing created only the public-client app.
Resolve whether proxy credentials are needed before making their creation
mandatory, or adjust the API contract so a first-party publish can proceed
without them while custom publishing still requires them. Add a regression
test with first-party publish and AddAppPasswordAsync returning null.
Reproduced: the platform mock was ready to succeed, but ExecuteAsync returned
false before publishing when secret creation was rejected.
The reason will be displayed to describe this comment to others. Learn more.
[P2] Use a cancellation-independent token for compensating deletion
Cancelling during PublishServerAsync is caught by the concrete tooling
service and converted to null. ExecuteAsync therefore enters this rollback
with an already-cancelled token. Both deletes inherit that token, so Graph's
authenticated request/SendAsync cannot complete; the helper logs the errors
and leaves the app registrations, including the new proxy app and its live
secret, behind. This PR extends the existing rollback problem to a newly
introduced credential-bearing app.
Use a separate, preferably bounded cleanup token, as the Public Clients
exception cleanup already does, and retain correct cancellation semantics
after cleanup. Add an end-to-end executor test where publishing cancels the
token and returns null.
Reproduced: neither application's deletion completed. This also confirms the
still-open review thread: #499 (comment)
The reason will be displayed to describe this comment to others. Learn more.
[P2] Cover exceptions during proxy-secret creation with orphan cleanup
The new proxy-provisioning call is outside the try/catch below. Its existing
helper deletes the app when AddAppPasswordAsync returns null, but not when
that call throws. GraphPostWithResponseAsync rethrows transport failures and
cancellation, so an addPassword connection reset after successful app
creation escapes the entire publish operation without any compensating
delete. If the password was created before the response was lost, the orphan
also retains a live credential. Previously this publish flow never performed
this proxy/password stage.
Make the provisioner clean up the known application object on exceptions
after creation using a cancellation-independent token, then preserve the
original error/cancellation. The later Public Clients catch cannot clean up
this earlier failure because it has not yet received the proxy result.
Reproduced with AddAppPasswordAsync throwing HttpRequestException: the
exception escaped and DeleteEntraAppAsync was never called.
var serviceTreeIdOption = new Option<string?>("--service-tree-id", description: "ServiceTree ID for Entra app registration (required in Microsoft corporate tenants)");
command.AddOption(serviceTreeIdOption);
var secretLifetimeMonthsOption = new Option<int?>(["--secret-lifetime-months", "-l"], description: "Lifetime in months (1-24) for the generated client secret on the A365 proxy Entra app. Default is 2 years. Set a value smaller than the appManagementPolicies cap in your tenant.");
The reason will be displayed to describe this comment to others. Learn more.
Document the new publish behavior and options in the release notes
The current PR head has no CHANGELOG.md diff. Its Unreleased section still
describes publish as creating only the PublicClients registration (line
171), and the secret-lifetime entry documents only register-external-mcp-server.
It does not document the new publish --service-tree-id and
--secret-lifetime-months options, proxy credential creation, or its cleanup
behavior. This is a user-visible provisioning and tenant-policy change.
Add crisp consumer-facing Unreleased entries with (#499), update the stale
PublicClients-only entry, and document the new flags in the command reference.
The earlier changelog review thread is marked resolved, but the change is
absent from the latest PR head.
The reason will be displayed to describe this comment to others. Learn more.
Reconciling the proxy app after the platform responds is a practical way to handle not being able to classify servers up front. The executor changes are cleanly structured, and the main paths have good test coverage.
Two minor comments inline.
Outside the changed lines:
Minor: Update the release notes for the proxy app and publish options (CHANGELOG.md:171). The maintained repository guidance requires every user-facing feature to have a crisp [Unreleased] entry and requires stale sibling entries to be fixed. The current changelog has no #499 entry and still says publish creates only <server-name>-PublicClients, omitting the confidential proxy app, connector behavior, and the two new options.
The reason will be displayed to describe this comment to others. Learn more.
Minor: A failed delete of the unused proxy app still reports a clean, successful publish
On a successful first-party or Dataverse publish, the unused proxy app is removed through TryDeleteEntraAppAsync, which returns nothing and only logs. If the delete fails, the only trace is an error log saying "Failed to roll back Entra app ... Delete it manually". warnings isn't updated, so DisplayResults prints the green "published" line and the command exits 0. In scripted or CI runs, an unused confidential app with a live client secret stays in the tenant with nothing in the result to show it. When the delete succeeds, the log says "Rolled back Entra app" after a publish that succeeded, which is confusing.
Evidence
PublishCommandExecutor.cs:408 declares private async Task TryDeleteEntraAppAsync(...), which returns a plain Task. Its outcomes are only logged: "Rolled back Entra app ..." at 420, "Failed to roll back Entra app ... Delete it manually" at 424-426, and "Exception rolling back ..." at 429-434. Lines 456-460 call it without touching concurrentWarnings or warnings. Lines 189-190 run DisplayResults(input, warnings); return true;, and lines 614-621 print the green "MCP server '...' published as '...'" line when warnings.Count == 0. Scenario: Graph returns 403 or a transient error on DELETE /applications/{id} after a Dataverse publish. The command exits 0 with the success banner, and <server>-A365Proxy keeps its secret.
Suggested fix: Have TryDeleteEntraAppAsync return a bool, or accept the warnings collection. On this path, add a warning such as "Unused A365 proxy app '' (clientId ...) could not be deleted; delete it manually." Log "Deleted" instead of "Rolled back" here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wire
develop-mcp publishto provision the A365 proxy Entra app (confidential app + secret) and forward its credentials to the platform, so custom (non-Dataverse) MCP servers actually get a Power Platform connector created at publish time. Previously publish created only the PublicClients app, so the platform's connector-creation step loggedA365ProxyConnectorCreation=SkippedNoCredentialsand skipped. The register flow already provisions this proxy app; this mirrors it in publish. -CreateEntraAppsAsyncnow creates{server}-A365Proxy(confidential, with secret) first, failing the publish if it can't be created;CreateProxyAppAsyncself-cleans its own orphan on partial failure. -PublishMcpServerRequestcarriesa365ProxyClientId/a365ProxyClientSecret;PublishMcpServerResponsereadsA365ProxyRedirectUri(+A365ProxyConnectorId). - After publish, the proxy app's redirect URIs are set fromA365ProxyRedirectUri(tc/non-tc list), mirroring register; warns if absent. - The McpServer required-resource-access grant is added onto both the A365 proxy app and the PublicClients app. The platform wires the connector with the proxy app as its OAuth client andMcpServerAppIdas the resource, so the proxy app must hold this grant or Entra rejects the connector token request with AADSTS650057. -a365ProxyClientSecretis added toRedactSecretFieldsso the new secret is masked as***REDACTED***in verbose request-payload logging (alongside the other client secrets). -RollbackEntraAppsAsyncnow deletes both apps. Paired MCP-Platform change (connector create at publish, tenant-publish at approve) is already merged. Tests: newPublishCommandExecutorEntraAppTests(incl. grant-on-both-apps assertion),RedactSecretsFromPayload_RedactsA365ProxyClientSecret; updated regression + dry-run tests. Full suite green (2019 passed).Before installing the version with this change(correct error message)
After installing the latest version of a365 cli with this change
