skills/sk-actions-custom-provider/SKILL.md
Create or review Scalekit custom providers/connectors for proxy-only usage, including MCP providers. Use this skill when the task is to gather API docs, infer whether a connector is OAuth, Basic, Bearer, or API Key, determine if it is an MCP provider, determine required tracked fields like domain or version, generate provider JSON, check for existing custom providers, show update diffs, run approved create or update curls, and print resolved delete curls.
npx skillsauth add scalekit-inc/skills sk-actions-custom-providerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this skill for Scalekit custom providers, also called connectors.
This skill is only for proxy-only connectors.
Dev or Production before doing anything else.Dev, the skill may run the token curl to generate env_access_token.Dev, the skill may run the read-only list providers curl to check existing custom providers.Dev, the skill may run the create curl only after explicit user approval.Dev, the skill may run the update curl only after the required diff review and explicit user confirmation.Production, the skill may run the token curl to generate env_access_token.Production, the skill may run read-only list providers curls.Production, the skill must never run create, update, or delete curls.Production, the skill may give the user resolved curls to run themselves after review.✅ for success or ❌ for failure.Help the user:
Dev or Productiondomain, version, or named path parameters are neededauth_header_key_override or auth_field_mutationsDevDevDev provider JSON as the source of truth when the user wants a Production providerFollow this sequence.
Share:
- Is this Scalekit environment Dev or Production?
This skill is only for proxy-only connectors.
Do not restate or paraphrase this startup request again in the same reply. 2. Read the user's answer and branch:
Dev, ask for SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET, custom provider name, whether this is an MCP provider, API docs link, auth docs link if separate, and base API URL or full MCP URL (this becomes proxy_url)Production, ask for PROD_SCALEKIT_ENVIRONMENT_URL, PROD_SCALEKIT_CLIENT_ID, PROD_SCALEKIT_CLIENT_SECRET, DEV_SCALEKIT_ENVIRONMENT_URL, DEV_SCALEKIT_CLIENT_ID, DEV_SCALEKIT_CLIENT_SECRET, and the provider name they want to replicate in ProductionProduction, do not generate provider JSON from scratch for production; first fetch the matching provider JSON from Dev and use that as the source of truthSCALEKIT_ENVIRONMENT_URL as env_url in Dev.Dev, generate env_access_token with:curl --location '{{SCALEKIT_ENVIRONMENT_URL}}/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id={{SCALEKIT_CLIENT_ID}}' \
--data-urlencode 'client_secret={{SCALEKIT_CLIENT_SECRET}}'
Dev, after the user provides the custom provider name, list existing custom providers with:curl --location '{{SCALEKIT_ENVIRONMENT_URL}}/api/v1/providers?filter.provider_type=CUSTOM&page_size=1000' \
--header 'Authorization: Bearer {{env_access_token}}'
Dev, compare the provided name against the returned custom providers.Production:
PROD_SCALEKIT_ENVIRONMENT_URL, PROD_SCALEKIT_CLIENT_ID, and PROD_SCALEKIT_CLIENT_SECRET if the user did not already provide them for ProductionDEV_SCALEKIT_ENVIRONMENT_URL, DEV_SCALEKIT_CLIENT_ID, and DEV_SCALEKIT_CLIENT_SECRET if the user did not already provide themenv_access_tokenenv_access_tokenidentifier, build a tabular diff with columns Dev, Current Production, and Proposed, and then print the update curl onlyproviders[]identifier field, not its id fieldidentifier, the actual SCALEKIT_ENVIRONMENT_URL, and the actual env_access_tokenOAUTHBASICBEARERAPI_KEYOAUTH: standard OAuth 2.0 flow with authorize/token endpoints and user authorizationBASIC: proxy sends Authorization: Basic base64(username:password)BEARER: proxy sends Authorization: Bearer <token>API_KEY: proxy sends Authorization: <api_key> as-isOAUTH and the provider is NOT an MCP provider, try to discover:authorize_uritoken_uriuser_info_uriOAUTH and the provider IS an MCP provider, skip this step — MCP OAuth only needs oauth_config: {"pkce_enabled": true}.tokenapi_keyusernamepassworddomainversionis_path_param: trueAuthorization, set auth_header_key_overrideapi_key, token, username, or password, set auth_field_mutationsproxy_url.proxy_url empty or set proxy_enabled to false, tell them that tool calling will not work in that configuration because custom providers support tool calling only through the tool proxy feature.proxy_url empty and do not set proxy_enabled to false.display_name, description, auth_patterns, proxy_url, and proxy_enabledproviders[] -> matching object -> identifier, not idPrefer short, concrete questions.
The initial request for required inputs is defined in Interaction Flow step 1. Do not repeat that opening block again.
Ask later only if needed:
Dev or Production?proxy_url empty or set proxy_enabled to false, tool calling will not work because custom providers support tool calling only through the tool proxy feature.Production. Please share PROD_SCALEKIT_ENVIRONMENT_URL, PROD_SCALEKIT_CLIENT_ID, PROD_SCALEKIT_CLIENT_SECRET, DEV_SCALEKIT_ENVIRONMENT_URL, DEV_SCALEKIT_CLIENT_ID, DEV_SCALEKIT_CLIENT_SECRET, and the provider name you want to replicate in Production.X because of Y. Confirm or correct me.authorize_uri or token_uri. Please provide the missing OAuth endpoints.proxy_url.auth_header_key_override to X.auth_field_mutations for X.Do not ask broad, open-ended questions when the docs already imply the answer.
The server derives the identifier from display_name.
When generating JSON:
display_name under 200 charactersdisplay_name before generating JSONDo not ask the user to choose the identifier.
Common top-level fields:
display_namedescriptionauth_patternsproxy_urlproxy_enabledCommon auth_patterns[] fields:
typedisplay_namedescriptionfieldsaccount_fields for account-scoped values when neededoauth_config for OAuth only; for MCP OAuth providers, always set to {"pkce_enabled": true} with no other fieldsis_mcp set to true for MCP providers; omit for non-MCP providersauth_header_key_override when the upstream auth header key is not Authorizationauth_field_mutations when the upstream requires a prefix, suffix, or default on api_key, token, username, or passwordSupported field input types:
textpasswordselectDefault assumptions:
proxy_enabled should be trueproxy_url must not be emptyAuthorization unless docs require an overrideauth_field_mutations unless docs require themIf a user asks to leave proxy_url empty or set proxy_enabled to false, tell them that tool calling will not work because custom providers support tool calling only through the tool proxy feature.
Use when the upstream service uses OAuth 2.0.
Required shape:
auth_patterns[].type = "OAUTH"oauth_config presentUsually includes:
authorize_uritoken_uriuser_info_uriavailable_scopesOptional OAuth config fields supported by the backend:
allow_use_scalekit_credentialscustom_scope_namepkce_enabledFor MCP OAuth providers, oauth_config must be {"pkce_enabled": true} and nothing else. Do not include authorize_uri, token_uri, user_info_uri, or available_scopes for MCP OAuth providers — they use DCR (Dynamic Client Registration) and do not need those fields.
OAuth fields are usually auth-time options, not long-lived secrets.
For OAuth providers:
account_fields, not fieldsOAuth auth patterns may still use:
auth_header_key_override if the upstream expects the token in a different header nameauth_field_mutations.token if the docs require prefix, suffix, or default handling before the proxy adds Bearer Example:
{
"display_name": "My Asana",
"description": "Connect to Asana. Manage tasks, projects, teams, and workflow automation",
"auth_patterns": [
{
"description": "Authenticate with Asana using OAuth 2.0",
"display_name": "OAuth 2.0",
"account_fields": [],
"fields": [],
"oauth_config": {
"authorize_uri": "https://app.asana.com/-/oauth_authorize",
"available_scopes": [
{
"description": "Read user profile and basic data",
"display_name": "Default Access",
"required": false,
"scope": "default"
}
],
"token_uri": "https://app.asana.com/-/oauth_token",
"user_info_uri": "https://app.asana.com/api/1.0/users/me"
},
"type": "OAUTH"
}
],
"proxy_url": "https://app.asana.com/api",
"proxy_enabled": true
}
Use when the upstream API expects HTTP Basic auth.
Required shape:
auth_patterns[].type = "BASIC"fields collects the values needed for Basic authTypical fields:
usernamepassworddomain if the API host varies per customerRuntime behavior:
Authorization: Basic base64(username:password)auth_field_mutations.username and auth_field_mutations.password are applied before the Basic value is base64-encodedExample:
{
"display_name": "My Freshdesk",
"description": "Connect to Freshdesk. Manage tickets, contacts, companies, and customer support workflows",
"auth_patterns": [
{
"description": "Authenticate with Freshdesk using Basic Auth",
"display_name": "Basic Auth",
"fields": [
{
"field_name": "domain",
"hint": "yourcompany.freshdesk.com",
"input_type": "text",
"label": "Freshdesk Domain",
"required": true
},
{
"field_name": "username",
"hint": "Your Freshdesk API Key",
"input_type": "password",
"label": "API Key",
"required": true
}
],
"auth_field_mutations": {
"password": {
"default": "X"
},
"username": {
"suffix": "/token"
}
},
"type": "BASIC"
}
],
"proxy_url": "https://{{domain}}/api",
"proxy_enabled": true
}
Use when the upstream API expects:
Authorization: Bearer <token>
Required shape:
auth_patterns[].type = "BEARER"fields usually includes tokenRuntime behavior:
auth_field_mutations.token first if present<header key>: Bearer <mutated token>Example:
{
"display_name": "My Tavily",
"description": "Use Tavily to connect your agent to the web and search for information across the internet",
"auth_patterns": [
{
"description": "Authenticate with Tavily using your API Key",
"display_name": "Bearer Auth",
"fields": [
{
"field_name": "token",
"hint": "Your Tavily API Key",
"input_type": "password",
"label": "API Key",
"required": true
}
],
"type": "BEARER"
}
],
"proxy_url": "https://api.tavily.com",
"proxy_enabled": true
}
Use when the upstream API expects the raw API key in Authorization with no prefix.
Required shape:
auth_patterns[].type = "API_KEY"fields usually includes api_keyRuntime behavior:
Authorization: <api_key>auth_field_mutations.api_key first if presentauth_header_key_override is set, proxy sends that header key instead of AuthorizationExample:
{
"display_name": "My Klaviyo",
"description": "Use Klaviyo to connect your agent to the AI marketing platform",
"auth_patterns": [
{
"description": "Authenticate with Klaviyo private API Key",
"display_name": "API Key",
"fields": [
{
"field_name": "api_key",
"hint": "Your Klaviyo API Key",
"input_type": "password",
"label": "API Key",
"required": true
}
],
"auth_header_key_override": "x-api-key",
"auth_field_mutations": {
"api_key": {
"prefix": "Klaviyo-API-Key "
}
},
"type": "API_KEY"
}
],
"proxy_url": "https://a.klaviyo.com",
"proxy_enabled": true
}
MCP (Model Context Protocol) providers are a special type of proxy-only connector where the upstream service exposes an MCP-compatible endpoint.
is_mcp: true in all auth_patterns[]OAUTH auth type:
oauth_config to {"pkce_enabled": true} onlyauthorize_uri, token_uri, user_info_uri, or available_scopes — MCP OAuth uses DCR and does not need themBASIC, BEARER, API_KEY):
oauth_config{
"display_name": "Github MCP",
"description": "Connect to Github MCP",
"auth_patterns": [
{
"description": "Authenticate with Github MCP using browser OAuth.",
"display_name": "OAuth 2.1/DCR",
"fields": [],
"is_mcp": true,
"oauth_config": {
"pkce_enabled": true
},
"type": "OAUTH"
}
],
"proxy_url": "https://api.githubcopilot.com/mcp/",
"proxy_enabled": true
}
{
"display_name": "Apify MCP",
"description": "Connect to Apify MCP to run web scraping, browser automation, and data extraction Actors directly from your AI workflows.",
"auth_patterns": [
{
"description": "Authenticate with Apify using your API Token.",
"display_name": "Apify Token",
"fields": [
{
"field_name": "token",
"hint": "Your Apify API Token",
"input_type": "password",
"label": "Apify Token",
"required": true
}
],
"is_mcp": true,
"type": "BEARER"
}
],
"proxy_url": "https://mcp.apify.com",
"proxy_enabled": true
}
Work from this known set first:
tokenapi_keyusernamepassworddomainversionis_path_param: trueUse only the fields the provider actually needs.
Examples:
domainproxy_url like https://{{domain}}/api/v2versionproxy_url like https://api.example.com/{{version}}is_path_param to true on that fieldOAUTH, put that field in account_fieldsBASIC, BEARER, API_KEY), put that field in fieldsproxy_urlExample field for a path placeholder:
{
"field_name": "path_param_1",
"hint": "Path Param 1",
"input_type": "text",
"is_path_param": true,
"label": "Path Param 1",
"required": true
}
If a path parameter appears in proxy_url, tell the user where to send its runtime value when creating or updating a connected account:
BASIC, BEARER, API_KEY), put it in connected_account.authorization_details.static_auth.details.path_variablesOAUTH, put it in connected_account.api_config.path_variablesIf the exact key names are unclear, ask the user to confirm them.
Only add these fields when the upstream docs require them.
auth_header_key_overrideUse this when the upstream expects the auth credential in a header other than Authorization.
Example:
"auth_header_key_override": "x-api-key"
auth_field_mutationsUse this when the upstream expects the raw credential to be transformed before proxy formatting:
prefix: prepend text to the stored valuesuffix: append text to the stored valuedefault: use this value when the stored value is emptySupported mutation targets:
api_keytokenusernamepasswordMutation order:
default if the stored value is emptyprefixsuffixBEARER, the proxy adds Bearer after mutationBASIC, the proxy base64-encodes username:password after mutationExamples:
Zendesk-style Basic auth:
"auth_field_mutations": {
"username": {
"suffix": "/token"
}
}
Freshdesk-style Basic auth:
"auth_field_mutations": {
"password": {
"default": "X"
}
}
Klaviyo-style API key auth:
"auth_field_mutations": {
"api_key": {
"prefix": "Klaviyo-API-Key "
}
}
Harvest-style API key auth:
{
"auth_header_key_override": "x-api-key"
}
The backend supports:
{{domain}}{{version}}is_path_param: trueUse placeholders only when the API contract requires them.
Pick one of these patterns:
"proxy_url": "https://api.example.com"
"proxy_url": "https://{{domain}}/api"
"proxy_url": "https://api.example.com/{{version}}"
"proxy_url": "https://api.example.com/resources/{{path_param_1}}"
When responding, state why the chosen shape is correct.
If you use named path placeholders in proxy_url, also tell the user how to pass path_variables during connected account create or update.
For static auth, the structure is:
{
"identifier": "some-identifier",
"connector": "mycustomprovider",
"connected_account": {
"authorization_details": {
"static_auth": {
"details": {
"...": "...",
"path_variables": {
"path_param_1": "value_1"
}
}
}
}
}
}
For OAuth, the structure is:
{
"identifier": "some-identifier",
"connector": "myoauthcustomconnector",
"connected_account": {
"authorization_details": {
"oauth_token": {}
},
"api_config": {
"...": "...",
"path_variables": {
"path_param_1": "value_1"
}
}
}
}
Before generating the final JSON, summarize:
If critical auth details are missing, stop and ask only for those missing values.
If the provided docs are too vague, say so directly and ask for the API auth reference.
When ready, respond in this order:
Dev or ProductionDev, the generated provider JSON; for Production, the provider JSON fetched from DevDev, a field diff table covering only display_name, description, auth_patterns, proxy_url, and proxy_enabledProduction, a tabular diff covering only display_name, description, auth_patterns, proxy_url, and proxy_enabled with columns Dev, Current Production, and ProposedDev, say whether you will run create or update after approvalProduction, print the create or update curl and tell the user to run it themselves; mention that you used Production credentials only for token generation, provider lookup, and curl construction, and that the printed curl includes the Production access token after Bearer HTTP method rules:
POSTPUTGETDELETEBefore listing, creating, updating, or deleting providers, generate env_access_token with:
curl --location '{{SCALEKIT_ENVIRONMENT_URL}}/oauth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id={{SCALEKIT_CLIENT_ID}}' \
--data-urlencode 'client_secret={{SCALEKIT_CLIENT_SECRET}}'
Use this list providers curl when checking for existing custom providers:
curl --location --request GET '{{SCALEKIT_ENVIRONMENT_URL}}/api/v1/providers?filter.provider_type=CUSTOM&page_size=1000' \
--header 'Authorization: Bearer {{env_access_token}}'
In Dev, only after explicit user approval, run:
curl --location --request POST '{{SCALEKIT_ENVIRONMENT_URL}}/api/v1/custom-providers' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{env_access_token}}' \
--data '{
...generated-json...
}'
After create, tell the user: Refresh the page on Scalekit Dashboard to see the new provider.
In Production, never run the create curl. Print the fully resolved curl and tell the user to run it from their terminal. The printed curl must use the Production access token in Authorization: Bearer <production-env-access-token>.
In Dev, before running the update curl:
providers[]identifier field, not its id fielddisplay_name, description, auth_patterns, proxy_url, and proxy_enabledAfter confirmation, run:
curl --location --request PUT '{{SCALEKIT_ENVIRONMENT_URL}}/api/v1/custom-providers/{{identifier}}' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer {{env_access_token}}' \
--data '{
...generated-json...
}'
After update, tell the user: Refresh the page on Scalekit Dashboard to see the new provider.
In Production:
Dev provider existsDev and use that provider JSON as the payloadproviders[] -> matching object -> identifierDev, Current Production, and Proposed for display_name, description, auth_patterns, proxy_url, and proxy_enabledAuthorization: Bearer <production-env-access-token>Do not fabricate identifiers.
If the user asks to delete the custom provider:
providers[]identifier field, not its id fieldSCALEKIT_ENVIRONMENT_URL and env_access_token with the actual values already available in the conversationAuthorization: Bearer <production-env-access-token>curl --location --request DELETE 'https://actual-environment-url/api/v1/custom-providers/{{provider_identifier_from_list_custom_provider_api}}' \
--header 'Authorization: Bearer actual-env-access-token'
Do not fabricate identifiers.
Before finalizing:
display_name is safe and under 200 charsis_mcp: true is present in all auth patternsoauth_config is {"pkce_enabled": true} with no other fieldsoauth_config is not presentoauth_config exists only for OAuth providersproxy_url matches the upstream host patternproxy_url is not emptyproxy_enabled is trueproxy_url or proxy_enabled: false, they were told tool calling would not work because custom providers support tool calling only through the tool proxy featuredisplay_name, description, auth_patterns, proxy_url, and proxy_enabledProduction updates, the diff table includes Dev, Current Production, and Proposed columnsProduction, the skill asks for Dev credentials if they were not providedProduction, the skill asks for Production credentials if they were not providedProduction, the skill fetches the matching Dev provider before preparing the Production curlProduction, the skill does not regenerate the provider JSON from scratch when the Dev provider existsProduction, the skill may run token and list-provider curls but never create, update, or delete curlsBearer POST, printed update curls use PUT, printed list curls use GET, and printed delete curls use DELETEProduction, create and update curls are printed but never executedproviders[] -> matching object -> identifierproviders[] -> matching object -> identifier✅ or ❌tools
Use when a developer is new to Scalekit and needs guidance on where to start, doesn't know which auth plugin or skill to choose, wants to connect an AI agent or agentic workflow to third-party services (Gmail, Slack, Notion, Google Calendar), needs OAuth or tool-calling auth for agents, wants to add authentication to a project but hasn't chosen an approach yet, or needs to install the Scalekit plugin for their AI coding tool (Claude Code, Codex, Copilot CLI, Cursor, or other agents).
tools
Use when a user asks to generate, review, validate, or fix any code snippet that uses Scalekit APIs or SDKs. This skill is the single source of truth for Scalekit code correctness — it can generate illustration-quality snippets from scratch (for docs, websites, or integration guides) and review existing code to catch wrong method names, missing parameters, security anti-patterns, and broken auth flows. Covers all four SDKs (Node, Python, Go, Java), raw REST API calls, and both Scalekit product suites — SaaSKit (SSO, login, sessions, RBAC, SCIM) and AgentKit (connections, tool calling, MCP auth). Use when the user says review my Scalekit code, generate a Scalekit example, validate this auth flow, check my SDK usage, fix my Scalekit integration, write a code sample for docs, or anything involving Scalekit code quality.
development
Walks through a structured production readiness checklist for Scalekit SSO implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, hardening their SSO setup, or wants to verify their Scalekit implementation is production-ready.
development
Walks through a structured production readiness checklist for Scalekit SCIM provisioning implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, or wants to verify their SCIM directory sync implementation is production-ready.