Skip to content

feat(plugins): a Skilled OpenAPI binding can send each caller's own connected credential - #1096

Merged
frontegg-david merged 3 commits into
feat/skilled-error-bodiesfrom
feat/skilled-caller-credentials
Nov 4, 2026
Merged

frontegg-david merged 3 commits into
feat/skilled-error-bodiesfrom
feat/skilled-caller-credentials

Conversation

@frontegg-david

Copy link
Copy Markdown
Contributor

What and why

Stacks on #1095.

When one server serves many callers, each caller should send the API their own credential. Up to now every caller of a Skilled OpenAPI server sent the same one: the credential resolver was told only a binding's vaultRef and the bundle id, so it had no way to tell callers apart. This PR adds two ways to send each caller's own credential, without changing what a bundle holds.

A binding can send the credential the caller connected. The new authProviders plugin option maps a binding's vaultRef to the auth provider each caller connects it as:

SkilledOpenApiPlugin.init({ source, authProviders: { 'billing-token': 'billing' } });

On each request, the plugin reads that caller's credential from the vaults @Tool({ authProviders }) already reads:

  • the auth providers vault (this.authProviders): oauth and oauth_pkce send their access token, bearer its token, api_key its key;
  • else the session credential vault (this.credentials), where local and remote modes keep what authenticate() returns at sign-in or through the connect link.

The mapping is a server option, not part of the bundle, so a signed bundle stays portable. It names only the vaultRef, and each server decides where that credential comes from, the same way credentials does today. Putting provider names in the bundle would tie a signed artifact to one server's auth wiring.

A caller who hasn't connected the credential is asked to connect it. The plugin uses the SDK's standard path for this, the error the @Tool({ authProviders }) credential gate raises:

  • the error is ToolCredentialsRequiredError (JSON-RPC -32001), with data: { tool, providers, authUrl }. authUrl is the session vault's signed /oauth/connect link (requireConnect) when the server has that vault;
  • nothing is sent to the API. A mapped vaultRef never falls back to credentials, to a resolver, or to another caller's credential;
  • run_workflow raises the error when the run fails because of it. The sandbox keeps only an error's message, so the tool keeps the error it saw. A script can't fix a missing credential, so the run ends even when the script passes { throwOnError: false }. An in-process this.callTool() of the operation tool throws the same error;
  • the message names the provider but leaves the link out. The link lets whoever holds it connect a credential for that caller, and the message ends up in workflow errors and audit records. The link travels only in data.authUrl, the same way the SDK's AuthorizationRequiredError carries its URL. The SDK's own gate message does include the link.

The SDK has no URL-mode elicitation, so -32001 with authUrl is the auth layer's connect path today.

A custom resolver is told the caller. SkilledOpenApiCredentialResolver.resolve(ref, opts) now gets opts.authInfo: the caller's token, user claims and sessionId. A vault adapter can use it to pick a per-tenant credential, for example by a tenant claim. Existing resolvers that read only bundleId keep working unchanged. The new CredentialResolveOptions and CallerAuthInfo types are exported.

No credential is shared between callers. The plugin keeps nothing between requests: a credential is read on each request, and the vault accessors are built for that request. The README says that a resolver that caches must key its cache by the caller, and that a global-scope auth provider is shared by design.

The static credentials option is unchanged for single-tenant servers, and so is passthroughCallerToken.

User-facing change

  • What changed:
    • new plugin option authProviders (Record<vaultRef, providerName>);
    • SkilledOpenApiCredentialResolver.resolve's opts gains authInfo;
    • a mapped binding whose credential the caller hasn't connected fails with ToolCredentialsRequiredError (-32001, data: { tool, providers, authUrl });
    • new exported types CredentialResolveOptions and CallerAuthInfo.
  • Before → after:
    • before: every caller sent the binding's one credential, and a resolver couldn't tell callers apart;
    • after: a binding named in authProviders sends each caller's own connected credential, and a caller without one is asked to connect it. Every resolver is told the caller.
  • frontmcp.dev pages affected (if known): Skilled OpenAPI reference (credentials, options, security); the auth providers and credential vault pages could mention that the plugin reads the same providers.
  • Skills catalog entries updated:
    • create-tool/references/auth-providers.md;
    • frontmcp-config/references/configure-auth.md (per-session credential vault);
    • frontmcp-development/references/openapi-adapter.md (Authentication).

Tests

  • New executor/__tests__/caller-credentials.spec.ts (13 tests):
    • each auth providers credential type the plugin can send, and a type it refuses;
    • the fallback to the session vault;
    • the -32001 error, with and without a connect URL, and without the URL in its message;
    • a mapped vaultRef that never reaches the resolver or credentials;
    • a custom resolver that receives { bundleId, authInfo };
    • static credentials without a custom resolver.
  • New __tests__/caller-credentials.integration.spec.ts (6 tests), on a real server with the real auth providers accessor and a user-scoped vault built per request:
    • two callers running at the same time each send their own token;
    • a caller without a connected credential gets ToolCredentialsRequiredError and nothing is sent, also with throwOnError: false;
    • the in-process this.callTool() path;
    • an unmapped binding still reads credentials;
    • a server-level resolver that picks each caller's tenant token from authInfo.
  • openapi-runtime.spec.ts: two new tests (the resolver is asked with the bundle and the caller; a missing credential throws without sending). The existing tests moved from callerToken to authInfo.
  • E2E demo-e2e-skilled-openapi/e2e/caller-credentials.e2e.spec.ts (2 tests): a local-mode entry whose authenticate() connects the caller's billing token. The https mock now accepts each caller's token and records which one arrived:
    • two signed-in callers running the same action each send their own token;
    • a caller who connected none gets -32001 with a /oauth/connect authUrl, a message without it, and no request at the API. The server's own billing token stays configured through its resolver and is never sent.
  • Run:
  • No change to libs/sdk or libs/auth, so the bundle size is unchanged.

…onnected credential

A server serving many callers sent every caller the same credential for a
binding: the resolver was told only the vaultRef and the bundle id.

- The new authProviders option maps a binding's vaultRef to the auth provider
  each caller connected it as. Each request reads that caller's credential
  from the vaults @tool({ authProviders }) reads (this.authProviders, else the
  session credential vault), and never falls back to credentials, a resolver
  or another caller's credential.
- A caller who hasn't connected it gets ToolCredentialsRequiredError (-32001)
  with the session vault's connect URL as authUrl, and nothing is sent.
  run_workflow raises it when the run fails on it; the message leaves the URL
  out, since the URL lets its holder connect a credential for the caller.
- A SkilledOpenApiCredentialResolver is told the caller as opts.authInfo, so
  it can pick a per-tenant credential. Existing resolvers keep working.
…nd one who hasn't connected it is asked to

A local-mode entry of the Skilled OpenAPI e2e app maps the bundle's billing
binding to the billing credential each caller connects at sign-in. The https
mock accepts each caller's token and records which one arrived: two callers
running the same action each send their own, and a caller who connected none
gets -32001 with a connect URL while nothing reaches the API, though the
server's own billing token is still configured.
@frontegg-david
frontegg-david marked this pull request as ready for review November 4, 2026 19:54
@frontegg-david

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Next included review available in 22 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 2 included reviews currently available.

Learn how review limits work.

Review configuration:

Run configuration
Commits

Reviewing files that changed from the base of the PR and between 51d1cb4 and 391c541.

Files selected for processing: 24
  • apps/e2e/demo-e2e-skilled-openapi/e2e/caller-credentials.e2e.spec.ts
  • apps/e2e/demo-e2e-skilled-openapi/e2e/helpers/local-sign-in.ts
  • apps/e2e/demo-e2e-skilled-openapi/e2e/helpers/mock-billing-api.ts
  • apps/e2e/demo-e2e-skilled-openapi/e2e/helpers/production-environment.ts
  • apps/e2e/demo-e2e-skilled-openapi/src/caller-credentials.main.ts
  • apps/e2e/demo-e2e-skilled-openapi/src/config.ts
  • libs/skills/catalog/create-tool/references/auth-providers.md
  • libs/skills/catalog/frontmcp-config/references/configure-auth.md
  • libs/skills/catalog/frontmcp-development/references/openapi-adapter.md
  • plugins/plugin-skilled-openapi/README.md
  • plugins/plugin-skilled-openapi/src/__tests__/caller-credentials.integration.spec.ts
  • plugins/plugin-skilled-openapi/src/__tests__/openapi-runtime.spec.ts
  • plugins/plugin-skilled-openapi/src/__tests__/plugin.spec.ts
  • plugins/plugin-skilled-openapi/src/executor/__tests__/caller-credentials.spec.ts
  • plugins/plugin-skilled-openapi/src/executor/__tests__/execute-skill-action.spec.ts
  • plugins/plugin-skilled-openapi/src/executor/connected-credential.ts
  • plugins/plugin-skilled-openapi/src/executor/credential-resolver.ts
  • plugins/plugin-skilled-openapi/src/executor/execute-skill-action.ts
  • plugins/plugin-skilled-openapi/src/executor/openapi-runtime.ts
  • plugins/plugin-skilled-openapi/src/index.ts
  • plugins/plugin-skilled-openapi/src/skilled-openapi.symbols.ts
  • plugins/plugin-skilled-openapi/src/skilled-openapi.types.ts
  • plugins/plugin-skilled-openapi/src/tools/operation-tool.factory.ts
  • plugins/plugin-skilled-openapi/src/tools/run-workflow.tool.ts
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor
Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Performance Test Results

Status: ✅ All tests passed

Summary

Project Tests Passed Warnings Failed Leaks
✅ demo-e2e-agents 4 4 0 0 0
✅ demo-e2e-cache 11 11 0 0 0
✅ demo-e2e-codecall 4 4 0 0 0
✅ demo-e2e-config 4 4 0 0 0
✅ demo-e2e-direct 3 3 0 0 0
✅ demo-e2e-elicitation 1 1 0 0 0
✅ demo-e2e-errors 4 4 0 0 0
✅ demo-e2e-hooks 3 3 0 0 0
✅ demo-e2e-multiapp 4 4 0 0 0
✅ demo-e2e-notifications 3 3 0 0 0
✅ demo-e2e-openapi 2 2 0 0 0
✅ demo-e2e-providers 4 4 0 0 0
✅ demo-e2e-public 4 4 0 0 0
✅ demo-e2e-redis 15 15 0 0 0
✅ demo-e2e-remember 4 4 0 0 0
✅ demo-e2e-remote 5 5 0 0 0
✅ demo-e2e-serverless 2 2 0 0 0
✅ demo-e2e-skills 15 15 0 0 0
✅ demo-e2e-standalone 2 2 0 0 0
✅ demo-e2e-transport-recreation 3 3 0 0 0
✅ demo-e2e-ui 4 4 0 0 0

Total: 101 tests across 21 projects

📊 View full report in workflow run


Generated at: 2026-11-04T20:10:44.854Z
Commit: e624bb4d

@frontegg-david
frontegg-david merged commit b1e5836 into release/1.11.x Nov 4, 2026
48 of 60 checks passed
@frontegg-david
frontegg-david deleted the feat/skilled-caller-credentials branch November 4, 2026 23:23
frontegg-david added a commit that referenced this pull request Nov 4, 2026
…aller's own connected credential (#1096)

Cherry-picked from #1096 (merged to release/1.11.x)
Original commit: b1e5836
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants