Repository navigation
fix(plugins,edge): managed mode takes confirm, and the audit log says whether the user confirmed a call - #1139
Merged
alexmercerpo merged 4 commits intoNov 5, 2026
Conversation
… whether the user confirmed a call
createEdgeMcp({ managed }) built the Skilled OpenAPI plugin without the
confirm option, so a managed Worker couldn't stop a destructive action.
managed.confirm now goes to the plugin, whose schema checks it. On a
Cloudflare Worker, where elicitation can't be enabled today, the actions it
names are refused (or run unasked with whenUnsupported: 'allow').
The audit records of a call didn't say whether the user confirmed it. Its
http-call-* records now carry an optional confirmation: not_required,
confirmed, skipped, declined or unanswered. A workflow's action calls carry
the workflow's answer. load_skill marks the actions that ask with
asksForConfirmation: true.
Contributor
|
Warning Review limit reachedYour organization has reached its usage spending cap. Adjust your spending cap in the billing tab. Next included review available in 28 minutes. View limit details
|
frontegg-david
marked this pull request as ready for review
November 5, 2026 11:48
Contributor
Performance Test ResultsStatus: ✅ All tests passed Summary
Total: 101 tests across 21 projects 📊 View full report in workflow run Generated at: 2026-11-05T17:46:02.232Z |
…ing' into fix/skilled-confirm-edge-audit
…ing' into fix/skilled-confirm-edge-audit
…ing' into fix/skilled-confirm-edge-audit
alexmercerpo
approved these changes
Nov 5, 2026
Contributor
frontegg-david
added a commit
that referenced
this pull request
Nov 5, 2026
* Cherry-pick: fix(adapters,plugins,edge): an older saas bundle, pulled or pushed, doesn't roll the server back Cherry-picked from #1119 (merged to release/1.11.x) Original commit: d88f422 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(sdk,plugins): an @app's Skilled OpenAPI credential resolver reaches execute_action and this.callTool() of an operation Cherry-picked from #1120 (merged to release/1.11.x) Original commit: 00a3b00 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(adapters,plugins,edge): a push token replayed to another replica or Worker isolate is refused Cherry-picked from #1122 (merged to release/1.11.x) Original commit: 1341026 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(plugins): a skill a skills:filter hook hides can't be run by naming its actions Cherry-picked from #1123 (merged to release/1.11.x) Original commit: a081fbf Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(sdk): a plugin listed twice in one plugins list is refused at startup instead of keeping only its last entry Cherry-picked from #1127 (merged to release/1.11.x) Original commit: 3dbadd3 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(plugins,edge): a Cron run that doesn't apply its bundle fails, and /readyz says the bundle served is stale Cherry-picked from #1128 (merged to release/1.11.x) Original commit: 4938d21 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(sdk): connect() with an authToken connects an anonymous caller again on a server without auth Cherry-picked from #1129 (merged to release/1.11.x) Original commit: 3d238da Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(auth,adapters,plugins,edge): operators can tell why a pull or push token was refused, while callers still get the generic answer Cherry-picked from #1130 (merged to release/1.11.x) Original commit: 913f4f5 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(sdk,auth,plugins): this.authProviders works: the SDK wires the AuthProviders vault from a new authProvidersVault option Cherry-picked from #1131 (merged to release/1.11.x) Original commit: e14c30c Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(sdk,auth): /oauth/connect connects a verified caller who has no vault yet, and asks an anonymous one to sign in Cherry-picked from #1132 (merged to release/1.11.x) Original commit: f46a87a Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(sdk,plugins): the SDK skill surfaces say a plugin's skills haven't loaded yet, retryable, and carry bundleVersion Cherry-picked from #1133 (merged to release/1.11.x) Original commit: 0d1dc94 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(sdk,auth,cli,plugins): the credentials-required error keeps its connect link out of the message, and asks through URL elicitation where the client supports it Cherry-picked from #1134 (merged to release/1.11.x) Original commit: 6d8ea24 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(sdk): a health check registered after startup runs in /readyz, and registerHealthProbe returns a remover Cherry-picked from #1135 (merged to release/1.11.x) Original commit: 7f2f48f Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(sdk,plugins): a bundle swap sends one list_changed per list it changed, not one per entry Cherry-picked from #1136 (merged to release/1.11.x) Original commit: 0296291 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(adapters,plugins,edge): a restarted server serves its cached bundle in place of an older or refused pull, and doesn't write it back Cherry-picked from #1137 (merged to release/1.11.x) Original commit: 358d114 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(sdk,adapters,plugins,edge): a push token with a body_sha256 claim can't carry another bundle Cherry-picked from #1138 (merged to release/1.11.x) Original commit: b870325 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(plugins,edge): managed mode takes confirm, and the audit log says whether the user confirmed a call Cherry-picked from #1139 (merged to release/1.11.x) Original commit: 588289f Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(cli): the generated CLI says unknown command, prints an MCP error prefix once, and offers load_skill only the run lines it has Cherry-picked from #1140 (merged to release/1.11.x) Original commit: 4cfe9a2 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(plugins): load_skill's outputSchema describes a loaded skill once Cherry-picked from #1141 (merged to release/1.11.x) Original commit: 470010e Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> * Cherry-pick: fix(sdk,cli): skills/load's default answer sends formattedContent again, as 1.11.0 did; format: 'markdown' is the opt-in for the smaller one Cherry-picked from #1142 (merged to release/1.11.x) Original commit: a680fe7 Co-Authored-By: frontegg-david <69419539+frontegg-david@users.noreply.github.com> --------- Co-authored-by: agentfront[bot] <agentfront[bot]@users.noreply.github.com> Co-authored-by: frontegg-david <69419539+frontegg-david@users.noreply.github.com>
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Stacks on #1138.
What and why
The Skilled OpenAPI plugin's
confirmoption names actions that ask the user before their request. Review found three gaps around it:confirm.createEdgeMcp({ managed })builds the plugin's options frommanaged, andconfirmwasn't one of them. A managed Worker had no way to stop a destructive action, so every action ran without asking.load_skilldidn't flag the actions that ask. The model learned an action asks only when it called it.managed.confirm.ManagedEdgeOptionshas aconfirmfield with the plugin's input type, passed to the plugin as it is. The plugin's schema checks it, so an invalid value refuses to start like any other plugin option:500 CONFIG_INVALID, and the log names the field (confirm.methods.0). The plugin's rules for a client that can't ask hold on the edge:elicitation: { enabled: true }, an actionconfirmnames is refused:the user's confirmation is needed for voidInvoice, and this server can't ask for it: elicitation is disabled; nothing was sent. WithwhenUnsupported: 'allow'it runs unasked.this client can't ask for it: it doesn't support elicitation.input_requiredresult with the question, and its retry withconfirmed: truesends the request.On a Cloudflare Worker,
elicitation: { enabled: true }doesn't start today: the SDK refuses an in-memory elicitation store on an edge runtime (Elicitation requires distributed storage when running on Edge runtime), andelicitation.redistakes Redis only. So a managed Worker refuses the actionsconfirmnames, even for a client that declares elicitation, or runs them unasked with'allow'. The READMEs and the catalog say so. Letting a Worker ask needs an SDK change (an elicitation store a Worker can use, or none for 2026-07-28 input requests), left for a follow-up.confirmationon the audit record.SkillAuditRecordhas a new optionalconfirmationfield, typedSkillAuditConfirmation, named after what the code tells apart:not_requiredconfirmrule names the actionconfirmedskippedwhenUnsupported: 'allow'sent the request unaskeddeclinedunanswered'refuse'), the question timed out, or a script reached the action by an id it didn't namehttp-call-successorhttp-call-failureonce its input passed the schema check. Theauthority-check-*records and an input-schema failure come before the confirmation and leave it out.run_workflow's action calls carry the workflow's answer:confirmed(orskipped) for the actions its question named,not_requiredfor the others. A declined workflow runs no action, so it still writes no records.--yesanswers the question on the client, so the server recordsconfirmed. It can't tell--yesapart from a user's yes.verifyChainverifies new ones.{ confirmation }or{ confirmation, refusal }) in place of a refusal string, and the executor puts it on the records written after it.load_skillmarks the actions that ask. Each action aconfirmrule names hasasksForConfirmation: true. The tool's description stays as it was: a line about the field would take it past the 1,024-character budget.Scenario: E7 (asking before a destructive action), on the managed edge, and in the audit log.
User-facing change
createEdgeMcp({ managed: { confirm } })takes the plugin'sconfirmoption and passes it through.confirmation:not_required,confirmed,skipped,declinedorunanswered.@frontmcp/adapters/skillsexports its type,SkillAuditConfirmation, andSkillAuditWriteContexttakes an optionalconfirmation.load_skilladdsasksForConfirmation: trueto each action that asks the user first.confirm: { methods: ['DELETE'] }, refused withthe user's confirmation is needed for voidInvoice, and this server can't ask for it: elicitation is disabled; nothing was sent.voidInvoicecall'shttp-call-successrecord:{ phase, status, ... }→{ phase, status, ..., confirmation: 'confirmed' }. A declined one'shttp-call-failurehasconfirmation: 'declined'.load_skill's action:{ actionId: 'voidInvoice', summary, ... }→{ actionId: 'voidInvoice', summary, ..., asksForConfirmation: true }.load_skill)frontmcp-extensibility/references/skill-audit-log.mdfrontmcp-deployment/references/deploy-to-cloudflare-skills-only.mdfrontmcp-config/references/configure-elicitation.mdThe plugin's and the edge's READMEs are updated too.
Tests
Added:
@frontmcp/adapters,audit-writer.spec.ts: the writer records theconfirmationa call passes, leaves it out of a record without one, and an editedconfirmationbreaks the chain.action-confirmation.spec.ts: each decision (confirmed,declinedfor a decline, a cancel orconfirmed: false,unansweredfor a timeout or a client that can't ask,skippedwith'allow'); a workflow's answer carried to its asked actions,skippedincluded;unansweredfor an action a workflow didn't ask about.execute-skill-action.spec.ts: which records carry the field on each path (not_required,confirmed,skipped, a failed call after a yes,unanswered, and none before the input check).confirm-actions.integration.spec.ts(a real server and client): the field on each path forexecute_actionandrun_workflow, andload_skill's marker with and withoutconfirm.managed.spec.ts:confirmmapped as it is, with the plugin's defaultwhenUnsupported, and left out when unset.index.spec.ts(a real scope): a managed edge refuses for a client that can't ask, runs unasked with'allow', refuses with elicitation disabled, asks a 2026-07-28 client through an input request and sends once confirmed, marks the action inload_skill, and refuses to start with an invalidconfirm, naming the field.demo-e2e-cloudflare, the managed Worker in workerd, now withconfirm: { methods: ['DELETE'] }and avoidInvoiceaction:execute_actionandrun_workflowrefuse it, sending nothing;load_skillmarks it.demo-e2e-skilled-openapi,confirm-destructive.e2e.spec.ts:load_skillmarks the DELETE action and no other.Run:
npx nx run-many -t build -p sdk,adapters,plugin-skilled-openapi,edge,testing: passed.@frontmcp/adapters: 63 suites, 1136 tests, passed.@frontmcp/plugin-skilled-openapi: 53 suites, 844 tests, passed.@frontmcp/edge: 10 suites, 118 tests, passed.@frontmcp/skills: 3 suites, 249 tests, passed.npx nx test demo-e2e-cloudflare: 3 suites, 37 tests, passed.npx nx test demo-e2e-skilled-openapi: 11 suites, 99 tests, passed.npx nx run <project>:typecheckfor adapters, plugin-skilled-openapi, edge and demo-e2e-skilled-openapi, andnpx tsc --noEmit -p <lib>/tsconfig.lib.jsonfor adapters, plugin-skilled-openapi and edge: no errors.The SDK is unchanged, so the bundle-size e2e doesn't apply.