From 19055100a8afa4c903813862830454d83f530038 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Tue, 5 May 2026 21:20:28 +0530 Subject: [PATCH 01/53] docs: reorganize agent-auth around canonical docs Shift the agent-auth plugin toward a Tessl-like docs/skills/rules layout so durable AgentKit guidance is separated from Claude-specific runtime files. This makes live metadata the documented source of truth for tools while keeping existing connector notes available as secondary references. Co-authored-by: Cursor --- plugins/agent-auth/.claude-plugin/plugin.json | 6 +- plugins/agent-auth/.env.example | 5 + plugins/agent-auth/README.md | 90 +++++ plugins/agent-auth/commands/test-tool.md | 91 +++++ plugins/agent-auth/docs/byoc.md | 44 +++ plugins/agent-auth/docs/code-samples.md | 47 +++ plugins/agent-auth/docs/connected-accounts.md | 51 +++ plugins/agent-auth/docs/connections.md | 53 +++ plugins/agent-auth/docs/connectors/README.md | 51 +++ plugins/agent-auth/docs/index.md | 38 +++ plugins/agent-auth/docs/tool-discovery.md | 68 ++++ .../references/agent-connectors/README.md | 17 +- plugins/agent-auth/references/byoc.md | 8 +- plugins/agent-auth/references/code-samples.md | 6 +- .../references/connected-accounts.md | 6 +- plugins/agent-auth/references/connections.md | 16 +- plugins/agent-auth/references/providers.md | 50 +-- .../agent-auth/references/tool-discovery.md | 88 +++++ .../agent-auth/rules/live-metadata-first.md | 19 ++ plugins/agent-auth/rules/terminology.md | 18 + plugins/agent-auth/rules/tool-selection.md | 22 ++ plugins/agent-auth/skills/agent-auth/SKILL.md | 322 ++++-------------- .../skills/building-agent-mcp-server/SKILL.md | 17 +- .../discovering-agentkit-tools/SKILL.md | 56 +++ .../production-readiness-scalekit/SKILL.md | 11 +- .../skills/testing-agentkit-tools/SKILL.md | 50 +++ .../testing-agentkit-tools/scripts/connect.py | 250 ++++++++++++++ 27 files changed, 1197 insertions(+), 303 deletions(-) create mode 100644 plugins/agent-auth/.env.example create mode 100644 plugins/agent-auth/README.md create mode 100644 plugins/agent-auth/commands/test-tool.md create mode 100644 plugins/agent-auth/docs/byoc.md create mode 100644 plugins/agent-auth/docs/code-samples.md create mode 100644 plugins/agent-auth/docs/connected-accounts.md create mode 100644 plugins/agent-auth/docs/connections.md create mode 100644 plugins/agent-auth/docs/connectors/README.md create mode 100644 plugins/agent-auth/docs/index.md create mode 100644 plugins/agent-auth/docs/tool-discovery.md create mode 100644 plugins/agent-auth/references/tool-discovery.md create mode 100644 plugins/agent-auth/rules/live-metadata-first.md create mode 100644 plugins/agent-auth/rules/terminology.md create mode 100644 plugins/agent-auth/rules/tool-selection.md create mode 100644 plugins/agent-auth/skills/discovering-agentkit-tools/SKILL.md create mode 100644 plugins/agent-auth/skills/testing-agentkit-tools/SKILL.md create mode 100644 plugins/agent-auth/skills/testing-agentkit-tools/scripts/connect.py diff --git a/plugins/agent-auth/.claude-plugin/plugin.json b/plugins/agent-auth/.claude-plugin/plugin.json index 195d3f3..9475a92 100644 --- a/plugins/agent-auth/.claude-plugin/plugin.json +++ b/plugins/agent-auth/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "agent-auth", - "description": "Implements Scalekit Agent Auth so AI agents can act in third-party apps (Gmail, Slack, Calendar, Notion) on behalf of users.", - "version": "1.8.2", + "description": "Sets up Scalekit AgentKit in Claude Code so agents can authorize users, discover tools, and execute authenticated tool calls across connectors.", + "version": "1.9.0", "author": { "name": "Scalekit", "email": "hi@scalekit.com" @@ -9,5 +9,5 @@ "homepage": "https://docs.scalekit.com/dev-kit/build-with-ai/agent-auth/", "repository": "https://github.com/scalekit-inc/claude-code-authstack", "license": "MIT", - "keywords": ["scalekit", "agent-auth", "oauth", "connected-accounts"] + "keywords": ["scalekit", "agentkit", "agent-auth", "connectors", "tools", "connected-accounts"] } diff --git a/plugins/agent-auth/.env.example b/plugins/agent-auth/.env.example new file mode 100644 index 0000000..437d1db --- /dev/null +++ b/plugins/agent-auth/.env.example @@ -0,0 +1,5 @@ +SCALEKIT_ENV_URL=https://your-env.scalekit.dev +SCALEKIT_CLIENT_ID=skc_your_client_id +SCALEKIT_CLIENT_SECRET=your_client_secret +# Optional sample connection name copied exactly from AgentKit -> Connections +GMAIL_CONNECTION_NAME=MY_GMAIL diff --git a/plugins/agent-auth/README.md b/plugins/agent-auth/README.md new file mode 100644 index 0000000..1208999 --- /dev/null +++ b/plugins/agent-auth/README.md @@ -0,0 +1,90 @@ +# AgentKit for Claude Code + +## Purpose +This plugin brings Scalekit AgentKit into Claude Code so an agent can connect users to third-party apps, discover the right tools, and execute authenticated tool calls on their behalf. It is organized as a hybrid structure: a Tessl-like canonical content layer and a Claude-specific adapter layer. + +Canonical content lives in: +- `docs/` for durable AgentKit knowledge +- `skills/` for task-oriented workflows +- `rules/` for cross-cutting guidance + +Claude runtime files remain in place as adapters: +- `.claude-plugin/` +- `.mcp.json` +- `commands/` +- `hooks/` +- `agents/` + +The plugin treats live AgentKit metadata as the source of truth for tool names, `input_schema`, and `output_schema`. Connector notes are curated guidance, not a guaranteed exhaustive catalog. + +## Installation +```sh +claude /plugin install agent-auth@scalekit-auth-stack +``` + +Start with the canonical docs entrypoint at [`docs/index.md`](docs/index.md). + +## Skills Reference +- `/agent-auth:integrating-agent-auth` + Integrates AgentKit into app code or an agent workflow and routes into the core docs. +- `/agent-auth:discovering-agentkit-tools` + Uses live AgentKit metadata to find tools, inspect schemas, and narrow the tool set. +- `/agent-auth:testing-agentkit-tools` + Generates authorization links, fetches live tool metadata, and executes tools from Claude Code. +- `/agent-auth:building-agent-mcp-server` + Exposes AgentKit tools through MCP for MCP-compatible runtimes. +- `/agent-auth:production-readiness-scalekit` + Runs a structured production-readiness checklist for AgentKit integrations. + +Command: +- `/test-tool [generate-link|get-tool|execute-tool ...]` + Runs the live AgentKit playground command. + +## Configuration +Required environment variables: +- `SCALEKIT_ENV_URL` +- `SCALEKIT_CLIENT_ID` +- `SCALEKIT_CLIENT_SECRET` + +Optional sample variable: +- `GMAIL_CONNECTION_NAME` + +Legacy aliases supported by the testing command: +- `TOOL_ENV_URL` +- `TOOL_CLIENT_ID` +- `TOOL_CLIENT_SECRET` + +Example `.mcp.json`: + +```json +{ + "mcpServers": { + "scalekit": { + "type": "http", + "url": "https://mcp.scalekit.com" + } + } +} +``` + +## Usage Examples +Typical flow for a new connector integration: +1. Read [`docs/index.md`](docs/index.md) for the canonical model and [`docs/connections.md`](docs/connections.md) for connection naming. +2. Create the connection in `AgentKit -> Connections`. +3. Use `/agent-auth:integrating-agent-auth` to scaffold connected-account creation and authorization. +4. Use `/agent-auth:discovering-agentkit-tools` or `/test-tool get-tool --provider GMAIL` to inspect the live tool catalog and schema. +5. Use `/test-tool generate-link --connection-name --identifier user_123` if the user still needs to authorize. +6. Use `/test-tool execute-tool --tool-name gmail_fetch_mails --connection-name --identifier user_123 --tool-input '{"query":"is:unread","max_results":5}'` to validate the payload before wiring it into application code. + +## Troubleshooting +1. No tools show up for a connector: + Use live discovery first. The current tool inventory comes from AgentKit metadata, not static connector notes. +2. The connection exists but auth or execution fails: + Verify that `connection_name` matches the exact dashboard value. It is not always the connector slug such as `gmail`. +3. Tool execution fails after a user already connected: + Re-check the connected account status and re-authorize if the account is not `ACTIVE`. + +## Security +This plugin needs AgentKit API credentials for your Scalekit environment. Store them in environment variables or a local secret manager, and never commit them to source control. + +Connected accounts are per-user authorization boundaries. Use the correct identifier, request minimum necessary scopes, and keep the tool set constrained before handing tools to an LLM. diff --git a/plugins/agent-auth/commands/test-tool.md b/plugins/agent-auth/commands/test-tool.md new file mode 100644 index 0000000..638bc06 --- /dev/null +++ b/plugins/agent-auth/commands/test-tool.md @@ -0,0 +1,91 @@ +--- +description: Test live AgentKit discovery and tool execution from Claude Code +argument-hint: "[generate-link|get-tool|execute-tool] [args...]" +allowed-tools: Bash +--- + +# AgentKit Tool Tester + +Test live AgentKit flows using the bundled Python playground script. + +**Arguments:** $ARGUMENTS + +## Operations + +### generate-link +Usage: `generate-link --connection-name --identifier ` + +Creates or fetches the connected account and prints an authorization link if the account is not yet `ACTIVE`. + +### get-tool +Usage: `get-tool [--tool-name ] [--provider ] [--page-size ] [--page-token ]` + +Fetches live tool metadata and prints the raw JSON response. Omitting `--tool-name` returns all matching tools for the filter. + +### execute-tool +Usage: `execute-tool --tool-name --connection-name --identifier --tool-input ''` + +Creates or fetches the connected account, prints an authorization link if needed, and executes the tool. + +## Your task + +Parse `$ARGUMENTS` to determine the operation, then run the bundled script from the plugin root: + +```bash +skills/testing-agentkit-tools/scripts/connect.py +``` + +### Runner selection + +Check which runner is available by running `which uv` once before any Python command: + +- if `uv` exists, use `uv run python` +- otherwise use `python3` +- if `python3` is unavailable, fall back to `python` + +### Credentials + +Before running any operation, check for these environment variables: + +- `SCALEKIT_ENV_URL` +- `SCALEKIT_CLIENT_ID` +- `SCALEKIT_CLIENT_SECRET` + +Also accept legacy aliases: + +- `TOOL_ENV_URL` +- `TOOL_CLIENT_ID` +- `TOOL_CLIENT_SECRET` + +If none of the supported variables are available, ask the user for the missing values before proceeding. Do not write secrets into source-controlled files unless the user explicitly asks you to. + +### Commands to run + +If operation is `generate-link`, run: + +```bash + skills/testing-agentkit-tools/scripts/connect.py --generate-link --connection-name --identifier +``` + +If operation is `get-tool`, run: + +```bash + skills/testing-agentkit-tools/scripts/connect.py --get-tool [--tool-name ] [--provider ] [--page-size ] [--page-token ] +``` + +If operation is `execute-tool`, run: + +```bash + skills/testing-agentkit-tools/scripts/connect.py --execute-tool --tool-name --connection-name --identifier --tool-input '' +``` + +If `tool_input` is missing for `execute-tool`, inspect the live tool metadata first or ask the user for the missing input values. + +### After running + +Show: + +1. the command output +2. the exact command that was run +3. the resolved parameters in a small structured summary +4. for `execute-tool`, the exact JSON payload that was passed to AgentKit diff --git a/plugins/agent-auth/docs/byoc.md b/plugins/agent-auth/docs/byoc.md new file mode 100644 index 0000000..1ff5319 --- /dev/null +++ b/plugins/agent-auth/docs/byoc.md @@ -0,0 +1,44 @@ +# Bring Your Own Credentials + +Bring Your Own Credentials (BYOC) lets you use your own OAuth applications and credentials instead of shared Scalekit defaults. + +## Why teams use BYOC + +Common reasons: + +- your branding appears during consent flows +- you want your own quotas and provider relationship +- you need stricter compliance or audit ownership +- you want a more fully whitelabeled production experience + +## What changes + +With BYOC: + +- the connection is still managed in AgentKit +- connected accounts still authorize users in the same model +- tools are still discovered and executed through AgentKit + +Only the credentials and upstream app ownership change. + +## Operational impact + +Moving to BYOC can require users to re-authorize, because the underlying OAuth application has changed. + +Plan for: + +- re-authentication +- updated consent screens +- different quotas or limits +- provider-specific app verification steps + +## Where it fits in the plugin + +BYOC is a connection-level concern, not a tool-discovery concern. + +Set up the connection correctly first, then discover tools and test execution as usual. + +## Related docs + +- [connections.md](connections.md) +- [connected-accounts.md](connected-accounts.md) diff --git a/plugins/agent-auth/docs/code-samples.md b/plugins/agent-auth/docs/code-samples.md new file mode 100644 index 0000000..b71f29d --- /dev/null +++ b/plugins/agent-auth/docs/code-samples.md @@ -0,0 +1,47 @@ +# Code Samples + +This page is the canonical code-sample entrypoint for the plugin. + +Use it to choose an implementation style before opening a larger sample repository. + +## Common paths + +| Goal | Recommended path | +|---|---| +| Validate one tool quickly | Use `/test-tool` with `testing-agentkit-tools` | +| Integrate AgentKit into app code | Use `integrating-agent-auth` | +| Build an agent with a framework | Use framework-specific examples below | +| Expose tools over MCP | Use `building-agent-mcp-server` | + +## Framework directions + +- LangChain: fetch a narrow tool set and hand only the relevant tools to the agent +- Google ADK: use AgentKit-authenticated tool wrappers for Gemini-based agents +- Direct SDK usage: best for deterministic or single-tool flows +- MCP: best when you want tools exposed to MCP-compatible runtimes + +## Important rule + +Do not treat example code as a fixed tool catalog. + +Examples show patterns, not the current truth of: + +- which tools exist +- what their schemas look like +- which fields are required today + +Use [tool-discovery.md](tool-discovery.md) first when the exact tool surface matters. + +## Suggested sample flow + +1. Set up a connection. +2. Create or fetch the connected account. +3. Discover the exact tool and schema. +4. Test it with the live playground. +5. Move the validated payload into application or agent code. + +## Related docs + +- [tool-discovery.md](tool-discovery.md) +- [connections.md](connections.md) +- [connected-accounts.md](connected-accounts.md) diff --git a/plugins/agent-auth/docs/connected-accounts.md b/plugins/agent-auth/docs/connected-accounts.md new file mode 100644 index 0000000..9a36988 --- /dev/null +++ b/plugins/agent-auth/docs/connected-accounts.md @@ -0,0 +1,51 @@ +# Connected Accounts + +Connected accounts are the per-user authorization records in AgentKit. + +If a connection is the reusable environment configuration, a connected account is the user-specific runtime object that lets the agent act on behalf of a particular user or identifier. + +## What a connected account stores + +A connected account tracks: + +- the linked connection +- the user or tenant identifier +- current authorization status +- granted scopes and token state + +## Lifecycle + +Typical lifecycle: + +1. Create or fetch the connected account for a user. +2. If the account is not `ACTIVE`, generate an authorization link. +3. The user completes the auth flow. +4. The account becomes `ACTIVE`. +5. AgentKit can execute tools on behalf of that user. + +## Operational rules + +- Always use the correct user identifier from your own system. +- Treat the connected account as the per-user boundary for tool execution. +- If tool execution fails unexpectedly, check the connected account status again before debugging deeper. + +## Why this matters for tool execution + +The tool catalog is connector-level, but execution is user-scoped through a connected account. + +That means: + +- tool discovery tells you what is possible +- the connected account determines whether the current user is actually authorized to do it + +## Common failure modes + +- `connection_name` does not match the dashboard value +- user never completed authorization +- account is no longer `ACTIVE` +- scopes are too narrow for the requested tool + +## Related docs + +- [connections.md](connections.md) +- [tool-discovery.md](tool-discovery.md) diff --git a/plugins/agent-auth/docs/connections.md b/plugins/agent-auth/docs/connections.md new file mode 100644 index 0000000..4e7bc17 --- /dev/null +++ b/plugins/agent-auth/docs/connections.md @@ -0,0 +1,53 @@ +# Connections + +Connections are the environment-level AgentKit configurations that tell Scalekit how to authenticate to a connector. + +One connection is created once in the dashboard, then reused across many users through connected accounts. + +## Core idea + +A connection defines: + +- which connector is being used +- which credentials or auth settings back it +- which scopes or permissions are requested +- the exact dashboard `connection_name` used later in SDK calls + +## Important distinction + +Do not confuse: + +- `connection_name`: the exact dashboard value used for authorization and connected-account flows +- connector slug or provider filter: the identifier used to group tools in live metadata + +They are related, but they are not always the same string. + +## Typical setup flow + +1. Go to `AgentKit -> Connections` in the Scalekit Dashboard. +2. Choose the connector you want to expose to the agent. +3. Configure credentials, scopes, and any connector-specific settings. +4. Save the connection with a stable `connection_name`. +5. Reuse that `connection_name` in your SDK code and testing commands. + +## Connection types + +Common patterns include: + +- OAuth 2.0 connectors +- API key connectors +- other connector-specific auth models supported by AgentKit + +Use the connector-specific notes in [connectors/README.md](connectors/README.md) for quirks, but treat live tool metadata as the source of truth for available tools. + +## Practical guidance + +- Use clear environment-specific names such as `MY_GMAIL` or `SALESFORCE_PROD`. +- Keep scopes minimal and workflow-specific. +- Do not assume Gmail is representative of every connector; Gmail is a special-case quickstart in several examples. + +## Related docs + +- [connected-accounts.md](connected-accounts.md) +- [tool-discovery.md](tool-discovery.md) +- [byoc.md](byoc.md) diff --git a/plugins/agent-auth/docs/connectors/README.md b/plugins/agent-auth/docs/connectors/README.md new file mode 100644 index 0000000..2b30396 --- /dev/null +++ b/plugins/agent-auth/docs/connectors/README.md @@ -0,0 +1,51 @@ +# Connector Notes + +This directory is the canonical connector-notes entrypoint for the plugin. + +Connector notes are intentionally curated and lightweight. They should explain: + +- what the connector is useful for +- auth quirks or setup gotchas +- example workflows +- guidance that is not obvious from live metadata + +They should not claim to be the exhaustive current tool catalog. + +## Source of truth + +Use live AgentKit metadata for: + +- tool names +- `input_schema` +- `output_schema` +- current connector coverage + +Use connector notes for: + +- workflow hints +- product context +- authentication caveats +- examples that help users orient quickly + +## Current connector coverage in the plugin + +The plugin already contains curated notes for connectors such as: + +- Gmail +- Google Calendar +- Google Sheets +- Slack +- Salesforce +- GitHub +- Notion +- Zendesk + +and many others in the legacy connector-notes set. + +During this hybrid migration, those existing notes remain available in the plugin as the backing source for connector-specific guidance while `docs/` becomes the canonical entry layer. + +## Related docs + +- [../tool-discovery.md](../tool-discovery.md) +- [../connections.md](../connections.md) +- [../connected-accounts.md](../connected-accounts.md) diff --git a/plugins/agent-auth/docs/index.md b/plugins/agent-auth/docs/index.md new file mode 100644 index 0000000..bd1d633 --- /dev/null +++ b/plugins/agent-auth/docs/index.md @@ -0,0 +1,38 @@ +# AgentKit Docs + +This `docs/` directory is the canonical documentation layer for the `agent-auth` plugin. + +Use it for the durable AgentKit model: + +- connectors +- connections +- connected accounts +- tools +- live tool discovery + +The Claude Code plugin still includes adapter/runtime files such as `.claude-plugin/`, `.mcp.json`, `commands/`, `hooks/`, and `agents/`, but those are secondary to the content model here. + +## How this directory is organized + +- [connections.md](connections.md) explains how AgentKit connections are configured and named. +- [connected-accounts.md](connected-accounts.md) explains the per-user authorization lifecycle. +- [tool-discovery.md](tool-discovery.md) explains how to treat live AgentKit metadata as the source of truth for tools and schemas. +- [byoc.md](byoc.md) explains Bring Your Own Credentials. +- [code-samples.md](code-samples.md) points to implementation patterns and runnable examples. +- [connectors/README.md](connectors/README.md) is the curated connector-notes entrypoint. + +## Source-of-truth rule + +Use live AgentKit metadata as the source of truth for: + +- current connector coverage +- tool names +- `input_schema` +- `output_schema` + +Treat connector notes as curated guidance, not as a guaranteed exhaustive catalog. + +## Relationship to skills and rules + +- `skills/` contains task-oriented workflows that should stay thin and point here for deeper reference. +- `rules/` contains stable cross-cutting guidance such as terminology and tool-selection discipline. diff --git a/plugins/agent-auth/docs/tool-discovery.md b/plugins/agent-auth/docs/tool-discovery.md new file mode 100644 index 0000000..cf35130 --- /dev/null +++ b/plugins/agent-auth/docs/tool-discovery.md @@ -0,0 +1,68 @@ +# Tool Discovery + +Live AgentKit metadata is the source of truth for: + +- current connector coverage +- tool names +- `input_schema` +- `output_schema` + +This is the most important rule in the hybrid structure. + +## What belongs in docs vs live metadata + +Use `docs/` for: + +- stable terminology +- auth and setup guidance +- connector quirks +- example workflows + +Use live metadata for: + +- current tools for a connector +- required input fields +- optional input fields +- output shape + +## Discovery workflow + +1. Start from a connector or exact tool name. +2. Query live AgentKit metadata. +3. Inspect: + - tool name + - description + - connector or provider grouping + - `input_schema.required` + - `input_schema.properties` + - `output_schema.properties` +4. Narrow the tool set to the few tools needed for the workflow. +5. Only execute after the schema is clear and the user has an `ACTIVE` connected account. + +## Important distinction + +Do not confuse: + +- dashboard `connection_name` +- connector or provider slug used for discovery filters + +The first is used for authorization and connected-account operations. +The second is used for catalog discovery. + +## Claude Code workflow + +Use: + +- `discovering-agentkit-tools` when the user needs current tools or schemas +- `testing-agentkit-tools` when the user wants to run a live tool and inspect the exact payload +- `/test-tool get-tool ...` for the runnable playground flow + +## Fallback rule + +If live credentials are unavailable, connector notes are still useful as a directional guide, but they must be treated as non-exhaustive. + +## Related docs + +- [connections.md](connections.md) +- [connected-accounts.md](connected-accounts.md) +- [connectors/README.md](connectors/README.md) diff --git a/plugins/agent-auth/references/agent-connectors/README.md b/plugins/agent-auth/references/agent-connectors/README.md index 807653a..a1d5853 100644 --- a/plugins/agent-auth/references/agent-connectors/README.md +++ b/plugins/agent-auth/references/agent-connectors/README.md @@ -1,6 +1,10 @@ # Agent Connectors Reference -This directory contains documentation for all supported agent connectors in the Scalekit Agent Auth platform. +> Canonical entrypoint: [../../docs/connectors/README.md](../../docs/connectors/README.md) + +This directory contains curated notes for AgentKit connectors in Scalekit. + +Use these files for connector-specific guidance, auth quirks, and example workflows. Do not treat them as the exhaustive source of truth for current tools or schemas; use live AgentKit tool metadata for that. ## Available Connectors @@ -51,17 +55,20 @@ This directory contains documentation for all supported agent connectors in the ## Getting Started -Each connector documentation includes: +Each connector document can include: - Service description and capabilities - Authentication requirements -- Complete API reference for all available tools -- Parameter specifications and examples +- Product and workflow context +- Authentication requirements +- Example tool patterns and usage notes - Usage guidelines and best practices +For the live tool catalog and current `input_schema` / `output_schema`, use [../tool-discovery.md](../tool-discovery.md). + ## Authentication -Connectors support OAuth 2.0, API Key, or Basic Auth authentication through the Agent Auth platform. You'll need to: +Connectors support OAuth 2.0, API Key, or Basic Auth authentication through AgentKit. You'll need to: 1. Create a connection for the desired service 2. Configure OAuth credentials in your connection diff --git a/plugins/agent-auth/references/byoc.md b/plugins/agent-auth/references/byoc.md index b88d0f6..5c43412 100644 --- a/plugins/agent-auth/references/byoc.md +++ b/plugins/agent-auth/references/byoc.md @@ -1,6 +1,8 @@ # Bring Your Own Credentials -Bring Your Own Credentials (BYOC) allows you to use your own OAuth applications and authentication credentials with Agent Auth instead of Scalekit's shared credentials. This provides complete control over the authentication experience and enables full whitelabeling of your application. +> Canonical doc: [../docs/byoc.md](../docs/byoc.md) + +Bring Your Own Credentials (BYOC) allows you to use your own OAuth applications and authentication credentials with AgentKit instead of Scalekit's shared credentials. This provides complete control over the authentication experience and enables full whitelabeling of your application. ## Why bring your own credentials? @@ -33,7 +35,7 @@ With BYOC, authentication flows work as follows: 1. **Scalekit** handles the initial authentication request with your OAuth client-id details 2. **Provider** authenticates the user and returns tokens to Scalekit -3. **Agent Auth** uses your tokens to execute tools on behalf of users +3. **AgentKit** uses your tokens to execute tools on behalf of users ## Setting up BYOC @@ -53,4 +55,4 @@ If you're currently using Scalekit's shared credentials and want to migrate to B > - Rate limits and quotas will change to your application's limits > - Some users may need to re-grant permissions -By implementing BYOC, you gain complete control over your users' authentication experience while maintaining the power and flexibility of Agent Auth's unified API for tool execution. +By implementing BYOC, you gain complete control over your users' authentication experience while maintaining the power and flexibility of AgentKit's unified API for tool execution. diff --git a/plugins/agent-auth/references/code-samples.md b/plugins/agent-auth/references/code-samples.md index 537ca57..19731d9 100644 --- a/plugins/agent-auth/references/code-samples.md +++ b/plugins/agent-auth/references/code-samples.md @@ -1,6 +1,8 @@ # Code Samples -This reference provides implementation examples for integrating Scalekit Agent Auth across different frameworks, languages, and use cases. +> Canonical doc: [../docs/code-samples.md](../docs/code-samples.md) + +This reference provides implementation examples for integrating Scalekit AgentKit across different frameworks, languages, and use cases. ## Quick Start Guide @@ -129,7 +131,7 @@ response = agent_executor.invoke({ ### Repository: [scalekit-inc/google-adk-agent-example](https://github.com/scalekit-inc/google-adk-agent-example.git) -**Overview:** Minimal Gmail-powered agent demonstrating Agent Auth integration with Google's Agent Development Kit. Entire integration fits in one file. +**Overview:** Minimal Gmail-powered agent demonstrating AgentKit integration with Google's Agent Development Kit. Entire integration fits in one file. **Requirements:** - Python >= 3.11 diff --git a/plugins/agent-auth/references/connected-accounts.md b/plugins/agent-auth/references/connected-accounts.md index 141dd65..bce2cb5 100644 --- a/plugins/agent-auth/references/connected-accounts.md +++ b/plugins/agent-auth/references/connected-accounts.md @@ -1,6 +1,8 @@ # Connected accounts -Connected accounts in Agent Auth represent individual user or organization connections to third-party providers. They contain the authentication state, tokens, and permissions needed to execute tools on behalf of a specific identifier (user_id, org_id, or custom identifier). +> Canonical doc: [../docs/connected-accounts.md](../docs/connected-accounts.md) + +Connected accounts in AgentKit represent individual user or organization authorizations for a connection. They contain the auth state, tokens, and permissions needed to execute tools on behalf of a specific identifier (`user_id`, `org_id`, or a custom identifier). ## What are connected accounts? @@ -51,7 +53,7 @@ F -> B ### Using the dashboard -1. Navigate to connected accounts in your Agent Auth dashboard +1. Navigate to connected accounts in your AgentKit dashboard 2. Click create account to start the process 3. Select connection to use for authentication 4. Enter identifier (user_id, email, or custom identifier) diff --git a/plugins/agent-auth/references/connections.md b/plugins/agent-auth/references/connections.md index ea68b33..d121801 100644 --- a/plugins/agent-auth/references/connections.md +++ b/plugins/agent-auth/references/connections.md @@ -1,6 +1,8 @@ # Connections -Connections in Agent Auth are specific configurations that define how your application authenticates and interacts with third-party providers. Each connection contains the necessary credentials, settings, and parameters required to establish secure communication with a provider's API. +> Canonical doc: [../docs/connections.md](../docs/connections.md) + +Connections in AgentKit are configurations that define how your application authenticates to a connector. A connection contains the credentials, scopes, and settings Scalekit needs before any user creates a connected account. ## Table of Contents @@ -18,17 +20,17 @@ Connections in Agent Auth are specific configurations that define how your appli ## What are connections? -Connections serve as the bridge between your Agent Auth setup and third-party providers. They contain: +Connections serve as the bridge between your AgentKit setup and third-party connectors. They contain: - **Authentication credentials** (OAuth client ID/secret, API keys, etc.) - **Configuration settings** (scopes, permissions, endpoints) -- **Tool definitions** and their parameters +- **Access configuration** used before tools are executed - **Rate limiting** and retry policies - **Custom settings** specific to your use case ## Connection types -Agent Auth supports various connection types based on different authentication methods: +AgentKit supports various connection types based on different authentication methods: ### OAuth 2.0 connections @@ -101,8 +103,8 @@ For providers with unique authentication requirements: ### Using the dashboard -1. **Navigate to connections** in your Agent Auth dashboard -2. **Select provider** from the list of available providers +1. **Navigate to connections** in your AgentKit dashboard +2. **Select connector** from the list of available connectors 3. **Choose connection type** based on your authentication method 4. **Configure credentials** by entering your API keys or OAuth settings 5. **Set permissions** and scopes for the connection @@ -111,7 +113,7 @@ For providers with unique authentication requirements: ### Using the API -Create connections programmatically using the Agent Auth API: +Create connections programmatically using the AgentKit API: **cURL:** diff --git a/plugins/agent-auth/references/providers.md b/plugins/agent-auth/references/providers.md index eea7217..8ccb64b 100644 --- a/plugins/agent-auth/references/providers.md +++ b/plugins/agent-auth/references/providers.md @@ -1,19 +1,21 @@ -# Providers +# Connectors Overview -Providers in Agent Auth represent third-party applications that your users can connect to and interact with through Scalekit's unified API. Each provider offers a set of tools and capabilities that can be executed on behalf of connected users. +> Canonical docs: [../docs/index.md](../docs/index.md) and [../docs/connectors/README.md](../docs/connectors/README.md) -## What are providers? +This page is a high-level connector overview for AgentKit. Use it for category-level guidance and terminology, not as the exhaustive source of truth for current tools. The live AgentKit tool metadata is the source of truth for tool names plus `input_schema` and `output_schema`. -Providers are pre-configured integrations with popular third-party applications that enable your users to: +## What are connectors? + +Connectors are pre-configured integrations with popular third-party applications that enable your users to: - **Connect their accounts** using secure authentication methods - **Execute tools and actions** through a unified API interface - **Access data and functionality** from external applications - **Maintain secure connections** with proper authorization scopes -## Supported providers +## Supported connectors -Agent Auth supports a wide range of popular business applications: +AgentKit supports a wide range of popular business applications: | Category | Providers | |---|---| @@ -27,11 +29,12 @@ Agent Auth supports a wide range of popular business applications: | **Data & Analytics** | BigQuery, Snowflake, Fathom | | **Service Management** | ServiceNow | -For per-connector tool specifications, see [agent-connectors/README.md](agent-connectors/README.md). +For curated connector notes, see [agent-connectors/README.md](agent-connectors/README.md). +For live tool discovery, see [tool-discovery.md](tool-discovery.md). -## Provider capabilities +## Connector capabilities -Each provider offers different capabilities based on their API and authentication model. +Each connector offers different capabilities based on its API and authentication model. ### Authentication methods @@ -39,9 +42,9 @@ Each provider offers different capabilities based on their API and authenticatio ### Available tools -Providers expose various tools that can be executed through Agent Auth: +Connectors expose various tools that can be executed through AgentKit: -> **Note:** Tool availability depends on the specific provider and the user's permissions within that application. +> **Note:** Tool availability depends on the specific connector, the current live catalog, and the user's permissions within that application. **Common tool categories:** @@ -59,17 +62,17 @@ Each provider has different rate limits and quotas: - **Data quotas**: Storage or transfer limitations - **Feature restrictions**: Premium features or enterprise-only capabilities -## Provider configuration +## Connector configuration -### Adding a provider +### Adding a connector -1. **Navigate to providers** in your Agent Auth dashboard -2. **Select provider** from the available options +1. **Navigate to connections** in your AgentKit dashboard +2. **Select connector** from the available options 3. **Configure settings** such as scopes and permissions 4. **Set up authentication** — configure OAuth client credentials if using custom OAuth apps 5. **Test connection** to verify provider setup -### Provider settings +### Connector settings Each provider can be configured with: @@ -83,11 +86,11 @@ Each provider can be configured with: - Request throttling settings - Backoff strategies for rate limit errors -## Working with provider APIs +## Working with connector APIs ### API integration -The Scalekit SDK abstracts provider-specific APIs — the workflow (create account → authorize → fetch token → call API) is identical for all providers. Only the downstream API call changes: +The Scalekit SDK abstracts connector-specific APIs. In AgentKit, prefer live tool discovery plus `execute_tool` over hand-coding upstream REST calls when a tool already exists. ```python # Step 3: Fetch token (always call this immediately before the API call) @@ -98,7 +101,7 @@ response = actions.get_connected_account( tokens = response.connected_account.authorization_details["oauth_token"] access_token = tokens["access_token"] -# Step 4: Call the provider API with the token +# Step 4: Call the connector API with the token headers = {"Authorization": f"Bearer {access_token}"} ``` @@ -106,7 +109,7 @@ Scalekit automatically refreshes expired tokens on `get_connected_account` — n ### Error handling -Agent Auth normalizes provider-specific errors into consistent error responses: +AgentKit normalizes connector-specific errors into consistent error responses: ```javascript { @@ -122,7 +125,7 @@ Agent Auth normalizes provider-specific errors into consistent error responses: } ``` -## Provider-specific considerations +## Connector-specific considerations ### Google Workspace @@ -183,7 +186,8 @@ Agent Auth normalizes provider-specific errors into consistent error responses: ## Related documentation -- [connections.md](connections.md) — how to configure authentication credentials for a provider +- [connections.md](connections.md) — how to configure authentication credentials for a connector - [connected-accounts.md](connected-accounts.md) — per-user account lifecycle and token management -- [agent-connectors/README.md](agent-connectors/README.md) — detailed API tools for each provider +- [agent-connectors/README.md](agent-connectors/README.md) — curated connector notes and examples +- [tool-discovery.md](tool-discovery.md) — live discovery model for current tools and schemas - [code-samples.md](code-samples.md) — implementation examples by framework diff --git a/plugins/agent-auth/references/tool-discovery.md b/plugins/agent-auth/references/tool-discovery.md new file mode 100644 index 0000000..9c472e6 --- /dev/null +++ b/plugins/agent-auth/references/tool-discovery.md @@ -0,0 +1,88 @@ +# Tool Discovery + +> Canonical doc: [../docs/tool-discovery.md](../docs/tool-discovery.md) + +## Overview + +In AgentKit, the live tool metadata is the source of truth for: + +- current connector coverage +- tool names +- `input_schema` +- `output_schema` + +The static connector notes in `references/agent-connectors/` are curated guidance. They are useful for auth quirks, example workflows, and product context, but they are not a guaranteed up-to-date catalog of every current tool. + +## Terminology + +- `connector`: the integration, such as Gmail, Slack, Salesforce, or a custom connector +- `connection`: the dashboard configuration created once per environment +- `connected account`: the per-user authorized instance of a connection +- `tool`: the executable action exposed by a connector + +Use `connector` in user-facing explanations. Use `provider` only when the SDK or API filter field literally uses that name. + +## Discovery rules + +1. Prefer live lookup over hand-maintained docs. +2. Narrow the search to a single connector or tool name whenever possible. +3. Summarize required inputs from `input_schema.required`. +4. Summarize optional inputs from `input_schema.properties`. +5. Describe likely results from `output_schema.properties`. +6. Recommend the smallest useful tool set before handing tools to an LLM. + +## What to inspect + +When live metadata is available, capture: + +- tool `name` +- tool `description` +- connector / provider slug +- `input_schema.properties` +- `input_schema.required` +- `output_schema.properties` + +If the metadata contains pagination or large result fields, mention them so the user can limit tool scope or post-process results before sending them back to the model. + +## How to use this in Claude Code + +For interactive discovery, use the live playground command: + +```sh +/test-tool get-tool --provider GMAIL +/test-tool get-tool --tool-name gmail_fetch_mails +``` + +For implementation guidance, use: + +- `discovering-agentkit-tools` when the user needs the current tool list or schema +- `testing-agentkit-tools` when the user wants to execute the tool and inspect the exact payload +- `integrating-agent-auth` when the user wants to wire the result into application code + +## Connection names vs connector names + +Do not confuse: + +- dashboard `connection_name`: exact value from `AgentKit -> Connections` +- connector / provider slug: value used to group live tools in metadata + +The first is for authorization and connected account flows. +The second is for catalog discovery and tool grouping. + +They are related, but they are not always the same string. + +## Example reasoning pattern + +1. User says: "What tools can I use for Google Sheets?" +2. Discover the live tool list for the Google Sheets connector. +3. Inspect the candidate tools and their `input_schema`. +4. Recommend only the few tools needed for the workflow, such as read values, update values, or append rows. +5. If the user wants to validate the flow, generate an auth link if needed and execute one tool with minimal input. + +## Fallback behavior + +If live credentials are not available: + +- use `references/agent-connectors/` as a directional guide +- clearly say the catalog may be stale +- avoid claiming that the listed tools are exhaustive diff --git a/plugins/agent-auth/rules/live-metadata-first.md b/plugins/agent-auth/rules/live-metadata-first.md new file mode 100644 index 0000000..43b6cda --- /dev/null +++ b/plugins/agent-auth/rules/live-metadata-first.md @@ -0,0 +1,19 @@ +# Live Metadata First + +Treat live AgentKit metadata as the source of truth for: + +- tool names +- connector coverage +- `input_schema` +- `output_schema` + +## Implications + +- Static connector notes are not exhaustive. +- Example code is not a catalog. +- Schema discovery should happen before guessing tool inputs. +- When credentials are available, prefer live lookup over prose documentation. + +## Fallback behavior + +If live credentials are unavailable, docs can still guide setup and workflow design, but they must be presented as directional guidance rather than guaranteed current truth. diff --git a/plugins/agent-auth/rules/terminology.md b/plugins/agent-auth/rules/terminology.md new file mode 100644 index 0000000..bc02173 --- /dev/null +++ b/plugins/agent-auth/rules/terminology.md @@ -0,0 +1,18 @@ +# Terminology + +Use these terms consistently in user-facing documentation and skills: + +- `connector`: the integration, such as Gmail, Slack, or Salesforce +- `connection`: the environment-level dashboard configuration +- `connected account`: the per-user authorization record +- `tool`: the executable action exposed by a connector + +## Preferred wording + +- Prefer `connector` in explanations to users. +- Use `provider` only when the SDK or API field literally uses that name. +- Do not imply that `connection_name` is always the same as the connector slug. + +## Why this rule exists + +The old language mixed `provider`, `connector`, and `connection_name` too freely. This rule keeps the AgentKit model stable across skills and docs. diff --git a/plugins/agent-auth/rules/tool-selection.md b/plugins/agent-auth/rules/tool-selection.md new file mode 100644 index 0000000..2b3dcd9 --- /dev/null +++ b/plugins/agent-auth/rules/tool-selection.md @@ -0,0 +1,22 @@ +# Tool Selection + +Keep the tool set as small as possible before handing it to an LLM. + +## Rules + +- Start from the workflow, not the full catalog. +- Discover only the connector or tool family relevant to the task. +- Inspect `input_schema` before execution when required fields are unclear. +- Avoid sending broad connector-wide tool lists to the model unless the task truly needs them. + +## Why this matters + +Too many tools degrade tool selection and parameter filling. Constraining the tool set improves both latency and correctness. + +## Practical pattern + +1. Identify the user goal. +2. Discover a narrow candidate set. +3. Inspect schema. +4. Validate with one live execution if needed. +5. Wire only the proven tools into the agent workflow. diff --git a/plugins/agent-auth/skills/agent-auth/SKILL.md b/plugins/agent-auth/skills/agent-auth/SKILL.md index 66e76c2..56db8af 100644 --- a/plugins/agent-auth/skills/agent-auth/SKILL.md +++ b/plugins/agent-auth/skills/agent-auth/SKILL.md @@ -1,304 +1,124 @@ --- name: integrating-agent-auth -description: Integrates Scalekit Agent Auth into a project to handle OAuth flows, token storage, and automatic refresh for third-party services (Gmail, Slack, Notion, Calendar). Use when a user needs to connect to an external service, authorize OAuth access, fetch access or refresh tokens, or execute API calls on behalf of a user. +description: Integrates Scalekit AgentKit into a project so an agent can create connections, authorize users, discover tools, and execute authenticated tool calls on their behalf. Use when a user needs to set up a connection, create a connected account, generate an authorization link, or wire AgentKit tools into application code or an agent framework. --- -# Agent Auth Integration +# AgentKit Integration -Scalekit handles the full OAuth lifecycle — authorization, token storage, and refresh — so agents can act on behalf of users in Gmail, Slack, Notion, Calendar, and other connectors. +Use this skill as the integration entrypoint for the plugin. It should stay thin and route into the canonical docs in `docs/`. -**Required env vars**: `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`, `SCALEKIT_ENV_URL` -→ Get from [app.scalekit.com](https://app.scalekit.com): Developers → Settings → API Credentials +## Mental model -## Setup +Keep these terms straight: -Install the SDK and initialize the client: +- `connector`: the integration, such as Gmail or Slack +- `connection`: the environment-level dashboard configuration +- `connected account`: the per-user authorization record +- `tool`: the executable action exposed by a connector -> **Important**: Except for Gmail, all connectors must be configured in the Scalekit Dashboard first before creating authorization URLs. -> -> To set up a connector: **Scalekit Dashboard → Agent Auth → Connections → + Create Connection → Select connector → Set Connection Name → Save** +Prefer live tool discovery over hand-maintained catalogs. If the user needs the current tool list or schema, switch to `discovering-agentkit-tools` or `testing-agentkit-tools`. - +## Default workflow -**Python** +1. Confirm `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, and `SCALEKIT_CLIENT_SECRET`. +2. Verify the connection exists in `AgentKit -> Connections`. +3. Create or fetch the connected account for the user. +4. If the account is not `ACTIVE`, generate an authorization link. +5. Discover the exact tool and schema before execution. +6. Execute the tool directly or hand only the needed tools to an agent framework. + +## Quick integration skeleton + +### Python ```bash pip install scalekit-sdk-python ``` + ```python import scalekit.client, os from dotenv import load_dotenv load_dotenv() -scalekit = scalekit.client.ScalekitClient( +client = scalekit.client.ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), env_url=os.getenv("SCALEKIT_ENV_URL"), ) -actions = scalekit.actions +actions = client.actions +response = actions.get_or_create_connected_account( + connection_name="MY_GMAIL", + identifier="user_123" +) +connected_account = response.connected_account +if connected_account.status != "ACTIVE": + link_response = actions.get_authorization_link( + connection_name="MY_GMAIL", + identifier="user_123" + ) + print("Authorize here:", link_response.link) +result = actions.execute_tool( + tool_name="gmail_fetch_mails", + identifier="user_123", + connected_account_id=connected_account.id, + tool_input={ + "query": "is:unread", + "max_results": 5, + }, +) +print(result) ``` -**Node.js** +### Node.js ```bash npm install @scalekit-sdk/node ``` + ```typescript import { ScalekitClient } from '@scalekit-sdk/node'; import 'dotenv/config'; -const scalekitClient = new ScalekitClient( +const client = new ScalekitClient( process.env.SCALEKIT_ENV_URL!, process.env.SCALEKIT_CLIENT_ID!, process.env.SCALEKIT_CLIENT_SECRET! ); -const { connectedAccounts } = scalekitClient; -``` - - - -## Connector setup - -Before integrating with a connector, follow these steps in the Scalekit Dashboard: - -> **Gmail is the only connector that does not require dashboard setup.** Skip this section for Gmail. - -For all other connectors (Slack, Notion, Google Calendar, etc.): - -1. Go to **Scalekit Dashboard → Agent Auth → Connections** -2. Click **+ Create Connection** -3. Select the connector you want to use -4. Enter a **Connection Name** (e.g., `MY_SLACK`, `MY_NOTION`) -5. Click **Save** - -> **Important**: The **Connection Name** you set in the dashboard is exactly what you use as the `connection_name` parameter in your code. They must match exactly. - -## Integration workflow - -**First, ask the user:** - -> Are you starting fresh and want a quick test with Gmail, or are you integrating directly into your project? - -- If **fresh / quick test**: Use the Gmail example below (Gmail is the only connector that doesn't require dashboard setup) -- If **integrating directly**: Create your connector in the Scalekit Dashboard first, then adapt the workflow below to your connector +const actions = client.actions; -Copy this checklist and check off steps as you complete them: - -``` -Agent Auth Integration Progress: -- [ ] Step 1: SDK installed and client initialized -- [ ] Step 2: Connected account created for the user -- [ ] Step 3: User has authorized the connection (status = ACTIVE) -- [ ] Step 4: Access token fetched successfully -- [ ] Step 5: Downstream API call succeeds with fetched token -``` - -### Step 1 — Create a connected account - -Replace `"user_123"` with the project's actual user ID. Replace `"gmail"` with the target connector. - -**Python** -```python -response = actions.get_or_create_connected_account( - connection_name="gmail", - identifier="user_123" -) -connected_account = response.connected_account -``` - -**Node.js** -```typescript -const response = await connectedAccounts.getOrCreateConnectedAccount({ - connector: 'gmail', +const response = await actions.getOrCreateConnectedAccount({ + connectionName: 'MY_GMAIL', identifier: 'user_123', }); const connectedAccount = response.connectedAccount; -``` - -### Step 2 — Authorize the user -If status is not `ACTIVE`, the user must complete OAuth. In a web app, redirect to `link`. In CLI/dev, print and wait. - -**Python** -```python -if connected_account.status != "ACTIVE": - link_response = actions.get_authorization_link( - connection_name="gmail", - identifier="user_123" - ) - print("Authorize here:", link_response.link) - input("Press Enter after authorizing...") -``` - -**Node.js** -```typescript if (connectedAccount?.status !== 'ACTIVE') { - const linkResponse = await connectedAccounts.getMagicLinkForConnectedAccount({ - connector: 'gmail', + const linkResponse = await actions.getAuthorizationLink({ + connectionName: 'MY_GMAIL', identifier: 'user_123', }); - console.log('Authorize here:', linkResponse.link); - // Web app: redirect user to linkResponse.link + console.log(linkResponse.link); } -``` -### Step 3 — Fetch OAuth tokens - -ALWAYS call `get_connected_account` immediately before any API call — Scalekit auto-refreshes tokens and this guarantees the latest valid token. - -**Python** -```python -response = actions.get_connected_account( - connection_name="gmail", - identifier="user_123" -) -tokens = response.connected_account.authorization_details["oauth_token"] -access_token = tokens["access_token"] -refresh_token = tokens["refresh_token"] -``` - -**Node.js** -```typescript -const accountResponse = await connectedAccounts.getConnectedAccountByIdentifier({ - connector: 'gmail', - identifier: 'user@example.com', +const result = await actions.executeTool({ + toolName: 'gmail_fetch_mails', + connectedAccountId: connectedAccount?.id, + identifier: 'user_123', + toolInput: { query: 'is:unread', max_results: 5 }, }); -const authDetails = accountResponse?.connectedAccount?.authorizationDetails; -const accessToken = authDetails?.details?.case === 'oauthToken' - ? authDetails.details.value?.accessToken : undefined; -const refreshToken = authDetails?.details?.case === 'oauthToken' - ? authDetails.details.value?.refreshToken : undefined; +console.log(result); ``` -### Step 4 — Call the third-party API - -Use `access_token` from Step 3 as a Bearer token. Example: fetch 5 unread Gmail messages. - -**Python** -```python -import requests - -headers = {"Authorization": f"Bearer {access_token}"} -list_url = "https://gmail.googleapis.com/gmail/v1/users/me/messages" - -messages = requests.get( - list_url, headers=headers, params={"q": "is:unread", "maxResults": 5} -).json().get("messages", []) - -for msg in messages: - data = requests.get( - f"{list_url}/{msg['id']}", headers=headers, - params={"format": "metadata", "metadataHeaders": ["From", "Subject", "Date"]} - ).json() - hdrs = data.get("payload", {}).get("headers", []) - print(next((h["value"] for h in hdrs if h["name"] == "Subject"), "No Subject")) - print(next((h["value"] for h in hdrs if h["name"] == "From"), "Unknown")) - print(data.get("snippet", "")) - print("-" * 50) -``` - -**Node.js** -```typescript -const listUrl = 'https://gmail.googleapis.com/gmail/v1/users/me/messages'; -const params = new URLSearchParams({ q: 'is:unread', maxResults: '5' }); - -const { messages = [] } = await fetch(`${listUrl}?${params}`, { - headers: { Authorization: `Bearer ${accessToken}` }, -}).then(r => r.json()); - -for (const msg of messages) { - const msgData = await fetch( - `${listUrl}/${msg.id}?format=metadata&metadataHeaders=From&metadataHeaders=Subject&metadataHeaders=Date`, - { headers: { Authorization: `Bearer ${accessToken}` } } - ).then(r => r.json()); - - const h = msgData.payload?.headers ?? []; - console.log('Subject:', h.find(x => x.name === 'Subject')?.value ?? 'No Subject'); - console.log('From:', h.find(x => x.name === 'From')?.value ?? 'Unknown'); - console.log('Snippet:', msgData.snippet ?? ''); - console.log('-'.repeat(50)); -} -``` - -## Adapting to other connectors - -Replace `"gmail"` with any supported connector name: `slack`, `notion`, `calendar`, etc. -The SDK workflow (Steps 1–3) is identical for all connectors. Only the downstream API call (Step 4) changes. - -For connector-specific API details, see [agent-connectors/README.md](../../references/agent-connectors/README.md). - -## Building agents - -Use Scalekit tools with AI frameworks to build agents that can execute actions on behalf of users. - -### LangChain agents - -Create conversational agents with LangChain that can autonomously call Scalekit tools based on user intent. - -**Python** -```python -from langchain_openai import ChatOpenAI -from langchain.agents import AgentExecutor, create_openai_tools_agent -from langchain_core.prompts import ChatPromptTemplate - -# Fetch tools from Scalekit in LangChain format -tools = actions.langchain.get_tools( - identifier="user_123", - providers=["GMAIL"], - page_size=100 -) - -# Define the agent prompt -prompt = ChatPromptTemplate.from_messages([ - ("system", "You are a helpful assistant with access to external tools."), - ("placeholder", "{chat_history}"), - ("human", "{input}"), - ("placeholder", "{agent_scratchpad}"), -]) - -# Create and run the agent -llm = ChatOpenAI(model="gpt-4o") -agent = create_openai_tools_agent(llm, tools, prompt) -executor = AgentExecutor(agent=agent, tools=tools, verbose=True) -result = executor.invoke({"input": "fetch my last 5 unread emails and summarize them"}) -``` - -### Google ADK agents - -Build agents using Google's Agent Development Kit with native Gemini integration. - -**Python** -```python -from google.adk.agents import Agent - -# Fetch tools from Scalekit in Google ADK format -gmail_tools = actions.google.get_tools( - providers=["GMAIL"], - identifier="user_123", - page_size=100 -) - -# Create the agent -agent = Agent( - name="gmail_assistant", - model="gemini-2.5-flash", - description="Gmail assistant that can read and manage emails", - instruction="You are a helpful Gmail assistant that can read, send, and organize emails.", - tools=gmail_tools -) - -# Run the agent -response = agent.process_request("fetch my last 5 unread emails and summarize them") -``` - -For more examples and framework-specific patterns, see [code-samples.md](../../references/code-samples.md). - ## Deep reference -For comprehensive documentation on connected accounts lifecycle, states, and API usage, see [connected-accounts.md](../../references/connected-accounts.md). - -For code samples and implementation examples by framework, see [code-samples.md](../../references/code-samples.md). - -For an overview of supported providers and their capabilities, see [providers.md](../../references/providers.md). +- Core docs: [../../docs/index.md](../../docs/index.md) +- Connections: [../../docs/connections.md](../../docs/connections.md) +- Connected accounts: [../../docs/connected-accounts.md](../../docs/connected-accounts.md) +- Tool discovery: [../../docs/tool-discovery.md](../../docs/tool-discovery.md) +- Code-sample entrypoint: [../../docs/code-samples.md](../../docs/code-samples.md) +- BYOC: [../../docs/byoc.md](../../docs/byoc.md) +- Connector notes: [../../docs/connectors/README.md](../../docs/connectors/README.md) -For token refresh behavior and operational guidance, see [connected-accounts.md](../../references/connected-accounts.md). +## When to switch skills -For configuring your own OAuth credentials per connector (whitelabeling, dedicated quotas), see [byoc.md](../../references/byoc.md). +- Use `discovering-agentkit-tools` when the user needs the current tool catalog or schema. +- Use `testing-agentkit-tools` when the user wants to validate a tool call in Claude Code. +- Use `building-agent-mcp-server` when the user wants AgentKit tools exposed over MCP. diff --git a/plugins/agent-auth/skills/building-agent-mcp-server/SKILL.md b/plugins/agent-auth/skills/building-agent-mcp-server/SKILL.md index b8e85b7..c3bd41b 100644 --- a/plugins/agent-auth/skills/building-agent-mcp-server/SKILL.md +++ b/plugins/agent-auth/skills/building-agent-mcp-server/SKILL.md @@ -1,15 +1,15 @@ --- name: building-agent-mcp-server -description: Guides developers through creating a Scalekit MCP server with authenticated tool access. Use when building an MCP server, exposing Scalekit tools over MCP, or connecting AI agents via LangChain/LangGraph MCP adapters. +description: Guides developers through creating a Scalekit AgentKit MCP server with authenticated tool access. Use when building an MCP server, exposing AgentKit tools over MCP, or connecting AI agents via LangChain or LangGraph MCP adapters. --- # Building an Agent MCP Server Scalekit lets you build MCP servers that manage authentication, create personalized access URLs for users, and define which tools are accessible. You can also bundle several toolkits (e.g., Gmail + Google Calendar) within a single server. -[Model Context Protocol (MCP)](https://modelcontextprotocol.io/docs/getting-started/intro) is an open-source standard that enables AI systems to interface with external tools and data sources. Where the `integrating-agent-auth` skill uses the SDK directly, this workflow exposes Scalekit tools over the MCP protocol so any compliant client — LangChain, Claude Desktop, MCP Inspector — can consume them. +[Model Context Protocol (MCP)](https://modelcontextprotocol.io/docs/getting-started/intro) is an open-source standard that enables AI systems to interface with external tools and data sources. Where the `integrating-agent-auth` skill uses the SDK directly, this workflow exposes AgentKit tools over the MCP protocol so any compliant client — LangChain, Claude Desktop, MCP Inspector — can consume them. -> **Note:** Agent Auth MCP servers only support Streamable HTTP transport. +> **Note:** AgentKit MCP servers only support Streamable HTTP transport. ## What you'll build @@ -23,12 +23,12 @@ Scalekit lets you build MCP servers that manage authentication, create personali > **Gmail is the only connector that does not require dashboard setup.** All other connectors (including Google Calendar) must be created in the Scalekit Dashboard before use: > -> Go to **Scalekit Dashboard → Agent Auth → Connections → + Create Connection → Select connector** → Set `Connection Name` → Save +> Go to **Scalekit Dashboard → AgentKit → Connections → + Create Connection → Select connector** → Set `Connection Name` → Save > **Important**: The **Connection Name** you set in the dashboard is exactly what you use as the `connection_name` parameter in your code. They must match exactly. For this example, create the Google Calendar connector: -- [ ] **Google Calendar connector**: Scalekit Dashboard → Agent Auth → Connections → Create Connection → Google Calendar → `Connection Name = MY_CALENDAR` → Save +- [ ] **Google Calendar connector**: Scalekit Dashboard → AgentKit → Connections → Create Connection → Google Calendar → `Connection Name = MY_CALENDAR` → Save ## Step 1 — Set up your environment @@ -156,3 +156,10 @@ asyncio.run(main()) > **Note — MCP client compatibility:** You can test this MCP server with popular clients like MCP Inspector, Claude Desktop, and other spec-compliant implementations. Note that ChatGPT's beta connector feature may not work properly as it's still in beta and doesn't fully adhere to the MCP specification yet. Full working example: [github.com/scalekit-inc/python-connect-demos/tree/main/mcp](https://github.com/scalekit-inc/python-connect-demos/tree/main/mcp) + +## Deep reference + +- Canonical docs entrypoint: [../../docs/index.md](../../docs/index.md) +- Connections: [../../docs/connections.md](../../docs/connections.md) +- Connected accounts: [../../docs/connected-accounts.md](../../docs/connected-accounts.md) +- Tool discovery: [../../docs/tool-discovery.md](../../docs/tool-discovery.md) diff --git a/plugins/agent-auth/skills/discovering-agentkit-tools/SKILL.md b/plugins/agent-auth/skills/discovering-agentkit-tools/SKILL.md new file mode 100644 index 0000000..3a29c71 --- /dev/null +++ b/plugins/agent-auth/skills/discovering-agentkit-tools/SKILL.md @@ -0,0 +1,56 @@ +--- +name: discovering-agentkit-tools +description: Discovers live Scalekit AgentKit tools for a connector and explains their input and output schemas. Use when a user asks what tools are available for Gmail, Slack, Salesforce, or another connector, wants to inspect `input_schema` or `output_schema`, or needs help narrowing the tool set for an agent. +--- + +# Discovering AgentKit Tools + +Use live AgentKit metadata as the source of truth for tool names, required inputs, and output schemas. + +Do not rely on the static connector notes as a complete catalog. Those files are curated reference material and may lag the live platform. + +## When to use this skill + +Use this skill when the user asks: + +- what tools exist for a connector +- which tool should the agent use +- what inputs a tool requires +- what output shape a tool returns +- how to reduce the tool set before giving tools to an LLM + +## Discovery workflow + +1. Identify the target connector or exact tool name. +2. Prefer live lookup through the built-in testing workflow or SDK metadata. +3. Summarize: + - tool name + - connector + - what the tool does + - required fields from `input_schema.required` + - optional fields from `input_schema.properties` + - important fields from `output_schema.properties` +4. Recommend the smallest useful tool set for the workflow. +5. If live credentials are unavailable, use the connector notes only as a fallback and say they may be stale. + +## Terminology + +- `connector`: Gmail, Slack, Salesforce, Notion, or a custom connector +- `connection`: the exact dashboard configuration name used for authorization +- `connected account`: the per-user authorized record +- `tool`: the executable action exposed by a connector + +Use `connector` in explanations. Only use `provider` when the SDK or API filter field literally expects that name. + +## What to emphasize + +- `connection_name` is the exact dashboard value and may not equal the connector slug. +- Tool metadata is the durable way to determine current inputs and outputs. +- Restrict the tool set before handing it to an LLM. Fewer relevant tools improve tool selection and parameter filling. + +## Deep reference + +- Canonical docs entrypoint: [../../docs/index.md](../../docs/index.md) +- Live discovery model: [../../docs/tool-discovery.md](../../docs/tool-discovery.md) +- Curated connector notes: [../../docs/connectors/README.md](../../docs/connectors/README.md) +- Broader implementation examples: [../../docs/code-samples.md](../../docs/code-samples.md) diff --git a/plugins/agent-auth/skills/production-readiness-scalekit/SKILL.md b/plugins/agent-auth/skills/production-readiness-scalekit/SKILL.md index 152ffd3..a79fcbe 100644 --- a/plugins/agent-auth/skills/production-readiness-scalekit/SKILL.md +++ b/plugins/agent-auth/skills/production-readiness-scalekit/SKILL.md @@ -1,9 +1,9 @@ --- name: production-readiness-scalekit -description: Walks through a structured production readiness checklist for Scalekit agent authentication implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, or wants to verify their agent OAuth implementation is production-ready. +description: Walks through a structured production readiness checklist for Scalekit AgentKit implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, or wants to verify their AgentKit authorization and tool-calling setup is production-ready. --- -# Scalekit Agent Auth Production Readiness +# Scalekit AgentKit Production Readiness Work through each section in order — earlier sections are blockers for later ones. @@ -60,3 +60,10 @@ Work through each section in order — earlier sections are blockers for later o - OAuth authorization completion rate (initiated vs completed) - Per-service API error rates (distinguish auth errors from service errors) - Token expiry distribution (are tokens being refreshed proactively?) + +## Deep reference + +- Canonical docs entrypoint: [../../docs/index.md](../../docs/index.md) +- Connections: [../../docs/connections.md](../../docs/connections.md) +- Connected accounts: [../../docs/connected-accounts.md](../../docs/connected-accounts.md) +- BYOC: [../../docs/byoc.md](../../docs/byoc.md) diff --git a/plugins/agent-auth/skills/testing-agentkit-tools/SKILL.md b/plugins/agent-auth/skills/testing-agentkit-tools/SKILL.md new file mode 100644 index 0000000..e989cff --- /dev/null +++ b/plugins/agent-auth/skills/testing-agentkit-tools/SKILL.md @@ -0,0 +1,50 @@ +--- +name: testing-agentkit-tools +description: Tests live Scalekit AgentKit flows from Claude Code by generating authorization links, fetching tool metadata, and executing a tool for a connected account. Use when a user wants to validate a connector, inspect the exact payload for `execute_tool`, or build a workflow step by step in the editor. +--- + +# Testing AgentKit Tools + +This skill is the live playground layer for AgentKit inside Claude Code. + +Use it to: + +- generate an authorization link for a connection +- fetch live tool metadata for a connector or tool name +- execute a tool with real inputs +- inspect the exact JSON payload sent to AgentKit + +## Default workflow + +1. Confirm the environment variables are available: + - `SCALEKIT_ENV_URL` + - `SCALEKIT_CLIENT_ID` + - `SCALEKIT_CLIENT_SECRET` + - legacy `TOOL_*` aliases are accepted for backward compatibility +2. Discover the tool first when the schema is unknown. +3. Generate an authorization link if the connected account is not `ACTIVE`. +4. Execute the tool with the smallest valid `tool_input`. +5. Show the exact command and payload used so the user can translate it into app code. + +## Command surface + +Use `/test-tool` for the runnable playground: + +- `/test-tool get-tool --provider GMAIL` +- `/test-tool get-tool --tool-name gmail_fetch_mails` +- `/test-tool generate-link --connection-name MY_GMAIL --identifier user_123` +- `/test-tool execute-tool --tool-name gmail_fetch_mails --connection-name MY_GMAIL --identifier user_123 --tool-input '{"query":"is:unread","max_results":5}'` + +## Guardrails + +- Treat live metadata as the source of truth for `input_schema` and `output_schema`. +- Do not assume the dashboard `connection_name` matches the connector slug. +- Ask for missing credentials instead of inventing placeholder values. +- Keep the tool set constrained to the current workflow. + +## Deep reference + +- Playground command: [../../commands/test-tool.md](../../commands/test-tool.md) +- Canonical docs entrypoint: [../../docs/index.md](../../docs/index.md) +- Live discovery model: [../../docs/tool-discovery.md](../../docs/tool-discovery.md) +- Integration workflow: [../agent-auth/SKILL.md](../agent-auth/SKILL.md) diff --git a/plugins/agent-auth/skills/testing-agentkit-tools/scripts/connect.py b/plugins/agent-auth/skills/testing-agentkit-tools/scripts/connect.py new file mode 100644 index 0000000..e628a4d --- /dev/null +++ b/plugins/agent-auth/skills/testing-agentkit-tools/scripts/connect.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +""" +AgentKit playground for Claude Code. + +Supports: +- generate-link +- get-tool +- execute-tool +""" + +import argparse +import json +import os +import sys + +try: + from dotenv import load_dotenv + load_dotenv() +except ImportError: + pass + +try: + from google.protobuf.json_format import MessageToDict +except ImportError: + MessageToDict = None + +import scalekit.client as scalekit_sdk + + +BOLD = "\033[1m" +GREEN = "\033[92m" +RED = "\033[91m" +YELLOW = "\033[93m" +BLUE = "\033[94m" +RESET = "\033[0m" + + +def env_value(primary: str, legacy: str) -> str: + return os.getenv(primary) or os.getenv(legacy) or "" + + +SCALEKIT_ENV_URL = env_value("SCALEKIT_ENV_URL", "TOOL_ENV_URL") +SCALEKIT_CLIENT_ID = env_value("SCALEKIT_CLIENT_ID", "TOOL_CLIENT_ID") +SCALEKIT_CLIENT_SECRET = env_value("SCALEKIT_CLIENT_SECRET", "TOOL_CLIENT_SECRET") + + +def require_env() -> None: + missing = [] + if not SCALEKIT_ENV_URL: + missing.append("SCALEKIT_ENV_URL") + if not SCALEKIT_CLIENT_ID: + missing.append("SCALEKIT_CLIENT_ID") + if not SCALEKIT_CLIENT_SECRET: + missing.append("SCALEKIT_CLIENT_SECRET") + if missing: + print(f"{RED}Missing required environment variables: {', '.join(missing)}{RESET}") + print("Legacy TOOL_* aliases are also supported.") + sys.exit(1) + + +def get_scalekit_client(): + require_env() + return scalekit_sdk.ScalekitClient( + SCALEKIT_ENV_URL, + SCALEKIT_CLIENT_ID, + SCALEKIT_CLIENT_SECRET, + ) + + +def get_connect_client(): + client = get_scalekit_client() + return client.actions if hasattr(client, "actions") else client.connect + + +def to_jsonable(value): + if MessageToDict is not None and hasattr(value, "DESCRIPTOR"): + return MessageToDict(value, preserving_proto_field_name=True) + if isinstance(value, (dict, list, str, int, float, bool)) or value is None: + return value + if isinstance(value, bytes): + return value.decode("utf-8", errors="replace") + if hasattr(value, "__dict__"): + return {key: to_jsonable(val) for key, val in vars(value).items() if not key.startswith("_")} + return str(value) + + +def print_json(value) -> None: + print(json.dumps(to_jsonable(value), indent=2)) + + +def get_or_create_account(connection_name: str, identifier: str): + connect = get_connect_client() + response = connect.get_or_create_connected_account( + connection_name=connection_name, + identifier=identifier, + ) + return response.connected_account + + +def generate_link(connection_name: str, identifier: str) -> None: + connect = get_connect_client() + + print(f" Connection: {connection_name}") + print(f" Identifier: {identifier}") + print() + + try: + connected_account = get_or_create_account(connection_name, identifier) + print(f" Connected Account ID: {connected_account.id}") + print(f" Status: {connected_account.status}") + + if connected_account.status != "ACTIVE": + link_response = connect.get_authorization_link( + connection_name=connection_name, + identifier=identifier, + ) + print(f"\n{YELLOW}⚠ Connected account is not ACTIVE yet.{RESET}") + print(f"\n🔗 Click the link to authorize {connection_name}:") + print(f" {BLUE}{link_response.link}{RESET}") + else: + print(f"\n{GREEN}✅ {connection_name} is already connected and active.{RESET}") + + except Exception as exc: + print(f"\n{RED}❌ Error: {exc}{RESET}") + sys.exit(1) + + +def execute_tool(tool_name: str, connection_name: str, identifier: str, tool_input: dict) -> None: + connect = get_connect_client() + + print(f" Tool: {tool_name}") + print(f" Connection: {connection_name}") + print(f" Identifier: {identifier}") + print(f" Input: {json.dumps(tool_input, indent=2)}") + print() + + try: + connected_account = get_or_create_account(connection_name, identifier) + print(f" Connected Account ID: {connected_account.id}") + print(f" Status: {connected_account.status}") + + if connected_account.status != "ACTIVE": + link_response = connect.get_authorization_link( + connection_name=connection_name, + identifier=identifier, + ) + print(f"\n{YELLOW}⚠ Connected account is not ACTIVE yet.{RESET}") + print(f"\n🔗 Authorize {connection_name} here:") + print(f" {BLUE}{link_response.link}{RESET}") + print(f"\n{YELLOW}Re-run this command after the user completes authorization.{RESET}") + sys.exit(0) + + print(f"\n🔧 Executing tool: {BOLD}{tool_name}{RESET}") + result = connect.execute_tool( + tool_name=tool_name, + identifier=identifier, + connected_account_id=connected_account.id, + tool_input=tool_input, + ) + + print(f"\n{GREEN}✅ Result:{RESET}") + print_json(result) + + except Exception as exc: + print(f"\n{RED}❌ Error: {exc}{RESET}") + sys.exit(1) + + +def get_tool(tool_name: str = None, provider: str = None, page_size: int = None, page_token: str = None) -> None: + client = get_scalekit_client() + + try: + from scalekit.v1.tools.tools_pb2 import Filter + + filter_kwargs = {} + if tool_name: + filter_kwargs["tool_name"] = [tool_name] + if provider: + filter_kwargs["provider"] = provider + + list_kwargs = {} + if filter_kwargs: + list_kwargs["filter"] = Filter(**filter_kwargs) + if page_size is not None: + list_kwargs["page_size"] = page_size + if page_token: + list_kwargs["page_token"] = page_token + + response, _ = client.tools.list_tools(**list_kwargs) + print_json(response) + + except Exception as exc: + print(f"\n{RED}❌ Error: {exc}{RESET}") + sys.exit(1) + + +def main() -> None: + parser = argparse.ArgumentParser( + description="AgentKit playground - generate auth links, fetch tool metadata, and execute tools", + ) + + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("--generate-link", action="store_true", help="Generate authorization link if needed") + group.add_argument("--get-tool", action="store_true", help="Fetch live tool metadata") + group.add_argument("--execute-tool", action="store_true", help="Execute a live tool") + + parser.add_argument("--connection-name", help="Exact dashboard connection name") + parser.add_argument("--identifier", help="User or account identifier") + parser.add_argument("--tool-name", help="Tool name to fetch or execute") + parser.add_argument("--tool-input", help="JSON string passed to the tool") + parser.add_argument("--provider", help="Provider filter for live tool discovery") + parser.add_argument("--page-size", type=int, help="Page size for tool listing") + parser.add_argument("--page-token", help="Pagination token for tool listing") + + args = parser.parse_args() + + if args.generate_link: + if not args.connection_name or not args.identifier: + parser.error("--connection-name and --identifier are required for --generate-link") + generate_link(args.connection_name, args.identifier) + return + + if args.get_tool: + get_tool( + tool_name=args.tool_name, + provider=args.provider, + page_size=args.page_size, + page_token=args.page_token, + ) + return + + if not args.connection_name or not args.identifier or not args.tool_name or not args.tool_input: + parser.error("--connection-name, --identifier, --tool-name, and --tool-input are required for --execute-tool") + + try: + tool_input = json.loads(args.tool_input) + except json.JSONDecodeError as exc: + print(f"{RED}❌ Invalid JSON for --tool-input: {exc}{RESET}") + sys.exit(1) + + execute_tool( + tool_name=args.tool_name, + connection_name=args.connection_name, + identifier=args.identifier, + tool_input=tool_input, + ) + + +if __name__ == "__main__": + main() From 93e21f5572fa6a09435fad6febb8e4e367c69d32 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Tue, 5 May 2026 21:21:29 +0530 Subject: [PATCH 02/53] chore: bump agent-auth plugin to v2 Raise the agent-auth plugin manifest to 2.0.0 so the hybrid AgentKit reorganization can ship as a major update and users can pick up the new version cleanly. Co-authored-by: Cursor --- plugins/agent-auth/.claude-plugin/plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/agent-auth/.claude-plugin/plugin.json b/plugins/agent-auth/.claude-plugin/plugin.json index 9475a92..f3aeedf 100644 --- a/plugins/agent-auth/.claude-plugin/plugin.json +++ b/plugins/agent-auth/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "agent-auth", "description": "Sets up Scalekit AgentKit in Claude Code so agents can authorize users, discover tools, and execute authenticated tool calls across connectors.", - "version": "1.9.0", + "version": "2.0.0", "author": { "name": "Scalekit", "email": "hi@scalekit.com" From 6c8d28a8417a0e4400c42ad3b9a36d2e04cf6f58 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Tue, 5 May 2026 21:25:49 +0530 Subject: [PATCH 03/53] docs: add Claude installer and AgentKit doc links Add a one-line Claude Code installer that adds the marketplace, installs the agent-auth plugin, and points users to enable auto-update. Also thread official docs.scalekit.com AgentKit links through the new canonical docs so the plugin can point back to the published product documentation. Co-authored-by: Cursor --- README.md | 22 ++++++++++-- install.sh | 35 +++++++++++++++++++ plugins/agent-auth/README.md | 6 ++++ plugins/agent-auth/docs/byoc.md | 4 +++ plugins/agent-auth/docs/code-samples.md | 7 ++++ plugins/agent-auth/docs/connected-accounts.md | 5 +++ plugins/agent-auth/docs/connections.md | 5 +++ plugins/agent-auth/docs/connectors/README.md | 4 +++ plugins/agent-auth/docs/index.md | 9 +++++ plugins/agent-auth/docs/tool-discovery.md | 6 ++++ scripts/install_claude_marketplace.sh | 31 ++++++++++++++++ 11 files changed, 132 insertions(+), 2 deletions(-) create mode 100755 install.sh create mode 100755 scripts/install_claude_marketplace.sh diff --git a/README.md b/README.md index 8ed1764..76723f9 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,13 @@ This plugin adds the complete Scalekit auth stack to your projects — whether t ### Installation +```sh +# One-line installer +curl -fsSL https://raw.githubusercontent.com/scalekit-inc/claude-code-authstack/main/install.sh | bash +``` + +Or install manually inside Claude Code: + ```sh # Start Claude REPL claude @@ -31,10 +38,20 @@ claude # Add Scalekit Auth Stack marketplace /plugin marketplace add scalekit-inc/claude-code-authstack -# Run the plugins wizard +# Install AgentKit for AI agents +/plugin install agent-auth@scalekit-auth-stack + +# Open the plugins wizard /plugins ``` +After installation, enable auto-update: + +1. Open `/plugins` +2. Go to `Marketplaces` +3. Select `scalekit-auth-stack` +4. Enable `auto-update` + --- ### Available Plugins @@ -130,7 +147,8 @@ Use this to add login, callback handling, sessions, and logout flows to web apps - [MCP Auth guide](https://docs.scalekit.com/authenticate/mcp/quickstart/) — Secure MCP servers - [Full-stack auth guide](https://docs.scalekit.com/authenticate/fsa/quickstart/) — Add login, callback, and session management - [SCIM directory sync guide](https://docs.scalekit.com/directory/scim/quickstart/) — Provision and deprovision users -- [Agent Auth Guide](https://docs.scalekit.com/agent-auth/quickstart/) — Authentication for AI agents +- [AgentKit overview](https://docs.scalekit.com/agentkit/overview.md) — Connect agents to authenticated tools through connectors, connections, and connected accounts +- [AgentKit quickstart](https://docs.scalekit.com/agentkit/quickstart.md) — Build an agent that makes authenticated tool calls on behalf of users #### Resources diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..07f9907 --- /dev/null +++ b/install.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +set -euo pipefail + +REPO_SLUG="${CLAUDE_CODE_AUTHSTACK_REPO:-scalekit-inc/claude-code-authstack}" +REPO_REF="${CLAUDE_CODE_AUTHSTACK_REF:-main}" +SOURCE_DIR="${CLAUDE_CODE_AUTHSTACK_SOURCE_DIR:-}" + +if [[ -n "$SOURCE_DIR" ]]; then + exec "${SOURCE_DIR%/}/scripts/install_claude_marketplace.sh" +fi + +TMP_DIR="$(mktemp -d)" +cleanup() { + rm -rf "$TMP_DIR" +} +trap cleanup EXIT + +ARCHIVE_URL="https://github.com/${REPO_SLUG}/archive/refs/heads/${REPO_REF}.tar.gz" +ARCHIVE_PATH="$TMP_DIR/claude-code-authstack.tar.gz" + +echo "Downloading Scalekit Auth Stack for Claude Code from:" +echo " $ARCHIVE_URL" + +curl -fsSL "$ARCHIVE_URL" -o "$ARCHIVE_PATH" +tar -xzf "$ARCHIVE_PATH" -C "$TMP_DIR" + +EXTRACTED_DIR="$(find "$TMP_DIR" -mindepth 1 -maxdepth 1 -type d | head -n 1)" + +if [[ -z "$EXTRACTED_DIR" ]] || [[ ! -x "$EXTRACTED_DIR/scripts/install_claude_marketplace.sh" ]]; then + echo "Failed to find installer in downloaded archive." >&2 + exit 1 +fi + +exec "$EXTRACTED_DIR/scripts/install_claude_marketplace.sh" diff --git a/plugins/agent-auth/README.md b/plugins/agent-auth/README.md index 1208999..47ccf84 100644 --- a/plugins/agent-auth/README.md +++ b/plugins/agent-auth/README.md @@ -24,6 +24,12 @@ claude /plugin install agent-auth@scalekit-auth-stack Start with the canonical docs entrypoint at [`docs/index.md`](docs/index.md). +Official Scalekit docs: +- [AgentKit overview](https://docs.scalekit.com/agentkit/overview.md) +- [AgentKit quickstart](https://docs.scalekit.com/agentkit/quickstart.md) +- [AgentKit connectors](https://docs.scalekit.com/agentkit/connectors.md) +- [AgentKit examples](https://docs.scalekit.com/agentkit/examples.md) + ## Skills Reference - `/agent-auth:integrating-agent-auth` Integrates AgentKit into app code or an agent workflow and routes into the core docs. diff --git a/plugins/agent-auth/docs/byoc.md b/plugins/agent-auth/docs/byoc.md index 1ff5319..b4963ff 100644 --- a/plugins/agent-auth/docs/byoc.md +++ b/plugins/agent-auth/docs/byoc.md @@ -11,6 +11,10 @@ Common reasons: - you need stricter compliance or audit ownership - you want a more fully whitelabeled production experience +## Official Scalekit docs + +- [Bring your own credentials](https://docs.scalekit.com/agentkit/advanced/bring-your-own-oauth.md) + ## What changes With BYOC: diff --git a/plugins/agent-auth/docs/code-samples.md b/plugins/agent-auth/docs/code-samples.md index b71f29d..3379b77 100644 --- a/plugins/agent-auth/docs/code-samples.md +++ b/plugins/agent-auth/docs/code-samples.md @@ -20,6 +20,13 @@ Use it to choose an implementation style before opening a larger sample reposito - Direct SDK usage: best for deterministic or single-tool flows - MCP: best when you want tools exposed to MCP-compatible runtimes +## Official Scalekit docs + +- [AgentKit examples](https://docs.scalekit.com/agentkit/examples.md) +- [Code samples](https://docs.scalekit.com/agentkit/code-samples.md) +- [LangChain example](https://docs.scalekit.com/agentkit/examples/langchain.md) +- [Google ADK example](https://docs.scalekit.com/agentkit/examples/google-adk.md) + ## Important rule Do not treat example code as a fixed tool catalog. diff --git a/plugins/agent-auth/docs/connected-accounts.md b/plugins/agent-auth/docs/connected-accounts.md index 9a36988..5d1f52f 100644 --- a/plugins/agent-auth/docs/connected-accounts.md +++ b/plugins/agent-auth/docs/connected-accounts.md @@ -23,6 +23,11 @@ Typical lifecycle: 4. The account becomes `ACTIVE`. 5. AgentKit can execute tools on behalf of that user. +## Official Scalekit docs + +- [Manage connected accounts](https://docs.scalekit.com/agentkit/connected-accounts.md) +- [Authorize a user](https://docs.scalekit.com/agentkit/tools/authorize.md) + ## Operational rules - Always use the correct user identifier from your own system. diff --git a/plugins/agent-auth/docs/connections.md b/plugins/agent-auth/docs/connections.md index 4e7bc17..8b437fd 100644 --- a/plugins/agent-auth/docs/connections.md +++ b/plugins/agent-auth/docs/connections.md @@ -22,6 +22,11 @@ Do not confuse: They are related, but they are not always the same string. +## Official Scalekit docs + +- [Configure a connection](https://docs.scalekit.com/agentkit/connections.md) +- [Scopes and permissions](https://docs.scalekit.com/agentkit/authentication/scopes-permissions.md) + ## Typical setup flow 1. Go to `AgentKit -> Connections` in the Scalekit Dashboard. diff --git a/plugins/agent-auth/docs/connectors/README.md b/plugins/agent-auth/docs/connectors/README.md index 2b30396..ac98d20 100644 --- a/plugins/agent-auth/docs/connectors/README.md +++ b/plugins/agent-auth/docs/connectors/README.md @@ -11,6 +11,10 @@ Connector notes are intentionally curated and lightweight. They should explain: They should not claim to be the exhaustive current tool catalog. +## Official Scalekit docs + +- [Agent connectors](https://docs.scalekit.com/agentkit/connectors.md) + ## Source of truth Use live AgentKit metadata for: diff --git a/plugins/agent-auth/docs/index.md b/plugins/agent-auth/docs/index.md index bd1d633..d584cbf 100644 --- a/plugins/agent-auth/docs/index.md +++ b/plugins/agent-auth/docs/index.md @@ -12,6 +12,15 @@ Use it for the durable AgentKit model: The Claude Code plugin still includes adapter/runtime files such as `.claude-plugin/`, `.mcp.json`, `commands/`, `hooks/`, and `agents/`, but those are secondary to the content model here. +## Official Scalekit docs + +These pages are the best external entrypoints for the material in this directory: + +- [AgentKit overview](https://docs.scalekit.com/agentkit/overview.md) +- [AgentKit quickstart](https://docs.scalekit.com/agentkit/quickstart.md) +- [AgentKit connectors](https://docs.scalekit.com/agentkit/connectors.md) +- [AgentKit examples](https://docs.scalekit.com/agentkit/examples.md) + ## How this directory is organized - [connections.md](connections.md) explains how AgentKit connections are configured and named. diff --git a/plugins/agent-auth/docs/tool-discovery.md b/plugins/agent-auth/docs/tool-discovery.md index cf35130..49643e5 100644 --- a/plugins/agent-auth/docs/tool-discovery.md +++ b/plugins/agent-auth/docs/tool-discovery.md @@ -25,6 +25,12 @@ Use live metadata for: - optional input fields - output shape +## Official Scalekit docs + +- [Tools overview](https://docs.scalekit.com/agentkit/tools/overview.md) +- [Scalekit optimized built-in tools](https://docs.scalekit.com/agentkit/tools/scalekit-optimized-tools.md) +- [AgentKit connectors](https://docs.scalekit.com/agentkit/connectors.md) + ## Discovery workflow 1. Start from a connector or exact tool name. diff --git a/scripts/install_claude_marketplace.sh b/scripts/install_claude_marketplace.sh new file mode 100755 index 0000000..ce1c53f --- /dev/null +++ b/scripts/install_claude_marketplace.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if ! command -v claude >/dev/null 2>&1; then + echo "Claude Code CLI is not installed or not on PATH." >&2 + echo "Install Claude Code first, then re-run this installer." >&2 + exit 1 +fi + +MARKETPLACE_SLUG="${CLAUDE_CODE_AUTHSTACK_MARKETPLACE:-scalekit-inc/claude-code-authstack}" +PLUGIN_SOURCE="${CLAUDE_CODE_AUTHSTACK_PLUGIN_SOURCE:-agent-auth@scalekit-auth-stack}" + +echo "Installing Scalekit Auth Stack for Claude Code" +echo "Marketplace: $MARKETPLACE_SLUG" +echo "Plugin: $PLUGIN_SOURCE" +echo + +claude plugin marketplace add "$MARKETPLACE_SLUG" +claude plugin install "$PLUGIN_SOURCE" + +cat < Date: Tue, 5 May 2026 21:28:49 +0530 Subject: [PATCH 04/53] docs: add llms and sitemap doc entrypoints Add the stable llms.txt and sitemap-0.xml links to the main Claude Code auth stack docs and the canonical agent-auth docs entrypoints so users and agents have clear fallback indexes into published Scalekit documentation. Co-authored-by: Cursor --- README.md | 2 ++ plugins/agent-auth/README.md | 2 ++ plugins/agent-auth/docs/index.md | 2 ++ 3 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 76723f9..b759034 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,8 @@ Use this to add login, callback handling, sessions, and logout flows to web apps #### Documentation - [Scalekit Documentation](https://docs.scalekit.com) — Complete guides and API reference +- [LLM docs map](https://docs.scalekit.com/llms.txt) — High-level index of published Scalekit documentation sets +- [Docs sitemap](https://docs.scalekit.com/sitemap-0.xml) — Stable sitemap for discovering published docs pages - [Build with AI overview](https://docs.scalekit.com/dev-kit/build-with-ai/) — Claude, Codex, Copilot CLI, Cursor setup flows - [Modular SSO guide](https://docs.scalekit.com/authenticate/sso/add-modular-sso/) — Implement enterprise SSO - [MCP Auth guide](https://docs.scalekit.com/authenticate/mcp/quickstart/) — Secure MCP servers diff --git a/plugins/agent-auth/README.md b/plugins/agent-auth/README.md index 47ccf84..785173f 100644 --- a/plugins/agent-auth/README.md +++ b/plugins/agent-auth/README.md @@ -25,6 +25,8 @@ claude /plugin install agent-auth@scalekit-auth-stack Start with the canonical docs entrypoint at [`docs/index.md`](docs/index.md). Official Scalekit docs: +- [LLM docs map](https://docs.scalekit.com/llms.txt) +- [Docs sitemap](https://docs.scalekit.com/sitemap-0.xml) - [AgentKit overview](https://docs.scalekit.com/agentkit/overview.md) - [AgentKit quickstart](https://docs.scalekit.com/agentkit/quickstart.md) - [AgentKit connectors](https://docs.scalekit.com/agentkit/connectors.md) diff --git a/plugins/agent-auth/docs/index.md b/plugins/agent-auth/docs/index.md index d584cbf..891c359 100644 --- a/plugins/agent-auth/docs/index.md +++ b/plugins/agent-auth/docs/index.md @@ -16,6 +16,8 @@ The Claude Code plugin still includes adapter/runtime files such as `.claude-plu These pages are the best external entrypoints for the material in this directory: +- [LLM docs map](https://docs.scalekit.com/llms.txt) +- [Docs sitemap](https://docs.scalekit.com/sitemap-0.xml) - [AgentKit overview](https://docs.scalekit.com/agentkit/overview.md) - [AgentKit quickstart](https://docs.scalekit.com/agentkit/quickstart.md) - [AgentKit connectors](https://docs.scalekit.com/agentkit/connectors.md) From 23e43b1dd3d2802a9d080cd15e2bbeae763341eb Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Tue, 5 May 2026 21:51:54 +0530 Subject: [PATCH 05/53] docs: make testing skill the canonical playground Move the live AgentKit discovery and execution workflow into the testing skill so the plugin follows the modern skills-first structure. Keep `/test-tool` only as a legacy compatibility alias to preserve older usage without making `commands/` the source of truth. Co-authored-by: Cursor --- plugins/agent-auth/README.md | 14 +-- plugins/agent-auth/commands/test-tool.md | 87 +++------------ plugins/agent-auth/docs/code-samples.md | 2 +- plugins/agent-auth/docs/tool-discovery.md | 3 +- .../agent-auth/references/tool-discovery.md | 8 +- .../discovering-agentkit-tools/SKILL.md | 11 +- .../skills/testing-agentkit-tools/SKILL.md | 103 ++++++++++++++++-- 7 files changed, 132 insertions(+), 96 deletions(-) diff --git a/plugins/agent-auth/README.md b/plugins/agent-auth/README.md index 785173f..2509297 100644 --- a/plugins/agent-auth/README.md +++ b/plugins/agent-auth/README.md @@ -11,7 +11,7 @@ Canonical content lives in: Claude runtime files remain in place as adapters: - `.claude-plugin/` - `.mcp.json` -- `commands/` +- `commands/` for legacy slash-command aliases and compatibility shims - `hooks/` - `agents/` @@ -38,15 +38,15 @@ Official Scalekit docs: - `/agent-auth:discovering-agentkit-tools` Uses live AgentKit metadata to find tools, inspect schemas, and narrow the tool set. - `/agent-auth:testing-agentkit-tools` - Generates authorization links, fetches live tool metadata, and executes tools from Claude Code. + Generates authorization links, fetches live tool metadata, and executes tools from Claude Code. This is the preferred runnable playground surface. - `/agent-auth:building-agent-mcp-server` Exposes AgentKit tools through MCP for MCP-compatible runtimes. - `/agent-auth:production-readiness-scalekit` Runs a structured production-readiness checklist for AgentKit integrations. -Command: +Legacy command alias: - `/test-tool [generate-link|get-tool|execute-tool ...]` - Runs the live AgentKit playground command. + Compatibility wrapper for older usage. Prefer `/agent-auth:testing-agentkit-tools ...`. ## Configuration Required environment variables: @@ -80,9 +80,9 @@ Typical flow for a new connector integration: 1. Read [`docs/index.md`](docs/index.md) for the canonical model and [`docs/connections.md`](docs/connections.md) for connection naming. 2. Create the connection in `AgentKit -> Connections`. 3. Use `/agent-auth:integrating-agent-auth` to scaffold connected-account creation and authorization. -4. Use `/agent-auth:discovering-agentkit-tools` or `/test-tool get-tool --provider GMAIL` to inspect the live tool catalog and schema. -5. Use `/test-tool generate-link --connection-name --identifier user_123` if the user still needs to authorize. -6. Use `/test-tool execute-tool --tool-name gmail_fetch_mails --connection-name --identifier user_123 --tool-input '{"query":"is:unread","max_results":5}'` to validate the payload before wiring it into application code. +4. Use `/agent-auth:discovering-agentkit-tools` or `/agent-auth:testing-agentkit-tools get-tool --provider GMAIL` to inspect the live tool catalog and schema. +5. Use `/agent-auth:testing-agentkit-tools generate-link --connection-name --identifier user_123` if the user still needs to authorize. +6. Use `/agent-auth:testing-agentkit-tools execute-tool --tool-name gmail_fetch_mails --connection-name --identifier user_123 --tool-input '{"query":"is:unread","max_results":5}'` to validate the payload before wiring it into application code. ## Troubleshooting 1. No tools show up for a connector: diff --git a/plugins/agent-auth/commands/test-tool.md b/plugins/agent-auth/commands/test-tool.md index 638bc06..36bac22 100644 --- a/plugins/agent-auth/commands/test-tool.md +++ b/plugins/agent-auth/commands/test-tool.md @@ -1,91 +1,34 @@ --- -description: Test live AgentKit discovery and tool execution from Claude Code +description: Legacy compatibility alias for the AgentKit testing skill argument-hint: "[generate-link|get-tool|execute-tool] [args...]" allowed-tools: Bash --- -# AgentKit Tool Tester +# Legacy AgentKit Tool Tester -Test live AgentKit flows using the bundled Python playground script. +This command is a legacy compatibility alias for `/agent-auth:testing-agentkit-tools`. -**Arguments:** $ARGUMENTS - -## Operations - -### generate-link -Usage: `generate-link --connection-name --identifier ` - -Creates or fetches the connected account and prints an authorization link if the account is not yet `ACTIVE`. +Prefer invoking the skill directly: -### get-tool -Usage: `get-tool [--tool-name ] [--provider ] [--page-size ] [--page-token ]` - -Fetches live tool metadata and prints the raw JSON response. Omitting `--tool-name` returns all matching tools for the filter. - -### execute-tool -Usage: `execute-tool --tool-name --connection-name --identifier --tool-input ''` +```text +/agent-auth:testing-agentkit-tools $ARGUMENTS +``` -Creates or fetches the connected account, prints an authorization link if needed, and executes the tool. +**Arguments:** $ARGUMENTS ## Your task -Parse `$ARGUMENTS` to determine the operation, then run the bundled script from the plugin root: +Parse `$ARGUMENTS` exactly as the testing skill would and run the same bundled script from the plugin root: ```bash skills/testing-agentkit-tools/scripts/connect.py ``` -### Runner selection - -Check which runner is available by running `which uv` once before any Python command: - -- if `uv` exists, use `uv run python` -- otherwise use `python3` -- if `python3` is unavailable, fall back to `python` - -### Credentials - -Before running any operation, check for these environment variables: - -- `SCALEKIT_ENV_URL` -- `SCALEKIT_CLIENT_ID` -- `SCALEKIT_CLIENT_SECRET` - -Also accept legacy aliases: - -- `TOOL_ENV_URL` -- `TOOL_CLIENT_ID` -- `TOOL_CLIENT_SECRET` - -If none of the supported variables are available, ask the user for the missing values before proceeding. Do not write secrets into source-controlled files unless the user explicitly asks you to. - -### Commands to run - -If operation is `generate-link`, run: - -```bash - skills/testing-agentkit-tools/scripts/connect.py --generate-link --connection-name --identifier -``` - -If operation is `get-tool`, run: - -```bash - skills/testing-agentkit-tools/scripts/connect.py --get-tool [--tool-name ] [--provider ] [--page-size ] [--page-token ] -``` - -If operation is `execute-tool`, run: - -```bash - skills/testing-agentkit-tools/scripts/connect.py --execute-tool --tool-name --connection-name --identifier --tool-input '' -``` - -If `tool_input` is missing for `execute-tool`, inspect the live tool metadata first or ask the user for the missing input values. - -### After running +Keep this command behavior aligned with the skill: -Show: +- use `uv run python` when `uv` exists, otherwise `python3`, otherwise `python` +- accept both `SCALEKIT_*` and legacy `TOOL_*` credential variables +- inspect live metadata before guessing `tool_input` +- show the command output, exact command, resolved parameters, and exact payload for `execute-tool` -1. the command output -2. the exact command that was run -3. the resolved parameters in a small structured summary -4. for `execute-tool`, the exact JSON payload that was passed to AgentKit +Do not maintain separate workflow rules here. The testing skill is the source of truth. diff --git a/plugins/agent-auth/docs/code-samples.md b/plugins/agent-auth/docs/code-samples.md index 3379b77..c65d62a 100644 --- a/plugins/agent-auth/docs/code-samples.md +++ b/plugins/agent-auth/docs/code-samples.md @@ -8,7 +8,7 @@ Use it to choose an implementation style before opening a larger sample reposito | Goal | Recommended path | |---|---| -| Validate one tool quickly | Use `/test-tool` with `testing-agentkit-tools` | +| Validate one tool quickly | Use `/agent-auth:testing-agentkit-tools` | | Integrate AgentKit into app code | Use `integrating-agent-auth` | | Build an agent with a framework | Use framework-specific examples below | | Expose tools over MCP | Use `building-agent-mcp-server` | diff --git a/plugins/agent-auth/docs/tool-discovery.md b/plugins/agent-auth/docs/tool-discovery.md index 49643e5..97a4f09 100644 --- a/plugins/agent-auth/docs/tool-discovery.md +++ b/plugins/agent-auth/docs/tool-discovery.md @@ -61,7 +61,8 @@ Use: - `discovering-agentkit-tools` when the user needs current tools or schemas - `testing-agentkit-tools` when the user wants to run a live tool and inspect the exact payload -- `/test-tool get-tool ...` for the runnable playground flow +- `/agent-auth:testing-agentkit-tools get-tool ...` for the preferred runnable playground flow +- `/test-tool get-tool ...` only as a legacy compatibility alias ## Fallback rule diff --git a/plugins/agent-auth/references/tool-discovery.md b/plugins/agent-auth/references/tool-discovery.md index 9c472e6..872d5e5 100644 --- a/plugins/agent-auth/references/tool-discovery.md +++ b/plugins/agent-auth/references/tool-discovery.md @@ -46,13 +46,15 @@ If the metadata contains pagination or large result fields, mention them so the ## How to use this in Claude Code -For interactive discovery, use the live playground command: +For interactive discovery, prefer the testing skill: ```sh -/test-tool get-tool --provider GMAIL -/test-tool get-tool --tool-name gmail_fetch_mails +/agent-auth:testing-agentkit-tools get-tool --provider GMAIL +/agent-auth:testing-agentkit-tools get-tool --tool-name gmail_fetch_mails ``` +The legacy `/test-tool ...` alias still works for compatibility, but it is no longer the canonical path. + For implementation guidance, use: - `discovering-agentkit-tools` when the user needs the current tool list or schema diff --git a/plugins/agent-auth/skills/discovering-agentkit-tools/SKILL.md b/plugins/agent-auth/skills/discovering-agentkit-tools/SKILL.md index 3a29c71..7710348 100644 --- a/plugins/agent-auth/skills/discovering-agentkit-tools/SKILL.md +++ b/plugins/agent-auth/skills/discovering-agentkit-tools/SKILL.md @@ -22,16 +22,17 @@ Use this skill when the user asks: ## Discovery workflow 1. Identify the target connector or exact tool name. -2. Prefer live lookup through the built-in testing workflow or SDK metadata. -3. Summarize: +2. Prefer live lookup through `/agent-auth:testing-agentkit-tools get-tool --provider ` or `/agent-auth:testing-agentkit-tools get-tool --tool-name `. +3. If older docs or muscle memory mention `/test-tool`, treat it as a legacy compatibility alias for the testing skill rather than the canonical workflow. +4. Summarize: - tool name - connector - what the tool does - required fields from `input_schema.required` - optional fields from `input_schema.properties` - important fields from `output_schema.properties` -4. Recommend the smallest useful tool set for the workflow. -5. If live credentials are unavailable, use the connector notes only as a fallback and say they may be stale. +5. Recommend the smallest useful tool set for the workflow. +6. If live credentials are unavailable, use the connector notes only as a fallback and say they may be stale. ## Terminology @@ -46,11 +47,13 @@ Use `connector` in explanations. Only use `provider` when the SDK or API filter - `connection_name` is the exact dashboard value and may not equal the connector slug. - Tool metadata is the durable way to determine current inputs and outputs. +- The preferred runnable surface is the testing skill in `skills/testing-agentkit-tools/`, not the legacy `commands/` alias. - Restrict the tool set before handing it to an LLM. Fewer relevant tools improve tool selection and parameter filling. ## Deep reference - Canonical docs entrypoint: [../../docs/index.md](../../docs/index.md) - Live discovery model: [../../docs/tool-discovery.md](../../docs/tool-discovery.md) +- Runnable testing workflow: [../testing-agentkit-tools/SKILL.md](../testing-agentkit-tools/SKILL.md) - Curated connector notes: [../../docs/connectors/README.md](../../docs/connectors/README.md) - Broader implementation examples: [../../docs/code-samples.md](../../docs/code-samples.md) diff --git a/plugins/agent-auth/skills/testing-agentkit-tools/SKILL.md b/plugins/agent-auth/skills/testing-agentkit-tools/SKILL.md index e989cff..ea25ef2 100644 --- a/plugins/agent-auth/skills/testing-agentkit-tools/SKILL.md +++ b/plugins/agent-auth/skills/testing-agentkit-tools/SKILL.md @@ -1,11 +1,16 @@ --- name: testing-agentkit-tools description: Tests live Scalekit AgentKit flows from Claude Code by generating authorization links, fetching tool metadata, and executing a tool for a connected account. Use when a user wants to validate a connector, inspect the exact payload for `execute_tool`, or build a workflow step by step in the editor. +argument-hint: "[generate-link|get-tool|execute-tool] [args...]" +disable-model-invocation: true +allowed-tools: Bash --- # Testing AgentKit Tools -This skill is the live playground layer for AgentKit inside Claude Code. +This skill is the canonical live playground layer for AgentKit inside Claude Code. + +**Arguments:** $ARGUMENTS Use it to: @@ -26,14 +31,96 @@ Use it to: 4. Execute the tool with the smallest valid `tool_input`. 5. Show the exact command and payload used so the user can translate it into app code. -## Command surface +## Preferred invocation + +Invoke this skill directly for the runnable playground: + +- `/agent-auth:testing-agentkit-tools get-tool --provider GMAIL` +- `/agent-auth:testing-agentkit-tools get-tool --tool-name gmail_fetch_mails` +- `/agent-auth:testing-agentkit-tools generate-link --connection-name MY_GMAIL --identifier user_123` +- `/agent-auth:testing-agentkit-tools execute-tool --tool-name gmail_fetch_mails --connection-name MY_GMAIL --identifier user_123 --tool-input '{"query":"is:unread","max_results":5}'` + +`/test-tool` remains available only as a legacy compatibility alias. + +## Operations + +### generate-link +Usage: `generate-link --connection-name --identifier ` + +Creates or fetches the connected account and prints an authorization link if the account is not yet `ACTIVE`. + +### get-tool +Usage: `get-tool [--tool-name ] [--provider ] [--page-size ] [--page-token ]` + +Fetches live tool metadata and prints the raw JSON response. Omitting `--tool-name` returns all matching tools for the filter. + +### execute-tool +Usage: `execute-tool --tool-name --connection-name --identifier --tool-input ''` + +Creates or fetches the connected account, prints an authorization link if needed, and executes the tool. + +## Your task + +Parse `$ARGUMENTS` to determine the operation, then run the bundled script from this skill directory: + +```bash +${CLAUDE_SKILL_DIR}/scripts/connect.py +``` + +### Runner selection + +Check which runner is available by running `which uv` once before any Python command: + +- if `uv` exists, use `uv run python` +- otherwise use `python3` +- if `python3` is unavailable, fall back to `python` + +### Credentials + +Before running any operation, check for these environment variables: + +- `SCALEKIT_ENV_URL` +- `SCALEKIT_CLIENT_ID` +- `SCALEKIT_CLIENT_SECRET` + +Also accept legacy aliases: + +- `TOOL_ENV_URL` +- `TOOL_CLIENT_ID` +- `TOOL_CLIENT_SECRET` + +If none of the supported variables are available, ask the user for the missing values before proceeding. Do not write secrets into source-controlled files unless the user explicitly asks you to. + +### Commands to run + +If operation is `generate-link`, run: + +```bash + "${CLAUDE_SKILL_DIR}/scripts/connect.py" --generate-link --connection-name --identifier +``` + +If operation is `get-tool`, run: + +```bash + "${CLAUDE_SKILL_DIR}/scripts/connect.py" --get-tool [--tool-name ] [--provider ] [--page-size ] [--page-token ] +``` + +If operation is `execute-tool`, run: + +```bash + "${CLAUDE_SKILL_DIR}/scripts/connect.py" --execute-tool --tool-name --connection-name --identifier --tool-input '' +``` + +If `tool_input` is missing for `execute-tool`, inspect the live tool metadata first or ask the user for the missing input values. + +### After running -Use `/test-tool` for the runnable playground: +Show: -- `/test-tool get-tool --provider GMAIL` -- `/test-tool get-tool --tool-name gmail_fetch_mails` -- `/test-tool generate-link --connection-name MY_GMAIL --identifier user_123` -- `/test-tool execute-tool --tool-name gmail_fetch_mails --connection-name MY_GMAIL --identifier user_123 --tool-input '{"query":"is:unread","max_results":5}'` +1. the command output +2. the exact command that was run +3. the resolved parameters in a small structured summary +4. for `execute-tool`, the exact JSON payload that was passed to AgentKit ## Guardrails @@ -44,7 +131,7 @@ Use `/test-tool` for the runnable playground: ## Deep reference -- Playground command: [../../commands/test-tool.md](../../commands/test-tool.md) +- Legacy alias: [../../commands/test-tool.md](../../commands/test-tool.md) - Canonical docs entrypoint: [../../docs/index.md](../../docs/index.md) - Live discovery model: [../../docs/tool-discovery.md](../../docs/tool-discovery.md) - Integration workflow: [../agent-auth/SKILL.md](../agent-auth/SKILL.md) From 1e9bc2d1f81892450a2d55ec684a55cc15b54605 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Fri, 8 May 2026 19:48:17 +0530 Subject: [PATCH 06/53] Consolidate 5 plugins into AgentKit and SaaSKit Restructure the marketplace from 5 separate plugins (agent-auth, full-stack-auth, mcp-auth, modular-sso, modular-scim) into 2 focused plugins: AgentKit (AI agent authentication) and SaaSKit (B2B SaaS auth). AgentKit (5 skills): - Renamed from agent-auth, adopting Scalekit AgentKit branding - Added docs/ + rules/ content model from PR #19 - Skills: integrating, discovering tools, testing tools, MCP server, production readiness SaaSKit (11 skills): - New plugin consolidating FSA, SSO, SCIM, and MCP server auth - 13 docs files including framework guides (Python, Next.js, Go, Spring Boot, Laravel) - Thin skills routing into docs/ layer - Skills: core auth, sessions, access control, SSO, SCIM, MCP OAuth, API auth, Python frameworks, Next.js, migration, production readiness - Consolidated agents, commands, hooks, and references from all 4 source plugins Both plugins at version 2.0.0 with the docs/ + rules/ + thin skills/ content model. --- CHANGELOG.md | 40 +- CLAUDE.md | 38 +- README.md | 66 +- .../.claude-plugin/plugin.json | 2 +- plugins/{agent-auth => agentkit}/.env.example | 0 plugins/{agent-auth => agentkit}/.mcp.json | 0 plugins/{agent-auth => agentkit}/README.md | 22 +- .../agents/setup-scalekit.md | 9 +- .../commands/test-tool.md | 4 +- plugins/{agent-auth => agentkit}/docs/byoc.md | 0 .../docs/code-samples.md | 4 +- .../docs/connected-accounts.md | 0 .../docs/connections.md | 0 .../docs/connectors/README.md | 0 .../{agent-auth => agentkit}/docs/index.md | 2 +- .../docs/tool-discovery.md | 2 +- .../{agent-auth => agentkit}/hooks/beacon.sh | 0 .../{agent-auth => agentkit}/hooks/hooks.json | 4 +- .../references/agent-connectors/README.md | 0 .../references/agent-connectors/airtable.md | 0 .../references/agent-connectors/asana.md | 0 .../references/agent-connectors/attention.md | 0 .../references/agent-connectors/bigquery.md | 0 .../references/agent-connectors/chorus.md | 0 .../agent-connectors/clari_copilot.md | 0 .../references/agent-connectors/clickup.md | 0 .../references/agent-connectors/confluence.md | 0 .../references/agent-connectors/dropbox.md | 0 .../references/agent-connectors/fathom.md | 0 .../references/agent-connectors/freshdesk.md | 0 .../references/agent-connectors/github.md | 0 .../references/agent-connectors/gmail.md | 0 .../references/agent-connectors/gong.md | 0 .../references/agent-connectors/google_ads.md | 0 .../agent-connectors/google_docs.md | 0 .../agent-connectors/google_drive.md | 0 .../agent-connectors/google_forms.md | 0 .../agent-connectors/google_meets.md | 0 .../agent-connectors/google_sheets.md | 0 .../agent-connectors/google_slides.md | 0 .../agent-connectors/googlecalendar.md | 0 .../references/agent-connectors/hubspot.md | 0 .../references/agent-connectors/intercom.md | 0 .../references/agent-connectors/jira.md | 0 .../references/agent-connectors/linear.md | 0 .../agent-connectors/microsoft_excel.md | 0 .../agent-connectors/microsoft_teams.md | 0 .../agent-connectors/microsoft_word.md | 0 .../references/agent-connectors/monday.md | 0 .../references/agent-connectors/notion.md | 0 .../references/agent-connectors/onedrive.md | 0 .../references/agent-connectors/onenote.md | 0 .../references/agent-connectors/outlook.md | 0 .../references/agent-connectors/salesforce.md | 0 .../references/agent-connectors/servicenow.md | 0 .../references/agent-connectors/sharepoint.md | 0 .../references/agent-connectors/slack.md | 0 .../references/agent-connectors/snowflake.md | 0 .../references/agent-connectors/trello.md | 0 .../references/agent-connectors/zendesk.md | 0 .../references/agent-connectors/zoom.md | 0 .../references/byoc.md | 0 .../references/code-samples.md | 0 .../references/connected-accounts.md | 0 .../references/connections.md | 0 .../references/providers.md | 0 .../references/redirects.md | 0 .../references/tool-discovery.md | 6 +- .../rules/live-metadata-first.md | 0 .../rules/terminology.md | 0 .../rules/tool-selection.md | 0 .../building-agentkit-mcp-server}/SKILL.md | 4 +- .../discovering-agentkit-tools/SKILL.md | 2 +- .../skills/integrating-agentkit}/SKILL.md | 2 +- .../production-readiness-agentkit}/SKILL.md | 2 +- .../skills/testing-agentkit-tools/SKILL.md | 10 +- .../testing-agentkit-tools/scripts/connect.py | 0 .../.claude-plugin/plugin.json | 13 - plugins/full-stack-auth/.mcp.json | 8 - .../full-stack-auth/agents/setup-scalekit.md | 42 - plugins/full-stack-auth/hooks/hooks.json | 16 - .../skills/adding-api-key-auth/SKILL.md | 380 --------- .../skills/adding-oauth2-to-apis/SKILL.md | 232 ------ .../skills/full-stack-auth/SKILL.md | 95 --- .../skills/implement-logout/SKILL.md | 187 ----- .../implementing-access-control/SKILL.md | 119 --- .../skills/implementing-admin-portal/SKILL.md | 152 ---- .../SKILL.md | 294 ------- .../SKILL.md | 460 ---------- .../implementing-scalekit-flask-auth/SKILL.md | 585 ------------- .../SKILL.md | 338 -------- .../SKILL.md | 237 ------ .../skills/manage-user-sessions/SKILL.md | 351 -------- .../migrating-to-scalekit-auth/SKILL.md | 148 ---- .../production-readiness-scalekit/SKILL.md | 159 ---- plugins/mcp-auth/.claude-plugin/plugin.json | 13 - plugins/mcp-auth/.mcp.json | 8 - plugins/mcp-auth/README.md | 254 ------ plugins/mcp-auth/hooks/beacon.sh | 20 - plugins/mcp-auth/hooks/hooks.json | 16 - plugins/mcp-auth/references/redirects.md | 76 -- .../mcp-auth/skills/add-auth-fastmcp/SKILL.md | 446 ---------- .../skills/express-mcp-server/SKILL.md | 783 ------------------ .../mcp-auth/skills/fastapi-fastmcp/SKILL.md | 411 --------- plugins/mcp-auth/skills/mcp-auth/SKILL.md | 450 ---------- .../production-readiness-scalekit/SKILL.md | 62 -- .../modular-scim/.claude-plugin/plugin.json | 13 - plugins/modular-scim/.mcp.json | 8 - plugins/modular-scim/agents/setup-scalekit.md | 42 - plugins/modular-scim/hooks/beacon.sh | 20 - plugins/modular-scim/hooks/hooks.json | 16 - plugins/modular-scim/references/redirects.md | 76 -- .../skills/implementing-admin-portal/SKILL.md | 155 ---- .../modular-scim/skills/modular-scim/SKILL.md | 230 ----- .../production-readiness-scalekit/SKILL.md | 86 -- .../modular-sso/.claude-plugin/plugin.json | 13 - plugins/modular-sso/.mcp.json | 8 - plugins/modular-sso/agents/setup-scalekit.md | 42 - plugins/modular-sso/commands/dryrun.md | 21 - plugins/modular-sso/hooks/beacon.sh | 20 - plugins/modular-sso/hooks/hooks.json | 16 - plugins/modular-sso/references/redirects.md | 76 -- .../skills/implementing-admin-portal/SKILL.md | 153 ---- .../modular-sso/skills/modular-sso/SKILL.md | 574 ------------- .../production-readiness-scalekit/SKILL.md | 110 --- plugins/saaskit/.claude-plugin/plugin.json | 13 + plugins/saaskit/.env.example | 3 + plugins/saaskit/README.md | 85 ++ .../scalekit-mcp-auth-troubleshooter.md | 0 .../agents/scalekit-mcp-helper.md | 0 .../agents/sdk-version-advisor.md | 0 .../agents/session-management-reviewer.md | 12 +- .../agents/setup-scalekit.md | 9 +- .../commands/dryrun.md | 0 plugins/saaskit/docs/access-control.md | 118 +++ plugins/saaskit/docs/api-auth.md | 161 ++++ plugins/saaskit/docs/auth-flows.md | 124 +++ plugins/saaskit/docs/frameworks/go.md | 161 ++++ plugins/saaskit/docs/frameworks/laravel.md | 127 +++ plugins/saaskit/docs/frameworks/nextjs.md | 114 +++ plugins/saaskit/docs/frameworks/python.md | 166 ++++ plugins/saaskit/docs/frameworks/springboot.md | 136 +++ plugins/saaskit/docs/index.md | 46 + plugins/saaskit/docs/mcp-server-auth.md | 153 ++++ plugins/saaskit/docs/scim.md | 125 +++ plugins/saaskit/docs/sessions.md | 113 +++ plugins/saaskit/docs/sso.md | 115 +++ .../hooks/beacon.sh | 0 plugins/saaskit/hooks/hooks.json | 16 + .../references/bring-your-own-auth.md | 0 .../references/redirects.md | 0 .../references/scalekit-logs.md | 0 .../references/scalekit-mcp-server.md | 0 .../references/scalekit-user-profiles.md | 0 plugins/saaskit/rules/redirect-urls.md | 21 + plugins/saaskit/rules/terminology.md | 20 + .../saaskit/skills/adding-api-auth/SKILL.md | 54 ++ .../saaskit/skills/adding-mcp-oauth/SKILL.md | 48 ++ .../adding-mcp-oauth/express-reference.md | 144 ++++ .../adding-mcp-oauth/fastapi-reference.md | 161 ++++ .../adding-mcp-oauth/fastmcp-reference.md | 136 +++ .../implementing-access-control/SKILL.md | 74 ++ .../skills/implementing-modular-sso/SKILL.md | 57 ++ .../implementing-saaskit-nextjs/SKILL.md | 68 ++ .../implementing-saaskit-python/SKILL.md | 68 ++ .../skills/implementing-saaskit/SKILL.md | 79 ++ .../implementing-saaskit/go-reference.md} | 89 +- .../implementing-saaskit/laravel-reference.md | 208 +++++ .../springboot-reference.md} | 77 +- .../implementing-scim-provisioning/SKILL.md | 57 ++ .../skills/managing-saaskit-sessions/SKILL.md | 66 ++ .../migrating-to-saaskit}/AUDIT-CHECKLIST.md | 0 .../migrating-to-saaskit}/IMPORT-SAMPLES.md | 17 + .../skills/migrating-to-saaskit/SKILL.md | 74 ++ .../production-readiness-saaskit/SKILL.md | 66 ++ scripts/install_claude_marketplace.sh | 13 +- 176 files changed, 3358 insertions(+), 8289 deletions(-) rename plugins/{agent-auth => agentkit}/.claude-plugin/plugin.json (95%) rename plugins/{agent-auth => agentkit}/.env.example (100%) rename plugins/{agent-auth => agentkit}/.mcp.json (100%) rename plugins/{agent-auth => agentkit}/README.md (76%) rename plugins/{agent-auth => agentkit}/agents/setup-scalekit.md (83%) rename plugins/{agent-auth => agentkit}/commands/test-tool.md (87%) rename plugins/{agent-auth => agentkit}/docs/byoc.md (100%) rename plugins/{agent-auth => agentkit}/docs/code-samples.md (92%) rename plugins/{agent-auth => agentkit}/docs/connected-accounts.md (100%) rename plugins/{agent-auth => agentkit}/docs/connections.md (100%) rename plugins/{agent-auth => agentkit}/docs/connectors/README.md (100%) rename plugins/{agent-auth => agentkit}/docs/index.md (98%) rename plugins/{agent-auth => agentkit}/docs/tool-discovery.md (95%) rename plugins/{agent-auth => agentkit}/hooks/beacon.sh (100%) rename plugins/{agent-auth => agentkit}/hooks/hooks.json (74%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/README.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/airtable.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/asana.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/attention.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/bigquery.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/chorus.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/clari_copilot.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/clickup.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/confluence.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/dropbox.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/fathom.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/freshdesk.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/github.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/gmail.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/gong.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/google_ads.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/google_docs.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/google_drive.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/google_forms.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/google_meets.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/google_sheets.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/google_slides.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/googlecalendar.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/hubspot.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/intercom.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/jira.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/linear.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/microsoft_excel.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/microsoft_teams.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/microsoft_word.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/monday.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/notion.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/onedrive.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/onenote.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/outlook.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/salesforce.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/servicenow.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/sharepoint.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/slack.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/snowflake.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/trello.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/zendesk.md (100%) rename plugins/{agent-auth => agentkit}/references/agent-connectors/zoom.md (100%) rename plugins/{agent-auth => agentkit}/references/byoc.md (100%) rename plugins/{agent-auth => agentkit}/references/code-samples.md (100%) rename plugins/{agent-auth => agentkit}/references/connected-accounts.md (100%) rename plugins/{agent-auth => agentkit}/references/connections.md (100%) rename plugins/{agent-auth => agentkit}/references/providers.md (100%) rename plugins/{agent-auth => agentkit}/references/redirects.md (100%) rename plugins/{agent-auth => agentkit}/references/tool-discovery.md (93%) rename plugins/{agent-auth => agentkit}/rules/live-metadata-first.md (100%) rename plugins/{agent-auth => agentkit}/rules/terminology.md (100%) rename plugins/{agent-auth => agentkit}/rules/tool-selection.md (100%) rename plugins/{agent-auth/skills/building-agent-mcp-server => agentkit/skills/building-agentkit-mcp-server}/SKILL.md (96%) rename plugins/{agent-auth => agentkit}/skills/discovering-agentkit-tools/SKILL.md (94%) rename plugins/{agent-auth/skills/agent-auth => agentkit/skills/integrating-agentkit}/SKILL.md (99%) rename plugins/{agent-auth/skills/production-readiness-scalekit => agentkit/skills/production-readiness-agentkit}/SKILL.md (98%) rename plugins/{agent-auth => agentkit}/skills/testing-agentkit-tools/SKILL.md (89%) rename plugins/{agent-auth => agentkit}/skills/testing-agentkit-tools/scripts/connect.py (100%) delete mode 100644 plugins/full-stack-auth/.claude-plugin/plugin.json delete mode 100644 plugins/full-stack-auth/.mcp.json delete mode 100644 plugins/full-stack-auth/agents/setup-scalekit.md delete mode 100644 plugins/full-stack-auth/hooks/hooks.json delete mode 100644 plugins/full-stack-auth/skills/adding-api-key-auth/SKILL.md delete mode 100644 plugins/full-stack-auth/skills/adding-oauth2-to-apis/SKILL.md delete mode 100644 plugins/full-stack-auth/skills/full-stack-auth/SKILL.md delete mode 100644 plugins/full-stack-auth/skills/implement-logout/SKILL.md delete mode 100644 plugins/full-stack-auth/skills/implementing-access-control/SKILL.md delete mode 100644 plugins/full-stack-auth/skills/implementing-admin-portal/SKILL.md delete mode 100644 plugins/full-stack-auth/skills/implementing-scalekit-django-auth/SKILL.md delete mode 100644 plugins/full-stack-auth/skills/implementing-scalekit-fastapi-auth/SKILL.md delete mode 100644 plugins/full-stack-auth/skills/implementing-scalekit-flask-auth/SKILL.md delete mode 100644 plugins/full-stack-auth/skills/implementing-scalekit-laravel-auth/SKILL.md delete mode 100644 plugins/full-stack-auth/skills/implementing-scalekit-nextjs-auth/SKILL.md delete mode 100644 plugins/full-stack-auth/skills/manage-user-sessions/SKILL.md delete mode 100644 plugins/full-stack-auth/skills/migrating-to-scalekit-auth/SKILL.md delete mode 100644 plugins/full-stack-auth/skills/production-readiness-scalekit/SKILL.md delete mode 100644 plugins/mcp-auth/.claude-plugin/plugin.json delete mode 100644 plugins/mcp-auth/.mcp.json delete mode 100644 plugins/mcp-auth/README.md delete mode 100755 plugins/mcp-auth/hooks/beacon.sh delete mode 100644 plugins/mcp-auth/hooks/hooks.json delete mode 100644 plugins/mcp-auth/references/redirects.md delete mode 100644 plugins/mcp-auth/skills/add-auth-fastmcp/SKILL.md delete mode 100644 plugins/mcp-auth/skills/express-mcp-server/SKILL.md delete mode 100644 plugins/mcp-auth/skills/fastapi-fastmcp/SKILL.md delete mode 100644 plugins/mcp-auth/skills/mcp-auth/SKILL.md delete mode 100644 plugins/mcp-auth/skills/production-readiness-scalekit/SKILL.md delete mode 100644 plugins/modular-scim/.claude-plugin/plugin.json delete mode 100644 plugins/modular-scim/.mcp.json delete mode 100644 plugins/modular-scim/agents/setup-scalekit.md delete mode 100755 plugins/modular-scim/hooks/beacon.sh delete mode 100644 plugins/modular-scim/hooks/hooks.json delete mode 100644 plugins/modular-scim/references/redirects.md delete mode 100644 plugins/modular-scim/skills/implementing-admin-portal/SKILL.md delete mode 100644 plugins/modular-scim/skills/modular-scim/SKILL.md delete mode 100644 plugins/modular-scim/skills/production-readiness-scalekit/SKILL.md delete mode 100644 plugins/modular-sso/.claude-plugin/plugin.json delete mode 100644 plugins/modular-sso/.mcp.json delete mode 100644 plugins/modular-sso/agents/setup-scalekit.md delete mode 100644 plugins/modular-sso/commands/dryrun.md delete mode 100755 plugins/modular-sso/hooks/beacon.sh delete mode 100644 plugins/modular-sso/hooks/hooks.json delete mode 100644 plugins/modular-sso/references/redirects.md delete mode 100644 plugins/modular-sso/skills/implementing-admin-portal/SKILL.md delete mode 100644 plugins/modular-sso/skills/modular-sso/SKILL.md delete mode 100644 plugins/modular-sso/skills/production-readiness-scalekit/SKILL.md create mode 100644 plugins/saaskit/.claude-plugin/plugin.json create mode 100644 plugins/saaskit/.env.example create mode 100644 plugins/saaskit/README.md rename plugins/{mcp-auth => saaskit}/agents/scalekit-mcp-auth-troubleshooter.md (100%) rename plugins/{full-stack-auth => saaskit}/agents/scalekit-mcp-helper.md (100%) rename plugins/{full-stack-auth => saaskit}/agents/sdk-version-advisor.md (100%) rename plugins/{full-stack-auth => saaskit}/agents/session-management-reviewer.md (95%) rename plugins/{mcp-auth => saaskit}/agents/setup-scalekit.md (82%) rename plugins/{full-stack-auth => saaskit}/commands/dryrun.md (100%) create mode 100644 plugins/saaskit/docs/access-control.md create mode 100644 plugins/saaskit/docs/api-auth.md create mode 100644 plugins/saaskit/docs/auth-flows.md create mode 100644 plugins/saaskit/docs/frameworks/go.md create mode 100644 plugins/saaskit/docs/frameworks/laravel.md create mode 100644 plugins/saaskit/docs/frameworks/nextjs.md create mode 100644 plugins/saaskit/docs/frameworks/python.md create mode 100644 plugins/saaskit/docs/frameworks/springboot.md create mode 100644 plugins/saaskit/docs/index.md create mode 100644 plugins/saaskit/docs/mcp-server-auth.md create mode 100644 plugins/saaskit/docs/scim.md create mode 100644 plugins/saaskit/docs/sessions.md create mode 100644 plugins/saaskit/docs/sso.md rename plugins/{full-stack-auth => saaskit}/hooks/beacon.sh (100%) create mode 100644 plugins/saaskit/hooks/hooks.json rename plugins/{mcp-auth => saaskit}/references/bring-your-own-auth.md (100%) rename plugins/{full-stack-auth => saaskit}/references/redirects.md (100%) rename plugins/{full-stack-auth => saaskit}/references/scalekit-logs.md (100%) rename plugins/{mcp-auth => saaskit}/references/scalekit-mcp-server.md (100%) rename plugins/{full-stack-auth => saaskit}/references/scalekit-user-profiles.md (100%) create mode 100644 plugins/saaskit/rules/redirect-urls.md create mode 100644 plugins/saaskit/rules/terminology.md create mode 100644 plugins/saaskit/skills/adding-api-auth/SKILL.md create mode 100644 plugins/saaskit/skills/adding-mcp-oauth/SKILL.md create mode 100644 plugins/saaskit/skills/adding-mcp-oauth/express-reference.md create mode 100644 plugins/saaskit/skills/adding-mcp-oauth/fastapi-reference.md create mode 100644 plugins/saaskit/skills/adding-mcp-oauth/fastmcp-reference.md create mode 100644 plugins/saaskit/skills/implementing-access-control/SKILL.md create mode 100644 plugins/saaskit/skills/implementing-modular-sso/SKILL.md create mode 100644 plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md create mode 100644 plugins/saaskit/skills/implementing-saaskit-python/SKILL.md create mode 100644 plugins/saaskit/skills/implementing-saaskit/SKILL.md rename plugins/{full-stack-auth/skills/implementing-scalekit-go-auth/SKILL.md => saaskit/skills/implementing-saaskit/go-reference.md} (74%) create mode 100644 plugins/saaskit/skills/implementing-saaskit/laravel-reference.md rename plugins/{full-stack-auth/skills/implementing-scalekit-springboot-auth/SKILL.md => saaskit/skills/implementing-saaskit/springboot-reference.md} (64%) create mode 100644 plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md create mode 100644 plugins/saaskit/skills/managing-saaskit-sessions/SKILL.md rename plugins/{full-stack-auth/skills/migrating-to-scalekit-auth => saaskit/skills/migrating-to-saaskit}/AUDIT-CHECKLIST.md (100%) rename plugins/{full-stack-auth/skills/migrating-to-scalekit-auth => saaskit/skills/migrating-to-saaskit}/IMPORT-SAMPLES.md (84%) create mode 100644 plugins/saaskit/skills/migrating-to-saaskit/SKILL.md create mode 100644 plugins/saaskit/skills/production-readiness-saaskit/SKILL.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 64515da..551085b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,44 @@ # Changelog -## Unreleased +## 2.0.0 + +### Breaking Changes + +- Consolidated 5 plugins into 2: **AgentKit** and **SaaSKit** +- Removed `full-stack-auth`, `mcp-auth`, `modular-sso`, `modular-scim` plugins +- All slash commands now use `/agentkit:` or `/saaskit:` namespace + +### Added + +- **AgentKit** plugin (`plugins/agentkit/`) — renamed from `agent-auth`, adopting Scalekit AgentKit branding + - New skills: `discovering-agentkit-tools`, `testing-agentkit-tools` + - Added `docs/` layer with canonical documentation (connections, tool-discovery, code-samples) + - Added `rules/` layer for cross-cutting guidance +- **SaaSKit** plugin (`plugins/saaskit/`) — consolidates FSA, SSO, SCIM, and MCP server auth + - 11 skills covering login, sessions, SSO, SCIM, RBAC, MCP server auth, API keys, migration, and production readiness + - `docs/` layer with 13 documentation files including framework-specific guides (Python, Next.js, Go, Spring Boot, Laravel) + - `rules/` layer with terminology and redirect URL guidance + - Framework-specific reference files (Go, Spring Boot, Laravel, FastMCP, Express, FastAPI) + - Consolidated agents, commands, hooks, and references from all 4 source plugins + +### Changed + +- Repository structure simplified from 5 plugins to 2 +- Content model: `docs/ + rules/ + thin skills/` pattern (established in PR #19) applied to both plugins +- Skill names follow updated Scalekit terminology (AgentKit, SaaSKit) +- Root README updated with 2-plugin listing and install instructions +- CLAUDE.md updated with new plugin layout + +### Removed + +- `plugins/full-stack-auth/` — skills migrated to SaaSKit +- `plugins/mcp-auth/` — server-side auth skills migrated to SaaSKit, MCP bridge stays in AgentKit +- `plugins/modular-sso/` — SSO + admin portal skills migrated to SaaSKit +- `plugins/modular-scim/` — SCIM provisioning skills migrated to SaaSKit + +--- + +## Unreleased (pre-2.0) - Align Claude marketplace metadata with current Build with AI docs: - Updated plugin categories to match product taxonomy used in docs and marketplaces. diff --git a/CLAUDE.md b/CLAUDE.md index 62d9b4d..6df4c95 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # CLAUDE.md (Repo guide for agents) -This repository contains multiple Claude Code plugins for marketplace distribution. -It is a monorepo. Always work inside one plugin directory at a time. +This repository contains two Claude Code plugins for marketplace distribution: +**AgentKit** (AI agent authentication) and **SaaSKit** (B2B SaaS authentication). If you are making changes, read AGENTS.md first and follow it as the source of truth. @@ -9,27 +9,29 @@ If you are making changes, read AGENTS.md first and follow it as the source of t Top level: -- plugins/ Monorepo root for all plugins +- plugins/ Monorepo root for both plugins - AGENTS.md Non negotiable rules for manifests, skills, hooks, MCP, security -- README.md Repo overview -- CHANGELOG.md Repo level changelog (also check plugin level changelogs if present) -- .lsp.json LSP configuration for this repo +- README.md Repo overview and install instructions +- CHANGELOG.md Repo level changelog -Plugins (examples you may see here): +Plugins: -- plugins/agent-auth/ -- plugins/full-stack-auth/ -- plugins/mcp-auth/ +- plugins/agentkit/ AI agent auth — connectors, token vault, tool discovery +- plugins/saaskit/ B2B SaaS auth — login, sessions, SSO, SCIM, RBAC, MCP server auth -Each plugin is expected to look like: +Each plugin follows the `docs/ + rules/ + skills/` content model: +``` plugins// -.claude-plugin/plugin.json Plugin manifest -README.md Required docs for that plugin -skills//SKILL.md Skill entrypoint -agents/ Optional sub agents -hooks/ Optional hooks.json -.mcp.json Optional MCP config -settings.json Optional default settings + .claude-plugin/plugin.json Plugin manifest + docs/ Canonical durable documentation + rules/ Cross-cutting guidance (terminology, etc.) + skills//SKILL.md Thin routing layer pointing to docs/ + agents/ Optional sub agents + hooks/ Optional hooks.json + commands/ Optional slash commands + references/ Optional reference material + README.md Required docs for that plugin +``` ## Golden rules diff --git a/README.md b/README.md index b759034..c78f367 100644 --- a/README.md +++ b/README.md @@ -2,21 +2,21 @@ Scalekit -

Scalekit Auth Plugins for Claude Code — the auth stack for agents.
-Add SSO, SCIM, MCP Auth, agent auth, and tool-calling from your Claude Code editor.

+

Scalekit Auth Stack for Claude Code — AgentKit and SaaSKit plugins.
+Add agent auth, tool calling, SSO, SCIM, MCP auth, and session management from Claude Code.

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/scalekit-inc/claude-code-authstack/pulls) -**[📖 Documentation](https://docs.scalekit.com)** · **[💬 Slack](https://join.slack.com/t/scalekit-community/shared_invite/zt-3gsxwr4hc-0tvhwT2b_qgVSIZQBQCWRw)** +**[📖 Documentation](https://docs.scalekit.com)** · **[📋 LLM Docs](https://docs.scalekit.com/llms.txt)** · **[💬 Slack](https://join.slack.com/t/scalekit-community/shared_invite/zt-3gsxwr4hc-0tvhwT2b_qgVSIZQBQCWRw)** --- -Setting up auth for B2B and AI apps is complex. Between auth flows, SSO providers, SCIM provisioning, MCP auth, and securing AI agents, most developers spend weeks on auth instead of shipping features with confidence. +Setting up auth for B2B and AI apps is complex. Between agent OAuth flows, SSO providers, SCIM provisioning, MCP server auth, and session management, most developers spend weeks on auth instead of shipping features. -This plugin adds the complete Scalekit auth stack to your projects — whether that's a B2B app, AI agent, or MCP server — directly from Claude Code. +This marketplace adds the complete Scalekit auth stack to your projects — whether that's an AI agent, a B2B SaaS app, or an MCP server — directly from Claude Code. ![Scalekit AuthStack demo](./images/scalekit-authstack-demo.gif) @@ -38,8 +38,9 @@ claude # Add Scalekit Auth Stack marketplace /plugin marketplace add scalekit-inc/claude-code-authstack -# Install AgentKit for AI agents -/plugin install agent-auth@scalekit-auth-stack +# Install a plugin +/plugin install agentkit@scalekit-auth-stack +/plugin install saaskit@scalekit-auth-stack # Open the plugins wizard /plugins @@ -58,57 +59,28 @@ After installation, enable auto-update: | Plugin | Description | |--------|-------------| -| **MCP Auth** | Add OAuth 2.1 authorization to Model Context Protocol servers. Guides you through token handling, refresh flows, and scope management. | -| **Modular SSO** | Integrate enterprise SSO providers (Okta, JumpCloud, Entra ID, etc.). Support 20+ identity providers without writing SAML parsers. | -| **Modular SCIM** | Enable user provisioning and directory sync. Let customers provision users automatically from their identity provider. | -| **Full Stack Auth** | Complete authentication setup for web applications. End-to-end auth including login pages, session management, and protected routes. | -| **Agent Auth** | Secure authentication for AI agents and services. OAuth flows designed for AI agents with token persistence and refresh logic. | +| **AgentKit** | Authentication for AI agents. OAuth flows, token vault, 40+ connectors (Gmail, Slack, Salesforce, etc.), tool discovery, and live testing — so agents can act on behalf of users. | +| **SaaSKit** | Production-ready auth for B2B SaaS apps. Login, sessions, SSO (Okta, Azure AD, Google), SCIM provisioning, RBAC, MCP server auth, and API key management. | --- ### Quick Start -After adding the marketplace, install a plugin based on your use case: - -#### For MCP Servers - -```sh -/plugin install mcp-auth@scalekit-auth-stack -``` - -Use this to secure your MCP servers with OAuth 2.1 authorization. - -#### For Enterprise SSO - -```sh -/plugin install modular-sso@scalekit-auth-stack -``` - -Use this to add SAML/OIDC SSO with providers like Okta, JumpCloud, or Entra ID. - #### For AI Agents ```sh -/plugin install agent-auth@scalekit-auth-stack -``` - -Use this to add authentication for AI agents that act on behalf of users. - -#### For User Provisioning - -```sh -/plugin install modular-scim@scalekit-auth-stack +/plugin install agentkit@scalekit-auth-stack ``` -Use this to enable SCIM directory sync for automatic user provisioning. +Use AgentKit to add authentication for AI agents that connect to third-party services, discover tools, and execute authenticated actions on behalf of users. -#### For Full-stack App Authentication +#### For B2B SaaS Apps ```sh -/plugin install full-stack-auth@scalekit-auth-stack +/plugin install saaskit@scalekit-auth-stack ``` -Use this to add login, callback handling, sessions, and logout flows to web apps. +Use SaaSKit to add login, session management, enterprise SSO, SCIM provisioning, RBAC, MCP server auth, and API key management to web applications. --- @@ -117,12 +89,10 @@ Use this to add login, callback handling, sessions, and logout flows to web apps ``` . ├── plugins/ -│ ├── mcp-auth/ # OAuth 2.1 for MCP servers -│ ├── modular-sso/ # Enterprise SSO integration -│ ├── modular-scim/ # SCIM provisioning -│ ├── full-stack-auth/ # Complete web app auth -│ └── agent-auth/ # AI agent authentication +│ ├── agentkit/ # AI agent authentication (AgentKit) +│ └── saaskit/ # B2B SaaS authentication (SaaSKit) ├── images/ # Documentation images +├── scripts/ # Install scripts ├── AGENTS.md # Contribution guidelines └── LICENSE # MIT License ``` diff --git a/plugins/agent-auth/.claude-plugin/plugin.json b/plugins/agentkit/.claude-plugin/plugin.json similarity index 95% rename from plugins/agent-auth/.claude-plugin/plugin.json rename to plugins/agentkit/.claude-plugin/plugin.json index f3aeedf..ab629d6 100644 --- a/plugins/agent-auth/.claude-plugin/plugin.json +++ b/plugins/agentkit/.claude-plugin/plugin.json @@ -1,5 +1,5 @@ { - "name": "agent-auth", + "name": "agentkit", "description": "Sets up Scalekit AgentKit in Claude Code so agents can authorize users, discover tools, and execute authenticated tool calls across connectors.", "version": "2.0.0", "author": { diff --git a/plugins/agent-auth/.env.example b/plugins/agentkit/.env.example similarity index 100% rename from plugins/agent-auth/.env.example rename to plugins/agentkit/.env.example diff --git a/plugins/agent-auth/.mcp.json b/plugins/agentkit/.mcp.json similarity index 100% rename from plugins/agent-auth/.mcp.json rename to plugins/agentkit/.mcp.json diff --git a/plugins/agent-auth/README.md b/plugins/agentkit/README.md similarity index 76% rename from plugins/agent-auth/README.md rename to plugins/agentkit/README.md index 2509297..53f271d 100644 --- a/plugins/agent-auth/README.md +++ b/plugins/agentkit/README.md @@ -19,7 +19,7 @@ The plugin treats live AgentKit metadata as the source of truth for tool names, ## Installation ```sh -claude /plugin install agent-auth@scalekit-auth-stack +claude /plugin install agentkit@scalekit-auth-stack ``` Start with the canonical docs entrypoint at [`docs/index.md`](docs/index.md). @@ -33,20 +33,20 @@ Official Scalekit docs: - [AgentKit examples](https://docs.scalekit.com/agentkit/examples.md) ## Skills Reference -- `/agent-auth:integrating-agent-auth` +- `/agentkit:integrating-agentkit` Integrates AgentKit into app code or an agent workflow and routes into the core docs. -- `/agent-auth:discovering-agentkit-tools` +- `/agentkit:discovering-agentkit-tools` Uses live AgentKit metadata to find tools, inspect schemas, and narrow the tool set. -- `/agent-auth:testing-agentkit-tools` +- `/agentkit:testing-agentkit-tools` Generates authorization links, fetches live tool metadata, and executes tools from Claude Code. This is the preferred runnable playground surface. -- `/agent-auth:building-agent-mcp-server` +- `/agentkit:building-agent-mcp-server` Exposes AgentKit tools through MCP for MCP-compatible runtimes. -- `/agent-auth:production-readiness-scalekit` +- `/agentkit:production-readiness-agentkit` Runs a structured production-readiness checklist for AgentKit integrations. Legacy command alias: - `/test-tool [generate-link|get-tool|execute-tool ...]` - Compatibility wrapper for older usage. Prefer `/agent-auth:testing-agentkit-tools ...`. + Compatibility wrapper for older usage. Prefer `/agentkit:testing-agentkit-tools ...`. ## Configuration Required environment variables: @@ -79,10 +79,10 @@ Example `.mcp.json`: Typical flow for a new connector integration: 1. Read [`docs/index.md`](docs/index.md) for the canonical model and [`docs/connections.md`](docs/connections.md) for connection naming. 2. Create the connection in `AgentKit -> Connections`. -3. Use `/agent-auth:integrating-agent-auth` to scaffold connected-account creation and authorization. -4. Use `/agent-auth:discovering-agentkit-tools` or `/agent-auth:testing-agentkit-tools get-tool --provider GMAIL` to inspect the live tool catalog and schema. -5. Use `/agent-auth:testing-agentkit-tools generate-link --connection-name --identifier user_123` if the user still needs to authorize. -6. Use `/agent-auth:testing-agentkit-tools execute-tool --tool-name gmail_fetch_mails --connection-name --identifier user_123 --tool-input '{"query":"is:unread","max_results":5}'` to validate the payload before wiring it into application code. +3. Use `/agentkit:integrating-agentkit` to scaffold connected-account creation and authorization. +4. Use `/agentkit:discovering-agentkit-tools` or `/agentkit:testing-agentkit-tools get-tool --provider GMAIL` to inspect the live tool catalog and schema. +5. Use `/agentkit:testing-agentkit-tools generate-link --connection-name --identifier user_123` if the user still needs to authorize. +6. Use `/agentkit:testing-agentkit-tools execute-tool --tool-name gmail_fetch_mails --connection-name --identifier user_123 --tool-input '{"query":"is:unread","max_results":5}'` to validate the payload before wiring it into application code. ## Troubleshooting 1. No tools show up for a connector: diff --git a/plugins/agent-auth/agents/setup-scalekit.md b/plugins/agentkit/agents/setup-scalekit.md similarity index 83% rename from plugins/agent-auth/agents/setup-scalekit.md rename to plugins/agentkit/agents/setup-scalekit.md index ed43763..8b70de2 100644 --- a/plugins/agent-auth/agents/setup-scalekit.md +++ b/plugins/agentkit/agents/setup-scalekit.md @@ -35,9 +35,10 @@ Workflow: - Incorrect client id/secret - Network/DNS issues 7) Only after verification succeeds, proceed to feature work and route to the correct Skill: - - SSO → plugins/modular-sso/skills/modular-sso/SKILL.md - - SCIM → plugins/modular-scim/skills/modular-scim/SKILL.md - - MCP auth → plugins/mcp-auth/skills/*/SKILL.md - - Full-stack auth → plugins/full-stack-auth/skills/full-stack-auth/SKILL.md + - Agent auth → plugins/agentkit/skills/integrating-agentkit/SKILL.md + - SSO → plugins/saaskit/skills/implementing-modular-sso/SKILL.md + - SCIM → plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md + - MCP server auth → plugins/saaskit/skills/adding-mcp-oauth/SKILL.md + - SaaSKit auth → plugins/saaskit/skills/implementing-saaskit/SKILL.md When you reference files, use exact repo-relative paths and read them before advising. diff --git a/plugins/agent-auth/commands/test-tool.md b/plugins/agentkit/commands/test-tool.md similarity index 87% rename from plugins/agent-auth/commands/test-tool.md rename to plugins/agentkit/commands/test-tool.md index 36bac22..16f117a 100644 --- a/plugins/agent-auth/commands/test-tool.md +++ b/plugins/agentkit/commands/test-tool.md @@ -6,12 +6,12 @@ allowed-tools: Bash # Legacy AgentKit Tool Tester -This command is a legacy compatibility alias for `/agent-auth:testing-agentkit-tools`. +This command is a legacy compatibility alias for `/agentkit:testing-agentkit-tools`. Prefer invoking the skill directly: ```text -/agent-auth:testing-agentkit-tools $ARGUMENTS +/agentkit:testing-agentkit-tools $ARGUMENTS ``` **Arguments:** $ARGUMENTS diff --git a/plugins/agent-auth/docs/byoc.md b/plugins/agentkit/docs/byoc.md similarity index 100% rename from plugins/agent-auth/docs/byoc.md rename to plugins/agentkit/docs/byoc.md diff --git a/plugins/agent-auth/docs/code-samples.md b/plugins/agentkit/docs/code-samples.md similarity index 92% rename from plugins/agent-auth/docs/code-samples.md rename to plugins/agentkit/docs/code-samples.md index c65d62a..20314b9 100644 --- a/plugins/agent-auth/docs/code-samples.md +++ b/plugins/agentkit/docs/code-samples.md @@ -8,8 +8,8 @@ Use it to choose an implementation style before opening a larger sample reposito | Goal | Recommended path | |---|---| -| Validate one tool quickly | Use `/agent-auth:testing-agentkit-tools` | -| Integrate AgentKit into app code | Use `integrating-agent-auth` | +| Validate one tool quickly | Use `/agentkit:testing-agentkit-tools` | +| Integrate AgentKit into app code | Use `integrating-agentkit` | | Build an agent with a framework | Use framework-specific examples below | | Expose tools over MCP | Use `building-agent-mcp-server` | diff --git a/plugins/agent-auth/docs/connected-accounts.md b/plugins/agentkit/docs/connected-accounts.md similarity index 100% rename from plugins/agent-auth/docs/connected-accounts.md rename to plugins/agentkit/docs/connected-accounts.md diff --git a/plugins/agent-auth/docs/connections.md b/plugins/agentkit/docs/connections.md similarity index 100% rename from plugins/agent-auth/docs/connections.md rename to plugins/agentkit/docs/connections.md diff --git a/plugins/agent-auth/docs/connectors/README.md b/plugins/agentkit/docs/connectors/README.md similarity index 100% rename from plugins/agent-auth/docs/connectors/README.md rename to plugins/agentkit/docs/connectors/README.md diff --git a/plugins/agent-auth/docs/index.md b/plugins/agentkit/docs/index.md similarity index 98% rename from plugins/agent-auth/docs/index.md rename to plugins/agentkit/docs/index.md index 891c359..12ea06c 100644 --- a/plugins/agent-auth/docs/index.md +++ b/plugins/agentkit/docs/index.md @@ -1,6 +1,6 @@ # AgentKit Docs -This `docs/` directory is the canonical documentation layer for the `agent-auth` plugin. +This `docs/` directory is the canonical documentation layer for the `agentkit` plugin. Use it for the durable AgentKit model: diff --git a/plugins/agent-auth/docs/tool-discovery.md b/plugins/agentkit/docs/tool-discovery.md similarity index 95% rename from plugins/agent-auth/docs/tool-discovery.md rename to plugins/agentkit/docs/tool-discovery.md index 97a4f09..fd48975 100644 --- a/plugins/agent-auth/docs/tool-discovery.md +++ b/plugins/agentkit/docs/tool-discovery.md @@ -61,7 +61,7 @@ Use: - `discovering-agentkit-tools` when the user needs current tools or schemas - `testing-agentkit-tools` when the user wants to run a live tool and inspect the exact payload -- `/agent-auth:testing-agentkit-tools get-tool ...` for the preferred runnable playground flow +- `/agentkit:testing-agentkit-tools get-tool ...` for the preferred runnable playground flow - `/test-tool get-tool ...` only as a legacy compatibility alias ## Fallback rule diff --git a/plugins/agent-auth/hooks/beacon.sh b/plugins/agentkit/hooks/beacon.sh similarity index 100% rename from plugins/agent-auth/hooks/beacon.sh rename to plugins/agentkit/hooks/beacon.sh diff --git a/plugins/agent-auth/hooks/hooks.json b/plugins/agentkit/hooks/hooks.json similarity index 74% rename from plugins/agent-auth/hooks/hooks.json rename to plugins/agentkit/hooks/hooks.json index b03e8ed..e82a73a 100644 --- a/plugins/agent-auth/hooks/hooks.json +++ b/plugins/agentkit/hooks/hooks.json @@ -1,12 +1,12 @@ { - "description": "Usage beacon for Scalekit agent-auth plugin", + "description": "Usage beacon for Scalekit agentkit plugin", "hooks": { "Stop": [ { "hooks": [ { "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh agent-auth stop", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh agentkit stop", "timeout": 10 } ] diff --git a/plugins/agent-auth/references/agent-connectors/README.md b/plugins/agentkit/references/agent-connectors/README.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/README.md rename to plugins/agentkit/references/agent-connectors/README.md diff --git a/plugins/agent-auth/references/agent-connectors/airtable.md b/plugins/agentkit/references/agent-connectors/airtable.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/airtable.md rename to plugins/agentkit/references/agent-connectors/airtable.md diff --git a/plugins/agent-auth/references/agent-connectors/asana.md b/plugins/agentkit/references/agent-connectors/asana.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/asana.md rename to plugins/agentkit/references/agent-connectors/asana.md diff --git a/plugins/agent-auth/references/agent-connectors/attention.md b/plugins/agentkit/references/agent-connectors/attention.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/attention.md rename to plugins/agentkit/references/agent-connectors/attention.md diff --git a/plugins/agent-auth/references/agent-connectors/bigquery.md b/plugins/agentkit/references/agent-connectors/bigquery.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/bigquery.md rename to plugins/agentkit/references/agent-connectors/bigquery.md diff --git a/plugins/agent-auth/references/agent-connectors/chorus.md b/plugins/agentkit/references/agent-connectors/chorus.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/chorus.md rename to plugins/agentkit/references/agent-connectors/chorus.md diff --git a/plugins/agent-auth/references/agent-connectors/clari_copilot.md b/plugins/agentkit/references/agent-connectors/clari_copilot.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/clari_copilot.md rename to plugins/agentkit/references/agent-connectors/clari_copilot.md diff --git a/plugins/agent-auth/references/agent-connectors/clickup.md b/plugins/agentkit/references/agent-connectors/clickup.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/clickup.md rename to plugins/agentkit/references/agent-connectors/clickup.md diff --git a/plugins/agent-auth/references/agent-connectors/confluence.md b/plugins/agentkit/references/agent-connectors/confluence.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/confluence.md rename to plugins/agentkit/references/agent-connectors/confluence.md diff --git a/plugins/agent-auth/references/agent-connectors/dropbox.md b/plugins/agentkit/references/agent-connectors/dropbox.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/dropbox.md rename to plugins/agentkit/references/agent-connectors/dropbox.md diff --git a/plugins/agent-auth/references/agent-connectors/fathom.md b/plugins/agentkit/references/agent-connectors/fathom.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/fathom.md rename to plugins/agentkit/references/agent-connectors/fathom.md diff --git a/plugins/agent-auth/references/agent-connectors/freshdesk.md b/plugins/agentkit/references/agent-connectors/freshdesk.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/freshdesk.md rename to plugins/agentkit/references/agent-connectors/freshdesk.md diff --git a/plugins/agent-auth/references/agent-connectors/github.md b/plugins/agentkit/references/agent-connectors/github.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/github.md rename to plugins/agentkit/references/agent-connectors/github.md diff --git a/plugins/agent-auth/references/agent-connectors/gmail.md b/plugins/agentkit/references/agent-connectors/gmail.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/gmail.md rename to plugins/agentkit/references/agent-connectors/gmail.md diff --git a/plugins/agent-auth/references/agent-connectors/gong.md b/plugins/agentkit/references/agent-connectors/gong.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/gong.md rename to plugins/agentkit/references/agent-connectors/gong.md diff --git a/plugins/agent-auth/references/agent-connectors/google_ads.md b/plugins/agentkit/references/agent-connectors/google_ads.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/google_ads.md rename to plugins/agentkit/references/agent-connectors/google_ads.md diff --git a/plugins/agent-auth/references/agent-connectors/google_docs.md b/plugins/agentkit/references/agent-connectors/google_docs.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/google_docs.md rename to plugins/agentkit/references/agent-connectors/google_docs.md diff --git a/plugins/agent-auth/references/agent-connectors/google_drive.md b/plugins/agentkit/references/agent-connectors/google_drive.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/google_drive.md rename to plugins/agentkit/references/agent-connectors/google_drive.md diff --git a/plugins/agent-auth/references/agent-connectors/google_forms.md b/plugins/agentkit/references/agent-connectors/google_forms.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/google_forms.md rename to plugins/agentkit/references/agent-connectors/google_forms.md diff --git a/plugins/agent-auth/references/agent-connectors/google_meets.md b/plugins/agentkit/references/agent-connectors/google_meets.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/google_meets.md rename to plugins/agentkit/references/agent-connectors/google_meets.md diff --git a/plugins/agent-auth/references/agent-connectors/google_sheets.md b/plugins/agentkit/references/agent-connectors/google_sheets.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/google_sheets.md rename to plugins/agentkit/references/agent-connectors/google_sheets.md diff --git a/plugins/agent-auth/references/agent-connectors/google_slides.md b/plugins/agentkit/references/agent-connectors/google_slides.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/google_slides.md rename to plugins/agentkit/references/agent-connectors/google_slides.md diff --git a/plugins/agent-auth/references/agent-connectors/googlecalendar.md b/plugins/agentkit/references/agent-connectors/googlecalendar.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/googlecalendar.md rename to plugins/agentkit/references/agent-connectors/googlecalendar.md diff --git a/plugins/agent-auth/references/agent-connectors/hubspot.md b/plugins/agentkit/references/agent-connectors/hubspot.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/hubspot.md rename to plugins/agentkit/references/agent-connectors/hubspot.md diff --git a/plugins/agent-auth/references/agent-connectors/intercom.md b/plugins/agentkit/references/agent-connectors/intercom.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/intercom.md rename to plugins/agentkit/references/agent-connectors/intercom.md diff --git a/plugins/agent-auth/references/agent-connectors/jira.md b/plugins/agentkit/references/agent-connectors/jira.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/jira.md rename to plugins/agentkit/references/agent-connectors/jira.md diff --git a/plugins/agent-auth/references/agent-connectors/linear.md b/plugins/agentkit/references/agent-connectors/linear.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/linear.md rename to plugins/agentkit/references/agent-connectors/linear.md diff --git a/plugins/agent-auth/references/agent-connectors/microsoft_excel.md b/plugins/agentkit/references/agent-connectors/microsoft_excel.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/microsoft_excel.md rename to plugins/agentkit/references/agent-connectors/microsoft_excel.md diff --git a/plugins/agent-auth/references/agent-connectors/microsoft_teams.md b/plugins/agentkit/references/agent-connectors/microsoft_teams.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/microsoft_teams.md rename to plugins/agentkit/references/agent-connectors/microsoft_teams.md diff --git a/plugins/agent-auth/references/agent-connectors/microsoft_word.md b/plugins/agentkit/references/agent-connectors/microsoft_word.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/microsoft_word.md rename to plugins/agentkit/references/agent-connectors/microsoft_word.md diff --git a/plugins/agent-auth/references/agent-connectors/monday.md b/plugins/agentkit/references/agent-connectors/monday.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/monday.md rename to plugins/agentkit/references/agent-connectors/monday.md diff --git a/plugins/agent-auth/references/agent-connectors/notion.md b/plugins/agentkit/references/agent-connectors/notion.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/notion.md rename to plugins/agentkit/references/agent-connectors/notion.md diff --git a/plugins/agent-auth/references/agent-connectors/onedrive.md b/plugins/agentkit/references/agent-connectors/onedrive.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/onedrive.md rename to plugins/agentkit/references/agent-connectors/onedrive.md diff --git a/plugins/agent-auth/references/agent-connectors/onenote.md b/plugins/agentkit/references/agent-connectors/onenote.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/onenote.md rename to plugins/agentkit/references/agent-connectors/onenote.md diff --git a/plugins/agent-auth/references/agent-connectors/outlook.md b/plugins/agentkit/references/agent-connectors/outlook.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/outlook.md rename to plugins/agentkit/references/agent-connectors/outlook.md diff --git a/plugins/agent-auth/references/agent-connectors/salesforce.md b/plugins/agentkit/references/agent-connectors/salesforce.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/salesforce.md rename to plugins/agentkit/references/agent-connectors/salesforce.md diff --git a/plugins/agent-auth/references/agent-connectors/servicenow.md b/plugins/agentkit/references/agent-connectors/servicenow.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/servicenow.md rename to plugins/agentkit/references/agent-connectors/servicenow.md diff --git a/plugins/agent-auth/references/agent-connectors/sharepoint.md b/plugins/agentkit/references/agent-connectors/sharepoint.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/sharepoint.md rename to plugins/agentkit/references/agent-connectors/sharepoint.md diff --git a/plugins/agent-auth/references/agent-connectors/slack.md b/plugins/agentkit/references/agent-connectors/slack.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/slack.md rename to plugins/agentkit/references/agent-connectors/slack.md diff --git a/plugins/agent-auth/references/agent-connectors/snowflake.md b/plugins/agentkit/references/agent-connectors/snowflake.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/snowflake.md rename to plugins/agentkit/references/agent-connectors/snowflake.md diff --git a/plugins/agent-auth/references/agent-connectors/trello.md b/plugins/agentkit/references/agent-connectors/trello.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/trello.md rename to plugins/agentkit/references/agent-connectors/trello.md diff --git a/plugins/agent-auth/references/agent-connectors/zendesk.md b/plugins/agentkit/references/agent-connectors/zendesk.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/zendesk.md rename to plugins/agentkit/references/agent-connectors/zendesk.md diff --git a/plugins/agent-auth/references/agent-connectors/zoom.md b/plugins/agentkit/references/agent-connectors/zoom.md similarity index 100% rename from plugins/agent-auth/references/agent-connectors/zoom.md rename to plugins/agentkit/references/agent-connectors/zoom.md diff --git a/plugins/agent-auth/references/byoc.md b/plugins/agentkit/references/byoc.md similarity index 100% rename from plugins/agent-auth/references/byoc.md rename to plugins/agentkit/references/byoc.md diff --git a/plugins/agent-auth/references/code-samples.md b/plugins/agentkit/references/code-samples.md similarity index 100% rename from plugins/agent-auth/references/code-samples.md rename to plugins/agentkit/references/code-samples.md diff --git a/plugins/agent-auth/references/connected-accounts.md b/plugins/agentkit/references/connected-accounts.md similarity index 100% rename from plugins/agent-auth/references/connected-accounts.md rename to plugins/agentkit/references/connected-accounts.md diff --git a/plugins/agent-auth/references/connections.md b/plugins/agentkit/references/connections.md similarity index 100% rename from plugins/agent-auth/references/connections.md rename to plugins/agentkit/references/connections.md diff --git a/plugins/agent-auth/references/providers.md b/plugins/agentkit/references/providers.md similarity index 100% rename from plugins/agent-auth/references/providers.md rename to plugins/agentkit/references/providers.md diff --git a/plugins/agent-auth/references/redirects.md b/plugins/agentkit/references/redirects.md similarity index 100% rename from plugins/agent-auth/references/redirects.md rename to plugins/agentkit/references/redirects.md diff --git a/plugins/agent-auth/references/tool-discovery.md b/plugins/agentkit/references/tool-discovery.md similarity index 93% rename from plugins/agent-auth/references/tool-discovery.md rename to plugins/agentkit/references/tool-discovery.md index 872d5e5..f020b01 100644 --- a/plugins/agent-auth/references/tool-discovery.md +++ b/plugins/agentkit/references/tool-discovery.md @@ -49,8 +49,8 @@ If the metadata contains pagination or large result fields, mention them so the For interactive discovery, prefer the testing skill: ```sh -/agent-auth:testing-agentkit-tools get-tool --provider GMAIL -/agent-auth:testing-agentkit-tools get-tool --tool-name gmail_fetch_mails +/agentkit:testing-agentkit-tools get-tool --provider GMAIL +/agentkit:testing-agentkit-tools get-tool --tool-name gmail_fetch_mails ``` The legacy `/test-tool ...` alias still works for compatibility, but it is no longer the canonical path. @@ -59,7 +59,7 @@ For implementation guidance, use: - `discovering-agentkit-tools` when the user needs the current tool list or schema - `testing-agentkit-tools` when the user wants to execute the tool and inspect the exact payload -- `integrating-agent-auth` when the user wants to wire the result into application code +- `integrating-agentkit` when the user wants to wire the result into application code ## Connection names vs connector names diff --git a/plugins/agent-auth/rules/live-metadata-first.md b/plugins/agentkit/rules/live-metadata-first.md similarity index 100% rename from plugins/agent-auth/rules/live-metadata-first.md rename to plugins/agentkit/rules/live-metadata-first.md diff --git a/plugins/agent-auth/rules/terminology.md b/plugins/agentkit/rules/terminology.md similarity index 100% rename from plugins/agent-auth/rules/terminology.md rename to plugins/agentkit/rules/terminology.md diff --git a/plugins/agent-auth/rules/tool-selection.md b/plugins/agentkit/rules/tool-selection.md similarity index 100% rename from plugins/agent-auth/rules/tool-selection.md rename to plugins/agentkit/rules/tool-selection.md diff --git a/plugins/agent-auth/skills/building-agent-mcp-server/SKILL.md b/plugins/agentkit/skills/building-agentkit-mcp-server/SKILL.md similarity index 96% rename from plugins/agent-auth/skills/building-agent-mcp-server/SKILL.md rename to plugins/agentkit/skills/building-agentkit-mcp-server/SKILL.md index c3bd41b..8161593 100644 --- a/plugins/agent-auth/skills/building-agent-mcp-server/SKILL.md +++ b/plugins/agentkit/skills/building-agentkit-mcp-server/SKILL.md @@ -1,5 +1,5 @@ --- -name: building-agent-mcp-server +name: building-agentkit-mcp-server description: Guides developers through creating a Scalekit AgentKit MCP server with authenticated tool access. Use when building an MCP server, exposing AgentKit tools over MCP, or connecting AI agents via LangChain or LangGraph MCP adapters. --- @@ -7,7 +7,7 @@ description: Guides developers through creating a Scalekit AgentKit MCP server w Scalekit lets you build MCP servers that manage authentication, create personalized access URLs for users, and define which tools are accessible. You can also bundle several toolkits (e.g., Gmail + Google Calendar) within a single server. -[Model Context Protocol (MCP)](https://modelcontextprotocol.io/docs/getting-started/intro) is an open-source standard that enables AI systems to interface with external tools and data sources. Where the `integrating-agent-auth` skill uses the SDK directly, this workflow exposes AgentKit tools over the MCP protocol so any compliant client — LangChain, Claude Desktop, MCP Inspector — can consume them. +[Model Context Protocol (MCP)](https://modelcontextprotocol.io/docs/getting-started/intro) is an open-source standard that enables AI systems to interface with external tools and data sources. Where the `integrating-agentkit` skill uses the SDK directly, this workflow exposes AgentKit tools over the MCP protocol so any compliant client — LangChain, Claude Desktop, MCP Inspector — can consume them. > **Note:** AgentKit MCP servers only support Streamable HTTP transport. diff --git a/plugins/agent-auth/skills/discovering-agentkit-tools/SKILL.md b/plugins/agentkit/skills/discovering-agentkit-tools/SKILL.md similarity index 94% rename from plugins/agent-auth/skills/discovering-agentkit-tools/SKILL.md rename to plugins/agentkit/skills/discovering-agentkit-tools/SKILL.md index 7710348..8d210a4 100644 --- a/plugins/agent-auth/skills/discovering-agentkit-tools/SKILL.md +++ b/plugins/agentkit/skills/discovering-agentkit-tools/SKILL.md @@ -22,7 +22,7 @@ Use this skill when the user asks: ## Discovery workflow 1. Identify the target connector or exact tool name. -2. Prefer live lookup through `/agent-auth:testing-agentkit-tools get-tool --provider ` or `/agent-auth:testing-agentkit-tools get-tool --tool-name `. +2. Prefer live lookup through `/agentkit:testing-agentkit-tools get-tool --provider ` or `/agentkit:testing-agentkit-tools get-tool --tool-name `. 3. If older docs or muscle memory mention `/test-tool`, treat it as a legacy compatibility alias for the testing skill rather than the canonical workflow. 4. Summarize: - tool name diff --git a/plugins/agent-auth/skills/agent-auth/SKILL.md b/plugins/agentkit/skills/integrating-agentkit/SKILL.md similarity index 99% rename from plugins/agent-auth/skills/agent-auth/SKILL.md rename to plugins/agentkit/skills/integrating-agentkit/SKILL.md index 56db8af..55b5097 100644 --- a/plugins/agent-auth/skills/agent-auth/SKILL.md +++ b/plugins/agentkit/skills/integrating-agentkit/SKILL.md @@ -1,5 +1,5 @@ --- -name: integrating-agent-auth +name: integrating-agentkit description: Integrates Scalekit AgentKit into a project so an agent can create connections, authorize users, discover tools, and execute authenticated tool calls on their behalf. Use when a user needs to set up a connection, create a connected account, generate an authorization link, or wire AgentKit tools into application code or an agent framework. --- diff --git a/plugins/agent-auth/skills/production-readiness-scalekit/SKILL.md b/plugins/agentkit/skills/production-readiness-agentkit/SKILL.md similarity index 98% rename from plugins/agent-auth/skills/production-readiness-scalekit/SKILL.md rename to plugins/agentkit/skills/production-readiness-agentkit/SKILL.md index a79fcbe..7349f5b 100644 --- a/plugins/agent-auth/skills/production-readiness-scalekit/SKILL.md +++ b/plugins/agentkit/skills/production-readiness-agentkit/SKILL.md @@ -1,5 +1,5 @@ --- -name: production-readiness-scalekit +name: production-readiness-agentkit description: Walks through a structured production readiness checklist for Scalekit AgentKit implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, or wants to verify their AgentKit authorization and tool-calling setup is production-ready. --- diff --git a/plugins/agent-auth/skills/testing-agentkit-tools/SKILL.md b/plugins/agentkit/skills/testing-agentkit-tools/SKILL.md similarity index 89% rename from plugins/agent-auth/skills/testing-agentkit-tools/SKILL.md rename to plugins/agentkit/skills/testing-agentkit-tools/SKILL.md index ea25ef2..4a6feb3 100644 --- a/plugins/agent-auth/skills/testing-agentkit-tools/SKILL.md +++ b/plugins/agentkit/skills/testing-agentkit-tools/SKILL.md @@ -35,10 +35,10 @@ Use it to: Invoke this skill directly for the runnable playground: -- `/agent-auth:testing-agentkit-tools get-tool --provider GMAIL` -- `/agent-auth:testing-agentkit-tools get-tool --tool-name gmail_fetch_mails` -- `/agent-auth:testing-agentkit-tools generate-link --connection-name MY_GMAIL --identifier user_123` -- `/agent-auth:testing-agentkit-tools execute-tool --tool-name gmail_fetch_mails --connection-name MY_GMAIL --identifier user_123 --tool-input '{"query":"is:unread","max_results":5}'` +- `/agentkit:testing-agentkit-tools get-tool --provider GMAIL` +- `/agentkit:testing-agentkit-tools get-tool --tool-name gmail_fetch_mails` +- `/agentkit:testing-agentkit-tools generate-link --connection-name MY_GMAIL --identifier user_123` +- `/agentkit:testing-agentkit-tools execute-tool --tool-name gmail_fetch_mails --connection-name MY_GMAIL --identifier user_123 --tool-input '{"query":"is:unread","max_results":5}'` `/test-tool` remains available only as a legacy compatibility alias. @@ -134,4 +134,4 @@ Show: - Legacy alias: [../../commands/test-tool.md](../../commands/test-tool.md) - Canonical docs entrypoint: [../../docs/index.md](../../docs/index.md) - Live discovery model: [../../docs/tool-discovery.md](../../docs/tool-discovery.md) -- Integration workflow: [../agent-auth/SKILL.md](../agent-auth/SKILL.md) +- Integration workflow: [../integrating-agentkit/SKILL.md](../integrating-agentkit/SKILL.md) diff --git a/plugins/agent-auth/skills/testing-agentkit-tools/scripts/connect.py b/plugins/agentkit/skills/testing-agentkit-tools/scripts/connect.py similarity index 100% rename from plugins/agent-auth/skills/testing-agentkit-tools/scripts/connect.py rename to plugins/agentkit/skills/testing-agentkit-tools/scripts/connect.py diff --git a/plugins/full-stack-auth/.claude-plugin/plugin.json b/plugins/full-stack-auth/.claude-plugin/plugin.json deleted file mode 100644 index 7dc1d20..0000000 --- a/plugins/full-stack-auth/.claude-plugin/plugin.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "full-stack-auth", - "description": "Production-ready authentication flows (sign-up, login, logout, sessions) using Scalekit full-stack auth across common stacks.", - "version": "1.4.1", - "author": { - "name": "Scalekit", - "email": "hi@scalekit.com" - }, - "homepage": "https://docs.scalekit.com/dev-kit/build-with-ai/full-stack-auth/", - "repository": "https://github.com/scalekit-inc/claude-code-authstack", - "license": "MIT", - "keywords": ["authentication", "oauth", "full-stack", "sessions"] -} diff --git a/plugins/full-stack-auth/.mcp.json b/plugins/full-stack-auth/.mcp.json deleted file mode 100644 index 783b9a1..0000000 --- a/plugins/full-stack-auth/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "scalekit": { - "type": "http", - "url": "https://mcp.scalekit.com" - } - } -} diff --git a/plugins/full-stack-auth/agents/setup-scalekit.md b/plugins/full-stack-auth/agents/setup-scalekit.md deleted file mode 100644 index 044e411..0000000 --- a/plugins/full-stack-auth/agents/setup-scalekit.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: scalekit-setup -description: Sets up Scalekit env vars, installs/initializes the SDK, and verifies credentials by listing organizations. Use proactively when user asks to set up, install, initialize, configure, or verify Scalekit. -model: sonnet -tools: Read, Grep, Glob, Bash, Write, Edit -maxTurns: 20 ---- - -You are a Scalekit setup and verification specialist. - -Mission: -- Help the user configure Scalekit credentials via environment variables. -- Help them install and initialize the official Scalekit SDK for their language. -- Verify the setup with the smallest reliable check (prefer listing organizations). -- Keep secrets out of chat and out of the repo. - -Hard rules: -- NEVER ask the user to paste SCALEKIT_CLIENT_SECRET into chat. -- NEVER hardcode credentials in code samples; always use environment variables. -- Prefer creating a local verification script (verify.js / verify.py / verify.go / Verify.java) and running it, but only if the user wants you to write files. - -Workflow: -1) Determine language/runtime (Node.js, Python, Go, Java) and where env vars should live (.env, shell, CI secrets). -2) Confirm required env vars exist: - - SCALEKIT_ENVIRONMENT_URL - - SCALEKIT_CLIENT_ID - - SCALEKIT_CLIENT_SECRET -3) Install the SDK (pick the official package for that language). -4) Initialize the SDK client using env vars. -5) Verify credentials by listing organizations with a small page size. -6) If verification fails, diagnose systematically: - - Wrong environment URL (dev vs prod) - - Missing env vars in current shell/process - - Incorrect client id/secret - - Network/DNS issues -7) Only after verification succeeds, proceed to feature work and route to the correct Skill: - - SSO → plugins/modular-sso/skills/modular-sso/SKILL.md - - SCIM → plugins/modular-scim/skills/modular-scim/SKILL.md - - MCP auth → plugins/mcp-auth/skills/*/SKILL.md - - Full-stack auth → plugins/full-stack-auth/skills/full-stack-auth/SKILL.md - -When you reference files, use exact repo-relative paths and read them before advising. diff --git a/plugins/full-stack-auth/hooks/hooks.json b/plugins/full-stack-auth/hooks/hooks.json deleted file mode 100644 index ed3878c..0000000 --- a/plugins/full-stack-auth/hooks/hooks.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "description": "Usage beacon for Scalekit full-stack-auth plugin", - "hooks": { - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh full-stack-auth stop", - "timeout": 10 - } - ] - } - ] - } -} diff --git a/plugins/full-stack-auth/skills/adding-api-key-auth/SKILL.md b/plugins/full-stack-auth/skills/adding-api-key-auth/SKILL.md deleted file mode 100644 index 8b9cfe5..0000000 --- a/plugins/full-stack-auth/skills/adding-api-key-auth/SKILL.md +++ /dev/null @@ -1,380 +0,0 @@ ---- -name: adding-api-key-auth -description: > - Creates, validates, lists, and revokes long-lived opaque API keys using - Scalekit for organization-scoped or user-scoped bearer authentication. - Use when adding API key auth to endpoints, building key management UIs, - filtering data by org/user context, or revoking compromised credentials. - Supports Node.js, Python, Go, and Java SDKs. ---- - -# Adding API Key Auth (Scalekit) - -## Flow overview - -``` -Your app creates token (org or user scoped) → Scalekit returns key + tokenId → -Customer stores key → API client sends Bearer key → Your server validates → -Scalekit returns org/user context → Filter data accordingly -``` - -The plain-text API key is **returned only once at creation**. Scalekit never stores it. - ---- - -## 1. Initialize the client - -```python -# Python -from scalekit import ScalekitClient -import os - -scalekit_client = ScalekitClient( - env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"], - client_id=os.environ["SCALEKIT_CLIENT_ID"], - client_secret=os.environ["SCALEKIT_CLIENT_SECRET"], -) -``` - -```javascript -// Node.js -import { ScalekitClient } from '@scalekit-sdk/node'; - -const scalekit = new ScalekitClient( - process.env.SCALEKIT_ENVIRONMENT_URL, - process.env.SCALEKIT_CLIENT_ID, - process.env.SCALEKIT_CLIENT_SECRET -); -``` - -```go -// Go -scalekitClient := scalekit.NewScalekitClient( - os.Getenv("SCALEKIT_ENVIRONMENT_URL"), - os.Getenv("SCALEKIT_CLIENT_ID"), - os.Getenv("SCALEKIT_CLIENT_SECRET"), -) -``` - -```java -// Java -ScalekitClient scalekitClient = new ScalekitClient( - System.getenv("SCALEKIT_ENVIRONMENT_URL"), - System.getenv("SCALEKIT_CLIENT_ID"), - System.getenv("SCALEKIT_CLIENT_SECRET") -); -``` - -Required env vars: `SCALEKIT_ENVIRONMENT_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`. - ---- - -## 2. Create a token - -### Organization-scoped (default) - -Grants access to all resources in the organization's workspace. Use for service-to-service integrations (CI/CD, partner integrations, internal tooling). - -```python -# Python -response = scalekit_client.tokens.create_token( - organization_id=organization_id, - description="CI/CD pipeline token", -) -opaque_token = response.token # show to user once; never stored by Scalekit -token_id = response.token_id # format: apit_xxxxx — use for lifecycle ops -``` - -```javascript -// Node.js -const response = await scalekit.token.createToken(organizationId, { - description: 'CI/CD pipeline token', -}); -const opaqueToken = response.token; -const tokenId = response.tokenId; -``` - -```go -// Go -response, err := scalekitClient.Token().CreateToken( - ctx, organizationId, scalekit.CreateTokenOptions{ - Description: "CI/CD pipeline token", - }, -) -opaqueToken := response.Token -tokenId := response.TokenId -``` - -```java -// Java -CreateTokenResponse response = scalekitClient.tokens().create(organizationId); -String opaqueToken = response.getToken(); -String tokenId = response.getTokenId(); -``` - -### User-scoped (optional `userId`) - -Adds user context so your API can filter data to only that user's resources (personal access tokens, per-user audit trails, user-level rate limiting). Attach `customClaims` for fine-grained authz without extra DB lookups. - -```python -# Python -response = scalekit_client.tokens.create_token( - organization_id=organization_id, - user_id="usr_12345", - custom_claims={"team": "engineering", "environment": "production"}, - description="Deployment service token", -) -``` - -```javascript -// Node.js -const response = await scalekit.token.createToken(organizationId, { - userId: 'usr_12345', - customClaims: { team: 'engineering', environment: 'production' }, - description: 'Deployment service token', -}); -``` - -```go -// Go -response, err := scalekitClient.Token().CreateToken( - ctx, organizationId, scalekit.CreateTokenOptions{ - UserId: "usr_12345", - CustomClaims: map[string]string{"team": "engineering", "environment": "production"}, - Description: "Deployment service token", - }, -) -``` - -```java -// Java -Map claims = Map.of("team", "engineering", "environment", "production"); -CreateTokenResponse response = scalekitClient.tokens().create( - organizationId, "usr_12345", claims, null, "Deployment service token" -); -``` - -**Response fields:** - -| Field | Description | -|--------------|-----------------------------------------------------------| -| `token` | Plain-text API key. **Returned only at creation.** | -| `token_id` | Stable ID (`apit_xxxxx`) for list/invalidate operations. | -| `token_info` | Metadata: org, user, custom claims, timestamps. | - ---- - -## 3. Validate a token - -Call this on every incoming API request. Returns org/user context; throws on invalid, expired, or revoked keys. - -```python -# Python -from scalekit import ScalekitValidateTokenFailureException - -try: - result = scalekit_client.tokens.validate_token(token=opaque_token) - org_id = result.token_info.organization_id - user_id = result.token_info.user_id # empty for org-scoped keys - claims = result.token_info.custom_claims - roles = result.token_info.roles # populated if RBAC is configured - ext_org = result.token_info.organization_external_id -except ScalekitValidateTokenFailureException: - return 401 -``` - -```javascript -// Node.js -import { ScalekitValidateTokenFailureException } from '@scalekit-sdk/node'; - -try { - const result = await scalekit.token.validateToken(opaqueToken); - const { organizationId, userId, customClaims, roles, organizationExternalId } = result.tokenInfo; -} catch (error) { - if (error instanceof ScalekitValidateTokenFailureException) return res.status(401).end(); - throw error; -} -``` - -```go -// Go -result, err := scalekitClient.Token().ValidateToken(ctx, opaqueToken) -if errors.Is(err, scalekit.ErrTokenValidationFailed) { - c.JSON(401, gin.H{"error": "Invalid or expired token"}) - return -} -orgId := result.TokenInfo.OrganizationId -userId := result.TokenInfo.GetUserId() // *string — nil for org-scoped tokens -claims := result.TokenInfo.CustomClaims -``` - -```java -// Java -try { - ValidateTokenResponse result = scalekitClient.tokens().validate(opaqueToken); - String orgId = result.getTokenInfo().getOrganizationId(); - String userId = result.getTokenInfo().getUserId(); - Map claims = result.getTokenInfo().getCustomClaimsMap(); -} catch (TokenInvalidException e) { - response.sendError(401); -} -``` - ---- - -## 4. List tokens - -Supports pagination and optional user filter. - -```python -# Python — list with pagination -response = scalekit_client.tokens.list_tokens( - organization_id=organization_id, - page_size=10, -) -for token in response.tokens: - print(token.token_id, token.description) - -if response.next_page_token: - next_page = scalekit_client.tokens.list_tokens( - organization_id=organization_id, - page_size=10, - page_token=response.next_page_token, - ) - -# Filter by user -user_tokens = scalekit_client.tokens.list_tokens( - organization_id=organization_id, - user_id="usr_12345", -) -``` - -```javascript -// Node.js -const response = await scalekit.token.listTokens(organizationId, { pageSize: 10 }); -if (response.nextPageToken) { - const next = await scalekit.token.listTokens(organizationId, { - pageSize: 10, pageToken: response.nextPageToken - }); -} -const userTokens = await scalekit.token.listTokens(organizationId, { userId: 'usr_12345' }); -``` - ---- - -## 5. Invalidate a token - -Revocation is **instant** — the next validation for that key fails immediately. -The operation is **idempotent**: safe to call on already-revoked keys. - -```python -# Python — by token string or token_id -scalekit_client.tokens.invalidate_token(token=opaque_token) -# or -scalekit_client.tokens.invalidate_token(token=token_id) -``` - -```javascript -// Node.js -await scalekit.token.invalidateToken(opaqueToken); // or tokenId -``` - -```go -// Go -_ = scalekitClient.Token().InvalidateToken(ctx, opaqueToken) // or tokenId -``` - -```java -// Java -scalekitClient.tokens().invalidate(opaqueToken); // or tokenId -``` - ---- - -## 6. Middleware pattern (protect endpoints) - -```python -# Python — Flask decorator -from functools import wraps -from flask import request, jsonify, g -from scalekit import ScalekitValidateTokenFailureException - -def authenticate_token(f): - @wraps(f) - def wrapper(*args, **kwargs): - auth = request.headers.get("Authorization", "") - if not auth.startswith("Bearer "): - return jsonify({"error": "Missing authorization token"}), 401 - try: - result = scalekit_client.tokens.validate_token(token=auth.split(" ", 1)[1]) - g.token_info = result.token_info - except ScalekitValidateTokenFailureException: - return jsonify({"error": "Invalid or expired token"}), 401 - return f(*args, **kwargs) - return wrapper - -@app.route("/api/resources") -@authenticate_token -def get_resources(): - org_id = g.token_info.organization_id # always present - user_id = g.token_info.user_id # present only for user-scoped keys - # query DB filtered by org_id (and user_id if set) -``` - -```javascript -// Node.js — Express middleware -async function authenticateToken(req, res, next) { - const token = (req.headers.authorization || '').replace('Bearer ', ''); - if (!token) return res.status(401).json({ error: 'Missing authorization token' }); - try { - const result = await scalekit.token.validateToken(token); - req.tokenInfo = result.tokenInfo; - next(); - } catch (error) { - if (error instanceof ScalekitValidateTokenFailureException) - return res.status(401).json({ error: 'Invalid or expired token' }); - throw error; - } -} - -app.get('/api/resources', authenticateToken, (req, res) => { - const { organizationId, userId } = req.tokenInfo; -}); -``` - -```go -// Go — Gin middleware -func AuthenticateToken(sc scalekit.Scalekit) gin.HandlerFunc { - return func(c *gin.Context) { - token := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ") - if token == "" { - c.JSON(401, gin.H{"error": "Missing authorization token"}); c.Abort(); return - } - result, err := sc.Token().ValidateToken(c.Request.Context(), token) - if err != nil { - c.JSON(401, gin.H{"error": "Invalid or expired token"}); c.Abort(); return - } - c.Set("tokenInfo", result.TokenInfo) - c.Next() - } -} -``` - -### Data filtering pattern - -| Key type | Filter query by | Example use case | -|---------------------|---------------------------------|-----------------------------------------| -| Organization-scoped | `organizationId` only | All workspace contacts in a CRM | -| User-scoped | `organizationId` + `userId` | Only tasks assigned to the calling user | -| Custom claims | Claims from `customClaims` map | Restrict by `environment`, `team`, etc. | - ---- - -## Key rules - -- **Show `token` once**: Display to user at creation, then discard — Scalekit cannot retrieve it. -- **Validate server-side on every request**: Never trust unverified tokens; call `validateToken` each time. -- **Use `token_id` for lifecycle ops**: Store `token_id` (not the key itself) for list/invalidate workflows. -- **Rotate safely**: Create new key → update consumer → verify → invalidate old key (avoids downtime). -- **Use `expiry` for time-limited access**: Limits blast radius if a key is compromised. -- **Never log or commit keys**: Treat API keys like passwords — use encrypted secrets managers or env vars. diff --git a/plugins/full-stack-auth/skills/adding-oauth2-to-apis/SKILL.md b/plugins/full-stack-auth/skills/adding-oauth2-to-apis/SKILL.md deleted file mode 100644 index 4ecedea..0000000 --- a/plugins/full-stack-auth/skills/adding-oauth2-to-apis/SKILL.md +++ /dev/null @@ -1,232 +0,0 @@ ---- -name: adding-oauth2-to-apis -description: > - Implements OAuth 2.0 client-credentials authentication on API endpoints using - Scalekit as the authorization server. Use when protecting APIs with - machine-to-machine auth, registering API clients for organizations, issuing - bearer tokens, validating JWTs via JWKS, or enforcing scopes in middleware. ---- - -# Adding OAuth 2.0 to APIs (Scalekit) - -## Flow overview - -``` -Register client (your app) → Issue client_id + secret (Scalekit) → -API client fetches bearer token → Your server validates JWT + scopes -``` - -Security-critical steps (token validation, scope enforcement) use **low freedom** — follow them exactly. - ---- - -## 1. Install - -```bash -pip install scalekit-sdk-python -# or -npm install @scalekit-sdk/node -``` - -Initialize once and reuse: - -```python -from scalekit import ScalekitClient -import os - -scalekit_client = ScalekitClient( - env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), - client_id=os.getenv("SCALEKIT_CLIENT_ID"), - client_secret=os.getenv("SCALEKIT_CLIENT_SECRET") -) -``` - -Required env vars: `SCALEKIT_ENVIRONMENT_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`. - ---- - -## 2. Register an API client for an organization - -One organization can have multiple API clients. Registration returns `client_id` and `plain_secret` — **`plain_secret` is shown only once; never stored by Scalekit**. - -```python -from scalekit.v1.clients.clients_pb2 import OrganizationClient - -response = scalekit_client.m2m_client.create_organization_client( - organization_id="", - m2m_client=OrganizationClient( - name="GitHub Actions Deployment Service", - description="Deploys to production via GitHub Actions", - scopes=["deploy:applications", "read:deployments"], # resource:action pattern - audience=["deployment-api.acmecorp.com"], - custom_claims=[ - {"key": "github_repository", "value": "acmecorp/inventory-service"}, - {"key": "environment", "value": "production_us"} - ], - expiry=3600 # seconds; default 3600 - ) -) - -client_id = response.client.client_id -plain_secret = response.plain_secret # store this securely; not retrievable again -``` - -**cURL equivalent** (if not using SDK): - -```bash -curl -X POST "$SCALEKIT_ENVIRONMENT_URL/api/v1/organizations//clients" \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -d '{ - "name": "GitHub Actions Deployment Service", - "scopes": ["deploy:applications", "read:deployments"], - "audience": ["deployment-api.acmecorp.com"], - "expiry": 3600 - }' -``` - -> Scope naming convention: use `resource:action` (e.g. `deployments:read`, `applications:create`). - ---- - -## 3. API client fetches a bearer token - -This step runs inside the **API client's** code, not your server. Shown here for reference. - -```bash -curl -X POST "$SCALEKIT_ENVIRONMENT_URL/oauth/token" \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "grant_type=client_credentials" \ - -d "client_id=" \ - -d "client_secret=" -``` - -Response: - -```json -{ - "access_token": "", - "token_type": "Bearer", - "expires_in": 86399, - "scope": "deploy:applications read:deployments" -} -``` - -The client sends this JWT in `Authorization: Bearer ` on every API request. - ---- - -## 4. Validate the JWT on your API server - -**Do this on EVERY request. Never trust unverified tokens.** - -### Python (SDK handles JWKS automatically) - -```python -token = request.headers.get("Authorization", "").removeprefix("Bearer ") - -try: - claims = scalekit_client.validate_access_token_and_get_claims(token=token) - # claims["scopes"] → list of granted scopes -except Exception: - return 401 # invalid or expired -``` - -### Node.js (manual JWKS + JWT verify) - -```js -import jwksClient from 'jwks-rsa'; -import jwt from 'jsonwebtoken'; - -const jwks = jwksClient({ - jwksUri: `${process.env.SCALEKIT_ENVIRONMENT_URL}/.well-known/jwks.json`, - cache: true -}); - -async function verifyToken(token) { - const decoded = jwt.decode(token, { complete: true }); - const key = await jwks.getSigningKey(decoded.header.kid); - return jwt.verify(token, key.getPublicKey(), { - algorithms: ['RS256'], - complete: true - }).payload; // contains scopes, sub, iss, exp, oid, etc. -} -``` - -Decoded JWT payload structure: - -```json -{ - "client_id": "m2morg_69038819013296423", - "oid": "org_59615193906282635", - "scopes": ["deploy:applications", "read:deployments"], - "iss": "", - "exp": 1745305340 -} -``` - ---- - -## 5. Enforce scopes in middleware - -### Flask (Python) - -```python -import functools -from flask import request, jsonify - -def require_scope(scope): - def decorator(f): - @functools.wraps(f) - def wrapper(*args, **kwargs): - token = request.headers.get("Authorization", "").removeprefix("Bearer ") - if not token: - return jsonify({"error": "Missing token"}), 401 - try: - claims = scalekit_client.validate_access_token_and_get_claims(token=token) - except Exception: - return jsonify({"error": "Invalid token"}), 401 - if scope not in claims.get("scopes", []): - return jsonify({"error": "Insufficient permissions"}), 403 - return f(*args, **kwargs) - return wrapper - return decorator - -# Usage: -# @app.route('/deploy', methods=['POST']) -# @require_scope('deploy:applications') -# def deploy(): ... -``` - -### Express (Node.js) - -```js -function requireScope(scope) { - return async (req, res, next) => { - const token = (req.headers.authorization || '').replace('Bearer ', ''); - if (!token) return res.status(401).send('Missing token'); - try { - const payload = await verifyToken(token); // from step 4 - if (!payload.scopes?.includes(scope)) - return res.status(403).send('Insufficient permissions'); - req.tokenClaims = payload; - next(); - } catch { - res.status(401).send('Invalid token'); - } - }; -} - -// Usage: -// app.post('/deploy', requireScope('deploy:applications'), handler); -``` - ---- - -## Key rules - -- `plain_secret` is **returned once only** — instruct customers to store it immediately. -- Always validate tokens **server-side** before trusting claims. -- Cache JWKS keys (avoid fetching on every request); rotate on `kid` mismatch. -- Use `resource:action` scope naming for clarity. -- An `organization_id` maps to one customer; multiple API clients per org are supported. diff --git a/plugins/full-stack-auth/skills/full-stack-auth/SKILL.md b/plugins/full-stack-auth/skills/full-stack-auth/SKILL.md deleted file mode 100644 index 389d2f1..0000000 --- a/plugins/full-stack-auth/skills/full-stack-auth/SKILL.md +++ /dev/null @@ -1,95 +0,0 @@ ---- -name: implementing-scalekit-fsa -description: Implements Scalekit full-stack authentication (FSA) including sign-up, login, logout, and secure session management using JWT tokens. Use when building or integrating user authentication with the Scalekit SDK across Node.js, Python, Go, or Java — or when the user asks about auth flows, OAuth callbacks, token refresh, or session handling with Scalekit. ---- - -# Scalekit Full-Stack Authentication - -## Setup - -Install the SDK and set credentials in `.env`: - -```sh -SCALEKIT_ENVIRONMENT_URL= -SCALEKIT_CLIENT_ID= -SCALEKIT_CLIENT_SECRET= -``` - -## Auth flow - -### 1. Redirect to login - -Generate an authorization URL and redirect the user: - -```js -// Node.js -const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, { - scopes: ['openid', 'profile', 'email', 'offline_access'] -}); -res.redirect(authorizationUrl); -``` - -> `redirectUri` must exactly match the allowed callback URL registered in the Scalekit dashboard. - -### 2. Handle the callback - -Exchange the authorization code for tokens: - -```js -// Node.js -const { user, idToken, accessToken, refreshToken } = - await scalekit.authenticateWithCode(code, redirectUri); -``` - -| Token | Purpose | -|---|---| -| `idToken` | Full user profile (sub, oid, email, name, exp) | -| `accessToken` | Roles + permissions; expires in 5 min (configurable) | -| `refreshToken` | Long-lived; use to renew access tokens | - -### 3. Create the session - -Store tokens in HttpOnly cookies: - -```js -// Node.js -res.cookie('accessToken', authResult.accessToken, { - maxAge: (authResult.expiresIn - 60) * 1000, - httpOnly: true, secure: true, path: '/api', sameSite: 'strict' -}); -res.cookie('refreshToken', authResult.refreshToken, { - httpOnly: true, secure: true, path: '/auth/refresh', sameSite: 'strict' -}); -``` - -**Token validation middleware pattern:** -1. Read `accessToken` cookie → decrypt → `scalekit.validateAccessToken(token)` -2. If invalid → `scalekit.refreshAccessToken(refreshToken)` → update cookies -3. If refresh fails → log out the user - -### 4. Log out - -Clear session data, then redirect to Scalekit's logout endpoint: - -```js -// Node.js -clearSessionData(); -const logoutUrl = scalekit.getLogoutUrl(idTokenHint, postLogoutRedirectUri); -res.redirect(logoutUrl); // One-time use URL; expires after logout -``` - -## Cross-language reference - -All SDK methods follow the same pattern across languages with minor naming conventions: - -| Operation | Node.js | Python | Go | Java | -|---|---|---|---|---| -| Auth URL | `getAuthorizationUrl` | `get_authorization_url` | `GetAuthorizationUrl` | `getAuthorizationUrl` | -| Exchange code | `authenticateWithCode` | `authenticate_with_code` | `AuthenticateWithCode` | `authenticateWithCode` | -| Validate token | `validateAccessToken` | `validate_access_token` | `ValidateAccessToken` | `validateAccessToken` | -| Refresh token | `refreshAccessToken` | `refresh_access_token` | `RefreshAccessToken` | `refreshToken` | -| Logout URL | `getLogoutUrl` | `get_logout_url` | `GetLogoutUrl` | `getLogoutUrl` | - -## What this unlocks - -One integration enables: Magic Link & OTP, social sign-ins, enterprise SSO, workspaces, MCP authentication, SCIM provisioning, and user management. diff --git a/plugins/full-stack-auth/skills/implement-logout/SKILL.md b/plugins/full-stack-auth/skills/implement-logout/SKILL.md deleted file mode 100644 index 47e76ef..0000000 --- a/plugins/full-stack-auth/skills/implement-logout/SKILL.md +++ /dev/null @@ -1,187 +0,0 @@ ---- -name: implementing-fsa-logout -description: Implements a complete logout flow for Scalekit FSA integrations by clearing application session cookies and redirecting the browser to Scalekit’s /oidc/logout endpoint to invalidate the Scalekit session. Use when adding or fixing logout in Node.js, Python, Go, or Java web apps that use Scalekit OIDC. ---- - -# Implementing logout (Scalekit FSA) - -## Goal -Implement a single `/logout` endpoint that: -- Clears the application session layer (your cookies/tokens). -- Invalidates the Scalekit session layer by redirecting the browser to Scalekit’s OIDC logout endpoint. -- Returns the user to a safe, allowlisted post-logout redirect URL. - -## Key constraints (must follow) -- The Scalekit logout call MUST be a browser redirect (top-level navigation), not a `fetch`/XHR from frontend and not a server-to-server API call. -- The ID token (often `idToken`) MUST be read BEFORE clearing cookies, because it is used as `id_token_hint`. -- The `post_logout_redirect_uri` MUST be allowlisted in Scalekit Dashboard (Post Logout URLs). - -## Inputs to collect from the user/project -Ask for (or infer from the codebase): -- Tech stack: Express/Fastify/Next.js (Node), Flask/Django (Python), Gin/Fiber (Go), Spring Boot (Java), etc. -- Where tokens are stored: cookie names (default examples: `accessToken`, `refreshToken`, `idToken`) and cookie attributes (Path, Domain, SameSite). -- The post-logout landing URL (example: `http://localhost:3000/login` or your production login page). -- Scalekit configuration: base URL / environment, and whether the project uses a Scalekit SDK helper like `getLogoutUrl(...)`. - -## Recommended implementation (workflow) -1. Locate the current auth/session code: -- Find where access/refresh/ID tokens are set. -- Note cookie names, paths, domains, and SameSite settings (you must match these when clearing). - -2. Add a GET `/logout` route: -- Extract `idToken` (or equivalent) from cookies/session storage. -- Compute `postLogoutRedirectUri`. -- Build the Scalekit logout URL pointing at `/oidc/logout`, preferably using the Scalekit SDK helper if present. -- Clear session cookies (access/refresh/id), preserving the correct Path/Domain so deletion actually works. -- Redirect (302) the browser to the Scalekit logout URL. - -3. Configure Scalekit Dashboard allowlist: -- Register `postLogoutRedirectUri` under: Redirects → Post Logout URL. - -4. Verify and iterate: -- In DevTools → Network, clicking logout should show a **document** navigation to Scalekit (not XHR/fetch). -- Confirm the request includes the Scalekit session cookie automatically. -- After redirecting back, logging in should not silently reuse the application cookies you intended to clear. - -## Reference behavior (pseudocode) -- Read `id_token_hint` from cookie/session. -- `logoutUrl = scalekit.getLogoutUrl(id_token_hint, post_logout_redirect_uri)` -- Clear cookies (access/refresh/id). -- `302 -> logoutUrl` - -## Implementation templates - -### Node.js (Express) -```js -app.get('/logout', (req, res) => { - const idTokenHint = req.cookies?.idToken; // read BEFORE clearing - const postLogoutRedirectUri = process.env.POST_LOGOUT_REDIRECT_URI ?? 'http://localhost:3000/login'; - - // Prefer SDK helper if available in your project - const logoutUrl = scalekit.getLogoutUrl(idTokenHint, postLogoutRedirectUri); - - // Clear cookies (match Path/Domain/SameSite used when setting them) - res.clearCookie('accessToken', { path: '/' }); - res.clearCookie('refreshToken', { path: '/' }); - res.clearCookie('idToken', { path: '/' }); - - return res.redirect(logoutUrl); -}); -``` - -### Python (Flask) -```py -from flask import request, redirect, make_response - -@app.get("/logout") -def logout(): - id_token = request.cookies.get("idToken") # read BEFORE clearing - post_logout_redirect_uri = os.getenv("POST_LOGOUT_REDIRECT_URI", "http://localhost:3000/login") - - logout_url = scalekit_client.get_logout_url( - id_token_hint=id_token, - post_logout_redirect_uri=post_logout_redirect_uri - ) - - resp = make_response(redirect(logout_url)) - resp.set_cookie("accessToken", "", max_age=0, path="/") - resp.set_cookie("refreshToken", "", max_age=0, path="/") - resp.set_cookie("idToken", "", max_age=0, path="/") - return resp -``` - -### Go (Gin) -```go -func LogoutHandler(c *gin.Context) { - idToken, _ := c.Cookie("idToken") // read BEFORE clearing - postLogoutRedirectURI := os.Getenv("POST_LOGOUT_REDIRECT_URI") - if postLogoutRedirectURI == "" { - postLogoutRedirectURI = "http://localhost:3000/login" - } - - logoutURL, err := scalekit.GetLogoutUrl(scalekit.LogoutUrlOptions{ - IdTokenHint: idToken, - PostLogoutRedirectUri: postLogoutRedirectURI, - }) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - - // Clear cookies (match original attributes) - c.SetCookie("accessToken", "", -1, "/", "", true, true) - c.SetCookie("refreshToken", "", -1, "/", "", true, true) - c.SetCookie("idToken", "", -1, "/", "", true, true) - - c.Redirect(http.StatusFound, logoutURL.String()) -} -``` - -### Java (Spring Boot) -```java -@GetMapping("/logout") -public void logout(HttpServletRequest request, HttpServletResponse response) throws IOException { - String idToken = null; - if (request.getCookies() != null) { - for (Cookie c : request.getCookies()) { - if ("idToken".equals(c.getName())) { - idToken = c.getValue(); - break; - } - } - } - - String postLogoutRedirectUri = System.getenv().getOrDefault( - "POST_LOGOUT_REDIRECT_URI", - "http://localhost:3000/login" - ); - - URL logoutUrl = scalekitClient.authentication().getLogoutUrl( - idToken, - postLogoutRedirectUri - ); - - // Clear cookies (ensure Path/Domain match your app cookies) - Cookie access = new Cookie("accessToken", ""); - access.setMaxAge(0); - access.setPath("/"); - access.setHttpOnly(true); - access.setSecure(true); - response.addCookie(access); - - Cookie refresh = new Cookie("refreshToken", ""); - refresh.setMaxAge(0); - refresh.setPath("/"); - refresh.setHttpOnly(true); - refresh.setSecure(true); - response.addCookie(refresh); - - Cookie id = new Cookie("idToken", ""); - id.setMaxAge(0); - id.setPath("/"); - id.setHttpOnly(true); - id.setSecure(true); - response.addCookie(id); - - response.sendRedirect(logoutUrl.toString()); -} -``` - -## Logout security checklist (copy/paste) -- Extract ID token BEFORE clearing cookies. -- Clear all application session cookies (access/refresh/id). -- Redirect browser (302) to Scalekit `/oidc/logout` via the generated logout URL. -- Ensure `post_logout_redirect_uri` is allowlisted in Scalekit dashboard. -- Validate logout is a document navigation (not XHR/fetch) and cookies are actually removed. - -## Common failure modes (what to check) -- “Logout doesn’t really log out”: cookie deletion mismatches Path/Domain/SameSite; clear cookies with the same attributes used when setting them. -- “Login immediately succeeds after logout”: identity provider session may still be active; this is expected for SSO providers, but your app cookies should still be cleared. -- “Scalekit logout doesn’t take effect”: logout was done via API call rather than browser redirect; use a redirect so the Scalekit session cookie is included automatically. -- “Redirect rejected”: `post_logout_redirect_uri` is not allowlisted in Scalekit dashboard. - -## Output expectations when using this skill -When asked to implement logout in a real repo, the assistant should: -- Identify the correct cookie names and where they are set. -- Implement `/logout` with the correct sequence (read id token → build logout URL → clear cookies → redirect). -- Provide a brief test plan and the exact dashboard value to allowlist for post-logout redirect. diff --git a/plugins/full-stack-auth/skills/implementing-access-control/SKILL.md b/plugins/full-stack-auth/skills/implementing-access-control/SKILL.md deleted file mode 100644 index 164a666..0000000 --- a/plugins/full-stack-auth/skills/implementing-access-control/SKILL.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -name: implementing-access-control -description: Implements server-side RBAC and permission checks by validating and decoding access tokens, extracting roles/permissions, and enforcing them with middleware/decorators at route boundaries. Use when building authorization around Scalekit tokens that embed roles and permissions. ---- - -# Implementing access control (Scalekit FSA) - -## When to use -Use this Skill after authentication is working and the app must authorize access to routes/actions by inspecting the user's access token for `roles` and `permissions`. -Scalekit can embed these authorization details in the access token during the authentication flow, so the app can make decisions without extra API calls. -Always validate the token's integrity before trusting any embedded roles/permissions. - -## Workflow -1. Validate the access token (expiry, issuer/audience as applicable) and then decode it to extract `sub`, `oid`, `roles`, and `permissions`. -2. Attach a normalized auth context to the request (ele: `req.user = { id, organizationId, roles, permissions }`) so downstream handlers can authorize consistently. -3. Enforce authorization at route boundaries using (a) role checks for broad access patterns and (b) permission checks for fine-grained actions (often `resource:action`). -4. Combine checks when needed (examples: "admin bypass", "resource ownership", time-based restrictions for sensitive operations). -5. Never rely on client-side authorization alone; enforce roles/permissions server-side. - -## Reference implementation - -### Node.js (Express-style middleware) - -Validate+extract, then RBAC/PBAC guards. - -```js -// validate + extract -const validateAndExtractAuth = async (req, res, next) => { - try { - const accessToken = decrypt(req.cookies.accessToken); // if encrypted - const isValid = await scalekit.validateAccessToken(accessToken); - if (!isValid) return res.status(401).json({ error: "Invalid or expired token" }); - - const tokenData = await dessToken(accessToken); // JWT decode library - req.user = { - id: tokenData.sub, - organizationId: tokenData.oid, - roles: tokenData.roles || [], - permissions: tokenData.permissions || [] - }; - next(); - } catch { - return res.status(401).json({ error: "Authentication failed" }); - } -}; - -// RBAC -const hasRole = (user, role) => user.roles?.includes(role); -const requireRole = (role) => (req, res, next) => - hasRole(req.user, role) ? next() : res.status(403).json({ error: `Access denied. Required role: ${role}` }); - -// PBAC -const hasPermission = (user, perm) => user.permissions?.includes(perm); -const requirePermission = (perm) => (req, res, next) => - hasPermission(req.user, perm) ? next() : res.status(403).json({ error: `Access denied. Required permission: ${perm}` }); - -// usage -app.get("/api/projects", validateAndExtractAuth, requirePermission("projects:read"), handler); -app.get("/api/admin/users", validateAndExtractAuth, requireRole("admin"), handler); -``` - -### Python (decorator pattern) - -Validate+extract, then RBAC/PBAC decorators. - -```py -from functools import wraps - -def validate_and_extract_auth(f): - @wraps(f) - def decorated(*args, **kwargs): - access_token = decrypt(request.cookies.get("accessToken")) - if not scalekit_client.validate_access_token(access_token): - return jsonify({"error": "Invalid or expired token"}), 401 - - token_data = scalekit_client.decode_access_token(access_token) - request.user = { - "id": token_data.get("sub"), - "organization_id": token_data.get("oid"), - "roles": token_data.get("roles", []), - "permissions": token_data.get("permissions", []), - } - return f(*args, **kwargs) - return decorated - -def require_role(role): - def decorator(f): - @wraps(f) - def decorated(*args, **kwargs): - if role not in getattr(request, "user", {}).get("roles", []): - return jsonify({"error": f"Access denied. Required role: {role}"}), 403 - return f(*args, **kwargs) - return decorated - return decorator - -def require_permission(permission): - def decorator(f): - @wraps(f) - def decorated(*args, **kwargs): - if permission not in getattr(request, "user", {}).get("permissions", []): - return jsonify({"error": f"Access denied. Required permission: {permission}"}), 403 - return f(*args, **kwargs) - return decorated - return decorator -``` - -## Patterns and pitfalls - -Prefer roles for broad tiers (admin/manager/member) and permissions for granular actions like `projects:create` or `tasks:assign`. -Common patterns include "admin bypass" (admins skip some permission checks) and "resource ownership" (user can edit only their own resource unless elevated). -Avoid building authorization solely in the frontend because it can be bypassed. - -## Checklist - -- Token is validated before decoding/using claims. -- `roles` and `permissions` are normalizeays and attached to request context. -- Every protected route applies `requireRole(...)` and/or `requirePermission(...)` at the boundary. -- Permission names follow a consistent `resource:action` convention. -- Client-side checks are treated as UX only; server-side checks are authoritative. diff --git a/plugins/full-stack-auth/skills/implementing-admin-portal/SKILL.md b/plugins/full-stack-auth/skills/implementing-admin-portal/SKILL.md deleted file mode 100644 index dd8b6a3..0000000 --- a/plugins/full-stack-auth/skills/implementing-admin-portal/SKILL.md +++ /dev/null @@ -1,152 +0,0 @@ ---- -name: implementing-admin-portal -description: Implements Scalekit's admin portal for customer self-serve SSO and SCIM configuration. Generates portal links server-side and embeds the portal as an iframe in the app's settings UI. Use when the user asks to add an admin portal, customer self-serve SSO setup, iframe embed for SSO config, shareable setup link, or let customers configure their own SSO or SCIM connection. ---- - -# Admin Portal with Scalekit - -Adds a self-serve portal where customers configure their own SSO and SCIM settings — embedded inside your app's settings UI. - -If the user only needs a quick shareable link with no code (e.g., for a one-time onboarding call), skip to the **Shareable link** section at the bottom. - ---- - -## Implementation progress - -``` -Admin Portal Implementation Progress: -- [ ] Step 1: Install SDK -- [ ] Step 2: Set environment credentials -- [ ] Step 3: Register app domain in dashboard -- [ ] Step 4: Generate portal link (server-side) -- [ ] Step 5: Render iframe (client-side) -- [ ] Step 6: Handle session expiry events -- [ ] Step 7: Verify portal loads and events fire correctly -``` - ---- - -## Step 1: Install SDK - -Detect the project's language/framework from existing files and install: - -| Stack | Install | -|---------|---------| -| Node.js | `npm install @scalekit-sdk/node` | -| Python | `pip install scalekit-sdk-python` | -| Go | `go get github.com/scalekit-inc/scalekit-sdk-go/v2` | -| Java | Add `com.scalekit:scalekit-sdk` to `pom.xml` | - ---- - -## Step 2: Set environment credentials - -Add to `.env` (never hardcode): - -```shell -SCALEKIT_ENVIRONMENT_URL='https://.scalekit.com' -SCALEKIT_CLIENT_ID='' -SCALEKIT_CLIENT_SECRET='' -``` - -Credentials are in **Dashboard > Developers > Settings > API Credentials**. - ---- - -## Step 3: Register app domain - -In **Dashboard > Developers > API Configuration > Redirect URIs**, add the domain where the portal will be embedded. The iframe will be blocked if this is missing. - ---- - -## Step 4: Generate the portal link (server-side) - -Generate a new link on every page load — links are single-use. Plug into the existing route or controller that serves the settings/admin page: - -**Node.js:** -```javascript -const { location } = await scalekit.organization.generatePortalLink(organizationId); -// Pass `location` to the frontend as a template variable or API response -``` - -**Python:** -```python -portal = scalekit_client.organization.generate_portal_link(organization_id) -location = portal.location -# Pass `location` to your template or JSON response -``` - -**Never cache this value** — each link is single-use and will fail if reused. - ---- - -## Step 5: Render the iframe (client-side) - -In the frontend settings/admin template, inject `location` as the `src`: - -```html - -``` - -Minimum recommended height: **600px**. Match the variable name to the project's existing templating convention. - ---- - -## Step 6: Handle portal UI events - -Listen for messages from the iframe to react to configuration changes and session expiry: - -```javascript -window.addEventListener('message', (event) => { - if (event.origin !== process.env.SCALEKIT_ENVIRONMENT_URL) return; - - const { type } = event.data; - switch (type) { - case 'SSO_CONFIGURED': - // Refresh org status, show success banner, etc. - break; - case 'SESSION_EXPIRED': - // Re-fetch a new portal link and reload the iframe src - reloadPortalIframe(); - break; - } -}); -``` - -`SESSION_EXPIRED` handling is required — without it the portal silently breaks for long-lived sessions. - ---- - -## Step 7: Verify - -- [ ] Open the settings page — confirm the iframe renders without console errors -- [ ] Complete a test SSO configuration inside the portal — confirm `SSO_CONFIGURED` fires -- [ ] Wait for session expiry (or simulate it) — confirm `SESSION_EXPIRED` triggers a link refresh -- [ ] Confirm portal link is never the same across two page loads (single-use verification) - ---- - -## Branding (optional) - -Configure at **Dashboard > Settings > Branding**: logo, accent color, favicon. Custom domain support (e.g., `sso.yourapp.com`) is available in the Scalekit dashboard. - ---- - -## Guardrails - -- **Generate link server-side only** — never expose `CLIENT_SECRET` to the browser -- **Re-generate on every page load** — caching will break the portal -- **Register your domain** in Redirect URIs before testing or the iframe will be blocked -- **Handle `SESSION_EXPIRED`** — re-generate and reload, don't let it fail silently - ---- - -## Shareable link (no-code alternative) - -For one-time onboarding calls or zero-engineering setup: go to **Dashboard > Organizations**, select the org, click **Generate link**, and share the URL directly. The link gives anyone who has it full access to configure that org's SSO/SCIM settings — use the iframe approach for production. Also share Scalekit's [SSO setup guides](https://docs.scalekit.com/guides/integrations/sso-integrations/) so the IT admin has provider-specific configuration steps alongside the portal link. diff --git a/plugins/full-stack-auth/skills/implementing-scalekit-django-auth/SKILL.md b/plugins/full-stack-auth/skills/implementing-scalekit-django-auth/SKILL.md deleted file mode 100644 index 7fffb09..0000000 --- a/plugins/full-stack-auth/skills/implementing-scalekit-django-auth/SKILL.md +++ /dev/null @@ -1,294 +0,0 @@ ---- -name: implementing-scalekit-django-auth -description: Implements Scalekit authentication in a Django project using the patterns from scalekit-inc/scalekit-django-auth-example. Handles login, OAuth callback, Django session storage, automatic token refresh via middleware, logout, and permission-based route protection using decorators. Use when adding auth views, protecting URLs, managing sessions, or checking permissions in a Django + Scalekit codebase. ---- - -# Scalekit Auth — Django - -Reference repo: [scalekit-inc/scalekit-django-auth-example](https://github.com/scalekit-inc/scalekit-django-auth-example) - -## Project structure - -``` -auth_app/ -├── scalekit_client.py # ScalekitClient class + scalekit_client() singleton -├── views.py # All auth + protected views -├── decorators.py # @login_required, @permission_required('perm:name') -├── middleware.py # ScalekitTokenRefreshMiddleware (auto token refresh) -└── urls.py # URL patterns (app_name = 'auth_app') - -scalekit_django_auth/ -└── settings.py # SCALEKIT_* settings, middleware registration, session config -``` - -## Environment variables - -```env -SCALEKIT_ENV_URL=https://your-env.scalekit.io -SCALEKIT_CLIENT_ID=your-client-id -SCALEKIT_CLIENT_SECRET=your-client-secret -SCALEKIT_REDIRECT_URI=http://localhost:8000/auth/callback -# SCALEKIT_SCOPES is set directly in settings.py, not from env -``` - -> `SCALEKIT_ENV_URL` also falls back to `SCALEKIT_DOMAIN` for backward compatibility. -> `SCALEKIT_REDIRECT_URI` has no trailing slash — this avoids Django redirect issues. - -## Django settings (`settings.py`) - -Key non-obvious settings to include: - -```python -INSTALLED_APPS = [ - 'django.contrib.contenttypes', - 'django.contrib.sessions', # Required for session storage - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'auth_app', -] - -MIDDLEWARE = [ - # ... - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'auth_app.middleware.ScalekitTokenRefreshMiddleware', # MUST come after SessionMiddleware - # ... -] - -SESSION_ENGINE = 'django.contrib.sessions.backends.db' -SESSION_COOKIE_AGE = 3600 -SESSION_COOKIE_HTTPONLY = True -SESSION_COOKIE_SAMESITE = 'Lax' -SESSION_SAVE_EVERY_REQUEST = True # Required — ensures OAuth state persists across requests - -SCALEKIT_ENV_URL = os.getenv('SCALEKIT_ENV_URL', os.getenv('SCALEKIT_DOMAIN', '')) -SCALEKIT_CLIENT_ID = os.getenv('SCALEKIT_CLIENT_ID', '') -SCALEKIT_CLIENT_SECRET = os.getenv('SCALEKIT_CLIENT_SECRET', '') -SCALEKIT_REDIRECT_URI = os.getenv('SCALEKIT_REDIRECT_URI', 'http://localhost:8000/auth/callback') -SCALEKIT_SCOPES = 'openid profile email offline_access' # offline_access required for refresh token - -LOGIN_URL = '/login' -``` - -## SDK client (`auth_app/scalekit_client.py`) - -Lazy singleton — always use `scalekit_client()`, never instantiate directly: - -```python -from auth_app.scalekit_client import scalekit_client - -client = scalekit_client() # raises ValueError with helpful message if env vars missing -``` - -SDK import paths: -```python -from scalekit import ScalekitClient as SDKClient -from scalekit.common.scalekit import ( - AuthorizationUrlOptions, - CodeAuthenticationOptions, - TokenValidationOptions, - LogoutUrlOptions, -) -``` - -Key methods on `ScalekitClient`: - -| Method | SDK call | Returns | -|---|---|---| -| `get_authorization_url(state)` | `sdk_client.get_authorization_url(redirect_uri, options)` | `str` URL | -| `exchange_code_for_tokens(code)` | `sdk_client.authenticate_with_code(code, redirect_uri, options)` | `dict` with `access_token`, `refresh_token`, `id_token`, `user`, `expires_in` | -| `get_user_info(access_token)` | `sdk_client.validate_access_token_and_get_claims(token, options)` | `dict` claims | -| `refresh_access_token(refresh_token)` | `sdk_client.refresh_access_token(refresh_token)` | `dict` with `access_token`, `refresh_token` | -| `validate_token_and_get_claims(token)` | `sdk_client.validate_access_token_and_get_claims(token, options)` | `dict` claims | -| `has_permission(access_token, permission)` | validates claims, checks permission key chain | `bool` | -| `logout(access_token, id_token)` | `sdk_client.get_logout_url(options)` | `str` URL | - -## Session storage schema - -All auth state is stored in Django's session (no extra DB tables): - -```python -request.session['scalekit_user'] = { - 'sub', 'email', 'name', 'given_name', 'family_name', - 'preferred_username', 'claims' # full access token claims dict -} -request.session['scalekit_tokens'] = { - 'access_token', 'refresh_token', 'id_token', - 'expires_at', # ISO 8601 string (timezone-aware) - 'expires_in' # int seconds -} -request.session['scalekit_roles'] = [] # from access token claims -request.session['scalekit_permissions'] = [] # from access token claims -``` - -Check authentication anywhere: `request.session.get('scalekit_user')` → truthy if logged in. - -## Auth flow - -### Login (`login_view` — GET `/login/`) - -```python -state = secrets.token_urlsafe(32) -request.session['oauth_state'] = state -request.session.save() # Explicit save — required for state to survive redirect -auth_url = client.get_authorization_url(state=state) -# Pass auth_url to template; user clicks it to redirect to Scalekit -``` - -### Callback (`callback_view` — GET `/auth/callback`) - -1. Validate `state` param vs `request.session['oauth_state']` → render error on mismatch -2. `request.session.pop('oauth_state', None)` -3. `token_response = client.exchange_code_for_tokens(code)` -4. `user_obj = token_response.get('user', {})` — camelCase fields (`givenName`, `familyName`, `id`) -5. `user_info = client.get_user_info(access_token)` — snake_case claims for roles/permissions -6. Name resolution: `user_obj.name` → `givenName + familyName` → `user_info claims` → `email` -7. `expires_at = timezone.now() + timedelta(seconds=expires_in)` -8. Write `scalekit_user`, `scalekit_tokens`, `scalekit_roles`, `scalekit_permissions` to session -9. Redirect to `auth_app:dashboard` - -Permission claim fallback chain (same as Node SDK): -```python -permissions = ( - claims.get('permissions', []) or - claims.get('https://scalekit.com/permissions', []) or - claims.get('scalekit:permissions', []) or - [] -) -``` - -### Logout (`logout_view` — GET `/logout/`) - -```python -logout_url = client.logout(access_token, id_token) -# post_logout_redirect_uri = SCALEKIT_REDIRECT_URI.replace('/auth/callback', '') -request.session.flush() # Wipes entire session -return redirect(logout_url) # Server-side redirect (not JSON like Next.js) -``` - -### Token refresh — middleware (`auth_app/middleware.py`) - -`ScalekitTokenRefreshMiddleware` runs on every request. Skipped paths: -`/login`, `/auth/callback`, `/logout`, `/static/`, `/sessions/refresh-token` - -Buffer: **1 minute** (vs 5 min in client `is_token_expired` helper). - -Also available as a manual API endpoint: `POST /sessions/refresh-token/` → `JsonResponse`. - -## Protecting views - -```python -from auth_app.decorators import login_required, permission_required - -@login_required -def dashboard_view(request): ... # Redirects to /login?next= if unauthenticated - -@permission_required('organization:settings') -def org_settings_view(request): ... # Renders permission_denied.html with 403 if missing -``` - -## URL configuration - -```python -# auth_app/urls.py — app_name = 'auth_app' -path('auth/callback', callback_view, name='callback'), # No trailing slash — intentional -path('sessions/validate-token/', validate_token_view), # POST only -path('sessions/refresh-token/', refresh_token_view), # POST only -``` - -Use `reverse('auth_app:dashboard')` / `{% url 'auth_app:login' %}` in templates. - -## Route map - -| URL | Auth | Notes | -|---|---|---| -| `/` | No | Redirects to dashboard if already logged in | -| `/login/` | No | Generates auth URL, stores CSRF state | -| `/auth/callback` | No | No trailing slash | -| `/dashboard/` | `@login_required` | | -| `/logout/` | `@login_required` | | -| `/sessions/` | `@login_required` | | -| `/sessions/validate-token/` | `@login_required` | POST | -| `/sessions/refresh-token/` | `@login_required` | POST | -| `/organization/settings/` | `@permission_required('organization:settings')` | | - -## Install - -```bash -pip install scalekit python-dotenv django -python manage.py migrate # Creates session table (db.sqlite3, zero-config) -python manage.py runserver -``` - -## Tactics - -### SameSite=Lax — never Strict -`SESSION_COOKIE_SAMESITE = 'Lax'` is correct. Do not change to `'Strict'` — it drops the session cookie on the cross-origin redirect from Scalekit back to `/auth/callback`, so `oauth_state` is unavailable and the CSRF check fails on every login. - -### SESSION_SAVE_EVERY_REQUEST = True — why it matters -The OAuth flow involves at least two redirects. Without `SESSION_SAVE_EVERY_REQUEST = True`, the session containing `oauth_state` may not be written to the database before Django redirects to Scalekit, causing a state mismatch on the callback. This setting ensures session writes happen on every response. - -### @csrf_exempt on the callback view -The OAuth callback receives a GET request from Scalekit (an external origin). Django's CSRF middleware does not block GETs, but the OAuth `state` parameter already serves as the CSRF token for this flow. If you ever add a POST-based callback, exempt it explicitly: - -```python -from django.views.decorators.csrf import csrf_exempt - -@csrf_exempt -def callback_view(request): ... -``` - -### Deep link preservation -`@login_required` already appends `?next=` when redirecting. Read it in `login_view` and restore it after a successful callback: - -```python -# In login_view -next_url = request.GET.get('next', reverse('auth_app:dashboard')) -request.session['next'] = next_url -request.session.save() # explicit save before redirect - -# In callback_view — after writing session data -next_url = request.session.pop('next', reverse('auth_app:dashboard')) -if not next_url.startswith('/'): # prevent open redirect - next_url = reverse('auth_app:dashboard') -return redirect(next_url) -``` - -### Cache-Control: no-store on protected views -Without this, the back button after logout serves a cached authenticated page: - -```python -from django.views.decorators.cache import never_cache - -@never_cache -@login_required -def dashboard_view(request): ... -``` - -### AJAX: 401 instead of redirect -If your frontend makes AJAX calls to protected views, return `401` instead of a redirect: - -```python -from functools import wraps -from django.http import JsonResponse - -def login_required_ajax(f): - @wraps(f) - def decorated(request, *args, **kwargs): - if not request.session.get('scalekit_user'): - if request.headers.get('Accept') == 'application/json': - return JsonResponse({'error': 'Authentication required'}, status=401) - return redirect(f"{reverse('auth_app:login')}?next={request.path}") - return f(request, *args, **kwargs) - return decorated -``` - -### Session fixation after login -Call `request.session.cycle_key()` immediately after writing session data in `callback_view` to prevent session fixation — an attacker who planted a known session ID before login cannot hijack the authenticated session: - -```python -# At the end of callback_view, after writing all session keys: -request.session.cycle_key() -return redirect(next_url) -``` diff --git a/plugins/full-stack-auth/skills/implementing-scalekit-fastapi-auth/SKILL.md b/plugins/full-stack-auth/skills/implementing-scalekit-fastapi-auth/SKILL.md deleted file mode 100644 index f22aee6..0000000 --- a/plugins/full-stack-auth/skills/implementing-scalekit-fastapi-auth/SKILL.md +++ /dev/null @@ -1,460 +0,0 @@ ---- -name: implementing-scalekit-fastapi-auth -description: Guides implementation of Scalekit OIDC/OAuth2 authentication and authorization in an existing FastAPI project. Use when the user wants to add Scalekit login, SSO, token management, session handling, or permission-based route protection to a FastAPI app. Triggers on: "add scalekit", "scalekit auth", "scalekit login", "scalekit SSO", "scalekit fastapi", "protect routes with scalekit". ---- - -# Scalekit Auth for FastAPI - -Reference implementation: [scalekit-inc/scalekit-fastapi-auth-example](https://github.com/scalekit-inc/scalekit-fastapi-auth-example) - -## Step 1 — Install dependencies - -```bash -pip install scalekit-sdk-python python-dotenv pydantic-settings starlette -``` - -Add to `requirements.txt`: -``` -scalekit-sdk-python -python-dotenv -pydantic-settings -starlette -``` - ---- - -## Step 2 — Environment variables - -Create `.env` (never commit this): - -```env -SCALEKIT_ENV_URL=https://your-env.scalekit.com -SCALEKIT_CLIENT_ID=your_client_id -SCALEKIT_CLIENT_SECRET=your_client_secret -SCALEKIT_REDIRECT_URI=http://localhost:8000/auth/callback -SCALEKIT_SCOPES=openid profile email offline_access -SECRET_KEY=change-me-in-production -DEBUG=True -``` - -> `offline_access` scope is required to receive a `refresh_token`. - ---- - -## Step 3 — Config (`app/config.py`) - -```python -import os -from typing import List -from pydantic_settings import BaseSettings -from dotenv import load_dotenv - -load_dotenv() - -class Settings(BaseSettings): - scalekit_env_url: str = os.getenv('SCALEKIT_ENV_URL', '') - scalekit_client_id: str = os.getenv('SCALEKIT_CLIENT_ID', '') - scalekit_client_secret: str = os.getenv('SCALEKIT_CLIENT_SECRET', '') - scalekit_redirect_uri: str = os.getenv('SCALEKIT_REDIRECT_URI', 'http://localhost:8000/auth/callback') - scalekit_scopes: List[str] = os.getenv('SCALEKIT_SCOPES', 'openid profile email offline_access').split() - debug: bool = os.getenv('DEBUG', 'True') == 'True' - secret_key: str = os.getenv('SECRET_KEY', 'change-me') - session_max_age: int = 3600 - -settings = Settings() -``` - ---- - -## Step 4 — Scalekit client wrapper (`app/scalekit_client.py`) - -```python -import logging -from functools import lru_cache -from scalekit import ScalekitClient as _ScalekitClient -from app.config import settings - -logger = logging.getLogger(__name__) - -class ScalekitClientWrapper: - def __init__(self): - self._client = _ScalekitClient( - env_url=settings.scalekit_env_url, - client_id=settings.scalekit_client_id, - client_secret=settings.scalekit_client_secret, - ) - - def get_authorization_url(self, state: str) -> str: - return self._client.get_authorization_url( - redirect_uri=settings.scalekit_redirect_uri, - scopes=settings.scalekit_scopes, - state=state, - ) - - def exchange_code_for_tokens(self, code: str) -> dict: - return self._client.authenticate_with_code( - code=code, - redirect_uri=settings.scalekit_redirect_uri, - ) - - def get_user_info(self, access_token: str) -> dict: - return self._client.get_user_info(access_token) - - def validate_token_and_get_claims(self, access_token: str) -> dict: - return self._client.validate_access_token(access_token) - - def refresh_access_token(self, refresh_token: str) -> dict: - return self._client.refresh_token(refresh_token) - - def has_permission(self, access_token: str, permission: str) -> bool: - try: - claims = self.validate_token_and_get_claims(access_token) - permissions = ( - claims.get('permissions', []) or - claims.get('https://scalekit.com/permissions', []) - ) - return permission in permissions - except Exception: - return False - - def logout(self, access_token: str) -> str: - return self._client.get_logout_url( - access_token=access_token, - post_logout_redirect_uri=settings.scalekit_redirect_uri.replace('/auth/callback', '/'), - ) - -@lru_cache(maxsize=1) -def scalekit_client() -> ScalekitClientWrapper: - return ScalekitClientWrapper() -``` - ---- - -## Step 5 — FastAPI dependencies (`app/dependencies.py`) - -```python -from typing import Union -from fastapi import HTTPException, Request, status -from fastapi.responses import RedirectResponse -from app.scalekit_client import scalekit_client - -def require_login(request: Request) -> Union[dict, RedirectResponse]: - user = request.session.get('scalekit_user') - if not user: - return RedirectResponse(url=f"/login?next={request.url.path}", status_code=302) - return user - -def require_permission(permission: str): - def checker(request: Request) -> Union[dict, RedirectResponse]: - user = request.session.get('scalekit_user') - if not user: - return RedirectResponse(url=f"/login?next={request.url.path}", status_code=302) - token_data = request.session.get('scalekit_tokens', {}) - access_token = token_data.get('access_token') - if not access_token: - raise HTTPException(status_code=403, detail="No access token") - client = scalekit_client() - if not client.has_permission(access_token, permission): - raise HTTPException(status_code=403, detail=f"Permission '{permission}' required") - return user - return checker -``` - ---- - -## Step 6 — Token refresh middleware (`app/middleware.py`) - -Auto-refreshes the access token 5 minutes before expiry on every request. - -```python -import logging -from datetime import datetime, timedelta -from starlette.middleware.base import BaseHTTPMiddleware -from starlette.requests import Request - -logger = logging.getLogger(__name__) -REFRESH_BEFORE_SECONDS = 300 # 5 minutes - -class ScalekitTokenRefreshMiddleware(BaseHTTPMiddleware): - async def dispatch(self, request: Request, call_next): - token_data = request.session.get('scalekit_tokens', {}) - if token_data.get('access_token') and token_data.get('refresh_token'): - try: - expires_at_str = token_data.get('expires_at') - if expires_at_str: - expires_at = datetime.fromisoformat(expires_at_str.replace('Z', '+00:00')) - now = datetime.now() - if expires_at.tzinfo: - from datetime import timezone - now = datetime.now(timezone.utc) - if (expires_at - now).total_seconds() < REFRESH_BEFORE_SECONDS: - from app.scalekit_client import scalekit_client - client = scalekit_client() - new_tokens = client.refresh_access_token(token_data['refresh_token']) - expires_in = new_tokens.get('expires_in', 3600) - request.session['scalekit_tokens'] = { - 'access_token': new_tokens.get('access_token'), - 'refresh_token': new_tokens.get('refresh_token', token_data['refresh_token']), - 'id_token': new_tokens.get('id_token', token_data.get('id_token')), - 'expires_at': (datetime.now() + timedelta(seconds=expires_in)).isoformat(), - 'expires_in': expires_in, - } - except Exception as e: - logger.warning(f"Token refresh failed in middleware: {e}") - return await call_next(request) -``` - ---- - -## Step 7 — Auth routes (`app/routes.py`) - -```python -import secrets -from datetime import datetime, timedelta -from fastapi import APIRouter, Request, Depends -from fastapi.responses import RedirectResponse, HTMLResponse -from app.scalekit_client import scalekit_client -from app.dependencies import require_login, require_permission - -router = APIRouter() - -@router.get("/login") -async def login(request: Request): - if request.session.get('scalekit_user'): - return RedirectResponse(url="/dashboard") - state = secrets.token_urlsafe(32) - request.session['oauth_state'] = state - client = scalekit_client() - auth_url = client.get_authorization_url(state=state) - return RedirectResponse(url=auth_url) - -@router.get("/auth/callback") -async def callback(request: Request): - # CSRF check - state = request.query_params.get('state') - if state != request.session.pop('oauth_state', None): - return HTMLResponse("Invalid state", status_code=400) - - code = request.query_params.get('code') - error = request.query_params.get('error') - if error or not code: - return HTMLResponse(f"Auth error: {error or 'no code'}", status_code=400) - - client = scalekit_client() - token_response = client.exchange_code_for_tokens(code) - - access_token = token_response.get('access_token') or token_response.get('accessToken') - refresh_token = token_response.get('refresh_token') or token_response.get('refreshToken') - id_token = token_response.get('id_token') or token_response.get('idToken') - expires_in = token_response.get('expires_in') or token_response.get('expiresIn') or 3600 - user_obj = token_response.get('user', {}) - - request.session['scalekit_user'] = { - 'sub': user_obj.get('id'), - 'email': user_obj.get('email'), - 'name': user_obj.get('name') or f"{user_obj.get('givenName','')} {user_obj.get('familyName','')}".strip(), - 'given_name': user_obj.get('givenName'), - 'family_name': user_obj.get('familyName'), - } - request.session['scalekit_tokens'] = { - 'access_token': access_token, - 'refresh_token': refresh_token, - 'id_token': id_token, - 'expires_at': (datetime.now() + timedelta(seconds=expires_in)).isoformat(), - 'expires_in': expires_in, - } - - # Store roles and permissions for route protection - try: - user_info = client.get_user_info(access_token) - request.session['scalekit_roles'] = user_info.get('roles', []) - request.session['scalekit_permissions'] = ( - user_info.get('permissions', []) or - user_info.get('https://scalekit.com/permissions', []) - ) - except Exception: - pass - - return RedirectResponse(url="/dashboard", status_code=302) - -@router.post("/logout") -async def logout(request: Request): - token_data = request.session.get('scalekit_tokens', {}) - access_token = token_data.get('access_token') - request.session.clear() - if access_token: - try: - client = scalekit_client() - logout_url = client.logout(access_token) - return RedirectResponse(url=logout_url, status_code=302) - except Exception: - pass - return RedirectResponse(url="/", status_code=302) - -@router.get("/dashboard") -async def dashboard(request: Request, user: dict = Depends(require_login)): - return {"user": user} - -@router.get("/admin/settings") -async def admin_settings(request: Request, user: dict = Depends(require_permission('organization:settings'))): - return {"message": "You have organization:settings permission"} -``` - ---- - -## Step 8 — Wire up `main.py` - -**Middleware registration order is critical.** In Starlette, middleware added later wraps earlier ones and executes first. - -```python -from fastapi import FastAPI -from fastapi.middleware.gzip import GZipMiddleware -from starlette.middleware.sessions import SessionMiddleware -from app.config import settings -from app.middleware import ScalekitTokenRefreshMiddleware -from app.routes import router - -app = FastAPI() - -# Order matters: add ScalekitTokenRefreshMiddleware first (runs after SessionMiddleware) -app.add_middleware(ScalekitTokenRefreshMiddleware) -# SessionMiddleware runs before token refresh so session data is available -app.add_middleware( - SessionMiddleware, - secret_key=settings.secret_key, - max_age=settings.session_max_age, - same_site='lax', - https_only=False, # Set True in production with HTTPS -) -app.add_middleware(GZipMiddleware, minimum_size=1000) - -app.include_router(router) -``` - ---- - -## Session data structure - -| Key | Contents | -|-----|----------| -| `scalekit_user` | `sub`, `email`, `name`, `given_name`, `family_name` | -| `scalekit_tokens` | `access_token`, `refresh_token`, `id_token`, `expires_at`, `expires_in` | -| `scalekit_roles` | `["admin", ...]` | -| `scalekit_permissions` | `["organization:settings", ...]` | - ---- - -## Common patterns - -**Read current user in any route:** -```python -user = request.session.get('scalekit_user', {}) -``` - -**Read access token:** -```python -token_data = request.session.get('scalekit_tokens', {}) -access_token = token_data.get('access_token') -``` - -**Check a permission ad-hoc:** -```python -client = scalekit_client() -if client.has_permission(access_token, 'reports:read'): - ... -``` - -**Decode JWT claims without validation (e.g. for expiry):** -```python -import base64, json -payload = access_token.split('.')[1] -payload += '=' * (4 - len(payload) % 4) -claims = json.loads(base64.urlsafe_b64decode(payload)) -``` - ---- - -## Checklist - -- [ ] `.env` populated with all 5 Scalekit env vars -- [ ] `SCALEKIT_REDIRECT_URI` matches the redirect URI registered in Scalekit dashboard -- [ ] `offline_access` in scopes (for refresh token) -- [ ] `SessionMiddleware` added **after** `ScalekitTokenRefreshMiddleware` in `main.py` -- [ ] `SECRET_KEY` is a strong random string in production -- [ ] `https_only=True` on `SessionMiddleware` in production -- [ ] CSRF state check in `/auth/callback` is present - -## Tactics - -### SameSite=Lax — never Strict -`SessionMiddleware` `same_site` must be `'lax'`, not `'strict'`. The OAuth callback is a cross-site redirect from Scalekit back to your app — `'strict'` drops the session cookie on that redirect so `oauth_state` is missing and the CSRF check fails. - -### CORS for browser clients -If a JavaScript frontend calls the FastAPI backend, add CORS before `SessionMiddleware`: - -```python -from fastapi.middleware.cors import CORSMiddleware - -app.add_middleware(CORSMiddleware, - allow_origins=["http://localhost:3000"], # explicit origin required - allow_credentials=True, # required for session cookies - allow_methods=["*"], - allow_headers=["*"], -) -``` - -> ⚠️ `allow_origins=["*"]` does not work with `allow_credentials=True`. Always specify explicit origins. - -### AJAX: 401 instead of redirect -Browser clients making AJAX calls expect `401`, not a `302` redirect. Detect JSON requests in `require_login`: - -```python -def require_login(request: Request): - user = request.session.get('scalekit_user') - if not user: - if 'application/json' in request.headers.get('Accept', ''): - raise HTTPException(status_code=401, detail="Authentication required") - return RedirectResponse(url=f"/login?next={request.url.path}", status_code=302) - return user -``` - -### Fix: clear session on invalid_grant in middleware -The middleware currently only logs `invalid_grant`. It should also clear the session to force re-login: - -```python -except Exception as e: - logger.warning(f"Token refresh failed in middleware: {e}") - if 'invalid_grant' in str(e).lower(): - request.session.clear() # force re-login on next request -``` - -### Deep link preservation - -```python -@router.get("/login") -async def login(request: Request, next: str = "/dashboard"): - state = secrets.token_urlsafe(32) - request.session['oauth_state'] = state - request.session['next'] = next # preserve intended URL - -@router.get("/auth/callback") -async def callback(request: Request): - ... - next_url = request.session.pop('next', '/dashboard') - if not next_url.startswith('/'): # prevent open redirect - next_url = '/dashboard' - return RedirectResponse(url=next_url, status_code=302) -``` - -### Cache-Control: no-store on protected responses - -```python -from fastapi import Response - -@router.get("/dashboard") -async def dashboard(request: Request, response: Response, user: dict = Depends(require_login)): - response.headers["Cache-Control"] = "no-store" - return {"user": user} -``` - -Prevents the browser from serving a cached authenticated page after logout via the back button. diff --git a/plugins/full-stack-auth/skills/implementing-scalekit-flask-auth/SKILL.md b/plugins/full-stack-auth/skills/implementing-scalekit-flask-auth/SKILL.md deleted file mode 100644 index d4aed17..0000000 --- a/plugins/full-stack-auth/skills/implementing-scalekit-flask-auth/SKILL.md +++ /dev/null @@ -1,585 +0,0 @@ ---- -name: implementing-scalekit-flask-auth -description: Guides implementation of Scalekit OIDC/OAuth2 authentication and authorization in an existing Flask project. Use when the user wants to add Scalekit login, SSO, token management, session handling, or permission-based route protection to a Flask app. Triggers on: "add scalekit", "scalekit auth", "scalekit login", "scalekit SSO", "scalekit flask", "protect flask routes with scalekit". ---- - -# Scalekit Auth for Flask - -Reference implementation: [scalekit-inc/scalekit-flask-auth-example](https://github.com/scalekit-inc/scalekit-flask-auth-example) - -## Step 1 — Install dependencies - -```bash -pip install scalekit-sdk-python python-dotenv flask -``` - -Add to `requirements.txt`: -``` -scalekit-sdk-python -python-dotenv -flask -``` - ---- - -## Step 2 — Environment variables - -Create `.env` (never commit this): - -```env -SCALEKIT_ENV_URL=https://your-env.scalekit.com -SCALEKIT_CLIENT_ID=your_client_id -SCALEKIT_CLIENT_SECRET=your_client_secret -SCALEKIT_REDIRECT_URI=http://localhost:5000/auth/callback -FLASK_SECRET_KEY=change-me-in-production -DEBUG=True -``` - -> `offline_access` scope is included by default in the config below. It is required to receive a `refresh_token`. - ---- - -## Step 3 — App factory (`app.py`) - -Use Flask's application factory pattern. All Scalekit config goes into `app.config` so that `current_app` is available inside request contexts. - -```python -import os -from flask import Flask -from dotenv import load_dotenv - -load_dotenv() - -def create_app(): - app = Flask(__name__) - - # Flask session config - app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET_KEY', 'change-me') - app.config['SESSION_COOKIE_HTTPONLY'] = True - app.config['SESSION_COOKIE_SECURE'] = False # Set True in production (HTTPS) - app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' - app.config['PERMANENT_SESSION_LIFETIME'] = 3600 - - # Scalekit config - app.config['SCALEKIT_ENV_URL'] = os.getenv('SCALEKIT_ENV_URL', '') - app.config['SCALEKIT_CLIENT_ID'] = os.getenv('SCALEKIT_CLIENT_ID', '') - app.config['SCALEKIT_CLIENT_SECRET'] = os.getenv('SCALEKIT_CLIENT_SECRET', '') - app.config['SCALEKIT_REDIRECT_URI'] = os.getenv('SCALEKIT_REDIRECT_URI', 'http://localhost:5000/auth/callback') - app.config['SCALEKIT_SCOPES'] = 'openid profile email offline_access' - - # Register blueprint - from auth_app.views import auth_bp - app.register_blueprint(auth_bp) - - # Register token refresh middleware as a before_request hook - from auth_app.middleware import TokenRefreshMiddleware - app.before_request(TokenRefreshMiddleware.process_request) - - return app - -if __name__ == '__main__': - app = create_app() - app.run(host='0.0.0.0', port=5000, debug=app.config['DEBUG']) -``` - ---- - -## Step 4 — Scalekit client wrapper (`auth_app/scalekit_client.py`) - -**Important**: `ScalekitClient` reads from `current_app.config`, so it must always be instantiated inside an active Flask request context (i.e., inside a view or `before_request` hook). - -```python -import logging -from datetime import datetime, timedelta -from flask import current_app -from scalekit import ScalekitClient as SDKClient -from scalekit.common.scalekit import ( - AuthorizationUrlOptions, - CodeAuthenticationOptions, - TokenValidationOptions, - LogoutUrlOptions, -) - -logger = logging.getLogger(__name__) - - -class ScalekitClient: - def __init__(self): - self.domain = current_app.config['SCALEKIT_ENV_URL'] - self.client_id = current_app.config['SCALEKIT_CLIENT_ID'] - self.client_secret = current_app.config['SCALEKIT_CLIENT_SECRET'] - self.redirect_uri = current_app.config['SCALEKIT_REDIRECT_URI'] - scopes = current_app.config.get('SCALEKIT_SCOPES', '') - self.scopes = scopes.split() if scopes else ['openid', 'profile', 'email', 'offline_access'] - - self.sdk_client = SDKClient( - env_url=self.domain, - client_id=self.client_id, - client_secret=self.client_secret, - ) - - def get_authorization_url(self, state=None) -> str: - options = AuthorizationUrlOptions() - options.state = state - options.scopes = self.scopes - return self.sdk_client.get_authorization_url(redirect_uri=self.redirect_uri, options=options) - - def exchange_code_for_tokens(self, code: str) -> dict: - options = CodeAuthenticationOptions() - token_response = self.sdk_client.authenticate_with_code( - code=code, redirect_uri=self.redirect_uri, options=options - ) - token_response.setdefault('expires_in', 3600) - return token_response - - def refresh_access_token(self, refresh_token: str) -> dict: - token_response = self.sdk_client.refresh_access_token(refresh_token) - token_response.setdefault('expires_in', 3600) - if not token_response.get('refresh_token'): - token_response['refresh_token'] = refresh_token - return token_response - - def get_user_info(self, access_token: str) -> dict: - options = TokenValidationOptions() - claims = self.sdk_client.validate_access_token_and_get_claims(token=access_token, options=options) - return claims if isinstance(claims, dict) else dict(claims) - - def validate_token_and_get_claims(self, access_token: str) -> dict: - return self.get_user_info(access_token) - - def has_permission(self, access_token: str, permission: str) -> bool: - try: - claims = self.validate_token_and_get_claims(access_token) - permissions = ( - claims.get('permissions', []) or - claims.get('https://scalekit.com/permissions', []) or - claims.get('scalekit:permissions', []) or - [] - ) - return permission in permissions - except Exception: - return False - - def logout(self, access_token: str) -> str: - try: - options = LogoutUrlOptions() - options.post_logout_redirect_uri = self.redirect_uri.split('/auth/callback')[0] - return self.sdk_client.get_logout_url(options) - except Exception: - return f"{self.domain}/oidc/logout" -``` - ---- - -## Step 5 — Decorators (`auth_app/decorators.py`) - -Flask uses decorators (not dependency injection) to protect routes. - -```python -from functools import wraps -from flask import session, redirect, url_for, request, render_template -from auth_app.scalekit_client import ScalekitClient - - -def login_required(f): - """Redirect to /login if user is not authenticated.""" - @wraps(f) - def decorated(*args, **kwargs): - if not session.get('scalekit_user'): - return redirect(url_for('auth.login', next=request.path)) - return f(*args, **kwargs) - return decorated - - -def permission_required(permission): - """Return 403 if authenticated user lacks the specified permission.""" - def decorator(f): - @wraps(f) - def decorated(*args, **kwargs): - if not session.get('scalekit_user'): - return redirect(url_for('auth.login', next=request.path)) - token_data = session.get('scalekit_tokens', {}) - access_token = token_data.get('access_token') - if not access_token: - return "No access token. Please log in again.", 403 - client = ScalekitClient() - if not client.has_permission(access_token, permission): - return render_template('permission_denied.html', - user=session.get('scalekit_user', {})), 403 - return f(*args, **kwargs) - return decorated - return decorator -``` - ---- - -## Step 6 — Token refresh middleware (`auth_app/middleware.py`) - -Registered as a `before_request` hook. Auto-refreshes access tokens within 5 minutes of expiry. On `invalid_grant`, clears the session to force re-login. - -```python -import logging -from datetime import datetime, timedelta -from flask import session, request -from auth_app.scalekit_client import ScalekitClient - -logger = logging.getLogger(__name__) - -REFRESH_BUFFER_MINUTES = 5 -SKIP_PATHS = ['/login', '/auth/callback', '/logout', '/static/', '/sessions/refresh-token'] - - -class TokenRefreshMiddleware: - @staticmethod - def process_request(): - if not session.get('scalekit_user'): - return None - if any(request.path.startswith(p) for p in SKIP_PATHS): - return None - - token_data = session.get('scalekit_tokens', {}) - expires_at_str = token_data.get('expires_at') - refresh_token = token_data.get('refresh_token') - - if not expires_at_str or not refresh_token: - return None - - try: - expires_at = datetime.fromisoformat(expires_at_str.replace('Z', '+00:00')) - if expires_at.tzinfo: - expires_at = expires_at.replace(tzinfo=None) - - if datetime.utcnow() + timedelta(minutes=REFRESH_BUFFER_MINUTES) >= expires_at: - client = ScalekitClient() - token_response = client.refresh_access_token(refresh_token) - expires_in = token_response.get('expires_in', 3600) - session['scalekit_tokens'] = { - 'access_token': token_response.get('access_token'), - 'refresh_token': token_response.get('refresh_token', refresh_token), - 'id_token': token_response.get('id_token', token_data.get('id_token')), - 'expires_at': (datetime.utcnow() + timedelta(seconds=expires_in)).isoformat(), - 'expires_in': expires_in, - } - except Exception as e: - logger.error(f"Token refresh failed: {e}") - if 'invalid_grant' in str(e): - logger.warning("Refresh token revoked — clearing session") - session.clear() - - return None -``` - ---- - -## Step 7 — Auth views (`auth_app/views.py`) - -```python -import secrets -import base64 -import json -import logging -from datetime import datetime, timedelta -from flask import Blueprint, render_template, redirect, url_for, request, session, jsonify -from auth_app.scalekit_client import ScalekitClient -from auth_app.decorators import login_required, permission_required - -logger = logging.getLogger(__name__) -auth_bp = Blueprint('auth', __name__) - - -@auth_bp.route('/login') -def login(): - if session.get('scalekit_user'): - return redirect(url_for('auth.dashboard')) - state = secrets.token_urlsafe(32) - session['oauth_state'] = state - client = ScalekitClient() - auth_url = client.get_authorization_url(state=state) - # Render a login template that links to auth_url, or redirect directly: - return redirect(auth_url) - - -@auth_bp.route('/auth/callback') -def callback(): - # CSRF check - state = request.args.get('state') - if not state or state != session.pop('oauth_state', None): - return render_template('error.html', error='Invalid state. Enable cookies and try again.'), 400 - - code = request.args.get('code') - error = request.args.get('error') - if error or not code: - return render_template('error.html', error=f'Auth error: {error or "no code"}'), 400 - - try: - client = ScalekitClient() - token_response = client.exchange_code_for_tokens(code) - - access_token = token_response.get('access_token') - refresh_token = token_response.get('refresh_token') - id_token = token_response.get('id_token') - expires_in = token_response.get('expires_in', 3600) - - # Decode ID token for user profile (primary source) - id_token_claims = {} - if id_token: - try: - payload = id_token.split('.')[1] - payload += '=' * (4 - len(payload) % 4) - id_token_claims = json.loads(base64.urlsafe_b64decode(payload)) - except Exception: - pass - - # Get user info from access token (for roles/permissions) - user_info = {} - try: - user_info = client.get_user_info(access_token) - except Exception as e: - logger.warning(f"Could not get user info: {e}") - - # Merge: access token claims first, then ID token claims override profile fields - merged = {**user_info, **id_token_claims} - - session['scalekit_user'] = { - 'sub': merged.get('sub'), - 'email': merged.get('email'), - 'name': merged.get('name'), - 'given_name': merged.get('given_name'), - 'family_name': merged.get('family_name'), - 'preferred_username': merged.get('preferred_username'), - 'claims': merged, - } - session['scalekit_tokens'] = { - 'access_token': access_token, - 'refresh_token': refresh_token, - 'id_token': id_token, - 'expires_at': (datetime.utcnow() + timedelta(seconds=expires_in)).isoformat(), - 'expires_in': expires_in, - } - session['scalekit_roles'] = user_info.get('roles', []) or user_info.get('https://scalekit.com/roles', []) - session['scalekit_permissions'] = ( - user_info.get('permissions', []) or user_info.get('https://scalekit.com/permissions', []) - ) - session.permanent = True - - return redirect(url_for('auth.dashboard')) - - except Exception as e: - logger.error(f"Auth error: {e}") - return render_template('error.html', error=str(e)), 500 - - -@auth_bp.route('/logout', methods=['GET', 'POST']) -@login_required -def logout(): - token_data = session.get('scalekit_tokens', {}) - access_token = token_data.get('access_token') - session.clear() - if access_token: - try: - logout_url = ScalekitClient().logout(access_token) - return redirect(logout_url) - except Exception: - pass - return redirect(url_for('auth.home')) - - -# --- Example: protected route --- -@auth_bp.route('/dashboard') -@login_required -def dashboard(): - return render_template('dashboard.html', user=session.get('scalekit_user', {})) - - -# --- Example: permission-gated route --- -@auth_bp.route('/organization/settings') -@permission_required('organization:settings') -def organization_settings(): - return render_template('organization_settings.html', user=session.get('scalekit_user', {})) - - -# --- API: manual token refresh --- -@auth_bp.route('/sessions/refresh-token', methods=['POST']) -@login_required -def refresh_token(): - token_data = session.get('scalekit_tokens', {}) - rt = token_data.get('refresh_token') - if not rt: - return jsonify({'success': False, 'error': 'No refresh token. Request offline_access scope.'}), 400 - try: - client = ScalekitClient() - resp = client.refresh_access_token(rt) - expires_in = resp.get('expires_in', 3600) - session['scalekit_tokens'] = { - 'access_token': resp.get('access_token'), - 'refresh_token': resp.get('refresh_token', rt), - 'id_token': resp.get('id_token', token_data.get('id_token')), - 'expires_at': (datetime.utcnow() + timedelta(seconds=expires_in)).isoformat(), - 'expires_in': expires_in, - } - return jsonify({'success': True, 'newAccessToken': resp.get('access_token')}) - except Exception as e: - if 'invalid_grant' in str(e): - session.clear() - return jsonify({'success': False, 'error': 'Refresh token expired. Re-login required.', 'requiresReauth': True}), 401 - return jsonify({'success': False, 'error': str(e)}) -``` - ---- - -## Key differences from FastAPI - -| | Flask | FastAPI | -|---|---|---| -| Route protection | `@login_required` decorator | `Depends(require_login)` | -| Permission check | `@permission_required('x')` decorator | `Depends(require_permission('x'))` | -| Middleware hook | `app.before_request(...)` | `app.add_middleware(...)` | -| Config access | `current_app.config` (request context) | `settings` singleton (module level) | -| Session import | `from flask import session` | `request.session` attribute | -| Claims source | ID token + access token merged | Access token / `user` object from SDK | -| Refresh token error | Clears session on `invalid_grant` | Logs error, lets next request retry | - ---- - -## Session data structure - -| Key | Contents | -|---|---| -| `scalekit_user` | `sub`, `email`, `name`, `given_name`, `family_name`, `preferred_username`, `claims` | -| `scalekit_tokens` | `access_token`, `refresh_token`, `id_token`, `expires_at`, `expires_in` | -| `scalekit_roles` | `["admin", ...]` | -| `scalekit_permissions` | `["organization:settings", ...]` | - ---- - -## Common patterns - -**Read current user in any view:** -```python -from flask import session -user = session.get('scalekit_user', {}) -``` - -**Check permission ad-hoc:** -```python -client = ScalekitClient() -if client.has_permission(session['scalekit_tokens']['access_token'], 'reports:read'): - ... -``` - -**Decode JWT claims without verification:** -```python -import base64, json -payload = access_token.split('.')[1] -payload += '=' * (4 - len(payload) % 4) -claims = json.loads(base64.urlsafe_b64decode(payload)) -``` - ---- - -## Checklist - -- [ ] `.env` populated with all 5 Scalekit env vars -- [ ] `SCALEKIT_REDIRECT_URI` matches the URI registered in Scalekit dashboard -- [ ] `offline_access` in `SCALEKIT_SCOPES` (required for `refresh_token`) -- [ ] `before_request` hook registered in `create_app()` — not on the module level -- [ ] `ScalekitClient()` instantiated only inside request contexts (views, hooks) -- [ ] `SESSION_COOKIE_SECURE = True` in production (HTTPS only) -- [ ] `FLASK_SECRET_KEY` is a strong random string in production -- [ ] CSRF `state` check present in `/auth/callback` -- [ ] `invalid_grant` handling in token refresh clears session - -## Tactics - -### SameSite=Lax — never Strict -`SESSION_COOKIE_SAMESITE = 'Lax'` is correct. Do not change to `'Strict'` — the OAuth callback is a cross-origin redirect from Scalekit back to `/auth/callback`. `'Strict'` drops the session cookie on that redirect, making `oauth_state` unavailable and causing the CSRF check to fail on every login. - -### CORS for JavaScript clients -If a JavaScript frontend calls the Flask backend: - -```bash -pip install flask-cors -``` - -```python -from flask_cors import CORS - -def create_app(): - app = Flask(__name__) - CORS(app, - origins=["http://localhost:3000"], # explicit origin required - supports_credentials=True) # required for session cookies - ... -``` - -> ⚠️ `origins="*"` does not work with `supports_credentials=True`. Always specify explicit origins. - -### Deep link preservation - -```python -@auth_bp.route('/login') -def login(): - next_url = request.args.get('next', url_for('auth.dashboard')) - state = secrets.token_urlsafe(32) - session['oauth_state'] = state - session['next'] = next_url # preserve intended URL - ... - -@auth_bp.route('/auth/callback') -def callback(): - ... - next_url = session.pop('next', url_for('auth.dashboard')) - if not next_url.startswith('/'): # prevent open redirect - next_url = url_for('auth.dashboard') - return redirect(next_url) -``` - -The `@login_required` decorator passes `?next=` automatically — read it in `login()`. - -### Cache-Control: no-store on protected responses - -```python -from flask import make_response - -@auth_bp.route('/dashboard') -@login_required -def dashboard(): - resp = make_response(render_template('dashboard.html', user=session.get('scalekit_user', {}))) - resp.headers['Cache-Control'] = 'no-store' - return resp -``` - -Prevents the browser from serving a cached authenticated page after logout via the back button. - -### AJAX: 401 instead of redirect -Update `@login_required` to return `401` for JSON requests: - -```python -def login_required(f): - @wraps(f) - def decorated(*args, **kwargs): - if not session.get('scalekit_user'): - if request.headers.get('Accept') == 'application/json': - return jsonify({'error': 'Authentication required'}), 401 - return redirect(url_for('auth.login', next=request.path)) - return f(*args, **kwargs) - return decorated -``` - -### Session fixation after login -Flask does not regenerate the session ID automatically. Call `session.modified = True` and use `flask.session` with a new session cookie after login. For a stronger fix, clear and re-create the session immediately after writing user data in `callback()`: - -```python -# After storing user/token data, regenerate session to prevent fixation: -user_data = session.get('scalekit_user') -token_data = session.get('scalekit_tokens') -session.clear() -session['scalekit_user'] = user_data -session['scalekit_tokens'] = token_data -session.permanent = True -``` - -### Production: Secure cookie flag -```python -app.config['SESSION_COOKIE_SECURE'] = not app.debug # True in production (HTTPS) -``` diff --git a/plugins/full-stack-auth/skills/implementing-scalekit-laravel-auth/SKILL.md b/plugins/full-stack-auth/skills/implementing-scalekit-laravel-auth/SKILL.md deleted file mode 100644 index 05bb2fe..0000000 --- a/plugins/full-stack-auth/skills/implementing-scalekit-laravel-auth/SKILL.md +++ /dev/null @@ -1,338 +0,0 @@ ---- -name: implementing-scalekit-laravel-auth -description: Implements Scalekit authentication in a Laravel app using the patterns from scalekit-inc/scalekit-laravel-auth-example. Handles login, OAuth callback, Laravel session storage, automatic token refresh via middleware, logout, and permission-based route protection. Uniquely uses Laravel's Http facade with raw HTTP calls instead of a PHP SDK — no official Scalekit PHP SDK exists. Use when adding auth controllers, protecting routes with middleware, managing sessions, or checking permissions in a Laravel + Scalekit codebase. ---- - -# Scalekit Auth — Laravel - -Reference repo: [scalekit-inc/scalekit-laravel-auth-example](https://github.com/scalekit-inc/scalekit-laravel-auth-example) - -## Project structure - -``` -app/ -├── Services/ -│ └── ScalekitClient.php # Raw HTTP OAuth client (no PHP SDK) -├── Http/ -│ ├── Controllers/ -│ │ └── AuthController.php -│ └── Middleware/ -│ ├── ScalekitAuth.php # Session auth gate -│ ├── ScalekitPermission.php # Per-route permission check -│ └── ScalekitTokenRefresh.php # Auto token refresh on every request - -config/ -└── scalekit.php # Reads from env via config('scalekit.*') - -routes/ -└── web.php # Named routes + middleware groups -``` - -## Environment variables - -```env -SCALEKIT_ENV_URL=https://your-env.scalekit.io -SCALEKIT_CLIENT_ID=your-client-id -SCALEKIT_CLIENT_SECRET=your-client-secret -SCALEKIT_REDIRECT_URI=http://localhost:8000/auth/callback -``` - -Scopes are hardcoded in `config/scalekit.php`, not from env: -```php -'scopes' => 'openid profile email offline_access', -// offline_access is required to receive a refresh token -``` - -## `ScalekitClient` service (`app/Services/ScalekitClient.php`) - -> ⚠️ No official Scalekit PHP SDK exists. This app uses **Laravel's `Http` facade** with raw HTTP calls. Always use `config('scalekit.*')` — do not read `env()` directly: - -```php -use App\Services\ScalekitClient; -// Injected via Laravel's service container — never `new ScalekitClient()` -``` - -### Key methods and their HTTP calls - -| Method | HTTP call | Auth | -|---|---|---| -| `getAuthorizationUrl($state)` | Builds `{env_url}/oauth/authorize?response_type=code&...` | None | -| `exchangeCodeForTokens($code)` | `POST {env_url}/oauth/token` with `grant_type=authorization_code` | Basic Auth | -| `refreshAccessToken($refreshToken)` | `POST {env_url}/oauth/token` with `grant_type=refresh_token` | Basic Auth | -| `getUserInfo($accessToken)` | Delegates to `validateTokenAndGetClaims()` | — | -| `validateTokenAndGetClaims($token)` | **Manual base64 JWT decode** — no signature verification | — | -| `hasPermission($token, $permission)` | Decodes JWT, checks permission claim chain | — | -| `logout($accessToken)` | Builds `{env_url}/oidc/logout?post_logout_redirect_uri=...` | None | -| `isTokenExpired($expiresAt)` | `now()->addMinutes(5)->gt(Carbon::parse($expiresAt))` | — | - -Token exchange and refresh use `Http::asForm()->withBasicAuth(clientId, clientSecret)`. Both fall back to `expires_in = 3600` if the field is missing. - -### JWT decode pattern (used in both token validation and ID token decode) - -```php -$parts = explode('.', $token); -$payload = $parts[1]; -$payload .= str_repeat('=', (4 - strlen($payload) % 4) % 4); // padding -$decoded = base64_decode(strtr($payload, '-_', '+/')); // URL-safe base64 -$claims = json_decode($decoded, true); -``` - -### Permission claim fallback chain - -```php -$permissions = $claims['permissions'] - ?? $claims['https://scalekit.com/permissions'] - ?? $claims['scalekit:permissions'] - ?? []; - -// Also falls back to scope string if all are empty -if (empty($permissions)) { - $permissions = explode(' ', $claims['scope'] ?? ''); -} -``` - -## Session storage schema - -All auth state lives in Laravel's session — no extra DB tables (uses default `database` or `file` driver): - -```php -session([ - 'scalekit_user' => [ - 'sub', 'email', 'name', 'given_name', 'family_name', - 'preferred_username', - 'claims' // merged array of ALL claims (ID token overlaid on access token) - ], - 'scalekit_tokens' => [ - 'access_token', 'refresh_token', 'id_token', - 'expires_at', // Carbon ISO 8601 string via ->toIso8601String() - 'expires_in', // int seconds - ], - 'scalekit_roles' => [], // from access token claims - 'scalekit_permissions' => [], // from access token claims -]); -``` - -Check auth status anywhere: `session()->has('scalekit_user')`. - -## Auth flow - -### Login (`GET /login` → `AuthController::login`) - -```php -$state = Str::random(32); // Illuminate\Support\Str -session(['oauth_state' => $state]); -$authUrl = $this->scalekitClient->getAuthorizationUrl($state); -return view('auth.login', ['auth_url' => $authUrl]); -// Template renders a link/button to $auth_url -``` - -### Callback (`GET /auth/callback` → `AuthController::callback`) - -1. Validate `$request->query('state')` vs `session('oauth_state')` → `response()->view('auth.error', [...], 400)` on mismatch -2. `session()->forget('oauth_state')` -3. `$tokenResponse = $this->scalekitClient->exchangeCodeForTokens($code)` -4. **Manually decode ID token** → `$idTokenClaims` -5. `$userInfo = $this->scalekitClient->getUserInfo($accessToken)` → access token claims -6. **Merge**: `$mergedClaims = array_merge($userInfo, $idTokenClaims)` — ID token wins (overlaid last) -7. `$expiresAt = now()->addSeconds($expiresIn)` -8. Write all four session keys -9. `return redirect()->route('auth.dashboard')` - -### Logout (`GET|POST /logout` → `AuthController::logout`) - -```php -$logoutUrl = $this->scalekitClient->logout($accessToken); -// → {env_url}/oidc/logout?post_logout_redirect_uri={base_url} -// post_logout_redirect_uri is derived from SCALEKIT_REDIRECT_URI, stripping /auth/callback - -session()->flush(); // Full session wipe -return redirect($logoutUrl); // Server-side redirect to Scalekit -``` - -### Token refresh — controller (`POST /sessions/refresh-token`) - -On `invalid_grant` error: `session()->flush()` + return `401` with `'requiresReauth' => true`. - -## Middleware - -### Registration in `bootstrap/app.php` (Laravel 11) or `Kernel.php` (Laravel ≤10) - -```php -// Laravel 11 — bootstrap/app.php -->withMiddleware(function (Middleware $middleware) { - $middleware->alias([ - 'scalekit.auth' => \App\Http\Middleware\ScalekitAuth::class, - 'scalekit.permission' => \App\Http\Middleware\ScalekitPermission::class, - ]); - $middleware->append(\App\Http\Middleware\ScalekitTokenRefresh::class); -}) -``` - -### `ScalekitAuth` — session gate - -Redirects to `auth.login` with `->with('next', $request->path())` if `scalekit_user` session key is missing. - -### `ScalekitPermission` — parameterised permission check - -Validates access token claims via `ScalekitClient::hasPermission()`. On failure: `response()->view('auth.permission_denied', [...], 403)`. Never returns a JSON 403 — always renders a view. - -### `ScalekitTokenRefresh` — auto refresh on every request - -Skipped paths: `login`, `auth/callback`, `logout`, `sessions/refresh-token`. - -Buffer: **5 minutes** (via `isTokenExpired()`). On `invalid_grant` during auto-refresh: `session()->flush()` (user gets redirected on next request). - -## Routes (`routes/web.php`) - -```php -// Public -Route::get('/', [AuthController::class, 'home'])->name('auth.home'); -Route::get('/login', [AuthController::class, 'login'])->name('auth.login'); -Route::get('/auth/callback', [AuthController::class, 'callback'])->name('auth.callback'); - -// Protected group -Route::middleware(['scalekit.auth'])->group(function () { - Route::get('/dashboard', [AuthController::class, 'dashboard'])->name('auth.dashboard'); - Route::match(['get', 'post'], '/logout', [AuthController::class, 'logout'])->name('auth.logout'); - Route::get('/sessions', [AuthController::class, 'sessions'])->name('auth.sessions'); - Route::post('/sessions/validate-token', [AuthController::class, 'validateToken'])->name('auth.validate_token'); - Route::post('/sessions/refresh-token', [AuthController::class, 'refreshToken'])->name('auth.refresh_token'); - - // Permission-gated — note colon syntax for middleware parameter - Route::get('/organization/settings', [AuthController::class, 'organizationSettings']) - ->middleware('scalekit.permission:organization:settings') - ->name('auth.organization_settings'); -}); -``` - -Key notes: -- Logout accepts both `GET` and `POST` (`Route::match`) -- Permission middleware receives the permission name as a colon-separated parameter -- Named routes use `auth.` prefix throughout — use `route('auth.dashboard')` in Blade - -## Route map - -| URL | Middleware | Auth | -|---|---|---| -| `GET /` | — | No | -| `GET /login` | — | No | -| `GET /auth/callback` | — | No | -| `GET /dashboard` | `scalekit.auth` | Yes | -| `GET\|POST /logout` | `scalekit.auth` | Yes | -| `GET /sessions` | `scalekit.auth` | Yes | -| `POST /sessions/validate-token` | `scalekit.auth` | Yes | -| `POST /sessions/refresh-token` | `scalekit.auth` | Yes | -| `GET /organization/settings` | `scalekit.auth` + `scalekit.permission:organization:settings` | Yes + permission | - -## Dependency injection - -`ScalekitClient` is resolved from Laravel's service container in every controller and middleware constructor. No singleton binding needed — Laravel resolves it fresh per request by default. Register it in `AppServiceProvider` only if you need to scope it as a singleton: - -```php -// Optional — only if you want to share a single instance -$this->app->singleton(ScalekitClient::class); -``` - -## Install - -```bash -composer require firebase/php-jwt # Only if using JWT signature verification -php artisan key:generate -php artisan migrate # Creates sessions table if using database driver -php artisan serve -``` - -Copy `.env.example` to `.env` and fill in the four `SCALEKIT_*` values. - -## Tactics - -### SameSite=Lax — required for OAuth callbacks -Verify your session cookie config in `config/session.php`: - -```php -'same_site' => 'lax', // Required — 'strict' breaks OAuth callbacks -'secure' => env('SESSION_SECURE_COOKIE', false), // true in production -'http_only' => true, -``` - -`SameSite: strict` drops the session cookie on the cross-origin redirect from Scalekit back to `/auth/callback`, making `oauth_state` unavailable and causing the state mismatch check to fail every time. - -### CSRF exclusion for the OAuth callback -The OAuth callback is a GET request and is not subject to Laravel's CSRF middleware. However, if you add any Scalekit webhook endpoints (POST), exclude them explicitly. In Laravel 11 (`bootstrap/app.php`): - -```php -->withMiddleware(function (Middleware $middleware) { - $middleware->validateCsrfTokens(except: [ - 'webhooks/scalekit', // example — callback is GET, not needed here - ]); -}) -``` - -### Deep link preservation - -```php -// In AuthController::login -$next = $request->query('next', route('auth.dashboard')); -// Validate: only relative paths -if (!str_starts_with($next, '/')) { - $next = route('auth.dashboard'); -} -session(['oauth_state' => $state, 'next' => $next]); - -// In AuthController::callback — after writing session data -$next = session()->pull('next', route('auth.dashboard')); -if (!str_starts_with($next, '/')) { - $next = route('auth.dashboard'); -} -return redirect($next); -``` - -`ScalekitAuth` middleware passes `->with('next', $request->path())` when redirecting to login — read it back in `login()` with `session('next')` or `$request->query('next')`. - -### Cache-Control: no-store on protected responses - -```php -return response() - ->view('auth.dashboard', ['user' => session('scalekit_user', [])]) - ->header('Cache-Control', 'no-store'); -``` - -Prevents the browser back button from serving a cached authenticated page after logout. - -### AJAX: 401 instead of redirect -Update `ScalekitAuth` middleware to return `401` for JSON requests: - -```php -public function handle(Request $request, Closure $next): Response -{ - if (!session()->has('scalekit_user')) { - if ($request->expectsJson()) { - return response()->json(['error' => 'Unauthenticated'], 401); - } - return redirect()->route('auth.login')->with('next', $request->path()); - } - return $next($request); -} -``` - -### CORS for JavaScript clients -Laravel ships with CORS support. In `config/cors.php`: - -```php -'paths' => ['api/*', 'auth/*', 'sessions/*'], -'allowed_origins' => ['http://localhost:3000'], // explicit origin required -'supports_credentials' => true, // required for session cookies -``` - -> ⚠️ `'allowed_origins' => ['*']` does not work with `supports_credentials => true`. - -### Session fixation after login -After writing all session data in `callback()`, regenerate the session ID to prevent session fixation: - -```php -// At the end of AuthController::callback, after writing session data: -session()->regenerate(); -return redirect($next); -``` - -`session()->regenerate()` issues a new session ID while preserving the session data — an attacker who set a known session ID before login cannot use it after authentication. diff --git a/plugins/full-stack-auth/skills/implementing-scalekit-nextjs-auth/SKILL.md b/plugins/full-stack-auth/skills/implementing-scalekit-nextjs-auth/SKILL.md deleted file mode 100644 index 3d4ba84..0000000 --- a/plugins/full-stack-auth/skills/implementing-scalekit-nextjs-auth/SKILL.md +++ /dev/null @@ -1,237 +0,0 @@ ---- -name: implementing-scalekit-nextjs-auth -description: Implements Scalekit authentication in a Next.js App Router project using the patterns from scalekit-inc/scalekit-nextjs-auth-example. Handles login, OAuth callback, session management, token refresh, logout, and permission-based access control using @scalekit-sdk/node. Use when adding auth routes, protecting pages, managing sessions, or checking permissions in a Next.js + Scalekit codebase. ---- - -# Scalekit Auth — Next.js App Router - -Reference repo: [scalekit-inc/scalekit-nextjs-auth-example](https://github.com/scalekit-inc/scalekit-nextjs-auth-example) - -## Project structure - -``` -app/api/auth/ -├── login/route.ts # GET — generates auth URL + sets CSRF state -├── callback/route.ts # GET — exchanges code, sets session cookie -├── logout/route.ts # POST — clears session, returns Scalekit logout URL -├── refresh/route.ts # POST — refreshes access token, updates session -└── validate/route.ts # Token validation endpoint - -lib/ -├── scalekit.ts # Singleton ScalekitClient + default scopes -├── cookies.ts # Session read/write/clear + OAuth state helpers -└── auth.ts # isAuthenticated(), getCurrentUser(), hasPermission() -``` - -## Environment variables - -```env -SCALEKIT_ENV_URL=https://your-env.scalekit.io -SCALEKIT_CLIENT_ID=your-client-id -SCALEKIT_CLIENT_SECRET=your-client-secret -SCALEKIT_REDIRECT_URI=http://localhost:3000/auth/callback -NEXT_PUBLIC_APP_URL=http://localhost:3000 -SCALEKIT_SCOPES=openid profile email offline_access # optional, space-separated -``` - -`SCALEKIT_REDIRECT_URI` must exactly match the allowed callback URL in the Scalekit dashboard. - -## SDK client (`lib/scalekit.ts`) - -Singleton pattern — always use `getScalekitClient()`, never instantiate directly. Throws if env vars are missing. - -```ts -import { getScalekitClient, getDefaultScopes } from '@/lib/scalekit'; - -const client = getScalekitClient(); -``` - -## Session shape (`lib/cookies.ts`) - -Session stored as JSON in a single `scalekit_session` HttpOnly cookie: - -```ts -interface SessionData { - user: { sub, email, name, given_name, family_name, preferred_username }; - tokens: { access_token, refresh_token, id_token, expires_at, expires_in }; - roles?: string[]; - permissions?: string[]; -} -``` - -Key helpers: -- `getSession()` — returns `SessionData | null` -- `setSession(data)` — writes HttpOnly cookie; expires = token `expires_at` -- `clearSession()` — deletes cookie (call on logout) -- `isTokenExpired(session)` — returns true if token expires within **5 minutes** -- `getOAuthState()` / `setOAuthState(state)` — CSRF state cookie, 10-min TTL -- Cookie config: `httpOnly: true`, `secure` in production, `sameSite: 'lax'`, `path: '/'` - -## Auth flow - -### Login (`app/api/auth/login/route.ts` — GET) - -```ts -const state = crypto.randomBytes(32).toString('base64url'); -await setOAuthState(state); -const authUrl = client.getAuthorizationUrl(redirectUri, { state, scopes: getDefaultScopes() }); -return NextResponse.json({ authUrl }); -``` - -### Callback (`app/api/auth/callback/route.ts` — GET) - -1. Validate `state` param against stored `oauth_state` cookie → redirect to `/error` on mismatch -2. `clearOAuthState()` -3. `client.authenticateWithCode(code, redirectUri)` → `authResponse` -4. `client.validateToken(authResponse.accessToken)` → extract `roles`, `permissions` - - Permission claims checked in order: `permissions` → `https://scalekit.com/permissions` → `scalekit:permissions` -5. Name resolution priority: `user.name` → `claims.name` → `givenName + familyName` → `email` → `preferred_username` → `'User'` -6. `setSession({ user, tokens, roles, permissions })` -7. Redirect to `/dashboard` - -### Logout (`app/api/auth/logout/route.ts` — POST) - -```ts -const logoutUrl = client.getLogoutUrl({ - idTokenHint: session.tokens.id_token, - postLogoutRedirectUri: process.env.NEXT_PUBLIC_APP_URL, -}); -await clearSession(); -return NextResponse.json({ logoutUrl }); -// Client receives logoutUrl and redirects -``` - -### Token refresh (`app/api/auth/refresh/route.ts` — POST) - -```ts -const refreshResponse = await client.refreshAccessToken(session.tokens.refresh_token); -// Decode exp from JWT using jose.decodeJwt(); fallback to 3600s if missing -await setSession({ ...session, tokens: { ...session.tokens, access_token, refresh_token, expires_at, expires_in } }); -``` - -## Auth utilities (`lib/auth.ts`) - -```ts -isAuthenticated() // → boolean (session exists) -getCurrentUser() // → session.user | null -getAccessToken() // → access_token string | null -hasPermission('read:data') // → validates token, checks permission claim -``` - -## Protecting routes - -For Server Components, call auth helpers directly: - -```ts -import { isAuthenticated, getCurrentUser } from '@/lib/auth'; -import { redirect } from 'next/navigation'; - -const authenticated = await isAuthenticated(); -if (!authenticated) redirect('/login'); -const user = await getCurrentUser(); -``` - -For permission-gated pages: - -```ts -import { hasPermission } from '@/lib/auth'; -const allowed = await hasPermission('org:admin'); -if (!allowed) redirect('/permission-denied'); -``` - -## Route map - -| Route | Auth required | -|---|---| -| `/` | No | -| `/login` | No | -| `/auth/callback` | No | -| `/dashboard` | Yes | -| `/sessions` | Yes | -| `/organization/settings` | Yes + permission | -| `/permission-denied` | No | -| `/error` | No | - -## Dependencies - -```bash -npm install @scalekit-sdk/node jose date-fns js-cookie -``` - -## Tactics - -### Edge middleware for route protection -Add `middleware.ts` at the project root to enforce auth before any Server Component renders: - -```ts -// middleware.ts -import { NextResponse } from 'next/server' -import type { NextRequest } from 'next/server' - -const PROTECTED_PATHS = ['/dashboard', '/sessions', '/organization'] - -export function middleware(request: NextRequest) { - const session = request.cookies.get('scalekit_session') - const isProtected = PROTECTED_PATHS.some(p => request.nextUrl.pathname.startsWith(p)) - if (isProtected && !session) { - const loginUrl = new URL('/login', request.url) - loginUrl.searchParams.set('next', request.nextUrl.pathname) - return NextResponse.redirect(loginUrl) - } - return NextResponse.next() -} - -export const config = { - matcher: ['/((?!_next|api|favicon).*)'], -} -``` - -Server Components should still call `isAuthenticated()` as a second layer. - -### Triggering login from a Client Component -`/api/auth/login` returns `{ authUrl }` — never navigate there with `router.push`. OAuth requires a full page navigation: - -```ts -const { authUrl } = await fetch('/api/auth/login').then(r => r.json()) -window.location.href = authUrl // full navigation, not client-side route change -``` - -### OIDC logout from the client -Logout returns `{ logoutUrl }` — the client must navigate to it: - -```ts -const { logoutUrl } = await fetch('/api/auth/logout', { method: 'POST' }).then(r => r.json()) -window.location.href = logoutUrl // navigates to Scalekit end-session endpoint -``` -Local session is already cleared; this step revokes the IdP session so the user isn't silently re-authenticated on next login. - -### Deep link preservation -In the login page, read `?next` from search params and carry it through the state: - -```ts -// app/login/page.tsx -const next = searchParams.get('next') || '/dashboard' -// Pass next to /api/auth/login as a query param, store in session before redirect -// In /api/auth/callback: redirect to stored next URL after setSession() -``` - -Validate `next` on the server: only allow relative paths (`/...`) to prevent open redirect. - -### SameSite=Lax — never Strict -The `scalekit_session` and `oauth_state` cookies must use `sameSite: 'lax'`. The OAuth callback is a cross-site redirect from Scalekit back to your app — `'strict'` drops the cookie on that redirect, causing a CSRF state mismatch error every time. - -### Cache-Control: no-store on protected pages -Without this, the browser back button after logout serves a cached authenticated page: - -```ts -// In a protected route handler or layout -export const dynamic = 'force-dynamic' - -// Or explicitly in a route handler: -return new Response(html, { - headers: { 'Cache-Control': 'no-store' }, -}) -``` - -### Token refresh race condition across tabs -Multiple browser tabs can simultaneously trigger token refresh with the same refresh token — most IdPs reject the second attempt. Mitigation: set a short-lived `refresh_in_progress` flag in the session before calling the refresh endpoint, and check it at the start of the refresh route to skip concurrent calls. diff --git a/plugins/full-stack-auth/skills/manage-user-sessions/SKILL.md b/plugins/full-stack-auth/skills/manage-user-sessions/SKILL.md deleted file mode 100644 index ea94dc5..0000000 --- a/plugins/full-stack-auth/skills/manage-user-sessions/SKILL.md +++ /dev/null @@ -1,351 +0,0 @@ ---- -name: managing-user-sessions -description: Manages Scalekit-backed user sessions by securely storing access/refresh/ID tokens (with encryption and correct cookie attributes), validating access tokens on every request, transparently refreshing tokens in middleware, and optionally revoking sessions remotely via Scalekit session APIs. Use when building session persistence for only for web apps. For SPAs this is NOT the skill. ---- - -# Manage user sessions (Scalekit FSA) - -## Skill contract -This SKILL.md must include `name` and `description` frontmatter fields, and the description should be written in third person for reliable skill discovery. - -## What “session management” means here -After successful authentication, the app receives session tokens (typically access + refresh, and sometimes an ID token) that determine how long the user stays signed in and whether refresh can happen without re-authentication. - -This skill implements a secure default for traditional web apps (encrypted HttpOnly cookies) and also supports SPA/mobile patterns (access token in memory + `Authorization: Bearer` headers). - -## Inputs to collect (ask before coding) -- App type: traditional server-rendered web app, SPA, mobile app, or hybrid. -- Framework: Express/Fastify/Next (Node), Flask/Django/FastAPI (Python), Gin/Fiber (Go), Spring Boot (Java), etc. -- Token storage plan: - - Cookie names (examples used below: `accessToken`, `refreshToken`, `idToken`). - - Cookie attributes actually used in the repo (Path, Domain, Secure, HttpOnly, SameSite). -- Encryption approach already present (KMS, libsodium, AES-GCM, framework session store), or whether the app needs one introduced. -- Scalekit SDK/client availability and the exact methods used (validate, refresh, sessions list/revoke). - -## Non-negotiable security rules (defaults) -- Store access and refresh tokens separately. -- Use HttpOnly cookies for tokens in traditional web apps to reduce XSS exposure. -- Use `Secure` in production (HTTPS-only) and set `SameSite` to `Strict` (or `Lax` if Strict breaks auth redirects). -- Scope cookies with `Path` to reduce exposure: - - Access token cookie: scope to `/api` (or your protected routes) when possible. - - Refresh token cookie: scope to the refresh endpoint only (example `/auth/refresh`). -- Rotate refresh tokens on each refresh if your Scalekit flow supports it (token rotation helps detect theft). - -## Workflow (implementation sequence) -1. Implement “store tokens” at login completion. -2. Implement “verify + refresh” middleware that runs on every protected request. -3. Implement a dedicated refresh endpoint (recommended even if middleware calls refresh internally). -4. Add logout and remote session revocation if the product needs “sign out this device / sign out all devices”. -5. Add a test checklist (cookie flags, refresh flow, failure modes). - -## 1) Store session tokens securely - -### Cookie-based approach (traditional web apps) -Use encryption-in-cookie as an extra layer, then store: -- Access token in an HttpOnly cookie with short TTL. -- Refresh token in a separate HttpOnly cookie, ideally scoped to the refresh route. -- ID token in a place that remains available at runtime if needed for logout flows (cookie or local storage depending on your logout design). - -#### Node.js (Express) -```js -import cookieParser from "cookie-parser"; -app.use(cookieParser()); - -// Example after successful authentication: -const { accessToken, expiresIn, refreshToken, idToken } = authResult; - -// Encrypt before storing (implementation is app-specific) -const encAccess = encrypt(accessToken); -const encRefresh = encrypt(refreshToken); - -// Access token: short-lived, cookie scoped -res.cookie("accessToken", encAccess, { - maxAge: (expiresIn - 60) * 1000, // clock-skew buffer - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - path: "/api", -}); - -// Refresh token: separate cookie, scoped to refresh endpoint -res.cookie("refreshToken", encRefresh, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - path: "/auth/refresh", -}); - -// Optional: ID token for logout (only if your logout needs it) -if (idToken) { - res.cookie("idToken", idToken, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - path: "/", - }); -} -``` - -#### Python (Flask) -```py -from flask import make_response -import os - -# auth_result: access_token, expires_in, refresh_token, id_token (optional) -enc_access = encrypt(auth_result.access_token) -enc_refresh = encrypt(auth_result.refresh_token) - -resp = make_response() - -resp.set_cookie( - "accessToken", - enc_access, - max_age=auth_result.expires_in - 60, - httponly=True, - secure=os.environ.get("FLASK_ENV") == "production", - samesite="Strict", - path="/api", -) - -resp.set_cookie( - "refreshToken", - enc_refresh, - httponly=True, - secure=os.environ.get("FLASK_ENV") == "production", - samesite="Strict", - path="/auth/refresh", -) - -if getattr(auth_result, "id_token", None): - resp.set_cookie( - "idToken", - auth_result.id_token, - httponly=True, - secure=os.environ.get("FLASK_ENV") == "production", - samesite="Strict", - path="/", - ) -``` - -#### Go (Gin) -```go -// accessToken, refreshToken, expiresIn come from your auth completion result -encAccess := encrypt(accessToken) -encRefresh := encrypt(refreshToken) - -c.SetSameSite(http.SameSiteStrictMode) - -c.SetCookie("accessToken", encAccess, expiresIn-60, "/api", "", isProd(), true) -c.SetCookie("refreshToken", encRefresh, 0, "/auth/refresh", "", isProd(), true) - -// Optional -if idToken != "" { - c.SetCookie("idToken", idToken, 0, "/", "", isProd(), true) -} -``` - -#### Java (Spring) -```java -// Encrypt tokens before storing (implementation is app-specific) -String encAccess = encrypt(authResult.getAccessToken()); -String encRefresh = encrypt(authResult.getRefreshToken()); - -Cookie access = new Cookie("accessToken", encAccess); -access.setMaxAge(authResult.getExpiresIn() - 60); -access.setHttpOnly(true); -access.setSecure(isProd()); -access.setPath("/api"); -response.addCookie(access); -// Ensure SameSite is applied (implementation depends on your framework version) - -Cookie refresh = new Cookie("refreshToken", encRefresh); -refresh.setHttpOnly(true); -refresh.setSecure(isProd()); -refresh.setPath("/auth/refresh"); -response.addCookie(refresh); -``` - -### SPA/mobile note (reduce CSRF exposure) -For SPAs and mobile apps, prefer: -- Access token stored in memory and sent via `Authorization: Bearer `. -- Refresh token stored in an HttpOnly cookie or secure device storage (platform dependent). -If using cookies in a browser SPA, configure CSRF protections explicitly. - -## 2) Validate access token on every request (and refresh transparently) - -### Behavior -- If access token is valid: proceed. -- If access token is expired and refresh token exists: refresh, rotate, rewrite cookies/headers, proceed. -- If refresh fails: return 401 and force re-login. - -### Node.js middleware (Express-style) -```js -export async function verifySession(req, res, next) { - const accessCookie = req.cookies?.accessToken; - const refreshCookie = req.cookies?.refreshToken; - - if (!accessCookie) return res.status(401).json({ error: "Authentication required" }); - - try { - const accessToken = decrypt(accessCookie); - const isValid = await scalekit.validateAccessToken(accessToken); - - if (isValid) return next(); - - // Not valid -> attempt refresh - if (!refreshCookie) { - return res.status(401).json({ error: "Session expired. Please sign in again." }); - } - - const refreshToken = decrypt(refreshCookie); - const authResult = await scalekit.refreshAccessToken(refreshToken); - - res.cookie("accessToken", encrypt(authResult.accessToken), { - maxAge: (authResult.expiresIn - 60) * 1000, - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - path: "/api", - }); - - res.cookie("refreshToken", encrypt(authResult.refreshToken), { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - path: "/auth/refresh", - }); - - return next(); - } catch (e) { - return res.status(401).json({ error: "Authentication failed" }); - } -} -``` - -### Python decorator (Flask) -```py -from functools import wraps -from flask import request, jsonify, make_response - -def verify_session(f): - @wraps(f) - def inner(*args, **kwargs): - access_cookie = request.cookies.get("accessToken") - refresh_cookie = request.cookies.get("refreshToken") - - if not access_cookie: - return jsonify({"error": "Authentication required"}), 401 - - try: - access_token = decrypt(access_cookie) - is_valid = scalekit_client.validate_access_token(access_token) - - if is_valid: - return f(*args, **kwargs) - - if not refresh_cookie: - return jsonify({"error": "Session expired. Please sign in again."}), 401 - - refresh_token = decrypt(refresh_cookie) - auth_result = scalekit_client.refresh_access_token(refresh_token) - - resp = make_response(f(*args, **kwargs)) - resp.set_cookie("accessToken", encrypt(auth_result.access_token), - max_age=auth_result.expires_in - 60, httponly=True, - secure=is_prod(), samesite="Strict", path="/api") - resp.set_cookie("refreshToken", encrypt(auth_result.refresh_token), - httponly=True, secure=is_prod(), samesite="Strict", path="/auth/refresh") - return resp - except Exception: - return jsonify({"error": "Authentication failed"}), 401 - return inner -``` - -### Go middleware (Gin) -```go -func VerifySession() gin.HandlerFunc { - return func(c *gin.Context) { - accessCookie, err := c.Cookie("accessToken") - if err != nil || accessCookie == "" { - c.JSON(http.StatusUnauthorized, gin.H{"error":"Authentication required"}) - c.Abort() - return - } - - accessToken := decrypt(accessCookie) - isValid, err := scalekitClient.ValidateAccessToken(accessToken) - if err == nil && isValid { - c.Next() - return - } - - refreshCookie, err := c.Cookie("refreshToken") - if err != nil || refreshCookie == "" { - c.JSON(http.StatusUnauthorized, gin.H{"error":"Session expired. Please sign in again."}) - c.Abort() - return - } - - refreshToken := decrypt(refreshCookie) - authResult, err := scalekitClient.RefreshAccessToken(refreshToken) - if err != nil { - c.JSON(http.StatusUnauthorized, gin.H{"error":"Session expired. Please sign in again."}) - c.Abort() - return - } - - c.SetSameSite(http.SameSiteStrictMode) - c.SetCookie("accessToken", encrypt(authResult.AccessToken), authResult.ExpiresIn-60, "/api", "", isProd(), true) - c.SetCookie("refreshToken", encrypt(authResult.RefreshToken), 0, "/auth/refresh", "", isProd(), true) - - c.Next() - } -} -``` - -### Java interceptor (Spring) -Implement `HandlerInterceptor#preHandle` (or a filter) to: -- Read cookies. -- Decrypt and validate access token. -- Refresh when invalid and refresh token exists. -- Rewrite cookies and allow the request to proceed. -Return 401 on failure. - -## 3) Configure session security and duration (dashboard-driven) -Session behavior should be adjustable without code changes (typical policy knobs): -- Absolute session timeout (max total session time). -- Idle session timeout (logout after inactivity). -- Access token lifetime (drives refresh frequency). - -## 4) Manage sessions remotely (API/SDK) -Use Scalekit session APIs to implement: -- “View active sessions” in account settings. -- “Sign out this device” (revoke a single session). -- “Sign out all devices” (revoke all sessions for a user). - -### Example (Node.js) -```js -const sessionDetails = await scalekit.session.getSession("ses_1234567890123456"); - -const userSessions = await scalekit.session.getUserSessions("usr_1234567890123456", { - pageSize: 10, - filter: { status: ["ACTIVE"] } -}); - -await scalekit.session.revokeSession("ses_1234567890123456"); -await scalekit.session.revokeAllUserSessions("usr_1234567890123456"); -``` - -## Testing checklist (must pass) -- Cookies are `HttpOnly`, `Secure` (in prod), and `SameSite` is set intentionally. -- Cookie `Path` scoping works: refresh token cookie is only sent to `/auth/refresh`. -- Protected routes reject missing/invalid access token with 401. -- Expired access token triggers refresh and continues the request without user interaction. -- Refresh failure forces re-login (401) and does not loop. -- Multi-device: remote revoke invalidates the targeted session(s) as expected. - -## Common failure modes -- Cookie deletion/overwrite doesn’t work due to mismatched Path/Domain. -- Refresh token accidentally sent to all endpoints (missing `Path=/auth/refresh`). -- Middleware refreshes but does not rotate tokens (misses theft detection benefits). -- SPA stores access token in localStorage (higher XSS risk) when memory storage was feasible. \ No newline at end of file diff --git a/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/SKILL.md b/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/SKILL.md deleted file mode 100644 index 66f84e5..0000000 --- a/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/SKILL.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -name: migrating-to-scalekit-auth -description: Plans and executes a safe, incremental migration from any existing auth system to Scalekit's full-stack auth platform. Use when the user asks to migrate authentication, replace session middleware, import users/organizations to Scalekit, configure SSO, or set up SCIM provisioning with Scalekit. ---- - -# Scalekit Auth Migration Planner - -Guides an incremental, reversible migration from an existing auth system to Scalekit. Follow these phases in order—do not skip phases. - -## Migration checklist - -Copy and track progress: - -``` -Migration Progress: -- [ ] Phase 1: Audit and export existing auth data -- [ ] Phase 2: Import organizations and users into Scalekit -- [ ] Phase 3: Configure redirects and roles -- [ ] Phase 4: Update application code -- [ ] Phase 5: Deploy and monitor -``` - ---- - -## Phase 1: Audit and export - -Conduct a code audit covering: -- Sign-up/login flows, session middleware, token validation -- RBAC logic, email verification, logout/session termination - -Export the following data: -- User records (email, name, `email_verified`) -- Org/tenant structure -- Role assignments and permissions -- SSO/IdP provider configs - -**Backup checklist before proceeding:** -- [ ] Export a sample JWT or session cookie (understand current format) -- [ ] Set up a feature flag to roll back to old auth system -- [ ] Document rollback procedure - -Minimum user schema: - -| Field | Required | -|---|---| -| `email` | Required | -| `first_name` | Optional | -| `last_name` | Optional | -| `email_verified` | Optional (defaults `false`) | - -See [AUDIT-CHECKLIST.md](AUDIT-CHECKLIST.md) for full code audit patterns. - ---- - -## Phase 2: Import organizations and users - -`external_id` is critical—store original PKs here to preserve system-to-system mappings. - -**Step 1: Create organizations first** - -Node.js example: -```javascript -const result = await scalekit.organization.createOrganization( - org.display_name, - { externalId: org.external_id, metadata: org.metadata } -); -``` - -**Step 2: Create users within organizations** - -```javascript -const { user } = await scalekit.user.createUserAndMembership("org_scalekit_id", { - email: "user@example.com", - externalId: "usr_987", - userProfile: { firstName: "John", lastName: "Doe" }, -}); -``` - -**Rules:** -- Set `sendInvitationEmail: false` during import to skip invite emails; membership auto-activates and email is marked verified -- Batch imports in parallel; respect Scalekit rate limits -- Validate `external_id` mappings match source data exactly - -For language-specific samples (Python, Go, Java, cURL): See [IMPORT-SAMPLES.md](IMPORT-SAMPLES.md). - ---- - -## Phase 3: Configure redirects and roles - -**Redirects:** -- Register callback URLs in **Settings → Redirects** in Scalekit dashboard -- Add post-logout URLs to control destination after logout - -**Roles:** -- Define roles under **User Management → Roles** or via SDK -- During user import, include `roles` array inside the `membership` object -- Verify role claims are readable from the token after login - ---- - -## Phase 4: Update application code - -**Session middleware:** Replace legacy JWT validation with Scalekit SDK or JWKS endpoint. - -Verify: -- [ ] Access tokens accepted on all protected routes -- [ ] Refresh token renewal works seamlessly -- [ ] `roles` claim from Scalekit tokens used for RBAC checks - -**Login page:** Update logo, colors, copy, and legal links in Scalekit dashboard under Branding. - -**Secondary flows to update:** -- Email verification prompt -- Logout redirect destination - ---- - -## Phase 5: Deploy and monitor - -**Pre-deployment:** -- [ ] Test login with a subset of migrated users -- [ ] Verify session creation, validation, and expiry -- [ ] Confirm role-based access works end-to-end - -**Deployment sequence:** -1. Deploy updated application code -2. Enable feature flag → route traffic to Scalekit -3. Start at 5–10% of users; expand after stability confirmed -4. Monitor auth success rates and error logs -5. Keep rollback plan active for first 48 hours - -**Post-deployment monitoring:** -- Auth error rates -- Session creation/validation metrics -- SSO connection health -- User-reported issues via support - ---- - -## Troubleshooting - -| Symptom | Fix | -|---|---| -| Users can't log in | Verify callback URLs registered; check `external_id` mappings; ensure emails match exactly | -| Session validation fails | Switch JWT validation to Scalekit JWKS endpoint; verify token expiry/refresh logic | -| SSO not working | Confirm org has SSO enabled; verify IdP config; test IdP-initiated login | - -> **Note:** Password migration support is coming. If required, contact Scalekit's Solutions team. diff --git a/plugins/full-stack-auth/skills/production-readiness-scalekit/SKILL.md b/plugins/full-stack-auth/skills/production-readiness-scalekit/SKILL.md deleted file mode 100644 index 8ab1a0a..0000000 --- a/plugins/full-stack-auth/skills/production-readiness-scalekit/SKILL.md +++ /dev/null @@ -1,159 +0,0 @@ ---- -name: production-readiness-scalekit -description: Walks through a structured production readiness checklist for Scalekit authentication implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, hardening their auth setup, or wants to verify their Scalekit implementation is production-ready. ---- - -# Scalekit Production Readiness - -Work through each section in order — earlier sections are blockers for later ones. Skip sections that don't apply to this implementation. - ---- - -## Quick checks (run first) - -- [ ] Production environment URL, client ID, and client secret are set (not dev/staging values) -- [ ] HTTPS enforced on all auth endpoints -- [ ] CORS restricted to your domains only -- [ ] API credentials stored in environment variables — never committed to code -- [ ] Only enabled auth methods are active in production - ---- - -## Customization - -- [ ] Login page branded with logo, colors, styling -- [ ] Email templates customized (sign-up, password reset, invitations) -- [ ] Custom domain configured for auth pages (if applicable) -- [ ] Email provider configured in **Dashboard > Customization > Emails** -- [ ] Email deliverability tested — check spam folders -- [ ] Webhooks configured with signature validation - ---- - -## Core auth flows - -- [ ] Test login initiation with authorization URL -- [ ] Validate redirect URLs match dashboard configuration exactly -- [ ] Test authentication completion and code exchange -- [ ] Validate `state` parameter in callbacks (CSRF protection) -- [ ] Verify session token storage uses `httpOnly`, `secure`, and `sameSite` flags -- [ ] Configure token lifetimes for your security requirements -- [ ] Test session timeout and automatic token refresh -- [ ] Verify logout clears sessions completely - -**Test each enabled auth method:** -- [ ] Email/password: sign-up, login, password reset -- [ ] Magic links: initiation, delivery, redemption, expiry -- [ ] Social logins: each configured provider (Google, Microsoft, GitHub, etc.) - → Provider setup guides: https://docs.scalekit.com/guides/integrations/social-connections/ -- [ ] Passkeys: registration, authentication, fallback -- [ ] Auth method selection UI renders correctly -- [ ] Fallback scenarios when an auth method fails - -**Error handling:** -- [ ] Expired tokens handled gracefully -- [ ] Invalid authorization codes rejected -- [ ] Network failures show user-friendly messages -- [ ] Complete end-to-end flow validated in staging before production - ---- - -## Enterprise auth (if enterprise customers at launch) - -**SSO:** -- [ ] Test SSO with target IdPs: Okta, Azure AD, Google Workspace - → IT admin setup guides per IdP: https://docs.scalekit.com/guides/integrations/sso-integrations/ -- [ ] Configure user attribute mapping (email, name, groups) -- [ ] Test both SP-initiated and IdP-initiated SSO flows -- [ ] Verify SSO error handling for misconfigured connections -- [ ] Test SSO with: new users, existing users, deactivated users - -**JIT provisioning:** -- [ ] Register all organization domains for JIT provisioning -- [ ] Configure consistent user identifiers across SSO connections (email or userPrincipalName) -- [ ] Set default roles for JIT-provisioned users -- [ ] Enable "Sync user attributes during login" -- [ ] Plan manual invitation process for contractors/external users with non-matching domains - -**SCIM provisioning:** -- [ ] Configure webhook endpoints to receive SCIM events - → IT admin setup guides per IdP: https://docs.scalekit.com/guides/integrations/scim-integrations/ -- [ ] Verify webhook security with signature validation -- [ ] Test user provisioning (automatic creation) -- [ ] Test user deprovisioning (deactivation/deletion) -- [ ] Test user profile updates and role changes -- [ ] Set up group-based role assignment and sync -- [ ] Test error cases: duplicate users, invalid data - -**Admin portal:** -- [ ] Configure admin portal access for enterprise customers -- [ ] Test admin portal SSO configuration flows -- [ ] Verify user management features in admin portal - -**Network/firewall — enterprise customers behind VPN must whitelist:** - -| Domain | Purpose | -|---|---| -| `.scalekit.com` | Auth + admin portal | -| `cdn.scalekit.com` | Static assets | -| `fonts.googleapis.com` | Font resources | - -- [ ] Customer firewalls allow Scalekit domains -- [ ] SSO tested from customer's network environment - ---- - -## User and organization management (if implemented) - -**User flows:** -- [ ] Configure profile fields collected at sign-up -- [ ] Test invitation flow and email templates -- [ ] Test user deletion flow - -**Organization flows:** -- [ ] Test organization creation -- [ ] Test adding and removing users from organizations -- [ ] Set allowed email domains for org sign-ups (if applicable) -- [ ] Verify organization switching for users in multiple orgs -- [ ] Test organization deletion flow - -**RBAC (if implemented):** -- [ ] Define and create roles and permissions -- [ ] Set default roles for new users -- [ ] Test role assignment to users and org members -- [ ] Verify permission checks in application code -- [ ] Test access control across all role levels -- [ ] Validate permission enforcement at API endpoints - ---- - -## MCP authentication (if implemented) - -- [ ] Test MCP server authentication flow -- [ ] Verify OAuth consent screen for MCP clients -- [ ] Test token exchange for MCP connections -- [ ] Verify custom auth handlers (if using) -- [ ] Test MCP session management - ---- - -## Monitoring and incident readiness - -**Observability:** -- [ ] Auth logs monitoring configured in **Dashboard > Auth Logs** -- [ ] Alerts set for suspicious activity (multiple failed logins, unusual locations) -- [ ] Webhook event monitoring and logging active -- [ ] Error tracking configured for authentication failures - -**Key metrics to track from day one:** -- Sign-up rate and conversion -- Login success/failure rates -- Session creation and duration -- Token refresh frequency -- Webhook delivery success rate - -**Reliability:** -- [ ] Log retention policies configured -- [ ] Webhook delivery and retry mechanism tested -- [ ] Incident response runbook written (who to contact, how to roll back, escalation path) -- [ ] Rollback plan ready (feature flag to disable new auth flows if needed) diff --git a/plugins/mcp-auth/.claude-plugin/plugin.json b/plugins/mcp-auth/.claude-plugin/plugin.json deleted file mode 100644 index 5893c0e..0000000 --- a/plugins/mcp-auth/.claude-plugin/plugin.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "mcp-auth", - "description": "OAuth 2.1 authorization for MCP servers using Scalekit to protect tools used by AI IDEs and agents.", - "version": "1.3.2", - "author": { - "name": "Scalekit Inc", - "email": "support@scalekit.com" - }, - "homepage": "https://docs.scalekit.com/dev-kit/build-with-ai/mcp-auth/", - "repository": "https://github.com/scalekit-inc/claude-code-authstack", - "license": "MIT", - "keywords": ["oauth", "mcp", "authentication", "scalekit"] -} diff --git a/plugins/mcp-auth/.mcp.json b/plugins/mcp-auth/.mcp.json deleted file mode 100644 index 783b9a1..0000000 --- a/plugins/mcp-auth/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "scalekit": { - "type": "http", - "url": "https://mcp.scalekit.com" - } - } -} diff --git a/plugins/mcp-auth/README.md b/plugins/mcp-auth/README.md deleted file mode 100644 index 88cfe13..0000000 --- a/plugins/mcp-auth/README.md +++ /dev/null @@ -1,254 +0,0 @@ -# MCP OAuth Authorization Skill - -A Claude Code skill that guides users through adding production-ready OAuth 2.1 authorization to Model Context Protocol (MCP) servers using Scalekit. - -## What This Skill Does - -This skill helps Claude assist users in: -- Setting up OAuth 2.1 authorization for MCP servers -- Implementing token validation middleware -- Configuring Scalekit as the authorization server -- Adding scope-based access control -- Testing and deploying secure MCP servers - -When users ask about securing MCP servers, implementing authentication for AI hosts (Claude Desktop, Cursor, VS Code), or integrating Scalekit with MCP, Claude will automatically use this skill to provide step-by-step guidance. - -## Installation - -### Option 1: Part of Plugin (Recommended) - -If this skill is part of the `mcp-auth` plugin, it will be automatically available when the plugin is loaded. - -1. Ensure the plugin structure is correct: -``` -claude-code-auth-plugin/ -└── plugins/ - └── mcp-auth/ - ├── .claude-plugin/ - │ └── plugin.json - └── skills/ - └── mcp-auth/ - ├── SKILL.md - └── README.md (this file) -``` - -2. Claude Code will automatically discover and load the skill when relevant. - -### Option 2: Standalone Skill - -Copy the `mcp-auth` directory to your Claude Code skills directory: - -**macOS/Linux:** -```bash -cp -r mcp-auth ~/.claude/skills/ -``` - -**Windows:** -```powershell -Copy-Item -Recurse mcp-auth "$env:USERPROFILE\.claude\skills\" -``` - -## Usage - -### Trigger Phrases - -Claude will activate this skill when users mention: -- "add OAuth to my MCP server" -- "secure my MCP server" -- "implement authentication for MCP" -- "use Scalekit with MCP" -- "add auth to Model Context Protocol server" -- "protect MCP tools" -- "MCP server security" - -### Example Interactions - -**User:** "I need to add authentication to my MCP server" - -**Claude:** Will provide a complete workflow with: -1. Scalekit SDK installation instructions -2. Dashboard configuration steps -3. Discovery endpoint implementation -4. Token validation middleware setup -5. Testing and deployment checklists - -**User:** "How do I implement scope-based authorization for my MCP tools?" - -**Claude:** Will guide through: -1. Defining scopes in Scalekit dashboard -2. Adding scope validation to tool execution -3. Handling insufficient scope errors -4. Testing scope enforcement - -## Prerequisites - -Users working with this skill should have: -- An MCP server project (Node.js or Python) -- A Scalekit account (free tier available at app.scalekit.com) -- Basic understanding of: - - REST APIs - - OAuth 2.1 concepts (token-based authentication) - - Express.js (Node.js) or FastAPI (Python) - -## Supported Frameworks - -The skill provides guidance for: -- **Node.js**: Express.js -- **Python**: FastAPI, FastMCP -- Both include complete code examples - -## What's Included - -### SKILL.md Structure - -1. **Setup Workflow**: 6-step checklist for complete OAuth implementation -2. **Step-by-step Instructions**: Detailed guidance for each phase -3. **Code Examples**: Working implementations for Node.js and Python -4. **Optional Features**: Scope-based authorization, additional auth methods -5. **Testing Guidance**: Checklists for verification and deployment -6. **Troubleshooting**: Common issues and solutions - -### Key Features - -- ✅ Progressive disclosure (core steps first, advanced features optional) -- ✅ Framework-specific code examples -- ✅ Copy-paste ready code snippets -- ✅ Production deployment checklist -- ✅ Testing guidance for multiple AI hosts -- ✅ Troubleshooting common issues - -## Skill Metadata - -```yaml -name: adding-mcp-oauth -description: Guides users through adding OAuth 2.1 authorization to Model Context Protocol (MCP) servers using Scalekit. Use when setting up MCP servers, implementing authentication for AI hosts like Claude Desktop, Cursor, or VS Code, or when users mention MCP security, OAuth, or Scalekit integration. -``` - -## Design Philosophy - -This skill follows Claude Code best practices: - -1. **Concise**: Assumes Claude already understands OAuth and APIs -2. **Workflow-based**: Provides clear sequential steps with checklists -3. **Medium freedom**: Specific patterns for fragile operations (token validation), flexibility for implementation details -4. **Practical**: Working code examples over theoretical explanations -5. **Testing-focused**: Includes verification at every step - -## Customization - -### Adding Framework Support - -To add support for additional frameworks: - -1. Add a new code example section in SKILL.md -2. Follow the same pattern: initialization → middleware → validation -3. Keep examples concise (20-30 lines max) -4. Test with Claude to ensure discoverability - -### Extending Authentication Methods - -To add guidance for additional auth methods: - -1. Add a new section under "Additional authentication methods" -2. Explain when to use the method -3. Link to relevant Scalekit documentation -4. Keep explanation under 5 lines - -## Testing This Skill - -### Manual Testing - -1. Start a conversation with Claude Code -2. Ask: "How do I add OAuth to my MCP server?" -3. Verify Claude: - - Provides the 6-step workflow - - Offers code examples for your chosen framework - - Includes testing and deployment checklists - -### Evaluation Scenarios - -**Scenario 1: New MCP server** -- Query: "I'm building a new MCP server and need to add authentication" -- Expected: Complete workflow from SDK installation through deployment - -**Scenario 2: Existing server** -- Query: "I have an Express MCP server, how do I secure it with OAuth?" -- Expected: Framework-specific guidance, focus on middleware implementation - -**Scenario 3: Scope-based auth** -- Query: "How do I restrict certain MCP tools to specific users?" -- Expected: Scope definition and validation guidance - -## Troubleshooting - -### Skill Not Triggering - -**Issue**: Claude doesn't use the skill when asked about MCP authentication - -**Solutions**: -- Verify SKILL.md has correct YAML frontmatter -- Check the description includes key terms: "MCP", "OAuth", "authorization" -- Try more specific phrases: "add OAuth to MCP server" - -### Code Examples Not Working - -**Issue**: Generated code doesn't match user's setup - -**Solutions**: -- Remind Claude of your specific framework (Express, FastAPI, etc.) -- Ask Claude to adapt the example for your environment -- Check Scalekit dashboard values match code placeholders - -## Contributing - -### Improving This Skill - -To enhance the skill: - -1. Test with real MCP server projects -2. Note where Claude struggles or provides incomplete guidance -3. Add missing information to SKILL.md -4. Keep additions concise (challenge each sentence) -5. Test changes with fresh Claude instances - -### Reporting Issues - -If you find issues: -- Describe the user query that triggered unexpected behavior -- Share what Claude provided vs. what was needed -- Suggest specific improvements - -## Related Resources - -- [Scalekit MCP Documentation](https://docs.scalekit.com/authenticate/mcp/) -- [MCP Specification](https://spec.modelcontextprotocol.io/) -- [Claude Code Skills Documentation](https://code.claude.com/docs/en/agent-skills) -- [Scalekit Dashboard](https://app.scalekit.com/) - -## Version History - -### v1.0.0 (February 2026) -- Initial release -- Support for Node.js (Express) and Python (FastAPI) -- 6-step OAuth implementation workflow -- Scope-based authorization guidance -- Testing and deployment checklists - -## License - -This skill is provided as part of the claude-code-auth-plugin. See the main plugin directory for license information. - -## Author - -Created by Saif Ali Shaik (@saif) for the Scalekit team. - -## Feedback - -For questions or suggestions about this skill: -- Open an issue in the plugin repository -- Reach out to the Scalekit team -- Test and iterate based on real usage - ---- - -**Note**: This skill assumes users have basic familiarity with OAuth concepts and MCP servers. For users new to these concepts, Claude may need to provide additional context beyond what's in the skill. diff --git a/plugins/mcp-auth/hooks/beacon.sh b/plugins/mcp-auth/hooks/beacon.sh deleted file mode 100755 index 98506da..0000000 --- a/plugins/mcp-auth/hooks/beacon.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -PLUGIN="${1:-unknown}" -HOOK="${2:-stop}" -INPUT=$(cat) - -SESSION_ID=$(echo "$INPUT" | python3 -c \ - "import sys,json; print(json.load(sys.stdin).get('session_id','anonymous'))" \ - 2>/dev/null || echo "anonymous") - -TOOL_NAME=$(echo "$INPUT" | python3 -c \ - "import sys,json; print(json.load(sys.stdin).get('tool_name',''))" \ - 2>/dev/null || echo "") - -curl -s -o /dev/null --max-time 5 \ - -X POST https://ph.scalekit.com/i/v0/e/ \ - -H "Content-Type: application/json" \ - -d "{\"token\":\"phc_85pLP8gwYvRCQdxgLQP24iqXHPRGaLgEw4S4dgZHJZ\",\ -\"event\":\"plugin_used\",\ -\"distinct_id\":\"${SESSION_ID}\",\ -\"properties\":{\"plugin\":\"${PLUGIN}\",\"coding_agent\":\"claude_code\",\"hook\":\"${HOOK}\",\"tool_name\":\"${TOOL_NAME}\"}}" diff --git a/plugins/mcp-auth/hooks/hooks.json b/plugins/mcp-auth/hooks/hooks.json deleted file mode 100644 index 23f021f..0000000 --- a/plugins/mcp-auth/hooks/hooks.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "description": "Usage beacon for Scalekit mcp-auth plugin", - "hooks": { - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh mcp-auth stop", - "timeout": 10 - } - ] - } - ] - } -} diff --git a/plugins/mcp-auth/references/redirects.md b/plugins/mcp-auth/references/redirects.md deleted file mode 100644 index dbe1977..0000000 --- a/plugins/mcp-auth/references/redirects.md +++ /dev/null @@ -1,76 +0,0 @@ -# Redirects - -Redirects are registered endpoints in Scalekit that control where users are directed during authentication flows. You must configure these endpoints in the Scalekit dashboard before they can be used. - -All redirect URIs must be registered under Authentication settings in your Scalekit dashboard. This is a security requirement to prevent unauthorized redirects. - - -## Redirect endpoint types - -### Allowed callback URLs -**Purpose**: Where users are sent after successful authentication to exchange authorization codes and retrieve profile information. - -**Example scenario**: A user completes sign-in and Scalekit redirects them to `https://yourapp.com/callback` where your application processes the authentication response. - -To add or remove an redirect URL, go to Dashboard > Authentication > Redirects > Allowed Callback URLs. - -### Initiate login URL -**Purpose**: When authentication does not initiate from your application, Scalekit redirects users back to your application's login initiation endpoint. This endpoint should point to a route in your application that ultimately redirects users to Scalekit's `/authorize` endpoint. - -**Example scenarios**: - -- **Bookmarked login page**: A user bookmarks your login page and visits it directly. Your application detects they're not authenticated and redirects them to Scalekit's authorization endpoint. - -- **Organization invitation flow**: A user clicks an invitation link to join an organization. Your application receives the invitation token and redirects the user to Scalekit's authorization endpoint to complete the sign-up process. - -- **IdP-initiated SSO**: An administrator initiates single sign-on from their identity provider dashboard. The IdP redirects users to your application, which then redirects them to Scalekit's authorization endpoint to complete authentication. - -- **Session expiration**: When a user's session expires or they access a protected resource, they're redirected to `https://yourapp.com/login` which then redirects to Scalekit's authentication endpoint. - -### Post logout URL -**Purpose**: Where users are sent after successfully signing out of your application. - -**Example scenario**: After logging out, users are redirected to `https://yourapp.com/goodbye` to confirm their session has ended. - -### Back channel logout URL -**Purpose**: A secure endpoint that receives notifications whenever a user is logged out from Scalekit, regardless of how the logout was initiated — admin triggered, user initiated, or due to session policies like idle timeout. - -**Example scenario**: When a user logs out from any application (user-initiated, admin-initiated, or due to session policies like idle timeout), Scalekit sends a logout notification to `https://yourapp.com/logout` to suggest termination of the user's session across all connected applications, ensuring coordinated logout for enhanced security. - -### Custom URI schemes - -Custom URI schemes allow for redirects, enabling deep linking and native app integrations. Some applications include: -- **Desktop applications**: Use schemes like `{scheme}://` for native app integration -- **Mobile apps**: Use schemes like `myapp://` for mobile app deep linking - -**Example custom schemes**: -- `{scheme}://auth/callback` - For custom scheme authentication -- `myapp://login/callback` - For mobile app authentication - - -## URI validation requirements - -Your redirect URIs must meet specific requirements that vary between development and production environments: - -| Requirement | Development | Production | -| ----------- | ----------- | ---------- | -| Supported schemes | `http`, `https`, `{scheme}` | `https`, `{scheme}` | -| Localhost support | Allowed | Not allowed | -| Wildcard domains | Allowed | Not allowed | -| URI length limit | 256 characters | 256 characters | -| Query parameters | Not allowed | Not allowed | -| URL fragments | Not allowed | Not allowed | - - -### Wildcard usage patterns - -Wildcards can simplify testing in development environments, but they must follow specific patterns: - -| Validation rule | Valid examples | Invalid examples | -| --------------- | -------------- | ---------------- | -| Wildcards cannot be used as root-level domains | `https://*.acmecorp.com`, `https://auth-*.acmecorp.com` | `https://*.com` | -| Only one wildcard character is allowed per URI | `https://*.acmecorp.com` | `https://*.*.acmecorp.com` | -| Wildcards must be in the hostname component only | `https://*.acmecorp.com` | `https://acmecorp.*.com` | -| Wildcards must be in the outermost subdomain | `https://*.auth.acmecorp.com` | `https://auth.*.acmecorp.com` | - -> **Note**: According to the [OAuth 2.0 specification](https://tools.ietf.org/html/rfc6749#section-3.1.2), redirect URIs must be absolute URIs. For development convenience, Scalekit relaxes this restriction slightly by allowing wildcards in development environments. diff --git a/plugins/mcp-auth/skills/add-auth-fastmcp/SKILL.md b/plugins/mcp-auth/skills/add-auth-fastmcp/SKILL.md deleted file mode 100644 index e2b5625..0000000 --- a/plugins/mcp-auth/skills/add-auth-fastmcp/SKILL.md +++ /dev/null @@ -1,446 +0,0 @@ ---- -name: mcp-oauth-fastmcp -description: Add OAuth 2.1 authorization to FastMCP servers using Scalekit provider plugin. Use when building FastMCP servers, when users mention FastMCP authentication, Python MCP servers with Scalekit, or need rapid OAuth integration with minimal code. ---- - -# FastMCP OAuth with Scalekit Provider - -Secure your FastMCP server with OAuth 2.1 in just 5 lines of code using Scalekit's built-in provider. This approach handles token validation, scope enforcement, and authentication flows automatically. - -## FastMCP advantage - -**Standard MCP OAuth**: ~30 lines of middleware code, manual token validation -**FastMCP with Scalekit provider**: ~5 lines of configuration, automatic token handling - -## Setup workflow - -Copy this checklist and track progress: - -``` -FastMCP OAuth Setup: -- [ ] Step 1: Register MCP server in Scalekit -- [ ] Step 2: Install FastMCP and dependencies -- [ ] Step 3: Configure Scalekit provider -- [ ] Step 4: Add scope validation to tools -- [ ] Step 5: Test with MCP Inspector -``` - -## Step 1: Register MCP server - -In Scalekit dashboard: - -1. Navigate to **Dashboard > MCP Servers > Add MCP Server** -2. Enter server name (e.g., `FastMCP Todo Server`) -3. Set **Server URL** to `http://localhost:3002/` (include trailing slash) -4. Define scopes for your tools (e.g., `todo:read`, `todo:write`) -5. Click **Save** and note the `resource_id` - -**Critical**: Use base URL with trailing slash. FastMCP appends `/mcp` automatically. -- ✓ Correct: `http://localhost:3002/` -- ✗ Wrong: `http://localhost:3002/mcp` - -## Step 2: Install dependencies - -Create project structure: - -```bash -mkdir fastmcp-server -cd fastmcp-server -python3 -m venv venv -source venv/bin/activate -``` - -Create `requirements.txt`: - -```txt -fastmcp>=2.13.0.2 -python-dotenv>=1.0.0 -``` - -Install: - -```bash -pip install -r requirements.txt -``` - -## Step 3: Configure Scalekit provider - -Create `.env` file with Scalekit credentials: - -```bash -PORT=3002 -SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com -SCALEKIT_CLIENT_ID=your_client_id -SCALEKIT_RESOURCE_ID=res_your_resource_id -MCP_URL=http://localhost:3002/ -``` - -Get these values from **Scalekit Dashboard > Settings** and your MCP server configuration. - -Initialize FastMCP server with Scalekit provider (`server.py`): - -```python -import os -from dotenv import load_dotenv -from fastmcp import FastMCP -from fastmcp.server.auth.providers.scalekit import ScalekitProvider - -load_dotenv() - -# 5-line OAuth setup -mcp = FastMCP( - "Your Server Name", - stateless_http=True, - auth=ScalekitProvider( - environment_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), - client_id=os.getenv("SCALEKIT_CLIENT_ID"), - resource_id=os.getenv("SCALEKIT_RESOURCE_ID"), - mcp_url=os.getenv("MCP_URL"), - ), -) - -if __name__ == "__main__": - mcp.run(transport="http", port=int(os.getenv("PORT", "3002"))) -``` - -That's it! The Scalekit provider handles: -- Token validation on every request -- OAuth flow with MCP clients -- WWW-Authenticate header responses -- Discovery endpoint (`/.well-known/oauth-protected-resource`) - -## Step 4: Add scope validation to tools - -Use the built-in `get_access_token()` dependency to validate scopes: - -```python -from fastmcp.server.dependencies import AccessToken, get_access_token - -def _require_scope(scope: str) -> str | None: - """Validate request token has required scope.""" - token: AccessToken = get_access_token() - if scope not in token.scopes: - return f"Insufficient permissions: `{scope}` scope required." - return None - -@mcp.tool -def create_todo(title: str, description: str = None) -> dict: - """Create a new todo item. Requires todo:write scope.""" - error = _require_scope("todo:write") - if error: - return {"error": error} - - # Your tool implementation - todo_id = str(uuid.uuid4()) - return {"id": todo_id, "title": title, "description": description} - -@mcp.tool -def list_todos() -> dict: - """List all todos. Requires todo:read scope.""" - error = _require_scope("todo:read") - if error: - return {"error": error} - - # Your tool implementation - return {"todos": [...]} -``` - -**Pattern**: Every tool that requires authorization should call `_require_scope()` first. - -## Step 5: Test with MCP Inspector - -Run your server: - -```bash -source venv/bin/activate -python server.py -``` - -Launch MCP Inspector: - -```bash -npx @modelcontextprotocol/inspector@latest -``` - -In Inspector: -1. Enter URL: `http://localhost:3002/mcp` -2. Leave authentication fields empty (uses dynamic client registration) -3. Click **Connect** -4. Complete OAuth flow when prompted -5. Test tools with scoped tokens - -**Testing scope enforcement**: -- Call `create_todo` with token that only has `todo:read` → should fail -- Call `list_todos` with `todo:read` scope → should succeed - -## Complete example: Todo server - -```python -import os -import uuid -from dataclasses import dataclass, asdict -from typing import Optional - -from dotenv import load_dotenv -from fastmcp import FastMCP -from fastmcp.server.auth.providers.scalekit import ScalekitProvider -from fastmcp.server.dependencies import AccessToken, get_access_token - -load_dotenv() - -mcp = FastMCP( - "Todo Server", - stateless_http=True, - auth=ScalekitProvider( - environment_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), - client_id=os.getenv("SCALEKIT_CLIENT_ID"), - resource_id=os.getenv("SCALEKIT_RESOURCE_ID"), - mcp_url=os.getenv("MCP_URL"), - ), -) - -@dataclass -class TodoItem: - id: str - title: str - description: Optional[str] - completed: bool = False - - def to_dict(self) -> dict: - return asdict(self) - -_TODO_STORE: dict[str, TodoItem] = {} - -def _require_scope(scope: str) -> Optional[str]: - token: AccessToken = get_access_token() - if scope not in token.scopes: - return f"Insufficient permissions: `{scope}` scope required." - return None - -@mcp.tool -def create_todo(title: str, description: Optional[str] = None) -> dict: - error = _require_scope("todo:write") - if error: - return {"error": error} - - todo = TodoItem(id=str(uuid.uuid4()), title=title, description=description) - _TODO_STORE[todo.id] = todo - return {"todo": todo.to_dict()} - -@mcp.tool -def list_todos(completed: Optional[bool] = None) -> dict: - error = _require_scope("todo:read") - if error: - return {"error": error} - - todos = [ - todo.to_dict() - for todo in _TODO_STORE.values() - if completed is None or todo.completed == completed - ] - return {"todos": todos} - -@mcp.tool -def get_todo(todo_id: str) -> dict: - error = _require_scope("todo:read") - if error: - return {"error": error} - - todo = _TODO_STORE.get(todo_id) - if todo is None: - return {"error": f"Todo `{todo_id}` not found."} - - return {"todo": todo.to_dict()} - -@mcp.tool -def update_todo( - todo_id: str, - title: Optional[str] = None, - description: Optional[str] = None, - completed: Optional[bool] = None, -) -> dict: - error = _require_scope("todo:write") - if error: - return {"error": error} - - todo = _TODO_STORE.get(todo_id) - if todo is None: - return {"error": f"Todo `{todo_id}` not found."} - - if title is not None: - todo.title = title - if description is not None: - todo.description = description - if completed is not None: - todo.completed = completed - - return {"todo": todo.to_dict()} - -@mcp.tool -def delete_todo(todo_id: str) -> dict: - error = _require_scope("todo:write") - if error: - return {"error": error} - - todo = _TODO_STORE.pop(todo_id, None) - if todo is None: - return {"error": f"Todo `{todo_id}` not found."} - - return {"deleted": todo_id} - -if __name__ == "__main__": - mcp.run(transport="http", port=int(os.getenv("PORT", "3002"))) -``` - -## Environment variable reference - -| Variable | Description | Example | -|----------|-------------|---------| -| `SCALEKIT_ENVIRONMENT_URL` | Your Scalekit environment URL | `https://yourenv.scalekit.com` | -| `SCALEKIT_CLIENT_ID` | Client ID from Scalekit dashboard | `skc_...` | -| `SCALEKIT_RESOURCE_ID` | MCP server resource ID | `res_...` | -| `MCP_URL` | Base URL with trailing slash | `http://localhost:3002/` | -| `PORT` | HTTP server port | `3002` | - -## Scope design patterns - -**Read-only operations**: Use `*:read` scope -- `todo:read`, `data:read`, `user:read` - -**Write operations**: Use `*:write` scope -- `todo:write`, `data:write`, `user:write` - -**Admin operations**: Use `*:admin` scope -- `system:admin`, `user:admin` - -**Multiple scopes per tool**: Return error if ANY required scope is missing - -```python -def _require_scopes(scopes: list[str]) -> str | None: - token: AccessToken = get_access_token() - missing = [s for s in scopes if s not in token.scopes] - if missing: - return f"Missing scopes: {', '.join(missing)}" - return None - -@mcp.tool -def admin_action() -> dict: - error = _require_scopes(["todo:write", "admin:access"]) - if error: - return {"error": error} - # Implementation -``` - -## Production deployment - -### Security checklist -- [ ] Store `.env` in secret manager (AWS Secrets Manager, Vault) -- [ ] Use HTTPS for all public endpoints -- [ ] Rotate `SCALEKIT_CLIENT_SECRET` regularly -- [ ] Enable rate limiting on MCP endpoints -- [ ] Log all authentication failures -- [ ] Monitor token validation errors - -### Environment-specific configuration - -**Development**: -```bash -MCP_URL=http://localhost:3002/ -``` - -**Production**: -```bash -MCP_URL=https://mcp.yourapp.com/ -``` - -Update Scalekit dashboard with production URL before deploying. - -## Common issues - -**Token validation fails**: -- Verify `SCALEKIT_RESOURCE_ID` matches dashboard -- Check `MCP_URL` has trailing slash -- Ensure environment variables are loaded (`load_dotenv()`) - -**Discovery endpoint not found**: -- Confirm server is running on correct port -- Verify MCP client uses base URL + `/mcp` path -- Check firewall/network allows connections - -**Scope errors persist**: -- Verify scopes are defined in Scalekit dashboard -- Check scope strings match exactly (case-sensitive) -- Ensure token was issued with required scopes - -**MCP Inspector connection fails**: -- Leave auth fields empty (uses DCR) -- Check browser console for OAuth errors -- Verify server logs show authentication attempt - -## Extending your server - -### Adding new tools with scopes - -```python -@mcp.tool -def new_operation(param: str) -> dict: - """Your tool description.""" - error = _require_scope("your:scope") - if error: - return {"error": error} - - # Your implementation - return {"result": "success"} -``` - -### Multiple scope requirements - -```python -@mcp.tool -def sensitive_operation() -> dict: - """Requires multiple scopes.""" - error = _require_scopes(["data:read", "data:write", "admin:access"]) - if error: - return {"error": error} - - # Your implementation - return {"result": "success"} -``` - -### Optional scope enhancement - -```python -@mcp.tool -def flexible_operation() -> dict: - """Returns different data based on scopes.""" - token: AccessToken = get_access_token() - - # Basic data for all authenticated users - result = {"basic": "data"} - - # Enhanced data if user has admin scope - if "admin:access" in token.scopes: - result["admin"] = "enhanced_data" - - return result -``` - -## Complete Working Example - -The complete FastMCP todo server shown above is available in the Scalekit MCP Auth Demos repository: - -**GitHub Repository:** [scalekit-inc/mcp-auth-demos/tree/main/todo-fastmcp](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/todo-fastmcp) - -This example demonstrates: -- Full CRUD operations with scope-based authorization -- In-memory todo storage for testing -- OAuth 2.1 integration via FastMCP ScalekitProvider -- Production-ready error handling and logging - -## Resources - -- Full example: [GitHub - todo-fastmcp](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/todo-fastmcp) -- [Scalekit MCP Auth Demos](https://github.com/scalekit-inc/mcp-auth-demos/tree/main) -- FastMCP docs: [fastmcp.dev](https://fastmcp.dev) -- Scalekit docs: [docs.scalekit.com/authenticate/mcp/fastmcp-quickstart](https://docs.scalekit.com/authenticate/mcp/fastmcp-quickstart) -- MCP Inspector: `npx @modelcontextprotocol/inspector@latest` diff --git a/plugins/mcp-auth/skills/express-mcp-server/SKILL.md b/plugins/mcp-auth/skills/express-mcp-server/SKILL.md deleted file mode 100644 index 4c7c5c5..0000000 --- a/plugins/mcp-auth/skills/express-mcp-server/SKILL.md +++ /dev/null @@ -1,783 +0,0 @@ -# Express.js MCP OAuth Authentication with Scalekit - -## Overview - -This skill documents the pattern for building production-ready MCP (Model Context Protocol) servers using Express.js, TypeScript, and OAuth 2.1 Bearer token authentication via Scalekit. This approach provides fine-grained control over HTTP request handling, middleware chains, and server behavior for Node.js-based MCP implementations. - -## When to Use This Pattern - -Use this Express.js MCP integration when you need: - -- **Node.js ecosystem**: Leverage existing npm packages, TypeScript tooling, and JavaScript libraries -- **Custom middleware chains**: Implement rate limiting, request logging, or complex authorization logic with Express middleware -- **Existing Express applications**: Add MCP capabilities to established Express.js codebases without rewriting -- **Fine-grained HTTP control**: Manage routing, CORS policies, health checks, and multiple endpoints -- **Production flexibility**: Deploy on serverless platforms (AWS Lambda, Vercel), containers, or traditional Node.js hosts - -**Don't use this pattern** if you prefer Python's ecosystem or if a simpler MCP server setup (without Express) meets your requirements. - -## Core Architecture - -### Token Validation Flow - -``` -MCP Client → Express Server (401 + WWW-Authenticate) -MCP Client → Scalekit (Exchange code for token) -Scalekit → MCP Client (Bearer token) -MCP Client → Express Server (POST /mcp + Bearer token) -Express Middleware → Scalekit SDK (Validate token) -McpServer → Tool Handler → Response -``` - -### Key Components - -1. **Express Middleware**: Custom authentication middleware that intercepts requests and validates Bearer tokens before routing to MCP handlers -2. **Scalekit Node SDK**: TypeScript SDK validates JWT signatures, expiration, issuer, and audience claims -3. **McpServer**: Official MCP SDK server that handles protocol details (JSON-RPC, tool registration) -4. **StreamableHTTPServerTransport**: MCP transport layer that bridges Express HTTP requests to MCP protocol -5. **Zod Schema Validation**: Type-safe input validation for MCP tool parameters -6. **OAuth Resource Metadata Endpoint**: Well-known endpoint (`/.well-known/oauth-protected-resource`) for client discovery - -## Implementation Patterns - -### 1. Environment Configuration - -**Required variables:** -- `SK_ENV_URL`: Scalekit environment URL (issuer) -- `SK_CLIENT_ID` + `SK_CLIENT_SECRET`: SDK authentication credentials -- `EXPECTED_AUDIENCE`: The resource identifier that tokens must target -- `PROTECTED_RESOURCE_METADATA`: Complete OAuth discovery metadata JSON -- `PORT`: Server listening port (must match registered server URL) - -**Security:** -- Never commit `.env` files to version control -- Add `.env` to `.gitignore` immediately -- Use secret managers in production (AWS Secrets Manager, Doppler, HashiCorp Vault) -- Rotate `SK_CLIENT_SECRET` regularly -- Validate `EXPECTED_AUDIENCE` matches your server's public URL exactly (including trailing slash) - -### 2. Scalekit Client Initialization - -```typescript -import { Scalekit } from '@scalekit-sdk/node'; - -const scalekit = new Scalekit( - SK_ENV_URL, - SK_CLIENT_ID, - SK_CLIENT_SECRET -); -``` - -**Best practices:** -- Initialize once at module level for connection pooling -- SDK handles token caching and JWKS key rotation automatically -- All validation methods are async—always use await - -### 3. MCP Server Setup - -```typescript -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { z } from 'zod'; - -const server = new McpServer({ - name: 'Greeting MCP', - version: '1.0.0' -}); - -server.tool( - 'greet_user', - 'Greets the user with a personalized message.', - { - name: z.string().min(1, 'Name is required'), - }, - async ({ name }: { name: string }) => ({ - content: [ - { - type: 'text', - text: `Hi ${name}, welcome to Scalekit!` - } - ] - }) -); -``` - -**Tool registration:** -- First parameter: Tool name (snake_case recommended for consistency) -- Second parameter: Human-readable description for AI discoverability -- Third parameter: Zod schema for input validation -- Fourth parameter: Async handler function - -**Zod validation benefits:** -- Type-safe parameters with TypeScript inference -- Runtime validation prevents malformed inputs -- Automatic error messages for invalid data -- Composable schemas for complex validation rules - -### 4. Express Middleware Authentication - -```typescript -app.use(async (req: Request, res: Response, next: NextFunction) => { - // Exempt public endpoints - if (req.path === '/.well-known/oauth-protected-resource' || req.path === '/health') { - next(); - return; - } - - // Extract Bearer token - const header = req.headers.authorization; - const token = header?.startsWith('Bearer ') - ? header.slice('Bearer '.length).trim() - : undefined; - - if (!token) { - res.status(401) - .set('WWW-Authenticate', WWW_HEADER_VALUE) - .json({ error: 'Missing Bearer token' }); - return; - } - - try { - // Validate with Scalekit SDK - await scalekit.validateToken(token, { - audience: [EXPECTED_AUDIENCE] - }); - next(); - } catch (error) { - res.status(401) - .set('WWW-Authenticate', WWW_HEADER_VALUE) - .json({ error: 'Token validation failed' }); - } -}); -``` - -**Key principles:** -- Use Express `app.use()` for middleware that runs on every request -- Explicitly exempt public endpoints before token extraction -- Return early with `return` after sending 401 responses (prevents "headers already sent" errors) -- Always set `WWW-Authenticate` header on 401 responses -- Use `next()` to pass control to subsequent middleware/routes - -**Common mistake:** Forgetting to `return` after sending a response leads to "Cannot set headers after they are sent" errors. - -### 5. MCP Transport Layer - -```typescript -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; - -app.post('/', async (req: Request, res: Response) => { - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined - }); - await server.connect(transport); - - try { - await transport.handleRequest(req, res, req.body); - } catch (error) { - res.status(500).json({ error: 'MCP transport error' }); - } -}); -``` - -**Transport responsibilities:** -- Converts HTTP requests to MCP JSON-RPC format -- Handles streaming responses for long-running operations -- Manages session state (stateless when `sessionIdGenerator: undefined`) -- Bridges Express request/response objects to MCP protocol - -**Stateless design:** Setting `sessionIdGenerator: undefined` ensures each request is independent—suitable for serverless deployments. - -### 6. Resource Metadata Endpoint - -```typescript -app.get('/.well-known/oauth-protected-resource', (_req: Request, res: Response) => { - if (!PROTECTED_RESOURCE_METADATA) { - res.status(500).json({ error: 'PROTECTED_RESOURCE_METADATA config missing' }); - return; - } - - const metadata = JSON.parse(PROTECTED_RESOURCE_METADATA); - res.type('application/json').send(JSON.stringify(metadata, null, 2)); -}); -``` - -**Purpose:** -- Enables MCP client discovery of authorization requirements -- Clients fetch this when they receive a 401 response with `WWW-Authenticate` header -- Contains authorization server endpoints, supported grant types, and token types -- Must be publicly accessible (no authentication required) - -**Error handling:** Return 500 if metadata is missing to signal misconfiguration (deployment should fail fast). - -### 7. CORS Configuration - -```typescript -import cors from 'cors'; - -app.use(cors({ - origin: true, // Allow all origins - credentials: false // No credentials needed for Bearer tokens -})); -``` - -**Configuration options:** -- `origin: true`: Reflects request origin (development convenience) -- `origin: ['https://app.example.com']`: Whitelist specific origins (production) -- `credentials: false`: Bearer tokens don't require cookies/credentials -- `methods: ['GET', 'POST', 'OPTIONS']`: Limit allowed HTTP methods - -**Production recommendation:** Use explicit origin whitelist instead of `origin: true`. - -## Security Considerations - -### Token Validation Requirements - -- **Always validate issuer**: Prevents tokens from other OAuth servers being accepted -- **Always validate audience**: Ensures token was issued for your specific resource -- **Check expiration**: Scalekit SDK automatically validates `exp` claim -- **Verify signature**: SDK checks JWT signature against Scalekit's public keys (JWKS) - -### Common Vulnerabilities to Avoid - -1. **Skipping audience validation**: Tokens from other Scalekit resources could be used -2. **Custom JWT parsing**: Use SDK validation—don't implement manual `jwt.verify()` -3. **Logging tokens**: Never log Bearer tokens in middleware, error handlers, or debug output -4. **Missing CORS configuration**: Can enable cross-origin attacks or block legitimate clients -5. **Hardcoded secrets**: Use environment variables and secret managers -6. **Not returning after response**: Causes "headers already sent" errors and potential security issues - -### Production Hardening - -- **HTTPS termination**: Run behind reverse proxy (Nginx, Caddy, AWS ALB) with TLS -- **Process management**: Use PM2, systemd, or container orchestration for auto-restart -- **Multiple workers**: Use Node.js cluster module or container scaling -- **Rate limiting**: Implement per-client/token rate limits using `express-rate-limit` -- **Request logging**: Add structured logging middleware (Winston, Pino) without token values -- **Health checks**: Separate health endpoint for load balancers and orchestrators -- **Error monitoring**: Integrate Sentry, Datadog, or similar for production error tracking - -## Testing Strategy - -### Local Testing with MCP Inspector - -```bash -npx @modelcontextprotocol/inspector@latest -``` - -**Testing workflow:** -1. Start your Express server: `npm run dev` -2. Launch MCP Inspector -3. Connect to `http://localhost:3002/` -4. Inspector automatically handles OAuth flow -5. Test each tool with various inputs -6. Verify middleware logs show successful validation - -### Manual Token Testing with cURL - -```bash -# Get token from Scalekit (via OAuth flow or test endpoint) -export TOKEN="" - -# Test authenticated MCP request -curl -X POST http://localhost:3002/ \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "jsonrpc": "2.0", - "id": 1, - "method": "tools/call", - "params": { - "name": "greet_user", - "arguments": { - "name": "Saif" - } - } - }' - -# Test missing token (should return 401) -curl -v -X POST http://localhost:3002/ - -# Test invalid token -curl -X POST http://localhost:3002/ \ - -H "Authorization: Bearer invalid-token" \ - -H "Content-Type: application/json" -``` - -### Integration Tests with Jest/Vitest - -```typescript -import request from 'supertest'; -import { app } from './server'; - -describe('MCP Authentication', () => { - test('returns 401 without token', async () => { - const response = await request(app).post('/'); - expect(response.status).toBe(401); - expect(response.headers['www-authenticate']).toContain('Bearer'); - }); - - test('returns 401 with invalid token', async () => { - const response = await request(app) - .post('/') - .set('Authorization', 'Bearer invalid-token'); - expect(response.status).toBe(401); - }); - - test('health check is public', async () => { - const response = await request(app).get('/health'); - expect(response.status).toBe(200); - expect(response.body).toEqual({ status: 'healthy' }); - }); - - test('metadata endpoint is public', async () => { - const response = await request(app).get('/.well-known/oauth-protected-resource'); - expect(response.status).toBe(200); - expect(response.headers['content-type']).toContain('application/json'); - }); -}); -``` - -**Test dependencies:** -```json -{ - "devDependencies": { - "@types/jest": "^29.5.12", - "jest": "^29.7.0", - "supertest": "^6.3.4", - "ts-jest": "^29.1.2" - } -} -``` - -### Load Testing - -```bash -# Install autocannon for HTTP load testing -npm install -g autocannon - -# Test authenticated endpoint throughput -autocannon -c 10 -d 30 \ - -m POST \ - -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -b '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \ - http://localhost:3002/ -``` - -## Common Pitfalls - -### 1. Mismatched Audience - -**Symptom**: Tokens fail validation with "invalid audience" error -**Cause**: `EXPECTED_AUDIENCE` doesn't match the Server URL registered in Scalekit -**Fix**: Ensure both values are identical including protocol, host, port, and trailing slash - -**Example:** -```typescript -// ❌ Wrong - missing trailing slash -EXPECTED_AUDIENCE=http://localhost:3002 - -// ✅ Correct - matches Scalekit registration -EXPECTED_AUDIENCE=http://localhost:3002/ -``` - -### 2. Headers Already Sent Error - -**Symptom**: `Error: Cannot set headers after they are sent to the client` -**Cause**: Forgetting to `return` after sending a response in middleware -**Fix**: Always `return` immediately after `res.json()` or `res.send()` - -**Example:** -```typescript -// ❌ Wrong - continues to next middleware -if (!token) { - res.status(401).json({ error: 'Missing token' }); -} -next(); // This runs even after sending 401 - -// ✅ Correct - returns after response -if (!token) { - res.status(401).json({ error: 'Missing token' }); - return; // Prevents calling next() -} -``` - -### 3. Middleware Order Issues - -**Symptom**: CORS errors, authentication bypassed, or parsing failures -**Cause**: Middleware execution order matters in Express -**Fix**: Correct order is: CORS → body parsing → authentication → routes - -**Example:** -```typescript -// ✅ Correct order -app.use(cors()); -app.use(express.json()); -app.use(authMiddleware); -app.get('/public', publicRoute); -app.post('/', protectedRoute); -``` - -### 4. Missing Resource Metadata - -**Symptom**: Clients can't discover how to authenticate -**Cause**: `PROTECTED_RESOURCE_METADATA` not set or malformed JSON -**Fix**: Copy exact JSON from Scalekit dashboard, verify with `JSON.parse()` - -**Debugging:** -```bash -# Test metadata endpoint -curl http://localhost:3002/.well-known/oauth-protected-resource - -# Should return valid JSON with authorization_endpoint -``` - -### 5. TypeScript Module Resolution - -**Symptom**: `Cannot find module '@modelcontextprotocol/sdk/server/mcp.js'` -**Cause**: Missing `.js` extension in ES module imports -**Fix**: Always include `.js` extension when importing from MCP SDK - -**Example:** -```typescript -// ❌ Wrong - missing .js -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; - -// ✅ Correct - includes .js -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -``` - -### 6. Token Expiration During Testing - -**Symptom**: Tests pass initially then fail after 1 hour -**Cause**: Access tokens expire (default 3600 seconds) -**Fix**: Implement token refresh before each test run or use shorter test cycles - -## Extension Patterns - -### Adding Scope-Based Authorization - -```typescript -import jwt from 'jsonwebtoken'; - -// Extend Express Request type -declare global { - namespace Express { - interface Request { - tokenPayload?: { - sub: string; - scope: string[]; - }; - } - } -} - -app.use(async (req: Request, res: Response, next: NextFunction) => { - // ... existing token validation ... - - // Decode token to access claims (after validation) - const decoded = jwt.decode(token) as any; - req.tokenPayload = { - sub: decoded.sub, - scope: decoded.scope?.split(' ') || [] - }; - - next(); -}); - -// Tool with scope requirement -server.tool( - 'admin_action', - 'Performs an admin action', - { action: z.string() }, - async ({ action }, { req }) => { - if (!req.tokenPayload?.scope.includes('admin')) { - throw new Error('Requires admin scope'); - } - // ... admin logic ... - } -); -``` - -### Multi-Tenancy Support - -```typescript -app.use(async (req: Request, res: Response, next: NextFunction) => { - // ... validate token ... - - const decoded = jwt.decode(token) as any; - req.orgId = decoded.org_id; - - next(); -}); - -server.tool( - 'get_org_data', - 'Retrieves organization-specific data', - {}, - async (_params, { req }) => { - const orgId = req.orgId; - const data = await fetchDataForOrg(orgId); - - return { - content: [{ type: 'text', text: JSON.stringify(data) }] - }; - } -); -``` - -### Rate Limiting - -```typescript -import rateLimit from 'express-rate-limit'; - -const limiter = rateLimit({ - windowMs: 60 * 1000, // 1 minute - max: 100, // 100 requests per window - message: 'Too many requests, please try again later', - standardHeaders: true, - legacyHeaders: false, - // Rate limit by token subject (user ID) - keyGenerator: (req: Request) => { - const token = req.headers.authorization?.slice('Bearer '.length); - if (!token) return req.ip; - - const decoded = jwt.decode(token) as any; - return decoded.sub || req.ip; - } -}); - -// Apply to MCP endpoint -app.post('/', limiter, async (req: Request, res: Response) => { - // ... MCP transport handling ... -}); -``` - -### Structured Logging - -```typescript -import pino from 'pino'; - -const logger = pino({ - level: process.env.LOG_LEVEL || 'info', - redact: ['req.headers.authorization'], // Never log tokens -}); - -app.use((req: Request, res: Response, next: NextFunction) => { - const start = Date.now(); - - res.on('finish', () => { - logger.info({ - method: req.method, - path: req.path, - status: res.statusCode, - duration: Date.now() - start, - }); - }); - - next(); -}); -``` - -### Error Handling Middleware - -```typescript -app.use((err: Error, req: Request, res: Response, next: NextFunction) => { - logger.error({ err, path: req.path }, 'Unhandled error'); - - res.status(500).json({ - error: process.env.NODE_ENV === 'production' - ? 'Internal server error' - : err.message - }); -}); -``` - -## Dependencies - -### Required Packages - -```json -{ - "dependencies": { - "@modelcontextprotocol/sdk": "^1.13.0", - "@scalekit-sdk/node": "^2.0.1", - "cors": "^2.8.5", - "dotenv": "^16.4.5", - "express": "^5.1.0", - "zod": "^3.25.57" - }, - "devDependencies": { - "@types/cors": "^2.8.19", - "@types/express": "^4.17.21", - "@types/node": "^20.11.19", - "tsx": "^4.7.0", - "typescript": "^5.4.5" - } -} -``` - -**Dependency purposes:** -- `@modelcontextprotocol/sdk`: Official MCP protocol implementation -- `@scalekit-sdk/node`: Scalekit authentication SDK for token validation -- `cors`: Cross-Origin Resource Sharing middleware -- `dotenv`: Environment variable loading from `.env` files -- `express`: Fast, unopinionated web framework -- `zod`: TypeScript-first schema validation -- `tsx`: TypeScript execution for development (faster than ts-node) -- `typescript`: TypeScript compiler - -### Optional Production Dependencies - -```json -{ - "dependencies": { - "express-rate-limit": "^7.1.5", - "helmet": "^7.1.0", - "pino": "^8.17.2", - "pino-http": "^9.0.0" - } -} -``` - -**Production enhancements:** -- `express-rate-limit`: Rate limiting middleware -- `helmet`: Security headers middleware -- `pino`: High-performance JSON logger -- `pino-http`: HTTP request logging middleware - -### Version Pinning Strategy - -- Pin exact versions in production `package.json` (use `"express": "5.1.0"` not `"^5.1.0"`) -- Use `^` for development flexibility -- Run `npm audit` regularly for security vulnerabilities -- Test version upgrades in staging before production -- Use `npm ci` in production for reproducible builds - -## Deployment Patterns - -### Docker - -```dockerfile -FROM node:20-alpine - -WORKDIR /app - -# Copy package files -COPY package*.json ./ - -# Install dependencies -RUN npm ci --only=production - -# Copy source -COPY . . - -# Build TypeScript -RUN npm run build - -# Expose port -EXPOSE 3002 - -# Start server -CMD ["npm", "start"] -``` - -### PM2 Process Manager - -```json -{ - "apps": [{ - "name": "mcp-server", - "script": "dist/server.js", - "instances": 4, - "exec_mode": "cluster", - "env": { - "NODE_ENV": "production" - } - }] -} -``` - -**Start with PM2:** -```bash -npm run build -pm2 start ecosystem.config.json -``` - -### AWS Lambda (Serverless) - -```typescript -import serverless from 'serverless-http'; - -// ... existing Express app setup ... - -export const handler = serverless(app); -``` - -**Note:** Ensure stateless transport configuration for serverless environments. - -### Environment-Specific Configuration - -```typescript -const config = { - development: { - port: 3002, - corsOrigin: true, - logLevel: 'debug' - }, - production: { - port: parseInt(process.env.PORT || '3002'), - corsOrigin: process.env.ALLOWED_ORIGINS?.split(',') || [], - logLevel: 'info' - } -}; - -const env = process.env.NODE_ENV || 'development'; -const appConfig = config[env]; -``` - -## Complete Working Example - -A full production-ready Express.js MCP server is available in the Scalekit MCP Auth Demos repository: - -**GitHub Repository:** [scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-node](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-node) - -This example includes: -- Complete server implementation with modular architecture -- OAuth 2.1 authentication middleware -- Tool registration with Zod validation -- CORS configuration and error handling -- Production-ready logging and monitoring - -### Key Files - -- `src/main.ts` - Main server entry point -- `src/lib/auth.ts` - OAuth discovery endpoint handler -- `src/lib/middleware.ts` - Token validation middleware -- `src/lib/transport.ts` - MCP transport layer setup -- `src/tools/` - Tool implementations - -### Getting Started - -```bash -cd greeting-mcp-node -npm install -npm run build -npm start -``` - -See [README.md](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-node) for complete setup instructions. - -## Related Resources - -- [MCP SDK Documentation](https://github.com/modelcontextprotocol/typescript-sdk) -- [Express.js Guide](https://expressjs.com/en/guide/routing.html) -- [Scalekit Node SDK](https://github.com/scalekit-inc/scalekit-sdk-node) -- [Zod Documentation](https://zod.dev/) -- [OAuth 2.1 Specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1) -- [Scalekit MCP Authentication Docs](https://docs.scalekit.com/guides/mcp/) -- [MCP Protocol Specification](https://spec.modelcontextprotocol.io/) -- [Scalekit MCP Auth Demos](https://github.com/scalekit-inc/mcp-auth-demos/tree/main) - -## Changelog - -- **2026-02-13**: Initial skill documentation based on Express.js MCP quickstart guide diff --git a/plugins/mcp-auth/skills/fastapi-fastmcp/SKILL.md b/plugins/mcp-auth/skills/fastapi-fastmcp/SKILL.md deleted file mode 100644 index 1538e1e..0000000 --- a/plugins/mcp-auth/skills/fastapi-fastmcp/SKILL.md +++ /dev/null @@ -1,411 +0,0 @@ -# FastAPI + FastMCP OAuth Authentication with Scalekit - -## Overview - -This skill documents the pattern for building production-ready MCP (Model Context Protocol) servers using FastAPI and FastMCP with OAuth 2.1 Bearer token authentication via Scalekit. This approach provides fine-grained control over authentication middleware, token validation, and server behavior compared to using FastMCP's built-in OAuth provider. - -## When to Use This Pattern - -Use this FastAPI + FastMCP integration when you need: - -- **Custom middleware requirements**: Implement rate limiting, request logging, or complex authorization logic beyond basic token validation -- **Existing FastAPI applications**: Integrate MCP tools into established FastAPI codebases without rewriting -- **Advanced authorization**: Enforce scope-based access control, multi-tenancy, or custom claims validation -- **Full HTTP control**: Manage CORS policies, health checks, and multiple endpoints alongside MCP tools -- **Production requirements**: Add monitoring, metrics, and deployment-specific configurations - -**Don't use this pattern** if FastMCP's built-in OAuth provider meets your needs—the additional FastAPI layer adds complexity. - -## Core Architecture - -### Token Validation Flow - -``` -MCP Client → FastAPI Server (401 + WWW-Authenticate) -MCP Client → Scalekit (Exchange code for token) -Scalekit → MCP Client (Bearer token) -MCP Client → FastAPI Server (Request + Bearer token) -FastAPI Middleware → Scalekit SDK (Validate token) -FastAPI → MCP Tool Handler → Response -``` - -### Key Components - -1. **FastAPI Middleware**: Custom HTTP middleware that intercepts all requests and validates Bearer tokens before routing to MCP handlers -2. **Scalekit SDK Integration**: Python SDK validates JWT signatures, expiration, issuer, and audience claims -3. **OAuth Resource Metadata Endpoint**: Well-known endpoint (`/.well-known/oauth-protected-resource`) for client discovery -4. **FastMCP Tool Registration**: Standard MCP tool definitions with `@mcp.tool` decorator -5. **WWW-Authenticate Headers**: Standard OAuth 2.1 challenge response directing clients to resource metadata - -## Implementation Patterns - -### 1. Environment Configuration - -**Required variables:** -- `SK_ENV_URL`: Scalekit environment URL (issuer) -- `SK_CLIENT_ID` + `SK_CLIENT_SECRET`: SDK authentication credentials -- `EXPECTED_AUDIENCE`: The resource identifier that tokens must target -- `PROTECTED_RESOURCE_METADATA`: Complete OAuth discovery metadata JSON -- `PORT`: Server listening port (must match registered server URL) - -**Security:** -- Never commit `.env` files to version control -- Use secret managers in production (AWS Secrets Manager, HashiCorp Vault) -- Rotate `SK_CLIENT_SECRET` regularly -- Validate `EXPECTED_AUDIENCE` matches your server's public URL exactly - -### 2. Middleware Authentication Pattern - -```python -@app.middleware("http") -async def auth_middleware(request: Request, call_next): - # Exempt public endpoints - if request.url.path in {"/health", "/.well-known/oauth-protected-resource"}: - return await call_next(request) - - # Extract Bearer token - auth_header = request.headers.get("authorization") - if not auth_header or not auth_header.startswith("Bearer "): - return Response( - '{"error": "Missing Bearer token"}', - status_code=401, - headers={"WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{RESOURCE_METADATA_URL}"'}, - media_type="application/json" - ) - - token = auth_header.split("Bearer ", 1)[1].strip() - - # Validate with Scalekit SDK - options = TokenValidationOptions( - issuer=SK_ENV_URL, - audience=[EXPECTED_AUDIENCE] - ) - - try: - is_valid = scalekit_client.validate_access_token(token, options=options) - if not is_valid: - raise ValueError("Invalid token") - except Exception: - return Response( - '{"error": "Token validation failed"}', - status_code=401, - headers=WWW_HEADER, - media_type="application/json" - ) - - return await call_next(request) -``` - -**Key principles:** -- Validate on every request except explicitly public endpoints -- Return 401 with WWW-Authenticate header for missing/invalid tokens -- Use Scalekit SDK for cryptographic validation—never implement custom JWT validation -- Verify issuer and audience claims to prevent token substitution attacks - -### 3. Resource Metadata Endpoint - -```python -@app.get("/.well-known/oauth-protected-resource") -async def oauth_metadata(): - if not PROTECTED_RESOURCE_METADATA: - return Response( - '{"error": "PROTECTED_RESOURCE_METADATA config missing"}', - status_code=500, - media_type="application/json" - ) - - metadata = json.loads(PROTECTED_RESOURCE_METADATA) - return Response( - json.dumps(metadata, indent=2), - media_type="application/json" - ) -``` - -**Purpose:** -- Enables MCP client discovery of authorization requirements -- Clients fetch this metadata when they receive a 401 response -- Contains authorization server endpoints and supported token types -- Must be publicly accessible (no authentication required) - -### 4. FastMCP Tool Registration - -```python -@mcp.tool( - name="greet_user", - description="Greets the user with a personalized message." -) -async def greet_user(name: str, ctx: Context | None = None) -> dict: - return { - "content": [ - { - "type": "text", - "text": f"Hi {name}, welcome to Scalekit!" - } - ] - } -``` - -**Tool design:** -- Use descriptive names and clear descriptions for AI discoverability -- Accept `ctx: Context | None` to access authenticated user context in future -- Return MCP-compliant response format with `content` array -- Tools automatically inherit authentication from middleware—no per-tool auth needed - -### 5. Application Mounting - -```python -mcp_app = mcp.http_app(path="/") -app = FastAPI(lifespan=mcp_app.lifespan) - -# Add middleware (CORS, auth, etc.) -app.add_middleware(CORSMiddleware, ...) - -# Add custom endpoints -@app.get("/health") -async def health_check(): - return {"status": "healthy"} - -# Mount MCP at root -app.mount("/", mcp_app) -``` - -**Layering order:** -1. Create FastMCP HTTP app -2. Create FastAPI app with shared lifespan -3. Add FastAPI middleware (CORS, auth) -4. Register custom endpoints (health, metadata) -5. Mount FastMCP app last - -## Security Considerations - -### Token Validation -- **Always validate issuer**: Prevents tokens from other OAuth servers being accepted -- **Always validate audience**: Ensures token was issued for your specific resource -- **Check expiration**: Scalekit SDK automatically validates `exp` claim -- **Verify signature**: SDK checks JWT signature against Scalekit's public keys - -### Common Vulnerabilities to Avoid -1. **Skipping audience validation**: Tokens from other Scalekit resources could be used -2. **Custom JWT parsing**: Use SDK validation—don't implement your own -3. **Logging tokens**: Never log Bearer tokens in middleware or error handlers -4. **Missing CORS configuration**: Can enable cross-origin attacks -5. **Hardcoded secrets**: Use environment variables and secret managers - -### Production Hardening -- Run behind reverse proxy (Nginx, Caddy) with HTTPS termination -- Use multiple Uvicorn workers: `uvicorn main:app --workers 4` -- Implement rate limiting per client/token -- Add request/response logging middleware (without tokens) -- Monitor token validation failures for attack patterns -- Set up health check endpoints for load balancers - -## Testing Strategy - -### Local Testing with MCP Inspector -```bash -npx @modelcontextprotocol/inspector@latest -``` - -1. Connect to `http://localhost:3002/` -2. Inspector handles OAuth flow automatically -3. Test each tool with various inputs -4. Verify middleware logs show successful validation - -### Manual Token Testing -```bash -# Get token from Scalekit (via OAuth flow or test endpoint) -export TOKEN="" - -# Test authenticated request -curl -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"method":"tools/call","params":{"name":"greet_user","arguments":{"name":"Saif"}}}' \ - http://localhost:3002/ - -# Test missing token (should return 401) -curl -v http://localhost:3002/ -``` - -### Integration Tests -```python -import pytest -from fastapi.testclient import TestClient -from main import app - -client = TestClient(app) - -def test_missing_token(): - response = client.post("/") - assert response.status_code == 401 - assert "WWW-Authenticate" in response.headers - -def test_invalid_token(): - response = client.post( - "/", - headers={"Authorization": "Bearer invalid-token"} - ) - assert response.status_code == 401 - -def test_health_check(): - response = client.get("/health") - assert response.status_code == 200 - assert response.json() == {"status": "healthy"} -``` - -## Common Pitfalls - -### 1. Mismatched Audience -**Symptom**: Tokens fail validation with "invalid audience" error -**Cause**: `EXPECTED_AUDIENCE` doesn't match the Server URL registered in Scalekit -**Fix**: Ensure both have identical values including trailing slashes - -### 2. Middleware Order Issues -**Symptom**: CORS errors or authentication bypassed -**Cause**: Mounting order affects execution sequence -**Fix**: Add middleware before mounting MCP app; mount MCP last - -### 3. Missing Resource Metadata -**Symptom**: Clients can't discover how to authenticate -**Cause**: `PROTECTED_RESOURCE_METADATA` not set or endpoint not working -**Fix**: Verify metadata JSON is copied correctly from Scalekit dashboard - -### 4. Development vs Production URLs -**Symptom**: Works locally but fails in production -**Cause**: Hardcoded localhost URLs in configuration -**Fix**: Use environment-specific values for `EXPECTED_AUDIENCE` and `RESOURCE_METADATA_URL` - -### 5. Token Expiration During Testing -**Symptom**: Tests pass initially then fail after 1 hour -**Cause**: Access tokens expire (default 3600 seconds) -**Fix**: Refresh tokens before each test run or implement automatic refresh - -## Extension Patterns - -### Adding Scope-Based Authorization -```python -@app.middleware("http") -async def auth_middleware(request: Request, call_next): - # ... existing token validation ... - - # Decode token to access claims (after validation) - import jwt - decoded = jwt.decode(token, options={"verify_signature": False}) - scopes = decoded.get("scope", "").split() - - # Attach to request state - request.state.scopes = scopes - request.state.user_id = decoded.get("sub") - - return await call_next(request) - -@mcp.tool() -async def admin_tool(ctx: Context) -> dict: - # Access request state in tool - if "admin" not in ctx.request_context.state.scopes: - raise PermissionError("Requires admin scope") - # ... tool logic ... -``` - -### Multi-Tenancy Support -```python -@app.middleware("http") -async def auth_middleware(request: Request, call_next): - # ... validate token ... - - decoded = jwt.decode(token, options={"verify_signature": False}) - org_id = decoded.get("org_id") - - request.state.org_id = org_id - return await call_next(request) - -@mcp.tool() -async def get_org_data(ctx: Context) -> dict: - org_id = ctx.request_context.state.org_id - # Fetch data scoped to org_id -``` - -### Rate Limiting -```python -from slowapi import Limiter, _rate_limit_exceeded_handler -from slowapi.util import get_remote_address -from slowapi.errors import RateLimitExceeded - -limiter = Limiter(key_func=get_remote_address) -app.state.limiter = limiter -app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) - -@app.post("/") -@limiter.limit("10/minute") -async def mcp_endpoint(request: Request): - # Rate-limited MCP calls - pass -``` - -## Dependencies - -### Required Python Packages -```txt -mcp>=1.0.0 # MCP protocol implementation -fastapi>=0.104.0 # Web framework -fastmcp>=0.8.0 # FastMCP integration -uvicorn>=0.24.0 # ASGI server -pydantic>=2.5.0 # Data validation -python-dotenv>=1.0.0 # Environment variables -httpx>=0.25.0 # HTTP client -python-jose[cryptography]>=3.3.0 # JWT handling -cryptography>=41.0.0 # Cryptographic operations -scalekit-sdk-python>=2.4.0 # Scalekit authentication -starlette>=0.27.0 # ASGI toolkit -``` - -### Version Pinning Strategy -- Pin exact versions in production `requirements.txt` -- Use `>=` for development flexibility -- Test version upgrades in staging before production -- Monitor security advisories for all dependencies - -## Complete Working Example - -A full production-ready FastAPI + FastMCP server is available in the Scalekit MCP Auth Demos repository: - -**GitHub Repository:** [scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-python](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-python) - -This example includes: -- Complete server implementation with modular architecture -- OAuth 2.1 authentication middleware -- FastMCP tool registration -- CORS configuration and health checks -- Production-ready logging and error handling - -### Key Files - -- `main.py` - Main server entry point with FastAPI app -- `src/config/config.py` - Environment configuration -- `src/lib/auth.py` - OAuth discovery endpoint handler -- `src/lib/middleware.py` - Token validation middleware -- `src/lib/transport.py` - MCP transport layer setup - -### Getting Started - -```bash -cd greeting-mcp-python -python3 -m venv venv -source venv/bin/activate -pip install -r requirements.txt -python main.py -``` - -See [README.md](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-python) for complete setup instructions. - -## Related Resources - -- [FastMCP Documentation](https://github.com/jlowin/fastmcp) -- [FastAPI Middleware Guide](https://fastapi.tiangolo.com/tutorial/middleware/) -- [OAuth 2.1 Specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1) -- [Scalekit MCP Authentication Docs](https://docs.scalekit.com/guides/mcp/) -- [MCP Protocol Specification](https://spec.modelcontextprotocol.io/) -- [Scalekit MCP Auth Demos](https://github.com/scalekit-inc/mcp-auth-demos/tree/main) - -## Changelog - -- **2026-02-13**: Initial skill documentation based on FastAPI + FastMCP quickstart guide diff --git a/plugins/mcp-auth/skills/mcp-auth/SKILL.md b/plugins/mcp-auth/skills/mcp-auth/SKILL.md deleted file mode 100644 index 9e26c9a..0000000 --- a/plugins/mcp-auth/skills/mcp-auth/SKILL.md +++ /dev/null @@ -1,450 +0,0 @@ ---- -name: adding-mcp-oauth -description: Guides users through adding OAuth 2.1 authorization to Model Context Protocol (MCP) servers using Scalekit. Use when setting up MCP servers, implementing authentication for AI hosts like Claude Desktop, Cursor, or VS Code, or when users mention MCP security, OAuth, or Scalekit integration. ---- - -# Adding OAuth 2.1 Authorization to MCP Servers - -Secure your MCP server with production-ready OAuth 2.1 authorization using Scalekit. This enables authenticated access through AI hosts like Claude Desktop, Cursor, and VS Code. - -## Critical Prerequisites - -⚠️ **MCP OAuth requires HTTP-based transport (Streamable HTTP)**: OAuth 2.1 authentication only works when your MCP server is exposed over **HTTP** using the **Streamable HTTP** transport. The standard `StdioServerTransport` (stdin/stdout) does **not** support OAuth flows. - -**Node.js requirement:** -```javascript -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; -``` - -**Python requirement (Streamable HTTP via ASGI app):** - -In Python, the practical equivalent of Node’s `StreamableHTTPServerTransport` is to **create a Streamable HTTP ASGI app** and run it behind an ASGI server (Uvicorn/Hypercorn). The official Python SDK exposes this as `streamable_http_app()` (convenience) or `create_streamable_http_app(...)` (lower-level). - -**Accurate Python snippet (FastMCP + Streamable HTTP):** -```python -from mcp.server.fastmcp import FastMCP - -mcp = FastMCP("My MCP Server") - -@mcp.tool -def ping() -> str: - return "pong" - -# HTTP-based transport required for OAuth-capable deployments -app = mcp.streamable_http_app(path="/mcp") -``` - -**Lower-level equivalent (explicit constructor):** -```python -from mcp.server.fastmcp import FastMCP -from fastmcp.server.http import create_streamable_http_app - -mcp = FastMCP("My MCP Server") -app = create_streamable_http_app(server=mcp, streamable_http_path="/mcp") -``` - -Notes: -- The imports above match the **official** Python MCP SDK on PyPI (`mcp`). See: `https://pypi.org/project/mcp/1.9.1/` -- The result is an **ASGI `app`** you run with an ASGI server (e.g. `uvicorn module:app`)—this is **Streamable HTTP**, not stdio. -- SSE-only transports are not the same as Streamable HTTP; for OAuth with MCP hosts (Claude Desktop/Cursor/VS Code), use Streamable HTTP. See: `https://gofastmcp.com/python-sdk/fastmcp-server-http` - - Example run: `uvicorn your_module:app --host 0.0.0.0 --port 8000` - -If your MCP server currently uses stdio transport, you must migrate to HTTP-based transport before implementing OAuth. See [MCP Transport Documentation](https://spec.modelcontextprotocol.io/specification/architecture/#transports) for migration guidance. - -## Setup workflow - -Copy this checklist and track progress: - -``` -MCP OAuth Setup: -- [ ] Step 1: Install Scalekit SDK -- [ ] Step 2: Register MCP server in Scalekit dashboard -- [ ] Step 3: Implement discovery endpoint -- [ ] Step 4: Add token validation middleware -- [ ] Step 5: (Optional) Add scope-based authorization -- [ ] Step 6: Test with AI hosts -``` - -## Step 1: Install Scalekit SDK - -**Node.js:** -```bash -npm install @scalekit-sdk/node -``` - -**Python:** -```bash -pip install scalekit-sdk-python -``` - -Get credentials from [Scalekit dashboard](https://app.scalekit.com/) after creating an account. - -## Step 2: Register MCP server - -In Scalekit dashboard: -1. Go to **MCP servers** → **Add MCP server** -2. Provide a descriptive **name** (appears on consent page) -3. Enable **dynamic client registration** (allows automatic MCP host registration) -4. Enable **Client ID Metadata Document (CIMD)** (fetches client metadata automatically) -5. Click **Save** - -**Advanced settings** (optional): -- **Server URL**: Your MCP server identifier (e.g., `https://mcp.yourapp.com`) -- **Access token lifetime**: 300-3600 seconds recommended -- **Scopes**: Define permissions like `todo:read`, `todo:write` - -**Important**: Restart your MCP server after toggling DCR or CIMD settings. - -## Step 3: Implement discovery endpoint - -Create `/.well-known/oauth-protected-resource` endpoint. Copy metadata JSON from **Dashboard > MCP Servers > Your server > Metadata JSON**. - -**Node.js (Express):** -```javascript -app.get('/.well-known/oauth-protected-resource', (req, res) => { - res.json({ - "authorization_servers": [ - "https:///resources/" - ], - "bearer_methods_supported": ["header"], - "resource": "https://mcp.yourapp.com", - "resource_documentation": "https://mcp.yourapp.com/docs", - "scopes_supported": ["todo:read", "todo:write"] - }); -}); -``` - -**Python (FastAPI):** -```python -@app.get("/.well-known/oauth-protected-resource") -async def get_oauth_protected_resource(): - return { - "authorization_servers": [ - "https:///resources/" - ], - "bearer_methods_supported": ["header"], - "resource": "https://mcp.yourapp.com", - "resource_documentation": "https://mcp.yourapp.com/docs", - "scopes_supported": ["todo:read", "todo:write"] - } -``` - -Replace placeholders with actual values from Scalekit dashboard. - -## Step 4: Add token validation middleware - -### Initialize Scalekit client - -**Node.js:** -```javascript -import { Scalekit } from '@scalekit-sdk/node'; - -const scalekit = new Scalekit( - process.env.SCALEKIT_ENVIRONMENT_URL, - process.env.SCALEKIT_CLIENT_ID, - process.env.SCALEKIT_CLIENT_SECRET -); - -const RESOURCE_ID = 'https://your-mcp-server.com'; // Or autogenerated ID from dashboard -const METADATA_ENDPOINT = 'https://your-mcp-server.com/.well-known/oauth-protected-resource'; - -export const WWWHeader = { - HeaderKey: 'WWW-Authenticate', - HeaderValue: `Bearer realm="OAuth", resource_metadata="${METADATA_ENDPOINT}"` -}; -``` - -**Python:** -```python -from scalekit import ScalekitClient -import os - -scalekit_client = ScalekitClient( - env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), - client_id=os.getenv("SCALEKIT_CLIENT_ID"), - client_secret=os.getenv("SCALEKIT_CLIENT_SECRET") -) - -RESOURCE_ID = "https://your-mcp-server.com" -METADATA_ENDPOINT = "https://your-mcp-server.com/.well-known/oauth-protected-resource" - -WWW_HEADER = { - "WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{METADATA_ENDPOINT}"' -} -``` - -### Implement authentication middleware - -**Node.js:** -```javascript -export async function authMiddleware(req, res, next) { - try { - // Allow public access to well-known endpoints - if (req.path.includes('.well-known')) { - return next(); - } - - // Extract Bearer token - const authHeader = req.headers['authorization']; - const token = authHeader?.startsWith('Bearer ') - ? authHeader.split('Bearer ')[1]?.trim() - : null; - - if (!token) { - throw new Error('Missing or invalid Bearer token'); - } - - // Validate token against resource audience - await scalekit.validateToken(token, { - audience: [RESOURCE_ID] - }); - - next(); - } catch (err) { - return res - .status(401) - .set(WWWHeader.HeaderKey, WWWHeader.HeaderValue) - .end(); - } -} - -// Apply to all MCP endpoints -app.use('/', authMiddleware); -``` - -**Python:** -```python -from scalekit.common.scalekit import TokenValidationOptions -from fastapi import Request, HTTPException, status - -async def auth_middleware(request: Request, call_next): - # Allow public access to well-known endpoints - if request.url.path.startswith("/.well-known"): - return await call_next(request) - - # Extract Bearer token - auth_header = request.headers.get("Authorization", "") - token = None - if auth_header.startswith("Bearer "): - token = auth_header.split("Bearer ")[1].strip() - - if not token: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - headers=WWW_HEADER - ) - - # Validate token - try: - options = TokenValidationOptions( - issuer=os.getenv("SCALEKIT_ENVIRONMENT_URL"), - audience=[RESOURCE_ID] - ) - scalekit_client.validate_token(token, options=options) - except Exception: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - headers=WWW_HEADER - ) - - return await call_next(request) - -# Apply to all MCP endpoints -app.middleware("http")(auth_middleware) -``` - -## Step 5: Scope-based tool authorization (Optional) - -Add fine-grained access control at the tool execution level: - -**Node.js:** -```javascript -try { - await scalekit.validateToken(token, { - audience: [RESOURCE_ID], - requiredScopes: [scope] // e.g., 'todo:write' - }); -} catch(error) { - return res.status(403).json({ - error: 'insufficient_scope', - error_description: `Required scope: ${scope}`, - scope: scope - }); -} -``` - -**Python:** -```python -try: - scalekit_client.validate_access_token( - token, - options=TokenValidationOptions( - audience=[RESOURCE_ID], - required_scopes=[scope] - ) - ) -except Exception: - return { - "error": "insufficient_scope", - "error_description": f"Required scope: {scope}", - "scope": scope - } -``` - -## Step 6: Verify and deploy - -### Verify your integration - -Before testing with AI hosts, Claude Code will scan your project to determine -the right URL to verify against. It will look for: - -- `RESOURCE_ID` or `resource` values in your code or `.env` -- The host/domain used in `/.well-known/oauth-protected-resource` -- Any deployed base URL in environment config (`SERVER_URL`, `PUBLIC_URL`, etc.) - -If no URL is found, you'll be asked: -> "What is your MCP server base URL? -> (e.g., `https://mcp.yourapp.com` or `https://mcp.yourapp.com/mcp`)" - -Once the URL is known, run these three checks: - -**Check 1 – Confirm 401 without token:** -```bash -curl -i -``` -Expected: `HTTP/1.1 401 Unauthorized` - -**Check 2 – Confirm WWW-Authenticate header:** -The response must include: -``` -WWW-Authenticate: Bearer realm="OAuth", resource_metadata="https:///.well-known/oauth-protected-resource" -``` -This is what triggers the MCP client's OAuth flow. A plain 401 without this header -will cause AI hosts (Claude Desktop, Cursor, VS Code) to fail silently. - -**Check 3 – Confirm metadata endpoint is reachable:** -```bash -curl https:///.well-known/oauth-protected-resource -``` -Expected: JSON with `resource`, `authorization_servers`, and `scopes_supported`. - -### Testing checklist (after verification passes) -- [ ] Test with Claude Desktop -- [ ] Test with Cursor -- [ ] Test with VS Code -- [ ] Verify token validation rejects invalid tokens -- [ ] Verify scope-based authorization (if implemented) - -### Production deployment checklist -- [ ] Configure CORS policies for endpoints -- [ ] Set up monitoring and logging for auth events -- [ ] Use HTTPS for all communications -- [ ] Store credentials in environment variables or secret management -- [ ] Configure appropriate token lifetimes -- [ ] Document authentication flow for users - -## Additional authentication methods - -Beyond OAuth 2.1, enable these methods through Scalekit (no code changes needed): - -**Enterprise SSO**: Organizations authenticate through Okta, Azure AD, Google Workspace -- Requires organization admins to register domains with Scalekit -- Centralized access control through enterprise identity systems - -**Social logins**: Users authenticate via Google, GitHub, Microsoft -- Quick onboarding for individual users -- Reduced friction for personal and small team use - -**Custom auth**: Use your own authentication system -- Integrate existing user management -- Maintain full control over authentication flow - -See Scalekit documentation for configuration details. - -## Framework-specific guides - -For detailed implementation guides with specific frameworks: -- **FastMCP**: 5-line integration with Scalekit provider (simplest approach) -- **Express.js**: Full OAuth implementation with manual middleware (most control) -- **FastAPI + FastMCP**: Python-based implementation with custom middleware - -See [Complete Working Examples](#complete-working-examples) below for production-ready code. - -## Complete Working Examples - -Production-ready examples demonstrating different implementation approaches: - -### FastMCP (5-Line OAuth Integration) -**Skill:** [add-auth-fastmcp](../add-auth-fastmcp/SKILL.md) -- Simplest approach with built-in OAuth provider -- Automatic token validation and scope enforcement -- Complete todo server with CRUD operations -- **GitHub:** [todo-fastmcp](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/todo-fastmcp) - -### Express.js (Full Manual OAuth) -**Skill:** [express-mcp-server](../express-mcp-server/SKILL.md) -- Complete control over authentication middleware -- Modular architecture with transport, tools, auth layers -- Production-ready with CORS, logging, error handling -- **GitHub:** [greeting-mcp-node](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-node) - -### FastAPI + FastMCP (Custom Middleware) -**Skill:** [fastapi-fastmcp](../fastapi-fastmcp/SKILL.md) -- Python-based with custom authentication middleware -- Combines FastAPI's HTTP control with FastMCP's tooling -- Ideal for existing FastAPI applications -- **GitHub:** [greeting-mcp-python](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-python) - -### Scalekit MCP Server (Production Reference) -**Reference:** [scalekit-mcp-server.md](../../references/scalekit-mcp-server.md) -- Official Scalekit production implementation -- Comprehensive tooling for identity management -- Advanced patterns: scope-based auth, pagination, multi-step operations -- Demonstrates best practices for complex MCP servers -- **GitHub:** [scalekit-inc/scalekit-mcp-server](https://github.com/scalekit-inc/scalekit-mcp-server) - -### Choosing an Example - -| Example | Complexity | Best For | Control Level | -|----------|-------------|-----------|---------------| -| FastMCP | Simplest | Quick prototypes, minimal code | Low (automatic) | -| Express.js | Medium | Production Node.js, custom logic | High (manual) | -| FastAPI + FastMCP | Medium | Production Python, existing apps | High (manual) | -| Scalekit Server | Advanced | Complex apps, reference patterns | Very High | - -All examples include: -- Complete source code -- Setup instructions -- Environment configuration -- Testing guidance - -## Common issues - -**Token validation fails**: -- Verify RESOURCE_ID matches Server URL in dashboard -- Check environment variables are set correctly -- Ensure token hasn't expired - -**Discovery endpoint not found**: -- Verify endpoint path is exactly `/.well-known/oauth-protected-resource` -- Check endpoint is publicly accessible (not protected by auth middleware) - -**Scope validation errors**: -- Verify scopes in dashboard match those in code -- Check token includes required scopes -- Ensure scope strings match exactly (case-sensitive) - -## Architecture summary - -**Scalekit OAuth server**: -- Authenticates users and agents -- Issues access tokens with scopes -- Manages OAuth 2.1 flows -- Supports dynamic client registration - -**Your MCP server**: -- Validates incoming tokens -- Enforces permissions from token scopes -- Executes tool calls for authorized requests - -This separation ensures clean boundaries: Scalekit handles identity and token issuance, your server focuses on business logic. diff --git a/plugins/mcp-auth/skills/production-readiness-scalekit/SKILL.md b/plugins/mcp-auth/skills/production-readiness-scalekit/SKILL.md deleted file mode 100644 index c3e4583..0000000 --- a/plugins/mcp-auth/skills/production-readiness-scalekit/SKILL.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -name: production-readiness-scalekit -description: Walks through a structured production readiness checklist for Scalekit MCP authentication implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, or wants to verify their MCP server authentication is production-ready. ---- - -# Scalekit MCP Auth Production Readiness - -Work through each section in order — earlier sections are blockers for later ones. - ---- - -## Quick checks (run first) - -- [ ] Production environment URL, client ID, and client secret are set (not dev/staging values) -- [ ] HTTPS enforced on all auth endpoints -- [ ] CORS restricted to your domains only -- [ ] API credentials stored in environment variables — never committed to code - ---- - -## Core auth flows - -- [ ] Test login initiation with authorization URL -- [ ] Validate redirect URLs match dashboard configuration exactly -- [ ] Test authentication completion and code exchange -- [ ] Validate `state` parameter in callbacks (CSRF protection) -- [ ] Verify session token storage uses `httpOnly`, `secure`, and `sameSite` flags -- [ ] Configure token lifetimes for your security requirements -- [ ] Test session timeout and automatic token refresh -- [ ] Verify logout clears sessions completely -- [ ] Expired tokens handled gracefully -- [ ] Network failures show user-friendly messages - ---- - -## MCP authentication - -- [ ] Test MCP server authentication flow end-to-end -- [ ] Verify OAuth consent screen displays correctly for MCP clients -- [ ] Test token exchange for MCP connections -- [ ] Verify resource metadata published at `/.well-known/oauth-protected-resource` -- [ ] Test MCP session management (session creation, expiry, refresh) -- [ ] Verify custom auth handlers behave correctly (if using) -- [ ] Test MCP client reconnection after token expiry -- [ ] Verify scopes are correctly enforced per MCP tool/resource - ---- - -## Monitoring and incident readiness - -- [ ] Auth logs monitoring configured in **Dashboard > Auth Logs** -- [ ] Alerts set for suspicious activity (repeated auth failures, unusual access patterns) -- [ ] Error tracking configured for authentication failures -- [ ] Log retention policies configured -- [ ] Incident response runbook written (who to contact, how to roll back) -- [ ] Rollback plan ready (disable MCP auth without breaking existing sessions) - -**Key metrics:** -- MCP auth success/failure rates -- Token exchange latency -- Session creation and duration -- Token refresh frequency diff --git a/plugins/modular-scim/.claude-plugin/plugin.json b/plugins/modular-scim/.claude-plugin/plugin.json deleted file mode 100644 index 533d296..0000000 --- a/plugins/modular-scim/.claude-plugin/plugin.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "modular-scim", - "description": "SCIM webhook provisioning with Scalekit for real-time user and group lifecycle management.", - "version": "1.2.1", - "author": { - "name": "Scalekit", - "email": "hi@scalekit.com" - }, - "homepage": "https://docs.scalekit.com/dev-kit/build-with-ai/scim/", - "repository": "https://github.com/scalekit-inc/claude-code-authstack", - "license": "MIT", - "keywords": ["scim", "provisioning", "webhooks", "users", "groups"] -} diff --git a/plugins/modular-scim/.mcp.json b/plugins/modular-scim/.mcp.json deleted file mode 100644 index 783b9a1..0000000 --- a/plugins/modular-scim/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "scalekit": { - "type": "http", - "url": "https://mcp.scalekit.com" - } - } -} diff --git a/plugins/modular-scim/agents/setup-scalekit.md b/plugins/modular-scim/agents/setup-scalekit.md deleted file mode 100644 index 044e411..0000000 --- a/plugins/modular-scim/agents/setup-scalekit.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: scalekit-setup -description: Sets up Scalekit env vars, installs/initializes the SDK, and verifies credentials by listing organizations. Use proactively when user asks to set up, install, initialize, configure, or verify Scalekit. -model: sonnet -tools: Read, Grep, Glob, Bash, Write, Edit -maxTurns: 20 ---- - -You are a Scalekit setup and verification specialist. - -Mission: -- Help the user configure Scalekit credentials via environment variables. -- Help them install and initialize the official Scalekit SDK for their language. -- Verify the setup with the smallest reliable check (prefer listing organizations). -- Keep secrets out of chat and out of the repo. - -Hard rules: -- NEVER ask the user to paste SCALEKIT_CLIENT_SECRET into chat. -- NEVER hardcode credentials in code samples; always use environment variables. -- Prefer creating a local verification script (verify.js / verify.py / verify.go / Verify.java) and running it, but only if the user wants you to write files. - -Workflow: -1) Determine language/runtime (Node.js, Python, Go, Java) and where env vars should live (.env, shell, CI secrets). -2) Confirm required env vars exist: - - SCALEKIT_ENVIRONMENT_URL - - SCALEKIT_CLIENT_ID - - SCALEKIT_CLIENT_SECRET -3) Install the SDK (pick the official package for that language). -4) Initialize the SDK client using env vars. -5) Verify credentials by listing organizations with a small page size. -6) If verification fails, diagnose systematically: - - Wrong environment URL (dev vs prod) - - Missing env vars in current shell/process - - Incorrect client id/secret - - Network/DNS issues -7) Only after verification succeeds, proceed to feature work and route to the correct Skill: - - SSO → plugins/modular-sso/skills/modular-sso/SKILL.md - - SCIM → plugins/modular-scim/skills/modular-scim/SKILL.md - - MCP auth → plugins/mcp-auth/skills/*/SKILL.md - - Full-stack auth → plugins/full-stack-auth/skills/full-stack-auth/SKILL.md - -When you reference files, use exact repo-relative paths and read them before advising. diff --git a/plugins/modular-scim/hooks/beacon.sh b/plugins/modular-scim/hooks/beacon.sh deleted file mode 100755 index 98506da..0000000 --- a/plugins/modular-scim/hooks/beacon.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -PLUGIN="${1:-unknown}" -HOOK="${2:-stop}" -INPUT=$(cat) - -SESSION_ID=$(echo "$INPUT" | python3 -c \ - "import sys,json; print(json.load(sys.stdin).get('session_id','anonymous'))" \ - 2>/dev/null || echo "anonymous") - -TOOL_NAME=$(echo "$INPUT" | python3 -c \ - "import sys,json; print(json.load(sys.stdin).get('tool_name',''))" \ - 2>/dev/null || echo "") - -curl -s -o /dev/null --max-time 5 \ - -X POST https://ph.scalekit.com/i/v0/e/ \ - -H "Content-Type: application/json" \ - -d "{\"token\":\"phc_85pLP8gwYvRCQdxgLQP24iqXHPRGaLgEw4S4dgZHJZ\",\ -\"event\":\"plugin_used\",\ -\"distinct_id\":\"${SESSION_ID}\",\ -\"properties\":{\"plugin\":\"${PLUGIN}\",\"coding_agent\":\"claude_code\",\"hook\":\"${HOOK}\",\"tool_name\":\"${TOOL_NAME}\"}}" diff --git a/plugins/modular-scim/hooks/hooks.json b/plugins/modular-scim/hooks/hooks.json deleted file mode 100644 index db70a7d..0000000 --- a/plugins/modular-scim/hooks/hooks.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "description": "Usage beacon for Scalekit modular-scim plugin", - "hooks": { - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh modular-scim stop", - "timeout": 10 - } - ] - } - ] - } -} diff --git a/plugins/modular-scim/references/redirects.md b/plugins/modular-scim/references/redirects.md deleted file mode 100644 index dbe1977..0000000 --- a/plugins/modular-scim/references/redirects.md +++ /dev/null @@ -1,76 +0,0 @@ -# Redirects - -Redirects are registered endpoints in Scalekit that control where users are directed during authentication flows. You must configure these endpoints in the Scalekit dashboard before they can be used. - -All redirect URIs must be registered under Authentication settings in your Scalekit dashboard. This is a security requirement to prevent unauthorized redirects. - - -## Redirect endpoint types - -### Allowed callback URLs -**Purpose**: Where users are sent after successful authentication to exchange authorization codes and retrieve profile information. - -**Example scenario**: A user completes sign-in and Scalekit redirects them to `https://yourapp.com/callback` where your application processes the authentication response. - -To add or remove an redirect URL, go to Dashboard > Authentication > Redirects > Allowed Callback URLs. - -### Initiate login URL -**Purpose**: When authentication does not initiate from your application, Scalekit redirects users back to your application's login initiation endpoint. This endpoint should point to a route in your application that ultimately redirects users to Scalekit's `/authorize` endpoint. - -**Example scenarios**: - -- **Bookmarked login page**: A user bookmarks your login page and visits it directly. Your application detects they're not authenticated and redirects them to Scalekit's authorization endpoint. - -- **Organization invitation flow**: A user clicks an invitation link to join an organization. Your application receives the invitation token and redirects the user to Scalekit's authorization endpoint to complete the sign-up process. - -- **IdP-initiated SSO**: An administrator initiates single sign-on from their identity provider dashboard. The IdP redirects users to your application, which then redirects them to Scalekit's authorization endpoint to complete authentication. - -- **Session expiration**: When a user's session expires or they access a protected resource, they're redirected to `https://yourapp.com/login` which then redirects to Scalekit's authentication endpoint. - -### Post logout URL -**Purpose**: Where users are sent after successfully signing out of your application. - -**Example scenario**: After logging out, users are redirected to `https://yourapp.com/goodbye` to confirm their session has ended. - -### Back channel logout URL -**Purpose**: A secure endpoint that receives notifications whenever a user is logged out from Scalekit, regardless of how the logout was initiated — admin triggered, user initiated, or due to session policies like idle timeout. - -**Example scenario**: When a user logs out from any application (user-initiated, admin-initiated, or due to session policies like idle timeout), Scalekit sends a logout notification to `https://yourapp.com/logout` to suggest termination of the user's session across all connected applications, ensuring coordinated logout for enhanced security. - -### Custom URI schemes - -Custom URI schemes allow for redirects, enabling deep linking and native app integrations. Some applications include: -- **Desktop applications**: Use schemes like `{scheme}://` for native app integration -- **Mobile apps**: Use schemes like `myapp://` for mobile app deep linking - -**Example custom schemes**: -- `{scheme}://auth/callback` - For custom scheme authentication -- `myapp://login/callback` - For mobile app authentication - - -## URI validation requirements - -Your redirect URIs must meet specific requirements that vary between development and production environments: - -| Requirement | Development | Production | -| ----------- | ----------- | ---------- | -| Supported schemes | `http`, `https`, `{scheme}` | `https`, `{scheme}` | -| Localhost support | Allowed | Not allowed | -| Wildcard domains | Allowed | Not allowed | -| URI length limit | 256 characters | 256 characters | -| Query parameters | Not allowed | Not allowed | -| URL fragments | Not allowed | Not allowed | - - -### Wildcard usage patterns - -Wildcards can simplify testing in development environments, but they must follow specific patterns: - -| Validation rule | Valid examples | Invalid examples | -| --------------- | -------------- | ---------------- | -| Wildcards cannot be used as root-level domains | `https://*.acmecorp.com`, `https://auth-*.acmecorp.com` | `https://*.com` | -| Only one wildcard character is allowed per URI | `https://*.acmecorp.com` | `https://*.*.acmecorp.com` | -| Wildcards must be in the hostname component only | `https://*.acmecorp.com` | `https://acmecorp.*.com` | -| Wildcards must be in the outermost subdomain | `https://*.auth.acmecorp.com` | `https://auth.*.acmecorp.com` | - -> **Note**: According to the [OAuth 2.0 specification](https://tools.ietf.org/html/rfc6749#section-3.1.2), redirect URIs must be absolute URIs. For development convenience, Scalekit relaxes this restriction slightly by allowing wildcards in development environments. diff --git a/plugins/modular-scim/skills/implementing-admin-portal/SKILL.md b/plugins/modular-scim/skills/implementing-admin-portal/SKILL.md deleted file mode 100644 index b1d4146..0000000 --- a/plugins/modular-scim/skills/implementing-admin-portal/SKILL.md +++ /dev/null @@ -1,155 +0,0 @@ ---- -name: implementing-admin-portal -description: Implements Scalekit's admin portal for customer self-serve SSO and SCIM configuration. Generates portal links server-side and embeds the portal as an iframe in the app's settings UI. Use when the user asks to add an admin portal, customer self-serve SCIM setup, iframe embed for directory sync config, shareable setup link, or let customers configure their own SSO or SCIM connection. ---- - -# Admin Portal with Scalekit - -Adds a self-serve portal where customers configure their own SSO and SCIM settings — embedded inside your app's settings UI. - -If the user only needs a quick shareable link with no code (e.g., for a one-time onboarding call), skip to the **Shareable link** section at the bottom. - ---- - -## Implementation progress - -``` -Admin Portal Implementation Progress: -- [ ] Step 1: Install SDK -- [ ] Step 2: Set environment credentials -- [ ] Step 3: Register app domain in dashboard -- [ ] Step 4: Generate portal link (server-side) -- [ ] Step 5: Render iframe (client-side) -- [ ] Step 6: Handle session expiry events -- [ ] Step 7: Verify portal loads and events fire correctly -``` - ---- - -## Step 1: Install SDK - -Detect the project's language/framework from existing files and install: - -| Stack | Install | -|---------|---------| -| Node.js | `npm install @scalekit-sdk/node` | -| Python | `pip install scalekit-sdk-python` | -| Go | `go get github.com/scalekit-inc/scalekit-sdk-go/v2` | -| Java | Add `com.scalekit:scalekit-sdk` to `pom.xml` | - ---- - -## Step 2: Set environment credentials - -Add to `.env` (never hardcode): - -```shell -SCALEKIT_ENVIRONMENT_URL='https://.scalekit.com' -SCALEKIT_CLIENT_ID='' -SCALEKIT_CLIENT_SECRET='' -``` - -Credentials are in **Dashboard > Developers > Settings > API Credentials**. - ---- - -## Step 3: Register app domain - -In **Dashboard > Developers > API Configuration > Redirect URIs**, add the domain where the portal will be embedded. The iframe will be blocked if this is missing. - ---- - -## Step 4: Generate the portal link (server-side) - -Generate a new link on every page load — links are single-use. Plug into the existing route or controller that serves the settings/admin page: - -**Node.js:** -```javascript -const { location } = await scalekit.organization.generatePortalLink(organizationId); -// Pass `location` to the frontend as a template variable or API response -``` - -**Python:** -```python -portal = scalekit_client.organization.generate_portal_link(organization_id) -location = portal.location -# Pass `location` to your template or JSON response -``` - -**Never cache this value** — each link is single-use and will fail if reused. - ---- - -## Step 5: Render the iframe (client-side) - -In the frontend settings/admin template, inject `location` as the `src`: - -```html - -``` - -Minimum recommended height: **600px**. Match the variable name to the project's existing templating convention. - ---- - -## Step 6: Handle portal UI events - -Listen for messages from the iframe to react to configuration changes and session expiry: - -```javascript -window.addEventListener('message', (event) => { - if (event.origin !== process.env.SCALEKIT_ENVIRONMENT_URL) return; - - const { type } = event.data; - switch (type) { - case 'SCIM_CONFIGURED': - // Refresh org SCIM status, show success banner, etc. - break; - case 'SSO_CONFIGURED': - // Refresh org SSO status if SSO is also in scope - break; - case 'SESSION_EXPIRED': - // Re-fetch a new portal link and reload the iframe src - reloadPortalIframe(); - break; - } -}); -``` - -`SESSION_EXPIRED` handling is required — without it the portal silently breaks for long-lived sessions. - ---- - -## Step 7: Verify - -- [ ] Open the settings page — confirm the iframe renders without console errors -- [ ] Complete a test SCIM configuration inside the portal — confirm `SCIM_CONFIGURED` fires -- [ ] Wait for session expiry (or simulate it) — confirm `SESSION_EXPIRED` triggers a link refresh -- [ ] Confirm portal link is never the same across two page loads (single-use verification) - ---- - -## Branding (optional) - -Configure at **Dashboard > Settings > Branding**: logo, accent color, favicon. Custom domain support (e.g., `scim.yourapp.com`) is available in the Scalekit dashboard. - ---- - -## Guardrails - -- **Generate link server-side only** — never expose `CLIENT_SECRET` to the browser -- **Re-generate on every page load** — caching will break the portal -- **Register your domain** in Redirect URIs before testing or the iframe will be blocked -- **Handle `SESSION_EXPIRED`** — re-generate and reload, don't let it fail silently - ---- - -## Shareable link (no-code alternative) - -For one-time onboarding calls or zero-engineering setup: go to **Dashboard > Organizations**, select the org, click **Generate link**, and share the URL directly. The link gives anyone who has it full access to configure that org's SSO/SCIM settings — use the iframe approach for production. Also share Scalekit's [SCIM setup guides](https://docs.scalekit.com/guides/integrations/scim-integrations/) so the IT admin has provider-specific directory sync steps alongside the portal link. diff --git a/plugins/modular-scim/skills/modular-scim/SKILL.md b/plugins/modular-scim/skills/modular-scim/SKILL.md deleted file mode 100644 index 5b1a6c2..0000000 --- a/plugins/modular-scim/skills/modular-scim/SKILL.md +++ /dev/null @@ -1,230 +0,0 @@ ---- -name: implementing-scim-provisioning -description: Implements SCIM user provisioning using Scalekit's Directory API and webhooks. Use when the user asks to add SCIM, directory sync, user provisioning, deprovisioning, or lifecycle management to their existing application. ---- - -# SCIM Provisioning with Scalekit - -Adds automated user lifecycle management (create, update, deactivate) via Scalekit's Directory API and real-time webhooks. - -## Workflow - -Copy and track progress: - -``` -SCIM Implementation Progress: -- [ ] Step 1: Detect stack and install SDK -- [ ] Step 2: Configure environment credentials -- [ ] Step 3: Initialize Scalekit client -- [ ] Step 4: Add Directory API sync (polling/on-demand) -- [ ] Step 5: Add webhook endpoint (real-time) -- [ ] Step 6: Register webhook in Scalekit dashboard -- [ ] Step 7: Map directory events to local user operations -- [ ] Step 8: Validate end-to-end -``` - ---- - -## Step 1: Detect stack and install SDK - -Detect the project's language/framework from existing files (`package.json`, `requirements.txt`, `go.mod`, `pom.xml`) and install accordingly: - -| Stack | Install command | -|-------|----------------| -| Node.js | `npm install @scalekit-sdk/node` | -| Python | `pip install scalekit-sdk-python` | -| Go | `go get github.com/scalekit-inc/scalekit-sdk-go/v2` | -| Java | Add `com.scalekit:scalekit-sdk` to `pom.xml` or `build.gradle` | - ---- - -## Step 2: Environment credentials - -Add to `.env` (never hardcode): - -```shell -SCALEKIT_ENVIRONMENT_URL='https://.scalekit.com' -SCALEKIT_CLIENT_ID='' -SCALEKIT_CLIENT_SECRET='' -SCALEKIT_WEBHOOK_SECRET='' -``` - -Credentials are found in **Dashboard > Developers > Settings > API Credentials**. -Webhook secret is found in **Dashboard > Webhooks** after registering an endpoint. - ---- - -## Step 3: Initialize the Scalekit client - -Insert initialization near the app's startup or service layer — match the project's existing patterns (singleton, DI, module export, etc.). - -**Node.js:** -```javascript -import { ScalekitClient } from '@scalekit-sdk/node'; -const scalekit = new ScalekitClient( - process.env.SCALEKIT_ENVIRONMENT_URL, - process.env.SCALEKIT_CLIENT_ID, - process.env.SCALEKIT_CLIENT_SECRET -); -``` - -**Python:** -```python -from scalekit import ScalekitClient -scalekit_client = ScalekitClient( - env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), - client_id=os.getenv("SCALEKIT_CLIENT_ID"), - client_secret=os.getenv("SCALEKIT_CLIENT_SECRET") -) -``` - -For Go and Java patterns, follow the language-specific SDK docs in [Scalekit API references](https://docs.scalekit.com/apis/). - ---- - -## Step 4: Directory API (on-demand sync) - -Use for scheduled jobs, onboarding flows, or bulk imports. Integrate into existing user service/repository layer — do not create a parallel user management path. - -**Fetch users and sync:** - -```javascript -// Node.js -const { directory } = await scalekit.directory.getPrimaryDirectoryByOrganizationId(orgId); -const { users } = await scalekit.directory.listDirectoryUsers(orgId, directory.id); - -for (const user of users) { - await upsertUser({ email: user.email, name: user.name, orgId }); -} -``` - -```python -# Python -directory = scalekit_client.directory.get_primary_directory_by_organization_id(org_id) -users = scalekit_client.directory.list_directory_users(org_id, directory.id) - -for user in users: - upsert_user(email=user.email, name=user.name, org_id=org_id) -``` - -**Group sync for RBAC:** - -```javascript -const { groups } = await scalekit.directory.listDirectoryGroups(orgId, directory.id); -for (const group of groups) { - await syncGroupPermissions(group.id, group.name); -} -``` - -Plug `upsertUser` / `syncGroupPermissions` into the project's **existing** user/role management functions — identify them by searching for `createUser`, `updateUser`, or equivalent patterns in the codebase. - ---- - -## Step 5: Webhook endpoint (real-time provisioning) - -Add a new route to the existing HTTP server/router. Match the framework pattern already in use (Express, FastAPI, Spring Boot, net/http, etc.). - -**ALWAYS verify the signature before processing. Return 400 on failure.** - -**Node.js (Express):** -```javascript -app.post('/webhooks/scalekit', async (req, res) => { - try { - await scalekit.verifyWebhookPayload( - process.env.SCALEKIT_WEBHOOK_SECRET, - req.headers, - req.body - ); - } catch { - return res.status(400).json({ error: 'Invalid signature' }); - } - - const { type, data } = req.body; - try { - await handleDirectoryEvent(type, data); - res.status(201).json({ status: 'processed' }); - } catch (err) { - res.status(500).json({ error: 'Processing failed' }); - } -}); -``` - -**Python (FastAPI):** -```python -@app.post("/webhooks/scalekit") -async def scalekit_webhook(request: Request): - body = await request.json() - valid = scalekit_client.verify_webhook_payload( - secret=os.getenv("SCALEKIT_WEBHOOK_SECRET"), - headers=request.headers, - payload=json.dumps(body).encode() - ) - if not valid: - raise HTTPException(status_code=400, detail="Invalid signature") - - await handle_directory_event(body.get("type"), body.get("data", {})) - return JSONResponse(status_code=201, content={"status": "processed"}) -``` - -For Go and Java webhook patterns, follow the language-specific SDK docs in [Scalekit API references](https://docs.scalekit.com/apis/). - ---- - -## Step 6: Event handler - -Create a single dispatcher that routes to existing user operations. Map events to the project's **existing** create/update/deactivate functions: - -```javascript -async function handleDirectoryEvent(type, data) { - switch (type) { - case 'organization.directory.user_created': - return createUser(data.email, data.name, data.organization_id); - case 'organization.directory.user_updated': - return updateUser(data.email, data.name); - case 'organization.directory.user_deleted': - return deactivateUser(data.email); // prefer deactivate over hard delete - case 'organization.directory.group_created': - case 'organization.directory.group_updated': - return syncGroup(data); - default: - console.log(`Unhandled event: ${type}`); - } -} -``` - -**Prefer deactivation over deletion** for `user_deleted` events unless the project explicitly hard-deletes users. - ---- - -## Step 7: Dashboard registration checklist - -After deploying the webhook endpoint: - -1. Go to **Dashboard > Webhooks > +Add Endpoint** -2. Enter the public HTTPS URL: `https://your-app.com/webhooks/scalekit` -3. Subscribe to events: - - `organization.directory.user_created` - - `organization.directory.user_updated` - - `organization.directory.user_deleted` - - `organization.directory.group_created` - - `organization.directory.group_updated` -4. Copy the webhook secret into `SCALEKIT_WEBHOOK_SECRET` -5. Share the [SCIM setup guide](https://docs.scalekit.com/guides/integrations/scim-integrations/) with the customer's IT admin for their IdP-specific directory sync steps. - ---- - -## Guardrails - -- **Never hardcode credentials** — always `process.env` / `os.getenv` / `System.getenv` -- **Idempotent operations** — `upsertUser` must handle duplicate events safely -- **Return 2xx quickly** — offload heavy processing to a queue if needed; Scalekit retries on non-2xx with exponential backoff (up to 8 attempts over ~10 hours) -- **Validate signatures** — every webhook request, every time -- **Deactivate, don't delete** — unless codebase explicitly hard-deletes users - ---- - -## Reference files - -- SCIM implementation guide → [Directory SCIM quickstart](https://docs.scalekit.com/directory/scim/quickstart/) -- Dashboard onboarding guide → [SCIM integration guides](https://docs.scalekit.com/guides/integrations/scim-integrations/) -- Redirect and callback reference → [redirects.md](../../references/redirects.md) diff --git a/plugins/modular-scim/skills/production-readiness-scalekit/SKILL.md b/plugins/modular-scim/skills/production-readiness-scalekit/SKILL.md deleted file mode 100644 index dca5b5f..0000000 --- a/plugins/modular-scim/skills/production-readiness-scalekit/SKILL.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -name: production-readiness-scalekit -description: 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. ---- - -# Scalekit SCIM Production Readiness - -Work through each section in order — earlier sections are blockers for later ones. - ---- - -## Quick checks (run first) - -- [ ] Production environment URL, client ID, and client secret are set (not dev/staging values) -- [ ] HTTPS enforced on all endpoints -- [ ] API credentials stored in environment variables — never committed to code -- [ ] Webhook secret stored in environment variables — never committed to code - ---- - -## SCIM provisioning - -- [ ] Configure webhook endpoints to receive SCIM events - → IT admin setup guides per IdP: https://docs.scalekit.com/guides/integrations/scim-integrations/ -- [ ] Verify webhook security with signature validation on every request -- [ ] Test user provisioning (automatic creation from IdP) -- [ ] Test user deprovisioning (deactivation/deletion when removed in IdP) -- [ ] Test user profile updates (name, email, attributes synced correctly) -- [ ] Test role changes propagated via group membership -- [ ] Set up group-based role assignment and sync -- [ ] Test error cases: duplicate users, invalid data, missing required fields -- [ ] Verify idempotent handling — duplicate events must not create duplicate records -- [ ] Deactivation preferred over hard deletion for `user_deleted` events - -**Webhook reliability:** -- [ ] Webhook endpoint returns 2xx quickly — offload heavy processing to a queue if needed -- [ ] Scalekit retries on non-2xx with exponential backoff (up to 8 attempts over ~10 hours) -- [ ] Tested webhook delivery end-to-end with a real IdP or Scalekit's test tool - ---- - -## User and organization management - -- [ ] Test organization creation and domain assignment -- [ ] Test adding and removing users from organizations -- [ ] Set allowed email domains for org provisioning (if applicable) -- [ ] Set default roles for auto-provisioned users -- [ ] Test user deletion flow - -**RBAC (if implemented):** -- [ ] Define roles and permissions that map to IdP groups -- [ ] Test role assignment via group membership sync -- [ ] Verify permission enforcement at API endpoints -- [ ] Test access control across all role levels - ---- - -## Network and firewall - -Enterprise customers behind VPN or corporate firewall must whitelist: - -| Domain | Purpose | -|---|---| -| `.scalekit.com` | Directory API + webhook delivery | -| `cdn.scalekit.com` | Static assets | - -- [ ] Customer firewalls allow Scalekit domains -- [ ] SCIM provisioning tested from customer's network environment - ---- - -## Monitoring and incident readiness - -- [ ] Webhook event monitoring and logging active -- [ ] Error tracking configured for provisioning failures -- [ ] Alerts configured for failed webhook deliveries -- [ ] Log retention policies configured -- [ ] Webhook delivery and retry mechanism tested -- [ ] Incident response runbook written (who to contact, how to roll back) -- [ ] Rollback plan ready (disable SCIM sync without breaking existing users) - -**Key metrics:** -- Webhook delivery success rate -- User provisioning/deprovisioning latency -- Failed sync events (by type and error) -- Group-to-role mapping accuracy diff --git a/plugins/modular-sso/.claude-plugin/plugin.json b/plugins/modular-sso/.claude-plugin/plugin.json deleted file mode 100644 index fd330f2..0000000 --- a/plugins/modular-sso/.claude-plugin/plugin.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "modular-sso", - "description": "Modular SSO flows using Scalekit for apps with existing user management, including IdP-initiated login and enterprise onboarding.", - "version": "1.2.1", - "author": { - "name": "Scalekit", - "email": "hi@scalekit.com" - }, - "homepage": "https://docs.scalekit.com/dev-kit/build-with-ai/sso/", - "repository": "https://github.com/scalekit-inc/claude-code-authstack", - "license": "MIT", - "keywords": ["sso", "saml", "oidc", "authentication", "enterprise"] -} diff --git a/plugins/modular-sso/.mcp.json b/plugins/modular-sso/.mcp.json deleted file mode 100644 index 783b9a1..0000000 --- a/plugins/modular-sso/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "scalekit": { - "type": "http", - "url": "https://mcp.scalekit.com" - } - } -} diff --git a/plugins/modular-sso/agents/setup-scalekit.md b/plugins/modular-sso/agents/setup-scalekit.md deleted file mode 100644 index 044e411..0000000 --- a/plugins/modular-sso/agents/setup-scalekit.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: scalekit-setup -description: Sets up Scalekit env vars, installs/initializes the SDK, and verifies credentials by listing organizations. Use proactively when user asks to set up, install, initialize, configure, or verify Scalekit. -model: sonnet -tools: Read, Grep, Glob, Bash, Write, Edit -maxTurns: 20 ---- - -You are a Scalekit setup and verification specialist. - -Mission: -- Help the user configure Scalekit credentials via environment variables. -- Help them install and initialize the official Scalekit SDK for their language. -- Verify the setup with the smallest reliable check (prefer listing organizations). -- Keep secrets out of chat and out of the repo. - -Hard rules: -- NEVER ask the user to paste SCALEKIT_CLIENT_SECRET into chat. -- NEVER hardcode credentials in code samples; always use environment variables. -- Prefer creating a local verification script (verify.js / verify.py / verify.go / Verify.java) and running it, but only if the user wants you to write files. - -Workflow: -1) Determine language/runtime (Node.js, Python, Go, Java) and where env vars should live (.env, shell, CI secrets). -2) Confirm required env vars exist: - - SCALEKIT_ENVIRONMENT_URL - - SCALEKIT_CLIENT_ID - - SCALEKIT_CLIENT_SECRET -3) Install the SDK (pick the official package for that language). -4) Initialize the SDK client using env vars. -5) Verify credentials by listing organizations with a small page size. -6) If verification fails, diagnose systematically: - - Wrong environment URL (dev vs prod) - - Missing env vars in current shell/process - - Incorrect client id/secret - - Network/DNS issues -7) Only after verification succeeds, proceed to feature work and route to the correct Skill: - - SSO → plugins/modular-sso/skills/modular-sso/SKILL.md - - SCIM → plugins/modular-scim/skills/modular-scim/SKILL.md - - MCP auth → plugins/mcp-auth/skills/*/SKILL.md - - Full-stack auth → plugins/full-stack-auth/skills/full-stack-auth/SKILL.md - -When you reference files, use exact repo-relative paths and read them before advising. diff --git a/plugins/modular-sso/commands/dryrun.md b/plugins/modular-sso/commands/dryrun.md deleted file mode 100644 index 1b23d0d..0000000 --- a/plugins/modular-sso/commands/dryrun.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -description: Run Scalekit dryrun in fsa -argument-hint: " [organization_id]" -allowed-tools: Bash(node *), Bash(npx *) ---- - -Run Scalekit dryrun with explicit arguments. - -Expected arguments: -1. mode (`sso`) -2. env_url -3. client_id -4. organization_id (required only for `sso`) - -Behavior: -- If mode is `fsa`, run: - `npx @scalekit-sdk/dryrun --env_url= --client_id= --mode=fsa` -- If mode is `sso`, run: - `npx @scalekit-sdk/dryrun --env_url= --client_id= --mode=sso --organization_id=` -- If mode is missing/invalid, explain the usage and ask for valid arguments. -- If `sso` is selected but organization_id is missing, ask for it before running. diff --git a/plugins/modular-sso/hooks/beacon.sh b/plugins/modular-sso/hooks/beacon.sh deleted file mode 100755 index 98506da..0000000 --- a/plugins/modular-sso/hooks/beacon.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -PLUGIN="${1:-unknown}" -HOOK="${2:-stop}" -INPUT=$(cat) - -SESSION_ID=$(echo "$INPUT" | python3 -c \ - "import sys,json; print(json.load(sys.stdin).get('session_id','anonymous'))" \ - 2>/dev/null || echo "anonymous") - -TOOL_NAME=$(echo "$INPUT" | python3 -c \ - "import sys,json; print(json.load(sys.stdin).get('tool_name',''))" \ - 2>/dev/null || echo "") - -curl -s -o /dev/null --max-time 5 \ - -X POST https://ph.scalekit.com/i/v0/e/ \ - -H "Content-Type: application/json" \ - -d "{\"token\":\"phc_85pLP8gwYvRCQdxgLQP24iqXHPRGaLgEw4S4dgZHJZ\",\ -\"event\":\"plugin_used\",\ -\"distinct_id\":\"${SESSION_ID}\",\ -\"properties\":{\"plugin\":\"${PLUGIN}\",\"coding_agent\":\"claude_code\",\"hook\":\"${HOOK}\",\"tool_name\":\"${TOOL_NAME}\"}}" diff --git a/plugins/modular-sso/hooks/hooks.json b/plugins/modular-sso/hooks/hooks.json deleted file mode 100644 index 2bfd375..0000000 --- a/plugins/modular-sso/hooks/hooks.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "description": "Usage beacon for Scalekit modular-sso plugin", - "hooks": { - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh modular-sso stop", - "timeout": 10 - } - ] - } - ] - } -} diff --git a/plugins/modular-sso/references/redirects.md b/plugins/modular-sso/references/redirects.md deleted file mode 100644 index dbe1977..0000000 --- a/plugins/modular-sso/references/redirects.md +++ /dev/null @@ -1,76 +0,0 @@ -# Redirects - -Redirects are registered endpoints in Scalekit that control where users are directed during authentication flows. You must configure these endpoints in the Scalekit dashboard before they can be used. - -All redirect URIs must be registered under Authentication settings in your Scalekit dashboard. This is a security requirement to prevent unauthorized redirects. - - -## Redirect endpoint types - -### Allowed callback URLs -**Purpose**: Where users are sent after successful authentication to exchange authorization codes and retrieve profile information. - -**Example scenario**: A user completes sign-in and Scalekit redirects them to `https://yourapp.com/callback` where your application processes the authentication response. - -To add or remove an redirect URL, go to Dashboard > Authentication > Redirects > Allowed Callback URLs. - -### Initiate login URL -**Purpose**: When authentication does not initiate from your application, Scalekit redirects users back to your application's login initiation endpoint. This endpoint should point to a route in your application that ultimately redirects users to Scalekit's `/authorize` endpoint. - -**Example scenarios**: - -- **Bookmarked login page**: A user bookmarks your login page and visits it directly. Your application detects they're not authenticated and redirects them to Scalekit's authorization endpoint. - -- **Organization invitation flow**: A user clicks an invitation link to join an organization. Your application receives the invitation token and redirects the user to Scalekit's authorization endpoint to complete the sign-up process. - -- **IdP-initiated SSO**: An administrator initiates single sign-on from their identity provider dashboard. The IdP redirects users to your application, which then redirects them to Scalekit's authorization endpoint to complete authentication. - -- **Session expiration**: When a user's session expires or they access a protected resource, they're redirected to `https://yourapp.com/login` which then redirects to Scalekit's authentication endpoint. - -### Post logout URL -**Purpose**: Where users are sent after successfully signing out of your application. - -**Example scenario**: After logging out, users are redirected to `https://yourapp.com/goodbye` to confirm their session has ended. - -### Back channel logout URL -**Purpose**: A secure endpoint that receives notifications whenever a user is logged out from Scalekit, regardless of how the logout was initiated — admin triggered, user initiated, or due to session policies like idle timeout. - -**Example scenario**: When a user logs out from any application (user-initiated, admin-initiated, or due to session policies like idle timeout), Scalekit sends a logout notification to `https://yourapp.com/logout` to suggest termination of the user's session across all connected applications, ensuring coordinated logout for enhanced security. - -### Custom URI schemes - -Custom URI schemes allow for redirects, enabling deep linking and native app integrations. Some applications include: -- **Desktop applications**: Use schemes like `{scheme}://` for native app integration -- **Mobile apps**: Use schemes like `myapp://` for mobile app deep linking - -**Example custom schemes**: -- `{scheme}://auth/callback` - For custom scheme authentication -- `myapp://login/callback` - For mobile app authentication - - -## URI validation requirements - -Your redirect URIs must meet specific requirements that vary between development and production environments: - -| Requirement | Development | Production | -| ----------- | ----------- | ---------- | -| Supported schemes | `http`, `https`, `{scheme}` | `https`, `{scheme}` | -| Localhost support | Allowed | Not allowed | -| Wildcard domains | Allowed | Not allowed | -| URI length limit | 256 characters | 256 characters | -| Query parameters | Not allowed | Not allowed | -| URL fragments | Not allowed | Not allowed | - - -### Wildcard usage patterns - -Wildcards can simplify testing in development environments, but they must follow specific patterns: - -| Validation rule | Valid examples | Invalid examples | -| --------------- | -------------- | ---------------- | -| Wildcards cannot be used as root-level domains | `https://*.acmecorp.com`, `https://auth-*.acmecorp.com` | `https://*.com` | -| Only one wildcard character is allowed per URI | `https://*.acmecorp.com` | `https://*.*.acmecorp.com` | -| Wildcards must be in the hostname component only | `https://*.acmecorp.com` | `https://acmecorp.*.com` | -| Wildcards must be in the outermost subdomain | `https://*.auth.acmecorp.com` | `https://auth.*.acmecorp.com` | - -> **Note**: According to the [OAuth 2.0 specification](https://tools.ietf.org/html/rfc6749#section-3.1.2), redirect URIs must be absolute URIs. For development convenience, Scalekit relaxes this restriction slightly by allowing wildcards in development environments. diff --git a/plugins/modular-sso/skills/implementing-admin-portal/SKILL.md b/plugins/modular-sso/skills/implementing-admin-portal/SKILL.md deleted file mode 100644 index 6ebb8b7..0000000 --- a/plugins/modular-sso/skills/implementing-admin-portal/SKILL.md +++ /dev/null @@ -1,153 +0,0 @@ ---- -name: implementing-admin-portal -description: Implements Scalekit's admin portal for customer self-serve SSO and SCIM configuration. Generates portal links server-side and embeds the portal as an iframe in the app's settings UI. Use when the user asks to add an admin portal, customer self-serve SSO setup, iframe embed for SSO config, shareable setup link, or let customers configure their own SSO or SCIM connection. ---- - -# Admin Portal with Scalekit - -Adds a self-serve portal where customers configure their own SSO and SCIM settings — embedded inside your app's settings UI. - -If the user only needs a quick shareable link with no code (e.g., for a one-time onboarding call), skip to the **Shareable link** section at the bottom. - ---- - -## Implementation progress - -``` -Admin Portal Implementation Progress: -- [ ] Step 1: Install SDK -- [ ] Step 2: Set environment credentials -- [ ] Step 3: Register app domain in dashboard -- [ ] Step 4: Generate portal link (server-side) -- [ ] Step 5: Render iframe (client-side) -- [ ] Step 6: Handle session expiry events -- [ ] Step 7: Verify portal loads and events fire correctly -``` - ---- - -## Step 1: Install SDK - -Detect the project's language/framework from existing files and install: - -| Stack | Install | -|---------|---------| -| Node.js | `npm install @scalekit-sdk/node` | -| Python | `pip install scalekit-sdk-python` | -| Go | `go get github.com/scalekit-inc/scalekit-sdk-go/v2` | -| Java | Add `com.scalekit:scalekit-sdk` to `pom.xml` | - ---- - -## Step 2: Set environment credentials - -Add to `.env` (never hardcode): - -```shell -SCALEKIT_ENVIRONMENT_URL='https://.scalekit.com' -SCALEKIT_CLIENT_ID='' -SCALEKIT_CLIENT_SECRET='' -``` - -Credentials are in **Dashboard > Developers > Settings > API Credentials**. - ---- - -## Step 3: Register app domain - -In **Dashboard > Developers > API Configuration > Redirect URIs**, add the domain where the portal will be embedded. The iframe will be blocked if this is missing. - ---- - -## Step 4: Generate the portal link (server-side) - -Generate a new link on every page load — links are single-use. Plug into the existing route or controller that serves the settings/admin page: - -**Node.js:** -```javascript -const { location } = await scalekit.organization.generatePortalLink(organizationId); -// Pass `location` to the frontend as a template variable or API response -``` - -**Python:** -```python -portal = scalekit_client.organization.generate_portal_link(organization_id) -location = portal.location -# Pass `location` to your template or JSON response -``` - -**Never cache this value** — each link is single-use and will fail if reused. - ---- - -## Step 5: Render the iframe (client-side) - -In the frontend settings/admin template, inject `location` as the `src`: - -```html - -``` - -Minimum recommended height: **600px**. Match the variable name to the project's existing templating convention. - ---- - -## Step 6: Handle portal UI events - -Listen for messages from the iframe to react to configuration changes and session expiry: - -```javascript -window.addEventListener('message', (event) => { - if (event.origin !== process.env.SCALEKIT_ENVIRONMENT_URL) return; - - const { type } = event.data; - switch (type) { - case 'SSO_CONFIGURED': - // Refresh org SSO status, show success banner, etc. - break; - case 'SESSION_EXPIRED': - // Re-fetch a new portal link and reload the iframe src - reloadPortalIframe(); - break; - } -}); -``` - -`SESSION_EXPIRED` handling is required — without it the portal silently breaks for long-lived sessions. - ---- - -## Step 7: Verify - -- [ ] Open the settings page — confirm the iframe renders without console errors -- [ ] Complete a test SSO configuration inside the portal — confirm `SSO_CONFIGURED` fires -- [ ] Wait for session expiry (or simulate it) — confirm `SESSION_EXPIRED` triggers a link refresh -- [ ] Confirm portal link is never the same across two page loads (single-use verification) - ---- - -## Branding (optional) - -Configure at **Dashboard > Settings > Branding**: logo, accent color, favicon. Custom domain support (e.g., `sso.yourapp.com`) is available in the Scalekit dashboard. - ---- - -## Guardrails - -- **Generate link server-side only** — never expose `CLIENT_SECRET` to the browser -- **Re-generate on every page load** — caching will break the portal -- **Register your domain** in Redirect URIs before testing or the iframe will be blocked -- **Handle `SESSION_EXPIRED`** — re-generate and reload, don't let it fail silently - ---- - -## Shareable link (no-code alternative) - -For one-time onboarding calls or zero-engineering setup: go to **Dashboard > Organizations**, select the org, click **Generate link**, and share the URL directly. The link gives anyone who has it full access to configure that org's SSO/SCIM settings — use the iframe approach for production. Also share Scalekit's [SSO setup guides](https://docs.scalekit.com/guides/integrations/sso-integrations/) so the IT admin has provider-specific configuration steps alongside the portal link. - diff --git a/plugins/modular-sso/skills/modular-sso/SKILL.md b/plugins/modular-sso/skills/modular-sso/SKILL.md deleted file mode 100644 index 5c4a23c..0000000 --- a/plugins/modular-sso/skills/modular-sso/SKILL.md +++ /dev/null @@ -1,574 +0,0 @@ ---- -name: modular-sso -description: Implements complete SSO and authentication flows using Scalekit. Handles modular SSO, IdP-initiated login, user session management, and enterprise customer onboarding. Use when adding authentication, SSO, SAML, OIDC, or user login to applications. ---- - -# Implement SSO as a Modular - -## Quick Start - -**Choose your authentication mode:** -- **Modular SSO**: You manage users and sessions (covered here) -- **Full-Stack Auth**: Scalekit manages users and sessions (built-in SSO) - -This skill covers Modular SSO for applications with existing user management. - -## Implementation Workflow - -Copy this checklist and track progress: - -``` -Authentication Integration Progress: -- [ ] Step 1: Configure Modular Auth mode -- [ ] Step 2: Install and configure Scalekit SDK -- [ ] Step 3: Implement authorization URL generation -- [ ] Step 4: Handle IdP-initiated SSO (RECOMMENDED) -- [ ] Step 5: Process authentication callback -- [ ] Step 6: Validate tokens and extract user profile -- [ ] Step 7: Test SSO integration -- [ ] Step 8: Set up customer onboarding flow -``` - -## Step 1: Configure Modular Auth Mode - -**Action**: Configure environment for Modular SSO: -1. Navigate to Dashboard > Authentication > General -2. Under "Full-Stack Auth" section, click "Disable Full-Stack Auth" - -**Result**: System ready for modular integration. - -## Step 2: Install and Configure SDK - -### Installation - -Choose the SDK for the project's tech stack: - -**Node.js:** -```bash -npm install @scalekit-sdk/node -``` - -**Python:** -```bash -pip install scalekit-sdk-python -``` - -**Go:** -```bash -go get github.com/scalekit-inc/scalekit-sdk-go -``` - -**Java:** -```xml - - com.scalekit - scalekit-sdk-java - -``` - -### Environment Configuration - -Add these credentials to `.env` file (fetch from Dashboard > Developers > Settings > API credentials): - -```env -SCALEKIT_ENVIRONMENT_URL= -SCALEKIT_CLIENT_ID= -SCALEKIT_CLIENT_SECRET= -``` - ---- - -## Step 3: Generate Authorization URL - -Create authorization URL to redirect users to their identity provider. - -### SSO Connection Selectors (Priority Order) - -Use ONE of these identifiers (evaluated in precedence order): - -1. **connectionId** (highest) - Direct SSO connection reference -2. **organizationId** - Routes to organization's active SSO -3. **loginHint** - Extracts domain from email to find connection - -### Implementation Pattern - -**Node.js:** -```javascript -const scalekit = new ScalekitClient( - process.env.SCALEKIT_ENVIRONMENT_URL, - process.env.SCALEKIT_CLIENT_ID, - process.env.SCALEKIT_CLIENT_SECRET -); - -const options = { - organizationId: 'org_15421144869927830', // OR - connectionId: 'conn_15696105471768821', // OR - loginHint: 'user@example.com' -}; - -const authUrl = scalekit.getAuthorizationUrl( - 'https://yourapp.com/auth/callback', - options -); - -// Redirect user to authUrl -``` - -**Python:** -```python -from scalekit import ScalekitClient, AuthorizationUrlOptions - -scalekit = ScalekitClient( - os.getenv('SCALEKIT_ENVIRONMENT_URL'), - os.getenv('SCALEKIT_CLIENT_ID'), - os.getenv('SCALEKIT_CLIENT_SECRET') -) - -options = AuthorizationUrlOptions() -options.organization_id = 'org_15421144869927830' - -auth_url = scalekit.get_authorization_url( - redirect_uri='https://yourapp.com/auth/callback', - options=options -) -``` - -**Direct URL (no SDK):** -``` -/oauth/authorize? - response_type=code& - client_id=& - redirect_uri=& - scope=openid profile email& - organization_id= -``` - -## Step 4: Handle IdP-Initiated SSO - -**CRITICAL**: Implement this to support users who start login from their identity provider portal. - -### Why This Matters - -IdP-initiated SSO converts potentially insecure flows into secure SP-initiated flows, protecting against SAML assertion theft and replay attacks. - -### Configuration Required - -1. Set initiate login endpoint: Dashboard > Authentication > Redirects -2. Configure endpoint: `https://yourapp.com/login` - -### Implementation - -**Node.js:** -```javascript -app.get('/login', async (req, res) => { - const { idp_initiated_login, error, error_description } = req.query; - - if (error) { - return res.status(400).json({ message: error_description }); - } - - if (idp_initiated_login) { - // Decode JWT to extract connection details - const claims = await scalekit.getIdpInitiatedLoginClaims(idp_initiated_login); - - const options = { - connectionId: claims.connection_id, - organizationId: claims.organization_id, - loginHint: claims.login_hint, - state: claims.relay_state - }; - - const authUrl = scalekit.getAuthorizationUrl( - 'https://yourapp.com/auth/callback', - options - ); - - return res.redirect(authUrl); - } - - // Handle normal login flow -}); -``` - -**Python:** -```python -@app.route('/login') -async def handle_login(): - idp_initiated_login = request.args.get('idp_initiated_login') - error = request.args.get('error') - - if error: - return {'error': request.args.get('error_description')}, 400 - - if idp_initiated_login: - claims = await scalekit.get_idp_initiated_login_claims(idp_initiated_login) - - options = AuthorizationUrlOptions() - options.connection_id = claims.get('connection_id') - options.organization_id = claims.get('organization_id') - options.state = claims.get('relay_state') - - auth_url = scalekit.get_authorization_url( - redirect_uri='https://yourapp.com/auth/callback', - options=options - ) - - return redirect(auth_url) -``` - ---- - -## Step 5: Process Authentication Callback - -Handle the callback after successful IdP authentication. - -### Callback Endpoint Setup - -1. Create endpoint: `/auth/callback` -2. Register in Dashboard > Authentication > Redirect URLs > Allowed Callback URLs - -### Implementation - -**Node.js:** -```javascript -app.get('/auth/callback', async (req, res) => { - const { code, error, error_description } = req.query; - - if (error) { - return res.status(400).json({ error: error_description }); - } - - try { - // Exchange code for user profile and tokens - const result = await scalekit.authenticateWithCode( - code, - 'https://yourapp.com/auth/callback' - ); - - // Extract user information - const userEmail = result.user.email; - const userName = result.user.givenName + ' ' + result.user.familyName; - const userId = result.user.id; - - // Create session for authenticated user - req.session.user = { - id: userId, - email: userEmail, - name: userName - }; - - res.redirect('/dashboard'); - } catch (err) { - res.status(500).json({ error: 'Authentication failed' }); - } -}); -``` - -**Python:** -```python -@app.route('/auth/callback') -async def auth_callback(): - code = request.args.get('code') - error = request.args.get('error') - - if error: - return {'error': request.args.get('error_description')}, 400 - - result = scalekit.authenticate_with_code( - code, - 'https://yourapp.com/auth/callback' - ) - - # Create session - session['user'] = { - 'id': result.user.id, - 'email': result.user.email, - 'name': f"{result.user.given_name} {result.user.family_name}" - } - - return redirect('/dashboard') -``` - ---- - -## Step 6: Validate Tokens - -**ALWAYS** validate tokens before trusting claims. - -**Node.js:** -```javascript -// Validate ID token -const idTokenClaims = await scalekit.validateToken(result.idToken); - -// Validate access token -const accessTokenClaims = await scalekit.validateToken(result.accessToken); -``` - -**Python:** -```python -id_token_claims = scalekit.validate_token(result['id_token']) -access_token_claims = scalekit.validate_token(result['access_token']) -``` - -### Token Structure - -**ID Token includes:** -- `email`: User's email address -- `given_name`, `family_name`: User's name -- `sub`: Unique user identifier (format: `connectionId;userId`) -- `oid`: Organization ID -- `amr`: Authentication method (SSO connection ID) - -**Access Token includes:** -- `sub`: User identifier -- `exp`: Expiration timestamp -- `client_id`: Your application client ID - -## Step 7: Test SSO Integration - -Use the built-in IdP Simulator for comprehensive testing. - -### Test Organization Setup - -Your environment includes pre-configured test organization with domains: -- `@example.com` -- `@example.org` - -### Testing Workflow - -1. **Find test organization**: Dashboard > Organizations -2. **Use test selector**: Pass one of these in authorization URL: - - Email with `@example.com` domain - - Test organization's connection ID - - Organization ID -3. **Simulate SSO flow**: IdP Simulator appears (mimics customer's IdP) -4. **Complete authentication**: Enter test credentials -5. **Verify callback**: Check user profile received correctly - -### Test Scenarios - -Test ALL three scenarios: -1. **SP-initiated SSO**: User starts login from your app -2. **IdP-initiated SSO**: User starts from IdP portal -3. **Domain-based routing**: User enters email, auto-routes to IdP - ---- - -## Step 8: Customer Onboarding - -Enable SSO for enterprise customers through self-service Admin Portal. - -### Quick Onboarding - -**Create organization**: Dashboard > Organizations > New Organization - -**Generate portal link** (Node.js): -```javascript -const portalLink = await scalekit.organization.generatePortalLink( - 'org_32656XXXXXX0438' -); - -// Share this link with customer's IT admin -console.log('Admin Portal:', portalLink.location); -``` - -**Share link**: Send to customer's IT administrator via email/Slack - -**Share setup guide**: Include the Scalekit [SSO setup guide](https://docs.scalekit.com/guides/integrations/sso-integrations/) — provider-specific steps for Okta, Azure AD, Google Workspace, and others. - -### Embedded Portal (Advanced) - -Embed Admin Portal in your app for seamless experience: - -```javascript -// Backend: Generate portal link -const portalLink = await scalekit.organization.generatePortalLink(orgId); -res.json({ portalUrl: portalLink.location }); -``` - -```html - - -``` -## Advanced Patterns - -### Pre-Check SSO Availability - -Prevent failed redirects by checking SSO configuration before redirecting: - -**Node.js:** -```javascript -const domain = email.split('@').pop()?.toLowerCase(); - -const connections = await scalekit.connections.listConnectionsByDomain({ - domain -}); - -if (connections.length > 0) { - // SSO available - redirect to IdP - const authUrl = scalekit.getAuthorizationUrl(redirectUri, { - domainHint: domain - }); - return res.redirect(authUrl); -} else { - // No SSO - show password login - return showPasswordLogin(); -} -``` - -### Domain Verification - -Enable seamless routing by verifying customer domains: -1. Customer verifies domain (e.g., `@megacorp.org`) in Admin Portal -2. Users sign in without organization selection -3. Scalekit auto-routes based on email domain - -### Session Management Best Practices - -**Set secure session configuration:** -```javascript -app.use(session({ - secret: process.env.SESSION_SECRET, - resave: false, - saveUninitialized: false, - cookie: { - secure: true, // HTTPS only - httpOnly: true, // Prevent XSS - maxAge: 86400000, // 24 hours - sameSite: 'lax' // CSRF protection - } -})); -``` - -**Implement session refresh:** -```javascript -// Check token expiration -if (Date.now() / 1000 > accessTokenClaims.exp) { - // Redirect to re-authentication - return res.redirect('/login'); -} -``` - -## Integration with Existing Auth Systems - -### Auth0 Integration - -Configure Scalekit as Custom Social Connection in Auth0: -1. Auth0 Dashboard > Authentication > Social > Create Connection -2. Use Scalekit OAuth2 endpoints -3. Map Scalekit user attributes to Auth0 profile - -### Firebase Integration - -Add Scalekit as Custom Auth Provider: -1. Use Firebase Custom Token generation -2. Exchange Scalekit tokens for Firebase tokens -3. Maintain session with Firebase SDK - -### AWS Cognito Integration - -Configure Scalekit as SAML Identity Provider: -1. Cognito User Pool > Identity Providers > SAML -2. Use Scalekit metadata URL -3. Map attributes to Cognito user attributes - -## Security Checklist - -Before production deployment, verify: - -- [ ] Environment variables stored securely (never in code) -- [ ] HTTPS enforced on all endpoints -- [ ] Tokens validated before trusting claims -- [ ] Session cookies use `secure` and `httpOnly` flags -- [ ] CSRF protection enabled -- [ ] Callback URLs registered in Scalekit dashboard -- [ ] Error messages don't expose sensitive information -- [ ] Rate limiting implemented on auth endpoints -- [ ] Logging configured (without exposing tokens) - -## Troubleshooting - -### "Invalid redirect_uri" Error - -**Cause**: Callback URL not registered in dashboard -**Fix**: Add URL to Dashboard > Authentication > Redirect URLs - -### "Organization not found" Error - -**Cause**: Invalid organization ID or user doesn't belong to organization -**Fix**: Verify organization ID and user's email domain - -### IdP-Initiated SSO Not Working - -**Cause**: Initiate login URL not configured -**Fix**: Set URL in Dashboard > Authentication > Redirects - -### Token Validation Fails - -**Cause**: Token expired or invalid signature -**Fix**: Check token expiration and environment URL configuration - -## Common Patterns - -### Multi-Tenant Architecture - -```javascript -// Determine organization from subdomain -const subdomain = req.hostname.split('.'); -const organization = await getOrganizationBySubdomain(subdomain); - -const authUrl = scalekit.getAuthorizationUrl(redirectUri, { - organizationId: organization.scalekitOrgId -}); -``` - -### Step-Up Authentication - -```javascript -// Require re-authentication for sensitive operations -if (requiresStepUp && !session.recentAuth) { - return res.redirect('/auth/step-up'); -} -``` - -### Logout Implementation - -```javascript -app.post('/logout', (req, res) => { - req.session.destroy(); - res.redirect('/'); -}); -``` - -## Reference - -**Scalekit Dashboard**: [https://app.scalekit.com](https://app.scalekit.com) - -**Connection Selector Precedence**: connectionId > organizationId > loginHint - -**Token Expiration**: ID tokens expire in 15 minutes, access tokens in 24 hours - -**Admin Portal Events**: Listen for `sso.enabled`, `sso.disabled`, `session.expired` - -**Support**: [docs.scalekit.com](https://docs.scalekit.com) - -## Implementation Notes - -**Always validate tokens**: Never trust token claims without validation - -**Handle errors gracefully**: Show user-friendly messages, log details internally - -**Test all scenarios**: SP-initiated, IdP-initiated, and domain-based routing - -**Enable domain verification**: Provides best user experience - -**Use progressive enhancement**: Start with basic SSO, add advanced features iteratively - -**Monitor authentication flows**: Track success rates and common failure points -``` \ No newline at end of file diff --git a/plugins/modular-sso/skills/production-readiness-scalekit/SKILL.md b/plugins/modular-sso/skills/production-readiness-scalekit/SKILL.md deleted file mode 100644 index b24ffab..0000000 --- a/plugins/modular-sso/skills/production-readiness-scalekit/SKILL.md +++ /dev/null @@ -1,110 +0,0 @@ ---- -name: production-readiness-scalekit -description: 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. ---- - -# Scalekit SSO Production Readiness - -Work through each section in order — earlier sections are blockers for later ones. - ---- - -## Quick checks (run first) - -- [ ] Production environment URL, client ID, and client secret are set (not dev/staging values) -- [ ] HTTPS enforced on all auth endpoints -- [ ] CORS restricted to your domains only -- [ ] API credentials stored in environment variables — never committed to code - ---- - -## Customization - -- [ ] Login page branded with logo, colors, styling -- [ ] Email templates customized (sign-up, password reset, invitations) -- [ ] Custom domain configured for auth pages (if applicable) -- [ ] Email provider configured in **Dashboard > Customization > Emails** -- [ ] Email deliverability tested — check spam folders -- [ ] Webhooks configured with signature validation - ---- - -## Core auth flows - -- [ ] Test login initiation with authorization URL -- [ ] Validate redirect URLs match dashboard configuration exactly -- [ ] Test authentication completion and code exchange -- [ ] Validate `state` parameter in callbacks (CSRF protection) -- [ ] Verify session token storage uses `httpOnly`, `secure`, and `sameSite` flags -- [ ] Test session timeout and automatic token refresh -- [ ] Verify logout clears sessions completely -- [ ] Expired tokens handled gracefully -- [ ] Network failures show user-friendly messages - ---- - -## SSO flows - -- [ ] Test SSO with target IdPs: Okta, Azure AD, Google Workspace - → IT admin setup guides per IdP: https://docs.scalekit.com/guides/integrations/sso-integrations/ -- [ ] Configure user attribute mapping (email, name, groups) -- [ ] Test both SP-initiated and IdP-initiated SSO flows -- [ ] Verify SSO error handling for misconfigured connections -- [ ] Test SSO with: new users, existing users, deactivated users - -**JIT provisioning:** -- [ ] Register all organization domains for JIT provisioning -- [ ] Configure consistent user identifiers across SSO connections (email or userPrincipalName) -- [ ] Set default roles for JIT-provisioned users -- [ ] Enable "Sync user attributes during login" -- [ ] Plan manual invitation process for contractors/external users with non-matching domains -- [ ] Set up review process for automatically provisioned users - -**Admin portal:** -- [ ] Configure admin portal access for enterprise customers -- [ ] Test admin portal SSO configuration flows -- [ ] Verify user management features in admin portal - ---- - -## Organization management - -- [ ] Test organization creation -- [ ] Test adding and removing users from organizations -- [ ] Set allowed email domains for org sign-ups (if applicable) -- [ ] Verify organization switching for users in multiple orgs -- [ ] Test invitation flow and email templates - ---- - -## Network and firewall - -Enterprise customers behind VPN or corporate firewall must whitelist: - -| Domain | Purpose | -|---|---| -| `.scalekit.com` | Auth + admin portal | -| `cdn.scalekit.com` | Static assets | -| `fonts.googleapis.com` | Font resources | - -- [ ] Customer firewalls allow Scalekit domains -- [ ] SSO tested from customer's network environment - ---- - -## Monitoring and incident readiness - -- [ ] Auth logs monitoring configured in **Dashboard > Auth Logs** -- [ ] Alerts set for suspicious activity (multiple failed logins, unusual locations) -- [ ] Webhook event monitoring and logging active -- [ ] Error tracking configured for authentication failures -- [ ] Log retention policies configured -- [ ] Webhook delivery and retry mechanism tested -- [ ] Incident response runbook written (who to contact, how to roll back, escalation path) -- [ ] Rollback plan ready (feature flag to disable SSO flows if needed) - -**Key metrics:** -- Login success/failure rates -- SSO initiation vs completion rate -- Session creation and duration -- Webhook delivery success rate diff --git a/plugins/saaskit/.claude-plugin/plugin.json b/plugins/saaskit/.claude-plugin/plugin.json new file mode 100644 index 0000000..d9f5803 --- /dev/null +++ b/plugins/saaskit/.claude-plugin/plugin.json @@ -0,0 +1,13 @@ +{ + "name": "saaskit", + "description": "Production-ready authentication for B2B SaaS apps using Scalekit — login, sessions, SSO, SCIM, MCP server auth, RBAC, and API key management.", + "version": "2.0.0", + "author": { + "name": "Scalekit", + "email": "hi@scalekit.com" + }, + "homepage": "https://docs.scalekit.com", + "repository": "https://github.com/scalekit-inc/claude-code-authstack", + "license": "MIT", + "keywords": ["scalekit", "saaskit", "authentication", "sso", "scim", "mcp-auth", "sessions", "rbac"] +} \ No newline at end of file diff --git a/plugins/saaskit/.env.example b/plugins/saaskit/.env.example new file mode 100644 index 0000000..5647833 --- /dev/null +++ b/plugins/saaskit/.env.example @@ -0,0 +1,3 @@ +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.io +SCALEKIT_CLIENT_ID=your-client-id +SCALEKIT_CLIENT_SECRET=your-client-secret \ No newline at end of file diff --git a/plugins/saaskit/README.md b/plugins/saaskit/README.md new file mode 100644 index 0000000..b6e3c71 --- /dev/null +++ b/plugins/saaskit/README.md @@ -0,0 +1,85 @@ +# SaaSKit for Claude Code + +## Purpose +This plugin brings Scalekit SaaSKit into Claude Code so agents can build production-ready B2B authentication into web applications. It covers the entire auth lifecycle: login, sessions, SSO, SCIM provisioning, RBAC, MCP server auth, and API key management across Node.js, Python, Go, Java, and PHP frameworks. + +Canonical content lives in: +- `docs/` for durable SaaSKit knowledge (auth flows, sessions, SSO, SCIM, etc.) +- `docs/frameworks/` for framework-specific guides (Python, Next.js, Go, Spring Boot, Laravel) +- `skills/` for task-oriented workflows (thin routing layers into docs/) +- `rules/` for cross-cutting guidance (terminology, redirect URLs) + +Claude runtime files: +- `.claude-plugin/` +- `commands/` for slash-command aliases +- `hooks/` +- `agents/` +- `references/` + +## Installation +```sh +claude /plugin install saaskit@scalekit-auth-stack +``` + +Official Scalekit docs: +- [LLM docs map](https://docs.scalekit.com/llms.txt) +- [Full-stack auth quickstart](https://docs.scalekit.com/authenticate/fsa/quickstart/) +- [Modular SSO guide](https://docs.scalekit.com/authenticate/sso/add-modular-sso/) +- [SCIM directory sync](https://docs.scalekit.com/directory/scim/quickstart/) +- [MCP Auth quickstart](https://docs.scalekit.com/authenticate/mcp/quickstart/) + +## Skills Reference +- `/saaskit:implementing-saaskit` — Core auth flow: login, signup, callback, token exchange, logout. Framework reference files for Go, Spring Boot, Laravel. +- `/saaskit:managing-saaskit-sessions` — Secure session storage, token refresh middleware, session revocation. +- `/saaskit:implementing-access-control` — RBAC and permission checks using Scalekit access tokens. +- `/saaskit:implementing-saaskit-python` — Auth for Django, FastAPI, or Flask using scalekit-sdk-python. +- `/saaskit:implementing-saaskit-nextjs` — Auth for Next.js App Router using @scalekit-sdk/node. +- `/saaskit:implementing-modular-sso` — Enterprise SSO (SAML/OIDC), IdP-initiated login, admin portal. +- `/saaskit:implementing-scim-provisioning` — SCIM webhooks, user/group lifecycle, directory API. +- `/saaskit:adding-mcp-oauth` — OAuth 2.1 for MCP servers. Reference files for FastMCP, Express, FastAPI. +- `/saaskit:adding-api-auth` — API keys (org/user scoped) and OAuth 2.0 client credentials. +- `/saaskit:migrating-to-saaskit` — Migration planning from Auth0, Firebase, Cognito, or custom auth. +- `/saaskit:production-readiness-saaskit` — Unified production checklist across all SaaSKit domains. + +## Configuration +Required environment variables: +- `SCALEKIT_ENVIRONMENT_URL` +- `SCALEKIT_CLIENT_ID` +- `SCALEKIT_CLIENT_SECRET` + +Get these from [app.scalekit.com](https://app.scalekit.com): Developers → Settings → API Credentials. + +See [`.env.example`](.env.example) for a template. + +## Usage Examples + +### Add login to a Next.js app +``` +/saaskit:implementing-saaskit-nextjs +``` +The skill detects your Next.js project and guides you through adding Scalekit auth routes, middleware, and session management. + +### Add SSO for enterprise customers +``` +/saaskit:implementing-modular-sso +``` +Adds SAML/OIDC SSO with self-serve configuration via the admin portal iframe. + +### Secure an MCP server +``` +/saaskit:adding-mcp-oauth +``` +Adds OAuth 2.1 authorization middleware to your MCP server (Node.js or Python). + +## Troubleshooting + +1. **Redirect URL mismatch** — The `redirect_uri` in your authorization request must exactly match a URL registered in the Scalekit dashboard. Check for trailing slash, scheme, and port mismatches. +2. **Token refresh fails** — Ensure the refresh token is stored securely (encrypted, HttpOnly cookie) and hasn't expired. Check `SCALEKIT_ENVIRONMENT_URL` is correct. +3. **SSO callback returns error** — Verify the SSO connection is active in the Scalekit dashboard and the IdP metadata (ACS URL, Entity ID) matches your app configuration. + +## Security + +- Store `SCALEKIT_CLIENT_SECRET` in environment variables or a secrets manager. Never commit it to version control. +- All tokens (access, refresh, ID) should be stored in HttpOnly, Secure, SameSite cookies or encrypted server-side storage. +- Validate access tokens on every request before trusting embedded roles/permissions. +- Use the admin portal iframe for customer self-serve SSO configuration rather than building custom SSO management UI. \ No newline at end of file diff --git a/plugins/mcp-auth/agents/scalekit-mcp-auth-troubleshooter.md b/plugins/saaskit/agents/scalekit-mcp-auth-troubleshooter.md similarity index 100% rename from plugins/mcp-auth/agents/scalekit-mcp-auth-troubleshooter.md rename to plugins/saaskit/agents/scalekit-mcp-auth-troubleshooter.md diff --git a/plugins/full-stack-auth/agents/scalekit-mcp-helper.md b/plugins/saaskit/agents/scalekit-mcp-helper.md similarity index 100% rename from plugins/full-stack-auth/agents/scalekit-mcp-helper.md rename to plugins/saaskit/agents/scalekit-mcp-helper.md diff --git a/plugins/full-stack-auth/agents/sdk-version-advisor.md b/plugins/saaskit/agents/sdk-version-advisor.md similarity index 100% rename from plugins/full-stack-auth/agents/sdk-version-advisor.md rename to plugins/saaskit/agents/sdk-version-advisor.md diff --git a/plugins/full-stack-auth/agents/session-management-reviewer.md b/plugins/saaskit/agents/session-management-reviewer.md similarity index 95% rename from plugins/full-stack-auth/agents/session-management-reviewer.md rename to plugins/saaskit/agents/session-management-reviewer.md index b7fe1f6..309babc 100644 --- a/plugins/full-stack-auth/agents/session-management-reviewer.md +++ b/plugins/saaskit/agents/session-management-reviewer.md @@ -21,10 +21,10 @@ Hard rules: - Always reference actual file paths found during discovery, not hypothetical ones. - Never give generic advice — every recommendation must be grounded in what you found. - When analyzing user identity, token claims, or profile data in session payloads, - consult `plugins/full-stack-auth/references/scalekit-user-profiles.md` for Scalekit's + consult `plugins/saaskit/references/scalekit-user-profiles.md` for Scalekit's attribute schema and SDK method reference before suggesting implementation. - When a session failure pattern is suspected or a webhook-triggered auth flow is being - debugged, consult `plugins/full-stack-auth/references/scalekit-logs.md` for filter + debugged, consult `plugins/saaskit/references/scalekit-logs.md` for filter strategies and status definitions before suggesting next steps. --- @@ -294,9 +294,9 @@ Produce this structured report after completing all phases: After delivering the report, offer to route to the appropriate skill for implementation: -- Option A selected → `plugins/full-stack-auth/skills/full-stack-auth/SKILL.md` -- Option B selected → `plugins/modular-sso/skills/modular-sso/SKILL.md` -- Option D selected → `plugins/agent-auth/skills/agent-auth/SKILL.md` +- Option A selected → `plugins/saaskit/skills/implementing-saaskit/SKILL.md` +- Option B selected → `plugins/saaskit/skills/implementing-modular-sso/SKILL.md` +- Option D selected → `plugins/agentkit/skills/integrating-agentkit/SKILL.md` If Scalekit is not yet set up, route to setup first: -- `plugins/full-stack-auth/agents/setup-scalekit.md` +- `plugins/saaskit/agents/setup-scalekit.md` diff --git a/plugins/mcp-auth/agents/setup-scalekit.md b/plugins/saaskit/agents/setup-scalekit.md similarity index 82% rename from plugins/mcp-auth/agents/setup-scalekit.md rename to plugins/saaskit/agents/setup-scalekit.md index 044e411..4ab6bb9 100644 --- a/plugins/mcp-auth/agents/setup-scalekit.md +++ b/plugins/saaskit/agents/setup-scalekit.md @@ -34,9 +34,10 @@ Workflow: - Incorrect client id/secret - Network/DNS issues 7) Only after verification succeeds, proceed to feature work and route to the correct Skill: - - SSO → plugins/modular-sso/skills/modular-sso/SKILL.md - - SCIM → plugins/modular-scim/skills/modular-scim/SKILL.md - - MCP auth → plugins/mcp-auth/skills/*/SKILL.md - - Full-stack auth → plugins/full-stack-auth/skills/full-stack-auth/SKILL.md + - SSO → plugins/saaskit/skills/implementing-modular-sso/SKILL.md + - SCIM → plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md + - MCP server auth → plugins/saaskit/skills/adding-mcp-oauth/SKILL.md + - SaaSKit auth → plugins/saaskit/skills/implementing-saaskit/SKILL.md + - Agent auth → plugins/agentkit/skills/integrating-agentkit/SKILL.md When you reference files, use exact repo-relative paths and read them before advising. diff --git a/plugins/full-stack-auth/commands/dryrun.md b/plugins/saaskit/commands/dryrun.md similarity index 100% rename from plugins/full-stack-auth/commands/dryrun.md rename to plugins/saaskit/commands/dryrun.md diff --git a/plugins/saaskit/docs/access-control.md b/plugins/saaskit/docs/access-control.md new file mode 100644 index 0000000..d611d35 --- /dev/null +++ b/plugins/saaskit/docs/access-control.md @@ -0,0 +1,118 @@ +# Access Control + +Scalekit embeds `roles` and `permissions` in the access token. After validating the token, decode it to extract claims and enforce authorization server-side. + +## Workflow + +1. Validate the access token (expiry, signature). +2. Decode to extract `sub`, `oid` (organization ID), `roles`, and `permissions`. +3. Attach an auth context to the request for downstream handlers. +4. Enforce role or permission checks at route boundaries. + +## Token claims + +| Claim | Purpose | +|---|---| +| `sub` | Scalekit user ID | +| `oid` | Organization ID | +| `roles` | User roles in the organization (e.g., `admin`, `member`) | +| `permissions` | Granular permissions (e.g., `projects:read`, `tasks:assign`) | + +## Middleware pattern + +### Node.js (Express) + +```js +const validateAndExtractAuth = async (req, res, next) => { + try { + const accessToken = decrypt(req.cookies.accessToken); + if (!(await scalekit.validateAccessToken(accessToken))) + return res.status(401).json({ error: 'Invalid or expired token' }); + const claims = decodeToken(accessToken); + req.user = { + id: claims.sub, + organizationId: claims.oid, + roles: claims.roles || [], + permissions: claims.permissions || [], + }; + next(); + } catch { return res.status(401).json({ error: 'Authentication failed' }); } +}; + +const requireRole = (role) => (req, res, next) => + req.user.roles.includes(role) + ? next() + : res.status(403).json({ error: `Required role: ${role}` }); + +const requirePermission = (perm) => (req, res, next) => + req.user.permissions.includes(perm) + ? next() + : res.status(403).json({ error: `Required permission: ${perm}` }); + +// Usage +app.get('/api/projects', validateAndExtractAuth, requirePermission('projects:read'), handler); +app.get('/api/admin/users', validateAndExtractAuth, requireRole('admin'), handler); +``` + +### Python (Flask) + +```python +def validate_and_extract_auth(f): + @wraps(f) + def decorated(*args, **kwargs): + access_token = decrypt(request.cookies.get('accessToken')) + if not scalekit_client.validate_access_token(access_token): + return jsonify({'error': 'Invalid or expired token'}), 401 + claims = scalekit_client.decode_access_token(access_token) + request.user = { + 'id': claims.get('sub'), + 'organization_id': claims.get('oid'), + 'roles': claims.get('roles', []), + 'permissions': claims.get('permissions', []), + } + return f(*args, **kwargs) + return decorated + +def require_role(role): + def decorator(f): + @wraps(f) + def decorated(*args, **kwargs): + if role not in getattr(request, 'user', {}).get('roles', []): + return jsonify({'error': f'Required role: {role}'}), 403 + return f(*args, **kwargs) + return decorated + return decorator + +def require_permission(permission): + def decorator(f): + @wraps(f) + def decorated(*args, **kwargs): + if permission not in getattr(request, 'user', {}).get('permissions', []): + return jsonify({'error': f'Required permission: {permission}'}), 403 + return f(*args, **kwargs) + return decorated + return decorator +``` + +## Guidance + +- Use **roles** for broad access tiers (`admin`, `manager`, `member`). +- Use **permissions** for granular actions (`projects:create`, `reports:read`). Follow a `resource:action` naming convention. +- Common pattern: "admin bypass" — admins skip certain permission checks. "Resource ownership" — users can edit only their own resources unless elevated. +- Client-side checks are UX only. Server-side checks are authoritative. +- Always validate the token before trusting any claims. + +## Permission claim fallback + +SDKs check multiple claim keys for compatibility: + +``` +permissions → https://scalekit.com/permissions → scalekit:permissions +``` + +Use the first non-empty value. + +## Related docs + +- [auth-flows.md](auth-flows.md) — How tokens are obtained. +- [sessions.md](sessions.md) — Token validation and refresh middleware. diff --git a/plugins/saaskit/docs/api-auth.md b/plugins/saaskit/docs/api-auth.md new file mode 100644 index 0000000..9aedee7 --- /dev/null +++ b/plugins/saaskit/docs/api-auth.md @@ -0,0 +1,161 @@ +# API Auth + +Two mechanisms for authenticating API requests: opaque **API keys** for simple bearer auth, and **OAuth 2.0 client credentials** for M2M (machine-to-machine) JWT auth. + +## API keys + +Long-lived opaque tokens scoped to an organization or user. Scalekit manages creation, validation, and revocation. + +### Create + +```js +// Node.js — org-scoped +const { token, tokenId } = await scalekit.token.createToken(organizationId, { + description: 'CI/CD pipeline token', +}); +// token is the plain-text key — shown only once. tokenId is for lifecycle ops. +``` + +```python +# Python — user-scoped with custom claims +response = scalekit_client.tokens.create_token( + organization_id=org_id, + user_id='usr_12345', + custom_claims={'team': 'engineering', 'environment': 'production'}, + description='Deployment token', +) +opaque_token = response.token +token_id = response.token_id +``` + +The plain-text key is **returned only once at creation**. Store it securely — Scalekit never stores it. + +### Validate + +Call on every incoming API request. Returns org/user context on success, throws on invalid or revoked keys. + +```js +// Node.js +const result = await scalekit.token.validateToken(bearerToken); +const { organizationId, userId, customClaims } = result.tokenInfo; +``` + +```python +# Python +result = scalekit_client.tokens.validate_token(token=bearer_token) +org_id = result.token_info.organization_id +user_id = result.token_info.user_id # empty for org-scoped keys +``` + +### Revoke + +Instant and idempotent — safe to call on already-revoked keys. + +```js +await scalekit.token.invalidateToken(tokenOrTokenId); +``` + +```python +scalekit_client.tokens.invalidate_token(token=token_or_token_id) +``` + +### Middleware pattern + +```js +// Node.js (Express) +async function authenticateToken(req, res, next) { + const token = (req.headers.authorization || '').replace('Bearer ', ''); + if (!token) return res.status(401).json({ error: 'Missing token' }); + try { + const result = await scalekit.token.validateToken(token); + req.tokenInfo = result.tokenInfo; + next(); + } catch { return res.status(401).json({ error: 'Invalid or expired token' }); } +} +``` + +```python +# Python (Flask) +def authenticate_token(f): + @wraps(f) + def wrapper(*args, **kwargs): + auth = request.headers.get('Authorization', '') + if not auth.startswith('Bearer '): + return jsonify({'error': 'Missing token'}), 401 + try: + result = scalekit_client.tokens.validate_token(token=auth.split(' ', 1)[1]) + g.token_info = result.token_info + except Exception: + return jsonify({'error': 'Invalid or expired token'}), 401 + return f(*args, **kwargs) + return wrapper +``` + +### Key rules + +- **Show `token` once** — display at creation, then discard. +- **Use `token_id`** for list/revoke operations (not the key itself). +- **Rotate safely** — create new → update consumer → verify → revoke old. + +## OAuth 2.0 client credentials (M2M) + +For service-to-service auth. Register an API client, issue `client_id` + `client_secret`, and the client fetches short-lived JWTs from Scalekit's token endpoint. + +### Register a client + +```python +from scalekit.v1.clients.clients_pb2 import OrganizationClient + +response = scalekit_client.m2m_client.create_organization_client( + organization_id='', + m2m_client=OrganizationClient( + name='GitHub Actions', + scopes=['deploy:applications', 'read:deployments'], + audience=['deployment-api.acmecorp.com'], + expiry=3600, + ) +) +client_id = response.client.client_id +plain_secret = response.plain_secret # shown once +``` + +### Client fetches a token + +```bash +curl -X POST "$SCALEKIT_ENVIRONMENT_URL/oauth/token" \ + -d "grant_type=client_credentials" \ + -d "client_id=" \ + -d "client_secret=" +``` + +Returns a JWT with `scopes`, `oid` (org ID), `iss`, and `exp`. + +### Validate and enforce scopes + +```python +# Python — SDK handles JWKS automatically +claims = scalekit_client.validate_access_token_and_get_claims(token=bearer_token) +if required_scope not in claims.get('scopes', []): + return 403 +``` + +```js +// Node.js — manual JWKS +import jwksClient from 'jwks-rsa'; +import jwt from 'jsonwebtoken'; + +const jwks = jwksClient({ jwksUri: `${ENV_URL}/.well-known/jwks.json`, cache: true }); +const decoded = jwt.decode(token, { complete: true }); +const key = await jwks.getSigningKey(decoded.header.kid); +const payload = jwt.verify(token, key.getPublicKey(), { algorithms: ['RS256'] }); +if (!payload.scopes.includes(requiredScope)) return res.status(403).end(); +``` + +### Scope naming + +Use `resource:action` convention: `deployments:read`, `applications:create`. + +## Related docs + +- [mcp-server-auth.md](mcp-server-auth.md) — OAuth 2.1 for MCP servers. +- [access-control.md](access-control.md) — Role/permission checks for user-facing auth. diff --git a/plugins/saaskit/docs/auth-flows.md b/plugins/saaskit/docs/auth-flows.md new file mode 100644 index 0000000..734185e --- /dev/null +++ b/plugins/saaskit/docs/auth-flows.md @@ -0,0 +1,124 @@ +# Auth Flows + +SaaSKit implements OIDC/OAuth 2.0 authorization code flow. The app redirects the user to Scalekit, receives an authorization code on callback, and exchanges it for tokens. + +## Environment setup + +```sh +SCALEKIT_ENVIRONMENT_URL= +SCALEKIT_CLIENT_ID= +SCALEKIT_CLIENT_SECRET= +``` + +## Login redirect + +Generate an authorization URL and redirect the user's browser: + +```js +// Node.js +const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, { + scopes: ['openid', 'profile', 'email', 'offline_access'] +}); +res.redirect(authorizationUrl); +``` + +```python +# Python +options = AuthorizationUrlOptions() +options.scopes = ['openid', 'profile', 'email', 'offline_access'] +auth_url = scalekit_client.get_authorization_url(redirect_uri, options=options) +``` + +`redirectUri` must exactly match the callback URL registered in the Scalekit dashboard. Include `offline_access` to receive a refresh token. + +## Dedicated signup + +To force the signup UI instead of login, add `prompt: 'create'` to the authorization URL options. + +## Callback and token exchange + +Exchange the authorization code for tokens: + +```js +// Node.js +const { user, idToken, accessToken, refreshToken } = + await scalekit.authenticateWithCode(code, redirectUri); +``` + +```python +# Python +result = scalekit_client.authenticate_with_code(code, redirect_uri) +access_token = result.get('access_token') +refresh_token = result.get('refresh_token') +``` + +### Token purposes + +| Token | Purpose | Lifetime | +|---|---|---| +| `accessToken` | Roles, permissions, org context. Used for API authorization. | ~5 min (configurable) | +| `refreshToken` | Renew access tokens without re-authentication. | Long-lived | +| `idToken` | User profile claims (sub, email, name). Used for logout. | ~15 min | + +## Token refresh + +When the access token expires, use the refresh token to obtain new tokens: + +```js +// Node.js +const refreshed = await scalekit.refreshAccessToken(refreshToken); +``` + +```python +# Python +refreshed = scalekit_client.refresh_access_token(refresh_token) +``` + +If refresh fails (e.g., `invalid_grant`), the user must re-authenticate. See [sessions.md](sessions.md) for middleware patterns that handle this transparently. + +## Logout + +Logout requires two steps: clear your application session, then redirect the browser to Scalekit's OIDC end-session endpoint. + +**Key constraints:** +- The logout call **must** be a browser redirect (top-level navigation), not a `fetch`/XHR. +- Read `idToken` **before** clearing cookies — it's used as `id_token_hint`. +- `post_logout_redirect_uri` must be allowlisted in the Scalekit dashboard. + +```js +// Node.js +const idTokenHint = req.cookies?.idToken; // read BEFORE clearing +const logoutUrl = scalekit.getLogoutUrl(idTokenHint, postLogoutRedirectUri); +res.clearCookie('accessToken', { path: '/' }); +res.clearCookie('refreshToken', { path: '/' }); +res.redirect(logoutUrl); +``` + +```python +# Python (Flask) +id_token = request.cookies.get("idToken") +logout_url = scalekit_client.get_logout_url( + id_token_hint=id_token, + post_logout_redirect_uri=post_logout_redirect_uri +) +resp = make_response(redirect(logout_url)) +resp.set_cookie("accessToken", "", max_age=0, path="/") +resp.set_cookie("refreshToken", "", max_age=0, path="/") +return resp +``` + +## Cross-language SDK reference + +| Operation | Node.js | Python | Go | Java | +|---|---|---|---|---| +| Auth URL | `getAuthorizationUrl` | `get_authorization_url` | `GetAuthorizationUrl` | `getAuthorizationUrl` | +| Exchange code | `authenticateWithCode` | `authenticate_with_code` | `AuthenticateWithCode` | `authenticateWithCode` | +| Validate token | `validateAccessToken` | `validate_access_token` | `ValidateAccessToken` | `validateAccessToken` | +| Refresh token | `refreshAccessToken` | `refresh_access_token` | `RefreshAccessToken` | `refreshToken` | +| Logout URL | `getLogoutUrl` | `get_logout_url` | `GetLogoutUrl` | `getLogoutUrl` | + +## Related docs + +- [sessions.md](sessions.md) — Storing tokens, refresh middleware, remote revocation. +- [access-control.md](access-control.md) — Using token claims for authorization. +- [sso.md](sso.md) — Enterprise SSO flows that build on the same auth flow. diff --git a/plugins/saaskit/docs/frameworks/go.md b/plugins/saaskit/docs/frameworks/go.md new file mode 100644 index 0000000..8ffcf79 --- /dev/null +++ b/plugins/saaskit/docs/frameworks/go.md @@ -0,0 +1,161 @@ +# Go (Gin) + +SaaSKit integration for Go using `scalekit-sdk-go/v2` with the Gin framework. + +Reference: [scalekit-inc/coffee-desk-demo](https://github.com/scalekit-inc/coffee-desk-demo) + +## Dependencies + +```bash +go get github.com/scalekit-inc/scalekit-sdk-go/v2 +go get github.com/gin-gonic/gin +go get github.com/gin-contrib/cors +go get github.com/golang-jwt/jwt/v5 +``` + +## Environment + +```bash +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.dev +SCALEKIT_CLIENT_ID=your_client_id +SCALEKIT_CLIENT_SECRET=your_client_secret +``` + +## Client initialization + +Use `sync.Once` to create a single client instance: + +```go +var ( + globalClient scalekit.Scalekit + clientOnce sync.Once +) + +func GetScaleKitClient() scalekit.Scalekit { + clientOnce.Do(func() { + globalClient = scalekit.NewScalekitClient( + os.Getenv("SCALEKIT_ENVIRONMENT_URL"), + os.Getenv("SCALEKIT_CLIENT_ID"), + os.Getenv("SCALEKIT_CLIENT_SECRET"), + ) + }) + return globalClient +} +``` + +## Auth flow + +### Authorize + +```go +func AuthorizeHandler(c *gin.Context) { + sc := GetScaleKitClient() + opts := scalekit.AuthorizationUrlOptions{ + State: base64EncodedState, // includes CSRF token + next URL + Scopes: []string{"openid", "profile", "email", "offline_access"}, + } + if v := c.Query("organization_id"); v != "" { opts.OrganizationId = v } + authURL, _ := sc.GetAuthorizationUrl(callbackURL(c), opts) + c.Redirect(http.StatusFound, authURL.String()) +} +``` + +### Callback + +```go +func CallbackHandler(c *gin.Context) { + sc := GetScaleKitClient() + resp, _ := sc.AuthenticateWithCode(c.Request.Context(), c.Query("code"), callbackURL(c), + scalekit.AuthenticationOptions{}) + c.SetCookie("auth_access_token", resp.AccessToken, 86400, "/", "", false, true) + c.SetCookie("auth_refresh_token", resp.RefreshToken, 2592000, "/", "", false, true) + c.SetCookie("id_token", resp.IdToken, 86400, "/", "", false, false) + // Route based on xoid claim: present → /dashboard, absent → /onboarding +} +``` + +### Session validation and refresh + +```go +func SessionHandler(c *gin.Context) { + c.Header("Cache-Control", "no-store") + accessToken, _ := c.Cookie("auth_access_token") + sc := GetScaleKitClient() + valid, _ := sc.ValidateAccessToken(c.Request.Context(), accessToken) + if !valid { + refreshToken, _ := c.Cookie("auth_refresh_token") + refreshed, err := sc.RefreshAccessToken(c.Request.Context(), refreshToken) + if err != nil { LogoutHandler(c); return } + c.SetCookie("auth_access_token", refreshed.AccessToken, 86400, "/", "", false, true) + accessToken = refreshed.AccessToken + } + claims, _ := decodeJWTPayload(accessToken) + // Return user info from claims +} +``` + +### Logout + +```go +func LogoutHandler(c *gin.Context) { + idToken, _ := c.Cookie("id_token") + sc := GetScaleKitClient() + logoutURL, _ := sc.GetLogoutUrl(scalekit.LogoutUrlOptions{ + IdTokenHint: idToken, PostLogoutRedirectUri: getUIBaseURL(c), + }) + c.SetCookie("auth_access_token", "", -1, "/", "", false, true) + c.SetCookie("auth_refresh_token", "", -1, "/", "", false, true) + c.SetCookie("id_token", "", -1, "/", "", false, false) + c.Redirect(http.StatusFound, logoutURL.String()) +} +``` + +### IdP-initiated login + +```go +func IdpInitiatedLoginHandler(c *gin.Context) { + sc := GetScaleKitClient() + claims, _ := sc.GetIdpInitiatedLoginClaims(c.Request.Context(), c.Query("idp_initiated_login")) + opts := scalekit.AuthorizationUrlOptions{ + Scopes: []string{"openid", "profile", "email", "offline_access"}, + } + if claims.OrganizationID != "" { opts.OrganizationId = claims.OrganizationID } + if claims.ConnectionID != "" { opts.ConnectionId = claims.ConnectionID } + authURL, _ := sc.GetAuthorizationUrl(callbackURL(c), opts) + c.Redirect(http.StatusFound, authURL.String()) +} +``` + +## JWT claims + +| Claim | Meaning | +|---|---| +| `sub` | Scalekit user ID | +| `xoid` | External org ID. Absent = user has no org → route to onboarding. | +| `xuid` | App's user DB ID. Absent = create user locally. | +| `permissions` | User permissions in org | +| `roles` | User roles in org | + +## Route registration + +```go +api := r.Group("/api") +api.GET("/authorize", AuthorizeHandler) +api.GET("/login/initiate", IdpInitiatedLoginHandler) +api.GET("/scalekit/callback", CallbackHandler) +api.GET("/session", SessionHandler) +api.GET("/logout", LogoutHandler) +``` + +## Tactics + +- **SameSite=Lax** — Gin's `c.SetCookie` doesn't expose SameSite. Use `http.SetCookie` directly with `SameSite: http.SameSiteLaxMode`. +- **Secure flag** — detect localhost at runtime: `!strings.Contains(c.Request.Host, "localhost")`. +- **CORS** — `AllowCredentials: true` is required for cookie-based auth. +- **Token refresh race** — use a per-user mutex or treat `invalid_grant` as session expiry. +- **JSON clients** — return `401` for `Accept: application/json`, redirect otherwise. + +## Related docs + +- [auth-flows.md](../auth-flows.md) — Framework-agnostic auth flow reference. +- [sessions.md](../sessions.md) — Token storage patterns. diff --git a/plugins/saaskit/docs/frameworks/laravel.md b/plugins/saaskit/docs/frameworks/laravel.md new file mode 100644 index 0000000..2646a17 --- /dev/null +++ b/plugins/saaskit/docs/frameworks/laravel.md @@ -0,0 +1,127 @@ +# Laravel + +SaaSKit integration for Laravel using raw HTTP calls. There is no official Scalekit PHP SDK — the app uses Laravel's `Http` facade with `client_id`/`client_secret` Basic Auth. + +Reference: [scalekit-inc/scalekit-laravel-auth-example](https://github.com/scalekit-inc/scalekit-laravel-auth-example) + +## Project structure + +``` +app/Services/ScalekitClient.php # Raw HTTP OAuth client +app/Http/Controllers/AuthController.php +app/Http/Middleware/ +├── ScalekitAuth.php # Session auth gate +├── ScalekitPermission.php # Per-route permission check +└── ScalekitTokenRefresh.php # Auto token refresh +config/scalekit.php # Reads from env +routes/web.php # Named routes + middleware groups +``` + +## Environment + +```env +SCALEKIT_ENV_URL=https://your-env.scalekit.io +SCALEKIT_CLIENT_ID=your-client-id +SCALEKIT_CLIENT_SECRET=your-client-secret +SCALEKIT_REDIRECT_URI=http://localhost:8000/auth/callback +``` + +Scopes are hardcoded in `config/scalekit.php`: `openid profile email offline_access`. + +## ScalekitClient (raw HTTP) + +Key methods and their HTTP calls: + +| Method | HTTP call | +|---|---| +| `getAuthorizationUrl($state)` | Builds `{env_url}/oauth/authorize?...` | +| `exchangeCodeForTokens($code)` | `POST {env_url}/oauth/token` with Basic Auth | +| `refreshAccessToken($rt)` | `POST {env_url}/oauth/token` with Basic Auth | +| `validateTokenAndGetClaims($token)` | Base64 JWT decode (no signature verification) | +| `logout($accessToken)` | Builds `{env_url}/oidc/logout?...` | + +JWT decode pattern: + +```php +$parts = explode('.', $token); +$payload = base64_decode(strtr($parts[1], '-_', '+/')); +$claims = json_decode($payload, true); +``` + +Permission claim fallback: + +```php +$permissions = $claims['permissions'] + ?? $claims['https://scalekit.com/permissions'] + ?? $claims['scalekit:permissions'] + ?? []; +``` + +## Session storage + +All auth state in Laravel's session — no extra DB tables: + +```php +session(['scalekit_user' => [...], 'scalekit_tokens' => [...], + 'scalekit_roles' => [...], 'scalekit_permissions' => [...]]); +``` + +## Middleware registration + +Laravel 11 (`bootstrap/app.php`): + +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->alias([ + 'scalekit.auth' => ScalekitAuth::class, + 'scalekit.permission' => ScalekitPermission::class, + ]); + $middleware->append(ScalekitTokenRefresh::class); +}) +``` + +## Routes + +```php +Route::middleware(['scalekit.auth'])->group(function () { + Route::get('/dashboard', [AuthController::class, 'dashboard']); + Route::get('/organization/settings', [AuthController::class, 'organizationSettings']) + ->middleware('scalekit.permission:organization:settings'); +}); +``` + +Permission middleware uses colon-separated syntax: `scalekit.permission:organization:settings`. + +## Auth flow + +**Login** — generates CSRF state, stores in session, builds auth URL, renders login template. + +**Callback** — validates state, exchanges code via `Http::asForm()->withBasicAuth(...)`, decodes ID token for profile, gets access token claims for roles/permissions, writes session, redirects to dashboard. + +**Logout** — reads access token, clears session with `session()->flush()`, redirects to Scalekit's `/oidc/logout`. + +**Token refresh** — `ScalekitTokenRefresh` middleware runs on every request (skips login/callback/logout paths). Buffer: 5 minutes. On `invalid_grant`, flushes session. + +## Tactics + +- **SameSite=Lax** in `config/session.php` — `strict` breaks OAuth callbacks. +- **No CSRF exclusion needed** for the callback — it's a GET request. +- **Deep link preservation** — `ScalekitAuth` middleware passes `->with('next', $request->path())`. Read in `login()` with `$request->query('next')`. +- **Session fixation** — call `session()->regenerate()` after writing session data in `callback()`. +- **Cache-Control: no-store** — add `->header('Cache-Control', 'no-store')` to protected responses. +- **AJAX** — update `ScalekitAuth` to return `401` for `$request->expectsJson()`. +- **CORS** — configure `config/cors.php` with `supports_credentials => true` and explicit origins. + +## Install + +```bash +composer require firebase/php-jwt # optional, for JWT signature verification +php artisan key:generate +php artisan migrate +php artisan serve +``` + +## Related docs + +- [auth-flows.md](../auth-flows.md) — Framework-agnostic auth flow reference. +- [sessions.md](../sessions.md) — Token storage patterns. diff --git a/plugins/saaskit/docs/frameworks/nextjs.md b/plugins/saaskit/docs/frameworks/nextjs.md new file mode 100644 index 0000000..ce3fe59 --- /dev/null +++ b/plugins/saaskit/docs/frameworks/nextjs.md @@ -0,0 +1,114 @@ +# Next.js (App Router) + +SaaSKit integration for Next.js using `@scalekit-sdk/node` with the App Router. + +Reference: [scalekit-inc/scalekit-nextjs-auth-example](https://github.com/scalekit-inc/scalekit-nextjs-auth-example) + +## Project structure + +``` +app/api/auth/ +├── login/route.ts # GET — auth URL + CSRF state +├── callback/route.ts # GET — code exchange, set session cookie +├── logout/route.ts # POST — clear session, return Scalekit logout URL +├── refresh/route.ts # POST — refresh access token +└── validate/route.ts # Token validation + +lib/ +├── scalekit.ts # Singleton client + default scopes +├── cookies.ts # Session read/write/clear + OAuth state +└── auth.ts # isAuthenticated(), getCurrentUser(), hasPermission() +``` + +## Environment + +```env +SCALEKIT_ENV_URL=https://your-env.scalekit.io +SCALEKIT_CLIENT_ID=your-client-id +SCALEKIT_CLIENT_SECRET=your-client-secret +SCALEKIT_REDIRECT_URI=http://localhost:3000/auth/callback +NEXT_PUBLIC_APP_URL=http://localhost:3000 +``` + +## Session shape + +Single `scalekit_session` HttpOnly cookie containing JSON: + +```ts +interface SessionData { + user: { sub, email, name, given_name, family_name, preferred_username }; + tokens: { access_token, refresh_token, id_token, expires_at, expires_in }; + roles?: string[]; + permissions?: string[]; +} +``` + +Cookie config: `httpOnly: true`, `secure` in production, `sameSite: 'lax'`, `path: '/'`. + +## Auth flow + +**Login** — `GET /api/auth/login` generates a CSRF state, stores it in a cookie, and returns `{ authUrl }`. The client must do a full page navigation: `window.location.href = authUrl`. + +**Callback** — `GET /api/auth/callback` validates the state parameter, exchanges the code, validates the access token to extract roles/permissions, writes the session cookie, and redirects to `/dashboard`. + +**Logout** — `POST /api/auth/logout` builds the Scalekit logout URL using the session's `id_token`, clears the session cookie, and returns `{ logoutUrl }`. The client navigates to it: `window.location.href = logoutUrl`. + +**Refresh** — `POST /api/auth/refresh` refreshes the access token using the stored refresh token and updates the session cookie. + +## Protecting routes + +### Edge middleware + +```ts +// middleware.ts (project root) +import { NextResponse } from 'next/server'; +import type { NextRequest } from 'next/server'; + +const PROTECTED = ['/dashboard', '/sessions', '/organization']; + +export function middleware(request: NextRequest) { + const session = request.cookies.get('scalekit_session'); + const isProtected = PROTECTED.some(p => request.nextUrl.pathname.startsWith(p)); + if (isProtected && !session) { + const loginUrl = new URL('/login', request.url); + loginUrl.searchParams.set('next', request.nextUrl.pathname); + return NextResponse.redirect(loginUrl); + } + return NextResponse.next(); +} + +export const config = { matcher: ['/((?!_next|api|favicon).*)'] }; +``` + +### Server Components + +```ts +import { isAuthenticated, getCurrentUser, hasPermission } from '@/lib/auth'; +import { redirect } from 'next/navigation'; + +const authed = await isAuthenticated(); +if (!authed) redirect('/login'); + +const allowed = await hasPermission('org:admin'); +if (!allowed) redirect('/permission-denied'); +``` + +## Tactics + +- **SameSite=Lax** — never Strict. Strict drops the cookie on the OAuth redirect from Scalekit, breaking state validation. +- **Full page navigation for OAuth** — `window.location.href`, not `router.push`. OAuth requires a top-level redirect. +- **Deep link preservation** — store `?next` in session state, validate it's a relative path to prevent open redirect. +- **Cache-Control: no-store** — use `export const dynamic = 'force-dynamic'` on protected pages to prevent cached authenticated pages after logout. +- **Token refresh race condition** — multiple tabs can attempt concurrent refresh. Use a short-lived `refresh_in_progress` flag in the session. + +## Dependencies + +```bash +npm install @scalekit-sdk/node jose date-fns js-cookie +``` + +## Related docs + +- [auth-flows.md](../auth-flows.md) — Framework-agnostic auth flow reference. +- [sessions.md](../sessions.md) — Token storage patterns. +- [access-control.md](../access-control.md) — Permission checks. diff --git a/plugins/saaskit/docs/frameworks/python.md b/plugins/saaskit/docs/frameworks/python.md new file mode 100644 index 0000000..c3dd6b7 --- /dev/null +++ b/plugins/saaskit/docs/frameworks/python.md @@ -0,0 +1,166 @@ +# Python Frameworks + +SaaSKit integration for Django, FastAPI, and Flask. All three use `scalekit-sdk-python` (or `scalekit` on PyPI) and share the same SDK methods, but differ in session handling and middleware patterns. + +## Common setup + +```bash +pip install scalekit-sdk-python python-dotenv +``` + +```env +SCALEKIT_ENV_URL=https://your-env.scalekit.com +SCALEKIT_CLIENT_ID=your_client_id +SCALEKIT_CLIENT_SECRET=your_client_secret +SCALEKIT_REDIRECT_URI=http://localhost:8000/auth/callback +``` + +Include `offline_access` in scopes to receive a refresh token. + +### SDK client wrapper + +All frameworks use the same SDK under the hood: + +```python +from scalekit import ScalekitClient + +client = ScalekitClient( + env_url=os.getenv('SCALEKIT_ENV_URL'), + client_id=os.getenv('SCALEKIT_CLIENT_ID'), + client_secret=os.getenv('SCALEKIT_CLIENT_SECRET'), +) +``` + +### Session data schema + +All three frameworks store the same keys in the session: + +| Key | Contents | +|---|---| +| `scalekit_user` | `sub`, `email`, `name`, `given_name`, `family_name` | +| `scalekit_tokens` | `access_token`, `refresh_token`, `id_token`, `expires_at`, `expires_in` | +| `scalekit_roles` | `['admin', ...]` | +| `scalekit_permissions` | `['organization:settings', ...]` | + +--- + +## Django + +Reference: [scalekit-inc/scalekit-django-auth-example](https://github.com/scalekit-inc/scalekit-django-auth-example) + +### Key settings + +```python +MIDDLEWARE = [ + 'django.contrib.sessions.middleware.SessionMiddleware', + 'auth_app.middleware.ScalekitTokenRefreshMiddleware', # after SessionMiddleware +] +SESSION_COOKIE_SAMESITE = 'Lax' # never Strict +SESSION_SAVE_EVERY_REQUEST = True # ensures OAuth state survives redirects +``` + +### Route protection + +```python +from auth_app.decorators import login_required, permission_required + +@login_required +def dashboard_view(request): ... + +@permission_required('organization:settings') +def org_settings_view(request): ... +``` + +`@login_required` appends `?next=` for deep link preservation. Session fixation: call `request.session.cycle_key()` after writing session data in the callback. + +### Token refresh + +`ScalekitTokenRefreshMiddleware` runs on every request (skips `/login`, `/auth/callback`, `/logout`). Buffer: 1 minute before expiry. + +--- + +## FastAPI + +Reference: [scalekit-inc/scalekit-fastapi-auth-example](https://github.com/scalekit-inc/scalekit-fastapi-auth-example) + +### App setup + +```python +from starlette.middleware.sessions import SessionMiddleware + +app = FastAPI() +app.add_middleware(ScalekitTokenRefreshMiddleware) # add first (runs after session) +app.add_middleware(SessionMiddleware, secret_key=settings.secret_key, + max_age=3600, same_site='lax', https_only=False) +``` + +Middleware registration order matters in Starlette: middleware added later wraps earlier ones and executes first. `SessionMiddleware` must run before `ScalekitTokenRefreshMiddleware`. + +### Route protection + +```python +from app.dependencies import require_login, require_permission + +@router.get("/dashboard") +async def dashboard(request: Request, user: dict = Depends(require_login)): + return {"user": user} + +@router.get("/admin/settings") +async def admin(request: Request, user: dict = Depends(require_permission('organization:settings'))): + return {"message": "Authorized"} +``` + +### Token refresh + +`ScalekitTokenRefreshMiddleware` auto-refreshes 5 minutes before expiry. On `invalid_grant`, clear the session to force re-login. + +--- + +## Flask + +Reference: [scalekit-inc/scalekit-flask-auth-example](https://github.com/scalekit-inc/scalekit-flask-auth-example) + +### App factory + +```python +def create_app(): + app = Flask(__name__) + app.config['SESSION_COOKIE_SAMESITE'] = 'Lax' + app.config['SESSION_COOKIE_HTTPONLY'] = True + app.before_request(TokenRefreshMiddleware.process_request) + return app +``` + +`ScalekitClient` must be instantiated inside a request context (not at module level) because it reads from `current_app.config`. + +### Route protection + +```python +from auth_app.decorators import login_required, permission_required + +@auth_bp.route('/dashboard') +@login_required +def dashboard(): ... + +@auth_bp.route('/organization/settings') +@permission_required('organization:settings') +def org_settings(): ... +``` + +### Token refresh + +`TokenRefreshMiddleware` runs as a `before_request` hook. Buffer: 5 minutes. On `invalid_grant`, clears the session. + +--- + +## Cross-framework notes + +- **SameSite=Lax** is required for all three. `Strict` drops the session cookie on the OAuth redirect from Scalekit, breaking the CSRF state check. +- **AJAX clients** expect `401`, not `302`. Detect `Accept: application/json` in auth guards and return JSON errors. +- **Cache-Control: no-store** on protected responses prevents the browser back button from serving cached authenticated pages after logout. +- **Permission claim fallback chain**: `permissions` → `https://scalekit.com/permissions` → `scalekit:permissions`. + +## Related docs + +- [auth-flows.md](../auth-flows.md) — Framework-agnostic auth flow reference. +- [sessions.md](../sessions.md) — Token storage and refresh patterns. diff --git a/plugins/saaskit/docs/frameworks/springboot.md b/plugins/saaskit/docs/frameworks/springboot.md new file mode 100644 index 0000000..f7f3be5 --- /dev/null +++ b/plugins/saaskit/docs/frameworks/springboot.md @@ -0,0 +1,136 @@ +# Spring Boot + +SaaSKit integration for Spring Boot 3.x using `spring-boot-starter-oauth2-client` and `scalekit-sdk-java`. + +Reference: [scalekit-inc/scalekit-springboot-auth-example](https://github.com/scalekit-inc/scalekit-springboot-auth-example) + +## Dependencies + +```xml + + org.springframework.boot + spring-boot-starter-oauth2-client + + + org.springframework.boot + spring-boot-starter-security + + + com.scalekit + scalekit-sdk-java + 2.0.4 + +``` + +Requires Spring Boot 3.2+ and Java 17+. + +## Configuration + +`application.yml`: + +```yaml +scalekit: + env-url: ${SCALEKIT_ENV_URL} + client-id: ${SCALEKIT_CLIENT_ID} + client-secret: ${SCALEKIT_CLIENT_SECRET} + redirect-uri: ${SCALEKIT_REDIRECT_URI:http://localhost:8080/login/oauth2/code/scalekit} + +spring: + security: + oauth2: + client: + registration: + scalekit: + client-id: ${scalekit.client-id} + client-secret: ${scalekit.client-secret} + authorization-grant-type: authorization_code + redirect-uri: ${scalekit.redirect-uri} + scope: openid,profile,email,offline_access + provider: + scalekit: + issuer-uri: ${scalekit.env-url} + authorization-uri: ${scalekit.env-url}/oauth/authorize + token-uri: ${scalekit.env-url}/oauth/token + jwk-set-uri: ${scalekit.env-url}/keys + user-name-attribute: sub + +server: + servlet: + session: + cookie: + same-site: lax + http-only: true + secure: true +``` + +## Scalekit SDK bean + +```java +@Configuration +public class ScalekitConfig { + @Value("${scalekit.env-url}") private String envUrl; + @Value("${scalekit.client-id}") private String clientId; + @Value("${scalekit.client-secret}") private String clientSecret; + + @Bean + public ScalekitClient scalekitClient() { + return new ScalekitClient(envUrl, clientId, clientSecret); + } +} +``` + +## Security filter chain + +Spring Security's `oauth2-client` handles the full authorization code flow automatically: + +```java +@Bean +public SecurityFilterChain filterChain(HttpSecurity http, + ClientRegistrationRepository repo) throws Exception { + http + .authorizeHttpRequests(authz -> authz + .requestMatchers("/", "/login", "/error", "/css/**", "/js/**").permitAll() + .anyRequest().authenticated()) + .oauth2Login(oauth2 -> oauth2 + .loginPage("/login") + .defaultSuccessUrl("/dashboard")) + .logout(logout -> logout + .logoutSuccessHandler(oidcLogoutHandler(repo)) + .invalidateHttpSession(true) + .clearAuthentication(true)); + return http.build(); +} + +private LogoutSuccessHandler oidcLogoutHandler(ClientRegistrationRepository repo) { + var handler = new OidcClientInitiatedLogoutSuccessHandler(repo); + handler.setPostLogoutRedirectUri("{baseUrl}"); + return handler; +} +``` + +Always use `OidcClientInitiatedLogoutSuccessHandler` — a plain `logoutSuccessUrl` only clears the local session, leaving the IdP session active. + +## Accessing user identity + +```java +@GetMapping("/dashboard") +public String dashboard(@AuthenticationPrincipal OidcUser user, Model model) { + model.addAttribute("name", user.getFullName()); + model.addAttribute("email", user.getEmail()); + model.addAttribute("claims", user.getClaims()); + return "dashboard"; +} +``` + +## Tactics + +- **SameSite=Lax** — set in `application.yml`. Without it, the session cookie is dropped on the OAuth redirect from Scalekit. +- **Deep links** — use `.defaultSuccessUrl("/dashboard")` without `true` to respect the saved request URL. +- **CORS** — add `CorsConfigurationSource` bean with `setAllowCredentials(true)` for browser clients. +- **AJAX** — override the authentication entry point to return `401` for `Accept: application/json`. +- **Cache-Control** — add `no-store` header on protected responses. +- **CSRF** — Spring Security disables CSRF for OAuth2 endpoints automatically; the `state` parameter serves as the CSRF token. + +## Related docs + +- [auth-flows.md](../auth-flows.md) — Framework-agnostic auth flow reference. diff --git a/plugins/saaskit/docs/index.md b/plugins/saaskit/docs/index.md new file mode 100644 index 0000000..37a6c3a --- /dev/null +++ b/plugins/saaskit/docs/index.md @@ -0,0 +1,46 @@ +# SaaSKit Docs + +This `docs/` directory is the canonical documentation layer for the `saaskit` plugin. + +SaaSKit (formerly Full-Stack Auth) is Scalekit's end-to-end authentication and authorization toolkit for SaaS applications. It covers the complete lifecycle: login, sessions, access control, SSO, SCIM provisioning, API auth, and MCP server auth. + +## Official Scalekit docs + +- [LLM docs map](https://docs.scalekit.com/llms.txt) +- [Docs sitemap](https://docs.scalekit.com/sitemap-0.xml) +- [Authentication overview](https://docs.scalekit.com/authentication) +- [SSO integrations](https://docs.scalekit.com/guides/integrations/sso-integrations/) +- [SCIM quickstart](https://docs.scalekit.com/directory/scim/quickstart/) +- [MCP authentication](https://docs.scalekit.com/guides/mcp/) +- [API references](https://docs.scalekit.com/apis/) + +## How this directory is organized + +Core concepts: + +- [auth-flows.md](auth-flows.md) — Login, signup, callback, token exchange, logout, and token refresh. +- [sessions.md](sessions.md) — Secure cookie storage, token separation, encryption, refresh middleware, remote revocation. +- [access-control.md](access-control.md) — Decoding access tokens for roles/permissions, middleware guards. + +Enterprise features: + +- [sso.md](sso.md) — Modular SSO flow, IdP-initiated login, admin portal embed. +- [scim.md](scim.md) — Directory sync webhooks, user lifecycle events, group sync. + +API and machine auth: + +- [api-auth.md](api-auth.md) — API key creation/validation/revocation, OAuth 2.0 client credentials for M2M. +- [mcp-server-auth.md](mcp-server-auth.md) — OAuth 2.1 for MCP servers, discovery endpoints, token validation. + +Framework guides: + +- [frameworks/nextjs.md](frameworks/nextjs.md) — Next.js App Router integration. +- [frameworks/python.md](frameworks/python.md) — Django, FastAPI, and Flask integration. +- [frameworks/go.md](frameworks/go.md) — Go/Gin integration. +- [frameworks/springboot.md](frameworks/springboot.md) — Spring Boot 3.x integration. +- [frameworks/laravel.md](frameworks/laravel.md) — Laravel integration (raw HTTP, no PHP SDK). + +## Relationship to skills and rules + +- `skills/` contains task-oriented workflows that should stay thin and point here for deeper reference. +- These docs extract the durable, canonical knowledge from skill files into a navigable reference layer. diff --git a/plugins/saaskit/docs/mcp-server-auth.md b/plugins/saaskit/docs/mcp-server-auth.md new file mode 100644 index 0000000..6339323 --- /dev/null +++ b/plugins/saaskit/docs/mcp-server-auth.md @@ -0,0 +1,153 @@ +# MCP Server Auth + +Secure MCP (Model Context Protocol) servers with OAuth 2.1 using Scalekit as the authorization server. This enables authenticated access from AI hosts like Claude Desktop, Cursor, and VS Code. + +## Prerequisites + +MCP OAuth requires **Streamable HTTP** transport. Stdio transport does not support OAuth. If your server uses stdio, migrate to HTTP first. + +```js +// Node.js — required transport +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +``` + +```python +# Python — Streamable HTTP via ASGI app +from mcp.server.fastmcp import FastMCP +mcp = FastMCP("My Server") +app = mcp.streamable_http_app(path="/mcp") +# Run with: uvicorn module:app --host 0.0.0.0 --port 8000 +``` + +## Flow + +1. MCP client sends a request — server returns `401` with `WWW-Authenticate` header. +2. Client reads the resource metadata endpoint to discover the authorization server. +3. Client completes OAuth 2.1 flow with Scalekit, obtains a bearer token. +4. Client retries with `Authorization: Bearer ` — server validates and processes. + +## Dashboard setup + +1. **MCP Servers > Add MCP Server** — provide a name, server URL, and scopes (e.g., `todo:read`, `todo:write`). +2. Enable **dynamic client registration** (allows MCP hosts to register automatically). +3. Copy the metadata JSON from **Dashboard > MCP Servers > Metadata JSON**. + +## Discovery endpoint + +Expose `/.well-known/oauth-protected-resource` on your server: + +```js +// Node.js (Express) +app.get('/.well-known/oauth-protected-resource', (req, res) => { + res.json({ + authorization_servers: ['https:///resources/'], + bearer_methods_supported: ['header'], + resource: 'https://mcp.yourapp.com', + scopes_supported: ['todo:read', 'todo:write'], + }); +}); +``` + +```python +# Python (FastAPI) +@app.get("/.well-known/oauth-protected-resource") +async def oauth_metadata(): + return { + "authorization_servers": ["https:///resources/"], + "bearer_methods_supported": ["header"], + "resource": "https://mcp.yourapp.com", + "scopes_supported": ["todo:read", "todo:write"], + } +``` + +## Token validation middleware + +```js +// Node.js (Express) +async function authMiddleware(req, res, next) { + if (req.path.includes('.well-known')) return next(); + const token = req.headers.authorization?.replace('Bearer ', ''); + if (!token) return res.status(401).set('WWW-Authenticate', wwwHeader).end(); + try { + await scalekit.validateToken(token, { audience: [RESOURCE_ID] }); + next(); + } catch { + res.status(401).set('WWW-Authenticate', wwwHeader).end(); + } +} +``` + +```python +# Python (FastAPI) +@app.middleware("http") +async def auth_middleware(request: Request, call_next): + if request.url.path.startswith("/.well-known"): + return await call_next(request) + auth_header = request.headers.get("Authorization", "") + token = auth_header.removeprefix("Bearer ").strip() if auth_header.startswith("Bearer ") else None + if not token: + raise HTTPException(status_code=401, headers=www_header) + try: + scalekit_client.validate_access_token(token, options=TokenValidationOptions( + issuer=os.getenv("SCALEKIT_ENVIRONMENT_URL"), audience=[RESOURCE_ID] + )) + except Exception: + raise HTTPException(status_code=401, headers=www_header) + return await call_next(request) +``` + +The `WWW-Authenticate` header must include `resource_metadata` pointing to your discovery endpoint — this is what triggers the MCP client's OAuth flow. + +## Scope-based authorization + +Enforce per-tool scopes after validating the token: + +```js +// Node.js +await scalekit.validateToken(token, { + audience: [RESOURCE_ID], + requiredScopes: ['todo:write'], +}); +``` + +```python +# Python +scalekit_client.validate_access_token(token, options=TokenValidationOptions( + audience=[RESOURCE_ID], required_scopes=['todo:write'] +)) +``` + +## Implementation approaches + +| Approach | Complexity | Language | +|---|---|---| +| **FastMCP + ScalekitProvider** | Simplest (~5 lines) | Python | +| **Express.js + manual middleware** | Medium (full control) | Node.js | +| **FastAPI + FastMCP + custom middleware** | Medium (existing FastAPI apps) | Python | + +### FastMCP (minimal code) + +```python +from fastmcp import FastMCP +from fastmcp.server.auth.providers.scalekit import ScalekitProvider + +mcp = FastMCP("My Server", stateless_http=True, auth=ScalekitProvider( + environment_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), + client_id=os.getenv("SCALEKIT_CLIENT_ID"), + resource_id=os.getenv("SCALEKIT_RESOURCE_ID"), + mcp_url=os.getenv("MCP_URL"), +)) +mcp.run(transport="http", port=3002) +``` + +The provider handles discovery, token validation, and `WWW-Authenticate` responses automatically. + +## Verification checklist + +1. `curl -i ` returns `401` with `WWW-Authenticate` header. +2. `curl /.well-known/oauth-protected-resource` returns valid metadata JSON. +3. Test with MCP Inspector: `npx @modelcontextprotocol/inspector@latest`. + +## Related docs + +- [api-auth.md](api-auth.md) — API keys and client credentials for non-MCP APIs. diff --git a/plugins/saaskit/docs/scim.md b/plugins/saaskit/docs/scim.md new file mode 100644 index 0000000..145b508 --- /dev/null +++ b/plugins/saaskit/docs/scim.md @@ -0,0 +1,125 @@ +# SCIM Provisioning + +Scalekit acts as a SCIM bridge: the customer's IdP (Okta, Azure AD, etc.) pushes user/group changes to Scalekit, and your app consumes them via the Directory API or webhooks. + +## Two integration paths + +| Path | Use case | +|---|---| +| **Directory API** (polling) | Scheduled sync jobs, bulk imports, onboarding flows | +| **Webhooks** (real-time) | Instant user create/update/deactivate as changes happen | + +Most apps use webhooks for real-time and the Directory API for initial sync. + +## Directory API + +Fetch users and groups for an organization: + +```js +// Node.js +const { directory } = await scalekit.directory.getPrimaryDirectoryByOrganizationId(orgId); +const { users } = await scalekit.directory.listDirectoryUsers(orgId, directory.id); +for (const user of users) { + await upsertUser({ email: user.email, name: user.name, orgId }); +} +``` + +```python +# Python +directory = scalekit_client.directory.get_primary_directory_by_organization_id(org_id) +users = scalekit_client.directory.list_directory_users(org_id, directory.id) +for user in users: + upsert_user(email=user.email, name=user.name, org_id=org_id) +``` + +Group sync for RBAC: + +```js +const { groups } = await scalekit.directory.listDirectoryGroups(orgId, directory.id); +for (const group of groups) { + await syncGroupPermissions(group.id, group.name); +} +``` + +## Webhooks + +### Endpoint setup + +Add a POST route, verify the signature, and dispatch events: + +```js +// Node.js (Express) +app.post('/webhooks/scalekit', async (req, res) => { + try { + await scalekit.verifyWebhookPayload( + process.env.SCALEKIT_WEBHOOK_SECRET, req.headers, req.body + ); + } catch { return res.status(400).json({ error: 'Invalid signature' }); } + + const { type, data } = req.body; + await handleDirectoryEvent(type, data); + res.status(201).json({ status: 'processed' }); +}); +``` + +```python +# Python (FastAPI) +@app.post("/webhooks/scalekit") +async def scalekit_webhook(request: Request): + body = await request.json() + valid = scalekit_client.verify_webhook_payload( + secret=os.getenv("SCALEKIT_WEBHOOK_SECRET"), + headers=request.headers, + payload=json.dumps(body).encode() + ) + if not valid: + raise HTTPException(status_code=400, detail="Invalid signature") + await handle_directory_event(body.get("type"), body.get("data", {})) + return JSONResponse(status_code=201, content={"status": "processed"}) +``` + +### Event types + +| Event | Action | +|---|---| +| `organization.directory.user_created` | Create or activate user | +| `organization.directory.user_updated` | Update user profile | +| `organization.directory.user_deleted` | Deactivate user (prefer over hard delete) | +| `organization.directory.group_created` | Create group / sync roles | +| `organization.directory.group_updated` | Update group membership | + +### Event handler pattern + +```js +async function handleDirectoryEvent(type, data) { + switch (type) { + case 'organization.directory.user_created': + return createUser(data.email, data.name, data.organization_id); + case 'organization.directory.user_updated': + return updateUser(data.email, data.name); + case 'organization.directory.user_deleted': + return deactivateUser(data.email); + case 'organization.directory.group_created': + case 'organization.directory.group_updated': + return syncGroup(data); + } +} +``` + +## Dashboard registration + +1. **Dashboard > Webhooks > +Add Endpoint** — enter your public HTTPS URL. +2. Subscribe to the events above. +3. Copy the webhook secret into `SCALEKIT_WEBHOOK_SECRET`. +4. Share the [SCIM setup guide](https://docs.scalekit.com/guides/integrations/scim-integrations/) with the customer's IT admin. + +## Guardrails + +- **Validate signatures** on every webhook request. +- **Idempotent operations** — `upsertUser` must handle duplicate events safely. +- **Return 2xx quickly** — offload heavy work to a queue. Scalekit retries non-2xx with exponential backoff (up to 8 attempts over ~10 hours). +- **Deactivate, don't delete** — unless the codebase explicitly hard-deletes users. + +## Related docs + +- [sso.md](sso.md) — SSO and admin portal, often configured alongside SCIM. diff --git a/plugins/saaskit/docs/sessions.md b/plugins/saaskit/docs/sessions.md new file mode 100644 index 0000000..9045b9c --- /dev/null +++ b/plugins/saaskit/docs/sessions.md @@ -0,0 +1,113 @@ +# Sessions + +After authentication, the app receives access, refresh, and (optionally) ID tokens. This doc covers secure storage, transparent refresh, and remote session management. + +## Storage rules + +- Store access and refresh tokens in **separate** HttpOnly cookies. +- Set `Secure` in production (HTTPS only). Set `SameSite` to `Lax` (not `Strict` — Strict breaks OAuth redirects). +- Scope cookies with `Path` to reduce exposure: access token to `/api`, refresh token to `/auth/refresh`. +- Encrypt tokens before storing them in cookies as an extra layer against cookie theft. + +## Storing tokens + +```js +// Node.js (Express) +res.cookie('accessToken', encrypt(accessToken), { + maxAge: (expiresIn - 60) * 1000, + httpOnly: true, secure: isProd, sameSite: 'lax', path: '/api', +}); +res.cookie('refreshToken', encrypt(refreshToken), { + httpOnly: true, secure: isProd, sameSite: 'lax', path: '/auth/refresh', +}); +``` + +```python +# Python (Flask) +resp.set_cookie('accessToken', encrypt(access_token), + max_age=expires_in - 60, httponly=True, secure=is_prod, + samesite='Lax', path='/api') +resp.set_cookie('refreshToken', encrypt(refresh_token), + httponly=True, secure=is_prod, samesite='Lax', path='/auth/refresh') +``` + +Store the ID token separately if your logout flow needs it (see [auth-flows.md](auth-flows.md)). + +## Validate-and-refresh middleware + +Run on every protected request: + +1. Read and decrypt the access token cookie. +2. Call `validateAccessToken()`. If valid, proceed. +3. If invalid and a refresh token exists, call `refreshAccessToken()`, update cookies, proceed. +4. If refresh fails, return 401 — force re-login. + +```js +// Node.js +async function verifySession(req, res, next) { + const accessCookie = req.cookies?.accessToken; + if (!accessCookie) return res.status(401).json({ error: 'Authentication required' }); + try { + const token = decrypt(accessCookie); + if (await scalekit.validateAccessToken(token)) return next(); + const refreshCookie = req.cookies?.refreshToken; + if (!refreshCookie) return res.status(401).json({ error: 'Session expired' }); + const result = await scalekit.refreshAccessToken(decrypt(refreshCookie)); + // Rewrite cookies with new tokens, then next() + } catch { return res.status(401).json({ error: 'Authentication failed' }); } +} +``` + +```python +# Python (Flask decorator) +def verify_session(f): + @wraps(f) + def inner(*args, **kwargs): + access_cookie = request.cookies.get('accessToken') + if not access_cookie: + return jsonify({'error': 'Authentication required'}), 401 + try: + token = decrypt(access_cookie) + if scalekit_client.validate_access_token(token): + return f(*args, **kwargs) + refresh_cookie = request.cookies.get('refreshToken') + if not refresh_cookie: + return jsonify({'error': 'Session expired'}), 401 + result = scalekit_client.refresh_access_token(decrypt(refresh_cookie)) + # Rewrite cookies with new tokens, then call f() + except Exception: + return jsonify({'error': 'Authentication failed'}), 401 + return inner +``` + +## Remote session management + +Use Scalekit session APIs for multi-device controls: + +```js +// Node.js +// List active sessions for a user +const sessions = await scalekit.session.getUserSessions(userId, { + pageSize: 10, filter: { status: ['ACTIVE'] } +}); +// Revoke a single session +await scalekit.session.revokeSession(sessionId); +// Revoke all sessions for a user +await scalekit.session.revokeAllUserSessions(userId); +``` + +## Dashboard-driven configuration + +Session timeouts and token lifetimes are configurable in the Scalekit dashboard without code changes: +- Absolute session timeout (max total session time) +- Idle session timeout (logout after inactivity) +- Access token lifetime (drives refresh frequency) + +## SPA/mobile note + +For SPAs, prefer: access token in memory via `Authorization: Bearer` headers, refresh token in an HttpOnly cookie. Configure CSRF protections explicitly when using cookies in browser SPAs. + +## Related docs + +- [auth-flows.md](auth-flows.md) — Login, callback, logout flows. +- [access-control.md](access-control.md) — Extracting roles/permissions from validated tokens. diff --git a/plugins/saaskit/docs/sso.md b/plugins/saaskit/docs/sso.md new file mode 100644 index 0000000..626aadb --- /dev/null +++ b/plugins/saaskit/docs/sso.md @@ -0,0 +1,115 @@ +# SSO + +SaaSKit supports two SSO modes: + +- **SaaSKit (Full-Stack Auth)**: Scalekit manages users and sessions. SSO is built in. +- **Modular SSO**: Your app manages users and sessions. Scalekit handles the IdP redirect and token exchange. + +This doc covers Modular SSO and the admin portal for customer self-serve configuration. + +## Modular SSO flow + +1. Generate an authorization URL with a connection selector. +2. Redirect the user to the IdP (via Scalekit). +3. Handle the callback — exchange the code for user profile and tokens. +4. Create a session in your app using the returned user data. + +### Connection selectors (precedence order) + +| Selector | Use case | +|---|---| +| `connectionId` | Direct SSO connection reference | +| `organizationId` | Routes to the org's active SSO connection | +| `loginHint` | Email — Scalekit extracts the domain to find the connection | + +```js +// Node.js +const authUrl = scalekit.getAuthorizationUrl(redirectUri, { + organizationId: 'org_15421144869927830', +}); +res.redirect(authUrl); +``` + +```python +# Python +options = AuthorizationUrlOptions() +options.organization_id = 'org_15421144869927830' +auth_url = scalekit_client.get_authorization_url(redirect_uri, options=options) +``` + +## IdP-initiated login + +When a user starts login from their IdP portal (e.g., Okta tile), Scalekit sends a signed JWT to your login endpoint. Decode it and convert to a standard SP-initiated flow: + +```js +// Node.js +app.get('/login', async (req, res) => { + const { idp_initiated_login } = req.query; + if (idp_initiated_login) { + const claims = await scalekit.getIdpInitiatedLoginClaims(idp_initiated_login); + const authUrl = scalekit.getAuthorizationUrl(redirectUri, { + connectionId: claims.connection_id, + organizationId: claims.organization_id, + loginHint: claims.login_hint, + }); + return res.redirect(authUrl); + } + // Normal login flow +}); +``` + +Configure the initiate login endpoint in **Dashboard > Authentication > Redirects**. + +## Supported IdPs + +Okta, Azure AD, Google Workspace, and any SAML 2.0 or OIDC-compliant identity provider. Customers configure their IdP through the admin portal. + +## Admin portal + +The admin portal lets your customers configure their own SSO (and SCIM) settings. Embed it as an iframe in your app's settings UI, or share a one-time link for onboarding. + +### Generate a portal link (server-side) + +```js +// Node.js +const { location } = await scalekit.organization.generatePortalLink(organizationId); +// Pass `location` to the frontend — links are single-use. +``` + +```python +# Python +portal = scalekit_client.organization.generate_portal_link(organization_id) +location = portal.location +``` + +### Embed the iframe + +```html + +``` + +### Handle portal events + +```js +window.addEventListener('message', (event) => { + if (event.origin !== process.env.SCALEKIT_ENVIRONMENT_URL) return; + if (event.data.type === 'SESSION_EXPIRED') { + // Re-fetch portal link and reload iframe + } +}); +``` + +**Requirements:** +- Register your app domain in **Dashboard > Redirect URIs** — the iframe is blocked otherwise. +- Generate a new link on every page load — links are single-use. +- Handle `SESSION_EXPIRED` events to prevent silent portal failures. + +### Shareable link (no-code) + +For one-time onboarding: **Dashboard > Organizations** > select org > **Generate link**. Share the URL and the [SSO setup guide](https://docs.scalekit.com/guides/integrations/sso-integrations/) with the customer's IT admin. + +## Related docs + +- [auth-flows.md](auth-flows.md) — The underlying OIDC flow that SSO builds on. +- [scim.md](scim.md) — Directory sync, often configured alongside SSO. diff --git a/plugins/full-stack-auth/hooks/beacon.sh b/plugins/saaskit/hooks/beacon.sh similarity index 100% rename from plugins/full-stack-auth/hooks/beacon.sh rename to plugins/saaskit/hooks/beacon.sh diff --git a/plugins/saaskit/hooks/hooks.json b/plugins/saaskit/hooks/hooks.json new file mode 100644 index 0000000..ff9da6e --- /dev/null +++ b/plugins/saaskit/hooks/hooks.json @@ -0,0 +1,16 @@ +{ + "description": "Usage beacon for Scalekit saaskit plugin", + "hooks": { + "Stop": [ + { + "type": "command", + "hooks": [ + { + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh saaskit stop", + "timeout": 10 + } + ] + } + ] + } +} \ No newline at end of file diff --git a/plugins/mcp-auth/references/bring-your-own-auth.md b/plugins/saaskit/references/bring-your-own-auth.md similarity index 100% rename from plugins/mcp-auth/references/bring-your-own-auth.md rename to plugins/saaskit/references/bring-your-own-auth.md diff --git a/plugins/full-stack-auth/references/redirects.md b/plugins/saaskit/references/redirects.md similarity index 100% rename from plugins/full-stack-auth/references/redirects.md rename to plugins/saaskit/references/redirects.md diff --git a/plugins/full-stack-auth/references/scalekit-logs.md b/plugins/saaskit/references/scalekit-logs.md similarity index 100% rename from plugins/full-stack-auth/references/scalekit-logs.md rename to plugins/saaskit/references/scalekit-logs.md diff --git a/plugins/mcp-auth/references/scalekit-mcp-server.md b/plugins/saaskit/references/scalekit-mcp-server.md similarity index 100% rename from plugins/mcp-auth/references/scalekit-mcp-server.md rename to plugins/saaskit/references/scalekit-mcp-server.md diff --git a/plugins/full-stack-auth/references/scalekit-user-profiles.md b/plugins/saaskit/references/scalekit-user-profiles.md similarity index 100% rename from plugins/full-stack-auth/references/scalekit-user-profiles.md rename to plugins/saaskit/references/scalekit-user-profiles.md diff --git a/plugins/saaskit/rules/redirect-urls.md b/plugins/saaskit/rules/redirect-urls.md new file mode 100644 index 0000000..f20742c --- /dev/null +++ b/plugins/saaskit/rules/redirect-urls.md @@ -0,0 +1,21 @@ +# Redirect URL Rules + +All OAuth and OIDC flows in Scalekit require registered redirect URLs. + +## Rules + +- The `redirect_uri` parameter in authorization requests must exactly match a URL registered in the Scalekit dashboard. +- Register both development (`http://localhost:*`) and production (`https://`) redirect URLs. +- The `post_logout_redirect_uri` must also be allowlisted in the dashboard under Post Logout URLs. +- Never use wildcard redirect URLs in production. + +## Common mistakes + +- Trailing slash mismatch: `https://app.com/callback` vs `https://app.com/callback/`. +- Scheme mismatch: `http://` registered but `https://` used in code (or vice versa). +- Port mismatch in development: registering `:3000` but the app runs on `:3001`. + +## Where to configure + +Scalekit Dashboard → Settings → Redirect URLs (for auth callbacks) +Scalekit Dashboard → Settings → Post Logout URLs (for logout redirects) \ No newline at end of file diff --git a/plugins/saaskit/rules/terminology.md b/plugins/saaskit/rules/terminology.md new file mode 100644 index 0000000..372d404 --- /dev/null +++ b/plugins/saaskit/rules/terminology.md @@ -0,0 +1,20 @@ +# Terminology + +Use these terms consistently in user-facing documentation and skills: + +- `SaaSKit`: the Scalekit product for B2B SaaS authentication (login, sessions, SSO, SCIM, RBAC). Previously called `Full Stack Auth` or `FSA`. +- `Modular SSO`: enterprise SSO integration where the app manages its own users and sessions. Scalekit handles only the SSO handshake. +- `Full Stack Auth`: Scalekit manages the entire auth lifecycle (login, sessions, users). Also referred to as `SaaSKit` in current docs. +- `SCIM provisioning`: directory sync for automatic user and group lifecycle management via webhooks. +- `Admin portal`: customer-facing iframe for self-serve SSO and SCIM configuration. +- `MCP server auth`: OAuth 2.1 authorization for Model Context Protocol servers. + +## Preferred wording + +- Prefer `SaaSKit` over `FSA` or `Full Stack Auth` in new content. +- Use `Modular SSO` when the app manages its own sessions. Use `SaaSKit SSO` when Scalekit manages everything. +- Do not mix `AgentKit` terminology in SaaSKit contexts. AgentKit is about agent-to-tool authentication via connectors. + +## Why this rule exists + +The product naming shifted from feature-specific names (`Full Stack Auth`, `Modular SSO`) to kit-based names (`SaaSKit`, `AgentKit`). This rule keeps the SaaSKit plugin aligned with current docs at `docs.scalekit.com/llms.txt`. \ No newline at end of file diff --git a/plugins/saaskit/skills/adding-api-auth/SKILL.md b/plugins/saaskit/skills/adding-api-auth/SKILL.md new file mode 100644 index 0000000..b58d011 --- /dev/null +++ b/plugins/saaskit/skills/adding-api-auth/SKILL.md @@ -0,0 +1,54 @@ +--- +name: adding-api-auth +description: Implements machine-to-machine authentication using Scalekit — either long-lived opaque API keys (org or user scoped) or OAuth 2.0 client credentials for service-to-service auth. Use when adding API key auth, building key management, or implementing client credentials flows. +--- + +# SaaSKit API Authentication + +Implements machine-to-machine auth for APIs using Scalekit — API keys or OAuth 2.0 client credentials. + +## Two approaches + +| Approach | Best for | Token type | Lifetime | +|---|---|---|---| +| **API keys** | Developer-facing APIs, integrations | Opaque string | Long-lived, manually revoked | +| **Client credentials** | Service-to-service, microservices | JWT (access token) | Short-lived, auto-refreshed | + +## When to use each + +### API keys +- Your users need to call your API from scripts, CI/CD, or third-party integrations. +- You want org-scoped or user-scoped keys with custom permissions. +- You need a key management UI (create, list, revoke). + +### Client credentials +- Backend services calling each other (no user context). +- You want automatic token rotation and expiry. +- You need audience-scoped tokens for zero-trust architectures. + +## Workflow overview + +### API keys + +1. Create key via Scalekit SDK or dashboard (org-scoped or user-scoped). +2. Client sends key in `Authorization: Bearer ` header. +3. Your middleware validates the key via Scalekit API on each request. +4. Extract org/user context and permissions from the key metadata. + +### Client credentials + +1. Register a service client in Scalekit dashboard. +2. Service calls `POST /oauth/token` with `grant_type=client_credentials`. +3. Scalekit returns a short-lived JWT access token. +4. Receiving service validates the JWT using Scalekit's JWKS endpoint. + +## Deep reference + +- API auth patterns and code: [../../docs/api-auth.md](../../docs/api-auth.md) +- Access control (permission enforcement): [../../docs/access-control.md](../../docs/access-control.md) + +## When to switch skills + +- Use `implementing-saaskit` for user-facing browser authentication. +- Use `implementing-access-control` for permission enforcement on API endpoints. +- Use `adding-mcp-oauth` for MCP server authentication specifically. diff --git a/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md b/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md new file mode 100644 index 0000000..a209cd0 --- /dev/null +++ b/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md @@ -0,0 +1,48 @@ +--- +name: adding-mcp-oauth +description: Adds OAuth 2.1 authorization to Model Context Protocol servers using Scalekit. Covers Streamable HTTP transport, token validation middleware, and scope-based authorization for Node.js and Python. Use when securing MCP servers, implementing authentication for AI hosts like Claude Desktop or Cursor. +--- + +# SaaSKit MCP Server Auth + +Adds OAuth 2.1 authorization to MCP servers using Scalekit for token validation and scope enforcement. + +## Critical prereqs + +- **Streamable HTTP transport only** — MCP OAuth requires HTTP. The stdio transport does not support authentication. +- **Scalekit MCP server registration** — register your server in Dashboard > MCP Servers to get a `resource_id`. +- **HTTPS in production** — token validation requires secure transport. + +## Setup workflow + +1. Register MCP server in Scalekit dashboard (get `resource_id`). +2. Set env vars: `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`, `SCALEKIT_RESOURCE_ID`. +3. Add token validation middleware (validates JWT on every request). +4. Publish `/.well-known/oauth-protected-resource` metadata endpoint. +5. Add scope checks to individual tools. +6. Test with MCP Inspector: `npx @modelcontextprotocol/inspector@latest`. + +## Implementation approaches + +| Approach | Framework | Complexity | Best for | +|---|---|---|---| +| FastMCP + ScalekitProvider | Python | Low (~5 lines) | New Python MCP servers | +| FastAPI + FastMCP | Python | Medium | Existing FastAPI apps adding MCP | +| Express.js + MCP SDK | Node.js | Medium | Node.js MCP servers | + +## Framework-specific references + +- FastMCP (Python, simplest): [fastmcp-reference.md](fastmcp-reference.md) +- Express.js (Node.js): [express-reference.md](express-reference.md) +- FastAPI + FastMCP (Python, custom middleware): [fastapi-reference.md](fastapi-reference.md) + +## Deep reference + +- MCP server auth patterns: [../../docs/mcp-server-auth.md](../../docs/mcp-server-auth.md) +- API auth (related — client credentials): [../../docs/api-auth.md](../../docs/api-auth.md) + +## When to switch skills + +- Use `implementing-saaskit` for user-facing browser authentication. +- Use `adding-api-auth` for non-MCP machine-to-machine auth. +- Use `production-readiness-saaskit` to validate MCP auth before launch. diff --git a/plugins/saaskit/skills/adding-mcp-oauth/express-reference.md b/plugins/saaskit/skills/adding-mcp-oauth/express-reference.md new file mode 100644 index 0000000..18b879d --- /dev/null +++ b/plugins/saaskit/skills/adding-mcp-oauth/express-reference.md @@ -0,0 +1,144 @@ +# Express.js MCP OAuth Authentication with Scalekit + +## Overview + +Pattern for building production-ready MCP servers using Express.js, TypeScript, and OAuth 2.1 Bearer token authentication via Scalekit. Provides fine-grained control over HTTP request handling, middleware chains, and server behavior. + +## When to Use This Pattern + +- **Node.js ecosystem**: Leverage existing npm packages and TypeScript tooling +- **Custom middleware chains**: Rate limiting, request logging, complex authorization +- **Existing Express applications**: Add MCP capabilities to established codebases +- **Fine-grained HTTP control**: Routing, CORS policies, health checks, multiple endpoints + +## Core Architecture + +### Token Validation Flow + +``` +MCP Client → Express Server (401 + WWW-Authenticate) +MCP Client → Scalekit (Exchange code for token) +Scalekit → MCP Client (Bearer token) +MCP Client → Express Server (POST /mcp + Bearer token) +Express Middleware → Scalekit SDK (Validate token) +McpServer → Tool Handler → Response +``` + +### Key Components + +1. **Express Middleware**: Validates Bearer tokens before routing to MCP handlers +2. **Scalekit Node SDK**: Validates JWT signatures, expiration, issuer, and audience +3. **McpServer**: Official MCP SDK server handling JSON-RPC and tool registration +4. **StreamableHTTPServerTransport**: Bridges Express HTTP to MCP protocol +5. **Zod Schema Validation**: Type-safe input validation for tool parameters +6. **OAuth Resource Metadata Endpoint**: `/.well-known/oauth-protected-resource` for client discovery + +## Implementation Patterns + +### Environment Configuration + +Required variables: `SK_ENV_URL`, `SK_CLIENT_ID`, `SK_CLIENT_SECRET`, `EXPECTED_AUDIENCE`, `PROTECTED_RESOURCE_METADATA`, `PORT`. + +### Scalekit Client Initialization + +```typescript +import { Scalekit } from '@scalekit-sdk/node'; + +const scalekit = new Scalekit(SK_ENV_URL, SK_CLIENT_ID, SK_CLIENT_SECRET); +``` + +Initialize once at module level for connection pooling. + +### MCP Server Setup + +```typescript +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { z } from 'zod'; + +const server = new McpServer({ name: 'Greeting MCP', version: '1.0.0' }); + +server.tool( + 'greet_user', + 'Greets the user with a personalized message.', + { name: z.string().min(1, 'Name is required') }, + async ({ name }: { name: string }) => ({ + content: [{ type: 'text', text: `Hi ${name}, welcome to Scalekit!` }] + }) +); +``` + +### Express Middleware Authentication + +```typescript +app.use(async (req: Request, res: Response, next: NextFunction) => { + if (req.path === '/.well-known/oauth-protected-resource' || req.path === '/health') { + next(); + return; + } + + const header = req.headers.authorization; + const token = header?.startsWith('Bearer ') + ? header.slice('Bearer '.length).trim() + : undefined; + + if (!token) { + res.status(401).set('WWW-Authenticate', WWW_HEADER_VALUE).json({ error: 'Missing Bearer token' }); + return; + } + + try { + await scalekit.validateToken(token, { audience: [EXPECTED_AUDIENCE] }); + next(); + } catch (error) { + res.status(401).set('WWW-Authenticate', WWW_HEADER_VALUE).json({ error: 'Token validation failed' }); + } +}); +``` + +**Key**: Always `return` after sending a response to prevent "headers already sent" errors. + +### MCP Transport Layer + +```typescript +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; + +app.post('/', async (req: Request, res: Response) => { + const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined }); + await server.connect(transport); + await transport.handleRequest(req, res, req.body); +}); +``` + +Setting `sessionIdGenerator: undefined` ensures stateless operation for serverless deployments. + +## Common Pitfalls + +**Mismatched Audience**: `EXPECTED_AUDIENCE` must match the Server URL in Scalekit exactly — including trailing slash. + +**Headers Already Sent**: Always `return` after `res.json()` or `res.send()` in middleware. + +**Middleware Order**: Correct order: CORS → body parsing → authentication → routes. + +**TypeScript Module Resolution**: Always include `.js` extension when importing from MCP SDK. + +## Dependencies + +```json +{ + "dependencies": { + "@modelcontextprotocol/sdk": "^1.13.0", + "@scalekit-sdk/node": "^2.0.1", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^5.1.0", + "zod": "^3.25.57" + } +} +``` + +## Reference + +- Full example: [scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-node](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-node) +- [MCP SDK Documentation](https://github.com/modelcontextprotocol/typescript-sdk) +- [Scalekit Node SDK](https://github.com/scalekit-inc/scalekit-sdk-node) +- [Scalekit MCP Auth Demos](https://github.com/scalekit-inc/mcp-auth-demos/tree/main) diff --git a/plugins/saaskit/skills/adding-mcp-oauth/fastapi-reference.md b/plugins/saaskit/skills/adding-mcp-oauth/fastapi-reference.md new file mode 100644 index 0000000..a0680af --- /dev/null +++ b/plugins/saaskit/skills/adding-mcp-oauth/fastapi-reference.md @@ -0,0 +1,161 @@ +# FastAPI + FastMCP OAuth Authentication with Scalekit + +## Overview + +Pattern for building production-ready MCP servers using FastAPI and FastMCP with OAuth 2.1 Bearer token authentication via Scalekit. Provides fine-grained control over authentication middleware, token validation, and server behavior compared to FastMCP's built-in OAuth provider. + +## When to Use This Pattern + +- **Custom middleware requirements**: Rate limiting, request logging, complex authorization +- **Existing FastAPI applications**: Integrate MCP tools into established codebases +- **Advanced authorization**: Scope-based access control, multi-tenancy, custom claims +- **Full HTTP control**: CORS policies, health checks, multiple endpoints alongside MCP tools + +**Don't use this pattern** if FastMCP's built-in OAuth provider meets your needs — the additional FastAPI layer adds complexity. + +## Core Architecture + +### Token Validation Flow + +``` +MCP Client → FastAPI Server (401 + WWW-Authenticate) +MCP Client → Scalekit (Exchange code for token) +Scalekit → MCP Client (Bearer token) +MCP Client → FastAPI Server (Request + Bearer token) +FastAPI Middleware → Scalekit SDK (Validate token) +FastAPI → MCP Tool Handler → Response +``` + +## Implementation Patterns + +### Middleware Authentication Pattern + +```python +@app.middleware("http") +async def auth_middleware(request: Request, call_next): + if request.url.path in {"/health", "/.well-known/oauth-protected-resource"}: + return await call_next(request) + + auth_header = request.headers.get("authorization") + if not auth_header or not auth_header.startswith("Bearer "): + return Response( + '{"error": "Missing Bearer token"}', + status_code=401, + headers={"WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{RESOURCE_METADATA_URL}"'}, + media_type="application/json" + ) + + token = auth_header.split("Bearer ", 1)[1].strip() + + options = TokenValidationOptions( + issuer=SK_ENV_URL, + audience=[EXPECTED_AUDIENCE] + ) + + try: + is_valid = scalekit_client.validate_access_token(token, options=options) + if not is_valid: + raise ValueError("Invalid token") + except Exception: + return Response( + '{"error": "Token validation failed"}', + status_code=401, + headers=WWW_HEADER, + media_type="application/json" + ) + + return await call_next(request) +``` + +### FastMCP Tool Registration + +```python +@mcp.tool( + name="greet_user", + description="Greets the user with a personalized message." +) +async def greet_user(name: str, ctx: Context | None = None) -> dict: + return { + "content": [{"type": "text", "text": f"Hi {name}, welcome to Scalekit!"}] + } +``` + +### Application Mounting + +```python +mcp_app = mcp.http_app(path="/") +app = FastAPI(lifespan=mcp_app.lifespan) + +# Add middleware (CORS, auth, etc.) +app.add_middleware(CORSMiddleware, ...) + +# Add custom endpoints +@app.get("/health") +async def health_check(): + return {"status": "healthy"} + +# Mount MCP at root — MUST be last +app.mount("/", mcp_app) +``` + +**Layering order**: Create FastMCP HTTP app → Create FastAPI app with shared lifespan → Add middleware → Register custom endpoints → Mount FastMCP last. + +## Common Pitfalls + +**Mismatched Audience**: `EXPECTED_AUDIENCE` must match the Server URL in Scalekit exactly. + +**Middleware Order**: Add middleware before mounting MCP app; mount MCP last. + +**Missing Resource Metadata**: Verify `PROTECTED_RESOURCE_METADATA` JSON is copied correctly from Scalekit dashboard. + +**Development vs Production URLs**: Use environment-specific values for `EXPECTED_AUDIENCE`. + +## Dependencies + +```txt +mcp>=1.0.0 +fastapi>=0.104.0 +fastmcp>=0.8.0 +uvicorn>=0.24.0 +pydantic>=2.5.0 +python-dotenv>=1.0.0 +httpx>=0.25.0 +python-jose[cryptography]>=3.3.0 +scalekit-sdk-python>=2.4.0 +``` + +## Extension Patterns + +### Scope-Based Authorization + +```python +# In middleware — attach scopes to request state +decoded = jwt.decode(token, options={"verify_signature": False}) +request.state.scopes = decoded.get("scope", "").split() + +# In tool — check scopes +@mcp.tool() +async def admin_tool(ctx: Context) -> dict: + if "admin" not in ctx.request_context.state.scopes: + raise PermissionError("Requires admin scope") +``` + +### Multi-Tenancy + +```python +# In middleware +request.state.org_id = decoded.get("org_id") + +# In tool +@mcp.tool() +async def get_org_data(ctx: Context) -> dict: + org_id = ctx.request_context.state.org_id + data = await fetch_data_for_org(org_id) + return {"content": [{"type": "text", "text": json.dumps(data)}]} +``` + +## Reference + +- Full example: [scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-python](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-python) +- [FastMCP Documentation](https://github.com/jlowin/fastmcp) +- [Scalekit MCP Auth Demos](https://github.com/scalekit-inc/mcp-auth-demos/tree/main) diff --git a/plugins/saaskit/skills/adding-mcp-oauth/fastmcp-reference.md b/plugins/saaskit/skills/adding-mcp-oauth/fastmcp-reference.md new file mode 100644 index 0000000..354075d --- /dev/null +++ b/plugins/saaskit/skills/adding-mcp-oauth/fastmcp-reference.md @@ -0,0 +1,136 @@ +# FastMCP OAuth with Scalekit Provider + +Secure your FastMCP server with OAuth 2.1 in just 5 lines of code using Scalekit's built-in provider. This approach handles token validation, scope enforcement, and authentication flows automatically. + +## FastMCP advantage + +**Standard MCP OAuth**: ~30 lines of middleware code, manual token validation +**FastMCP with Scalekit provider**: ~5 lines of configuration, automatic token handling + +## Setup workflow + +``` +FastMCP OAuth Setup: +- [ ] Step 1: Register MCP server in Scalekit +- [ ] Step 2: Install FastMCP and dependencies +- [ ] Step 3: Configure Scalekit provider +- [ ] Step 4: Add scope validation to tools +- [ ] Step 5: Test with MCP Inspector +``` + +## Step 1: Register MCP server + +In Scalekit dashboard: + +1. Navigate to **Dashboard > MCP Servers > Add MCP Server** +2. Enter server name (e.g., `FastMCP Todo Server`) +3. Set **Server URL** to `http://localhost:3002/` (include trailing slash) +4. Define scopes for your tools (e.g., `todo:read`, `todo:write`) +5. Click **Save** and note the `resource_id` + +**Critical**: Use base URL with trailing slash. FastMCP appends `/mcp` automatically. + +## Step 2: Install dependencies + +```bash +mkdir fastmcp-server && cd fastmcp-server +python3 -m venv venv && source venv/bin/activate +pip install "fastmcp>=2.13.0.2" python-dotenv +``` + +## Step 3: Configure Scalekit provider + +```python +import os +from dotenv import load_dotenv +from fastmcp import FastMCP +from fastmcp.server.auth.providers.scalekit import ScalekitProvider + +load_dotenv() + +mcp = FastMCP( + "Your Server Name", + stateless_http=True, + auth=ScalekitProvider( + environment_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), + client_id=os.getenv("SCALEKIT_CLIENT_ID"), + resource_id=os.getenv("SCALEKIT_RESOURCE_ID"), + mcp_url=os.getenv("MCP_URL"), + ), +) + +if __name__ == "__main__": + mcp.run(transport="http", port=int(os.getenv("PORT", "3002"))) +``` + +The Scalekit provider handles token validation, OAuth flow, WWW-Authenticate headers, and the discovery endpoint automatically. + +## Step 4: Add scope validation to tools + +```python +from fastmcp.server.dependencies import AccessToken, get_access_token + +def _require_scope(scope: str) -> str | None: + token: AccessToken = get_access_token() + if scope not in token.scopes: + return f"Insufficient permissions: `{scope}` scope required." + return None + +@mcp.tool +def create_todo(title: str, description: str = None) -> dict: + """Create a new todo item. Requires todo:write scope.""" + error = _require_scope("todo:write") + if error: + return {"error": error} + todo_id = str(uuid.uuid4()) + return {"id": todo_id, "title": title, "description": description} + +@mcp.tool +def list_todos() -> dict: + """List all todos. Requires todo:read scope.""" + error = _require_scope("todo:read") + if error: + return {"error": error} + return {"todos": [...]} +``` + +## Step 5: Test with MCP Inspector + +```bash +python server.py +# In another terminal: +npx @modelcontextprotocol/inspector@latest +``` + +In Inspector: enter URL `http://localhost:3002/mcp`, leave auth fields empty (uses dynamic client registration), click Connect. + +## Environment variable reference + +| Variable | Description | Example | +|----------|-------------|---------| +| `SCALEKIT_ENVIRONMENT_URL` | Your Scalekit environment URL | `https://yourenv.scalekit.com` | +| `SCALEKIT_CLIENT_ID` | Client ID from Scalekit dashboard | `skc_...` | +| `SCALEKIT_RESOURCE_ID` | MCP server resource ID | `res_...` | +| `MCP_URL` | Base URL with trailing slash | `http://localhost:3002/` | +| `PORT` | HTTP server port | `3002` | + +## Scope design patterns + +- **Read-only**: `*:read` (e.g., `todo:read`, `data:read`) +- **Write operations**: `*:write` (e.g., `todo:write`) +- **Admin operations**: `*:admin` (e.g., `system:admin`) +- **Multiple scopes per tool**: Return error if ANY required scope is missing + +## Common issues + +**Token validation fails**: Verify `SCALEKIT_RESOURCE_ID` matches dashboard. Check `MCP_URL` has trailing slash. + +**Scope errors persist**: Verify scopes are defined in Scalekit dashboard. Check scope strings match exactly (case-sensitive). + +**MCP Inspector connection fails**: Leave auth fields empty (uses DCR). Check browser console for OAuth errors. + +## Reference + +- Full example: [scalekit-inc/mcp-auth-demos/tree/main/todo-fastmcp](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/todo-fastmcp) +- FastMCP docs: [fastmcp.dev](https://fastmcp.dev) +- Scalekit docs: [docs.scalekit.com/authenticate/mcp/fastmcp-quickstart](https://docs.scalekit.com/authenticate/mcp/fastmcp-quickstart) diff --git a/plugins/saaskit/skills/implementing-access-control/SKILL.md b/plugins/saaskit/skills/implementing-access-control/SKILL.md new file mode 100644 index 0000000..9dcee74 --- /dev/null +++ b/plugins/saaskit/skills/implementing-access-control/SKILL.md @@ -0,0 +1,74 @@ +--- +name: implementing-access-control +description: Implements server-side RBAC and permission checks by validating access tokens, extracting roles and permissions, and enforcing them with middleware or decorators. Use when building authorization around Scalekit tokens. +--- + +# SaaSKit Access Control + +Implements RBAC and permission enforcement using claims from Scalekit access tokens. + +## When to use + +Use this skill **after** authentication is working (tokens are being issued and validated). Access control builds on top of a working auth flow — if login/sessions aren't set up yet, start with `implementing-saaskit`. + +## How it works + +Scalekit embeds `roles` and `permissions` claims directly in the access token JWT. Your app extracts these claims and enforces them at the middleware or handler level. + +### Token claims used for access control + +| Claim | Type | Example | +|---|---|---| +| `roles` | `string[]` | `["admin", "member"]` | +| `permissions` | `string[]` | `["organization:settings", "billing:read"]` | +| `xoid` | `string` | `wspace_abc` — the org context | + +## Workflow overview + +1. **Define roles and permissions** in Scalekit dashboard (User Management → Roles). +2. **Assign roles** to users during import, invitation, or via the SDK. +3. **Extract claims** from the validated access token in your middleware. +4. **Enforce** — check `permissions` array before allowing the action. + +## Enforcement patterns + +### Middleware-based (recommended) + +``` +Request → Auth middleware (validate token) → Permission middleware (check claims) → Handler +``` + +### Decorator/guard-based + +```python +@require_permission("billing:read") +def get_invoices(request): + ... +``` + +### Inline check + +```typescript +if (!tokenClaims.permissions.includes('organization:settings')) { + return res.status(403).json({ error: 'Forbidden' }); +} +``` + +## Best practices + +- Check `permissions`, not `roles` — permissions are granular and composable. +- Return `403 Forbidden` (not `401`) when the user is authenticated but lacks permission. +- Log permission denials for security auditing. +- Set default roles for new users and JIT-provisioned users. + +## Deep reference + +- Access control patterns and code: [../../docs/access-control.md](../../docs/access-control.md) +- Session management (where claims are extracted): [../../docs/sessions.md](../../docs/sessions.md) +- Framework-specific examples: [../../docs/frameworks/](../../docs/frameworks/) + +## When to switch skills + +- Use `implementing-saaskit` if auth isn't set up yet. +- Use `managing-saaskit-sessions` for token validation and refresh. +- Use `production-readiness-saaskit` to verify RBAC enforcement before launch. diff --git a/plugins/saaskit/skills/implementing-modular-sso/SKILL.md b/plugins/saaskit/skills/implementing-modular-sso/SKILL.md new file mode 100644 index 0000000..373211b --- /dev/null +++ b/plugins/saaskit/skills/implementing-modular-sso/SKILL.md @@ -0,0 +1,57 @@ +--- +name: implementing-modular-sso +description: Implements enterprise SSO and authentication flows using Scalekit, including modular SSO (SAML/OIDC), IdP-initiated login, and admin portal for self-serve configuration. Use when adding SSO, integrating identity providers like Okta or Azure AD, or embedding the Scalekit admin portal. +--- + +# SaaSKit Enterprise SSO + +Implements enterprise SSO (SAML/OIDC) via Scalekit's modular SSO feature, including IdP-initiated login and the self-serve admin portal. + +## When to use + +- Enterprise customers require SSO with their identity provider (Okta, Azure AD, Google Workspace, etc.). +- You need to support both SP-initiated and IdP-initiated login flows. +- You want a self-serve admin portal where customer IT admins configure SSO themselves. + +## SSO flow overview + +### SP-initiated (your app starts the login) + +``` +User clicks login → Your app → Scalekit (with organization_id or connection_id) + → IdP (Okta, Azure AD, etc.) → Scalekit callback → Your app callback +``` + +### IdP-initiated (IdP starts the login) + +``` +User clicks app tile in IdP → Scalekit (signed JWT) → Your app's IdP-initiated handler + → Build auth URL with claims → Scalekit → IdP → Callback → Your app +``` + +## Key integration points + +1. **Authorization URL** — pass `organization_id` or `connection_id` to scope to the right SSO connection. +2. **Callback handler** — same as standard auth; SSO tokens contain the same claims. +3. **IdP-initiated handler** — validate the signed JWT from Scalekit, extract org/connection hints, redirect to auth URL. +4. **Admin portal** — embed or link to Scalekit's admin portal for self-serve SSO configuration. + +## Admin portal + +The admin portal lets your enterprise customers configure SSO without your intervention: + +- Generate a portal link via the SDK: `sc.organization.generatePortalLink(orgId)` +- Embed it in your app's settings page or send it to the customer's IT admin. +- Customers can configure SAML/OIDC connections, test them, and manage user attribute mapping. + +## Deep reference + +- SSO patterns and code: [../../docs/sso.md](../../docs/sso.md) +- Auth flows (shared callback): [../../docs/auth-flows.md](../../docs/auth-flows.md) +- Framework-specific IdP-initiated handling: [../../docs/frameworks/](../../docs/frameworks/) + +## When to switch skills + +- Use `implementing-saaskit` for the base auth flow that SSO builds on. +- Use `implementing-scim-provisioning` for automated user provisioning alongside SSO. +- Use `production-readiness-saaskit` to validate SSO configuration before launch. diff --git a/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md b/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md new file mode 100644 index 0000000..1ff7668 --- /dev/null +++ b/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md @@ -0,0 +1,68 @@ +--- +name: implementing-saaskit-nextjs +description: Implements Scalekit SaaSKit authentication in a Next.js App Router project using @scalekit-sdk/node. Use when adding auth routes, protecting pages, managing sessions, or checking permissions in Next.js with Scalekit. +--- + +# SaaSKit Auth — Next.js + +Implements Scalekit authentication in Next.js App Router projects using `@scalekit-sdk/node`. + +## Reference repo + +[scalekit-inc/scalekit-nextjs-example](https://github.com/scalekit-inc/scalekit-nextjs-example) + +## Project structure overview + +``` +app/ +├── api/ +│ ├── auth/ +│ │ ├── login/route.ts # Builds auth URL → redirects to Scalekit +│ │ ├── callback/route.ts # Exchanges code → sets session cookie +│ │ ├── session/route.ts # Validates token → returns user JSON +│ │ └── logout/route.ts # Clears session → redirects to end-session +│ └── ... +├── dashboard/ +│ └── page.tsx # Protected page +├── middleware.ts # Checks session on every request +└── lib/ + └── scalekit.ts # SDK singleton +``` + +## Setup + +```bash +npm install @scalekit-sdk/node +``` + +```env +SCALEKIT_ENV_URL=https://your-env.scalekit.dev +SCALEKIT_CLIENT_ID=your_client_id +SCALEKIT_CLIENT_SECRET=your_client_secret +``` + +## Key patterns + +- **Route Handlers** (`app/api/auth/*/route.ts`) implement the OAuth flow. +- **Middleware** (`middleware.ts`) protects pages by checking for a valid session cookie. +- **Server Components** can read session data from cookies for SSR. +- **Client Components** call `/api/auth/session` to get user data. + +## Auth flow + +1. User clicks login → `GET /api/auth/login` → redirect to Scalekit +2. Scalekit redirects back → `GET /api/auth/callback` → exchange code → set httpOnly cookie +3. Middleware checks cookie on every navigation +4. Logout → `GET /api/auth/logout` → clear cookie → redirect to Scalekit end-session + +## Deep reference + +- Next.js patterns and code: [../../docs/frameworks/nextjs.md](../../docs/frameworks/nextjs.md) +- Auth flows: [../../docs/auth-flows.md](../../docs/auth-flows.md) +- Sessions: [../../docs/sessions.md](../../docs/sessions.md) + +## When to switch skills + +- Use `implementing-saaskit` for the general (non-framework-specific) guide. +- Use `managing-saaskit-sessions` for advanced session handling. +- Use `implementing-access-control` for RBAC and permission checks. diff --git a/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md b/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md new file mode 100644 index 0000000..2853541 --- /dev/null +++ b/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md @@ -0,0 +1,68 @@ +--- +name: implementing-saaskit-python +description: Implements Scalekit SaaSKit authentication in Python web frameworks (Django, FastAPI, or Flask) using scalekit-sdk-python. Use when adding auth to a Django, FastAPI, or Flask project, or when the user mentions Python web authentication with Scalekit. +--- + +# SaaSKit Auth — Python + +Implements Scalekit authentication in Django, FastAPI, or Flask using `scalekit-sdk-python`. + +## Framework detection + +Before generating code, detect which framework is in use: + +1. Check for `django` in `requirements.txt` / `pyproject.toml` → Django +2. Check for `fastapi` → FastAPI +3. Check for `flask` → Flask +4. If unclear, ask the user. + +## Quick setup + +```bash +pip install scalekit-sdk-python python-dotenv +``` + +```python +import os +from dotenv import load_dotenv +from scalekit import ScalekitClient + +load_dotenv() + +sc = ScalekitClient( + env_url=os.getenv("SCALEKIT_ENV_URL"), + client_id=os.getenv("SCALEKIT_CLIENT_ID"), + client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), +) +``` + +## Framework routing + +Each framework has different patterns for routes, middleware, and session storage: + +| Framework | Auth middleware | Session store | Docs section | +|---|---|---|---| +| Django | Custom middleware class | Django sessions (DB/cache) | Django section | +| FastAPI | Dependency injection | Server-side or JWT | FastAPI section | +| Flask | `@login_required` decorator | Flask-Session | Flask section | + +## Default workflow + +1. Set `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET` in `.env`. +2. Initialize `ScalekitClient` at module level. +3. Create a login route that redirects to `sc.get_authorization_url(redirect_uri, options)`. +4. Create a callback route that calls `sc.authenticate_with_code(code, redirect_uri)`. +5. Store tokens in the framework's session mechanism. +6. Create a logout route that clears the session and redirects to `sc.get_logout_url(options)`. + +## Deep reference + +- Python framework patterns: [../../docs/frameworks/python.md](../../docs/frameworks/python.md) +- Auth flows: [../../docs/auth-flows.md](../../docs/auth-flows.md) +- Sessions: [../../docs/sessions.md](../../docs/sessions.md) + +## When to switch skills + +- Use `implementing-saaskit` for the general (non-Python-specific) integration guide. +- Use `managing-saaskit-sessions` for advanced session handling. +- Use `implementing-access-control` for RBAC after auth is working. diff --git a/plugins/saaskit/skills/implementing-saaskit/SKILL.md b/plugins/saaskit/skills/implementing-saaskit/SKILL.md new file mode 100644 index 0000000..ecb1ee7 --- /dev/null +++ b/plugins/saaskit/skills/implementing-saaskit/SKILL.md @@ -0,0 +1,79 @@ +--- +name: implementing-saaskit +description: Implements Scalekit SaaSKit authentication (sign-up, login, logout, sessions) using JWT tokens across Node.js, Python, Go, Java, or PHP. Use when building or integrating user authentication with Scalekit, setting up OAuth callbacks, token refresh, or session handling. +--- + +# SaaSKit Authentication + +Use this skill as the auth integration entrypoint. It should stay thin and route into the canonical docs in `docs/`. + +## Mental model + +SaaSKit is Scalekit-managed login, session, and RBAC for SaaS apps. Scalekit acts as an OIDC/OAuth 2.0 provider — your app implements the authorization code flow against it. The SDK handles token exchange, validation, and refresh. + +Key concepts: +- **Auth URL**: Scalekit-hosted login page your app redirects to +- **Callback**: Your endpoint that receives the authorization code +- **Access token**: JWT carrying user identity, org, roles, and permissions +- **Refresh token**: Long-lived token used to renew access tokens silently + +## Default workflow + +1. Set `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET` in env. +2. Initialize the SDK client (once, at startup). +3. Build the authorization URL and redirect the user to Scalekit. +4. Handle the callback — exchange the code for tokens. +5. Store tokens in a secure session (httpOnly cookies or server-side store). +6. On logout, clear the session and redirect to Scalekit's end-session endpoint. + +## Quick skeleton + +### Node.js +```bash +npm install @scalekit-sdk/node +``` +```typescript +import { ScalekitClient } from '@scalekit-sdk/node'; +const sc = new ScalekitClient( + process.env.SCALEKIT_ENV_URL!, + process.env.SCALEKIT_CLIENT_ID!, + process.env.SCALEKIT_CLIENT_SECRET! +); +// Step 3 — redirect to auth URL +// Step 4 — sc.authenticateWithCode(code, redirectUri) +// Step 5 — store tokens in session +// Step 6 — sc.getLogoutUrl(options) +``` + +### Python +```bash +pip install scalekit-sdk-python +``` +```python +from scalekit import ScalekitClient +sc = ScalekitClient(env_url, client_id, client_secret) +# Same 6-step flow — see docs/auth-flows.md for full patterns +``` + +## Framework-specific references + +- Go (Gin): [go-reference.md](go-reference.md) +- Spring Boot: [springboot-reference.md](springboot-reference.md) +- Laravel: [laravel-reference.md](laravel-reference.md) +- Python (Django/FastAPI/Flask): use `implementing-saaskit-python` skill +- Next.js: use `implementing-saaskit-nextjs` skill + +## Deep reference + +- Auth flows: [../../docs/auth-flows.md](../../docs/auth-flows.md) +- Sessions: [../../docs/sessions.md](../../docs/sessions.md) +- Access control: [../../docs/access-control.md](../../docs/access-control.md) +- API auth: [../../docs/api-auth.md](../../docs/api-auth.md) +- All frameworks: [../../docs/frameworks/](../../docs/frameworks/) + +## When to switch skills + +- Use `managing-saaskit-sessions` for token storage, refresh middleware, and session auditing. +- Use `implementing-access-control` for RBAC and permission enforcement. +- Use `migrating-to-saaskit` when replacing an existing auth system. +- Use `production-readiness-saaskit` before going live. diff --git a/plugins/full-stack-auth/skills/implementing-scalekit-go-auth/SKILL.md b/plugins/saaskit/skills/implementing-saaskit/go-reference.md similarity index 74% rename from plugins/full-stack-auth/skills/implementing-scalekit-go-auth/SKILL.md rename to plugins/saaskit/skills/implementing-saaskit/go-reference.md index 8364c1c..24d8681 100644 --- a/plugins/full-stack-auth/skills/implementing-scalekit-go-auth/SKILL.md +++ b/plugins/saaskit/skills/implementing-saaskit/go-reference.md @@ -1,8 +1,3 @@ ---- -name: implementing-scalekit-go-auth -description: Guides Go developers implementing Scalekit authentication in Gin-based web apps using scalekit-sdk-go. Use when the developer mentions Scalekit, enterprise SSO, OIDC login, OAuth2 callback, access token validation, token refresh, session cookies, logout, IDP-initiated login, or xoid/xuid JWT claims in a Go project. ---- - # Scalekit Auth in Go (Gin) Scalekit is an OIDC/OAuth2 provider. Unlike frameworks that auto-wire OAuth2, Go requires you to @@ -86,7 +81,6 @@ func AuthorizeHandler(c *gin.Context) { State: state, Scopes: []string{"openid", "profile", "email", "offline_access"}, } - // Scope to a specific org, connection, or hint when provided if v := c.Query("organization_id"); v != "" { opts.OrganizationId = v } if v := c.Query("connection_id"); v != "" { opts.ConnectionId = v } if v := c.Query("login_hint"); v != "" { opts.LoginHint = v } @@ -133,7 +127,6 @@ func CallbackHandler(c *gin.Context) { c.SetCookie("auth_refresh_token", resp.RefreshToken, 2592000, "/", "", false, true) c.SetCookie("id_token", resp.IdToken, 86400, "/", "", false, false) - // Route: no org in token → new user needs onboarding claims, _ := decodeJWTPayload(resp.AccessToken) redirect := "/onboarding" if _, hasOrg := claims["xoid"]; hasOrg { @@ -160,7 +153,7 @@ func SessionHandler(c *gin.Context) { if err != nil || !valid { refreshed, err := sc.RefreshAccessToken(c.Request.Context(), refreshToken) if err != nil { - LogoutHandler(c) // force re-login + LogoutHandler(c) return } c.SetCookie("auth_access_token", refreshed.AccessToken, 86400, "/", "", false, true) @@ -235,8 +228,6 @@ func IdpInitiatedLoginHandler(c *gin.Context) { ## JWT utility helpers ```go -// decodeJWTPayload decodes the payload of a JWT without verifying the signature. -// Always use ValidateAccessToken() for security — this is only for claim extraction after validation. func decodeJWTPayload(token string) (map[string]interface{}, error) { parts := strings.Split(token, ".") if len(parts) != 3 { @@ -287,7 +278,7 @@ r.Use(cors.New(cors.Config{ AllowOrigins: []string{"https://yourdomain.com"}, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Origin", "Content-Type", "Authorization"}, - AllowCredentials: true, // MUST be true when cookies carry tokens + AllowCredentials: true, MaxAge: 12 * time.Hour, })) ``` @@ -311,15 +302,13 @@ r.Use(cors.New(cors.Config{ ## Troubleshooting -**`invalid_grant` on callback**: The `redirectURL` in `AuthenticateWithCode` must exactly match the URI registered in the Scalekit dashboard — including scheme and path. One mismatch silently breaks the exchange. - -**Session handler stuck in logout loop**: `ValidateAccessToken` returns `false` on both expiry *and* network errors. Log `err` before deciding to refresh vs. logout so you can distinguish the two. +**`invalid_grant` on callback**: The `redirectURL` in `AuthenticateWithCode` must exactly match the URI registered in the Scalekit dashboard — including scheme and path. -**`xoid` missing**: The user has no organization. This is expected for new signups — route to `/onboarding` to create or join a workspace. +**Session handler stuck in logout loop**: `ValidateAccessToken` returns `false` on both expiry *and* network errors. Log `err` before deciding to refresh vs. logout. -**CORS / cookie not sent**: Ensure `AllowCredentials: true` is set in CORS config. Without it, the browser strips cookies from cross-origin requests. +**`xoid` missing**: The user has no organization. This is expected for new signups — route to `/onboarding`. -**`toExternalWorkspaceID` format**: Internal org IDs are `org_`. Strip the prefix and prepend `wspace_` to get the external workspace ID used in the access token's `xoid` claim. +**CORS / cookie not sent**: Ensure `AllowCredentials: true` is set in CORS config. ## Reference @@ -331,7 +320,7 @@ r.Use(cors.New(cors.Config{ ### SameSite=Lax — set explicitly on each cookie -Gin's `c.SetCookie` does not expose a `SameSite` parameter. Use `http.SetCookie` directly for full control: +Gin's `c.SetCookie` does not expose a `SameSite` parameter. Use `http.SetCookie` directly: ```go http.SetCookie(c.Writer, &http.Cookie{ @@ -340,13 +329,11 @@ http.SetCookie(c.Writer, &http.Cookie{ Path: "/", MaxAge: 86400, HttpOnly: true, - SameSite: http.SameSiteLaxMode, // Required — Strict drops cookie on OAuth redirect back + SameSite: http.SameSiteLaxMode, Secure: !strings.Contains(c.Request.Host, "localhost"), }) ``` -`SameSite: Strict` drops the session cookie on the cross-origin redirect from Scalekit back to `/api/scalekit/callback` — the callback receives no cookies and the auth flow breaks silently. - ### Secure flag in production Never hardcode `secure: false`. Detect localhost at runtime: @@ -357,43 +344,24 @@ func isSecure(c *gin.Context) bool { } ``` -Pass `Secure: isSecure(c)` when setting every cookie. This ensures `Secure` is always set in production (HTTPS) without breaking local development. - ### CSRF via state parameter -The base64-encoded state in `AuthorizeHandler` already carries a CSRF token (`"csrf": randomString(12)`). Validate it in `CallbackHandler` before exchanging the code: - -```go -stateRaw, err := base64.StdEncoding.DecodeString(c.Query("state")) -if err != nil { - c.JSON(400, gin.H{"error": "invalid state"}) - return -} -var stateData map[string]string -json.Unmarshal(stateRaw, &stateData) -// Optionally compare stateData["csrf"] against a cookie set in AuthorizeHandler -``` - -For stronger CSRF protection, store the `csrf` value in a short-lived cookie in `AuthorizeHandler` and verify it matches in `CallbackHandler`. +The base64-encoded state in `AuthorizeHandler` already carries a CSRF token. Validate it in `CallbackHandler` before exchanging the code. ### Deep link preservation via state -The state JSON already includes `"next"`. After a successful callback, extract it and redirect: +The state JSON includes `"next"`. After a successful callback, extract it and redirect: ```go next := stateData["next"] if next == "" || !strings.HasPrefix(next, "/") { - next = "/dashboard" // prevent open redirect + next = "/dashboard" } c.Redirect(http.StatusFound, getUIBaseURL(c)+next) ``` -Never redirect to an absolute URL from state — only relative paths starting with `/`. - ### Cache-Control: no-store on protected endpoints -After logout, the browser back button can serve a cached `/api/session` response showing the user as authenticated. Add the header on every session/protected response: - ```go func SessionHandler(c *gin.Context) { c.Header("Cache-Control", "no-store") @@ -403,39 +371,16 @@ func SessionHandler(c *gin.Context) { ### Token refresh race condition -Multiple browser tabs hitting `/api/session` simultaneously can each attempt a refresh with the same refresh token — the second call will receive `invalid_grant`. Use a per-user mutex or a distributed lock: - -```go -var refreshMu sync.Map // keyed by refresh token hash - -func SessionHandler(c *gin.Context) { - refreshToken, _ := c.Cookie("auth_refresh_token") - key := fmt.Sprintf("%x", sha256.Sum256([]byte(refreshToken))) - - mu, _ := refreshMu.LoadOrStore(key, &sync.Mutex{}) - mu.(*sync.Mutex).Lock() - defer mu.(*sync.Mutex).Unlock() - // ...refresh logic... -} -``` - -For stateless deployments, treat `invalid_grant` on refresh as a session expiry and redirect to login rather than erroring. +Multiple browser tabs hitting `/api/session` simultaneously can each attempt a refresh with the same refresh token. Use a per-user mutex or treat `invalid_grant` on refresh as session expiry. ### 401 vs redirect for JSON clients -If a JavaScript frontend calls `/api/session` and gets a `302` redirect, the browser follows it silently and the client receives HTML instead of JSON. Return `401` for `Accept: application/json` requests: +Return `401` for `Accept: application/json` requests instead of redirecting: ```go -func SessionHandler(c *gin.Context) { - accessToken, err := c.Cookie("auth_access_token") - if err != nil || accessToken == "" { - if strings.Contains(c.GetHeader("Accept"), "application/json") { - c.JSON(401, gin.H{"error": "unauthenticated"}) - } else { - c.Redirect(http.StatusFound, "/login") - } - return - } - // ... +if strings.Contains(c.GetHeader("Accept"), "application/json") { + c.JSON(401, gin.H{"error": "unauthenticated"}) +} else { + c.Redirect(http.StatusFound, "/login") } ``` diff --git a/plugins/saaskit/skills/implementing-saaskit/laravel-reference.md b/plugins/saaskit/skills/implementing-saaskit/laravel-reference.md new file mode 100644 index 0000000..9891a98 --- /dev/null +++ b/plugins/saaskit/skills/implementing-saaskit/laravel-reference.md @@ -0,0 +1,208 @@ +# Scalekit Auth — Laravel + +Reference repo: [scalekit-inc/scalekit-laravel-auth-example](https://github.com/scalekit-inc/scalekit-laravel-auth-example) + +## Project structure + +``` +app/ +├── Services/ +│ └── ScalekitClient.php # Raw HTTP OAuth client (no PHP SDK) +├── Http/ +│ ├── Controllers/ +│ │ └── AuthController.php +│ └── Middleware/ +│ ├── ScalekitAuth.php # Session auth gate +│ ├── ScalekitPermission.php # Per-route permission check +│ └── ScalekitTokenRefresh.php # Auto token refresh on every request + +config/ +└── scalekit.php # Reads from env via config('scalekit.*') + +routes/ +└── web.php # Named routes + middleware groups +``` + +## Environment variables + +```env +SCALEKIT_ENV_URL=https://your-env.scalekit.io +SCALEKIT_CLIENT_ID=your-client-id +SCALEKIT_CLIENT_SECRET=your-client-secret +SCALEKIT_REDIRECT_URI=http://localhost:8000/auth/callback +``` + +> No official Scalekit PHP SDK exists. This app uses **Laravel's `Http` facade** with raw HTTP calls. + +## Key methods (ScalekitClient service) + +| Method | HTTP call | Auth | +|---|---|---| +| `getAuthorizationUrl($state)` | Builds `{env_url}/oauth/authorize?response_type=code&...` | None | +| `exchangeCodeForTokens($code)` | `POST {env_url}/oauth/token` with `grant_type=authorization_code` | Basic Auth | +| `refreshAccessToken($refreshToken)` | `POST {env_url}/oauth/token` with `grant_type=refresh_token` | Basic Auth | +| `validateTokenAndGetClaims($token)` | Manual base64 JWT decode — no signature verification | — | +| `hasPermission($token, $permission)` | Decodes JWT, checks permission claim chain | — | +| `logout($accessToken)` | Builds `{env_url}/oidc/logout?post_logout_redirect_uri=...` | None | + +## Session storage schema + +```php +session([ + 'scalekit_user' => [ + 'sub', 'email', 'name', 'given_name', 'family_name', + 'preferred_username', + 'claims' // merged array of ALL claims + ], + 'scalekit_tokens' => [ + 'access_token', 'refresh_token', 'id_token', + 'expires_at', 'expires_in', + ], + 'scalekit_roles' => [], + 'scalekit_permissions' => [], +]); +``` + +## Auth flow + +### Login (`GET /login`) + +```php +$state = Str::random(32); +session(['oauth_state' => $state]); +$authUrl = $this->scalekitClient->getAuthorizationUrl($state); +return view('auth.login', ['auth_url' => $authUrl]); +``` + +### Callback (`GET /auth/callback`) + +1. Validate `state` vs `session('oauth_state')` → 400 on mismatch +2. Exchange code for tokens +3. Decode ID token + access token claims, merge +4. Write session keys +5. Redirect to dashboard + +### Logout (`GET|POST /logout`) + +```php +$logoutUrl = $this->scalekitClient->logout($accessToken); +session()->flush(); +return redirect($logoutUrl); +``` + +## Middleware + +### Registration in `bootstrap/app.php` (Laravel 11) + +```php +->withMiddleware(function (Middleware $middleware) { + $middleware->alias([ + 'scalekit.auth' => \App\Http\Middleware\ScalekitAuth::class, + 'scalekit.permission' => \App\Http\Middleware\ScalekitPermission::class, + ]); + $middleware->append(\App\Http\Middleware\ScalekitTokenRefresh::class); +}) +``` + +### `ScalekitAuth` — session gate + +Redirects to `auth.login` with `->with('next', $request->path())` if `scalekit_user` session key is missing. + +### `ScalekitPermission` — parameterised permission check + +Validates access token claims via `ScalekitClient::hasPermission()`. On failure: 403 view. + +### `ScalekitTokenRefresh` — auto refresh on every request + +Skipped paths: `login`, `auth/callback`, `logout`. Buffer: 5 minutes. On `invalid_grant`: flush session. + +## Routes + +```php +// Public +Route::get('/', [AuthController::class, 'home'])->name('auth.home'); +Route::get('/login', [AuthController::class, 'login'])->name('auth.login'); +Route::get('/auth/callback', [AuthController::class, 'callback'])->name('auth.callback'); + +// Protected group +Route::middleware(['scalekit.auth'])->group(function () { + Route::get('/dashboard', [AuthController::class, 'dashboard'])->name('auth.dashboard'); + Route::match(['get', 'post'], '/logout', [AuthController::class, 'logout'])->name('auth.logout'); + Route::get('/sessions', [AuthController::class, 'sessions'])->name('auth.sessions'); + Route::post('/sessions/refresh-token', [AuthController::class, 'refreshToken'])->name('auth.refresh_token'); + + Route::get('/organization/settings', [AuthController::class, 'organizationSettings']) + ->middleware('scalekit.permission:organization:settings') + ->name('auth.organization_settings'); +}); +``` + +## Install + +```bash +composer require firebase/php-jwt # Only if using JWT signature verification +php artisan key:generate +php artisan migrate +php artisan serve +``` + +Copy `.env.example` to `.env` and fill in the four `SCALEKIT_*` values. + +## Tactics + +### SameSite=Lax — required for OAuth callbacks + +In `config/session.php`: + +```php +'same_site' => 'lax', +'secure' => env('SESSION_SECURE_COOKIE', false), +'http_only' => true, +``` + +### CSRF exclusion for the OAuth callback + +GET callbacks are not subject to CSRF. For webhook POST endpoints, exclude them in `bootstrap/app.php`. + +### Deep link preservation + +```php +// In login +$next = $request->query('next', route('auth.dashboard')); +if (!str_starts_with($next, '/')) { $next = route('auth.dashboard'); } +session(['next' => $next]); + +// In callback +$next = session()->pull('next', route('auth.dashboard')); +return redirect($next); +``` + +### Cache-Control: no-store on protected responses + +```php +return response() + ->view('auth.dashboard', ['user' => session('scalekit_user', [])]) + ->header('Cache-Control', 'no-store'); +``` + +### CORS for JavaScript clients + +In `config/cors.php`: + +```php +'paths' => ['api/*', 'auth/*', 'sessions/*'], +'allowed_origins' => ['http://localhost:3000'], +'supports_credentials' => true, +``` + +### Session fixation after login + +```php +session()->regenerate(); +return redirect($next); +``` + +## Reference + +- Full working example: [scalekit-inc/scalekit-laravel-auth-example](https://github.com/scalekit-inc/scalekit-laravel-auth-example) +- Scalekit docs: https://docs.scalekit.com diff --git a/plugins/full-stack-auth/skills/implementing-scalekit-springboot-auth/SKILL.md b/plugins/saaskit/skills/implementing-saaskit/springboot-reference.md similarity index 64% rename from plugins/full-stack-auth/skills/implementing-scalekit-springboot-auth/SKILL.md rename to plugins/saaskit/skills/implementing-saaskit/springboot-reference.md index fb6b34a..3a9dc8a 100644 --- a/plugins/full-stack-auth/skills/implementing-scalekit-springboot-auth/SKILL.md +++ b/plugins/saaskit/skills/implementing-saaskit/springboot-reference.md @@ -1,8 +1,3 @@ ---- -name: implementing-scalekit-springboot-auth -description: Guides Java developers integrating Scalekit OIDC authentication into Spring Boot 3.x apps. Use when the developer mentions Scalekit, enterprise SSO, OIDC login, OAuth2 client setup, protected routes, ID token claims, or logout in a Spring Boot project. ---- - # Scalekit Auth in Spring Boot Scalekit acts as an OIDC provider. Spring Security's `oauth2-client` starter handles the full @@ -21,7 +16,6 @@ Add to `pom.xml` (Spring Boot 3.2+, Java 17+): org.springframework.boot spring-boot-starter-security - com.scalekit scalekit-sdk-java @@ -62,8 +56,6 @@ spring: user-name-attribute: sub ``` -For local dev, use `application-local.properties` — never commit secrets. - ## Scalekit SDK bean ```java @@ -136,9 +128,6 @@ public String dashboard(@AuthenticationPrincipal OidcUser oidcUser, Model model) } ``` -Key `OidcUser` accessors: `getFullName()`, `getEmail()`, `getSubject()`, `getClaims()`, -`getAuthorities()`. - ## Application routes | Route | Auth? | Notes | @@ -159,12 +148,9 @@ Key `OidcUser` accessors: `getFullName()`, `getEmail()`, `getSubject()`, `getCla - [ ] Optionally add post-logout redirect: http://localhost:8080 ``` -## Workflows - -### Add Scalekit auth to an existing Spring Boot app +## Workflow ``` -Progress: - [ ] Step 1: Add Maven dependencies - [ ] Step 2: Add application.yml OAuth2 provider/registration config - [ ] Step 3: Create ScalekitConfig bean @@ -176,76 +162,51 @@ Progress: ## Troubleshooting -**JWKS timeout / JWT verification errors**: Spring Security fetches JWKS on every token -validation. Increase the decoder timeout — see [Spring docs on JWT decoder timeouts](https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html#oauth2resourceserver-jwt-timeouts). +**JWKS timeout / JWT verification errors**: Spring Security fetches JWKS on every token validation. Increase the decoder timeout. -**Redirect URI mismatch**: The `redirect-uri` in `application.yml` must exactly match what is -registered in the Scalekit dashboard. +**Redirect URI mismatch**: The `redirect-uri` in `application.yml` must exactly match what is registered in the Scalekit dashboard. -**Enable debug logging** in `application.yml`: +**Enable debug logging**: ```yaml logging: level: org.springframework.security.oauth2: TRACE - com.example.scalekit: DEBUG ``` ## Reference - Full working example: [scalekit-inc/scalekit-springboot-auth-example](https://github.com/scalekit-inc/scalekit-springboot-auth-example) - Scalekit docs: https://docs.scalekit.com -- Spring Security OAuth2 docs: https://docs.spring.io/spring-security/reference/servlet/oauth2 ## Tactics ### SameSite=Lax on the session cookie -Spring Boot does not set `SameSite` by default. Add to `application.yml`: ```yaml server: servlet: session: cookie: - same-site: lax # Required — 'strict' breaks OAuth callbacks + same-site: lax http-only: true - secure: true # Set to true in production (HTTPS) + secure: true ``` -Without `SameSite: Lax`, some browsers drop the session cookie on the cross-origin redirect from Scalekit back to your app, causing the OAuth state to be lost. - -### Deep link preservation — use SavedRequestAwareAuthenticationSuccessHandler -The default `defaultSuccessUrl("/dashboard", true)` ignores the originally requested URL. Remove `true` to restore the saved-request redirect: +### Deep link preservation -```java -.oauth2Login(oauth2 -> oauth2 - .loginPage("/login") - .defaultSuccessUrl("/dashboard") // without `true` — respects saved request -) -``` - -Spring Security stores the pre-login URL in the session automatically via `RequestCache`. The user lands on `/dashboard` only if no prior request was saved. +Remove `true` from `defaultSuccessUrl("/dashboard", true)` to respect saved-request redirect. ### CORS for browser clients -Add CORS support in `SecurityFilterChain`: ```java -@Bean -public SecurityFilterChain filterChain(HttpSecurity http, - ClientRegistrationRepository clientRegistrationRepository) throws Exception { - http - .cors(cors -> cors.configurationSource(corsConfigurationSource())) - ... - return http.build(); -} - @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(List.of("http://localhost:3000")); config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); config.setAllowedHeaders(List.of("*")); - config.setAllowCredentials(true); // required for session cookies + config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; @@ -253,7 +214,6 @@ public CorsConfigurationSource corsConfigurationSource() { ``` ### AJAX: 401 instead of redirect -Spring Security redirects unauthenticated requests to the login page by default. Browser AJAX clients expect `401`, not `302`. Override the entry point in `SecurityFilterChain`: ```java .exceptionHandling(ex -> ex @@ -267,21 +227,6 @@ Spring Security redirects unauthenticated requests to the login page by default. })) ``` -### Cache-Control: no-store on protected responses - -```java -@GetMapping("/dashboard") -public String dashboard(@AuthenticationPrincipal OidcUser oidcUser, - Model model, HttpServletResponse response) { - response.setHeader("Cache-Control", "no-store"); - model.addAttribute("name", oidcUser.getFullName()); - model.addAttribute("email", oidcUser.getEmail()); - return "dashboard"; -} -``` - -### CSRF and OAuth2 — what Spring Security does automatically -Spring Security disables CSRF protection for OAuth2 login endpoints (`/oauth2/authorization/**` and `/login/oauth2/code/**`) by default. The `state` parameter in the authorization URL serves as the CSRF token for the OAuth flow. You do not need to add any CSRF configuration for basic Scalekit auth. - ### OIDC logout vs local logout -`OidcClientInitiatedLogoutSuccessHandler` calls the Scalekit end-session endpoint and revokes the IdP session. If you use a plain `logoutSuccessUrl()` instead, only the local Spring session is cleared — the user will be silently re-authenticated on the next login attempt. Always use the OIDC handler. + +`OidcClientInitiatedLogoutSuccessHandler` calls the Scalekit end-session endpoint. Always use the OIDC handler — a plain `logoutSuccessUrl()` only clears the local session. diff --git a/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md b/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md new file mode 100644 index 0000000..2419ab8 --- /dev/null +++ b/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md @@ -0,0 +1,57 @@ +--- +name: implementing-scim-provisioning +description: Implements SCIM user provisioning using Scalekit directory API and webhooks for real-time user and group lifecycle management. Use when adding directory sync, user provisioning, or automated user lifecycle management. +--- + +# SaaSKit SCIM Provisioning + +Implements automated user and group lifecycle management via SCIM directory sync and Scalekit webhooks. + +## What SCIM covers + +SCIM (System for Cross-domain Identity Management) automates the user lifecycle between an enterprise IdP and your app: + +- **User provisioning** — new users in the IdP are automatically created in your app. +- **User deprovisioning** — removed users are deactivated (prefer deactivation over hard delete). +- **Profile updates** — name, email, and attribute changes sync automatically. +- **Group sync** — IdP groups map to roles/permissions in your app. + +## Webhook setup overview + +Scalekit delivers SCIM events to your webhook endpoint in real time: + +1. **Register** a webhook endpoint in Scalekit dashboard or via SDK. +2. **Validate** every request using the webhook signature (HMAC). +3. **Handle** these event types: + +| Event | Action | +|---|---| +| `user_created` | Create user in your DB, assign default role | +| `user_updated` | Update profile fields | +| `user_deleted` | Deactivate user (don't hard delete) | +| `group_created` | Create role/group mapping | +| `group_updated` | Update role assignments | +| `group_deleted` | Remove role mapping | + +4. **Return 2xx quickly** — offload heavy processing to a background queue. +5. **Handle idempotently** — duplicate events must not create duplicate records. + +## Retry behavior + +Scalekit retries on non-2xx responses with exponential backoff (up to 8 attempts over ~10 hours). + +## Integration with SSO + +SCIM provisioning works alongside SSO — SSO handles authentication, SCIM handles the user lifecycle. For enterprises that use both, configure SCIM first so users exist before their first SSO login. + +## Deep reference + +- SCIM patterns and code: [../../docs/scim.md](../../docs/scim.md) +- SSO (complementary feature): [../../docs/sso.md](../../docs/sso.md) +- Access control (role mapping from groups): [../../docs/access-control.md](../../docs/access-control.md) + +## When to switch skills + +- Use `implementing-modular-sso` for SSO configuration alongside SCIM. +- Use `implementing-access-control` for mapping SCIM groups to app permissions. +- Use `production-readiness-saaskit` to validate SCIM webhooks before launch. diff --git a/plugins/saaskit/skills/managing-saaskit-sessions/SKILL.md b/plugins/saaskit/skills/managing-saaskit-sessions/SKILL.md new file mode 100644 index 0000000..1abee86 --- /dev/null +++ b/plugins/saaskit/skills/managing-saaskit-sessions/SKILL.md @@ -0,0 +1,66 @@ +--- +name: managing-saaskit-sessions +description: Manages Scalekit SaaSKit user sessions by securely storing tokens, validating access tokens on requests, refreshing tokens in middleware, and revoking sessions via Scalekit APIs. Use when building session persistence for web apps or auditing session management. +--- + +# SaaSKit Session Management + +Covers secure token storage, validation middleware, silent refresh, and session revocation for Scalekit-powered apps. + +## What this skill covers + +- Choosing a token storage strategy (httpOnly cookies vs server-side session store) +- Validating access tokens on every authenticated request +- Implementing silent token refresh in middleware +- Revoking sessions and handling forced logout +- Auditing active sessions + +## Inputs to collect + +Before implementing, determine: + +1. **App type** — server-rendered (Next.js, Django, Rails) or SPA + API? +2. **Framework** — which SDK/framework is in use? (routes to correct patterns) +3. **Token storage plan** — httpOnly cookies (default recommendation) or server-side store (Redis, DB)? + +## Session lifecycle + +``` +Login callback → Store tokens → Validate on each request → Refresh when expired → Revoke on logout +``` + +### Storage recommendations + +| App type | Recommended storage | Notes | +|---|---|---| +| Server-rendered | httpOnly + secure + SameSite=Lax cookies | Simplest; no JS access to tokens | +| SPA + API | Server-side session store (Redis) | API sets a session ID cookie; tokens stay server-side | +| Mobile / native | Secure enclave / Keychain | Never store in localStorage | + +### Token refresh pattern + +Middleware should check token expiry **before** it expires — use a 5-minute buffer: + +``` +if (tokenExpiresAt - now < 5 minutes) → refresh silently +if (refresh fails with invalid_grant) → clear session → redirect to login +``` + +### Session revocation + +On logout, always: +1. Clear local session/cookies +2. Redirect to Scalekit's end-session endpoint (OIDC RP-initiated logout) +3. The end-session endpoint invalidates the refresh token server-side + +## Deep reference + +- Session patterns and code: [../../docs/sessions.md](../../docs/sessions.md) +- Auth flow (where tokens come from): [../../docs/auth-flows.md](../../docs/auth-flows.md) +- Framework-specific session handling: [../../docs/frameworks/](../../docs/frameworks/) + +## When to switch skills + +- Use `implementing-saaskit` for the initial auth setup that produces the tokens. +- Use `implementing-access-control` for RBAC checks on the validated session. +- Use `production-readiness-saaskit` to audit session security before launch. diff --git a/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/AUDIT-CHECKLIST.md b/plugins/saaskit/skills/migrating-to-saaskit/AUDIT-CHECKLIST.md similarity index 100% rename from plugins/full-stack-auth/skills/migrating-to-scalekit-auth/AUDIT-CHECKLIST.md rename to plugins/saaskit/skills/migrating-to-saaskit/AUDIT-CHECKLIST.md diff --git a/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/IMPORT-SAMPLES.md b/plugins/saaskit/skills/migrating-to-saaskit/IMPORT-SAMPLES.md similarity index 84% rename from plugins/full-stack-auth/skills/migrating-to-scalekit-auth/IMPORT-SAMPLES.md rename to plugins/saaskit/skills/migrating-to-saaskit/IMPORT-SAMPLES.md index 6b149b5..403fb54 100644 --- a/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/IMPORT-SAMPLES.md +++ b/plugins/saaskit/skills/migrating-to-saaskit/IMPORT-SAMPLES.md @@ -65,6 +65,23 @@ CreateUser createUser = CreateUser.newBuilder() scalekitClient.users().createUserAndMembership("org_123", createUser); ``` +## Node.js — Create organization +```javascript +const result = await scalekit.organization.createOrganization( + "Megasoft Inc", + { externalId: "org_123", metadata: { plan: "enterprise" } } +); +``` + +## Node.js — Create user in organization +```javascript +const { user } = await scalekit.user.createUserAndMembership("org_scalekit_id", { + email: "user@example.com", + externalId: "usr_987", + userProfile: { firstName: "John", lastName: "Doe" }, +}); +``` + ## cURL — Create organization ```sh curl -L -X POST '/api/v1/organizations' \ diff --git a/plugins/saaskit/skills/migrating-to-saaskit/SKILL.md b/plugins/saaskit/skills/migrating-to-saaskit/SKILL.md new file mode 100644 index 0000000..2eb934b --- /dev/null +++ b/plugins/saaskit/skills/migrating-to-saaskit/SKILL.md @@ -0,0 +1,74 @@ +--- +name: migrating-to-saaskit +description: Plans and executes incremental migration from any existing authentication system (Auth0, Firebase, Cognito, custom) to Scalekit SaaSKit. Use when a user mentions migrating, switching, or moving away from their current auth provider. +--- + +# SaaSKit Migration Planner + +Guides an incremental, reversible migration from an existing auth system to Scalekit SaaSKit. Follow these phases in order — do not skip phases. + +## Migration checklist + +``` +Migration Progress: +- [ ] Phase 1: Audit and export existing auth data +- [ ] Phase 2: Import organizations and users into Scalekit +- [ ] Phase 3: Configure redirects and roles +- [ ] Phase 4: Update application code +- [ ] Phase 5: Deploy and monitor +``` + +## Phase 1: Audit and export + +Conduct a code audit covering: +- Sign-up/login flows, session middleware, token validation +- RBAC logic, email verification, logout/session termination + +Export: user records, org/tenant structure, role assignments, SSO/IdP configs. + +**Before proceeding:** +- [ ] Export a sample JWT or session cookie (understand current format) +- [ ] Set up a feature flag to roll back to old auth system +- [ ] Document rollback procedure + +See [AUDIT-CHECKLIST.md](AUDIT-CHECKLIST.md) for full code audit patterns. + +## Phase 2: Import organizations and users + +`external_id` is critical — store original PKs to preserve mappings. + +1. Create organizations first (with `externalId`). +2. Create users within organizations (with `externalId`). +3. Set `sendInvitationEmail: false` during import to skip invite emails. +4. Batch imports in parallel; respect Scalekit rate limits. + +For language-specific samples (Node.js, Python, Go, Java, cURL): See [IMPORT-SAMPLES.md](IMPORT-SAMPLES.md). + +## Phase 3: Configure redirects and roles + +- Register callback URLs in **Settings → Redirects** in Scalekit dashboard. +- Define roles under **User Management → Roles** or via SDK. +- Verify role claims are readable from the token after login. + +## Phase 4: Update application code + +- Replace legacy JWT validation with Scalekit SDK or JWKS endpoint. +- Update login page branding in Scalekit dashboard. +- Update secondary flows: email verification, logout redirect. + +## Phase 5: Deploy and monitor + +1. Test login with a subset of migrated users. +2. Enable feature flag → route 5–10% of traffic to Scalekit. +3. Expand after stability confirmed. +4. Keep rollback plan active for first 48 hours. + +## Deep reference + +- Auth flows: [../../docs/auth-flows.md](../../docs/auth-flows.md) | Sessions: [../../docs/sessions.md](../../docs/sessions.md) | SSO: [../../docs/sso.md](../../docs/sso.md) + +## When to switch skills + +- Use `implementing-saaskit` for the new auth integration code. +- Use `implementing-modular-sso` if migrating SSO connections. +- Use `production-readiness-saaskit` before going live with the migration. diff --git a/plugins/saaskit/skills/production-readiness-saaskit/SKILL.md b/plugins/saaskit/skills/production-readiness-saaskit/SKILL.md new file mode 100644 index 0000000..3b18a84 --- /dev/null +++ b/plugins/saaskit/skills/production-readiness-saaskit/SKILL.md @@ -0,0 +1,66 @@ +--- +name: production-readiness-saaskit +description: Walks through a structured production readiness checklist for Scalekit SaaSKit implementations covering authentication, SSO, SCIM, MCP server auth, and API security. Use when going live, launching to production, or doing a pre-launch review. +--- + +# SaaSKit Production Readiness + +Unified checklist for all SaaSKit domains. Work through in order — skip sections that don't apply. + +## Quick checks (run first) + +- [ ] Production env URL, client ID, and client secret set (not dev/staging) +- [ ] HTTPS enforced; CORS restricted to your domains only +- [ ] All credentials in environment variables — never committed to code +- [ ] Only enabled auth methods active in production + +## Customization + +- [ ] Login page branded; email templates customized +- [ ] Custom domain configured (if applicable); email deliverability tested +- [ ] Webhooks configured with signature validation + +## Core auth flows + +- [ ] Login initiation, code exchange, and redirect URLs match dashboard exactly +- [ ] `state` parameter validated in callbacks (CSRF); tokens stored with `httpOnly`, `secure`, `sameSite` +- [ ] Token refresh and session timeout working; logout calls Scalekit end-session +- [ ] Each enabled auth method tested; errors handled gracefully + +## SSO (if applicable) + +- [ ] SSO tested with target IdPs (Okta, Azure AD, Google Workspace) +- [ ] SP-initiated and IdP-initiated flows both working +- [ ] Admin portal configured for self-serve SSO setup +- [ ] JIT provisioning: domains registered, default roles set, attribute sync enabled +- Deep reference: [../../docs/sso.md](../../docs/sso.md) + +## SCIM provisioning (if applicable) + +- [ ] Webhook endpoints receiving events with signature validation +- [ ] User provisioning, deprovisioning, and profile updates tested +- [ ] Group-based role sync working; idempotent handling verified +- Deep reference: [../../docs/scim.md](../../docs/scim.md) + +## MCP authentication (if applicable) + +- [ ] MCP auth flow tested end-to-end; resource metadata published +- [ ] Scopes enforced per tool; client reconnection after token expiry working +- Deep reference: [../../docs/mcp-server-auth.md](../../docs/mcp-server-auth.md) + +## RBAC (if applicable) + +- [ ] Roles and permissions defined; default roles set for new users +- [ ] Permission enforcement verified at API endpoints +- Deep reference: [../../docs/access-control.md](../../docs/access-control.md) + +## Network / firewall + +Enterprise VPN customers must whitelist: `.scalekit.com`, `cdn.scalekit.com`, `fonts.googleapis.com`. + +## Monitoring + +- [ ] Auth logs monitoring active; alerts for suspicious activity configured +- [ ] Webhook monitoring active; error tracking for auth and provisioning failures +- [ ] Incident response runbook written; rollback plan ready (feature flag) +- **Key metrics:** login success/failure rates, session duration, webhook delivery, SSO completion rate diff --git a/scripts/install_claude_marketplace.sh b/scripts/install_claude_marketplace.sh index ce1c53f..73292d2 100755 --- a/scripts/install_claude_marketplace.sh +++ b/scripts/install_claude_marketplace.sh @@ -9,11 +9,11 @@ if ! command -v claude >/dev/null 2>&1; then fi MARKETPLACE_SLUG="${CLAUDE_CODE_AUTHSTACK_MARKETPLACE:-scalekit-inc/claude-code-authstack}" -PLUGIN_SOURCE="${CLAUDE_CODE_AUTHSTACK_PLUGIN_SOURCE:-agent-auth@scalekit-auth-stack}" +PLUGIN_SOURCE="${CLAUDE_CODE_AUTHSTACK_PLUGIN_SOURCE:-agentkit@scalekit-auth-stack}" echo "Installing Scalekit Auth Stack for Claude Code" echo "Marketplace: $MARKETPLACE_SLUG" -echo "Plugin: $PLUGIN_SOURCE" +echo "Default plugin: $PLUGIN_SOURCE" echo claude plugin marketplace add "$MARKETPLACE_SLUG" @@ -21,7 +21,14 @@ claude plugin install "$PLUGIN_SOURCE" cat < Date: Sun, 10 May 2026 20:52:55 +0530 Subject: [PATCH 07/53] remove legacy test-tool.md command alias --- plugins/agentkit/commands/test-tool.md | 34 -------------------------- 1 file changed, 34 deletions(-) delete mode 100644 plugins/agentkit/commands/test-tool.md diff --git a/plugins/agentkit/commands/test-tool.md b/plugins/agentkit/commands/test-tool.md deleted file mode 100644 index 16f117a..0000000 --- a/plugins/agentkit/commands/test-tool.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -description: Legacy compatibility alias for the AgentKit testing skill -argument-hint: "[generate-link|get-tool|execute-tool] [args...]" -allowed-tools: Bash ---- - -# Legacy AgentKit Tool Tester - -This command is a legacy compatibility alias for `/agentkit:testing-agentkit-tools`. - -Prefer invoking the skill directly: - -```text -/agentkit:testing-agentkit-tools $ARGUMENTS -``` - -**Arguments:** $ARGUMENTS - -## Your task - -Parse `$ARGUMENTS` exactly as the testing skill would and run the same bundled script from the plugin root: - -```bash -skills/testing-agentkit-tools/scripts/connect.py -``` - -Keep this command behavior aligned with the skill: - -- use `uv run python` when `uv` exists, otherwise `python3`, otherwise `python` -- accept both `SCALEKIT_*` and legacy `TOOL_*` credential variables -- inspect live metadata before guessing `tool_input` -- show the command output, exact command, resolved parameters, and exact payload for `execute-tool` - -Do not maintain separate workflow rules here. The testing skill is the source of truth. From a19062fac89e8f86a41351dca11b2b7f4dab479f Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Sun, 10 May 2026 21:07:42 +0530 Subject: [PATCH 08/53] remove static agent-connectors, point to docs.scalekit.com/agentkit/connectors --- .../references/agent-connectors/README.md | 78 ----- .../references/agent-connectors/airtable.md | 3 - .../references/agent-connectors/asana.md | 3 - .../references/agent-connectors/attention.md | 3 - .../references/agent-connectors/bigquery.md | 3 - .../references/agent-connectors/chorus.md | 3 - .../agent-connectors/clari_copilot.md | 3 - .../references/agent-connectors/clickup.md | 3 - .../references/agent-connectors/confluence.md | 3 - .../references/agent-connectors/dropbox.md | 3 - .../references/agent-connectors/fathom.md | 3 - .../references/agent-connectors/freshdesk.md | 149 --------- .../references/agent-connectors/github.md | 137 -------- .../references/agent-connectors/gmail.md | 79 ----- .../references/agent-connectors/gong.md | 3 - .../references/agent-connectors/google_ads.md | 3 - .../agent-connectors/google_docs.md | 3 - .../agent-connectors/google_drive.md | 3 - .../agent-connectors/google_forms.md | 3 - .../agent-connectors/google_meets.md | 3 - .../agent-connectors/google_sheets.md | 3 - .../agent-connectors/google_slides.md | 32 -- .../agent-connectors/googlecalendar.md | 128 ------- .../references/agent-connectors/hubspot.md | 143 -------- .../references/agent-connectors/intercom.md | 3 - .../references/agent-connectors/jira.md | 3 - .../references/agent-connectors/linear.md | 58 ---- .../agent-connectors/microsoft_excel.md | 3 - .../agent-connectors/microsoft_teams.md | 3 - .../agent-connectors/microsoft_word.md | 3 - .../references/agent-connectors/monday.md | 3 - .../references/agent-connectors/notion.md | 189 ----------- .../references/agent-connectors/onedrive.md | 3 - .../references/agent-connectors/onenote.md | 3 - .../references/agent-connectors/outlook.md | 3 - .../references/agent-connectors/salesforce.md | 316 ------------------ .../references/agent-connectors/servicenow.md | 3 - .../references/agent-connectors/sharepoint.md | 3 - .../references/agent-connectors/slack.md | 197 ----------- .../references/agent-connectors/snowflake.md | 3 - .../references/agent-connectors/trello.md | 3 - .../references/agent-connectors/zendesk.md | 3 - .../references/agent-connectors/zoom.md | 3 - plugins/agentkit/references/providers.md | 4 +- plugins/agentkit/references/tool-discovery.md | 6 +- 45 files changed, 5 insertions(+), 1607 deletions(-) delete mode 100644 plugins/agentkit/references/agent-connectors/README.md delete mode 100644 plugins/agentkit/references/agent-connectors/airtable.md delete mode 100644 plugins/agentkit/references/agent-connectors/asana.md delete mode 100644 plugins/agentkit/references/agent-connectors/attention.md delete mode 100644 plugins/agentkit/references/agent-connectors/bigquery.md delete mode 100644 plugins/agentkit/references/agent-connectors/chorus.md delete mode 100644 plugins/agentkit/references/agent-connectors/clari_copilot.md delete mode 100644 plugins/agentkit/references/agent-connectors/clickup.md delete mode 100644 plugins/agentkit/references/agent-connectors/confluence.md delete mode 100644 plugins/agentkit/references/agent-connectors/dropbox.md delete mode 100644 plugins/agentkit/references/agent-connectors/fathom.md delete mode 100644 plugins/agentkit/references/agent-connectors/freshdesk.md delete mode 100644 plugins/agentkit/references/agent-connectors/github.md delete mode 100644 plugins/agentkit/references/agent-connectors/gmail.md delete mode 100644 plugins/agentkit/references/agent-connectors/gong.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_ads.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_docs.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_drive.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_forms.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_meets.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_sheets.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_slides.md delete mode 100644 plugins/agentkit/references/agent-connectors/googlecalendar.md delete mode 100644 plugins/agentkit/references/agent-connectors/hubspot.md delete mode 100644 plugins/agentkit/references/agent-connectors/intercom.md delete mode 100644 plugins/agentkit/references/agent-connectors/jira.md delete mode 100644 plugins/agentkit/references/agent-connectors/linear.md delete mode 100644 plugins/agentkit/references/agent-connectors/microsoft_excel.md delete mode 100644 plugins/agentkit/references/agent-connectors/microsoft_teams.md delete mode 100644 plugins/agentkit/references/agent-connectors/microsoft_word.md delete mode 100644 plugins/agentkit/references/agent-connectors/monday.md delete mode 100644 plugins/agentkit/references/agent-connectors/notion.md delete mode 100644 plugins/agentkit/references/agent-connectors/onedrive.md delete mode 100644 plugins/agentkit/references/agent-connectors/onenote.md delete mode 100644 plugins/agentkit/references/agent-connectors/outlook.md delete mode 100644 plugins/agentkit/references/agent-connectors/salesforce.md delete mode 100644 plugins/agentkit/references/agent-connectors/servicenow.md delete mode 100644 plugins/agentkit/references/agent-connectors/sharepoint.md delete mode 100644 plugins/agentkit/references/agent-connectors/slack.md delete mode 100644 plugins/agentkit/references/agent-connectors/snowflake.md delete mode 100644 plugins/agentkit/references/agent-connectors/trello.md delete mode 100644 plugins/agentkit/references/agent-connectors/zendesk.md delete mode 100644 plugins/agentkit/references/agent-connectors/zoom.md diff --git a/plugins/agentkit/references/agent-connectors/README.md b/plugins/agentkit/references/agent-connectors/README.md deleted file mode 100644 index a1d5853..0000000 --- a/plugins/agentkit/references/agent-connectors/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# Agent Connectors Reference - -> Canonical entrypoint: [../../docs/connectors/README.md](../../docs/connectors/README.md) - -This directory contains curated notes for AgentKit connectors in Scalekit. - -Use these files for connector-specific guidance, auth quirks, and example workflows. Do not treat them as the exhaustive source of truth for current tools or schemas; use live AgentKit tool metadata for that. - -## Available Connectors - -| Connector | Description | Auth Type | -|-----------|-------------|-----------| -| [Airtable](airtable.md) | Connect to Airtable bases for data management | OAuth 2.0 | -| [Asana](asana.md) | Project management and task tracking | OAuth 2.0 | -| [Attention](attention.md) | AI insights, conversations, teams, and workflows | API Key | -| [BigQuery](bigquery.md) | Google BigQuery data warehouse | OAuth 2.0 | -| [Chorus](chorus.md) | Sync calls, transcripts, conversation intelligence, and analytics | Basic Auth | -| [Clari Copilot](clari_copilot.md) | Sales call transcripts, analytics, call data, and insights | API Key | -| [ClickUp](clickup.md) | Project management and collaboration | OAuth 2.0 | -| [Confluence](confluence.md) | Atlassian Confluence wiki pages | OAuth 2.0 | -| [Dropbox](dropbox.md) | File storage and sharing | OAuth 2.0 | -| [Fathom](fathom.md) | Website analytics | OAuth 2.0 | -| [Freshdesk](freshdesk.md) | Customer support ticketing | OAuth 2.0 | -| [GitHub](github.md) | Code repository and development tools | OAuth 2.0 | -| [Gmail](gmail.md) | Google Gmail email service | OAuth 2.0 | -| [Google Ads](google_ads.md) | Google advertising platform | OAuth 2.0 | -| [Google Calendar](googlecalendar.md) | Google Calendar events and scheduling | OAuth 2.0 | -| [Google Docs](google_docs.md) | Google Docs document editing | OAuth 2.0 | -| [Google Drive](google_drive.md) | Google Drive file storage | OAuth 2.0 | -| [Google Forms](google_forms.md) | Google Forms survey creation | OAuth 2.0 | -| [Google Meet](google_meets.md) | Google Meet video conferencing | OAuth 2.0 | -| [Google Sheets](google_sheets.md) | Google Sheets spreadsheet editing | OAuth 2.0 | -| [Google Slides](google_slides.md) | Create, read, and modify presentations programmatically | OAuth 2.0 | -| [Gong](gong.md) | Sales conversation intelligence | OAuth 2.0 | -| [HubSpot](hubspot.md) | CRM and marketing automation | OAuth 2.0 | -| [Intercom](intercom.md) | Customer messaging platform | OAuth 2.0 | -| [Jira](jira.md) | Atlassian Jira issue tracking | OAuth 2.0 | -| [Linear](linear.md) | Software development issue tracking | OAuth 2.0 | -| [Microsoft Excel](microsoft_excel.md) | Microsoft Excel spreadsheet editing | OAuth 2.0 | -| [Microsoft Teams](microsoft_teams.md) | Microsoft Teams collaboration | OAuth 2.0 | -| [Microsoft Word](microsoft_word.md) | Microsoft Word document editing | OAuth 2.0 | -| [Monday](monday.md) | Work management platform | OAuth 2.0 | -| [Notion](notion.md) | Notion workspace and pages | OAuth 2.0 | -| [OneDrive](onedrive.md) | Microsoft OneDrive file storage | OAuth 2.0 | -| [OneNote](onenote.md) | Microsoft OneNote note-taking | OAuth 2.0 | -| [Outlook](outlook.md) | Microsoft Outlook email | OAuth 2.0 | -| [Salesforce](salesforce.md) | Salesforce CRM platform | OAuth 2.0 | -| [ServiceNow](servicenow.md) | IT service management | OAuth 2.0 | -| [SharePoint](sharepoint.md) | Microsoft SharePoint collaboration | OAuth 2.0 | -| [Slack](slack.md) | Slack messaging and collaboration | OAuth 2.0 | -| [Snowflake](snowflake.md) | Snowflake data warehouse | OAuth 2.0 | -| [Trello](trello.md) | Trello project boards | OAuth 2.0 | -| [Zendesk](zendesk.md) | Customer support platform | OAuth 2.0 | -| [Zoom](zoom.md) | Zoom video conferencing | OAuth 2.0 | - -## Getting Started - -Each connector document can include: - -- Service description and capabilities -- Authentication requirements -- Product and workflow context -- Authentication requirements -- Example tool patterns and usage notes -- Usage guidelines and best practices - -For the live tool catalog and current `input_schema` / `output_schema`, use [../tool-discovery.md](../tool-discovery.md). - -## Authentication - -Connectors support OAuth 2.0, API Key, or Basic Auth authentication through AgentKit. You'll need to: - -1. Create a connection for the desired service -2. Configure OAuth credentials in your connection -3. Create connected accounts for your users -4. Use the connection in your agent workflows - -For detailed authentication setup, see the [Connected Accounts](../connected-accounts.md) documentation. \ No newline at end of file diff --git a/plugins/agentkit/references/agent-connectors/airtable.md b/plugins/agentkit/references/agent-connectors/airtable.md deleted file mode 100644 index f3acf0b..0000000 --- a/plugins/agentkit/references/agent-connectors/airtable.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Airtable. Manage databases, tables, records, and collaborate on structured data - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/asana.md b/plugins/agentkit/references/agent-connectors/asana.md deleted file mode 100644 index cea2fc7..0000000 --- a/plugins/agentkit/references/agent-connectors/asana.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Asana. Manage tasks, projects, teams, and workflow automation - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/attention.md b/plugins/agentkit/references/agent-connectors/attention.md deleted file mode 100644 index 9af975e..0000000 --- a/plugins/agentkit/references/agent-connectors/attention.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Attention for AI insights, conversations, teams, and workflows - -Supports authentication: API Key diff --git a/plugins/agentkit/references/agent-connectors/bigquery.md b/plugins/agentkit/references/agent-connectors/bigquery.md deleted file mode 100644 index 64b8a33..0000000 --- a/plugins/agentkit/references/agent-connectors/bigquery.md +++ /dev/null @@ -1,3 +0,0 @@ -BigQuery is Google Cloud’s fully-managed enterprise data warehouse for analytics at scale. - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/chorus.md b/plugins/agentkit/references/agent-connectors/chorus.md deleted file mode 100644 index b57dd23..0000000 --- a/plugins/agentkit/references/agent-connectors/chorus.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Chorus.ai to sync calls, transcripts, conversation intelligence, and analytics. - -Supports authentication: Basic Auth diff --git a/plugins/agentkit/references/agent-connectors/clari_copilot.md b/plugins/agentkit/references/agent-connectors/clari_copilot.md deleted file mode 100644 index 2949e94..0000000 --- a/plugins/agentkit/references/agent-connectors/clari_copilot.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Clari Copilot for sales call transcripts, analytics, call data, and insights. - -Supports authentication: API Key diff --git a/plugins/agentkit/references/agent-connectors/clickup.md b/plugins/agentkit/references/agent-connectors/clickup.md deleted file mode 100644 index dd7d216..0000000 --- a/plugins/agentkit/references/agent-connectors/clickup.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to ClickUp. Manage tasks, projects, workspaces, and team collaboration - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/confluence.md b/plugins/agentkit/references/agent-connectors/confluence.md deleted file mode 100644 index 96d699f..0000000 --- a/plugins/agentkit/references/agent-connectors/confluence.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Confluence. Manage spaces, pages, content, and team collaboration - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/dropbox.md b/plugins/agentkit/references/agent-connectors/dropbox.md deleted file mode 100644 index 1bfe1d3..0000000 --- a/plugins/agentkit/references/agent-connectors/dropbox.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Dropbox. Manage files, folders, sharing, and cloud storage workflows - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/fathom.md b/plugins/agentkit/references/agent-connectors/fathom.md deleted file mode 100644 index 3408c98..0000000 --- a/plugins/agentkit/references/agent-connectors/fathom.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Fathom AI meeting assistant. Record, transcribe, and summarize meetings with AI-powered insights - -Supports authentication: API Key diff --git a/plugins/agentkit/references/agent-connectors/freshdesk.md b/plugins/agentkit/references/agent-connectors/freshdesk.md deleted file mode 100644 index 424a753..0000000 --- a/plugins/agentkit/references/agent-connectors/freshdesk.md +++ /dev/null @@ -1,149 +0,0 @@ -Connect to Freshdesk. Manage tickets, contacts, companies, and customer support workflows - -Supports authentication: Basic Auth - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `freshdesk_agent_create` - -Create a new agent in Freshdesk. Email is required and must be unique. Agent will receive invitation email to set up account. At least one role must be assigned. - -| Properties | Description | Type | -| --- | --- | --- | -| `agent_type` | Type of agent (1=Support Agent, 2=Field Agent, 3=Collaborator) | number | null | -| `email` | Email address of the agent (must be unique) | string | -| `focus_mode` | Focus mode setting for the agent | boolean | null | -| `group_ids` | Array of group IDs to assign the agent to | `array` | null | -| `language` | Language preference of the agent | string | null | -| `name` | Full name of the agent | string | null | -| `occasional` | Whether the agent is occasional (true) or full-time (false) | boolean | null | -| `role_ids` | Array of role IDs to assign to the agent (at least one required) | `array` | -| `signature` | Agent email signature in HTML format | string | null | -| `skill_ids` | Array of skill IDs to assign to the agent | `array` | null | -| `ticket_scope` | Ticket permission level (1=Global Access, 2=Group Access, 3=Restricted Access) | number | -| `time_zone` | Time zone of the agent | string | null | - -## `freshdesk_agent_delete` - -Delete an agent from Freshdesk. This action is irreversible and will remove the agent from the system. The agent will no longer have access to the helpdesk and all associated data will be permanently deleted. - -| Properties | Description | Type | -| --- | --- | --- | -| `agent_id` | ID of the agent to delete | number | - -## `freshdesk_agents_list` - -Retrieve a list of agents from Freshdesk with filtering options. Returns agent details including IDs, contact information, roles, and availability status. Supports pagination with up to 100 agents per page. - -| Properties | Description | Type | -| --- | --- | --- | -| `email` | Filter agents by email address | string | null | -| `mobile` | Filter agents by mobile number | string | null | -| `page` | Page number for pagination (starts from 1) | number | null | -| `per_page` | Number of agents per page (max 100) | number | null | -| `phone` | Filter agents by phone number | string | null | -| `state` | Filter agents by state (fulltime or occasional) | string | null | - -## `freshdesk_contact_create` - -Create a new contact in Freshdesk. Email and name are required. Supports custom fields, company assignment, and contact segmentation. - -| Properties | Description | Type | -| --- | --- | --- | -| `address` | Address of the contact | string | null | -| `company_id` | Company ID to associate with the contact | number | null | -| `custom_fields` | Key-value pairs for custom field values | `object` | null | -| `description` | Description about the contact | string | null | -| `email` | Email address of the contact | string | -| `job_title` | Job title of the contact | string | null | -| `language` | Language preference of the contact | string | null | -| `mobile` | Mobile number of the contact | string | null | -| `name` | Full name of the contact | string | -| `phone` | Phone number of the contact | string | null | -| `tags` | Array of tags to associate with the contact | `array` | null | -| `time_zone` | Time zone of the contact | string | null | - -## `freshdesk_roles_list` - -Retrieve a list of all roles from Freshdesk. Returns role details including IDs, names, descriptions, default status, and timestamps. This endpoint provides information about the different permission levels and access controls available in the Freshdesk system. - -## `freshdesk_ticket_create` - -Create a new ticket in Freshdesk. Requires either requester_id, email, facebook_id, phone, twitter_id, or unique_external_id to identify the requester. - -| Properties | Description | Type | -| --- | --- | --- | -| `cc_emails` | Array of email addresses to be added in CC | `array` | null | -| `custom_fields` | Key-value pairs containing custom field names and values | `object` | null | -| `description` | HTML content of the ticket describing the issue | string | null | -| `email` | Email address of the requester. If no contact exists, will be added as new contact. | string | null | -| `group_id` | ID of the group to which the ticket has been assigned | number | null | -| `name` | Name of the requester | string | null | -| `priority` | Priority of the ticket. 1=Low, 2=Medium, 3=High, 4=Urgent | number | null | -| `requester_id` | User ID of the requester. For existing contacts, can be passed instead of email. | number | null | -| `responder_id` | ID of the agent to whom the ticket has been assigned | number | null | -| `source` | Channel through which ticket was created. 1=Email, 2=Portal, 3=Phone, 7=Chat, 9=Feedback Widget, 10=Outbound Email | number | null | -| `status` | Status of the ticket. 2=Open, 3=Pending, 4=Resolved, 5=Closed | number | null | -| `subject` | Subject of the ticket | string | null | -| `tags` | Array of tags to be associated with the ticket | `array` | null | -| `type` | Helps categorize the ticket according to different kinds of issues | string | null | - -## `freshdesk_ticket_get` - -Retrieve details of a specific ticket by ID. Includes ticket properties, conversations, and metadata. - -| Properties | Description | Type | -| --- | --- | --- | -| `include` | Additional resources to include (stats, requester, company, conversations) | string | null | -| `ticket_id` | ID of the ticket to retrieve | number | - -## `freshdesk_ticket_update` - -Update an existing ticket in Freshdesk. Note: Subject and description of outbound tickets cannot be updated. - -| Properties | Description | Type | -| --- | --- | --- | -| `custom_fields` | Key-value pairs containing custom field names and values | `object` | null | -| `description` | HTML content of the ticket (cannot be updated for outbound tickets) | string | null | -| `group_id` | ID of the group to which the ticket has been assigned | number | null | -| `name` | Name of the requester | string | null | -| `priority` | Priority of the ticket. 1=Low, 2=Medium, 3=High, 4=Urgent | number | null | -| `responder_id` | ID of the agent to whom the ticket has been assigned | number | null | -| `status` | Status of the ticket. 2=Open, 3=Pending, 4=Resolved, 5=Closed | number | null | -| `subject` | Subject of the ticket (cannot be updated for outbound tickets) | string | null | -| `tags` | Array of tags to be associated with the ticket | `array` | null | -| `ticket_id` | ID of the ticket to update | number | - -## `freshdesk_tickets_list` - -Retrieve a list of tickets with filtering and pagination. Supports filtering by status, priority, requester, and more. Returns 30 tickets per page by default. - -| Properties | Description | Type | -| --- | --- | --- | -| `company_id` | Filter by company ID | number | null | -| `email` | Filter by requester email | string | null | -| `filter` | Filter name (new_and_my_open, watching, spam, deleted) | string | null | -| `include` | Additional resources to include (description, requester, company, stats) | string | null | -| `page` | Page number for pagination (starts from 1) | number | null | -| `per_page` | Number of tickets per page (max 100) | number | null | -| `requester_id` | Filter by requester ID | number | null | -| `updated_since` | Filter tickets updated since this timestamp (ISO 8601) | string | null | - -## `freshdesk_tickets_reply` - -Add a public reply to a ticket conversation. The reply will be visible to the customer and will update the ticket status if specified. - -| Properties | Description | Type | -| --- | --- | --- | -| `bcc_emails` | Array of email addresses to BCC on the reply | `array` | null | -| `body` | HTML content of the reply | string | -| `cc_emails` | Array of email addresses to CC on the reply | `array` | null | -| `from_email` | Email address to send the reply from | string | null | -| `ticket_id` | ID of the ticket to reply to | number | -| `user_id` | ID of the agent sending the reply | number | null | diff --git a/plugins/agentkit/references/agent-connectors/github.md b/plugins/agentkit/references/agent-connectors/github.md deleted file mode 100644 index c9cf234..0000000 --- a/plugins/agentkit/references/agent-connectors/github.md +++ /dev/null @@ -1,137 +0,0 @@ -GitHub is a cloud-based Git repository hosting service that allows developers to store, manage, and track changes to their code. - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `github_file_contents_get` - -Get the contents of a file or directory from a GitHub repository. Returns Base64 encoded content for files. - -| Properties | Description | Type | -| --- | --- | --- | -| `owner` | The account owner of the repository | string | -| `path` | The content path (file or directory path in the repository) | string | -| `ref` | The name of the commit/branch/tag | string | null | -| `repo` | The name of the repository | string | - -## `github_file_create_update` - -Create a new file or update an existing file in a GitHub repository. Content must be Base64 encoded. Requires SHA when updating existing files. - -| Properties | Description | Type | -| --- | --- | --- | -| `author` | Author information object with name and email | `object` | null | -| `branch` | The branch name | string | null | -| `committer` | Committer information object with name and email | `object` | null | -| `content` | The new file content (Base64 encoded) | string | -| `message` | The commit message for this change | string | -| `owner` | The account owner of the repository | string | -| `path` | The file path in the repository | string | -| `repo` | The name of the repository | string | -| `sha` | The blob SHA of the file being replaced (required when updating existing files) | string | null | - -## `github_issue_create` - -Create a new issue in a repository. Requires push access to set assignees, milestones, and labels. - -| Properties | Description | Type | -| --- | --- | --- | -| `assignees` | GitHub usernames to assign to the issue | `array` | null | -| `body` | The contents of the issue | string | null | -| `labels` | Labels to associate with the issue | `array` | null | -| `milestone` | Milestone number to associate with the issue | number | null | -| `owner` | The account owner of the repository | string | -| `repo` | The name of the repository | string | -| `title` | The title of the issue | string | -| `type` | The name of the issue type | string | null | - -## `github_issues_list` - -List issues in a repository. Both issues and pull requests are returned as issues in the GitHub API. - -| Properties | Description | Type | -| --- | --- | --- | -| `assignee` | Filter by assigned user | string | null | -| `creator` | Filter by issue creator | string | null | -| `direction` | Sort order | string | null | -| `labels` | Filter by comma-separated list of label names | string | null | -| `milestone` | Filter by milestone number or state | string | null | -| `owner` | The account owner of the repository | string | -| `page` | Page number of results to fetch | number | null | -| `per_page` | Number of results per page (max 100) | number | null | -| `repo` | The name of the repository | string | -| `since` | Show issues updated after this timestamp (ISO 8601 format) | string | null | -| `sort` | Property to sort issues by | string | null | -| `state` | Filter by issue state | string | null | - -## `github_public_repos_list` - -List public repositories for a specified user. Does not require authentication. - -| Properties | Description | Type | -| --- | --- | --- | -| `direction` | Sort order | string | null | -| `page` | Page number of results to fetch | number | null | -| `per_page` | Number of results per page (max 100) | number | null | -| `sort` | Property to sort repositories by | string | null | -| `type` | Filter repositories by type | string | null | -| `username` | The GitHub username to list repositories for | string | - -## `github_pull_request_create` - -Create a new pull request in a repository. Requires write access to the head branch. - -| Properties | Description | Type | -| --- | --- | --- | -| `base` | The name of the branch you want the changes pulled into | string | -| `body` | The contents of the pull request description | string | null | -| `draft` | Indicates whether the pull request is a draft | boolean | null | -| `head` | The name of the branch where your changes are implemented (format: user:branch) | string | -| `maintainer_can_modify` | Indicates whether maintainers can modify the pull request | boolean | null | -| `owner` | The account owner of the repository | string | -| `repo` | The name of the repository | string | -| `title` | The title of the pull request | string | null | - -## `github_pull_requests_list` - -List pull requests in a repository with optional filtering by state, head, and base branches. - -| Properties | Description | Type | -| --- | --- | --- | -| `base` | Filter by base branch name | string | null | -| `direction` | Sort order | string | null | -| `head` | Filter by head branch (format: user:ref-name) | string | null | -| `owner` | The account owner of the repository | string | -| `page` | Page number of results to fetch | number | null | -| `per_page` | Number of results per page (max 100) | number | null | -| `repo` | The name of the repository | string | -| `sort` | Property to sort pull requests by | string | null | -| `state` | Filter by pull request state | string | null | - -## `github_repo_get` - -Get detailed information about a GitHub repository including metadata, settings, and statistics. - -| Properties | Description | Type | -| --- | --- | --- | -| `owner` | The account owner of the repository (case-insensitive) | string | -| `repo` | The name of the repository without the .git extension (case-insensitive) | string | - -## `github_user_repos_list` - -List repositories for the authenticated user. Requires authentication. - -| Properties | Description | Type | -| --- | --- | --- | -| `direction` | Sort order | string | null | -| `page` | Page number of results to fetch | number | null | -| `per_page` | Number of results per page (max 100) | number | null | -| `sort` | Property to sort repositories by | string | null | -| `type` | Filter repositories by type | string | null | diff --git a/plugins/agentkit/references/agent-connectors/gmail.md b/plugins/agentkit/references/agent-connectors/gmail.md deleted file mode 100644 index d668c75..0000000 --- a/plugins/agentkit/references/agent-connectors/gmail.md +++ /dev/null @@ -1,79 +0,0 @@ -Gmail is Google's cloud based email service that allows you to access your messages from any computer or device with just a web browser. - -Supports authentication: OAuth 2.0 - -## Tool list - -## `gmail_fetch_mails` - -Fetch emails from a connected Gmail account using search filters. Requires a valid Gmail OAuth2 connection. - -| Properties | Description | Type | -| --- | --- | --- | -| `format` | Format of the returned message. | string | null | -| `include_spam_trash` | Whether to fetch emails from spam and trash folders | boolean | null | -| `label_ids` | Gmail label IDs to filter messages | `array` | null | -| `max_results` | Maximum number of emails to fetch | integer | null | -| `page_token` | Page token for pagination | string | null | -| `query` | Search query string using Gmail's search syntax (e.g., 'is:unread from:user@example.com') | string | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `gmail_get_attachment_by_id` - -Retrieve a specific attachment from a Gmail message using the message ID and attachment ID. - -| Properties | Description | Type | -| --- | --- | --- | -| `attachment_id` | Unique Gmail attachment ID | string | -| `file_name` | Preferred filename to use when saving/returning the attachment | string | null | -| `message_id` | Unique Gmail message ID that contains the attachment | string | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `gmail_get_contacts` - -Fetch a list of contacts from the connected Gmail account. Supports pagination and field filtering. - -| Properties | Description | Type | -| --- | --- | --- | -| `max_results` | Maximum number of contacts to fetch | integer | null | -| `page_token` | Token to retrieve the next page of results | string | null | -| `person_fields` | Fields to include for each person | `array` | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `gmail_get_message_by_id` - -Retrieve a specific Gmail message using its message ID. Optionally control the format of the returned data. - -| Properties | Description | Type | -| --- | --- | --- | -| `format` | Format of the returned message. | string | null | -| `message_id` | Unique Gmail message ID | string | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `gmail_list_drafts` - -List draft emails from a connected Gmail account. Requires a valid Gmail OAuth2 connection. - -| Properties | Description | Type | -| --- | --- | --- | -| `max_results` | Maximum number of drafts to fetch | integer | null | -| `page_token` | Page token for pagination | string | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `gmail_search_people` - -Search people or contacts in the connected Google account using a query. Requires a valid Google OAuth2 connection with People API scopes. - -| Properties | Description | Type | -| --- | --- | --- | -| `other_contacts` | Whether to include people not in the user's contacts (from 'Other Contacts'). | boolean | null | -| `page_size` | Maximum number of people to return. | integer | null | -| `person_fields` | Fields to retrieve for each person. | `array` | null | -| `query` | Text query to search people (e.g., name, email address). | string | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | diff --git a/plugins/agentkit/references/agent-connectors/gong.md b/plugins/agentkit/references/agent-connectors/gong.md deleted file mode 100644 index 26a36e3..0000000 --- a/plugins/agentkit/references/agent-connectors/gong.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect with Gong to sync calls, transcripts, insights, coaching and CRM activity - -Supports authentication: OAuth 2.0 , Api Key diff --git a/plugins/agentkit/references/agent-connectors/google_ads.md b/plugins/agentkit/references/agent-connectors/google_ads.md deleted file mode 100644 index 9718639..0000000 --- a/plugins/agentkit/references/agent-connectors/google_ads.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Google Ads to manage advertising campaigns, analyze performance metrics, and optimize ad spending across Google's advertising platform - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_docs.md b/plugins/agentkit/references/agent-connectors/google_docs.md deleted file mode 100644 index e790d5d..0000000 --- a/plugins/agentkit/references/agent-connectors/google_docs.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Google Docs. Create, edit, and collaborate on documents - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_drive.md b/plugins/agentkit/references/agent-connectors/google_drive.md deleted file mode 100644 index ef6120e..0000000 --- a/plugins/agentkit/references/agent-connectors/google_drive.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Google Drive. Manage files, folders, and sharing permissions - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_forms.md b/plugins/agentkit/references/agent-connectors/google_forms.md deleted file mode 100644 index fde9e1d..0000000 --- a/plugins/agentkit/references/agent-connectors/google_forms.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Google Forms. Create, view, and manage forms and responses securely - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_meets.md b/plugins/agentkit/references/agent-connectors/google_meets.md deleted file mode 100644 index 814ff7b..0000000 --- a/plugins/agentkit/references/agent-connectors/google_meets.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Google Meet. Create and manage video meetings with powerful collaboration features - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_sheets.md b/plugins/agentkit/references/agent-connectors/google_sheets.md deleted file mode 100644 index 8df9a54..0000000 --- a/plugins/agentkit/references/agent-connectors/google_sheets.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Google Sheets. Create, edit, and analyze spreadsheets with powerful data management capabilities - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_slides.md b/plugins/agentkit/references/agent-connectors/google_slides.md deleted file mode 100644 index 242ef37..0000000 --- a/plugins/agentkit/references/agent-connectors/google_slides.md +++ /dev/null @@ -1,32 +0,0 @@ -Connect to Google Slides to create, read, and modify presentations programmatically. - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `googleslides_create_presentation` - -Create a new Google Slides presentation with an optional title. - -| Properties | Description | Type | -| --- | --- | --- | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `title` | Title of the new presentation | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `googleslides_read_presentation` - -Read the complete structure and content of a Google Slides presentation including slides, text, images, shapes, and metadata. - -| Properties | Description | Type | -| --- | --- | --- | -| `fields` | Fields to include in the response | string | null | -| `presentation_id` | The ID of the Google Slides presentation to read | string | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | diff --git a/plugins/agentkit/references/agent-connectors/googlecalendar.md b/plugins/agentkit/references/agent-connectors/googlecalendar.md deleted file mode 100644 index 8168441..0000000 --- a/plugins/agentkit/references/agent-connectors/googlecalendar.md +++ /dev/null @@ -1,128 +0,0 @@ -Google Calendar is Google's cloud-based calendar service that allows you to manage your events, appointments, and schedules from any computer or device with just a web browser. - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `googlecalendar_create_event` - -Create a new event in a connected Google Calendar account. Supports meeting links, recurrence, attendees, and more. - -| Properties | Description | Type | -| --- | --- | --- | -| `attendees_emails` | Attendee email addresses | `array` | null | -| `calendar_id` | Calendar ID to create the event in | string | null | -| `create_meeting_room` | Generate a Google Meet link for this event | boolean | null | -| `description` | Optional event description | string | null | -| `event_duration_hour` | Duration of event in hours | integer | null | -| `event_duration_minutes` | Duration of event in minutes | integer | null | -| `event_type` | Event type for display purposes | string | null | -| `guests_can_invite_others` | Allow guests to invite others | boolean | null | -| `guests_can_modify` | Allow guests to modify the event | boolean | null | -| `guests_can_see_other_guests` | Allow guests to see each other | boolean | null | -| `location` | Location of the event | string | null | -| `recurrence` | Recurrence rules (iCalendar RRULE format) | `array` | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `send_updates` | Send update notifications to attendees | boolean | null | -| `start_datetime` | Event start time in RFC3339 format | string | -| `summary` | Event title/summary | string | -| `timezone` | Timezone for the event (IANA time zone identifier) | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | -| `transparency` | Calendar transparency (free/busy) | string | null | -| `visibility` | Visibility of the event | string | null | - -## `googlecalendar_delete_event` - -Delete an event from a connected Google Calendar account. Requires the calendar ID and event ID. - -| Properties | Description | Type | -| --- | --- | --- | -| `calendar_id` | The ID of the calendar from which the event should be deleted | string | null | -| `event_id` | The ID of the calendar event to delete | string | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `googlecalendar_get_event_by_id` - -Retrieve a specific calendar event by its ID using optional filtering and list parameters. - -| Properties | Description | Type | -| --- | --- | --- | -| `calendar_id` | The calendar ID to search in | string | null | -| `event_id` | The unique identifier of the calendar event to fetch | string | -| `event_types` | Filter by Google event types | `array` | null | -| `query` | Free text search query | string | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `show_deleted` | Include deleted events in results | boolean | null | -| `single_events` | Expand recurring events into instances | boolean | null | -| `time_max` | Upper bound for event start time (RFC3339) | string | null | -| `time_min` | Lower bound for event start time (RFC3339) | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | -| `updated_min` | Filter events updated after this time (RFC3339) | string | null | - -## `googlecalendar_list_calendars` - -List all accessible Google Calendar calendars for the authenticated user. Supports filters and pagination. - -| Properties | Description | Type | -| --- | --- | --- | -| `max_results` | Maximum number of calendars to fetch | integer | null | -| `min_access_role` | Minimum access role to include in results | string | null | -| `page_token` | Token to retrieve the next page of results | string | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `show_deleted` | Include deleted calendars in the list | boolean | null | -| `show_hidden` | Include calendars that are hidden from the calendar list | boolean | null | -| `sync_token` | Token to get updates since the last sync | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `googlecalendar_list_events` - -List events from a connected Google Calendar account with filtering options. Requires a valid Google Calendar OAuth2 connection. - -| Properties | Description | Type | -| --- | --- | --- | -| `calendar_id` | Calendar ID to list events from | string | null | -| `max_results` | Maximum number of events to fetch | integer | null | -| `order_by` | Order of events in the result | string | null | -| `page_token` | Page token for pagination | string | null | -| `query` | Free text search query | string | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `single_events` | Expand recurring events into single events | boolean | null | -| `time_max` | Upper bound for event start time (RFC3339 timestamp) | string | null | -| `time_min` | Lower bound for event start time (RFC3339 timestamp) | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `googlecalendar_update_event` - -Update an existing event in a connected Google Calendar account. Only provided fields will be updated. Supports updating time, attendees, location, meeting links, and more. - -| Properties | Description | Type | -| --- | --- | --- | -| `attendees_emails` | Attendee email addresses | `array` | null | -| `calendar_id` | Calendar ID containing the event | string | -| `create_meeting_room` | Generate a Google Meet link for this event | boolean | null | -| `description` | Optional event description | string | null | -| `end_datetime` | Event end time in RFC3339 format | string | null | -| `event_duration_hour` | Duration of event in hours | integer | null | -| `event_duration_minutes` | Duration of event in minutes | integer | null | -| `event_id` | The ID of the calendar event to update | string | -| `event_type` | Event type for display purposes | string | null | -| `guests_can_invite_others` | Allow guests to invite others | boolean | null | -| `guests_can_modify` | Allow guests to modify the event | boolean | null | -| `guests_can_see_other_guests` | Allow guests to see each other | boolean | null | -| `location` | Location of the event | string | null | -| `recurrence` | Recurrence rules (iCalendar RRULE format) | `array` | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `send_updates` | Send update notifications to attendees | boolean | null | -| `start_datetime` | Event start time in RFC3339 format | string | null | -| `summary` | Event title/summary | string | null | -| `timezone` | Timezone for the event (IANA time zone identifier) | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | -| `transparency` | Calendar transparency (free/busy) | string | null | -| `visibility` | Visibility of the event | string | null | diff --git a/plugins/agentkit/references/agent-connectors/hubspot.md b/plugins/agentkit/references/agent-connectors/hubspot.md deleted file mode 100644 index ac44f2c..0000000 --- a/plugins/agentkit/references/agent-connectors/hubspot.md +++ /dev/null @@ -1,143 +0,0 @@ -Connect to HubSpot CRM. Manage contacts, deals, companies, and marketing automation - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `hubspot_companies_search` - -Search HubSpot companies using full-text search and pagination. Returns matching companies with specified properties. - -| Properties | Description | Type | -| --- | --- | --- | -| `after` | Pagination offset to get results starting from a specific position | string | null | -| `filterGroups` | JSON string containing filter groups for advanced filtering | string | null | -| `limit` | Number of results to return per page (max 100) | number | null | -| `properties` | Comma-separated list of properties to include in the response | string | null | -| `query` | Search term for full-text search across company properties | string | null | - -## `hubspot_company_create` - -Create a new company in HubSpot CRM. Requires a company name as the unique identifier. Supports additional properties like domain, industry, phone, location, and revenue information. - -| Properties | Description | Type | -| --- | --- | --- | -| `annualrevenue` | Annual revenue of the company | number | null | -| `city` | Company city location | string | null | -| `country` | Company country location | string | null | -| `description` | Company description or overview | string | null | -| `domain` | Company website domain | string | null | -| `industry` | Industry type of the company | string | null | -| `name` | Company name (required, serves as primary identifier) | string | -| `numberofemployees` | Number of employees at the company | number | null | -| `phone` | Company phone number | string | null | -| `state` | Company state or region | string | null | - -## `hubspot_company_get` - -Retrieve details of a specific company from HubSpot by company ID. Returns company properties and associated data. - -| Properties | Description | Type | -| --- | --- | --- | -| `company_id` | ID of the company to retrieve | string | -| `properties` | Comma-separated list of properties to include in the response | string | null | - -## `hubspot_contact_create` - -Create a new contact in HubSpot CRM. Requires an email address as the unique identifier. Supports additional properties like name, company, phone, and lifecycle stage. - -| Properties | Description | Type | -| --- | --- | --- | -| `company` | Company name where the contact works | string | null | -| `email` | Primary email address for the contact (required, serves as unique identifier) | string | -| `firstname` | First name of the contact | string | null | -| `hs_lead_status` | Lead status of the contact | string | null | -| `jobtitle` | Job title of the contact | string | null | -| `lastname` | Last name of the contact | string | null | -| `lifecyclestage` | Lifecycle stage of the contact | string | null | -| `phone` | Phone number of the contact | string | null | -| `website` | Personal or company website URL | string | null | - -## `hubspot_contact_get` - -Retrieve details of a specific contact from HubSpot by contact ID. Returns contact properties and associated data. - -| Properties | Description | Type | -| --- | --- | --- | -| `contact_id` | ID of the contact to retrieve | string | -| `properties` | Comma-separated list of properties to include in the response | string | null | - -## `hubspot_contact_update` - -Update an existing contact in HubSpot CRM by contact ID. Allows updating contact properties like name, email, company, phone, and lifecycle stage. - -| Properties | Description | Type | -| --- | --- | --- | -| `contact_id` | ID of the contact to update | string | -| `props` | Object containing properties like first name, last name, email, company, phone, and job title to update all these should be provided inside props as a JSON object, this is required | `object` | null | - -## `hubspot_contacts_list` - -Retrieve a list of contacts from HubSpot with filtering and pagination. Returns contact properties and supports pagination through cursor-based navigation. - -| Properties | Description | Type | -| --- | --- | --- | -| `after` | Pagination cursor to get the next set of results | string | null | -| `archived` | Whether to include archived contacts in the results | boolean | null | -| `limit` | Number of results to return per page (max 100) | number | null | -| `properties` | Comma-separated list of properties to include in the response | string | null | - -## `hubspot_contacts_search` - -Search HubSpot contacts using full-text search and pagination. Returns matching contacts with specified properties. - -| Properties | Description | Type | -| --- | --- | --- | -| `after` | Pagination offset to get results starting from a specific position | string | null | -| `filterGroups` | JSON string containing filter groups for advanced filtering | string | null | -| `limit` | Number of results to return per page (max 100) | number | null | -| `properties` | Comma-separated list of properties to include in the response | string | null | -| `query` | Search term for full-text search across contact properties | string | null | - -## `hubspot_deal_create` - -Create a new deal in HubSpot CRM. Requires dealname, amount, and dealstage. Supports additional properties like pipeline, close date, and deal type. - -| Properties | Description | Type | -| --- | --- | --- | -| `amount` | Deal amount/value (required) | number | -| `closedate` | Expected close date (YYYY-MM-DD format) | string | null | -| `dealname` | Name of the deal (required) | string | -| `dealstage` | Current stage of the deal (required) | string | -| `dealtype` | Type of deal | string | null | -| `description` | Deal description | string | null | -| `hs_priority` | Deal priority (HIGH, MEDIUM, LOW) | string | null | -| `pipeline` | Deal pipeline | string | null | - -## `hubspot_deal_update` - -Update an existing deal in HubSpot CRM by deal ID. Allows updating deal properties like name, amount, stage, pipeline, close date, and priority. - -| Properties | Description | Type | -| --- | --- | --- | -| `deal_id` | ID of the deal to update | string | -| `good_deal` | Boolean flag indicating if this is a good deal | boolean | null | -| `properties` | Object containing deal properties to update | `object` | - -## `hubspot_deals_search` - -Search HubSpot deals using full-text search and pagination. Returns matching deals with specified properties. - -| Properties | Description | Type | -| --- | --- | --- | -| `after` | Pagination offset to get results starting from a specific position | string | null | -| `filterGroups` | JSON string containing filter groups for advanced filtering | string | null | -| `limit` | Number of results to return per page (max 100) | number | null | -| `properties` | Comma-separated list of properties to include in the response | string | null | -| `query` | Search term for full-text search across deal properties | string | null | diff --git a/plugins/agentkit/references/agent-connectors/intercom.md b/plugins/agentkit/references/agent-connectors/intercom.md deleted file mode 100644 index 77ef5d8..0000000 --- a/plugins/agentkit/references/agent-connectors/intercom.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Intercom. Send messages, manage conversations, and interact with users and contacts. - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/jira.md b/plugins/agentkit/references/agent-connectors/jira.md deleted file mode 100644 index 1675086..0000000 --- a/plugins/agentkit/references/agent-connectors/jira.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Jira. Manage issues, projects, workflows, and agile development processes - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/linear.md b/plugins/agentkit/references/agent-connectors/linear.md deleted file mode 100644 index 9b0f877..0000000 --- a/plugins/agentkit/references/agent-connectors/linear.md +++ /dev/null @@ -1,58 +0,0 @@ -Connect to Linear. Manage issues, projects, sprints, and development workflows - -Supports authentication: OAuth 2.0 - -## Tool list - -## `linear_graphql_query` - -Execute a custom GraphQL query or mutation against the Linear API. Allows running any valid GraphQL operation with variables support for advanced use cases. - -| Properties | Description | Type | -| --- | --- | --- | -| `query` | The GraphQL query or mutation to execute | string | -| `variables` | Variables to pass to the GraphQL query | `object` | null | - -## `linear_issue_create` - -Create a new issue in Linear using the issueCreate mutation. Requires a team ID and title at minimum. - -| Properties | Description | Type | -| --- | --- | --- | -| `assigneeId` | ID of the user to assign the issue to | string | null | -| `description` | Description of the issue | string | null | -| `estimate` | Story point estimate for the issue | string | null | -| `labelIds` | Array of label IDs to apply to the issue | `array` | null | -| `priority` | Priority level of the issue (1-4, where 1 is urgent) | string | null | -| `projectId` | ID of the project to associate the issue with | string | null | -| `stateId` | ID of the workflow state to set | string | null | -| `teamId` | ID of the team to create the issue in | string | -| `title` | Title of the issue | string | - -## `linear_issue_update` - -Update an existing issue in Linear. You can update title, description, priority, state, and assignee. - -| Properties | Description | Type | -| --- | --- | --- | -| `assigneeId` | ID of the user to assign the issue to | string | null | -| `description` | New description for the issue | string | null | -| `issueId` | ID of the issue to update | string | -| `priority` | Priority level of the issue (1-4, where 1 is urgent) | string | null | -| `stateId` | ID of the workflow state to set | string | null | -| `title` | New title for the issue | string | null | - -## `linear_issues_list` - -List issues in Linear using the issues query with simple filtering and pagination support. - -| Properties | Description | Type | -| --- | --- | --- | -| `after` | Cursor for pagination (returns issues after this cursor) | string | null | -| `assignee` | Filter by assignee email (e.g., 'user@example.com') | string | null | -| `before` | Cursor for pagination (returns issues before this cursor) | string | null | -| `first` | Number of issues to return (pagination) | integer | null | -| `labels` | Filter by label names (array of strings) | `array` | null | -| `priority` | Filter by priority level (1=Urgent, 2=High, 3=Medium, 4=Low) | string | null | -| `project` | Filter by project name (e.g., 'Q4 Goals') | string | null | -| `state` | Filter by state name (e.g., 'In Progress', 'Done') | string | null | diff --git a/plugins/agentkit/references/agent-connectors/microsoft_excel.md b/plugins/agentkit/references/agent-connectors/microsoft_excel.md deleted file mode 100644 index 2fa4c19..0000000 --- a/plugins/agentkit/references/agent-connectors/microsoft_excel.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Microsoft Excel. Access, read, and modify spreadsheets stored in OneDrive or SharePoint through Microsoft Graph API. - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/microsoft_teams.md b/plugins/agentkit/references/agent-connectors/microsoft_teams.md deleted file mode 100644 index 85790a1..0000000 --- a/plugins/agentkit/references/agent-connectors/microsoft_teams.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Microsoft Teams. Manage messages, channels, meetings, and team collaboration - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/microsoft_word.md b/plugins/agentkit/references/agent-connectors/microsoft_word.md deleted file mode 100644 index 7b2d530..0000000 --- a/plugins/agentkit/references/agent-connectors/microsoft_word.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Microsoft Word. Authenticate with your Microsoft account to create, read, and edit Word documents stored in OneDrive or SharePoint through Microsoft Graph API. - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/monday.md b/plugins/agentkit/references/agent-connectors/monday.md deleted file mode 100644 index 6cc8ab0..0000000 --- a/plugins/agentkit/references/agent-connectors/monday.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Monday.com. Manage boards, tasks, workflows, teams, and project collaboration - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/notion.md b/plugins/agentkit/references/agent-connectors/notion.md deleted file mode 100644 index 22b58d5..0000000 --- a/plugins/agentkit/references/agent-connectors/notion.md +++ /dev/null @@ -1,189 +0,0 @@ -Connect to Notion workspace. Create, edit pages, manage databases, and collaborate on content - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `notion_comment_create` - -Create a comment in Notion. Provide a comment object with rich_text content and either a parent object (with page_id) for a page-level comment or a discussion_id to reply in an existing thread. - -| Properties | Description | Type | -| --- | --- | --- | -| `comment` | Comment object containing a rich_text array. Example: `{"rich_text":[{"type":"text","text":{"content":"Hello"}}]}` | `object` | -| `discussion_id` | Existing discussion thread ID to reply to. | string | null | -| `notion_version` | Optional override for the Notion-Version header (e.g., 2022-06-28). | string | null | -| `parent` | Parent object for a new top-level comment. Shape: `{"page_id":""}`. | `object` | null | -| `schema_version` | Internal override for schema version. | string | null | -| `tool_version` | Internal override for tool implementation version. | string | null | - -## `notion_comment_retrieve` - -Retrieve a single Notion comment by its `comment_id`. LLM tip: you typically obtain `comment_id` from the response of creating a comment or by first listing comments for a page/block and selecting the desired item’s `id`. - -| Properties | Description | Type | -| --- | --- | --- | -| `comment_id` | The identifier of the comment to retrieve (hyphenated UUID). Obtain it from Create-Comment responses or from a prior List-Comments call. | string | -| `notion_version` | Optional Notion-Version header override (e.g., 2022-06-28). | string | null | -| `schema_version` | Internal override for schema version. | string | null | -| `tool_version` | Internal override for tool implementation version. | string | null | - -## `notion_comments_fetch` - -Fetch comments for a given Notion block. Provide a `block_id` (the target page/block ID, hyphenated UUID). Supports pagination via `start_cursor` and `page_size` (1–100). LLM tip: extract `block_id` from a Notion URL’s trailing 32-char id, then insert hyphens (8-4-4-4-12). - -| Properties | Description | Type | -| --- | --- | --- | -| `block_id` | Target Notion block (or page) ID to fetch comments for. Use a hyphenated UUID. | string | -| `notion_version` | Optional Notion-Version header override (e.g., 2022-06-28). | string | null | -| `page_size` | Maximum number of comments to return (1–100). | integer | null | -| `schema_version` | Internal override for schema version. | string | null | -| `start_cursor` | Cursor to fetch the next page of results. | string | null | -| `tool_version` | Internal override for tool implementation version. | string | null | - -## `notion_data_fetch` - -Fetch data from Notion using the workspace search API (/search). Supports pagination via start_cursor. - -| Properties | Description | Type | -| --- | --- | --- | -| `page_size` | Max number of results to return (1–100) | integer | null | -| `query` | Text query used by /search | string | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `start_cursor` | Cursor for pagination; pass the previous response's next_cursor | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `notion_database_create` - -Create a new database in Notion under a parent page. Provide a parent object with page_id, a database title (rich_text array), and a properties object that defines the database schema (columns). - -| Properties | Description | Type | -| --- | --- | --- | -| `notion_version` | Optional override for the Notion-Version header (e.g., 2022-06-28). | string | null | -| `parent` | Parent object specifying the page under which the database is created. Example: `{"page_id": "2561ab6c-418b-8072-beec-c4779fa811cf"}` | `object` | -| `properties` | Database schema object defining properties (columns). Example: `{"Name": {"title": {}}, "Status": {"select": {"options": [{"name": "Todo"}, {"name": "Doing"}, {"name": "Done"}]}}}` | `object` | -| `schema_version` | Internal override for schema version. | string | null | -| `title` | Database title as a Notion rich_text array. | `array` | -| `tool_version` | Internal override for tool implementation version. | string | null | - -## `notion_database_fetch` - -Retrieve a Notion database’s full definition, including title, properties, and schema. Required: `database_id` (hyphenated UUID). LLM tip: Extract the last 32 characters from a Notion database URL, then insert hyphens (8-4-4-4-12). - -| Properties | Description | Type | -| --- | --- | --- | -| `database_id` | The target database ID in UUID format with hyphens. | string | -| `notion_version` | Optional override for the Notion-Version header. | string | null | -| `schema_version` | Optional schema version override. | string | null | -| `tool_version` | Optional tool version override. | string | null | - -## `notion_database_insert_row` - -Insert a new row (page) into a Notion database. Required: `database_id` (hyphenated UUID) and `properties` (object mapping database column names to Notion **property values). Optional: child_blocks` (content blocks), `icon` (page icon object), and `cover` (page cover object). - -LLM guidance: -- `properties` must use **property values** (not schema). Example: - -```json - { - "title": { "title": [ { "text": { "content": "Task A" } } ] }, - "Status": { "select": { "name": "Todo" } }, - "Due": { "date": { "start": "2025-09-01" } } - } -``` -- Use the **exact property key** as defined in the database (case‑sensitive), or the property id. -- `icon` example (emoji): `{"type":"emoji","emoji":"📝"}` -- `cover` example (external): `{"type":"external","external":{"url":"https://example.com/image.jpg"}}` -- Runtime note: the executor/host should synthesize `parent = {"database_id": database_id}` before sending to Notion. - -| Properties | Description | Type | -| --- | --- | --- | -| `_parent` | Computed by host: `{ "database_id": "" }`. Do not supply manually. | `object` | null | -| `child_blocks` | Optional array of Notion blocks to append as page content (paragraph, heading, to_do, etc.). | `array` | null | -| `cover` | Optional page cover object. Example external: `{"type":"external","external":{"url":"https://example.com/cover.jpg"}}`. | `object` | null | -| `database_id` | Target database ID (hyphenated UUID). | string | -| `icon` | Optional page icon object. Examples: `{"type":"emoji","emoji":"📝"}` or `{"type":"external","external":{"url":"https://..."}}`. | `object` | null | -| `notion_version` | Optional Notion-Version header override (e.g., 2022-06-28). | string | null | -| `properties` | Object mapping **column names (or property ids)** to **property values**. - -️ **CRITICAL: Property Identification Rules:** -- For title fields: ALWAYS use 'title' as the property key (not 'Name' or display names) -- For other properties: Use exact property names from database schema (case-sensitive) -- DO NOT use URL-encoded property IDs with special characters - - **Recommended Workflow:** -1. Call fetch_database first to see exact property names -2. Use 'title' for title-type properties -3. Match other property names exactly as shown in schema - -Example: - -```json -{ - "title": { "title": [ { "text": { "content": "Task A" } } ] }, - "Status": { "select": { "name": "Todo" } }, - "Due": { "date": { "start": "2025-09-01" } } -} -``` | `object` | -| `schema_version` | Optional schema version override. | string | null | -| `tool_version` | Optional tool version override. | string | null | - -## `notion_database_property_retrieve` - -Query a Notion database and return only specific properties by supplying one or more property IDs. Use when you need page rows but want to limit the returned properties to reduce payload. Provide the database_id and an array of filter_properties (each item is a property id like "title") - -| Properties | Description | Type | -| --- | --- | --- | -| `database_id` | Target database ID (hyphenated UUID). | string | -| `property_id` | property ID to filter results by a specific property. get the property id by querying database. | string | null | -| `schema_version` | Optional schema version override. | string | null | -| `tool_version` | Optional tool version override. | string | null | - -## `notion_database_query` - -Query a Notion database for rows (pages). Provide database_id (hyphenated UUID). Optional: page_size, start_cursor for pagination, and sorts (array of sort objects). LLM guidance: extract the last 32 characters from a Notion database URL and insert hyphens (8-4-4-4-12) to form database_id. Sort rules: each sort item MUST include either property OR timestamp (last_edited_time/created_time), not both. - -| Properties | Description | Type | -| --- | --- | --- | -| `database_id` | Target database ID (hyphenated UUID). | string | -| `notion_version` | Optional Notion-Version header override. | string | null | -| `page_size` | Maximum number of rows to return (1–100). | integer | null | -| `schema_version` | Optional schema version override. | string | null | -| `sorts` | Order the results. Each item must include either property or timestamp, plus direction. | `array` | null | -| `start_cursor` | Cursor to fetch the next page of results. | string | null | -| `tool_version` | Optional tool version override. | string | null | - -## `notion_page_create` - -Create a page in Notion either inside a database (as a row) or as a child of a page. Use exactly one parent mode: provide database_id to create a database row (page with properties) OR provide parent_page_id to create a child page. When creating in a database, properties must use Notion property value shapes and the title property key must be "title" (not the display name). Children (content blocks), icon, and cover are optional. The executor should synthesize the Notion parent object from the chosen parent input. - -Target rules: -- Use database_id OR parent_page_id (not both) -- If database_id is provided → properties are required -- If parent_page_id is provided → properties are optional - -| Properties | Description | Type | -| --- | --- | --- | -| `_parent` | Computed by the executor: `{"database_id": "..."}` OR `{"page_id": "..."}` derived from database_id/parent_page_id. | `object` | null | -| `child_blocks` | Optional blocks to add as page content (children). | `array` | null | -| `cover` | Optional page cover object. | `object` | null | -| `database_id` | Create a page as a new row in this database (hyphenated UUID). Extract from the database URL (last 32 chars → hyphenate 8-4-4-4-12). | string | null | -| `icon` | Optional page icon object. | `object` | null | -| `notion_version` | Optional Notion-Version header override. | string | null | -| `parent_page_id` | Create a child page under this page (hyphenated UUID). Extract from the parent page URL. | string | null | -| `properties` | For database rows, supply property values keyed by property name (or id). For title properties, the key must be "title". - -Example (database row): -{ - "title": { "title": [ { "text": { "content": "Task A" } } ] }, - "Status": { "select": { "name": "Todo" } }, - "Due": { "date": { "start": "2025-09-01" } } -} | `object` | null | -| `schema_version` | Optional schema version override. | string | null | -| `tool_version` | Optional tool version override. | string | null | diff --git a/plugins/agentkit/references/agent-connectors/onedrive.md b/plugins/agentkit/references/agent-connectors/onedrive.md deleted file mode 100644 index 601c43f..0000000 --- a/plugins/agentkit/references/agent-connectors/onedrive.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to OneDrive. Manage files, folders, and cloud storage with Microsoft OneDrive - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/onenote.md b/plugins/agentkit/references/agent-connectors/onenote.md deleted file mode 100644 index 47a835b..0000000 --- a/plugins/agentkit/references/agent-connectors/onenote.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Microsoft OneNote. Access, create, and manage notebooks, sections, and pages stored in OneDrive or SharePoint through Microsoft Graph API. - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/outlook.md b/plugins/agentkit/references/agent-connectors/outlook.md deleted file mode 100644 index 9dae658..0000000 --- a/plugins/agentkit/references/agent-connectors/outlook.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Microsoft Outlook. Manage emails, calendar events, contacts, and tasks - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/salesforce.md b/plugins/agentkit/references/agent-connectors/salesforce.md deleted file mode 100644 index f0441b1..0000000 --- a/plugins/agentkit/references/agent-connectors/salesforce.md +++ /dev/null @@ -1,316 +0,0 @@ -Connect to Salesforce CRM. Manage leads, opportunities, accounts, and customer relationships - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `salesforce_account_create` - -Create a new Account in Salesforce. Supports standard fields - -| Properties | Description | Type | -| --- | --- | --- | -| `AccountNumber` | Account number for the organization | string | null | -| `AnnualRevenue` | Annual revenue | number | null | -| `BillingCity` | Billing city | string | null | -| `BillingCountry` | Billing country | string | null | -| `BillingPostalCode` | Billing postal code | string | null | -| `BillingState` | Billing state/province | string | null | -| `BillingStreet` | Billing street | string | null | -| `Description` | Description | string | null | -| `Industry` | Industry | string | null | -| `Name` | Account Name | string | -| `NumberOfEmployees` | Number of employees | integer | null | -| `OwnerId` | Record owner (User/Queue Id) | string | null | -| `Phone` | Main phone number | string | null | -| `RecordTypeId` | Record Type Id | string | null | -| `Website` | Website URL | string | null | - -## `salesforce_account_delete` - -Delete an existing Account from Salesforce by account ID. This is a destructive operation that permanently removes the account record. - -| Properties | Description | Type | -| --- | --- | --- | -| `account_id` | ID of the account to delete | string | - -## `salesforce_account_get` - -Retrieve details of a specific account from Salesforce by account ID. Returns account properties and associated data. - -| Properties | Description | Type | -| --- | --- | --- | -| `account_id` | ID of the account to retrieve | string | -| `fields` | Comma-separated list of fields to include in the response | string | null | - -## `salesforce_account_update` - -Update an existing Account in Salesforce by account ID. Allows updating account properties like name, phone, website, industry, billing information, and more. - -| Properties | Description | Type | -| --- | --- | --- | -| `AccountNumber` | Account number for the organization | string | null | -| `AccountSource` | Lead source for this account | string | null | -| `AnnualRevenue` | Annual revenue | number | null | -| `BillingCity` | Billing city | string | null | -| `BillingCountry` | Billing country | string | null | -| `BillingGeocodeAccuracy` | Billing geocode accuracy | string | null | -| `BillingLatitude` | Billing address latitude | number | null | -| `BillingLongitude` | Billing address longitude | number | null | -| `BillingPostalCode` | Billing postal code | string | null | -| `BillingState` | Billing state/province | string | null | -| `BillingStreet` | Billing street | string | null | -| `CleanStatus` | Data.com clean status | string | null | -| `Description` | Description | string | null | -| `DunsNumber` | D-U-N-S Number | string | null | -| `Fax` | Fax number | string | null | -| `Industry` | Industry | string | null | -| `Jigsaw` | Data.com key | string | null | -| `JigsawCompanyId` | Jigsaw company ID | string | null | -| `NaicsCode` | NAICS code | string | null | -| `NaicsDesc` | NAICS description | string | null | -| `Name` | Account Name | string | null | -| `NumberOfEmployees` | Number of employees | integer | null | -| `OwnerId` | Record owner (User/Queue Id) | string | null | -| `Ownership` | Ownership type | string | null | -| `ParentId` | Parent Account Id | string | null | -| `Phone` | Main phone number | string | null | -| `Rating` | Account rating | string | null | -| `RecordTypeId` | Record Type Id | string | null | -| `ShippingCity` | Shipping city | string | null | -| `ShippingCountry` | Shipping country | string | null | -| `ShippingGeocodeAccuracy` | Shipping geocode accuracy | string | null | -| `ShippingLatitude` | Shipping address latitude | number | null | -| `ShippingLongitude` | Shipping address longitude | number | null | -| `ShippingPostalCode` | Shipping postal code | string | null | -| `ShippingState` | Shipping state/province | string | null | -| `ShippingStreet` | Shipping street | string | null | -| `Sic` | SIC code | string | null | -| `SicDesc` | SIC description | string | null | -| `Site` | Account site or location | string | null | -| `TickerSymbol` | Stock ticker symbol | string | null | -| `Tradestyle` | Trade style name | string | null | -| `Type` | Account type | string | null | -| `Website` | Website URL | string | null | -| `YearStarted` | Year the company started | string | null | -| `account_id` | ID of the account to update | string | - -## `salesforce_accounts_list` - -Retrieve a list of accounts from Salesforce using a pre-built SOQL query. Returns basic account information. - -| Properties | Description | Type | -| --- | --- | --- | -| `limit` | Number of results to return per page | number | - -## `salesforce_composite` - -Execute multiple Salesforce REST API requests in a single call using the Composite API. Allows for efficient batch operations and related data retrieval. - -| Properties | Description | Type | -| --- | --- | --- | -| `composite_request` | JSON string containing composite request with multiple sub-requests | string | - -## `salesforce_contact_create` - -Create a new contact in Salesforce. Allows setting contact properties like name, email, phone, account association, and other standard fields. - -| Properties | Description | Type | -| --- | --- | --- | -| `AccountId` | Salesforce Account Id associated with this contact | string | null | -| `Department` | Department of the contact | string | null | -| `Description` | Free-form description | string | null | -| `Email` | Email address of the contact | string | null | -| `FirstName` | First name of the contact | string | null | -| `LastName` | Last name of the contact (required) | string | -| `LeadSource` | Lead source for the contact | string | null | -| `MailingCity` | Mailing city | string | null | -| `MailingCountry` | Mailing country | string | null | -| `MailingPostalCode` | Mailing postal code | string | null | -| `MailingState` | Mailing state/province | string | null | -| `MailingStreet` | Mailing street | string | null | -| `MobilePhone` | Mobile phone of the contact | string | null | -| `Phone` | Phone number of the contact | string | null | -| `Title` | Job title of the contact | string | null | - -## `salesforce_contact_get` - -Retrieve details of a specific contact from Salesforce by contact ID. Returns contact properties and associated data. - -| Properties | Description | Type | -| --- | --- | --- | -| `contact_id` | ID of the contact to retrieve | string | -| `fields` | Comma-separated list of fields to include in the response | string | null | - -## `salesforce_dashboard_metadata_get` - -Retrieve metadata for a Salesforce dashboard, including dashboard components, filters, layout, and the running user. - -| Properties | Description | Type | -| --- | --- | --- | -| `dashboard_id` | The unique ID of the Salesforce dashboard | string | - -## `salesforce_global_describe` - -Retrieve metadata about all available SObjects in the Salesforce organization. Returns list of all objects with basic information. - -## `salesforce_limits_get` - -Retrieve organization limits information from Salesforce. Returns API usage limits, data storage limits, and other organizational constraints. - -## `salesforce_object_describe` - -Retrieve detailed metadata about a specific SObject in Salesforce. Returns fields, relationships, and other object metadata. - -| Properties | Description | Type | -| --- | --- | --- | -| `sobject` | SObject API name to describe | string | - -## `salesforce_opportunities_list` - -Retrieve a list of opportunities from Salesforce using a pre-built SOQL query. Returns basic opportunity information. - -| Properties | Description | Type | -| --- | --- | --- | -| `limit` | Number of results to return per page | number | null | - -## `salesforce_opportunity_create` - -Create a new opportunity in Salesforce. Allows setting opportunity properties like name, amount, stage, close date, and account association. - -| Properties | Description | Type | -| --- | --- | --- | -| `AccountId` | Associated Account Id | string | null | -| `Amount` | Opportunity amount | number | null | -| `CampaignId` | Related Campaign Id | string | null | -| `CloseDate` | Expected close date (YYYY-MM-DD, required) | string | -| `Custom_Field__c` | Example custom field (replace with your org’s custom field API name) | string | null | -| `Description` | Opportunity description | string | null | -| `ForecastCategoryName` | Forecast category name | string | null | -| `LeadSource` | Lead source | string | null | -| `Name` | Opportunity name (required) | string | -| `NextStep` | Next step in the sales process | string | null | -| `OwnerId` | Record owner (User/Queue Id) | string | null | -| `PricebookId` | Associated Price Book Id | string | null | -| `Probability` | Probability percentage (0–100) | number | null | -| `RecordTypeId` | Record Type Id for Opportunity | string | null | -| `StageName` | Current sales stage (required) | string | -| `Type` | Opportunity type | string | null | - -## `salesforce_opportunity_get` - -Retrieve details of a specific opportunity from Salesforce by opportunity ID. Returns opportunity properties and associated data. - -| Properties | Description | Type | -| --- | --- | --- | -| `fields` | Comma-separated list of fields to include in the response | string | null | -| `opportunity_id` | ID of the opportunity to retrieve | string | - -## `salesforce_opportunity_update` - -Update an existing opportunity in Salesforce by opportunity ID. Allows updating opportunity properties like name, amount, stage, and close date. - -| Properties | Description | Type | -| --- | --- | --- | -| `AccountId` | Associated Account Id | string | null | -| `Amount` | Opportunity amount | number | null | -| `CampaignId` | Related Campaign Id | string | null | -| `CloseDate` | Expected close date (YYYY-MM-DD) | string | null | -| `Description` | Opportunity description | string | null | -| `ForecastCategoryName` | Forecast category name | string | null | -| `LeadSource` | Lead source | string | null | -| `Name` | Opportunity name | string | null | -| `NextStep` | Next step in the sales process | string | null | -| `OwnerId` | Record owner (User/Queue Id) | string | null | -| `Pricebook2Id` | Associated Price Book Id | string | null | -| `Probability` | Probability percentage (0–100) | number | null | -| `RecordTypeId` | Record Type Id for Opportunity | string | null | -| `StageName` | Current sales stage | string | null | -| `Type` | Opportunity type | string | null | -| `opportunity_id` | ID of the opportunity to update | string | - -## `salesforce_query_soql` - -Execute SOQL queries against Salesforce data. Supports complex queries with joins, filters, and aggregations. - -| Properties | Description | Type | -| --- | --- | --- | -| `query` | SOQL query string to execute | string | - -## `salesforce_report_metadata_get` - -Retrieve report, report type, and related metadata for a Salesforce report. Returns information about report structure, fields, groupings, and configuration. - -| Properties | Description | Type | -| --- | --- | --- | -| `report_id` | The unique ID of the Salesforce report | string | - -## `salesforce_search_parameterized` - -Execute parameterized searches against Salesforce data. Provides simplified search interface with predefined parameters. - -| Properties | Description | Type | -| --- | --- | --- | -| `fields` | Comma-separated list of fields to return | string | null | -| `search_text` | Text to search for | string | -| `sobject` | SObject type to search in | string | - -## `salesforce_search_sosl` - -Execute SOSL searches against Salesforce data. Performs full-text search across multiple objects and fields. - -| Properties | Description | Type | -| --- | --- | --- | -| `search_query` | SOSL search query string to execute | string | - -## `salesforce_sobject_create` - -Create a new record for any Salesforce SObject type (Account, Contact, Lead, Opportunity, custom objects, etc.). Provide the object type and fields as a dynamic object. - -| Properties | Description | Type | -| --- | --- | --- | -| `fields` | Object containing field names and values to set on the new record | `object` | -| `sobject_type` | The Salesforce SObject API name (e.g., Account, Contact, Lead, CustomObject__c) | string | - -## `salesforce_sobject_delete` - -Delete a record from any Salesforce SObject type by ID. This is a destructive operation that permanently removes the record. - -| Properties | Description | Type | -| --- | --- | --- | -| `record_id` | ID of the record to delete | string | -| `sobject_type` | The Salesforce SObject API name (e.g., Account, Contact, Lead, CustomObject__c) | string | - -## `salesforce_sobject_get` - -Retrieve a record from any Salesforce SObject type by ID. Optionally specify which fields to return. - -| Properties | Description | Type | -| --- | --- | --- | -| `fields` | Comma-separated list of fields to include in the response | string | null | -| `record_id` | ID of the record to retrieve | string | -| `sobject_type` | The Salesforce SObject API name (e.g., Account, Contact, Lead, CustomObject__c) | string | - -## `salesforce_sobject_update` - -Update an existing record for any Salesforce SObject type by ID. Only the fields provided will be updated. - -| Properties | Description | Type | -| --- | --- | --- | -| `fields` | Object containing field names and values to update on the record | `object` | -| `record_id` | ID of the record to update | string | -| `sobject_type` | The Salesforce SObject API name (e.g., Account, Contact, Lead, CustomObject__c) | string | - -## `salesforce_soql_execute` - -Execute custom SOQL queries against Salesforce data. Supports complex queries with joins, filters, aggregations, and custom field selection. - -| Properties | Description | Type | -| --- | --- | --- | -| `soql_query` | SOQL query string to execute | string | diff --git a/plugins/agentkit/references/agent-connectors/servicenow.md b/plugins/agentkit/references/agent-connectors/servicenow.md deleted file mode 100644 index b1dc3b0..0000000 --- a/plugins/agentkit/references/agent-connectors/servicenow.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to ServiceNow. Manage incidents, service requests, CMDB, and IT service management workflows - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/sharepoint.md b/plugins/agentkit/references/agent-connectors/sharepoint.md deleted file mode 100644 index c2d88ae..0000000 --- a/plugins/agentkit/references/agent-connectors/sharepoint.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to SharePoint. Manage sites, documents, lists, and collaborative content - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/slack.md b/plugins/agentkit/references/agent-connectors/slack.md deleted file mode 100644 index 8a52cba..0000000 --- a/plugins/agentkit/references/agent-connectors/slack.md +++ /dev/null @@ -1,197 +0,0 @@ -Connect to Slack workspace. Send Messages as Bots or on behalf of users - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `slack_add_reaction` - -Add an emoji reaction to a message in Slack. Requires a valid Slack OAuth2 connection with reactions:write scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID or channel name where the message exists | string | -| `name` | Emoji name to react with (without colons) | string | -| `timestamp` | Timestamp of the message to add reaction to | string | - -## `slack_create_channel` - -Creates a new public or private channel in a Slack workspace. Requires a valid Slack OAuth2 connection with channels:manage scope for public channels or groups:write scope for private channels. - -| Properties | Description | Type | -| --- | --- | --- | -| `is_private` | Create a private channel instead of public | boolean | null | -| `name` | Name of the channel to create (without # prefix) | string | -| `team_id` | Encoded team ID to create channel in (if using org tokens) | string | null | - -## `slack_delete_message` - -Deletes a message from a Slack channel or direct message. Requires a valid Slack OAuth2 connection with chat:write scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID, channel name (#general), or user ID for DM where the message was sent | string | -| `ts` | Timestamp of the message to delete | string | - -## `slack_fetch_conversation_history` - -Fetches conversation history from a Slack channel or direct message with pagination support. Requires a valid Slack OAuth2 connection with channels:history scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID, channel name (#general), or user ID for DM | string | -| `cursor` | Paginate through collections by cursor for pagination | string | null | -| `latest` | End of time range of messages to include in results | string | null | -| `limit` | Number of messages to return (1-1000, default 100) | integer | null | -| `oldest` | Start of time range of messages to include in results | string | null | - -## `slack_get_conversation_info` - -Retrieve information about a Slack channel, including metadata, settings, and member count. Requires a valid Slack OAuth2 connection with channels:read scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID, channel name (#general), or user ID for DM | string | -| `include_locale` | Set to true to include the locale for this conversation | boolean | null | -| `include_num_members` | Set to true to include the member count for the conversation | boolean | null | - -## `slack_get_conversation_replies` - -Retrieve replies to a specific message thread in a Slack channel or direct message. Requires a valid Slack OAuth2 connection with channels:history or groups:history scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID, channel name (#general), or user ID for DM | string | -| `cursor` | Pagination cursor for retrieving next page of results | string | null | -| `inclusive` | Include messages with latest or oldest timestamp in results | boolean | null | -| `latest` | End of time range of messages to include in results | string | null | -| `limit` | Number of messages to return (default 100, max 1000) | integer | null | -| `oldest` | Start of time range of messages to include in results | string | null | -| `ts` | Timestamp of the parent message to get replies for | string | - -## `slack_get_user_info` - -Retrieves detailed information about a specific Slack user, including profile data, status, and workspace information. Requires a valid Slack OAuth2 connection with users:read scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `include_locale` | Set to true to include locale information for the user | boolean | null | -| `user` | User ID to get information about | string | - -## `slack_get_user_presence` - -Gets the current presence status of a Slack user (active, away, etc.). Indicates whether the user is currently online and available. Requires a valid Slack OAuth2 connection with users:read scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `user` | User ID to check presence for | string | - -## `slack_invite_users_to_channel` - -Invites one or more users to a Slack channel. Requires a valid Slack OAuth2 connection with channels:write scope for public channels or groups:write for private channels. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID or channel name (#general) to invite users to | string | -| `users` | Comma-separated list of user IDs to invite to the channel | string | - -## `slack_join_conversation` - -Joins an existing Slack channel. The authenticated user will become a member of the channel. Requires a valid Slack OAuth2 connection with channels:write scope for public channels. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID or channel name (#general) to join | string | - -## `slack_leave_conversation` - -Leaves a Slack channel. The authenticated user will be removed from the channel and will no longer receive messages from it. Requires a valid Slack OAuth2 connection with channels:write scope for public channels or groups:write for private channels. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID or channel name (#general) to leave | string | - -## `slack_list_channels` - -List all public and private channels in a Slack workspace that the authenticated user has access to. Requires a valid Slack OAuth2 connection with channels:read, groups:read, mpim:read, and/or im:read scopes depending on conversation types needed. - -| Properties | Description | Type | -| --- | --- | --- | -| `cursor` | Pagination cursor for retrieving next page of results | string | null | -| `exclude_archived` | Exclude archived channels from the list | boolean | null | -| `limit` | Number of channels to return (default 100, max 1000) | integer | null | -| `team_id` | Encoded team ID to list channels for (optional) | string | null | -| `types` | Mix and match channel types (public_channel, private_channel, mpim, im) | string | null | - -## `slack_list_users` - -Lists all users in a Slack workspace, including information about their status, profile, and presence. Requires a valid Slack OAuth2 connection with users:read scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `cursor` | Pagination cursor for fetching additional pages of users | string | null | -| `include_locale` | Set to true to include locale information for each user | boolean | null | -| `limit` | Number of users to return (1-1000) | number | null | -| `team_id` | Encoded team ID to list users for (if using org tokens) | string | null | - -## `slack_lookup_user_by_email` - -Find a user by their registered email address in a Slack workspace. Requires a valid Slack OAuth2 connection with users:read.email scope. Cannot be used by custom bot users. - -| Properties | Description | Type | -| --- | --- | --- | -| `email` | Email address to search for users by | string | - -## `slack_pin_message` - -Pin a message to a Slack channel. Pinned messages are highlighted and easily accessible to channel members. Requires a valid Slack OAuth2 connection with pins:write scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID or channel name where the message exists | string | -| `timestamp` | Timestamp of the message to pin | string | - -## `slack_send_message` - -Sends a message to a Slack channel or direct message. Requires a valid Slack OAuth2 connection with chat:write scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `attachments` | JSON-encoded array of attachment objects for additional message formatting | string | null | -| `blocks` | JSON-encoded array of Block Kit block elements for rich message formatting | string | null | -| `channel` | Channel ID, channel name (#general), or user ID for DM | string | -| `reply_broadcast` | Used in conjunction with thread_ts to broadcast reply to channel | boolean | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `text` | Message text content | string | -| `thread_ts` | Timestamp of parent message to reply in thread | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | -| `unfurl_links` | Enable or disable link previews | boolean | null | -| `unfurl_media` | Enable or disable media link previews | boolean | null | - -## `slack_set_user_status` - -Set the user's custom status with text and emoji. This appears in their profile and can include an expiration time. Requires a valid Slack OAuth2 connection with users.profile:write scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `status_emoji` | Emoji to display with status (without colons) | string | null | -| `status_expiration` | Unix timestamp when status should expire | integer | null | -| `status_text` | Status text to display | string | null | - -## `slack_update_message` - -Updates/edits a previously sent message in a Slack channel or direct message. Requires a valid Slack OAuth2 connection with chat:write scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `attachments` | JSON-encoded array of attachment objects for additional message formatting | string | null | -| `blocks` | JSON-encoded array of Block Kit block elements for rich message formatting | string | null | -| `channel` | Channel ID, channel name (#general), or user ID for DM where the message was sent | string | -| `text` | New message text content | string | null | -| `ts` | Timestamp of the message to update | string | diff --git a/plugins/agentkit/references/agent-connectors/snowflake.md b/plugins/agentkit/references/agent-connectors/snowflake.md deleted file mode 100644 index 12dba3f..0000000 --- a/plugins/agentkit/references/agent-connectors/snowflake.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Snowflake to manage and analyze your data warehouse workloads - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/trello.md b/plugins/agentkit/references/agent-connectors/trello.md deleted file mode 100644 index 181e36c..0000000 --- a/plugins/agentkit/references/agent-connectors/trello.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Trello. Manage boards, cards, lists, and team collaboration workflows - -Supports authentication: OAuth 1.0a diff --git a/plugins/agentkit/references/agent-connectors/zendesk.md b/plugins/agentkit/references/agent-connectors/zendesk.md deleted file mode 100644 index f993da1..0000000 --- a/plugins/agentkit/references/agent-connectors/zendesk.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Zendesk. Manage customer support tickets, users, organizations, and help desk operations - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/zoom.md b/plugins/agentkit/references/agent-connectors/zoom.md deleted file mode 100644 index 9bfdda0..0000000 --- a/plugins/agentkit/references/agent-connectors/zoom.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Zoom. Schedule meetings, manage recordings, and handle video conferencing workflows - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/providers.md b/plugins/agentkit/references/providers.md index 8ccb64b..2f95108 100644 --- a/plugins/agentkit/references/providers.md +++ b/plugins/agentkit/references/providers.md @@ -29,7 +29,7 @@ AgentKit supports a wide range of popular business applications: | **Data & Analytics** | BigQuery, Snowflake, Fathom | | **Service Management** | ServiceNow | -For curated connector notes, see [agent-connectors/README.md](agent-connectors/README.md). +For curated connector notes, see [docs.scalekit.com/agentkit/connectors](https://docs.scalekit.com/agentkit/connectors/). For live tool discovery, see [tool-discovery.md](tool-discovery.md). ## Connector capabilities @@ -188,6 +188,6 @@ AgentKit normalizes connector-specific errors into consistent error responses: - [connections.md](connections.md) — how to configure authentication credentials for a connector - [connected-accounts.md](connected-accounts.md) — per-user account lifecycle and token management -- [agent-connectors/README.md](agent-connectors/README.md) — curated connector notes and examples +- [AgentKit connectors](https://docs.scalekit.com/agentkit/connectors/) — canonical connector docs - [tool-discovery.md](tool-discovery.md) — live discovery model for current tools and schemas - [code-samples.md](code-samples.md) — implementation examples by framework diff --git a/plugins/agentkit/references/tool-discovery.md b/plugins/agentkit/references/tool-discovery.md index f020b01..b975b30 100644 --- a/plugins/agentkit/references/tool-discovery.md +++ b/plugins/agentkit/references/tool-discovery.md @@ -11,7 +11,7 @@ In AgentKit, the live tool metadata is the source of truth for: - `input_schema` - `output_schema` -The static connector notes in `references/agent-connectors/` are curated guidance. They are useful for auth quirks, example workflows, and product context, but they are not a guaranteed up-to-date catalog of every current tool. +For connector-specific guidance, auth quirks, and example workflows, see the canonical connector docs at [docs.scalekit.com/agentkit/connectors](https://docs.scalekit.com/agentkit/connectors/). ## Terminology @@ -85,6 +85,6 @@ They are related, but they are not always the same string. If live credentials are not available: -- use `references/agent-connectors/` as a directional guide -- clearly say the catalog may be stale +- refer to [docs.scalekit.com/agentkit/connectors](https://docs.scalekit.com/agentkit/connectors/) as a directional guide +- clearly say the catalog may be stale without live credentials - avoid claiming that the listed tools are exhaustive From 18027a650730a4300f8cccaaeb44cd36223a2f72 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Sun, 10 May 2026 21:09:53 +0530 Subject: [PATCH 09/53] rename providers.md to connectors.md to match product terminology --- plugins/agentkit/references/{providers.md => connectors.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/agentkit/references/{providers.md => connectors.md} (100%) diff --git a/plugins/agentkit/references/providers.md b/plugins/agentkit/references/connectors.md similarity index 100% rename from plugins/agentkit/references/providers.md rename to plugins/agentkit/references/connectors.md From f8d7e7cc30581ff695bebc84bea801c0cc896f28 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Sun, 10 May 2026 21:28:23 +0530 Subject: [PATCH 10/53] rename building-agentkit-mcp-server to exposing-agentkit-via-mcp --- plugins/agentkit/README.md | 2 +- plugins/agentkit/docs/code-samples.md | 2 +- .../SKILL.md | 10 +++++----- plugins/agentkit/skills/integrating-agentkit/SKILL.md | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) rename plugins/agentkit/skills/{building-agentkit-mcp-server => exposing-agentkit-via-mcp}/SKILL.md (88%) diff --git a/plugins/agentkit/README.md b/plugins/agentkit/README.md index 53f271d..461b7df 100644 --- a/plugins/agentkit/README.md +++ b/plugins/agentkit/README.md @@ -39,7 +39,7 @@ Official Scalekit docs: Uses live AgentKit metadata to find tools, inspect schemas, and narrow the tool set. - `/agentkit:testing-agentkit-tools` Generates authorization links, fetches live tool metadata, and executes tools from Claude Code. This is the preferred runnable playground surface. -- `/agentkit:building-agent-mcp-server` +- `/agentkit:exposing-agentkit-via-mcp` Exposes AgentKit tools through MCP for MCP-compatible runtimes. - `/agentkit:production-readiness-agentkit` Runs a structured production-readiness checklist for AgentKit integrations. diff --git a/plugins/agentkit/docs/code-samples.md b/plugins/agentkit/docs/code-samples.md index 20314b9..8d856b3 100644 --- a/plugins/agentkit/docs/code-samples.md +++ b/plugins/agentkit/docs/code-samples.md @@ -11,7 +11,7 @@ Use it to choose an implementation style before opening a larger sample reposito | Validate one tool quickly | Use `/agentkit:testing-agentkit-tools` | | Integrate AgentKit into app code | Use `integrating-agentkit` | | Build an agent with a framework | Use framework-specific examples below | -| Expose tools over MCP | Use `building-agent-mcp-server` | +| Expose tools over MCP | Use `exposing-agentkit-via-mcp` | ## Framework directions diff --git a/plugins/agentkit/skills/building-agentkit-mcp-server/SKILL.md b/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md similarity index 88% rename from plugins/agentkit/skills/building-agentkit-mcp-server/SKILL.md rename to plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md index 8161593..6b6d6e7 100644 --- a/plugins/agentkit/skills/building-agentkit-mcp-server/SKILL.md +++ b/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md @@ -1,13 +1,13 @@ --- -name: building-agentkit-mcp-server -description: Guides developers through creating a Scalekit AgentKit MCP server with authenticated tool access. Use when building an MCP server, exposing AgentKit tools over MCP, or connecting AI agents via LangChain or LangGraph MCP adapters. +name: exposing-agentkit-via-mcp +description: Guides developers through configuring a Scalekit AgentKit MCP endpoint with authenticated tool access. Use when exposing AgentKit tools over MCP, generating per-user MCP URLs, or connecting AI agents via LangChain or LangGraph MCP adapters. --- -# Building an Agent MCP Server +# Exposing AgentKit via MCP -Scalekit lets you build MCP servers that manage authentication, create personalized access URLs for users, and define which tools are accessible. You can also bundle several toolkits (e.g., Gmail + Google Calendar) within a single server. +Scalekit lets you configure MCP endpoints that manage authentication, create personalized access URLs for users, and define which AgentKit tools are accessible. You can also bundle several toolkits (e.g., Gmail + Google Calendar) within a single endpoint. -[Model Context Protocol (MCP)](https://modelcontextprotocol.io/docs/getting-started/intro) is an open-source standard that enables AI systems to interface with external tools and data sources. Where the `integrating-agentkit` skill uses the SDK directly, this workflow exposes AgentKit tools over the MCP protocol so any compliant client — LangChain, Claude Desktop, MCP Inspector — can consume them. +[Model Context Protocol (MCP)](https://modelcontextprotocol.io/docs/getting-started/intro) is an open-source standard that enables AI systems to interface with external tools and data sources. Where the `integrating-agentkit` skill uses the SDK directly, this workflow configures AgentKit to expose tools over the MCP protocol so any compliant client — LangChain, Claude Desktop, MCP Inspector — can consume them. > **Note:** AgentKit MCP servers only support Streamable HTTP transport. diff --git a/plugins/agentkit/skills/integrating-agentkit/SKILL.md b/plugins/agentkit/skills/integrating-agentkit/SKILL.md index 55b5097..8ce52df 100644 --- a/plugins/agentkit/skills/integrating-agentkit/SKILL.md +++ b/plugins/agentkit/skills/integrating-agentkit/SKILL.md @@ -121,4 +121,4 @@ console.log(result); - Use `discovering-agentkit-tools` when the user needs the current tool catalog or schema. - Use `testing-agentkit-tools` when the user wants to validate a tool call in Claude Code. -- Use `building-agent-mcp-server` when the user wants AgentKit tools exposed over MCP. +- Use `exposing-agentkit-via-mcp` when the user wants AgentKit tools exposed over MCP. From 8ac436bdb44112011005542088eb5f5e340deeef Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Sun, 10 May 2026 21:42:05 +0530 Subject: [PATCH 11/53] convert dryrun command to testing-auth-setup skill --- plugins/saaskit/commands/dryrun.md | 21 ------- .../skills/testing-auth-setup/SKILL.md | 56 +++++++++++++++++++ 2 files changed, 56 insertions(+), 21 deletions(-) delete mode 100644 plugins/saaskit/commands/dryrun.md create mode 100644 plugins/saaskit/skills/testing-auth-setup/SKILL.md diff --git a/plugins/saaskit/commands/dryrun.md b/plugins/saaskit/commands/dryrun.md deleted file mode 100644 index 984828a..0000000 --- a/plugins/saaskit/commands/dryrun.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -description: Run Scalekit dryrun in fsa -argument-hint: " [organization_id]" -allowed-tools: Bash(node *), Bash(npx *) ---- - -Run Scalekit dryrun with explicit arguments. - -Expected arguments: -1. mode (`fsa`) -2. env_url -3. client_id -4. organization_id (required only for `sso`) - -Behavior: -- If mode is `fsa`, run: - `npx @scalekit-sdk/dryrun --env_url= --client_id= --mode=fsa` -- If mode is `sso`, run: - `npx @scalekit-sdk/dryrun --env_url= --client_id= --mode=sso --organization_id=` -- If mode is missing/invalid, explain the usage and ask for valid arguments. -- If `sso` is selected but organization_id is missing, ask for it before running. diff --git a/plugins/saaskit/skills/testing-auth-setup/SKILL.md b/plugins/saaskit/skills/testing-auth-setup/SKILL.md new file mode 100644 index 0000000..88716e6 --- /dev/null +++ b/plugins/saaskit/skills/testing-auth-setup/SKILL.md @@ -0,0 +1,56 @@ +--- +name: testing-auth-setup +description: Validates a Scalekit auth integration by running the dryrun CLI against a live environment. Use when the user says "test my auth", "verify SSO setup", "check my login flow", "dryrun", or wants to confirm their Scalekit credentials and configuration are working. +argument-hint: "[fsa|sso]" +allowed-tools: Bash(npx *) +--- + +# Testing Auth Setup + +Runs the Scalekit dryrun CLI to validate that your auth integration is correctly configured against a live environment. + +## Modes + +| Mode | What it tests | When to use | +|------|--------------|-------------| +| `fsa` | Full-stack auth login flow | User is setting up or verifying login, callback, and session handling | +| `sso` | Enterprise SSO flow | User is setting up or verifying SAML/OIDC SSO with an identity provider | + +## Prerequisites + +Before running, confirm these environment variables are available: + +- `SCALEKIT_ENV_URL` — your Scalekit environment URL +- `SCALEKIT_CLIENT_ID` — your client ID from app.scalekit.com → Settings + +If either is missing, ask the user to provide them. Do not write credentials into source-controlled files. + +## Running the test + +### Full-stack auth (fsa) + +```bash +npx @scalekit-sdk/dryrun --env_url= --client_id= --mode=fsa +``` + +### Enterprise SSO + +Requires an `organization_id` — ask for it if not provided. + +```bash +npx @scalekit-sdk/dryrun --env_url= --client_id= --mode=sso --organization_id= +``` + +## Choosing the mode + +If the user doesn't specify a mode: + +1. Check the project context — if there's SSO configuration (identity providers, SAML metadata), suggest `sso`. +2. Otherwise default to `fsa` as the most common starting point. +3. If ambiguous, ask which mode to use. + +## After running + +- Show the command output. +- Explain what passed and what failed in plain language. +- If the test fails, suggest specific next steps based on the error (missing redirect URI, invalid credentials, organization not found, etc.). \ No newline at end of file From a46d3645994f5f71446d68abf7800badc3962fee Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Sun, 10 May 2026 21:53:59 +0530 Subject: [PATCH 12/53] rename discovering-agentkit-tools to discovering-connector-tools --- plugins/agentkit/README.md | 4 ++-- plugins/agentkit/docs/tool-discovery.md | 2 +- plugins/agentkit/references/tool-discovery.md | 2 +- .../SKILL.md | 6 +++--- plugins/agentkit/skills/integrating-agentkit/SKILL.md | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) rename plugins/agentkit/skills/{discovering-agentkit-tools => discovering-connector-tools}/SKILL.md (95%) diff --git a/plugins/agentkit/README.md b/plugins/agentkit/README.md index 461b7df..3132e0b 100644 --- a/plugins/agentkit/README.md +++ b/plugins/agentkit/README.md @@ -35,7 +35,7 @@ Official Scalekit docs: ## Skills Reference - `/agentkit:integrating-agentkit` Integrates AgentKit into app code or an agent workflow and routes into the core docs. -- `/agentkit:discovering-agentkit-tools` +- `/agentkit:discovering-connector-tools` Uses live AgentKit metadata to find tools, inspect schemas, and narrow the tool set. - `/agentkit:testing-agentkit-tools` Generates authorization links, fetches live tool metadata, and executes tools from Claude Code. This is the preferred runnable playground surface. @@ -80,7 +80,7 @@ Typical flow for a new connector integration: 1. Read [`docs/index.md`](docs/index.md) for the canonical model and [`docs/connections.md`](docs/connections.md) for connection naming. 2. Create the connection in `AgentKit -> Connections`. 3. Use `/agentkit:integrating-agentkit` to scaffold connected-account creation and authorization. -4. Use `/agentkit:discovering-agentkit-tools` or `/agentkit:testing-agentkit-tools get-tool --provider GMAIL` to inspect the live tool catalog and schema. +4. Use `/agentkit:discovering-connector-tools` or `/agentkit:testing-agentkit-tools get-tool --provider GMAIL` to inspect the live tool catalog and schema. 5. Use `/agentkit:testing-agentkit-tools generate-link --connection-name --identifier user_123` if the user still needs to authorize. 6. Use `/agentkit:testing-agentkit-tools execute-tool --tool-name gmail_fetch_mails --connection-name --identifier user_123 --tool-input '{"query":"is:unread","max_results":5}'` to validate the payload before wiring it into application code. diff --git a/plugins/agentkit/docs/tool-discovery.md b/plugins/agentkit/docs/tool-discovery.md index fd48975..78d960d 100644 --- a/plugins/agentkit/docs/tool-discovery.md +++ b/plugins/agentkit/docs/tool-discovery.md @@ -59,7 +59,7 @@ The second is used for catalog discovery. Use: -- `discovering-agentkit-tools` when the user needs current tools or schemas +- `discovering-connector-tools` when the user needs current tools or schemas - `testing-agentkit-tools` when the user wants to run a live tool and inspect the exact payload - `/agentkit:testing-agentkit-tools get-tool ...` for the preferred runnable playground flow - `/test-tool get-tool ...` only as a legacy compatibility alias diff --git a/plugins/agentkit/references/tool-discovery.md b/plugins/agentkit/references/tool-discovery.md index b975b30..5016005 100644 --- a/plugins/agentkit/references/tool-discovery.md +++ b/plugins/agentkit/references/tool-discovery.md @@ -57,7 +57,7 @@ The legacy `/test-tool ...` alias still works for compatibility, but it is no lo For implementation guidance, use: -- `discovering-agentkit-tools` when the user needs the current tool list or schema +- `discovering-connector-tools` when the user needs the current tool list or schema - `testing-agentkit-tools` when the user wants to execute the tool and inspect the exact payload - `integrating-agentkit` when the user wants to wire the result into application code diff --git a/plugins/agentkit/skills/discovering-agentkit-tools/SKILL.md b/plugins/agentkit/skills/discovering-connector-tools/SKILL.md similarity index 95% rename from plugins/agentkit/skills/discovering-agentkit-tools/SKILL.md rename to plugins/agentkit/skills/discovering-connector-tools/SKILL.md index 8d210a4..407196e 100644 --- a/plugins/agentkit/skills/discovering-agentkit-tools/SKILL.md +++ b/plugins/agentkit/skills/discovering-connector-tools/SKILL.md @@ -1,9 +1,9 @@ --- -name: discovering-agentkit-tools -description: Discovers live Scalekit AgentKit tools for a connector and explains their input and output schemas. Use when a user asks what tools are available for Gmail, Slack, Salesforce, or another connector, wants to inspect `input_schema` or `output_schema`, or needs help narrowing the tool set for an agent. +name: discovering-connector-tools +description: Discovers live tools for a Scalekit AgentKit connector and explains their input and output schemas. Use when a user asks what tools are available for Gmail, Slack, Salesforce, or another connector, wants to inspect `input_schema` or `output_schema`, or needs help narrowing the tool set for an agent. --- -# Discovering AgentKit Tools +# Discovering Connector Tools Use live AgentKit metadata as the source of truth for tool names, required inputs, and output schemas. diff --git a/plugins/agentkit/skills/integrating-agentkit/SKILL.md b/plugins/agentkit/skills/integrating-agentkit/SKILL.md index 8ce52df..92ff8ee 100644 --- a/plugins/agentkit/skills/integrating-agentkit/SKILL.md +++ b/plugins/agentkit/skills/integrating-agentkit/SKILL.md @@ -16,7 +16,7 @@ Keep these terms straight: - `connected account`: the per-user authorization record - `tool`: the executable action exposed by a connector -Prefer live tool discovery over hand-maintained catalogs. If the user needs the current tool list or schema, switch to `discovering-agentkit-tools` or `testing-agentkit-tools`. +Prefer live tool discovery over hand-maintained catalogs. If the user needs the current tool list or schema, switch to `discovering-connector-tools` or `testing-agentkit-tools`. ## Default workflow @@ -119,6 +119,6 @@ console.log(result); ## When to switch skills -- Use `discovering-agentkit-tools` when the user needs the current tool catalog or schema. +- Use `discovering-connector-tools` when the user needs the current tool catalog or schema. - Use `testing-agentkit-tools` when the user wants to validate a tool call in Claude Code. - Use `exposing-agentkit-via-mcp` when the user wants AgentKit tools exposed over MCP. From ab4a57042e88d77fd99991bdb39eb4b9ed83b0fb Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Sun, 10 May 2026 22:12:00 +0530 Subject: [PATCH 13/53] fix saaskit hooks.json: move type to inner hook object --- plugins/saaskit/hooks/hooks.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/saaskit/hooks/hooks.json b/plugins/saaskit/hooks/hooks.json index ff9da6e..85bfcf0 100644 --- a/plugins/saaskit/hooks/hooks.json +++ b/plugins/saaskit/hooks/hooks.json @@ -3,9 +3,9 @@ "hooks": { "Stop": [ { - "type": "command", "hooks": [ { + "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh saaskit stop", "timeout": 10 } From 4fa84305808a021d91507df7baf72987991c082d Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Tue, 12 May 2026 21:22:23 +0530 Subject: [PATCH 14/53] add backward-compat symlinks for old plugin names --- plugins/agent-auth | 1 + plugins/full-stack-auth | 1 + plugins/mcp-auth | 1 + plugins/modular-scim | 1 + plugins/modular-sso | 1 + 5 files changed, 5 insertions(+) create mode 120000 plugins/agent-auth create mode 120000 plugins/full-stack-auth create mode 120000 plugins/mcp-auth create mode 120000 plugins/modular-scim create mode 120000 plugins/modular-sso diff --git a/plugins/agent-auth b/plugins/agent-auth new file mode 120000 index 0000000..1cdf127 --- /dev/null +++ b/plugins/agent-auth @@ -0,0 +1 @@ +agentkit \ No newline at end of file diff --git a/plugins/full-stack-auth b/plugins/full-stack-auth new file mode 120000 index 0000000..995039d --- /dev/null +++ b/plugins/full-stack-auth @@ -0,0 +1 @@ +saaskit \ No newline at end of file diff --git a/plugins/mcp-auth b/plugins/mcp-auth new file mode 120000 index 0000000..995039d --- /dev/null +++ b/plugins/mcp-auth @@ -0,0 +1 @@ +saaskit \ No newline at end of file diff --git a/plugins/modular-scim b/plugins/modular-scim new file mode 120000 index 0000000..995039d --- /dev/null +++ b/plugins/modular-scim @@ -0,0 +1 @@ +saaskit \ No newline at end of file diff --git a/plugins/modular-sso b/plugins/modular-sso new file mode 120000 index 0000000..995039d --- /dev/null +++ b/plugins/modular-sso @@ -0,0 +1 @@ +saaskit \ No newline at end of file From 137b8f765503f868fd8a26bc0108729cc9aeb094 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Tue, 12 May 2026 21:33:01 +0530 Subject: [PATCH 15/53] install both plugins, clean up old v1.x names on reinstall --- scripts/install_claude_marketplace.sh | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/scripts/install_claude_marketplace.sh b/scripts/install_claude_marketplace.sh index 73292d2..3750a68 100755 --- a/scripts/install_claude_marketplace.sh +++ b/scripts/install_claude_marketplace.sh @@ -9,27 +9,30 @@ if ! command -v claude >/dev/null 2>&1; then fi MARKETPLACE_SLUG="${CLAUDE_CODE_AUTHSTACK_MARKETPLACE:-scalekit-inc/claude-code-authstack}" -PLUGIN_SOURCE="${CLAUDE_CODE_AUTHSTACK_PLUGIN_SOURCE:-agentkit@scalekit-auth-stack}" +OLD_PLUGINS=("agent-auth" "full-stack-auth" "mcp-auth" "modular-sso" "modular-scim") echo "Installing Scalekit Auth Stack for Claude Code" echo "Marketplace: $MARKETPLACE_SLUG" -echo "Default plugin: $PLUGIN_SOURCE" echo claude plugin marketplace add "$MARKETPLACE_SLUG" -claude plugin install "$PLUGIN_SOURCE" + +# Remove old plugin names from v1.x (now consolidated into agentkit + saaskit) +for old in "${OLD_PLUGINS[@]}"; do + claude plugin uninstall "${old}@scalekit-auth-stack" 2>/dev/null || true +done + +claude plugin install agentkit@scalekit-auth-stack +claude plugin install saaskit@scalekit-auth-stack cat < Date: Wed, 13 May 2026 14:15:26 +0530 Subject: [PATCH 16/53] =?UTF-8?q?remove=20testing-agentkit-tools=20skill?= =?UTF-8?q?=20=E2=80=94=20redundant=20with=20MCP=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Scalekit MCP server at mcp.scalekit.com provides tool discovery, auth link generation, and tool execution natively. The testing skill forced users to set up env vars, remember slash command syntax, and wait for a Python script — all to get the same result. Updated discovering-connector-tools, integrating-agentkit, and docs to point to the MCP server as the live playground. --- plugins/agentkit/README.md | 21 +- plugins/agentkit/docs/code-samples.md | 2 +- plugins/agentkit/docs/tool-discovery.md | 3 +- plugins/agentkit/references/tool-discovery.md | 11 +- .../discovering-connector-tools/SKILL.md | 6 +- .../skills/integrating-agentkit/SKILL.md | 4 +- .../skills/testing-agentkit-tools/SKILL.md | 137 ---------- .../testing-agentkit-tools/scripts/connect.py | 250 ------------------ 8 files changed, 12 insertions(+), 422 deletions(-) delete mode 100644 plugins/agentkit/skills/testing-agentkit-tools/SKILL.md delete mode 100644 plugins/agentkit/skills/testing-agentkit-tools/scripts/connect.py diff --git a/plugins/agentkit/README.md b/plugins/agentkit/README.md index 3132e0b..45390ed 100644 --- a/plugins/agentkit/README.md +++ b/plugins/agentkit/README.md @@ -37,31 +37,17 @@ Official Scalekit docs: Integrates AgentKit into app code or an agent workflow and routes into the core docs. - `/agentkit:discovering-connector-tools` Uses live AgentKit metadata to find tools, inspect schemas, and narrow the tool set. -- `/agentkit:testing-agentkit-tools` - Generates authorization links, fetches live tool metadata, and executes tools from Claude Code. This is the preferred runnable playground surface. - `/agentkit:exposing-agentkit-via-mcp` Exposes AgentKit tools through MCP for MCP-compatible runtimes. - `/agentkit:production-readiness-agentkit` Runs a structured production-readiness checklist for AgentKit integrations. -Legacy command alias: -- `/test-tool [generate-link|get-tool|execute-tool ...]` - Compatibility wrapper for older usage. Prefer `/agentkit:testing-agentkit-tools ...`. - ## Configuration -Required environment variables: +Required environment variables (for SDK-based integrations): - `SCALEKIT_ENV_URL` - `SCALEKIT_CLIENT_ID` - `SCALEKIT_CLIENT_SECRET` -Optional sample variable: -- `GMAIL_CONNECTION_NAME` - -Legacy aliases supported by the testing command: -- `TOOL_ENV_URL` -- `TOOL_CLIENT_ID` -- `TOOL_CLIENT_SECRET` - Example `.mcp.json`: ```json @@ -80,9 +66,8 @@ Typical flow for a new connector integration: 1. Read [`docs/index.md`](docs/index.md) for the canonical model and [`docs/connections.md`](docs/connections.md) for connection naming. 2. Create the connection in `AgentKit -> Connections`. 3. Use `/agentkit:integrating-agentkit` to scaffold connected-account creation and authorization. -4. Use `/agentkit:discovering-connector-tools` or `/agentkit:testing-agentkit-tools get-tool --provider GMAIL` to inspect the live tool catalog and schema. -5. Use `/agentkit:testing-agentkit-tools generate-link --connection-name --identifier user_123` if the user still needs to authorize. -6. Use `/agentkit:testing-agentkit-tools execute-tool --tool-name gmail_fetch_mails --connection-name --identifier user_123 --tool-input '{"query":"is:unread","max_results":5}'` to validate the payload before wiring it into application code. +4. Use `/agentkit:discovering-connector-tools` to inspect the live tool catalog and schema via the Scalekit MCP server. +5. Use the Scalekit MCP server tools directly to generate auth links, discover tools, and execute tool calls interactively. ## Troubleshooting 1. No tools show up for a connector: diff --git a/plugins/agentkit/docs/code-samples.md b/plugins/agentkit/docs/code-samples.md index 8d856b3..baf9622 100644 --- a/plugins/agentkit/docs/code-samples.md +++ b/plugins/agentkit/docs/code-samples.md @@ -8,7 +8,7 @@ Use it to choose an implementation style before opening a larger sample reposito | Goal | Recommended path | |---|---| -| Validate one tool quickly | Use `/agentkit:testing-agentkit-tools` | +| Validate one tool quickly | Use the Scalekit MCP server tools directly | | Integrate AgentKit into app code | Use `integrating-agentkit` | | Build an agent with a framework | Use framework-specific examples below | | Expose tools over MCP | Use `exposing-agentkit-via-mcp` | diff --git a/plugins/agentkit/docs/tool-discovery.md b/plugins/agentkit/docs/tool-discovery.md index 78d960d..9d6fbf0 100644 --- a/plugins/agentkit/docs/tool-discovery.md +++ b/plugins/agentkit/docs/tool-discovery.md @@ -60,8 +60,7 @@ The second is used for catalog discovery. Use: - `discovering-connector-tools` when the user needs current tools or schemas -- `testing-agentkit-tools` when the user wants to run a live tool and inspect the exact payload -- `/agentkit:testing-agentkit-tools get-tool ...` for the preferred runnable playground flow +- The Scalekit MCP server (`https://mcp.scalekit.com`) when the user wants to run a live tool and inspect the exact payload - `/test-tool get-tool ...` only as a legacy compatibility alias ## Fallback rule diff --git a/plugins/agentkit/references/tool-discovery.md b/plugins/agentkit/references/tool-discovery.md index 5016005..02a5fc1 100644 --- a/plugins/agentkit/references/tool-discovery.md +++ b/plugins/agentkit/references/tool-discovery.md @@ -46,19 +46,12 @@ If the metadata contains pagination or large result fields, mention them so the ## How to use this in Claude Code -For interactive discovery, prefer the testing skill: - -```sh -/agentkit:testing-agentkit-tools get-tool --provider GMAIL -/agentkit:testing-agentkit-tools get-tool --tool-name gmail_fetch_mails -``` - -The legacy `/test-tool ...` alias still works for compatibility, but it is no longer the canonical path. +For interactive discovery, use the Scalekit MCP server. When connected at `https://mcp.scalekit.com`, you can query tool metadata, generate auth links, and execute tools directly through MCP tool calls. For implementation guidance, use: - `discovering-connector-tools` when the user needs the current tool list or schema -- `testing-agentkit-tools` when the user wants to execute the tool and inspect the exact payload +- The Scalekit MCP server when the user wants to execute a tool and inspect the payload interactively - `integrating-agentkit` when the user wants to wire the result into application code ## Connection names vs connector names diff --git a/plugins/agentkit/skills/discovering-connector-tools/SKILL.md b/plugins/agentkit/skills/discovering-connector-tools/SKILL.md index 407196e..3818783 100644 --- a/plugins/agentkit/skills/discovering-connector-tools/SKILL.md +++ b/plugins/agentkit/skills/discovering-connector-tools/SKILL.md @@ -22,7 +22,7 @@ Use this skill when the user asks: ## Discovery workflow 1. Identify the target connector or exact tool name. -2. Prefer live lookup through `/agentkit:testing-agentkit-tools get-tool --provider ` or `/agentkit:testing-agentkit-tools get-tool --tool-name `. +2. Use the Scalekit MCP server to fetch live tool metadata. If the MCP server is connected, query it directly. Otherwise, use the SDK. 3. If older docs or muscle memory mention `/test-tool`, treat it as a legacy compatibility alias for the testing skill rather than the canonical workflow. 4. Summarize: - tool name @@ -47,13 +47,13 @@ Use `connector` in explanations. Only use `provider` when the SDK or API filter - `connection_name` is the exact dashboard value and may not equal the connector slug. - Tool metadata is the durable way to determine current inputs and outputs. -- The preferred runnable surface is the testing skill in `skills/testing-agentkit-tools/`, not the legacy `commands/` alias. +- The preferred runnable surface is the Scalekit MCP server at `https://mcp.scalekit.com`. - Restrict the tool set before handing it to an LLM. Fewer relevant tools improve tool selection and parameter filling. ## Deep reference - Canonical docs entrypoint: [../../docs/index.md](../../docs/index.md) - Live discovery model: [../../docs/tool-discovery.md](../../docs/tool-discovery.md) -- Runnable testing workflow: [../testing-agentkit-tools/SKILL.md](../testing-agentkit-tools/SKILL.md) +- Scalekit MCP server: `https://mcp.scalekit.com` - Curated connector notes: [../../docs/connectors/README.md](../../docs/connectors/README.md) - Broader implementation examples: [../../docs/code-samples.md](../../docs/code-samples.md) diff --git a/plugins/agentkit/skills/integrating-agentkit/SKILL.md b/plugins/agentkit/skills/integrating-agentkit/SKILL.md index 92ff8ee..8f6802d 100644 --- a/plugins/agentkit/skills/integrating-agentkit/SKILL.md +++ b/plugins/agentkit/skills/integrating-agentkit/SKILL.md @@ -16,7 +16,7 @@ Keep these terms straight: - `connected account`: the per-user authorization record - `tool`: the executable action exposed by a connector -Prefer live tool discovery over hand-maintained catalogs. If the user needs the current tool list or schema, switch to `discovering-connector-tools` or `testing-agentkit-tools`. +Prefer live tool discovery over hand-maintained catalogs. If the user needs the current tool list or schema, switch to `discovering-connector-tools` or use the Scalekit MCP server tools directly. ## Default workflow @@ -120,5 +120,5 @@ console.log(result); ## When to switch skills - Use `discovering-connector-tools` when the user needs the current tool catalog or schema. -- Use `testing-agentkit-tools` when the user wants to validate a tool call in Claude Code. +- Use the Scalekit MCP server tools to validate a tool call interactively. - Use `exposing-agentkit-via-mcp` when the user wants AgentKit tools exposed over MCP. diff --git a/plugins/agentkit/skills/testing-agentkit-tools/SKILL.md b/plugins/agentkit/skills/testing-agentkit-tools/SKILL.md deleted file mode 100644 index 4a6feb3..0000000 --- a/plugins/agentkit/skills/testing-agentkit-tools/SKILL.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -name: testing-agentkit-tools -description: Tests live Scalekit AgentKit flows from Claude Code by generating authorization links, fetching tool metadata, and executing a tool for a connected account. Use when a user wants to validate a connector, inspect the exact payload for `execute_tool`, or build a workflow step by step in the editor. -argument-hint: "[generate-link|get-tool|execute-tool] [args...]" -disable-model-invocation: true -allowed-tools: Bash ---- - -# Testing AgentKit Tools - -This skill is the canonical live playground layer for AgentKit inside Claude Code. - -**Arguments:** $ARGUMENTS - -Use it to: - -- generate an authorization link for a connection -- fetch live tool metadata for a connector or tool name -- execute a tool with real inputs -- inspect the exact JSON payload sent to AgentKit - -## Default workflow - -1. Confirm the environment variables are available: - - `SCALEKIT_ENV_URL` - - `SCALEKIT_CLIENT_ID` - - `SCALEKIT_CLIENT_SECRET` - - legacy `TOOL_*` aliases are accepted for backward compatibility -2. Discover the tool first when the schema is unknown. -3. Generate an authorization link if the connected account is not `ACTIVE`. -4. Execute the tool with the smallest valid `tool_input`. -5. Show the exact command and payload used so the user can translate it into app code. - -## Preferred invocation - -Invoke this skill directly for the runnable playground: - -- `/agentkit:testing-agentkit-tools get-tool --provider GMAIL` -- `/agentkit:testing-agentkit-tools get-tool --tool-name gmail_fetch_mails` -- `/agentkit:testing-agentkit-tools generate-link --connection-name MY_GMAIL --identifier user_123` -- `/agentkit:testing-agentkit-tools execute-tool --tool-name gmail_fetch_mails --connection-name MY_GMAIL --identifier user_123 --tool-input '{"query":"is:unread","max_results":5}'` - -`/test-tool` remains available only as a legacy compatibility alias. - -## Operations - -### generate-link -Usage: `generate-link --connection-name --identifier ` - -Creates or fetches the connected account and prints an authorization link if the account is not yet `ACTIVE`. - -### get-tool -Usage: `get-tool [--tool-name ] [--provider ] [--page-size ] [--page-token ]` - -Fetches live tool metadata and prints the raw JSON response. Omitting `--tool-name` returns all matching tools for the filter. - -### execute-tool -Usage: `execute-tool --tool-name --connection-name --identifier --tool-input ''` - -Creates or fetches the connected account, prints an authorization link if needed, and executes the tool. - -## Your task - -Parse `$ARGUMENTS` to determine the operation, then run the bundled script from this skill directory: - -```bash -${CLAUDE_SKILL_DIR}/scripts/connect.py -``` - -### Runner selection - -Check which runner is available by running `which uv` once before any Python command: - -- if `uv` exists, use `uv run python` -- otherwise use `python3` -- if `python3` is unavailable, fall back to `python` - -### Credentials - -Before running any operation, check for these environment variables: - -- `SCALEKIT_ENV_URL` -- `SCALEKIT_CLIENT_ID` -- `SCALEKIT_CLIENT_SECRET` - -Also accept legacy aliases: - -- `TOOL_ENV_URL` -- `TOOL_CLIENT_ID` -- `TOOL_CLIENT_SECRET` - -If none of the supported variables are available, ask the user for the missing values before proceeding. Do not write secrets into source-controlled files unless the user explicitly asks you to. - -### Commands to run - -If operation is `generate-link`, run: - -```bash - "${CLAUDE_SKILL_DIR}/scripts/connect.py" --generate-link --connection-name --identifier -``` - -If operation is `get-tool`, run: - -```bash - "${CLAUDE_SKILL_DIR}/scripts/connect.py" --get-tool [--tool-name ] [--provider ] [--page-size ] [--page-token ] -``` - -If operation is `execute-tool`, run: - -```bash - "${CLAUDE_SKILL_DIR}/scripts/connect.py" --execute-tool --tool-name --connection-name --identifier --tool-input '' -``` - -If `tool_input` is missing for `execute-tool`, inspect the live tool metadata first or ask the user for the missing input values. - -### After running - -Show: - -1. the command output -2. the exact command that was run -3. the resolved parameters in a small structured summary -4. for `execute-tool`, the exact JSON payload that was passed to AgentKit - -## Guardrails - -- Treat live metadata as the source of truth for `input_schema` and `output_schema`. -- Do not assume the dashboard `connection_name` matches the connector slug. -- Ask for missing credentials instead of inventing placeholder values. -- Keep the tool set constrained to the current workflow. - -## Deep reference - -- Legacy alias: [../../commands/test-tool.md](../../commands/test-tool.md) -- Canonical docs entrypoint: [../../docs/index.md](../../docs/index.md) -- Live discovery model: [../../docs/tool-discovery.md](../../docs/tool-discovery.md) -- Integration workflow: [../integrating-agentkit/SKILL.md](../integrating-agentkit/SKILL.md) diff --git a/plugins/agentkit/skills/testing-agentkit-tools/scripts/connect.py b/plugins/agentkit/skills/testing-agentkit-tools/scripts/connect.py deleted file mode 100644 index e628a4d..0000000 --- a/plugins/agentkit/skills/testing-agentkit-tools/scripts/connect.py +++ /dev/null @@ -1,250 +0,0 @@ -#!/usr/bin/env python3 -""" -AgentKit playground for Claude Code. - -Supports: -- generate-link -- get-tool -- execute-tool -""" - -import argparse -import json -import os -import sys - -try: - from dotenv import load_dotenv - load_dotenv() -except ImportError: - pass - -try: - from google.protobuf.json_format import MessageToDict -except ImportError: - MessageToDict = None - -import scalekit.client as scalekit_sdk - - -BOLD = "\033[1m" -GREEN = "\033[92m" -RED = "\033[91m" -YELLOW = "\033[93m" -BLUE = "\033[94m" -RESET = "\033[0m" - - -def env_value(primary: str, legacy: str) -> str: - return os.getenv(primary) or os.getenv(legacy) or "" - - -SCALEKIT_ENV_URL = env_value("SCALEKIT_ENV_URL", "TOOL_ENV_URL") -SCALEKIT_CLIENT_ID = env_value("SCALEKIT_CLIENT_ID", "TOOL_CLIENT_ID") -SCALEKIT_CLIENT_SECRET = env_value("SCALEKIT_CLIENT_SECRET", "TOOL_CLIENT_SECRET") - - -def require_env() -> None: - missing = [] - if not SCALEKIT_ENV_URL: - missing.append("SCALEKIT_ENV_URL") - if not SCALEKIT_CLIENT_ID: - missing.append("SCALEKIT_CLIENT_ID") - if not SCALEKIT_CLIENT_SECRET: - missing.append("SCALEKIT_CLIENT_SECRET") - if missing: - print(f"{RED}Missing required environment variables: {', '.join(missing)}{RESET}") - print("Legacy TOOL_* aliases are also supported.") - sys.exit(1) - - -def get_scalekit_client(): - require_env() - return scalekit_sdk.ScalekitClient( - SCALEKIT_ENV_URL, - SCALEKIT_CLIENT_ID, - SCALEKIT_CLIENT_SECRET, - ) - - -def get_connect_client(): - client = get_scalekit_client() - return client.actions if hasattr(client, "actions") else client.connect - - -def to_jsonable(value): - if MessageToDict is not None and hasattr(value, "DESCRIPTOR"): - return MessageToDict(value, preserving_proto_field_name=True) - if isinstance(value, (dict, list, str, int, float, bool)) or value is None: - return value - if isinstance(value, bytes): - return value.decode("utf-8", errors="replace") - if hasattr(value, "__dict__"): - return {key: to_jsonable(val) for key, val in vars(value).items() if not key.startswith("_")} - return str(value) - - -def print_json(value) -> None: - print(json.dumps(to_jsonable(value), indent=2)) - - -def get_or_create_account(connection_name: str, identifier: str): - connect = get_connect_client() - response = connect.get_or_create_connected_account( - connection_name=connection_name, - identifier=identifier, - ) - return response.connected_account - - -def generate_link(connection_name: str, identifier: str) -> None: - connect = get_connect_client() - - print(f" Connection: {connection_name}") - print(f" Identifier: {identifier}") - print() - - try: - connected_account = get_or_create_account(connection_name, identifier) - print(f" Connected Account ID: {connected_account.id}") - print(f" Status: {connected_account.status}") - - if connected_account.status != "ACTIVE": - link_response = connect.get_authorization_link( - connection_name=connection_name, - identifier=identifier, - ) - print(f"\n{YELLOW}⚠ Connected account is not ACTIVE yet.{RESET}") - print(f"\n🔗 Click the link to authorize {connection_name}:") - print(f" {BLUE}{link_response.link}{RESET}") - else: - print(f"\n{GREEN}✅ {connection_name} is already connected and active.{RESET}") - - except Exception as exc: - print(f"\n{RED}❌ Error: {exc}{RESET}") - sys.exit(1) - - -def execute_tool(tool_name: str, connection_name: str, identifier: str, tool_input: dict) -> None: - connect = get_connect_client() - - print(f" Tool: {tool_name}") - print(f" Connection: {connection_name}") - print(f" Identifier: {identifier}") - print(f" Input: {json.dumps(tool_input, indent=2)}") - print() - - try: - connected_account = get_or_create_account(connection_name, identifier) - print(f" Connected Account ID: {connected_account.id}") - print(f" Status: {connected_account.status}") - - if connected_account.status != "ACTIVE": - link_response = connect.get_authorization_link( - connection_name=connection_name, - identifier=identifier, - ) - print(f"\n{YELLOW}⚠ Connected account is not ACTIVE yet.{RESET}") - print(f"\n🔗 Authorize {connection_name} here:") - print(f" {BLUE}{link_response.link}{RESET}") - print(f"\n{YELLOW}Re-run this command after the user completes authorization.{RESET}") - sys.exit(0) - - print(f"\n🔧 Executing tool: {BOLD}{tool_name}{RESET}") - result = connect.execute_tool( - tool_name=tool_name, - identifier=identifier, - connected_account_id=connected_account.id, - tool_input=tool_input, - ) - - print(f"\n{GREEN}✅ Result:{RESET}") - print_json(result) - - except Exception as exc: - print(f"\n{RED}❌ Error: {exc}{RESET}") - sys.exit(1) - - -def get_tool(tool_name: str = None, provider: str = None, page_size: int = None, page_token: str = None) -> None: - client = get_scalekit_client() - - try: - from scalekit.v1.tools.tools_pb2 import Filter - - filter_kwargs = {} - if tool_name: - filter_kwargs["tool_name"] = [tool_name] - if provider: - filter_kwargs["provider"] = provider - - list_kwargs = {} - if filter_kwargs: - list_kwargs["filter"] = Filter(**filter_kwargs) - if page_size is not None: - list_kwargs["page_size"] = page_size - if page_token: - list_kwargs["page_token"] = page_token - - response, _ = client.tools.list_tools(**list_kwargs) - print_json(response) - - except Exception as exc: - print(f"\n{RED}❌ Error: {exc}{RESET}") - sys.exit(1) - - -def main() -> None: - parser = argparse.ArgumentParser( - description="AgentKit playground - generate auth links, fetch tool metadata, and execute tools", - ) - - group = parser.add_mutually_exclusive_group(required=True) - group.add_argument("--generate-link", action="store_true", help="Generate authorization link if needed") - group.add_argument("--get-tool", action="store_true", help="Fetch live tool metadata") - group.add_argument("--execute-tool", action="store_true", help="Execute a live tool") - - parser.add_argument("--connection-name", help="Exact dashboard connection name") - parser.add_argument("--identifier", help="User or account identifier") - parser.add_argument("--tool-name", help="Tool name to fetch or execute") - parser.add_argument("--tool-input", help="JSON string passed to the tool") - parser.add_argument("--provider", help="Provider filter for live tool discovery") - parser.add_argument("--page-size", type=int, help="Page size for tool listing") - parser.add_argument("--page-token", help="Pagination token for tool listing") - - args = parser.parse_args() - - if args.generate_link: - if not args.connection_name or not args.identifier: - parser.error("--connection-name and --identifier are required for --generate-link") - generate_link(args.connection_name, args.identifier) - return - - if args.get_tool: - get_tool( - tool_name=args.tool_name, - provider=args.provider, - page_size=args.page_size, - page_token=args.page_token, - ) - return - - if not args.connection_name or not args.identifier or not args.tool_name or not args.tool_input: - parser.error("--connection-name, --identifier, --tool-name, and --tool-input are required for --execute-tool") - - try: - tool_input = json.loads(args.tool_input) - except json.JSONDecodeError as exc: - print(f"{RED}❌ Invalid JSON for --tool-input: {exc}{RESET}") - sys.exit(1) - - execute_tool( - tool_name=args.tool_name, - connection_name=args.connection_name, - identifier=args.identifier, - tool_input=tool_input, - ) - - -if __name__ == "__main__": - main() From 2e21c5a476bdecbbfe25e68bba60ccecd7e2ccd8 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 13 May 2026 14:28:35 +0530 Subject: [PATCH 17/53] trim agents: remove sdk-version-advisor, session-management-reviewer, mcp-helper Removed 3 agents that don't justify being separate agents: - sdk-version-advisor: just a lookup, skills already cover SDK install - session-management-reviewer: preserved as references/session-management-patterns.md - scalekit-mcp-helper: 22 lines, MCP server + skills cover this Also removed duplicate setup-scalekit from agentkit (kept in saaskit). Remaining agents: setup-scalekit, scalekit-mcp-auth-troubleshooter. --- plugins/agentkit/agents/setup-scalekit.md | 44 --------- plugins/saaskit/agents/scalekit-mcp-helper.md | 21 ----- plugins/saaskit/agents/sdk-version-advisor.md | 93 ------------------- .../session-management-patterns.md} | 33 +------ 4 files changed, 5 insertions(+), 186 deletions(-) delete mode 100644 plugins/agentkit/agents/setup-scalekit.md delete mode 100644 plugins/saaskit/agents/scalekit-mcp-helper.md delete mode 100644 plugins/saaskit/agents/sdk-version-advisor.md rename plugins/saaskit/{agents/session-management-reviewer.md => references/session-management-patterns.md} (86%) diff --git a/plugins/agentkit/agents/setup-scalekit.md b/plugins/agentkit/agents/setup-scalekit.md deleted file mode 100644 index 8b70de2..0000000 --- a/plugins/agentkit/agents/setup-scalekit.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -name: scalekit-setup -description: Sets up Scalekit env vars, installs/initializes the SDK, and verifies credentials by listing organizations. Use proactively when user asks to set up, install, initialize, configure, or verify Scalekit. -model: sonnet -tools: Read, Grep, Glob, Bash, Write, Edit -maxTurns: 20 ---- - -You are a Scalekit setup and verification specialist. - -Mission: -- Help the user configure Scalekit credentials via environment variables. -- Help them install and initialize the official Scalekit SDK for their language. -- Verify the setup with the smallest reliable check (prefer listing organizations). -- Keep secrets out of chat and out of the repo. - -Hard rules: -- NEVER ask the user to paste SCALEKIT_CLIENT_SECRET into chat. -- NEVER hardcode credentials in code samples; always use environment variables. -- Prefer creating a local verification script (verify.js / verify.py / verify.go / Verify.java) and running it, but only if the user wants you to write files. -- When using agent auth for nodejs, use npm install @scalekit-sdk/node - -Workflow: -1) Determine language/runtime (Node.js, Python, Go, Java) and where env vars should live (.env, shell, CI secrets). -2) Confirm required env vars exist: - - SCALEKIT_ENVIRONMENT_URL - - SCALEKIT_CLIENT_ID - - SCALEKIT_CLIENT_SECRET -3) Install the SDK (pick the official package for that language). -4) Initialize the SDK client using env vars. -5) Verify credentials by listing organizations with a small page size. -6) If verification fails, diagnose systematically: - - Wrong environment URL (dev vs prod) - - Missing env vars in current shell/process - - Incorrect client id/secret - - Network/DNS issues -7) Only after verification succeeds, proceed to feature work and route to the correct Skill: - - Agent auth → plugins/agentkit/skills/integrating-agentkit/SKILL.md - - SSO → plugins/saaskit/skills/implementing-modular-sso/SKILL.md - - SCIM → plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md - - MCP server auth → plugins/saaskit/skills/adding-mcp-oauth/SKILL.md - - SaaSKit auth → plugins/saaskit/skills/implementing-saaskit/SKILL.md - -When you reference files, use exact repo-relative paths and read them before advising. diff --git a/plugins/saaskit/agents/scalekit-mcp-helper.md b/plugins/saaskit/agents/scalekit-mcp-helper.md deleted file mode 100644 index 39a155b..0000000 --- a/plugins/saaskit/agents/scalekit-mcp-helper.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: scalekit-mcp-helper -description: Helps configure Scalekit MCP client settings (Claude Desktop, Cursor, Windsurf, VS Code) and explains the OAuth connection flow. Use when user asks about MCP setup, Claude Desktop config, Cursor MCP, Windsurf MCP, or VS Code MCP. -model: sonnet -tools: Read, Grep, Glob -maxTurns: 15 ---- - -You are a Scalekit MCP setup assistant. - -Scope: -- Provide correct MCP client configuration snippets. -- Explain what the user should expect during the OAuth authorization flow. -- Do not write files unless explicitly asked. - -Rules: -- Do not request secrets. -- Prefer the exact documented JSON snippets the user can paste into their MCP client configuration. -- If the repo contains MCP-specific docs, read them and follow the repo’s guidance. - - diff --git a/plugins/saaskit/agents/sdk-version-advisor.md b/plugins/saaskit/agents/sdk-version-advisor.md deleted file mode 100644 index 404b8f5..0000000 --- a/plugins/saaskit/agents/sdk-version-advisor.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -name: scalekit-sdk-version-advisor -description: Determine the user’s current tech stack (language, framework, runtime, package manager) and recommend the correct Scalekit SDK version(s) and integration path (MCP Auth, Agent Auth, SSO, or full-stack), including install commands and minimal setup snippets. -maxTurns: 12 -permissionMode: plan -tools: - - Read - - Glob - - Grep - - Bash -disallowedTools: - - Write - - Edit ---- - -# Role -You are a Scalekit SDK & integration advisor. Your job is to recommend the *right SDK version(s)* to install for the user’s *current* tech stack, and the right modular auth component(s): MCP Auth, Agent Auth, SSO, or full-stack. Scalekit provides official SDKs across Node.js (TypeScript/ESM, Express/NestJS/Next.js), Python (async-first, Pydantic v2, FastAPI/Django/Flask), Go (zero-dependency, Gin/Echo/Chi), and Java (Spring Boot, Maven Central). [web:3] - -# Hard rules -- Do not modify files. Only inspect and propose changes/commands. -- Prefer compatibility over “latest”: pick the newest version that fits the user’s runtime constraints and dependency ecosystem. -- If key facts are missing (runtime versions, framework, deployment target), ask short, specific questions before finalizing. -- Output must be actionable: exact package coordinates + install commands + minimal initialization snippet + auth module choice. - -# Workflow (follow in order) - -## 1) Detect the user’s stack (evidence-based) -Inspect the repo to infer: -- Languages/services present (Node/Python/Go/Java/Expo). -- Frameworks (Next.js, Express, NestJS, FastAPI, Django, Flask, Gin/Echo/Chi, Spring Boot). -- Package managers and lockfiles (pnpm/yarn/npm; uv/poetry/pip; go modules; maven/gradle). -- Runtime versions & module systems: - - Node: `package.json` engines, `.nvmrc`, `.node-version`, `type: module`, tsconfig, bundler. - - Python: `pyproject.toml` requires-python, dependency pins (esp. pydantic major), `requirements.txt`, `uv.lock`, `poetry.lock`. - - Go: `go.mod` go version. - - Java: `pom.xml` / `build.gradle` (Java target, Spring Boot version). - - Expo: `app.json`, `package.json` + React Native / Expo SDK version. - -Commands you may run (read-only): -- `node -v`, `python --version`, `go version`, `java -version` when available. -- `cat`/`grep` on config files; `ls` to discover structure. - -## 2) Decide the Scalekit integration path (module choice) -Choose one (or more) based on what the project is building: -- MCP Auth: if the repo is an MCP server or exposes MCP tools to an LLM runtime. -- Agent Auth: if the app runs autonomous agents that need scoped access to external tools/APIs on behalf of users or orgs. -- SSO: if this is a B2B SaaS that needs enterprise SSO discovery/enforcement. -- Full-stack: if the app needs a broader auth platform approach (user/org/session management + multiple auth methods). - -Explain the reasoning in 2–4 bullets. - -## 3) Pick the correct SDK(s) and versioning strategy -For each service in the repo: -1. Pick the correct language SDK. -2. Select a version strategy: - - If the user has an existing Scalekit SDK already installed, prefer upgrading within the same major unless they ask for a major bump. - - If new install, prefer the newest stable release for that SDK *that matches stack constraints*. -3. Compatibility checks you must do: - - Node: confirm ESM vs CJS expectations; confirm TS usage; note Next.js/Express/NestJS integration expectations. [web:3] - - Python: confirm Pydantic major version alignment (Scalekit Python SDK is “Pydantic v2 validated”). [web:3] - - Java: confirm Spring Boot and build tool (Maven/Gradle) alignment. [web:3] - - Go: confirm module mode and service framework. [web:3] - -If you cannot reliably determine the newest compatible version (no network / no registry access), ask the user whether to: -- Use the version shown in Scalekit Docs they referenced, or -- Keep the current installed version and only adjust integration code. - -## 4) Produce the final recommendation (strict output format) -Return a single Markdown response with these sections: - -### A) Detected stack (with evidence) -- Bullet list of findings, each referencing the file/command output you used (e.g., “package.json engines.node = …”). - -### B) Recommended Scalekit components -- MCP Auth / Agent Auth / SSO / Full-stack, with rationale. - -### C) SDK install plan -For each relevant service/language: -- Package name + recommended version range or exact pin. -- Install command(s) for the detected package manager. -- Any required peer dependency notes (e.g., Pydantic v2 alignment for Python). [web:3] - -### D) Minimal setup snippet -Provide the smallest “hello-world” initialization snippet for that language/framework (no secrets hardcoded; env vars only). - -### E) Risks & gotchas -List 3–6 concise bullets tailored to the repo (ESM/CJS mismatch, Pydantic major mismatch, Spring Boot/JDK target mismatch, monorepo workspace constraints, etc.). - -## 5) Clarifying questions (only if needed) -Ask at most 3 questions, each answerable in one line. - -# Quality bar -Your recommendation should let a developer copy/paste install commands and the init snippet, and confidently proceed without version conflicts. diff --git a/plugins/saaskit/agents/session-management-reviewer.md b/plugins/saaskit/references/session-management-patterns.md similarity index 86% rename from plugins/saaskit/agents/session-management-reviewer.md rename to plugins/saaskit/references/session-management-patterns.md index 309babc..5eb795e 100644 --- a/plugins/saaskit/agents/session-management-reviewer.md +++ b/plugins/saaskit/references/session-management-patterns.md @@ -1,33 +1,10 @@ ---- -name: scalekit-session-management-reviewer -description: > - Reviews existing session management implementation in the codebase and suggests - options for implementing or improving it using Scalekit. Use proactively when - working on authentication flows, middleware, token handling, or session-related - code. Invoke explicitly for session security audits or Scalekit integration planning. -tools: Read, Grep, Glob, Bash -model: sonnet -maxTurns: 30 ---- - -# Scalekit Session Management Reviewer +# Session Management Patterns -You are a senior authentication architect specializing in Scalekit's session management -system. Your goal is to analyze the existing codebase for session-related patterns and -provide concrete, tiered implementation options using Scalekit. +Reference guide for evaluating and implementing session management with Scalekit. Covers audit checklists, implementation options (FSA, Modular SSO, Remote API, Agent Auth), and code patterns. -Hard rules: -- Never suggest an implementation path without completing Phase 1 first. -- Always reference actual file paths found during discovery, not hypothetical ones. -- Never give generic advice — every recommendation must be grounded in what you found. -- When analyzing user identity, token claims, or profile data in session payloads, - consult `plugins/saaskit/references/scalekit-user-profiles.md` for Scalekit's - attribute schema and SDK method reference before suggesting implementation. -- When a session failure pattern is suspected or a webhook-triggered auth flow is being - debugged, consult `plugins/saaskit/references/scalekit-logs.md` for filter - strategies and status definitions before suggesting next steps. - ---- +Related references: +- [scalekit-user-profiles.md](scalekit-user-profiles.md) — attribute schema and SDK methods +- [scalekit-logs.md](scalekit-logs.md) — filter strategies and status definitions ## Phase 1: Discovery — Understand the Existing Setup From 0bacb87aa7bacead512bfe5758a4c557e29bba92 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 13 May 2026 15:06:31 +0530 Subject: [PATCH 18/53] add .mcp.json to saaskit plugin for Scalekit MCP server --- plugins/saaskit/.mcp.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 plugins/saaskit/.mcp.json diff --git a/plugins/saaskit/.mcp.json b/plugins/saaskit/.mcp.json new file mode 100644 index 0000000..783b9a1 --- /dev/null +++ b/plugins/saaskit/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "scalekit": { + "type": "http", + "url": "https://mcp.scalekit.com" + } + } +} From 9b026ca0d92e6bb0f9ce537a446dc71094d0c79d Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 13 May 2026 16:53:03 +0530 Subject: [PATCH 19/53] rename scripts/install_claude_marketplace.sh to scripts/install.sh --- install.sh | 6 +++--- scripts/{install_claude_marketplace.sh => install.sh} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename scripts/{install_claude_marketplace.sh => install.sh} (100%) diff --git a/install.sh b/install.sh index 07f9907..4dff2cc 100755 --- a/install.sh +++ b/install.sh @@ -7,7 +7,7 @@ REPO_REF="${CLAUDE_CODE_AUTHSTACK_REF:-main}" SOURCE_DIR="${CLAUDE_CODE_AUTHSTACK_SOURCE_DIR:-}" if [[ -n "$SOURCE_DIR" ]]; then - exec "${SOURCE_DIR%/}/scripts/install_claude_marketplace.sh" + exec "${SOURCE_DIR%/}/scripts/install.sh" fi TMP_DIR="$(mktemp -d)" @@ -27,9 +27,9 @@ tar -xzf "$ARCHIVE_PATH" -C "$TMP_DIR" EXTRACTED_DIR="$(find "$TMP_DIR" -mindepth 1 -maxdepth 1 -type d | head -n 1)" -if [[ -z "$EXTRACTED_DIR" ]] || [[ ! -x "$EXTRACTED_DIR/scripts/install_claude_marketplace.sh" ]]; then +if [[ -z "$EXTRACTED_DIR" ]] || [[ ! -x "$EXTRACTED_DIR/scripts/install.sh" ]]; then echo "Failed to find installer in downloaded archive." >&2 exit 1 fi -exec "$EXTRACTED_DIR/scripts/install_claude_marketplace.sh" +exec "$EXTRACTED_DIR/scripts/install.sh" diff --git a/scripts/install_claude_marketplace.sh b/scripts/install.sh similarity index 100% rename from scripts/install_claude_marketplace.sh rename to scripts/install.sh From fcc24b2e443ee069be9625224cd0079daa981f45 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 13 May 2026 17:21:00 +0530 Subject: [PATCH 20/53] Improve post-install message with plugin activation and auto-update guidance --- scripts/install.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 3750a68..007dcbb 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -33,9 +33,14 @@ Installed plugins: agentkit — AI agent authentication (connectors, tool discovery, token vault) saaskit — B2B SaaS authentication (login, SSO, SCIM, RBAC, MCP server auth) -Next steps: -1. Run \`/plugins\` in Claude Code. -2. Open \`Marketplaces\`. -3. Select \`scalekit-auth-stack\`. -4. Enable auto-update. +To activate the plugins in Claude Code: +1. Open Claude Code. +2. Type \`/plugins\` to open the plugin manager. +3. Go to \`Marketplaces\` → select \`scalekit-auth-stack\`. +4. You should see agentkit and saaskit listed as installed. +5. Set update policy to "Auto-update (recommended)" to stay current. + +To verify: + Type \`/plugins\` and confirm both agentkit and saaskit show as installed. + Try a skill: ask "help me integrate agentkit" or "test my auth setup". EOF From 52f1b56fda756dbc94e38d0fe56dacd66d2e5d36 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 13 May 2026 17:30:56 +0530 Subject: [PATCH 21/53] Simplify post-install message to focus on what to look for, not UI steps --- scripts/install.sh | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 007dcbb..3b19c6f 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -33,14 +33,11 @@ Installed plugins: agentkit — AI agent authentication (connectors, tool discovery, token vault) saaskit — B2B SaaS authentication (login, SSO, SCIM, RBAC, MCP server auth) -To activate the plugins in Claude Code: -1. Open Claude Code. -2. Type \`/plugins\` to open the plugin manager. -3. Go to \`Marketplaces\` → select \`scalekit-auth-stack\`. -4. You should see agentkit and saaskit listed as installed. -5. Set update policy to "Auto-update (recommended)" to stay current. - -To verify: - Type \`/plugins\` and confirm both agentkit and saaskit show as installed. - Try a skill: ask "help me integrate agentkit" or "test my auth setup". +What to do next in Claude Code: +- Look for the "scalekit-auth-stack" marketplace in your plugin settings. +- Install both plugins: "agentkit" and "saaskit". +- Set the update policy to auto-update so you always have the latest skills. + +To verify it works: + Ask Claude Code to "help me integrate agentkit" or "test my auth setup". EOF From 7e1e9b0b5674a1fffde8a8f0108174511f8ac05a Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 13 May 2026 18:14:56 +0530 Subject: [PATCH 22/53] Make bootstrap installer resilient to missing execute bit in tarball --- install.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/install.sh b/install.sh index 4dff2cc..2c5442b 100755 --- a/install.sh +++ b/install.sh @@ -27,9 +27,10 @@ tar -xzf "$ARCHIVE_PATH" -C "$TMP_DIR" EXTRACTED_DIR="$(find "$TMP_DIR" -mindepth 1 -maxdepth 1 -type d | head -n 1)" -if [[ -z "$EXTRACTED_DIR" ]] || [[ ! -x "$EXTRACTED_DIR/scripts/install.sh" ]]; then +if [[ -z "$EXTRACTED_DIR" ]] || [[ ! -f "$EXTRACTED_DIR/scripts/install.sh" ]]; then echo "Failed to find installer in downloaded archive." >&2 exit 1 fi +chmod +x "$EXTRACTED_DIR/scripts/install.sh" exec "$EXTRACTED_DIR/scripts/install.sh" From f3bbff434efc080516e40dede87d4f2414bdf57d Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 13 May 2026 20:55:11 +0530 Subject: [PATCH 23/53] Add 43 agent-connectors reference docs to agentkit Per-connector docs (scopes, quirks, tool categories) were present in cursor and codex but missing from claude-code. Copied full set to maintain parity across all auth stacks. --- plugins/agentkit/README.md | 2 +- .../references/agent-connectors/README.md | 71 ++++ .../references/agent-connectors/airtable.md | 3 + .../references/agent-connectors/asana.md | 3 + .../references/agent-connectors/attention.md | 3 + .../references/agent-connectors/bigquery.md | 3 + .../references/agent-connectors/chorus.md | 3 + .../agent-connectors/clari_copilot.md | 3 + .../references/agent-connectors/clickup.md | 3 + .../references/agent-connectors/confluence.md | 3 + .../references/agent-connectors/dropbox.md | 3 + .../references/agent-connectors/fathom.md | 3 + .../references/agent-connectors/freshdesk.md | 149 +++++++++ .../references/agent-connectors/github.md | 137 ++++++++ .../references/agent-connectors/gmail.md | 79 +++++ .../references/agent-connectors/gong.md | 3 + .../references/agent-connectors/google_ads.md | 3 + .../agent-connectors/google_docs.md | 3 + .../agent-connectors/google_drive.md | 3 + .../agent-connectors/google_forms.md | 3 + .../agent-connectors/google_meets.md | 3 + .../agent-connectors/google_sheets.md | 3 + .../agent-connectors/google_slides.md | 32 ++ .../agent-connectors/googlecalendar.md | 128 +++++++ .../references/agent-connectors/hubspot.md | 143 ++++++++ .../references/agent-connectors/intercom.md | 3 + .../references/agent-connectors/jira.md | 3 + .../references/agent-connectors/linear.md | 58 ++++ .../agent-connectors/microsoft_excel.md | 3 + .../agent-connectors/microsoft_teams.md | 3 + .../agent-connectors/microsoft_word.md | 3 + .../references/agent-connectors/monday.md | 3 + .../references/agent-connectors/notion.md | 189 +++++++++++ .../references/agent-connectors/onedrive.md | 3 + .../references/agent-connectors/onenote.md | 3 + .../references/agent-connectors/outlook.md | 3 + .../references/agent-connectors/salesforce.md | 316 ++++++++++++++++++ .../references/agent-connectors/servicenow.md | 3 + .../references/agent-connectors/sharepoint.md | 3 + .../references/agent-connectors/slack.md | 197 +++++++++++ .../references/agent-connectors/snowflake.md | 3 + .../references/agent-connectors/trello.md | 3 + .../references/agent-connectors/zendesk.md | 3 + .../references/agent-connectors/zoom.md | 3 + 44 files changed, 1596 insertions(+), 1 deletion(-) create mode 100644 plugins/agentkit/references/agent-connectors/README.md create mode 100644 plugins/agentkit/references/agent-connectors/airtable.md create mode 100644 plugins/agentkit/references/agent-connectors/asana.md create mode 100644 plugins/agentkit/references/agent-connectors/attention.md create mode 100644 plugins/agentkit/references/agent-connectors/bigquery.md create mode 100644 plugins/agentkit/references/agent-connectors/chorus.md create mode 100644 plugins/agentkit/references/agent-connectors/clari_copilot.md create mode 100644 plugins/agentkit/references/agent-connectors/clickup.md create mode 100644 plugins/agentkit/references/agent-connectors/confluence.md create mode 100644 plugins/agentkit/references/agent-connectors/dropbox.md create mode 100644 plugins/agentkit/references/agent-connectors/fathom.md create mode 100644 plugins/agentkit/references/agent-connectors/freshdesk.md create mode 100644 plugins/agentkit/references/agent-connectors/github.md create mode 100644 plugins/agentkit/references/agent-connectors/gmail.md create mode 100644 plugins/agentkit/references/agent-connectors/gong.md create mode 100644 plugins/agentkit/references/agent-connectors/google_ads.md create mode 100644 plugins/agentkit/references/agent-connectors/google_docs.md create mode 100644 plugins/agentkit/references/agent-connectors/google_drive.md create mode 100644 plugins/agentkit/references/agent-connectors/google_forms.md create mode 100644 plugins/agentkit/references/agent-connectors/google_meets.md create mode 100644 plugins/agentkit/references/agent-connectors/google_sheets.md create mode 100644 plugins/agentkit/references/agent-connectors/google_slides.md create mode 100644 plugins/agentkit/references/agent-connectors/googlecalendar.md create mode 100644 plugins/agentkit/references/agent-connectors/hubspot.md create mode 100644 plugins/agentkit/references/agent-connectors/intercom.md create mode 100644 plugins/agentkit/references/agent-connectors/jira.md create mode 100644 plugins/agentkit/references/agent-connectors/linear.md create mode 100644 plugins/agentkit/references/agent-connectors/microsoft_excel.md create mode 100644 plugins/agentkit/references/agent-connectors/microsoft_teams.md create mode 100644 plugins/agentkit/references/agent-connectors/microsoft_word.md create mode 100644 plugins/agentkit/references/agent-connectors/monday.md create mode 100644 plugins/agentkit/references/agent-connectors/notion.md create mode 100644 plugins/agentkit/references/agent-connectors/onedrive.md create mode 100644 plugins/agentkit/references/agent-connectors/onenote.md create mode 100644 plugins/agentkit/references/agent-connectors/outlook.md create mode 100644 plugins/agentkit/references/agent-connectors/salesforce.md create mode 100644 plugins/agentkit/references/agent-connectors/servicenow.md create mode 100644 plugins/agentkit/references/agent-connectors/sharepoint.md create mode 100644 plugins/agentkit/references/agent-connectors/slack.md create mode 100644 plugins/agentkit/references/agent-connectors/snowflake.md create mode 100644 plugins/agentkit/references/agent-connectors/trello.md create mode 100644 plugins/agentkit/references/agent-connectors/zendesk.md create mode 100644 plugins/agentkit/references/agent-connectors/zoom.md diff --git a/plugins/agentkit/README.md b/plugins/agentkit/README.md index 45390ed..2a20201 100644 --- a/plugins/agentkit/README.md +++ b/plugins/agentkit/README.md @@ -15,7 +15,7 @@ Claude runtime files remain in place as adapters: - `hooks/` - `agents/` -The plugin treats live AgentKit metadata as the source of truth for tool names, `input_schema`, and `output_schema`. Connector notes are curated guidance, not a guaranteed exhaustive catalog. +The plugin treats live AgentKit metadata as the source of truth for tool names, `input_schema`, and `output_schema`. Per-connector reference docs in `references/agent-connectors/` provide curated guidance on scopes, quirks, and tool categories — but they are not a guaranteed exhaustive catalog. ## Installation ```sh diff --git a/plugins/agentkit/references/agent-connectors/README.md b/plugins/agentkit/references/agent-connectors/README.md new file mode 100644 index 0000000..8386ae0 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/README.md @@ -0,0 +1,71 @@ +# Agent Connectors Reference + +This directory contains documentation for all supported agent connectors in the Scalekit AgentKit platform. + +## Available Connectors + +| Connector | Description | Auth Type | +|-----------|-------------|-----------| +| [Airtable](airtable.md) | Connect to Airtable bases for data management | OAuth 2.0 | +| [Asana](asana.md) | Project management and task tracking | OAuth 2.0 | +| [BigQuery](bigquery.md) | Google BigQuery data warehouse | OAuth 2.0 | +| [ClickUp](clickup.md) | Project management and collaboration | OAuth 2.0 | +| [Confluence](confluence.md) | Atlassian Confluence wiki pages | OAuth 2.0 | +| [Dropbox](dropbox.md) | File storage and sharing | OAuth 2.0 | +| [Fathom](fathom.md) | Website analytics | OAuth 2.0 | +| [Freshdesk](freshdesk.md) | Customer support ticketing | OAuth 2.0 | +| [GitHub](github.md) | Code repository and development tools | OAuth 2.0 | +| [Gmail](gmail.md) | Google Gmail email service | OAuth 2.0 | +| [Google Ads](google_ads.md) | Google advertising platform | OAuth 2.0 | +| [Google Calendar](googlecalendar.md) | Google Calendar events and scheduling | OAuth 2.0 | +| [Google Docs](google_docs.md) | Google Docs document editing | OAuth 2.0 | +| [Google Drive](google_drive.md) | Google Drive file storage | OAuth 2.0 | +| [Google Forms](google_forms.md) | Google Forms survey creation | OAuth 2.0 | +| [Google Meet](google_meets.md) | Google Meet video conferencing | OAuth 2.0 | +| [Google Sheets](google_sheets.md) | Google Sheets spreadsheet editing | OAuth 2.0 | +| [Gong](gong.md) | Sales conversation intelligence | OAuth 2.0 | +| [HubSpot](hubspot.md) | CRM and marketing automation | OAuth 2.0 | +| [Intercom](intercom.md) | Customer messaging platform | OAuth 2.0 | +| [Jira](jira.md) | Atlassian Jira issue tracking | OAuth 2.0 | +| [Linear](linear.md) | Software development issue tracking | OAuth 2.0 | +| [Microsoft Excel](microsoft_excel.md) | Microsoft Excel spreadsheet editing | OAuth 2.0 | +| [Microsoft Teams](microsoft_teams.md) | Microsoft Teams collaboration | OAuth 2.0 | +| [Microsoft Word](microsoft_word.md) | Microsoft Word document editing | OAuth 2.0 | +| [Monday](monday.md) | Work management platform | OAuth 2.0 | +| [Notion](notion.md) | Notion workspace and pages | OAuth 2.0 | +| [OneDrive](onedrive.md) | Microsoft OneDrive file storage | OAuth 2.0 | +| [OneNote](onenote.md) | Microsoft OneNote note-taking | OAuth 2.0 | +| [Outlook](outlook.md) | Microsoft Outlook email | OAuth 2.0 | +| [Salesforce](salesforce.md) | Salesforce CRM platform | OAuth 2.0 | +| [ServiceNow](servicenow.md) | IT service management | OAuth 2.0 | +| [SharePoint](sharepoint.md) | Microsoft SharePoint collaboration | OAuth 2.0 | +| [Slack](slack.md) | Slack messaging and collaboration | OAuth 2.0 | +| [Snowflake](snowflake.md) | Snowflake data warehouse | OAuth 2.0 | +| [Trello](trello.md) | Trello project boards | OAuth 2.0 | +| [Zendesk](zendesk.md) | Customer support platform | OAuth 2.0 | +| [Zoom](zoom.md) | Zoom video conferencing | OAuth 2.0 | +| [Attention](attention.md) | Meeting intelligence and note-taking | API Key | +| [Chorus](chorus.md) | Conversation intelligence for sales | OAuth 2.0 | +| [Clari Copilot](clari_copilot.md) | Revenue intelligence and forecasting | API Key | +| [Google Slides](google_slides.md) | Google Slides presentation editing | OAuth 2.0 | + +## Getting Started + +Each connector documentation includes: + +- Service description and capabilities +- Authentication requirements +- Complete API reference for all available tools +- Parameter specifications and examples +- Usage guidelines and best practices + +## Authentication + +Most connectors use OAuth 2.0 authentication through the AgentKit platform; some use API Key or Basic Auth. You'll need to: + +1. Create a connection for the desired service +2. Configure OAuth credentials in your connection +3. Create connected accounts for your users +4. Use the connection in your agent workflows + +For detailed authentication setup, see the [Connected Accounts](../connected-accounts.md) documentation. \ No newline at end of file diff --git a/plugins/agentkit/references/agent-connectors/airtable.md b/plugins/agentkit/references/agent-connectors/airtable.md new file mode 100644 index 0000000..f3acf0b --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/airtable.md @@ -0,0 +1,3 @@ +Connect to Airtable. Manage databases, tables, records, and collaborate on structured data + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/asana.md b/plugins/agentkit/references/agent-connectors/asana.md new file mode 100644 index 0000000..cea2fc7 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/asana.md @@ -0,0 +1,3 @@ +Connect to Asana. Manage tasks, projects, teams, and workflow automation + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/attention.md b/plugins/agentkit/references/agent-connectors/attention.md new file mode 100644 index 0000000..9af975e --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/attention.md @@ -0,0 +1,3 @@ +Connect to Attention for AI insights, conversations, teams, and workflows + +Supports authentication: API Key diff --git a/plugins/agentkit/references/agent-connectors/bigquery.md b/plugins/agentkit/references/agent-connectors/bigquery.md new file mode 100644 index 0000000..64b8a33 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/bigquery.md @@ -0,0 +1,3 @@ +BigQuery is Google Cloud’s fully-managed enterprise data warehouse for analytics at scale. + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/chorus.md b/plugins/agentkit/references/agent-connectors/chorus.md new file mode 100644 index 0000000..b57dd23 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/chorus.md @@ -0,0 +1,3 @@ +Connect to Chorus.ai to sync calls, transcripts, conversation intelligence, and analytics. + +Supports authentication: Basic Auth diff --git a/plugins/agentkit/references/agent-connectors/clari_copilot.md b/plugins/agentkit/references/agent-connectors/clari_copilot.md new file mode 100644 index 0000000..2949e94 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/clari_copilot.md @@ -0,0 +1,3 @@ +Connect to Clari Copilot for sales call transcripts, analytics, call data, and insights. + +Supports authentication: API Key diff --git a/plugins/agentkit/references/agent-connectors/clickup.md b/plugins/agentkit/references/agent-connectors/clickup.md new file mode 100644 index 0000000..dd7d216 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/clickup.md @@ -0,0 +1,3 @@ +Connect to ClickUp. Manage tasks, projects, workspaces, and team collaboration + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/confluence.md b/plugins/agentkit/references/agent-connectors/confluence.md new file mode 100644 index 0000000..96d699f --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/confluence.md @@ -0,0 +1,3 @@ +Connect to Confluence. Manage spaces, pages, content, and team collaboration + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/dropbox.md b/plugins/agentkit/references/agent-connectors/dropbox.md new file mode 100644 index 0000000..1bfe1d3 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/dropbox.md @@ -0,0 +1,3 @@ +Connect to Dropbox. Manage files, folders, sharing, and cloud storage workflows + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/fathom.md b/plugins/agentkit/references/agent-connectors/fathom.md new file mode 100644 index 0000000..3408c98 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/fathom.md @@ -0,0 +1,3 @@ +Connect to Fathom AI meeting assistant. Record, transcribe, and summarize meetings with AI-powered insights + +Supports authentication: API Key diff --git a/plugins/agentkit/references/agent-connectors/freshdesk.md b/plugins/agentkit/references/agent-connectors/freshdesk.md new file mode 100644 index 0000000..424a753 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/freshdesk.md @@ -0,0 +1,149 @@ +Connect to Freshdesk. Manage tickets, contacts, companies, and customer support workflows + +Supports authentication: Basic Auth + +## Table of Contents + +- [Tool list](#tool-list) + +--- + +## Tool list + +## `freshdesk_agent_create` + +Create a new agent in Freshdesk. Email is required and must be unique. Agent will receive invitation email to set up account. At least one role must be assigned. + +| Properties | Description | Type | +| --- | --- | --- | +| `agent_type` | Type of agent (1=Support Agent, 2=Field Agent, 3=Collaborator) | number | null | +| `email` | Email address of the agent (must be unique) | string | +| `focus_mode` | Focus mode setting for the agent | boolean | null | +| `group_ids` | Array of group IDs to assign the agent to | `array` | null | +| `language` | Language preference of the agent | string | null | +| `name` | Full name of the agent | string | null | +| `occasional` | Whether the agent is occasional (true) or full-time (false) | boolean | null | +| `role_ids` | Array of role IDs to assign to the agent (at least one required) | `array` | +| `signature` | Agent email signature in HTML format | string | null | +| `skill_ids` | Array of skill IDs to assign to the agent | `array` | null | +| `ticket_scope` | Ticket permission level (1=Global Access, 2=Group Access, 3=Restricted Access) | number | +| `time_zone` | Time zone of the agent | string | null | + +## `freshdesk_agent_delete` + +Delete an agent from Freshdesk. This action is irreversible and will remove the agent from the system. The agent will no longer have access to the helpdesk and all associated data will be permanently deleted. + +| Properties | Description | Type | +| --- | --- | --- | +| `agent_id` | ID of the agent to delete | number | + +## `freshdesk_agents_list` + +Retrieve a list of agents from Freshdesk with filtering options. Returns agent details including IDs, contact information, roles, and availability status. Supports pagination with up to 100 agents per page. + +| Properties | Description | Type | +| --- | --- | --- | +| `email` | Filter agents by email address | string | null | +| `mobile` | Filter agents by mobile number | string | null | +| `page` | Page number for pagination (starts from 1) | number | null | +| `per_page` | Number of agents per page (max 100) | number | null | +| `phone` | Filter agents by phone number | string | null | +| `state` | Filter agents by state (fulltime or occasional) | string | null | + +## `freshdesk_contact_create` + +Create a new contact in Freshdesk. Email and name are required. Supports custom fields, company assignment, and contact segmentation. + +| Properties | Description | Type | +| --- | --- | --- | +| `address` | Address of the contact | string | null | +| `company_id` | Company ID to associate with the contact | number | null | +| `custom_fields` | Key-value pairs for custom field values | `object` | null | +| `description` | Description about the contact | string | null | +| `email` | Email address of the contact | string | +| `job_title` | Job title of the contact | string | null | +| `language` | Language preference of the contact | string | null | +| `mobile` | Mobile number of the contact | string | null | +| `name` | Full name of the contact | string | +| `phone` | Phone number of the contact | string | null | +| `tags` | Array of tags to associate with the contact | `array` | null | +| `time_zone` | Time zone of the contact | string | null | + +## `freshdesk_roles_list` + +Retrieve a list of all roles from Freshdesk. Returns role details including IDs, names, descriptions, default status, and timestamps. This endpoint provides information about the different permission levels and access controls available in the Freshdesk system. + +## `freshdesk_ticket_create` + +Create a new ticket in Freshdesk. Requires either requester_id, email, facebook_id, phone, twitter_id, or unique_external_id to identify the requester. + +| Properties | Description | Type | +| --- | --- | --- | +| `cc_emails` | Array of email addresses to be added in CC | `array` | null | +| `custom_fields` | Key-value pairs containing custom field names and values | `object` | null | +| `description` | HTML content of the ticket describing the issue | string | null | +| `email` | Email address of the requester. If no contact exists, will be added as new contact. | string | null | +| `group_id` | ID of the group to which the ticket has been assigned | number | null | +| `name` | Name of the requester | string | null | +| `priority` | Priority of the ticket. 1=Low, 2=Medium, 3=High, 4=Urgent | number | null | +| `requester_id` | User ID of the requester. For existing contacts, can be passed instead of email. | number | null | +| `responder_id` | ID of the agent to whom the ticket has been assigned | number | null | +| `source` | Channel through which ticket was created. 1=Email, 2=Portal, 3=Phone, 7=Chat, 9=Feedback Widget, 10=Outbound Email | number | null | +| `status` | Status of the ticket. 2=Open, 3=Pending, 4=Resolved, 5=Closed | number | null | +| `subject` | Subject of the ticket | string | null | +| `tags` | Array of tags to be associated with the ticket | `array` | null | +| `type` | Helps categorize the ticket according to different kinds of issues | string | null | + +## `freshdesk_ticket_get` + +Retrieve details of a specific ticket by ID. Includes ticket properties, conversations, and metadata. + +| Properties | Description | Type | +| --- | --- | --- | +| `include` | Additional resources to include (stats, requester, company, conversations) | string | null | +| `ticket_id` | ID of the ticket to retrieve | number | + +## `freshdesk_ticket_update` + +Update an existing ticket in Freshdesk. Note: Subject and description of outbound tickets cannot be updated. + +| Properties | Description | Type | +| --- | --- | --- | +| `custom_fields` | Key-value pairs containing custom field names and values | `object` | null | +| `description` | HTML content of the ticket (cannot be updated for outbound tickets) | string | null | +| `group_id` | ID of the group to which the ticket has been assigned | number | null | +| `name` | Name of the requester | string | null | +| `priority` | Priority of the ticket. 1=Low, 2=Medium, 3=High, 4=Urgent | number | null | +| `responder_id` | ID of the agent to whom the ticket has been assigned | number | null | +| `status` | Status of the ticket. 2=Open, 3=Pending, 4=Resolved, 5=Closed | number | null | +| `subject` | Subject of the ticket (cannot be updated for outbound tickets) | string | null | +| `tags` | Array of tags to be associated with the ticket | `array` | null | +| `ticket_id` | ID of the ticket to update | number | + +## `freshdesk_tickets_list` + +Retrieve a list of tickets with filtering and pagination. Supports filtering by status, priority, requester, and more. Returns 30 tickets per page by default. + +| Properties | Description | Type | +| --- | --- | --- | +| `company_id` | Filter by company ID | number | null | +| `email` | Filter by requester email | string | null | +| `filter` | Filter name (new_and_my_open, watching, spam, deleted) | string | null | +| `include` | Additional resources to include (description, requester, company, stats) | string | null | +| `page` | Page number for pagination (starts from 1) | number | null | +| `per_page` | Number of tickets per page (max 100) | number | null | +| `requester_id` | Filter by requester ID | number | null | +| `updated_since` | Filter tickets updated since this timestamp (ISO 8601) | string | null | + +## `freshdesk_tickets_reply` + +Add a public reply to a ticket conversation. The reply will be visible to the customer and will update the ticket status if specified. + +| Properties | Description | Type | +| --- | --- | --- | +| `bcc_emails` | Array of email addresses to BCC on the reply | `array` | null | +| `body` | HTML content of the reply | string | +| `cc_emails` | Array of email addresses to CC on the reply | `array` | null | +| `from_email` | Email address to send the reply from | string | null | +| `ticket_id` | ID of the ticket to reply to | number | +| `user_id` | ID of the agent sending the reply | number | null | diff --git a/plugins/agentkit/references/agent-connectors/github.md b/plugins/agentkit/references/agent-connectors/github.md new file mode 100644 index 0000000..c9cf234 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/github.md @@ -0,0 +1,137 @@ +GitHub is a cloud-based Git repository hosting service that allows developers to store, manage, and track changes to their code. + +Supports authentication: OAuth 2.0 + +## Table of Contents + +- [Tool list](#tool-list) + +--- + +## Tool list + +## `github_file_contents_get` + +Get the contents of a file or directory from a GitHub repository. Returns Base64 encoded content for files. + +| Properties | Description | Type | +| --- | --- | --- | +| `owner` | The account owner of the repository | string | +| `path` | The content path (file or directory path in the repository) | string | +| `ref` | The name of the commit/branch/tag | string | null | +| `repo` | The name of the repository | string | + +## `github_file_create_update` + +Create a new file or update an existing file in a GitHub repository. Content must be Base64 encoded. Requires SHA when updating existing files. + +| Properties | Description | Type | +| --- | --- | --- | +| `author` | Author information object with name and email | `object` | null | +| `branch` | The branch name | string | null | +| `committer` | Committer information object with name and email | `object` | null | +| `content` | The new file content (Base64 encoded) | string | +| `message` | The commit message for this change | string | +| `owner` | The account owner of the repository | string | +| `path` | The file path in the repository | string | +| `repo` | The name of the repository | string | +| `sha` | The blob SHA of the file being replaced (required when updating existing files) | string | null | + +## `github_issue_create` + +Create a new issue in a repository. Requires push access to set assignees, milestones, and labels. + +| Properties | Description | Type | +| --- | --- | --- | +| `assignees` | GitHub usernames to assign to the issue | `array` | null | +| `body` | The contents of the issue | string | null | +| `labels` | Labels to associate with the issue | `array` | null | +| `milestone` | Milestone number to associate with the issue | number | null | +| `owner` | The account owner of the repository | string | +| `repo` | The name of the repository | string | +| `title` | The title of the issue | string | +| `type` | The name of the issue type | string | null | + +## `github_issues_list` + +List issues in a repository. Both issues and pull requests are returned as issues in the GitHub API. + +| Properties | Description | Type | +| --- | --- | --- | +| `assignee` | Filter by assigned user | string | null | +| `creator` | Filter by issue creator | string | null | +| `direction` | Sort order | string | null | +| `labels` | Filter by comma-separated list of label names | string | null | +| `milestone` | Filter by milestone number or state | string | null | +| `owner` | The account owner of the repository | string | +| `page` | Page number of results to fetch | number | null | +| `per_page` | Number of results per page (max 100) | number | null | +| `repo` | The name of the repository | string | +| `since` | Show issues updated after this timestamp (ISO 8601 format) | string | null | +| `sort` | Property to sort issues by | string | null | +| `state` | Filter by issue state | string | null | + +## `github_public_repos_list` + +List public repositories for a specified user. Does not require authentication. + +| Properties | Description | Type | +| --- | --- | --- | +| `direction` | Sort order | string | null | +| `page` | Page number of results to fetch | number | null | +| `per_page` | Number of results per page (max 100) | number | null | +| `sort` | Property to sort repositories by | string | null | +| `type` | Filter repositories by type | string | null | +| `username` | The GitHub username to list repositories for | string | + +## `github_pull_request_create` + +Create a new pull request in a repository. Requires write access to the head branch. + +| Properties | Description | Type | +| --- | --- | --- | +| `base` | The name of the branch you want the changes pulled into | string | +| `body` | The contents of the pull request description | string | null | +| `draft` | Indicates whether the pull request is a draft | boolean | null | +| `head` | The name of the branch where your changes are implemented (format: user:branch) | string | +| `maintainer_can_modify` | Indicates whether maintainers can modify the pull request | boolean | null | +| `owner` | The account owner of the repository | string | +| `repo` | The name of the repository | string | +| `title` | The title of the pull request | string | null | + +## `github_pull_requests_list` + +List pull requests in a repository with optional filtering by state, head, and base branches. + +| Properties | Description | Type | +| --- | --- | --- | +| `base` | Filter by base branch name | string | null | +| `direction` | Sort order | string | null | +| `head` | Filter by head branch (format: user:ref-name) | string | null | +| `owner` | The account owner of the repository | string | +| `page` | Page number of results to fetch | number | null | +| `per_page` | Number of results per page (max 100) | number | null | +| `repo` | The name of the repository | string | +| `sort` | Property to sort pull requests by | string | null | +| `state` | Filter by pull request state | string | null | + +## `github_repo_get` + +Get detailed information about a GitHub repository including metadata, settings, and statistics. + +| Properties | Description | Type | +| --- | --- | --- | +| `owner` | The account owner of the repository (case-insensitive) | string | +| `repo` | The name of the repository without the .git extension (case-insensitive) | string | + +## `github_user_repos_list` + +List repositories for the authenticated user. Requires authentication. + +| Properties | Description | Type | +| --- | --- | --- | +| `direction` | Sort order | string | null | +| `page` | Page number of results to fetch | number | null | +| `per_page` | Number of results per page (max 100) | number | null | +| `sort` | Property to sort repositories by | string | null | +| `type` | Filter repositories by type | string | null | diff --git a/plugins/agentkit/references/agent-connectors/gmail.md b/plugins/agentkit/references/agent-connectors/gmail.md new file mode 100644 index 0000000..d668c75 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/gmail.md @@ -0,0 +1,79 @@ +Gmail is Google's cloud based email service that allows you to access your messages from any computer or device with just a web browser. + +Supports authentication: OAuth 2.0 + +## Tool list + +## `gmail_fetch_mails` + +Fetch emails from a connected Gmail account using search filters. Requires a valid Gmail OAuth2 connection. + +| Properties | Description | Type | +| --- | --- | --- | +| `format` | Format of the returned message. | string | null | +| `include_spam_trash` | Whether to fetch emails from spam and trash folders | boolean | null | +| `label_ids` | Gmail label IDs to filter messages | `array` | null | +| `max_results` | Maximum number of emails to fetch | integer | null | +| `page_token` | Page token for pagination | string | null | +| `query` | Search query string using Gmail's search syntax (e.g., 'is:unread from:user@example.com') | string | null | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | + +## `gmail_get_attachment_by_id` + +Retrieve a specific attachment from a Gmail message using the message ID and attachment ID. + +| Properties | Description | Type | +| --- | --- | --- | +| `attachment_id` | Unique Gmail attachment ID | string | +| `file_name` | Preferred filename to use when saving/returning the attachment | string | null | +| `message_id` | Unique Gmail message ID that contains the attachment | string | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | + +## `gmail_get_contacts` + +Fetch a list of contacts from the connected Gmail account. Supports pagination and field filtering. + +| Properties | Description | Type | +| --- | --- | --- | +| `max_results` | Maximum number of contacts to fetch | integer | null | +| `page_token` | Token to retrieve the next page of results | string | null | +| `person_fields` | Fields to include for each person | `array` | null | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | + +## `gmail_get_message_by_id` + +Retrieve a specific Gmail message using its message ID. Optionally control the format of the returned data. + +| Properties | Description | Type | +| --- | --- | --- | +| `format` | Format of the returned message. | string | null | +| `message_id` | Unique Gmail message ID | string | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | + +## `gmail_list_drafts` + +List draft emails from a connected Gmail account. Requires a valid Gmail OAuth2 connection. + +| Properties | Description | Type | +| --- | --- | --- | +| `max_results` | Maximum number of drafts to fetch | integer | null | +| `page_token` | Page token for pagination | string | null | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | + +## `gmail_search_people` + +Search people or contacts in the connected Google account using a query. Requires a valid Google OAuth2 connection with People API scopes. + +| Properties | Description | Type | +| --- | --- | --- | +| `other_contacts` | Whether to include people not in the user's contacts (from 'Other Contacts'). | boolean | null | +| `page_size` | Maximum number of people to return. | integer | null | +| `person_fields` | Fields to retrieve for each person. | `array` | null | +| `query` | Text query to search people (e.g., name, email address). | string | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | diff --git a/plugins/agentkit/references/agent-connectors/gong.md b/plugins/agentkit/references/agent-connectors/gong.md new file mode 100644 index 0000000..26a36e3 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/gong.md @@ -0,0 +1,3 @@ +Connect with Gong to sync calls, transcripts, insights, coaching and CRM activity + +Supports authentication: OAuth 2.0 , Api Key diff --git a/plugins/agentkit/references/agent-connectors/google_ads.md b/plugins/agentkit/references/agent-connectors/google_ads.md new file mode 100644 index 0000000..9718639 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/google_ads.md @@ -0,0 +1,3 @@ +Connect to Google Ads to manage advertising campaigns, analyze performance metrics, and optimize ad spending across Google's advertising platform + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_docs.md b/plugins/agentkit/references/agent-connectors/google_docs.md new file mode 100644 index 0000000..e790d5d --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/google_docs.md @@ -0,0 +1,3 @@ +Connect to Google Docs. Create, edit, and collaborate on documents + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_drive.md b/plugins/agentkit/references/agent-connectors/google_drive.md new file mode 100644 index 0000000..ef6120e --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/google_drive.md @@ -0,0 +1,3 @@ +Connect to Google Drive. Manage files, folders, and sharing permissions + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_forms.md b/plugins/agentkit/references/agent-connectors/google_forms.md new file mode 100644 index 0000000..fde9e1d --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/google_forms.md @@ -0,0 +1,3 @@ +Connect to Google Forms. Create, view, and manage forms and responses securely + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_meets.md b/plugins/agentkit/references/agent-connectors/google_meets.md new file mode 100644 index 0000000..814ff7b --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/google_meets.md @@ -0,0 +1,3 @@ +Connect to Google Meet. Create and manage video meetings with powerful collaboration features + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_sheets.md b/plugins/agentkit/references/agent-connectors/google_sheets.md new file mode 100644 index 0000000..8df9a54 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/google_sheets.md @@ -0,0 +1,3 @@ +Connect to Google Sheets. Create, edit, and analyze spreadsheets with powerful data management capabilities + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_slides.md b/plugins/agentkit/references/agent-connectors/google_slides.md new file mode 100644 index 0000000..242ef37 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/google_slides.md @@ -0,0 +1,32 @@ +Connect to Google Slides to create, read, and modify presentations programmatically. + +Supports authentication: OAuth 2.0 + +## Table of Contents + +- [Tool list](#tool-list) + +--- + +## Tool list + +## `googleslides_create_presentation` + +Create a new Google Slides presentation with an optional title. + +| Properties | Description | Type | +| --- | --- | --- | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `title` | Title of the new presentation | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | + +## `googleslides_read_presentation` + +Read the complete structure and content of a Google Slides presentation including slides, text, images, shapes, and metadata. + +| Properties | Description | Type | +| --- | --- | --- | +| `fields` | Fields to include in the response | string | null | +| `presentation_id` | The ID of the Google Slides presentation to read | string | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | diff --git a/plugins/agentkit/references/agent-connectors/googlecalendar.md b/plugins/agentkit/references/agent-connectors/googlecalendar.md new file mode 100644 index 0000000..8168441 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/googlecalendar.md @@ -0,0 +1,128 @@ +Google Calendar is Google's cloud-based calendar service that allows you to manage your events, appointments, and schedules from any computer or device with just a web browser. + +Supports authentication: OAuth 2.0 + +## Table of Contents + +- [Tool list](#tool-list) + +--- + +## Tool list + +## `googlecalendar_create_event` + +Create a new event in a connected Google Calendar account. Supports meeting links, recurrence, attendees, and more. + +| Properties | Description | Type | +| --- | --- | --- | +| `attendees_emails` | Attendee email addresses | `array` | null | +| `calendar_id` | Calendar ID to create the event in | string | null | +| `create_meeting_room` | Generate a Google Meet link for this event | boolean | null | +| `description` | Optional event description | string | null | +| `event_duration_hour` | Duration of event in hours | integer | null | +| `event_duration_minutes` | Duration of event in minutes | integer | null | +| `event_type` | Event type for display purposes | string | null | +| `guests_can_invite_others` | Allow guests to invite others | boolean | null | +| `guests_can_modify` | Allow guests to modify the event | boolean | null | +| `guests_can_see_other_guests` | Allow guests to see each other | boolean | null | +| `location` | Location of the event | string | null | +| `recurrence` | Recurrence rules (iCalendar RRULE format) | `array` | null | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `send_updates` | Send update notifications to attendees | boolean | null | +| `start_datetime` | Event start time in RFC3339 format | string | +| `summary` | Event title/summary | string | +| `timezone` | Timezone for the event (IANA time zone identifier) | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | +| `transparency` | Calendar transparency (free/busy) | string | null | +| `visibility` | Visibility of the event | string | null | + +## `googlecalendar_delete_event` + +Delete an event from a connected Google Calendar account. Requires the calendar ID and event ID. + +| Properties | Description | Type | +| --- | --- | --- | +| `calendar_id` | The ID of the calendar from which the event should be deleted | string | null | +| `event_id` | The ID of the calendar event to delete | string | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | + +## `googlecalendar_get_event_by_id` + +Retrieve a specific calendar event by its ID using optional filtering and list parameters. + +| Properties | Description | Type | +| --- | --- | --- | +| `calendar_id` | The calendar ID to search in | string | null | +| `event_id` | The unique identifier of the calendar event to fetch | string | +| `event_types` | Filter by Google event types | `array` | null | +| `query` | Free text search query | string | null | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `show_deleted` | Include deleted events in results | boolean | null | +| `single_events` | Expand recurring events into instances | boolean | null | +| `time_max` | Upper bound for event start time (RFC3339) | string | null | +| `time_min` | Lower bound for event start time (RFC3339) | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | +| `updated_min` | Filter events updated after this time (RFC3339) | string | null | + +## `googlecalendar_list_calendars` + +List all accessible Google Calendar calendars for the authenticated user. Supports filters and pagination. + +| Properties | Description | Type | +| --- | --- | --- | +| `max_results` | Maximum number of calendars to fetch | integer | null | +| `min_access_role` | Minimum access role to include in results | string | null | +| `page_token` | Token to retrieve the next page of results | string | null | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `show_deleted` | Include deleted calendars in the list | boolean | null | +| `show_hidden` | Include calendars that are hidden from the calendar list | boolean | null | +| `sync_token` | Token to get updates since the last sync | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | + +## `googlecalendar_list_events` + +List events from a connected Google Calendar account with filtering options. Requires a valid Google Calendar OAuth2 connection. + +| Properties | Description | Type | +| --- | --- | --- | +| `calendar_id` | Calendar ID to list events from | string | null | +| `max_results` | Maximum number of events to fetch | integer | null | +| `order_by` | Order of events in the result | string | null | +| `page_token` | Page token for pagination | string | null | +| `query` | Free text search query | string | null | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `single_events` | Expand recurring events into single events | boolean | null | +| `time_max` | Upper bound for event start time (RFC3339 timestamp) | string | null | +| `time_min` | Lower bound for event start time (RFC3339 timestamp) | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | + +## `googlecalendar_update_event` + +Update an existing event in a connected Google Calendar account. Only provided fields will be updated. Supports updating time, attendees, location, meeting links, and more. + +| Properties | Description | Type | +| --- | --- | --- | +| `attendees_emails` | Attendee email addresses | `array` | null | +| `calendar_id` | Calendar ID containing the event | string | +| `create_meeting_room` | Generate a Google Meet link for this event | boolean | null | +| `description` | Optional event description | string | null | +| `end_datetime` | Event end time in RFC3339 format | string | null | +| `event_duration_hour` | Duration of event in hours | integer | null | +| `event_duration_minutes` | Duration of event in minutes | integer | null | +| `event_id` | The ID of the calendar event to update | string | +| `event_type` | Event type for display purposes | string | null | +| `guests_can_invite_others` | Allow guests to invite others | boolean | null | +| `guests_can_modify` | Allow guests to modify the event | boolean | null | +| `guests_can_see_other_guests` | Allow guests to see each other | boolean | null | +| `location` | Location of the event | string | null | +| `recurrence` | Recurrence rules (iCalendar RRULE format) | `array` | null | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `send_updates` | Send update notifications to attendees | boolean | null | +| `start_datetime` | Event start time in RFC3339 format | string | null | +| `summary` | Event title/summary | string | null | +| `timezone` | Timezone for the event (IANA time zone identifier) | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | +| `transparency` | Calendar transparency (free/busy) | string | null | +| `visibility` | Visibility of the event | string | null | diff --git a/plugins/agentkit/references/agent-connectors/hubspot.md b/plugins/agentkit/references/agent-connectors/hubspot.md new file mode 100644 index 0000000..ac44f2c --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/hubspot.md @@ -0,0 +1,143 @@ +Connect to HubSpot CRM. Manage contacts, deals, companies, and marketing automation + +Supports authentication: OAuth 2.0 + +## Table of Contents + +- [Tool list](#tool-list) + +--- + +## Tool list + +## `hubspot_companies_search` + +Search HubSpot companies using full-text search and pagination. Returns matching companies with specified properties. + +| Properties | Description | Type | +| --- | --- | --- | +| `after` | Pagination offset to get results starting from a specific position | string | null | +| `filterGroups` | JSON string containing filter groups for advanced filtering | string | null | +| `limit` | Number of results to return per page (max 100) | number | null | +| `properties` | Comma-separated list of properties to include in the response | string | null | +| `query` | Search term for full-text search across company properties | string | null | + +## `hubspot_company_create` + +Create a new company in HubSpot CRM. Requires a company name as the unique identifier. Supports additional properties like domain, industry, phone, location, and revenue information. + +| Properties | Description | Type | +| --- | --- | --- | +| `annualrevenue` | Annual revenue of the company | number | null | +| `city` | Company city location | string | null | +| `country` | Company country location | string | null | +| `description` | Company description or overview | string | null | +| `domain` | Company website domain | string | null | +| `industry` | Industry type of the company | string | null | +| `name` | Company name (required, serves as primary identifier) | string | +| `numberofemployees` | Number of employees at the company | number | null | +| `phone` | Company phone number | string | null | +| `state` | Company state or region | string | null | + +## `hubspot_company_get` + +Retrieve details of a specific company from HubSpot by company ID. Returns company properties and associated data. + +| Properties | Description | Type | +| --- | --- | --- | +| `company_id` | ID of the company to retrieve | string | +| `properties` | Comma-separated list of properties to include in the response | string | null | + +## `hubspot_contact_create` + +Create a new contact in HubSpot CRM. Requires an email address as the unique identifier. Supports additional properties like name, company, phone, and lifecycle stage. + +| Properties | Description | Type | +| --- | --- | --- | +| `company` | Company name where the contact works | string | null | +| `email` | Primary email address for the contact (required, serves as unique identifier) | string | +| `firstname` | First name of the contact | string | null | +| `hs_lead_status` | Lead status of the contact | string | null | +| `jobtitle` | Job title of the contact | string | null | +| `lastname` | Last name of the contact | string | null | +| `lifecyclestage` | Lifecycle stage of the contact | string | null | +| `phone` | Phone number of the contact | string | null | +| `website` | Personal or company website URL | string | null | + +## `hubspot_contact_get` + +Retrieve details of a specific contact from HubSpot by contact ID. Returns contact properties and associated data. + +| Properties | Description | Type | +| --- | --- | --- | +| `contact_id` | ID of the contact to retrieve | string | +| `properties` | Comma-separated list of properties to include in the response | string | null | + +## `hubspot_contact_update` + +Update an existing contact in HubSpot CRM by contact ID. Allows updating contact properties like name, email, company, phone, and lifecycle stage. + +| Properties | Description | Type | +| --- | --- | --- | +| `contact_id` | ID of the contact to update | string | +| `props` | Object containing properties like first name, last name, email, company, phone, and job title to update all these should be provided inside props as a JSON object, this is required | `object` | null | + +## `hubspot_contacts_list` + +Retrieve a list of contacts from HubSpot with filtering and pagination. Returns contact properties and supports pagination through cursor-based navigation. + +| Properties | Description | Type | +| --- | --- | --- | +| `after` | Pagination cursor to get the next set of results | string | null | +| `archived` | Whether to include archived contacts in the results | boolean | null | +| `limit` | Number of results to return per page (max 100) | number | null | +| `properties` | Comma-separated list of properties to include in the response | string | null | + +## `hubspot_contacts_search` + +Search HubSpot contacts using full-text search and pagination. Returns matching contacts with specified properties. + +| Properties | Description | Type | +| --- | --- | --- | +| `after` | Pagination offset to get results starting from a specific position | string | null | +| `filterGroups` | JSON string containing filter groups for advanced filtering | string | null | +| `limit` | Number of results to return per page (max 100) | number | null | +| `properties` | Comma-separated list of properties to include in the response | string | null | +| `query` | Search term for full-text search across contact properties | string | null | + +## `hubspot_deal_create` + +Create a new deal in HubSpot CRM. Requires dealname, amount, and dealstage. Supports additional properties like pipeline, close date, and deal type. + +| Properties | Description | Type | +| --- | --- | --- | +| `amount` | Deal amount/value (required) | number | +| `closedate` | Expected close date (YYYY-MM-DD format) | string | null | +| `dealname` | Name of the deal (required) | string | +| `dealstage` | Current stage of the deal (required) | string | +| `dealtype` | Type of deal | string | null | +| `description` | Deal description | string | null | +| `hs_priority` | Deal priority (HIGH, MEDIUM, LOW) | string | null | +| `pipeline` | Deal pipeline | string | null | + +## `hubspot_deal_update` + +Update an existing deal in HubSpot CRM by deal ID. Allows updating deal properties like name, amount, stage, pipeline, close date, and priority. + +| Properties | Description | Type | +| --- | --- | --- | +| `deal_id` | ID of the deal to update | string | +| `good_deal` | Boolean flag indicating if this is a good deal | boolean | null | +| `properties` | Object containing deal properties to update | `object` | + +## `hubspot_deals_search` + +Search HubSpot deals using full-text search and pagination. Returns matching deals with specified properties. + +| Properties | Description | Type | +| --- | --- | --- | +| `after` | Pagination offset to get results starting from a specific position | string | null | +| `filterGroups` | JSON string containing filter groups for advanced filtering | string | null | +| `limit` | Number of results to return per page (max 100) | number | null | +| `properties` | Comma-separated list of properties to include in the response | string | null | +| `query` | Search term for full-text search across deal properties | string | null | diff --git a/plugins/agentkit/references/agent-connectors/intercom.md b/plugins/agentkit/references/agent-connectors/intercom.md new file mode 100644 index 0000000..77ef5d8 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/intercom.md @@ -0,0 +1,3 @@ +Connect to Intercom. Send messages, manage conversations, and interact with users and contacts. + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/jira.md b/plugins/agentkit/references/agent-connectors/jira.md new file mode 100644 index 0000000..1675086 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/jira.md @@ -0,0 +1,3 @@ +Connect to Jira. Manage issues, projects, workflows, and agile development processes + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/linear.md b/plugins/agentkit/references/agent-connectors/linear.md new file mode 100644 index 0000000..9b0f877 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/linear.md @@ -0,0 +1,58 @@ +Connect to Linear. Manage issues, projects, sprints, and development workflows + +Supports authentication: OAuth 2.0 + +## Tool list + +## `linear_graphql_query` + +Execute a custom GraphQL query or mutation against the Linear API. Allows running any valid GraphQL operation with variables support for advanced use cases. + +| Properties | Description | Type | +| --- | --- | --- | +| `query` | The GraphQL query or mutation to execute | string | +| `variables` | Variables to pass to the GraphQL query | `object` | null | + +## `linear_issue_create` + +Create a new issue in Linear using the issueCreate mutation. Requires a team ID and title at minimum. + +| Properties | Description | Type | +| --- | --- | --- | +| `assigneeId` | ID of the user to assign the issue to | string | null | +| `description` | Description of the issue | string | null | +| `estimate` | Story point estimate for the issue | string | null | +| `labelIds` | Array of label IDs to apply to the issue | `array` | null | +| `priority` | Priority level of the issue (1-4, where 1 is urgent) | string | null | +| `projectId` | ID of the project to associate the issue with | string | null | +| `stateId` | ID of the workflow state to set | string | null | +| `teamId` | ID of the team to create the issue in | string | +| `title` | Title of the issue | string | + +## `linear_issue_update` + +Update an existing issue in Linear. You can update title, description, priority, state, and assignee. + +| Properties | Description | Type | +| --- | --- | --- | +| `assigneeId` | ID of the user to assign the issue to | string | null | +| `description` | New description for the issue | string | null | +| `issueId` | ID of the issue to update | string | +| `priority` | Priority level of the issue (1-4, where 1 is urgent) | string | null | +| `stateId` | ID of the workflow state to set | string | null | +| `title` | New title for the issue | string | null | + +## `linear_issues_list` + +List issues in Linear using the issues query with simple filtering and pagination support. + +| Properties | Description | Type | +| --- | --- | --- | +| `after` | Cursor for pagination (returns issues after this cursor) | string | null | +| `assignee` | Filter by assignee email (e.g., 'user@example.com') | string | null | +| `before` | Cursor for pagination (returns issues before this cursor) | string | null | +| `first` | Number of issues to return (pagination) | integer | null | +| `labels` | Filter by label names (array of strings) | `array` | null | +| `priority` | Filter by priority level (1=Urgent, 2=High, 3=Medium, 4=Low) | string | null | +| `project` | Filter by project name (e.g., 'Q4 Goals') | string | null | +| `state` | Filter by state name (e.g., 'In Progress', 'Done') | string | null | diff --git a/plugins/agentkit/references/agent-connectors/microsoft_excel.md b/plugins/agentkit/references/agent-connectors/microsoft_excel.md new file mode 100644 index 0000000..2fa4c19 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/microsoft_excel.md @@ -0,0 +1,3 @@ +Connect to Microsoft Excel. Access, read, and modify spreadsheets stored in OneDrive or SharePoint through Microsoft Graph API. + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/microsoft_teams.md b/plugins/agentkit/references/agent-connectors/microsoft_teams.md new file mode 100644 index 0000000..85790a1 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/microsoft_teams.md @@ -0,0 +1,3 @@ +Connect to Microsoft Teams. Manage messages, channels, meetings, and team collaboration + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/microsoft_word.md b/plugins/agentkit/references/agent-connectors/microsoft_word.md new file mode 100644 index 0000000..7b2d530 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/microsoft_word.md @@ -0,0 +1,3 @@ +Connect to Microsoft Word. Authenticate with your Microsoft account to create, read, and edit Word documents stored in OneDrive or SharePoint through Microsoft Graph API. + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/monday.md b/plugins/agentkit/references/agent-connectors/monday.md new file mode 100644 index 0000000..6cc8ab0 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/monday.md @@ -0,0 +1,3 @@ +Connect to Monday.com. Manage boards, tasks, workflows, teams, and project collaboration + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/notion.md b/plugins/agentkit/references/agent-connectors/notion.md new file mode 100644 index 0000000..22b58d5 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/notion.md @@ -0,0 +1,189 @@ +Connect to Notion workspace. Create, edit pages, manage databases, and collaborate on content + +Supports authentication: OAuth 2.0 + +## Table of Contents + +- [Tool list](#tool-list) + +--- + +## Tool list + +## `notion_comment_create` + +Create a comment in Notion. Provide a comment object with rich_text content and either a parent object (with page_id) for a page-level comment or a discussion_id to reply in an existing thread. + +| Properties | Description | Type | +| --- | --- | --- | +| `comment` | Comment object containing a rich_text array. Example: `{"rich_text":[{"type":"text","text":{"content":"Hello"}}]}` | `object` | +| `discussion_id` | Existing discussion thread ID to reply to. | string | null | +| `notion_version` | Optional override for the Notion-Version header (e.g., 2022-06-28). | string | null | +| `parent` | Parent object for a new top-level comment. Shape: `{"page_id":""}`. | `object` | null | +| `schema_version` | Internal override for schema version. | string | null | +| `tool_version` | Internal override for tool implementation version. | string | null | + +## `notion_comment_retrieve` + +Retrieve a single Notion comment by its `comment_id`. LLM tip: you typically obtain `comment_id` from the response of creating a comment or by first listing comments for a page/block and selecting the desired item’s `id`. + +| Properties | Description | Type | +| --- | --- | --- | +| `comment_id` | The identifier of the comment to retrieve (hyphenated UUID). Obtain it from Create-Comment responses or from a prior List-Comments call. | string | +| `notion_version` | Optional Notion-Version header override (e.g., 2022-06-28). | string | null | +| `schema_version` | Internal override for schema version. | string | null | +| `tool_version` | Internal override for tool implementation version. | string | null | + +## `notion_comments_fetch` + +Fetch comments for a given Notion block. Provide a `block_id` (the target page/block ID, hyphenated UUID). Supports pagination via `start_cursor` and `page_size` (1–100). LLM tip: extract `block_id` from a Notion URL’s trailing 32-char id, then insert hyphens (8-4-4-4-12). + +| Properties | Description | Type | +| --- | --- | --- | +| `block_id` | Target Notion block (or page) ID to fetch comments for. Use a hyphenated UUID. | string | +| `notion_version` | Optional Notion-Version header override (e.g., 2022-06-28). | string | null | +| `page_size` | Maximum number of comments to return (1–100). | integer | null | +| `schema_version` | Internal override for schema version. | string | null | +| `start_cursor` | Cursor to fetch the next page of results. | string | null | +| `tool_version` | Internal override for tool implementation version. | string | null | + +## `notion_data_fetch` + +Fetch data from Notion using the workspace search API (/search). Supports pagination via start_cursor. + +| Properties | Description | Type | +| --- | --- | --- | +| `page_size` | Max number of results to return (1–100) | integer | null | +| `query` | Text query used by /search | string | null | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `start_cursor` | Cursor for pagination; pass the previous response's next_cursor | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | + +## `notion_database_create` + +Create a new database in Notion under a parent page. Provide a parent object with page_id, a database title (rich_text array), and a properties object that defines the database schema (columns). + +| Properties | Description | Type | +| --- | --- | --- | +| `notion_version` | Optional override for the Notion-Version header (e.g., 2022-06-28). | string | null | +| `parent` | Parent object specifying the page under which the database is created. Example: `{"page_id": "2561ab6c-418b-8072-beec-c4779fa811cf"}` | `object` | +| `properties` | Database schema object defining properties (columns). Example: `{"Name": {"title": {}}, "Status": {"select": {"options": [{"name": "Todo"}, {"name": "Doing"}, {"name": "Done"}]}}}` | `object` | +| `schema_version` | Internal override for schema version. | string | null | +| `title` | Database title as a Notion rich_text array. | `array` | +| `tool_version` | Internal override for tool implementation version. | string | null | + +## `notion_database_fetch` + +Retrieve a Notion database’s full definition, including title, properties, and schema. Required: `database_id` (hyphenated UUID). LLM tip: Extract the last 32 characters from a Notion database URL, then insert hyphens (8-4-4-4-12). + +| Properties | Description | Type | +| --- | --- | --- | +| `database_id` | The target database ID in UUID format with hyphens. | string | +| `notion_version` | Optional override for the Notion-Version header. | string | null | +| `schema_version` | Optional schema version override. | string | null | +| `tool_version` | Optional tool version override. | string | null | + +## `notion_database_insert_row` + +Insert a new row (page) into a Notion database. Required: `database_id` (hyphenated UUID) and `properties` (object mapping database column names to Notion **property values). Optional: child_blocks` (content blocks), `icon` (page icon object), and `cover` (page cover object). + +LLM guidance: +- `properties` must use **property values** (not schema). Example: + +```json + { + "title": { "title": [ { "text": { "content": "Task A" } } ] }, + "Status": { "select": { "name": "Todo" } }, + "Due": { "date": { "start": "2025-09-01" } } + } +``` +- Use the **exact property key** as defined in the database (case‑sensitive), or the property id. +- `icon` example (emoji): `{"type":"emoji","emoji":"📝"}` +- `cover` example (external): `{"type":"external","external":{"url":"https://example.com/image.jpg"}}` +- Runtime note: the executor/host should synthesize `parent = {"database_id": database_id}` before sending to Notion. + +| Properties | Description | Type | +| --- | --- | --- | +| `_parent` | Computed by host: `{ "database_id": "" }`. Do not supply manually. | `object` | null | +| `child_blocks` | Optional array of Notion blocks to append as page content (paragraph, heading, to_do, etc.). | `array` | null | +| `cover` | Optional page cover object. Example external: `{"type":"external","external":{"url":"https://example.com/cover.jpg"}}`. | `object` | null | +| `database_id` | Target database ID (hyphenated UUID). | string | +| `icon` | Optional page icon object. Examples: `{"type":"emoji","emoji":"📝"}` or `{"type":"external","external":{"url":"https://..."}}`. | `object` | null | +| `notion_version` | Optional Notion-Version header override (e.g., 2022-06-28). | string | null | +| `properties` | Object mapping **column names (or property ids)** to **property values**. + +️ **CRITICAL: Property Identification Rules:** +- For title fields: ALWAYS use 'title' as the property key (not 'Name' or display names) +- For other properties: Use exact property names from database schema (case-sensitive) +- DO NOT use URL-encoded property IDs with special characters + + **Recommended Workflow:** +1. Call fetch_database first to see exact property names +2. Use 'title' for title-type properties +3. Match other property names exactly as shown in schema + +Example: + +```json +{ + "title": { "title": [ { "text": { "content": "Task A" } } ] }, + "Status": { "select": { "name": "Todo" } }, + "Due": { "date": { "start": "2025-09-01" } } +} +``` | `object` | +| `schema_version` | Optional schema version override. | string | null | +| `tool_version` | Optional tool version override. | string | null | + +## `notion_database_property_retrieve` + +Query a Notion database and return only specific properties by supplying one or more property IDs. Use when you need page rows but want to limit the returned properties to reduce payload. Provide the database_id and an array of filter_properties (each item is a property id like "title") + +| Properties | Description | Type | +| --- | --- | --- | +| `database_id` | Target database ID (hyphenated UUID). | string | +| `property_id` | property ID to filter results by a specific property. get the property id by querying database. | string | null | +| `schema_version` | Optional schema version override. | string | null | +| `tool_version` | Optional tool version override. | string | null | + +## `notion_database_query` + +Query a Notion database for rows (pages). Provide database_id (hyphenated UUID). Optional: page_size, start_cursor for pagination, and sorts (array of sort objects). LLM guidance: extract the last 32 characters from a Notion database URL and insert hyphens (8-4-4-4-12) to form database_id. Sort rules: each sort item MUST include either property OR timestamp (last_edited_time/created_time), not both. + +| Properties | Description | Type | +| --- | --- | --- | +| `database_id` | Target database ID (hyphenated UUID). | string | +| `notion_version` | Optional Notion-Version header override. | string | null | +| `page_size` | Maximum number of rows to return (1–100). | integer | null | +| `schema_version` | Optional schema version override. | string | null | +| `sorts` | Order the results. Each item must include either property or timestamp, plus direction. | `array` | null | +| `start_cursor` | Cursor to fetch the next page of results. | string | null | +| `tool_version` | Optional tool version override. | string | null | + +## `notion_page_create` + +Create a page in Notion either inside a database (as a row) or as a child of a page. Use exactly one parent mode: provide database_id to create a database row (page with properties) OR provide parent_page_id to create a child page. When creating in a database, properties must use Notion property value shapes and the title property key must be "title" (not the display name). Children (content blocks), icon, and cover are optional. The executor should synthesize the Notion parent object from the chosen parent input. + +Target rules: +- Use database_id OR parent_page_id (not both) +- If database_id is provided → properties are required +- If parent_page_id is provided → properties are optional + +| Properties | Description | Type | +| --- | --- | --- | +| `_parent` | Computed by the executor: `{"database_id": "..."}` OR `{"page_id": "..."}` derived from database_id/parent_page_id. | `object` | null | +| `child_blocks` | Optional blocks to add as page content (children). | `array` | null | +| `cover` | Optional page cover object. | `object` | null | +| `database_id` | Create a page as a new row in this database (hyphenated UUID). Extract from the database URL (last 32 chars → hyphenate 8-4-4-4-12). | string | null | +| `icon` | Optional page icon object. | `object` | null | +| `notion_version` | Optional Notion-Version header override. | string | null | +| `parent_page_id` | Create a child page under this page (hyphenated UUID). Extract from the parent page URL. | string | null | +| `properties` | For database rows, supply property values keyed by property name (or id). For title properties, the key must be "title". + +Example (database row): +{ + "title": { "title": [ { "text": { "content": "Task A" } } ] }, + "Status": { "select": { "name": "Todo" } }, + "Due": { "date": { "start": "2025-09-01" } } +} | `object` | null | +| `schema_version` | Optional schema version override. | string | null | +| `tool_version` | Optional tool version override. | string | null | diff --git a/plugins/agentkit/references/agent-connectors/onedrive.md b/plugins/agentkit/references/agent-connectors/onedrive.md new file mode 100644 index 0000000..601c43f --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/onedrive.md @@ -0,0 +1,3 @@ +Connect to OneDrive. Manage files, folders, and cloud storage with Microsoft OneDrive + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/onenote.md b/plugins/agentkit/references/agent-connectors/onenote.md new file mode 100644 index 0000000..47a835b --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/onenote.md @@ -0,0 +1,3 @@ +Connect to Microsoft OneNote. Access, create, and manage notebooks, sections, and pages stored in OneDrive or SharePoint through Microsoft Graph API. + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/outlook.md b/plugins/agentkit/references/agent-connectors/outlook.md new file mode 100644 index 0000000..9dae658 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/outlook.md @@ -0,0 +1,3 @@ +Connect to Microsoft Outlook. Manage emails, calendar events, contacts, and tasks + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/salesforce.md b/plugins/agentkit/references/agent-connectors/salesforce.md new file mode 100644 index 0000000..f0441b1 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/salesforce.md @@ -0,0 +1,316 @@ +Connect to Salesforce CRM. Manage leads, opportunities, accounts, and customer relationships + +Supports authentication: OAuth 2.0 + +## Table of Contents + +- [Tool list](#tool-list) + +--- + +## Tool list + +## `salesforce_account_create` + +Create a new Account in Salesforce. Supports standard fields + +| Properties | Description | Type | +| --- | --- | --- | +| `AccountNumber` | Account number for the organization | string | null | +| `AnnualRevenue` | Annual revenue | number | null | +| `BillingCity` | Billing city | string | null | +| `BillingCountry` | Billing country | string | null | +| `BillingPostalCode` | Billing postal code | string | null | +| `BillingState` | Billing state/province | string | null | +| `BillingStreet` | Billing street | string | null | +| `Description` | Description | string | null | +| `Industry` | Industry | string | null | +| `Name` | Account Name | string | +| `NumberOfEmployees` | Number of employees | integer | null | +| `OwnerId` | Record owner (User/Queue Id) | string | null | +| `Phone` | Main phone number | string | null | +| `RecordTypeId` | Record Type Id | string | null | +| `Website` | Website URL | string | null | + +## `salesforce_account_delete` + +Delete an existing Account from Salesforce by account ID. This is a destructive operation that permanently removes the account record. + +| Properties | Description | Type | +| --- | --- | --- | +| `account_id` | ID of the account to delete | string | + +## `salesforce_account_get` + +Retrieve details of a specific account from Salesforce by account ID. Returns account properties and associated data. + +| Properties | Description | Type | +| --- | --- | --- | +| `account_id` | ID of the account to retrieve | string | +| `fields` | Comma-separated list of fields to include in the response | string | null | + +## `salesforce_account_update` + +Update an existing Account in Salesforce by account ID. Allows updating account properties like name, phone, website, industry, billing information, and more. + +| Properties | Description | Type | +| --- | --- | --- | +| `AccountNumber` | Account number for the organization | string | null | +| `AccountSource` | Lead source for this account | string | null | +| `AnnualRevenue` | Annual revenue | number | null | +| `BillingCity` | Billing city | string | null | +| `BillingCountry` | Billing country | string | null | +| `BillingGeocodeAccuracy` | Billing geocode accuracy | string | null | +| `BillingLatitude` | Billing address latitude | number | null | +| `BillingLongitude` | Billing address longitude | number | null | +| `BillingPostalCode` | Billing postal code | string | null | +| `BillingState` | Billing state/province | string | null | +| `BillingStreet` | Billing street | string | null | +| `CleanStatus` | Data.com clean status | string | null | +| `Description` | Description | string | null | +| `DunsNumber` | D-U-N-S Number | string | null | +| `Fax` | Fax number | string | null | +| `Industry` | Industry | string | null | +| `Jigsaw` | Data.com key | string | null | +| `JigsawCompanyId` | Jigsaw company ID | string | null | +| `NaicsCode` | NAICS code | string | null | +| `NaicsDesc` | NAICS description | string | null | +| `Name` | Account Name | string | null | +| `NumberOfEmployees` | Number of employees | integer | null | +| `OwnerId` | Record owner (User/Queue Id) | string | null | +| `Ownership` | Ownership type | string | null | +| `ParentId` | Parent Account Id | string | null | +| `Phone` | Main phone number | string | null | +| `Rating` | Account rating | string | null | +| `RecordTypeId` | Record Type Id | string | null | +| `ShippingCity` | Shipping city | string | null | +| `ShippingCountry` | Shipping country | string | null | +| `ShippingGeocodeAccuracy` | Shipping geocode accuracy | string | null | +| `ShippingLatitude` | Shipping address latitude | number | null | +| `ShippingLongitude` | Shipping address longitude | number | null | +| `ShippingPostalCode` | Shipping postal code | string | null | +| `ShippingState` | Shipping state/province | string | null | +| `ShippingStreet` | Shipping street | string | null | +| `Sic` | SIC code | string | null | +| `SicDesc` | SIC description | string | null | +| `Site` | Account site or location | string | null | +| `TickerSymbol` | Stock ticker symbol | string | null | +| `Tradestyle` | Trade style name | string | null | +| `Type` | Account type | string | null | +| `Website` | Website URL | string | null | +| `YearStarted` | Year the company started | string | null | +| `account_id` | ID of the account to update | string | + +## `salesforce_accounts_list` + +Retrieve a list of accounts from Salesforce using a pre-built SOQL query. Returns basic account information. + +| Properties | Description | Type | +| --- | --- | --- | +| `limit` | Number of results to return per page | number | + +## `salesforce_composite` + +Execute multiple Salesforce REST API requests in a single call using the Composite API. Allows for efficient batch operations and related data retrieval. + +| Properties | Description | Type | +| --- | --- | --- | +| `composite_request` | JSON string containing composite request with multiple sub-requests | string | + +## `salesforce_contact_create` + +Create a new contact in Salesforce. Allows setting contact properties like name, email, phone, account association, and other standard fields. + +| Properties | Description | Type | +| --- | --- | --- | +| `AccountId` | Salesforce Account Id associated with this contact | string | null | +| `Department` | Department of the contact | string | null | +| `Description` | Free-form description | string | null | +| `Email` | Email address of the contact | string | null | +| `FirstName` | First name of the contact | string | null | +| `LastName` | Last name of the contact (required) | string | +| `LeadSource` | Lead source for the contact | string | null | +| `MailingCity` | Mailing city | string | null | +| `MailingCountry` | Mailing country | string | null | +| `MailingPostalCode` | Mailing postal code | string | null | +| `MailingState` | Mailing state/province | string | null | +| `MailingStreet` | Mailing street | string | null | +| `MobilePhone` | Mobile phone of the contact | string | null | +| `Phone` | Phone number of the contact | string | null | +| `Title` | Job title of the contact | string | null | + +## `salesforce_contact_get` + +Retrieve details of a specific contact from Salesforce by contact ID. Returns contact properties and associated data. + +| Properties | Description | Type | +| --- | --- | --- | +| `contact_id` | ID of the contact to retrieve | string | +| `fields` | Comma-separated list of fields to include in the response | string | null | + +## `salesforce_dashboard_metadata_get` + +Retrieve metadata for a Salesforce dashboard, including dashboard components, filters, layout, and the running user. + +| Properties | Description | Type | +| --- | --- | --- | +| `dashboard_id` | The unique ID of the Salesforce dashboard | string | + +## `salesforce_global_describe` + +Retrieve metadata about all available SObjects in the Salesforce organization. Returns list of all objects with basic information. + +## `salesforce_limits_get` + +Retrieve organization limits information from Salesforce. Returns API usage limits, data storage limits, and other organizational constraints. + +## `salesforce_object_describe` + +Retrieve detailed metadata about a specific SObject in Salesforce. Returns fields, relationships, and other object metadata. + +| Properties | Description | Type | +| --- | --- | --- | +| `sobject` | SObject API name to describe | string | + +## `salesforce_opportunities_list` + +Retrieve a list of opportunities from Salesforce using a pre-built SOQL query. Returns basic opportunity information. + +| Properties | Description | Type | +| --- | --- | --- | +| `limit` | Number of results to return per page | number | null | + +## `salesforce_opportunity_create` + +Create a new opportunity in Salesforce. Allows setting opportunity properties like name, amount, stage, close date, and account association. + +| Properties | Description | Type | +| --- | --- | --- | +| `AccountId` | Associated Account Id | string | null | +| `Amount` | Opportunity amount | number | null | +| `CampaignId` | Related Campaign Id | string | null | +| `CloseDate` | Expected close date (YYYY-MM-DD, required) | string | +| `Custom_Field__c` | Example custom field (replace with your org’s custom field API name) | string | null | +| `Description` | Opportunity description | string | null | +| `ForecastCategoryName` | Forecast category name | string | null | +| `LeadSource` | Lead source | string | null | +| `Name` | Opportunity name (required) | string | +| `NextStep` | Next step in the sales process | string | null | +| `OwnerId` | Record owner (User/Queue Id) | string | null | +| `PricebookId` | Associated Price Book Id | string | null | +| `Probability` | Probability percentage (0–100) | number | null | +| `RecordTypeId` | Record Type Id for Opportunity | string | null | +| `StageName` | Current sales stage (required) | string | +| `Type` | Opportunity type | string | null | + +## `salesforce_opportunity_get` + +Retrieve details of a specific opportunity from Salesforce by opportunity ID. Returns opportunity properties and associated data. + +| Properties | Description | Type | +| --- | --- | --- | +| `fields` | Comma-separated list of fields to include in the response | string | null | +| `opportunity_id` | ID of the opportunity to retrieve | string | + +## `salesforce_opportunity_update` + +Update an existing opportunity in Salesforce by opportunity ID. Allows updating opportunity properties like name, amount, stage, and close date. + +| Properties | Description | Type | +| --- | --- | --- | +| `AccountId` | Associated Account Id | string | null | +| `Amount` | Opportunity amount | number | null | +| `CampaignId` | Related Campaign Id | string | null | +| `CloseDate` | Expected close date (YYYY-MM-DD) | string | null | +| `Description` | Opportunity description | string | null | +| `ForecastCategoryName` | Forecast category name | string | null | +| `LeadSource` | Lead source | string | null | +| `Name` | Opportunity name | string | null | +| `NextStep` | Next step in the sales process | string | null | +| `OwnerId` | Record owner (User/Queue Id) | string | null | +| `Pricebook2Id` | Associated Price Book Id | string | null | +| `Probability` | Probability percentage (0–100) | number | null | +| `RecordTypeId` | Record Type Id for Opportunity | string | null | +| `StageName` | Current sales stage | string | null | +| `Type` | Opportunity type | string | null | +| `opportunity_id` | ID of the opportunity to update | string | + +## `salesforce_query_soql` + +Execute SOQL queries against Salesforce data. Supports complex queries with joins, filters, and aggregations. + +| Properties | Description | Type | +| --- | --- | --- | +| `query` | SOQL query string to execute | string | + +## `salesforce_report_metadata_get` + +Retrieve report, report type, and related metadata for a Salesforce report. Returns information about report structure, fields, groupings, and configuration. + +| Properties | Description | Type | +| --- | --- | --- | +| `report_id` | The unique ID of the Salesforce report | string | + +## `salesforce_search_parameterized` + +Execute parameterized searches against Salesforce data. Provides simplified search interface with predefined parameters. + +| Properties | Description | Type | +| --- | --- | --- | +| `fields` | Comma-separated list of fields to return | string | null | +| `search_text` | Text to search for | string | +| `sobject` | SObject type to search in | string | + +## `salesforce_search_sosl` + +Execute SOSL searches against Salesforce data. Performs full-text search across multiple objects and fields. + +| Properties | Description | Type | +| --- | --- | --- | +| `search_query` | SOSL search query string to execute | string | + +## `salesforce_sobject_create` + +Create a new record for any Salesforce SObject type (Account, Contact, Lead, Opportunity, custom objects, etc.). Provide the object type and fields as a dynamic object. + +| Properties | Description | Type | +| --- | --- | --- | +| `fields` | Object containing field names and values to set on the new record | `object` | +| `sobject_type` | The Salesforce SObject API name (e.g., Account, Contact, Lead, CustomObject__c) | string | + +## `salesforce_sobject_delete` + +Delete a record from any Salesforce SObject type by ID. This is a destructive operation that permanently removes the record. + +| Properties | Description | Type | +| --- | --- | --- | +| `record_id` | ID of the record to delete | string | +| `sobject_type` | The Salesforce SObject API name (e.g., Account, Contact, Lead, CustomObject__c) | string | + +## `salesforce_sobject_get` + +Retrieve a record from any Salesforce SObject type by ID. Optionally specify which fields to return. + +| Properties | Description | Type | +| --- | --- | --- | +| `fields` | Comma-separated list of fields to include in the response | string | null | +| `record_id` | ID of the record to retrieve | string | +| `sobject_type` | The Salesforce SObject API name (e.g., Account, Contact, Lead, CustomObject__c) | string | + +## `salesforce_sobject_update` + +Update an existing record for any Salesforce SObject type by ID. Only the fields provided will be updated. + +| Properties | Description | Type | +| --- | --- | --- | +| `fields` | Object containing field names and values to update on the record | `object` | +| `record_id` | ID of the record to update | string | +| `sobject_type` | The Salesforce SObject API name (e.g., Account, Contact, Lead, CustomObject__c) | string | + +## `salesforce_soql_execute` + +Execute custom SOQL queries against Salesforce data. Supports complex queries with joins, filters, aggregations, and custom field selection. + +| Properties | Description | Type | +| --- | --- | --- | +| `soql_query` | SOQL query string to execute | string | diff --git a/plugins/agentkit/references/agent-connectors/servicenow.md b/plugins/agentkit/references/agent-connectors/servicenow.md new file mode 100644 index 0000000..b1dc3b0 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/servicenow.md @@ -0,0 +1,3 @@ +Connect to ServiceNow. Manage incidents, service requests, CMDB, and IT service management workflows + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/sharepoint.md b/plugins/agentkit/references/agent-connectors/sharepoint.md new file mode 100644 index 0000000..c2d88ae --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/sharepoint.md @@ -0,0 +1,3 @@ +Connect to SharePoint. Manage sites, documents, lists, and collaborative content + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/slack.md b/plugins/agentkit/references/agent-connectors/slack.md new file mode 100644 index 0000000..8a52cba --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/slack.md @@ -0,0 +1,197 @@ +Connect to Slack workspace. Send Messages as Bots or on behalf of users + +Supports authentication: OAuth 2.0 + +## Table of Contents + +- [Tool list](#tool-list) + +--- + +## Tool list + +## `slack_add_reaction` + +Add an emoji reaction to a message in Slack. Requires a valid Slack OAuth2 connection with reactions:write scope. + +| Properties | Description | Type | +| --- | --- | --- | +| `channel` | Channel ID or channel name where the message exists | string | +| `name` | Emoji name to react with (without colons) | string | +| `timestamp` | Timestamp of the message to add reaction to | string | + +## `slack_create_channel` + +Creates a new public or private channel in a Slack workspace. Requires a valid Slack OAuth2 connection with channels:manage scope for public channels or groups:write scope for private channels. + +| Properties | Description | Type | +| --- | --- | --- | +| `is_private` | Create a private channel instead of public | boolean | null | +| `name` | Name of the channel to create (without # prefix) | string | +| `team_id` | Encoded team ID to create channel in (if using org tokens) | string | null | + +## `slack_delete_message` + +Deletes a message from a Slack channel or direct message. Requires a valid Slack OAuth2 connection with chat:write scope. + +| Properties | Description | Type | +| --- | --- | --- | +| `channel` | Channel ID, channel name (#general), or user ID for DM where the message was sent | string | +| `ts` | Timestamp of the message to delete | string | + +## `slack_fetch_conversation_history` + +Fetches conversation history from a Slack channel or direct message with pagination support. Requires a valid Slack OAuth2 connection with channels:history scope. + +| Properties | Description | Type | +| --- | --- | --- | +| `channel` | Channel ID, channel name (#general), or user ID for DM | string | +| `cursor` | Paginate through collections by cursor for pagination | string | null | +| `latest` | End of time range of messages to include in results | string | null | +| `limit` | Number of messages to return (1-1000, default 100) | integer | null | +| `oldest` | Start of time range of messages to include in results | string | null | + +## `slack_get_conversation_info` + +Retrieve information about a Slack channel, including metadata, settings, and member count. Requires a valid Slack OAuth2 connection with channels:read scope. + +| Properties | Description | Type | +| --- | --- | --- | +| `channel` | Channel ID, channel name (#general), or user ID for DM | string | +| `include_locale` | Set to true to include the locale for this conversation | boolean | null | +| `include_num_members` | Set to true to include the member count for the conversation | boolean | null | + +## `slack_get_conversation_replies` + +Retrieve replies to a specific message thread in a Slack channel or direct message. Requires a valid Slack OAuth2 connection with channels:history or groups:history scope. + +| Properties | Description | Type | +| --- | --- | --- | +| `channel` | Channel ID, channel name (#general), or user ID for DM | string | +| `cursor` | Pagination cursor for retrieving next page of results | string | null | +| `inclusive` | Include messages with latest or oldest timestamp in results | boolean | null | +| `latest` | End of time range of messages to include in results | string | null | +| `limit` | Number of messages to return (default 100, max 1000) | integer | null | +| `oldest` | Start of time range of messages to include in results | string | null | +| `ts` | Timestamp of the parent message to get replies for | string | + +## `slack_get_user_info` + +Retrieves detailed information about a specific Slack user, including profile data, status, and workspace information. Requires a valid Slack OAuth2 connection with users:read scope. + +| Properties | Description | Type | +| --- | --- | --- | +| `include_locale` | Set to true to include locale information for the user | boolean | null | +| `user` | User ID to get information about | string | + +## `slack_get_user_presence` + +Gets the current presence status of a Slack user (active, away, etc.). Indicates whether the user is currently online and available. Requires a valid Slack OAuth2 connection with users:read scope. + +| Properties | Description | Type | +| --- | --- | --- | +| `user` | User ID to check presence for | string | + +## `slack_invite_users_to_channel` + +Invites one or more users to a Slack channel. Requires a valid Slack OAuth2 connection with channels:write scope for public channels or groups:write for private channels. + +| Properties | Description | Type | +| --- | --- | --- | +| `channel` | Channel ID or channel name (#general) to invite users to | string | +| `users` | Comma-separated list of user IDs to invite to the channel | string | + +## `slack_join_conversation` + +Joins an existing Slack channel. The authenticated user will become a member of the channel. Requires a valid Slack OAuth2 connection with channels:write scope for public channels. + +| Properties | Description | Type | +| --- | --- | --- | +| `channel` | Channel ID or channel name (#general) to join | string | + +## `slack_leave_conversation` + +Leaves a Slack channel. The authenticated user will be removed from the channel and will no longer receive messages from it. Requires a valid Slack OAuth2 connection with channels:write scope for public channels or groups:write for private channels. + +| Properties | Description | Type | +| --- | --- | --- | +| `channel` | Channel ID or channel name (#general) to leave | string | + +## `slack_list_channels` + +List all public and private channels in a Slack workspace that the authenticated user has access to. Requires a valid Slack OAuth2 connection with channels:read, groups:read, mpim:read, and/or im:read scopes depending on conversation types needed. + +| Properties | Description | Type | +| --- | --- | --- | +| `cursor` | Pagination cursor for retrieving next page of results | string | null | +| `exclude_archived` | Exclude archived channels from the list | boolean | null | +| `limit` | Number of channels to return (default 100, max 1000) | integer | null | +| `team_id` | Encoded team ID to list channels for (optional) | string | null | +| `types` | Mix and match channel types (public_channel, private_channel, mpim, im) | string | null | + +## `slack_list_users` + +Lists all users in a Slack workspace, including information about their status, profile, and presence. Requires a valid Slack OAuth2 connection with users:read scope. + +| Properties | Description | Type | +| --- | --- | --- | +| `cursor` | Pagination cursor for fetching additional pages of users | string | null | +| `include_locale` | Set to true to include locale information for each user | boolean | null | +| `limit` | Number of users to return (1-1000) | number | null | +| `team_id` | Encoded team ID to list users for (if using org tokens) | string | null | + +## `slack_lookup_user_by_email` + +Find a user by their registered email address in a Slack workspace. Requires a valid Slack OAuth2 connection with users:read.email scope. Cannot be used by custom bot users. + +| Properties | Description | Type | +| --- | --- | --- | +| `email` | Email address to search for users by | string | + +## `slack_pin_message` + +Pin a message to a Slack channel. Pinned messages are highlighted and easily accessible to channel members. Requires a valid Slack OAuth2 connection with pins:write scope. + +| Properties | Description | Type | +| --- | --- | --- | +| `channel` | Channel ID or channel name where the message exists | string | +| `timestamp` | Timestamp of the message to pin | string | + +## `slack_send_message` + +Sends a message to a Slack channel or direct message. Requires a valid Slack OAuth2 connection with chat:write scope. + +| Properties | Description | Type | +| --- | --- | --- | +| `attachments` | JSON-encoded array of attachment objects for additional message formatting | string | null | +| `blocks` | JSON-encoded array of Block Kit block elements for rich message formatting | string | null | +| `channel` | Channel ID, channel name (#general), or user ID for DM | string | +| `reply_broadcast` | Used in conjunction with thread_ts to broadcast reply to channel | boolean | null | +| `schema_version` | Optional schema version to use for tool execution | string | null | +| `text` | Message text content | string | +| `thread_ts` | Timestamp of parent message to reply in thread | string | null | +| `tool_version` | Optional tool version to use for execution | string | null | +| `unfurl_links` | Enable or disable link previews | boolean | null | +| `unfurl_media` | Enable or disable media link previews | boolean | null | + +## `slack_set_user_status` + +Set the user's custom status with text and emoji. This appears in their profile and can include an expiration time. Requires a valid Slack OAuth2 connection with users.profile:write scope. + +| Properties | Description | Type | +| --- | --- | --- | +| `status_emoji` | Emoji to display with status (without colons) | string | null | +| `status_expiration` | Unix timestamp when status should expire | integer | null | +| `status_text` | Status text to display | string | null | + +## `slack_update_message` + +Updates/edits a previously sent message in a Slack channel or direct message. Requires a valid Slack OAuth2 connection with chat:write scope. + +| Properties | Description | Type | +| --- | --- | --- | +| `attachments` | JSON-encoded array of attachment objects for additional message formatting | string | null | +| `blocks` | JSON-encoded array of Block Kit block elements for rich message formatting | string | null | +| `channel` | Channel ID, channel name (#general), or user ID for DM where the message was sent | string | +| `text` | New message text content | string | null | +| `ts` | Timestamp of the message to update | string | diff --git a/plugins/agentkit/references/agent-connectors/snowflake.md b/plugins/agentkit/references/agent-connectors/snowflake.md new file mode 100644 index 0000000..12dba3f --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/snowflake.md @@ -0,0 +1,3 @@ +Connect to Snowflake to manage and analyze your data warehouse workloads + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/trello.md b/plugins/agentkit/references/agent-connectors/trello.md new file mode 100644 index 0000000..181e36c --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/trello.md @@ -0,0 +1,3 @@ +Connect to Trello. Manage boards, cards, lists, and team collaboration workflows + +Supports authentication: OAuth 1.0a diff --git a/plugins/agentkit/references/agent-connectors/zendesk.md b/plugins/agentkit/references/agent-connectors/zendesk.md new file mode 100644 index 0000000..f993da1 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/zendesk.md @@ -0,0 +1,3 @@ +Connect to Zendesk. Manage customer support tickets, users, organizations, and help desk operations + +Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/zoom.md b/plugins/agentkit/references/agent-connectors/zoom.md new file mode 100644 index 0000000..9bfdda0 --- /dev/null +++ b/plugins/agentkit/references/agent-connectors/zoom.md @@ -0,0 +1,3 @@ +Connect to Zoom. Schedule meetings, manage recordings, and handle video conferencing workflows + +Supports authentication: OAuth 2.0 From e59be456964bf0b334d8281b83d66a725915b7bc Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 13 May 2026 20:58:42 +0530 Subject: [PATCH 24/53] Remove agent-connectors directory, point to live docs instead MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-connector docs are unmaintainable — live AgentKit metadata and docs.scalekit.com/agentkit/connectors/ are the source of truth. --- plugins/agentkit/README.md | 2 +- .../references/agent-connectors/README.md | 71 ---- .../references/agent-connectors/airtable.md | 3 - .../references/agent-connectors/asana.md | 3 - .../references/agent-connectors/attention.md | 3 - .../references/agent-connectors/bigquery.md | 3 - .../references/agent-connectors/chorus.md | 3 - .../agent-connectors/clari_copilot.md | 3 - .../references/agent-connectors/clickup.md | 3 - .../references/agent-connectors/confluence.md | 3 - .../references/agent-connectors/dropbox.md | 3 - .../references/agent-connectors/fathom.md | 3 - .../references/agent-connectors/freshdesk.md | 149 --------- .../references/agent-connectors/github.md | 137 -------- .../references/agent-connectors/gmail.md | 79 ----- .../references/agent-connectors/gong.md | 3 - .../references/agent-connectors/google_ads.md | 3 - .../agent-connectors/google_docs.md | 3 - .../agent-connectors/google_drive.md | 3 - .../agent-connectors/google_forms.md | 3 - .../agent-connectors/google_meets.md | 3 - .../agent-connectors/google_sheets.md | 3 - .../agent-connectors/google_slides.md | 32 -- .../agent-connectors/googlecalendar.md | 128 ------- .../references/agent-connectors/hubspot.md | 143 -------- .../references/agent-connectors/intercom.md | 3 - .../references/agent-connectors/jira.md | 3 - .../references/agent-connectors/linear.md | 58 ---- .../agent-connectors/microsoft_excel.md | 3 - .../agent-connectors/microsoft_teams.md | 3 - .../agent-connectors/microsoft_word.md | 3 - .../references/agent-connectors/monday.md | 3 - .../references/agent-connectors/notion.md | 189 ----------- .../references/agent-connectors/onedrive.md | 3 - .../references/agent-connectors/onenote.md | 3 - .../references/agent-connectors/outlook.md | 3 - .../references/agent-connectors/salesforce.md | 316 ------------------ .../references/agent-connectors/servicenow.md | 3 - .../references/agent-connectors/sharepoint.md | 3 - .../references/agent-connectors/slack.md | 197 ----------- .../references/agent-connectors/snowflake.md | 3 - .../references/agent-connectors/trello.md | 3 - .../references/agent-connectors/zendesk.md | 3 - .../references/agent-connectors/zoom.md | 3 - 44 files changed, 1 insertion(+), 1596 deletions(-) delete mode 100644 plugins/agentkit/references/agent-connectors/README.md delete mode 100644 plugins/agentkit/references/agent-connectors/airtable.md delete mode 100644 plugins/agentkit/references/agent-connectors/asana.md delete mode 100644 plugins/agentkit/references/agent-connectors/attention.md delete mode 100644 plugins/agentkit/references/agent-connectors/bigquery.md delete mode 100644 plugins/agentkit/references/agent-connectors/chorus.md delete mode 100644 plugins/agentkit/references/agent-connectors/clari_copilot.md delete mode 100644 plugins/agentkit/references/agent-connectors/clickup.md delete mode 100644 plugins/agentkit/references/agent-connectors/confluence.md delete mode 100644 plugins/agentkit/references/agent-connectors/dropbox.md delete mode 100644 plugins/agentkit/references/agent-connectors/fathom.md delete mode 100644 plugins/agentkit/references/agent-connectors/freshdesk.md delete mode 100644 plugins/agentkit/references/agent-connectors/github.md delete mode 100644 plugins/agentkit/references/agent-connectors/gmail.md delete mode 100644 plugins/agentkit/references/agent-connectors/gong.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_ads.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_docs.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_drive.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_forms.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_meets.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_sheets.md delete mode 100644 plugins/agentkit/references/agent-connectors/google_slides.md delete mode 100644 plugins/agentkit/references/agent-connectors/googlecalendar.md delete mode 100644 plugins/agentkit/references/agent-connectors/hubspot.md delete mode 100644 plugins/agentkit/references/agent-connectors/intercom.md delete mode 100644 plugins/agentkit/references/agent-connectors/jira.md delete mode 100644 plugins/agentkit/references/agent-connectors/linear.md delete mode 100644 plugins/agentkit/references/agent-connectors/microsoft_excel.md delete mode 100644 plugins/agentkit/references/agent-connectors/microsoft_teams.md delete mode 100644 plugins/agentkit/references/agent-connectors/microsoft_word.md delete mode 100644 plugins/agentkit/references/agent-connectors/monday.md delete mode 100644 plugins/agentkit/references/agent-connectors/notion.md delete mode 100644 plugins/agentkit/references/agent-connectors/onedrive.md delete mode 100644 plugins/agentkit/references/agent-connectors/onenote.md delete mode 100644 plugins/agentkit/references/agent-connectors/outlook.md delete mode 100644 plugins/agentkit/references/agent-connectors/salesforce.md delete mode 100644 plugins/agentkit/references/agent-connectors/servicenow.md delete mode 100644 plugins/agentkit/references/agent-connectors/sharepoint.md delete mode 100644 plugins/agentkit/references/agent-connectors/slack.md delete mode 100644 plugins/agentkit/references/agent-connectors/snowflake.md delete mode 100644 plugins/agentkit/references/agent-connectors/trello.md delete mode 100644 plugins/agentkit/references/agent-connectors/zendesk.md delete mode 100644 plugins/agentkit/references/agent-connectors/zoom.md diff --git a/plugins/agentkit/README.md b/plugins/agentkit/README.md index 2a20201..4e845ef 100644 --- a/plugins/agentkit/README.md +++ b/plugins/agentkit/README.md @@ -15,7 +15,7 @@ Claude runtime files remain in place as adapters: - `hooks/` - `agents/` -The plugin treats live AgentKit metadata as the source of truth for tool names, `input_schema`, and `output_schema`. Per-connector reference docs in `references/agent-connectors/` provide curated guidance on scopes, quirks, and tool categories — but they are not a guaranteed exhaustive catalog. +The plugin treats live AgentKit metadata as the source of truth for tool names, `input_schema`, and `output_schema`. For per-connector details, see the [AgentKit connectors catalog](https://docs.scalekit.com/agentkit/connectors/). ## Installation ```sh diff --git a/plugins/agentkit/references/agent-connectors/README.md b/plugins/agentkit/references/agent-connectors/README.md deleted file mode 100644 index 8386ae0..0000000 --- a/plugins/agentkit/references/agent-connectors/README.md +++ /dev/null @@ -1,71 +0,0 @@ -# Agent Connectors Reference - -This directory contains documentation for all supported agent connectors in the Scalekit AgentKit platform. - -## Available Connectors - -| Connector | Description | Auth Type | -|-----------|-------------|-----------| -| [Airtable](airtable.md) | Connect to Airtable bases for data management | OAuth 2.0 | -| [Asana](asana.md) | Project management and task tracking | OAuth 2.0 | -| [BigQuery](bigquery.md) | Google BigQuery data warehouse | OAuth 2.0 | -| [ClickUp](clickup.md) | Project management and collaboration | OAuth 2.0 | -| [Confluence](confluence.md) | Atlassian Confluence wiki pages | OAuth 2.0 | -| [Dropbox](dropbox.md) | File storage and sharing | OAuth 2.0 | -| [Fathom](fathom.md) | Website analytics | OAuth 2.0 | -| [Freshdesk](freshdesk.md) | Customer support ticketing | OAuth 2.0 | -| [GitHub](github.md) | Code repository and development tools | OAuth 2.0 | -| [Gmail](gmail.md) | Google Gmail email service | OAuth 2.0 | -| [Google Ads](google_ads.md) | Google advertising platform | OAuth 2.0 | -| [Google Calendar](googlecalendar.md) | Google Calendar events and scheduling | OAuth 2.0 | -| [Google Docs](google_docs.md) | Google Docs document editing | OAuth 2.0 | -| [Google Drive](google_drive.md) | Google Drive file storage | OAuth 2.0 | -| [Google Forms](google_forms.md) | Google Forms survey creation | OAuth 2.0 | -| [Google Meet](google_meets.md) | Google Meet video conferencing | OAuth 2.0 | -| [Google Sheets](google_sheets.md) | Google Sheets spreadsheet editing | OAuth 2.0 | -| [Gong](gong.md) | Sales conversation intelligence | OAuth 2.0 | -| [HubSpot](hubspot.md) | CRM and marketing automation | OAuth 2.0 | -| [Intercom](intercom.md) | Customer messaging platform | OAuth 2.0 | -| [Jira](jira.md) | Atlassian Jira issue tracking | OAuth 2.0 | -| [Linear](linear.md) | Software development issue tracking | OAuth 2.0 | -| [Microsoft Excel](microsoft_excel.md) | Microsoft Excel spreadsheet editing | OAuth 2.0 | -| [Microsoft Teams](microsoft_teams.md) | Microsoft Teams collaboration | OAuth 2.0 | -| [Microsoft Word](microsoft_word.md) | Microsoft Word document editing | OAuth 2.0 | -| [Monday](monday.md) | Work management platform | OAuth 2.0 | -| [Notion](notion.md) | Notion workspace and pages | OAuth 2.0 | -| [OneDrive](onedrive.md) | Microsoft OneDrive file storage | OAuth 2.0 | -| [OneNote](onenote.md) | Microsoft OneNote note-taking | OAuth 2.0 | -| [Outlook](outlook.md) | Microsoft Outlook email | OAuth 2.0 | -| [Salesforce](salesforce.md) | Salesforce CRM platform | OAuth 2.0 | -| [ServiceNow](servicenow.md) | IT service management | OAuth 2.0 | -| [SharePoint](sharepoint.md) | Microsoft SharePoint collaboration | OAuth 2.0 | -| [Slack](slack.md) | Slack messaging and collaboration | OAuth 2.0 | -| [Snowflake](snowflake.md) | Snowflake data warehouse | OAuth 2.0 | -| [Trello](trello.md) | Trello project boards | OAuth 2.0 | -| [Zendesk](zendesk.md) | Customer support platform | OAuth 2.0 | -| [Zoom](zoom.md) | Zoom video conferencing | OAuth 2.0 | -| [Attention](attention.md) | Meeting intelligence and note-taking | API Key | -| [Chorus](chorus.md) | Conversation intelligence for sales | OAuth 2.0 | -| [Clari Copilot](clari_copilot.md) | Revenue intelligence and forecasting | API Key | -| [Google Slides](google_slides.md) | Google Slides presentation editing | OAuth 2.0 | - -## Getting Started - -Each connector documentation includes: - -- Service description and capabilities -- Authentication requirements -- Complete API reference for all available tools -- Parameter specifications and examples -- Usage guidelines and best practices - -## Authentication - -Most connectors use OAuth 2.0 authentication through the AgentKit platform; some use API Key or Basic Auth. You'll need to: - -1. Create a connection for the desired service -2. Configure OAuth credentials in your connection -3. Create connected accounts for your users -4. Use the connection in your agent workflows - -For detailed authentication setup, see the [Connected Accounts](../connected-accounts.md) documentation. \ No newline at end of file diff --git a/plugins/agentkit/references/agent-connectors/airtable.md b/plugins/agentkit/references/agent-connectors/airtable.md deleted file mode 100644 index f3acf0b..0000000 --- a/plugins/agentkit/references/agent-connectors/airtable.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Airtable. Manage databases, tables, records, and collaborate on structured data - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/asana.md b/plugins/agentkit/references/agent-connectors/asana.md deleted file mode 100644 index cea2fc7..0000000 --- a/plugins/agentkit/references/agent-connectors/asana.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Asana. Manage tasks, projects, teams, and workflow automation - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/attention.md b/plugins/agentkit/references/agent-connectors/attention.md deleted file mode 100644 index 9af975e..0000000 --- a/plugins/agentkit/references/agent-connectors/attention.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Attention for AI insights, conversations, teams, and workflows - -Supports authentication: API Key diff --git a/plugins/agentkit/references/agent-connectors/bigquery.md b/plugins/agentkit/references/agent-connectors/bigquery.md deleted file mode 100644 index 64b8a33..0000000 --- a/plugins/agentkit/references/agent-connectors/bigquery.md +++ /dev/null @@ -1,3 +0,0 @@ -BigQuery is Google Cloud’s fully-managed enterprise data warehouse for analytics at scale. - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/chorus.md b/plugins/agentkit/references/agent-connectors/chorus.md deleted file mode 100644 index b57dd23..0000000 --- a/plugins/agentkit/references/agent-connectors/chorus.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Chorus.ai to sync calls, transcripts, conversation intelligence, and analytics. - -Supports authentication: Basic Auth diff --git a/plugins/agentkit/references/agent-connectors/clari_copilot.md b/plugins/agentkit/references/agent-connectors/clari_copilot.md deleted file mode 100644 index 2949e94..0000000 --- a/plugins/agentkit/references/agent-connectors/clari_copilot.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Clari Copilot for sales call transcripts, analytics, call data, and insights. - -Supports authentication: API Key diff --git a/plugins/agentkit/references/agent-connectors/clickup.md b/plugins/agentkit/references/agent-connectors/clickup.md deleted file mode 100644 index dd7d216..0000000 --- a/plugins/agentkit/references/agent-connectors/clickup.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to ClickUp. Manage tasks, projects, workspaces, and team collaboration - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/confluence.md b/plugins/agentkit/references/agent-connectors/confluence.md deleted file mode 100644 index 96d699f..0000000 --- a/plugins/agentkit/references/agent-connectors/confluence.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Confluence. Manage spaces, pages, content, and team collaboration - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/dropbox.md b/plugins/agentkit/references/agent-connectors/dropbox.md deleted file mode 100644 index 1bfe1d3..0000000 --- a/plugins/agentkit/references/agent-connectors/dropbox.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Dropbox. Manage files, folders, sharing, and cloud storage workflows - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/fathom.md b/plugins/agentkit/references/agent-connectors/fathom.md deleted file mode 100644 index 3408c98..0000000 --- a/plugins/agentkit/references/agent-connectors/fathom.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Fathom AI meeting assistant. Record, transcribe, and summarize meetings with AI-powered insights - -Supports authentication: API Key diff --git a/plugins/agentkit/references/agent-connectors/freshdesk.md b/plugins/agentkit/references/agent-connectors/freshdesk.md deleted file mode 100644 index 424a753..0000000 --- a/plugins/agentkit/references/agent-connectors/freshdesk.md +++ /dev/null @@ -1,149 +0,0 @@ -Connect to Freshdesk. Manage tickets, contacts, companies, and customer support workflows - -Supports authentication: Basic Auth - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `freshdesk_agent_create` - -Create a new agent in Freshdesk. Email is required and must be unique. Agent will receive invitation email to set up account. At least one role must be assigned. - -| Properties | Description | Type | -| --- | --- | --- | -| `agent_type` | Type of agent (1=Support Agent, 2=Field Agent, 3=Collaborator) | number | null | -| `email` | Email address of the agent (must be unique) | string | -| `focus_mode` | Focus mode setting for the agent | boolean | null | -| `group_ids` | Array of group IDs to assign the agent to | `array` | null | -| `language` | Language preference of the agent | string | null | -| `name` | Full name of the agent | string | null | -| `occasional` | Whether the agent is occasional (true) or full-time (false) | boolean | null | -| `role_ids` | Array of role IDs to assign to the agent (at least one required) | `array` | -| `signature` | Agent email signature in HTML format | string | null | -| `skill_ids` | Array of skill IDs to assign to the agent | `array` | null | -| `ticket_scope` | Ticket permission level (1=Global Access, 2=Group Access, 3=Restricted Access) | number | -| `time_zone` | Time zone of the agent | string | null | - -## `freshdesk_agent_delete` - -Delete an agent from Freshdesk. This action is irreversible and will remove the agent from the system. The agent will no longer have access to the helpdesk and all associated data will be permanently deleted. - -| Properties | Description | Type | -| --- | --- | --- | -| `agent_id` | ID of the agent to delete | number | - -## `freshdesk_agents_list` - -Retrieve a list of agents from Freshdesk with filtering options. Returns agent details including IDs, contact information, roles, and availability status. Supports pagination with up to 100 agents per page. - -| Properties | Description | Type | -| --- | --- | --- | -| `email` | Filter agents by email address | string | null | -| `mobile` | Filter agents by mobile number | string | null | -| `page` | Page number for pagination (starts from 1) | number | null | -| `per_page` | Number of agents per page (max 100) | number | null | -| `phone` | Filter agents by phone number | string | null | -| `state` | Filter agents by state (fulltime or occasional) | string | null | - -## `freshdesk_contact_create` - -Create a new contact in Freshdesk. Email and name are required. Supports custom fields, company assignment, and contact segmentation. - -| Properties | Description | Type | -| --- | --- | --- | -| `address` | Address of the contact | string | null | -| `company_id` | Company ID to associate with the contact | number | null | -| `custom_fields` | Key-value pairs for custom field values | `object` | null | -| `description` | Description about the contact | string | null | -| `email` | Email address of the contact | string | -| `job_title` | Job title of the contact | string | null | -| `language` | Language preference of the contact | string | null | -| `mobile` | Mobile number of the contact | string | null | -| `name` | Full name of the contact | string | -| `phone` | Phone number of the contact | string | null | -| `tags` | Array of tags to associate with the contact | `array` | null | -| `time_zone` | Time zone of the contact | string | null | - -## `freshdesk_roles_list` - -Retrieve a list of all roles from Freshdesk. Returns role details including IDs, names, descriptions, default status, and timestamps. This endpoint provides information about the different permission levels and access controls available in the Freshdesk system. - -## `freshdesk_ticket_create` - -Create a new ticket in Freshdesk. Requires either requester_id, email, facebook_id, phone, twitter_id, or unique_external_id to identify the requester. - -| Properties | Description | Type | -| --- | --- | --- | -| `cc_emails` | Array of email addresses to be added in CC | `array` | null | -| `custom_fields` | Key-value pairs containing custom field names and values | `object` | null | -| `description` | HTML content of the ticket describing the issue | string | null | -| `email` | Email address of the requester. If no contact exists, will be added as new contact. | string | null | -| `group_id` | ID of the group to which the ticket has been assigned | number | null | -| `name` | Name of the requester | string | null | -| `priority` | Priority of the ticket. 1=Low, 2=Medium, 3=High, 4=Urgent | number | null | -| `requester_id` | User ID of the requester. For existing contacts, can be passed instead of email. | number | null | -| `responder_id` | ID of the agent to whom the ticket has been assigned | number | null | -| `source` | Channel through which ticket was created. 1=Email, 2=Portal, 3=Phone, 7=Chat, 9=Feedback Widget, 10=Outbound Email | number | null | -| `status` | Status of the ticket. 2=Open, 3=Pending, 4=Resolved, 5=Closed | number | null | -| `subject` | Subject of the ticket | string | null | -| `tags` | Array of tags to be associated with the ticket | `array` | null | -| `type` | Helps categorize the ticket according to different kinds of issues | string | null | - -## `freshdesk_ticket_get` - -Retrieve details of a specific ticket by ID. Includes ticket properties, conversations, and metadata. - -| Properties | Description | Type | -| --- | --- | --- | -| `include` | Additional resources to include (stats, requester, company, conversations) | string | null | -| `ticket_id` | ID of the ticket to retrieve | number | - -## `freshdesk_ticket_update` - -Update an existing ticket in Freshdesk. Note: Subject and description of outbound tickets cannot be updated. - -| Properties | Description | Type | -| --- | --- | --- | -| `custom_fields` | Key-value pairs containing custom field names and values | `object` | null | -| `description` | HTML content of the ticket (cannot be updated for outbound tickets) | string | null | -| `group_id` | ID of the group to which the ticket has been assigned | number | null | -| `name` | Name of the requester | string | null | -| `priority` | Priority of the ticket. 1=Low, 2=Medium, 3=High, 4=Urgent | number | null | -| `responder_id` | ID of the agent to whom the ticket has been assigned | number | null | -| `status` | Status of the ticket. 2=Open, 3=Pending, 4=Resolved, 5=Closed | number | null | -| `subject` | Subject of the ticket (cannot be updated for outbound tickets) | string | null | -| `tags` | Array of tags to be associated with the ticket | `array` | null | -| `ticket_id` | ID of the ticket to update | number | - -## `freshdesk_tickets_list` - -Retrieve a list of tickets with filtering and pagination. Supports filtering by status, priority, requester, and more. Returns 30 tickets per page by default. - -| Properties | Description | Type | -| --- | --- | --- | -| `company_id` | Filter by company ID | number | null | -| `email` | Filter by requester email | string | null | -| `filter` | Filter name (new_and_my_open, watching, spam, deleted) | string | null | -| `include` | Additional resources to include (description, requester, company, stats) | string | null | -| `page` | Page number for pagination (starts from 1) | number | null | -| `per_page` | Number of tickets per page (max 100) | number | null | -| `requester_id` | Filter by requester ID | number | null | -| `updated_since` | Filter tickets updated since this timestamp (ISO 8601) | string | null | - -## `freshdesk_tickets_reply` - -Add a public reply to a ticket conversation. The reply will be visible to the customer and will update the ticket status if specified. - -| Properties | Description | Type | -| --- | --- | --- | -| `bcc_emails` | Array of email addresses to BCC on the reply | `array` | null | -| `body` | HTML content of the reply | string | -| `cc_emails` | Array of email addresses to CC on the reply | `array` | null | -| `from_email` | Email address to send the reply from | string | null | -| `ticket_id` | ID of the ticket to reply to | number | -| `user_id` | ID of the agent sending the reply | number | null | diff --git a/plugins/agentkit/references/agent-connectors/github.md b/plugins/agentkit/references/agent-connectors/github.md deleted file mode 100644 index c9cf234..0000000 --- a/plugins/agentkit/references/agent-connectors/github.md +++ /dev/null @@ -1,137 +0,0 @@ -GitHub is a cloud-based Git repository hosting service that allows developers to store, manage, and track changes to their code. - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `github_file_contents_get` - -Get the contents of a file or directory from a GitHub repository. Returns Base64 encoded content for files. - -| Properties | Description | Type | -| --- | --- | --- | -| `owner` | The account owner of the repository | string | -| `path` | The content path (file or directory path in the repository) | string | -| `ref` | The name of the commit/branch/tag | string | null | -| `repo` | The name of the repository | string | - -## `github_file_create_update` - -Create a new file or update an existing file in a GitHub repository. Content must be Base64 encoded. Requires SHA when updating existing files. - -| Properties | Description | Type | -| --- | --- | --- | -| `author` | Author information object with name and email | `object` | null | -| `branch` | The branch name | string | null | -| `committer` | Committer information object with name and email | `object` | null | -| `content` | The new file content (Base64 encoded) | string | -| `message` | The commit message for this change | string | -| `owner` | The account owner of the repository | string | -| `path` | The file path in the repository | string | -| `repo` | The name of the repository | string | -| `sha` | The blob SHA of the file being replaced (required when updating existing files) | string | null | - -## `github_issue_create` - -Create a new issue in a repository. Requires push access to set assignees, milestones, and labels. - -| Properties | Description | Type | -| --- | --- | --- | -| `assignees` | GitHub usernames to assign to the issue | `array` | null | -| `body` | The contents of the issue | string | null | -| `labels` | Labels to associate with the issue | `array` | null | -| `milestone` | Milestone number to associate with the issue | number | null | -| `owner` | The account owner of the repository | string | -| `repo` | The name of the repository | string | -| `title` | The title of the issue | string | -| `type` | The name of the issue type | string | null | - -## `github_issues_list` - -List issues in a repository. Both issues and pull requests are returned as issues in the GitHub API. - -| Properties | Description | Type | -| --- | --- | --- | -| `assignee` | Filter by assigned user | string | null | -| `creator` | Filter by issue creator | string | null | -| `direction` | Sort order | string | null | -| `labels` | Filter by comma-separated list of label names | string | null | -| `milestone` | Filter by milestone number or state | string | null | -| `owner` | The account owner of the repository | string | -| `page` | Page number of results to fetch | number | null | -| `per_page` | Number of results per page (max 100) | number | null | -| `repo` | The name of the repository | string | -| `since` | Show issues updated after this timestamp (ISO 8601 format) | string | null | -| `sort` | Property to sort issues by | string | null | -| `state` | Filter by issue state | string | null | - -## `github_public_repos_list` - -List public repositories for a specified user. Does not require authentication. - -| Properties | Description | Type | -| --- | --- | --- | -| `direction` | Sort order | string | null | -| `page` | Page number of results to fetch | number | null | -| `per_page` | Number of results per page (max 100) | number | null | -| `sort` | Property to sort repositories by | string | null | -| `type` | Filter repositories by type | string | null | -| `username` | The GitHub username to list repositories for | string | - -## `github_pull_request_create` - -Create a new pull request in a repository. Requires write access to the head branch. - -| Properties | Description | Type | -| --- | --- | --- | -| `base` | The name of the branch you want the changes pulled into | string | -| `body` | The contents of the pull request description | string | null | -| `draft` | Indicates whether the pull request is a draft | boolean | null | -| `head` | The name of the branch where your changes are implemented (format: user:branch) | string | -| `maintainer_can_modify` | Indicates whether maintainers can modify the pull request | boolean | null | -| `owner` | The account owner of the repository | string | -| `repo` | The name of the repository | string | -| `title` | The title of the pull request | string | null | - -## `github_pull_requests_list` - -List pull requests in a repository with optional filtering by state, head, and base branches. - -| Properties | Description | Type | -| --- | --- | --- | -| `base` | Filter by base branch name | string | null | -| `direction` | Sort order | string | null | -| `head` | Filter by head branch (format: user:ref-name) | string | null | -| `owner` | The account owner of the repository | string | -| `page` | Page number of results to fetch | number | null | -| `per_page` | Number of results per page (max 100) | number | null | -| `repo` | The name of the repository | string | -| `sort` | Property to sort pull requests by | string | null | -| `state` | Filter by pull request state | string | null | - -## `github_repo_get` - -Get detailed information about a GitHub repository including metadata, settings, and statistics. - -| Properties | Description | Type | -| --- | --- | --- | -| `owner` | The account owner of the repository (case-insensitive) | string | -| `repo` | The name of the repository without the .git extension (case-insensitive) | string | - -## `github_user_repos_list` - -List repositories for the authenticated user. Requires authentication. - -| Properties | Description | Type | -| --- | --- | --- | -| `direction` | Sort order | string | null | -| `page` | Page number of results to fetch | number | null | -| `per_page` | Number of results per page (max 100) | number | null | -| `sort` | Property to sort repositories by | string | null | -| `type` | Filter repositories by type | string | null | diff --git a/plugins/agentkit/references/agent-connectors/gmail.md b/plugins/agentkit/references/agent-connectors/gmail.md deleted file mode 100644 index d668c75..0000000 --- a/plugins/agentkit/references/agent-connectors/gmail.md +++ /dev/null @@ -1,79 +0,0 @@ -Gmail is Google's cloud based email service that allows you to access your messages from any computer or device with just a web browser. - -Supports authentication: OAuth 2.0 - -## Tool list - -## `gmail_fetch_mails` - -Fetch emails from a connected Gmail account using search filters. Requires a valid Gmail OAuth2 connection. - -| Properties | Description | Type | -| --- | --- | --- | -| `format` | Format of the returned message. | string | null | -| `include_spam_trash` | Whether to fetch emails from spam and trash folders | boolean | null | -| `label_ids` | Gmail label IDs to filter messages | `array` | null | -| `max_results` | Maximum number of emails to fetch | integer | null | -| `page_token` | Page token for pagination | string | null | -| `query` | Search query string using Gmail's search syntax (e.g., 'is:unread from:user@example.com') | string | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `gmail_get_attachment_by_id` - -Retrieve a specific attachment from a Gmail message using the message ID and attachment ID. - -| Properties | Description | Type | -| --- | --- | --- | -| `attachment_id` | Unique Gmail attachment ID | string | -| `file_name` | Preferred filename to use when saving/returning the attachment | string | null | -| `message_id` | Unique Gmail message ID that contains the attachment | string | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `gmail_get_contacts` - -Fetch a list of contacts from the connected Gmail account. Supports pagination and field filtering. - -| Properties | Description | Type | -| --- | --- | --- | -| `max_results` | Maximum number of contacts to fetch | integer | null | -| `page_token` | Token to retrieve the next page of results | string | null | -| `person_fields` | Fields to include for each person | `array` | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `gmail_get_message_by_id` - -Retrieve a specific Gmail message using its message ID. Optionally control the format of the returned data. - -| Properties | Description | Type | -| --- | --- | --- | -| `format` | Format of the returned message. | string | null | -| `message_id` | Unique Gmail message ID | string | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `gmail_list_drafts` - -List draft emails from a connected Gmail account. Requires a valid Gmail OAuth2 connection. - -| Properties | Description | Type | -| --- | --- | --- | -| `max_results` | Maximum number of drafts to fetch | integer | null | -| `page_token` | Page token for pagination | string | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `gmail_search_people` - -Search people or contacts in the connected Google account using a query. Requires a valid Google OAuth2 connection with People API scopes. - -| Properties | Description | Type | -| --- | --- | --- | -| `other_contacts` | Whether to include people not in the user's contacts (from 'Other Contacts'). | boolean | null | -| `page_size` | Maximum number of people to return. | integer | null | -| `person_fields` | Fields to retrieve for each person. | `array` | null | -| `query` | Text query to search people (e.g., name, email address). | string | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | diff --git a/plugins/agentkit/references/agent-connectors/gong.md b/plugins/agentkit/references/agent-connectors/gong.md deleted file mode 100644 index 26a36e3..0000000 --- a/plugins/agentkit/references/agent-connectors/gong.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect with Gong to sync calls, transcripts, insights, coaching and CRM activity - -Supports authentication: OAuth 2.0 , Api Key diff --git a/plugins/agentkit/references/agent-connectors/google_ads.md b/plugins/agentkit/references/agent-connectors/google_ads.md deleted file mode 100644 index 9718639..0000000 --- a/plugins/agentkit/references/agent-connectors/google_ads.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Google Ads to manage advertising campaigns, analyze performance metrics, and optimize ad spending across Google's advertising platform - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_docs.md b/plugins/agentkit/references/agent-connectors/google_docs.md deleted file mode 100644 index e790d5d..0000000 --- a/plugins/agentkit/references/agent-connectors/google_docs.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Google Docs. Create, edit, and collaborate on documents - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_drive.md b/plugins/agentkit/references/agent-connectors/google_drive.md deleted file mode 100644 index ef6120e..0000000 --- a/plugins/agentkit/references/agent-connectors/google_drive.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Google Drive. Manage files, folders, and sharing permissions - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_forms.md b/plugins/agentkit/references/agent-connectors/google_forms.md deleted file mode 100644 index fde9e1d..0000000 --- a/plugins/agentkit/references/agent-connectors/google_forms.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Google Forms. Create, view, and manage forms and responses securely - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_meets.md b/plugins/agentkit/references/agent-connectors/google_meets.md deleted file mode 100644 index 814ff7b..0000000 --- a/plugins/agentkit/references/agent-connectors/google_meets.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Google Meet. Create and manage video meetings with powerful collaboration features - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_sheets.md b/plugins/agentkit/references/agent-connectors/google_sheets.md deleted file mode 100644 index 8df9a54..0000000 --- a/plugins/agentkit/references/agent-connectors/google_sheets.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Google Sheets. Create, edit, and analyze spreadsheets with powerful data management capabilities - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/google_slides.md b/plugins/agentkit/references/agent-connectors/google_slides.md deleted file mode 100644 index 242ef37..0000000 --- a/plugins/agentkit/references/agent-connectors/google_slides.md +++ /dev/null @@ -1,32 +0,0 @@ -Connect to Google Slides to create, read, and modify presentations programmatically. - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `googleslides_create_presentation` - -Create a new Google Slides presentation with an optional title. - -| Properties | Description | Type | -| --- | --- | --- | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `title` | Title of the new presentation | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `googleslides_read_presentation` - -Read the complete structure and content of a Google Slides presentation including slides, text, images, shapes, and metadata. - -| Properties | Description | Type | -| --- | --- | --- | -| `fields` | Fields to include in the response | string | null | -| `presentation_id` | The ID of the Google Slides presentation to read | string | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | diff --git a/plugins/agentkit/references/agent-connectors/googlecalendar.md b/plugins/agentkit/references/agent-connectors/googlecalendar.md deleted file mode 100644 index 8168441..0000000 --- a/plugins/agentkit/references/agent-connectors/googlecalendar.md +++ /dev/null @@ -1,128 +0,0 @@ -Google Calendar is Google's cloud-based calendar service that allows you to manage your events, appointments, and schedules from any computer or device with just a web browser. - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `googlecalendar_create_event` - -Create a new event in a connected Google Calendar account. Supports meeting links, recurrence, attendees, and more. - -| Properties | Description | Type | -| --- | --- | --- | -| `attendees_emails` | Attendee email addresses | `array` | null | -| `calendar_id` | Calendar ID to create the event in | string | null | -| `create_meeting_room` | Generate a Google Meet link for this event | boolean | null | -| `description` | Optional event description | string | null | -| `event_duration_hour` | Duration of event in hours | integer | null | -| `event_duration_minutes` | Duration of event in minutes | integer | null | -| `event_type` | Event type for display purposes | string | null | -| `guests_can_invite_others` | Allow guests to invite others | boolean | null | -| `guests_can_modify` | Allow guests to modify the event | boolean | null | -| `guests_can_see_other_guests` | Allow guests to see each other | boolean | null | -| `location` | Location of the event | string | null | -| `recurrence` | Recurrence rules (iCalendar RRULE format) | `array` | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `send_updates` | Send update notifications to attendees | boolean | null | -| `start_datetime` | Event start time in RFC3339 format | string | -| `summary` | Event title/summary | string | -| `timezone` | Timezone for the event (IANA time zone identifier) | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | -| `transparency` | Calendar transparency (free/busy) | string | null | -| `visibility` | Visibility of the event | string | null | - -## `googlecalendar_delete_event` - -Delete an event from a connected Google Calendar account. Requires the calendar ID and event ID. - -| Properties | Description | Type | -| --- | --- | --- | -| `calendar_id` | The ID of the calendar from which the event should be deleted | string | null | -| `event_id` | The ID of the calendar event to delete | string | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `googlecalendar_get_event_by_id` - -Retrieve a specific calendar event by its ID using optional filtering and list parameters. - -| Properties | Description | Type | -| --- | --- | --- | -| `calendar_id` | The calendar ID to search in | string | null | -| `event_id` | The unique identifier of the calendar event to fetch | string | -| `event_types` | Filter by Google event types | `array` | null | -| `query` | Free text search query | string | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `show_deleted` | Include deleted events in results | boolean | null | -| `single_events` | Expand recurring events into instances | boolean | null | -| `time_max` | Upper bound for event start time (RFC3339) | string | null | -| `time_min` | Lower bound for event start time (RFC3339) | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | -| `updated_min` | Filter events updated after this time (RFC3339) | string | null | - -## `googlecalendar_list_calendars` - -List all accessible Google Calendar calendars for the authenticated user. Supports filters and pagination. - -| Properties | Description | Type | -| --- | --- | --- | -| `max_results` | Maximum number of calendars to fetch | integer | null | -| `min_access_role` | Minimum access role to include in results | string | null | -| `page_token` | Token to retrieve the next page of results | string | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `show_deleted` | Include deleted calendars in the list | boolean | null | -| `show_hidden` | Include calendars that are hidden from the calendar list | boolean | null | -| `sync_token` | Token to get updates since the last sync | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `googlecalendar_list_events` - -List events from a connected Google Calendar account with filtering options. Requires a valid Google Calendar OAuth2 connection. - -| Properties | Description | Type | -| --- | --- | --- | -| `calendar_id` | Calendar ID to list events from | string | null | -| `max_results` | Maximum number of events to fetch | integer | null | -| `order_by` | Order of events in the result | string | null | -| `page_token` | Page token for pagination | string | null | -| `query` | Free text search query | string | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `single_events` | Expand recurring events into single events | boolean | null | -| `time_max` | Upper bound for event start time (RFC3339 timestamp) | string | null | -| `time_min` | Lower bound for event start time (RFC3339 timestamp) | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `googlecalendar_update_event` - -Update an existing event in a connected Google Calendar account. Only provided fields will be updated. Supports updating time, attendees, location, meeting links, and more. - -| Properties | Description | Type | -| --- | --- | --- | -| `attendees_emails` | Attendee email addresses | `array` | null | -| `calendar_id` | Calendar ID containing the event | string | -| `create_meeting_room` | Generate a Google Meet link for this event | boolean | null | -| `description` | Optional event description | string | null | -| `end_datetime` | Event end time in RFC3339 format | string | null | -| `event_duration_hour` | Duration of event in hours | integer | null | -| `event_duration_minutes` | Duration of event in minutes | integer | null | -| `event_id` | The ID of the calendar event to update | string | -| `event_type` | Event type for display purposes | string | null | -| `guests_can_invite_others` | Allow guests to invite others | boolean | null | -| `guests_can_modify` | Allow guests to modify the event | boolean | null | -| `guests_can_see_other_guests` | Allow guests to see each other | boolean | null | -| `location` | Location of the event | string | null | -| `recurrence` | Recurrence rules (iCalendar RRULE format) | `array` | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `send_updates` | Send update notifications to attendees | boolean | null | -| `start_datetime` | Event start time in RFC3339 format | string | null | -| `summary` | Event title/summary | string | null | -| `timezone` | Timezone for the event (IANA time zone identifier) | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | -| `transparency` | Calendar transparency (free/busy) | string | null | -| `visibility` | Visibility of the event | string | null | diff --git a/plugins/agentkit/references/agent-connectors/hubspot.md b/plugins/agentkit/references/agent-connectors/hubspot.md deleted file mode 100644 index ac44f2c..0000000 --- a/plugins/agentkit/references/agent-connectors/hubspot.md +++ /dev/null @@ -1,143 +0,0 @@ -Connect to HubSpot CRM. Manage contacts, deals, companies, and marketing automation - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `hubspot_companies_search` - -Search HubSpot companies using full-text search and pagination. Returns matching companies with specified properties. - -| Properties | Description | Type | -| --- | --- | --- | -| `after` | Pagination offset to get results starting from a specific position | string | null | -| `filterGroups` | JSON string containing filter groups for advanced filtering | string | null | -| `limit` | Number of results to return per page (max 100) | number | null | -| `properties` | Comma-separated list of properties to include in the response | string | null | -| `query` | Search term for full-text search across company properties | string | null | - -## `hubspot_company_create` - -Create a new company in HubSpot CRM. Requires a company name as the unique identifier. Supports additional properties like domain, industry, phone, location, and revenue information. - -| Properties | Description | Type | -| --- | --- | --- | -| `annualrevenue` | Annual revenue of the company | number | null | -| `city` | Company city location | string | null | -| `country` | Company country location | string | null | -| `description` | Company description or overview | string | null | -| `domain` | Company website domain | string | null | -| `industry` | Industry type of the company | string | null | -| `name` | Company name (required, serves as primary identifier) | string | -| `numberofemployees` | Number of employees at the company | number | null | -| `phone` | Company phone number | string | null | -| `state` | Company state or region | string | null | - -## `hubspot_company_get` - -Retrieve details of a specific company from HubSpot by company ID. Returns company properties and associated data. - -| Properties | Description | Type | -| --- | --- | --- | -| `company_id` | ID of the company to retrieve | string | -| `properties` | Comma-separated list of properties to include in the response | string | null | - -## `hubspot_contact_create` - -Create a new contact in HubSpot CRM. Requires an email address as the unique identifier. Supports additional properties like name, company, phone, and lifecycle stage. - -| Properties | Description | Type | -| --- | --- | --- | -| `company` | Company name where the contact works | string | null | -| `email` | Primary email address for the contact (required, serves as unique identifier) | string | -| `firstname` | First name of the contact | string | null | -| `hs_lead_status` | Lead status of the contact | string | null | -| `jobtitle` | Job title of the contact | string | null | -| `lastname` | Last name of the contact | string | null | -| `lifecyclestage` | Lifecycle stage of the contact | string | null | -| `phone` | Phone number of the contact | string | null | -| `website` | Personal or company website URL | string | null | - -## `hubspot_contact_get` - -Retrieve details of a specific contact from HubSpot by contact ID. Returns contact properties and associated data. - -| Properties | Description | Type | -| --- | --- | --- | -| `contact_id` | ID of the contact to retrieve | string | -| `properties` | Comma-separated list of properties to include in the response | string | null | - -## `hubspot_contact_update` - -Update an existing contact in HubSpot CRM by contact ID. Allows updating contact properties like name, email, company, phone, and lifecycle stage. - -| Properties | Description | Type | -| --- | --- | --- | -| `contact_id` | ID of the contact to update | string | -| `props` | Object containing properties like first name, last name, email, company, phone, and job title to update all these should be provided inside props as a JSON object, this is required | `object` | null | - -## `hubspot_contacts_list` - -Retrieve a list of contacts from HubSpot with filtering and pagination. Returns contact properties and supports pagination through cursor-based navigation. - -| Properties | Description | Type | -| --- | --- | --- | -| `after` | Pagination cursor to get the next set of results | string | null | -| `archived` | Whether to include archived contacts in the results | boolean | null | -| `limit` | Number of results to return per page (max 100) | number | null | -| `properties` | Comma-separated list of properties to include in the response | string | null | - -## `hubspot_contacts_search` - -Search HubSpot contacts using full-text search and pagination. Returns matching contacts with specified properties. - -| Properties | Description | Type | -| --- | --- | --- | -| `after` | Pagination offset to get results starting from a specific position | string | null | -| `filterGroups` | JSON string containing filter groups for advanced filtering | string | null | -| `limit` | Number of results to return per page (max 100) | number | null | -| `properties` | Comma-separated list of properties to include in the response | string | null | -| `query` | Search term for full-text search across contact properties | string | null | - -## `hubspot_deal_create` - -Create a new deal in HubSpot CRM. Requires dealname, amount, and dealstage. Supports additional properties like pipeline, close date, and deal type. - -| Properties | Description | Type | -| --- | --- | --- | -| `amount` | Deal amount/value (required) | number | -| `closedate` | Expected close date (YYYY-MM-DD format) | string | null | -| `dealname` | Name of the deal (required) | string | -| `dealstage` | Current stage of the deal (required) | string | -| `dealtype` | Type of deal | string | null | -| `description` | Deal description | string | null | -| `hs_priority` | Deal priority (HIGH, MEDIUM, LOW) | string | null | -| `pipeline` | Deal pipeline | string | null | - -## `hubspot_deal_update` - -Update an existing deal in HubSpot CRM by deal ID. Allows updating deal properties like name, amount, stage, pipeline, close date, and priority. - -| Properties | Description | Type | -| --- | --- | --- | -| `deal_id` | ID of the deal to update | string | -| `good_deal` | Boolean flag indicating if this is a good deal | boolean | null | -| `properties` | Object containing deal properties to update | `object` | - -## `hubspot_deals_search` - -Search HubSpot deals using full-text search and pagination. Returns matching deals with specified properties. - -| Properties | Description | Type | -| --- | --- | --- | -| `after` | Pagination offset to get results starting from a specific position | string | null | -| `filterGroups` | JSON string containing filter groups for advanced filtering | string | null | -| `limit` | Number of results to return per page (max 100) | number | null | -| `properties` | Comma-separated list of properties to include in the response | string | null | -| `query` | Search term for full-text search across deal properties | string | null | diff --git a/plugins/agentkit/references/agent-connectors/intercom.md b/plugins/agentkit/references/agent-connectors/intercom.md deleted file mode 100644 index 77ef5d8..0000000 --- a/plugins/agentkit/references/agent-connectors/intercom.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Intercom. Send messages, manage conversations, and interact with users and contacts. - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/jira.md b/plugins/agentkit/references/agent-connectors/jira.md deleted file mode 100644 index 1675086..0000000 --- a/plugins/agentkit/references/agent-connectors/jira.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Jira. Manage issues, projects, workflows, and agile development processes - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/linear.md b/plugins/agentkit/references/agent-connectors/linear.md deleted file mode 100644 index 9b0f877..0000000 --- a/plugins/agentkit/references/agent-connectors/linear.md +++ /dev/null @@ -1,58 +0,0 @@ -Connect to Linear. Manage issues, projects, sprints, and development workflows - -Supports authentication: OAuth 2.0 - -## Tool list - -## `linear_graphql_query` - -Execute a custom GraphQL query or mutation against the Linear API. Allows running any valid GraphQL operation with variables support for advanced use cases. - -| Properties | Description | Type | -| --- | --- | --- | -| `query` | The GraphQL query or mutation to execute | string | -| `variables` | Variables to pass to the GraphQL query | `object` | null | - -## `linear_issue_create` - -Create a new issue in Linear using the issueCreate mutation. Requires a team ID and title at minimum. - -| Properties | Description | Type | -| --- | --- | --- | -| `assigneeId` | ID of the user to assign the issue to | string | null | -| `description` | Description of the issue | string | null | -| `estimate` | Story point estimate for the issue | string | null | -| `labelIds` | Array of label IDs to apply to the issue | `array` | null | -| `priority` | Priority level of the issue (1-4, where 1 is urgent) | string | null | -| `projectId` | ID of the project to associate the issue with | string | null | -| `stateId` | ID of the workflow state to set | string | null | -| `teamId` | ID of the team to create the issue in | string | -| `title` | Title of the issue | string | - -## `linear_issue_update` - -Update an existing issue in Linear. You can update title, description, priority, state, and assignee. - -| Properties | Description | Type | -| --- | --- | --- | -| `assigneeId` | ID of the user to assign the issue to | string | null | -| `description` | New description for the issue | string | null | -| `issueId` | ID of the issue to update | string | -| `priority` | Priority level of the issue (1-4, where 1 is urgent) | string | null | -| `stateId` | ID of the workflow state to set | string | null | -| `title` | New title for the issue | string | null | - -## `linear_issues_list` - -List issues in Linear using the issues query with simple filtering and pagination support. - -| Properties | Description | Type | -| --- | --- | --- | -| `after` | Cursor for pagination (returns issues after this cursor) | string | null | -| `assignee` | Filter by assignee email (e.g., 'user@example.com') | string | null | -| `before` | Cursor for pagination (returns issues before this cursor) | string | null | -| `first` | Number of issues to return (pagination) | integer | null | -| `labels` | Filter by label names (array of strings) | `array` | null | -| `priority` | Filter by priority level (1=Urgent, 2=High, 3=Medium, 4=Low) | string | null | -| `project` | Filter by project name (e.g., 'Q4 Goals') | string | null | -| `state` | Filter by state name (e.g., 'In Progress', 'Done') | string | null | diff --git a/plugins/agentkit/references/agent-connectors/microsoft_excel.md b/plugins/agentkit/references/agent-connectors/microsoft_excel.md deleted file mode 100644 index 2fa4c19..0000000 --- a/plugins/agentkit/references/agent-connectors/microsoft_excel.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Microsoft Excel. Access, read, and modify spreadsheets stored in OneDrive or SharePoint through Microsoft Graph API. - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/microsoft_teams.md b/plugins/agentkit/references/agent-connectors/microsoft_teams.md deleted file mode 100644 index 85790a1..0000000 --- a/plugins/agentkit/references/agent-connectors/microsoft_teams.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Microsoft Teams. Manage messages, channels, meetings, and team collaboration - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/microsoft_word.md b/plugins/agentkit/references/agent-connectors/microsoft_word.md deleted file mode 100644 index 7b2d530..0000000 --- a/plugins/agentkit/references/agent-connectors/microsoft_word.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Microsoft Word. Authenticate with your Microsoft account to create, read, and edit Word documents stored in OneDrive or SharePoint through Microsoft Graph API. - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/monday.md b/plugins/agentkit/references/agent-connectors/monday.md deleted file mode 100644 index 6cc8ab0..0000000 --- a/plugins/agentkit/references/agent-connectors/monday.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Monday.com. Manage boards, tasks, workflows, teams, and project collaboration - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/notion.md b/plugins/agentkit/references/agent-connectors/notion.md deleted file mode 100644 index 22b58d5..0000000 --- a/plugins/agentkit/references/agent-connectors/notion.md +++ /dev/null @@ -1,189 +0,0 @@ -Connect to Notion workspace. Create, edit pages, manage databases, and collaborate on content - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `notion_comment_create` - -Create a comment in Notion. Provide a comment object with rich_text content and either a parent object (with page_id) for a page-level comment or a discussion_id to reply in an existing thread. - -| Properties | Description | Type | -| --- | --- | --- | -| `comment` | Comment object containing a rich_text array. Example: `{"rich_text":[{"type":"text","text":{"content":"Hello"}}]}` | `object` | -| `discussion_id` | Existing discussion thread ID to reply to. | string | null | -| `notion_version` | Optional override for the Notion-Version header (e.g., 2022-06-28). | string | null | -| `parent` | Parent object for a new top-level comment. Shape: `{"page_id":""}`. | `object` | null | -| `schema_version` | Internal override for schema version. | string | null | -| `tool_version` | Internal override for tool implementation version. | string | null | - -## `notion_comment_retrieve` - -Retrieve a single Notion comment by its `comment_id`. LLM tip: you typically obtain `comment_id` from the response of creating a comment or by first listing comments for a page/block and selecting the desired item’s `id`. - -| Properties | Description | Type | -| --- | --- | --- | -| `comment_id` | The identifier of the comment to retrieve (hyphenated UUID). Obtain it from Create-Comment responses or from a prior List-Comments call. | string | -| `notion_version` | Optional Notion-Version header override (e.g., 2022-06-28). | string | null | -| `schema_version` | Internal override for schema version. | string | null | -| `tool_version` | Internal override for tool implementation version. | string | null | - -## `notion_comments_fetch` - -Fetch comments for a given Notion block. Provide a `block_id` (the target page/block ID, hyphenated UUID). Supports pagination via `start_cursor` and `page_size` (1–100). LLM tip: extract `block_id` from a Notion URL’s trailing 32-char id, then insert hyphens (8-4-4-4-12). - -| Properties | Description | Type | -| --- | --- | --- | -| `block_id` | Target Notion block (or page) ID to fetch comments for. Use a hyphenated UUID. | string | -| `notion_version` | Optional Notion-Version header override (e.g., 2022-06-28). | string | null | -| `page_size` | Maximum number of comments to return (1–100). | integer | null | -| `schema_version` | Internal override for schema version. | string | null | -| `start_cursor` | Cursor to fetch the next page of results. | string | null | -| `tool_version` | Internal override for tool implementation version. | string | null | - -## `notion_data_fetch` - -Fetch data from Notion using the workspace search API (/search). Supports pagination via start_cursor. - -| Properties | Description | Type | -| --- | --- | --- | -| `page_size` | Max number of results to return (1–100) | integer | null | -| `query` | Text query used by /search | string | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `start_cursor` | Cursor for pagination; pass the previous response's next_cursor | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | - -## `notion_database_create` - -Create a new database in Notion under a parent page. Provide a parent object with page_id, a database title (rich_text array), and a properties object that defines the database schema (columns). - -| Properties | Description | Type | -| --- | --- | --- | -| `notion_version` | Optional override for the Notion-Version header (e.g., 2022-06-28). | string | null | -| `parent` | Parent object specifying the page under which the database is created. Example: `{"page_id": "2561ab6c-418b-8072-beec-c4779fa811cf"}` | `object` | -| `properties` | Database schema object defining properties (columns). Example: `{"Name": {"title": {}}, "Status": {"select": {"options": [{"name": "Todo"}, {"name": "Doing"}, {"name": "Done"}]}}}` | `object` | -| `schema_version` | Internal override for schema version. | string | null | -| `title` | Database title as a Notion rich_text array. | `array` | -| `tool_version` | Internal override for tool implementation version. | string | null | - -## `notion_database_fetch` - -Retrieve a Notion database’s full definition, including title, properties, and schema. Required: `database_id` (hyphenated UUID). LLM tip: Extract the last 32 characters from a Notion database URL, then insert hyphens (8-4-4-4-12). - -| Properties | Description | Type | -| --- | --- | --- | -| `database_id` | The target database ID in UUID format with hyphens. | string | -| `notion_version` | Optional override for the Notion-Version header. | string | null | -| `schema_version` | Optional schema version override. | string | null | -| `tool_version` | Optional tool version override. | string | null | - -## `notion_database_insert_row` - -Insert a new row (page) into a Notion database. Required: `database_id` (hyphenated UUID) and `properties` (object mapping database column names to Notion **property values). Optional: child_blocks` (content blocks), `icon` (page icon object), and `cover` (page cover object). - -LLM guidance: -- `properties` must use **property values** (not schema). Example: - -```json - { - "title": { "title": [ { "text": { "content": "Task A" } } ] }, - "Status": { "select": { "name": "Todo" } }, - "Due": { "date": { "start": "2025-09-01" } } - } -``` -- Use the **exact property key** as defined in the database (case‑sensitive), or the property id. -- `icon` example (emoji): `{"type":"emoji","emoji":"📝"}` -- `cover` example (external): `{"type":"external","external":{"url":"https://example.com/image.jpg"}}` -- Runtime note: the executor/host should synthesize `parent = {"database_id": database_id}` before sending to Notion. - -| Properties | Description | Type | -| --- | --- | --- | -| `_parent` | Computed by host: `{ "database_id": "" }`. Do not supply manually. | `object` | null | -| `child_blocks` | Optional array of Notion blocks to append as page content (paragraph, heading, to_do, etc.). | `array` | null | -| `cover` | Optional page cover object. Example external: `{"type":"external","external":{"url":"https://example.com/cover.jpg"}}`. | `object` | null | -| `database_id` | Target database ID (hyphenated UUID). | string | -| `icon` | Optional page icon object. Examples: `{"type":"emoji","emoji":"📝"}` or `{"type":"external","external":{"url":"https://..."}}`. | `object` | null | -| `notion_version` | Optional Notion-Version header override (e.g., 2022-06-28). | string | null | -| `properties` | Object mapping **column names (or property ids)** to **property values**. - -️ **CRITICAL: Property Identification Rules:** -- For title fields: ALWAYS use 'title' as the property key (not 'Name' or display names) -- For other properties: Use exact property names from database schema (case-sensitive) -- DO NOT use URL-encoded property IDs with special characters - - **Recommended Workflow:** -1. Call fetch_database first to see exact property names -2. Use 'title' for title-type properties -3. Match other property names exactly as shown in schema - -Example: - -```json -{ - "title": { "title": [ { "text": { "content": "Task A" } } ] }, - "Status": { "select": { "name": "Todo" } }, - "Due": { "date": { "start": "2025-09-01" } } -} -``` | `object` | -| `schema_version` | Optional schema version override. | string | null | -| `tool_version` | Optional tool version override. | string | null | - -## `notion_database_property_retrieve` - -Query a Notion database and return only specific properties by supplying one or more property IDs. Use when you need page rows but want to limit the returned properties to reduce payload. Provide the database_id and an array of filter_properties (each item is a property id like "title") - -| Properties | Description | Type | -| --- | --- | --- | -| `database_id` | Target database ID (hyphenated UUID). | string | -| `property_id` | property ID to filter results by a specific property. get the property id by querying database. | string | null | -| `schema_version` | Optional schema version override. | string | null | -| `tool_version` | Optional tool version override. | string | null | - -## `notion_database_query` - -Query a Notion database for rows (pages). Provide database_id (hyphenated UUID). Optional: page_size, start_cursor for pagination, and sorts (array of sort objects). LLM guidance: extract the last 32 characters from a Notion database URL and insert hyphens (8-4-4-4-12) to form database_id. Sort rules: each sort item MUST include either property OR timestamp (last_edited_time/created_time), not both. - -| Properties | Description | Type | -| --- | --- | --- | -| `database_id` | Target database ID (hyphenated UUID). | string | -| `notion_version` | Optional Notion-Version header override. | string | null | -| `page_size` | Maximum number of rows to return (1–100). | integer | null | -| `schema_version` | Optional schema version override. | string | null | -| `sorts` | Order the results. Each item must include either property or timestamp, plus direction. | `array` | null | -| `start_cursor` | Cursor to fetch the next page of results. | string | null | -| `tool_version` | Optional tool version override. | string | null | - -## `notion_page_create` - -Create a page in Notion either inside a database (as a row) or as a child of a page. Use exactly one parent mode: provide database_id to create a database row (page with properties) OR provide parent_page_id to create a child page. When creating in a database, properties must use Notion property value shapes and the title property key must be "title" (not the display name). Children (content blocks), icon, and cover are optional. The executor should synthesize the Notion parent object from the chosen parent input. - -Target rules: -- Use database_id OR parent_page_id (not both) -- If database_id is provided → properties are required -- If parent_page_id is provided → properties are optional - -| Properties | Description | Type | -| --- | --- | --- | -| `_parent` | Computed by the executor: `{"database_id": "..."}` OR `{"page_id": "..."}` derived from database_id/parent_page_id. | `object` | null | -| `child_blocks` | Optional blocks to add as page content (children). | `array` | null | -| `cover` | Optional page cover object. | `object` | null | -| `database_id` | Create a page as a new row in this database (hyphenated UUID). Extract from the database URL (last 32 chars → hyphenate 8-4-4-4-12). | string | null | -| `icon` | Optional page icon object. | `object` | null | -| `notion_version` | Optional Notion-Version header override. | string | null | -| `parent_page_id` | Create a child page under this page (hyphenated UUID). Extract from the parent page URL. | string | null | -| `properties` | For database rows, supply property values keyed by property name (or id). For title properties, the key must be "title". - -Example (database row): -{ - "title": { "title": [ { "text": { "content": "Task A" } } ] }, - "Status": { "select": { "name": "Todo" } }, - "Due": { "date": { "start": "2025-09-01" } } -} | `object` | null | -| `schema_version` | Optional schema version override. | string | null | -| `tool_version` | Optional tool version override. | string | null | diff --git a/plugins/agentkit/references/agent-connectors/onedrive.md b/plugins/agentkit/references/agent-connectors/onedrive.md deleted file mode 100644 index 601c43f..0000000 --- a/plugins/agentkit/references/agent-connectors/onedrive.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to OneDrive. Manage files, folders, and cloud storage with Microsoft OneDrive - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/onenote.md b/plugins/agentkit/references/agent-connectors/onenote.md deleted file mode 100644 index 47a835b..0000000 --- a/plugins/agentkit/references/agent-connectors/onenote.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Microsoft OneNote. Access, create, and manage notebooks, sections, and pages stored in OneDrive or SharePoint through Microsoft Graph API. - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/outlook.md b/plugins/agentkit/references/agent-connectors/outlook.md deleted file mode 100644 index 9dae658..0000000 --- a/plugins/agentkit/references/agent-connectors/outlook.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Microsoft Outlook. Manage emails, calendar events, contacts, and tasks - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/salesforce.md b/plugins/agentkit/references/agent-connectors/salesforce.md deleted file mode 100644 index f0441b1..0000000 --- a/plugins/agentkit/references/agent-connectors/salesforce.md +++ /dev/null @@ -1,316 +0,0 @@ -Connect to Salesforce CRM. Manage leads, opportunities, accounts, and customer relationships - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `salesforce_account_create` - -Create a new Account in Salesforce. Supports standard fields - -| Properties | Description | Type | -| --- | --- | --- | -| `AccountNumber` | Account number for the organization | string | null | -| `AnnualRevenue` | Annual revenue | number | null | -| `BillingCity` | Billing city | string | null | -| `BillingCountry` | Billing country | string | null | -| `BillingPostalCode` | Billing postal code | string | null | -| `BillingState` | Billing state/province | string | null | -| `BillingStreet` | Billing street | string | null | -| `Description` | Description | string | null | -| `Industry` | Industry | string | null | -| `Name` | Account Name | string | -| `NumberOfEmployees` | Number of employees | integer | null | -| `OwnerId` | Record owner (User/Queue Id) | string | null | -| `Phone` | Main phone number | string | null | -| `RecordTypeId` | Record Type Id | string | null | -| `Website` | Website URL | string | null | - -## `salesforce_account_delete` - -Delete an existing Account from Salesforce by account ID. This is a destructive operation that permanently removes the account record. - -| Properties | Description | Type | -| --- | --- | --- | -| `account_id` | ID of the account to delete | string | - -## `salesforce_account_get` - -Retrieve details of a specific account from Salesforce by account ID. Returns account properties and associated data. - -| Properties | Description | Type | -| --- | --- | --- | -| `account_id` | ID of the account to retrieve | string | -| `fields` | Comma-separated list of fields to include in the response | string | null | - -## `salesforce_account_update` - -Update an existing Account in Salesforce by account ID. Allows updating account properties like name, phone, website, industry, billing information, and more. - -| Properties | Description | Type | -| --- | --- | --- | -| `AccountNumber` | Account number for the organization | string | null | -| `AccountSource` | Lead source for this account | string | null | -| `AnnualRevenue` | Annual revenue | number | null | -| `BillingCity` | Billing city | string | null | -| `BillingCountry` | Billing country | string | null | -| `BillingGeocodeAccuracy` | Billing geocode accuracy | string | null | -| `BillingLatitude` | Billing address latitude | number | null | -| `BillingLongitude` | Billing address longitude | number | null | -| `BillingPostalCode` | Billing postal code | string | null | -| `BillingState` | Billing state/province | string | null | -| `BillingStreet` | Billing street | string | null | -| `CleanStatus` | Data.com clean status | string | null | -| `Description` | Description | string | null | -| `DunsNumber` | D-U-N-S Number | string | null | -| `Fax` | Fax number | string | null | -| `Industry` | Industry | string | null | -| `Jigsaw` | Data.com key | string | null | -| `JigsawCompanyId` | Jigsaw company ID | string | null | -| `NaicsCode` | NAICS code | string | null | -| `NaicsDesc` | NAICS description | string | null | -| `Name` | Account Name | string | null | -| `NumberOfEmployees` | Number of employees | integer | null | -| `OwnerId` | Record owner (User/Queue Id) | string | null | -| `Ownership` | Ownership type | string | null | -| `ParentId` | Parent Account Id | string | null | -| `Phone` | Main phone number | string | null | -| `Rating` | Account rating | string | null | -| `RecordTypeId` | Record Type Id | string | null | -| `ShippingCity` | Shipping city | string | null | -| `ShippingCountry` | Shipping country | string | null | -| `ShippingGeocodeAccuracy` | Shipping geocode accuracy | string | null | -| `ShippingLatitude` | Shipping address latitude | number | null | -| `ShippingLongitude` | Shipping address longitude | number | null | -| `ShippingPostalCode` | Shipping postal code | string | null | -| `ShippingState` | Shipping state/province | string | null | -| `ShippingStreet` | Shipping street | string | null | -| `Sic` | SIC code | string | null | -| `SicDesc` | SIC description | string | null | -| `Site` | Account site or location | string | null | -| `TickerSymbol` | Stock ticker symbol | string | null | -| `Tradestyle` | Trade style name | string | null | -| `Type` | Account type | string | null | -| `Website` | Website URL | string | null | -| `YearStarted` | Year the company started | string | null | -| `account_id` | ID of the account to update | string | - -## `salesforce_accounts_list` - -Retrieve a list of accounts from Salesforce using a pre-built SOQL query. Returns basic account information. - -| Properties | Description | Type | -| --- | --- | --- | -| `limit` | Number of results to return per page | number | - -## `salesforce_composite` - -Execute multiple Salesforce REST API requests in a single call using the Composite API. Allows for efficient batch operations and related data retrieval. - -| Properties | Description | Type | -| --- | --- | --- | -| `composite_request` | JSON string containing composite request with multiple sub-requests | string | - -## `salesforce_contact_create` - -Create a new contact in Salesforce. Allows setting contact properties like name, email, phone, account association, and other standard fields. - -| Properties | Description | Type | -| --- | --- | --- | -| `AccountId` | Salesforce Account Id associated with this contact | string | null | -| `Department` | Department of the contact | string | null | -| `Description` | Free-form description | string | null | -| `Email` | Email address of the contact | string | null | -| `FirstName` | First name of the contact | string | null | -| `LastName` | Last name of the contact (required) | string | -| `LeadSource` | Lead source for the contact | string | null | -| `MailingCity` | Mailing city | string | null | -| `MailingCountry` | Mailing country | string | null | -| `MailingPostalCode` | Mailing postal code | string | null | -| `MailingState` | Mailing state/province | string | null | -| `MailingStreet` | Mailing street | string | null | -| `MobilePhone` | Mobile phone of the contact | string | null | -| `Phone` | Phone number of the contact | string | null | -| `Title` | Job title of the contact | string | null | - -## `salesforce_contact_get` - -Retrieve details of a specific contact from Salesforce by contact ID. Returns contact properties and associated data. - -| Properties | Description | Type | -| --- | --- | --- | -| `contact_id` | ID of the contact to retrieve | string | -| `fields` | Comma-separated list of fields to include in the response | string | null | - -## `salesforce_dashboard_metadata_get` - -Retrieve metadata for a Salesforce dashboard, including dashboard components, filters, layout, and the running user. - -| Properties | Description | Type | -| --- | --- | --- | -| `dashboard_id` | The unique ID of the Salesforce dashboard | string | - -## `salesforce_global_describe` - -Retrieve metadata about all available SObjects in the Salesforce organization. Returns list of all objects with basic information. - -## `salesforce_limits_get` - -Retrieve organization limits information from Salesforce. Returns API usage limits, data storage limits, and other organizational constraints. - -## `salesforce_object_describe` - -Retrieve detailed metadata about a specific SObject in Salesforce. Returns fields, relationships, and other object metadata. - -| Properties | Description | Type | -| --- | --- | --- | -| `sobject` | SObject API name to describe | string | - -## `salesforce_opportunities_list` - -Retrieve a list of opportunities from Salesforce using a pre-built SOQL query. Returns basic opportunity information. - -| Properties | Description | Type | -| --- | --- | --- | -| `limit` | Number of results to return per page | number | null | - -## `salesforce_opportunity_create` - -Create a new opportunity in Salesforce. Allows setting opportunity properties like name, amount, stage, close date, and account association. - -| Properties | Description | Type | -| --- | --- | --- | -| `AccountId` | Associated Account Id | string | null | -| `Amount` | Opportunity amount | number | null | -| `CampaignId` | Related Campaign Id | string | null | -| `CloseDate` | Expected close date (YYYY-MM-DD, required) | string | -| `Custom_Field__c` | Example custom field (replace with your org’s custom field API name) | string | null | -| `Description` | Opportunity description | string | null | -| `ForecastCategoryName` | Forecast category name | string | null | -| `LeadSource` | Lead source | string | null | -| `Name` | Opportunity name (required) | string | -| `NextStep` | Next step in the sales process | string | null | -| `OwnerId` | Record owner (User/Queue Id) | string | null | -| `PricebookId` | Associated Price Book Id | string | null | -| `Probability` | Probability percentage (0–100) | number | null | -| `RecordTypeId` | Record Type Id for Opportunity | string | null | -| `StageName` | Current sales stage (required) | string | -| `Type` | Opportunity type | string | null | - -## `salesforce_opportunity_get` - -Retrieve details of a specific opportunity from Salesforce by opportunity ID. Returns opportunity properties and associated data. - -| Properties | Description | Type | -| --- | --- | --- | -| `fields` | Comma-separated list of fields to include in the response | string | null | -| `opportunity_id` | ID of the opportunity to retrieve | string | - -## `salesforce_opportunity_update` - -Update an existing opportunity in Salesforce by opportunity ID. Allows updating opportunity properties like name, amount, stage, and close date. - -| Properties | Description | Type | -| --- | --- | --- | -| `AccountId` | Associated Account Id | string | null | -| `Amount` | Opportunity amount | number | null | -| `CampaignId` | Related Campaign Id | string | null | -| `CloseDate` | Expected close date (YYYY-MM-DD) | string | null | -| `Description` | Opportunity description | string | null | -| `ForecastCategoryName` | Forecast category name | string | null | -| `LeadSource` | Lead source | string | null | -| `Name` | Opportunity name | string | null | -| `NextStep` | Next step in the sales process | string | null | -| `OwnerId` | Record owner (User/Queue Id) | string | null | -| `Pricebook2Id` | Associated Price Book Id | string | null | -| `Probability` | Probability percentage (0–100) | number | null | -| `RecordTypeId` | Record Type Id for Opportunity | string | null | -| `StageName` | Current sales stage | string | null | -| `Type` | Opportunity type | string | null | -| `opportunity_id` | ID of the opportunity to update | string | - -## `salesforce_query_soql` - -Execute SOQL queries against Salesforce data. Supports complex queries with joins, filters, and aggregations. - -| Properties | Description | Type | -| --- | --- | --- | -| `query` | SOQL query string to execute | string | - -## `salesforce_report_metadata_get` - -Retrieve report, report type, and related metadata for a Salesforce report. Returns information about report structure, fields, groupings, and configuration. - -| Properties | Description | Type | -| --- | --- | --- | -| `report_id` | The unique ID of the Salesforce report | string | - -## `salesforce_search_parameterized` - -Execute parameterized searches against Salesforce data. Provides simplified search interface with predefined parameters. - -| Properties | Description | Type | -| --- | --- | --- | -| `fields` | Comma-separated list of fields to return | string | null | -| `search_text` | Text to search for | string | -| `sobject` | SObject type to search in | string | - -## `salesforce_search_sosl` - -Execute SOSL searches against Salesforce data. Performs full-text search across multiple objects and fields. - -| Properties | Description | Type | -| --- | --- | --- | -| `search_query` | SOSL search query string to execute | string | - -## `salesforce_sobject_create` - -Create a new record for any Salesforce SObject type (Account, Contact, Lead, Opportunity, custom objects, etc.). Provide the object type and fields as a dynamic object. - -| Properties | Description | Type | -| --- | --- | --- | -| `fields` | Object containing field names and values to set on the new record | `object` | -| `sobject_type` | The Salesforce SObject API name (e.g., Account, Contact, Lead, CustomObject__c) | string | - -## `salesforce_sobject_delete` - -Delete a record from any Salesforce SObject type by ID. This is a destructive operation that permanently removes the record. - -| Properties | Description | Type | -| --- | --- | --- | -| `record_id` | ID of the record to delete | string | -| `sobject_type` | The Salesforce SObject API name (e.g., Account, Contact, Lead, CustomObject__c) | string | - -## `salesforce_sobject_get` - -Retrieve a record from any Salesforce SObject type by ID. Optionally specify which fields to return. - -| Properties | Description | Type | -| --- | --- | --- | -| `fields` | Comma-separated list of fields to include in the response | string | null | -| `record_id` | ID of the record to retrieve | string | -| `sobject_type` | The Salesforce SObject API name (e.g., Account, Contact, Lead, CustomObject__c) | string | - -## `salesforce_sobject_update` - -Update an existing record for any Salesforce SObject type by ID. Only the fields provided will be updated. - -| Properties | Description | Type | -| --- | --- | --- | -| `fields` | Object containing field names and values to update on the record | `object` | -| `record_id` | ID of the record to update | string | -| `sobject_type` | The Salesforce SObject API name (e.g., Account, Contact, Lead, CustomObject__c) | string | - -## `salesforce_soql_execute` - -Execute custom SOQL queries against Salesforce data. Supports complex queries with joins, filters, aggregations, and custom field selection. - -| Properties | Description | Type | -| --- | --- | --- | -| `soql_query` | SOQL query string to execute | string | diff --git a/plugins/agentkit/references/agent-connectors/servicenow.md b/plugins/agentkit/references/agent-connectors/servicenow.md deleted file mode 100644 index b1dc3b0..0000000 --- a/plugins/agentkit/references/agent-connectors/servicenow.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to ServiceNow. Manage incidents, service requests, CMDB, and IT service management workflows - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/sharepoint.md b/plugins/agentkit/references/agent-connectors/sharepoint.md deleted file mode 100644 index c2d88ae..0000000 --- a/plugins/agentkit/references/agent-connectors/sharepoint.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to SharePoint. Manage sites, documents, lists, and collaborative content - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/slack.md b/plugins/agentkit/references/agent-connectors/slack.md deleted file mode 100644 index 8a52cba..0000000 --- a/plugins/agentkit/references/agent-connectors/slack.md +++ /dev/null @@ -1,197 +0,0 @@ -Connect to Slack workspace. Send Messages as Bots or on behalf of users - -Supports authentication: OAuth 2.0 - -## Table of Contents - -- [Tool list](#tool-list) - ---- - -## Tool list - -## `slack_add_reaction` - -Add an emoji reaction to a message in Slack. Requires a valid Slack OAuth2 connection with reactions:write scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID or channel name where the message exists | string | -| `name` | Emoji name to react with (without colons) | string | -| `timestamp` | Timestamp of the message to add reaction to | string | - -## `slack_create_channel` - -Creates a new public or private channel in a Slack workspace. Requires a valid Slack OAuth2 connection with channels:manage scope for public channels or groups:write scope for private channels. - -| Properties | Description | Type | -| --- | --- | --- | -| `is_private` | Create a private channel instead of public | boolean | null | -| `name` | Name of the channel to create (without # prefix) | string | -| `team_id` | Encoded team ID to create channel in (if using org tokens) | string | null | - -## `slack_delete_message` - -Deletes a message from a Slack channel or direct message. Requires a valid Slack OAuth2 connection with chat:write scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID, channel name (#general), or user ID for DM where the message was sent | string | -| `ts` | Timestamp of the message to delete | string | - -## `slack_fetch_conversation_history` - -Fetches conversation history from a Slack channel or direct message with pagination support. Requires a valid Slack OAuth2 connection with channels:history scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID, channel name (#general), or user ID for DM | string | -| `cursor` | Paginate through collections by cursor for pagination | string | null | -| `latest` | End of time range of messages to include in results | string | null | -| `limit` | Number of messages to return (1-1000, default 100) | integer | null | -| `oldest` | Start of time range of messages to include in results | string | null | - -## `slack_get_conversation_info` - -Retrieve information about a Slack channel, including metadata, settings, and member count. Requires a valid Slack OAuth2 connection with channels:read scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID, channel name (#general), or user ID for DM | string | -| `include_locale` | Set to true to include the locale for this conversation | boolean | null | -| `include_num_members` | Set to true to include the member count for the conversation | boolean | null | - -## `slack_get_conversation_replies` - -Retrieve replies to a specific message thread in a Slack channel or direct message. Requires a valid Slack OAuth2 connection with channels:history or groups:history scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID, channel name (#general), or user ID for DM | string | -| `cursor` | Pagination cursor for retrieving next page of results | string | null | -| `inclusive` | Include messages with latest or oldest timestamp in results | boolean | null | -| `latest` | End of time range of messages to include in results | string | null | -| `limit` | Number of messages to return (default 100, max 1000) | integer | null | -| `oldest` | Start of time range of messages to include in results | string | null | -| `ts` | Timestamp of the parent message to get replies for | string | - -## `slack_get_user_info` - -Retrieves detailed information about a specific Slack user, including profile data, status, and workspace information. Requires a valid Slack OAuth2 connection with users:read scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `include_locale` | Set to true to include locale information for the user | boolean | null | -| `user` | User ID to get information about | string | - -## `slack_get_user_presence` - -Gets the current presence status of a Slack user (active, away, etc.). Indicates whether the user is currently online and available. Requires a valid Slack OAuth2 connection with users:read scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `user` | User ID to check presence for | string | - -## `slack_invite_users_to_channel` - -Invites one or more users to a Slack channel. Requires a valid Slack OAuth2 connection with channels:write scope for public channels or groups:write for private channels. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID or channel name (#general) to invite users to | string | -| `users` | Comma-separated list of user IDs to invite to the channel | string | - -## `slack_join_conversation` - -Joins an existing Slack channel. The authenticated user will become a member of the channel. Requires a valid Slack OAuth2 connection with channels:write scope for public channels. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID or channel name (#general) to join | string | - -## `slack_leave_conversation` - -Leaves a Slack channel. The authenticated user will be removed from the channel and will no longer receive messages from it. Requires a valid Slack OAuth2 connection with channels:write scope for public channels or groups:write for private channels. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID or channel name (#general) to leave | string | - -## `slack_list_channels` - -List all public and private channels in a Slack workspace that the authenticated user has access to. Requires a valid Slack OAuth2 connection with channels:read, groups:read, mpim:read, and/or im:read scopes depending on conversation types needed. - -| Properties | Description | Type | -| --- | --- | --- | -| `cursor` | Pagination cursor for retrieving next page of results | string | null | -| `exclude_archived` | Exclude archived channels from the list | boolean | null | -| `limit` | Number of channels to return (default 100, max 1000) | integer | null | -| `team_id` | Encoded team ID to list channels for (optional) | string | null | -| `types` | Mix and match channel types (public_channel, private_channel, mpim, im) | string | null | - -## `slack_list_users` - -Lists all users in a Slack workspace, including information about their status, profile, and presence. Requires a valid Slack OAuth2 connection with users:read scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `cursor` | Pagination cursor for fetching additional pages of users | string | null | -| `include_locale` | Set to true to include locale information for each user | boolean | null | -| `limit` | Number of users to return (1-1000) | number | null | -| `team_id` | Encoded team ID to list users for (if using org tokens) | string | null | - -## `slack_lookup_user_by_email` - -Find a user by their registered email address in a Slack workspace. Requires a valid Slack OAuth2 connection with users:read.email scope. Cannot be used by custom bot users. - -| Properties | Description | Type | -| --- | --- | --- | -| `email` | Email address to search for users by | string | - -## `slack_pin_message` - -Pin a message to a Slack channel. Pinned messages are highlighted and easily accessible to channel members. Requires a valid Slack OAuth2 connection with pins:write scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `channel` | Channel ID or channel name where the message exists | string | -| `timestamp` | Timestamp of the message to pin | string | - -## `slack_send_message` - -Sends a message to a Slack channel or direct message. Requires a valid Slack OAuth2 connection with chat:write scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `attachments` | JSON-encoded array of attachment objects for additional message formatting | string | null | -| `blocks` | JSON-encoded array of Block Kit block elements for rich message formatting | string | null | -| `channel` | Channel ID, channel name (#general), or user ID for DM | string | -| `reply_broadcast` | Used in conjunction with thread_ts to broadcast reply to channel | boolean | null | -| `schema_version` | Optional schema version to use for tool execution | string | null | -| `text` | Message text content | string | -| `thread_ts` | Timestamp of parent message to reply in thread | string | null | -| `tool_version` | Optional tool version to use for execution | string | null | -| `unfurl_links` | Enable or disable link previews | boolean | null | -| `unfurl_media` | Enable or disable media link previews | boolean | null | - -## `slack_set_user_status` - -Set the user's custom status with text and emoji. This appears in their profile and can include an expiration time. Requires a valid Slack OAuth2 connection with users.profile:write scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `status_emoji` | Emoji to display with status (without colons) | string | null | -| `status_expiration` | Unix timestamp when status should expire | integer | null | -| `status_text` | Status text to display | string | null | - -## `slack_update_message` - -Updates/edits a previously sent message in a Slack channel or direct message. Requires a valid Slack OAuth2 connection with chat:write scope. - -| Properties | Description | Type | -| --- | --- | --- | -| `attachments` | JSON-encoded array of attachment objects for additional message formatting | string | null | -| `blocks` | JSON-encoded array of Block Kit block elements for rich message formatting | string | null | -| `channel` | Channel ID, channel name (#general), or user ID for DM where the message was sent | string | -| `text` | New message text content | string | null | -| `ts` | Timestamp of the message to update | string | diff --git a/plugins/agentkit/references/agent-connectors/snowflake.md b/plugins/agentkit/references/agent-connectors/snowflake.md deleted file mode 100644 index 12dba3f..0000000 --- a/plugins/agentkit/references/agent-connectors/snowflake.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Snowflake to manage and analyze your data warehouse workloads - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/trello.md b/plugins/agentkit/references/agent-connectors/trello.md deleted file mode 100644 index 181e36c..0000000 --- a/plugins/agentkit/references/agent-connectors/trello.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Trello. Manage boards, cards, lists, and team collaboration workflows - -Supports authentication: OAuth 1.0a diff --git a/plugins/agentkit/references/agent-connectors/zendesk.md b/plugins/agentkit/references/agent-connectors/zendesk.md deleted file mode 100644 index f993da1..0000000 --- a/plugins/agentkit/references/agent-connectors/zendesk.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Zendesk. Manage customer support tickets, users, organizations, and help desk operations - -Supports authentication: OAuth 2.0 diff --git a/plugins/agentkit/references/agent-connectors/zoom.md b/plugins/agentkit/references/agent-connectors/zoom.md deleted file mode 100644 index 9bfdda0..0000000 --- a/plugins/agentkit/references/agent-connectors/zoom.md +++ /dev/null @@ -1,3 +0,0 @@ -Connect to Zoom. Schedule meetings, manage recordings, and handle video conferencing workflows - -Supports authentication: OAuth 2.0 From 1a6532a17b49e1411da1271829fa49d28e4f21f0 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 13 May 2026 22:21:33 +0530 Subject: [PATCH 25/53] fix: remove and re-add marketplace to ensure latest version on reinstall --- scripts/install.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/install.sh b/scripts/install.sh index 3b19c6f..37fd65c 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -9,12 +9,15 @@ if ! command -v claude >/dev/null 2>&1; then fi MARKETPLACE_SLUG="${CLAUDE_CODE_AUTHSTACK_MARKETPLACE:-scalekit-inc/claude-code-authstack}" +MARKETPLACE_NAME="scalekit-auth-stack" OLD_PLUGINS=("agent-auth" "full-stack-auth" "mcp-auth" "modular-sso" "modular-scim") echo "Installing Scalekit Auth Stack for Claude Code" echo "Marketplace: $MARKETPLACE_SLUG" echo +# Remove and re-add marketplace to ensure the latest version is fetched +claude plugin marketplace remove "$MARKETPLACE_NAME" 2>/dev/null || true claude plugin marketplace add "$MARKETPLACE_SLUG" # Remove old plugin names from v1.x (now consolidated into agentkit + saaskit) From a427f952b58ae5d79853df72975312f97d0635af Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 13 May 2026 22:24:10 +0530 Subject: [PATCH 26/53] fix: use local path for marketplace when running from git checkout --- scripts/install.sh | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index 37fd65c..d2e010d 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -8,25 +8,38 @@ if ! command -v claude >/dev/null 2>&1; then exit 1 fi +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + MARKETPLACE_SLUG="${CLAUDE_CODE_AUTHSTACK_MARKETPLACE:-scalekit-inc/claude-code-authstack}" MARKETPLACE_NAME="scalekit-auth-stack" OLD_PLUGINS=("agent-auth" "full-stack-auth" "mcp-auth" "modular-sso" "modular-scim") +# Use local path when running from a git checkout; GitHub slug when from a downloaded archive +if [[ -d "$REPO_ROOT/.git" ]]; then + MARKETPLACE_SOURCE="$REPO_ROOT" +else + MARKETPLACE_SOURCE="$MARKETPLACE_SLUG" +fi + echo "Installing Scalekit Auth Stack for Claude Code" -echo "Marketplace: $MARKETPLACE_SLUG" +echo "Source: $MARKETPLACE_SOURCE" echo -# Remove and re-add marketplace to ensure the latest version is fetched +# Overwrite any previously registered version of this marketplace +echo "Updating marketplace..." claude plugin marketplace remove "$MARKETPLACE_NAME" 2>/dev/null || true -claude plugin marketplace add "$MARKETPLACE_SLUG" +claude plugin marketplace add "$MARKETPLACE_SOURCE" >/dev/null +echo "Marketplace \"${MARKETPLACE_NAME}\" is up to date." +echo # Remove old plugin names from v1.x (now consolidated into agentkit + saaskit) for old in "${OLD_PLUGINS[@]}"; do - claude plugin uninstall "${old}@scalekit-auth-stack" 2>/dev/null || true + claude plugin uninstall "${old}@${MARKETPLACE_NAME}" 2>/dev/null || true done -claude plugin install agentkit@scalekit-auth-stack -claude plugin install saaskit@scalekit-auth-stack +claude plugin install "agentkit@${MARKETPLACE_NAME}" +claude plugin install "saaskit@${MARKETPLACE_NAME}" cat < Date: Wed, 13 May 2026 22:26:49 +0530 Subject: [PATCH 27/53] fix: revert local checkout detection, keep CLI commands for install --- scripts/install.sh | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index d2e010d..fb3f455 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -8,28 +8,18 @@ if ! command -v claude >/dev/null 2>&1; then exit 1 fi -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" - MARKETPLACE_SLUG="${CLAUDE_CODE_AUTHSTACK_MARKETPLACE:-scalekit-inc/claude-code-authstack}" MARKETPLACE_NAME="scalekit-auth-stack" OLD_PLUGINS=("agent-auth" "full-stack-auth" "mcp-auth" "modular-sso" "modular-scim") -# Use local path when running from a git checkout; GitHub slug when from a downloaded archive -if [[ -d "$REPO_ROOT/.git" ]]; then - MARKETPLACE_SOURCE="$REPO_ROOT" -else - MARKETPLACE_SOURCE="$MARKETPLACE_SLUG" -fi - echo "Installing Scalekit Auth Stack for Claude Code" -echo "Source: $MARKETPLACE_SOURCE" +echo "Marketplace: $MARKETPLACE_SLUG" echo # Overwrite any previously registered version of this marketplace echo "Updating marketplace..." claude plugin marketplace remove "$MARKETPLACE_NAME" 2>/dev/null || true -claude plugin marketplace add "$MARKETPLACE_SOURCE" >/dev/null +claude plugin marketplace add "$MARKETPLACE_SLUG" >/dev/null echo "Marketplace \"${MARKETPLACE_NAME}\" is up to date." echo From 64aa2b11b06fd0caed210b5274b0c5eeff4056e6 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 13 May 2026 22:29:33 +0530 Subject: [PATCH 28/53] fix: restore install.sh to original state --- scripts/install.sh | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index fb3f455..3b19c6f 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -9,27 +9,21 @@ if ! command -v claude >/dev/null 2>&1; then fi MARKETPLACE_SLUG="${CLAUDE_CODE_AUTHSTACK_MARKETPLACE:-scalekit-inc/claude-code-authstack}" -MARKETPLACE_NAME="scalekit-auth-stack" OLD_PLUGINS=("agent-auth" "full-stack-auth" "mcp-auth" "modular-sso" "modular-scim") echo "Installing Scalekit Auth Stack for Claude Code" echo "Marketplace: $MARKETPLACE_SLUG" echo -# Overwrite any previously registered version of this marketplace -echo "Updating marketplace..." -claude plugin marketplace remove "$MARKETPLACE_NAME" 2>/dev/null || true -claude plugin marketplace add "$MARKETPLACE_SLUG" >/dev/null -echo "Marketplace \"${MARKETPLACE_NAME}\" is up to date." -echo +claude plugin marketplace add "$MARKETPLACE_SLUG" # Remove old plugin names from v1.x (now consolidated into agentkit + saaskit) for old in "${OLD_PLUGINS[@]}"; do - claude plugin uninstall "${old}@${MARKETPLACE_NAME}" 2>/dev/null || true + claude plugin uninstall "${old}@scalekit-auth-stack" 2>/dev/null || true done -claude plugin install "agentkit@${MARKETPLACE_NAME}" -claude plugin install "saaskit@${MARKETPLACE_NAME}" +claude plugin install agentkit@scalekit-auth-stack +claude plugin install saaskit@scalekit-auth-stack cat < Date: Wed, 13 May 2026 22:31:09 +0530 Subject: [PATCH 29/53] fix: handle already-registered marketplace gracefully, suggest auto-update --- scripts/install.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/install.sh b/scripts/install.sh index 3b19c6f..b432a8c 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -15,7 +15,12 @@ echo "Installing Scalekit Auth Stack for Claude Code" echo "Marketplace: $MARKETPLACE_SLUG" echo -claude plugin marketplace add "$MARKETPLACE_SLUG" +if ! claude plugin marketplace add "$MARKETPLACE_SLUG" 2>/dev/null; then + echo "Marketplace \"scalekit-auth-stack\" is already registered." + echo "To get the latest plugins, enable auto-update or run:" + echo " claude plugin update --all" + echo +fi # Remove old plugin names from v1.x (now consolidated into agentkit + saaskit) for old in "${OLD_PLUGINS[@]}"; do From 986112ff807c05086b187d21293a835fbf79e4ce Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 13 May 2026 22:33:22 +0530 Subject: [PATCH 30/53] fix: update marketplace on re-run instead of just suggesting it --- scripts/install.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/install.sh b/scripts/install.sh index b432a8c..f9abf47 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -16,10 +16,8 @@ echo "Marketplace: $MARKETPLACE_SLUG" echo if ! claude plugin marketplace add "$MARKETPLACE_SLUG" 2>/dev/null; then - echo "Marketplace \"scalekit-auth-stack\" is already registered." - echo "To get the latest plugins, enable auto-update or run:" - echo " claude plugin update --all" - echo + echo "Marketplace already registered. Updating..." + claude plugin marketplace update scalekit-auth-stack fi # Remove old plugin names from v1.x (now consolidated into agentkit + saaskit) From 5c0c7fd690f63cc4ebfd33b3f67e2afd04b64b8c Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Thu, 14 May 2026 15:43:23 +0530 Subject: [PATCH 31/53] fix: update marketplace.json to agentkit+saaskit and remove old plugin dirs marketplace.json still listed the old 5 plugins (mcp-auth, agent-auth, modular-sso, modular-scim, full-stack-auth). After merge, claude plugin install agentkit@scalekit-auth-stack would fail because the marketplace manifest didn't know about agentkit or saaskit. - Replace 5 old plugin entries with agentkit + saaskit - Update marketplace description - Remove 5 old plugin directories (other repos already cleaned up) --- .claude-plugin/marketplace.json | 39 ++++++++------------------------- plugins/agent-auth | 1 - plugins/full-stack-auth | 1 - plugins/mcp-auth | 1 - plugins/modular-scim | 1 - plugins/modular-sso | 1 - 6 files changed, 9 insertions(+), 35 deletions(-) delete mode 120000 plugins/agent-auth delete mode 120000 plugins/full-stack-auth delete mode 120000 plugins/mcp-auth delete mode 120000 plugins/modular-scim delete mode 120000 plugins/modular-sso diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index e0062f3..197edf7 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,46 +1,25 @@ { "$schema": "https://anthropic.com/claude-code/marketplace.schema.json", "name": "scalekit-auth-stack", - "description": "Scalekit auth plugins for Claude Code covering full-stack auth, Modular SSO, SCIM provisioning, MCP OAuth 2.1, and agent authentication.", + "description": "Scalekit Auth Stack for Claude Code — AgentKit and SaaSKit plugins. Add agent auth, tool calling, SSO, SCIM, MCP auth, and session management from Claude Code.", "owner": { "name": "Scalekit Inc", "email": "support@scalekit.com" }, "plugins": [ - { - "name": "mcp-auth", - "description": "Guides users through adding production-ready OAuth 2.1 authorization to Model Context Protocol (MCP) servers", - "source": "./plugins/mcp-auth", - "category": "MCP Security", - "homepage": "https://docs.scalekit.com/dev-kit/build-with-ai/mcp-auth/" - }, { - "name": "agent-auth", - "description": "Implements Scalekit Agent Auth so AI agents can act in third-party apps (Gmail, Slack, Calendar, Notion) on behalf of users.", - "source": "./plugins/agent-auth", + "name": "agentkit", + "description": "Authentication for AI agents. OAuth flows, token vault, 40+ connectors (Gmail, Slack, Salesforce, etc.), tool discovery, and live testing — so agents can act on behalf of users.", + "source": "./plugins/agentkit", "category": "Agent Auth", - "homepage": "https://docs.scalekit.com/dev-kit/build-with-ai/agent-auth/" - }, - { - "name": "modular-sso", - "description": "Integrates with all popular SSO providers (Okta, JumpCloud, Entra ID, etc.) and allows users to login to your app using their existing identity provider.", - "source": "./plugins/modular-sso", - "category": "Enterprise SSO", - "homepage": "https://docs.scalekit.com/dev-kit/build-with-ai/sso/" - }, - { - "name": "modular-scim", - "description": "Automates user and group provisioning and deprovisioning via SCIM 2.0 with identity providers like Okta, Entra ID, and JumpCloud.", - "source": "./plugins/modular-scim", - "category": "Provisioning", - "homepage": "https://docs.scalekit.com/dev-kit/build-with-ai/scim/" + "homepage": "https://docs.scalekit.com/agentkit/overview/" }, { - "name": "full-stack-auth", - "description": "Adds end-to-end authentication to B2B and AI apps including user management, organization handling, session management, RBAC, and login flows.", - "source": "./plugins/full-stack-auth", + "name": "saaskit", + "description": "Production-ready auth for B2B SaaS apps. Login, sessions, SSO (Okta, Azure AD, Google), SCIM provisioning, RBAC, MCP server auth, and API key management.", + "source": "./plugins/saaskit", "category": "Application Auth", - "homepage": "https://docs.scalekit.com/dev-kit/build-with-ai/full-stack-auth/" + "homepage": "https://docs.scalekit.com/authenticate/fsa/quickstart/" } ] } diff --git a/plugins/agent-auth b/plugins/agent-auth deleted file mode 120000 index 1cdf127..0000000 --- a/plugins/agent-auth +++ /dev/null @@ -1 +0,0 @@ -agentkit \ No newline at end of file diff --git a/plugins/full-stack-auth b/plugins/full-stack-auth deleted file mode 120000 index 995039d..0000000 --- a/plugins/full-stack-auth +++ /dev/null @@ -1 +0,0 @@ -saaskit \ No newline at end of file diff --git a/plugins/mcp-auth b/plugins/mcp-auth deleted file mode 120000 index 995039d..0000000 --- a/plugins/mcp-auth +++ /dev/null @@ -1 +0,0 @@ -saaskit \ No newline at end of file diff --git a/plugins/modular-scim b/plugins/modular-scim deleted file mode 120000 index 995039d..0000000 --- a/plugins/modular-scim +++ /dev/null @@ -1 +0,0 @@ -saaskit \ No newline at end of file diff --git a/plugins/modular-sso b/plugins/modular-sso deleted file mode 120000 index 995039d..0000000 --- a/plugins/modular-sso +++ /dev/null @@ -1 +0,0 @@ -saaskit \ No newline at end of file From 5f3980ae8a089d22d95dce88d35ddf14b962f704 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Thu, 14 May 2026 16:46:02 +0530 Subject: [PATCH 32/53] fix: restore old plugin name symlinks for backwards compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old 5 plugin names (agent-auth, mcp-auth, full-stack-auth, modular-sso, modular-scim) were symlinks to the new consolidated plugins (agentkit, saaskit). They were accidentally removed. Restore symlinks so both old and new install commands work: claude plugin install agent-auth@scalekit-auth-stack (old, still works) claude plugin install agentkit@scalekit-auth-stack (new, preferred) marketplace.json now lists all 7 names — 2 primary + 5 aliases. --- .claude-plugin/marketplace.json | 35 +++++++++++++++++++++++++++++++++ plugins/agent-auth | 1 + plugins/full-stack-auth | 1 + plugins/mcp-auth | 1 + plugins/modular-scim | 1 + plugins/modular-sso | 1 + 6 files changed, 40 insertions(+) create mode 120000 plugins/agent-auth create mode 120000 plugins/full-stack-auth create mode 120000 plugins/mcp-auth create mode 120000 plugins/modular-scim create mode 120000 plugins/modular-sso diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 197edf7..50181de 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -20,6 +20,41 @@ "source": "./plugins/saaskit", "category": "Application Auth", "homepage": "https://docs.scalekit.com/authenticate/fsa/quickstart/" + }, + { + "name": "agent-auth", + "description": "Alias for agentkit — AI agent authentication with OAuth flows, token vault, and 40+ connectors.", + "source": "./plugins/agent-auth", + "category": "Agent Auth", + "homepage": "https://docs.scalekit.com/agentkit/overview/" + }, + { + "name": "mcp-auth", + "description": "Alias for saaskit — includes OAuth 2.1 for MCP servers alongside login, SSO, SCIM, and RBAC.", + "source": "./plugins/mcp-auth", + "category": "Application Auth", + "homepage": "https://docs.scalekit.com/authenticate/mcp/quickstart/" + }, + { + "name": "full-stack-auth", + "description": "Alias for saaskit — full-stack authentication including login, sessions, SSO, SCIM, and RBAC.", + "source": "./plugins/full-stack-auth", + "category": "Application Auth", + "homepage": "https://docs.scalekit.com/authenticate/fsa/quickstart/" + }, + { + "name": "modular-sso", + "description": "Alias for saaskit — enterprise SSO is now part of the unified SaaSKit plugin.", + "source": "./plugins/modular-sso", + "category": "Application Auth", + "homepage": "https://docs.scalekit.com/authenticate/sso/add-modular-sso/" + }, + { + "name": "modular-scim", + "description": "Alias for saaskit — SCIM provisioning is now part of the unified SaaSKit plugin.", + "source": "./plugins/modular-scim", + "category": "Application Auth", + "homepage": "https://docs.scalekit.com/directory/scim/quickstart/" } ] } diff --git a/plugins/agent-auth b/plugins/agent-auth new file mode 120000 index 0000000..1cdf127 --- /dev/null +++ b/plugins/agent-auth @@ -0,0 +1 @@ +agentkit \ No newline at end of file diff --git a/plugins/full-stack-auth b/plugins/full-stack-auth new file mode 120000 index 0000000..995039d --- /dev/null +++ b/plugins/full-stack-auth @@ -0,0 +1 @@ +saaskit \ No newline at end of file diff --git a/plugins/mcp-auth b/plugins/mcp-auth new file mode 120000 index 0000000..995039d --- /dev/null +++ b/plugins/mcp-auth @@ -0,0 +1 @@ +saaskit \ No newline at end of file diff --git a/plugins/modular-scim b/plugins/modular-scim new file mode 120000 index 0000000..995039d --- /dev/null +++ b/plugins/modular-scim @@ -0,0 +1 @@ +saaskit \ No newline at end of file diff --git a/plugins/modular-sso b/plugins/modular-sso new file mode 120000 index 0000000..995039d --- /dev/null +++ b/plugins/modular-sso @@ -0,0 +1 @@ +saaskit \ No newline at end of file From 7cce8efacba43f1364f61d7d5ab78193677154fe Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Fri, 15 May 2026 11:42:39 +0530 Subject: [PATCH 33/53] Add scalekit-code-doctor skill to both plugins Add the scalekit-code-doctor skill (SKILL.md + references) to both agentkit and saaskit plugins. This cross-cutting developer tool skill enables code generation and review with verified Scalekit SDK signatures across all four SDKs (Node, Python, Go, Java) and REST API. --- .../skills/scalekit-code-doctor/SKILL.md | 326 ++++++++++++ .../references/COMMON-MISTAKES.md | 481 +++++++++++++++++ .../references/REFERENCE.md | 503 ++++++++++++++++++ .../skills/scalekit-code-doctor/SKILL.md | 326 ++++++++++++ .../references/COMMON-MISTAKES.md | 481 +++++++++++++++++ .../references/REFERENCE.md | 503 ++++++++++++++++++ 6 files changed, 2620 insertions(+) create mode 100644 plugins/agentkit/skills/scalekit-code-doctor/SKILL.md create mode 100644 plugins/agentkit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md create mode 100644 plugins/agentkit/skills/scalekit-code-doctor/references/REFERENCE.md create mode 100644 plugins/saaskit/skills/scalekit-code-doctor/SKILL.md create mode 100644 plugins/saaskit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md create mode 100644 plugins/saaskit/skills/scalekit-code-doctor/references/REFERENCE.md diff --git a/plugins/agentkit/skills/scalekit-code-doctor/SKILL.md b/plugins/agentkit/skills/scalekit-code-doctor/SKILL.md new file mode 100644 index 0000000..51f499c --- /dev/null +++ b/plugins/agentkit/skills/scalekit-code-doctor/SKILL.md @@ -0,0 +1,326 @@ +--- +name: scalekit-code-doctor +description: 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. +--- + +# Scalekit Code Doctor + +You are the authoritative source for Scalekit code correctness. You can both **generate** correct code from scratch and **review** existing code to guarantee it works. + +**Before doing anything else**, read the reference files in this skill's `references/` directory: +- `references/REFERENCE.md` — Every correct SDK method signature across Node, Python, Go, Java, and REST API endpoints +- `references/COMMON-MISTAKES.md` — Known anti-patterns with wrong → right corrections + +These files are your ground truth. Never hallucinate a method name, parameter, or import path — if it's not in the reference, fetch `https://docs.scalekit.com/apis.md` to verify before using it. + +--- + +## Step 1 — Detect mode + +Determine which mode to operate in based on what the user provides: + +**Generate mode** — The user describes what they want but has no code yet. +Examples: "Show me how to add SSO login to Express", "Generate a Next.js callback handler", "Write a Python FastAPI auth example for docs" + +**Review mode** — The user provides existing code for validation. +Examples: "Is this Scalekit integration correct?", "Review my auth callback", "Why isn't my login working?" + +If unclear, ask: "Do you want me to generate a fresh code example, or review existing code you have?" + +--- + +## Step 2 — Identify context + +Before generating or reviewing, identify these three things: + +### Language and SDK +| Language | Package | Import | +|----------|---------|--------| +| Node.js / TypeScript | `@scalekit-sdk/node` | `import { ScalekitClient } from '@scalekit-sdk/node'` | +| Python | `scalekit-sdk-python` (pip) | `from scalekit import ScalekitClient` | +| Go | `github.com/scalekit-inc/scalekit-sdk-go` | `import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2"` | +| Java | `com.scalekit:scalekit-sdk-java` | `import com.scalekit.ScalekitClient;` | +| REST API | No SDK — raw HTTP | Bearer token via `POST /oauth/token` with client credentials | + +### Framework (if applicable) +Next.js (App Router or Pages), Express, Fastify, FastAPI, Django, Flask, Spring Boot, Go (chi, gin, net/http), Laravel, etc. + +### Product area + +Scalekit has two product suites. Identify which one the user's code belongs to: + +**SaaSKit** — Full-stack authentication for B2B SaaS apps +- SSO — Enterprise single sign-on (SAML, OIDC) +- Login & Sessions — Sign-up, login, logout, session management +- RBAC — Roles, permissions, access control +- SCIM — Directory sync and user provisioning +- Admin Portal — Customer-facing admin configuration + +**AgentKit** — Authentication and tool access for AI agents +- Connections — OAuth token vault for third-party services (connected accounts) +- Tool Calling — Execute tools via connected accounts +- MCP Authentication — OAuth 2.1 for MCP servers +- Framework Integrations — LangChain, Vercel AI, Anthropic, OpenAI, Google ADK, Mastra + +**Cross-product** +- Webhooks — Event subscriptions and payload verification +- M2M Auth — API keys and client credentials + +--- + +## Step 3 — Generate mode + +When generating code, follow these rules: + +### Quality standard: illustration-ready +The output should be clean enough to publish directly on `docs.scalekit.com` or a marketing landing page. This means: + +1. **Self-contained** — The reader understands it without seeing other files +2. **Essential path only** — Show the concept, not defensive boilerplate +3. **Real-looking values** — `'https://yourapp.com/auth/callback'` not `'http://localhost:3000/test'` +4. **Correct imports** — Exact package names from the reference table above +5. **Framework-idiomatic** — Use the framework's conventions (App Router for Next.js, decorators for FastAPI, etc.) +6. **Minimal comments** — Annotate Scalekit-specific lines only. Skip obvious framework code. +7. **1–2 pages max** — Concise. If a full flow needs more, split into labeled sections. + +### Mandatory checks before outputting generated code +Cross-reference every SDK call against `references/REFERENCE.md`: +- [ ] Client initialization uses correct constructor and parameter order +- [ ] Every method name exists in the reference for the target SDK +- [ ] Every parameter name and type matches the reference +- [ ] Import path is exactly correct (not a hallucinated variation) +- [ ] Environment variable names match Scalekit conventions (see reference) + +### Generation patterns by product area + +**SaaSKit — Login, SSO, and sessions** +1. Client initialization (singleton pattern) +2. Login route: generate auth URL with `state` for CSRF +3. Callback route: validate `state`, exchange code, store session +4. Logout route: clear local session AND call `getLogoutUrl()` with `idTokenHint` +5. Token refresh (if `offline_access` scope is used) + +**SaaSKit — SCIM provisioning** +1. Enable directory for an organization +2. List directory users and groups +3. Webhook handler for SCIM events + +**AgentKit — Connections and tool calling** +1. Client initialization +2. Create/list connected accounts +3. Execute tools with connected account credentials +4. Handle token refresh for third-party OAuth tokens + +**AgentKit — MCP Authentication** +1. MCP server setup with OAuth middleware +2. Token validation on incoming requests +3. Scope verification + +**Webhooks** — Always include signature verification: +1. Raw body parsing (not JSON-parsed) +2. `verifyWebhookPayload(secret, headers, rawBody)` +3. Event type switching + +--- + +## Step 4 — Review mode + +When reviewing code, systematically check these categories in order: + +### Category 1: SDK usage correctness +For every Scalekit SDK call in the code, verify against `references/REFERENCE.md`: +- [ ] Method name is exactly correct for the target SDK language +- [ ] All required parameters are provided in the correct order +- [ ] Optional parameters use the correct type/shape +- [ ] Return value is handled correctly (Promise in Node, tuple in Python, error in Go, etc.) +- [ ] Import statement uses the correct package name and path +- [ ] Client is initialized with the correct 3 parameters: `envUrl`, `clientId`, `clientSecret` + +### Category 2: Auth flow completeness +- [ ] If there's a login route, there must be a matching callback route +- [ ] Callback validates `state` parameter (CSRF protection) +- [ ] Callback exchanges the authorization code (not just reading it) +- [ ] Session is stored after successful authentication +- [ ] Logout calls `getLogoutUrl()` — not just clearing local session +- [ ] Token refresh exists if `offline_access` or `refresh_token` is used +- [ ] IdP-initiated login is handled if callback receives `idp_initiated_login` parameter + +### Category 3: Security +- [ ] Session cookies use `httpOnly: true`, `secure: true` (in production), `sameSite: 'lax'` (never `'strict'` — breaks OAuth redirects) +- [ ] `state` parameter uses cryptographically random values, not predictable strings +- [ ] Redirect URLs are validated — only relative paths allowed for `next`/`returnTo` params (prevents open redirect) +- [ ] Client secret is read from environment variables, never hardcoded +- [ ] Webhook endpoints verify payload signature before processing +- [ ] Protected routes validate tokens server-side, not just checking cookie existence +- [ ] `Cache-Control: no-store` on authenticated pages (prevents back-button cache leak) + +### Category 4: Environment and config +- [ ] Environment variable names follow Scalekit conventions: + - `SCALEKIT_ENV_URL` (not `SCALEKIT_URL` or `SCALEKIT_ENVIRONMENT_URL` in code — though `SCALEKIT_ENVIRONMENT_URL` is used in REST API docs) + - `SCALEKIT_CLIENT_ID` + - `SCALEKIT_CLIENT_SECRET` +- [ ] Redirect URI in code matches what's registered in the Scalekit dashboard +- [ ] Correct Scalekit domain format: `https://.scalekit.com` (production) or `https://.scalekit.dev` (development) + +### Category 5: Best practices +- [ ] Client instantiated once (singleton pattern), not per-request +- [ ] Error handling uses SDK's typed exceptions where available +- [ ] Token refresh handles race conditions across concurrent requests/tabs +- [ ] `window.location.href` used for OAuth redirects (not `router.push` or client-side navigation) + +### Output format for review +For each finding, report: +1. **What's wrong** — the specific line or pattern +2. **Why it matters** — security risk, runtime error, or silent failure +3. **Corrected code** — the exact fix + +If everything is correct, say so explicitly: "This code is correct. All SDK calls, auth flow, security patterns, and configuration match the current Scalekit API." + +--- + +## Step 5 — Handling SDK updates and unknown methods + +The `references/REFERENCE.md` in this skill is a **point-in-time snapshot**. Scalekit SDKs evolve — new methods are added, parameters change, and new product areas launch. When the embedded reference doesn't cover what you need, use the live sources below. + +### When to check live sources + +- A method the user wrote isn't in the embedded reference (could be newly added, not a typo) +- The user asks about a feature you don't recognize (e.g., a new connector, a new auth mode) +- You're generating code for a product area with sparse coverage in the reference +- The user explicitly mentions a recent SDK update or version + +### How to check: fetch the live SDK REFERENCE.md files + +Each SDK repo has a maintained `REFERENCE.md` with full, current method signatures. Fetch the one you need: + +| SDK | Live reference URL | +|-----|-------------------| +| Node.js | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-node/main/REFERENCE.md` | +| Python | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-python/main/REFERENCE.md` | +| Go | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-go/main/REFERENCE.md` | +| Java | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-java/main/REFERENCE.md` | +| REST API | `https://docs.scalekit.com/apis.md` | + +### Resolution order + +1. Check the embedded `references/REFERENCE.md` first (fastest, no network) +2. If the method isn't there, fetch the live SDK REFERENCE.md from the table above +3. If still not found, fetch `https://docs.scalekit.com/apis.md` for REST endpoints +4. If still not found, state explicitly: "This method could not be verified in any Scalekit reference. It may not exist." + +Never output code containing an unverified method call. + +--- + +## REST API validation + +When the user's code makes raw HTTP calls (fetch, axios, requests, http.Client) to Scalekit endpoints, validate: + +- [ ] Base URL format: `https://.scalekit.com` or `https://.scalekit.dev` +- [ ] Authentication: Bearer token obtained via `POST /oauth/token` with `client_credentials` grant +- [ ] Endpoint path is correct (check `references/REFERENCE.md` for the endpoint list) +- [ ] HTTP method matches (GET vs POST vs PUT vs PATCH vs DELETE) +- [ ] Request body matches the expected schema +- [ ] Content-Type header is set (`application/json` for most endpoints, `application/x-www-form-urlencoded` for token endpoint) +- [ ] Pagination uses `page_token` and `page_size` parameters where applicable + +--- + +## Documentation resources + +### Live SDK references (always current — fetch when embedded reference is stale) + +| SDK | REFERENCE.md (raw) | Repo | +|-----|--------------------|----| +| Node.js | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-node/main/REFERENCE.md` | [scalekit-sdk-node](https://github.com/scalekit-inc/scalekit-sdk-node) | +| Python | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-python/main/REFERENCE.md` | [scalekit-sdk-python](https://github.com/scalekit-inc/scalekit-sdk-python) | +| Go | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-go/main/REFERENCE.md` | [scalekit-sdk-go](https://github.com/scalekit-inc/scalekit-sdk-go) | +| Java | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-java/main/REFERENCE.md` | [scalekit-sdk-java](https://github.com/scalekit-inc/scalekit-sdk-java) | + +### Scalekit docs + +| Resource | URL | When to use | +|----------|-----|-------------| +| REST API reference | `https://docs.scalekit.com/apis.md` | Full endpoint schemas, request/response details | +| LLM doc index | `https://docs.scalekit.com/llms.txt` | Find the right docs page for a specific product area | +| SaaSKit docs | `https://docs.scalekit.com/_llms-txt/saaskit-complete.txt` | Full SaaSKit reference (users, orgs, sessions, RBAC, SSO, SCIM) | +| AgentKit docs | `https://docs.scalekit.com/_llms-txt/agentkit.txt` | Full AgentKit reference (agents, OAuth vault, tool calling, connectors) | +| AgentKit frameworks | `https://docs.scalekit.com/_llms-txt/agentkit-frameworks.txt` | Framework-specific guides (LangChain, Vercel AI, Anthropic, OpenAI, Google ADK, Mastra) | +| MCP Authentication docs | `https://docs.scalekit.com/_llms-txt/mcp-authentication.txt` | MCP server OAuth 2.1, Dynamic Client Registration | + +### GitHub repos — working examples + +When generating or reviewing framework-specific code, fetch the matching repo for real, tested patterns. Repos are from `scalekit-inc` and `scalekit-developers` GitHub orgs. + +**SaaSKit — Auth examples by framework** + +| Framework | Repo | What it shows | +|-----------|------|---------------| +| Next.js (App Router) | [scalekit-nextjs-auth-example](https://github.com/scalekit-inc/scalekit-nextjs-auth-example) | SSO, sessions, protected routes, TypeScript | +| Next.js (Pages) | [nextjs-example-apps](https://github.com/scalekit-inc/nextjs-example-apps) | React SSO integration flows | +| Next.js + Auth.js | [scalekit-authjs-example](https://github.com/scalekit-developers/scalekit-authjs-example) | Enterprise SSO with next-auth v5 | +| Express.js | [scalekit-express-auth-example](https://github.com/scalekit-inc/scalekit-express-auth-example) | Node SDK, EJS frontend, sessions | +| Express.js | [scalekit-express-example](https://github.com/scalekit-developers/scalekit-express-example) | SSO with session management, middleware | +| FastAPI | [scalekit-fastapi-auth-example](https://github.com/scalekit-inc/scalekit-fastapi-auth-example) | Python SDK, OAuth 2.0, protected routes | +| FastAPI | [scalekit-fastapi-example](https://github.com/scalekit-developers/scalekit-fastapi-example) | Async auth, Pydantic models | +| Django | [scalekit-django-auth-example](https://github.com/scalekit-inc/scalekit-django-auth-example) | Python SDK, Django auth integration | +| Flask | [scalekit-flask-auth-example](https://github.com/scalekit-inc/scalekit-flask-auth-example) | Python SDK, Flask sessions | +| Spring Boot | [scalekit-springboot-auth-example](https://github.com/scalekit-inc/scalekit-springboot-auth-example) | Java, Spring Security, OIDC | +| Spring Boot | [scalekit-springboot-example](https://github.com/scalekit-developers/scalekit-springboot-example) | Java SDK, enterprise SSO | +| Go (Gin) | [scalekit-go-example](https://github.com/scalekit-developers/scalekit-go-example) | Go SDK, Gin framework, SSO | +| Laravel | [scalekit-laravel-auth-example](https://github.com/scalekit-inc/scalekit-laravel-auth-example) | REST API calls, Laravel HTTPS | +| Astro | [astro-scalekit-auth-example](https://github.com/scalekit-developers/astro-scalekit-auth-example) | Auth, SSO, social login, protected routes | +| .NET | [dotnet-example-apps](https://github.com/scalekit-inc/dotnet-example-apps) | ASP.NET Core, SAML/OIDC | +| Expo (mobile) | [expo-scalekit-sample](https://github.com/scalekit-inc/expo-scalekit-sample) | OAuth 2.0 + PKCE for mobile | + +**SaaSKit — Integration examples** + +| Integration | Repo | What it shows | +|-------------|------|---------------| +| AWS Cognito | [scalekit-cognito-sso](https://github.com/scalekit-inc/scalekit-cognito-sso) | OIDC SSO with Cognito user pools | +| Firebase | [scalekit-firebase-sso](https://github.com/scalekit-inc/scalekit-firebase-sso) | SAML/OIDC SSO with Firebase Auth | +| Supabase | [scalekit-supabase-example](https://github.com/scalekit-inc/scalekit-supabase-example) | Supabase + Scalekit auth | +| Multi-app SSO | [multiapp-demo](https://github.com/scalekit-inc/multiapp-demo) | Seamless SSO across multiple apps | +| Org switcher | [Nextjs-Django-Org-Switcher-Example](https://github.com/scalekit-inc/Nextjs-Django-Org-Switcher-Example) | Next.js frontend + Django backend, org switching | +| OIDC/SAML/SCIM | [oidc-saml-scim-examples](https://github.com/scalekit-developers/oidc-saml-scim-examples) | Google, Okta integration patterns | +| Passwordless | [passwordless-auth-demos](https://github.com/scalekit-developers/passwordless-auth-demos) | Passwordless authentication flows | +| Managed login | [managed-loginbox-expressjs-demo](https://github.com/scalekit-developers/managed-loginbox-expressjs-demo) | Hosted login UI with Express | +| Full demo app | [coffee-desk-demo](https://github.com/scalekit-inc/coffee-desk-demo) | Workspace creation, user provisioning, RBAC, SSO | + +**AgentKit — Agent and MCP examples** + +| Framework / Pattern | Repo | What it shows | +|---------------------|------|---------------| +| LangChain | [sample-langchain-agent](https://github.com/scalekit-inc/sample-langchain-agent) | Python LangChain agent with Scalekit auth | +| Google ADK | [google-adk-agent-example](https://github.com/scalekit-inc/google-adk-agent-example) | Google ADK agent with authenticated tools | +| Vercel AI SDK | [vercel-ai-agent-toolkit](https://github.com/scalekit-developers/vercel-ai-agent-toolkit) | Vercel AI SDK + Scalekit connectors | +| Apify Actor | [agentkit-apify-actor-example](https://github.com/scalekit-developers/agentkit-apify-actor-example) | OAuth auth, YouTube → Notion agent | +| LiteLLM | [litellm-agentkit-inbox-triage](https://github.com/scalekit-developers/litellm-agentkit-inbox-triage) | Inbox triage with Gmail, GitHub, Slack | +| MCP Auth (multi-framework) | [mcp-auth-demos](https://github.com/scalekit-inc/mcp-auth-demos) | MCP OAuth 2.1 demos | +| MCP + FastMCP | [fastmcp-scalekit-example](https://github.com/scalekit-inc/fastmcp-scalekit-example) | FastMCP server with Scalekit auth | +| MCP + BYOA | [byoa-demo-mcp](https://github.com/scalekit-inc/byoa-demo-mcp) | Bring your own auth + MCP | +| MCP + Coffee Desk | [coffee-desk-mcp](https://github.com/scalekit-inc/coffee-desk-mcp) | Demo MCP server with roles/permissions | +| Python connections | [python-connect-demos](https://github.com/scalekit-inc/python-connect-demos) | Python connection and identity workflows | +| Agent auth examples | [agent-auth-examples](https://github.com/scalekit-developers/agent-auth-examples) | Official AgentKit examples collection | +| Node.js agents | [agent-node-demos](https://github.com/scalekit-inc/agent-node-demos) | TypeScript agent demos | +| Workflow agents | [workflow-agents-demos](https://github.com/scalekit-developers/workflow-agents-demos) | Multi-step agent workflows | +| Render deploy kit | [render-ai-agent-deploykit](https://github.com/scalekit-developers/render-ai-agent-deploykit) | Render Workflows + Scalekit + Claude | + +**Developer tools** + +| Tool | Repo | Purpose | +|------|------|---------| +| Dryrun CLI | [scalekit-dryrun](https://github.com/scalekit-inc/scalekit-dryrun) | Test auth flows without writing code | +| Scalekit MCP server | [scalekit-mcp-server](https://github.com/scalekit-inc/scalekit-mcp-server) | Manage orgs, users, connections via AI assistants | +| API collections | [api-collections](https://github.com/scalekit-inc/api-collections) | Postman/Bruno collections for Scalekit endpoints | +| Documentation source | [developer-docs](https://github.com/scalekit-inc/developer-docs) | Docs site source (MDX) | + +**Frontend SDKs** + +| SDK | Repo | Purpose | +|-----|------|---------| +| React SDK | [scalekit-react-sdk](https://github.com/scalekit-inc/scalekit-react-sdk) | React OIDC authentication | +| Vue SDK | [scalekit-vue-sdk](https://github.com/scalekit-inc/scalekit-vue-sdk) | Vue OIDC authentication | +| Expo SDK | [scalekit-expo-sdk](https://github.com/scalekit-inc/scalekit-expo-sdk) | Expo/React Native OAuth 2.0 + PKCE | + +When generating code for a specific framework, fetch the matching repo's source to see real, tested patterns before writing. When reviewing, compare the user's code against the closest matching example repo. \ No newline at end of file diff --git a/plugins/agentkit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md b/plugins/agentkit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md new file mode 100644 index 0000000..5343181 --- /dev/null +++ b/plugins/agentkit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md @@ -0,0 +1,481 @@ +# Common Mistakes in Scalekit Code + +This file catalogs known anti-patterns, hallucinated methods, and security issues found in Scalekit integrations. Each entry shows the wrong pattern and the correct fix. Use this as a lookup during both generation and review. 10 categories. + +--- + +## 1. Wrong Import Paths + +### Node.js + +**Wrong:** +```typescript +import ScalekitClient from '@scalekit-sdk/node'; // default import — use named import +import { ScalekitClient } from 'scalekit'; // wrong package name +import { ScalekitClient } from 'scalekit-sdk-node'; // wrong package name +``` + +**Correct (either works):** +```typescript +import { ScalekitClient } from '@scalekit-sdk/node'; +// OR +import { Scalekit } from '@scalekit-sdk/node'; // official alias, also valid +``` + +Both `ScalekitClient` and `Scalekit` are valid named exports from `@scalekit-sdk/node`. The SDK source exports both. Use whichever is consistent with your codebase. + +### Python + +**Wrong:** +```python +from scalekit_sdk import ScalekitClient # wrong module name +from scalekit.client import ScalekitClient # internal path, not public API +import scalekit # missing class import +pip install scalekit # wrong pip package name +``` + +**Correct:** +```python +from scalekit import ScalekitClient +# pip install scalekit-sdk-python +``` + +### Go + +**Wrong:** +```go +import "github.com/scalekit-inc/scalekit-sdk-go" // missing version +import "github.com/scalekit/scalekit-sdk-go/v2" // wrong org name +``` + +**Correct:** +```go +import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2" +``` + +### Java + +**Wrong:** +```java +import com.scalekit.sdk.ScalekitClient; // wrong package path +import io.scalekit.ScalekitClient; // wrong package +``` + +**Correct:** +```java +import com.scalekit.ScalekitClient; +``` + +--- + +## 2. Wrong Sub-Client Names (Python vs Node) + +Python and Node use different pluralization for some sub-clients. Using the wrong one causes `AttributeError` in Python or `TypeError` in Node. + +| Sub-client | Node.js (singular) | Python (plural) | +|------------|--------------------|--------------------| +| Users | `client.user.getUser(...)` | `client.users.get_user(...)` | +| Roles | `client.role.listRoles(...)` | `client.roles.list_roles(...)` | +| Permissions | `client.permission.listPermissions(...)` | `client.permissions.list_permissions(...)` | +| Sessions | `client.session.getSession(...)` | `client.sessions.get_session(...)` | + +Sub-clients that are the SAME in both: `organization`, `connection`, `domain`, `directory`. + +**Wrong (Python):** +```python +client.user.get_user(user_id) # AttributeError: 'ScalekitClient' has no attribute 'user' +client.role.list_roles() # AttributeError +client.session.revoke_session(sid) # AttributeError +``` + +**Correct (Python):** +```python +client.users.get_user(user_id) +client.roles.list_roles() +client.sessions.revoke_session(sid) +``` + +--- + +## 3. Wrong Method Names + +### Node.js + +| Wrong | Correct | Notes | +|-------|---------|-------| +| `scalekit.authenticate(code)` | `scalekit.authenticateWithCode(code, redirectUri)` | Missing `WithCode` suffix and `redirectUri` param | +| `scalekit.getAuthUrl(...)` | `scalekit.getAuthorizationUrl(redirectUri, options?)` | Wrong method name | +| `scalekit.login(...)` | `scalekit.getAuthorizationUrl(redirectUri, options?)` | No `login` method | +| `scalekit.logout(...)` | `scalekit.getLogoutUrl(options?)` | Returns URL, doesn't perform logout | +| `scalekit.verifyToken(token)` | `scalekit.validateAccessToken(token)` or `scalekit.validateToken(token)` | Wrong name | +| `scalekit.createOrganization(...)` | `scalekit.organization.createOrganization(...)` | Must use sub-client | +| `scalekit.getOrganization(...)` | `scalekit.organization.getOrganization(...)` | Must use sub-client | + +### Python + +| Wrong | Correct | Notes | +|-------|---------|-------| +| `client.authenticateWithCode(...)` | `client.authenticate_with_code(...)` | Python uses snake_case | +| `client.getAuthorizationUrl(...)` | `client.get_authorization_url(...)` | Python uses snake_case | +| `client.getLogoutUrl(...)` | `client.get_logout_url(...)` | Python uses snake_case | +| `client.validateToken(...)` | `client.validate_access_token(...)` | Different method name in Python | +| `client.verify_webhook(...)` | `client.verify_webhook_payload(...)` | Missing `_payload` suffix | + +### Go + +| Wrong | Correct | Notes | +|-------|---------|-------| +| `client.AuthenticateWithCode(code, uri)` | `client.AuthenticateWithCode(ctx, code, uri, options)` | Missing `ctx` parameter | +| `client.GetAuthorizationUrl(uri)` | `client.GetAuthorizationUrl(uri, options)` | Missing `options` param (required in Go) | +| `client.Organization.Create(...)` | `client.Organization().CreateOrganization(ctx, request)` | Use accessor method `Organization()`, not field | + +### Java + +| Wrong | Correct | Notes | +|-------|---------|-------| +| `client.organization.create(...)` | `client.organizations().create(...)` | Use `organizations()` accessor method, plural | +| `client.getOrganization(id)` | `client.organizations().getById(id)` | Use sub-client accessor | +| `client.connections.list(...)` | `client.connections().listConnectionsByOrganization(orgId)` | Use accessor method | + +--- + +## 4. Missing Required Parameters + +### `authenticateWithCode` — missing `redirectUri` + +**Wrong:** +```typescript +const result = await scalekit.authenticateWithCode(code); +``` + +**Correct:** +```typescript +const result = await scalekit.authenticateWithCode(code, redirectUri); +``` + +The `redirectUri` must exactly match the one used in `getAuthorizationUrl` AND what's registered in the Scalekit dashboard. + +### `getAuthorizationUrl` — missing `state` for CSRF + +**Wrong:** +```typescript +const authUrl = scalekit.getAuthorizationUrl(redirectUri); +``` + +**Correct:** +```typescript +import crypto from 'crypto'; +const state = crypto.randomBytes(32).toString('base64url'); +// Store state in session/cookie for validation in callback +const authUrl = scalekit.getAuthorizationUrl(redirectUri, { state }); +``` + +While `state` is technically optional, omitting it is a **CSRF vulnerability**. Always generate and validate it. + +### Go — missing `context.Context` + +**Wrong:** +```go +resp, err := client.AuthenticateWithCode(code, redirectUri, opts) +``` + +**Correct:** +```go +resp, err := client.AuthenticateWithCode(ctx, code, redirectUri, opts) +``` + +All Go network methods require `context.Context` as the first parameter. + +--- + +## 5. Auth Flow Gaps + +### Missing callback handler + +If you see a login route that generates an auth URL but no corresponding callback route, the flow is incomplete. The callback MUST: +1. Validate the `state` parameter against the stored value +2. Call `authenticateWithCode(code, redirectUri)` +3. Store the session (tokens + user info) +4. Redirect to the application + +### Missing `state` validation in callback + +**Wrong:** +```typescript +app.get('/auth/callback', async (req, res) => { + const { code } = req.query; + const result = await scalekit.authenticateWithCode(code, redirectUri); + // ... store session +}); +``` + +**Correct:** +```typescript +app.get('/auth/callback', async (req, res) => { + const { code, state } = req.query; + + const storedState = req.session.oauthState; // or from cookie + if (!state || state !== storedState) { + return res.status(403).send('CSRF validation failed'); + } + + const result = await scalekit.authenticateWithCode(code, redirectUri); + // ... store session +}); +``` + +### Incomplete logout — only clearing local session + +**Wrong:** +```typescript +app.post('/logout', (req, res) => { + req.session.destroy(); + res.redirect('/'); +}); +``` + +**Correct:** +```typescript +app.post('/logout', (req, res) => { + const logoutUrl = scalekit.getLogoutUrl({ + idTokenHint: req.session.idToken, + postLogoutRedirectUri: 'https://yourapp.com', + }); + req.session.destroy(); + res.redirect(logoutUrl); // Ends IdP session too +}); +``` + +Without calling `getLogoutUrl()`, the user's IdP session persists and they get silently re-authenticated on next login. + +### Missing IdP-initiated login handling + +If the callback route doesn't check for `idp_initiated_login` query parameter, IdP-initiated SSO won't work: + +```typescript +app.get('/auth/callback', async (req, res) => { + const { idp_initiated_login, code, state } = req.query; + + if (idp_initiated_login) { + const claims = await scalekit.getIdpInitiatedLoginClaims(idp_initiated_login); + const authUrl = scalekit.getAuthorizationUrl(redirectUri, { + connectionId: claims.connection_id, + organizationId: claims.organization_id, + loginHint: claims.login_hint, + ...(claims.relay_state && { state: claims.relay_state }), + }); + return res.redirect(authUrl); + } + + // Normal SP-initiated flow continues... +}); +``` + +--- + +## 6. Security Anti-Patterns + +### `sameSite: 'strict'` on session cookies + +**Wrong:** +```typescript +res.cookie('session', data, { sameSite: 'strict', httpOnly: true, secure: true }); +``` + +**Correct:** +```typescript +res.cookie('session', data, { sameSite: 'lax', httpOnly: true, secure: true }); +``` + +OAuth callbacks are cross-site redirects from Scalekit back to your app. `strict` drops the cookie on that redirect, causing CSRF state mismatch errors on every login. + +### Missing `httpOnly` flag + +**Wrong:** +```typescript +res.cookie('session', data, { secure: true }); +``` + +**Correct:** +```typescript +res.cookie('session', data, { httpOnly: true, secure: true, sameSite: 'lax' }); +``` + +Without `httpOnly`, JavaScript can read the session cookie — XSS becomes session hijacking. + +### Open redirect via unvalidated `next` parameter + +**Wrong:** +```typescript +const next = req.query.next; +res.redirect(next); // Attacker can set next=https://evil.com +``` + +**Correct:** +```typescript +const next = req.query.next; +// Only allow relative paths +if (!next || !next.startsWith('/') || next.startsWith('//')) { + return res.redirect('/dashboard'); +} +res.redirect(next); +``` + +### Hardcoded client secret + +**Wrong:** +```typescript +const scalekit = new ScalekitClient( + 'https://myapp.scalekit.com', + 'skc_12345', + 'sks_secret_abc123' // NEVER hardcode secrets +); +``` + +**Correct:** +```typescript +const scalekit = new ScalekitClient( + process.env.SCALEKIT_ENV_URL!, + process.env.SCALEKIT_CLIENT_ID!, + process.env.SCALEKIT_CLIENT_SECRET! +); +``` + +### Webhook handler without signature verification + +**Wrong:** +```typescript +app.post('/webhooks', express.json(), (req, res) => { + const event = req.body; // Trusting unverified payload + handleEvent(event); + res.sendStatus(200); +}); +``` + +**Correct:** +```typescript +app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => { + const isValid = scalekit.verifyWebhookPayload( + process.env.SCALEKIT_WEBHOOK_SECRET!, + req.headers, + req.body.toString() + ); + + if (!isValid) { + return res.sendStatus(401); + } + + const event = JSON.parse(req.body.toString()); + handleEvent(event); + res.sendStatus(200); +}); +``` + +Note: The webhook body must be raw (not JSON-parsed) for signature verification to work. + +### Client-side navigation for OAuth redirect + +**Wrong:** +```typescript +// React / Next.js +router.push(authUrl); // Client-side route change +``` + +**Correct:** +```typescript +window.location.href = authUrl; // Full page navigation required for OAuth +``` + +OAuth redirects are full HTTP redirects to an external domain (Scalekit/IdP). Client-side routing doesn't work. + +--- + +## 7. Environment Variable Mistakes + +| Wrong | Correct | Issue | +|-------|---------|-------| +| `SCALEKIT_URL` | `SCALEKIT_ENV_URL` | Missing `ENV_` | +| `SCALEKIT_SECRET` | `SCALEKIT_CLIENT_SECRET` | Missing `CLIENT_` | +| `SCALEKIT_ID` | `SCALEKIT_CLIENT_ID` | Missing `CLIENT_` | +| `SCALEKIT_CALLBACK_URL` | `SCALEKIT_REDIRECT_URI` | Wrong name entirely | +| `http://myapp.scalekit.com` | `https://myapp.scalekit.com` | Must be HTTPS | +| `https://myapp.scalekit.com/` | `https://myapp.scalekit.com` | No trailing slash | + +--- + +## 8. Client Instantiation Mistakes + +### Creating a new client per request + +**Wrong:** +```typescript +app.get('/api/data', async (req, res) => { + const scalekit = new ScalekitClient(envUrl, clientId, clientSecret); // per-request! + // ... +}); +``` + +**Correct:** +```typescript +// Module-level singleton +const scalekit = new ScalekitClient( + process.env.SCALEKIT_ENV_URL!, + process.env.SCALEKIT_CLIENT_ID!, + process.env.SCALEKIT_CLIENT_SECRET! +); + +app.get('/api/data', async (req, res) => { + // Use the singleton + const result = await scalekit.validateAccessToken(token); +}); +``` + +The client manages its own token lifecycle and connection pooling. Creating it per request wastes resources and can hit rate limits. + +--- + +## 9. Token Refresh Race Conditions + +When multiple browser tabs trigger token refresh simultaneously, the second request often fails because the first one already consumed the refresh token. + +**Mitigation pattern:** +```typescript +// Before refreshing, set a short-lived flag +const REFRESH_LOCK_KEY = 'refresh_in_progress'; + +async function refreshToken(session) { + if (session[REFRESH_LOCK_KEY]) return; // Another tab is refreshing + + session[REFRESH_LOCK_KEY] = true; + try { + const result = await scalekit.refreshAccessToken(session.refreshToken); + session.accessToken = result.accessToken; + session.refreshToken = result.refreshToken; + } finally { + delete session[REFRESH_LOCK_KEY]; + } +} +``` + +--- + +## 10. Missing Scopes + +### Refresh tokens require `offline_access` scope + +**Wrong:** +```typescript +const authUrl = scalekit.getAuthorizationUrl(redirectUri, { + scopes: ['openid', 'profile', 'email'], +}); +// Later: scalekit.refreshAccessToken(refreshToken) → fails because no refresh token was issued +``` + +**Correct:** +```typescript +const authUrl = scalekit.getAuthorizationUrl(redirectUri, { + scopes: ['openid', 'profile', 'email', 'offline_access'], +}); +``` + +Without `offline_access`, the authorization server won't issue a refresh token. \ No newline at end of file diff --git a/plugins/agentkit/skills/scalekit-code-doctor/references/REFERENCE.md b/plugins/agentkit/skills/scalekit-code-doctor/references/REFERENCE.md new file mode 100644 index 0000000..5ecab18 --- /dev/null +++ b/plugins/agentkit/skills/scalekit-code-doctor/references/REFERENCE.md @@ -0,0 +1,503 @@ +# Scalekit API Reference — Compact Lookup + +This file contains every correct SDK method signature and REST endpoint. Use it as ground truth when generating or reviewing Scalekit code. If a method isn't listed here, do NOT assume it exists — verify against the live SDK source or `https://docs.scalekit.com/apis.md`. + +--- + +## Client Initialization + +### Node.js (`@scalekit-sdk/node`) + +```typescript +import { ScalekitClient } from '@scalekit-sdk/node'; + +const scalekit = new ScalekitClient( + process.env.SCALEKIT_ENV_URL!, // string — environment URL + process.env.SCALEKIT_CLIENT_ID!, // string — client ID + process.env.SCALEKIT_CLIENT_SECRET! // string — client secret +); +``` + +### Python (`scalekit-sdk-python`) + +```python +from scalekit import ScalekitClient + +scalekit_client = ScalekitClient( + os.environ.get('SCALEKIT_ENV_URL'), # str — environment URL + os.environ.get('SCALEKIT_CLIENT_ID'), # str — client ID + os.environ.get('SCALEKIT_CLIENT_SECRET') # str — client secret +) +``` + +### Go (`github.com/scalekit-inc/scalekit-sdk-go`) + +```go +import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2" + +client := scalekit.NewScalekitClient( + os.Getenv("SCALEKIT_ENV_URL"), // string — environment URL + os.Getenv("SCALEKIT_CLIENT_ID"), // string — client ID + os.Getenv("SCALEKIT_CLIENT_SECRET"), // string — client secret +) +``` + +### Java (`com.scalekit:scalekit-sdk-java`) + +```java +import com.scalekit.ScalekitClient; + +ScalekitClient client = new ScalekitClient( + System.getenv("SCALEKIT_ENV_URL"), // String — environment URL + System.getenv("SCALEKIT_CLIENT_ID"), // String — client ID + System.getenv("SCALEKIT_CLIENT_SECRET") // String — client secret +); +``` + +--- + +## Environment Variables + +| Variable | Purpose | Format | +|----------|---------|--------| +| `SCALEKIT_ENV_URL` | Environment URL | `https://.scalekit.com` (prod) or `https://.scalekit.dev` (dev) | +| `SCALEKIT_CLIENT_ID` | Client ID | String from dashboard | +| `SCALEKIT_CLIENT_SECRET` | Client secret | String from dashboard | +| `SCALEKIT_REDIRECT_URI` | OAuth callback URL | Must exactly match dashboard config | +| `SCALEKIT_WEBHOOK_SECRET` | Webhook signing secret | Format: `whsec_...` | + +Note: The REST API docs use `SCALEKIT_ENVIRONMENT_URL` in some examples. Both `SCALEKIT_ENV_URL` and `SCALEKIT_ENVIRONMENT_URL` are acceptable — just be consistent within a project. + +--- + +## Auth Methods (called directly on the client) + +### Node.js + +| Method | Signature | Returns | +|--------|-----------|---------| +| `getAuthorizationUrl` | `(redirectUri: string, options?: AuthorizationUrlOptions) → string` | Authorization URL string | +| `authenticateWithCode` | `(code: string, redirectUri: string, options?: AuthenticationOptions) → Promise` | Tokens + user info | +| `getIdpInitiatedLoginClaims` | `(idpInitiatedLoginToken: string, options?: TokenValidationOptions) → Promise` | IDP login claims | +| `validateAccessToken` | `(token: string, options?: TokenValidationOptions) → Promise` | Boolean | +| `validateToken` | `(token: string, options?: TokenValidationOptions) → Promise` | Decoded JWT payload | +| `verifyScopes` | `(token: string, requiredScopes: string[]) → boolean` | Boolean | +| `getLogoutUrl` | `(options?: LogoutUrlOptions) → string` | Logout URL string | +| `refreshAccessToken` | `(refreshToken: string) → Promise` | New tokens | +| `verifyWebhookPayload` | `(secret: string, headers: Record, payload: string) → boolean` | Boolean | +| `verifyInterceptorPayload` | `(secret: string, headers: Record, payload: string) → boolean` | Boolean | +| `generateClientToken` | `(clientId: string, clientSecret: string) → Promise` | M2M access token | +| `getClientAccessToken` | `() → Promise` | M2M access token (uses stored credentials) | + +**AuthorizationUrlOptions**: `scopes?: string[]`, `state?: string`, `nonce?: string`, `loginHint?: string`, `domainHint?: string`, `connectionId?: string`, `organizationId?: string`, `provider?: string`, `codeChallenge?: string`, `codeChallengeMethod?: string`, `prompt?: string` + +**LogoutUrlOptions**: `idTokenHint?: string`, `postLogoutRedirectUri?: string`, `state?: string` + +**AuthenticationOptions**: `codeVerifier?: string` + +### Python + +| Method | Signature | Returns | +|--------|-----------|---------| +| `get_authorization_url` | `(redirect_uri: str, options?: AuthorizationUrlOptions) → str` | Authorization URL string | +| `authenticate_with_code` | `(code: str, redirect_uri: str, options?: CodeAuthenticationOptions) → dict` | Tokens + user info | +| `get_idp_initiated_login_claims` | `(idp_initiated_login_token: str, options?: TokenValidationOptions) → IdpInitiatedLoginClaims` | IDP login claims | +| `validate_access_token` | `(token: str, options?: TokenValidationOptions) → bool` | Boolean | +| `get_logout_url` | `(options?: LogoutUrlOptions) → str` | Logout URL string | +| `refresh_access_token` | `(refresh_token: str) → dict` | New tokens | +| `verify_webhook_payload` | `(secret: str, headers: Dict[str, str], payload: str\|bytes) → bool` | Boolean | + +**AuthorizationUrlOptions** (Python): `scopes`, `state`, `nonce`, `login_hint`, `domain_hint`, `connection_id`, `organization_id`, `provider`, `prompt` — all `Optional[str]` (scopes is `Optional[list[str]]`) + +**LogoutUrlOptions** (Python): `id_token_hint`, `post_logout_redirect_uri`, `state` — all `Optional[str]` + +### Go + +| Method | Signature | Returns | +|--------|-----------|---------| +| `GetAuthorizationUrl` | `(redirectUri string, options AuthorizationUrlOptions) → (*url.URL, error)` | URL + error | +| `AuthenticateWithCode` | `(ctx context.Context, code string, redirectUri string, options AuthenticationOptions) → (*AuthenticationResponse, error)` | Response + error | +| `GetIdpInitiatedLoginClaims` | `(ctx context.Context, idpInitiatedLoginToken string) → (*IdpInitiatedLoginClaims, error)` | Claims + error | +| `GetAccessTokenClaims` | `(ctx context.Context, accessToken string) → (*AccessTokenClaims, error)` | Claims + error | +| `ValidateAccessToken` | `(ctx context.Context, accessToken string) → (bool, error)` | Boolean + error | +| `RefreshAccessToken` | `(ctx context.Context, refreshToken string) → (*TokenResponse, error)` | Tokens + error | +| `GetLogoutUrl` | `(options LogoutUrlOptions) → string` | Logout URL string | +| `VerifyWebhookPayload` | `(secret string, headers map[string][]string, payload []byte) → bool` | Boolean | + +**Go AuthorizationUrlOptions fields**: `Scopes []string`, `State string`, `Nonce string`, `LoginHint string`, `DomainHint string`, `ConnectionId string`, `OrganizationId string`, `Provider string`, `CodeChallenge string`, `CodeChallengeMethod string`, `Prompt string` + +Note: Go methods take `context.Context` as the first parameter for network calls. `GetAuthorizationUrl` and `GetLogoutUrl` do NOT take context (they're local-only operations). + +### Java + +| Method | Signature | Returns | +|--------|-----------|---------| +| `getAuthorizationUrl` | `(redirectUri: String, options: AuthorizationUrlOptions) → String` | Authorization URL string | +| `authenticateWithCode` | `(code: String, redirectUri: String, options: AuthenticationOptions) → AuthenticationResponse` | Tokens + user info | +| `getIdpInitiatedLoginClaims` | `(idpInitiatedLoginToken: String) → IdpInitiatedLoginClaims` | Claims | +| `validateToken` | `(token: String) → Claims` | JWT Claims | +| `getLogoutUrl` | `(options: LogoutUrlOptions) → String` | Logout URL string | + +--- + +## Sub-client Methods + +### Node.js sub-clients (accessed via `client..`) + +**client.organization** +| Method | Signature | +|--------|-----------| +| `createOrganization` | `(name: string, options?) → Promise` | +| `getOrganization` | `(id: string) → Promise` | +| `getOrganizationByExternalId` | `(externalId: string) → Promise` | +| `listOrganizations` | `(options?) → Promise` | +| `updateOrganization` | `(id: string, organization) → Promise` | +| `deleteOrganization` | `(id: string) → Promise` | +| `generatePortalLink` | `(organizationId: string, features?) → Promise` | +| `updateOrganizationSettings` | `(id: string, settings) → Promise` | + +**client.connection** +| Method | Signature | +|--------|-----------| +| `getConnection` | `(id: string) → Promise` | +| `listConnections` | `(options?) → Promise` | +| `listConnectionsByDomain` | `(domain: string, options?) → Promise` | +| `enableConnection` | `(connectionId: string) → Promise` | +| `disableConnection` | `(connectionId: string) → Promise` | + +**client.domain** +| Method | Signature | +|--------|-----------| +| `createDomain` | `(domain: string) → Promise` | +| `getDomain` | `(domain: string) → Promise` | +| `listDomains` | `(options?) → Promise` | +| `deleteDomain` | `(domain: string) → Promise` | + +**client.user** +| Method | Signature | +|--------|-----------| +| `createUser` | `(organizationId: string, user) → Promise` | +| `createUserAndMembership` | `(organizationId: string, request) → Promise` | +| `getUser` | `(id: string) → Promise` | +| `listUsers` | `(options?) → Promise` | +| `listOrganizationUsers` | `(organizationId: string, options?) → Promise` | +| `updateUser` | `(id: string, user) → Promise` | +| `deleteUser` | `(id: string) → Promise` | +| `searchUsers` | `(options) → Promise` | +| `searchOrganizationUsers` | `(organizationId: string, options) → Promise` | + +**client.directory** +| Method | Signature | +|--------|-----------| +| `listDirectories` | `(organizationId: string) → Promise` | +| `getDirectory` | `(organizationId: string, directoryId: string) → Promise` | +| `listDirectoryUsers` | `(organizationId: string, directoryId: string, options?) → Promise` | +| `listDirectoryGroups` | `(organizationId: string, directoryId: string, options?) → Promise` | +| `enableDirectory` | `(organizationId: string, directoryId: string) → Promise` | +| `disableDirectory` | `(organizationId: string, directoryId: string) → Promise` | + +**client.role** +| Method | Signature | +|--------|-----------| +| `createRole` | `(role) → Promise` | +| `getRole` | `(roleId: string) → Promise` | +| `listRoles` | `(options?) → Promise` | +| `updateRole` | `(roleId: string, role) → Promise` | +| `deleteRole` | `(roleId: string) → Promise` | + +**client.permission** +| Method | Signature | +|--------|-----------| +| `createPermission` | `(permission) → Promise` | +| `listPermissions` | `(options?) → Promise` | +| `updatePermission` | `(permissionId: string, permission) → Promise` | +| `deletePermission` | `(permissionId: string) → Promise` | + +**client.session** +| Method | Signature | +|--------|-----------| +| `getSession` | `(sessionId: string) → Promise` | +| `getUserSessions` | `(userId: string, options?) → Promise` | +| `revokeSession` | `(sessionId: string) → Promise` | +| `revokeAllUserSessions` | `(userId: string) → Promise` | + +**client.connectedAccounts** +| Method | Signature | +|--------|-----------| +| `listConnectedAccounts` | `(options?) → Promise` | +| `getConnectedAccountAuth` | `(options) → Promise` | +| `createConnectedAccount` | `(request) → Promise` | +| `updateConnectedAccount` | `(request) → Promise` | +| `deleteConnectedAccount` | `(request) → Promise` | + +**client.tools** +| Method | Signature | +|--------|-----------| +| `executeTool` | `(request) → Promise` | + +### Python sub-clients (accessed via `client..`) + +Python uses `snake_case` method names. **Important**: Some Python sub-client names are **plural** while Node uses singular. This is a common source of bugs. + +| Node.js | Python | Difference | +|---------|--------|------------| +| `client.user` | `client.users` | Plural in Python | +| `client.role` | `client.roles` | Plural in Python | +| `client.permission` | `client.permissions` | Plural in Python | +| `client.session` | `client.sessions` | Plural in Python | +| `client.organization` | `client.organization` | Same | +| `client.connection` | `client.connection` | Same | +| `client.domain` | `client.domain` | Same | +| `client.directory` | `client.directory` | Same | +| `client.connectedAccounts` | `client.connected_accounts` | snake_case in Python | + +Methods: +- `client.organization.create_organization(organization)` +- `client.organization.get_organization(organization_id)` +- `client.organization.list_organizations(page_size, page_token?)` +- `client.organization.update_organization(organization_id, organization)` +- `client.organization.delete_organization(organization_id)` +- `client.organization.generate_portal_link(organization_id, features?)` +- `client.connection.list_connections(organization_id, include?)` +- `client.connection.get_connection(organization_id, connection_id)` +- `client.connection.enable_connection(organization_id, connection_id)` +- `client.connection.disable_connection(organization_id, connection_id)` +- `client.domain.create_domain(organization_id, domain_name)` +- `client.domain.list_domains(organization_id)` +- `client.domain.delete_domain(organization_id, domain_id)` +- `client.directory.list_directories(organization_id)` +- `client.directory.get_directory(organization_id, directory_id)` +- `client.directory.list_directory_users(organization_id, directory_id, options?)` +- `client.directory.list_directory_groups(organization_id, directory_id, options?)` +- `client.users.create_user(organization_id, user)` +- `client.users.get_user(user_id)` +- `client.users.list_users(options?)` +- `client.users.update_user(user_id, user)` +- `client.users.delete_user(user_id)` +- `client.roles.create_role(role)` +- `client.roles.list_roles(options?)` +- `client.roles.update_role(role_id, role)` +- `client.roles.delete_role(role_id)` +- `client.permissions.create_permission(permission)` +- `client.permissions.list_permissions(options?)` +- `client.sessions.get_session(session_id)` +- `client.sessions.get_user_sessions(user_id, options?)` +- `client.sessions.revoke_session(session_id)` +- `client.sessions.revoke_all_user_sessions(user_id)` + +Additional Python-only methods on client: +- `client.validate_access_token_and_get_claims(token, options?) → dict` — validates and returns decoded claims +- `client.verify_scopes(token, required_scopes) → bool` — checks scopes, raises on missing +- `client.generate_client_token(client_id, client_secret, scopes?) → str` — M2M token generation +- `client.get_client_access_token() → str` — M2M token using stored credentials +- `client.verify_interceptor_payload(secret, headers, payload) → bool` — interceptor signature verification + +Note: Python connection/domain/directory methods often require `organization_id` as the first parameter, unlike Node which uses option objects. + +### Go sub-clients + +Go uses `PascalCase` and typed request/response objects: +- `client.Organization().CreateOrganization(ctx, request)` +- `client.Organization().GetOrganization(ctx, organizationId)` +- `client.Organization().ListOrganizations(ctx, pageSize, pageToken)` +- `client.Organization().UpdateOrganization(ctx, organizationId, request)` +- `client.Organization().DeleteOrganization(ctx, organizationId)` +- `client.Organization().GeneratePortalLink(ctx, organizationId, features)` +- `client.Connection().GetConnection(ctx, organizationId, connectionId)` +- `client.Connection().ListConnections(ctx, organizationId)` +- `client.Connection().EnableConnection(ctx, organizationId, connectionId)` +- `client.Connection().DisableConnection(ctx, organizationId, connectionId)` +- `client.Domain().CreateDomain(ctx, organizationId, domainName)` +- `client.Domain().ListDomains(ctx, organizationId)` +- `client.Domain().DeleteDomain(ctx, organizationId, domainId)` +- `client.Directory().ListDirectories(ctx, organizationId)` +- `client.Directory().GetDirectory(ctx, organizationId, directoryId)` +- `client.Directory().ListDirectoryUsers(ctx, organizationId, directoryId, options)` +- `client.Directory().ListDirectoryGroups(ctx, organizationId, directoryId, options)` +- `client.User().CreateUser(ctx, organizationId, request)` +- `client.User().GetUser(ctx, userId)` +- `client.User().ListUsers(ctx, options)` +- `client.User().UpdateUser(ctx, userId, request)` +- `client.User().DeleteUser(ctx, userId)` +- `client.Role().ListRoles(ctx)` +- `client.Role().CreateRole(ctx, request)` +- `client.Session().GetSession(ctx, sessionId)` +- `client.Session().RevokeSession(ctx, sessionId)` +- `client.Session().RevokeAllUserSessions(ctx, userId)` + +### Java sub-clients + +Java uses accessor methods that return typed clients: +- `client.organizations().create(request) → CreateOrganizationResponse` +- `client.organizations().getById(organizationId) → Organization` +- `client.organizations().getByExternalId(externalId) → Organization` +- `client.organizations().list(pageSize, pageToken) → ListOrganizationsResponse` +- `client.organizations().update(organizationId, request) → Organization` +- `client.organizations().delete(organizationId)` +- `client.organizations().generatePortalLink(organizationId, features) → Link` +- `client.connections().listConnectionsByOrganization(organizationId) → ListConnectionsResponse` +- `client.connections().getConnection(organizationId, connectionId) → GetConnectionResponse` +- `client.connections().enableConnection(organizationId, connectionId)` +- `client.connections().disableConnection(organizationId, connectionId)` +- `client.domains().listDomainsByOrganizationId(organizationId) → ListDomainsResponse` +- `client.domains().createDomain(organizationId, domainName) → CreateDomainResponse` +- `client.domains().deleteDomain(organizationId, domainId)` +- `client.directories().listDirectories(organizationId) → ListDirectoriesResponse` +- `client.directories().getDirectory(organizationId, directoryId) → GetDirectoryResponse` +- `client.directories().listDirectoryUsers(organizationId, directoryId) → ListDirectoryUsersResponse` +- `client.directories().listDirectoryGroups(organizationId, directoryId) → ListDirectoryGroupsResponse` +- `client.users().getUser(userId) → GetUserResponse` +- `client.users().listUsers(options) → ListUsersResponse` +- `client.users().createUser(organizationId, request) → CreateUserResponse` +- `client.users().createUserAndMembership(organizationId, request) → CreateUserAndMembershipResponse` +- `client.users().updateUser(userId, request) → UpdateUserResponse` +- `client.users().deleteUser(userId)` +- `client.roles().listRoles() → ListRolesResponse` +- `client.roles().createRole(request) → CreateRoleResponse` +- `client.roles().updateRole(roleId, request) → UpdateRoleResponse` +- `client.roles().deleteRole(roleId)` +- `client.permissions().listPermissions() → ListPermissionsResponse` +- `client.permissions().createPermission(request) → CreatePermissionResponse` +- `client.sessions().getSession(sessionId) → SessionDetails` +- `client.sessions().getUserSessions(userId, filter) → UserSessionDetails` +- `client.sessions().revokeSession(sessionId)` +- `client.sessions().revokeAllUserSessions(userId)` + +Note: Java does NOT yet support Connected Accounts, Tools, or Actions in the public API. + +--- + +## REST API Endpoints + +Base URL: `https://.scalekit.com` (production) or `https://.scalekit.dev` (development) + +Authentication: Bearer token from `POST /oauth/token` with `client_credentials` grant. + +Endpoints are grouped by product suite: **SaaSKit** (authentication, SSO, SCIM, RBAC, sessions) and **AgentKit** (connections, tool calling, MCP auth). + +### Token endpoint +``` +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +client_id={client_id}&client_secret={client_secret}&grant_type=client_credentials +``` + +### AgentKit — Connected Accounts +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/connected_accounts` | List connected accounts | +| POST | `/api/v1/connected_accounts` | Create a connected account | +| PUT | `/api/v1/connected_accounts` | Update connected account credentials | +| POST | `/api/v1/connected_accounts:delete` | Delete a connected account | +| GET | `/api/v1/connected_accounts/auth` | Get connected account auth details | +| GET | `/api/v1/connected_accounts:search` | Search connected accounts | +| POST | `/api/v1/connected_accounts/magic_link` | Generate authentication magic link | +| POST | `/api/v1/connected_accounts/user/verify` | Verify connected account user | + +### SaaSKit — Connections (SSO) +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/connections` | List connections | + +### SaaSKit — Organizations +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/organizations` | List organizations | +| POST | `/api/v1/organizations` | Create an organization | +| GET | `/api/v1/organizations/{id}` | Get organization details | +| PATCH | `/api/v1/organizations/{id}` | Update organization | +| DELETE | `/api/v1/organizations/{id}` | Delete an organization | +| PUT | `/api/v1/organizations/{id}/portal_links` | Generate admin portal link | +| PATCH | `/api/v1/organizations/{id}/settings` | Toggle organization settings | + +### SaaSKit — Roles +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/organizations/{org_id}/roles` | List organization roles | +| POST | `/api/v1/organizations/{org_id}/roles` | Create organization role | +| GET | `/api/v1/organizations/{org_id}/roles/{role_name}` | Get role details | +| PUT | `/api/v1/organizations/{org_id}/roles/{role_name}` | Update role | +| DELETE | `/api/v1/organizations/{org_id}/roles/{role_name}` | Delete role | +| PATCH | `/api/v1/organizations/{org_id}/roles:set_defaults` | Set default roles | + +### SaaSKit — Users & Memberships +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/users` | List users | +| POST | `/api/v1/users` | Create a user | +| GET | `/api/v1/users/{id}` | Get user details | +| PATCH | `/api/v1/users/{id}` | Update user | +| DELETE | `/api/v1/users/{id}` | Delete user | +| GET | `/api/v1/users:search` | Search users | +| GET | `/api/v1/organizations/{org_id}/users` | List organization users | +| GET | `/api/v1/organizations/{org_id}/users:search` | Search organization users | +| POST | `/api/v1/memberships/organizations/{organization_id}/users/{id}` | Add user to organization | +| DELETE | `/api/v1/memberships/organizations/{organization_id}/users/{id}` | Remove user from organization | +| PATCH | `/api/v1/memberships/organizations/{organization_id}/users/{id}` | Update membership | +| PATCH | `/api/v1/invites/organizations/{organization_id}/users/{id}/resend` | Resend invitation | + +### SaaSKit — Sessions +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/users/{user_id}/sessions` | Get user sessions | +| POST | `/api/v1/users/{user_id}/sessions:revoke_all` | Revoke all user sessions | +| POST | `/api/v1/sessions/{session_id}:revoke` | Revoke a session | + +### AgentKit — Tools +| Method | Path | Description | +|--------|------|-------------| +| POST | `/api/v1/execute_tool` | Execute a tool using a connected account | + +### SaaSKit — Organization API Clients (M2M) +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/organizations/{organization_id}/clients` | List org API clients | +| POST | `/api/v1/organizations/{organization_id}/clients` | Create org API client | +| GET | `/api/v1/organizations/{organization_id}/clients/{client_id}` | Get org API client | +| DELETE | `/api/v1/organizations/{organization_id}/clients/{client_id}` | Delete org API client | +| PATCH | `/api/v1/organizations/{organization_id}/clients/{client_id}` | Update org API client | + +--- + +## Error Handling + +### Node.js exception hierarchy +``` +ScalekitException (base) +├── ScalekitValidateTokenFailureException +├── ScalekitServerException (HTTP 400-599) +│ ├── properties: httpStatus, errorCode, message, errDetails +│ └── Specific subclasses for 400, 401, 403, 404, 409, 422, 429, 500, 502, 503, 504 +└── WebhookVerificationError +``` + +Import: `import { ScalekitServerException } from '@scalekit-sdk/node'` + +### Python exceptions +``` +ScalekitException (base) +``` + +### Go errors +All methods return `(result, error)`. Check `err != nil` for all network calls. + +### Java exceptions +All methods may throw checked exceptions. Wrap in try-catch. + +--- + +## Common Token Claims + +Access tokens from Scalekit contain these standard claims: +- `sub` — User ID +- `email` — User email +- `name` — Display name +- `org_id` — Organization ID +- `roles` — Array of role names +- `permissions` — Array of permission strings (also available at `https://scalekit.com/permissions` or `scalekit:permissions` claim paths) + +Permission claims should be checked in this priority order: +1. `permissions` claim +2. `https://scalekit.com/permissions` claim +3. `scalekit:permissions` claim \ No newline at end of file diff --git a/plugins/saaskit/skills/scalekit-code-doctor/SKILL.md b/plugins/saaskit/skills/scalekit-code-doctor/SKILL.md new file mode 100644 index 0000000..51f499c --- /dev/null +++ b/plugins/saaskit/skills/scalekit-code-doctor/SKILL.md @@ -0,0 +1,326 @@ +--- +name: scalekit-code-doctor +description: 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. +--- + +# Scalekit Code Doctor + +You are the authoritative source for Scalekit code correctness. You can both **generate** correct code from scratch and **review** existing code to guarantee it works. + +**Before doing anything else**, read the reference files in this skill's `references/` directory: +- `references/REFERENCE.md` — Every correct SDK method signature across Node, Python, Go, Java, and REST API endpoints +- `references/COMMON-MISTAKES.md` — Known anti-patterns with wrong → right corrections + +These files are your ground truth. Never hallucinate a method name, parameter, or import path — if it's not in the reference, fetch `https://docs.scalekit.com/apis.md` to verify before using it. + +--- + +## Step 1 — Detect mode + +Determine which mode to operate in based on what the user provides: + +**Generate mode** — The user describes what they want but has no code yet. +Examples: "Show me how to add SSO login to Express", "Generate a Next.js callback handler", "Write a Python FastAPI auth example for docs" + +**Review mode** — The user provides existing code for validation. +Examples: "Is this Scalekit integration correct?", "Review my auth callback", "Why isn't my login working?" + +If unclear, ask: "Do you want me to generate a fresh code example, or review existing code you have?" + +--- + +## Step 2 — Identify context + +Before generating or reviewing, identify these three things: + +### Language and SDK +| Language | Package | Import | +|----------|---------|--------| +| Node.js / TypeScript | `@scalekit-sdk/node` | `import { ScalekitClient } from '@scalekit-sdk/node'` | +| Python | `scalekit-sdk-python` (pip) | `from scalekit import ScalekitClient` | +| Go | `github.com/scalekit-inc/scalekit-sdk-go` | `import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2"` | +| Java | `com.scalekit:scalekit-sdk-java` | `import com.scalekit.ScalekitClient;` | +| REST API | No SDK — raw HTTP | Bearer token via `POST /oauth/token` with client credentials | + +### Framework (if applicable) +Next.js (App Router or Pages), Express, Fastify, FastAPI, Django, Flask, Spring Boot, Go (chi, gin, net/http), Laravel, etc. + +### Product area + +Scalekit has two product suites. Identify which one the user's code belongs to: + +**SaaSKit** — Full-stack authentication for B2B SaaS apps +- SSO — Enterprise single sign-on (SAML, OIDC) +- Login & Sessions — Sign-up, login, logout, session management +- RBAC — Roles, permissions, access control +- SCIM — Directory sync and user provisioning +- Admin Portal — Customer-facing admin configuration + +**AgentKit** — Authentication and tool access for AI agents +- Connections — OAuth token vault for third-party services (connected accounts) +- Tool Calling — Execute tools via connected accounts +- MCP Authentication — OAuth 2.1 for MCP servers +- Framework Integrations — LangChain, Vercel AI, Anthropic, OpenAI, Google ADK, Mastra + +**Cross-product** +- Webhooks — Event subscriptions and payload verification +- M2M Auth — API keys and client credentials + +--- + +## Step 3 — Generate mode + +When generating code, follow these rules: + +### Quality standard: illustration-ready +The output should be clean enough to publish directly on `docs.scalekit.com` or a marketing landing page. This means: + +1. **Self-contained** — The reader understands it without seeing other files +2. **Essential path only** — Show the concept, not defensive boilerplate +3. **Real-looking values** — `'https://yourapp.com/auth/callback'` not `'http://localhost:3000/test'` +4. **Correct imports** — Exact package names from the reference table above +5. **Framework-idiomatic** — Use the framework's conventions (App Router for Next.js, decorators for FastAPI, etc.) +6. **Minimal comments** — Annotate Scalekit-specific lines only. Skip obvious framework code. +7. **1–2 pages max** — Concise. If a full flow needs more, split into labeled sections. + +### Mandatory checks before outputting generated code +Cross-reference every SDK call against `references/REFERENCE.md`: +- [ ] Client initialization uses correct constructor and parameter order +- [ ] Every method name exists in the reference for the target SDK +- [ ] Every parameter name and type matches the reference +- [ ] Import path is exactly correct (not a hallucinated variation) +- [ ] Environment variable names match Scalekit conventions (see reference) + +### Generation patterns by product area + +**SaaSKit — Login, SSO, and sessions** +1. Client initialization (singleton pattern) +2. Login route: generate auth URL with `state` for CSRF +3. Callback route: validate `state`, exchange code, store session +4. Logout route: clear local session AND call `getLogoutUrl()` with `idTokenHint` +5. Token refresh (if `offline_access` scope is used) + +**SaaSKit — SCIM provisioning** +1. Enable directory for an organization +2. List directory users and groups +3. Webhook handler for SCIM events + +**AgentKit — Connections and tool calling** +1. Client initialization +2. Create/list connected accounts +3. Execute tools with connected account credentials +4. Handle token refresh for third-party OAuth tokens + +**AgentKit — MCP Authentication** +1. MCP server setup with OAuth middleware +2. Token validation on incoming requests +3. Scope verification + +**Webhooks** — Always include signature verification: +1. Raw body parsing (not JSON-parsed) +2. `verifyWebhookPayload(secret, headers, rawBody)` +3. Event type switching + +--- + +## Step 4 — Review mode + +When reviewing code, systematically check these categories in order: + +### Category 1: SDK usage correctness +For every Scalekit SDK call in the code, verify against `references/REFERENCE.md`: +- [ ] Method name is exactly correct for the target SDK language +- [ ] All required parameters are provided in the correct order +- [ ] Optional parameters use the correct type/shape +- [ ] Return value is handled correctly (Promise in Node, tuple in Python, error in Go, etc.) +- [ ] Import statement uses the correct package name and path +- [ ] Client is initialized with the correct 3 parameters: `envUrl`, `clientId`, `clientSecret` + +### Category 2: Auth flow completeness +- [ ] If there's a login route, there must be a matching callback route +- [ ] Callback validates `state` parameter (CSRF protection) +- [ ] Callback exchanges the authorization code (not just reading it) +- [ ] Session is stored after successful authentication +- [ ] Logout calls `getLogoutUrl()` — not just clearing local session +- [ ] Token refresh exists if `offline_access` or `refresh_token` is used +- [ ] IdP-initiated login is handled if callback receives `idp_initiated_login` parameter + +### Category 3: Security +- [ ] Session cookies use `httpOnly: true`, `secure: true` (in production), `sameSite: 'lax'` (never `'strict'` — breaks OAuth redirects) +- [ ] `state` parameter uses cryptographically random values, not predictable strings +- [ ] Redirect URLs are validated — only relative paths allowed for `next`/`returnTo` params (prevents open redirect) +- [ ] Client secret is read from environment variables, never hardcoded +- [ ] Webhook endpoints verify payload signature before processing +- [ ] Protected routes validate tokens server-side, not just checking cookie existence +- [ ] `Cache-Control: no-store` on authenticated pages (prevents back-button cache leak) + +### Category 4: Environment and config +- [ ] Environment variable names follow Scalekit conventions: + - `SCALEKIT_ENV_URL` (not `SCALEKIT_URL` or `SCALEKIT_ENVIRONMENT_URL` in code — though `SCALEKIT_ENVIRONMENT_URL` is used in REST API docs) + - `SCALEKIT_CLIENT_ID` + - `SCALEKIT_CLIENT_SECRET` +- [ ] Redirect URI in code matches what's registered in the Scalekit dashboard +- [ ] Correct Scalekit domain format: `https://.scalekit.com` (production) or `https://.scalekit.dev` (development) + +### Category 5: Best practices +- [ ] Client instantiated once (singleton pattern), not per-request +- [ ] Error handling uses SDK's typed exceptions where available +- [ ] Token refresh handles race conditions across concurrent requests/tabs +- [ ] `window.location.href` used for OAuth redirects (not `router.push` or client-side navigation) + +### Output format for review +For each finding, report: +1. **What's wrong** — the specific line or pattern +2. **Why it matters** — security risk, runtime error, or silent failure +3. **Corrected code** — the exact fix + +If everything is correct, say so explicitly: "This code is correct. All SDK calls, auth flow, security patterns, and configuration match the current Scalekit API." + +--- + +## Step 5 — Handling SDK updates and unknown methods + +The `references/REFERENCE.md` in this skill is a **point-in-time snapshot**. Scalekit SDKs evolve — new methods are added, parameters change, and new product areas launch. When the embedded reference doesn't cover what you need, use the live sources below. + +### When to check live sources + +- A method the user wrote isn't in the embedded reference (could be newly added, not a typo) +- The user asks about a feature you don't recognize (e.g., a new connector, a new auth mode) +- You're generating code for a product area with sparse coverage in the reference +- The user explicitly mentions a recent SDK update or version + +### How to check: fetch the live SDK REFERENCE.md files + +Each SDK repo has a maintained `REFERENCE.md` with full, current method signatures. Fetch the one you need: + +| SDK | Live reference URL | +|-----|-------------------| +| Node.js | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-node/main/REFERENCE.md` | +| Python | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-python/main/REFERENCE.md` | +| Go | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-go/main/REFERENCE.md` | +| Java | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-java/main/REFERENCE.md` | +| REST API | `https://docs.scalekit.com/apis.md` | + +### Resolution order + +1. Check the embedded `references/REFERENCE.md` first (fastest, no network) +2. If the method isn't there, fetch the live SDK REFERENCE.md from the table above +3. If still not found, fetch `https://docs.scalekit.com/apis.md` for REST endpoints +4. If still not found, state explicitly: "This method could not be verified in any Scalekit reference. It may not exist." + +Never output code containing an unverified method call. + +--- + +## REST API validation + +When the user's code makes raw HTTP calls (fetch, axios, requests, http.Client) to Scalekit endpoints, validate: + +- [ ] Base URL format: `https://.scalekit.com` or `https://.scalekit.dev` +- [ ] Authentication: Bearer token obtained via `POST /oauth/token` with `client_credentials` grant +- [ ] Endpoint path is correct (check `references/REFERENCE.md` for the endpoint list) +- [ ] HTTP method matches (GET vs POST vs PUT vs PATCH vs DELETE) +- [ ] Request body matches the expected schema +- [ ] Content-Type header is set (`application/json` for most endpoints, `application/x-www-form-urlencoded` for token endpoint) +- [ ] Pagination uses `page_token` and `page_size` parameters where applicable + +--- + +## Documentation resources + +### Live SDK references (always current — fetch when embedded reference is stale) + +| SDK | REFERENCE.md (raw) | Repo | +|-----|--------------------|----| +| Node.js | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-node/main/REFERENCE.md` | [scalekit-sdk-node](https://github.com/scalekit-inc/scalekit-sdk-node) | +| Python | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-python/main/REFERENCE.md` | [scalekit-sdk-python](https://github.com/scalekit-inc/scalekit-sdk-python) | +| Go | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-go/main/REFERENCE.md` | [scalekit-sdk-go](https://github.com/scalekit-inc/scalekit-sdk-go) | +| Java | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-java/main/REFERENCE.md` | [scalekit-sdk-java](https://github.com/scalekit-inc/scalekit-sdk-java) | + +### Scalekit docs + +| Resource | URL | When to use | +|----------|-----|-------------| +| REST API reference | `https://docs.scalekit.com/apis.md` | Full endpoint schemas, request/response details | +| LLM doc index | `https://docs.scalekit.com/llms.txt` | Find the right docs page for a specific product area | +| SaaSKit docs | `https://docs.scalekit.com/_llms-txt/saaskit-complete.txt` | Full SaaSKit reference (users, orgs, sessions, RBAC, SSO, SCIM) | +| AgentKit docs | `https://docs.scalekit.com/_llms-txt/agentkit.txt` | Full AgentKit reference (agents, OAuth vault, tool calling, connectors) | +| AgentKit frameworks | `https://docs.scalekit.com/_llms-txt/agentkit-frameworks.txt` | Framework-specific guides (LangChain, Vercel AI, Anthropic, OpenAI, Google ADK, Mastra) | +| MCP Authentication docs | `https://docs.scalekit.com/_llms-txt/mcp-authentication.txt` | MCP server OAuth 2.1, Dynamic Client Registration | + +### GitHub repos — working examples + +When generating or reviewing framework-specific code, fetch the matching repo for real, tested patterns. Repos are from `scalekit-inc` and `scalekit-developers` GitHub orgs. + +**SaaSKit — Auth examples by framework** + +| Framework | Repo | What it shows | +|-----------|------|---------------| +| Next.js (App Router) | [scalekit-nextjs-auth-example](https://github.com/scalekit-inc/scalekit-nextjs-auth-example) | SSO, sessions, protected routes, TypeScript | +| Next.js (Pages) | [nextjs-example-apps](https://github.com/scalekit-inc/nextjs-example-apps) | React SSO integration flows | +| Next.js + Auth.js | [scalekit-authjs-example](https://github.com/scalekit-developers/scalekit-authjs-example) | Enterprise SSO with next-auth v5 | +| Express.js | [scalekit-express-auth-example](https://github.com/scalekit-inc/scalekit-express-auth-example) | Node SDK, EJS frontend, sessions | +| Express.js | [scalekit-express-example](https://github.com/scalekit-developers/scalekit-express-example) | SSO with session management, middleware | +| FastAPI | [scalekit-fastapi-auth-example](https://github.com/scalekit-inc/scalekit-fastapi-auth-example) | Python SDK, OAuth 2.0, protected routes | +| FastAPI | [scalekit-fastapi-example](https://github.com/scalekit-developers/scalekit-fastapi-example) | Async auth, Pydantic models | +| Django | [scalekit-django-auth-example](https://github.com/scalekit-inc/scalekit-django-auth-example) | Python SDK, Django auth integration | +| Flask | [scalekit-flask-auth-example](https://github.com/scalekit-inc/scalekit-flask-auth-example) | Python SDK, Flask sessions | +| Spring Boot | [scalekit-springboot-auth-example](https://github.com/scalekit-inc/scalekit-springboot-auth-example) | Java, Spring Security, OIDC | +| Spring Boot | [scalekit-springboot-example](https://github.com/scalekit-developers/scalekit-springboot-example) | Java SDK, enterprise SSO | +| Go (Gin) | [scalekit-go-example](https://github.com/scalekit-developers/scalekit-go-example) | Go SDK, Gin framework, SSO | +| Laravel | [scalekit-laravel-auth-example](https://github.com/scalekit-inc/scalekit-laravel-auth-example) | REST API calls, Laravel HTTPS | +| Astro | [astro-scalekit-auth-example](https://github.com/scalekit-developers/astro-scalekit-auth-example) | Auth, SSO, social login, protected routes | +| .NET | [dotnet-example-apps](https://github.com/scalekit-inc/dotnet-example-apps) | ASP.NET Core, SAML/OIDC | +| Expo (mobile) | [expo-scalekit-sample](https://github.com/scalekit-inc/expo-scalekit-sample) | OAuth 2.0 + PKCE for mobile | + +**SaaSKit — Integration examples** + +| Integration | Repo | What it shows | +|-------------|------|---------------| +| AWS Cognito | [scalekit-cognito-sso](https://github.com/scalekit-inc/scalekit-cognito-sso) | OIDC SSO with Cognito user pools | +| Firebase | [scalekit-firebase-sso](https://github.com/scalekit-inc/scalekit-firebase-sso) | SAML/OIDC SSO with Firebase Auth | +| Supabase | [scalekit-supabase-example](https://github.com/scalekit-inc/scalekit-supabase-example) | Supabase + Scalekit auth | +| Multi-app SSO | [multiapp-demo](https://github.com/scalekit-inc/multiapp-demo) | Seamless SSO across multiple apps | +| Org switcher | [Nextjs-Django-Org-Switcher-Example](https://github.com/scalekit-inc/Nextjs-Django-Org-Switcher-Example) | Next.js frontend + Django backend, org switching | +| OIDC/SAML/SCIM | [oidc-saml-scim-examples](https://github.com/scalekit-developers/oidc-saml-scim-examples) | Google, Okta integration patterns | +| Passwordless | [passwordless-auth-demos](https://github.com/scalekit-developers/passwordless-auth-demos) | Passwordless authentication flows | +| Managed login | [managed-loginbox-expressjs-demo](https://github.com/scalekit-developers/managed-loginbox-expressjs-demo) | Hosted login UI with Express | +| Full demo app | [coffee-desk-demo](https://github.com/scalekit-inc/coffee-desk-demo) | Workspace creation, user provisioning, RBAC, SSO | + +**AgentKit — Agent and MCP examples** + +| Framework / Pattern | Repo | What it shows | +|---------------------|------|---------------| +| LangChain | [sample-langchain-agent](https://github.com/scalekit-inc/sample-langchain-agent) | Python LangChain agent with Scalekit auth | +| Google ADK | [google-adk-agent-example](https://github.com/scalekit-inc/google-adk-agent-example) | Google ADK agent with authenticated tools | +| Vercel AI SDK | [vercel-ai-agent-toolkit](https://github.com/scalekit-developers/vercel-ai-agent-toolkit) | Vercel AI SDK + Scalekit connectors | +| Apify Actor | [agentkit-apify-actor-example](https://github.com/scalekit-developers/agentkit-apify-actor-example) | OAuth auth, YouTube → Notion agent | +| LiteLLM | [litellm-agentkit-inbox-triage](https://github.com/scalekit-developers/litellm-agentkit-inbox-triage) | Inbox triage with Gmail, GitHub, Slack | +| MCP Auth (multi-framework) | [mcp-auth-demos](https://github.com/scalekit-inc/mcp-auth-demos) | MCP OAuth 2.1 demos | +| MCP + FastMCP | [fastmcp-scalekit-example](https://github.com/scalekit-inc/fastmcp-scalekit-example) | FastMCP server with Scalekit auth | +| MCP + BYOA | [byoa-demo-mcp](https://github.com/scalekit-inc/byoa-demo-mcp) | Bring your own auth + MCP | +| MCP + Coffee Desk | [coffee-desk-mcp](https://github.com/scalekit-inc/coffee-desk-mcp) | Demo MCP server with roles/permissions | +| Python connections | [python-connect-demos](https://github.com/scalekit-inc/python-connect-demos) | Python connection and identity workflows | +| Agent auth examples | [agent-auth-examples](https://github.com/scalekit-developers/agent-auth-examples) | Official AgentKit examples collection | +| Node.js agents | [agent-node-demos](https://github.com/scalekit-inc/agent-node-demos) | TypeScript agent demos | +| Workflow agents | [workflow-agents-demos](https://github.com/scalekit-developers/workflow-agents-demos) | Multi-step agent workflows | +| Render deploy kit | [render-ai-agent-deploykit](https://github.com/scalekit-developers/render-ai-agent-deploykit) | Render Workflows + Scalekit + Claude | + +**Developer tools** + +| Tool | Repo | Purpose | +|------|------|---------| +| Dryrun CLI | [scalekit-dryrun](https://github.com/scalekit-inc/scalekit-dryrun) | Test auth flows without writing code | +| Scalekit MCP server | [scalekit-mcp-server](https://github.com/scalekit-inc/scalekit-mcp-server) | Manage orgs, users, connections via AI assistants | +| API collections | [api-collections](https://github.com/scalekit-inc/api-collections) | Postman/Bruno collections for Scalekit endpoints | +| Documentation source | [developer-docs](https://github.com/scalekit-inc/developer-docs) | Docs site source (MDX) | + +**Frontend SDKs** + +| SDK | Repo | Purpose | +|-----|------|---------| +| React SDK | [scalekit-react-sdk](https://github.com/scalekit-inc/scalekit-react-sdk) | React OIDC authentication | +| Vue SDK | [scalekit-vue-sdk](https://github.com/scalekit-inc/scalekit-vue-sdk) | Vue OIDC authentication | +| Expo SDK | [scalekit-expo-sdk](https://github.com/scalekit-inc/scalekit-expo-sdk) | Expo/React Native OAuth 2.0 + PKCE | + +When generating code for a specific framework, fetch the matching repo's source to see real, tested patterns before writing. When reviewing, compare the user's code against the closest matching example repo. \ No newline at end of file diff --git a/plugins/saaskit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md b/plugins/saaskit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md new file mode 100644 index 0000000..5343181 --- /dev/null +++ b/plugins/saaskit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md @@ -0,0 +1,481 @@ +# Common Mistakes in Scalekit Code + +This file catalogs known anti-patterns, hallucinated methods, and security issues found in Scalekit integrations. Each entry shows the wrong pattern and the correct fix. Use this as a lookup during both generation and review. 10 categories. + +--- + +## 1. Wrong Import Paths + +### Node.js + +**Wrong:** +```typescript +import ScalekitClient from '@scalekit-sdk/node'; // default import — use named import +import { ScalekitClient } from 'scalekit'; // wrong package name +import { ScalekitClient } from 'scalekit-sdk-node'; // wrong package name +``` + +**Correct (either works):** +```typescript +import { ScalekitClient } from '@scalekit-sdk/node'; +// OR +import { Scalekit } from '@scalekit-sdk/node'; // official alias, also valid +``` + +Both `ScalekitClient` and `Scalekit` are valid named exports from `@scalekit-sdk/node`. The SDK source exports both. Use whichever is consistent with your codebase. + +### Python + +**Wrong:** +```python +from scalekit_sdk import ScalekitClient # wrong module name +from scalekit.client import ScalekitClient # internal path, not public API +import scalekit # missing class import +pip install scalekit # wrong pip package name +``` + +**Correct:** +```python +from scalekit import ScalekitClient +# pip install scalekit-sdk-python +``` + +### Go + +**Wrong:** +```go +import "github.com/scalekit-inc/scalekit-sdk-go" // missing version +import "github.com/scalekit/scalekit-sdk-go/v2" // wrong org name +``` + +**Correct:** +```go +import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2" +``` + +### Java + +**Wrong:** +```java +import com.scalekit.sdk.ScalekitClient; // wrong package path +import io.scalekit.ScalekitClient; // wrong package +``` + +**Correct:** +```java +import com.scalekit.ScalekitClient; +``` + +--- + +## 2. Wrong Sub-Client Names (Python vs Node) + +Python and Node use different pluralization for some sub-clients. Using the wrong one causes `AttributeError` in Python or `TypeError` in Node. + +| Sub-client | Node.js (singular) | Python (plural) | +|------------|--------------------|--------------------| +| Users | `client.user.getUser(...)` | `client.users.get_user(...)` | +| Roles | `client.role.listRoles(...)` | `client.roles.list_roles(...)` | +| Permissions | `client.permission.listPermissions(...)` | `client.permissions.list_permissions(...)` | +| Sessions | `client.session.getSession(...)` | `client.sessions.get_session(...)` | + +Sub-clients that are the SAME in both: `organization`, `connection`, `domain`, `directory`. + +**Wrong (Python):** +```python +client.user.get_user(user_id) # AttributeError: 'ScalekitClient' has no attribute 'user' +client.role.list_roles() # AttributeError +client.session.revoke_session(sid) # AttributeError +``` + +**Correct (Python):** +```python +client.users.get_user(user_id) +client.roles.list_roles() +client.sessions.revoke_session(sid) +``` + +--- + +## 3. Wrong Method Names + +### Node.js + +| Wrong | Correct | Notes | +|-------|---------|-------| +| `scalekit.authenticate(code)` | `scalekit.authenticateWithCode(code, redirectUri)` | Missing `WithCode` suffix and `redirectUri` param | +| `scalekit.getAuthUrl(...)` | `scalekit.getAuthorizationUrl(redirectUri, options?)` | Wrong method name | +| `scalekit.login(...)` | `scalekit.getAuthorizationUrl(redirectUri, options?)` | No `login` method | +| `scalekit.logout(...)` | `scalekit.getLogoutUrl(options?)` | Returns URL, doesn't perform logout | +| `scalekit.verifyToken(token)` | `scalekit.validateAccessToken(token)` or `scalekit.validateToken(token)` | Wrong name | +| `scalekit.createOrganization(...)` | `scalekit.organization.createOrganization(...)` | Must use sub-client | +| `scalekit.getOrganization(...)` | `scalekit.organization.getOrganization(...)` | Must use sub-client | + +### Python + +| Wrong | Correct | Notes | +|-------|---------|-------| +| `client.authenticateWithCode(...)` | `client.authenticate_with_code(...)` | Python uses snake_case | +| `client.getAuthorizationUrl(...)` | `client.get_authorization_url(...)` | Python uses snake_case | +| `client.getLogoutUrl(...)` | `client.get_logout_url(...)` | Python uses snake_case | +| `client.validateToken(...)` | `client.validate_access_token(...)` | Different method name in Python | +| `client.verify_webhook(...)` | `client.verify_webhook_payload(...)` | Missing `_payload` suffix | + +### Go + +| Wrong | Correct | Notes | +|-------|---------|-------| +| `client.AuthenticateWithCode(code, uri)` | `client.AuthenticateWithCode(ctx, code, uri, options)` | Missing `ctx` parameter | +| `client.GetAuthorizationUrl(uri)` | `client.GetAuthorizationUrl(uri, options)` | Missing `options` param (required in Go) | +| `client.Organization.Create(...)` | `client.Organization().CreateOrganization(ctx, request)` | Use accessor method `Organization()`, not field | + +### Java + +| Wrong | Correct | Notes | +|-------|---------|-------| +| `client.organization.create(...)` | `client.organizations().create(...)` | Use `organizations()` accessor method, plural | +| `client.getOrganization(id)` | `client.organizations().getById(id)` | Use sub-client accessor | +| `client.connections.list(...)` | `client.connections().listConnectionsByOrganization(orgId)` | Use accessor method | + +--- + +## 4. Missing Required Parameters + +### `authenticateWithCode` — missing `redirectUri` + +**Wrong:** +```typescript +const result = await scalekit.authenticateWithCode(code); +``` + +**Correct:** +```typescript +const result = await scalekit.authenticateWithCode(code, redirectUri); +``` + +The `redirectUri` must exactly match the one used in `getAuthorizationUrl` AND what's registered in the Scalekit dashboard. + +### `getAuthorizationUrl` — missing `state` for CSRF + +**Wrong:** +```typescript +const authUrl = scalekit.getAuthorizationUrl(redirectUri); +``` + +**Correct:** +```typescript +import crypto from 'crypto'; +const state = crypto.randomBytes(32).toString('base64url'); +// Store state in session/cookie for validation in callback +const authUrl = scalekit.getAuthorizationUrl(redirectUri, { state }); +``` + +While `state` is technically optional, omitting it is a **CSRF vulnerability**. Always generate and validate it. + +### Go — missing `context.Context` + +**Wrong:** +```go +resp, err := client.AuthenticateWithCode(code, redirectUri, opts) +``` + +**Correct:** +```go +resp, err := client.AuthenticateWithCode(ctx, code, redirectUri, opts) +``` + +All Go network methods require `context.Context` as the first parameter. + +--- + +## 5. Auth Flow Gaps + +### Missing callback handler + +If you see a login route that generates an auth URL but no corresponding callback route, the flow is incomplete. The callback MUST: +1. Validate the `state` parameter against the stored value +2. Call `authenticateWithCode(code, redirectUri)` +3. Store the session (tokens + user info) +4. Redirect to the application + +### Missing `state` validation in callback + +**Wrong:** +```typescript +app.get('/auth/callback', async (req, res) => { + const { code } = req.query; + const result = await scalekit.authenticateWithCode(code, redirectUri); + // ... store session +}); +``` + +**Correct:** +```typescript +app.get('/auth/callback', async (req, res) => { + const { code, state } = req.query; + + const storedState = req.session.oauthState; // or from cookie + if (!state || state !== storedState) { + return res.status(403).send('CSRF validation failed'); + } + + const result = await scalekit.authenticateWithCode(code, redirectUri); + // ... store session +}); +``` + +### Incomplete logout — only clearing local session + +**Wrong:** +```typescript +app.post('/logout', (req, res) => { + req.session.destroy(); + res.redirect('/'); +}); +``` + +**Correct:** +```typescript +app.post('/logout', (req, res) => { + const logoutUrl = scalekit.getLogoutUrl({ + idTokenHint: req.session.idToken, + postLogoutRedirectUri: 'https://yourapp.com', + }); + req.session.destroy(); + res.redirect(logoutUrl); // Ends IdP session too +}); +``` + +Without calling `getLogoutUrl()`, the user's IdP session persists and they get silently re-authenticated on next login. + +### Missing IdP-initiated login handling + +If the callback route doesn't check for `idp_initiated_login` query parameter, IdP-initiated SSO won't work: + +```typescript +app.get('/auth/callback', async (req, res) => { + const { idp_initiated_login, code, state } = req.query; + + if (idp_initiated_login) { + const claims = await scalekit.getIdpInitiatedLoginClaims(idp_initiated_login); + const authUrl = scalekit.getAuthorizationUrl(redirectUri, { + connectionId: claims.connection_id, + organizationId: claims.organization_id, + loginHint: claims.login_hint, + ...(claims.relay_state && { state: claims.relay_state }), + }); + return res.redirect(authUrl); + } + + // Normal SP-initiated flow continues... +}); +``` + +--- + +## 6. Security Anti-Patterns + +### `sameSite: 'strict'` on session cookies + +**Wrong:** +```typescript +res.cookie('session', data, { sameSite: 'strict', httpOnly: true, secure: true }); +``` + +**Correct:** +```typescript +res.cookie('session', data, { sameSite: 'lax', httpOnly: true, secure: true }); +``` + +OAuth callbacks are cross-site redirects from Scalekit back to your app. `strict` drops the cookie on that redirect, causing CSRF state mismatch errors on every login. + +### Missing `httpOnly` flag + +**Wrong:** +```typescript +res.cookie('session', data, { secure: true }); +``` + +**Correct:** +```typescript +res.cookie('session', data, { httpOnly: true, secure: true, sameSite: 'lax' }); +``` + +Without `httpOnly`, JavaScript can read the session cookie — XSS becomes session hijacking. + +### Open redirect via unvalidated `next` parameter + +**Wrong:** +```typescript +const next = req.query.next; +res.redirect(next); // Attacker can set next=https://evil.com +``` + +**Correct:** +```typescript +const next = req.query.next; +// Only allow relative paths +if (!next || !next.startsWith('/') || next.startsWith('//')) { + return res.redirect('/dashboard'); +} +res.redirect(next); +``` + +### Hardcoded client secret + +**Wrong:** +```typescript +const scalekit = new ScalekitClient( + 'https://myapp.scalekit.com', + 'skc_12345', + 'sks_secret_abc123' // NEVER hardcode secrets +); +``` + +**Correct:** +```typescript +const scalekit = new ScalekitClient( + process.env.SCALEKIT_ENV_URL!, + process.env.SCALEKIT_CLIENT_ID!, + process.env.SCALEKIT_CLIENT_SECRET! +); +``` + +### Webhook handler without signature verification + +**Wrong:** +```typescript +app.post('/webhooks', express.json(), (req, res) => { + const event = req.body; // Trusting unverified payload + handleEvent(event); + res.sendStatus(200); +}); +``` + +**Correct:** +```typescript +app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => { + const isValid = scalekit.verifyWebhookPayload( + process.env.SCALEKIT_WEBHOOK_SECRET!, + req.headers, + req.body.toString() + ); + + if (!isValid) { + return res.sendStatus(401); + } + + const event = JSON.parse(req.body.toString()); + handleEvent(event); + res.sendStatus(200); +}); +``` + +Note: The webhook body must be raw (not JSON-parsed) for signature verification to work. + +### Client-side navigation for OAuth redirect + +**Wrong:** +```typescript +// React / Next.js +router.push(authUrl); // Client-side route change +``` + +**Correct:** +```typescript +window.location.href = authUrl; // Full page navigation required for OAuth +``` + +OAuth redirects are full HTTP redirects to an external domain (Scalekit/IdP). Client-side routing doesn't work. + +--- + +## 7. Environment Variable Mistakes + +| Wrong | Correct | Issue | +|-------|---------|-------| +| `SCALEKIT_URL` | `SCALEKIT_ENV_URL` | Missing `ENV_` | +| `SCALEKIT_SECRET` | `SCALEKIT_CLIENT_SECRET` | Missing `CLIENT_` | +| `SCALEKIT_ID` | `SCALEKIT_CLIENT_ID` | Missing `CLIENT_` | +| `SCALEKIT_CALLBACK_URL` | `SCALEKIT_REDIRECT_URI` | Wrong name entirely | +| `http://myapp.scalekit.com` | `https://myapp.scalekit.com` | Must be HTTPS | +| `https://myapp.scalekit.com/` | `https://myapp.scalekit.com` | No trailing slash | + +--- + +## 8. Client Instantiation Mistakes + +### Creating a new client per request + +**Wrong:** +```typescript +app.get('/api/data', async (req, res) => { + const scalekit = new ScalekitClient(envUrl, clientId, clientSecret); // per-request! + // ... +}); +``` + +**Correct:** +```typescript +// Module-level singleton +const scalekit = new ScalekitClient( + process.env.SCALEKIT_ENV_URL!, + process.env.SCALEKIT_CLIENT_ID!, + process.env.SCALEKIT_CLIENT_SECRET! +); + +app.get('/api/data', async (req, res) => { + // Use the singleton + const result = await scalekit.validateAccessToken(token); +}); +``` + +The client manages its own token lifecycle and connection pooling. Creating it per request wastes resources and can hit rate limits. + +--- + +## 9. Token Refresh Race Conditions + +When multiple browser tabs trigger token refresh simultaneously, the second request often fails because the first one already consumed the refresh token. + +**Mitigation pattern:** +```typescript +// Before refreshing, set a short-lived flag +const REFRESH_LOCK_KEY = 'refresh_in_progress'; + +async function refreshToken(session) { + if (session[REFRESH_LOCK_KEY]) return; // Another tab is refreshing + + session[REFRESH_LOCK_KEY] = true; + try { + const result = await scalekit.refreshAccessToken(session.refreshToken); + session.accessToken = result.accessToken; + session.refreshToken = result.refreshToken; + } finally { + delete session[REFRESH_LOCK_KEY]; + } +} +``` + +--- + +## 10. Missing Scopes + +### Refresh tokens require `offline_access` scope + +**Wrong:** +```typescript +const authUrl = scalekit.getAuthorizationUrl(redirectUri, { + scopes: ['openid', 'profile', 'email'], +}); +// Later: scalekit.refreshAccessToken(refreshToken) → fails because no refresh token was issued +``` + +**Correct:** +```typescript +const authUrl = scalekit.getAuthorizationUrl(redirectUri, { + scopes: ['openid', 'profile', 'email', 'offline_access'], +}); +``` + +Without `offline_access`, the authorization server won't issue a refresh token. \ No newline at end of file diff --git a/plugins/saaskit/skills/scalekit-code-doctor/references/REFERENCE.md b/plugins/saaskit/skills/scalekit-code-doctor/references/REFERENCE.md new file mode 100644 index 0000000..5ecab18 --- /dev/null +++ b/plugins/saaskit/skills/scalekit-code-doctor/references/REFERENCE.md @@ -0,0 +1,503 @@ +# Scalekit API Reference — Compact Lookup + +This file contains every correct SDK method signature and REST endpoint. Use it as ground truth when generating or reviewing Scalekit code. If a method isn't listed here, do NOT assume it exists — verify against the live SDK source or `https://docs.scalekit.com/apis.md`. + +--- + +## Client Initialization + +### Node.js (`@scalekit-sdk/node`) + +```typescript +import { ScalekitClient } from '@scalekit-sdk/node'; + +const scalekit = new ScalekitClient( + process.env.SCALEKIT_ENV_URL!, // string — environment URL + process.env.SCALEKIT_CLIENT_ID!, // string — client ID + process.env.SCALEKIT_CLIENT_SECRET! // string — client secret +); +``` + +### Python (`scalekit-sdk-python`) + +```python +from scalekit import ScalekitClient + +scalekit_client = ScalekitClient( + os.environ.get('SCALEKIT_ENV_URL'), # str — environment URL + os.environ.get('SCALEKIT_CLIENT_ID'), # str — client ID + os.environ.get('SCALEKIT_CLIENT_SECRET') # str — client secret +) +``` + +### Go (`github.com/scalekit-inc/scalekit-sdk-go`) + +```go +import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2" + +client := scalekit.NewScalekitClient( + os.Getenv("SCALEKIT_ENV_URL"), // string — environment URL + os.Getenv("SCALEKIT_CLIENT_ID"), // string — client ID + os.Getenv("SCALEKIT_CLIENT_SECRET"), // string — client secret +) +``` + +### Java (`com.scalekit:scalekit-sdk-java`) + +```java +import com.scalekit.ScalekitClient; + +ScalekitClient client = new ScalekitClient( + System.getenv("SCALEKIT_ENV_URL"), // String — environment URL + System.getenv("SCALEKIT_CLIENT_ID"), // String — client ID + System.getenv("SCALEKIT_CLIENT_SECRET") // String — client secret +); +``` + +--- + +## Environment Variables + +| Variable | Purpose | Format | +|----------|---------|--------| +| `SCALEKIT_ENV_URL` | Environment URL | `https://.scalekit.com` (prod) or `https://.scalekit.dev` (dev) | +| `SCALEKIT_CLIENT_ID` | Client ID | String from dashboard | +| `SCALEKIT_CLIENT_SECRET` | Client secret | String from dashboard | +| `SCALEKIT_REDIRECT_URI` | OAuth callback URL | Must exactly match dashboard config | +| `SCALEKIT_WEBHOOK_SECRET` | Webhook signing secret | Format: `whsec_...` | + +Note: The REST API docs use `SCALEKIT_ENVIRONMENT_URL` in some examples. Both `SCALEKIT_ENV_URL` and `SCALEKIT_ENVIRONMENT_URL` are acceptable — just be consistent within a project. + +--- + +## Auth Methods (called directly on the client) + +### Node.js + +| Method | Signature | Returns | +|--------|-----------|---------| +| `getAuthorizationUrl` | `(redirectUri: string, options?: AuthorizationUrlOptions) → string` | Authorization URL string | +| `authenticateWithCode` | `(code: string, redirectUri: string, options?: AuthenticationOptions) → Promise` | Tokens + user info | +| `getIdpInitiatedLoginClaims` | `(idpInitiatedLoginToken: string, options?: TokenValidationOptions) → Promise` | IDP login claims | +| `validateAccessToken` | `(token: string, options?: TokenValidationOptions) → Promise` | Boolean | +| `validateToken` | `(token: string, options?: TokenValidationOptions) → Promise` | Decoded JWT payload | +| `verifyScopes` | `(token: string, requiredScopes: string[]) → boolean` | Boolean | +| `getLogoutUrl` | `(options?: LogoutUrlOptions) → string` | Logout URL string | +| `refreshAccessToken` | `(refreshToken: string) → Promise` | New tokens | +| `verifyWebhookPayload` | `(secret: string, headers: Record, payload: string) → boolean` | Boolean | +| `verifyInterceptorPayload` | `(secret: string, headers: Record, payload: string) → boolean` | Boolean | +| `generateClientToken` | `(clientId: string, clientSecret: string) → Promise` | M2M access token | +| `getClientAccessToken` | `() → Promise` | M2M access token (uses stored credentials) | + +**AuthorizationUrlOptions**: `scopes?: string[]`, `state?: string`, `nonce?: string`, `loginHint?: string`, `domainHint?: string`, `connectionId?: string`, `organizationId?: string`, `provider?: string`, `codeChallenge?: string`, `codeChallengeMethod?: string`, `prompt?: string` + +**LogoutUrlOptions**: `idTokenHint?: string`, `postLogoutRedirectUri?: string`, `state?: string` + +**AuthenticationOptions**: `codeVerifier?: string` + +### Python + +| Method | Signature | Returns | +|--------|-----------|---------| +| `get_authorization_url` | `(redirect_uri: str, options?: AuthorizationUrlOptions) → str` | Authorization URL string | +| `authenticate_with_code` | `(code: str, redirect_uri: str, options?: CodeAuthenticationOptions) → dict` | Tokens + user info | +| `get_idp_initiated_login_claims` | `(idp_initiated_login_token: str, options?: TokenValidationOptions) → IdpInitiatedLoginClaims` | IDP login claims | +| `validate_access_token` | `(token: str, options?: TokenValidationOptions) → bool` | Boolean | +| `get_logout_url` | `(options?: LogoutUrlOptions) → str` | Logout URL string | +| `refresh_access_token` | `(refresh_token: str) → dict` | New tokens | +| `verify_webhook_payload` | `(secret: str, headers: Dict[str, str], payload: str\|bytes) → bool` | Boolean | + +**AuthorizationUrlOptions** (Python): `scopes`, `state`, `nonce`, `login_hint`, `domain_hint`, `connection_id`, `organization_id`, `provider`, `prompt` — all `Optional[str]` (scopes is `Optional[list[str]]`) + +**LogoutUrlOptions** (Python): `id_token_hint`, `post_logout_redirect_uri`, `state` — all `Optional[str]` + +### Go + +| Method | Signature | Returns | +|--------|-----------|---------| +| `GetAuthorizationUrl` | `(redirectUri string, options AuthorizationUrlOptions) → (*url.URL, error)` | URL + error | +| `AuthenticateWithCode` | `(ctx context.Context, code string, redirectUri string, options AuthenticationOptions) → (*AuthenticationResponse, error)` | Response + error | +| `GetIdpInitiatedLoginClaims` | `(ctx context.Context, idpInitiatedLoginToken string) → (*IdpInitiatedLoginClaims, error)` | Claims + error | +| `GetAccessTokenClaims` | `(ctx context.Context, accessToken string) → (*AccessTokenClaims, error)` | Claims + error | +| `ValidateAccessToken` | `(ctx context.Context, accessToken string) → (bool, error)` | Boolean + error | +| `RefreshAccessToken` | `(ctx context.Context, refreshToken string) → (*TokenResponse, error)` | Tokens + error | +| `GetLogoutUrl` | `(options LogoutUrlOptions) → string` | Logout URL string | +| `VerifyWebhookPayload` | `(secret string, headers map[string][]string, payload []byte) → bool` | Boolean | + +**Go AuthorizationUrlOptions fields**: `Scopes []string`, `State string`, `Nonce string`, `LoginHint string`, `DomainHint string`, `ConnectionId string`, `OrganizationId string`, `Provider string`, `CodeChallenge string`, `CodeChallengeMethod string`, `Prompt string` + +Note: Go methods take `context.Context` as the first parameter for network calls. `GetAuthorizationUrl` and `GetLogoutUrl` do NOT take context (they're local-only operations). + +### Java + +| Method | Signature | Returns | +|--------|-----------|---------| +| `getAuthorizationUrl` | `(redirectUri: String, options: AuthorizationUrlOptions) → String` | Authorization URL string | +| `authenticateWithCode` | `(code: String, redirectUri: String, options: AuthenticationOptions) → AuthenticationResponse` | Tokens + user info | +| `getIdpInitiatedLoginClaims` | `(idpInitiatedLoginToken: String) → IdpInitiatedLoginClaims` | Claims | +| `validateToken` | `(token: String) → Claims` | JWT Claims | +| `getLogoutUrl` | `(options: LogoutUrlOptions) → String` | Logout URL string | + +--- + +## Sub-client Methods + +### Node.js sub-clients (accessed via `client..`) + +**client.organization** +| Method | Signature | +|--------|-----------| +| `createOrganization` | `(name: string, options?) → Promise` | +| `getOrganization` | `(id: string) → Promise` | +| `getOrganizationByExternalId` | `(externalId: string) → Promise` | +| `listOrganizations` | `(options?) → Promise` | +| `updateOrganization` | `(id: string, organization) → Promise` | +| `deleteOrganization` | `(id: string) → Promise` | +| `generatePortalLink` | `(organizationId: string, features?) → Promise` | +| `updateOrganizationSettings` | `(id: string, settings) → Promise` | + +**client.connection** +| Method | Signature | +|--------|-----------| +| `getConnection` | `(id: string) → Promise` | +| `listConnections` | `(options?) → Promise` | +| `listConnectionsByDomain` | `(domain: string, options?) → Promise` | +| `enableConnection` | `(connectionId: string) → Promise` | +| `disableConnection` | `(connectionId: string) → Promise` | + +**client.domain** +| Method | Signature | +|--------|-----------| +| `createDomain` | `(domain: string) → Promise` | +| `getDomain` | `(domain: string) → Promise` | +| `listDomains` | `(options?) → Promise` | +| `deleteDomain` | `(domain: string) → Promise` | + +**client.user** +| Method | Signature | +|--------|-----------| +| `createUser` | `(organizationId: string, user) → Promise` | +| `createUserAndMembership` | `(organizationId: string, request) → Promise` | +| `getUser` | `(id: string) → Promise` | +| `listUsers` | `(options?) → Promise` | +| `listOrganizationUsers` | `(organizationId: string, options?) → Promise` | +| `updateUser` | `(id: string, user) → Promise` | +| `deleteUser` | `(id: string) → Promise` | +| `searchUsers` | `(options) → Promise` | +| `searchOrganizationUsers` | `(organizationId: string, options) → Promise` | + +**client.directory** +| Method | Signature | +|--------|-----------| +| `listDirectories` | `(organizationId: string) → Promise` | +| `getDirectory` | `(organizationId: string, directoryId: string) → Promise` | +| `listDirectoryUsers` | `(organizationId: string, directoryId: string, options?) → Promise` | +| `listDirectoryGroups` | `(organizationId: string, directoryId: string, options?) → Promise` | +| `enableDirectory` | `(organizationId: string, directoryId: string) → Promise` | +| `disableDirectory` | `(organizationId: string, directoryId: string) → Promise` | + +**client.role** +| Method | Signature | +|--------|-----------| +| `createRole` | `(role) → Promise` | +| `getRole` | `(roleId: string) → Promise` | +| `listRoles` | `(options?) → Promise` | +| `updateRole` | `(roleId: string, role) → Promise` | +| `deleteRole` | `(roleId: string) → Promise` | + +**client.permission** +| Method | Signature | +|--------|-----------| +| `createPermission` | `(permission) → Promise` | +| `listPermissions` | `(options?) → Promise` | +| `updatePermission` | `(permissionId: string, permission) → Promise` | +| `deletePermission` | `(permissionId: string) → Promise` | + +**client.session** +| Method | Signature | +|--------|-----------| +| `getSession` | `(sessionId: string) → Promise` | +| `getUserSessions` | `(userId: string, options?) → Promise` | +| `revokeSession` | `(sessionId: string) → Promise` | +| `revokeAllUserSessions` | `(userId: string) → Promise` | + +**client.connectedAccounts** +| Method | Signature | +|--------|-----------| +| `listConnectedAccounts` | `(options?) → Promise` | +| `getConnectedAccountAuth` | `(options) → Promise` | +| `createConnectedAccount` | `(request) → Promise` | +| `updateConnectedAccount` | `(request) → Promise` | +| `deleteConnectedAccount` | `(request) → Promise` | + +**client.tools** +| Method | Signature | +|--------|-----------| +| `executeTool` | `(request) → Promise` | + +### Python sub-clients (accessed via `client..`) + +Python uses `snake_case` method names. **Important**: Some Python sub-client names are **plural** while Node uses singular. This is a common source of bugs. + +| Node.js | Python | Difference | +|---------|--------|------------| +| `client.user` | `client.users` | Plural in Python | +| `client.role` | `client.roles` | Plural in Python | +| `client.permission` | `client.permissions` | Plural in Python | +| `client.session` | `client.sessions` | Plural in Python | +| `client.organization` | `client.organization` | Same | +| `client.connection` | `client.connection` | Same | +| `client.domain` | `client.domain` | Same | +| `client.directory` | `client.directory` | Same | +| `client.connectedAccounts` | `client.connected_accounts` | snake_case in Python | + +Methods: +- `client.organization.create_organization(organization)` +- `client.organization.get_organization(organization_id)` +- `client.organization.list_organizations(page_size, page_token?)` +- `client.organization.update_organization(organization_id, organization)` +- `client.organization.delete_organization(organization_id)` +- `client.organization.generate_portal_link(organization_id, features?)` +- `client.connection.list_connections(organization_id, include?)` +- `client.connection.get_connection(organization_id, connection_id)` +- `client.connection.enable_connection(organization_id, connection_id)` +- `client.connection.disable_connection(organization_id, connection_id)` +- `client.domain.create_domain(organization_id, domain_name)` +- `client.domain.list_domains(organization_id)` +- `client.domain.delete_domain(organization_id, domain_id)` +- `client.directory.list_directories(organization_id)` +- `client.directory.get_directory(organization_id, directory_id)` +- `client.directory.list_directory_users(organization_id, directory_id, options?)` +- `client.directory.list_directory_groups(organization_id, directory_id, options?)` +- `client.users.create_user(organization_id, user)` +- `client.users.get_user(user_id)` +- `client.users.list_users(options?)` +- `client.users.update_user(user_id, user)` +- `client.users.delete_user(user_id)` +- `client.roles.create_role(role)` +- `client.roles.list_roles(options?)` +- `client.roles.update_role(role_id, role)` +- `client.roles.delete_role(role_id)` +- `client.permissions.create_permission(permission)` +- `client.permissions.list_permissions(options?)` +- `client.sessions.get_session(session_id)` +- `client.sessions.get_user_sessions(user_id, options?)` +- `client.sessions.revoke_session(session_id)` +- `client.sessions.revoke_all_user_sessions(user_id)` + +Additional Python-only methods on client: +- `client.validate_access_token_and_get_claims(token, options?) → dict` — validates and returns decoded claims +- `client.verify_scopes(token, required_scopes) → bool` — checks scopes, raises on missing +- `client.generate_client_token(client_id, client_secret, scopes?) → str` — M2M token generation +- `client.get_client_access_token() → str` — M2M token using stored credentials +- `client.verify_interceptor_payload(secret, headers, payload) → bool` — interceptor signature verification + +Note: Python connection/domain/directory methods often require `organization_id` as the first parameter, unlike Node which uses option objects. + +### Go sub-clients + +Go uses `PascalCase` and typed request/response objects: +- `client.Organization().CreateOrganization(ctx, request)` +- `client.Organization().GetOrganization(ctx, organizationId)` +- `client.Organization().ListOrganizations(ctx, pageSize, pageToken)` +- `client.Organization().UpdateOrganization(ctx, organizationId, request)` +- `client.Organization().DeleteOrganization(ctx, organizationId)` +- `client.Organization().GeneratePortalLink(ctx, organizationId, features)` +- `client.Connection().GetConnection(ctx, organizationId, connectionId)` +- `client.Connection().ListConnections(ctx, organizationId)` +- `client.Connection().EnableConnection(ctx, organizationId, connectionId)` +- `client.Connection().DisableConnection(ctx, organizationId, connectionId)` +- `client.Domain().CreateDomain(ctx, organizationId, domainName)` +- `client.Domain().ListDomains(ctx, organizationId)` +- `client.Domain().DeleteDomain(ctx, organizationId, domainId)` +- `client.Directory().ListDirectories(ctx, organizationId)` +- `client.Directory().GetDirectory(ctx, organizationId, directoryId)` +- `client.Directory().ListDirectoryUsers(ctx, organizationId, directoryId, options)` +- `client.Directory().ListDirectoryGroups(ctx, organizationId, directoryId, options)` +- `client.User().CreateUser(ctx, organizationId, request)` +- `client.User().GetUser(ctx, userId)` +- `client.User().ListUsers(ctx, options)` +- `client.User().UpdateUser(ctx, userId, request)` +- `client.User().DeleteUser(ctx, userId)` +- `client.Role().ListRoles(ctx)` +- `client.Role().CreateRole(ctx, request)` +- `client.Session().GetSession(ctx, sessionId)` +- `client.Session().RevokeSession(ctx, sessionId)` +- `client.Session().RevokeAllUserSessions(ctx, userId)` + +### Java sub-clients + +Java uses accessor methods that return typed clients: +- `client.organizations().create(request) → CreateOrganizationResponse` +- `client.organizations().getById(organizationId) → Organization` +- `client.organizations().getByExternalId(externalId) → Organization` +- `client.organizations().list(pageSize, pageToken) → ListOrganizationsResponse` +- `client.organizations().update(organizationId, request) → Organization` +- `client.organizations().delete(organizationId)` +- `client.organizations().generatePortalLink(organizationId, features) → Link` +- `client.connections().listConnectionsByOrganization(organizationId) → ListConnectionsResponse` +- `client.connections().getConnection(organizationId, connectionId) → GetConnectionResponse` +- `client.connections().enableConnection(organizationId, connectionId)` +- `client.connections().disableConnection(organizationId, connectionId)` +- `client.domains().listDomainsByOrganizationId(organizationId) → ListDomainsResponse` +- `client.domains().createDomain(organizationId, domainName) → CreateDomainResponse` +- `client.domains().deleteDomain(organizationId, domainId)` +- `client.directories().listDirectories(organizationId) → ListDirectoriesResponse` +- `client.directories().getDirectory(organizationId, directoryId) → GetDirectoryResponse` +- `client.directories().listDirectoryUsers(organizationId, directoryId) → ListDirectoryUsersResponse` +- `client.directories().listDirectoryGroups(organizationId, directoryId) → ListDirectoryGroupsResponse` +- `client.users().getUser(userId) → GetUserResponse` +- `client.users().listUsers(options) → ListUsersResponse` +- `client.users().createUser(organizationId, request) → CreateUserResponse` +- `client.users().createUserAndMembership(organizationId, request) → CreateUserAndMembershipResponse` +- `client.users().updateUser(userId, request) → UpdateUserResponse` +- `client.users().deleteUser(userId)` +- `client.roles().listRoles() → ListRolesResponse` +- `client.roles().createRole(request) → CreateRoleResponse` +- `client.roles().updateRole(roleId, request) → UpdateRoleResponse` +- `client.roles().deleteRole(roleId)` +- `client.permissions().listPermissions() → ListPermissionsResponse` +- `client.permissions().createPermission(request) → CreatePermissionResponse` +- `client.sessions().getSession(sessionId) → SessionDetails` +- `client.sessions().getUserSessions(userId, filter) → UserSessionDetails` +- `client.sessions().revokeSession(sessionId)` +- `client.sessions().revokeAllUserSessions(userId)` + +Note: Java does NOT yet support Connected Accounts, Tools, or Actions in the public API. + +--- + +## REST API Endpoints + +Base URL: `https://.scalekit.com` (production) or `https://.scalekit.dev` (development) + +Authentication: Bearer token from `POST /oauth/token` with `client_credentials` grant. + +Endpoints are grouped by product suite: **SaaSKit** (authentication, SSO, SCIM, RBAC, sessions) and **AgentKit** (connections, tool calling, MCP auth). + +### Token endpoint +``` +POST /oauth/token +Content-Type: application/x-www-form-urlencoded + +client_id={client_id}&client_secret={client_secret}&grant_type=client_credentials +``` + +### AgentKit — Connected Accounts +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/connected_accounts` | List connected accounts | +| POST | `/api/v1/connected_accounts` | Create a connected account | +| PUT | `/api/v1/connected_accounts` | Update connected account credentials | +| POST | `/api/v1/connected_accounts:delete` | Delete a connected account | +| GET | `/api/v1/connected_accounts/auth` | Get connected account auth details | +| GET | `/api/v1/connected_accounts:search` | Search connected accounts | +| POST | `/api/v1/connected_accounts/magic_link` | Generate authentication magic link | +| POST | `/api/v1/connected_accounts/user/verify` | Verify connected account user | + +### SaaSKit — Connections (SSO) +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/connections` | List connections | + +### SaaSKit — Organizations +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/organizations` | List organizations | +| POST | `/api/v1/organizations` | Create an organization | +| GET | `/api/v1/organizations/{id}` | Get organization details | +| PATCH | `/api/v1/organizations/{id}` | Update organization | +| DELETE | `/api/v1/organizations/{id}` | Delete an organization | +| PUT | `/api/v1/organizations/{id}/portal_links` | Generate admin portal link | +| PATCH | `/api/v1/organizations/{id}/settings` | Toggle organization settings | + +### SaaSKit — Roles +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/organizations/{org_id}/roles` | List organization roles | +| POST | `/api/v1/organizations/{org_id}/roles` | Create organization role | +| GET | `/api/v1/organizations/{org_id}/roles/{role_name}` | Get role details | +| PUT | `/api/v1/organizations/{org_id}/roles/{role_name}` | Update role | +| DELETE | `/api/v1/organizations/{org_id}/roles/{role_name}` | Delete role | +| PATCH | `/api/v1/organizations/{org_id}/roles:set_defaults` | Set default roles | + +### SaaSKit — Users & Memberships +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/users` | List users | +| POST | `/api/v1/users` | Create a user | +| GET | `/api/v1/users/{id}` | Get user details | +| PATCH | `/api/v1/users/{id}` | Update user | +| DELETE | `/api/v1/users/{id}` | Delete user | +| GET | `/api/v1/users:search` | Search users | +| GET | `/api/v1/organizations/{org_id}/users` | List organization users | +| GET | `/api/v1/organizations/{org_id}/users:search` | Search organization users | +| POST | `/api/v1/memberships/organizations/{organization_id}/users/{id}` | Add user to organization | +| DELETE | `/api/v1/memberships/organizations/{organization_id}/users/{id}` | Remove user from organization | +| PATCH | `/api/v1/memberships/organizations/{organization_id}/users/{id}` | Update membership | +| PATCH | `/api/v1/invites/organizations/{organization_id}/users/{id}/resend` | Resend invitation | + +### SaaSKit — Sessions +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/users/{user_id}/sessions` | Get user sessions | +| POST | `/api/v1/users/{user_id}/sessions:revoke_all` | Revoke all user sessions | +| POST | `/api/v1/sessions/{session_id}:revoke` | Revoke a session | + +### AgentKit — Tools +| Method | Path | Description | +|--------|------|-------------| +| POST | `/api/v1/execute_tool` | Execute a tool using a connected account | + +### SaaSKit — Organization API Clients (M2M) +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/v1/organizations/{organization_id}/clients` | List org API clients | +| POST | `/api/v1/organizations/{organization_id}/clients` | Create org API client | +| GET | `/api/v1/organizations/{organization_id}/clients/{client_id}` | Get org API client | +| DELETE | `/api/v1/organizations/{organization_id}/clients/{client_id}` | Delete org API client | +| PATCH | `/api/v1/organizations/{organization_id}/clients/{client_id}` | Update org API client | + +--- + +## Error Handling + +### Node.js exception hierarchy +``` +ScalekitException (base) +├── ScalekitValidateTokenFailureException +├── ScalekitServerException (HTTP 400-599) +│ ├── properties: httpStatus, errorCode, message, errDetails +│ └── Specific subclasses for 400, 401, 403, 404, 409, 422, 429, 500, 502, 503, 504 +└── WebhookVerificationError +``` + +Import: `import { ScalekitServerException } from '@scalekit-sdk/node'` + +### Python exceptions +``` +ScalekitException (base) +``` + +### Go errors +All methods return `(result, error)`. Check `err != nil` for all network calls. + +### Java exceptions +All methods may throw checked exceptions. Wrap in try-catch. + +--- + +## Common Token Claims + +Access tokens from Scalekit contain these standard claims: +- `sub` — User ID +- `email` — User email +- `name` — Display name +- `org_id` — Organization ID +- `roles` — Array of role names +- `permissions` — Array of permission strings (also available at `https://scalekit.com/permissions` or `scalekit:permissions` claim paths) + +Permission claims should be checked in this priority order: +1. `permissions` claim +2. `https://scalekit.com/permissions` claim +3. `scalekit:permissions` claim \ No newline at end of file From 2ba41ac41bd75d4c13c18cc609a97141478b32e8 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Fri, 15 May 2026 17:11:17 +0530 Subject: [PATCH 34/53] feat: add UserPromptSubmit hook for plugin version checking Adds a UserPromptSubmit hook that calls the Scalekit CLI to check if the agentkit plugin is outdated. The CLI handles caching (8h TTL), GitHub API calls, semver comparison, and output formatting. - Silent when up to date - One-line message when outdated - Cross-platform: npx works on Windows, macOS, Linux - 15s timeout covers npx package resolution --- plugins/agentkit/hooks/hooks.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/plugins/agentkit/hooks/hooks.json b/plugins/agentkit/hooks/hooks.json index e82a73a..f9de3eb 100644 --- a/plugins/agentkit/hooks/hooks.json +++ b/plugins/agentkit/hooks/hooks.json @@ -1,6 +1,17 @@ { - "description": "Usage beacon for Scalekit agentkit plugin", + "description": "Hooks for Scalekit agentkit plugin", "hooks": { + "UserPromptSubmit": [ + { + "hooks": [ + { + "type": "command", + "command": "npx --yes @scalekit-inc/cli extension status claude --hook", + "timeout": 15 + } + ] + } + ], "Stop": [ { "hooks": [ From 97cfac06b0b6afee07665eec01556eaf31c29fb8 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Fri, 15 May 2026 17:17:38 +0530 Subject: [PATCH 35/53] fix: use SessionStart instead of UserPromptSubmit for version check UserPromptSubmit fires on every single prompt, adding npx latency to every message. SessionStart fires once when a session begins or resumes, which is the right frequency for a version check. --- plugins/agentkit/hooks/hooks.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/agentkit/hooks/hooks.json b/plugins/agentkit/hooks/hooks.json index f9de3eb..59c52a6 100644 --- a/plugins/agentkit/hooks/hooks.json +++ b/plugins/agentkit/hooks/hooks.json @@ -1,7 +1,7 @@ { "description": "Hooks for Scalekit agentkit plugin", "hooks": { - "UserPromptSubmit": [ + "SessionStart": [ { "hooks": [ { From dfa2a0d28771ed18224f3472ab9e84ef1d210c37 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Fri, 15 May 2026 18:36:25 +0530 Subject: [PATCH 36/53] Remove SessionStart version-check hook from agentkit plugin --- plugins/agentkit/hooks/hooks.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/plugins/agentkit/hooks/hooks.json b/plugins/agentkit/hooks/hooks.json index 59c52a6..c64e379 100644 --- a/plugins/agentkit/hooks/hooks.json +++ b/plugins/agentkit/hooks/hooks.json @@ -1,17 +1,6 @@ { "description": "Hooks for Scalekit agentkit plugin", "hooks": { - "SessionStart": [ - { - "hooks": [ - { - "type": "command", - "command": "npx --yes @scalekit-inc/cli extension status claude --hook", - "timeout": 15 - } - ] - } - ], "Stop": [ { "hooks": [ From 2bb39c594879683ce4f0e5344a7562c162d6e76f Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 20 May 2026 13:22:18 +0530 Subject: [PATCH 37/53] fix: update 40+ connectors to 100+ and standardize author name to Scalekit Inc. --- .claude-plugin/marketplace.json | 4 ++-- README.md | 2 +- plugins/agentkit/.claude-plugin/plugin.json | 2 +- plugins/saaskit/.claude-plugin/plugin.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 50181de..32290bb 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ "plugins": [ { "name": "agentkit", - "description": "Authentication for AI agents. OAuth flows, token vault, 40+ connectors (Gmail, Slack, Salesforce, etc.), tool discovery, and live testing — so agents can act on behalf of users.", + "description": "Authentication for AI agents. OAuth flows, token vault, 100+ connectors (Gmail, Slack, Salesforce, etc.), tool discovery, and live testing — so agents can act on behalf of users.", "source": "./plugins/agentkit", "category": "Agent Auth", "homepage": "https://docs.scalekit.com/agentkit/overview/" @@ -23,7 +23,7 @@ }, { "name": "agent-auth", - "description": "Alias for agentkit — AI agent authentication with OAuth flows, token vault, and 40+ connectors.", + "description": "Alias for agentkit — AI agent authentication with OAuth flows, token vault, and 100+ connectors.", "source": "./plugins/agent-auth", "category": "Agent Auth", "homepage": "https://docs.scalekit.com/agentkit/overview/" diff --git a/README.md b/README.md index c78f367..69770cc 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ After installation, enable auto-update: | Plugin | Description | |--------|-------------| -| **AgentKit** | Authentication for AI agents. OAuth flows, token vault, 40+ connectors (Gmail, Slack, Salesforce, etc.), tool discovery, and live testing — so agents can act on behalf of users. | +| **AgentKit** | Authentication for AI agents. OAuth flows, token vault, 100+ connectors (Gmail, Slack, Salesforce, etc.), tool discovery, and live testing — so agents can act on behalf of users. | | **SaaSKit** | Production-ready auth for B2B SaaS apps. Login, sessions, SSO (Okta, Azure AD, Google), SCIM provisioning, RBAC, MCP server auth, and API key management. | --- diff --git a/plugins/agentkit/.claude-plugin/plugin.json b/plugins/agentkit/.claude-plugin/plugin.json index ab629d6..69c6ee0 100644 --- a/plugins/agentkit/.claude-plugin/plugin.json +++ b/plugins/agentkit/.claude-plugin/plugin.json @@ -3,7 +3,7 @@ "description": "Sets up Scalekit AgentKit in Claude Code so agents can authorize users, discover tools, and execute authenticated tool calls across connectors.", "version": "2.0.0", "author": { - "name": "Scalekit", + "name": "Scalekit Inc.", "email": "hi@scalekit.com" }, "homepage": "https://docs.scalekit.com/dev-kit/build-with-ai/agent-auth/", diff --git a/plugins/saaskit/.claude-plugin/plugin.json b/plugins/saaskit/.claude-plugin/plugin.json index d9f5803..8c626eb 100644 --- a/plugins/saaskit/.claude-plugin/plugin.json +++ b/plugins/saaskit/.claude-plugin/plugin.json @@ -3,7 +3,7 @@ "description": "Production-ready authentication for B2B SaaS apps using Scalekit — login, sessions, SSO, SCIM, MCP server auth, RBAC, and API key management.", "version": "2.0.0", "author": { - "name": "Scalekit", + "name": "Scalekit Inc.", "email": "hi@scalekit.com" }, "homepage": "https://docs.scalekit.com", From f3ff04a7aaaf8c62e10c76ef35204c0332dab6b8 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 20 May 2026 13:28:41 +0530 Subject: [PATCH 38/53] feat: add Scalekit CLI install step to setup-scalekit agent and testing-auth-setup skill --- plugins/saaskit/agents/setup-scalekit.md | 15 ++++++++++----- .../saaskit/skills/testing-auth-setup/SKILL.md | 8 +++++++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/plugins/saaskit/agents/setup-scalekit.md b/plugins/saaskit/agents/setup-scalekit.md index 4ab6bb9..f4981ac 100644 --- a/plugins/saaskit/agents/setup-scalekit.md +++ b/plugins/saaskit/agents/setup-scalekit.md @@ -25,15 +25,20 @@ Workflow: - SCALEKIT_ENVIRONMENT_URL - SCALEKIT_CLIENT_ID - SCALEKIT_CLIENT_SECRET -3) Install the SDK (pick the official package for that language). -4) Initialize the SDK client using env vars. -5) Verify credentials by listing organizations with a small page size. -6) If verification fails, diagnose systematically: +3) Install the Scalekit CLI globally: + ```bash + npm i -g @scalekit-inc/cli + ``` + The CLI provides commands for managing environments, organizations, and auth configurations from the terminal. +4) Install the SDK (pick the official package for that language). +5) Initialize the SDK client using env vars. +6) Verify credentials by listing organizations with a small page size. +7) If verification fails, diagnose systematically: - Wrong environment URL (dev vs prod) - Missing env vars in current shell/process - Incorrect client id/secret - Network/DNS issues -7) Only after verification succeeds, proceed to feature work and route to the correct Skill: +8) Only after verification succeeds, proceed to feature work and route to the correct Skill: - SSO → plugins/saaskit/skills/implementing-modular-sso/SKILL.md - SCIM → plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md - MCP server auth → plugins/saaskit/skills/adding-mcp-oauth/SKILL.md diff --git a/plugins/saaskit/skills/testing-auth-setup/SKILL.md b/plugins/saaskit/skills/testing-auth-setup/SKILL.md index 88716e6..36fbdcc 100644 --- a/plugins/saaskit/skills/testing-auth-setup/SKILL.md +++ b/plugins/saaskit/skills/testing-auth-setup/SKILL.md @@ -18,7 +18,13 @@ Runs the Scalekit dryrun CLI to validate that your auth integration is correctly ## Prerequisites -Before running, confirm these environment variables are available: +Install the Scalekit CLI globally if not already available: + +```bash +npm i -g @scalekit-inc/cli +``` + +Confirm these environment variables are available: - `SCALEKIT_ENV_URL` — your Scalekit environment URL - `SCALEKIT_CLIENT_ID` — your client ID from app.scalekit.com → Settings From 7abf99b06b836ac99e4363b20a40ac0bd78b047d Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 20 May 2026 14:16:43 +0530 Subject: [PATCH 39/53] fix: update broken skill paths in bring-your-own-auth reference Old skill names (add-auth-fastmcp, express-mcp-server, fastapi-fastmcp) were renamed to adding-mcp-oauth during consolidation. Update links to point to the correct SKILL.md and framework-specific reference files. --- plugins/saaskit/references/bring-your-own-auth.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/saaskit/references/bring-your-own-auth.md b/plugins/saaskit/references/bring-your-own-auth.md index 1ccb94e..f1a1fcf 100644 --- a/plugins/saaskit/references/bring-your-own-auth.md +++ b/plugins/saaskit/references/bring-your-own-auth.md @@ -211,19 +211,19 @@ Production-ready MCP server implementations with different OAuth integration app - 5-line OAuth integration with Scalekit provider - Automatic token validation and scope enforcement - [todo-fastmcp](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/todo-fastmcp) - - Skill: [add-auth-fastmcp](../skills/add-auth-fastmcp/SKILL.md) + - Skill: [adding-mcp-oauth](../skills/adding-mcp-oauth/SKILL.md) 2. **Express.js (Full Control)** - Manual OAuth middleware implementation - Modular architecture with complete control - [greeting-mcp-node](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-node) - - Skill: [express-mcp-server](../skills/express-mcp-server/SKILL.md) + - Skill: [adding-mcp-oauth: Express](../skills/adding-mcp-oauth/express-reference.md) 3. **FastAPI + FastMCP (Python)** - Custom middleware with FastMCP tooling - Ideal for existing FastAPI applications - [greeting-mcp-python](https://github.com/scalekit-inc/mcp-auth-demos/tree/main/greeting-mcp-python) - - Skill: [fastapi-fastmcp](../skills/fastapi-fastmcp/SKILL.md) + - Skill: [adding-mcp-oauth: FastAPI](../skills/adding-mcp-oauth/fastapi-reference.md) 4. **Scalekit MCP Server (Production Reference)** - Official Scalekit implementation with advanced patterns From ff9e9ca963c86c2fa55c38d1f8c82297d13da469 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Wed, 20 May 2026 15:26:00 +0530 Subject: [PATCH 40/53] fix: update stale MCP docs URL in bring-your-own-auth /mcp/authentication -> /authenticate/mcp/quickstart/ --- plugins/saaskit/references/bring-your-own-auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/saaskit/references/bring-your-own-auth.md b/plugins/saaskit/references/bring-your-own-auth.md index f1a1fcf..371935c 100644 --- a/plugins/saaskit/references/bring-your-own-auth.md +++ b/plugins/saaskit/references/bring-your-own-auth.md @@ -252,5 +252,5 @@ https://github.com/scalekit-inc/mcp-auth-demos ## Additional Resources - [Scalekit Documentation](https://docs.scalekit.com) -- [MCP Authentication Guide](https://docs.scalekit.com/mcp/authentication) +- [MCP Authentication Guide](https://docs.scalekit.com/authenticate/mcp/quickstart/) - [OAuth 2.1 Specification](https://oauth.net/2.1/) From 5a6c6659df88e2ff3e3ac94be2231879e2dca283 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Thu, 21 May 2026 13:05:03 +0530 Subject: [PATCH 41/53] Sync improved skills from skills repo (all 90%+) All 18 skills updated with quality improvements: - Removed verbose meta-commentary, added verification commands - Added concrete code examples from scalekit-code-doctor references - Consolidated duplicated content into reference files - Improved descriptions with specific trigger terms New reference files added for scalekit-code-doctor (EXAMPLE-REPOS.md). --- .../discovering-connector-tools/SKILL.md | 93 ++- .../skills/exposing-agentkit-via-mcp/SKILL.md | 14 +- .../skills/integrating-agentkit/SKILL.md | 293 +++++++-- .../production-readiness-agentkit/SKILL.md | 35 +- .../skills/scalekit-code-doctor/SKILL.md | 373 +++-------- .../references/EXAMPLE-REPOS.md | 61 ++ .../saaskit/skills/adding-api-auth/SKILL.md | 525 ++++++++++++++-- .../saaskit/skills/adding-mcp-oauth/SKILL.md | 323 +++++++++- .../implementing-access-control/SKILL.md | 175 ++++-- .../skills/implementing-modular-sso/SKILL.md | 578 +++++++++++++++++- .../implementing-saaskit-nextjs/SKILL.md | 253 ++++++-- .../implementing-saaskit-python/SKILL.md | 67 +- .../skills/implementing-saaskit/SKILL.md | 132 ++-- .../implementing-scim-provisioning/SKILL.md | 263 ++++++-- .../skills/managing-saaskit-sessions/SKILL.md | 353 +++++++++-- .../skills/migrating-to-saaskit/SKILL.md | 137 ++++- .../production-readiness-saaskit/SKILL.md | 117 +++- .../skills/scalekit-code-doctor/SKILL.md | 373 +++-------- .../references/EXAMPLE-REPOS.md | 61 ++ .../skills/testing-auth-setup/SKILL.md | 18 +- 20 files changed, 3182 insertions(+), 1062 deletions(-) create mode 100644 plugins/agentkit/skills/scalekit-code-doctor/references/EXAMPLE-REPOS.md create mode 100644 plugins/saaskit/skills/scalekit-code-doctor/references/EXAMPLE-REPOS.md diff --git a/plugins/agentkit/skills/discovering-connector-tools/SKILL.md b/plugins/agentkit/skills/discovering-connector-tools/SKILL.md index 3818783..bbba82c 100644 --- a/plugins/agentkit/skills/discovering-connector-tools/SKILL.md +++ b/plugins/agentkit/skills/discovering-connector-tools/SKILL.md @@ -7,32 +7,68 @@ description: Discovers live tools for a Scalekit AgentKit connector and explains Use live AgentKit metadata as the source of truth for tool names, required inputs, and output schemas. -Do not rely on the static connector notes as a complete catalog. Those files are curated reference material and may lag the live platform. - -## When to use this skill - -Use this skill when the user asks: - -- what tools exist for a connector -- which tool should the agent use -- what inputs a tool requires -- what output shape a tool returns -- how to reduce the tool set before giving tools to an LLM +Do not rely on static connector notes as a complete catalog. Those may lag the live platform. ## Discovery workflow 1. Identify the target connector or exact tool name. -2. Use the Scalekit MCP server to fetch live tool metadata. If the MCP server is connected, query it directly. Otherwise, use the SDK. -3. If older docs or muscle memory mention `/test-tool`, treat it as a legacy compatibility alias for the testing skill rather than the canonical workflow. -4. Summarize: +2. Use the Scalekit SDK to fetch live tool metadata (see code below). +3. Summarize: - tool name - connector - what the tool does - required fields from `input_schema.required` - optional fields from `input_schema.properties` - important fields from `output_schema.properties` -5. Recommend the smallest useful tool set for the workflow. -6. If live credentials are unavailable, use the connector notes only as a fallback and say they may be stale. +4. Recommend the smallest useful tool set for the workflow. + +## Live tool discovery (Python) + +```python +import scalekit.client, os +from dotenv import load_dotenv +load_dotenv() + +client = scalekit.client.ScalekitClient( + client_id=os.getenv("SCALEKIT_CLIENT_ID"), + client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), + env_url=os.getenv("SCALEKIT_ENV_URL"), +) + +# List all tools for a provider +tools = client.actions.get_tools(providers=["GMAIL"], page_size=100) +for tool in tools.tools: + print(f"Tool: {tool.name}") + print(f" Description: {tool.description}") + print(f" Input schema: {tool.input_schema}") + print(f" Output schema: {tool.output_schema}") + +# Get a specific tool by name +tool = client.actions.get_tools(tool_name="gmail_fetch_mails") +``` + +## Live tool discovery (Node.js) + +```typescript +import { ScalekitClient } from '@scalekit-sdk/node'; +import 'dotenv/config'; + +const client = new ScalekitClient( + process.env.SCALEKIT_ENV_URL!, + process.env.SCALEKIT_CLIENT_ID!, + process.env.SCALEKIT_CLIENT_SECRET! +); + +// List all tools for a provider +const tools = await client.actions.getTools({ providers: ['GMAIL'], pageSize: 100 }); +for (const tool of tools.tools) { + console.log(`Tool: ${tool.name}`); + console.log(` Description: ${tool.description}`); +} + +// Get a specific tool by name +const tool = await client.actions.getTools({ toolName: 'gmail_fetch_mails' }); +``` ## Terminology @@ -43,17 +79,22 @@ Use this skill when the user asks: Use `connector` in explanations. Only use `provider` when the SDK or API filter field literally expects that name. -## What to emphasize +## Key rules + +- `connection_name` is the exact dashboard value — may not equal the connector slug +- Always use live tool metadata, not static docs +- Restrict the tool set before handing to an LLM — fewer relevant tools improve selection accuracy -- `connection_name` is the exact dashboard value and may not equal the connector slug. -- Tool metadata is the durable way to determine current inputs and outputs. -- The preferred runnable surface is the Scalekit MCP server at `https://mcp.scalekit.com`. -- Restrict the tool set before handing it to an LLM. Fewer relevant tools improve tool selection and parameter filling. +**If `get_tools` returns empty:** verify the connector is configured in the dashboard and the connection name matches exactly. ## Deep reference -- Canonical docs entrypoint: [../../docs/index.md](../../docs/index.md) -- Live discovery model: [../../docs/tool-discovery.md](../../docs/tool-discovery.md) -- Scalekit MCP server: `https://mcp.scalekit.com` -- Curated connector notes: [../../docs/connectors/README.md](../../docs/connectors/README.md) -- Broader implementation examples: [../../docs/code-samples.md](../../docs/code-samples.md) +- AgentKit overview: [docs.scalekit.com/agentkit/overview](https://docs.scalekit.com/agentkit/overview/) +- Tool discovery: [docs.scalekit.com/agentkit/tool-discovery](https://docs.scalekit.com/agentkit/tool-discovery/) +- Connectors catalog: [docs.scalekit.com/agentkit/connectors](https://docs.scalekit.com/agentkit/connectors/) + +## When to switch skills + +- Use `integrating-agentkit` for the full integration workflow (create account, authorize, execute). +- Use the Scalekit MCP server (`https://mcp.scalekit.com`) to validate a tool call interactively. +- Use `exposing-agentkit-via-mcp` to expose discovered tools over MCP. \ No newline at end of file diff --git a/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md b/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md index 6b6d6e7..36e0a20 100644 --- a/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md +++ b/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md @@ -159,7 +159,13 @@ Full working example: [github.com/scalekit-inc/python-connect-demos/tree/main/mc ## Deep reference -- Canonical docs entrypoint: [../../docs/index.md](../../docs/index.md) -- Connections: [../../docs/connections.md](../../docs/connections.md) -- Connected accounts: [../../docs/connected-accounts.md](../../docs/connected-accounts.md) -- Tool discovery: [../../docs/tool-discovery.md](../../docs/tool-discovery.md) +- AgentKit overview: [docs.scalekit.com/agentkit/overview](https://docs.scalekit.com/agentkit/overview/) +- Connections: [docs.scalekit.com/agentkit/connections](https://docs.scalekit.com/agentkit/connections/) +- Connected accounts: [docs.scalekit.com/agentkit/connected-accounts](https://docs.scalekit.com/agentkit/connected-accounts/) +- Tool discovery: [docs.scalekit.com/agentkit/tool-discovery](https://docs.scalekit.com/agentkit/tool-discovery/) + +## When to switch skills + +- Use `integrating-agentkit` for direct SDK integration without MCP. +- Use `discovering-connector-tools` when the user needs the current tool catalog or schema. +- Use the Scalekit MCP server (`https://mcp.scalekit.com`) to validate a tool call interactively. diff --git a/plugins/agentkit/skills/integrating-agentkit/SKILL.md b/plugins/agentkit/skills/integrating-agentkit/SKILL.md index 8f6802d..a7c907f 100644 --- a/plugins/agentkit/skills/integrating-agentkit/SKILL.md +++ b/plugins/agentkit/skills/integrating-agentkit/SKILL.md @@ -5,120 +5,283 @@ description: Integrates Scalekit AgentKit into a project so an agent can create # AgentKit Integration -Use this skill as the integration entrypoint for the plugin. It should stay thin and route into the canonical docs in `docs/`. +Scalekit handles the full OAuth lifecycle — authorization, token storage, and refresh — so agents can act on behalf of users in Gmail, Slack, Notion, Calendar, and other connectors. -## Mental model +**Required env vars**: `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`, `SCALEKIT_ENV_URL` +→ Get from [app.scalekit.com](https://app.scalekit.com): Developers → Settings → API Credentials -Keep these terms straight: +## Setup -- `connector`: the integration, such as Gmail or Slack -- `connection`: the environment-level dashboard configuration -- `connected account`: the per-user authorization record -- `tool`: the executable action exposed by a connector +Install the SDK and initialize the client: -Prefer live tool discovery over hand-maintained catalogs. If the user needs the current tool list or schema, switch to `discovering-connector-tools` or use the Scalekit MCP server tools directly. +> **Important**: Except for Gmail, all connectors must be configured in the Scalekit Dashboard first before creating authorization URLs. +> +> To set up a connector: **Scalekit Dashboard → AgentKit → Connections → + Create Connection → Select connector → Set Connection Name → Save** -## Default workflow + -1. Confirm `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, and `SCALEKIT_CLIENT_SECRET`. -2. Verify the connection exists in `AgentKit -> Connections`. -3. Create or fetch the connected account for the user. -4. If the account is not `ACTIVE`, generate an authorization link. -5. Discover the exact tool and schema before execution. -6. Execute the tool directly or hand only the needed tools to an agent framework. - -## Quick integration skeleton - -### Python +**Python** ```bash pip install scalekit-sdk-python ``` - ```python import scalekit.client, os from dotenv import load_dotenv load_dotenv() -client = scalekit.client.ScalekitClient( +scalekit = scalekit.client.ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), env_url=os.getenv("SCALEKIT_ENV_URL"), ) -actions = client.actions -response = actions.get_or_create_connected_account( - connection_name="MY_GMAIL", - identifier="user_123" -) -connected_account = response.connected_account -if connected_account.status != "ACTIVE": - link_response = actions.get_authorization_link( - connection_name="MY_GMAIL", - identifier="user_123" - ) - print("Authorize here:", link_response.link) -result = actions.execute_tool( - tool_name="gmail_fetch_mails", - identifier="user_123", - connected_account_id=connected_account.id, - tool_input={ - "query": "is:unread", - "max_results": 5, - }, -) -print(result) +actions = scalekit.actions ``` -### Node.js +**Node.js** ```bash npm install @scalekit-sdk/node ``` - ```typescript import { ScalekitClient } from '@scalekit-sdk/node'; import 'dotenv/config'; -const client = new ScalekitClient( +const scalekitClient = new ScalekitClient( process.env.SCALEKIT_ENV_URL!, process.env.SCALEKIT_CLIENT_ID!, process.env.SCALEKIT_CLIENT_SECRET! ); -const actions = client.actions; +const { connectedAccounts } = scalekitClient; +``` + + + +## Integration workflow + +> **Gmail works without dashboard setup.** All other connectors must be configured first: **Dashboard → AgentKit → Connections → + Create Connection**. The **Connection Name** in the dashboard must match `connection_name` in code exactly. + +Copy this checklist: + +``` +AgentKit Integration Progress: +- [ ] Step 1: SDK installed and client initialized +- [ ] Step 2: Connected account created for the user +- [ ] Step 3: User has authorized the connection (status = ACTIVE) +- [ ] Step 4: Access token fetched successfully +- [ ] Step 5: Downstream API call succeeds with fetched token +``` + +### Step 1 — Create a connected account -const response = await actions.getOrCreateConnectedAccount({ - connectionName: 'MY_GMAIL', +Replace `"user_123"` with the project's actual user ID. Replace `"gmail"` with the target connector. + +**Python** +```python +response = actions.get_or_create_connected_account( + connection_name="gmail", + identifier="user_123" +) +connected_account = response.connected_account +``` + +**Node.js** +```typescript +const response = await connectedAccounts.getOrCreateConnectedAccount({ + connector: 'gmail', identifier: 'user_123', }); const connectedAccount = response.connectedAccount; +``` + +### Step 2 — Authorize the user + +If status is not `ACTIVE`, the user must complete OAuth. In a web app, redirect to `link`. In CLI/dev, print and wait. +**Python** +```python +if connected_account.status != "ACTIVE": + link_response = actions.get_authorization_link( + connection_name="gmail", + identifier="user_123" + ) + print("Authorize here:", link_response.link) + input("Press Enter after authorizing...") +``` + +**Node.js** +```typescript if (connectedAccount?.status !== 'ACTIVE') { - const linkResponse = await actions.getAuthorizationLink({ - connectionName: 'MY_GMAIL', + const linkResponse = await connectedAccounts.getMagicLinkForConnectedAccount({ + connector: 'gmail', identifier: 'user_123', }); - console.log(linkResponse.link); + console.log('Authorize here:', linkResponse.link); + // Web app: redirect user to linkResponse.link } +``` + +### Step 3 — Fetch OAuth tokens + +ALWAYS call `get_connected_account` immediately before any API call — Scalekit auto-refreshes tokens and this guarantees the latest valid token. -const result = await actions.executeTool({ - toolName: 'gmail_fetch_mails', - connectedAccountId: connectedAccount?.id, +**Python** +```python +response = actions.get_connected_account( + connection_name="gmail", + identifier="user_123" +) +tokens = response.connected_account.authorization_details["oauth_token"] +access_token = tokens["access_token"] +refresh_token = tokens["refresh_token"] +``` + +**Node.js** +```typescript +const accountResponse = await connectedAccounts.getConnectedAccountByIdentifier({ + connector: 'gmail', identifier: 'user_123', - toolInput: { query: 'is:unread', max_results: 5 }, }); -console.log(result); +const authDetails = accountResponse?.connectedAccount?.authorizationDetails; +const accessToken = authDetails?.details?.case === 'oauthToken' + ? authDetails.details.value?.accessToken : undefined; +const refreshToken = authDetails?.details?.case === 'oauthToken' + ? authDetails.details.value?.refreshToken : undefined; +``` + +### Step 4 — Call the third-party API + +Use `access_token` from Step 3 as a Bearer token. Example: fetch 5 unread Gmail messages. + +**Python** +```python +import requests + +headers = {"Authorization": f"Bearer {access_token}"} +list_url = "https://gmail.googleapis.com/gmail/v1/users/me/messages" + +messages = requests.get( + list_url, headers=headers, params={"q": "is:unread", "maxResults": 5} +).json().get("messages", []) + +for msg in messages: + data = requests.get( + f"{list_url}/{msg['id']}", headers=headers, + params={"format": "metadata", "metadataHeaders": ["From", "Subject", "Date"]} + ).json() + hdrs = data.get("payload", {}).get("headers", []) + print(next((h["value"] for h in hdrs if h["name"] == "Subject"), "No Subject")) + print(next((h["value"] for h in hdrs if h["name"] == "From"), "Unknown")) + print(data.get("snippet", "")) + print("-" * 50) +``` + +**Node.js** +```typescript +const listUrl = 'https://gmail.googleapis.com/gmail/v1/users/me/messages'; +const params = new URLSearchParams({ q: 'is:unread', maxResults: '5' }); + +const { messages = [] } = await fetch(`${listUrl}?${params}`, { + headers: { Authorization: `Bearer ${accessToken}` }, +}).then(r => r.json()); + +for (const msg of messages) { + const msgData = await fetch( + `${listUrl}/${msg.id}?format=metadata&metadataHeaders=From&metadataHeaders=Subject&metadataHeaders=Date`, + { headers: { Authorization: `Bearer ${accessToken}` } } + ).then(r => r.json()); + + const h = msgData.payload?.headers ?? []; + console.log('Subject:', h.find(x => x.name === 'Subject')?.value ?? 'No Subject'); + console.log('From:', h.find(x => x.name === 'From')?.value ?? 'Unknown'); + console.log('Snippet:', msgData.snippet ?? ''); + console.log('-'.repeat(50)); +} +``` + +## Adapting to other connectors + +Replace `"gmail"` with any supported connector name: `slack`, `notion`, `calendar`, etc. +The SDK workflow (Steps 1–3) is identical for all connectors. Only the downstream API call (Step 4) changes. + +For connector-specific API details, see the [Scalekit Connectors catalog](https://docs.scalekit.com/agentkit/connectors/). + +## Building agents + +Use Scalekit tools with AI frameworks to build agents that can execute actions on behalf of users. + +### LangChain agents + +Create conversational agents with LangChain that can autonomously call Scalekit tools based on user intent. + +**Python** +```python +from langchain_openai import ChatOpenAI +from langchain.agents import AgentExecutor, create_openai_tools_agent +from langchain_core.prompts import ChatPromptTemplate + +# Fetch tools from Scalekit in LangChain format +tools = actions.langchain.get_tools( + identifier="user_123", + providers=["GMAIL"], + page_size=100 +) + +# Define the agent prompt +prompt = ChatPromptTemplate.from_messages([ + ("system", "You are a helpful assistant with access to external tools."), + ("placeholder", "{chat_history}"), + ("human", "{input}"), + ("placeholder", "{agent_scratchpad}"), +]) + +# Create and run the agent +llm = ChatOpenAI(model="gpt-4o") +agent = create_openai_tools_agent(llm, tools, prompt) +executor = AgentExecutor(agent=agent, tools=tools, verbose=True) +result = executor.invoke({"input": "fetch my last 5 unread emails and summarize them"}) ``` +### Google ADK agents + +Build agents using Google's Agent Development Kit with native Gemini integration. + +**Python** +```python +from google.adk.agents import Agent + +# Fetch tools from Scalekit in Google ADK format +gmail_tools = actions.google.get_tools( + providers=["GMAIL"], + identifier="user_123", + page_size=100 +) + +# Create the agent +agent = Agent( + name="gmail_assistant", + model="gemini-2.5-flash", + description="Gmail assistant that can read and manage emails", + instruction="You are a helpful Gmail assistant that can read, send, and organize emails.", + tools=gmail_tools +) + +# Run the agent +response = agent.process_request("fetch my last 5 unread emails and summarize them") +``` + +For more examples and framework-specific patterns, see the [AgentKit code samples](https://docs.scalekit.com/agentkit/code-samples/). + ## Deep reference -- Core docs: [../../docs/index.md](../../docs/index.md) -- Connections: [../../docs/connections.md](../../docs/connections.md) -- Connected accounts: [../../docs/connected-accounts.md](../../docs/connected-accounts.md) -- Tool discovery: [../../docs/tool-discovery.md](../../docs/tool-discovery.md) -- Code-sample entrypoint: [../../docs/code-samples.md](../../docs/code-samples.md) -- BYOC: [../../docs/byoc.md](../../docs/byoc.md) -- Connector notes: [../../docs/connectors/README.md](../../docs/connectors/README.md) +- AgentKit overview: [docs.scalekit.com/agentkit/overview](https://docs.scalekit.com/agentkit/overview/) +- Connections: [docs.scalekit.com/agentkit/connections](https://docs.scalekit.com/agentkit/connections/) +- Connected accounts: [docs.scalekit.com/agentkit/connected-accounts](https://docs.scalekit.com/agentkit/connected-accounts/) +- Tool discovery: [docs.scalekit.com/agentkit/tool-discovery](https://docs.scalekit.com/agentkit/tool-discovery/) +- Connectors catalog: [docs.scalekit.com/agentkit/connectors](https://docs.scalekit.com/agentkit/connectors/) +- BYOC (Bring Your Own Credentials): [docs.scalekit.com/agentkit/byoc](https://docs.scalekit.com/agentkit/launch-checklist/byoc/) ## When to switch skills - Use `discovering-connector-tools` when the user needs the current tool catalog or schema. -- Use the Scalekit MCP server tools to validate a tool call interactively. +- Use the Scalekit MCP server (`https://mcp.scalekit.com`) to validate a tool call interactively. - Use `exposing-agentkit-via-mcp` when the user wants AgentKit tools exposed over MCP. +- Use `sk-actions-custom-provider` to create custom connectors. diff --git a/plugins/agentkit/skills/production-readiness-agentkit/SKILL.md b/plugins/agentkit/skills/production-readiness-agentkit/SKILL.md index 7349f5b..fd3625f 100644 --- a/plugins/agentkit/skills/production-readiness-agentkit/SKILL.md +++ b/plugins/agentkit/skills/production-readiness-agentkit/SKILL.md @@ -1,6 +1,6 @@ --- name: production-readiness-agentkit -description: Walks through a structured production readiness checklist for Scalekit AgentKit implementations. Use when the user says they are going live, launching to production, doing a pre-launch review, or wants to verify their AgentKit authorization and tool-calling setup is production-ready. +description: Validates OAuth token flows, audits token storage security, verifies per-connector authorization, and checks monitoring configuration for Scalekit AgentKit implementations before production launch. Use when going live, doing a pre-launch review, or verifying AgentKit authorization and tool-calling setup is production-ready. --- # Scalekit AgentKit Production Readiness @@ -11,9 +11,20 @@ Work through each section in order — earlier sections are blockers for later o ## Quick checks (run first) -- [ ] Production environment URL, client ID, and client secret are set (not dev/staging values) +```bash +# Confirm production credentials are set (not dev/staging) +echo $SCALEKIT_ENV_URL # should be https://.scalekit.com (not .scalekit.dev) +echo $SCALEKIT_CLIENT_ID # should be set +echo $SCALEKIT_CLIENT_SECRET # should be set + +# Verify token endpoint works +curl -s -o /dev/null -w "%{http_code}" -X POST "$SCALEKIT_ENV_URL/oauth/token" \ + -d "client_id=$SCALEKIT_CLIENT_ID&client_secret=$SCALEKIT_CLIENT_SECRET&grant_type=client_credentials" +# Expected: 200 +``` + - [ ] HTTPS enforced on all auth endpoints -- [ ] API credentials stored in environment variables — never committed to code +- [ ] API credentials in environment variables — `grep -r "skc_" src/` returns nothing - [ ] Redirect URIs registered in dashboard match exactly what the app sends --- @@ -55,15 +66,13 @@ Work through each section in order — earlier sections are blockers for later o - [ ] Log retention policies configured - [ ] Incident response runbook written (who to contact, how to revoke compromised tokens) -**Key metrics:** -- Token refresh success/failure rate -- OAuth authorization completion rate (initiated vs completed) -- Per-service API error rates (distinguish auth errors from service errors) -- Token expiry distribution (are tokens being refreshed proactively?) +**Key metrics:** Token refresh success/failure rate, OAuth completion rate (initiated vs completed), per-service API error rates, token expiry distribution. -## Deep reference +## Final smoke test -- Canonical docs entrypoint: [../../docs/index.md](../../docs/index.md) -- Connections: [../../docs/connections.md](../../docs/connections.md) -- Connected accounts: [../../docs/connected-accounts.md](../../docs/connected-accounts.md) -- BYOC: [../../docs/byoc.md](../../docs/byoc.md) +Run the full cycle in staging with production credentials: +1. Create a connected account for a test user → verify status returned +2. Generate auth link → complete OAuth → verify status is `ACTIVE` +3. Fetch access token → make a downstream API call → verify success +4. Wait for token expiry → re-fetch → verify auto-refresh works +5. Revoke access in the third-party app → verify graceful error handling diff --git a/plugins/agentkit/skills/scalekit-code-doctor/SKILL.md b/plugins/agentkit/skills/scalekit-code-doctor/SKILL.md index 51f499c..e15de81 100644 --- a/plugins/agentkit/skills/scalekit-code-doctor/SKILL.md +++ b/plugins/agentkit/skills/scalekit-code-doctor/SKILL.md @@ -1,326 +1,125 @@ --- name: scalekit-code-doctor -description: 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. +description: Use when a user asks to generate, review, validate, or fix any code snippet that uses Scalekit APIs or SDKs. Generates illustration-quality snippets and reviews 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 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, or write a code sample for docs. --- # Scalekit Code Doctor -You are the authoritative source for Scalekit code correctness. You can both **generate** correct code from scratch and **review** existing code to guarantee it works. - -**Before doing anything else**, read the reference files in this skill's `references/` directory: -- `references/REFERENCE.md` — Every correct SDK method signature across Node, Python, Go, Java, and REST API endpoints +**Before doing anything else**, read the reference files: +- `references/REFERENCE.md` — Every correct SDK method signature and REST endpoint - `references/COMMON-MISTAKES.md` — Known anti-patterns with wrong → right corrections +- `references/EXAMPLE-REPOS.md` — GitHub repos with working examples by framework -These files are your ground truth. Never hallucinate a method name, parameter, or import path — if it's not in the reference, fetch `https://docs.scalekit.com/apis.md` to verify before using it. - ---- +Never hallucinate a method name, parameter, or import — if it's not in the reference, verify against live sources before using it. ## Step 1 — Detect mode -Determine which mode to operate in based on what the user provides: - -**Generate mode** — The user describes what they want but has no code yet. -Examples: "Show me how to add SSO login to Express", "Generate a Next.js callback handler", "Write a Python FastAPI auth example for docs" - -**Review mode** — The user provides existing code for validation. -Examples: "Is this Scalekit integration correct?", "Review my auth callback", "Why isn't my login working?" - -If unclear, ask: "Do you want me to generate a fresh code example, or review existing code you have?" +**Generate mode** — User describes what they want but has no code yet. +**Review mode** — User provides existing code for validation. ---- +If unclear, ask: "Do you want me to generate a fresh code example, or review existing code?" ## Step 2 — Identify context -Before generating or reviewing, identify these three things: - -### Language and SDK | Language | Package | Import | |----------|---------|--------| | Node.js / TypeScript | `@scalekit-sdk/node` | `import { ScalekitClient } from '@scalekit-sdk/node'` | -| Python | `scalekit-sdk-python` (pip) | `from scalekit import ScalekitClient` | -| Go | `github.com/scalekit-inc/scalekit-sdk-go` | `import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2"` | -| Java | `com.scalekit:scalekit-sdk-java` | `import com.scalekit.ScalekitClient;` | -| REST API | No SDK — raw HTTP | Bearer token via `POST /oauth/token` with client credentials | - -### Framework (if applicable) -Next.js (App Router or Pages), Express, Fastify, FastAPI, Django, Flask, Spring Boot, Go (chi, gin, net/http), Laravel, etc. - -### Product area - -Scalekit has two product suites. Identify which one the user's code belongs to: - -**SaaSKit** — Full-stack authentication for B2B SaaS apps -- SSO — Enterprise single sign-on (SAML, OIDC) -- Login & Sessions — Sign-up, login, logout, session management -- RBAC — Roles, permissions, access control -- SCIM — Directory sync and user provisioning -- Admin Portal — Customer-facing admin configuration +| Python | `scalekit-sdk-python` | `from scalekit import ScalekitClient` | +| Go | `scalekit-sdk-go` | `import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2"` | +| Java | `scalekit-sdk-java` | `import com.scalekit.ScalekitClient;` | -**AgentKit** — Authentication and tool access for AI agents -- Connections — OAuth token vault for third-party services (connected accounts) -- Tool Calling — Execute tools via connected accounts -- MCP Authentication — OAuth 2.1 for MCP servers -- Framework Integrations — LangChain, Vercel AI, Anthropic, OpenAI, Google ADK, Mastra - -**Cross-product** -- Webhooks — Event subscriptions and payload verification -- M2M Auth — API keys and client credentials - ---- +Product area: **SaaSKit** (SSO, login, sessions, RBAC, SCIM) or **AgentKit** (connections, tool calling, MCP auth). ## Step 3 — Generate mode -When generating code, follow these rules: - -### Quality standard: illustration-ready -The output should be clean enough to publish directly on `docs.scalekit.com` or a marketing landing page. This means: - -1. **Self-contained** — The reader understands it without seeing other files -2. **Essential path only** — Show the concept, not defensive boilerplate -3. **Real-looking values** — `'https://yourapp.com/auth/callback'` not `'http://localhost:3000/test'` -4. **Correct imports** — Exact package names from the reference table above -5. **Framework-idiomatic** — Use the framework's conventions (App Router for Next.js, decorators for FastAPI, etc.) -6. **Minimal comments** — Annotate Scalekit-specific lines only. Skip obvious framework code. -7. **1–2 pages max** — Concise. If a full flow needs more, split into labeled sections. - -### Mandatory checks before outputting generated code -Cross-reference every SDK call against `references/REFERENCE.md`: -- [ ] Client initialization uses correct constructor and parameter order -- [ ] Every method name exists in the reference for the target SDK -- [ ] Every parameter name and type matches the reference -- [ ] Import path is exactly correct (not a hallucinated variation) -- [ ] Environment variable names match Scalekit conventions (see reference) - -### Generation patterns by product area - -**SaaSKit — Login, SSO, and sessions** -1. Client initialization (singleton pattern) -2. Login route: generate auth URL with `state` for CSRF -3. Callback route: validate `state`, exchange code, store session -4. Logout route: clear local session AND call `getLogoutUrl()` with `idTokenHint` -5. Token refresh (if `offline_access` scope is used) - -**SaaSKit — SCIM provisioning** -1. Enable directory for an organization -2. List directory users and groups -3. Webhook handler for SCIM events - -**AgentKit — Connections and tool calling** -1. Client initialization -2. Create/list connected accounts -3. Execute tools with connected account credentials -4. Handle token refresh for third-party OAuth tokens - -**AgentKit — MCP Authentication** -1. MCP server setup with OAuth middleware -2. Token validation on incoming requests -3. Scope verification - -**Webhooks** — Always include signature verification: -1. Raw body parsing (not JSON-parsed) -2. `verifyWebhookPayload(secret, headers, rawBody)` -3. Event type switching - ---- +Output should be illustration-ready: self-contained, essential path only, correct imports, framework-idiomatic, 1–2 pages max. + +**Correct SaaSKit login+callback example (Node.js/Express):** + +```typescript +import { ScalekitClient } from '@scalekit-sdk/node'; +import crypto from 'crypto'; + +const scalekit = new ScalekitClient( + process.env.SCALEKIT_ENV_URL!, + process.env.SCALEKIT_CLIENT_ID!, + process.env.SCALEKIT_CLIENT_SECRET! +); + +const REDIRECT_URI = 'https://yourapp.com/auth/callback'; + +// Login — generate auth URL with CSRF state +app.get('/auth/login', (req, res) => { + const state = crypto.randomBytes(32).toString('base64url'); + res.cookie('oauth_state', state, { httpOnly: true, sameSite: 'lax', secure: true }); + const authUrl = scalekit.getAuthorizationUrl(REDIRECT_URI, { state }); + res.redirect(authUrl); +}); + +// Callback — validate state, exchange code, store session +app.get('/auth/callback', async (req, res) => { + const { code, state } = req.query; + if (state !== req.cookies.oauth_state) return res.status(403).send('CSRF mismatch'); + + const result = await scalekit.authenticateWithCode(code as string, REDIRECT_URI); + req.session.user = { id: result.user.id, email: result.user.email }; + req.session.idToken = result.idToken; + res.redirect('/dashboard'); +}); + +// Logout — clear local + end IdP session +app.post('/auth/logout', (req, res) => { + const logoutUrl = scalekit.getLogoutUrl({ + idTokenHint: req.session.idToken, + postLogoutRedirectUri: 'https://yourapp.com', + }); + req.session.destroy(() => res.redirect(logoutUrl)); +}); +``` + +**Mandatory checks before outputting generated code** — cross-reference every SDK call against `references/REFERENCE.md`: +- [ ] Method names exist for the target SDK +- [ ] Parameters match in name, order, and type +- [ ] Import path is exactly correct +- [ ] Environment variable names follow Scalekit conventions ## Step 4 — Review mode -When reviewing code, systematically check these categories in order: - -### Category 1: SDK usage correctness -For every Scalekit SDK call in the code, verify against `references/REFERENCE.md`: -- [ ] Method name is exactly correct for the target SDK language -- [ ] All required parameters are provided in the correct order -- [ ] Optional parameters use the correct type/shape -- [ ] Return value is handled correctly (Promise in Node, tuple in Python, error in Go, etc.) -- [ ] Import statement uses the correct package name and path -- [ ] Client is initialized with the correct 3 parameters: `envUrl`, `clientId`, `clientSecret` - -### Category 2: Auth flow completeness -- [ ] If there's a login route, there must be a matching callback route -- [ ] Callback validates `state` parameter (CSRF protection) -- [ ] Callback exchanges the authorization code (not just reading it) -- [ ] Session is stored after successful authentication -- [ ] Logout calls `getLogoutUrl()` — not just clearing local session -- [ ] Token refresh exists if `offline_access` or `refresh_token` is used -- [ ] IdP-initiated login is handled if callback receives `idp_initiated_login` parameter - -### Category 3: Security -- [ ] Session cookies use `httpOnly: true`, `secure: true` (in production), `sameSite: 'lax'` (never `'strict'` — breaks OAuth redirects) -- [ ] `state` parameter uses cryptographically random values, not predictable strings -- [ ] Redirect URLs are validated — only relative paths allowed for `next`/`returnTo` params (prevents open redirect) -- [ ] Client secret is read from environment variables, never hardcoded -- [ ] Webhook endpoints verify payload signature before processing -- [ ] Protected routes validate tokens server-side, not just checking cookie existence -- [ ] `Cache-Control: no-store` on authenticated pages (prevents back-button cache leak) - -### Category 4: Environment and config -- [ ] Environment variable names follow Scalekit conventions: - - `SCALEKIT_ENV_URL` (not `SCALEKIT_URL` or `SCALEKIT_ENVIRONMENT_URL` in code — though `SCALEKIT_ENVIRONMENT_URL` is used in REST API docs) - - `SCALEKIT_CLIENT_ID` - - `SCALEKIT_CLIENT_SECRET` -- [ ] Redirect URI in code matches what's registered in the Scalekit dashboard -- [ ] Correct Scalekit domain format: `https://.scalekit.com` (production) or `https://.scalekit.dev` (development) - -### Category 5: Best practices -- [ ] Client instantiated once (singleton pattern), not per-request -- [ ] Error handling uses SDK's typed exceptions where available -- [ ] Token refresh handles race conditions across concurrent requests/tabs -- [ ] `window.location.href` used for OAuth redirects (not `router.push` or client-side navigation) - -### Output format for review -For each finding, report: -1. **What's wrong** — the specific line or pattern -2. **Why it matters** — security risk, runtime error, or silent failure -3. **Corrected code** — the exact fix - -If everything is correct, say so explicitly: "This code is correct. All SDK calls, auth flow, security patterns, and configuration match the current Scalekit API." - ---- +Check these categories in order: -## Step 5 — Handling SDK updates and unknown methods +**1. SDK correctness** — Every method name, parameter, import, and return type matches `references/REFERENCE.md`. -The `references/REFERENCE.md` in this skill is a **point-in-time snapshot**. Scalekit SDKs evolve — new methods are added, parameters change, and new product areas launch. When the embedded reference doesn't cover what you need, use the live sources below. +**2. Auth flow completeness** — Login has a callback. Callback validates `state`. Logout calls `getLogoutUrl()`. Token refresh exists if `offline_access` is used. IdP-initiated login handled if applicable. -### When to check live sources +**3. Security** — Cookies: `httpOnly`, `secure`, `sameSite: 'lax'`. State: cryptographically random. Redirects: only relative paths. Secrets: from env vars. Webhooks: signature verified before processing. -- A method the user wrote isn't in the embedded reference (could be newly added, not a typo) -- The user asks about a feature you don't recognize (e.g., a new connector, a new auth mode) -- You're generating code for a product area with sparse coverage in the reference -- The user explicitly mentions a recent SDK update or version +**4. Environment** — `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`. Redirect URI matches dashboard. Domain format: `https://.scalekit.com`. -### How to check: fetch the live SDK REFERENCE.md files +**5. Best practices** — Client is singleton. Error handling uses typed exceptions. `window.location.href` for OAuth redirects (not `router.push`). -Each SDK repo has a maintained `REFERENCE.md` with full, current method signatures. Fetch the one you need: +**Output for each finding:** What's wrong → Why it matters → Corrected code. -| SDK | Live reference URL | -|-----|-------------------| -| Node.js | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-node/main/REFERENCE.md` | -| Python | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-python/main/REFERENCE.md` | -| Go | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-go/main/REFERENCE.md` | -| Java | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-java/main/REFERENCE.md` | -| REST API | `https://docs.scalekit.com/apis.md` | +## Step 5 — Unknown methods -### Resolution order +Resolution order when a method isn't in `references/REFERENCE.md`: -1. Check the embedded `references/REFERENCE.md` first (fastest, no network) -2. If the method isn't there, fetch the live SDK REFERENCE.md from the table above -3. If still not found, fetch `https://docs.scalekit.com/apis.md` for REST endpoints -4. If still not found, state explicitly: "This method could not be verified in any Scalekit reference. It may not exist." +| Priority | Source | +|----------|--------| +| 1 | Embedded `references/REFERENCE.md` | +| 2 | Live SDK reference: `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-{node,python,go,java}/main/REFERENCE.md` | +| 3 | REST API: `https://docs.scalekit.com/apis.md` | +| 4 | State explicitly: "This method could not be verified." | Never output code containing an unverified method call. ---- - -## REST API validation +## Documentation -When the user's code makes raw HTTP calls (fetch, axios, requests, http.Client) to Scalekit endpoints, validate: - -- [ ] Base URL format: `https://.scalekit.com` or `https://.scalekit.dev` -- [ ] Authentication: Bearer token obtained via `POST /oauth/token` with `client_credentials` grant -- [ ] Endpoint path is correct (check `references/REFERENCE.md` for the endpoint list) -- [ ] HTTP method matches (GET vs POST vs PUT vs PATCH vs DELETE) -- [ ] Request body matches the expected schema -- [ ] Content-Type header is set (`application/json` for most endpoints, `application/x-www-form-urlencoded` for token endpoint) -- [ ] Pagination uses `page_token` and `page_size` parameters where applicable - ---- +| Resource | URL | +|----------|-----| +| REST API reference | `https://docs.scalekit.com/apis.md` | +| LLM doc index | `https://docs.scalekit.com/llms.txt` | +| SaaSKit docs | `https://docs.scalekit.com/_llms-txt/saaskit-complete.txt` | +| AgentKit docs | `https://docs.scalekit.com/_llms-txt/agentkit.txt` | +| MCP Auth docs | `https://docs.scalekit.com/_llms-txt/mcp-authentication.txt` | -## Documentation resources - -### Live SDK references (always current — fetch when embedded reference is stale) - -| SDK | REFERENCE.md (raw) | Repo | -|-----|--------------------|----| -| Node.js | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-node/main/REFERENCE.md` | [scalekit-sdk-node](https://github.com/scalekit-inc/scalekit-sdk-node) | -| Python | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-python/main/REFERENCE.md` | [scalekit-sdk-python](https://github.com/scalekit-inc/scalekit-sdk-python) | -| Go | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-go/main/REFERENCE.md` | [scalekit-sdk-go](https://github.com/scalekit-inc/scalekit-sdk-go) | -| Java | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-java/main/REFERENCE.md` | [scalekit-sdk-java](https://github.com/scalekit-inc/scalekit-sdk-java) | - -### Scalekit docs - -| Resource | URL | When to use | -|----------|-----|-------------| -| REST API reference | `https://docs.scalekit.com/apis.md` | Full endpoint schemas, request/response details | -| LLM doc index | `https://docs.scalekit.com/llms.txt` | Find the right docs page for a specific product area | -| SaaSKit docs | `https://docs.scalekit.com/_llms-txt/saaskit-complete.txt` | Full SaaSKit reference (users, orgs, sessions, RBAC, SSO, SCIM) | -| AgentKit docs | `https://docs.scalekit.com/_llms-txt/agentkit.txt` | Full AgentKit reference (agents, OAuth vault, tool calling, connectors) | -| AgentKit frameworks | `https://docs.scalekit.com/_llms-txt/agentkit-frameworks.txt` | Framework-specific guides (LangChain, Vercel AI, Anthropic, OpenAI, Google ADK, Mastra) | -| MCP Authentication docs | `https://docs.scalekit.com/_llms-txt/mcp-authentication.txt` | MCP server OAuth 2.1, Dynamic Client Registration | - -### GitHub repos — working examples - -When generating or reviewing framework-specific code, fetch the matching repo for real, tested patterns. Repos are from `scalekit-inc` and `scalekit-developers` GitHub orgs. - -**SaaSKit — Auth examples by framework** - -| Framework | Repo | What it shows | -|-----------|------|---------------| -| Next.js (App Router) | [scalekit-nextjs-auth-example](https://github.com/scalekit-inc/scalekit-nextjs-auth-example) | SSO, sessions, protected routes, TypeScript | -| Next.js (Pages) | [nextjs-example-apps](https://github.com/scalekit-inc/nextjs-example-apps) | React SSO integration flows | -| Next.js + Auth.js | [scalekit-authjs-example](https://github.com/scalekit-developers/scalekit-authjs-example) | Enterprise SSO with next-auth v5 | -| Express.js | [scalekit-express-auth-example](https://github.com/scalekit-inc/scalekit-express-auth-example) | Node SDK, EJS frontend, sessions | -| Express.js | [scalekit-express-example](https://github.com/scalekit-developers/scalekit-express-example) | SSO with session management, middleware | -| FastAPI | [scalekit-fastapi-auth-example](https://github.com/scalekit-inc/scalekit-fastapi-auth-example) | Python SDK, OAuth 2.0, protected routes | -| FastAPI | [scalekit-fastapi-example](https://github.com/scalekit-developers/scalekit-fastapi-example) | Async auth, Pydantic models | -| Django | [scalekit-django-auth-example](https://github.com/scalekit-inc/scalekit-django-auth-example) | Python SDK, Django auth integration | -| Flask | [scalekit-flask-auth-example](https://github.com/scalekit-inc/scalekit-flask-auth-example) | Python SDK, Flask sessions | -| Spring Boot | [scalekit-springboot-auth-example](https://github.com/scalekit-inc/scalekit-springboot-auth-example) | Java, Spring Security, OIDC | -| Spring Boot | [scalekit-springboot-example](https://github.com/scalekit-developers/scalekit-springboot-example) | Java SDK, enterprise SSO | -| Go (Gin) | [scalekit-go-example](https://github.com/scalekit-developers/scalekit-go-example) | Go SDK, Gin framework, SSO | -| Laravel | [scalekit-laravel-auth-example](https://github.com/scalekit-inc/scalekit-laravel-auth-example) | REST API calls, Laravel HTTPS | -| Astro | [astro-scalekit-auth-example](https://github.com/scalekit-developers/astro-scalekit-auth-example) | Auth, SSO, social login, protected routes | -| .NET | [dotnet-example-apps](https://github.com/scalekit-inc/dotnet-example-apps) | ASP.NET Core, SAML/OIDC | -| Expo (mobile) | [expo-scalekit-sample](https://github.com/scalekit-inc/expo-scalekit-sample) | OAuth 2.0 + PKCE for mobile | - -**SaaSKit — Integration examples** - -| Integration | Repo | What it shows | -|-------------|------|---------------| -| AWS Cognito | [scalekit-cognito-sso](https://github.com/scalekit-inc/scalekit-cognito-sso) | OIDC SSO with Cognito user pools | -| Firebase | [scalekit-firebase-sso](https://github.com/scalekit-inc/scalekit-firebase-sso) | SAML/OIDC SSO with Firebase Auth | -| Supabase | [scalekit-supabase-example](https://github.com/scalekit-inc/scalekit-supabase-example) | Supabase + Scalekit auth | -| Multi-app SSO | [multiapp-demo](https://github.com/scalekit-inc/multiapp-demo) | Seamless SSO across multiple apps | -| Org switcher | [Nextjs-Django-Org-Switcher-Example](https://github.com/scalekit-inc/Nextjs-Django-Org-Switcher-Example) | Next.js frontend + Django backend, org switching | -| OIDC/SAML/SCIM | [oidc-saml-scim-examples](https://github.com/scalekit-developers/oidc-saml-scim-examples) | Google, Okta integration patterns | -| Passwordless | [passwordless-auth-demos](https://github.com/scalekit-developers/passwordless-auth-demos) | Passwordless authentication flows | -| Managed login | [managed-loginbox-expressjs-demo](https://github.com/scalekit-developers/managed-loginbox-expressjs-demo) | Hosted login UI with Express | -| Full demo app | [coffee-desk-demo](https://github.com/scalekit-inc/coffee-desk-demo) | Workspace creation, user provisioning, RBAC, SSO | - -**AgentKit — Agent and MCP examples** - -| Framework / Pattern | Repo | What it shows | -|---------------------|------|---------------| -| LangChain | [sample-langchain-agent](https://github.com/scalekit-inc/sample-langchain-agent) | Python LangChain agent with Scalekit auth | -| Google ADK | [google-adk-agent-example](https://github.com/scalekit-inc/google-adk-agent-example) | Google ADK agent with authenticated tools | -| Vercel AI SDK | [vercel-ai-agent-toolkit](https://github.com/scalekit-developers/vercel-ai-agent-toolkit) | Vercel AI SDK + Scalekit connectors | -| Apify Actor | [agentkit-apify-actor-example](https://github.com/scalekit-developers/agentkit-apify-actor-example) | OAuth auth, YouTube → Notion agent | -| LiteLLM | [litellm-agentkit-inbox-triage](https://github.com/scalekit-developers/litellm-agentkit-inbox-triage) | Inbox triage with Gmail, GitHub, Slack | -| MCP Auth (multi-framework) | [mcp-auth-demos](https://github.com/scalekit-inc/mcp-auth-demos) | MCP OAuth 2.1 demos | -| MCP + FastMCP | [fastmcp-scalekit-example](https://github.com/scalekit-inc/fastmcp-scalekit-example) | FastMCP server with Scalekit auth | -| MCP + BYOA | [byoa-demo-mcp](https://github.com/scalekit-inc/byoa-demo-mcp) | Bring your own auth + MCP | -| MCP + Coffee Desk | [coffee-desk-mcp](https://github.com/scalekit-inc/coffee-desk-mcp) | Demo MCP server with roles/permissions | -| Python connections | [python-connect-demos](https://github.com/scalekit-inc/python-connect-demos) | Python connection and identity workflows | -| Agent auth examples | [agent-auth-examples](https://github.com/scalekit-developers/agent-auth-examples) | Official AgentKit examples collection | -| Node.js agents | [agent-node-demos](https://github.com/scalekit-inc/agent-node-demos) | TypeScript agent demos | -| Workflow agents | [workflow-agents-demos](https://github.com/scalekit-developers/workflow-agents-demos) | Multi-step agent workflows | -| Render deploy kit | [render-ai-agent-deploykit](https://github.com/scalekit-developers/render-ai-agent-deploykit) | Render Workflows + Scalekit + Claude | - -**Developer tools** - -| Tool | Repo | Purpose | -|------|------|---------| -| Dryrun CLI | [scalekit-dryrun](https://github.com/scalekit-inc/scalekit-dryrun) | Test auth flows without writing code | -| Scalekit MCP server | [scalekit-mcp-server](https://github.com/scalekit-inc/scalekit-mcp-server) | Manage orgs, users, connections via AI assistants | -| API collections | [api-collections](https://github.com/scalekit-inc/api-collections) | Postman/Bruno collections for Scalekit endpoints | -| Documentation source | [developer-docs](https://github.com/scalekit-inc/developer-docs) | Docs site source (MDX) | - -**Frontend SDKs** - -| SDK | Repo | Purpose | -|-----|------|---------| -| React SDK | [scalekit-react-sdk](https://github.com/scalekit-inc/scalekit-react-sdk) | React OIDC authentication | -| Vue SDK | [scalekit-vue-sdk](https://github.com/scalekit-inc/scalekit-vue-sdk) | Vue OIDC authentication | -| Expo SDK | [scalekit-expo-sdk](https://github.com/scalekit-inc/scalekit-expo-sdk) | Expo/React Native OAuth 2.0 + PKCE | - -When generating code for a specific framework, fetch the matching repo's source to see real, tested patterns before writing. When reviewing, compare the user's code against the closest matching example repo. \ No newline at end of file +For framework-specific example repos, see `references/EXAMPLE-REPOS.md`. \ No newline at end of file diff --git a/plugins/agentkit/skills/scalekit-code-doctor/references/EXAMPLE-REPOS.md b/plugins/agentkit/skills/scalekit-code-doctor/references/EXAMPLE-REPOS.md new file mode 100644 index 0000000..a66718b --- /dev/null +++ b/plugins/agentkit/skills/scalekit-code-doctor/references/EXAMPLE-REPOS.md @@ -0,0 +1,61 @@ +# Example Repos + +Working examples by framework. Fetch the matching repo for real, tested patterns when generating or reviewing code. + +## SaaSKit — Auth examples + +| Framework | Repo | What it shows | +|-----------|------|---------------| +| Next.js (App Router) | [scalekit-nextjs-auth-example](https://github.com/scalekit-inc/scalekit-nextjs-auth-example) | SSO, sessions, protected routes | +| Next.js (Pages) | [nextjs-example-apps](https://github.com/scalekit-inc/nextjs-example-apps) | React SSO flows | +| Next.js + Auth.js | [scalekit-authjs-example](https://github.com/scalekit-developers/scalekit-authjs-example) | Enterprise SSO with next-auth v5 | +| Express.js | [scalekit-express-auth-example](https://github.com/scalekit-inc/scalekit-express-auth-example) | Node SDK, sessions | +| Express.js | [scalekit-express-example](https://github.com/scalekit-developers/scalekit-express-example) | SSO, middleware | +| FastAPI | [scalekit-fastapi-auth-example](https://github.com/scalekit-inc/scalekit-fastapi-auth-example) | Python SDK, OAuth 2.0 | +| FastAPI | [scalekit-fastapi-example](https://github.com/scalekit-developers/scalekit-fastapi-example) | Async auth, Pydantic | +| Django | [scalekit-django-auth-example](https://github.com/scalekit-inc/scalekit-django-auth-example) | Django auth integration | +| Flask | [scalekit-flask-auth-example](https://github.com/scalekit-inc/scalekit-flask-auth-example) | Flask sessions | +| Spring Boot | [scalekit-springboot-auth-example](https://github.com/scalekit-inc/scalekit-springboot-auth-example) | Spring Security, OIDC | +| Go (Gin) | [scalekit-go-example](https://github.com/scalekit-developers/scalekit-go-example) | Go SDK, SSO | +| Laravel | [scalekit-laravel-auth-example](https://github.com/scalekit-inc/scalekit-laravel-auth-example) | REST API, Laravel | +| Astro | [astro-scalekit-auth-example](https://github.com/scalekit-developers/astro-scalekit-auth-example) | Auth, SSO, social login | +| .NET | [dotnet-example-apps](https://github.com/scalekit-inc/dotnet-example-apps) | ASP.NET Core, SAML/OIDC | +| Expo (mobile) | [expo-scalekit-sample](https://github.com/scalekit-inc/expo-scalekit-sample) | OAuth 2.0 + PKCE | + +## SaaSKit — Integration examples + +| Integration | Repo | +|-------------|------| +| AWS Cognito | [scalekit-cognito-sso](https://github.com/scalekit-inc/scalekit-cognito-sso) | +| Firebase | [scalekit-firebase-sso](https://github.com/scalekit-inc/scalekit-firebase-sso) | +| Supabase | [scalekit-supabase-example](https://github.com/scalekit-inc/scalekit-supabase-example) | +| Multi-app SSO | [multiapp-demo](https://github.com/scalekit-inc/multiapp-demo) | +| OIDC/SAML/SCIM | [oidc-saml-scim-examples](https://github.com/scalekit-developers/oidc-saml-scim-examples) | +| Full demo app | [coffee-desk-demo](https://github.com/scalekit-inc/coffee-desk-demo) | + +## AgentKit — Agent and MCP examples + +| Framework | Repo | +|-----------|------| +| LangChain | [sample-langchain-agent](https://github.com/scalekit-inc/sample-langchain-agent) | +| Google ADK | [google-adk-agent-example](https://github.com/scalekit-inc/google-adk-agent-example) | +| Vercel AI SDK | [vercel-ai-agent-toolkit](https://github.com/scalekit-developers/vercel-ai-agent-toolkit) | +| MCP Auth | [mcp-auth-demos](https://github.com/scalekit-inc/mcp-auth-demos) | +| FastMCP | [fastmcp-scalekit-example](https://github.com/scalekit-inc/fastmcp-scalekit-example) | +| Agent auth | [agent-auth-examples](https://github.com/scalekit-developers/agent-auth-examples) | + +## Developer tools + +| Tool | Repo | +|------|------| +| Dryrun CLI | [scalekit-dryrun](https://github.com/scalekit-inc/scalekit-dryrun) | +| MCP server | [scalekit-mcp-server](https://github.com/scalekit-inc/scalekit-mcp-server) | +| API collections | [api-collections](https://github.com/scalekit-inc/api-collections) | + +## Frontend SDKs + +| SDK | Repo | +|-----|------| +| React | [scalekit-react-sdk](https://github.com/scalekit-inc/scalekit-react-sdk) | +| Vue | [scalekit-vue-sdk](https://github.com/scalekit-inc/scalekit-vue-sdk) | +| Expo | [scalekit-expo-sdk](https://github.com/scalekit-inc/scalekit-expo-sdk) | \ No newline at end of file diff --git a/plugins/saaskit/skills/adding-api-auth/SKILL.md b/plugins/saaskit/skills/adding-api-auth/SKILL.md index b58d011..6b2a240 100644 --- a/plugins/saaskit/skills/adding-api-auth/SKILL.md +++ b/plugins/saaskit/skills/adding-api-auth/SKILL.md @@ -3,52 +3,509 @@ name: adding-api-auth description: Implements machine-to-machine authentication using Scalekit — either long-lived opaque API keys (org or user scoped) or OAuth 2.0 client credentials for service-to-service auth. Use when adding API key auth, building key management, or implementing client credentials flows. --- -# SaaSKit API Authentication +# Adding API Key Auth (Scalekit) -Implements machine-to-machine auth for APIs using Scalekit — API keys or OAuth 2.0 client credentials. +## Flow overview -## Two approaches +``` +Your app creates token (org or user scoped) → Scalekit returns key + tokenId → +Customer stores key → API client sends Bearer key → Your server validates → +Scalekit returns org/user context → Filter data accordingly +``` -| Approach | Best for | Token type | Lifetime | -|---|---|---|---| -| **API keys** | Developer-facing APIs, integrations | Opaque string | Long-lived, manually revoked | -| **Client credentials** | Service-to-service, microservices | JWT (access token) | Short-lived, auto-refreshed | +The plain-text API key is **returned only once at creation**. Scalekit never stores it. -## When to use each +--- + +## 1. Initialize the client + +```python +# Python +from scalekit import ScalekitClient +import os + +scalekit_client = ScalekitClient( + env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"], + client_id=os.environ["SCALEKIT_CLIENT_ID"], + client_secret=os.environ["SCALEKIT_CLIENT_SECRET"], +) +``` + +```javascript +// Node.js +import { ScalekitClient } from '@scalekit-sdk/node'; + +const scalekit = new ScalekitClient( + process.env.SCALEKIT_ENVIRONMENT_URL, + process.env.SCALEKIT_CLIENT_ID, + process.env.SCALEKIT_CLIENT_SECRET +); +``` + +```go +// Go +scalekitClient := scalekit.NewScalekitClient( + os.Getenv("SCALEKIT_ENVIRONMENT_URL"), + os.Getenv("SCALEKIT_CLIENT_ID"), + os.Getenv("SCALEKIT_CLIENT_SECRET"), +) +``` + +```java +// Java +ScalekitClient scalekitClient = new ScalekitClient( + System.getenv("SCALEKIT_ENVIRONMENT_URL"), + System.getenv("SCALEKIT_CLIENT_ID"), + System.getenv("SCALEKIT_CLIENT_SECRET") +); +``` + +Required env vars: `SCALEKIT_ENVIRONMENT_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`. + +--- + +## 2. Create a token + +### Organization-scoped (default) + +Grants access to all resources in the organization's workspace. Use for service-to-service integrations (CI/CD, partner integrations, internal tooling). + +```python +# Python +response = scalekit_client.tokens.create_token( + organization_id=organization_id, + description="CI/CD pipeline token", +) +opaque_token = response.token # show to user once; never stored by Scalekit +token_id = response.token_id # format: apit_xxxxx — use for lifecycle ops +``` + +```javascript +// Node.js +const response = await scalekit.token.createToken(organizationId, { + description: 'CI/CD pipeline token', +}); +const opaqueToken = response.token; +const tokenId = response.tokenId; +``` + +```go +// Go +response, err := scalekitClient.Token().CreateToken( + ctx, organizationId, scalekit.CreateTokenOptions{ + Description: "CI/CD pipeline token", + }, +) +opaqueToken := response.Token +tokenId := response.TokenId +``` + +```java +// Java +CreateTokenResponse response = scalekitClient.tokens().create(organizationId); +String opaqueToken = response.getToken(); +String tokenId = response.getTokenId(); +``` + +### User-scoped (optional `userId`) + +Adds user context so your API can filter data to only that user's resources (personal access tokens, per-user audit trails, user-level rate limiting). Attach `customClaims` for fine-grained authz without extra DB lookups. + +```python +# Python +response = scalekit_client.tokens.create_token( + organization_id=organization_id, + user_id="usr_12345", + custom_claims={"team": "engineering", "environment": "production"}, + description="Deployment service token", +) +``` + +```javascript +// Node.js +const response = await scalekit.token.createToken(organizationId, { + userId: 'usr_12345', + customClaims: { team: 'engineering', environment: 'production' }, + description: 'Deployment service token', +}); +``` + +```go +// Go +response, err := scalekitClient.Token().CreateToken( + ctx, organizationId, scalekit.CreateTokenOptions{ + UserId: "usr_12345", + CustomClaims: map[string]string{"team": "engineering", "environment": "production"}, + Description: "Deployment service token", + }, +) +``` + +```java +// Java +Map claims = Map.of("team", "engineering", "environment", "production"); +CreateTokenResponse response = scalekitClient.tokens().create( + organizationId, "usr_12345", claims, null, "Deployment service token" +); +``` + +**Response fields:** + +| Field | Description | +|--------------|-----------------------------------------------------------| +| `token` | Plain-text API key. **Returned only at creation.** | +| `token_id` | Stable ID (`apit_xxxxx`) for list/invalidate operations. | +| `token_info` | Metadata: org, user, custom claims, timestamps. | + +--- + +## 3. Validate a token + +Call this on every incoming API request. Returns org/user context; throws on invalid, expired, or revoked keys. + +```python +# Python +from scalekit import ScalekitValidateTokenFailureException + +try: + result = scalekit_client.tokens.validate_token(token=opaque_token) + org_id = result.token_info.organization_id + user_id = result.token_info.user_id # empty for org-scoped keys + claims = result.token_info.custom_claims + roles = result.token_info.roles # populated if RBAC is configured + ext_org = result.token_info.organization_external_id +except ScalekitValidateTokenFailureException: + return 401 +``` + +```javascript +// Node.js +import { ScalekitValidateTokenFailureException } from '@scalekit-sdk/node'; + +try { + const result = await scalekit.token.validateToken(opaqueToken); + const { organizationId, userId, customClaims, roles, organizationExternalId } = result.tokenInfo; +} catch (error) { + if (error instanceof ScalekitValidateTokenFailureException) return res.status(401).end(); + throw error; +} +``` + +```go +// Go +result, err := scalekitClient.Token().ValidateToken(ctx, opaqueToken) +if errors.Is(err, scalekit.ErrTokenValidationFailed) { + c.JSON(401, gin.H{"error": "Invalid or expired token"}) + return +} +orgId := result.TokenInfo.OrganizationId +userId := result.TokenInfo.GetUserId() // *string — nil for org-scoped tokens +claims := result.TokenInfo.CustomClaims +``` + +```java +// Java +try { + ValidateTokenResponse result = scalekitClient.tokens().validate(opaqueToken); + String orgId = result.getTokenInfo().getOrganizationId(); + String userId = result.getTokenInfo().getUserId(); + Map claims = result.getTokenInfo().getCustomClaimsMap(); +} catch (TokenInvalidException e) { + response.sendError(401); +} +``` + +--- + +## 4. List tokens + +Supports pagination and optional user filter. + +```python +# Python — list with pagination +response = scalekit_client.tokens.list_tokens( + organization_id=organization_id, + page_size=10, +) +for token in response.tokens: + print(token.token_id, token.description) + +if response.next_page_token: + next_page = scalekit_client.tokens.list_tokens( + organization_id=organization_id, + page_size=10, + page_token=response.next_page_token, + ) + +# Filter by user +user_tokens = scalekit_client.tokens.list_tokens( + organization_id=organization_id, + user_id="usr_12345", +) +``` + +```javascript +// Node.js +const response = await scalekit.token.listTokens(organizationId, { pageSize: 10 }); +if (response.nextPageToken) { + const next = await scalekit.token.listTokens(organizationId, { + pageSize: 10, pageToken: response.nextPageToken + }); +} +const userTokens = await scalekit.token.listTokens(organizationId, { userId: 'usr_12345' }); +``` + +--- + +## 5. Invalidate a token + +Revocation is **instant** — the next validation for that key fails immediately. +The operation is **idempotent**: safe to call on already-revoked keys. + +```python +# Python — by token string or token_id +scalekit_client.tokens.invalidate_token(token=opaque_token) +# or +scalekit_client.tokens.invalidate_token(token=token_id) +``` + +```javascript +// Node.js +await scalekit.token.invalidateToken(opaqueToken); // or tokenId +``` + +```go +// Go +_ = scalekitClient.Token().InvalidateToken(ctx, opaqueToken) // or tokenId +``` + +```java +// Java +scalekitClient.tokens().invalidate(opaqueToken); // or tokenId +``` + +--- + +## 6. Middleware pattern (protect endpoints) + +```python +# Python — Flask decorator +from functools import wraps +from flask import request, jsonify, g +from scalekit import ScalekitValidateTokenFailureException + +def authenticate_token(f): + @wraps(f) + def wrapper(*args, **kwargs): + auth = request.headers.get("Authorization", "") + if not auth.startswith("Bearer "): + return jsonify({"error": "Missing authorization token"}), 401 + try: + result = scalekit_client.tokens.validate_token(token=auth.split(" ", 1)[1]) + g.token_info = result.token_info + except ScalekitValidateTokenFailureException: + return jsonify({"error": "Invalid or expired token"}), 401 + return f(*args, **kwargs) + return wrapper + +@app.route("/api/resources") +@authenticate_token +def get_resources(): + org_id = g.token_info.organization_id # always present + user_id = g.token_info.user_id # present only for user-scoped keys + # query DB filtered by org_id (and user_id if set) +``` + +```javascript +// Node.js — Express middleware +async function authenticateToken(req, res, next) { + const token = (req.headers.authorization || '').replace('Bearer ', ''); + if (!token) return res.status(401).json({ error: 'Missing authorization token' }); + try { + const result = await scalekit.token.validateToken(token); + req.tokenInfo = result.tokenInfo; + next(); + } catch (error) { + if (error instanceof ScalekitValidateTokenFailureException) + return res.status(401).json({ error: 'Invalid or expired token' }); + throw error; + } +} + +app.get('/api/resources', authenticateToken, (req, res) => { + const { organizationId, userId } = req.tokenInfo; +}); +``` + +```go +// Go — Gin middleware +func AuthenticateToken(sc scalekit.Scalekit) gin.HandlerFunc { + return func(c *gin.Context) { + token := strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ") + if token == "" { + c.JSON(401, gin.H{"error": "Missing authorization token"}); c.Abort(); return + } + result, err := sc.Token().ValidateToken(c.Request.Context(), token) + if err != nil { + c.JSON(401, gin.H{"error": "Invalid or expired token"}); c.Abort(); return + } + c.Set("tokenInfo", result.TokenInfo) + c.Next() + } +} +``` + +### Data filtering pattern + +| Key type | Filter query by | Example use case | +|---------------------|---------------------------------|-----------------------------------------| +| Organization-scoped | `organizationId` only | All workspace contacts in a CRM | +| User-scoped | `organizationId` + `userId` | Only tasks assigned to the calling user | +| Custom claims | Claims from `customClaims` map | Restrict by `environment`, `team`, etc. | + +--- + +## Key rules + +- **Show `token` once**: Display to user at creation, then discard — Scalekit cannot retrieve it. +- **Validate server-side on every request**: Never trust unverified tokens; call `validateToken` each time. +- **Use `token_id` for lifecycle ops**: Store `token_id` (not the key itself) for list/invalidate workflows. +- **Rotate safely**: Create new key → update consumer → verify → invalidate old key (avoids downtime). +- **Use `expiry` for time-limited access**: Limits blast radius if a key is compromised. +- **Never log or commit keys**: Treat API keys like passwords — use encrypted secrets managers or env vars. + +--- + +## Client Credentials (OAuth 2.0) + +For service-to-service (machine-to-machine) auth using JWT bearer tokens instead of opaque API keys. Use when APIs need scope-based access control, JWT validation via JWKS, or standard OAuth 2.0 client credentials flow. + +### Flow + +``` +Register client (your app) → Issue client_id + secret (Scalekit) → +API client fetches bearer token → Your server validates JWT + scopes +``` + +### Register an API client for an organization + +One organization can have multiple API clients. `plain_secret` is **returned only once**. + +```python +# Python +from scalekit.v1.clients.clients_pb2 import OrganizationClient + +response = scalekit_client.m2m_client.create_organization_client( + organization_id="", + m2m_client=OrganizationClient( + name="GitHub Actions Deployment Service", + description="Deploys to production via GitHub Actions", + scopes=["deploy:applications", "read:deployments"], # resource:action pattern + audience=["deployment-api.acmecorp.com"], + custom_claims=[ + {"key": "github_repository", "value": "acmecorp/inventory-service"}, + {"key": "environment", "value": "production_us"} + ], + expiry=3600 # seconds; default 3600 + ) +) +client_id = response.client.client_id +plain_secret = response.plain_secret # store securely; not retrievable again +``` + +### API client fetches a bearer token + +Runs inside the **API client's** code, not your server: + +```bash +curl -X POST "$SCALEKIT_ENVIRONMENT_URL/oauth/token" \ + -H "Content-Type: application/x-www-form-urlencoded" \ + -d "grant_type=client_credentials" \ + -d "client_id=" \ + -d "client_secret=" +``` + +Response includes `access_token` (JWT), `token_type`, `expires_in`, and `scope`. -### API keys -- Your users need to call your API from scripts, CI/CD, or third-party integrations. -- You want org-scoped or user-scoped keys with custom permissions. -- You need a key management UI (create, list, revoke). +### Validate the JWT on your API server -### Client credentials -- Backend services calling each other (no user context). -- You want automatic token rotation and expiry. -- You need audience-scoped tokens for zero-trust architectures. +**Do this on EVERY request. Never trust unverified tokens.** -## Workflow overview +```python +# Python — SDK handles JWKS automatically +token = request.headers.get("Authorization", "").removeprefix("Bearer ") +try: + claims = scalekit_client.validate_access_token_and_get_claims(token=token) + # claims["scopes"] → list of granted scopes +except Exception: + return 401 # invalid or expired +``` -### API keys +```javascript +// Node.js — manual JWKS + JWT verify +import jwksClient from 'jwks-rsa'; +import jwt from 'jsonwebtoken'; -1. Create key via Scalekit SDK or dashboard (org-scoped or user-scoped). -2. Client sends key in `Authorization: Bearer ` header. -3. Your middleware validates the key via Scalekit API on each request. -4. Extract org/user context and permissions from the key metadata. +const jwks = jwksClient({ + jwksUri: `${process.env.SCALEKIT_ENVIRONMENT_URL}/.well-known/jwks.json`, + cache: true +}); -### Client credentials +async function verifyToken(token) { + const decoded = jwt.decode(token, { complete: true }); + const key = await jwks.getSigningKey(decoded.header.kid); + return jwt.verify(token, key.getPublicKey(), { + algorithms: ['RS256'], + complete: true + }).payload; +} +``` -1. Register a service client in Scalekit dashboard. -2. Service calls `POST /oauth/token` with `grant_type=client_credentials`. -3. Scalekit returns a short-lived JWT access token. -4. Receiving service validates the JWT using Scalekit's JWKS endpoint. +### Enforce scopes in middleware -## Deep reference +```python +# Python — Flask +def require_scope(scope): + def decorator(f): + @functools.wraps(f) + def wrapper(*args, **kwargs): + token = request.headers.get("Authorization", "").removeprefix("Bearer ") + if not token: + return jsonify({"error": "Missing token"}), 401 + try: + claims = scalekit_client.validate_access_token_and_get_claims(token=token) + except Exception: + return jsonify({"error": "Invalid token"}), 401 + if scope not in claims.get("scopes", []): + return jsonify({"error": "Insufficient permissions"}), 403 + return f(*args, **kwargs) + return wrapper + return decorator +``` -- API auth patterns and code: [../../docs/api-auth.md](../../docs/api-auth.md) -- Access control (permission enforcement): [../../docs/access-control.md](../../docs/access-control.md) +```javascript +// Node.js — Express +function requireScope(scope) { + return async (req, res, next) => { + const token = (req.headers.authorization || '').replace('Bearer ', ''); + if (!token) return res.status(401).send('Missing token'); + try { + const payload = await verifyToken(token); + if (!payload.scopes?.includes(scope)) + return res.status(403).send('Insufficient permissions'); + req.tokenClaims = payload; + next(); + } catch { + res.status(401).send('Invalid token'); + } + }; +} +``` -## When to switch skills +### Client credentials key rules -- Use `implementing-saaskit` for user-facing browser authentication. -- Use `implementing-access-control` for permission enforcement on API endpoints. -- Use `adding-mcp-oauth` for MCP server authentication specifically. +- `plain_secret` is **returned once only** — instruct customers to store it immediately. +- Always validate tokens **server-side** before trusting claims. +- Cache JWKS keys (avoid fetching on every request); rotate on `kid` mismatch. +- Use `resource:action` scope naming (e.g. `deployments:read`, `applications:create`). +- An `organization_id` maps to one customer; multiple API clients per org are supported. diff --git a/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md b/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md index a209cd0..4421637 100644 --- a/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md +++ b/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md @@ -1,34 +1,298 @@ --- name: adding-mcp-oauth -description: Adds OAuth 2.1 authorization to Model Context Protocol servers using Scalekit. Covers Streamable HTTP transport, token validation middleware, and scope-based authorization for Node.js and Python. Use when securing MCP servers, implementing authentication for AI hosts like Claude Desktop or Cursor. +description: Guides users through adding OAuth 2.1 authorization to MCP servers using Scalekit — configures discovery endpoints, sets up token validation middleware, and enables scope-based tool authorization. Use when setting up MCP servers, implementing authentication for AI hosts like Claude Desktop, Cursor, or VS Code, or when users mention MCP security, OAuth, or Scalekit integration. --- -# SaaSKit MCP Server Auth +# Adding OAuth 2.1 Authorization to MCP Servers -Adds OAuth 2.1 authorization to MCP servers using Scalekit for token validation and scope enforcement. +## Prerequisite: HTTP transport -## Critical prereqs +MCP OAuth requires **Streamable HTTP** transport. Stdio does not support OAuth. -- **Streamable HTTP transport only** — MCP OAuth requires HTTP. The stdio transport does not support authentication. -- **Scalekit MCP server registration** — register your server in Dashboard > MCP Servers to get a `resource_id`. -- **HTTPS in production** — token validation requires secure transport. +**Node.js:** Use `StreamableHTTPServerTransport` from `@modelcontextprotocol/sdk/server/streamableHttp.js` + +**Python:** Use `mcp.streamable_http_app(path="/mcp")` and run with `uvicorn module:app` + +If currently using stdio, migrate to HTTP first. See [MCP Transport Docs](https://spec.modelcontextprotocol.io/specification/architecture/#transports). ## Setup workflow -1. Register MCP server in Scalekit dashboard (get `resource_id`). -2. Set env vars: `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`, `SCALEKIT_RESOURCE_ID`. -3. Add token validation middleware (validates JWT on every request). -4. Publish `/.well-known/oauth-protected-resource` metadata endpoint. -5. Add scope checks to individual tools. -6. Test with MCP Inspector: `npx @modelcontextprotocol/inspector@latest`. +Copy this checklist and track progress: + +``` +MCP OAuth Setup: +- [ ] Step 1: Install Scalekit SDK +- [ ] Step 2: Register MCP server in Scalekit dashboard +- [ ] Step 3: Implement discovery endpoint +- [ ] Step 4: Add token validation middleware +- [ ] Step 5: (Optional) Add scope-based authorization +- [ ] Step 6: Test with AI hosts +``` + +## Step 1: Install Scalekit SDK + +**Node.js:** +```bash +npm install @scalekit-sdk/node +``` + +**Python:** +```bash +pip install scalekit-sdk-python +``` + +Get credentials from [Scalekit dashboard](https://app.scalekit.com/) after creating an account. + +## Step 2: Register MCP server + +In Scalekit dashboard: +1. Go to **MCP servers** → **Add MCP server** +2. Provide a descriptive **name** (appears on consent page) +3. Enable **dynamic client registration** (allows automatic MCP host registration) +4. Enable **Client ID Metadata Document (CIMD)** (fetches client metadata automatically) +5. Click **Save** + +**Advanced settings** (optional): +- **Server URL**: Your MCP server identifier (e.g., `https://mcp.yourapp.com`) +- **Access token lifetime**: 300-3600 seconds recommended +- **Scopes**: Define permissions like `todo:read`, `todo:write` + +**Important**: Restart your MCP server after toggling DCR or CIMD settings. + +## Step 3: Implement discovery endpoint + +Create `/.well-known/oauth-protected-resource` endpoint. Copy metadata JSON from **Dashboard > MCP Servers > Your server > Metadata JSON**. + +**Node.js (Express):** +```javascript +app.get('/.well-known/oauth-protected-resource', (req, res) => { + res.json({ + "authorization_servers": [ + "https:///resources/" + ], + "bearer_methods_supported": ["header"], + "resource": "https://mcp.yourapp.com", + "resource_documentation": "https://mcp.yourapp.com/docs", + "scopes_supported": ["todo:read", "todo:write"] + }); +}); +``` + +**Python (FastAPI):** +```python +@app.get("/.well-known/oauth-protected-resource") +async def get_oauth_protected_resource(): + return { + "authorization_servers": [ + "https:///resources/" + ], + "bearer_methods_supported": ["header"], + "resource": "https://mcp.yourapp.com", + "resource_documentation": "https://mcp.yourapp.com/docs", + "scopes_supported": ["todo:read", "todo:write"] + } +``` + +Replace placeholders with actual values from Scalekit dashboard. + +## Step 4: Add token validation middleware + +### Initialize Scalekit client + +**Node.js:** +```javascript +import { Scalekit } from '@scalekit-sdk/node'; + +const scalekit = new Scalekit( + process.env.SCALEKIT_ENVIRONMENT_URL, + process.env.SCALEKIT_CLIENT_ID, + process.env.SCALEKIT_CLIENT_SECRET +); + +const RESOURCE_ID = 'https://your-mcp-server.com'; // Or autogenerated ID from dashboard +const METADATA_ENDPOINT = 'https://your-mcp-server.com/.well-known/oauth-protected-resource'; + +export const WWWHeader = { + HeaderKey: 'WWW-Authenticate', + HeaderValue: `Bearer realm="OAuth", resource_metadata="${METADATA_ENDPOINT}"` +}; +``` + +**Python:** +```python +from scalekit import ScalekitClient +import os + +scalekit_client = ScalekitClient( + env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), + client_id=os.getenv("SCALEKIT_CLIENT_ID"), + client_secret=os.getenv("SCALEKIT_CLIENT_SECRET") +) + +RESOURCE_ID = "https://your-mcp-server.com" +METADATA_ENDPOINT = "https://your-mcp-server.com/.well-known/oauth-protected-resource" + +WWW_HEADER = { + "WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{METADATA_ENDPOINT}"' +} +``` + +### Implement authentication middleware -## Implementation approaches +**Node.js:** +```javascript +export async function authMiddleware(req, res, next) { + try { + // Allow public access to well-known endpoints + if (req.path.includes('.well-known')) { + return next(); + } -| Approach | Framework | Complexity | Best for | -|---|---|---|---| -| FastMCP + ScalekitProvider | Python | Low (~5 lines) | New Python MCP servers | -| FastAPI + FastMCP | Python | Medium | Existing FastAPI apps adding MCP | -| Express.js + MCP SDK | Node.js | Medium | Node.js MCP servers | + // Extract Bearer token + const authHeader = req.headers['authorization']; + const token = authHeader?.startsWith('Bearer ') + ? authHeader.split('Bearer ')[1]?.trim() + : null; + + if (!token) { + throw new Error('Missing or invalid Bearer token'); + } + + // Validate token against resource audience + await scalekit.validateToken(token, { + audience: [RESOURCE_ID] + }); + + next(); + } catch (err) { + return res + .status(401) + .set(WWWHeader.HeaderKey, WWWHeader.HeaderValue) + .end(); + } +} + +// Apply to all MCP endpoints +app.use('/', authMiddleware); +``` + +**Python:** +```python +from scalekit.common.scalekit import TokenValidationOptions +from fastapi import Request, HTTPException, status + +async def auth_middleware(request: Request, call_next): + # Allow public access to well-known endpoints + if request.url.path.startswith("/.well-known"): + return await call_next(request) + + # Extract Bearer token + auth_header = request.headers.get("Authorization", "") + token = None + if auth_header.startswith("Bearer "): + token = auth_header.split("Bearer ")[1].strip() + + if not token: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + headers=WWW_HEADER + ) + + # Validate token + try: + options = TokenValidationOptions( + issuer=os.getenv("SCALEKIT_ENVIRONMENT_URL"), + audience=[RESOURCE_ID] + ) + scalekit_client.validate_token(token, options=options) + except Exception: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + headers=WWW_HEADER + ) + + return await call_next(request) + +# Apply to all MCP endpoints +app.middleware("http")(auth_middleware) +``` + +## Step 5: Scope-based tool authorization (Optional) + +Add fine-grained access control at the tool execution level: + +**Node.js:** +```javascript +try { + await scalekit.validateToken(token, { + audience: [RESOURCE_ID], + requiredScopes: [scope] // e.g., 'todo:write' + }); +} catch(error) { + return res.status(403).json({ + error: 'insufficient_scope', + error_description: `Required scope: ${scope}`, + scope: scope + }); +} +``` + +**Python:** +```python +try: + scalekit_client.validate_access_token( + token, + options=TokenValidationOptions( + audience=[RESOURCE_ID], + required_scopes=[scope] + ) + ) +except Exception: + return { + "error": "insufficient_scope", + "error_description": f"Required scope: {scope}", + "scope": scope + } +``` + +## Step 6: Verify and deploy + +### Verify your integration + +Before testing with AI hosts, the coding agent will scan your project to determine +the right URL to verify against. It will look for: + +- `RESOURCE_ID` or `resource` values in your code or `.env` +- The host/domain used in `/.well-known/oauth-protected-resource` +- Any deployed base URL in environment config (`SERVER_URL`, `PUBLIC_URL`, etc.) + +If no URL is found, you'll be asked: +> "What is your MCP server base URL? +> (e.g., `https://mcp.yourapp.com` or `https://mcp.yourapp.com/mcp`)" + +Once the URL is known, run these three checks: + +**Check 1 – Confirm 401 without token:** +```bash +curl -i +``` +Expected: `HTTP/1.1 401 Unauthorized` + +**Check 2 – Confirm WWW-Authenticate header:** +The response must include: +``` +WWW-Authenticate: Bearer realm="OAuth", resource_metadata="https:///.well-known/oauth-protected-resource" +``` +This is what triggers the MCP client's OAuth flow. A plain 401 without this header +will cause AI hosts (Claude Desktop, Cursor, VS Code) to fail silently. + +**Check 3 – Confirm metadata endpoint is reachable:** +```bash +curl https:///.well-known/oauth-protected-resource +``` +Expected: JSON with `resource`, `authorization_servers`, and `scopes_supported`. + +After verification passes, test with Claude Desktop, Cursor, and VS Code. Ensure invalid tokens get 401, and scope-based authorization (if implemented) rejects insufficient scopes. ## Framework-specific references @@ -36,13 +300,20 @@ Adds OAuth 2.1 authorization to MCP servers using Scalekit for token validation - Express.js (Node.js): [express-reference.md](express-reference.md) - FastAPI + FastMCP (Python, custom middleware): [fastapi-reference.md](fastapi-reference.md) -## Deep reference +## Common issues + +**Token validation fails**: +- Verify RESOURCE_ID matches Server URL in dashboard +- Check environment variables are set correctly +- Ensure token hasn't expired + +**Discovery endpoint not found**: +- Verify endpoint path is exactly `/.well-known/oauth-protected-resource` +- Check endpoint is publicly accessible (not protected by auth middleware) -- MCP server auth patterns: [../../docs/mcp-server-auth.md](../../docs/mcp-server-auth.md) -- API auth (related — client credentials): [../../docs/api-auth.md](../../docs/api-auth.md) +**Scope validation errors**: +- Verify scopes in dashboard match those in code +- Check token includes required scopes +- Ensure scope strings match exactly (case-sensitive) -## When to switch skills -- Use `implementing-saaskit` for user-facing browser authentication. -- Use `adding-api-auth` for non-MCP machine-to-machine auth. -- Use `production-readiness-saaskit` to validate MCP auth before launch. diff --git a/plugins/saaskit/skills/implementing-access-control/SKILL.md b/plugins/saaskit/skills/implementing-access-control/SKILL.md index 9dcee74..3b0c835 100644 --- a/plugins/saaskit/skills/implementing-access-control/SKILL.md +++ b/plugins/saaskit/skills/implementing-access-control/SKILL.md @@ -1,74 +1,137 @@ --- name: implementing-access-control -description: Implements server-side RBAC and permission checks by validating access tokens, extracting roles and permissions, and enforcing them with middleware or decorators. Use when building authorization around Scalekit tokens. +description: Implements server-side RBAC and permission checks by validating and decoding Scalekit access tokens, extracting roles/permissions, and enforcing them with middleware/decorators at route boundaries. Use when adding role-based access control, protecting routes or endpoints, building auth middleware, or checking JWT permissions with Scalekit tokens. --- -# SaaSKit Access Control - -Implements RBAC and permission enforcement using claims from Scalekit access tokens. +# Implementing access control (Scalekit FSA) ## When to use - -Use this skill **after** authentication is working (tokens are being issued and validated). Access control builds on top of a working auth flow — if login/sessions aren't set up yet, start with `implementing-saaskit`. - -## How it works - -Scalekit embeds `roles` and `permissions` claims directly in the access token JWT. Your app extracts these claims and enforces them at the middleware or handler level. - -### Token claims used for access control - -| Claim | Type | Example | -|---|---|---| -| `roles` | `string[]` | `["admin", "member"]` | -| `permissions` | `string[]` | `["organization:settings", "billing:read"]` | -| `xoid` | `string` | `wspace_abc` — the org context | - -## Workflow overview - -1. **Define roles and permissions** in Scalekit dashboard (User Management → Roles). -2. **Assign roles** to users during import, invitation, or via the SDK. -3. **Extract claims** from the validated access token in your middleware. -4. **Enforce** — check `permissions` array before allowing the action. - -## Enforcement patterns - -### Middleware-based (recommended) - +After authentication is working and the app must authorize access to routes/actions by inspecting the user's access token for `roles` and `permissions`. + +## Workflow +1. Validate the access token (expiry, issuer/audience as applicable) and then decode it to extract `sub`, `oid`, `roles`, and `permissions`. +2. Attach a normalized auth context to the request (ele: `req.user = { id, organizationId, roles, permissions }`) so downstream handlers can authorize consistently. +3. Enforce authorization at route boundaries using (a) role checks for broad access patterns and (b) permission checks for fine-grained actions (often `resource:action`). +4. Combine checks when needed (examples: "admin bypass", "resource ownership", time-based restrictions for sensitive operations). +5. Never rely on client-side authorization alone; enforce roles/permissions server-side. + +## Reference implementation + +### Node.js (Express-style middleware) + +Validate+extract, then RBAC/PBAC guards. + +```js +// validate + extract +const validateAndExtractAuth = async (req, res, next) => { + try { + const accessToken = decrypt(req.cookies.accessToken); // if encrypted + const isValid = await scalekit.validateAccessToken(accessToken); + if (!isValid) return res.status(401).json({ error: "Invalid or expired token" }); + + const tokenData = await decodeToken(accessToken); // JWT decode library + req.user = { + id: tokenData.sub, + organizationId: tokenData.oid, + roles: tokenData.roles || [], + permissions: tokenData.permissions || [] + }; + next(); + } catch { + return res.status(401).json({ error: "Authentication failed" }); + } +}; + +// RBAC +const hasRole = (user, role) => user.roles?.includes(role); +const requireRole = (role) => (req, res, next) => + hasRole(req.user, role) ? next() : res.status(403).json({ error: `Access denied. Required role: ${role}` }); + +// PBAC +const hasPermission = (user, perm) => user.permissions?.includes(perm); +const requirePermission = (perm) => (req, res, next) => + hasPermission(req.user, perm) ? next() : res.status(403).json({ error: `Access denied. Required permission: ${perm}` }); + +// usage +app.get("/api/projects", validateAndExtractAuth, requirePermission("projects:read"), handler); +app.get("/api/admin/users", validateAndExtractAuth, requireRole("admin"), handler); ``` -Request → Auth middleware (validate token) → Permission middleware (check claims) → Handler + +### Python (decorator pattern) + +Validate+extract, then RBAC/PBAC decorators. + +```py +from functools import wraps + +def validate_and_extract_auth(f): + @wraps(f) + def decorated(*args, **kwargs): + access_token = decrypt(request.cookies.get("accessToken")) + if not scalekit_client.validate_access_token(access_token): + return jsonify({"error": "Invalid or expired token"}), 401 + + token_data = scalekit_client.decode_access_token(access_token) + request.user = { + "id": token_data.get("sub"), + "organization_id": token_data.get("oid"), + "roles": token_data.get("roles", []), + "permissions": token_data.get("permissions", []), + } + return f(*args, **kwargs) + return decorated + +def require_role(role): + def decorator(f): + @wraps(f) + def decorated(*args, **kwargs): + if role not in getattr(request, "user", {}).get("roles", []): + return jsonify({"error": f"Access denied. Required role: {role}"}), 403 + return f(*args, **kwargs) + return decorated + return decorator + +def require_permission(permission): + def decorator(f): + @wraps(f) + def decorated(*args, **kwargs): + if permission not in getattr(request, "user", {}).get("permissions", []): + return jsonify({"error": f"Access denied. Required permission: {permission}"}), 403 + return f(*args, **kwargs) + return decorated + return decorator ``` -### Decorator/guard-based +## Verification -```python -@require_permission("billing:read") -def get_invoices(request): - ... -``` +After implementing, test these cases: -### Inline check +```bash +# Test with a valid token that has the required role +curl -H "Cookie: accessToken=" http://localhost:3000/api/admin/users +# Expected: 200 -```typescript -if (!tokenClaims.permissions.includes('organization:settings')) { - return res.status(403).json({ error: 'Forbidden' }); -} -``` +# Test with a token missing the required role +curl -H "Cookie: accessToken=" http://localhost:3000/api/admin/users +# Expected: 403 {"error": "Access denied. Required role: admin"} -## Best practices +# Test with an expired/invalid token +curl -H "Cookie: accessToken=expired_token" http://localhost:3000/api/projects +# Expected: 401 {"error": "Invalid or expired token"} +``` -- Check `permissions`, not `roles` — permissions are granular and composable. -- Return `403 Forbidden` (not `401`) when the user is authenticated but lacks permission. -- Log permission denials for security auditing. -- Set default roles for new users and JIT-provisioned users. +If 403 isn't returned for unauthorized users, check that the middleware chain order is correct: `validateAndExtractAuth` must run before `requireRole`/`requirePermission`. -## Deep reference +## Patterns -- Access control patterns and code: [../../docs/access-control.md](../../docs/access-control.md) -- Session management (where claims are extracted): [../../docs/sessions.md](../../docs/sessions.md) -- Framework-specific examples: [../../docs/frameworks/](../../docs/frameworks/) +- Roles for broad tiers (admin/manager/member), permissions for granular actions (`projects:create`, `tasks:assign`) +- Admin bypass: admins skip permission checks for operational tasks +- Resource ownership: user can edit only their own resource unless role-elevated -## When to switch skills +## Checklist -- Use `implementing-saaskit` if auth isn't set up yet. -- Use `managing-saaskit-sessions` for token validation and refresh. -- Use `production-readiness-saaskit` to verify RBAC enforcement before launch. +- [ ] Token validated before decoding claims +- [ ] `roles` and `permissions` normalized as arrays in request context +- [ ] Every protected route uses `requireRole(...)` and/or `requirePermission(...)` at the boundary +- [ ] Permission names follow `resource:action` convention +- [ ] Server-side checks are authoritative; client-side checks are UX only diff --git a/plugins/saaskit/skills/implementing-modular-sso/SKILL.md b/plugins/saaskit/skills/implementing-modular-sso/SKILL.md index 373211b..aa9d09e 100644 --- a/plugins/saaskit/skills/implementing-modular-sso/SKILL.md +++ b/plugins/saaskit/skills/implementing-modular-sso/SKILL.md @@ -3,55 +3,577 @@ name: implementing-modular-sso description: Implements enterprise SSO and authentication flows using Scalekit, including modular SSO (SAML/OIDC), IdP-initiated login, and admin portal for self-serve configuration. Use when adding SSO, integrating identity providers like Okta or Azure AD, or embedding the Scalekit admin portal. --- -# SaaSKit Enterprise SSO +# Implement Modular SSO -Implements enterprise SSO (SAML/OIDC) via Scalekit's modular SSO feature, including IdP-initiated login and the self-serve admin portal. +## Quick Start -## When to use +**Choose your authentication mode:** +- **Modular SSO**: You manage users and sessions (covered here) +- **SaaSKit (Full-Stack Auth)**: Scalekit manages users and sessions (built-in SSO) -- Enterprise customers require SSO with their identity provider (Okta, Azure AD, Google Workspace, etc.). -- You need to support both SP-initiated and IdP-initiated login flows. -- You want a self-serve admin portal where customer IT admins configure SSO themselves. +This skill covers Modular SSO for applications with existing user management. -## SSO flow overview +## Implementation Workflow -### SP-initiated (your app starts the login) +Copy this checklist and track progress: ``` -User clicks login → Your app → Scalekit (with organization_id or connection_id) - → IdP (Okta, Azure AD, etc.) → Scalekit callback → Your app callback +Authentication Integration Progress: +- [ ] Step 1: Configure Modular Auth mode +- [ ] Step 2: Install and configure Scalekit SDK +- [ ] Step 3: Implement authorization URL generation +- [ ] Step 4: Handle IdP-initiated SSO (RECOMMENDED) +- [ ] Step 5: Process authentication callback +- [ ] Step 6: Validate tokens and extract user profile +- [ ] Step 7: Test SSO integration +- [ ] Step 8: Set up customer onboarding flow ``` -### IdP-initiated (IdP starts the login) +## Step 1: Configure Modular Auth Mode +**Action**: Configure environment for Modular SSO: +1. Navigate to Dashboard > Authentication > General +2. Under "Full-Stack Auth" section, click "Disable Full-Stack Auth" + +**Result**: System ready for modular integration. + +## Step 2: Install and Configure SDK + +### Installation + +Choose the SDK for the project's tech stack: + +**Node.js:** +```bash +npm install @scalekit-sdk/node +``` + +**Python:** +```bash +pip install scalekit-sdk-python +``` + +**Go:** +```bash +go get github.com/scalekit-inc/scalekit-sdk-go +``` + +**Java:** +```xml + + com.scalekit + scalekit-sdk-java + +``` + +### Environment Configuration + +Add these credentials to `.env` file (fetch from Dashboard > Developers > Settings > API credentials): + +```env +SCALEKIT_ENVIRONMENT_URL= +SCALEKIT_CLIENT_ID= +SCALEKIT_CLIENT_SECRET= +``` + +--- + +## Step 3: Generate Authorization URL + +Create authorization URL to redirect users to their identity provider. + +### SSO Connection Selectors (Priority Order) + +Use ONE of these identifiers (evaluated in precedence order): + +1. **connectionId** (highest) - Direct SSO connection reference +2. **organizationId** - Routes to organization's active SSO +3. **loginHint** - Extracts domain from email to find connection + +### Implementation Pattern + +**Node.js:** +```javascript +const scalekit = new ScalekitClient( + process.env.SCALEKIT_ENVIRONMENT_URL, + process.env.SCALEKIT_CLIENT_ID, + process.env.SCALEKIT_CLIENT_SECRET +); + +const options = { + organizationId: 'org_15421144869927830', // OR + connectionId: 'conn_15696105471768821', // OR + loginHint: 'user@example.com' +}; + +const authUrl = scalekit.getAuthorizationUrl( + 'https://yourapp.com/auth/callback', + options +); + +// Redirect user to authUrl +``` + +**Python:** +```python +from scalekit import ScalekitClient, AuthorizationUrlOptions + +scalekit = ScalekitClient( + os.getenv('SCALEKIT_ENVIRONMENT_URL'), + os.getenv('SCALEKIT_CLIENT_ID'), + os.getenv('SCALEKIT_CLIENT_SECRET') +) + +options = AuthorizationUrlOptions() +options.organization_id = 'org_15421144869927830' + +auth_url = scalekit.get_authorization_url( + redirect_uri='https://yourapp.com/auth/callback', + options=options +) +``` + +**Direct URL (no SDK):** +``` +/oauth/authorize? + response_type=code& + client_id=& + redirect_uri=& + scope=openid profile email& + organization_id= +``` + +## Step 4: Handle IdP-Initiated SSO + +**CRITICAL**: Implement this to support users who start login from their identity provider portal. + +### Why This Matters + +IdP-initiated SSO converts potentially insecure flows into secure SP-initiated flows, protecting against SAML assertion theft and replay attacks. + +### Configuration Required + +1. Set initiate login endpoint: Dashboard > Authentication > Redirects +2. Configure endpoint: `https://yourapp.com/login` + +### Implementation + +**Node.js:** +```javascript +app.get('/login', async (req, res) => { + const { idp_initiated_login, error, error_description } = req.query; + + if (error) { + return res.status(400).json({ message: error_description }); + } + + if (idp_initiated_login) { + // Decode JWT to extract connection details + const claims = await scalekit.getIdpInitiatedLoginClaims(idp_initiated_login); + + const options = { + connectionId: claims.connection_id, + organizationId: claims.organization_id, + loginHint: claims.login_hint, + state: claims.relay_state + }; + + const authUrl = scalekit.getAuthorizationUrl( + 'https://yourapp.com/auth/callback', + options + ); + + return res.redirect(authUrl); + } + + // Handle normal login flow +}); ``` -User clicks app tile in IdP → Scalekit (signed JWT) → Your app's IdP-initiated handler - → Build auth URL with claims → Scalekit → IdP → Callback → Your app + +**Python:** +```python +@app.route('/login') +async def handle_login(): + idp_initiated_login = request.args.get('idp_initiated_login') + error = request.args.get('error') + + if error: + return {'error': request.args.get('error_description')}, 400 + + if idp_initiated_login: + claims = await scalekit.get_idp_initiated_login_claims(idp_initiated_login) + + options = AuthorizationUrlOptions() + options.connection_id = claims.get('connection_id') + options.organization_id = claims.get('organization_id') + options.state = claims.get('relay_state') + + auth_url = scalekit.get_authorization_url( + redirect_uri='https://yourapp.com/auth/callback', + options=options + ) + + return redirect(auth_url) ``` -## Key integration points +--- + +## Step 5: Process Authentication Callback + +Handle the callback after successful IdP authentication. + +### Callback Endpoint Setup + +1. Create endpoint: `/auth/callback` +2. Register in Dashboard > Authentication > Redirect URLs > Allowed Callback URLs + +### Implementation + +**Node.js:** +```javascript +app.get('/auth/callback', async (req, res) => { + const { code, error, error_description } = req.query; + + if (error) { + return res.status(400).json({ error: error_description }); + } + + try { + // Exchange code for user profile and tokens + const result = await scalekit.authenticateWithCode( + code, + 'https://yourapp.com/auth/callback' + ); + + // Extract user information + const userEmail = result.user.email; + const userName = result.user.givenName + ' ' + result.user.familyName; + const userId = result.user.id; + + // Create session for authenticated user + req.session.user = { + id: userId, + email: userEmail, + name: userName + }; + + res.redirect('/dashboard'); + } catch (err) { + res.status(500).json({ error: 'Authentication failed' }); + } +}); +``` + +**Python:** +```python +@app.route('/auth/callback') +async def auth_callback(): + code = request.args.get('code') + error = request.args.get('error') + + if error: + return {'error': request.args.get('error_description')}, 400 + + result = scalekit.authenticate_with_code( + code, + 'https://yourapp.com/auth/callback' + ) + + # Create session + session['user'] = { + 'id': result.user.id, + 'email': result.user.email, + 'name': f"{result.user.given_name} {result.user.family_name}" + } + + return redirect('/dashboard') +``` + +--- + +## Step 6: Validate Tokens + +**ALWAYS** validate tokens before trusting claims. + +**Node.js:** +```javascript +// Validate ID token +const idTokenClaims = await scalekit.validateToken(result.idToken); + +// Validate access token +const accessTokenClaims = await scalekit.validateToken(result.accessToken); +``` + +**Python:** +```python +id_token_claims = scalekit.validate_token(result['id_token']) +access_token_claims = scalekit.validate_token(result['access_token']) +``` + +### Token Structure + +**ID Token includes:** +- `email`: User's email address +- `given_name`, `family_name`: User's name +- `sub`: Unique user identifier (format: `connectionId;userId`) +- `oid`: Organization ID +- `amr`: Authentication method (SSO connection ID) + +**Access Token includes:** +- `sub`: User identifier +- `exp`: Expiration timestamp +- `client_id`: Your application client ID + +## Step 7: Test SSO Integration + +Use the built-in IdP Simulator for comprehensive testing. + +### Test Organization Setup + +Your environment includes pre-configured test organization with domains: +- `@example.com` +- `@example.org` + +### Testing Workflow + +1. **Find test organization**: Dashboard > Organizations +2. **Use test selector**: Pass one of these in authorization URL: + - Email with `@example.com` domain + - Test organization's connection ID + - Organization ID +3. **Simulate SSO flow**: IdP Simulator appears (mimics customer's IdP) +4. **Complete authentication**: Enter test credentials +5. **Verify callback**: Check user profile received correctly + +### Test Scenarios + +Test ALL three scenarios: +1. **SP-initiated SSO**: User starts login from your app +2. **IdP-initiated SSO**: User starts from IdP portal +3. **Domain-based routing**: User enters email, auto-routes to IdP + +--- + +## Step 8: Customer Onboarding + +Enable SSO for enterprise customers through self-service Admin Portal. + +### Quick Onboarding + +**Create organization**: Dashboard > Organizations > New Organization + +**Generate portal link** (Node.js): +```javascript +const portalLink = await scalekit.organization.generatePortalLink( + 'org_32656XXXXXX0438' +); + +// Share this link with customer's IT admin +console.log('Admin Portal:', portalLink.location); +``` + +**Share link**: Send to customer's IT administrator via email/Slack + +**Share setup guide**: Include the Scalekit [SSO setup guide](https://docs.scalekit.com/guides/integrations/sso-integrations/) — provider-specific steps for Okta, Azure AD, Google Workspace, and others. + +### Embedded Portal (Advanced) + +Embed Admin Portal in your app for seamless experience: + +```javascript +// Backend: Generate portal link +const portalLink = await scalekit.organization.generatePortalLink(orgId); +res.json({ portalUrl: portalLink.location }); +``` + +```html + + +``` +## Advanced Patterns + +### Pre-Check SSO Availability + +Prevent failed redirects by checking SSO configuration before redirecting: + +**Node.js:** +```javascript +const domain = email.split('@')[1].toLowerCase(); + +const connections = await scalekit.connections.listConnectionsByDomain({ + domain +}); + +if (connections.length > 0) { + // SSO available - redirect to IdP + const authUrl = scalekit.getAuthorizationUrl(redirectUri, { + domainHint: domain + }); + return res.redirect(authUrl); +} else { + // No SSO - show password login + return showPasswordLogin(); +} +``` + +### Domain Verification + +Enable seamless routing by verifying customer domains: +1. Customer verifies domain (e.g., `@megacorp.org`) in Admin Portal +2. Users sign in without organization selection +3. Scalekit auto-routes based on email domain + +### Session Management Best Practices + +**Set secure session configuration:** +```javascript +app.use(session({ + secret: process.env.SESSION_SECRET, + resave: false, + saveUninitialized: false, + cookie: { + secure: true, // HTTPS only + httpOnly: true, // Prevent XSS + maxAge: 86400000, // 24 hours + sameSite: 'lax' // CSRF protection + } +})); +``` + +**Implement session refresh:** +```javascript +// Check token expiration +if (Date.now() / 1000 > accessTokenClaims.exp) { + // Redirect to re-authentication + return res.redirect('/login'); +} +``` + +## Integration with Existing Auth Systems + +### Auth0 Integration + +Configure Scalekit as Custom Social Connection in Auth0: +1. Auth0 Dashboard > Authentication > Social > Create Connection +2. Use Scalekit OAuth2 endpoints +3. Map Scalekit user attributes to Auth0 profile + +### Firebase Integration + +Add Scalekit as Custom Auth Provider: +1. Use Firebase Custom Token generation +2. Exchange Scalekit tokens for Firebase tokens +3. Maintain session with Firebase SDK + +### AWS Cognito Integration + +Configure Scalekit as SAML Identity Provider: +1. Cognito User Pool > Identity Providers > SAML +2. Use Scalekit metadata URL +3. Map attributes to Cognito user attributes + +## Security Checklist + +Before production deployment, verify: + +- [ ] Environment variables stored securely (never in code) +- [ ] HTTPS enforced on all endpoints +- [ ] Tokens validated before trusting claims +- [ ] Session cookies use `secure` and `httpOnly` flags +- [ ] CSRF protection enabled +- [ ] Callback URLs registered in Scalekit dashboard +- [ ] Error messages don't expose sensitive information +- [ ] Rate limiting implemented on auth endpoints +- [ ] Logging configured (without exposing tokens) + +## Troubleshooting + +### "Invalid redirect_uri" Error + +**Cause**: Callback URL not registered in dashboard +**Fix**: Add URL to Dashboard > Authentication > Redirect URLs + +### "Organization not found" Error + +**Cause**: Invalid organization ID or user doesn't belong to organization +**Fix**: Verify organization ID and user's email domain + +### IdP-Initiated SSO Not Working + +**Cause**: Initiate login URL not configured +**Fix**: Set URL in Dashboard > Authentication > Redirects + +### Token Validation Fails + +**Cause**: Token expired or invalid signature +**Fix**: Check token expiration and environment URL configuration + +## Common Patterns + +### Multi-Tenant Architecture + +```javascript +// Determine organization from subdomain +const subdomain = req.hostname.split('.'); +const organization = await getOrganizationBySubdomain(subdomain); + +const authUrl = scalekit.getAuthorizationUrl(redirectUri, { + organizationId: organization.scalekitOrgId +}); +``` + +### Step-Up Authentication + +```javascript +// Require re-authentication for sensitive operations +if (requiresStepUp && !session.recentAuth) { + return res.redirect('/auth/step-up'); +} +``` + +### Logout Implementation + +```javascript +app.post('/logout', (req, res) => { + req.session.destroy(); + res.redirect('/'); +}); +``` + +## Reference + +**Scalekit Dashboard**: [https://app.scalekit.com](https://app.scalekit.com) + +**Connection Selector Precedence**: connectionId > organizationId > loginHint + +**Token Expiration**: ID tokens expire in 15 minutes, access tokens in 24 hours + +**Admin Portal Events**: Listen for `sso.enabled`, `sso.disabled`, `session.expired` + +**Support**: [docs.scalekit.com](https://docs.scalekit.com) + +## Implementation Notes -1. **Authorization URL** — pass `organization_id` or `connection_id` to scope to the right SSO connection. -2. **Callback handler** — same as standard auth; SSO tokens contain the same claims. -3. **IdP-initiated handler** — validate the signed JWT from Scalekit, extract org/connection hints, redirect to auth URL. -4. **Admin portal** — embed or link to Scalekit's admin portal for self-serve SSO configuration. +**Always validate tokens**: Never trust token claims without validation -## Admin portal +**Handle errors gracefully**: Show user-friendly messages, log details internally -The admin portal lets your enterprise customers configure SSO without your intervention: +**Test all scenarios**: SP-initiated, IdP-initiated, and domain-based routing -- Generate a portal link via the SDK: `sc.organization.generatePortalLink(orgId)` -- Embed it in your app's settings page or send it to the customer's IT admin. -- Customers can configure SAML/OIDC connections, test them, and manage user attribute mapping. +**Enable domain verification**: Provides best user experience -## Deep reference +**Use progressive enhancement**: Start with basic SSO, add advanced features iteratively -- SSO patterns and code: [../../docs/sso.md](../../docs/sso.md) -- Auth flows (shared callback): [../../docs/auth-flows.md](../../docs/auth-flows.md) -- Framework-specific IdP-initiated handling: [../../docs/frameworks/](../../docs/frameworks/) +**Monitor authentication flows**: Track success rates and common failure points ## When to switch skills - Use `implementing-saaskit` for the base auth flow that SSO builds on. - Use `implementing-scim-provisioning` for automated user provisioning alongside SSO. -- Use `production-readiness-saaskit` to validate SSO configuration before launch. +- Use `production-readiness-saaskit` to validate SSO configuration before launch. \ No newline at end of file diff --git a/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md b/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md index 1ff7668..de49b3e 100644 --- a/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md +++ b/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md @@ -3,66 +3,235 @@ name: implementing-saaskit-nextjs description: Implements Scalekit SaaSKit authentication in a Next.js App Router project using @scalekit-sdk/node. Use when adding auth routes, protecting pages, managing sessions, or checking permissions in Next.js with Scalekit. --- -# SaaSKit Auth — Next.js +# Scalekit Auth — Next.js App Router -Implements Scalekit authentication in Next.js App Router projects using `@scalekit-sdk/node`. +Reference repo: [scalekit-inc/scalekit-nextjs-auth-example](https://github.com/scalekit-inc/scalekit-nextjs-auth-example) -## Reference repo +## Project structure -[scalekit-inc/scalekit-nextjs-example](https://github.com/scalekit-inc/scalekit-nextjs-example) +``` +app/api/auth/ +├── login/route.ts # GET — generates auth URL + sets CSRF state +├── callback/route.ts # GET — exchanges code, sets session cookie +├── logout/route.ts # POST — clears session, returns Scalekit logout URL +├── refresh/route.ts # POST — refreshes access token, updates session +└── validate/route.ts # Token validation endpoint + +lib/ +├── scalekit.ts # Singleton ScalekitClient + default scopes +├── cookies.ts # Session read/write/clear + OAuth state helpers +└── auth.ts # isAuthenticated(), getCurrentUser(), hasPermission() +``` + +## Environment variables + +```env +SCALEKIT_ENV_URL=https://your-env.scalekit.io +SCALEKIT_CLIENT_ID=your-client-id +SCALEKIT_CLIENT_SECRET=your-client-secret +SCALEKIT_REDIRECT_URI=http://localhost:3000/auth/callback +NEXT_PUBLIC_APP_URL=http://localhost:3000 +SCALEKIT_SCOPES=openid profile email offline_access # optional, space-separated +``` + +`SCALEKIT_REDIRECT_URI` must exactly match the allowed callback URL in the Scalekit dashboard. + +## SDK client (`lib/scalekit.ts`) + +Singleton pattern — always use `getScalekitClient()`, never instantiate directly. Throws if env vars are missing. + +```ts +import { getScalekitClient, getDefaultScopes } from '@/lib/scalekit'; + +const client = getScalekitClient(); +``` + +## Session shape (`lib/cookies.ts`) + +Session stored as JSON in a single `scalekit_session` HttpOnly cookie: + +```ts +interface SessionData { + user: { sub, email, name, given_name, family_name, preferred_username }; + tokens: { access_token, refresh_token, id_token, expires_at, expires_in }; + roles?: string[]; + permissions?: string[]; +} +``` + +Key helpers: +- `getSession()` — returns `SessionData | null` +- `setSession(data)` — writes HttpOnly cookie; expires = token `expires_at` +- `clearSession()` — deletes cookie (call on logout) +- `isTokenExpired(session)` — returns true if token expires within **5 minutes** +- `getOAuthState()` / `setOAuthState(state)` — CSRF state cookie, 10-min TTL +- Cookie config: `httpOnly: true`, `secure` in production, `sameSite: 'lax'`, `path: '/'` + +## Auth flow + +### Login (`app/api/auth/login/route.ts` — GET) + +```ts +const state = crypto.randomBytes(32).toString('base64url'); +await setOAuthState(state); +const authUrl = client.getAuthorizationUrl(redirectUri, { state, scopes: getDefaultScopes() }); +return NextResponse.json({ authUrl }); +``` + +### Callback (`app/api/auth/callback/route.ts` — GET) + +1. Validate `state` param against stored `oauth_state` cookie → redirect to `/error` on mismatch +2. `clearOAuthState()` +3. `client.authenticateWithCode(code, redirectUri)` → `authResponse` +4. `client.validateToken(authResponse.accessToken)` → extract `roles`, `permissions` + - Permission claims checked in order: `permissions` → `https://scalekit.com/permissions` → `scalekit:permissions` +5. Name resolution priority: `user.name` → `claims.name` → `givenName + familyName` → `email` → `preferred_username` → `'User'` +6. `setSession({ user, tokens, roles, permissions })` +7. Redirect to `/dashboard` + +### Logout (`app/api/auth/logout/route.ts` — POST) + +```ts +const logoutUrl = client.getLogoutUrl({ + idTokenHint: session.tokens.id_token, + postLogoutRedirectUri: process.env.NEXT_PUBLIC_APP_URL, +}); +await clearSession(); +return NextResponse.json({ logoutUrl }); +// Client receives logoutUrl and redirects +``` + +### Token refresh (`app/api/auth/refresh/route.ts` — POST) + +```ts +const refreshResponse = await client.refreshAccessToken(session.tokens.refresh_token); +// Decode exp from JWT using jose.decodeJwt(); fallback to 3600s if missing +await setSession({ ...session, tokens: { ...session.tokens, access_token, refresh_token, expires_at, expires_in } }); +``` -## Project structure overview +## Auth utilities (`lib/auth.ts`) +```ts +isAuthenticated() // → boolean (session exists) +getCurrentUser() // → session.user | null +getAccessToken() // → access_token string | null +hasPermission('read:data') // → validates token, checks permission claim ``` -app/ -├── api/ -│ ├── auth/ -│ │ ├── login/route.ts # Builds auth URL → redirects to Scalekit -│ │ ├── callback/route.ts # Exchanges code → sets session cookie -│ │ ├── session/route.ts # Validates token → returns user JSON -│ │ └── logout/route.ts # Clears session → redirects to end-session -│ └── ... -├── dashboard/ -│ └── page.tsx # Protected page -├── middleware.ts # Checks session on every request -└── lib/ - └── scalekit.ts # SDK singleton + +## Protecting routes + +For Server Components, call auth helpers directly: + +```ts +import { isAuthenticated, getCurrentUser } from '@/lib/auth'; +import { redirect } from 'next/navigation'; + +const authenticated = await isAuthenticated(); +if (!authenticated) redirect('/login'); +const user = await getCurrentUser(); ``` -## Setup +For permission-gated pages: + +```ts +import { hasPermission } from '@/lib/auth'; +const allowed = await hasPermission('org:admin'); +if (!allowed) redirect('/permission-denied'); +``` + +## Route map + +| Route | Auth required | +|---|---| +| `/` | No | +| `/login` | No | +| `/auth/callback` | No | +| `/dashboard` | Yes | +| `/sessions` | Yes | +| `/organization/settings` | Yes + permission | +| `/permission-denied` | No | +| `/error` | No | + +## Dependencies ```bash -npm install @scalekit-sdk/node +npm install @scalekit-sdk/node jose date-fns js-cookie ``` -```env -SCALEKIT_ENV_URL=https://your-env.scalekit.dev -SCALEKIT_CLIENT_ID=your_client_id -SCALEKIT_CLIENT_SECRET=your_client_secret +## Tactics + +### Edge middleware for route protection +Add `middleware.ts` at the project root to enforce auth before any Server Component renders: + +```ts +// middleware.ts +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' + +const PROTECTED_PATHS = ['/dashboard', '/sessions', '/organization'] + +export function middleware(request: NextRequest) { + const session = request.cookies.get('scalekit_session') + const isProtected = PROTECTED_PATHS.some(p => request.nextUrl.pathname.startsWith(p)) + if (isProtected && !session) { + const loginUrl = new URL('/login', request.url) + loginUrl.searchParams.set('next', request.nextUrl.pathname) + return NextResponse.redirect(loginUrl) + } + return NextResponse.next() +} + +export const config = { + matcher: ['/((?!_next|api|favicon).*)'], +} ``` -## Key patterns +Server Components should still call `isAuthenticated()` as a second layer. -- **Route Handlers** (`app/api/auth/*/route.ts`) implement the OAuth flow. -- **Middleware** (`middleware.ts`) protects pages by checking for a valid session cookie. -- **Server Components** can read session data from cookies for SSR. -- **Client Components** call `/api/auth/session` to get user data. +### Triggering login from a Client Component +`/api/auth/login` returns `{ authUrl }` — never navigate there with `router.push`. OAuth requires a full page navigation: -## Auth flow +```ts +const { authUrl } = await fetch('/api/auth/login').then(r => r.json()) +window.location.href = authUrl // full navigation, not client-side route change +``` -1. User clicks login → `GET /api/auth/login` → redirect to Scalekit -2. Scalekit redirects back → `GET /api/auth/callback` → exchange code → set httpOnly cookie -3. Middleware checks cookie on every navigation -4. Logout → `GET /api/auth/logout` → clear cookie → redirect to Scalekit end-session +### OIDC logout from the client +Logout returns `{ logoutUrl }` — the client must navigate to it: -## Deep reference +```ts +const { logoutUrl } = await fetch('/api/auth/logout', { method: 'POST' }).then(r => r.json()) +window.location.href = logoutUrl // navigates to Scalekit end-session endpoint +``` +Local session is already cleared; this step revokes the IdP session so the user isn't silently re-authenticated on next login. -- Next.js patterns and code: [../../docs/frameworks/nextjs.md](../../docs/frameworks/nextjs.md) -- Auth flows: [../../docs/auth-flows.md](../../docs/auth-flows.md) -- Sessions: [../../docs/sessions.md](../../docs/sessions.md) +### Deep link preservation +In the login page, read `?next` from search params and carry it through the state: -## When to switch skills +```ts +// app/login/page.tsx +const next = searchParams.get('next') || '/dashboard' +// Pass next to /api/auth/login as a query param, store in session before redirect +// In /api/auth/callback: redirect to stored next URL after setSession() +``` + +Validate `next` on the server: only allow relative paths (`/...`) to prevent open redirect. + +### SameSite=Lax — never Strict +The `scalekit_session` and `oauth_state` cookies must use `sameSite: 'lax'`. The OAuth callback is a cross-site redirect from Scalekit back to your app — `'strict'` drops the cookie on that redirect, causing a CSRF state mismatch error every time. + +### Cache-Control: no-store on protected pages +Without this, the browser back button after logout serves a cached authenticated page: + +```ts +// In a protected route handler or layout +export const dynamic = 'force-dynamic' + +// Or explicitly in a route handler: +return new Response(html, { + headers: { 'Cache-Control': 'no-store' }, +}) +``` -- Use `implementing-saaskit` for the general (non-framework-specific) guide. -- Use `managing-saaskit-sessions` for advanced session handling. -- Use `implementing-access-control` for RBAC and permission checks. +### Token refresh race condition across tabs +Multiple browser tabs can simultaneously trigger token refresh with the same refresh token — most IdPs reject the second attempt. Mitigation: set a short-lived `refresh_in_progress` flag in the session before calling the refresh endpoint, and check it at the start of the refresh route to skip concurrent calls. diff --git a/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md b/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md index 2853541..f071ce2 100644 --- a/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md +++ b/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md @@ -40,26 +40,65 @@ sc = ScalekitClient( Each framework has different patterns for routes, middleware, and session storage: -| Framework | Auth middleware | Session store | Docs section | +| Framework | Auth middleware | Session store | Reference | |---|---|---|---| -| Django | Custom middleware class | Django sessions (DB/cache) | Django section | -| FastAPI | Dependency injection | Server-side or JWT | FastAPI section | -| Flask | `@login_required` decorator | Flask-Session | Flask section | +| Django | Custom middleware class | Django sessions (DB/cache) | [django-reference.md](django-reference.md) | +| FastAPI | Dependency injection | Server-side or JWT | [fastapi-reference.md](fastapi-reference.md) | +| Flask | `@login_required` decorator | Flask-Session | [flask-reference.md](flask-reference.md) | -## Default workflow +## Default workflow (FastAPI example) -1. Set `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET` in `.env`. -2. Initialize `ScalekitClient` at module level. -3. Create a login route that redirects to `sc.get_authorization_url(redirect_uri, options)`. -4. Create a callback route that calls `sc.authenticate_with_code(code, redirect_uri)`. -5. Store tokens in the framework's session mechanism. -6. Create a logout route that clears the session and redirects to `sc.get_logout_url(options)`. +```python +import os, secrets +from fastapi import FastAPI, Request, Response +from fastapi.responses import RedirectResponse +from dotenv import load_dotenv +from scalekit import ScalekitClient + +load_dotenv() + +app = FastAPI() +REDIRECT_URI = os.getenv("SCALEKIT_REDIRECT_URI", "http://localhost:8000/auth/callback") + +sc = ScalekitClient( + env_url=os.getenv("SCALEKIT_ENV_URL"), + client_id=os.getenv("SCALEKIT_CLIENT_ID"), + client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), +) + +@app.get("/auth/login") +def login(response: Response): + state = secrets.token_urlsafe(32) + response = RedirectResponse(sc.get_authorization_url(REDIRECT_URI, {"state": state})) + response.set_cookie("oauth_state", state, httponly=True, samesite="lax", secure=True) + return response + +@app.get("/auth/callback") +def callback(request: Request, code: str, state: str): + stored = request.cookies.get("oauth_state") + if not stored or stored != state: + return Response("CSRF mismatch", status_code=403) + result = sc.authenticate_with_code(code, REDIRECT_URI) + # Store result.user and tokens in your session mechanism + response = RedirectResponse("/dashboard") + response.delete_cookie("oauth_state") + return response + +@app.get("/auth/logout") +def logout(request: Request): + logout_url = sc.get_logout_url({"post_logout_redirect_uri": "http://localhost:8000"}) + # Clear your session here + return RedirectResponse(logout_url) +``` + +If `authenticate_with_code` raises an exception, verify the redirect URI matches the dashboard exactly. + +For Django and Flask patterns, see the framework-specific references linked in the table above. ## Deep reference -- Python framework patterns: [../../docs/frameworks/python.md](../../docs/frameworks/python.md) -- Auth flows: [../../docs/auth-flows.md](../../docs/auth-flows.md) -- Sessions: [../../docs/sessions.md](../../docs/sessions.md) +- Auth flows: [docs.scalekit.com/authenticate/fsa/quickstart](https://docs.scalekit.com/authenticate/fsa/quickstart/) +- Sessions: [docs.scalekit.com/authenticate/fsa/sessions](https://docs.scalekit.com/authenticate/fsa/sessions/) ## When to switch skills diff --git a/plugins/saaskit/skills/implementing-saaskit/SKILL.md b/plugins/saaskit/skills/implementing-saaskit/SKILL.md index ecb1ee7..f36bb4d 100644 --- a/plugins/saaskit/skills/implementing-saaskit/SKILL.md +++ b/plugins/saaskit/skills/implementing-saaskit/SKILL.md @@ -3,77 +3,115 @@ name: implementing-saaskit description: Implements Scalekit SaaSKit authentication (sign-up, login, logout, sessions) using JWT tokens across Node.js, Python, Go, Java, or PHP. Use when building or integrating user authentication with Scalekit, setting up OAuth callbacks, token refresh, or session handling. --- -# SaaSKit Authentication +# Scalekit SaaSKit (Full-Stack Authentication) -Use this skill as the auth integration entrypoint. It should stay thin and route into the canonical docs in `docs/`. +## Setup -## Mental model +Install the SDK and set credentials in `.env`: -SaaSKit is Scalekit-managed login, session, and RBAC for SaaS apps. Scalekit acts as an OIDC/OAuth 2.0 provider — your app implements the authorization code flow against it. The SDK handles token exchange, validation, and refresh. - -Key concepts: -- **Auth URL**: Scalekit-hosted login page your app redirects to -- **Callback**: Your endpoint that receives the authorization code -- **Access token**: JWT carrying user identity, org, roles, and permissions -- **Refresh token**: Long-lived token used to renew access tokens silently +```sh +SCALEKIT_ENVIRONMENT_URL= +SCALEKIT_CLIENT_ID= +SCALEKIT_CLIENT_SECRET= +``` -## Default workflow +## Auth flow -1. Set `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET` in env. -2. Initialize the SDK client (once, at startup). -3. Build the authorization URL and redirect the user to Scalekit. -4. Handle the callback — exchange the code for tokens. -5. Store tokens in a secure session (httpOnly cookies or server-side store). -6. On logout, clear the session and redirect to Scalekit's end-session endpoint. +### 1. Redirect to login -## Quick skeleton +Generate an authorization URL and redirect the user: -### Node.js -```bash -npm install @scalekit-sdk/node +```js +// Node.js +const authorizationUrl = scalekit.getAuthorizationUrl(redirectUri, { + scopes: ['openid', 'profile', 'email', 'offline_access'] +}); +res.redirect(authorizationUrl); ``` -```typescript -import { ScalekitClient } from '@scalekit-sdk/node'; -const sc = new ScalekitClient( - process.env.SCALEKIT_ENV_URL!, - process.env.SCALEKIT_CLIENT_ID!, - process.env.SCALEKIT_CLIENT_SECRET! -); -// Step 3 — redirect to auth URL -// Step 4 — sc.authenticateWithCode(code, redirectUri) -// Step 5 — store tokens in session -// Step 6 — sc.getLogoutUrl(options) + +> `redirectUri` must exactly match the allowed callback URL registered in the Scalekit dashboard. + +### 2. Handle the callback + +Exchange the authorization code for tokens: + +```js +// Node.js +const { user, idToken, accessToken, refreshToken } = + await scalekit.authenticateWithCode(code, redirectUri); ``` -### Python -```bash -pip install scalekit-sdk-python +| Token | Purpose | +|---|---| +| `idToken` | Full user profile (sub, oid, email, name, exp) | +| `accessToken` | Roles + permissions; expires in 5 min (configurable) | +| `refreshToken` | Long-lived; use to renew access tokens | + +### 3. Create the session + +Store tokens in HttpOnly cookies: + +```js +// Node.js +res.cookie('accessToken', authResult.accessToken, { + maxAge: (authResult.expiresIn - 60) * 1000, + httpOnly: true, secure: true, path: '/api', sameSite: 'strict' +}); +res.cookie('refreshToken', authResult.refreshToken, { + httpOnly: true, secure: true, path: '/auth/refresh', sameSite: 'strict' +}); ``` -```python -from scalekit import ScalekitClient -sc = ScalekitClient(env_url, client_id, client_secret) -# Same 6-step flow — see docs/auth-flows.md for full patterns + +**Token validation middleware pattern:** +1. Read `accessToken` cookie → decrypt → `scalekit.validateAccessToken(token)` +2. If invalid → `scalekit.refreshAccessToken(refreshToken)` → update cookies +3. If refresh fails → log out the user + +### 4. Log out + +Clear session data, then redirect to Scalekit's logout endpoint: + +```js +// Node.js +clearSessionData(); +const logoutUrl = scalekit.getLogoutUrl(idTokenHint, postLogoutRedirectUri); +res.redirect(logoutUrl); // One-time use URL; expires after logout ``` +## Cross-language reference + +All SDK methods follow the same pattern across languages with minor naming conventions: + +| Operation | Node.js | Python | Go | Java | +|---|---|---|---|---| +| Auth URL | `getAuthorizationUrl` | `get_authorization_url` | `GetAuthorizationUrl` | `getAuthorizationUrl` | +| Exchange code | `authenticateWithCode` | `authenticate_with_code` | `AuthenticateWithCode` | `authenticateWithCode` | +| Validate token | `validateAccessToken` | `validate_access_token` | `ValidateAccessToken` | `validateAccessToken` | +| Refresh token | `refreshAccessToken` | `refresh_access_token` | `RefreshAccessToken` | `refreshToken` | +| Logout URL | `getLogoutUrl` | `get_logout_url` | `GetLogoutUrl` | `getLogoutUrl` | + +## What this unlocks + +One integration enables: Magic Link & OTP, social sign-ins, enterprise SSO, workspaces, MCP authentication, SCIM provisioning, and user management. + ## Framework-specific references -- Go (Gin): [go-reference.md](go-reference.md) -- Spring Boot: [springboot-reference.md](springboot-reference.md) -- Laravel: [laravel-reference.md](laravel-reference.md) - Python (Django/FastAPI/Flask): use `implementing-saaskit-python` skill - Next.js: use `implementing-saaskit-nextjs` skill +- Go (Gin): see [go-reference.md](go-reference.md) +- Spring Boot: see [springboot-reference.md](springboot-reference.md) +- Laravel: see [laravel-reference.md](laravel-reference.md) ## Deep reference -- Auth flows: [../../docs/auth-flows.md](../../docs/auth-flows.md) -- Sessions: [../../docs/sessions.md](../../docs/sessions.md) -- Access control: [../../docs/access-control.md](../../docs/access-control.md) -- API auth: [../../docs/api-auth.md](../../docs/api-auth.md) -- All frameworks: [../../docs/frameworks/](../../docs/frameworks/) +- Auth flows: [docs.scalekit.com/authenticate/fsa/quickstart](https://docs.scalekit.com/authenticate/fsa/quickstart/) +- Sessions: [docs.scalekit.com/authenticate/fsa/sessions](https://docs.scalekit.com/authenticate/fsa/sessions/) +- Access control: [docs.scalekit.com/authenticate/fsa/access-control](https://docs.scalekit.com/authenticate/fsa/access-control/) ## When to switch skills - Use `managing-saaskit-sessions` for token storage, refresh middleware, and session auditing. - Use `implementing-access-control` for RBAC and permission enforcement. +- Use `implementing-modular-sso` for enterprise SSO on top of SaaSKit. - Use `migrating-to-saaskit` when replacing an existing auth system. - Use `production-readiness-saaskit` before going live. diff --git a/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md b/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md index 2419ab8..998920a 100644 --- a/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md +++ b/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md @@ -1,57 +1,248 @@ --- name: implementing-scim-provisioning -description: Implements SCIM user provisioning using Scalekit directory API and webhooks for real-time user and group lifecycle management. Use when adding directory sync, user provisioning, or automated user lifecycle management. +description: Sets up SCIM endpoints, handles directory webhook events, maps user attributes, and manages group memberships using Scalekit's Directory API. Use when the user asks to add SCIM, directory sync, user provisioning, deprovisioning, or lifecycle management to their application. --- -# SaaSKit SCIM Provisioning +# SCIM Provisioning with Scalekit -Implements automated user and group lifecycle management via SCIM directory sync and Scalekit webhooks. +Adds automated user lifecycle management (create, update, deactivate) via Scalekit's Directory API and real-time webhooks. -## What SCIM covers +## Workflow -SCIM (System for Cross-domain Identity Management) automates the user lifecycle between an enterprise IdP and your app: +Copy and track progress: -- **User provisioning** — new users in the IdP are automatically created in your app. -- **User deprovisioning** — removed users are deactivated (prefer deactivation over hard delete). -- **Profile updates** — name, email, and attribute changes sync automatically. -- **Group sync** — IdP groups map to roles/permissions in your app. +``` +SCIM Implementation Progress: +- [ ] Step 1: Detect stack and install SDK +- [ ] Step 2: Configure environment credentials +- [ ] Step 3: Initialize Scalekit client +- [ ] Step 4: Add Directory API sync (polling/on-demand) +- [ ] Step 5: Add webhook endpoint (real-time) +- [ ] Step 6: Register webhook in Scalekit dashboard +- [ ] Step 7: Map directory events to local user operations +- [ ] Step 8: Validate end-to-end +``` -## Webhook setup overview +--- + +## Step 1: Detect stack and install SDK + +Detect the project's language/framework from existing files (`package.json`, `requirements.txt`, `go.mod`, `pom.xml`) and install accordingly: + +| Stack | Install command | +|-------|----------------| +| Node.js | `npm install @scalekit-sdk/node` | +| Python | `pip install scalekit-sdk` | +| Go | `go get github.com/scalekit/scalekit-go` | +| Java | Add `com.scalekit:scalekit-sdk` to `pom.xml` or `build.gradle` | + +--- + +## Step 2: Environment credentials + +Add to `.env` (never hardcode): + +```shell +SCALEKIT_ENVIRONMENT_URL='https://.scalekit.com' +SCALEKIT_CLIENT_ID='' +SCALEKIT_CLIENT_SECRET='' +SCALEKIT_WEBHOOK_SECRET='' +``` + +Credentials are found in **Dashboard > Developers > Settings > API Credentials**. +Webhook secret is found in **Dashboard > Webhooks** after registering an endpoint. + +--- + +## Step 3: Initialize the Scalekit client + +Insert initialization near the app's startup or service layer — match the project's existing patterns (singleton, DI, module export, etc.). + +**Node.js:** +```javascript +import { ScalekitClient } from '@scalekit-sdk/node'; +const scalekit = new ScalekitClient( + process.env.SCALEKIT_ENVIRONMENT_URL, + process.env.SCALEKIT_CLIENT_ID, + process.env.SCALEKIT_CLIENT_SECRET +); +``` + +**Python:** +```python +from scalekit import ScalekitClient +scalekit_client = ScalekitClient( + env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), + client_id=os.getenv("SCALEKIT_CLIENT_ID"), + client_secret=os.getenv("SCALEKIT_CLIENT_SECRET") +) +``` + +For Go and Java patterns, see the [Scalekit SDK documentation](https://docs.scalekit.com/apis). + +--- + +## Step 4: Directory API (on-demand sync) + +Use for scheduled jobs, onboarding flows, or bulk imports. Integrate into existing user service/repository layer — do not create a parallel user management path. + +**Fetch users and sync:** -Scalekit delivers SCIM events to your webhook endpoint in real time: +```javascript +// Node.js +const { directory } = await scalekit.directory.getPrimaryDirectoryByOrganizationId(orgId); +const { users } = await scalekit.directory.listDirectoryUsers(orgId, directory.id); -1. **Register** a webhook endpoint in Scalekit dashboard or via SDK. -2. **Validate** every request using the webhook signature (HMAC). -3. **Handle** these event types: +for (const user of users) { + await upsertUser({ email: user.email, name: user.name, orgId }); +} +``` -| Event | Action | -|---|---| -| `user_created` | Create user in your DB, assign default role | -| `user_updated` | Update profile fields | -| `user_deleted` | Deactivate user (don't hard delete) | -| `group_created` | Create role/group mapping | -| `group_updated` | Update role assignments | -| `group_deleted` | Remove role mapping | +```python +# Python +directory = scalekit_client.directory.get_primary_directory_by_organization_id(org_id) +users = scalekit_client.directory.list_directory_users(org_id, directory.id) -4. **Return 2xx quickly** — offload heavy processing to a background queue. -5. **Handle idempotently** — duplicate events must not create duplicate records. +for user in users: + upsert_user(email=user.email, name=user.name, org_id=org_id) +``` -## Retry behavior +**Group sync for RBAC:** -Scalekit retries on non-2xx responses with exponential backoff (up to 8 attempts over ~10 hours). +```javascript +const { groups } = await scalekit.directory.listDirectoryGroups(orgId, directory.id); +for (const group of groups) { + await syncGroupPermissions(group.id, group.name); +} +``` -## Integration with SSO +Plug `upsertUser` / `syncGroupPermissions` into the project's **existing** user/role management functions — identify them by searching for `createUser`, `updateUser`, or equivalent patterns in the codebase. -SCIM provisioning works alongside SSO — SSO handles authentication, SCIM handles the user lifecycle. For enterprises that use both, configure SCIM first so users exist before their first SSO login. +--- + +## Step 5: Webhook endpoint (real-time provisioning) + +Add a new route to the existing HTTP server/router. Match the framework pattern already in use (Express, FastAPI, Spring Boot, net/http, etc.). + +**ALWAYS verify the signature before processing. Return 400 on failure.** + +**Node.js (Express):** +```javascript +app.post('/webhooks/scalekit', async (req, res) => { + try { + await scalekit.verifyWebhookPayload( + process.env.SCALEKIT_WEBHOOK_SECRET, + req.headers, + req.body + ); + } catch { + return res.status(400).json({ error: 'Invalid signature' }); + } + + const { type, data } = req.body; + try { + await handleDirectoryEvent(type, data); + res.status(201).json({ status: 'processed' }); + } catch (err) { + res.status(500).json({ error: 'Processing failed' }); + } +}); +``` + +**Python (FastAPI):** +```python +@app.post("/webhooks/scalekit") +async def scalekit_webhook(request: Request): + body = await request.json() + valid = scalekit_client.verify_webhook_payload( + secret=os.getenv("SCALEKIT_WEBHOOK_SECRET"), + headers=request.headers, + payload=json.dumps(body).encode() + ) + if not valid: + raise HTTPException(status_code=400, detail="Invalid signature") + + await handle_directory_event(body.get("type"), body.get("data", {})) + return JSONResponse(status_code=201, content={"status": "processed"}) +``` + +For Go and Java, see the [Scalekit SDK documentation](https://docs.scalekit.com/apis). + +--- + +## Step 6: Event handler + +Create a single dispatcher that routes to existing user operations. Map events to the project's **existing** create/update/deactivate functions: + +```javascript +async function handleDirectoryEvent(type, data) { + switch (type) { + case 'organization.directory.user_created': + return createUser(data.email, data.name, data.organization_id); + case 'organization.directory.user_updated': + return updateUser(data.email, data.name); + case 'organization.directory.user_deleted': + return deactivateUser(data.email); // prefer deactivate over hard delete + case 'organization.directory.group_created': + case 'organization.directory.group_updated': + return syncGroup(data); + default: + console.log(`Unhandled event: ${type}`); + } +} +``` + +**Prefer deactivation over deletion** for `user_deleted` events unless the project explicitly hard-deletes users. + +--- + +## Step 7: Dashboard registration checklist + +After deploying the webhook endpoint: + +1. Go to **Dashboard > Webhooks > +Add Endpoint** +2. Enter the public HTTPS URL: `https://your-app.com/webhooks/scalekit` +3. Subscribe to events: + - `organization.directory.user_created` + - `organization.directory.user_updated` + - `organization.directory.user_deleted` + - `organization.directory.group_created` + - `organization.directory.group_updated` +4. Copy the webhook secret into `SCALEKIT_WEBHOOK_SECRET` +5. Share the [SCIM setup guide](https://docs.scalekit.com/guides/integrations/scim-integrations/) with the customer's IT admin for their IdP-specific directory sync steps. + +--- + +## Guardrails + +- **Never hardcode credentials** — always `process.env` / `os.getenv` / `System.getenv` +- **Idempotent operations** — `upsertUser` must handle duplicate events safely +- **Return 2xx quickly** — offload heavy processing to a queue if needed; Scalekit retries on non-2xx with exponential backoff (up to 8 attempts over ~10 hours) +- **Validate signatures** — every webhook request, every time +- **Deactivate, don't delete** — unless codebase explicitly hard-deletes users + +--- + +## Customer self-serve SCIM setup (admin portal) -## Deep reference +Let customers configure directory sync via an embedded admin portal. Generate a single-use portal link server-side, embed it in an iframe, and handle `SCIM_CONFIGURED` and `SESSION_EXPIRED` postMessage events. -- SCIM patterns and code: [../../docs/scim.md](../../docs/scim.md) -- SSO (complementary feature): [../../docs/sso.md](../../docs/sso.md) -- Access control (role mapping from groups): [../../docs/access-control.md](../../docs/access-control.md) +```javascript +// Server: generate link (single-use, regenerate on each page load) +const { location } = await scalekit.organization.generatePortalLink(organizationId); + +// Client: embed in iframe +// +``` + +Register your app domain in **Dashboard > Developers > API Configuration > Redirect URIs** or the iframe will be blocked. + +For no-code onboarding: **Dashboard > Organizations** → select org → **Generate link** → share URL directly. Also share [SCIM setup guides](https://docs.scalekit.com/guides/integrations/scim-integrations/) for IdP-specific steps. + +--- -## When to switch skills +## Reference -- Use `implementing-modular-sso` for SSO configuration alongside SCIM. -- Use `implementing-access-control` for mapping SCIM groups to app permissions. -- Use `production-readiness-saaskit` to validate SCIM webhooks before launch. +- Full Go/Java SDK examples → [Scalekit SDK documentation](https://docs.scalekit.com/apis) +- Webhook event payload schemas → [Scalekit webhook events](https://docs.scalekit.com/directory/scim/quickstart/) +- RBAC group-to-role mapping patterns → [Role based access control](https://docs.scalekit.com/authenticate/fsa/rbac/) diff --git a/plugins/saaskit/skills/managing-saaskit-sessions/SKILL.md b/plugins/saaskit/skills/managing-saaskit-sessions/SKILL.md index 1abee86..e3c5548 100644 --- a/plugins/saaskit/skills/managing-saaskit-sessions/SKILL.md +++ b/plugins/saaskit/skills/managing-saaskit-sessions/SKILL.md @@ -1,66 +1,349 @@ --- name: managing-saaskit-sessions -description: Manages Scalekit SaaSKit user sessions by securely storing tokens, validating access tokens on requests, refreshing tokens in middleware, and revoking sessions via Scalekit APIs. Use when building session persistence for web apps or auditing session management. +description: Manages Scalekit SaaSKit user sessions by securely storing tokens, validating access tokens on requests, refreshing tokens in middleware, and revoking sessions via Scalekit APIs. Use when building session persistence, implementing login/logout, managing cookies, handling JWT tokens, fixing session expiry, or auditing session security in a Scalekit web app. --- # SaaSKit Session Management -Covers secure token storage, validation middleware, silent refresh, and session revocation for Scalekit-powered apps. +## Inputs to collect (ask before coding) +- App type: traditional server-rendered web app, SPA, mobile app, or hybrid. +- Framework: Express/Fastify/Next (Node), Flask/Django/FastAPI (Python), Gin/Fiber (Go), Spring Boot (Java), etc. +- Token storage plan: + - Cookie names (examples used below: `accessToken`, `refreshToken`, `idToken`). + - Cookie attributes actually used in the repo (Path, Domain, Secure, HttpOnly, SameSite). +- Encryption approach already present (KMS, libsodium, AES-GCM, framework session store), or whether the app needs one introduced. +- Scalekit SDK/client availability and the exact methods used (validate, refresh, sessions list/revoke). -## What this skill covers +## Non-negotiable security rules (defaults) +- Store access and refresh tokens separately. +- Use HttpOnly cookies for tokens in traditional web apps to reduce XSS exposure. +- Use `Secure` in production (HTTPS-only) and set `SameSite` to `Lax` (required for OAuth callback redirects to work correctly; `Strict` breaks auth flows). +- Scope cookies with `Path` to reduce exposure: + - Access token cookie: scope to `/api` (or your protected routes) when possible. + - Refresh token cookie: scope to the refresh endpoint only (example `/auth/refresh`). +- Rotate refresh tokens on each refresh if your Scalekit flow supports it (token rotation helps detect theft). -- Choosing a token storage strategy (httpOnly cookies vs server-side session store) -- Validating access tokens on every authenticated request -- Implementing silent token refresh in middleware -- Revoking sessions and handling forced logout -- Auditing active sessions +## Workflow (implementation sequence) +1. Implement “store tokens” at login completion. +2. Implement “verify + refresh” middleware that runs on every protected request. +3. Implement a dedicated refresh endpoint (recommended even if middleware calls refresh internally). +4. Add logout and remote session revocation if the product needs “sign out this device / sign out all devices”. +5. Add a test checklist (cookie flags, refresh flow, failure modes). -## Inputs to collect +## 1) Store session tokens securely -Before implementing, determine: +### Cookie-based approach (traditional web apps) +Use encryption-in-cookie as an extra layer, then store: +- Access token in an HttpOnly cookie with short TTL. +- Refresh token in a separate HttpOnly cookie, ideally scoped to the refresh route. +- ID token in a place that remains available at runtime if needed for logout flows (cookie or local storage depending on your logout design). -1. **App type** — server-rendered (Next.js, Django, Rails) or SPA + API? -2. **Framework** — which SDK/framework is in use? (routes to correct patterns) -3. **Token storage plan** — httpOnly cookies (default recommendation) or server-side store (Redis, DB)? +#### Node.js (Express) +```js +import cookieParser from "cookie-parser"; +app.use(cookieParser()); -## Session lifecycle +// Example after successful authentication: +const { accessToken, expiresIn, refreshToken, idToken } = authResult; +// Encrypt before storing (implementation is app-specific) +const encAccess = encrypt(accessToken); +const encRefresh = encrypt(refreshToken); + +// Access token: short-lived, cookie scoped +res.cookie("accessToken", encAccess, { + maxAge: (expiresIn - 60) * 1000, // clock-skew buffer + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + path: "/api", +}); + +// Refresh token: separate cookie, scoped to refresh endpoint +res.cookie("refreshToken", encRefresh, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + path: "/auth/refresh", +}); + +// Optional: ID token for logout (only if your logout needs it) +if (idToken) { + res.cookie("idToken", idToken, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + path: "/", + }); +} ``` -Login callback → Store tokens → Validate on each request → Refresh when expired → Revoke on logout + +#### Python (Flask) +```py +from flask import make_response +import os + +# auth_result: access_token, expires_in, refresh_token, id_token (optional) +enc_access = encrypt(auth_result.access_token) +enc_refresh = encrypt(auth_result.refresh_token) + +resp = make_response() + +resp.set_cookie( + "accessToken", + enc_access, + max_age=auth_result.expires_in - 60, + httponly=True, + secure=os.environ.get("FLASK_ENV") == "production", + samesite="Lax", + path="/api", +) + +resp.set_cookie( + "refreshToken", + enc_refresh, + httponly=True, + secure=os.environ.get("FLASK_ENV") == "production", + samesite="Lax", + path="/auth/refresh", +) + +if getattr(auth_result, "id_token", None): + resp.set_cookie( + "idToken", + auth_result.id_token, + httponly=True, + secure=os.environ.get("FLASK_ENV") == "production", + samesite="Lax", + path="/", + ) ``` -### Storage recommendations +#### Go (Gin) +```go +// accessToken, refreshToken, expiresIn come from your auth completion result +encAccess := encrypt(accessToken) +encRefresh := encrypt(refreshToken) -| App type | Recommended storage | Notes | -|---|---|---| -| Server-rendered | httpOnly + secure + SameSite=Lax cookies | Simplest; no JS access to tokens | -| SPA + API | Server-side session store (Redis) | API sets a session ID cookie; tokens stay server-side | -| Mobile / native | Secure enclave / Keychain | Never store in localStorage | +c.SetSameSite(http.SameSiteLaxMode) + +c.SetCookie("accessToken", encAccess, expiresIn-60, "/api", "", isProd(), true) +c.SetCookie("refreshToken", encRefresh, 0, "/auth/refresh", "", isProd(), true) + +// Optional +if idToken != "" { + c.SetCookie("idToken", idToken, 0, "/", "", isProd(), true) +} +``` -### Token refresh pattern +#### Java (Spring) +```java +// Encrypt tokens before storing (implementation is app-specific) +String encAccess = encrypt(authResult.getAccessToken()); +String encRefresh = encrypt(authResult.getRefreshToken()); -Middleware should check token expiry **before** it expires — use a 5-minute buffer: +Cookie access = new Cookie("accessToken", encAccess); +access.setMaxAge(authResult.getExpiresIn() - 60); +access.setHttpOnly(true); +access.setSecure(isProd()); +access.setPath("/api"); +response.addCookie(access); +// Ensure SameSite is applied (implementation depends on your framework version) +Cookie refresh = new Cookie("refreshToken", encRefresh); +refresh.setHttpOnly(true); +refresh.setSecure(isProd()); +refresh.setPath("/auth/refresh"); +response.addCookie(refresh); ``` -if (tokenExpiresAt - now < 5 minutes) → refresh silently -if (refresh fails with invalid_grant) → clear session → redirect to login + +### SPA/mobile note (reduce CSRF exposure) +For SPAs and mobile apps, prefer: +- Access token stored in memory and sent via `Authorization: Bearer `. +- Refresh token stored in an HttpOnly cookie or secure device storage (platform dependent). +If using cookies in a browser SPA, configure CSRF protections explicitly. + +## 2) Validate access token on every request (and refresh transparently) + +### Behavior +- If access token is valid: proceed. +- If access token is expired and refresh token exists: refresh, rotate, rewrite cookies/headers, proceed. +- If refresh fails: return 401 and force re-login. + +### Node.js middleware (Express-style) +```js +export async function verifySession(req, res, next) { + const accessCookie = req.cookies?.accessToken; + const refreshCookie = req.cookies?.refreshToken; + + if (!accessCookie) return res.status(401).json({ error: "Authentication required" }); + + try { + const accessToken = decrypt(accessCookie); + const isValid = await scalekit.validateAccessToken(accessToken); + + if (isValid) return next(); + + // Not valid -> attempt refresh + if (!refreshCookie) { + return res.status(401).json({ error: "Session expired. Please sign in again." }); + } + + const refreshToken = decrypt(refreshCookie); + const authResult = await scalekit.refreshAccessToken(refreshToken); + + res.cookie("accessToken", encrypt(authResult.accessToken), { + maxAge: (authResult.expiresIn - 60) * 1000, + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + path: "/api", + }); + + res.cookie("refreshToken", encrypt(authResult.refreshToken), { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + path: "/auth/refresh", + }); + + return next(); + } catch (e) { + return res.status(401).json({ error: "Authentication failed" }); + } +} +``` + +### Python decorator (Flask) +```py +from functools import wraps +from flask import request, jsonify, make_response + +def verify_session(f): + @wraps(f) + def inner(*args, **kwargs): + access_cookie = request.cookies.get("accessToken") + refresh_cookie = request.cookies.get("refreshToken") + + if not access_cookie: + return jsonify({"error": "Authentication required"}), 401 + + try: + access_token = decrypt(access_cookie) + is_valid = scalekit_client.validate_access_token(access_token) + + if is_valid: + return f(*args, **kwargs) + + if not refresh_cookie: + return jsonify({"error": "Session expired. Please sign in again."}), 401 + + refresh_token = decrypt(refresh_cookie) + auth_result = scalekit_client.refresh_access_token(refresh_token) + + resp = make_response(f(*args, **kwargs)) + resp.set_cookie("accessToken", encrypt(auth_result.access_token), + max_age=auth_result.expires_in - 60, httponly=True, + secure=is_prod(), samesite="Lax", path="/api") + resp.set_cookie("refreshToken", encrypt(auth_result.refresh_token), + httponly=True, secure=is_prod(), samesite="Lax", path="/auth/refresh") + return resp + except Exception: + return jsonify({"error": "Authentication failed"}), 401 + return inner +``` + +### Go middleware (Gin) +```go +func VerifySession() gin.HandlerFunc { + return func(c *gin.Context) { + accessCookie, err := c.Cookie("accessToken") + if err != nil || accessCookie == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error":"Authentication required"}) + c.Abort() + return + } + + accessToken := decrypt(accessCookie) + isValid, err := scalekitClient.ValidateAccessToken(accessToken) + if err == nil && isValid { + c.Next() + return + } + + refreshCookie, err := c.Cookie("refreshToken") + if err != nil || refreshCookie == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error":"Session expired. Please sign in again."}) + c.Abort() + return + } + + refreshToken := decrypt(refreshCookie) + authResult, err := scalekitClient.RefreshAccessToken(refreshToken) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error":"Session expired. Please sign in again."}) + c.Abort() + return + } + + c.SetSameSite(http.SameSiteLaxMode) + c.SetCookie("accessToken", encrypt(authResult.AccessToken), authResult.ExpiresIn-60, "/api", "", isProd(), true) + c.SetCookie("refreshToken", encrypt(authResult.RefreshToken), 0, "/auth/refresh", "", isProd(), true) + + c.Next() + } +} ``` -### Session revocation +### Java interceptor (Spring) +Implement `HandlerInterceptor#preHandle` (or a filter) to: +- Read cookies. +- Decrypt and validate access token. +- Refresh when invalid and refresh token exists. +- Rewrite cookies and allow the request to proceed. +Return 401 on failure. -On logout, always: -1. Clear local session/cookies -2. Redirect to Scalekit's end-session endpoint (OIDC RP-initiated logout) -3. The end-session endpoint invalidates the refresh token server-side +## 3) Configure session security and duration (dashboard-driven) +Session behavior should be adjustable without code changes (typical policy knobs): +- Absolute session timeout (max total session time). +- Idle session timeout (logout after inactivity). +- Access token lifetime (drives refresh frequency). + +## 4) Manage sessions remotely (API/SDK) +Use Scalekit session APIs to implement: +- “View active sessions” in account settings. +- “Sign out this device” (revoke a single session). +- “Sign out all devices” (revoke all sessions for a user). + +### Example (Node.js) +```js +const sessionDetails = await scalekit.session.getSession("ses_1234567890123456"); + +const userSessions = await scalekit.session.getUserSessions("usr_1234567890123456", { + pageSize: 10, + filter: { status: ["ACTIVE"] } +}); + +await scalekit.session.revokeSession("ses_1234567890123456"); +await scalekit.session.revokeAllUserSessions("usr_1234567890123456"); +``` -## Deep reference +## Testing checklist (must pass) +- Cookies are `HttpOnly`, `Secure` (in prod), and `SameSite` is set intentionally. +- Cookie `Path` scoping works: refresh token cookie is only sent to `/auth/refresh`. +- Protected routes reject missing/invalid access token with 401. +- Expired access token triggers refresh and continues the request without user interaction. +- Refresh failure forces re-login (401) and does not loop. +- Multi-device: remote revoke invalidates the targeted session(s) as expected. -- Session patterns and code: [../../docs/sessions.md](../../docs/sessions.md) -- Auth flow (where tokens come from): [../../docs/auth-flows.md](../../docs/auth-flows.md) -- Framework-specific session handling: [../../docs/frameworks/](../../docs/frameworks/) +## Common failure modes +- Cookie deletion/overwrite doesn’t work due to mismatched Path/Domain. +- Refresh token accidentally sent to all endpoints (missing `Path=/auth/refresh`). +- Middleware refreshes but does not rotate tokens (misses theft detection benefits). +- SPA stores access token in localStorage (higher XSS risk) when memory storage was feasible. ## When to switch skills - Use `implementing-saaskit` for the initial auth setup that produces the tokens. - Use `implementing-access-control` for RBAC checks on the validated session. -- Use `production-readiness-saaskit` to audit session security before launch. +- Use `production-readiness-saaskit` to audit session security before launch. \ No newline at end of file diff --git a/plugins/saaskit/skills/migrating-to-saaskit/SKILL.md b/plugins/saaskit/skills/migrating-to-saaskit/SKILL.md index 2eb934b..b579143 100644 --- a/plugins/saaskit/skills/migrating-to-saaskit/SKILL.md +++ b/plugins/saaskit/skills/migrating-to-saaskit/SKILL.md @@ -1,14 +1,16 @@ --- name: migrating-to-saaskit -description: Plans and executes incremental migration from any existing authentication system (Auth0, Firebase, Cognito, custom) to Scalekit SaaSKit. Use when a user mentions migrating, switching, or moving away from their current auth provider. +description: Audits the existing auth system, exports users and orgs, imports them into Scalekit via SDK, configures redirects and roles, and deploys with a gradual rollout behind a feature flag. Use when a user mentions migrating, switching, or moving away from their current auth provider (Auth0, Firebase, Cognito, custom). --- -# SaaSKit Migration Planner +# Scalekit Auth Migration Planner -Guides an incremental, reversible migration from an existing auth system to Scalekit SaaSKit. Follow these phases in order — do not skip phases. +Guides an incremental, reversible migration from an existing auth system to Scalekit. Follow these phases in order—do not skip phases. ## Migration checklist +Copy and track progress: + ``` Migration Progress: - [ ] Phase 1: Audit and export existing auth data @@ -18,57 +20,138 @@ Migration Progress: - [ ] Phase 5: Deploy and monitor ``` +--- + ## Phase 1: Audit and export Conduct a code audit covering: - Sign-up/login flows, session middleware, token validation - RBAC logic, email verification, logout/session termination -Export: user records, org/tenant structure, role assignments, SSO/IdP configs. +Export the following data: +- User records (email, name, `email_verified`) +- Org/tenant structure +- Role assignments and permissions +- SSO/IdP provider configs -**Before proceeding:** +**Backup checklist before proceeding:** - [ ] Export a sample JWT or session cookie (understand current format) - [ ] Set up a feature flag to roll back to old auth system - [ ] Document rollback procedure +Minimum user schema: + +| Field | Required | +|---|---| +| `email` | Required | +| `first_name` | Optional | +| `last_name` | Optional | +| `email_verified` | Optional (defaults `false`) | + See [AUDIT-CHECKLIST.md](AUDIT-CHECKLIST.md) for full code audit patterns. +--- + ## Phase 2: Import organizations and users -`external_id` is critical — store original PKs to preserve mappings. +`external_id` is critical—store original PKs here to preserve system-to-system mappings. + +**Step 1: Create organizations first** + +Node.js example: +```javascript +const result = await scalekit.organization.createOrganization( + org.display_name, + { externalId: org.external_id, metadata: org.metadata } +); +``` + +**Step 2: Create users within organizations** + +```javascript +const { user } = await scalekit.user.createUserAndMembership("org_scalekit_id", { + email: "user@example.com", + externalId: "usr_987", + userProfile: { firstName: "John", lastName: "Doe" }, +}); +``` + +**Rules:** +- Set `sendInvitationEmail: false` during import to skip invite emails; membership auto-activates and email is marked verified +- Batch imports in parallel; respect Scalekit rate limits +- Validate `external_id` mappings match source data exactly -1. Create organizations first (with `externalId`). -2. Create users within organizations (with `externalId`). -3. Set `sendInvitationEmail: false` during import to skip invite emails. -4. Batch imports in parallel; respect Scalekit rate limits. +For language-specific samples (Python, Go, Java, cURL): See [IMPORT-SAMPLES.md](IMPORT-SAMPLES.md). -For language-specific samples (Node.js, Python, Go, Java, cURL): See [IMPORT-SAMPLES.md](IMPORT-SAMPLES.md). +--- ## Phase 3: Configure redirects and roles -- Register callback URLs in **Settings → Redirects** in Scalekit dashboard. -- Define roles under **User Management → Roles** or via SDK. -- Verify role claims are readable from the token after login. +**Redirects:** +- Register callback URLs in **Settings → Redirects** in Scalekit dashboard +- Add post-logout URLs to control destination after logout + +**Roles:** +- Define roles under **User Management → Roles** or via SDK +- During user import, include `roles` array inside the `membership` object +- Verify role claims are readable from the token after login + +--- ## Phase 4: Update application code -- Replace legacy JWT validation with Scalekit SDK or JWKS endpoint. -- Update login page branding in Scalekit dashboard. -- Update secondary flows: email verification, logout redirect. +**Session middleware:** Replace legacy JWT validation with Scalekit SDK or JWKS endpoint. + +Verify: +- [ ] Access tokens accepted on all protected routes +- [ ] Refresh token renewal works seamlessly +- [ ] `roles` claim from Scalekit tokens used for RBAC checks + +**Login page:** Update logo, colors, copy, and legal links in Scalekit dashboard under Branding. + +**Secondary flows to update:** +- Email verification prompt +- Logout redirect destination + +--- ## Phase 5: Deploy and monitor -1. Test login with a subset of migrated users. -2. Enable feature flag → route 5–10% of traffic to Scalekit. -3. Expand after stability confirmed. -4. Keep rollback plan active for first 48 hours. +**Pre-deployment:** +- [ ] Test login with a subset of migrated users +- [ ] Verify session creation, validation, and expiry +- [ ] Confirm role-based access works end-to-end + +**Deployment sequence:** +1. Deploy updated application code +2. Enable feature flag → route traffic to Scalekit +3. Start at 5–10% of users; expand after stability confirmed +4. Monitor auth success rates and error logs +5. Keep rollback plan active for first 48 hours + +**Post-deployment verification:** + +```bash +# Verify token endpoint works with Scalekit credentials +curl -s -o /dev/null -w "%{http_code}" -X POST "$SCALEKIT_ENV_URL/oauth/token" \ + -d "client_id=$SCALEKIT_CLIENT_ID&client_secret=$SCALEKIT_CLIENT_SECRET&grant_type=client_credentials" +# Expected: 200 + +# Verify a migrated user can be looked up +curl -s -H "Authorization: Bearer $TOKEN" "$SCALEKIT_ENV_URL/api/v1/organizations//users?email=migrated-user@example.com" | jq . +# Should return the user with correct external_id +``` + +Monitor: auth error rates, session creation/validation, SSO connection health, user-reported issues. -## Deep reference +--- -- Auth flows: [../../docs/auth-flows.md](../../docs/auth-flows.md) | Sessions: [../../docs/sessions.md](../../docs/sessions.md) | SSO: [../../docs/sso.md](../../docs/sso.md) +## Troubleshooting -## When to switch skills +| Symptom | Fix | +|---|---| +| Users can't log in | Verify callback URLs registered; check `external_id` mappings; ensure emails match exactly | +| Session validation fails | Switch JWT validation to Scalekit JWKS endpoint; verify token expiry/refresh logic | +| SSO not working | Confirm org has SSO enabled; verify IdP config; test IdP-initiated login | -- Use `implementing-saaskit` for the new auth integration code. -- Use `implementing-modular-sso` if migrating SSO connections. -- Use `production-readiness-saaskit` before going live with the migration. +> **Note:** Password migration support is coming. If required, contact Scalekit's Solutions team. diff --git a/plugins/saaskit/skills/production-readiness-saaskit/SKILL.md b/plugins/saaskit/skills/production-readiness-saaskit/SKILL.md index 3b18a84..b3e9d56 100644 --- a/plugins/saaskit/skills/production-readiness-saaskit/SKILL.md +++ b/plugins/saaskit/skills/production-readiness-saaskit/SKILL.md @@ -1,58 +1,112 @@ --- name: production-readiness-saaskit -description: Walks through a structured production readiness checklist for Scalekit SaaSKit implementations covering authentication, SSO, SCIM, MCP server auth, and API security. Use when going live, launching to production, or doing a pre-launch review. +description: Validates SSO configuration, checks SCIM provisioning, audits token security, and verifies MCP auth flows for Scalekit SaaSKit implementations before production launch. Use when going live, launching to production, or doing a pre-launch review. --- # SaaSKit Production Readiness -Unified checklist for all SaaSKit domains. Work through in order — skip sections that don't apply. +Work through in order — skip sections that don't apply. Earlier sections are blockers for later ones. -## Quick checks (run first) +## Quick checks + +```bash +# Confirm production credentials are set (not dev/staging) +echo $SCALEKIT_ENV_URL # should be https://.scalekit.com (not .scalekit.dev) +echo $SCALEKIT_CLIENT_ID # should be set +echo $SCALEKIT_CLIENT_SECRET # should be set +``` -- [ ] Production env URL, client ID, and client secret set (not dev/staging) - [ ] HTTPS enforced; CORS restricted to your domains only -- [ ] All credentials in environment variables — never committed to code -- [ ] Only enabled auth methods active in production +- [ ] All credentials in environment variables — `grep -r "sks_" src/` returns nothing +- [ ] Webhook secret in env vars (if using webhooks) + +## Core auth flows -## Customization +- [ ] Redirect URLs in code match dashboard exactly +- [ ] `state` parameter validated in callbacks (CSRF) +- [ ] Tokens stored with `httpOnly: true`, `secure: true`, `sameSite: 'lax'` +- [ ] Token refresh working; logout calls `getLogoutUrl()` with `idTokenHint` -- [ ] Login page branded; email templates customized -- [ ] Custom domain configured (if applicable); email deliverability tested -- [ ] Webhooks configured with signature validation +**Verify with curl:** -## Core auth flows +```bash +# Test token endpoint reachability +curl -s -o /dev/null -w "%{http_code}" -X POST "$SCALEKIT_ENV_URL/oauth/token" \ + -d "client_id=$SCALEKIT_CLIENT_ID&client_secret=$SCALEKIT_CLIENT_SECRET&grant_type=client_credentials" +# Expected: 200 +``` -- [ ] Login initiation, code exchange, and redirect URLs match dashboard exactly -- [ ] `state` parameter validated in callbacks (CSRF); tokens stored with `httpOnly`, `secure`, `sameSite` -- [ ] Token refresh and session timeout working; logout calls Scalekit end-session -- [ ] Each enabled auth method tested; errors handled gracefully +**Test each enabled auth method:** email/password, magic links, social logins, passkeys. For each: complete the full sign-up → login → logout cycle. + +**If a flow fails:** check redirect URI mismatch first (most common), then verify `state` cookie is `sameSite: 'lax'` (not `'strict'`). ## SSO (if applicable) - [ ] SSO tested with target IdPs (Okta, Azure AD, Google Workspace) - [ ] SP-initiated and IdP-initiated flows both working - [ ] Admin portal configured for self-serve SSO setup -- [ ] JIT provisioning: domains registered, default roles set, attribute sync enabled -- Deep reference: [../../docs/sso.md](../../docs/sso.md) +- [ ] JIT provisioning: domains registered, default roles set + +**Verify SSO round-trip:** + +```bash +# Generate an auth URL with organization_id to trigger SSO +node -e " +const { ScalekitClient } = require('@scalekit-sdk/node'); +const sc = new ScalekitClient(process.env.SCALEKIT_ENV_URL, process.env.SCALEKIT_CLIENT_ID, process.env.SCALEKIT_CLIENT_SECRET); +console.log(sc.getAuthorizationUrl(process.env.SCALEKIT_REDIRECT_URI, { organizationId: '' })); +" +# Open the URL — should redirect to the IdP login page +``` + +Test with: new users, existing users, deactivated users. ## SCIM provisioning (if applicable) -- [ ] Webhook endpoints receiving events with signature validation +- [ ] Webhook endpoints verify signature before processing - [ ] User provisioning, deprovisioning, and profile updates tested -- [ ] Group-based role sync working; idempotent handling verified -- Deep reference: [../../docs/scim.md](../../docs/scim.md) +- [ ] Deactivation preferred over hard deletion for `user_deleted` events +- [ ] Endpoint returns 2xx quickly — offload heavy processing to a queue + +**Verify webhook signature validation:** + +```typescript +// In your webhook handler — this MUST be present +const isValid = scalekit.verifyWebhookPayload( + process.env.SCALEKIT_WEBHOOK_SECRET!, + req.headers, + req.body.toString() +); +if (!isValid) return res.sendStatus(401); +``` + +**If webhooks aren't arriving:** check that the endpoint URL in the dashboard is publicly reachable and returns 2xx. ## MCP authentication (if applicable) -- [ ] MCP auth flow tested end-to-end; resource metadata published -- [ ] Scopes enforced per tool; client reconnection after token expiry working -- Deep reference: [../../docs/mcp-server-auth.md](../../docs/mcp-server-auth.md) +- [ ] Resource metadata published at `/.well-known/oauth-protected-resource` +- [ ] Scopes enforced per tool +- [ ] Client reconnection after token expiry working + +```bash +# Verify well-known endpoint is reachable +curl -s https://your-mcp-server.com/.well-known/oauth-protected-resource | jq . +# Should return JSON with resource, authorization_servers, scopes_supported +``` ## RBAC (if applicable) - [ ] Roles and permissions defined; default roles set for new users - [ ] Permission enforcement verified at API endpoints -- Deep reference: [../../docs/access-control.md](../../docs/access-control.md) + +```typescript +// Verify token contains expected claims +const claims = await scalekit.validateToken(accessToken); +console.log('roles:', claims.roles); // should list assigned roles +console.log('permissions:', claims.permissions); // should list granted permissions +``` + +**If permissions are empty:** check that roles are assigned to the user in the dashboard and that the role has permissions attached. ## Network / firewall @@ -60,7 +114,16 @@ Enterprise VPN customers must whitelist: `.scalekit.com`, `cdn.scaleki ## Monitoring -- [ ] Auth logs monitoring active; alerts for suspicious activity configured -- [ ] Webhook monitoring active; error tracking for auth and provisioning failures +- [ ] Auth logs monitoring active; alerts for suspicious activity +- [ ] Webhook error tracking configured - [ ] Incident response runbook written; rollback plan ready (feature flag) -- **Key metrics:** login success/failure rates, session duration, webhook delivery, SSO completion rate +- **Key metrics:** login success/failure rate, token refresh frequency, webhook delivery rate, SSO completion rate + +## Final smoke test + +Run the full cycle in staging with production credentials before flipping DNS: +1. Sign up a new user → verify session cookies are set correctly +2. Log out → verify IdP session ends (re-visiting login should prompt credentials) +3. Trigger SSO login → verify callback completes +4. If SCIM: trigger a directory sync event → verify user appears +5. If MCP: connect a client → verify tool execution succeeds diff --git a/plugins/saaskit/skills/scalekit-code-doctor/SKILL.md b/plugins/saaskit/skills/scalekit-code-doctor/SKILL.md index 51f499c..e15de81 100644 --- a/plugins/saaskit/skills/scalekit-code-doctor/SKILL.md +++ b/plugins/saaskit/skills/scalekit-code-doctor/SKILL.md @@ -1,326 +1,125 @@ --- name: scalekit-code-doctor -description: 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. +description: Use when a user asks to generate, review, validate, or fix any code snippet that uses Scalekit APIs or SDKs. Generates illustration-quality snippets and reviews 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 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, or write a code sample for docs. --- # Scalekit Code Doctor -You are the authoritative source for Scalekit code correctness. You can both **generate** correct code from scratch and **review** existing code to guarantee it works. - -**Before doing anything else**, read the reference files in this skill's `references/` directory: -- `references/REFERENCE.md` — Every correct SDK method signature across Node, Python, Go, Java, and REST API endpoints +**Before doing anything else**, read the reference files: +- `references/REFERENCE.md` — Every correct SDK method signature and REST endpoint - `references/COMMON-MISTAKES.md` — Known anti-patterns with wrong → right corrections +- `references/EXAMPLE-REPOS.md` — GitHub repos with working examples by framework -These files are your ground truth. Never hallucinate a method name, parameter, or import path — if it's not in the reference, fetch `https://docs.scalekit.com/apis.md` to verify before using it. - ---- +Never hallucinate a method name, parameter, or import — if it's not in the reference, verify against live sources before using it. ## Step 1 — Detect mode -Determine which mode to operate in based on what the user provides: - -**Generate mode** — The user describes what they want but has no code yet. -Examples: "Show me how to add SSO login to Express", "Generate a Next.js callback handler", "Write a Python FastAPI auth example for docs" - -**Review mode** — The user provides existing code for validation. -Examples: "Is this Scalekit integration correct?", "Review my auth callback", "Why isn't my login working?" - -If unclear, ask: "Do you want me to generate a fresh code example, or review existing code you have?" +**Generate mode** — User describes what they want but has no code yet. +**Review mode** — User provides existing code for validation. ---- +If unclear, ask: "Do you want me to generate a fresh code example, or review existing code?" ## Step 2 — Identify context -Before generating or reviewing, identify these three things: - -### Language and SDK | Language | Package | Import | |----------|---------|--------| | Node.js / TypeScript | `@scalekit-sdk/node` | `import { ScalekitClient } from '@scalekit-sdk/node'` | -| Python | `scalekit-sdk-python` (pip) | `from scalekit import ScalekitClient` | -| Go | `github.com/scalekit-inc/scalekit-sdk-go` | `import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2"` | -| Java | `com.scalekit:scalekit-sdk-java` | `import com.scalekit.ScalekitClient;` | -| REST API | No SDK — raw HTTP | Bearer token via `POST /oauth/token` with client credentials | - -### Framework (if applicable) -Next.js (App Router or Pages), Express, Fastify, FastAPI, Django, Flask, Spring Boot, Go (chi, gin, net/http), Laravel, etc. - -### Product area - -Scalekit has two product suites. Identify which one the user's code belongs to: - -**SaaSKit** — Full-stack authentication for B2B SaaS apps -- SSO — Enterprise single sign-on (SAML, OIDC) -- Login & Sessions — Sign-up, login, logout, session management -- RBAC — Roles, permissions, access control -- SCIM — Directory sync and user provisioning -- Admin Portal — Customer-facing admin configuration +| Python | `scalekit-sdk-python` | `from scalekit import ScalekitClient` | +| Go | `scalekit-sdk-go` | `import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2"` | +| Java | `scalekit-sdk-java` | `import com.scalekit.ScalekitClient;` | -**AgentKit** — Authentication and tool access for AI agents -- Connections — OAuth token vault for third-party services (connected accounts) -- Tool Calling — Execute tools via connected accounts -- MCP Authentication — OAuth 2.1 for MCP servers -- Framework Integrations — LangChain, Vercel AI, Anthropic, OpenAI, Google ADK, Mastra - -**Cross-product** -- Webhooks — Event subscriptions and payload verification -- M2M Auth — API keys and client credentials - ---- +Product area: **SaaSKit** (SSO, login, sessions, RBAC, SCIM) or **AgentKit** (connections, tool calling, MCP auth). ## Step 3 — Generate mode -When generating code, follow these rules: - -### Quality standard: illustration-ready -The output should be clean enough to publish directly on `docs.scalekit.com` or a marketing landing page. This means: - -1. **Self-contained** — The reader understands it without seeing other files -2. **Essential path only** — Show the concept, not defensive boilerplate -3. **Real-looking values** — `'https://yourapp.com/auth/callback'` not `'http://localhost:3000/test'` -4. **Correct imports** — Exact package names from the reference table above -5. **Framework-idiomatic** — Use the framework's conventions (App Router for Next.js, decorators for FastAPI, etc.) -6. **Minimal comments** — Annotate Scalekit-specific lines only. Skip obvious framework code. -7. **1–2 pages max** — Concise. If a full flow needs more, split into labeled sections. - -### Mandatory checks before outputting generated code -Cross-reference every SDK call against `references/REFERENCE.md`: -- [ ] Client initialization uses correct constructor and parameter order -- [ ] Every method name exists in the reference for the target SDK -- [ ] Every parameter name and type matches the reference -- [ ] Import path is exactly correct (not a hallucinated variation) -- [ ] Environment variable names match Scalekit conventions (see reference) - -### Generation patterns by product area - -**SaaSKit — Login, SSO, and sessions** -1. Client initialization (singleton pattern) -2. Login route: generate auth URL with `state` for CSRF -3. Callback route: validate `state`, exchange code, store session -4. Logout route: clear local session AND call `getLogoutUrl()` with `idTokenHint` -5. Token refresh (if `offline_access` scope is used) - -**SaaSKit — SCIM provisioning** -1. Enable directory for an organization -2. List directory users and groups -3. Webhook handler for SCIM events - -**AgentKit — Connections and tool calling** -1. Client initialization -2. Create/list connected accounts -3. Execute tools with connected account credentials -4. Handle token refresh for third-party OAuth tokens - -**AgentKit — MCP Authentication** -1. MCP server setup with OAuth middleware -2. Token validation on incoming requests -3. Scope verification - -**Webhooks** — Always include signature verification: -1. Raw body parsing (not JSON-parsed) -2. `verifyWebhookPayload(secret, headers, rawBody)` -3. Event type switching - ---- +Output should be illustration-ready: self-contained, essential path only, correct imports, framework-idiomatic, 1–2 pages max. + +**Correct SaaSKit login+callback example (Node.js/Express):** + +```typescript +import { ScalekitClient } from '@scalekit-sdk/node'; +import crypto from 'crypto'; + +const scalekit = new ScalekitClient( + process.env.SCALEKIT_ENV_URL!, + process.env.SCALEKIT_CLIENT_ID!, + process.env.SCALEKIT_CLIENT_SECRET! +); + +const REDIRECT_URI = 'https://yourapp.com/auth/callback'; + +// Login — generate auth URL with CSRF state +app.get('/auth/login', (req, res) => { + const state = crypto.randomBytes(32).toString('base64url'); + res.cookie('oauth_state', state, { httpOnly: true, sameSite: 'lax', secure: true }); + const authUrl = scalekit.getAuthorizationUrl(REDIRECT_URI, { state }); + res.redirect(authUrl); +}); + +// Callback — validate state, exchange code, store session +app.get('/auth/callback', async (req, res) => { + const { code, state } = req.query; + if (state !== req.cookies.oauth_state) return res.status(403).send('CSRF mismatch'); + + const result = await scalekit.authenticateWithCode(code as string, REDIRECT_URI); + req.session.user = { id: result.user.id, email: result.user.email }; + req.session.idToken = result.idToken; + res.redirect('/dashboard'); +}); + +// Logout — clear local + end IdP session +app.post('/auth/logout', (req, res) => { + const logoutUrl = scalekit.getLogoutUrl({ + idTokenHint: req.session.idToken, + postLogoutRedirectUri: 'https://yourapp.com', + }); + req.session.destroy(() => res.redirect(logoutUrl)); +}); +``` + +**Mandatory checks before outputting generated code** — cross-reference every SDK call against `references/REFERENCE.md`: +- [ ] Method names exist for the target SDK +- [ ] Parameters match in name, order, and type +- [ ] Import path is exactly correct +- [ ] Environment variable names follow Scalekit conventions ## Step 4 — Review mode -When reviewing code, systematically check these categories in order: - -### Category 1: SDK usage correctness -For every Scalekit SDK call in the code, verify against `references/REFERENCE.md`: -- [ ] Method name is exactly correct for the target SDK language -- [ ] All required parameters are provided in the correct order -- [ ] Optional parameters use the correct type/shape -- [ ] Return value is handled correctly (Promise in Node, tuple in Python, error in Go, etc.) -- [ ] Import statement uses the correct package name and path -- [ ] Client is initialized with the correct 3 parameters: `envUrl`, `clientId`, `clientSecret` - -### Category 2: Auth flow completeness -- [ ] If there's a login route, there must be a matching callback route -- [ ] Callback validates `state` parameter (CSRF protection) -- [ ] Callback exchanges the authorization code (not just reading it) -- [ ] Session is stored after successful authentication -- [ ] Logout calls `getLogoutUrl()` — not just clearing local session -- [ ] Token refresh exists if `offline_access` or `refresh_token` is used -- [ ] IdP-initiated login is handled if callback receives `idp_initiated_login` parameter - -### Category 3: Security -- [ ] Session cookies use `httpOnly: true`, `secure: true` (in production), `sameSite: 'lax'` (never `'strict'` — breaks OAuth redirects) -- [ ] `state` parameter uses cryptographically random values, not predictable strings -- [ ] Redirect URLs are validated — only relative paths allowed for `next`/`returnTo` params (prevents open redirect) -- [ ] Client secret is read from environment variables, never hardcoded -- [ ] Webhook endpoints verify payload signature before processing -- [ ] Protected routes validate tokens server-side, not just checking cookie existence -- [ ] `Cache-Control: no-store` on authenticated pages (prevents back-button cache leak) - -### Category 4: Environment and config -- [ ] Environment variable names follow Scalekit conventions: - - `SCALEKIT_ENV_URL` (not `SCALEKIT_URL` or `SCALEKIT_ENVIRONMENT_URL` in code — though `SCALEKIT_ENVIRONMENT_URL` is used in REST API docs) - - `SCALEKIT_CLIENT_ID` - - `SCALEKIT_CLIENT_SECRET` -- [ ] Redirect URI in code matches what's registered in the Scalekit dashboard -- [ ] Correct Scalekit domain format: `https://.scalekit.com` (production) or `https://.scalekit.dev` (development) - -### Category 5: Best practices -- [ ] Client instantiated once (singleton pattern), not per-request -- [ ] Error handling uses SDK's typed exceptions where available -- [ ] Token refresh handles race conditions across concurrent requests/tabs -- [ ] `window.location.href` used for OAuth redirects (not `router.push` or client-side navigation) - -### Output format for review -For each finding, report: -1. **What's wrong** — the specific line or pattern -2. **Why it matters** — security risk, runtime error, or silent failure -3. **Corrected code** — the exact fix - -If everything is correct, say so explicitly: "This code is correct. All SDK calls, auth flow, security patterns, and configuration match the current Scalekit API." - ---- +Check these categories in order: -## Step 5 — Handling SDK updates and unknown methods +**1. SDK correctness** — Every method name, parameter, import, and return type matches `references/REFERENCE.md`. -The `references/REFERENCE.md` in this skill is a **point-in-time snapshot**. Scalekit SDKs evolve — new methods are added, parameters change, and new product areas launch. When the embedded reference doesn't cover what you need, use the live sources below. +**2. Auth flow completeness** — Login has a callback. Callback validates `state`. Logout calls `getLogoutUrl()`. Token refresh exists if `offline_access` is used. IdP-initiated login handled if applicable. -### When to check live sources +**3. Security** — Cookies: `httpOnly`, `secure`, `sameSite: 'lax'`. State: cryptographically random. Redirects: only relative paths. Secrets: from env vars. Webhooks: signature verified before processing. -- A method the user wrote isn't in the embedded reference (could be newly added, not a typo) -- The user asks about a feature you don't recognize (e.g., a new connector, a new auth mode) -- You're generating code for a product area with sparse coverage in the reference -- The user explicitly mentions a recent SDK update or version +**4. Environment** — `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`. Redirect URI matches dashboard. Domain format: `https://.scalekit.com`. -### How to check: fetch the live SDK REFERENCE.md files +**5. Best practices** — Client is singleton. Error handling uses typed exceptions. `window.location.href` for OAuth redirects (not `router.push`). -Each SDK repo has a maintained `REFERENCE.md` with full, current method signatures. Fetch the one you need: +**Output for each finding:** What's wrong → Why it matters → Corrected code. -| SDK | Live reference URL | -|-----|-------------------| -| Node.js | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-node/main/REFERENCE.md` | -| Python | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-python/main/REFERENCE.md` | -| Go | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-go/main/REFERENCE.md` | -| Java | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-java/main/REFERENCE.md` | -| REST API | `https://docs.scalekit.com/apis.md` | +## Step 5 — Unknown methods -### Resolution order +Resolution order when a method isn't in `references/REFERENCE.md`: -1. Check the embedded `references/REFERENCE.md` first (fastest, no network) -2. If the method isn't there, fetch the live SDK REFERENCE.md from the table above -3. If still not found, fetch `https://docs.scalekit.com/apis.md` for REST endpoints -4. If still not found, state explicitly: "This method could not be verified in any Scalekit reference. It may not exist." +| Priority | Source | +|----------|--------| +| 1 | Embedded `references/REFERENCE.md` | +| 2 | Live SDK reference: `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-{node,python,go,java}/main/REFERENCE.md` | +| 3 | REST API: `https://docs.scalekit.com/apis.md` | +| 4 | State explicitly: "This method could not be verified." | Never output code containing an unverified method call. ---- - -## REST API validation +## Documentation -When the user's code makes raw HTTP calls (fetch, axios, requests, http.Client) to Scalekit endpoints, validate: - -- [ ] Base URL format: `https://.scalekit.com` or `https://.scalekit.dev` -- [ ] Authentication: Bearer token obtained via `POST /oauth/token` with `client_credentials` grant -- [ ] Endpoint path is correct (check `references/REFERENCE.md` for the endpoint list) -- [ ] HTTP method matches (GET vs POST vs PUT vs PATCH vs DELETE) -- [ ] Request body matches the expected schema -- [ ] Content-Type header is set (`application/json` for most endpoints, `application/x-www-form-urlencoded` for token endpoint) -- [ ] Pagination uses `page_token` and `page_size` parameters where applicable - ---- +| Resource | URL | +|----------|-----| +| REST API reference | `https://docs.scalekit.com/apis.md` | +| LLM doc index | `https://docs.scalekit.com/llms.txt` | +| SaaSKit docs | `https://docs.scalekit.com/_llms-txt/saaskit-complete.txt` | +| AgentKit docs | `https://docs.scalekit.com/_llms-txt/agentkit.txt` | +| MCP Auth docs | `https://docs.scalekit.com/_llms-txt/mcp-authentication.txt` | -## Documentation resources - -### Live SDK references (always current — fetch when embedded reference is stale) - -| SDK | REFERENCE.md (raw) | Repo | -|-----|--------------------|----| -| Node.js | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-node/main/REFERENCE.md` | [scalekit-sdk-node](https://github.com/scalekit-inc/scalekit-sdk-node) | -| Python | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-python/main/REFERENCE.md` | [scalekit-sdk-python](https://github.com/scalekit-inc/scalekit-sdk-python) | -| Go | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-go/main/REFERENCE.md` | [scalekit-sdk-go](https://github.com/scalekit-inc/scalekit-sdk-go) | -| Java | `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-java/main/REFERENCE.md` | [scalekit-sdk-java](https://github.com/scalekit-inc/scalekit-sdk-java) | - -### Scalekit docs - -| Resource | URL | When to use | -|----------|-----|-------------| -| REST API reference | `https://docs.scalekit.com/apis.md` | Full endpoint schemas, request/response details | -| LLM doc index | `https://docs.scalekit.com/llms.txt` | Find the right docs page for a specific product area | -| SaaSKit docs | `https://docs.scalekit.com/_llms-txt/saaskit-complete.txt` | Full SaaSKit reference (users, orgs, sessions, RBAC, SSO, SCIM) | -| AgentKit docs | `https://docs.scalekit.com/_llms-txt/agentkit.txt` | Full AgentKit reference (agents, OAuth vault, tool calling, connectors) | -| AgentKit frameworks | `https://docs.scalekit.com/_llms-txt/agentkit-frameworks.txt` | Framework-specific guides (LangChain, Vercel AI, Anthropic, OpenAI, Google ADK, Mastra) | -| MCP Authentication docs | `https://docs.scalekit.com/_llms-txt/mcp-authentication.txt` | MCP server OAuth 2.1, Dynamic Client Registration | - -### GitHub repos — working examples - -When generating or reviewing framework-specific code, fetch the matching repo for real, tested patterns. Repos are from `scalekit-inc` and `scalekit-developers` GitHub orgs. - -**SaaSKit — Auth examples by framework** - -| Framework | Repo | What it shows | -|-----------|------|---------------| -| Next.js (App Router) | [scalekit-nextjs-auth-example](https://github.com/scalekit-inc/scalekit-nextjs-auth-example) | SSO, sessions, protected routes, TypeScript | -| Next.js (Pages) | [nextjs-example-apps](https://github.com/scalekit-inc/nextjs-example-apps) | React SSO integration flows | -| Next.js + Auth.js | [scalekit-authjs-example](https://github.com/scalekit-developers/scalekit-authjs-example) | Enterprise SSO with next-auth v5 | -| Express.js | [scalekit-express-auth-example](https://github.com/scalekit-inc/scalekit-express-auth-example) | Node SDK, EJS frontend, sessions | -| Express.js | [scalekit-express-example](https://github.com/scalekit-developers/scalekit-express-example) | SSO with session management, middleware | -| FastAPI | [scalekit-fastapi-auth-example](https://github.com/scalekit-inc/scalekit-fastapi-auth-example) | Python SDK, OAuth 2.0, protected routes | -| FastAPI | [scalekit-fastapi-example](https://github.com/scalekit-developers/scalekit-fastapi-example) | Async auth, Pydantic models | -| Django | [scalekit-django-auth-example](https://github.com/scalekit-inc/scalekit-django-auth-example) | Python SDK, Django auth integration | -| Flask | [scalekit-flask-auth-example](https://github.com/scalekit-inc/scalekit-flask-auth-example) | Python SDK, Flask sessions | -| Spring Boot | [scalekit-springboot-auth-example](https://github.com/scalekit-inc/scalekit-springboot-auth-example) | Java, Spring Security, OIDC | -| Spring Boot | [scalekit-springboot-example](https://github.com/scalekit-developers/scalekit-springboot-example) | Java SDK, enterprise SSO | -| Go (Gin) | [scalekit-go-example](https://github.com/scalekit-developers/scalekit-go-example) | Go SDK, Gin framework, SSO | -| Laravel | [scalekit-laravel-auth-example](https://github.com/scalekit-inc/scalekit-laravel-auth-example) | REST API calls, Laravel HTTPS | -| Astro | [astro-scalekit-auth-example](https://github.com/scalekit-developers/astro-scalekit-auth-example) | Auth, SSO, social login, protected routes | -| .NET | [dotnet-example-apps](https://github.com/scalekit-inc/dotnet-example-apps) | ASP.NET Core, SAML/OIDC | -| Expo (mobile) | [expo-scalekit-sample](https://github.com/scalekit-inc/expo-scalekit-sample) | OAuth 2.0 + PKCE for mobile | - -**SaaSKit — Integration examples** - -| Integration | Repo | What it shows | -|-------------|------|---------------| -| AWS Cognito | [scalekit-cognito-sso](https://github.com/scalekit-inc/scalekit-cognito-sso) | OIDC SSO with Cognito user pools | -| Firebase | [scalekit-firebase-sso](https://github.com/scalekit-inc/scalekit-firebase-sso) | SAML/OIDC SSO with Firebase Auth | -| Supabase | [scalekit-supabase-example](https://github.com/scalekit-inc/scalekit-supabase-example) | Supabase + Scalekit auth | -| Multi-app SSO | [multiapp-demo](https://github.com/scalekit-inc/multiapp-demo) | Seamless SSO across multiple apps | -| Org switcher | [Nextjs-Django-Org-Switcher-Example](https://github.com/scalekit-inc/Nextjs-Django-Org-Switcher-Example) | Next.js frontend + Django backend, org switching | -| OIDC/SAML/SCIM | [oidc-saml-scim-examples](https://github.com/scalekit-developers/oidc-saml-scim-examples) | Google, Okta integration patterns | -| Passwordless | [passwordless-auth-demos](https://github.com/scalekit-developers/passwordless-auth-demos) | Passwordless authentication flows | -| Managed login | [managed-loginbox-expressjs-demo](https://github.com/scalekit-developers/managed-loginbox-expressjs-demo) | Hosted login UI with Express | -| Full demo app | [coffee-desk-demo](https://github.com/scalekit-inc/coffee-desk-demo) | Workspace creation, user provisioning, RBAC, SSO | - -**AgentKit — Agent and MCP examples** - -| Framework / Pattern | Repo | What it shows | -|---------------------|------|---------------| -| LangChain | [sample-langchain-agent](https://github.com/scalekit-inc/sample-langchain-agent) | Python LangChain agent with Scalekit auth | -| Google ADK | [google-adk-agent-example](https://github.com/scalekit-inc/google-adk-agent-example) | Google ADK agent with authenticated tools | -| Vercel AI SDK | [vercel-ai-agent-toolkit](https://github.com/scalekit-developers/vercel-ai-agent-toolkit) | Vercel AI SDK + Scalekit connectors | -| Apify Actor | [agentkit-apify-actor-example](https://github.com/scalekit-developers/agentkit-apify-actor-example) | OAuth auth, YouTube → Notion agent | -| LiteLLM | [litellm-agentkit-inbox-triage](https://github.com/scalekit-developers/litellm-agentkit-inbox-triage) | Inbox triage with Gmail, GitHub, Slack | -| MCP Auth (multi-framework) | [mcp-auth-demos](https://github.com/scalekit-inc/mcp-auth-demos) | MCP OAuth 2.1 demos | -| MCP + FastMCP | [fastmcp-scalekit-example](https://github.com/scalekit-inc/fastmcp-scalekit-example) | FastMCP server with Scalekit auth | -| MCP + BYOA | [byoa-demo-mcp](https://github.com/scalekit-inc/byoa-demo-mcp) | Bring your own auth + MCP | -| MCP + Coffee Desk | [coffee-desk-mcp](https://github.com/scalekit-inc/coffee-desk-mcp) | Demo MCP server with roles/permissions | -| Python connections | [python-connect-demos](https://github.com/scalekit-inc/python-connect-demos) | Python connection and identity workflows | -| Agent auth examples | [agent-auth-examples](https://github.com/scalekit-developers/agent-auth-examples) | Official AgentKit examples collection | -| Node.js agents | [agent-node-demos](https://github.com/scalekit-inc/agent-node-demos) | TypeScript agent demos | -| Workflow agents | [workflow-agents-demos](https://github.com/scalekit-developers/workflow-agents-demos) | Multi-step agent workflows | -| Render deploy kit | [render-ai-agent-deploykit](https://github.com/scalekit-developers/render-ai-agent-deploykit) | Render Workflows + Scalekit + Claude | - -**Developer tools** - -| Tool | Repo | Purpose | -|------|------|---------| -| Dryrun CLI | [scalekit-dryrun](https://github.com/scalekit-inc/scalekit-dryrun) | Test auth flows without writing code | -| Scalekit MCP server | [scalekit-mcp-server](https://github.com/scalekit-inc/scalekit-mcp-server) | Manage orgs, users, connections via AI assistants | -| API collections | [api-collections](https://github.com/scalekit-inc/api-collections) | Postman/Bruno collections for Scalekit endpoints | -| Documentation source | [developer-docs](https://github.com/scalekit-inc/developer-docs) | Docs site source (MDX) | - -**Frontend SDKs** - -| SDK | Repo | Purpose | -|-----|------|---------| -| React SDK | [scalekit-react-sdk](https://github.com/scalekit-inc/scalekit-react-sdk) | React OIDC authentication | -| Vue SDK | [scalekit-vue-sdk](https://github.com/scalekit-inc/scalekit-vue-sdk) | Vue OIDC authentication | -| Expo SDK | [scalekit-expo-sdk](https://github.com/scalekit-inc/scalekit-expo-sdk) | Expo/React Native OAuth 2.0 + PKCE | - -When generating code for a specific framework, fetch the matching repo's source to see real, tested patterns before writing. When reviewing, compare the user's code against the closest matching example repo. \ No newline at end of file +For framework-specific example repos, see `references/EXAMPLE-REPOS.md`. \ No newline at end of file diff --git a/plugins/saaskit/skills/scalekit-code-doctor/references/EXAMPLE-REPOS.md b/plugins/saaskit/skills/scalekit-code-doctor/references/EXAMPLE-REPOS.md new file mode 100644 index 0000000..a66718b --- /dev/null +++ b/plugins/saaskit/skills/scalekit-code-doctor/references/EXAMPLE-REPOS.md @@ -0,0 +1,61 @@ +# Example Repos + +Working examples by framework. Fetch the matching repo for real, tested patterns when generating or reviewing code. + +## SaaSKit — Auth examples + +| Framework | Repo | What it shows | +|-----------|------|---------------| +| Next.js (App Router) | [scalekit-nextjs-auth-example](https://github.com/scalekit-inc/scalekit-nextjs-auth-example) | SSO, sessions, protected routes | +| Next.js (Pages) | [nextjs-example-apps](https://github.com/scalekit-inc/nextjs-example-apps) | React SSO flows | +| Next.js + Auth.js | [scalekit-authjs-example](https://github.com/scalekit-developers/scalekit-authjs-example) | Enterprise SSO with next-auth v5 | +| Express.js | [scalekit-express-auth-example](https://github.com/scalekit-inc/scalekit-express-auth-example) | Node SDK, sessions | +| Express.js | [scalekit-express-example](https://github.com/scalekit-developers/scalekit-express-example) | SSO, middleware | +| FastAPI | [scalekit-fastapi-auth-example](https://github.com/scalekit-inc/scalekit-fastapi-auth-example) | Python SDK, OAuth 2.0 | +| FastAPI | [scalekit-fastapi-example](https://github.com/scalekit-developers/scalekit-fastapi-example) | Async auth, Pydantic | +| Django | [scalekit-django-auth-example](https://github.com/scalekit-inc/scalekit-django-auth-example) | Django auth integration | +| Flask | [scalekit-flask-auth-example](https://github.com/scalekit-inc/scalekit-flask-auth-example) | Flask sessions | +| Spring Boot | [scalekit-springboot-auth-example](https://github.com/scalekit-inc/scalekit-springboot-auth-example) | Spring Security, OIDC | +| Go (Gin) | [scalekit-go-example](https://github.com/scalekit-developers/scalekit-go-example) | Go SDK, SSO | +| Laravel | [scalekit-laravel-auth-example](https://github.com/scalekit-inc/scalekit-laravel-auth-example) | REST API, Laravel | +| Astro | [astro-scalekit-auth-example](https://github.com/scalekit-developers/astro-scalekit-auth-example) | Auth, SSO, social login | +| .NET | [dotnet-example-apps](https://github.com/scalekit-inc/dotnet-example-apps) | ASP.NET Core, SAML/OIDC | +| Expo (mobile) | [expo-scalekit-sample](https://github.com/scalekit-inc/expo-scalekit-sample) | OAuth 2.0 + PKCE | + +## SaaSKit — Integration examples + +| Integration | Repo | +|-------------|------| +| AWS Cognito | [scalekit-cognito-sso](https://github.com/scalekit-inc/scalekit-cognito-sso) | +| Firebase | [scalekit-firebase-sso](https://github.com/scalekit-inc/scalekit-firebase-sso) | +| Supabase | [scalekit-supabase-example](https://github.com/scalekit-inc/scalekit-supabase-example) | +| Multi-app SSO | [multiapp-demo](https://github.com/scalekit-inc/multiapp-demo) | +| OIDC/SAML/SCIM | [oidc-saml-scim-examples](https://github.com/scalekit-developers/oidc-saml-scim-examples) | +| Full demo app | [coffee-desk-demo](https://github.com/scalekit-inc/coffee-desk-demo) | + +## AgentKit — Agent and MCP examples + +| Framework | Repo | +|-----------|------| +| LangChain | [sample-langchain-agent](https://github.com/scalekit-inc/sample-langchain-agent) | +| Google ADK | [google-adk-agent-example](https://github.com/scalekit-inc/google-adk-agent-example) | +| Vercel AI SDK | [vercel-ai-agent-toolkit](https://github.com/scalekit-developers/vercel-ai-agent-toolkit) | +| MCP Auth | [mcp-auth-demos](https://github.com/scalekit-inc/mcp-auth-demos) | +| FastMCP | [fastmcp-scalekit-example](https://github.com/scalekit-inc/fastmcp-scalekit-example) | +| Agent auth | [agent-auth-examples](https://github.com/scalekit-developers/agent-auth-examples) | + +## Developer tools + +| Tool | Repo | +|------|------| +| Dryrun CLI | [scalekit-dryrun](https://github.com/scalekit-inc/scalekit-dryrun) | +| MCP server | [scalekit-mcp-server](https://github.com/scalekit-inc/scalekit-mcp-server) | +| API collections | [api-collections](https://github.com/scalekit-inc/api-collections) | + +## Frontend SDKs + +| SDK | Repo | +|-----|------| +| React | [scalekit-react-sdk](https://github.com/scalekit-inc/scalekit-react-sdk) | +| Vue | [scalekit-vue-sdk](https://github.com/scalekit-inc/scalekit-vue-sdk) | +| Expo | [scalekit-expo-sdk](https://github.com/scalekit-inc/scalekit-expo-sdk) | \ No newline at end of file diff --git a/plugins/saaskit/skills/testing-auth-setup/SKILL.md b/plugins/saaskit/skills/testing-auth-setup/SKILL.md index 36fbdcc..45f3f96 100644 --- a/plugins/saaskit/skills/testing-auth-setup/SKILL.md +++ b/plugins/saaskit/skills/testing-auth-setup/SKILL.md @@ -1,8 +1,6 @@ --- name: testing-auth-setup description: Validates a Scalekit auth integration by running the dryrun CLI against a live environment. Use when the user says "test my auth", "verify SSO setup", "check my login flow", "dryrun", or wants to confirm their Scalekit credentials and configuration are working. -argument-hint: "[fsa|sso]" -allowed-tools: Bash(npx *) --- # Testing Auth Setup @@ -27,16 +25,14 @@ npm i -g @scalekit-inc/cli Confirm these environment variables are available: - `SCALEKIT_ENV_URL` — your Scalekit environment URL -- `SCALEKIT_CLIENT_ID` — your client ID from app.scalekit.com → Settings - -If either is missing, ask the user to provide them. Do not write credentials into source-controlled files. +- `SCALEKIT_CLIENT_ID` — your client ID from app.scalekit.com > Settings ## Running the test ### Full-stack auth (fsa) ```bash -npx @scalekit-sdk/dryrun --env_url= --client_id= --mode=fsa +npx @scalekit-sdk/dryrun --env_url=$SCALEKIT_ENV_URL --client_id=$SCALEKIT_CLIENT_ID --mode=fsa ``` ### Enterprise SSO @@ -44,7 +40,7 @@ npx @scalekit-sdk/dryrun --env_url= --client_id= --mode=fsa Requires an `organization_id` — ask for it if not provided. ```bash -npx @scalekit-sdk/dryrun --env_url= --client_id= --mode=sso --organization_id= +npx @scalekit-sdk/dryrun --env_url=$SCALEKIT_ENV_URL --client_id=$SCALEKIT_CLIENT_ID --mode=sso --organization_id= ``` ## Choosing the mode @@ -59,4 +55,10 @@ If the user doesn't specify a mode: - Show the command output. - Explain what passed and what failed in plain language. -- If the test fails, suggest specific next steps based on the error (missing redirect URI, invalid credentials, organization not found, etc.). \ No newline at end of file +- If the test fails, suggest specific next steps based on the error (missing redirect URI, invalid credentials, organization not found, etc.). + +## When to switch skills + +- Use `implementing-saaskit` for the initial auth setup. +- Use `implementing-modular-sso` for SSO configuration. +- Use `production-readiness-saaskit` for a full pre-launch review. \ No newline at end of file From 6656fec7195701af4ca616f27917e7ac16485c3e Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Thu, 21 May 2026 22:17:06 +0530 Subject: [PATCH 42/53] fix: wrong pip package, SDK class name, stale URL, and missing README skills MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - install.sh: make plugin install idempotent (|| true); fix post-install message - agentkit plugin.json: update stale homepage URL to /agentkit/overview/ - scim-provisioning: pip install scalekit-sdk → scalekit-sdk-python - adding-mcp-oauth: Scalekit → ScalekitClient (import + constructor) - connectors/README.md: remove false claim about deleted connector notes - CHANGELOG.md: fix wrong skill names, wrong count, Removed → Aliased - agentkit README: add missing scalekit-code-doctor skill - saaskit README: add missing testing-auth-setup and scalekit-code-doctor skills Co-Authored-By: Claude Sonnet 4.6 --- CHANGELOG.md | 16 ++++++++-------- plugins/agentkit/.claude-plugin/plugin.json | 2 +- plugins/agentkit/README.md | 2 ++ plugins/agentkit/docs/connectors/README.md | 17 ++++------------- plugins/saaskit/README.md | 2 ++ .../saaskit/skills/adding-mcp-oauth/SKILL.md | 4 ++-- .../implementing-scim-provisioning/SKILL.md | 2 +- scripts/install.sh | 8 ++++---- 8 files changed, 24 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 551085b..b591252 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,17 +5,17 @@ ### Breaking Changes - Consolidated 5 plugins into 2: **AgentKit** and **SaaSKit** -- Removed `full-stack-auth`, `mcp-auth`, `modular-sso`, `modular-scim` plugins +- Consolidated `full-stack-auth`, `mcp-auth`, `modular-sso`, `modular-scim` as symlink aliases — old install commands still work - All slash commands now use `/agentkit:` or `/saaskit:` namespace ### Added - **AgentKit** plugin (`plugins/agentkit/`) — renamed from `agent-auth`, adopting Scalekit AgentKit branding - - New skills: `discovering-agentkit-tools`, `testing-agentkit-tools` + - New skills: `discovering-connector-tools`, `exposing-agentkit-via-mcp`, `scalekit-code-doctor` - Added `docs/` layer with canonical documentation (connections, tool-discovery, code-samples) - Added `rules/` layer for cross-cutting guidance - **SaaSKit** plugin (`plugins/saaskit/`) — consolidates FSA, SSO, SCIM, and MCP server auth - - 11 skills covering login, sessions, SSO, SCIM, RBAC, MCP server auth, API keys, migration, and production readiness + - 13 skills covering login, sessions, SSO, SCIM, RBAC, MCP server auth, API keys, migration, production readiness, auth testing, and SDK debugging - `docs/` layer with 13 documentation files including framework-specific guides (Python, Next.js, Go, Spring Boot, Laravel) - `rules/` layer with terminology and redirect URL guidance - Framework-specific reference files (Go, Spring Boot, Laravel, FastMCP, Express, FastAPI) @@ -29,12 +29,12 @@ - Root README updated with 2-plugin listing and install instructions - CLAUDE.md updated with new plugin layout -### Removed +### Aliased (backward-compatible) -- `plugins/full-stack-auth/` — skills migrated to SaaSKit -- `plugins/mcp-auth/` — server-side auth skills migrated to SaaSKit, MCP bridge stays in AgentKit -- `plugins/modular-sso/` — SSO + admin portal skills migrated to SaaSKit -- `plugins/modular-scim/` — SCIM provisioning skills migrated to SaaSKit +- `plugins/full-stack-auth/` → symlink to `saaskit/`; old install commands continue to work +- `plugins/mcp-auth/` → symlink to `saaskit/`; old install commands continue to work +- `plugins/modular-sso/` → symlink to `saaskit/`; old install commands continue to work +- `plugins/modular-scim/` → symlink to `saaskit/`; old install commands continue to work --- diff --git a/plugins/agentkit/.claude-plugin/plugin.json b/plugins/agentkit/.claude-plugin/plugin.json index 69c6ee0..9d473b8 100644 --- a/plugins/agentkit/.claude-plugin/plugin.json +++ b/plugins/agentkit/.claude-plugin/plugin.json @@ -6,7 +6,7 @@ "name": "Scalekit Inc.", "email": "hi@scalekit.com" }, - "homepage": "https://docs.scalekit.com/dev-kit/build-with-ai/agent-auth/", + "homepage": "https://docs.scalekit.com/agentkit/overview/", "repository": "https://github.com/scalekit-inc/claude-code-authstack", "license": "MIT", "keywords": ["scalekit", "agentkit", "agent-auth", "connectors", "tools", "connected-accounts"] diff --git a/plugins/agentkit/README.md b/plugins/agentkit/README.md index 4e845ef..5ddf699 100644 --- a/plugins/agentkit/README.md +++ b/plugins/agentkit/README.md @@ -41,6 +41,8 @@ Official Scalekit docs: Exposes AgentKit tools through MCP for MCP-compatible runtimes. - `/agentkit:production-readiness-agentkit` Runs a structured production-readiness checklist for AgentKit integrations. +- `/agentkit:scalekit-code-doctor` + Diagnoses SDK usage issues, import errors, and common mistakes across AgentKit and SaaSKit. ## Configuration Required environment variables (for SDK-based integrations): diff --git a/plugins/agentkit/docs/connectors/README.md b/plugins/agentkit/docs/connectors/README.md index ac98d20..e732f52 100644 --- a/plugins/agentkit/docs/connectors/README.md +++ b/plugins/agentkit/docs/connectors/README.md @@ -33,20 +33,11 @@ Use connector notes for: ## Current connector coverage in the plugin -The plugin already contains curated notes for connectors such as: +Per-connector implementation notes were removed in v2.0.0. Use live AgentKit metadata +via the Scalekit MCP server as the current source of truth for tool schemas and coverage. -- Gmail -- Google Calendar -- Google Sheets -- Slack -- Salesforce -- GitHub -- Notion -- Zendesk - -and many others in the legacy connector-notes set. - -During this hybrid migration, those existing notes remain available in the plugin as the backing source for connector-specific guidance while `docs/` becomes the canonical entry layer. +For official connector documentation see the +[Scalekit connector catalog](https://docs.scalekit.com/agentkit/connectors.md). ## Related docs diff --git a/plugins/saaskit/README.md b/plugins/saaskit/README.md index b6e3c71..8106233 100644 --- a/plugins/saaskit/README.md +++ b/plugins/saaskit/README.md @@ -40,6 +40,8 @@ Official Scalekit docs: - `/saaskit:adding-api-auth` — API keys (org/user scoped) and OAuth 2.0 client credentials. - `/saaskit:migrating-to-saaskit` — Migration planning from Auth0, Firebase, Cognito, or custom auth. - `/saaskit:production-readiness-saaskit` — Unified production checklist across all SaaSKit domains. +- `/saaskit:testing-auth-setup` — Validates auth configuration end-to-end using the Scalekit dryrun CLI. +- `/saaskit:scalekit-code-doctor` — Diagnoses SDK usage issues, import errors, and common mistakes across AgentKit and SaaSKit. ## Configuration Required environment variables: diff --git a/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md b/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md index 4421637..af7f029 100644 --- a/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md +++ b/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md @@ -101,9 +101,9 @@ Replace placeholders with actual values from Scalekit dashboard. **Node.js:** ```javascript -import { Scalekit } from '@scalekit-sdk/node'; +import { ScalekitClient } from '@scalekit-sdk/node'; -const scalekit = new Scalekit( +const scalekit = new ScalekitClient( process.env.SCALEKIT_ENVIRONMENT_URL, process.env.SCALEKIT_CLIENT_ID, process.env.SCALEKIT_CLIENT_SECRET diff --git a/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md b/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md index 998920a..53f6550 100644 --- a/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md +++ b/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md @@ -32,7 +32,7 @@ Detect the project's language/framework from existing files (`package.json`, `re | Stack | Install command | |-------|----------------| | Node.js | `npm install @scalekit-sdk/node` | -| Python | `pip install scalekit-sdk` | +| Python | `pip install scalekit-sdk-python` | | Go | `go get github.com/scalekit/scalekit-go` | | Java | Add `com.scalekit:scalekit-sdk` to `pom.xml` or `build.gradle` | diff --git a/scripts/install.sh b/scripts/install.sh index f9abf47..35f3967 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -25,8 +25,8 @@ for old in "${OLD_PLUGINS[@]}"; do claude plugin uninstall "${old}@scalekit-auth-stack" 2>/dev/null || true done -claude plugin install agentkit@scalekit-auth-stack -claude plugin install saaskit@scalekit-auth-stack +claude plugin install agentkit@scalekit-auth-stack || true +claude plugin install saaskit@scalekit-auth-stack || true cat < Date: Thu, 21 May 2026 22:27:47 +0530 Subject: [PATCH 43/53] =?UTF-8?q?fix:=20remaining=20CTO=20review=20issues?= =?UTF-8?q?=20=E2=80=94=20token=20expiry,=20missing=20refs,=20README,=20de?= =?UTF-8?q?dup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - implementing-modular-sso: fix access token expiry 24h → 5min (configurable) - implementing-saaskit-python: add django-reference.md and flask-reference.md - agentkit: remove duplicate scalekit-code-doctor; point README to /saaskit:scalekit-code-doctor - agentkit/saaskit README: fix invalid shell install command (claude /plugin → /plugin inside REPL) - agentkit README: document MCP OAuth 2.1 auth behavior and troubleshooting steps - root README: add Windows install caveat (install.sh requires macOS/Linux/WSL) Co-Authored-By: Claude Sonnet 4.6 --- README.md | 2 + plugins/agentkit/README.md | 18 +- .../skills/scalekit-code-doctor/SKILL.md | 125 ----- .../references/COMMON-MISTAKES.md | 481 ----------------- .../references/EXAMPLE-REPOS.md | 61 --- .../references/REFERENCE.md | 503 ------------------ plugins/saaskit/README.md | 9 +- .../skills/implementing-modular-sso/SKILL.md | 2 +- .../django-reference.md | 247 +++++++++ .../flask-reference.md | 221 ++++++++ 10 files changed, 494 insertions(+), 1175 deletions(-) delete mode 100644 plugins/agentkit/skills/scalekit-code-doctor/SKILL.md delete mode 100644 plugins/agentkit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md delete mode 100644 plugins/agentkit/skills/scalekit-code-doctor/references/EXAMPLE-REPOS.md delete mode 100644 plugins/agentkit/skills/scalekit-code-doctor/references/REFERENCE.md create mode 100644 plugins/saaskit/skills/implementing-saaskit-python/django-reference.md create mode 100644 plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md diff --git a/README.md b/README.md index 69770cc..2f26592 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,8 @@ Use SaaSKit to add login, session management, enterprise SSO, SCIM provisioning, - Claude Code installed and configured - Project where you want to add authentication +> **Windows**: `install.sh` requires macOS or Linux (or WSL on Windows). Native Windows PowerShell install is not yet supported. + --- ### Helpful Links diff --git a/plugins/agentkit/README.md b/plugins/agentkit/README.md index 5ddf699..6e51ce1 100644 --- a/plugins/agentkit/README.md +++ b/plugins/agentkit/README.md @@ -18,8 +18,15 @@ Claude runtime files remain in place as adapters: The plugin treats live AgentKit metadata as the source of truth for tool names, `input_schema`, and `output_schema`. For per-connector details, see the [AgentKit connectors catalog](https://docs.scalekit.com/agentkit/connectors/). ## Installation + +Run the install script (macOS/Linux): ```sh -claude /plugin install agentkit@scalekit-auth-stack +curl -fsSL https://raw.githubusercontent.com/scalekit-inc/claude-code-authstack/main/scripts/install.sh | bash +``` + +Or inside Claude Code, run: +``` +/plugin install agentkit@scalekit-auth-stack ``` Start with the canonical docs entrypoint at [`docs/index.md`](docs/index.md). @@ -41,8 +48,8 @@ Official Scalekit docs: Exposes AgentKit tools through MCP for MCP-compatible runtimes. - `/agentkit:production-readiness-agentkit` Runs a structured production-readiness checklist for AgentKit integrations. -- `/agentkit:scalekit-code-doctor` - Diagnoses SDK usage issues, import errors, and common mistakes across AgentKit and SaaSKit. +- `/saaskit:scalekit-code-doctor` (cross-plugin) + Diagnoses SDK usage issues, import errors, and common mistakes across AgentKit and SaaSKit. Requires the saaskit plugin. ## Configuration Required environment variables (for SDK-based integrations): @@ -63,6 +70,11 @@ Example `.mcp.json`: } ``` +**MCP authentication**: Claude Code handles auth for `https://mcp.scalekit.com` automatically via OAuth 2.1 dynamic client registration. No auth fields are needed in `.mcp.json`. If MCP tool calls fail: +1. Verify `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, and `SCALEKIT_CLIENT_SECRET` are set in your environment. +2. Re-open Claude Code — it re-runs the OAuth handshake on startup. +3. Check that your Scalekit environment has MCP server access enabled in the dashboard. + ## Usage Examples Typical flow for a new connector integration: 1. Read [`docs/index.md`](docs/index.md) for the canonical model and [`docs/connections.md`](docs/connections.md) for connection naming. diff --git a/plugins/agentkit/skills/scalekit-code-doctor/SKILL.md b/plugins/agentkit/skills/scalekit-code-doctor/SKILL.md deleted file mode 100644 index e15de81..0000000 --- a/plugins/agentkit/skills/scalekit-code-doctor/SKILL.md +++ /dev/null @@ -1,125 +0,0 @@ ---- -name: scalekit-code-doctor -description: Use when a user asks to generate, review, validate, or fix any code snippet that uses Scalekit APIs or SDKs. Generates illustration-quality snippets and reviews 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 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, or write a code sample for docs. ---- - -# Scalekit Code Doctor - -**Before doing anything else**, read the reference files: -- `references/REFERENCE.md` — Every correct SDK method signature and REST endpoint -- `references/COMMON-MISTAKES.md` — Known anti-patterns with wrong → right corrections -- `references/EXAMPLE-REPOS.md` — GitHub repos with working examples by framework - -Never hallucinate a method name, parameter, or import — if it's not in the reference, verify against live sources before using it. - -## Step 1 — Detect mode - -**Generate mode** — User describes what they want but has no code yet. -**Review mode** — User provides existing code for validation. - -If unclear, ask: "Do you want me to generate a fresh code example, or review existing code?" - -## Step 2 — Identify context - -| Language | Package | Import | -|----------|---------|--------| -| Node.js / TypeScript | `@scalekit-sdk/node` | `import { ScalekitClient } from '@scalekit-sdk/node'` | -| Python | `scalekit-sdk-python` | `from scalekit import ScalekitClient` | -| Go | `scalekit-sdk-go` | `import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2"` | -| Java | `scalekit-sdk-java` | `import com.scalekit.ScalekitClient;` | - -Product area: **SaaSKit** (SSO, login, sessions, RBAC, SCIM) or **AgentKit** (connections, tool calling, MCP auth). - -## Step 3 — Generate mode - -Output should be illustration-ready: self-contained, essential path only, correct imports, framework-idiomatic, 1–2 pages max. - -**Correct SaaSKit login+callback example (Node.js/Express):** - -```typescript -import { ScalekitClient } from '@scalekit-sdk/node'; -import crypto from 'crypto'; - -const scalekit = new ScalekitClient( - process.env.SCALEKIT_ENV_URL!, - process.env.SCALEKIT_CLIENT_ID!, - process.env.SCALEKIT_CLIENT_SECRET! -); - -const REDIRECT_URI = 'https://yourapp.com/auth/callback'; - -// Login — generate auth URL with CSRF state -app.get('/auth/login', (req, res) => { - const state = crypto.randomBytes(32).toString('base64url'); - res.cookie('oauth_state', state, { httpOnly: true, sameSite: 'lax', secure: true }); - const authUrl = scalekit.getAuthorizationUrl(REDIRECT_URI, { state }); - res.redirect(authUrl); -}); - -// Callback — validate state, exchange code, store session -app.get('/auth/callback', async (req, res) => { - const { code, state } = req.query; - if (state !== req.cookies.oauth_state) return res.status(403).send('CSRF mismatch'); - - const result = await scalekit.authenticateWithCode(code as string, REDIRECT_URI); - req.session.user = { id: result.user.id, email: result.user.email }; - req.session.idToken = result.idToken; - res.redirect('/dashboard'); -}); - -// Logout — clear local + end IdP session -app.post('/auth/logout', (req, res) => { - const logoutUrl = scalekit.getLogoutUrl({ - idTokenHint: req.session.idToken, - postLogoutRedirectUri: 'https://yourapp.com', - }); - req.session.destroy(() => res.redirect(logoutUrl)); -}); -``` - -**Mandatory checks before outputting generated code** — cross-reference every SDK call against `references/REFERENCE.md`: -- [ ] Method names exist for the target SDK -- [ ] Parameters match in name, order, and type -- [ ] Import path is exactly correct -- [ ] Environment variable names follow Scalekit conventions - -## Step 4 — Review mode - -Check these categories in order: - -**1. SDK correctness** — Every method name, parameter, import, and return type matches `references/REFERENCE.md`. - -**2. Auth flow completeness** — Login has a callback. Callback validates `state`. Logout calls `getLogoutUrl()`. Token refresh exists if `offline_access` is used. IdP-initiated login handled if applicable. - -**3. Security** — Cookies: `httpOnly`, `secure`, `sameSite: 'lax'`. State: cryptographically random. Redirects: only relative paths. Secrets: from env vars. Webhooks: signature verified before processing. - -**4. Environment** — `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`. Redirect URI matches dashboard. Domain format: `https://.scalekit.com`. - -**5. Best practices** — Client is singleton. Error handling uses typed exceptions. `window.location.href` for OAuth redirects (not `router.push`). - -**Output for each finding:** What's wrong → Why it matters → Corrected code. - -## Step 5 — Unknown methods - -Resolution order when a method isn't in `references/REFERENCE.md`: - -| Priority | Source | -|----------|--------| -| 1 | Embedded `references/REFERENCE.md` | -| 2 | Live SDK reference: `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-{node,python,go,java}/main/REFERENCE.md` | -| 3 | REST API: `https://docs.scalekit.com/apis.md` | -| 4 | State explicitly: "This method could not be verified." | - -Never output code containing an unverified method call. - -## Documentation - -| Resource | URL | -|----------|-----| -| REST API reference | `https://docs.scalekit.com/apis.md` | -| LLM doc index | `https://docs.scalekit.com/llms.txt` | -| SaaSKit docs | `https://docs.scalekit.com/_llms-txt/saaskit-complete.txt` | -| AgentKit docs | `https://docs.scalekit.com/_llms-txt/agentkit.txt` | -| MCP Auth docs | `https://docs.scalekit.com/_llms-txt/mcp-authentication.txt` | - -For framework-specific example repos, see `references/EXAMPLE-REPOS.md`. \ No newline at end of file diff --git a/plugins/agentkit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md b/plugins/agentkit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md deleted file mode 100644 index 5343181..0000000 --- a/plugins/agentkit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md +++ /dev/null @@ -1,481 +0,0 @@ -# Common Mistakes in Scalekit Code - -This file catalogs known anti-patterns, hallucinated methods, and security issues found in Scalekit integrations. Each entry shows the wrong pattern and the correct fix. Use this as a lookup during both generation and review. 10 categories. - ---- - -## 1. Wrong Import Paths - -### Node.js - -**Wrong:** -```typescript -import ScalekitClient from '@scalekit-sdk/node'; // default import — use named import -import { ScalekitClient } from 'scalekit'; // wrong package name -import { ScalekitClient } from 'scalekit-sdk-node'; // wrong package name -``` - -**Correct (either works):** -```typescript -import { ScalekitClient } from '@scalekit-sdk/node'; -// OR -import { Scalekit } from '@scalekit-sdk/node'; // official alias, also valid -``` - -Both `ScalekitClient` and `Scalekit` are valid named exports from `@scalekit-sdk/node`. The SDK source exports both. Use whichever is consistent with your codebase. - -### Python - -**Wrong:** -```python -from scalekit_sdk import ScalekitClient # wrong module name -from scalekit.client import ScalekitClient # internal path, not public API -import scalekit # missing class import -pip install scalekit # wrong pip package name -``` - -**Correct:** -```python -from scalekit import ScalekitClient -# pip install scalekit-sdk-python -``` - -### Go - -**Wrong:** -```go -import "github.com/scalekit-inc/scalekit-sdk-go" // missing version -import "github.com/scalekit/scalekit-sdk-go/v2" // wrong org name -``` - -**Correct:** -```go -import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2" -``` - -### Java - -**Wrong:** -```java -import com.scalekit.sdk.ScalekitClient; // wrong package path -import io.scalekit.ScalekitClient; // wrong package -``` - -**Correct:** -```java -import com.scalekit.ScalekitClient; -``` - ---- - -## 2. Wrong Sub-Client Names (Python vs Node) - -Python and Node use different pluralization for some sub-clients. Using the wrong one causes `AttributeError` in Python or `TypeError` in Node. - -| Sub-client | Node.js (singular) | Python (plural) | -|------------|--------------------|--------------------| -| Users | `client.user.getUser(...)` | `client.users.get_user(...)` | -| Roles | `client.role.listRoles(...)` | `client.roles.list_roles(...)` | -| Permissions | `client.permission.listPermissions(...)` | `client.permissions.list_permissions(...)` | -| Sessions | `client.session.getSession(...)` | `client.sessions.get_session(...)` | - -Sub-clients that are the SAME in both: `organization`, `connection`, `domain`, `directory`. - -**Wrong (Python):** -```python -client.user.get_user(user_id) # AttributeError: 'ScalekitClient' has no attribute 'user' -client.role.list_roles() # AttributeError -client.session.revoke_session(sid) # AttributeError -``` - -**Correct (Python):** -```python -client.users.get_user(user_id) -client.roles.list_roles() -client.sessions.revoke_session(sid) -``` - ---- - -## 3. Wrong Method Names - -### Node.js - -| Wrong | Correct | Notes | -|-------|---------|-------| -| `scalekit.authenticate(code)` | `scalekit.authenticateWithCode(code, redirectUri)` | Missing `WithCode` suffix and `redirectUri` param | -| `scalekit.getAuthUrl(...)` | `scalekit.getAuthorizationUrl(redirectUri, options?)` | Wrong method name | -| `scalekit.login(...)` | `scalekit.getAuthorizationUrl(redirectUri, options?)` | No `login` method | -| `scalekit.logout(...)` | `scalekit.getLogoutUrl(options?)` | Returns URL, doesn't perform logout | -| `scalekit.verifyToken(token)` | `scalekit.validateAccessToken(token)` or `scalekit.validateToken(token)` | Wrong name | -| `scalekit.createOrganization(...)` | `scalekit.organization.createOrganization(...)` | Must use sub-client | -| `scalekit.getOrganization(...)` | `scalekit.organization.getOrganization(...)` | Must use sub-client | - -### Python - -| Wrong | Correct | Notes | -|-------|---------|-------| -| `client.authenticateWithCode(...)` | `client.authenticate_with_code(...)` | Python uses snake_case | -| `client.getAuthorizationUrl(...)` | `client.get_authorization_url(...)` | Python uses snake_case | -| `client.getLogoutUrl(...)` | `client.get_logout_url(...)` | Python uses snake_case | -| `client.validateToken(...)` | `client.validate_access_token(...)` | Different method name in Python | -| `client.verify_webhook(...)` | `client.verify_webhook_payload(...)` | Missing `_payload` suffix | - -### Go - -| Wrong | Correct | Notes | -|-------|---------|-------| -| `client.AuthenticateWithCode(code, uri)` | `client.AuthenticateWithCode(ctx, code, uri, options)` | Missing `ctx` parameter | -| `client.GetAuthorizationUrl(uri)` | `client.GetAuthorizationUrl(uri, options)` | Missing `options` param (required in Go) | -| `client.Organization.Create(...)` | `client.Organization().CreateOrganization(ctx, request)` | Use accessor method `Organization()`, not field | - -### Java - -| Wrong | Correct | Notes | -|-------|---------|-------| -| `client.organization.create(...)` | `client.organizations().create(...)` | Use `organizations()` accessor method, plural | -| `client.getOrganization(id)` | `client.organizations().getById(id)` | Use sub-client accessor | -| `client.connections.list(...)` | `client.connections().listConnectionsByOrganization(orgId)` | Use accessor method | - ---- - -## 4. Missing Required Parameters - -### `authenticateWithCode` — missing `redirectUri` - -**Wrong:** -```typescript -const result = await scalekit.authenticateWithCode(code); -``` - -**Correct:** -```typescript -const result = await scalekit.authenticateWithCode(code, redirectUri); -``` - -The `redirectUri` must exactly match the one used in `getAuthorizationUrl` AND what's registered in the Scalekit dashboard. - -### `getAuthorizationUrl` — missing `state` for CSRF - -**Wrong:** -```typescript -const authUrl = scalekit.getAuthorizationUrl(redirectUri); -``` - -**Correct:** -```typescript -import crypto from 'crypto'; -const state = crypto.randomBytes(32).toString('base64url'); -// Store state in session/cookie for validation in callback -const authUrl = scalekit.getAuthorizationUrl(redirectUri, { state }); -``` - -While `state` is technically optional, omitting it is a **CSRF vulnerability**. Always generate and validate it. - -### Go — missing `context.Context` - -**Wrong:** -```go -resp, err := client.AuthenticateWithCode(code, redirectUri, opts) -``` - -**Correct:** -```go -resp, err := client.AuthenticateWithCode(ctx, code, redirectUri, opts) -``` - -All Go network methods require `context.Context` as the first parameter. - ---- - -## 5. Auth Flow Gaps - -### Missing callback handler - -If you see a login route that generates an auth URL but no corresponding callback route, the flow is incomplete. The callback MUST: -1. Validate the `state` parameter against the stored value -2. Call `authenticateWithCode(code, redirectUri)` -3. Store the session (tokens + user info) -4. Redirect to the application - -### Missing `state` validation in callback - -**Wrong:** -```typescript -app.get('/auth/callback', async (req, res) => { - const { code } = req.query; - const result = await scalekit.authenticateWithCode(code, redirectUri); - // ... store session -}); -``` - -**Correct:** -```typescript -app.get('/auth/callback', async (req, res) => { - const { code, state } = req.query; - - const storedState = req.session.oauthState; // or from cookie - if (!state || state !== storedState) { - return res.status(403).send('CSRF validation failed'); - } - - const result = await scalekit.authenticateWithCode(code, redirectUri); - // ... store session -}); -``` - -### Incomplete logout — only clearing local session - -**Wrong:** -```typescript -app.post('/logout', (req, res) => { - req.session.destroy(); - res.redirect('/'); -}); -``` - -**Correct:** -```typescript -app.post('/logout', (req, res) => { - const logoutUrl = scalekit.getLogoutUrl({ - idTokenHint: req.session.idToken, - postLogoutRedirectUri: 'https://yourapp.com', - }); - req.session.destroy(); - res.redirect(logoutUrl); // Ends IdP session too -}); -``` - -Without calling `getLogoutUrl()`, the user's IdP session persists and they get silently re-authenticated on next login. - -### Missing IdP-initiated login handling - -If the callback route doesn't check for `idp_initiated_login` query parameter, IdP-initiated SSO won't work: - -```typescript -app.get('/auth/callback', async (req, res) => { - const { idp_initiated_login, code, state } = req.query; - - if (idp_initiated_login) { - const claims = await scalekit.getIdpInitiatedLoginClaims(idp_initiated_login); - const authUrl = scalekit.getAuthorizationUrl(redirectUri, { - connectionId: claims.connection_id, - organizationId: claims.organization_id, - loginHint: claims.login_hint, - ...(claims.relay_state && { state: claims.relay_state }), - }); - return res.redirect(authUrl); - } - - // Normal SP-initiated flow continues... -}); -``` - ---- - -## 6. Security Anti-Patterns - -### `sameSite: 'strict'` on session cookies - -**Wrong:** -```typescript -res.cookie('session', data, { sameSite: 'strict', httpOnly: true, secure: true }); -``` - -**Correct:** -```typescript -res.cookie('session', data, { sameSite: 'lax', httpOnly: true, secure: true }); -``` - -OAuth callbacks are cross-site redirects from Scalekit back to your app. `strict` drops the cookie on that redirect, causing CSRF state mismatch errors on every login. - -### Missing `httpOnly` flag - -**Wrong:** -```typescript -res.cookie('session', data, { secure: true }); -``` - -**Correct:** -```typescript -res.cookie('session', data, { httpOnly: true, secure: true, sameSite: 'lax' }); -``` - -Without `httpOnly`, JavaScript can read the session cookie — XSS becomes session hijacking. - -### Open redirect via unvalidated `next` parameter - -**Wrong:** -```typescript -const next = req.query.next; -res.redirect(next); // Attacker can set next=https://evil.com -``` - -**Correct:** -```typescript -const next = req.query.next; -// Only allow relative paths -if (!next || !next.startsWith('/') || next.startsWith('//')) { - return res.redirect('/dashboard'); -} -res.redirect(next); -``` - -### Hardcoded client secret - -**Wrong:** -```typescript -const scalekit = new ScalekitClient( - 'https://myapp.scalekit.com', - 'skc_12345', - 'sks_secret_abc123' // NEVER hardcode secrets -); -``` - -**Correct:** -```typescript -const scalekit = new ScalekitClient( - process.env.SCALEKIT_ENV_URL!, - process.env.SCALEKIT_CLIENT_ID!, - process.env.SCALEKIT_CLIENT_SECRET! -); -``` - -### Webhook handler without signature verification - -**Wrong:** -```typescript -app.post('/webhooks', express.json(), (req, res) => { - const event = req.body; // Trusting unverified payload - handleEvent(event); - res.sendStatus(200); -}); -``` - -**Correct:** -```typescript -app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => { - const isValid = scalekit.verifyWebhookPayload( - process.env.SCALEKIT_WEBHOOK_SECRET!, - req.headers, - req.body.toString() - ); - - if (!isValid) { - return res.sendStatus(401); - } - - const event = JSON.parse(req.body.toString()); - handleEvent(event); - res.sendStatus(200); -}); -``` - -Note: The webhook body must be raw (not JSON-parsed) for signature verification to work. - -### Client-side navigation for OAuth redirect - -**Wrong:** -```typescript -// React / Next.js -router.push(authUrl); // Client-side route change -``` - -**Correct:** -```typescript -window.location.href = authUrl; // Full page navigation required for OAuth -``` - -OAuth redirects are full HTTP redirects to an external domain (Scalekit/IdP). Client-side routing doesn't work. - ---- - -## 7. Environment Variable Mistakes - -| Wrong | Correct | Issue | -|-------|---------|-------| -| `SCALEKIT_URL` | `SCALEKIT_ENV_URL` | Missing `ENV_` | -| `SCALEKIT_SECRET` | `SCALEKIT_CLIENT_SECRET` | Missing `CLIENT_` | -| `SCALEKIT_ID` | `SCALEKIT_CLIENT_ID` | Missing `CLIENT_` | -| `SCALEKIT_CALLBACK_URL` | `SCALEKIT_REDIRECT_URI` | Wrong name entirely | -| `http://myapp.scalekit.com` | `https://myapp.scalekit.com` | Must be HTTPS | -| `https://myapp.scalekit.com/` | `https://myapp.scalekit.com` | No trailing slash | - ---- - -## 8. Client Instantiation Mistakes - -### Creating a new client per request - -**Wrong:** -```typescript -app.get('/api/data', async (req, res) => { - const scalekit = new ScalekitClient(envUrl, clientId, clientSecret); // per-request! - // ... -}); -``` - -**Correct:** -```typescript -// Module-level singleton -const scalekit = new ScalekitClient( - process.env.SCALEKIT_ENV_URL!, - process.env.SCALEKIT_CLIENT_ID!, - process.env.SCALEKIT_CLIENT_SECRET! -); - -app.get('/api/data', async (req, res) => { - // Use the singleton - const result = await scalekit.validateAccessToken(token); -}); -``` - -The client manages its own token lifecycle and connection pooling. Creating it per request wastes resources and can hit rate limits. - ---- - -## 9. Token Refresh Race Conditions - -When multiple browser tabs trigger token refresh simultaneously, the second request often fails because the first one already consumed the refresh token. - -**Mitigation pattern:** -```typescript -// Before refreshing, set a short-lived flag -const REFRESH_LOCK_KEY = 'refresh_in_progress'; - -async function refreshToken(session) { - if (session[REFRESH_LOCK_KEY]) return; // Another tab is refreshing - - session[REFRESH_LOCK_KEY] = true; - try { - const result = await scalekit.refreshAccessToken(session.refreshToken); - session.accessToken = result.accessToken; - session.refreshToken = result.refreshToken; - } finally { - delete session[REFRESH_LOCK_KEY]; - } -} -``` - ---- - -## 10. Missing Scopes - -### Refresh tokens require `offline_access` scope - -**Wrong:** -```typescript -const authUrl = scalekit.getAuthorizationUrl(redirectUri, { - scopes: ['openid', 'profile', 'email'], -}); -// Later: scalekit.refreshAccessToken(refreshToken) → fails because no refresh token was issued -``` - -**Correct:** -```typescript -const authUrl = scalekit.getAuthorizationUrl(redirectUri, { - scopes: ['openid', 'profile', 'email', 'offline_access'], -}); -``` - -Without `offline_access`, the authorization server won't issue a refresh token. \ No newline at end of file diff --git a/plugins/agentkit/skills/scalekit-code-doctor/references/EXAMPLE-REPOS.md b/plugins/agentkit/skills/scalekit-code-doctor/references/EXAMPLE-REPOS.md deleted file mode 100644 index a66718b..0000000 --- a/plugins/agentkit/skills/scalekit-code-doctor/references/EXAMPLE-REPOS.md +++ /dev/null @@ -1,61 +0,0 @@ -# Example Repos - -Working examples by framework. Fetch the matching repo for real, tested patterns when generating or reviewing code. - -## SaaSKit — Auth examples - -| Framework | Repo | What it shows | -|-----------|------|---------------| -| Next.js (App Router) | [scalekit-nextjs-auth-example](https://github.com/scalekit-inc/scalekit-nextjs-auth-example) | SSO, sessions, protected routes | -| Next.js (Pages) | [nextjs-example-apps](https://github.com/scalekit-inc/nextjs-example-apps) | React SSO flows | -| Next.js + Auth.js | [scalekit-authjs-example](https://github.com/scalekit-developers/scalekit-authjs-example) | Enterprise SSO with next-auth v5 | -| Express.js | [scalekit-express-auth-example](https://github.com/scalekit-inc/scalekit-express-auth-example) | Node SDK, sessions | -| Express.js | [scalekit-express-example](https://github.com/scalekit-developers/scalekit-express-example) | SSO, middleware | -| FastAPI | [scalekit-fastapi-auth-example](https://github.com/scalekit-inc/scalekit-fastapi-auth-example) | Python SDK, OAuth 2.0 | -| FastAPI | [scalekit-fastapi-example](https://github.com/scalekit-developers/scalekit-fastapi-example) | Async auth, Pydantic | -| Django | [scalekit-django-auth-example](https://github.com/scalekit-inc/scalekit-django-auth-example) | Django auth integration | -| Flask | [scalekit-flask-auth-example](https://github.com/scalekit-inc/scalekit-flask-auth-example) | Flask sessions | -| Spring Boot | [scalekit-springboot-auth-example](https://github.com/scalekit-inc/scalekit-springboot-auth-example) | Spring Security, OIDC | -| Go (Gin) | [scalekit-go-example](https://github.com/scalekit-developers/scalekit-go-example) | Go SDK, SSO | -| Laravel | [scalekit-laravel-auth-example](https://github.com/scalekit-inc/scalekit-laravel-auth-example) | REST API, Laravel | -| Astro | [astro-scalekit-auth-example](https://github.com/scalekit-developers/astro-scalekit-auth-example) | Auth, SSO, social login | -| .NET | [dotnet-example-apps](https://github.com/scalekit-inc/dotnet-example-apps) | ASP.NET Core, SAML/OIDC | -| Expo (mobile) | [expo-scalekit-sample](https://github.com/scalekit-inc/expo-scalekit-sample) | OAuth 2.0 + PKCE | - -## SaaSKit — Integration examples - -| Integration | Repo | -|-------------|------| -| AWS Cognito | [scalekit-cognito-sso](https://github.com/scalekit-inc/scalekit-cognito-sso) | -| Firebase | [scalekit-firebase-sso](https://github.com/scalekit-inc/scalekit-firebase-sso) | -| Supabase | [scalekit-supabase-example](https://github.com/scalekit-inc/scalekit-supabase-example) | -| Multi-app SSO | [multiapp-demo](https://github.com/scalekit-inc/multiapp-demo) | -| OIDC/SAML/SCIM | [oidc-saml-scim-examples](https://github.com/scalekit-developers/oidc-saml-scim-examples) | -| Full demo app | [coffee-desk-demo](https://github.com/scalekit-inc/coffee-desk-demo) | - -## AgentKit — Agent and MCP examples - -| Framework | Repo | -|-----------|------| -| LangChain | [sample-langchain-agent](https://github.com/scalekit-inc/sample-langchain-agent) | -| Google ADK | [google-adk-agent-example](https://github.com/scalekit-inc/google-adk-agent-example) | -| Vercel AI SDK | [vercel-ai-agent-toolkit](https://github.com/scalekit-developers/vercel-ai-agent-toolkit) | -| MCP Auth | [mcp-auth-demos](https://github.com/scalekit-inc/mcp-auth-demos) | -| FastMCP | [fastmcp-scalekit-example](https://github.com/scalekit-inc/fastmcp-scalekit-example) | -| Agent auth | [agent-auth-examples](https://github.com/scalekit-developers/agent-auth-examples) | - -## Developer tools - -| Tool | Repo | -|------|------| -| Dryrun CLI | [scalekit-dryrun](https://github.com/scalekit-inc/scalekit-dryrun) | -| MCP server | [scalekit-mcp-server](https://github.com/scalekit-inc/scalekit-mcp-server) | -| API collections | [api-collections](https://github.com/scalekit-inc/api-collections) | - -## Frontend SDKs - -| SDK | Repo | -|-----|------| -| React | [scalekit-react-sdk](https://github.com/scalekit-inc/scalekit-react-sdk) | -| Vue | [scalekit-vue-sdk](https://github.com/scalekit-inc/scalekit-vue-sdk) | -| Expo | [scalekit-expo-sdk](https://github.com/scalekit-inc/scalekit-expo-sdk) | \ No newline at end of file diff --git a/plugins/agentkit/skills/scalekit-code-doctor/references/REFERENCE.md b/plugins/agentkit/skills/scalekit-code-doctor/references/REFERENCE.md deleted file mode 100644 index 5ecab18..0000000 --- a/plugins/agentkit/skills/scalekit-code-doctor/references/REFERENCE.md +++ /dev/null @@ -1,503 +0,0 @@ -# Scalekit API Reference — Compact Lookup - -This file contains every correct SDK method signature and REST endpoint. Use it as ground truth when generating or reviewing Scalekit code. If a method isn't listed here, do NOT assume it exists — verify against the live SDK source or `https://docs.scalekit.com/apis.md`. - ---- - -## Client Initialization - -### Node.js (`@scalekit-sdk/node`) - -```typescript -import { ScalekitClient } from '@scalekit-sdk/node'; - -const scalekit = new ScalekitClient( - process.env.SCALEKIT_ENV_URL!, // string — environment URL - process.env.SCALEKIT_CLIENT_ID!, // string — client ID - process.env.SCALEKIT_CLIENT_SECRET! // string — client secret -); -``` - -### Python (`scalekit-sdk-python`) - -```python -from scalekit import ScalekitClient - -scalekit_client = ScalekitClient( - os.environ.get('SCALEKIT_ENV_URL'), # str — environment URL - os.environ.get('SCALEKIT_CLIENT_ID'), # str — client ID - os.environ.get('SCALEKIT_CLIENT_SECRET') # str — client secret -) -``` - -### Go (`github.com/scalekit-inc/scalekit-sdk-go`) - -```go -import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2" - -client := scalekit.NewScalekitClient( - os.Getenv("SCALEKIT_ENV_URL"), // string — environment URL - os.Getenv("SCALEKIT_CLIENT_ID"), // string — client ID - os.Getenv("SCALEKIT_CLIENT_SECRET"), // string — client secret -) -``` - -### Java (`com.scalekit:scalekit-sdk-java`) - -```java -import com.scalekit.ScalekitClient; - -ScalekitClient client = new ScalekitClient( - System.getenv("SCALEKIT_ENV_URL"), // String — environment URL - System.getenv("SCALEKIT_CLIENT_ID"), // String — client ID - System.getenv("SCALEKIT_CLIENT_SECRET") // String — client secret -); -``` - ---- - -## Environment Variables - -| Variable | Purpose | Format | -|----------|---------|--------| -| `SCALEKIT_ENV_URL` | Environment URL | `https://.scalekit.com` (prod) or `https://.scalekit.dev` (dev) | -| `SCALEKIT_CLIENT_ID` | Client ID | String from dashboard | -| `SCALEKIT_CLIENT_SECRET` | Client secret | String from dashboard | -| `SCALEKIT_REDIRECT_URI` | OAuth callback URL | Must exactly match dashboard config | -| `SCALEKIT_WEBHOOK_SECRET` | Webhook signing secret | Format: `whsec_...` | - -Note: The REST API docs use `SCALEKIT_ENVIRONMENT_URL` in some examples. Both `SCALEKIT_ENV_URL` and `SCALEKIT_ENVIRONMENT_URL` are acceptable — just be consistent within a project. - ---- - -## Auth Methods (called directly on the client) - -### Node.js - -| Method | Signature | Returns | -|--------|-----------|---------| -| `getAuthorizationUrl` | `(redirectUri: string, options?: AuthorizationUrlOptions) → string` | Authorization URL string | -| `authenticateWithCode` | `(code: string, redirectUri: string, options?: AuthenticationOptions) → Promise` | Tokens + user info | -| `getIdpInitiatedLoginClaims` | `(idpInitiatedLoginToken: string, options?: TokenValidationOptions) → Promise` | IDP login claims | -| `validateAccessToken` | `(token: string, options?: TokenValidationOptions) → Promise` | Boolean | -| `validateToken` | `(token: string, options?: TokenValidationOptions) → Promise` | Decoded JWT payload | -| `verifyScopes` | `(token: string, requiredScopes: string[]) → boolean` | Boolean | -| `getLogoutUrl` | `(options?: LogoutUrlOptions) → string` | Logout URL string | -| `refreshAccessToken` | `(refreshToken: string) → Promise` | New tokens | -| `verifyWebhookPayload` | `(secret: string, headers: Record, payload: string) → boolean` | Boolean | -| `verifyInterceptorPayload` | `(secret: string, headers: Record, payload: string) → boolean` | Boolean | -| `generateClientToken` | `(clientId: string, clientSecret: string) → Promise` | M2M access token | -| `getClientAccessToken` | `() → Promise` | M2M access token (uses stored credentials) | - -**AuthorizationUrlOptions**: `scopes?: string[]`, `state?: string`, `nonce?: string`, `loginHint?: string`, `domainHint?: string`, `connectionId?: string`, `organizationId?: string`, `provider?: string`, `codeChallenge?: string`, `codeChallengeMethod?: string`, `prompt?: string` - -**LogoutUrlOptions**: `idTokenHint?: string`, `postLogoutRedirectUri?: string`, `state?: string` - -**AuthenticationOptions**: `codeVerifier?: string` - -### Python - -| Method | Signature | Returns | -|--------|-----------|---------| -| `get_authorization_url` | `(redirect_uri: str, options?: AuthorizationUrlOptions) → str` | Authorization URL string | -| `authenticate_with_code` | `(code: str, redirect_uri: str, options?: CodeAuthenticationOptions) → dict` | Tokens + user info | -| `get_idp_initiated_login_claims` | `(idp_initiated_login_token: str, options?: TokenValidationOptions) → IdpInitiatedLoginClaims` | IDP login claims | -| `validate_access_token` | `(token: str, options?: TokenValidationOptions) → bool` | Boolean | -| `get_logout_url` | `(options?: LogoutUrlOptions) → str` | Logout URL string | -| `refresh_access_token` | `(refresh_token: str) → dict` | New tokens | -| `verify_webhook_payload` | `(secret: str, headers: Dict[str, str], payload: str\|bytes) → bool` | Boolean | - -**AuthorizationUrlOptions** (Python): `scopes`, `state`, `nonce`, `login_hint`, `domain_hint`, `connection_id`, `organization_id`, `provider`, `prompt` — all `Optional[str]` (scopes is `Optional[list[str]]`) - -**LogoutUrlOptions** (Python): `id_token_hint`, `post_logout_redirect_uri`, `state` — all `Optional[str]` - -### Go - -| Method | Signature | Returns | -|--------|-----------|---------| -| `GetAuthorizationUrl` | `(redirectUri string, options AuthorizationUrlOptions) → (*url.URL, error)` | URL + error | -| `AuthenticateWithCode` | `(ctx context.Context, code string, redirectUri string, options AuthenticationOptions) → (*AuthenticationResponse, error)` | Response + error | -| `GetIdpInitiatedLoginClaims` | `(ctx context.Context, idpInitiatedLoginToken string) → (*IdpInitiatedLoginClaims, error)` | Claims + error | -| `GetAccessTokenClaims` | `(ctx context.Context, accessToken string) → (*AccessTokenClaims, error)` | Claims + error | -| `ValidateAccessToken` | `(ctx context.Context, accessToken string) → (bool, error)` | Boolean + error | -| `RefreshAccessToken` | `(ctx context.Context, refreshToken string) → (*TokenResponse, error)` | Tokens + error | -| `GetLogoutUrl` | `(options LogoutUrlOptions) → string` | Logout URL string | -| `VerifyWebhookPayload` | `(secret string, headers map[string][]string, payload []byte) → bool` | Boolean | - -**Go AuthorizationUrlOptions fields**: `Scopes []string`, `State string`, `Nonce string`, `LoginHint string`, `DomainHint string`, `ConnectionId string`, `OrganizationId string`, `Provider string`, `CodeChallenge string`, `CodeChallengeMethod string`, `Prompt string` - -Note: Go methods take `context.Context` as the first parameter for network calls. `GetAuthorizationUrl` and `GetLogoutUrl` do NOT take context (they're local-only operations). - -### Java - -| Method | Signature | Returns | -|--------|-----------|---------| -| `getAuthorizationUrl` | `(redirectUri: String, options: AuthorizationUrlOptions) → String` | Authorization URL string | -| `authenticateWithCode` | `(code: String, redirectUri: String, options: AuthenticationOptions) → AuthenticationResponse` | Tokens + user info | -| `getIdpInitiatedLoginClaims` | `(idpInitiatedLoginToken: String) → IdpInitiatedLoginClaims` | Claims | -| `validateToken` | `(token: String) → Claims` | JWT Claims | -| `getLogoutUrl` | `(options: LogoutUrlOptions) → String` | Logout URL string | - ---- - -## Sub-client Methods - -### Node.js sub-clients (accessed via `client..`) - -**client.organization** -| Method | Signature | -|--------|-----------| -| `createOrganization` | `(name: string, options?) → Promise` | -| `getOrganization` | `(id: string) → Promise` | -| `getOrganizationByExternalId` | `(externalId: string) → Promise` | -| `listOrganizations` | `(options?) → Promise` | -| `updateOrganization` | `(id: string, organization) → Promise` | -| `deleteOrganization` | `(id: string) → Promise` | -| `generatePortalLink` | `(organizationId: string, features?) → Promise` | -| `updateOrganizationSettings` | `(id: string, settings) → Promise` | - -**client.connection** -| Method | Signature | -|--------|-----------| -| `getConnection` | `(id: string) → Promise` | -| `listConnections` | `(options?) → Promise` | -| `listConnectionsByDomain` | `(domain: string, options?) → Promise` | -| `enableConnection` | `(connectionId: string) → Promise` | -| `disableConnection` | `(connectionId: string) → Promise` | - -**client.domain** -| Method | Signature | -|--------|-----------| -| `createDomain` | `(domain: string) → Promise` | -| `getDomain` | `(domain: string) → Promise` | -| `listDomains` | `(options?) → Promise` | -| `deleteDomain` | `(domain: string) → Promise` | - -**client.user** -| Method | Signature | -|--------|-----------| -| `createUser` | `(organizationId: string, user) → Promise` | -| `createUserAndMembership` | `(organizationId: string, request) → Promise` | -| `getUser` | `(id: string) → Promise` | -| `listUsers` | `(options?) → Promise` | -| `listOrganizationUsers` | `(organizationId: string, options?) → Promise` | -| `updateUser` | `(id: string, user) → Promise` | -| `deleteUser` | `(id: string) → Promise` | -| `searchUsers` | `(options) → Promise` | -| `searchOrganizationUsers` | `(organizationId: string, options) → Promise` | - -**client.directory** -| Method | Signature | -|--------|-----------| -| `listDirectories` | `(organizationId: string) → Promise` | -| `getDirectory` | `(organizationId: string, directoryId: string) → Promise` | -| `listDirectoryUsers` | `(organizationId: string, directoryId: string, options?) → Promise` | -| `listDirectoryGroups` | `(organizationId: string, directoryId: string, options?) → Promise` | -| `enableDirectory` | `(organizationId: string, directoryId: string) → Promise` | -| `disableDirectory` | `(organizationId: string, directoryId: string) → Promise` | - -**client.role** -| Method | Signature | -|--------|-----------| -| `createRole` | `(role) → Promise` | -| `getRole` | `(roleId: string) → Promise` | -| `listRoles` | `(options?) → Promise` | -| `updateRole` | `(roleId: string, role) → Promise` | -| `deleteRole` | `(roleId: string) → Promise` | - -**client.permission** -| Method | Signature | -|--------|-----------| -| `createPermission` | `(permission) → Promise` | -| `listPermissions` | `(options?) → Promise` | -| `updatePermission` | `(permissionId: string, permission) → Promise` | -| `deletePermission` | `(permissionId: string) → Promise` | - -**client.session** -| Method | Signature | -|--------|-----------| -| `getSession` | `(sessionId: string) → Promise` | -| `getUserSessions` | `(userId: string, options?) → Promise` | -| `revokeSession` | `(sessionId: string) → Promise` | -| `revokeAllUserSessions` | `(userId: string) → Promise` | - -**client.connectedAccounts** -| Method | Signature | -|--------|-----------| -| `listConnectedAccounts` | `(options?) → Promise` | -| `getConnectedAccountAuth` | `(options) → Promise` | -| `createConnectedAccount` | `(request) → Promise` | -| `updateConnectedAccount` | `(request) → Promise` | -| `deleteConnectedAccount` | `(request) → Promise` | - -**client.tools** -| Method | Signature | -|--------|-----------| -| `executeTool` | `(request) → Promise` | - -### Python sub-clients (accessed via `client..`) - -Python uses `snake_case` method names. **Important**: Some Python sub-client names are **plural** while Node uses singular. This is a common source of bugs. - -| Node.js | Python | Difference | -|---------|--------|------------| -| `client.user` | `client.users` | Plural in Python | -| `client.role` | `client.roles` | Plural in Python | -| `client.permission` | `client.permissions` | Plural in Python | -| `client.session` | `client.sessions` | Plural in Python | -| `client.organization` | `client.organization` | Same | -| `client.connection` | `client.connection` | Same | -| `client.domain` | `client.domain` | Same | -| `client.directory` | `client.directory` | Same | -| `client.connectedAccounts` | `client.connected_accounts` | snake_case in Python | - -Methods: -- `client.organization.create_organization(organization)` -- `client.organization.get_organization(organization_id)` -- `client.organization.list_organizations(page_size, page_token?)` -- `client.organization.update_organization(organization_id, organization)` -- `client.organization.delete_organization(organization_id)` -- `client.organization.generate_portal_link(organization_id, features?)` -- `client.connection.list_connections(organization_id, include?)` -- `client.connection.get_connection(organization_id, connection_id)` -- `client.connection.enable_connection(organization_id, connection_id)` -- `client.connection.disable_connection(organization_id, connection_id)` -- `client.domain.create_domain(organization_id, domain_name)` -- `client.domain.list_domains(organization_id)` -- `client.domain.delete_domain(organization_id, domain_id)` -- `client.directory.list_directories(organization_id)` -- `client.directory.get_directory(organization_id, directory_id)` -- `client.directory.list_directory_users(organization_id, directory_id, options?)` -- `client.directory.list_directory_groups(organization_id, directory_id, options?)` -- `client.users.create_user(organization_id, user)` -- `client.users.get_user(user_id)` -- `client.users.list_users(options?)` -- `client.users.update_user(user_id, user)` -- `client.users.delete_user(user_id)` -- `client.roles.create_role(role)` -- `client.roles.list_roles(options?)` -- `client.roles.update_role(role_id, role)` -- `client.roles.delete_role(role_id)` -- `client.permissions.create_permission(permission)` -- `client.permissions.list_permissions(options?)` -- `client.sessions.get_session(session_id)` -- `client.sessions.get_user_sessions(user_id, options?)` -- `client.sessions.revoke_session(session_id)` -- `client.sessions.revoke_all_user_sessions(user_id)` - -Additional Python-only methods on client: -- `client.validate_access_token_and_get_claims(token, options?) → dict` — validates and returns decoded claims -- `client.verify_scopes(token, required_scopes) → bool` — checks scopes, raises on missing -- `client.generate_client_token(client_id, client_secret, scopes?) → str` — M2M token generation -- `client.get_client_access_token() → str` — M2M token using stored credentials -- `client.verify_interceptor_payload(secret, headers, payload) → bool` — interceptor signature verification - -Note: Python connection/domain/directory methods often require `organization_id` as the first parameter, unlike Node which uses option objects. - -### Go sub-clients - -Go uses `PascalCase` and typed request/response objects: -- `client.Organization().CreateOrganization(ctx, request)` -- `client.Organization().GetOrganization(ctx, organizationId)` -- `client.Organization().ListOrganizations(ctx, pageSize, pageToken)` -- `client.Organization().UpdateOrganization(ctx, organizationId, request)` -- `client.Organization().DeleteOrganization(ctx, organizationId)` -- `client.Organization().GeneratePortalLink(ctx, organizationId, features)` -- `client.Connection().GetConnection(ctx, organizationId, connectionId)` -- `client.Connection().ListConnections(ctx, organizationId)` -- `client.Connection().EnableConnection(ctx, organizationId, connectionId)` -- `client.Connection().DisableConnection(ctx, organizationId, connectionId)` -- `client.Domain().CreateDomain(ctx, organizationId, domainName)` -- `client.Domain().ListDomains(ctx, organizationId)` -- `client.Domain().DeleteDomain(ctx, organizationId, domainId)` -- `client.Directory().ListDirectories(ctx, organizationId)` -- `client.Directory().GetDirectory(ctx, organizationId, directoryId)` -- `client.Directory().ListDirectoryUsers(ctx, organizationId, directoryId, options)` -- `client.Directory().ListDirectoryGroups(ctx, organizationId, directoryId, options)` -- `client.User().CreateUser(ctx, organizationId, request)` -- `client.User().GetUser(ctx, userId)` -- `client.User().ListUsers(ctx, options)` -- `client.User().UpdateUser(ctx, userId, request)` -- `client.User().DeleteUser(ctx, userId)` -- `client.Role().ListRoles(ctx)` -- `client.Role().CreateRole(ctx, request)` -- `client.Session().GetSession(ctx, sessionId)` -- `client.Session().RevokeSession(ctx, sessionId)` -- `client.Session().RevokeAllUserSessions(ctx, userId)` - -### Java sub-clients - -Java uses accessor methods that return typed clients: -- `client.organizations().create(request) → CreateOrganizationResponse` -- `client.organizations().getById(organizationId) → Organization` -- `client.organizations().getByExternalId(externalId) → Organization` -- `client.organizations().list(pageSize, pageToken) → ListOrganizationsResponse` -- `client.organizations().update(organizationId, request) → Organization` -- `client.organizations().delete(organizationId)` -- `client.organizations().generatePortalLink(organizationId, features) → Link` -- `client.connections().listConnectionsByOrganization(organizationId) → ListConnectionsResponse` -- `client.connections().getConnection(organizationId, connectionId) → GetConnectionResponse` -- `client.connections().enableConnection(organizationId, connectionId)` -- `client.connections().disableConnection(organizationId, connectionId)` -- `client.domains().listDomainsByOrganizationId(organizationId) → ListDomainsResponse` -- `client.domains().createDomain(organizationId, domainName) → CreateDomainResponse` -- `client.domains().deleteDomain(organizationId, domainId)` -- `client.directories().listDirectories(organizationId) → ListDirectoriesResponse` -- `client.directories().getDirectory(organizationId, directoryId) → GetDirectoryResponse` -- `client.directories().listDirectoryUsers(organizationId, directoryId) → ListDirectoryUsersResponse` -- `client.directories().listDirectoryGroups(organizationId, directoryId) → ListDirectoryGroupsResponse` -- `client.users().getUser(userId) → GetUserResponse` -- `client.users().listUsers(options) → ListUsersResponse` -- `client.users().createUser(organizationId, request) → CreateUserResponse` -- `client.users().createUserAndMembership(organizationId, request) → CreateUserAndMembershipResponse` -- `client.users().updateUser(userId, request) → UpdateUserResponse` -- `client.users().deleteUser(userId)` -- `client.roles().listRoles() → ListRolesResponse` -- `client.roles().createRole(request) → CreateRoleResponse` -- `client.roles().updateRole(roleId, request) → UpdateRoleResponse` -- `client.roles().deleteRole(roleId)` -- `client.permissions().listPermissions() → ListPermissionsResponse` -- `client.permissions().createPermission(request) → CreatePermissionResponse` -- `client.sessions().getSession(sessionId) → SessionDetails` -- `client.sessions().getUserSessions(userId, filter) → UserSessionDetails` -- `client.sessions().revokeSession(sessionId)` -- `client.sessions().revokeAllUserSessions(userId)` - -Note: Java does NOT yet support Connected Accounts, Tools, or Actions in the public API. - ---- - -## REST API Endpoints - -Base URL: `https://.scalekit.com` (production) or `https://.scalekit.dev` (development) - -Authentication: Bearer token from `POST /oauth/token` with `client_credentials` grant. - -Endpoints are grouped by product suite: **SaaSKit** (authentication, SSO, SCIM, RBAC, sessions) and **AgentKit** (connections, tool calling, MCP auth). - -### Token endpoint -``` -POST /oauth/token -Content-Type: application/x-www-form-urlencoded - -client_id={client_id}&client_secret={client_secret}&grant_type=client_credentials -``` - -### AgentKit — Connected Accounts -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/v1/connected_accounts` | List connected accounts | -| POST | `/api/v1/connected_accounts` | Create a connected account | -| PUT | `/api/v1/connected_accounts` | Update connected account credentials | -| POST | `/api/v1/connected_accounts:delete` | Delete a connected account | -| GET | `/api/v1/connected_accounts/auth` | Get connected account auth details | -| GET | `/api/v1/connected_accounts:search` | Search connected accounts | -| POST | `/api/v1/connected_accounts/magic_link` | Generate authentication magic link | -| POST | `/api/v1/connected_accounts/user/verify` | Verify connected account user | - -### SaaSKit — Connections (SSO) -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/v1/connections` | List connections | - -### SaaSKit — Organizations -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/v1/organizations` | List organizations | -| POST | `/api/v1/organizations` | Create an organization | -| GET | `/api/v1/organizations/{id}` | Get organization details | -| PATCH | `/api/v1/organizations/{id}` | Update organization | -| DELETE | `/api/v1/organizations/{id}` | Delete an organization | -| PUT | `/api/v1/organizations/{id}/portal_links` | Generate admin portal link | -| PATCH | `/api/v1/organizations/{id}/settings` | Toggle organization settings | - -### SaaSKit — Roles -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/v1/organizations/{org_id}/roles` | List organization roles | -| POST | `/api/v1/organizations/{org_id}/roles` | Create organization role | -| GET | `/api/v1/organizations/{org_id}/roles/{role_name}` | Get role details | -| PUT | `/api/v1/organizations/{org_id}/roles/{role_name}` | Update role | -| DELETE | `/api/v1/organizations/{org_id}/roles/{role_name}` | Delete role | -| PATCH | `/api/v1/organizations/{org_id}/roles:set_defaults` | Set default roles | - -### SaaSKit — Users & Memberships -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/v1/users` | List users | -| POST | `/api/v1/users` | Create a user | -| GET | `/api/v1/users/{id}` | Get user details | -| PATCH | `/api/v1/users/{id}` | Update user | -| DELETE | `/api/v1/users/{id}` | Delete user | -| GET | `/api/v1/users:search` | Search users | -| GET | `/api/v1/organizations/{org_id}/users` | List organization users | -| GET | `/api/v1/organizations/{org_id}/users:search` | Search organization users | -| POST | `/api/v1/memberships/organizations/{organization_id}/users/{id}` | Add user to organization | -| DELETE | `/api/v1/memberships/organizations/{organization_id}/users/{id}` | Remove user from organization | -| PATCH | `/api/v1/memberships/organizations/{organization_id}/users/{id}` | Update membership | -| PATCH | `/api/v1/invites/organizations/{organization_id}/users/{id}/resend` | Resend invitation | - -### SaaSKit — Sessions -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/v1/users/{user_id}/sessions` | Get user sessions | -| POST | `/api/v1/users/{user_id}/sessions:revoke_all` | Revoke all user sessions | -| POST | `/api/v1/sessions/{session_id}:revoke` | Revoke a session | - -### AgentKit — Tools -| Method | Path | Description | -|--------|------|-------------| -| POST | `/api/v1/execute_tool` | Execute a tool using a connected account | - -### SaaSKit — Organization API Clients (M2M) -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/v1/organizations/{organization_id}/clients` | List org API clients | -| POST | `/api/v1/organizations/{organization_id}/clients` | Create org API client | -| GET | `/api/v1/organizations/{organization_id}/clients/{client_id}` | Get org API client | -| DELETE | `/api/v1/organizations/{organization_id}/clients/{client_id}` | Delete org API client | -| PATCH | `/api/v1/organizations/{organization_id}/clients/{client_id}` | Update org API client | - ---- - -## Error Handling - -### Node.js exception hierarchy -``` -ScalekitException (base) -├── ScalekitValidateTokenFailureException -├── ScalekitServerException (HTTP 400-599) -│ ├── properties: httpStatus, errorCode, message, errDetails -│ └── Specific subclasses for 400, 401, 403, 404, 409, 422, 429, 500, 502, 503, 504 -└── WebhookVerificationError -``` - -Import: `import { ScalekitServerException } from '@scalekit-sdk/node'` - -### Python exceptions -``` -ScalekitException (base) -``` - -### Go errors -All methods return `(result, error)`. Check `err != nil` for all network calls. - -### Java exceptions -All methods may throw checked exceptions. Wrap in try-catch. - ---- - -## Common Token Claims - -Access tokens from Scalekit contain these standard claims: -- `sub` — User ID -- `email` — User email -- `name` — Display name -- `org_id` — Organization ID -- `roles` — Array of role names -- `permissions` — Array of permission strings (also available at `https://scalekit.com/permissions` or `scalekit:permissions` claim paths) - -Permission claims should be checked in this priority order: -1. `permissions` claim -2. `https://scalekit.com/permissions` claim -3. `scalekit:permissions` claim \ No newline at end of file diff --git a/plugins/saaskit/README.md b/plugins/saaskit/README.md index 8106233..4e80be5 100644 --- a/plugins/saaskit/README.md +++ b/plugins/saaskit/README.md @@ -17,8 +17,15 @@ Claude runtime files: - `references/` ## Installation + +Run the install script (macOS/Linux): ```sh -claude /plugin install saaskit@scalekit-auth-stack +curl -fsSL https://raw.githubusercontent.com/scalekit-inc/claude-code-authstack/main/scripts/install.sh | bash +``` + +Or inside Claude Code, run: +``` +/plugin install saaskit@scalekit-auth-stack ``` Official Scalekit docs: diff --git a/plugins/saaskit/skills/implementing-modular-sso/SKILL.md b/plugins/saaskit/skills/implementing-modular-sso/SKILL.md index aa9d09e..0624059 100644 --- a/plugins/saaskit/skills/implementing-modular-sso/SKILL.md +++ b/plugins/saaskit/skills/implementing-modular-sso/SKILL.md @@ -552,7 +552,7 @@ app.post('/logout', (req, res) => { **Connection Selector Precedence**: connectionId > organizationId > loginHint -**Token Expiration**: ID tokens expire in 15 minutes, access tokens in 24 hours +**Token Expiration**: ID tokens expire in 15 minutes, access tokens in 5 minutes (configurable in dashboard) **Admin Portal Events**: Listen for `sso.enabled`, `sso.disabled`, `session.expired` diff --git a/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md new file mode 100644 index 0000000..2096a0c --- /dev/null +++ b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md @@ -0,0 +1,247 @@ +# Scalekit Auth — Django + +## Dependencies + +```bash +pip install scalekit-sdk-python python-dotenv django +``` + +## Environment variables + +```env +SCALEKIT_ENV_URL=https://your-env.scalekit.dev +SCALEKIT_CLIENT_ID=your_client_id +SCALEKIT_CLIENT_SECRET=your_client_secret +SCALEKIT_REDIRECT_URI=http://localhost:8000/auth/callback +``` + +Load with `python-dotenv` or Django's built-in settings from env. + +## Client initialization — initialize once + +In `yourapp/auth_client.py`: + +```python +import os +from scalekit import ScalekitClient + +_sc = None + +def get_scalekit_client() -> ScalekitClient: + global _sc + if _sc is None: + _sc = ScalekitClient( + env_url=os.getenv("SCALEKIT_ENV_URL"), + client_id=os.getenv("SCALEKIT_CLIENT_ID"), + client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), + ) + return _sc +``` + +## Auth flow at a glance + +``` +GET /auth/login + → get_authorization_url() → 302 to Scalekit + +GET /auth/callback?code=... + → authenticate_with_code() → set session → redirect to /dashboard + +Middleware: ScalekitAuthMiddleware + → validate_access_token() → refresh if expired → pass or redirect /auth/login + +GET /auth/logout + → get_logout_url() → clear session → 302 to Scalekit end-session +``` + +## Views + +```python +# yourapp/views.py +import os, secrets +from django.shortcuts import redirect +from django.http import HttpRequest, HttpResponse +from .auth_client import get_scalekit_client + +REDIRECT_URI = os.getenv("SCALEKIT_REDIRECT_URI", "http://localhost:8000/auth/callback") + +def login(request: HttpRequest): + state = secrets.token_urlsafe(32) + request.session["oauth_state"] = state + sc = get_scalekit_client() + auth_url = sc.get_authorization_url(REDIRECT_URI, options={"state": state}) + return redirect(auth_url) + +def callback(request: HttpRequest): + stored_state = request.session.pop("oauth_state", None) + if not stored_state or stored_state != request.GET.get("state"): + return HttpResponse("CSRF mismatch", status=403) + if error := request.GET.get("error"): + return HttpResponse(f"Auth error: {error}", status=400) + + sc = get_scalekit_client() + result = sc.authenticate_with_code(request.GET["code"], REDIRECT_URI) + + request.session["access_token"] = result.access_token + request.session["refresh_token"] = result.refresh_token + request.session["id_token"] = result.id_token + request.session.cycle_key() # session fixation protection + + return redirect("/dashboard") + +def logout(request: HttpRequest): + id_token = request.session.get("id_token", "") + sc = get_scalekit_client() + logout_url = sc.get_logout_url({"post_logout_redirect_uri": "http://localhost:8000"}) + request.session.flush() + return redirect(logout_url) +``` + +## Middleware + +```python +# yourapp/middleware.py +from django.shortcuts import redirect +from .auth_client import get_scalekit_client + +SKIP_PATHS = {"/auth/login", "/auth/callback", "/auth/logout"} + +class ScalekitAuthMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if request.path in SKIP_PATHS: + return self.get_response(request) + + access_token = request.session.get("access_token") + refresh_token = request.session.get("refresh_token") + + if not access_token: + return redirect(f"/auth/login?next={request.path}") + + sc = get_scalekit_client() + try: + sc.validate_access_token(access_token) + except Exception: + # Token expired — attempt silent refresh + if not refresh_token: + request.session.flush() + return redirect("/auth/login") + try: + refreshed = sc.refresh_access_token(refresh_token) + request.session["access_token"] = refreshed.access_token + request.session["refresh_token"] = refreshed.refresh_token + except Exception: + request.session.flush() + return redirect("/auth/login") + + return self.get_response(request) +``` + +Register in `settings.py`: + +```python +MIDDLEWARE = [ + # ... Django default middleware ... + "yourapp.middleware.ScalekitAuthMiddleware", +] +``` + +## URL configuration + +```python +# yourapp/urls.py +from django.urls import path +from . import views + +urlpatterns = [ + path("auth/login", views.login, name="auth_login"), + path("auth/callback", views.callback, name="auth_callback"), + path("auth/logout", views.logout, name="auth_logout"), + path("dashboard/", views.dashboard, name="dashboard"), +] +``` + +## Session storage + +Use database-backed sessions for production: + +```python +# settings.py +SESSION_ENGINE = "django.contrib.sessions.backends.db" +SESSION_COOKIE_HTTPONLY = True +SESSION_COOKIE_SECURE = True # HTTPS only +SESSION_COOKIE_SAMESITE = "Lax" +SESSION_COOKIE_AGE = 86400 # 24 hours +``` + +## Implementation checklist + +``` +- [ ] Step 1: pip install scalekit-sdk-python python-dotenv django +- [ ] Step 2: Set SCALEKIT_ENV_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET in .env +- [ ] Step 3: Create auth_client.py with singleton ScalekitClient +- [ ] Step 4: Implement login, callback, logout views +- [ ] Step 5: Add ScalekitAuthMiddleware to MIDDLEWARE in settings.py +- [ ] Step 6: Register /auth/login, /auth/callback, /auth/logout routes +- [ ] Step 7: Configure SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SECURE=True +- [ ] Step 8: Register callback URI in Scalekit dashboard +- [ ] Step 9: Call session.cycle_key() after login (session fixation protection) +- [ ] Step 10: Test: login → /dashboard → token refresh → logout +``` + +## Troubleshooting + +**`authenticate_with_code` raises exception**: The `redirect_uri` must exactly match the URI in the Scalekit dashboard — including scheme, host, and path. + +**Session not persisting across requests**: Ensure `django.contrib.sessions` is in `INSTALLED_APPS` and `python manage.py migrate` has been run. + +**CSRF errors on callback**: The `/auth/callback` GET route is not subject to Django's CSRF middleware (CSRF applies to POST). Add it to `CSRF_EXEMPT_URLS` if you hit issues. + +**Redirect loop in middleware**: Verify `SKIP_PATHS` includes `/auth/login`, `/auth/callback`, and `/auth/logout`. Also ensure static file paths are excluded. + +## Tactics + +### Deep link preservation + +```python +# In login view +next_url = request.GET.get("next", "/dashboard") +if not next_url.startswith("/"): + next_url = "/dashboard" +request.session["next"] = next_url + +# In callback view +next_url = request.session.pop("next", "/dashboard") +return redirect(next_url) +``` + +### Cache-Control: no-store on protected views + +```python +from django.views.decorators.cache import never_cache + +@never_cache +def dashboard(request): + ... +``` + +### IDP-initiated SSO + +```python +def idp_login(request): + sc = get_scalekit_client() + claims = sc.get_idp_initiated_login_claims(request.GET.get("idp_initiated_login", "")) + options = {} + if claims.organization_id: options["organization_id"] = claims.organization_id + if claims.connection_id: options["connection_id"] = claims.connection_id + if claims.login_hint: options["login_hint"] = claims.login_hint + auth_url = sc.get_authorization_url(REDIRECT_URI, options=options) + return redirect(auth_url) +``` + +## Reference + +- Scalekit Python SDK: [docs.scalekit.com/apis](https://docs.scalekit.com/apis) +- Django sessions: [docs.djangoproject.com/topics/http/sessions](https://docs.djangoproject.com/en/stable/topics/http/sessions/) diff --git a/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md b/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md new file mode 100644 index 0000000..bf79540 --- /dev/null +++ b/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md @@ -0,0 +1,221 @@ +# Scalekit Auth — Flask + +## Dependencies + +```bash +pip install scalekit-sdk-python python-dotenv flask flask-session +``` + +## Environment variables + +```env +SCALEKIT_ENV_URL=https://your-env.scalekit.dev +SCALEKIT_CLIENT_ID=your_client_id +SCALEKIT_CLIENT_SECRET=your_client_secret +SCALEKIT_REDIRECT_URI=http://localhost:5000/auth/callback +SECRET_KEY=your-flask-secret-key +``` + +## App setup + +```python +# app.py +import os, secrets +from dotenv import load_dotenv +from flask import Flask, redirect, request, session, url_for, jsonify +from flask_session import Session +from scalekit import ScalekitClient + +load_dotenv() + +app = Flask(__name__) +app.secret_key = os.getenv("SECRET_KEY") +app.config["SESSION_TYPE"] = "filesystem" # or "redis", "sqlalchemy" +app.config["SESSION_COOKIE_HTTPONLY"] = True +app.config["SESSION_COOKIE_SECURE"] = True +app.config["SESSION_COOKIE_SAMESITE"] = "Lax" +Session(app) + +REDIRECT_URI = os.getenv("SCALEKIT_REDIRECT_URI", "http://localhost:5000/auth/callback") + +sc = ScalekitClient( + env_url=os.getenv("SCALEKIT_ENV_URL"), + client_id=os.getenv("SCALEKIT_CLIENT_ID"), + client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), +) +``` + +## Auth flow at a glance + +``` +GET /auth/login + → get_authorization_url() → 302 to Scalekit + +GET /auth/callback?code=... + → authenticate_with_code() → set session → redirect to /dashboard + +@require_auth decorator + → validate_access_token() → refresh if expired → pass or redirect /auth/login + +GET /auth/logout + → get_logout_url() → clear session → 302 to Scalekit end-session +``` + +## Routes + +```python +@app.get("/auth/login") +def login(): + state = secrets.token_urlsafe(32) + session["oauth_state"] = state + auth_url = sc.get_authorization_url(REDIRECT_URI, options={"state": state}) + return redirect(auth_url) + +@app.get("/auth/callback") +def callback(): + stored = session.pop("oauth_state", None) + if not stored or stored != request.args.get("state"): + return "CSRF mismatch", 403 + if error := request.args.get("error"): + return f"Auth error: {error}", 400 + + result = sc.authenticate_with_code(request.args["code"], REDIRECT_URI) + session["access_token"] = result.access_token + session["refresh_token"] = result.refresh_token + session["id_token"] = result.id_token + session.modified = True + + return redirect(session.pop("next", "/dashboard")) + +@app.get("/auth/logout") +def logout(): + id_token = session.get("id_token", "") + logout_url = sc.get_logout_url({"post_logout_redirect_uri": "http://localhost:5000"}) + session.clear() + return redirect(logout_url) +``` + +## Auth decorator + +```python +from functools import wraps +from flask import g + +def require_auth(f): + @wraps(f) + def decorated(*args, **kwargs): + access_token = session.get("access_token") + refresh_token = session.get("refresh_token") + + if not access_token: + session["next"] = request.path + return redirect(url_for("login")) + + try: + sc.validate_access_token(access_token) + except Exception: + if not refresh_token: + session.clear() + return redirect(url_for("login")) + try: + refreshed = sc.refresh_access_token(refresh_token) + session["access_token"] = refreshed.access_token + session["refresh_token"] = refreshed.refresh_token + except Exception: + session.clear() + return redirect(url_for("login")) + + return f(*args, **kwargs) + return decorated +``` + +## Protected routes + +```python +@app.get("/dashboard") +@require_auth +def dashboard(): + return jsonify({"status": "authenticated"}) +``` + +## Implementation checklist + +``` +- [ ] Step 1: pip install scalekit-sdk-python python-dotenv flask flask-session +- [ ] Step 2: Set SCALEKIT_ENV_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET, SECRET_KEY in .env +- [ ] Step 3: Initialize ScalekitClient at module level (single instance per process) +- [ ] Step 4: Configure Flask-Session with filesystem or Redis backend +- [ ] Step 5: Implement /auth/login, /auth/callback, /auth/logout routes +- [ ] Step 6: Create require_auth decorator; apply to protected routes +- [ ] Step 7: Set SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SECURE=True, SESSION_COOKIE_SAMESITE=Lax +- [ ] Step 8: Register callback URI in Scalekit dashboard +- [ ] Step 9: Test: login → /dashboard → token refresh → logout +``` + +## Troubleshooting + +**`authenticate_with_code` raises exception**: The `redirect_uri` must exactly match the URI in the Scalekit dashboard — including scheme, host, path, and no trailing slash. + +**Session data lost between requests**: Ensure `SECRET_KEY` is set and consistent. With `SESSION_TYPE="filesystem"`, verify the session directory is writable. + +**`SameSite=Lax` breaking SSO callback**: Some IdPs POST the callback. If you use POST-based SSO, set `SESSION_COOKIE_SAMESITE = "None"` with `SESSION_COOKIE_SECURE = True`. + +**Token refresh race condition**: Multiple concurrent requests with the same refresh token can exhaust it. Use a per-user lock or treat `invalid_grant` as session expiry. + +## Tactics + +### Blueprint structure for larger apps + +```python +# auth/routes.py +from flask import Blueprint +auth_bp = Blueprint("auth", __name__, url_prefix="/auth") + +@auth_bp.get("/login") +def login(): ... + +# app.py +from auth.routes import auth_bp +app.register_blueprint(auth_bp) +``` + +### IDP-initiated SSO + +```python +@app.get("/auth/idp-login") +def idp_login(): + claims = sc.get_idp_initiated_login_claims(request.args.get("idp_initiated_login", "")) + options = {} + if claims.organization_id: options["organization_id"] = claims.organization_id + if claims.connection_id: options["connection_id"] = claims.connection_id + if claims.login_hint: options["login_hint"] = claims.login_hint + return redirect(sc.get_authorization_url(REDIRECT_URI, options=options)) +``` + +### Cache-Control: no-store on protected responses + +```python +from flask import make_response + +@app.get("/dashboard") +@require_auth +def dashboard(): + resp = make_response(jsonify({"status": "authenticated"})) + resp.headers["Cache-Control"] = "no-store" + return resp +``` + +### Redis session backend (production) + +```python +app.config["SESSION_TYPE"] = "redis" +app.config["SESSION_REDIS"] = redis.from_url(os.getenv("REDIS_URL")) +app.config["SESSION_USE_SIGNER"] = True +app.config["SESSION_KEY_PREFIX"] = "sk_session:" +app.config["PERMANENT_SESSION_LIFETIME"] = 86400 +``` + +## Reference + +- Scalekit Python SDK: [docs.scalekit.com/apis](https://docs.scalekit.com/apis) +- Flask-Session: [flask-session.readthedocs.io](https://flask-session.readthedocs.io/) From 4b1e839a5ef6df54f969fa8b3257de8a084b3d72 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Thu, 21 May 2026 22:29:20 +0530 Subject: [PATCH 44/53] feat: add setup/onboarding skill to agentkit and saaskit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses CTO review issue #6 — new users facing 13 overlapping choices with no entry point. Both /agentkit:setup and /saaskit:setup ask 2-3 questions about framework and use case, then route to the right skill. Covers orientation (concepts table), environment setup steps, and cross-skill navigation. Co-Authored-By: Claude Sonnet 4.6 --- plugins/agentkit/README.md | 2 + plugins/agentkit/skills/setup/SKILL.md | 73 +++++++++++++++++++++++++ plugins/saaskit/README.md | 2 + plugins/saaskit/skills/setup/SKILL.md | 74 ++++++++++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 plugins/agentkit/skills/setup/SKILL.md create mode 100644 plugins/saaskit/skills/setup/SKILL.md diff --git a/plugins/agentkit/README.md b/plugins/agentkit/README.md index 6e51ce1..f458afd 100644 --- a/plugins/agentkit/README.md +++ b/plugins/agentkit/README.md @@ -40,6 +40,8 @@ Official Scalekit docs: - [AgentKit examples](https://docs.scalekit.com/agentkit/examples.md) ## Skills Reference +- `/agentkit:setup` + New to AgentKit? Start here — answers 2 questions and routes you to the right skill. - `/agentkit:integrating-agentkit` Integrates AgentKit into app code or an agent workflow and routes into the core docs. - `/agentkit:discovering-connector-tools` diff --git a/plugins/agentkit/skills/setup/SKILL.md b/plugins/agentkit/skills/setup/SKILL.md new file mode 100644 index 0000000..3e8ec1f --- /dev/null +++ b/plugins/agentkit/skills/setup/SKILL.md @@ -0,0 +1,73 @@ +--- +name: setup +description: Starting point for any Scalekit AgentKit integration. Use when the user says "I want to add agent auth", "set up AgentKit", "where do I start", or is new to AgentKit and doesn't know which skill to use. Routes to the right skill based on what they're building. +--- + +# AgentKit — Where to Start + +Answer 2 questions, then follow the link for your exact skill. + +--- + +## Step 1: Ask the user these questions + +If answers aren't already clear from context, ask: + +1. **What are you building?** + - New agent that needs to call third-party tools on behalf of users (Gmail, Slack, Salesforce, etc.) + - Existing agent — adding connector access or fixing auth + - MCP server that exposes AgentKit tools + +2. **What's your current state?** + - Starting from scratch + - Have a Scalekit account and environment already + - Have AgentKit set up, stuck on a specific step + +--- + +## Step 2: Route to the right skill + +| What you're building | Skill | +|---|---| +| Connect users to third-party apps, execute tools on their behalf | `/agentkit:integrating-agentkit` | +| Discover available tools for a connector, inspect schemas | `/agentkit:discovering-connector-tools` | +| Expose AgentKit tools over MCP for Claude Desktop, Cursor, VS Code | `/agentkit:exposing-agentkit-via-mcp` | +| Pre-launch checklist for production | `/agentkit:production-readiness-agentkit` | +| SDK errors, wrong imports, broken auth calls | `/saaskit:scalekit-code-doctor` | + +--- + +## Step 3: Environment setup (if new project) + +Before starting any skill, verify credentials exist: + +```bash +SCALEKIT_ENV_URL=https://your-env.scalekit.dev +SCALEKIT_CLIENT_ID= +SCALEKIT_CLIENT_SECRET= +``` + +Get these from [app.scalekit.com](https://app.scalekit.com) → Developers → Settings → API Credentials. + +The Scalekit MCP server (`https://mcp.scalekit.com`) is pre-configured in `.mcp.json`. Claude Code handles OAuth 2.1 auth automatically — no additional setup needed. + +--- + +## Core AgentKit concepts (30-second orientation) + +| Concept | What it is | +|---|---| +| **Connector** | A third-party app (Gmail, Slack, Salesforce, GitHub, etc.) | +| **Connection** | Your app's agreement with a connector (configured in dashboard) | +| **Connected account** | A specific user's authorization to use a connection | +| **Tool** | An action the agent can take (send email, create issue, etc.) | + +Flow: User authorizes → connected account created → agent discovers tools → agent executes tool calls using that account. + +--- + +## When to switch skills + +- **Already know what you need?** Skip this skill and invoke the target directly. +- **SDK errors?** Use `/saaskit:scalekit-code-doctor`. +- **Want to add B2B auth (login, SSO, SCIM) to your app?** Switch to the `saaskit` plugin: `/saaskit:setup`. diff --git a/plugins/saaskit/README.md b/plugins/saaskit/README.md index 4e80be5..fc7fdc3 100644 --- a/plugins/saaskit/README.md +++ b/plugins/saaskit/README.md @@ -36,6 +36,8 @@ Official Scalekit docs: - [MCP Auth quickstart](https://docs.scalekit.com/authenticate/mcp/quickstart/) ## Skills Reference +- `/saaskit:setup` + New to SaaSKit? Start here — answers 3 questions and routes you to the right skill. - `/saaskit:implementing-saaskit` — Core auth flow: login, signup, callback, token exchange, logout. Framework reference files for Go, Spring Boot, Laravel. - `/saaskit:managing-saaskit-sessions` — Secure session storage, token refresh middleware, session revocation. - `/saaskit:implementing-access-control` — RBAC and permission checks using Scalekit access tokens. diff --git a/plugins/saaskit/skills/setup/SKILL.md b/plugins/saaskit/skills/setup/SKILL.md new file mode 100644 index 0000000..a8af32f --- /dev/null +++ b/plugins/saaskit/skills/setup/SKILL.md @@ -0,0 +1,74 @@ +--- +name: setup +description: Starting point for any Scalekit SaaSKit integration. Use when the user says "I want to add auth", "set up Scalekit", "where do I start", or is new to SaaSKit and doesn't know which skill to use. Routes to the right skill based on framework and what they're building. +--- + +# SaaSKit — Where to Start + +Answer 3 questions, then follow the link for your exact skill. + +--- + +## Step 1: Ask the user these questions + +If answers aren't already clear from context, ask: + +1. **New or existing codebase?** + - New project + - Adding auth to an existing app + +2. **Framework?** + - Next.js (App Router or Pages Router) + - Python (Django / FastAPI / Flask) + - Go + - Other / not sure + +3. **What are you adding?** + - Login, sessions, and user management (most common starting point) + - Enterprise SSO (Okta, Azure AD, Google Workspace, etc.) + - SCIM / user provisioning (sync users from a directory) + - Secure an MCP server with OAuth 2.1 + - API keys for developers + - Not sure / full auth stack + +--- + +## Step 2: Route to the right skill + +| Framework | What you're adding | Skill | +|---|---|---| +| Next.js | Login + sessions | `/saaskit:implementing-saaskit-nextjs` | +| Python | Login + sessions | `/saaskit:implementing-saaskit-python` | +| Go / other | Login + sessions | `/saaskit:implementing-saaskit` | +| Any | Enterprise SSO | `/saaskit:implementing-modular-sso` | +| Any | SCIM provisioning | `/saaskit:implementing-scim-provisioning` | +| Any | MCP server auth | `/saaskit:adding-mcp-oauth` | +| Any | API keys | `/saaskit:adding-api-auth` | +| Any | RBAC / permissions | `/saaskit:implementing-access-control` | +| Any | Migrating from Auth0 / Firebase / custom auth | `/saaskit:migrating-to-saaskit` | + +If the user wants **login + SSO + SCIM** (full B2B auth stack), start with `/saaskit:implementing-saaskit` or the framework-specific variant, then chain to `/saaskit:implementing-modular-sso` once login is working. + +--- + +## Step 3: Environment setup (if new project) + +Before starting any skill, verify credentials exist: + +```bash +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.dev +SCALEKIT_CLIENT_ID= +SCALEKIT_CLIENT_SECRET= +``` + +Get these from [app.scalekit.com](https://app.scalekit.com) → Developers → Settings → API Credentials. + +Use `/saaskit:testing-auth-setup` to validate credentials and connection end-to-end before writing any auth code. + +--- + +## When to switch skills + +- **Already know what you need?** Skip this skill and invoke the target directly. +- **SDK errors or wrong imports?** Use `/saaskit:scalekit-code-doctor`. +- **Production checklist?** Use `/saaskit:production-readiness-saaskit`. From 69c40668ecbce11e6bc71239e8f823353fff1975 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Thu, 21 May 2026 22:43:45 +0530 Subject: [PATCH 45/53] fix: wrong Python SDK methods, stale .io domain across skills and docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - implementing-saaskit-nextjs: scalekit.io → scalekit.com in env URL - docs/frameworks/laravel.md: scalekit.io → scalekit.com - docs/frameworks/nextjs.md: scalekit.io → scalekit.com - implementing-saaskit/laravel-reference.md: scalekit.io → scalekit.com - implementing-modular-sso: validate_token → validate_access_token_and_get_claims (Python) - implementing-access-control SKILL.md: decode_access_token → validate_access_token_and_get_claims - docs/access-control.md: decode_access_token → validate_access_token_and_get_claims - adding-mcp-oauth SKILL.md: validate_token → validate_access_token_and_get_claims (Python middleware) Co-Authored-By: Claude Sonnet 4.6 --- plugins/saaskit/docs/access-control.md | 5 +++-- plugins/saaskit/docs/frameworks/laravel.md | 2 +- plugins/saaskit/docs/frameworks/nextjs.md | 2 +- plugins/saaskit/skills/adding-mcp-oauth/SKILL.md | 2 +- plugins/saaskit/skills/implementing-access-control/SKILL.md | 6 +++--- plugins/saaskit/skills/implementing-modular-sso/SKILL.md | 4 ++-- plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md | 2 +- .../skills/implementing-saaskit/laravel-reference.md | 2 +- 8 files changed, 13 insertions(+), 12 deletions(-) diff --git a/plugins/saaskit/docs/access-control.md b/plugins/saaskit/docs/access-control.md index d611d35..c0f6715 100644 --- a/plugins/saaskit/docs/access-control.md +++ b/plugins/saaskit/docs/access-control.md @@ -61,9 +61,10 @@ def validate_and_extract_auth(f): @wraps(f) def decorated(*args, **kwargs): access_token = decrypt(request.cookies.get('accessToken')) - if not scalekit_client.validate_access_token(access_token): + try: + claims = scalekit_client.validate_access_token_and_get_claims(access_token) + except Exception: return jsonify({'error': 'Invalid or expired token'}), 401 - claims = scalekit_client.decode_access_token(access_token) request.user = { 'id': claims.get('sub'), 'organization_id': claims.get('oid'), diff --git a/plugins/saaskit/docs/frameworks/laravel.md b/plugins/saaskit/docs/frameworks/laravel.md index 2646a17..10ad275 100644 --- a/plugins/saaskit/docs/frameworks/laravel.md +++ b/plugins/saaskit/docs/frameworks/laravel.md @@ -20,7 +20,7 @@ routes/web.php # Named routes + middleware groups ## Environment ```env -SCALEKIT_ENV_URL=https://your-env.scalekit.io +SCALEKIT_ENV_URL=https://your-env.scalekit.com SCALEKIT_CLIENT_ID=your-client-id SCALEKIT_CLIENT_SECRET=your-client-secret SCALEKIT_REDIRECT_URI=http://localhost:8000/auth/callback diff --git a/plugins/saaskit/docs/frameworks/nextjs.md b/plugins/saaskit/docs/frameworks/nextjs.md index ce3fe59..52ca66d 100644 --- a/plugins/saaskit/docs/frameworks/nextjs.md +++ b/plugins/saaskit/docs/frameworks/nextjs.md @@ -23,7 +23,7 @@ lib/ ## Environment ```env -SCALEKIT_ENV_URL=https://your-env.scalekit.io +SCALEKIT_ENV_URL=https://your-env.scalekit.com SCALEKIT_CLIENT_ID=your-client-id SCALEKIT_CLIENT_SECRET=your-client-secret SCALEKIT_REDIRECT_URI=http://localhost:3000/auth/callback diff --git a/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md b/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md index af7f029..8236bcf 100644 --- a/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md +++ b/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md @@ -204,7 +204,7 @@ async def auth_middleware(request: Request, call_next): issuer=os.getenv("SCALEKIT_ENVIRONMENT_URL"), audience=[RESOURCE_ID] ) - scalekit_client.validate_token(token, options=options) + scalekit_client.validate_access_token_and_get_claims(token, options=options) except Exception: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/plugins/saaskit/skills/implementing-access-control/SKILL.md b/plugins/saaskit/skills/implementing-access-control/SKILL.md index 3b0c835..14fdc2b 100644 --- a/plugins/saaskit/skills/implementing-access-control/SKILL.md +++ b/plugins/saaskit/skills/implementing-access-control/SKILL.md @@ -68,10 +68,10 @@ def validate_and_extract_auth(f): @wraps(f) def decorated(*args, **kwargs): access_token = decrypt(request.cookies.get("accessToken")) - if not scalekit_client.validate_access_token(access_token): + try: + token_data = scalekit_client.validate_access_token_and_get_claims(access_token) + except Exception: return jsonify({"error": "Invalid or expired token"}), 401 - - token_data = scalekit_client.decode_access_token(access_token) request.user = { "id": token_data.get("sub"), "organization_id": token_data.get("oid"), diff --git a/plugins/saaskit/skills/implementing-modular-sso/SKILL.md b/plugins/saaskit/skills/implementing-modular-sso/SKILL.md index 0624059..6f1b49a 100644 --- a/plugins/saaskit/skills/implementing-modular-sso/SKILL.md +++ b/plugins/saaskit/skills/implementing-modular-sso/SKILL.md @@ -306,8 +306,8 @@ const accessTokenClaims = await scalekit.validateToken(result.accessToken); **Python:** ```python -id_token_claims = scalekit.validate_token(result['id_token']) -access_token_claims = scalekit.validate_token(result['access_token']) +id_token_claims = scalekit.validate_access_token_and_get_claims(result['id_token']) +access_token_claims = scalekit.validate_access_token_and_get_claims(result['access_token']) ``` ### Token Structure diff --git a/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md b/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md index de49b3e..e231806 100644 --- a/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md +++ b/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md @@ -26,7 +26,7 @@ lib/ ## Environment variables ```env -SCALEKIT_ENV_URL=https://your-env.scalekit.io +SCALEKIT_ENV_URL=https://your-env.scalekit.com SCALEKIT_CLIENT_ID=your-client-id SCALEKIT_CLIENT_SECRET=your-client-secret SCALEKIT_REDIRECT_URI=http://localhost:3000/auth/callback diff --git a/plugins/saaskit/skills/implementing-saaskit/laravel-reference.md b/plugins/saaskit/skills/implementing-saaskit/laravel-reference.md index 9891a98..89ff114 100644 --- a/plugins/saaskit/skills/implementing-saaskit/laravel-reference.md +++ b/plugins/saaskit/skills/implementing-saaskit/laravel-reference.md @@ -26,7 +26,7 @@ routes/ ## Environment variables ```env -SCALEKIT_ENV_URL=https://your-env.scalekit.io +SCALEKIT_ENV_URL=https://your-env.scalekit.com SCALEKIT_CLIENT_ID=your-client-id SCALEKIT_CLIENT_SECRET=your-client-secret SCALEKIT_REDIRECT_URI=http://localhost:8000/auth/callback From 819143b945debfea673e0728d78c0224889156ea Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Fri, 22 May 2026 11:28:41 +0530 Subject: [PATCH 46/53] fix: SDK methods, webhook verification, browser bug, env var canonicalization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Node: Scalekit → ScalekitClient in express-reference and bring-your-own-auth - SCIM: getPrimaryDirectoryByOrganizationId → listDirectories pattern - SCIM webhook: fix try/catch on bool return, use raw body for signature verification - MCP server: use sub-client methods (organization.createOrganization etc.) - auth-flows: getLogoutUrl positional args → options object - sso.md: replace process.env in browser with literal constant - Python: dict options → typed AuthorizationUrlOptions/LogoutUrlOptions classes - Env var: SCALEKIT_ENV_URL → SCALEKIT_ENVIRONMENT_URL - Domain: .scalekit.io → .scalekit.com in .env.example --- plugins/agentkit/README.md | 4 +- plugins/saaskit/.env.example | 2 +- plugins/saaskit/docs/auth-flows.md | 2 +- plugins/saaskit/docs/scim.md | 37 ++++++++------- plugins/saaskit/docs/sso.md | 6 ++- .../saaskit/references/bring-your-own-auth.md | 4 +- .../saaskit/references/scalekit-mcp-server.md | 12 +++-- .../adding-mcp-oauth/express-reference.md | 4 +- .../implementing-saaskit-nextjs/SKILL.md | 2 +- .../django-reference.md | 5 +- .../flask-reference.md | 5 +- .../springboot-reference.md | 2 +- .../implementing-scim-provisioning/SKILL.md | 47 ++++++++++--------- .../skills/migrating-to-saaskit/SKILL.md | 4 +- .../production-readiness-saaskit/SKILL.md | 6 +-- .../skills/testing-auth-setup/SKILL.md | 6 +-- 16 files changed, 82 insertions(+), 66 deletions(-) diff --git a/plugins/agentkit/README.md b/plugins/agentkit/README.md index f458afd..8e725a1 100644 --- a/plugins/agentkit/README.md +++ b/plugins/agentkit/README.md @@ -55,7 +55,7 @@ Official Scalekit docs: ## Configuration Required environment variables (for SDK-based integrations): -- `SCALEKIT_ENV_URL` +- `SCALEKIT_ENVIRONMENT_URL` - `SCALEKIT_CLIENT_ID` - `SCALEKIT_CLIENT_SECRET` @@ -73,7 +73,7 @@ Example `.mcp.json`: ``` **MCP authentication**: Claude Code handles auth for `https://mcp.scalekit.com` automatically via OAuth 2.1 dynamic client registration. No auth fields are needed in `.mcp.json`. If MCP tool calls fail: -1. Verify `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, and `SCALEKIT_CLIENT_SECRET` are set in your environment. +1. Verify `SCALEKIT_ENVIRONMENT_URL`, `SCALEKIT_CLIENT_ID`, and `SCALEKIT_CLIENT_SECRET` are set in your environment. 2. Re-open Claude Code — it re-runs the OAuth handshake on startup. 3. Check that your Scalekit environment has MCP server access enabled in the dashboard. diff --git a/plugins/saaskit/.env.example b/plugins/saaskit/.env.example index 5647833..33188af 100644 --- a/plugins/saaskit/.env.example +++ b/plugins/saaskit/.env.example @@ -1,3 +1,3 @@ -SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.io +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com SCALEKIT_CLIENT_ID=your-client-id SCALEKIT_CLIENT_SECRET=your-client-secret \ No newline at end of file diff --git a/plugins/saaskit/docs/auth-flows.md b/plugins/saaskit/docs/auth-flows.md index 734185e..b847fbe 100644 --- a/plugins/saaskit/docs/auth-flows.md +++ b/plugins/saaskit/docs/auth-flows.md @@ -88,7 +88,7 @@ Logout requires two steps: clear your application session, then redirect the bro ```js // Node.js const idTokenHint = req.cookies?.idToken; // read BEFORE clearing -const logoutUrl = scalekit.getLogoutUrl(idTokenHint, postLogoutRedirectUri); +const logoutUrl = scalekit.getLogoutUrl({ idTokenHint, postLogoutRedirectUri }); res.clearCookie('accessToken', { path: '/' }); res.clearCookie('refreshToken', { path: '/' }); res.redirect(logoutUrl); diff --git a/plugins/saaskit/docs/scim.md b/plugins/saaskit/docs/scim.md index 145b508..b8d4b75 100644 --- a/plugins/saaskit/docs/scim.md +++ b/plugins/saaskit/docs/scim.md @@ -17,7 +17,9 @@ Fetch users and groups for an organization: ```js // Node.js -const { directory } = await scalekit.directory.getPrimaryDirectoryByOrganizationId(orgId); +// Note: returns the first directory; multi-directory orgs need an explicit directory ID. +const { directories } = await scalekit.directory.listDirectories(orgId); +const directory = directories[0]; const { users } = await scalekit.directory.listDirectoryUsers(orgId, directory.id); for (const user of users) { await upsertUser({ email: user.email, name: user.name, orgId }); @@ -26,7 +28,8 @@ for (const user of users) { ```python # Python -directory = scalekit_client.directory.get_primary_directory_by_organization_id(org_id) +# Note: returns the first directory; multi-directory orgs need an explicit directory ID. +directory = scalekit_client.directory.list_directories(organization_id=org_id).directories[0] users = scalekit_client.directory.list_directory_users(org_id, directory.id) for user in users: upsert_user(email=user.email, name=user.name, org_id=org_id) @@ -48,32 +51,34 @@ for (const group of groups) { Add a POST route, verify the signature, and dispatch events: ```js -// Node.js (Express) -app.post('/webhooks/scalekit', async (req, res) => { - try { - await scalekit.verifyWebhookPayload( - process.env.SCALEKIT_WEBHOOK_SECRET, req.headers, req.body - ); - } catch { return res.status(400).json({ error: 'Invalid signature' }); } - - const { type, data } = req.body; +// Node.js (Express) — mount with `express.raw({ type: 'application/json' })` +// so req.body is a Buffer for accurate signature verification. +app.post('/webhooks/scalekit', express.raw({ type: 'application/json' }), async (req, res) => { + const ok = await scalekit.verifyWebhookPayload( + process.env.SCALEKIT_WEBHOOK_SECRET, req.headers, req.body + ); + if (!ok) return res.status(401).end(); + + const { type, data } = JSON.parse(req.body.toString('utf8')); await handleDirectoryEvent(type, data); res.status(201).json({ status: 'processed' }); }); ``` ```python -# Python (FastAPI) +# Python (FastAPI) — read the RAW body BEFORE parsing JSON so the bytes +# match exactly what was signed. @app.post("/webhooks/scalekit") async def scalekit_webhook(request: Request): - body = await request.json() + raw_body = await request.body() valid = scalekit_client.verify_webhook_payload( secret=os.getenv("SCALEKIT_WEBHOOK_SECRET"), - headers=request.headers, - payload=json.dumps(body).encode() + headers=dict(request.headers), + payload=raw_body, ) if not valid: - raise HTTPException(status_code=400, detail="Invalid signature") + raise HTTPException(status_code=401, detail="Invalid signature") + body = json.loads(raw_body) await handle_directory_event(body.get("type"), body.get("data", {})) return JSONResponse(status_code=201, content={"status": "processed"}) ``` diff --git a/plugins/saaskit/docs/sso.md b/plugins/saaskit/docs/sso.md index 626aadb..ccc9e34 100644 --- a/plugins/saaskit/docs/sso.md +++ b/plugins/saaskit/docs/sso.md @@ -92,8 +92,12 @@ location = portal.location ### Handle portal events ```js +// Must be a literal — `process.env` is undefined in the browser. +// Set this to the same Scalekit environment URL configured at build time. +const SCALEKIT_ORIGIN = "https://your-env.scalekit.com"; + window.addEventListener('message', (event) => { - if (event.origin !== process.env.SCALEKIT_ENVIRONMENT_URL) return; + if (event.origin !== SCALEKIT_ORIGIN) return; if (event.data.type === 'SESSION_EXPIRED') { // Re-fetch portal link and reload iframe } diff --git a/plugins/saaskit/references/bring-your-own-auth.md b/plugins/saaskit/references/bring-your-own-auth.md index 371935c..b421781 100644 --- a/plugins/saaskit/references/bring-your-own-auth.md +++ b/plugins/saaskit/references/bring-your-own-auth.md @@ -87,9 +87,9 @@ npm install @scalekit-sdk/node ``` ```javascript -import { Scalekit } from '@scalekit-sdk/node'; +import { ScalekitClient } from '@scalekit-sdk/node'; -const scalekit = new Scalekit( +const scalekit = new ScalekitClient( process.env.SCALEKIT_ENVIRONMENT_URL, process.env.SCALEKIT_CLIENT_ID, process.env.SCALEKIT_CLIENT_SECRET diff --git a/plugins/saaskit/references/scalekit-mcp-server.md b/plugins/saaskit/references/scalekit-mcp-server.md index e950b57..628fd71 100644 --- a/plugins/saaskit/references/scalekit-mcp-server.md +++ b/plugins/saaskit/references/scalekit-mcp-server.md @@ -1,5 +1,7 @@ # Scalekit MCP Server - Production Reference Implementation +> **Note:** This document describes Scalekit's MCP server architecture conceptually. The methods shown are illustrative — refer to the SDK reference for the actual API surface. + ## Overview This document provides an architectural overview and key patterns from Scalekit's official MCP server implementation. This production-ready server demonstrates advanced OAuth 2.1 patterns, comprehensive tooling, and best practices for building secure, scalable MCP servers. @@ -73,7 +75,7 @@ server.tool( }, async ({ environmentId, name }, { token }) => { // Token already validated by middleware - const organization = await scalekit.createOrganization({ + const organization = await scalekit.organization.createOrganization({ environmentId, name, }); @@ -174,7 +176,7 @@ server.tool( pageToken: z.string().optional().default('1'), }, async ({ environmentId, pageToken }) => { - const result = await scalekit.listOrganizations({ + const result = await scalekit.organization.listOrganization({ environmentId, page: parseInt(pageToken, 10), }); @@ -210,7 +212,7 @@ server.tool( }, async ({ environmentId, organizationId }) => { // Safe to proceed with validated IDs - const organization = await scalekit.getOrganization({ + const organization = await scalekit.organization.getOrganization({ environmentId, organizationId }); @@ -257,7 +259,7 @@ server.tool( ```bash # Scalekit Configuration -SCALEKIT_ENV_URL=https://your-env.scalekit.com +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com SCALEKIT_CLIENT_ID=your_client_id SCALEKIT_CLIENT_SECRET=your_client_secret @@ -276,7 +278,7 @@ NODE_ENV=production ```typescript const config = { port: parseInt(process.env.PORT || '3002', 10), - environmentUrl: process.env.SCALEKIT_ENV_URL, + environmentUrl: process.env.SCALEKIT_ENVIRONMENT_URL, clientId: process.env.SCALEKIT_CLIENT_ID, clientSecret: process.env.SCALEKIT_CLIENT_SECRET, expectedAudience: process.env.EXPECTED_AUDIENCE, diff --git a/plugins/saaskit/skills/adding-mcp-oauth/express-reference.md b/plugins/saaskit/skills/adding-mcp-oauth/express-reference.md index 18b879d..dccb7ea 100644 --- a/plugins/saaskit/skills/adding-mcp-oauth/express-reference.md +++ b/plugins/saaskit/skills/adding-mcp-oauth/express-reference.md @@ -42,9 +42,9 @@ Required variables: `SK_ENV_URL`, `SK_CLIENT_ID`, `SK_CLIENT_SECRET`, `EXPECTED_ ### Scalekit Client Initialization ```typescript -import { Scalekit } from '@scalekit-sdk/node'; +import { ScalekitClient } from '@scalekit-sdk/node'; -const scalekit = new Scalekit(SK_ENV_URL, SK_CLIENT_ID, SK_CLIENT_SECRET); +const scalekit = new ScalekitClient(SK_ENV_URL, SK_CLIENT_ID, SK_CLIENT_SECRET); ``` Initialize once at module level for connection pooling. diff --git a/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md b/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md index e231806..5a3b241 100644 --- a/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md +++ b/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md @@ -26,7 +26,7 @@ lib/ ## Environment variables ```env -SCALEKIT_ENV_URL=https://your-env.scalekit.com +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com SCALEKIT_CLIENT_ID=your-client-id SCALEKIT_CLIENT_SECRET=your-client-secret SCALEKIT_REDIRECT_URI=http://localhost:3000/auth/callback diff --git a/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md index 2096a0c..a7ce9dd 100644 --- a/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md +++ b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md @@ -61,6 +61,7 @@ GET /auth/logout import os, secrets from django.shortcuts import redirect from django.http import HttpRequest, HttpResponse +from scalekit.common.scalekit import AuthorizationUrlOptions, LogoutUrlOptions from .auth_client import get_scalekit_client REDIRECT_URI = os.getenv("SCALEKIT_REDIRECT_URI", "http://localhost:8000/auth/callback") @@ -69,7 +70,7 @@ def login(request: HttpRequest): state = secrets.token_urlsafe(32) request.session["oauth_state"] = state sc = get_scalekit_client() - auth_url = sc.get_authorization_url(REDIRECT_URI, options={"state": state}) + auth_url = sc.get_authorization_url(REDIRECT_URI, options=AuthorizationUrlOptions(state=state)) return redirect(auth_url) def callback(request: HttpRequest): @@ -92,7 +93,7 @@ def callback(request: HttpRequest): def logout(request: HttpRequest): id_token = request.session.get("id_token", "") sc = get_scalekit_client() - logout_url = sc.get_logout_url({"post_logout_redirect_uri": "http://localhost:8000"}) + logout_url = sc.get_logout_url(LogoutUrlOptions(post_logout_redirect_uri="http://localhost:8000")) request.session.flush() return redirect(logout_url) ``` diff --git a/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md b/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md index bf79540..84594cf 100644 --- a/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md +++ b/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md @@ -25,6 +25,7 @@ from dotenv import load_dotenv from flask import Flask, redirect, request, session, url_for, jsonify from flask_session import Session from scalekit import ScalekitClient +from scalekit.common.scalekit import AuthorizationUrlOptions, LogoutUrlOptions load_dotenv() @@ -68,7 +69,7 @@ GET /auth/logout def login(): state = secrets.token_urlsafe(32) session["oauth_state"] = state - auth_url = sc.get_authorization_url(REDIRECT_URI, options={"state": state}) + auth_url = sc.get_authorization_url(REDIRECT_URI, options=AuthorizationUrlOptions(state=state)) return redirect(auth_url) @app.get("/auth/callback") @@ -90,7 +91,7 @@ def callback(): @app.get("/auth/logout") def logout(): id_token = session.get("id_token", "") - logout_url = sc.get_logout_url({"post_logout_redirect_uri": "http://localhost:5000"}) + logout_url = sc.get_logout_url(LogoutUrlOptions(post_logout_redirect_uri="http://localhost:5000")) session.clear() return redirect(logout_url) ``` diff --git a/plugins/saaskit/skills/implementing-saaskit/springboot-reference.md b/plugins/saaskit/skills/implementing-saaskit/springboot-reference.md index 3a9dc8a..15e8fa9 100644 --- a/plugins/saaskit/skills/implementing-saaskit/springboot-reference.md +++ b/plugins/saaskit/skills/implementing-saaskit/springboot-reference.md @@ -29,7 +29,7 @@ Add to `pom.xml` (Spring Boot 3.2+, Java 17+): ```yaml scalekit: - env-url: ${SCALEKIT_ENV_URL} + env-url: ${SCALEKIT_ENVIRONMENT_URL} client-id: ${SCALEKIT_CLIENT_ID} client-secret: ${SCALEKIT_CLIENT_SECRET} redirect-uri: ${SCALEKIT_REDIRECT_URI:http://localhost:8080/login/oauth2/code/scalekit} diff --git a/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md b/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md index 53f6550..8b6b158 100644 --- a/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md +++ b/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md @@ -33,8 +33,8 @@ Detect the project's language/framework from existing files (`package.json`, `re |-------|----------------| | Node.js | `npm install @scalekit-sdk/node` | | Python | `pip install scalekit-sdk-python` | -| Go | `go get github.com/scalekit/scalekit-go` | -| Java | Add `com.scalekit:scalekit-sdk` to `pom.xml` or `build.gradle` | +| Go | `go get github.com/scalekit-inc/scalekit-sdk-go/v2` | +| Java | Add `com.scalekit:scalekit-sdk-java` to `pom.xml` or `build.gradle` | --- @@ -90,7 +90,9 @@ Use for scheduled jobs, onboarding flows, or bulk imports. Integrate into existi ```javascript // Node.js -const { directory } = await scalekit.directory.getPrimaryDirectoryByOrganizationId(orgId); +// Note: returns the first directory; multi-directory orgs need an explicit directory ID. +const { directories } = await scalekit.directory.listDirectories(orgId); +const directory = directories[0]; const { users } = await scalekit.directory.listDirectoryUsers(orgId, directory.id); for (const user of users) { @@ -100,7 +102,8 @@ for (const user of users) { ```python # Python -directory = scalekit_client.directory.get_primary_directory_by_organization_id(org_id) +# Note: returns the first directory; multi-directory orgs need an explicit directory ID. +directory = scalekit_client.directory.list_directories(organization_id=org_id).directories[0] users = scalekit_client.directory.list_directory_users(org_id, directory.id) for user in users: @@ -126,20 +129,18 @@ Add a new route to the existing HTTP server/router. Match the framework pattern **ALWAYS verify the signature before processing. Return 400 on failure.** -**Node.js (Express):** -```javascript -app.post('/webhooks/scalekit', async (req, res) => { - try { - await scalekit.verifyWebhookPayload( - process.env.SCALEKIT_WEBHOOK_SECRET, - req.headers, - req.body - ); - } catch { - return res.status(400).json({ error: 'Invalid signature' }); - } +**Node.js (Express):** mount the route with `express.raw({ type: 'application/json' })` so `req.body` is the raw `Buffer` — signature verification must run on the exact bytes that were signed. - const { type, data } = req.body; +```javascript +app.post('/webhooks/scalekit', express.raw({ type: 'application/json' }), async (req, res) => { + const ok = await scalekit.verifyWebhookPayload( + process.env.SCALEKIT_WEBHOOK_SECRET, + req.headers, + req.body + ); + if (!ok) return res.status(401).end(); + + const { type, data } = JSON.parse(req.body.toString('utf8')); try { await handleDirectoryEvent(type, data); res.status(201).json({ status: 'processed' }); @@ -149,19 +150,21 @@ app.post('/webhooks/scalekit', async (req, res) => { }); ``` -**Python (FastAPI):** +**Python (FastAPI):** read the raw body BEFORE parsing JSON so the bytes match exactly what Scalekit signed. + ```python @app.post("/webhooks/scalekit") async def scalekit_webhook(request: Request): - body = await request.json() + raw_body = await request.body() valid = scalekit_client.verify_webhook_payload( secret=os.getenv("SCALEKIT_WEBHOOK_SECRET"), - headers=request.headers, - payload=json.dumps(body).encode() + headers=dict(request.headers), + payload=raw_body, ) if not valid: - raise HTTPException(status_code=400, detail="Invalid signature") + raise HTTPException(status_code=401, detail="Invalid signature") + body = json.loads(raw_body) await handle_directory_event(body.get("type"), body.get("data", {})) return JSONResponse(status_code=201, content={"status": "processed"}) ``` diff --git a/plugins/saaskit/skills/migrating-to-saaskit/SKILL.md b/plugins/saaskit/skills/migrating-to-saaskit/SKILL.md index b579143..445003b 100644 --- a/plugins/saaskit/skills/migrating-to-saaskit/SKILL.md +++ b/plugins/saaskit/skills/migrating-to-saaskit/SKILL.md @@ -133,12 +133,12 @@ Verify: ```bash # Verify token endpoint works with Scalekit credentials -curl -s -o /dev/null -w "%{http_code}" -X POST "$SCALEKIT_ENV_URL/oauth/token" \ +curl -s -o /dev/null -w "%{http_code}" -X POST "$SCALEKIT_ENVIRONMENT_URL/oauth/token" \ -d "client_id=$SCALEKIT_CLIENT_ID&client_secret=$SCALEKIT_CLIENT_SECRET&grant_type=client_credentials" # Expected: 200 # Verify a migrated user can be looked up -curl -s -H "Authorization: Bearer $TOKEN" "$SCALEKIT_ENV_URL/api/v1/organizations//users?email=migrated-user@example.com" | jq . +curl -s -H "Authorization: Bearer $TOKEN" "$SCALEKIT_ENVIRONMENT_URL/api/v1/organizations//users?email=migrated-user@example.com" | jq . # Should return the user with correct external_id ``` diff --git a/plugins/saaskit/skills/production-readiness-saaskit/SKILL.md b/plugins/saaskit/skills/production-readiness-saaskit/SKILL.md index b3e9d56..11e094e 100644 --- a/plugins/saaskit/skills/production-readiness-saaskit/SKILL.md +++ b/plugins/saaskit/skills/production-readiness-saaskit/SKILL.md @@ -11,7 +11,7 @@ Work through in order — skip sections that don't apply. Earlier sections are b ```bash # Confirm production credentials are set (not dev/staging) -echo $SCALEKIT_ENV_URL # should be https://.scalekit.com (not .scalekit.dev) +echo $SCALEKIT_ENVIRONMENT_URL # should be https://.scalekit.com (not .scalekit.dev) echo $SCALEKIT_CLIENT_ID # should be set echo $SCALEKIT_CLIENT_SECRET # should be set ``` @@ -31,7 +31,7 @@ echo $SCALEKIT_CLIENT_SECRET # should be set ```bash # Test token endpoint reachability -curl -s -o /dev/null -w "%{http_code}" -X POST "$SCALEKIT_ENV_URL/oauth/token" \ +curl -s -o /dev/null -w "%{http_code}" -X POST "$SCALEKIT_ENVIRONMENT_URL/oauth/token" \ -d "client_id=$SCALEKIT_CLIENT_ID&client_secret=$SCALEKIT_CLIENT_SECRET&grant_type=client_credentials" # Expected: 200 ``` @@ -53,7 +53,7 @@ curl -s -o /dev/null -w "%{http_code}" -X POST "$SCALEKIT_ENV_URL/oauth/token" \ # Generate an auth URL with organization_id to trigger SSO node -e " const { ScalekitClient } = require('@scalekit-sdk/node'); -const sc = new ScalekitClient(process.env.SCALEKIT_ENV_URL, process.env.SCALEKIT_CLIENT_ID, process.env.SCALEKIT_CLIENT_SECRET); +const sc = new ScalekitClient(process.env.SCALEKIT_ENVIRONMENT_URL, process.env.SCALEKIT_CLIENT_ID, process.env.SCALEKIT_CLIENT_SECRET); console.log(sc.getAuthorizationUrl(process.env.SCALEKIT_REDIRECT_URI, { organizationId: '' })); " # Open the URL — should redirect to the IdP login page diff --git a/plugins/saaskit/skills/testing-auth-setup/SKILL.md b/plugins/saaskit/skills/testing-auth-setup/SKILL.md index 45f3f96..1517a40 100644 --- a/plugins/saaskit/skills/testing-auth-setup/SKILL.md +++ b/plugins/saaskit/skills/testing-auth-setup/SKILL.md @@ -24,7 +24,7 @@ npm i -g @scalekit-inc/cli Confirm these environment variables are available: -- `SCALEKIT_ENV_URL` — your Scalekit environment URL +- `SCALEKIT_ENVIRONMENT_URL` — your Scalekit environment URL - `SCALEKIT_CLIENT_ID` — your client ID from app.scalekit.com > Settings ## Running the test @@ -32,7 +32,7 @@ Confirm these environment variables are available: ### Full-stack auth (fsa) ```bash -npx @scalekit-sdk/dryrun --env_url=$SCALEKIT_ENV_URL --client_id=$SCALEKIT_CLIENT_ID --mode=fsa +npx @scalekit-sdk/dryrun --env_url=$SCALEKIT_ENVIRONMENT_URL --client_id=$SCALEKIT_CLIENT_ID --mode=fsa ``` ### Enterprise SSO @@ -40,7 +40,7 @@ npx @scalekit-sdk/dryrun --env_url=$SCALEKIT_ENV_URL --client_id=$SCALEKIT_CLIEN Requires an `organization_id` — ask for it if not provided. ```bash -npx @scalekit-sdk/dryrun --env_url=$SCALEKIT_ENV_URL --client_id=$SCALEKIT_CLIENT_ID --mode=sso --organization_id= +npx @scalekit-sdk/dryrun --env_url=$SCALEKIT_ENVIRONMENT_URL --client_id=$SCALEKIT_CLIENT_ID --mode=sso --organization_id= ``` ## Choosing the mode From d28765da9e3ff5eec79d05e36ab96472e1eeb138 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Fri, 22 May 2026 11:36:21 +0530 Subject: [PATCH 47/53] fix: .md URL suffixes, env var canonicalization, module shadowing, truncated agent file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Strip .md suffix from 22 docs.scalekit.com URLs - SCALEKIT_ENV_URL → SCALEKIT_ENVIRONMENT_URL (41 remaining instances) - Fix Python module shadowing: scalekit = scalekit.client.ScalekitClient → sk_client = ScalekitClient - Complete truncated scalekit-mcp-auth-troubleshooter section 8 + add section 9 - Remove 5 [web:NN] citation markers from troubleshooter agent --- plugins/agentkit/README.md | 8 ++--- plugins/agentkit/docs/byoc.md | 2 +- plugins/agentkit/docs/code-samples.md | 8 ++--- plugins/agentkit/docs/connected-accounts.md | 4 +-- plugins/agentkit/docs/connections.md | 4 +-- plugins/agentkit/docs/connectors/README.md | 4 +-- plugins/agentkit/docs/index.md | 8 ++--- plugins/agentkit/docs/tool-discovery.md | 6 ++-- plugins/agentkit/references/code-samples.md | 20 ++++++------- .../discovering-connector-tools/SKILL.md | 4 +-- .../skills/exposing-agentkit-via-mcp/SKILL.md | 8 ++--- .../skills/integrating-agentkit/SKILL.md | 13 ++++---- .../production-readiness-agentkit/SKILL.md | 4 +-- plugins/agentkit/skills/setup/SKILL.md | 2 +- .../scalekit-mcp-auth-troubleshooter.md | 30 ++++++++++++++----- plugins/saaskit/docs/frameworks/laravel.md | 2 +- plugins/saaskit/docs/frameworks/nextjs.md | 2 +- plugins/saaskit/docs/frameworks/python.md | 4 +-- plugins/saaskit/docs/frameworks/springboot.md | 2 +- .../implementing-saaskit-python/SKILL.md | 4 +-- .../django-reference.md | 6 ++-- .../flask-reference.md | 6 ++-- .../implementing-saaskit/laravel-reference.md | 2 +- .../skills/scalekit-code-doctor/SKILL.md | 8 ++--- .../references/COMMON-MISTAKES.md | 12 ++++---- .../references/REFERENCE.md | 14 ++++----- 26 files changed, 101 insertions(+), 86 deletions(-) diff --git a/plugins/agentkit/README.md b/plugins/agentkit/README.md index 8e725a1..08ddf23 100644 --- a/plugins/agentkit/README.md +++ b/plugins/agentkit/README.md @@ -34,10 +34,10 @@ Start with the canonical docs entrypoint at [`docs/index.md`](docs/index.md). Official Scalekit docs: - [LLM docs map](https://docs.scalekit.com/llms.txt) - [Docs sitemap](https://docs.scalekit.com/sitemap-0.xml) -- [AgentKit overview](https://docs.scalekit.com/agentkit/overview.md) -- [AgentKit quickstart](https://docs.scalekit.com/agentkit/quickstart.md) -- [AgentKit connectors](https://docs.scalekit.com/agentkit/connectors.md) -- [AgentKit examples](https://docs.scalekit.com/agentkit/examples.md) +- [AgentKit overview](https://docs.scalekit.com/agentkit/overview) +- [AgentKit quickstart](https://docs.scalekit.com/agentkit/quickstart) +- [AgentKit connectors](https://docs.scalekit.com/agentkit/connectors) +- [AgentKit examples](https://docs.scalekit.com/agentkit/examples) ## Skills Reference - `/agentkit:setup` diff --git a/plugins/agentkit/docs/byoc.md b/plugins/agentkit/docs/byoc.md index b4963ff..d1d8d82 100644 --- a/plugins/agentkit/docs/byoc.md +++ b/plugins/agentkit/docs/byoc.md @@ -13,7 +13,7 @@ Common reasons: ## Official Scalekit docs -- [Bring your own credentials](https://docs.scalekit.com/agentkit/advanced/bring-your-own-oauth.md) +- [Bring your own credentials](https://docs.scalekit.com/agentkit/advanced/bring-your-own-oauth) ## What changes diff --git a/plugins/agentkit/docs/code-samples.md b/plugins/agentkit/docs/code-samples.md index baf9622..1452d1a 100644 --- a/plugins/agentkit/docs/code-samples.md +++ b/plugins/agentkit/docs/code-samples.md @@ -22,10 +22,10 @@ Use it to choose an implementation style before opening a larger sample reposito ## Official Scalekit docs -- [AgentKit examples](https://docs.scalekit.com/agentkit/examples.md) -- [Code samples](https://docs.scalekit.com/agentkit/code-samples.md) -- [LangChain example](https://docs.scalekit.com/agentkit/examples/langchain.md) -- [Google ADK example](https://docs.scalekit.com/agentkit/examples/google-adk.md) +- [AgentKit examples](https://docs.scalekit.com/agentkit/examples) +- [Code samples](https://docs.scalekit.com/agentkit/code-samples) +- [LangChain example](https://docs.scalekit.com/agentkit/examples/langchain) +- [Google ADK example](https://docs.scalekit.com/agentkit/examples/google-adk) ## Important rule diff --git a/plugins/agentkit/docs/connected-accounts.md b/plugins/agentkit/docs/connected-accounts.md index 5d1f52f..a113912 100644 --- a/plugins/agentkit/docs/connected-accounts.md +++ b/plugins/agentkit/docs/connected-accounts.md @@ -25,8 +25,8 @@ Typical lifecycle: ## Official Scalekit docs -- [Manage connected accounts](https://docs.scalekit.com/agentkit/connected-accounts.md) -- [Authorize a user](https://docs.scalekit.com/agentkit/tools/authorize.md) +- [Manage connected accounts](https://docs.scalekit.com/agentkit/connected-accounts) +- [Authorize a user](https://docs.scalekit.com/agentkit/tools/authorize) ## Operational rules diff --git a/plugins/agentkit/docs/connections.md b/plugins/agentkit/docs/connections.md index 8b437fd..5897920 100644 --- a/plugins/agentkit/docs/connections.md +++ b/plugins/agentkit/docs/connections.md @@ -24,8 +24,8 @@ They are related, but they are not always the same string. ## Official Scalekit docs -- [Configure a connection](https://docs.scalekit.com/agentkit/connections.md) -- [Scopes and permissions](https://docs.scalekit.com/agentkit/authentication/scopes-permissions.md) +- [Configure a connection](https://docs.scalekit.com/agentkit/connections) +- [Scopes and permissions](https://docs.scalekit.com/agentkit/authentication/scopes-permissions) ## Typical setup flow diff --git a/plugins/agentkit/docs/connectors/README.md b/plugins/agentkit/docs/connectors/README.md index e732f52..beb86b7 100644 --- a/plugins/agentkit/docs/connectors/README.md +++ b/plugins/agentkit/docs/connectors/README.md @@ -13,7 +13,7 @@ They should not claim to be the exhaustive current tool catalog. ## Official Scalekit docs -- [Agent connectors](https://docs.scalekit.com/agentkit/connectors.md) +- [Agent connectors](https://docs.scalekit.com/agentkit/connectors) ## Source of truth @@ -37,7 +37,7 @@ Per-connector implementation notes were removed in v2.0.0. Use live AgentKit met via the Scalekit MCP server as the current source of truth for tool schemas and coverage. For official connector documentation see the -[Scalekit connector catalog](https://docs.scalekit.com/agentkit/connectors.md). +[Scalekit connector catalog](https://docs.scalekit.com/agentkit/connectors). ## Related docs diff --git a/plugins/agentkit/docs/index.md b/plugins/agentkit/docs/index.md index 12ea06c..0376592 100644 --- a/plugins/agentkit/docs/index.md +++ b/plugins/agentkit/docs/index.md @@ -18,10 +18,10 @@ These pages are the best external entrypoints for the material in this directory - [LLM docs map](https://docs.scalekit.com/llms.txt) - [Docs sitemap](https://docs.scalekit.com/sitemap-0.xml) -- [AgentKit overview](https://docs.scalekit.com/agentkit/overview.md) -- [AgentKit quickstart](https://docs.scalekit.com/agentkit/quickstart.md) -- [AgentKit connectors](https://docs.scalekit.com/agentkit/connectors.md) -- [AgentKit examples](https://docs.scalekit.com/agentkit/examples.md) +- [AgentKit overview](https://docs.scalekit.com/agentkit/overview) +- [AgentKit quickstart](https://docs.scalekit.com/agentkit/quickstart) +- [AgentKit connectors](https://docs.scalekit.com/agentkit/connectors) +- [AgentKit examples](https://docs.scalekit.com/agentkit/examples) ## How this directory is organized diff --git a/plugins/agentkit/docs/tool-discovery.md b/plugins/agentkit/docs/tool-discovery.md index 9d6fbf0..e300d0b 100644 --- a/plugins/agentkit/docs/tool-discovery.md +++ b/plugins/agentkit/docs/tool-discovery.md @@ -27,9 +27,9 @@ Use live metadata for: ## Official Scalekit docs -- [Tools overview](https://docs.scalekit.com/agentkit/tools/overview.md) -- [Scalekit optimized built-in tools](https://docs.scalekit.com/agentkit/tools/scalekit-optimized-tools.md) -- [AgentKit connectors](https://docs.scalekit.com/agentkit/connectors.md) +- [Tools overview](https://docs.scalekit.com/agentkit/tools/overview) +- [Scalekit optimized built-in tools](https://docs.scalekit.com/agentkit/tools/scalekit-optimized-tools) +- [AgentKit connectors](https://docs.scalekit.com/agentkit/connectors) ## Discovery workflow diff --git a/plugins/agentkit/references/code-samples.md b/plugins/agentkit/references/code-samples.md index 19731d9..426ae80 100644 --- a/plugins/agentkit/references/code-samples.md +++ b/plugins/agentkit/references/code-samples.md @@ -38,7 +38,7 @@ Choose the right sample based on your needs: OPENAI_API_KEY=your_openai_api_key_here SCALEKIT_CLIENT_ID=your_scalekit_client_id SCALEKIT_CLIENT_SECRET=your_scalekit_client_secret -SCALEKIT_ENV_URL=your_scalekit_environment_url +SCALEKIT_ENVIRONMENT_URL=your_scalekit_environment_url ``` **Installation:** @@ -52,17 +52,17 @@ python main.py **1. SDK Initialization** ```python import os -import scalekit.client +from scalekit import ScalekitClient from dotenv import load_dotenv load_dotenv() -scalekit = scalekit.client.ScalekitClient( +sk_client = ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), - env_url=os.getenv("SCALEKIT_ENV_URL"), + env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), ) -actions = scalekit.actions +actions = sk_client.actions ``` **2. OAuth Authorization Flow** @@ -145,7 +145,7 @@ GOOGLE_GENAI_USE_VERTEXAI=FALSE GOOGLE_API_KEY=your_google_api_key_here SCALEKIT_CLIENT_ID=your_scalekit_client_id SCALEKIT_CLIENT_SECRET=your_scalekit_client_secret -SCALEKIT_ENV_URL=your_scalekit_environment_url +SCALEKIT_ENVIRONMENT_URL=your_scalekit_environment_url ``` **Installation:** @@ -169,7 +169,7 @@ connection_name = "gmail" client = scalekit.client.ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), - env_url=os.getenv("SCALEKIT_ENV_URL") + env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL") ) # Generate OAuth authorization link @@ -500,10 +500,10 @@ from dotenv import load_dotenv load_dotenv() -scalekit = scalekit.client.ScalekitClient( +sk_client = ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), - env_url=os.getenv("SCALEKIT_ENV_URL"), + env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), ) ``` @@ -534,6 +534,6 @@ scalekit = scalekit.client.ScalekitClient( ### Troubleshooting 1. **Connection not ACTIVE:** Check OAuth flow completed in browser 2. **Token expired:** Scalekit auto-refreshes tokens; call `get_connected_account` before tool calls -3. **Invalid credentials:** Verify `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, and `SCALEKIT_CLIENT_SECRET` +3. **Invalid credentials:** Verify `SCALEKIT_ENVIRONMENT_URL`, `SCALEKIT_CLIENT_ID`, and `SCALEKIT_CLIENT_SECRET` 4. **Tool not found:** Verify connection name matches Scalekit Dashboard exactly 5. **Scope errors:** Check connection configuration has required scopes in Scalekit Dashboard diff --git a/plugins/agentkit/skills/discovering-connector-tools/SKILL.md b/plugins/agentkit/skills/discovering-connector-tools/SKILL.md index bbba82c..5523882 100644 --- a/plugins/agentkit/skills/discovering-connector-tools/SKILL.md +++ b/plugins/agentkit/skills/discovering-connector-tools/SKILL.md @@ -32,7 +32,7 @@ load_dotenv() client = scalekit.client.ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), - env_url=os.getenv("SCALEKIT_ENV_URL"), + env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), ) # List all tools for a provider @@ -54,7 +54,7 @@ import { ScalekitClient } from '@scalekit-sdk/node'; import 'dotenv/config'; const client = new ScalekitClient( - process.env.SCALEKIT_ENV_URL!, + process.env.SCALEKIT_ENVIRONMENT_URL!, process.env.SCALEKIT_CLIENT_ID!, process.env.SCALEKIT_CLIENT_SECRET! ); diff --git a/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md b/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md index 36e0a20..59f7c46 100644 --- a/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md +++ b/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md @@ -18,7 +18,7 @@ Scalekit lets you configure MCP endpoints that manage authentication, create per ## Prerequisites -- [ ] **Scalekit credentials**: [app.scalekit.com](https://app.scalekit.com) → Settings → Copy `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`, `SCALEKIT_ENV_URL` +- [ ] **Scalekit credentials**: [app.scalekit.com](https://app.scalekit.com) → Settings → Copy `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`, `SCALEKIT_ENVIRONMENT_URL` - [ ] **OpenAI API key**: `OPENAI_API_KEY` > **Gmail is the only connector that does not require dashboard setup.** All other connectors (including Google Calendar) must be created in the Scalekit Dashboard before use: @@ -62,12 +62,12 @@ Initialize the Scalekit client: ```python load_dotenv() -scalekit = scalekit.client.ScalekitClient( +sk_client = ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), - env_url=os.getenv("SCALEKIT_ENV_URL"), + env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), ) -my_mcp = scalekit.actions.mcp +my_mcp = sk_client.actions.mcp ``` ## Step 2 — Create an MCP config and server instance diff --git a/plugins/agentkit/skills/integrating-agentkit/SKILL.md b/plugins/agentkit/skills/integrating-agentkit/SKILL.md index a7c907f..bc44ca8 100644 --- a/plugins/agentkit/skills/integrating-agentkit/SKILL.md +++ b/plugins/agentkit/skills/integrating-agentkit/SKILL.md @@ -7,7 +7,7 @@ description: Integrates Scalekit AgentKit into a project so an agent can create Scalekit handles the full OAuth lifecycle — authorization, token storage, and refresh — so agents can act on behalf of users in Gmail, Slack, Notion, Calendar, and other connectors. -**Required env vars**: `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`, `SCALEKIT_ENV_URL` +**Required env vars**: `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`, `SCALEKIT_ENVIRONMENT_URL` → Get from [app.scalekit.com](https://app.scalekit.com): Developers → Settings → API Credentials ## Setup @@ -25,16 +25,17 @@ Install the SDK and initialize the client: pip install scalekit-sdk-python ``` ```python -import scalekit.client, os +from scalekit import ScalekitClient +import os from dotenv import load_dotenv load_dotenv() -scalekit = scalekit.client.ScalekitClient( +sk_client = ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), - env_url=os.getenv("SCALEKIT_ENV_URL"), + env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), ) -actions = scalekit.actions +actions = sk_client.actions ``` **Node.js** @@ -46,7 +47,7 @@ import { ScalekitClient } from '@scalekit-sdk/node'; import 'dotenv/config'; const scalekitClient = new ScalekitClient( - process.env.SCALEKIT_ENV_URL!, + process.env.SCALEKIT_ENVIRONMENT_URL!, process.env.SCALEKIT_CLIENT_ID!, process.env.SCALEKIT_CLIENT_SECRET! ); diff --git a/plugins/agentkit/skills/production-readiness-agentkit/SKILL.md b/plugins/agentkit/skills/production-readiness-agentkit/SKILL.md index fd3625f..d28d40d 100644 --- a/plugins/agentkit/skills/production-readiness-agentkit/SKILL.md +++ b/plugins/agentkit/skills/production-readiness-agentkit/SKILL.md @@ -13,12 +13,12 @@ Work through each section in order — earlier sections are blockers for later o ```bash # Confirm production credentials are set (not dev/staging) -echo $SCALEKIT_ENV_URL # should be https://.scalekit.com (not .scalekit.dev) +echo $SCALEKIT_ENVIRONMENT_URL # should be https://.scalekit.com (not .scalekit.dev) echo $SCALEKIT_CLIENT_ID # should be set echo $SCALEKIT_CLIENT_SECRET # should be set # Verify token endpoint works -curl -s -o /dev/null -w "%{http_code}" -X POST "$SCALEKIT_ENV_URL/oauth/token" \ +curl -s -o /dev/null -w "%{http_code}" -X POST "$SCALEKIT_ENVIRONMENT_URL/oauth/token" \ -d "client_id=$SCALEKIT_CLIENT_ID&client_secret=$SCALEKIT_CLIENT_SECRET&grant_type=client_credentials" # Expected: 200 ``` diff --git a/plugins/agentkit/skills/setup/SKILL.md b/plugins/agentkit/skills/setup/SKILL.md index 3e8ec1f..b9dd5a4 100644 --- a/plugins/agentkit/skills/setup/SKILL.md +++ b/plugins/agentkit/skills/setup/SKILL.md @@ -42,7 +42,7 @@ If answers aren't already clear from context, ask: Before starting any skill, verify credentials exist: ```bash -SCALEKIT_ENV_URL=https://your-env.scalekit.dev +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.dev SCALEKIT_CLIENT_ID= SCALEKIT_CLIENT_SECRET= ``` diff --git a/plugins/saaskit/agents/scalekit-mcp-auth-troubleshooter.md b/plugins/saaskit/agents/scalekit-mcp-auth-troubleshooter.md index f7777e4..9d1a426 100644 --- a/plugins/saaskit/agents/scalekit-mcp-auth-troubleshooter.md +++ b/plugins/saaskit/agents/scalekit-mcp-auth-troubleshooter.md @@ -32,14 +32,14 @@ Ask (or infer from context) which MCP client is failing: MCP Inspector, MCP-Remo Also capture: MCP server URL, Scalekit environment URL, and whether this is dev or prod. ## 2) Confirm the auth handshake basics (server-side) -Goal: verify the server challenges unauthenticated requests correctly and points clients to resource metadata. [web:45] +Goal: verify the server challenges unauthenticated requests correctly and points clients to resource metadata. Run these checks (use curl if available, otherwise browser devtools): -- Request the MCP server base URL and confirm it returns HTTP 401. [web:45] -- Confirm the response includes a `WWW-Authenticate` (or `www-authenticate`) header containing `resource_metadata=""`. [web:51] +- Request the MCP server base URL and confirm it returns HTTP 401. +- Confirm the response includes a `WWW-Authenticate` (or `www-authenticate`) header containing `resource_metadata=""`. - Open the `` in a browser and confirm the JSON matches the Scalekit dashboard configuration for that environment. -If the 401/header/metadata checks fail, classify as “metadata/handshake misconfiguration” and recommend fixing the protected resource metadata wiring first. [web:45] +If the 401/header/metadata checks fail, classify as “metadata/handshake misconfiguration” and recommend fixing the protected resource metadata wiring first. ## 3) If MCP Inspector won’t connect: check cached client state If the handshake looks correct but the client still fails, suspect the client cached an old domain/metadata after a domain change. @@ -66,7 +66,7 @@ Checklist: - Test direct connectivity from the same machine running the MCP client (bypass proxy if possible). ## 6) Client-specific: Claude Desktop port limitations -Claude Desktop only supports standard HTTPS on port 443; it will ignore custom ports and still attempt 443. [web:48] +Claude Desktop only supports standard HTTPS on port 443; it will ignore custom ports and still attempt 443. Workarounds: - Expose your MCP server on 443 via a load balancer or reverse proxy. @@ -80,5 +80,21 @@ Fixes: - Windows: enable default app management permissions (Settings → Privacy → App permissions), then restart the client. - Linux: ensure `xdg-open` exists (`which xdg-open`) and is on PATH, then restart the client. -## 8) Claude Code / OAuth registration mismatch (when relevant) -If you see errors like “Incompat +## 8) OAuth registration mismatch +If you see errors like "Incompatible client registration", "invalid_client", or "redirect_uri_mismatch" during the OAuth flow: + +Diagnosis: +- The MCP server's OAuth client registration in Scalekit may have stale callback URLs, wrong scopes, or a mismatched client ID/secret. +- A recent domain or environment URL change may not have propagated to the OAuth client settings. + +Resolution: +- In Scalekit Dashboard > Authentication > Applications, confirm the OAuth client redirect URIs include the MCP client callback URL. +- Verify `SCALEKIT_ENVIRONMENT_URL`, `SCALEKIT_CLIENT_ID`, and `SCALEKIT_CLIENT_SECRET` match the dashboard values exactly (no trailing slashes, correct environment). +- If the environment was recently changed (e.g., dev to prod), re-register the OAuth client or update the existing registration. +- Clear any cached client state (see section 3) and retry. + +## 9) Deliver the fix plan +After identifying the root cause, present: +1. **Diagnosis** -- one-sentence summary of the failure mode. +2. **Fix steps** -- numbered, smallest-change-first. +3. **Verification** -- curl or client commands the user can run to confirm the fix works. diff --git a/plugins/saaskit/docs/frameworks/laravel.md b/plugins/saaskit/docs/frameworks/laravel.md index 10ad275..1c0694e 100644 --- a/plugins/saaskit/docs/frameworks/laravel.md +++ b/plugins/saaskit/docs/frameworks/laravel.md @@ -20,7 +20,7 @@ routes/web.php # Named routes + middleware groups ## Environment ```env -SCALEKIT_ENV_URL=https://your-env.scalekit.com +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com SCALEKIT_CLIENT_ID=your-client-id SCALEKIT_CLIENT_SECRET=your-client-secret SCALEKIT_REDIRECT_URI=http://localhost:8000/auth/callback diff --git a/plugins/saaskit/docs/frameworks/nextjs.md b/plugins/saaskit/docs/frameworks/nextjs.md index 52ca66d..95f6f55 100644 --- a/plugins/saaskit/docs/frameworks/nextjs.md +++ b/plugins/saaskit/docs/frameworks/nextjs.md @@ -23,7 +23,7 @@ lib/ ## Environment ```env -SCALEKIT_ENV_URL=https://your-env.scalekit.com +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com SCALEKIT_CLIENT_ID=your-client-id SCALEKIT_CLIENT_SECRET=your-client-secret SCALEKIT_REDIRECT_URI=http://localhost:3000/auth/callback diff --git a/plugins/saaskit/docs/frameworks/python.md b/plugins/saaskit/docs/frameworks/python.md index c3dd6b7..6055286 100644 --- a/plugins/saaskit/docs/frameworks/python.md +++ b/plugins/saaskit/docs/frameworks/python.md @@ -9,7 +9,7 @@ pip install scalekit-sdk-python python-dotenv ``` ```env -SCALEKIT_ENV_URL=https://your-env.scalekit.com +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com SCALEKIT_CLIENT_ID=your_client_id SCALEKIT_CLIENT_SECRET=your_client_secret SCALEKIT_REDIRECT_URI=http://localhost:8000/auth/callback @@ -25,7 +25,7 @@ All frameworks use the same SDK under the hood: from scalekit import ScalekitClient client = ScalekitClient( - env_url=os.getenv('SCALEKIT_ENV_URL'), + env_url=os.getenv('SCALEKIT_ENVIRONMENT_URL'), client_id=os.getenv('SCALEKIT_CLIENT_ID'), client_secret=os.getenv('SCALEKIT_CLIENT_SECRET'), ) diff --git a/plugins/saaskit/docs/frameworks/springboot.md b/plugins/saaskit/docs/frameworks/springboot.md index f7f3be5..0b37ff4 100644 --- a/plugins/saaskit/docs/frameworks/springboot.md +++ b/plugins/saaskit/docs/frameworks/springboot.md @@ -30,7 +30,7 @@ Requires Spring Boot 3.2+ and Java 17+. ```yaml scalekit: - env-url: ${SCALEKIT_ENV_URL} + env-url: ${SCALEKIT_ENVIRONMENT_URL} client-id: ${SCALEKIT_CLIENT_ID} client-secret: ${SCALEKIT_CLIENT_SECRET} redirect-uri: ${SCALEKIT_REDIRECT_URI:http://localhost:8080/login/oauth2/code/scalekit} diff --git a/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md b/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md index f071ce2..c5589ef 100644 --- a/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md +++ b/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md @@ -30,7 +30,7 @@ from scalekit import ScalekitClient load_dotenv() sc = ScalekitClient( - env_url=os.getenv("SCALEKIT_ENV_URL"), + env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), ) @@ -61,7 +61,7 @@ app = FastAPI() REDIRECT_URI = os.getenv("SCALEKIT_REDIRECT_URI", "http://localhost:8000/auth/callback") sc = ScalekitClient( - env_url=os.getenv("SCALEKIT_ENV_URL"), + env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), ) diff --git a/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md index a7ce9dd..471c70e 100644 --- a/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md +++ b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md @@ -9,7 +9,7 @@ pip install scalekit-sdk-python python-dotenv django ## Environment variables ```env -SCALEKIT_ENV_URL=https://your-env.scalekit.dev +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.dev SCALEKIT_CLIENT_ID=your_client_id SCALEKIT_CLIENT_SECRET=your_client_secret SCALEKIT_REDIRECT_URI=http://localhost:8000/auth/callback @@ -31,7 +31,7 @@ def get_scalekit_client() -> ScalekitClient: global _sc if _sc is None: _sc = ScalekitClient( - env_url=os.getenv("SCALEKIT_ENV_URL"), + env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), ) @@ -181,7 +181,7 @@ SESSION_COOKIE_AGE = 86400 # 24 hours ``` - [ ] Step 1: pip install scalekit-sdk-python python-dotenv django -- [ ] Step 2: Set SCALEKIT_ENV_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET in .env +- [ ] Step 2: Set SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET in .env - [ ] Step 3: Create auth_client.py with singleton ScalekitClient - [ ] Step 4: Implement login, callback, logout views - [ ] Step 5: Add ScalekitAuthMiddleware to MIDDLEWARE in settings.py diff --git a/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md b/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md index 84594cf..4e5120e 100644 --- a/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md +++ b/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md @@ -9,7 +9,7 @@ pip install scalekit-sdk-python python-dotenv flask flask-session ## Environment variables ```env -SCALEKIT_ENV_URL=https://your-env.scalekit.dev +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.dev SCALEKIT_CLIENT_ID=your_client_id SCALEKIT_CLIENT_SECRET=your_client_secret SCALEKIT_REDIRECT_URI=http://localhost:5000/auth/callback @@ -40,7 +40,7 @@ Session(app) REDIRECT_URI = os.getenv("SCALEKIT_REDIRECT_URI", "http://localhost:5000/auth/callback") sc = ScalekitClient( - env_url=os.getenv("SCALEKIT_ENV_URL"), + env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), ) @@ -143,7 +143,7 @@ def dashboard(): ``` - [ ] Step 1: pip install scalekit-sdk-python python-dotenv flask flask-session -- [ ] Step 2: Set SCALEKIT_ENV_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET, SECRET_KEY in .env +- [ ] Step 2: Set SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET, SECRET_KEY in .env - [ ] Step 3: Initialize ScalekitClient at module level (single instance per process) - [ ] Step 4: Configure Flask-Session with filesystem or Redis backend - [ ] Step 5: Implement /auth/login, /auth/callback, /auth/logout routes diff --git a/plugins/saaskit/skills/implementing-saaskit/laravel-reference.md b/plugins/saaskit/skills/implementing-saaskit/laravel-reference.md index 89ff114..3a4f0dc 100644 --- a/plugins/saaskit/skills/implementing-saaskit/laravel-reference.md +++ b/plugins/saaskit/skills/implementing-saaskit/laravel-reference.md @@ -26,7 +26,7 @@ routes/ ## Environment variables ```env -SCALEKIT_ENV_URL=https://your-env.scalekit.com +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com SCALEKIT_CLIENT_ID=your-client-id SCALEKIT_CLIENT_SECRET=your-client-secret SCALEKIT_REDIRECT_URI=http://localhost:8000/auth/callback diff --git a/plugins/saaskit/skills/scalekit-code-doctor/SKILL.md b/plugins/saaskit/skills/scalekit-code-doctor/SKILL.md index e15de81..8a42e5a 100644 --- a/plugins/saaskit/skills/scalekit-code-doctor/SKILL.md +++ b/plugins/saaskit/skills/scalekit-code-doctor/SKILL.md @@ -41,7 +41,7 @@ import { ScalekitClient } from '@scalekit-sdk/node'; import crypto from 'crypto'; const scalekit = new ScalekitClient( - process.env.SCALEKIT_ENV_URL!, + process.env.SCALEKIT_ENVIRONMENT_URL!, process.env.SCALEKIT_CLIENT_ID!, process.env.SCALEKIT_CLIENT_SECRET! ); @@ -93,7 +93,7 @@ Check these categories in order: **3. Security** — Cookies: `httpOnly`, `secure`, `sameSite: 'lax'`. State: cryptographically random. Redirects: only relative paths. Secrets: from env vars. Webhooks: signature verified before processing. -**4. Environment** — `SCALEKIT_ENV_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`. Redirect URI matches dashboard. Domain format: `https://.scalekit.com`. +**4. Environment** — `SCALEKIT_ENVIRONMENT_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`. Redirect URI matches dashboard. Domain format: `https://.scalekit.com`. **5. Best practices** — Client is singleton. Error handling uses typed exceptions. `window.location.href` for OAuth redirects (not `router.push`). @@ -107,7 +107,7 @@ Resolution order when a method isn't in `references/REFERENCE.md`: |----------|--------| | 1 | Embedded `references/REFERENCE.md` | | 2 | Live SDK reference: `https://raw.githubusercontent.com/scalekit-inc/scalekit-sdk-{node,python,go,java}/main/REFERENCE.md` | -| 3 | REST API: `https://docs.scalekit.com/apis.md` | +| 3 | REST API: `https://docs.scalekit.com/apis` | | 4 | State explicitly: "This method could not be verified." | Never output code containing an unverified method call. @@ -116,7 +116,7 @@ Never output code containing an unverified method call. | Resource | URL | |----------|-----| -| REST API reference | `https://docs.scalekit.com/apis.md` | +| REST API reference | `https://docs.scalekit.com/apis` | | LLM doc index | `https://docs.scalekit.com/llms.txt` | | SaaSKit docs | `https://docs.scalekit.com/_llms-txt/saaskit-complete.txt` | | AgentKit docs | `https://docs.scalekit.com/_llms-txt/agentkit.txt` | diff --git a/plugins/saaskit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md b/plugins/saaskit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md index 5343181..45ea547 100644 --- a/plugins/saaskit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md +++ b/plugins/saaskit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md @@ -15,14 +15,12 @@ import { ScalekitClient } from 'scalekit'; // wrong package name import { ScalekitClient } from 'scalekit-sdk-node'; // wrong package name ``` -**Correct (either works):** +**Correct:** ```typescript import { ScalekitClient } from '@scalekit-sdk/node'; -// OR -import { Scalekit } from '@scalekit-sdk/node'; // official alias, also valid ``` -Both `ScalekitClient` and `Scalekit` are valid named exports from `@scalekit-sdk/node`. The SDK source exports both. Use whichever is consistent with your codebase. +Only `ScalekitClient` is the canonical named export from `@scalekit-sdk/node`. Older docs sometimes show `Scalekit` as an alias — treat that as deprecated/wrong; the runtime export is `ScalekitClient`. Always use `ScalekitClient`. ### Python @@ -335,7 +333,7 @@ const scalekit = new ScalekitClient( **Correct:** ```typescript const scalekit = new ScalekitClient( - process.env.SCALEKIT_ENV_URL!, + process.env.SCALEKIT_ENVIRONMENT_URL!, process.env.SCALEKIT_CLIENT_ID!, process.env.SCALEKIT_CLIENT_SECRET! ); @@ -394,7 +392,7 @@ OAuth redirects are full HTTP redirects to an external domain (Scalekit/IdP). Cl | Wrong | Correct | Issue | |-------|---------|-------| -| `SCALEKIT_URL` | `SCALEKIT_ENV_URL` | Missing `ENV_` | +| `SCALEKIT_URL` | `SCALEKIT_ENVIRONMENT_URL` | Missing `ENV_` | | `SCALEKIT_SECRET` | `SCALEKIT_CLIENT_SECRET` | Missing `CLIENT_` | | `SCALEKIT_ID` | `SCALEKIT_CLIENT_ID` | Missing `CLIENT_` | | `SCALEKIT_CALLBACK_URL` | `SCALEKIT_REDIRECT_URI` | Wrong name entirely | @@ -419,7 +417,7 @@ app.get('/api/data', async (req, res) => { ```typescript // Module-level singleton const scalekit = new ScalekitClient( - process.env.SCALEKIT_ENV_URL!, + process.env.SCALEKIT_ENVIRONMENT_URL!, process.env.SCALEKIT_CLIENT_ID!, process.env.SCALEKIT_CLIENT_SECRET! ); diff --git a/plugins/saaskit/skills/scalekit-code-doctor/references/REFERENCE.md b/plugins/saaskit/skills/scalekit-code-doctor/references/REFERENCE.md index 5ecab18..43fb013 100644 --- a/plugins/saaskit/skills/scalekit-code-doctor/references/REFERENCE.md +++ b/plugins/saaskit/skills/scalekit-code-doctor/references/REFERENCE.md @@ -1,6 +1,6 @@ # Scalekit API Reference — Compact Lookup -This file contains every correct SDK method signature and REST endpoint. Use it as ground truth when generating or reviewing Scalekit code. If a method isn't listed here, do NOT assume it exists — verify against the live SDK source or `https://docs.scalekit.com/apis.md`. +This file contains every correct SDK method signature and REST endpoint. Use it as ground truth when generating or reviewing Scalekit code. If a method isn't listed here, do NOT assume it exists — verify against the live SDK source or `https://docs.scalekit.com/apis`. --- @@ -12,7 +12,7 @@ This file contains every correct SDK method signature and REST endpoint. Use it import { ScalekitClient } from '@scalekit-sdk/node'; const scalekit = new ScalekitClient( - process.env.SCALEKIT_ENV_URL!, // string — environment URL + process.env.SCALEKIT_ENVIRONMENT_URL!, // string — environment URL process.env.SCALEKIT_CLIENT_ID!, // string — client ID process.env.SCALEKIT_CLIENT_SECRET! // string — client secret ); @@ -24,7 +24,7 @@ const scalekit = new ScalekitClient( from scalekit import ScalekitClient scalekit_client = ScalekitClient( - os.environ.get('SCALEKIT_ENV_URL'), # str — environment URL + os.environ.get('SCALEKIT_ENVIRONMENT_URL'), # str — environment URL os.environ.get('SCALEKIT_CLIENT_ID'), # str — client ID os.environ.get('SCALEKIT_CLIENT_SECRET') # str — client secret ) @@ -36,7 +36,7 @@ scalekit_client = ScalekitClient( import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2" client := scalekit.NewScalekitClient( - os.Getenv("SCALEKIT_ENV_URL"), // string — environment URL + os.Getenv("SCALEKIT_ENVIRONMENT_URL"), // string — environment URL os.Getenv("SCALEKIT_CLIENT_ID"), // string — client ID os.Getenv("SCALEKIT_CLIENT_SECRET"), // string — client secret ) @@ -48,7 +48,7 @@ client := scalekit.NewScalekitClient( import com.scalekit.ScalekitClient; ScalekitClient client = new ScalekitClient( - System.getenv("SCALEKIT_ENV_URL"), // String — environment URL + System.getenv("SCALEKIT_ENVIRONMENT_URL"), // String — environment URL System.getenv("SCALEKIT_CLIENT_ID"), // String — client ID System.getenv("SCALEKIT_CLIENT_SECRET") // String — client secret ); @@ -60,13 +60,13 @@ ScalekitClient client = new ScalekitClient( | Variable | Purpose | Format | |----------|---------|--------| -| `SCALEKIT_ENV_URL` | Environment URL | `https://.scalekit.com` (prod) or `https://.scalekit.dev` (dev) | +| `SCALEKIT_ENVIRONMENT_URL` | Environment URL | `https://.scalekit.com` (prod) or `https://.scalekit.dev` (dev) | | `SCALEKIT_CLIENT_ID` | Client ID | String from dashboard | | `SCALEKIT_CLIENT_SECRET` | Client secret | String from dashboard | | `SCALEKIT_REDIRECT_URI` | OAuth callback URL | Must exactly match dashboard config | | `SCALEKIT_WEBHOOK_SECRET` | Webhook signing secret | Format: `whsec_...` | -Note: The REST API docs use `SCALEKIT_ENVIRONMENT_URL` in some examples. Both `SCALEKIT_ENV_URL` and `SCALEKIT_ENVIRONMENT_URL` are acceptable — just be consistent within a project. +Note: The REST API docs use `SCALEKIT_ENVIRONMENT_URL` in some examples. Both `SCALEKIT_ENVIRONMENT_URL` and `SCALEKIT_ENVIRONMENT_URL` are acceptable — just be consistent within a project. --- From 8b659a3cd8bfff2bc25e3d9ccd77fc30db9d60c8 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Fri, 22 May 2026 12:15:07 +0530 Subject: [PATCH 48/53] fix: scalekit.auth sub-client, getLogoutUrl signature, sameSite, internal imports, Go SDK path --- CHANGELOG.md | 2 +- README.md | 4 ++-- plugins/agentkit/references/code-samples.md | 4 ++-- .../skills/discovering-connector-tools/SKILL.md | 9 +++++---- .../skills/exposing-agentkit-via-mcp/SKILL.md | 2 +- plugins/saaskit/references/bring-your-own-auth.md | 10 +++++----- .../references/session-management-patterns.md | 12 +++++------- .../saaskit/skills/implementing-modular-sso/SKILL.md | 2 +- plugins/saaskit/skills/implementing-saaskit/SKILL.md | 6 +++--- 9 files changed, 25 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b591252..7633487 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ - Added `docs/` layer with canonical documentation (connections, tool-discovery, code-samples) - Added `rules/` layer for cross-cutting guidance - **SaaSKit** plugin (`plugins/saaskit/`) — consolidates FSA, SSO, SCIM, and MCP server auth - - 13 skills covering login, sessions, SSO, SCIM, RBAC, MCP server auth, API keys, migration, production readiness, auth testing, and SDK debugging + - 14 skills covering login, sessions, SSO, SCIM, RBAC, MCP server auth, API keys, migration, production readiness, auth testing, and SDK debugging - `docs/` layer with 13 documentation files including framework-specific guides (Python, Next.js, Go, Spring Boot, Laravel) - `rules/` layer with terminology and redirect URL guidance - Framework-specific reference files (Go, Spring Boot, Laravel, FastMCP, Express, FastAPI) diff --git a/README.md b/README.md index 2f26592..dc4ac67 100644 --- a/README.md +++ b/README.md @@ -121,8 +121,8 @@ Use SaaSKit to add login, session management, enterprise SSO, SCIM provisioning, - [MCP Auth guide](https://docs.scalekit.com/authenticate/mcp/quickstart/) — Secure MCP servers - [Full-stack auth guide](https://docs.scalekit.com/authenticate/fsa/quickstart/) — Add login, callback, and session management - [SCIM directory sync guide](https://docs.scalekit.com/directory/scim/quickstart/) — Provision and deprovision users -- [AgentKit overview](https://docs.scalekit.com/agentkit/overview.md) — Connect agents to authenticated tools through connectors, connections, and connected accounts -- [AgentKit quickstart](https://docs.scalekit.com/agentkit/quickstart.md) — Build an agent that makes authenticated tool calls on behalf of users +- [AgentKit overview](https://docs.scalekit.com/agentkit/overview) — Connect agents to authenticated tools through connectors, connections, and connected accounts +- [AgentKit quickstart](https://docs.scalekit.com/agentkit/quickstart) — Build an agent that makes authenticated tool calls on behalf of users #### Resources diff --git a/plugins/agentkit/references/code-samples.md b/plugins/agentkit/references/code-samples.md index 426ae80..a4c3a48 100644 --- a/plugins/agentkit/references/code-samples.md +++ b/plugins/agentkit/references/code-samples.md @@ -159,14 +159,14 @@ adk run scalekit_tool_agent **1. Complete Agent Implementation** ```python from google.adk.agents import Agent -import scalekit.client +from scalekit import ScalekitClient import os identifier = "user-1234" connection_name = "gmail" # Initialize Scalekit client -client = scalekit.client.ScalekitClient( +client = ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL") diff --git a/plugins/agentkit/skills/discovering-connector-tools/SKILL.md b/plugins/agentkit/skills/discovering-connector-tools/SKILL.md index 5523882..6cbe8e3 100644 --- a/plugins/agentkit/skills/discovering-connector-tools/SKILL.md +++ b/plugins/agentkit/skills/discovering-connector-tools/SKILL.md @@ -25,18 +25,19 @@ Do not rely on static connector notes as a complete catalog. Those may lag the l ## Live tool discovery (Python) ```python -import scalekit.client, os +from scalekit import ScalekitClient +import os from dotenv import load_dotenv load_dotenv() -client = scalekit.client.ScalekitClient( +sk_client = ScalekitClient( client_id=os.getenv("SCALEKIT_CLIENT_ID"), client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"), env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"), ) # List all tools for a provider -tools = client.actions.get_tools(providers=["GMAIL"], page_size=100) +tools = sk_client.actions.get_tools(providers=["GMAIL"], page_size=100) for tool in tools.tools: print(f"Tool: {tool.name}") print(f" Description: {tool.description}") @@ -44,7 +45,7 @@ for tool in tools.tools: print(f" Output schema: {tool.output_schema}") # Get a specific tool by name -tool = client.actions.get_tools(tool_name="gmail_fetch_mails") +tool = sk_client.actions.get_tools(tool_name="gmail_fetch_mails") ``` ## Live tool discovery (Node.js) diff --git a/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md b/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md index 59f7c46..0f04789 100644 --- a/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md +++ b/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md @@ -44,7 +44,7 @@ Add these imports to `main.py`: import os import asyncio from dotenv import load_dotenv -import scalekit.client +from scalekit import ScalekitClient from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping from scalekit.actions.types import GetMcpInstanceAuthStateResponse from langgraph.prebuilt import create_react_agent diff --git a/plugins/saaskit/references/bring-your-own-auth.md b/plugins/saaskit/references/bring-your-own-auth.md index b421781..d18c538 100644 --- a/plugins/saaskit/references/bring-your-own-auth.md +++ b/plugins/saaskit/references/bring-your-own-auth.md @@ -64,13 +64,13 @@ from scalekit import ScalekitClient import os scalekit = ScalekitClient( - os.environ.get('SCALEKIT_ENVIRONMENT_URL'), - os.environ.get('SCALEKIT_CLIENT_ID'), - os.environ.get('SCALEKIT_CLIENT_SECRET') + env_url=os.environ.get('SCALEKIT_ENVIRONMENT_URL'), + client_id=os.environ.get('SCALEKIT_CLIENT_ID'), + client_secret=os.environ.get('SCALEKIT_CLIENT_SECRET') ) # Update login user details -scalekit.auth.update_login_user_details( +scalekit.update_login_user_details( connection_id="{{connection_id}}", login_request_id="{{login_request_id}}", user={ @@ -95,7 +95,7 @@ const scalekit = new ScalekitClient( process.env.SCALEKIT_CLIENT_SECRET ); -await scalekit.auth.updateLoginUserDetails( +await scalekit.updateLoginUserDetails( '{{connection_id}}', '{{login_request_id}}', { diff --git a/plugins/saaskit/references/session-management-patterns.md b/plugins/saaskit/references/session-management-patterns.md index 5eb795e..dda0659 100644 --- a/plugins/saaskit/references/session-management-patterns.md +++ b/plugins/saaskit/references/session-management-patterns.md @@ -103,7 +103,7 @@ res.cookie('access_token', tokens.access_token, { res.cookie('refresh_token', tokens.refresh_token, { httpOnly: true, secure: true, - sameSite: 'strict', + sameSite: 'lax', path: '/auth/refresh', // scope to refresh endpoint only maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days }); @@ -123,7 +123,7 @@ async function scalekitSessionMiddleware(req, res, next) { if (!accessToken) return res.redirect('/login'); try { - const payload = await scalekit.auth.validateAccessToken(accessToken); + const payload = await scalekit.validateAccessToken(accessToken); req.user = payload; return next(); } catch (err) { @@ -139,10 +139,10 @@ async function handleTokenRefresh(req, res, next) { if (!refreshToken) return res.redirect('/login'); try { - const newTokens = await scalekit.auth.refreshTokens(refreshToken); + const newTokens = await scalekit.refreshAccessToken(refreshToken); res.cookie('access_token', newTokens.access_token, { /* same options */ }); res.cookie('refresh_token', newTokens.refresh_token, { /* same options */ }); - req.user = await scalekit.auth.validateAccessToken(newTokens.access_token); + req.user = await scalekit.validateAccessToken(newTokens.access_token); return next(); } catch (err) { res.clearCookie('access_token'); @@ -170,9 +170,7 @@ lucia) that want enterprise SSO without rebuilding auth. app.get('/auth/callback', async (req, res) => { const { code } = req.query; - const { user, idTokenClaims } = await scalekit.auth.authenticateWithCode(code, { - redirectUri: process.env.REDIRECT_URI - }); + const { user, idTokenClaims } = await scalekit.authenticateWithCode(code, process.env.REDIRECT_URI); // Create session using YOUR existing mechanism req.session.userId = user.id; diff --git a/plugins/saaskit/skills/implementing-modular-sso/SKILL.md b/plugins/saaskit/skills/implementing-modular-sso/SKILL.md index 6f1b49a..ea3e367 100644 --- a/plugins/saaskit/skills/implementing-modular-sso/SKILL.md +++ b/plugins/saaskit/skills/implementing-modular-sso/SKILL.md @@ -55,7 +55,7 @@ pip install scalekit-sdk-python **Go:** ```bash -go get github.com/scalekit-inc/scalekit-sdk-go +go get github.com/scalekit-inc/scalekit-sdk-go/v2 ``` **Java:** diff --git a/plugins/saaskit/skills/implementing-saaskit/SKILL.md b/plugins/saaskit/skills/implementing-saaskit/SKILL.md index f36bb4d..20c900f 100644 --- a/plugins/saaskit/skills/implementing-saaskit/SKILL.md +++ b/plugins/saaskit/skills/implementing-saaskit/SKILL.md @@ -55,10 +55,10 @@ Store tokens in HttpOnly cookies: // Node.js res.cookie('accessToken', authResult.accessToken, { maxAge: (authResult.expiresIn - 60) * 1000, - httpOnly: true, secure: true, path: '/api', sameSite: 'strict' + httpOnly: true, secure: true, path: '/api', sameSite: 'lax' }); res.cookie('refreshToken', authResult.refreshToken, { - httpOnly: true, secure: true, path: '/auth/refresh', sameSite: 'strict' + httpOnly: true, secure: true, path: '/auth/refresh', sameSite: 'lax' }); ``` @@ -74,7 +74,7 @@ Clear session data, then redirect to Scalekit's logout endpoint: ```js // Node.js clearSessionData(); -const logoutUrl = scalekit.getLogoutUrl(idTokenHint, postLogoutRedirectUri); +const logoutUrl = scalekit.getLogoutUrl({ idTokenHint, postLogoutRedirectUri }); res.redirect(logoutUrl); // One-time use URL; expires after logout ``` From 605fe354ea7cb6841a3844c9ef25c90a5ab95403 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Fri, 22 May 2026 12:40:27 +0530 Subject: [PATCH 49/53] =?UTF-8?q?fix:=20SK=5FENV=5FURL=E2=86=92SCALEKIT=5F?= =?UTF-8?q?ENVIRONMENT=5FURL=20in=20MCP=20OAuth=20refs,=20Python=20dict?= =?UTF-8?q?=E2=86=92AuthorizationUrlOptions=20in=20flask/django=20refs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../skills/adding-mcp-oauth/express-reference.md | 4 ++-- .../skills/adding-mcp-oauth/fastapi-reference.md | 2 +- .../implementing-saaskit-python/django-reference.md | 11 ++++++----- .../implementing-saaskit-python/flask-reference.md | 11 ++++++----- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/plugins/saaskit/skills/adding-mcp-oauth/express-reference.md b/plugins/saaskit/skills/adding-mcp-oauth/express-reference.md index dccb7ea..80a48a3 100644 --- a/plugins/saaskit/skills/adding-mcp-oauth/express-reference.md +++ b/plugins/saaskit/skills/adding-mcp-oauth/express-reference.md @@ -37,14 +37,14 @@ McpServer → Tool Handler → Response ### Environment Configuration -Required variables: `SK_ENV_URL`, `SK_CLIENT_ID`, `SK_CLIENT_SECRET`, `EXPECTED_AUDIENCE`, `PROTECTED_RESOURCE_METADATA`, `PORT`. +Required variables: `SCALEKIT_ENVIRONMENT_URL`, `SCALEKIT_CLIENT_ID`, `SCALEKIT_CLIENT_SECRET`, `EXPECTED_AUDIENCE`, `PROTECTED_RESOURCE_METADATA`, `PORT`. ### Scalekit Client Initialization ```typescript import { ScalekitClient } from '@scalekit-sdk/node'; -const scalekit = new ScalekitClient(SK_ENV_URL, SK_CLIENT_ID, SK_CLIENT_SECRET); +const scalekit = new ScalekitClient(SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET); ``` Initialize once at module level for connection pooling. diff --git a/plugins/saaskit/skills/adding-mcp-oauth/fastapi-reference.md b/plugins/saaskit/skills/adding-mcp-oauth/fastapi-reference.md index a0680af..3df366b 100644 --- a/plugins/saaskit/skills/adding-mcp-oauth/fastapi-reference.md +++ b/plugins/saaskit/skills/adding-mcp-oauth/fastapi-reference.md @@ -48,7 +48,7 @@ async def auth_middleware(request: Request, call_next): token = auth_header.split("Bearer ", 1)[1].strip() options = TokenValidationOptions( - issuer=SK_ENV_URL, + issuer=SCALEKIT_ENVIRONMENT_URL, audience=[EXPECTED_AUDIENCE] ) diff --git a/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md index 471c70e..ff8cf35 100644 --- a/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md +++ b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md @@ -234,11 +234,12 @@ def dashboard(request): def idp_login(request): sc = get_scalekit_client() claims = sc.get_idp_initiated_login_claims(request.GET.get("idp_initiated_login", "")) - options = {} - if claims.organization_id: options["organization_id"] = claims.organization_id - if claims.connection_id: options["connection_id"] = claims.connection_id - if claims.login_hint: options["login_hint"] = claims.login_hint - auth_url = sc.get_authorization_url(REDIRECT_URI, options=options) + opts = AuthorizationUrlOptions( + organization_id=claims.organization_id or None, + connection_id=claims.connection_id or None, + login_hint=claims.login_hint or None, + ) + auth_url = sc.get_authorization_url(REDIRECT_URI, options=opts) return redirect(auth_url) ``` diff --git a/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md b/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md index 4e5120e..cd61fdd 100644 --- a/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md +++ b/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md @@ -186,11 +186,12 @@ app.register_blueprint(auth_bp) @app.get("/auth/idp-login") def idp_login(): claims = sc.get_idp_initiated_login_claims(request.args.get("idp_initiated_login", "")) - options = {} - if claims.organization_id: options["organization_id"] = claims.organization_id - if claims.connection_id: options["connection_id"] = claims.connection_id - if claims.login_hint: options["login_hint"] = claims.login_hint - return redirect(sc.get_authorization_url(REDIRECT_URI, options=options)) + opts = AuthorizationUrlOptions( + organization_id=claims.organization_id or None, + connection_id=claims.connection_id or None, + login_hint=claims.login_hint or None, + ) + return redirect(sc.get_authorization_url(REDIRECT_URI, options=opts)) ``` ### Cache-Control: no-store on protected responses From 158a073050cade048874b39a011ef57c17fe80e7 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Fri, 22 May 2026 12:46:13 +0530 Subject: [PATCH 50/53] =?UTF-8?q?fix:=20decodeToken=E2=86=92validateAccess?= =?UTF-8?q?TokenAndGetClaims=20in=20access-control?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/saaskit/docs/access-control.md | 4 ++-- plugins/saaskit/skills/implementing-access-control/SKILL.md | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/plugins/saaskit/docs/access-control.md b/plugins/saaskit/docs/access-control.md index c0f6715..5a6fe8d 100644 --- a/plugins/saaskit/docs/access-control.md +++ b/plugins/saaskit/docs/access-control.md @@ -26,9 +26,9 @@ Scalekit embeds `roles` and `permissions` in the access token. After validating const validateAndExtractAuth = async (req, res, next) => { try { const accessToken = decrypt(req.cookies.accessToken); - if (!(await scalekit.validateAccessToken(accessToken))) + const claims = await scalekit.validateAccessTokenAndGetClaims(accessToken); + if (!claims) return res.status(401).json({ error: 'Invalid or expired token' }); - const claims = decodeToken(accessToken); req.user = { id: claims.sub, organizationId: claims.oid, diff --git a/plugins/saaskit/skills/implementing-access-control/SKILL.md b/plugins/saaskit/skills/implementing-access-control/SKILL.md index 14fdc2b..3c67799 100644 --- a/plugins/saaskit/skills/implementing-access-control/SKILL.md +++ b/plugins/saaskit/skills/implementing-access-control/SKILL.md @@ -26,10 +26,8 @@ Validate+extract, then RBAC/PBAC guards. const validateAndExtractAuth = async (req, res, next) => { try { const accessToken = decrypt(req.cookies.accessToken); // if encrypted - const isValid = await scalekit.validateAccessToken(accessToken); - if (!isValid) return res.status(401).json({ error: "Invalid or expired token" }); - - const tokenData = await decodeToken(accessToken); // JWT decode library + const tokenData = await scalekit.validateAccessTokenAndGetClaims(accessToken); + if (!tokenData) return res.status(401).json({ error: "Invalid or expired token" }); req.user = { id: tokenData.sub, organizationId: tokenData.oid, From 37edb2bf51c04c20087afb5bf29e364bb887ba2e Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Fri, 22 May 2026 16:57:02 +0530 Subject: [PATCH 51/53] fix: correct Python SDK call patterns for get_authorization_url and get_logout_url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - get_authorization_url: pass AuthorizationUrlOptions object, not dict - get_logout_url: pass LogoutUrlOptions object, not bare kwargs Both methods use attribute access internally — dicts/kwargs crash at runtime. --- plugins/saaskit/docs/auth-flows.md | 7 ++++--- .../saaskit/skills/implementing-saaskit-python/SKILL.md | 8 ++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/plugins/saaskit/docs/auth-flows.md b/plugins/saaskit/docs/auth-flows.md index b847fbe..c3bfd11 100644 --- a/plugins/saaskit/docs/auth-flows.md +++ b/plugins/saaskit/docs/auth-flows.md @@ -97,10 +97,11 @@ res.redirect(logoutUrl); ```python # Python (Flask) id_token = request.cookies.get("idToken") -logout_url = scalekit_client.get_logout_url( +from scalekit.common.scalekit import LogoutUrlOptions +logout_url = scalekit_client.get_logout_url(LogoutUrlOptions( id_token_hint=id_token, - post_logout_redirect_uri=post_logout_redirect_uri -) + post_logout_redirect_uri=post_logout_redirect_uri, +)) resp = make_response(redirect(logout_url)) resp.set_cookie("accessToken", "", max_age=0, path="/") resp.set_cookie("refreshToken", "", max_age=0, path="/") diff --git a/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md b/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md index c5589ef..5b82d1a 100644 --- a/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md +++ b/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md @@ -69,7 +69,10 @@ sc = ScalekitClient( @app.get("/auth/login") def login(response: Response): state = secrets.token_urlsafe(32) - response = RedirectResponse(sc.get_authorization_url(REDIRECT_URI, {"state": state})) + from scalekit.common.scalekit import AuthorizationUrlOptions + options = AuthorizationUrlOptions() + options.state = state + response = RedirectResponse(sc.get_authorization_url(REDIRECT_URI, options)) response.set_cookie("oauth_state", state, httponly=True, samesite="lax", secure=True) return response @@ -86,7 +89,8 @@ def callback(request: Request, code: str, state: str): @app.get("/auth/logout") def logout(request: Request): - logout_url = sc.get_logout_url({"post_logout_redirect_uri": "http://localhost:8000"}) + from scalekit.common.scalekit import LogoutUrlOptions + logout_url = sc.get_logout_url(LogoutUrlOptions(post_logout_redirect_uri="http://localhost:8000")) # Clear your session here return RedirectResponse(logout_url) ``` From 2e36fa3255ae28674bb903b9164b68505de04b96 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Fri, 22 May 2026 17:00:47 +0530 Subject: [PATCH 52/53] fix: AuthorizationUrlOptions takes no constructor args in django/flask refs --- .../skills/implementing-saaskit-python/django-reference.md | 4 +++- .../skills/implementing-saaskit-python/flask-reference.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md index ff8cf35..63de064 100644 --- a/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md +++ b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md @@ -70,7 +70,9 @@ def login(request: HttpRequest): state = secrets.token_urlsafe(32) request.session["oauth_state"] = state sc = get_scalekit_client() - auth_url = sc.get_authorization_url(REDIRECT_URI, options=AuthorizationUrlOptions(state=state)) + options = AuthorizationUrlOptions() + options.state = state + auth_url = sc.get_authorization_url(REDIRECT_URI, options) return redirect(auth_url) def callback(request: HttpRequest): diff --git a/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md b/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md index cd61fdd..8ca4950 100644 --- a/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md +++ b/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md @@ -69,7 +69,9 @@ GET /auth/logout def login(): state = secrets.token_urlsafe(32) session["oauth_state"] = state - auth_url = sc.get_authorization_url(REDIRECT_URI, options=AuthorizationUrlOptions(state=state)) + options = AuthorizationUrlOptions() + options.state = state + auth_url = sc.get_authorization_url(REDIRECT_URI, options) return redirect(auth_url) @app.get("/auth/callback") From 9f5cf64a92815e997ce41e1db303bde1749479c8 Mon Sep 17 00:00:00 2001 From: Saif Shines Date: Fri, 22 May 2026 17:17:09 +0530 Subject: [PATCH 53/53] fix: AuthorizationUrlOptions in IdP-initiated login (django, flask references) --- .../implementing-saaskit-python/django-reference.md | 9 ++++----- .../implementing-saaskit-python/flask-reference.md | 9 ++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md index 63de064..91b8e35 100644 --- a/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md +++ b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md @@ -236,11 +236,10 @@ def dashboard(request): def idp_login(request): sc = get_scalekit_client() claims = sc.get_idp_initiated_login_claims(request.GET.get("idp_initiated_login", "")) - opts = AuthorizationUrlOptions( - organization_id=claims.organization_id or None, - connection_id=claims.connection_id or None, - login_hint=claims.login_hint or None, - ) + opts = AuthorizationUrlOptions() + opts.organization_id = claims.organization_id or None + opts.connection_id = claims.connection_id or None + opts.login_hint = claims.login_hint or None auth_url = sc.get_authorization_url(REDIRECT_URI, options=opts) return redirect(auth_url) ``` diff --git a/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md b/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md index 8ca4950..0a533d1 100644 --- a/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md +++ b/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md @@ -188,11 +188,10 @@ app.register_blueprint(auth_bp) @app.get("/auth/idp-login") def idp_login(): claims = sc.get_idp_initiated_login_claims(request.args.get("idp_initiated_login", "")) - opts = AuthorizationUrlOptions( - organization_id=claims.organization_id or None, - connection_id=claims.connection_id or None, - login_hint=claims.login_hint or None, - ) + opts = AuthorizationUrlOptions() + opts.organization_id = claims.organization_id or None + opts.connection_id = claims.connection_id or None + opts.login_hint = claims.login_hint or None return redirect(sc.get_authorization_url(REDIRECT_URI, options=opts)) ```