* 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>
Stacks on #1133.
What and why
The credentials-required error (
-32001) no longer puts the connect link in its message. A client that can take a URL-mode elicitation is asked to open the link instead. An anonymous caller is told to sign in, and gets no link.Why:
… Connect via: https://…/oauth/connect?token=…. Clients echo error messages into model transcripts, logs and telemetry.sub: ''. Since fix(sdk,auth): /oauth/connect connects a verified caller who has no vault yet, and asks an anonymous one to sign in #1132 it only opens a "sign in" page, so it could never work.What changes:
data.authUrl/data.auth_url, as before.@frontmcp/plugin-skilled-openapi(its caller-credential bindings, throughrun_workflow,execute_actionand the operation tools) both build it in one place now:ToolCredentialsRequiredError's default message. The plugin's own message already left the link out. It now uses the shared one, so both paths read the same.this.elicit(…, { mode: 'url', url })), so the gate uses it. It asks only when all of these hold:elicitation: { enabled: true };capabilities.elicitation.url;The gate asks for each provider's connect page. When the user accepts, it checks the credentials again: the call runs if they're connected now, else it answers the same
-32001. A decline, a cancel or a failed ask answers-32001too.elicitation/createrequest during the call.input_requiredresult, and the client's retry carries the answer. The question's key isconnect-<provider>, notelicitation-N. The gate asks only on some runs of a call (until the credential is connected), so a numbered key would hand the connect page's answer to the tool's own first question on the retry.MrtrExchange.resolveElicitation()andperformElicit()'s deps take an optional input key for this.this.credentials.requireConnect()mints no link for an anonymous caller and returnssignInRequired: true.-32001data then carriessignInRequired: trueand noauthUrl. The message says to sign in first, and no elicitation is sent.resumeUrlstays astring:CredentialNotConnected.resumeUrl: stringis public in@frontmcp/auth, and tools return it as a typed output field (connectUrl: res.resumeUrl). Making it optional would break those builds. So it stays a string, empty for an anonymous caller, and the new optionalsignInRequired?: truesays why. It's optional so code that builds aCredentialNotConnected, such as a test double, still compiles.403 login_requiredfor asub: ''link stays, for a link signed some other way.infoline names only the providers. The flow logs only the error message, which has no link now. The accessor's debug line never had it._exitWithErrornow addsConnect github at: <url>, orSign in to the server first, then connect github., from the error's data.User-facing change
ToolCredentialsRequiredError(-32001): the message no longer holds the connect URL;data.authUrl/data.auth_urlstill do. Newdata.signInRequired: truefor a caller that isn't signed in, which gets no URL. New constructor optionsignInRequiredand read-only propertysignInRequired.elicitation: { enabled: true }and a client that declaredelicitation.url, the credential gate asks the client to open the connect page (URL-mode elicitation), then checks again.this.credentials.requireConnect()for an anonymous caller:{ connected: false, resumeUrl: '', signInRequired: true }, with no signed link.@frontmcp/plugin-skilled-openapi: a caller missing a caller-credential binding's credential gets the shared message, andsignInRequiredwhen anonymous.-32001with a connect link prints it on a second line.Tool "deploy" requires credentials for github which are not connected for this session. Connect via: https://host/oauth/connect?token=…Tool "deploy" requires credentials for github which are not connected for this session. Connect github to continue: open the link in this error's data (authUrl).Tool "deploy" requires credentials for github, and this caller isn't signed in. Sign in to this server, then connect github.Data:{ tool, providers, signInRequired: true }, noauthUrl.Tool "run_workflow" sends the caller's own "billing" credential, which this caller hasn't connected. Connect "billing" and retry.Tool "run_workflow" requires credentials for billing which are not connected for this session. Connect billing to continue: open the link in this error's data (authUrl).elicitation.urlon a server with elicitation enabled: an immediate-32001→ askedConnect github to continue: tool "deploy" needs it.with the URL. The call runs if the credential is connected when the user accepts.requireConnect()for an anonymous caller: a link signed forsub: ''→resumeUrl: '',signInRequired: true, messageSign in to this server to connect credential "acme".authProviderscredential-gate page (the-32001shape, the message, URL-mode elicitation); the local-mode auth page's session credential vault section (requireConnect()for an anonymous caller); the Skilled OpenAPI page's caller credentials, if it has one.create-tool/references/auth-providers.md: "When auth is missing" (signInRequired, the message without the URL, URL-mode elicitation).frontmcp-config/references/configure-auth.md:requireConnect()for an anonymous caller, and the plugin's-32001.frontmcp-config/examples/configure-auth/local-credential-vault.md: the emptyresumeUrlfor an anonymous caller.create-tool/examples/13-tool-with-single-auth-provider.mdandfrontmcp-development/references/openapi-adapter.md: the URL is in the data, not the message.plugins/plugin-skilled-openapi/README.md: caller credentials.Follow-ups, left out of this PR:
acceptas soon as the user opens the page, before they finish. So the gate's second check usually still finds no credential, and the call answers-32001; the user retries. Two ways to close this, neither in the SDK yet:notifications/elicitation/complete, and the gate waits for it;URLElicitationRequiredError(-32042, exported by@frontmcp/protocol), so the client retries after the user finishes.@frontmcp/plugin-skilled-openapipath raises the error from inside a workflow or operation tool, after the gate. It doesn't ask by URL-mode elicitation.AuthorizationRequiredError(progressive authorization) still puts itsauth_urlin its message text and content. It's a different error and link, not changed here.Tests
Added:
libs/sdktool-credentials-required.error.spec.ts(5): the message without the URL, the data with it, sign-in required, no URL, a given message.libs/sdkcall-tool.credential-gate.spec.ts(+9, 19 in all): the message has no URL; an anonymous caller'ssignInRequired; a client with URL mode is asked, and the call runs once connected; still missing after accept; decline and cancel; a failed ask; no ask when sign-in is required; no ask for a form-only client.libs/sdkcall-tool.credential-gate.url-elicitation.spec.ts(5, protocol 2026-07-28 over the real flow): theconnect-githubinput request with the URL; the retry runs the tool once connected; a decline answers-32001, with the URL in the data only; no ask for a form-only client; the tool's own question keepselicitation-1.libs/sdkmrtr.spec.ts(+2): a named elicitation's key; the numbered keys don't shift.libs/authcredentials-accessor.spec.ts(+1, one rewritten): an anonymous caller getssignInRequiredand no link; a signed-in one doesn't.plugins/plugin-skilled-openapicaller-credentials.spec.ts(+1, one updated): the shared message; sign-in required.libs/cliextract-public-message-parity.spec.ts(+3): the connect hint, sign-in hint, and none for other errors.demo-e2e-local-authlocal-auth-credentials-required.e2e.spec.ts(4), over real sessions:-32001with the link in the data;main.credentials.tsnow enables elicitation and takesALLOW_ANONYMOUS=true.local-auth-credential-gateasserts the message has no link;demo-e2e-skilled-openapicaller-credentialsmatches the new message.Run:
nx test sdk: 698 suites, 9721 tests pass.nx test auth: 103 suites, 2897 tests pass.nx test plugin-skilled-openapi: 52 suites, 795 tests pass.nx test skills: 3 suites, 249 tests pass.nx test cli: 132 of 133 suites, 2219 tests pass.resolve-dev-launch.spec.tsdidn't run: another process on the machine holds port 4310, which that spec needs free. It isn't touched here.demo-e2e-local-auth: 13 suites, 82 tests pass.demo-e2e-skilled-openapi: 11 suites, 94 tests pass.demo-e2e-elicitation: 3 suites, 45 tests pass.demo-e2e-bundle-size: 12 suites, 63 tests pass, with the budgets set to the measured sizes:create() + one tool: 1,293,668 → 1,295,269 (+1,601);create() + WebMcpPlugin + one tool: 1,300,900 → 1,302,499 (+1,599);The growth is the gate's URL-mode elicitation ask and the new error messages.
nx run <project>:typecheckpasses for sdk, auth, cli, plugin-skilled-openapi, demo-e2e-local-auth and demo-e2e-skilled-openapi.tsc --noEmit -p tsconfig.lib.jsonpasses for sdk, auth, cli and plugin-skilled-openapi.Summary by CodeRabbit