diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index 98e1eed..52e2677 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -1,49 +1,30 @@ { - "name": "scalekit-authstack", + "name": "scalekit-auth-stack", "owner": { "name": "Scalekit Inc", "email": "support@scalekit.com" }, "metadata": { - "description": "Everything that you need to add authstack to your projects. MCP Auth, Agent Auth, Full Stack Auth, Enterprise SSO, User Provisioning, and more.", - "version": "1.0.0", + "description": "Scalekit Auth Stack for Cursor — AgentKit and SaaSKit plugins. Add agent auth, tool calling, SSO, SCIM, MCP auth, and session management from Cursor.", + "version": "2.0.0", "pluginRoot": "plugins" }, "plugins": [ { - "name": "mcp-auth", - "source": "mcp-auth", - "description": "Guides users through adding production-ready OAuth 2.1 authorization to Model Context Protocol (MCP) servers", - "category": "testing", - "homepage": "https://docs.scalekit.com/authenticate/mcp/start-mcp-auth-coding-agents" - }, - { - "name": "agent-auth", - "source": "agent-auth", - "description": "Implements Scalekit Agent Auth so AI agents can act in third-party apps (Gmail, Slack, Calendar, Notion) on behalf of users.", + "name": "agentkit", + "source": "agentkit", + "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.", "category": "agent auth", - "homepage": "https://docs.scalekit.com/authenticate/agent/start-agent-auth-coding-agents" + "homepage": "https://docs.scalekit.com/agentkit/overview", + "logo": "../assets/logo.svg" }, { - "name": "full-stack-auth", - "source": "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.", + "name": "saaskit", + "source": "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.", "category": "auth", - "homepage": "https://docs.scalekit.com" - }, - { - "name": "modular-sso", - "source": "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.", - "category": "enterprise auth", - "homepage": "https://docs.scalekit.com" - }, - { - "name": "modular-scim", - "source": "modular-scim", - "description": "Automates user and group provisioning and deprovisioning via SCIM 2.0 with identity providers like Okta, Entra ID, and JumpCloud.", - "category": "enterprise auth", - "homepage": "https://docs.scalekit.com" + "homepage": "https://docs.scalekit.com", + "logo": "../assets/logo.svg" } ] } diff --git a/AGENTS.md b/AGENTS.md index a461b6f..88097aa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,104 +27,49 @@ plugins// agents/ Optional: custom sub-agents rules/ Optional: Cursor rules (.mdc files with frontmatter) commands/ Optional: slash commands - .mcp.json Optional: MCP server configuration + mcp.json Optional: MCP server configuration + hooks/hooks.json Optional: lifecycle hooks ``` ## Plugins -### mcp-auth +### agentkit -OAuth 2.1 authorization for MCP servers using Scalekit. +Authentication for AI agents. OAuth flows, token vault, 100+ connectors, tool discovery, and live testing. Skills: -- `add-mcp-auth` — adds OAuth 2.1 auth to any MCP server -- `mcp-auth-expressjs-scalekit` — Express.js MCP server with OAuth -- `mcp-auth-fastapi-fastmcp-scalekit` — FastAPI + FastMCP with OAuth -- `mcp-auth-fastmcp-scalekit` — FastMCP with Scalekit provider -- `production-readiness-scalekit` — MCP auth production readiness checklist +- `integrating-agentkit` — core integration: SDK setup, connected accounts, OAuth flows, token fetching, agent frameworks +- `discovering-connector-tools` — live tool metadata discovery, schema inspection, tool set narrowing +- `exposing-agentkit-via-mcp` — expose AgentKit tools through MCP for compatible runtimes +- `production-readiness-agentkit` — production readiness checklist for AgentKit integrations -Agents: `setup-auth.md`, `validate-mcp-auth.md` +Rules: `terminology.mdc`, `live-metadata-first.mdc`, `tool-selection.mdc` -Rules: `mcp-oauth-discovery.mdc`, `mcp-scope-authorization.mdc`, `mcp-secrets-hygiene.mdc`, `mcp-token-validation.mdc`, `no-secrets.mdc` +References: `connected-accounts.md`, `code-samples.md`, `connectors.md`, `connections.md`, `byoc.md`, `redirects.md`, `tool-discovery.md` -### agent-auth +### saaskit -Implements Scalekit Agent Auth so AI agents can act in third-party apps (Gmail, Slack, Calendar, Notion) on behalf of users. +Production-ready auth for B2B SaaS apps. Login, sessions, SSO, SCIM, MCP server auth. Skills: -- `agent-auth` — integrates Scalekit Agent Auth (OAuth flows, token storage, auto-refresh) -- `building-agent-mcp-server` — creates Scalekit MCP servers with authenticated tool access -- `production-readiness-scalekit` — agent auth production readiness checklist +- `implementing-saaskit` — core auth flow: login, signup, callback, token exchange, session management, logout +- `implementing-modular-sso` — enterprise SSO (SAML/OIDC) with 20+ IdPs, admin portal, JIT provisioning +- `implementing-scim-provisioning` — SCIM 2.0 webhooks, user/group lifecycle, directory API +- `adding-mcp-oauth` — OAuth 2.1 for MCP servers (FastMCP, Express, FastAPI reference files included) +- `adding-api-auth` — API keys and client credentials for machine-to-machine auth +- `implementing-access-control` — RBAC and permission enforcement using token claims +- `implementing-saaskit-nextjs` — Next.js App Router integration +- `implementing-saaskit-python` — Django, FastAPI, Flask integration +- `managing-saaskit-sessions` — token storage, validation, refresh, revocation +- `migrating-to-saaskit` — migration planner from existing auth systems +- `testing-auth-setup` — validates auth config with the dryrun CLI +- `production-readiness-saaskit` — unified production checklist across all SaaSKit domains -Agents: `setup-scalekit.md` +Agents: `setup-scalekit.md`, `scalekit-mcp-auth-troubleshooter.md` -Rules: `oauth-security.mdc` +Rules: `terminology.mdc`, `redirect-urls.mdc` -References: `agent-connectors/` (connector-specific docs), `connected-accounts.md`, `code-samples.md`, `providers.md`, `connections.md`, `byoc.md`, `redirects.md` - -### full-stack-auth - -Production-ready authentication flows using Scalekit full-stack auth across common stacks. - -Skills: -- `full-stack-auth` — complete auth flow (sign-up, login, logout, sessions) -- `implementing-scalekit-nextjs-auth` — Next.js App Router integration -- `implementing-scalekit-django-auth` — Django integration -- `implementing-scalekit-fastapi-auth` — FastAPI integration -- `implementing-scalekit-flask-auth` — Flask integration -- `implementing-scalekit-go-auth` — Go (Gin) integration -- `implementing-scalekit-springboot-auth` — Spring Boot integration -- `implementing-scalekit-laravel-auth` — Laravel integration -- `implement-logout` — complete logout flows across stacks -- `implementing-access-control` — RBAC and permission checks -- `implementing-admin-portal` — self-serve SSO/SCIM customer portal -- `adding-api-key-auth` — API key creation, validation, and revocation -- `adding-oauth2-to-apis` — OAuth 2.0 client-credentials for machine-to-machine auth -- `manage-user-sessions` — secure session storage and token refresh -- `migrating-to-scalekit-auth` — incremental migration from existing auth -- `production-readiness-scalekit` — production readiness checklist - -Agents: `setup-scalekit.md`, `sdk-version-advisor.md`, `session-management-reviewer.md`, `scalekit-mcp-helper.md` - -Commands: `dryrun.md` - -Rules: `web-auth-security.mdc` - -References: `redirects.md`, `scalekit-logs.md`, `scalekit-user-profiles.md` - -### modular-sso - -Modular SSO flows using Scalekit for apps with existing user management. - -Skills: -- `modular-sso` — complete SSO and authentication flows, IdP-initiated login, enterprise onboarding -- `implementing-admin-portal` — self-serve SSO configuration portal -- `production-readiness-scalekit` — SSO production readiness checklist - -Agents: `setup-scalekit.md`, `sso-validate.md` - -Commands: `dryrun-sso.md` - -Rules: `sso-security.mdc` - -References: `redirects.md` - -### modular-scim - -SCIM webhook provisioning with Scalekit for real-time user and group lifecycle management. - -Skills: -- `modular-scim` — SCIM user provisioning via Scalekit's Directory API and webhooks -- `implementing-admin-portal` — self-serve SCIM configuration portal -- `production-readiness-scalekit` — SCIM production readiness checklist - -Agents: `setup-scalekit.md`, `scim-validate.md` - -Commands: `dryrun-scim.md` - -Rules: `scim-security.mdc` - -References: `redirects.md` +References: `bring-your-own-auth.md`, `redirects.md`, `scalekit-logs.md`, `scalekit-mcp-server.md`, `scalekit-user-profiles.md`, `session-management-patterns.md` ## Non-negotiable rules @@ -172,7 +117,7 @@ Context budget: ## MCP rules -- `.mcp.json` must use environment variables for secrets, never inline credentials. +- `mcp.json` must use environment variables for secrets, never inline credentials. - Tools must be outcome-focused and handle common failures inside the tool. - Validate all tool inputs at boundaries. diff --git a/README.md b/README.md index 9381c9b..155f12d 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ Scalekit -

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

+

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

[![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/cursor-authstack/pulls) @@ -14,27 +14,24 @@ Add SSO, SCIM, MCP Auth, agent auth, and tool-calling from your Cursor editor. **Windows**: install.sh requires macOS or Linux (or WSL on Windows). Native Windows PowerShell install is not yet supported. + --- ### Helpful Links @@ -126,9 +100,9 @@ The `modular-scim` plugin adds SCIM 2.0 directory sync to applications. It handl #### Documentation - [Scalekit Documentation](https://docs.scalekit.com) — Complete guides and API reference -- [SSO Quickstart](https://docs.scalekit.com/sso/quickstart/) — Implement enterprise SSO -- [MCP Auth Guide](https://docs.scalekit.com/mcp-auth/quickstart/) — Secure MCP servers -- [Agent Auth Guide](https://docs.scalekit.com/agent-auth/quickstart/) — Authentication for AI agents +- [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 +- [AgentKit overview](https://docs.scalekit.com/agentkit/overview) — Connect agents to authenticated tools #### Resources diff --git a/install.sh b/install.sh index 8c394ab..0628c58 100755 --- a/install.sh +++ b/install.sh @@ -7,7 +7,7 @@ REPO_REF="${CURSOR_AUTHSTACK_REF:-main}" SOURCE_DIR="${CURSOR_AUTHSTACK_SOURCE_DIR:-}" if [[ -n "$SOURCE_DIR" ]]; then - exec "${SOURCE_DIR%/}/scripts/install_locally.sh" + exec "${SOURCE_DIR%/}/scripts/install.sh" fi TMP_DIR="$(mktemp -d)" @@ -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_locally.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 -exec "$EXTRACTED_DIR/scripts/install_locally.sh" +chmod +x "$EXTRACTED_DIR/scripts/install.sh" +exec "$EXTRACTED_DIR/scripts/install.sh" diff --git a/plugins/agent-auth/.cursor-plugin/plugin.json b/plugins/agent-auth/.cursor-plugin/plugin.json deleted file mode 100644 index dc4fbc7..0000000 --- a/plugins/agent-auth/.cursor-plugin/plugin.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "agent-auth", - "displayName": "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.0.0", - "author": { - "name": "Scalekit", - "email": "hi@scalekit.com" - }, - "homepage": "https://docs.scalekit.com", - "repository": "https://github.com/scalekit-inc/cursor-authstack", - "license": "MIT", - "keywords": ["scalekit", "agent-auth", "oauth", "connected-accounts"], - "skills": "./skills", - "agents": "./agents", - "rules": "./rules", - "mcpServers": "./.mcp.json" -} diff --git a/plugins/agent-auth/.mcp.json b/plugins/agent-auth/.mcp.json deleted file mode 100644 index 36dbbf4..0000000 --- a/plugins/agent-auth/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "scalekit": { - "command": "npx", - "args": ["-y", "mcp-remote", "https://mcp.scalekit.com"] - } - } -} diff --git a/plugins/agent-auth/README.md b/plugins/agent-auth/README.md deleted file mode 100644 index ecf72d4..0000000 --- a/plugins/agent-auth/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# agent-auth - -Implements Scalekit Agent Auth so AI agents can act in third-party apps (Gmail, Slack, Calendar, Notion) on behalf of users. - -## Purpose - -This plugin handles the full OAuth lifecycle — authorization URL generation, token storage, and automatic token refresh — so AI agents can call third-party APIs on behalf of users without managing OAuth themselves. - -**Non-goals:** This plugin does not cover user-facing authentication flows (see `full-stack-auth`) or MCP server auth (see `mcp-auth`). - ---- - -## Install - -Clone or install the cursor-authstack repository and activate the `agent-auth` plugin from the Cursor plugin panel. - -Required environment variables (add to `.env`): - -```env -SCALEKIT_ENV_URL=https://your-env.scalekit.com -SCALEKIT_CLIENT_ID=your_client_id -SCALEKIT_CLIENT_SECRET=your_client_secret -``` - -Get credentials from [app.scalekit.com](https://app.scalekit.com) → Developers → Settings → API Credentials. - ---- - -## Skills - -### agent-auth - -Integrates Scalekit Agent Auth into a project. - -**When to use:** When a user wants to connect to an external service, authorize OAuth access, fetch access or refresh tokens, or execute API calls on behalf of a user. - -**Example invocations:** -- "Set up agent auth so my agent can read Gmail" -- "Connect to Slack using Scalekit agent auth" -- "Add OAuth token refresh for Notion integration" - -### building-agent-mcp-server - -Creates a Scalekit MCP server with authenticated tool access. - -**When to use:** When building an MCP server that manages authentication, creates personalized access URLs for users, and defines which tools are accessible. - -**Example invocations:** -- "Build an MCP server that connects to Gmail and Google Calendar" -- "Create a Scalekit MCP server for my agent" - -### production-readiness-scalekit - -Walks through a production readiness checklist for Scalekit agent auth implementations. - -**When to use:** When going live, launching to production, or doing a pre-launch review. - -**Example invocations:** -- "Run a production readiness check on my agent auth setup" -- "What do I need to check before going live with agent auth?" - ---- - -## Agents - -### setup-scalekit - -Sets up Scalekit env vars, installs/initializes the SDK, and verifies credentials. Use proactively when the user asks to set up, install, initialize, or configure Scalekit. - ---- - -## Configuration - -The MCP server configuration in `.mcp.json` connects to `https://mcp.scalekit.com` — the hosted Scalekit MCP server. No additional configuration is required. - -### Connector setup (non-Gmail) - -For connectors other than Gmail, you must first create the connector in the Scalekit Dashboard: -1. Go to **Scalekit Dashboard → Agent Auth → Connections** -2. Click **+ Create Connection** -3. Select the connector and enter a Connection Name -4. Save - -The Connection Name you set is the exact value used as `connection_name` in your code. - ---- - -## Troubleshooting - -**"Connection not found"**: The connector must be created in the Scalekit Dashboard first (except Gmail). Verify the connection name matches exactly. - -**"Invalid token"**: Always call `get_connected_account` immediately before any API call — Scalekit auto-refreshes tokens and this guarantees the latest valid token. - -**"Authorization required"**: The user must complete OAuth via the authorization link. Check that the link was opened and OAuth was completed (status should be `ACTIVE`). - ---- - -## Security notes - -- Never log or store access tokens in version control -- Always use environment variables for `SCALEKIT_CLIENT_ID` and `SCALEKIT_CLIENT_SECRET` -- Call `get_connected_account` before each API call (not cached tokens) to ensure automatic refresh -- Revoke connected accounts when users disconnect or delete their account diff --git a/plugins/agent-auth/agent-connectors/README.md b/plugins/agent-auth/agent-connectors/README.md deleted file mode 100644 index 807653a..0000000 --- a/plugins/agent-auth/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 Agent Auth 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 | -| [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 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 - -Connectors support OAuth 2.0, API Key, or Basic Auth authentication through the Agent Auth platform. 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/agent-auth/agent-connectors/airtable.md b/plugins/agent-auth/agent-connectors/airtable.md deleted file mode 100644 index f3acf0b..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/asana.md b/plugins/agent-auth/agent-connectors/asana.md deleted file mode 100644 index cea2fc7..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/attention.md b/plugins/agent-auth/agent-connectors/attention.md deleted file mode 100644 index 9af975e..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/bigquery.md b/plugins/agent-auth/agent-connectors/bigquery.md deleted file mode 100644 index 64b8a33..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/chorus.md b/plugins/agent-auth/agent-connectors/chorus.md deleted file mode 100644 index b57dd23..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/clari_copilot.md b/plugins/agent-auth/agent-connectors/clari_copilot.md deleted file mode 100644 index 2949e94..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/clickup.md b/plugins/agent-auth/agent-connectors/clickup.md deleted file mode 100644 index dd7d216..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/confluence.md b/plugins/agent-auth/agent-connectors/confluence.md deleted file mode 100644 index 96d699f..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/dropbox.md b/plugins/agent-auth/agent-connectors/dropbox.md deleted file mode 100644 index 1bfe1d3..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/fathom.md b/plugins/agent-auth/agent-connectors/fathom.md deleted file mode 100644 index 3408c98..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/freshdesk.md b/plugins/agent-auth/agent-connectors/freshdesk.md deleted file mode 100644 index 424a753..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/github.md b/plugins/agent-auth/agent-connectors/github.md deleted file mode 100644 index c9cf234..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/gmail.md b/plugins/agent-auth/agent-connectors/gmail.md deleted file mode 100644 index d668c75..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/gong.md b/plugins/agent-auth/agent-connectors/gong.md deleted file mode 100644 index 26a36e3..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/google_ads.md b/plugins/agent-auth/agent-connectors/google_ads.md deleted file mode 100644 index 9718639..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/google_docs.md b/plugins/agent-auth/agent-connectors/google_docs.md deleted file mode 100644 index e790d5d..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/google_drive.md b/plugins/agent-auth/agent-connectors/google_drive.md deleted file mode 100644 index ef6120e..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/google_forms.md b/plugins/agent-auth/agent-connectors/google_forms.md deleted file mode 100644 index fde9e1d..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/google_meets.md b/plugins/agent-auth/agent-connectors/google_meets.md deleted file mode 100644 index 814ff7b..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/google_sheets.md b/plugins/agent-auth/agent-connectors/google_sheets.md deleted file mode 100644 index 8df9a54..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/google_slides.md b/plugins/agent-auth/agent-connectors/google_slides.md deleted file mode 100644 index 242ef37..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/googlecalendar.md b/plugins/agent-auth/agent-connectors/googlecalendar.md deleted file mode 100644 index 8168441..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/hubspot.md b/plugins/agent-auth/agent-connectors/hubspot.md deleted file mode 100644 index ac44f2c..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/intercom.md b/plugins/agent-auth/agent-connectors/intercom.md deleted file mode 100644 index 77ef5d8..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/jira.md b/plugins/agent-auth/agent-connectors/jira.md deleted file mode 100644 index 1675086..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/linear.md b/plugins/agent-auth/agent-connectors/linear.md deleted file mode 100644 index 9b0f877..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/microsoft_excel.md b/plugins/agent-auth/agent-connectors/microsoft_excel.md deleted file mode 100644 index 2fa4c19..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/microsoft_teams.md b/plugins/agent-auth/agent-connectors/microsoft_teams.md deleted file mode 100644 index 85790a1..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/microsoft_word.md b/plugins/agent-auth/agent-connectors/microsoft_word.md deleted file mode 100644 index 7b2d530..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/monday.md b/plugins/agent-auth/agent-connectors/monday.md deleted file mode 100644 index 6cc8ab0..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/notion.md b/plugins/agent-auth/agent-connectors/notion.md deleted file mode 100644 index 22b58d5..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/onedrive.md b/plugins/agent-auth/agent-connectors/onedrive.md deleted file mode 100644 index 601c43f..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/onenote.md b/plugins/agent-auth/agent-connectors/onenote.md deleted file mode 100644 index 47a835b..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/outlook.md b/plugins/agent-auth/agent-connectors/outlook.md deleted file mode 100644 index 9dae658..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/salesforce.md b/plugins/agent-auth/agent-connectors/salesforce.md deleted file mode 100644 index f0441b1..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/servicenow.md b/plugins/agent-auth/agent-connectors/servicenow.md deleted file mode 100644 index b1dc3b0..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/sharepoint.md b/plugins/agent-auth/agent-connectors/sharepoint.md deleted file mode 100644 index c2d88ae..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/slack.md b/plugins/agent-auth/agent-connectors/slack.md deleted file mode 100644 index 8a52cba..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/snowflake.md b/plugins/agent-auth/agent-connectors/snowflake.md deleted file mode 100644 index 12dba3f..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/trello.md b/plugins/agent-auth/agent-connectors/trello.md deleted file mode 100644 index 181e36c..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/zendesk.md b/plugins/agent-auth/agent-connectors/zendesk.md deleted file mode 100644 index f993da1..0000000 --- a/plugins/agent-auth/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/agent-auth/agent-connectors/zoom.md b/plugins/agent-auth/agent-connectors/zoom.md deleted file mode 100644 index 9bfdda0..0000000 --- a/plugins/agent-auth/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/agent-auth/agents/setup-scalekit.md b/plugins/agent-auth/agents/setup-scalekit.md deleted file mode 100644 index 7227f6f..0000000 --- a/plugins/agent-auth/agents/setup-scalekit.md +++ /dev/null @@ -1,43 +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@2.2.0-beta.1 - -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/agent-auth/byoc.md b/plugins/agent-auth/byoc.md deleted file mode 100644 index b88d0f6..0000000 --- a/plugins/agent-auth/byoc.md +++ /dev/null @@ -1,56 +0,0 @@ -# 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. - -## Why bring your own credentials? - -### Complete whitelabeling - -When you use your own OAuth credentials, users see your application name and branding throughout the authentication flow instead of Scalekit's: - -- **OAuth consent screens** display your app name and logo -- **Authorization URLs** use your domain and branding -- **Email notifications** from providers reference your application -- **User permissions** are granted directly to your application - -### Enhanced security and control - -- **Direct relationship**: Maintain direct OAuth relationships with providers -- **Full audit trail**: Complete visibility into authentication flows and user consent -- **Custom verification**: Complete OAuth app verification with your company details -- **Compliance control**: Meet regulatory requirements for direct provider relationships - -### Production-grade capabilities - -- **Dedicated quotas**: Avoid sharing rate limits with other Scalekit customers -- **Higher limits**: Access provider-specific quota increases for your application -- **Priority support**: Direct support relationships with OAuth providers -- **Custom integrations**: Build provider-specific customizations - -## How BYOC works - -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 - -## Setting up BYOC - -1. Log in to the Scalekit Dashboard and click **Edit Connection** for the connector you want to configure. -2. Choose the option **"Use your own credentials"** and enter the **Client ID** and **Client Secret** obtained from the provider. -3. Copy the **Redirect URL** shown in the dashboard and add it as one of the authorized redirect URIs in the provider's developer console. - -> In the dashboard: Edit Connection → "Use your own credentials" → enter Client ID and Client Secret → copy the Redirect URL shown and add it to your OAuth app's authorized redirect URIs. - -## Migration from shared credentials - -If you're currently using Scalekit's shared credentials and want to migrate to BYOC: - -> **Note:** Migration considerations: -> - Users will need to re-authenticate with your OAuth applications -> - OAuth consent screens will change to show your branding -> - 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. diff --git a/plugins/agent-auth/connected-accounts.md b/plugins/agent-auth/connected-accounts.md deleted file mode 100644 index c32f761..0000000 --- a/plugins/agent-auth/connected-accounts.md +++ /dev/null @@ -1,559 +0,0 @@ -# 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). - -## What are connected accounts? - -Connected accounts are the runtime instances that link your users to their third-party application accounts. Each connected account: - -- **Links to a connection**: Uses a pre-configured connection for authentication -- **Has a unique identifier**: Associated with a user_id, org_id, or custom identifier -- **Maintains auth state**: Tracks whether the user has completed authentication -- **Stores tokens**: Securely holds access tokens and refresh tokens -- **Manages permissions**: Tracks granted scopes and permissions - -## Connected account lifecycle - -Connected accounts go through several states during their lifecycle: - -### Account states - -1. **Pending**: Account created but user hasn't completed authentication -2. **Active**: User has authenticated and tokens are valid -3. **Expired**: Tokens have expired and need refresh -4. **Revoked**: User has revoked access to the application -5. **Error**: Account has authentication or configuration errors -6. **Suspended**: Account temporarily disabled - -### State transitions - -```d2 -direction: right - -A: Pending -B: Active -C: Expired -D: Revoked -E: Error -F: Suspended - -A -> B -B -> C -C -> B -B -> D -B -> E -E -> B -B -> F -F -> B -``` - -## Creating connected accounts - -### Using the dashboard - -1. Navigate to connected accounts in your Agent Auth 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) -5. Configure settings such as scopes and permissions -6. Generate auth URL for the user to complete authentication -7. Monitor status until user completes the flow - -### Using the API - -Create connected accounts programmatically: - -**cURL** - -```bash -curl -X POST "https://api.scalekit.com/v1/connect/accounts" \ - -H "Authorization: Bearer YOUR_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "connection_id": "conn_gmail_oauth", - "identifier": "user_123", - "identifier_type": "user_id", - "scopes": ["https://www.googleapis.com/auth/gmail.send"], - "settings": { - "auto_refresh": true, - "expires_in": 3600 - } - }' -``` - -**JavaScript** - -```javascript -const connectedAccount = await agentConnect.accounts.create({ - connection_id: 'conn_gmail_oauth', - identifier: 'user_123', - identifier_type: 'user_id', - scopes: ['https://www.googleapis.com/auth/gmail.send'], - settings: { - auto_refresh: true, - expires_in: 3600 - } -}); - -// Generate authorization URL for user -const authUrl = await agentConnect.accounts.getAuthUrl(connectedAccount.id); -``` - -**Python** - -```python -connected_account = agent_connect.accounts.create( - connection_id='conn_gmail_oauth', - identifier='user_123', - identifier_type='user_id', - scopes=['https://www.googleapis.com/auth/gmail.send'], - settings={ - 'auto_refresh': True, - 'expires_in': 3600 - } -) - -# Generate authorization URL for user -auth_url = agent_connect.accounts.get_auth_url(connected_account.id) -``` - -## Authentication flow - -### OAuth 2.0 flow - -For OAuth connections, connected accounts follow the standard OAuth flow: - -1. Create connected account with pending status -2. Generate authorization URL for the user -3. User completes OAuth flow with the third-party provider -4. Provider redirects back with authorization code -5. Exchange code for tokens and update account status -6. Account becomes active and ready for tool execution - -### Authorization URL generation - -Generate URLs for users to complete authentication: - -```javascript -// Generate authorization URL -const authUrl = await agentConnect.accounts.getAuthUrl('account_id', { - state: 'custom_state_value', - redirect_uri: 'https://your-app.com/callback', - scopes: ['additional_scope'] -}); - -// Example generated URL -// https://accounts.google.com/oauth/authorize? -// client_id=your_client_id& -// redirect_uri=https://your-app.com/callback& -// scope=https://www.googleapis.com/auth/gmail.send& -// response_type=code& -// state=custom_state_value -``` - -### Handling callbacks - -Process the OAuth callback to complete authentication: - -**JavaScript** - -```javascript -// Handle OAuth callback -app.get('/callback', async (req, res) => { - const { code, state, error } = req.query; - - if (error) { - // Handle OAuth error - return res.status(400).json({ error: error }); - } - - try { - // Exchange code for tokens - const result = await agentConnect.accounts.exchangeCode( - 'account_id', - code, - state - ); - - // Account is now active - res.json({ status: 'success', account: result }); - } catch (err) { - res.status(500).json({ error: err.message }); - } -}); -``` - -**Python** - -```python -@app.route('/callback') -def handle_callback(): - code = request.args.get('code') - state = request.args.get('state') - error = request.args.get('error') - - if error: - return jsonify({'error': error}), 400 - - try: - # Exchange code for tokens - result = agent_connect.accounts.exchange_code( - 'account_id', - code, - state - ) - - # Account is now active - return jsonify({'status': 'success', 'account': result}) - except Exception as e: - return jsonify({'error': str(e)}), 500 -``` - -## Managing connected accounts - -### Account information - -Retrieve connected account details: - -```javascript -const account = await agentConnect.accounts.get('account_id'); - -// Account object structure -{ - "id": "account_123", - "connection_id": "conn_gmail_oauth", - "identifier": "user_123", - "identifier_type": "user_id", - "provider": "gmail", - "status": "active", - "scopes": ["https://www.googleapis.com/auth/gmail.send"], - "created_at": "2024-01-15T10:30:00Z", - "updated_at": "2024-01-15T10:45:00Z", - "expires_at": "2024-01-15T11:45:00Z", - "metadata": { - "user_email": "user@example.com", - "provider_account_id": "google_user_123" - } -} -``` - -### Token management - -Connected accounts automatically handle token lifecycle: - -**Automatic token refresh:** -- Tokens are refreshed automatically before expiration -- Refresh happens transparently during tool execution -- Failed refresh attempts update account status to expired - -**Manual token refresh:** -```javascript -// Manually refresh tokens -const refreshed = await agentConnect.accounts.refreshTokens('account_id'); - -// Check token status -const tokenStatus = await agentConnect.accounts.getTokenStatus('account_id'); -``` - -For detailed token management including automatic refresh, error handling, and security, see [token-management.md](token-management.md). - -### Account status monitoring - -Monitor account authentication status: - -```javascript -// Check account status -const status = await agentConnect.accounts.getStatus('account_id'); - -// Possible status values: -// - pending: Waiting for user authentication -// - active: Authenticated and ready -// - expired: Tokens expired, needs refresh -// - revoked: User revoked access -// - error: Authentication error -// - suspended: Account temporarily disabled -``` - -## Account permissions and scopes - -### Scope management - -Connected accounts can have different scopes than their parent connection: - -```javascript -// Create account with custom scopes -const account = await agentConnect.accounts.create({ - connection_id: 'conn_gmail_oauth', - identifier: 'user_123', - scopes: [ - 'https://www.googleapis.com/auth/gmail.readonly', // Read-only access - 'https://www.googleapis.com/auth/gmail.send' // Send emails - ] -}); - -// Update account scopes (requires re-authentication) -await agentConnect.accounts.updateScopes('account_id', { - scopes: ['https://www.googleapis.com/auth/gmail.modify'] -}); -``` - -### Permission validation - -Verify account permissions before tool execution: - -```javascript -// Check if account has required permissions -const hasPermission = await agentConnect.accounts.hasPermission( - 'account_id', - 'https://www.googleapis.com/auth/gmail.send' -); - -// Get all granted permissions -const permissions = await agentConnect.accounts.getPermissions('account_id'); -``` - -## Account metadata and settings - -### Custom metadata - -Store additional information with connected accounts: - -```javascript -// Add custom metadata -await agentConnect.accounts.updateMetadata('account_id', { - user_email: 'user@example.com', - department: 'engineering', - preferences: { - notification_settings: 'email', - timezone: 'UTC' - } -}); - -// Retrieve metadata -const metadata = await agentConnect.accounts.getMetadata('account_id'); -``` - -### Account settings - -Configure account-specific settings: - -```javascript -// Update account settings -await agentConnect.accounts.updateSettings('account_id', { - auto_refresh: true, - rate_limit: 100, - timeout: 30, - retry_attempts: 3 -}); -``` - -## Bulk operations - -### Managing multiple accounts - -Handle multiple connected accounts efficiently: - -```javascript -// Create multiple accounts -const accounts = await agentConnect.accounts.createBulk([ - { - connection_id: 'conn_gmail_oauth', - identifier: 'user_1', - identifier_type: 'user_id' - }, - { - connection_id: 'conn_gmail_oauth', - identifier: 'user_2', - identifier_type: 'user_id' - } -]); - -// Get accounts by connection -const gmailAccounts = await agentConnect.accounts.list({ - connection_id: 'conn_gmail_oauth' -}); - -// Get accounts by status -const activeAccounts = await agentConnect.accounts.list({ - status: 'active' -}); -``` - -### Batch operations - -Perform operations on multiple accounts: - -```javascript -// Refresh tokens for multiple accounts -const refreshResults = await agentConnect.accounts.refreshTokensBulk([ - 'account_1', - 'account_2', - 'account_3' -]); - -// Update settings for multiple accounts -await agentConnect.accounts.updateSettingsBulk([ - 'account_1', - 'account_2' -], { - auto_refresh: true, - rate_limit: 150 -}); -``` - -## Error handling - -### Common errors - -Handle common connected account errors: - -```javascript -try { - const account = await agentConnect.accounts.get('account_id'); -} catch (error) { - switch (error.code) { - case 'ACCOUNT_NOT_FOUND': - // Account doesn't exist - break; - case 'ACCOUNT_EXPIRED': - // Tokens expired, refresh needed - break; - case 'ACCOUNT_REVOKED': - // User revoked access - break; - case 'INVALID_PERMISSIONS': - // Insufficient permissions - break; - default: - // Other errors - break; - } -} -``` - -### Error recovery - -Implement error recovery strategies: - -1. Detect error - Monitor account status and API responses -2. Classify error - Determine if error is recoverable -3. Attempt recovery - Try token refresh or re-authentication -4. Notify user - Inform user if manual action is required -5. Update status - Update account status based on recovery result - -## Security considerations - -### Token security - -Protect user tokens and credentials: - -- **Encryption**: All tokens are encrypted at rest and in transit -- **Token rotation**: Implement regular token rotation -- **Access logging**: Log all token access and usage -- **Secure storage**: Use secure storage mechanisms for tokens - -### Permission management - -Follow principle of least privilege: - -- **Minimal scopes**: Request only necessary permissions -- **Scope validation**: Verify permissions before tool execution -- **Regular audit**: Review granted permissions regularly -- **User consent**: Ensure users understand granted permissions - -### Account isolation - -Ensure proper account isolation: - -- **Tenant isolation**: Separate accounts by tenant/organization -- **User isolation**: Prevent cross-user data access -- **Connection isolation**: Separate different connection types -- **Audit trail**: Maintain detailed audit logs - -## Monitoring and analytics - -### Account health monitoring - -Monitor connected account health: - -```javascript -// Get account health metrics -const health = await agentConnect.accounts.getHealth('account_id'); - -// Health metrics include: -// - Token expiry status -// - Authentication success rate -// - API error rates -// - Last successful authentication -``` - -### Usage analytics - -Track account usage patterns: - -```javascript -// Get account usage statistics -const usage = await agentConnect.accounts.getUsage('account_id', { - start_date: '2024-01-01', - end_date: '2024-01-31' -}); - -// Usage data includes: -// - Total tool executions -// - API requests made -// - Error rates -// - Most used tools -``` - -## Best practices - -### Account lifecycle management - -- **Regular cleanup**: Remove unused or expired accounts -- **Status monitoring**: Monitor account status changes -- **Proactive refresh**: Refresh tokens before expiration -- **User notifications**: Notify users of authentication issues - -### Performance optimization - -- **Connection pooling**: Reuse connections efficiently -- **Token caching**: Cache tokens appropriately -- **Batch operations**: Use bulk operations when possible -- **Async processing**: Handle authentication flows asynchronously - -### User experience - -- **Clear error messages**: Provide helpful error messages to users -- **Seamless re-auth**: Make re-authentication flows smooth -- **Status visibility**: Show users their connection status -- **Easy revocation**: Allow users to easily revoke access - -## Testing connected accounts - -### Development testing - -Test connected accounts in development: - -```javascript -// Create test account -const testAccount = await agentConnect.accounts.create({ - connection_id: 'conn_test_provider', - identifier: 'test_user', - identifier_type: 'user_id', - test_mode: true -}); - -// Use test tokens -const testTokens = await agentConnect.accounts.getTestTokens('account_id'); -``` - -### Integration testing - -Test authentication flows: - -1. Create test connection with test credentials -2. Create connected account with test identifier -3. Generate auth URL and complete OAuth flow -4. Verify account status becomes active -5. Test tool execution with the account -6. Test token refresh and error scenarios diff --git a/plugins/agent-auth/hooks/hooks.json b/plugins/agent-auth/hooks/hooks.json deleted file mode 100644 index 5eb61b3..0000000 --- a/plugins/agent-auth/hooks/hooks.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "description": "Usage beacon for Scalekit agent-auth plugin", - "hooks": { - "PostToolUse": [ - { - "matcher": ".*", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh agent-auth post_tool", - "timeout": 10 - } - ] - } - ], - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh agent-auth stop", - "timeout": 10 - } - ] - } - ] - } -} diff --git a/plugins/agent-auth/references/agent-connectors/README.md b/plugins/agent-auth/references/agent-connectors/README.md deleted file mode 100644 index a91420e..0000000 --- a/plugins/agent-auth/references/agent-connectors/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# Agent Connectors Reference - -This directory contains documentation for all supported agent connectors in the Scalekit Agent Auth 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 | - -## 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 - -All connectors support OAuth 2.0 authentication through the Agent Auth platform. 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/agent-auth/references/agent-connectors/airtable.md b/plugins/agent-auth/references/agent-connectors/airtable.md deleted file mode 100644 index f3acf0b..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/asana.md b/plugins/agent-auth/references/agent-connectors/asana.md deleted file mode 100644 index cea2fc7..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/attention.md b/plugins/agent-auth/references/agent-connectors/attention.md deleted file mode 100644 index 9af975e..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/bigquery.md b/plugins/agent-auth/references/agent-connectors/bigquery.md deleted file mode 100644 index 64b8a33..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/chorus.md b/plugins/agent-auth/references/agent-connectors/chorus.md deleted file mode 100644 index b57dd23..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/clari_copilot.md b/plugins/agent-auth/references/agent-connectors/clari_copilot.md deleted file mode 100644 index 2949e94..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/clickup.md b/plugins/agent-auth/references/agent-connectors/clickup.md deleted file mode 100644 index dd7d216..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/confluence.md b/plugins/agent-auth/references/agent-connectors/confluence.md deleted file mode 100644 index 96d699f..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/dropbox.md b/plugins/agent-auth/references/agent-connectors/dropbox.md deleted file mode 100644 index 1bfe1d3..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/fathom.md b/plugins/agent-auth/references/agent-connectors/fathom.md deleted file mode 100644 index 3408c98..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/freshdesk.md b/plugins/agent-auth/references/agent-connectors/freshdesk.md deleted file mode 100644 index 424a753..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/github.md b/plugins/agent-auth/references/agent-connectors/github.md deleted file mode 100644 index c9cf234..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/gmail.md b/plugins/agent-auth/references/agent-connectors/gmail.md deleted file mode 100644 index d668c75..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/gong.md b/plugins/agent-auth/references/agent-connectors/gong.md deleted file mode 100644 index 26a36e3..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/google_ads.md b/plugins/agent-auth/references/agent-connectors/google_ads.md deleted file mode 100644 index 9718639..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/google_docs.md b/plugins/agent-auth/references/agent-connectors/google_docs.md deleted file mode 100644 index e790d5d..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/google_drive.md b/plugins/agent-auth/references/agent-connectors/google_drive.md deleted file mode 100644 index ef6120e..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/google_forms.md b/plugins/agent-auth/references/agent-connectors/google_forms.md deleted file mode 100644 index fde9e1d..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/google_meets.md b/plugins/agent-auth/references/agent-connectors/google_meets.md deleted file mode 100644 index 814ff7b..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/google_sheets.md b/plugins/agent-auth/references/agent-connectors/google_sheets.md deleted file mode 100644 index 8df9a54..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/google_slides.md b/plugins/agent-auth/references/agent-connectors/google_slides.md deleted file mode 100644 index 242ef37..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/googlecalendar.md b/plugins/agent-auth/references/agent-connectors/googlecalendar.md deleted file mode 100644 index 8168441..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/hubspot.md b/plugins/agent-auth/references/agent-connectors/hubspot.md deleted file mode 100644 index ac44f2c..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/intercom.md b/plugins/agent-auth/references/agent-connectors/intercom.md deleted file mode 100644 index 77ef5d8..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/jira.md b/plugins/agent-auth/references/agent-connectors/jira.md deleted file mode 100644 index 1675086..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/linear.md b/plugins/agent-auth/references/agent-connectors/linear.md deleted file mode 100644 index 9b0f877..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/microsoft_excel.md b/plugins/agent-auth/references/agent-connectors/microsoft_excel.md deleted file mode 100644 index 2fa4c19..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/microsoft_teams.md b/plugins/agent-auth/references/agent-connectors/microsoft_teams.md deleted file mode 100644 index 85790a1..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/microsoft_word.md b/plugins/agent-auth/references/agent-connectors/microsoft_word.md deleted file mode 100644 index 7b2d530..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/monday.md b/plugins/agent-auth/references/agent-connectors/monday.md deleted file mode 100644 index 6cc8ab0..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/notion.md b/plugins/agent-auth/references/agent-connectors/notion.md deleted file mode 100644 index 22b58d5..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/onedrive.md b/plugins/agent-auth/references/agent-connectors/onedrive.md deleted file mode 100644 index 601c43f..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/onenote.md b/plugins/agent-auth/references/agent-connectors/onenote.md deleted file mode 100644 index 47a835b..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/outlook.md b/plugins/agent-auth/references/agent-connectors/outlook.md deleted file mode 100644 index 9dae658..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/salesforce.md b/plugins/agent-auth/references/agent-connectors/salesforce.md deleted file mode 100644 index f0441b1..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/servicenow.md b/plugins/agent-auth/references/agent-connectors/servicenow.md deleted file mode 100644 index b1dc3b0..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/sharepoint.md b/plugins/agent-auth/references/agent-connectors/sharepoint.md deleted file mode 100644 index c2d88ae..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/slack.md b/plugins/agent-auth/references/agent-connectors/slack.md deleted file mode 100644 index 8a52cba..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/snowflake.md b/plugins/agent-auth/references/agent-connectors/snowflake.md deleted file mode 100644 index 12dba3f..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/trello.md b/plugins/agent-auth/references/agent-connectors/trello.md deleted file mode 100644 index 181e36c..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/zendesk.md b/plugins/agent-auth/references/agent-connectors/zendesk.md deleted file mode 100644 index f993da1..0000000 --- a/plugins/agent-auth/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/agent-auth/references/agent-connectors/zoom.md b/plugins/agent-auth/references/agent-connectors/zoom.md deleted file mode 100644 index 9bfdda0..0000000 --- a/plugins/agent-auth/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/agent-auth/references/code-samples.md b/plugins/agent-auth/references/code-samples.md deleted file mode 100644 index 537ca57..0000000 --- a/plugins/agent-auth/references/code-samples.md +++ /dev/null @@ -1,537 +0,0 @@ -# Code Samples - -This reference provides implementation examples for integrating Scalekit Agent Auth across different frameworks, languages, and use cases. - -## Quick Start Guide - -Choose the right sample based on your needs: - -| Your Goal | Recommended Sample | Framework | Complexity | -|-----------|-------------------|-----------|------------| -| **I just want to see working code** | [google-adk-agent-example](#google-adk-framework-samples) | Google ADK | ⭐ Simple (1 file) | -| Build a conversational AI agent | [sample-langchain-agent](#langchain-framework-samples) | LangChain | ⭐⭐ Medium | -| Use OpenAI's native tool calling | [python-connect-demos/openai](#openai-integration) | OpenAI | ⭐⭐ Medium | -| Multi-tool, reusable workflows | [python-connect-demos/mcp](#mcp-model-context-protocol) | MCP | ⭐⭐⭐ Advanced | -| Simple one-off tool calls | [python-connect-demos/direct](#direct-sdk-usage) | Direct SDK | ⭐ Simple | -| Agent-based natural language | [python-connect-demos/langchain](#langchain-integration) | LangChain | ⭐⭐ Medium | -| Custom API endpoints | [python-connect-demos/proxy](#proxyraw-api) | Proxy | ⭐⭐ Medium | - ---- - -## LangChain Framework Samples - -### Repository: [scalekit-inc/sample-langchain-agent](https://github.com/scalekit-inc/sample-langchain-agent.git) - -**Overview:** Conversational AI agent that can access external APIs (Gmail, etc.) via OAuth, hold multi-turn conversations, and autonomously execute tool calls based on user intent. - -**Requirements:** -- Python >= 3.11 -- `scalekit-sdk-python` >= 2.4.3 -- `langchain` >= 0.1.0 -- `langchain-openai` >= 0.1.0 -- GPT-4o API access - -**Environment Variables:** -```bash -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 -``` - -**Installation:** -```bash -pip install -r requirements.txt -python main.py -``` - -**Key Code Examples:** - -**1. SDK Initialization** -```python -import os -import scalekit.client -from dotenv import load_dotenv - -load_dotenv() - -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 = scalekit.actions -``` - -**2. OAuth Authorization Flow** -```python -user_name = "user-1234" -connection_names = ["gmail"] - -# Generate authorization links for each service -for conn_name in connection_names: - link = actions.get_authorization_link( - identifier=user_name, - connection_name=conn_name - ) - print(f"🔗 Authorize {conn_name}: {link.link}") - input("✅ Press Enter after authorization...") -``` - -**3. Tool Discovery & Agent Creation** -```python -from langchain.agents import create_tool_calling_agent, AgentExecutor -from langchain_core.prompts import ChatPromptTemplate -from langchain_openai import ChatOpenAI - -# Discover tools from Scalekit -scalekit_tools = actions.langchain.get_tools( - identifier=user_name, - connection_names=connection_names, - page_size=100 -) - -# Create agent -llm = ChatOpenAI(model="gpt-4o", temperature=0.1) -prompt = ChatPromptTemplate.from_messages([ - ("system", "You are a helpful conversational assistant with access to tools."), - ("placeholder", "{chat_history}"), - ("human", "{input}"), - ("placeholder", "{agent_scratchpad}"), -]) - -agent = create_tool_calling_agent(llm, scalekit_tools, prompt) -agent_executor = AgentExecutor( - agent=agent, - tools=scalekit_tools, - verbose=False, - handle_parsing_errors=True -) -``` - -**4. Execute with Chat History** -```python -response = agent_executor.invoke({ - "input": "fetch my first unread email", - "chat_history": chat_history -}) -``` - -**Use Cases:** -- Email management (fetch, read, search) -- Multi-turn conversations with context -- OAuth flow for multiple services -- Tool discovery from Scalekit - ---- - -## Google ADK Framework Samples - -### 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. - -**Requirements:** -- Python >= 3.11 -- `google-adk` >= 1.15.1 -- `scalekit-sdk-python` >= 2.4.6 -- Google API key - -**Environment Variables:** -```bash -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 -``` - -**Installation:** -```bash -pip install -r requirements.txt -adk run scalekit_tool_agent -``` - -**Key Code Examples:** - -**1. Complete Agent Implementation** -```python -from google.adk.agents import Agent -import scalekit.client -import os - -identifier = "user-1234" -connection_name = "gmail" - -# Initialize Scalekit client -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") -) - -# Generate OAuth authorization link -auth = client.actions.get_authorization_link( - identifier=identifier, - connection_name=connection_name -) - -print("📧 Gmail Authorization Required") -print(f"🔗 Visit this URL to authorize the gmail connection:\n\n {auth.link}\n") -input("✅ Press Enter after authorization...") - -# Generate Gmail tools with authenticated access -gmail_tools = client.actions.google.get_tools( - providers=["GMAIL"], - identifier=identifier, - page_size=100 -) - -# Create ADK agent -root_agent = Agent( - name="scalekit_tool_agent", - model="gemini-2.5-flash", - description="Tool agent for Gmail and general questions", - instruction=( - "You are a helpful assistant that can use gmail tools to answer " - "user questions based on their emails and general questions." - ), - tools=gmail_tools, -) -``` - -**Use Cases:** -- Read and analyze user emails -- Search inbox content -- Natural language email queries -- Quick start pattern for new developers - ---- - -## Python Integration Patterns - -### Repository: [scalekit-inc/python-connect-demos](https://github.com/scalekit-inc/python-connect-demos.git) - -Comprehensive collection of integration patterns organized by framework and use case. - -#### Direct SDK Usage - -**Pattern:** Simple one-off tool calls with direct SDK methods. - -**Gmail - Fetch Emails** -```python -response = connect.execute_tool( - tool_name="gmail_fetch_mails", - identifier="user@example.com", - tool_input={ - "max_results": 10, - "query": "is:unread" - } -) -``` - -**Slack - Send Message** -```python -response = connect.execute_tool( - tool_name="slack_send_message", - identifier="user_id", - tool_input={ - "channel": "#connect", - "text": "Hello from demo!" - } -) -``` - -**Salesforce - SOQL Query** -```python -response = connect.execute_tool( - tool_name="salesforce_soql_execute", - identifier="user_id", - tool_input={ - "soql_query": "SELECT Id, Name FROM Account" - } -) -``` - -**Pre/Post Modifiers** (reduce token usage) -```python -from scalekit.connect.types import ToolInput, ToolOutput - -@connect.pre_modifier(tool_names=["gmail_fetch_mails"]) -def gmail_pre_modifier(tool_input: ToolInput): - tool_input['query'] = 'is:unread' - return tool_input - -@connect.post_modifier(tool_names=["gmail_fetch_mails"]) -def gmail_post_modifier(output: ToolOutput): - return {"response": output['messages'][0]['snippet']} -``` - -#### LangChain Integration - -**Pattern:** Agent-based workflows with natural language interactions. - -**Calendar Agent** -```python -from langchain_openai import ChatOpenAI -from langchain.agents import AgentExecutor, create_openai_tools_agent - -tools = connect.langchain.get_tools( - identifier="user_123", - providers=["GOOGLECALENDAR"], - tool_names=["googlecalendar_list_events"] -) - -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": "List my events for today"}) -``` - -**Salesforce Agent** -```python -tools = connect.langchain.get_tools( - identifier="user_123", - providers=["SALESFORCE"], - tool_names=["salesforce_soql_execute"] -) -result = executor.invoke({"input": "Get all accounts with 'united' in the name"}) -``` - -**Freshdesk Customer Support Workflow** -```python -# Complete workflow: create contact, ticket, assign, reply, update status -tools = connect.langchain.get_tools( - identifier="user_123", - providers=["FRESHDESK"] -) -``` - -#### OpenAI Integration - -**Pattern:** Multi-step workflows using OpenAI's native tool calling. - -**Gmail → Summary → Slack** -```python -from openai import OpenAI - -client = OpenAI() -response = client.responses.create( - model="gpt-4.1", - input=[{"role": "user", "content": "Read emails, send summary to Slack"}], - tools=tool.ALL_TOOLS, - tool_choice="auto" -) - -# Scalekit handles tool execution -tool_response = sk.connect.handle_tool_calls( - input_messages=input_messages, - openai_response=response, - identifier="user@example.com" -) -``` - -#### MCP (Model Context Protocol) - -**Pattern:** Multi-tool, reusable config patterns for complex workflows. - -**Email Reminder Automation** -```python -from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping - -# Create MCP config -config_response = my_mcp.create_config( - name="reminder-manager", - description="Summarizes emails and creates calendar events", - connection_tool_mappings=[ - # Gmail works directly — no dashboard setup required - McpConfigConnectionToolMapping( - connection_name="gmail", - tools=[] - ), - # Google Calendar must be created in dashboard first - McpConfigConnectionToolMapping( - connection_name="MY_CALENDAR", - tools=["googlecalendar_create_event", "googlecalendar_delete_event"] - ) - ] -) - -# Get MCP instance and URL -instance = my_mcp.ensure_instance( - config_name="reminder-manager", - user_identifier="john-doe", - name="reminder-mcp-john" -) -mcp_url = instance.instance.url -``` - -#### Proxy/Raw API - -**Pattern:** Custom API endpoints not in tool catalog. - -**Google Drive Operations** -```python -# Upload file -upload_response = client.actions.request( - connection_name="GOOGLE_DRIVE", - identifier="user@example.com", - path="/upload/drive/v3/files", - method="POST", - query_params={"uploadType": "media", "name": "demo.pdf"}, - form_data=file_bytes, - headers={"Content-Type": "application/pdf"} -) -``` - -#### Static Auth - -**Pattern:** API key or Basic authentication (no OAuth). - -**API Key Auth** -```python -response = actions.get_or_create_connected_account( - connection_name="fathom", - identifier="your_user", - authorization_details={ - "static_auth": { - "api_key": "your-api-key" - } - } -) -``` - -**Basic Auth** -```python -response = actions.get_or_create_connected_account( - connection_name="gong", - identifier="your_user", - authorization_details={ - "static_auth": { - "domain": "account.gong.io", - "username": "api-key", - "password": "api-secret" - } - } -) -``` - -**Freshdesk (Domain + Username + Password)** -```python -response = connect.execute_tool( - tool_name="freshdesk_create_ticket", - identifier="user_id", - tool_input={ - "name": "John Doe", - "email": "john@example.com", - "subject": "Website server down", - "description": "
Site has crashed
", - "type": "Problem", - "tags": ["urgent", "server"] - } -) -``` - ---- - -## Provider Reference - -| Provider | Sample Repo | Demo File | Auth Type | -|----------|-------------|-----------|-----------| -| **Attention** | python-connect-demos | `direct/attention.py` | API Key | -| **Chorus** | python-connect-demos | `direct/chorus.py` | Basic | -| **Clari** | python-connect-demos | `direct/clari.py` | Basic | -| **Fathom** | python-connect-demos | `direct/fathom.py` | API Key | -| **Freshdesk** | python-connect-demos | `static/freshdesk.py`, `langchain/freshdesk.py` | Basic | -| **Gmail** | sample-langchain-agent, google-adk-agent-example, python-connect-demos | `main.py`, `agent.py`, `direct/gmail.py` | OAuth | -| **Gong** | python-connect-demos | `direct/gong.py` | Basic | -| **Google Calendar** | python-connect-demos | `langchain/main.py`, `mcp/main.py` | OAuth | -| **Google Drive** | python-connect-demos | `proxy/gdrive.py` | OAuth | -| **HubSpot** | python-connect-demos | `langchain/hubspot.py` | OAuth | -| **Salesforce** | python-connect-demos | `direct/salesforce.py`, `langchain/salesforce.py` | OAuth | -| **Slack** | python-connect-demos, openai integration | `direct/slack.py` | OAuth | -| **Snowflake** | python-connect-demos | `direct/snowflake.py` | OAuth | - ---- - -## Common Patterns - -### Token Management - -**Extract tokens from connected account:** -```python -account = actions.get_connected_account(connection_name, identifier) -tokens = account.connected_account.authorization_details["oauth_token"] -access_token = tokens["access_token"] -refresh_token = tokens["refresh_token"] -``` - -**Check connection status:** -```python -response = connect.get_connected_account(connection_name, identifier) -if response.connected_account.status != "ACTIVE": - # Re-authenticate -``` - -### Error Handling - -**Check connection before executing tools:** -```python -def authenticate_tool(connect, connection_name, identifier): - response = connect.get_connected_account(connection_name, identifier) - if response.connected_account.status != "ACTIVE": - link = connect.get_authorization_link( - connection_name=connection_name, - identifier=identifier - ) - print(f"Authorize: {link.link}") - input("Press Enter after authorizing...") - return True -``` - -### Configuration - -**Environment setup:** -```python -import os -from dotenv import load_dotenv - -load_dotenv() - -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"), -) -``` - ---- - -## Getting Help - -### Scalekit Documentation -- **Official Docs:** [docs.scalekit.com](https://docs.scalekit.com) -- **Scalekit Dashboard:** [app.scalekit.com](https://app.scalekit.com) -- **API Credentials:** Dashboard → Developers → Settings → API Credentials - -### Sample Repositories -- **LangChain Agent:** [github.com/scalekit-inc/sample-langchain-agent](https://github.com/scalekit-inc/sample-langchain-agent) -- **Google ADK Agent:** [github.com/scalekit-inc/google-adk-agent-example](https://github.com/scalekit-inc/google-adk-agent-example) -- **Python Connect Demos:** [github.com/scalekit-inc/python-connect-demos](https://github.com/scalekit-inc/python-connect-demos) - -### Framework Documentation -- **LangChain:** [python.langchain.com](https://python.langchain.com) -- **Google ADK:** [google.github.io/adk-docs](https://google.github.io/adk-docs) -- **MCP Protocol:** [spec.modelcontextprotocol.io](https://spec.modelcontextprotocol.io) -- **OpenAI API:** [platform.openai.com/docs](https://platform.openai.com/docs) - -### SDK Documentation -- **Python SDK:** [github.com/scalekit-inc/scalekit-sdk-python](https://github.com/scalekit-inc/scalekit-sdk-python) -- **Node SDK:** [github.com/scalekit-inc/scalekit-sdk-node](https://github.com/scalekit-inc/scalekit-sdk-node) - -### 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` -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/agent-auth/references/connections.md b/plugins/agent-auth/references/connections.md deleted file mode 100644 index 118dc1f..0000000 --- a/plugins/agent-auth/references/connections.md +++ /dev/null @@ -1,434 +0,0 @@ -# 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. - -## Table of Contents - -- [What are connections?](#what-are-connections) -- [Connection types](#connection-types) -- [Creating connections](#creating-connections) -- [Connection configuration](#connection-configuration) -- [Managing connections](#managing-connections) -- [Security considerations](#security-considerations) -- [Troubleshooting connections](#troubleshooting-connections) -- [Best practices](#best-practices) -- [Connection templates](#connection-templates) - ---- - -## What are connections? - -Connections serve as the bridge between your Agent Auth setup and third-party providers. They contain: - -- **Authentication credentials** (OAuth client ID/secret, API keys, etc.) -- **Configuration settings** (scopes, permissions, endpoints) -- **Tool definitions** and their parameters -- **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: - -### OAuth 2.0 connections - -Most modern APIs use OAuth 2.0 for secure authentication: - -```json -{ - "connection_id": "conn_gmail_oauth", - "provider": "gmail", - "auth_type": "oauth2", - "credentials": { - "client_id": "your-client-id", - "client_secret": "your-client-secret", - "redirect_uri": "https://your-app.com/callback" - }, - "scopes": ["https://www.googleapis.com/auth/gmail.send"], - "settings": { - "auto_refresh": true, - "expires_in": 3600 - } -} -``` - -### API key connections - -Simple authentication using static API keys: - -```json -{ - "connection_id": "conn_jira_api", - "provider": "jira", - "auth_type": "api_key", - "credentials": { - "api_key": "your-api-key", - "base_url": "https://your-domain.atlassian.net" - }, - "settings": { - "rate_limit": 100, - "timeout": 30 - } -} -``` - -### Custom authentication - -For providers with unique authentication requirements: - -```json -{ - "connection_id": "conn_custom_auth", - "provider": "custom_provider", - "auth_type": "custom", - "credentials": { - "username": "your-username", - "password": "your-password", - "token": "bearer-token" - }, - "settings": { - "auth_endpoint": "https://api.provider.com/auth", - "refresh_endpoint": "https://api.provider.com/refresh" - } -} -``` - -## Creating connections - -> **Important**: Gmail is the only connector that does not require dashboard or API connection setup. Gmail can be used directly with `connection_name="gmail"` without any pre-configuration. All other connectors must be created via dashboard or API before use. - -> **Note**: The **Connection Name** you create in the dashboard or API is exactly what you use as the `connection_name` parameter in your SDK code. They must match exactly. - -### Using the dashboard - -1. **Navigate to connections** in your Agent Auth dashboard -2. **Select provider** from the list of available providers -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 -6. **Test connection** to verify configuration -7. **Save connection** for use with connected accounts - -### Using the API - -Create connections programmatically using the Agent Auth API: - -**cURL:** - -```bash -curl -X POST "https://api.scalekit.com/v1/connect/connections" \ - -H "Authorization: Bearer YOUR_API_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{ - "provider": "gmail", - "auth_type": "oauth2", - "credentials": { - "client_id": "your-client-id", - "client_secret": "your-client-secret" - }, - "scopes": ["https://www.googleapis.com/auth/gmail.send"], - "settings": { - "auto_refresh": true - } - }' -``` - -**JavaScript:** - -```javascript -const connection = await agentConnect.connections.create({ - provider: 'gmail', - auth_type: 'oauth2', - credentials: { - client_id: 'your-client-id', - client_secret: 'your-client-secret' - }, - scopes: ['https://www.googleapis.com/auth/gmail.send'], - settings: { - auto_refresh: true - } -}); -``` - -**Python:** - -```python -connection = agent_connect.connections.create( - provider='gmail', - auth_type='oauth2', - credentials={ - 'client_id': 'your-client-id', - 'client_secret': 'your-client-secret' - }, - scopes=['https://www.googleapis.com/auth/gmail.send'], - settings={ - 'auto_refresh': True - } -) -``` - -## Connection configuration - -### Authentication settings - -Configure authentication based on your provider's requirements: - -**OAuth 2.0 settings:** -- **Client ID**: Your OAuth application's client identifier -- **Client Secret**: Your OAuth application's client secret -- **Redirect URI**: Where users return after authorization -- **Scopes**: Permissions your application requests -- **Authorization URL**: Provider's OAuth authorization endpoint -- **Token URL**: Provider's token exchange endpoint - -**API Key settings:** -- **API Key**: Your provider-issued API key -- **Base URL**: Provider's API base URL -- **Authentication Header**: How the API key is sent (header, query param) -- **Key Prefix**: Any prefix required (e.g., "Bearer ", "API-Key ") - -### Scopes and permissions - -Define what your application can access: - -**Note:** Scopes determine what data and actions your application can access on behalf of users. Request only the minimum scopes necessary for your use case. - -**Common scope patterns:** - -- **Read-only**: Access to view data only -- **Read-write**: Access to view and modify data -- **Admin**: Full administrative access -- **Specific resources**: Access to particular data types - -**Example scopes for popular providers:** - -```javascript -// Gmail scopes -const gmailScopes = [ - 'https://www.googleapis.com/auth/gmail.readonly', // Read emails - 'https://www.googleapis.com/auth/gmail.send', // Send emails - 'https://www.googleapis.com/auth/gmail.modify' // Modify emails -]; - -// Slack scopes -const slackScopes = [ - 'channels:read', // Read channel information - 'chat:write', // Send messages - 'files:read' // Read file information -]; - -// Jira scopes -const jiraScopes = [ - 'read:jira-work', // Read issues and projects - 'write:jira-work', // Create and update issues - 'manage:jira-project' // Manage projects -]; -``` - -### Rate limiting and throttling - -Configure how your connection handles API rate limits: - -```json -{ - "rate_limiting": { - "requests_per_minute": 100, - "requests_per_hour": 1000, - "burst_limit": 10, - "backoff_strategy": "exponential", - "retry_attempts": 3 - } -} -``` - -### Connection settings - -Customize connection behavior: - -**Token management:** -- **Auto-refresh**: Automatically refresh expired tokens -- **Token expiry**: How long tokens remain valid -- **Refresh buffer**: Refresh tokens before expiry -- **Token storage**: Where tokens are securely stored - -**Request settings:** -- **Timeout**: Maximum request duration -- **Retry policy**: How failed requests are retried -- **User agent**: Custom user agent string -- **Base headers**: Headers sent with every request - -## Managing connections - -### Connection lifecycle - -Connections go through various states: - -1. **Draft**: Connection is being configured -2. **Active**: Connection is ready for use -3. **Testing**: Connection is being validated -4. **Inactive**: Connection is disabled -5. **Error**: Connection has configuration issues - -### Updating connections - -Modify existing connections when requirements change: - -**Dashboard:** - -1. Navigate to your connections list -2. Select the connection to modify -3. Update credentials, scopes, or settings -4. Test the updated connection -5. Save changes - -**API:** - -```javascript -const updatedConnection = await agentConnect.connections.update('conn_id', { - scopes: ['new-scope-1', 'new-scope-2'], - settings: { - rate_limit: 200, - timeout: 60 - } -}); -``` - -### Connection monitoring - -Monitor connection health and performance: - -- **Authentication status**: Track token validity and refresh cycles -- **API usage**: Monitor request volume and rate limit consumption -- **Error rates**: Track failed requests and common errors -- **Performance metrics**: Response times and throughput - -## Security considerations - -### Credential management - -Secure handling of connection credentials: - -- **Encryption**: All credentials are encrypted at rest -- **Access control**: Limit who can view or modify connections -- **Audit logging**: Track all credential access and changes -- **Rotation**: Regular credential rotation policies - -### OAuth best practices - -Follow OAuth 2.0 security best practices: - -- **Use PKCE**: Proof Key for Code Exchange for public clients -- **Validate state**: Prevent CSRF attacks with state parameters -- **Scope limitation**: Request minimal necessary scopes -- **Token storage**: Secure token storage and transmission - -### API key security - -Protect API keys properly: - -- **Environment variables**: Store keys in environment variables -- **Key rotation**: Regular key rotation schedules -- **Access logging**: Log API key usage -- **Least privilege**: Use keys with minimal required permissions - -## Troubleshooting connections - -### Common issues - -**Authentication failures:** -- Invalid credentials -- Expired tokens -- Incorrect scopes -- Provider API changes - -**Rate limiting errors:** -- Exceeded request limits -- Incorrect rate limit configuration -- Burst limit violations -- Shared quota issues - -**Configuration problems:** -- Incorrect endpoint URLs -- Missing required settings -- Invalid scope combinations -- Provider-specific requirements - -### Debugging steps - -1. **Check credentials** - Verify all credentials are correct and current -2. **Test authentication** - Use the connection test feature -3. **Review logs** - Check connection logs for error details -4. **Validate settings** - Ensure all settings match provider requirements -5. **Check provider status** - Verify provider API is operational -6. **Update configuration** - Apply any necessary fixes -7. **Re-test connection** - Confirm issues are resolved - -## Best practices - -### Connection organization - -- **Naming convention**: Use clear, descriptive connection names -- **Environment separation**: Separate connections for dev/staging/prod -- **Documentation**: Document connection purposes and configurations -- **Version control**: Track connection configuration changes - -### Security practices - -- **Regular updates**: Keep credentials and settings current -- **Monitoring**: Continuously monitor connection health -- **Backup**: Maintain secure backups of connection configurations -- **Access review**: Regularly review who has access to connections - -### Performance optimization - -- **Connection pooling**: Reuse connections efficiently -- **Caching**: Cache frequently accessed configuration data -- **Batch operations**: Group API calls when possible -- **Error handling**: Implement robust error handling and retry logic - -## Connection templates - -Use templates for common connection patterns: - -### Google Workspace template - -```json -{ - "provider": "google_workspace", - "auth_type": "oauth2", - "scopes": [ - "https://www.googleapis.com/auth/gmail.readonly", - "https://www.googleapis.com/auth/calendar.readonly" - ], - "settings": { - "auto_refresh": true, - "rate_limit": 100, - "timeout": 30 - } -} -``` - -### Slack workspace template - -```json -{ - "provider": "slack", - "auth_type": "oauth2", - "scopes": [ - "channels:read", - "chat:write", - "users:read" - ], - "settings": { - "auto_refresh": true, - "rate_limit": 50, - "timeout": 15 - } -} -``` - -Next, learn how to create and manage [Connected accounts](/agent-auth/connected-accounts) that use these connections to authenticate and execute tools for your users. - -## Related documentation - -- [byoc.md](byoc.md) — use your own OAuth credentials instead of Scalekit's shared defaults \ No newline at end of file diff --git a/plugins/agent-auth/references/providers.md b/plugins/agent-auth/references/providers.md deleted file mode 100644 index 5a4270b..0000000 --- a/plugins/agent-auth/references/providers.md +++ /dev/null @@ -1,189 +0,0 @@ -# Providers - -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. - -## What are providers? - -Providers 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 - -Agent Auth supports a wide range of popular business applications: - -| Category | Providers | -|---|---| -| **Google Workspace** | Gmail, Google Calendar, Google Drive, Google Docs, Google Sheets, Google Forms, Google Meet, Google Ads | -| **Microsoft 365** | Outlook, OneDrive, SharePoint, Microsoft Teams, Microsoft Excel, Microsoft Word, OneNote | -| **Communication** | Slack, Zoom | -| **Project Management** | Jira, Asana, Trello, Monday.com, ClickUp, Linear, Confluence | -| **CRM & Sales** | Salesforce, HubSpot, Zendesk, Freshdesk, Intercom, Gong | -| **Development** | GitHub | -| **Productivity** | Notion, Airtable, Dropbox | -| **Data & Analytics** | BigQuery, Snowflake, Fathom | -| **Service Management** | ServiceNow | - -For per-connector tool specifications, see [agent-connectors/README.md](agent-connectors/README.md). - -## Provider capabilities - -Each provider offers different capabilities based on their API and authentication model. - -### Authentication methods - -- **OAuth 2.0**: Standard method for all supported providers - -### Available tools - -Providers expose various tools that can be executed through Agent Auth: - -> **Note:** Tool availability depends on the specific provider and the user's permissions within that application. - -**Common tool categories:** - -- **Data retrieval**: Fetch emails, calendar events, files, or records -- **Data creation**: Create new items, send messages, or schedule events -- **Data modification**: Update existing records or settings -- **File operations**: Upload, download, or manage files -- **Communication**: Send notifications, messages, or alerts - -### Rate limits and quotas - -Each provider has different rate limits and quotas: - -- **API rate limits**: Requests per minute/hour limitations -- **Data quotas**: Storage or transfer limitations -- **Feature restrictions**: Premium features or enterprise-only capabilities - -## Provider configuration - -### Adding a provider - -1. **Navigate to providers** in your Agent Auth dashboard -2. **Select provider** 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 - -Each provider can be configured with: - -**Authentication settings:** -- OAuth client credentials (if using custom OAuth apps) -- API endpoint URLs -- Supported scopes and permissions -- Token refresh settings - -**Rate limiting:** -- Request throttling settings -- Backoff strategies for rate limit errors - -## Working with provider 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: - -```python -# Step 3: Fetch token (always call this immediately before the API call) -response = actions.get_connected_account( - connection_name="slack", # Replace with any connector name - identifier="user_123" -) -tokens = response.connected_account.authorization_details["oauth_token"] -access_token = tokens["access_token"] - -# Step 4: Call the provider API with the token -headers = {"Authorization": f"Bearer {access_token}"} -``` - -Scalekit automatically refreshes expired tokens on `get_connected_account` — no manual refresh logic needed. - -### Error handling - -Agent Auth normalizes provider-specific errors into consistent error responses: - -```javascript -{ - error: { - code: 'RATE_LIMIT_EXCEEDED', - message: 'Provider rate limit exceeded', - provider: 'gmail', - details: { - retryAfter: 60, - limitType: 'requests_per_minute' - } - } -} -``` - -## Provider-specific considerations - -### Google Workspace - -- **OAuth scopes**: Requires specific scopes for different Google services -- **Rate limits**: Generous limits but varies by service -- **Data access**: Supports both personal and organization data -- **Security**: Supports domain-wide delegation for enterprise - -### Microsoft 365 - -- **Authentication**: Supports both personal and work accounts -- **Graph API**: Unified API for all Microsoft services -- **Permissions**: Granular permission model -- **Compliance**: Built-in compliance and audit features - -### Slack - -- **Workspace apps**: Requires installation in each workspace -- **Bot tokens**: Different capabilities for bot vs user tokens -- **Rate limits**: Tier-based limits depending on workspace size -- **Channels**: Requires specific permissions for private channels - -### Jira - -- **Project access**: Permissions are project-specific -- **Issue types**: Different issue types have different fields -- **Workflows**: Custom workflows affect available actions - -## Best practices - -### Authentication setup - -- **Use minimal scopes**: Request only necessary permissions -- **Token refresh**: Scalekit handles this automatically — call `get_connected_account` before every API call -- **Monitor auth status**: Track connected account status; re-authorize if status is not `ACTIVE` - -### Tool execution - -- **Respect rate limits**: Implement throttling and exponential backoff for 429 errors -- **Cache results**: Cache frequently accessed data to avoid redundant API calls -- **Error recovery**: Retry transient failures; surface permanent errors to users - -## Monitoring and analytics - -### Provider health - -- **API uptime**: Track provider API availability -- **Response times**: Monitor latency for different operations -- **Error rates**: Track errors by provider and tool type -- **Rate limit usage**: Monitor quota consumption - -### Usage analytics - -- **Popular providers**: Which providers are used most -- **Tool usage**: Which tools are executed most frequently -- **User adoption**: How many users connect to each provider -- **Error patterns**: Common failure modes by provider - -## Related documentation - -- [connections.md](connections.md) — how to configure authentication credentials for a provider -- [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 -- [code-samples.md](code-samples.md) — implementation examples by framework diff --git a/plugins/agent-auth/rules/oauth-security.mdc b/plugins/agent-auth/rules/oauth-security.mdc deleted file mode 100644 index b4d77d8..0000000 --- a/plugins/agent-auth/rules/oauth-security.mdc +++ /dev/null @@ -1,21 +0,0 @@ ---- -description: OAuth security best practices for MCP servers and agents. -alwaysApply: true -globs: ["**/*.{js,ts,py,go}"] ---- - -oauth-security: - -- Always validate OAuth tokens before processing requests -- Implement proper token expiration and refresh logic -- Use HTTPS for all OAuth endpoints -- Validate token scopes before granting access -- Store tokens securely (encrypted at rest) -- Implement proper PKCE for public clients -- Validate redirect URIs to prevent open redirects - -Token handling: -- Check token expiration before each API call -- Implement automatic token refresh with proper retry logic -- Revoke tokens on logout or session end -- Log token validation failures (without exposing sensitive data) diff --git a/plugins/agentkit/.cursor-plugin/plugin.json b/plugins/agentkit/.cursor-plugin/plugin.json new file mode 100644 index 0000000..e766afa --- /dev/null +++ b/plugins/agentkit/.cursor-plugin/plugin.json @@ -0,0 +1,19 @@ +{ + "name": "agentkit", + "displayName": "AgentKit", + "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.", + "version": "2.0.0", + "author": { + "name": "Scalekit Inc.", + "email": "hi@scalekit.com" + }, + "homepage": "https://docs.scalekit.com/agentkit/overview", + "repository": "https://github.com/scalekit-inc/cursor-authstack", + "license": "MIT", + "keywords": ["scalekit", "agentkit", "agent-auth", "oauth", "connectors", "tool-calling"], + "logo": "../../assets/logo.svg", + "skills": "./skills", + "rules": "./rules", + "hooks": "./hooks/hooks.json", + "mcpServers": "./mcp.json" +} diff --git a/plugins/agentkit/.env.example b/plugins/agentkit/.env.example new file mode 100644 index 0000000..41d4cc6 --- /dev/null +++ b/plugins/agentkit/.env.example @@ -0,0 +1,5 @@ +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com +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/agentkit/README.md b/plugins/agentkit/README.md new file mode 100644 index 0000000..a4cb50a --- /dev/null +++ b/plugins/agentkit/README.md @@ -0,0 +1,59 @@ +# AgentKit for Cursor + +## Purpose + +Authentication for AI agents. This plugin brings Scalekit AgentKit into Cursor so agents can connect users to third-party apps, discover the right tools, and execute authenticated tool calls on their behalf. + +AgentKit handles the full OAuth lifecycle — authorization, token vault, and automatic refresh — across 100+ connectors (Gmail, Slack, Salesforce, Notion, and more). + +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 + +1. Run the bootstrap installer in your terminal: + +```bash +curl -fsSL https://raw.githubusercontent.com/scalekit-inc/cursor-authstack/main/install.sh | bash +``` + +2. Open Cursor, then run the plugin install command from the Command Palette: + +``` +> Plugins: Install Plugin +``` + +Select **AgentKit** from the Scalekit Auth Stack. + +## Skills Reference + +- `/agentkit:setup` + New to AgentKit? Start here — answers 2 questions and routes you to the right skill. +- `integrating-agentkit` — Core integration: SDK setup, connected accounts, OAuth flows, token fetching, downstream API calls, and agent framework examples. +- `discovering-connector-tools` — Uses live AgentKit metadata to find tools, inspect schemas, and narrow the tool set. +- `exposing-agentkit-via-mcp` — Exposes AgentKit tools through MCP for MCP-compatible runtimes. +- `production-readiness-agentkit` — Structured production readiness checklist for AgentKit integrations. +- `/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: + +- `SCALEKIT_ENVIRONMENT_URL` +- `SCALEKIT_CLIENT_ID` +- `SCALEKIT_CLIENT_SECRET` + +Get these from [app.scalekit.com](https://app.scalekit.com): Developers → Settings → API Credentials. + +## Helpful Links + +- [AgentKit overview](https://docs.scalekit.com/agentkit/overview) +- [AgentKit quickstart](https://docs.scalekit.com/agentkit/quickstart) +- [LLM docs map](https://docs.scalekit.com/llms.txt) +- [Docs sitemap](https://docs.scalekit.com/sitemap-0.xml) + +## Security + +Store API credentials in environment variables or a secret manager. 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/agentkit/hooks/beacon.sh b/plugins/agentkit/hooks/beacon.sh new file mode 100755 index 0000000..8c8d091 --- /dev/null +++ b/plugins/agentkit/hooks/beacon.sh @@ -0,0 +1,20 @@ +#!/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\":\"cursor\",\"hook\":\"${HOOK}\",\"tool_name\":\"${TOOL_NAME}\"}}" \ No newline at end of file diff --git a/plugins/agentkit/hooks/hooks.json b/plugins/agentkit/hooks/hooks.json new file mode 100644 index 0000000..12344f4 --- /dev/null +++ b/plugins/agentkit/hooks/hooks.json @@ -0,0 +1,9 @@ +{ + "hooks": { + "sessionEnd": [ + { + "command": "./hooks/beacon.sh agentkit stop" + } + ] + } +} \ No newline at end of file diff --git a/plugins/agentkit/mcp.json b/plugins/agentkit/mcp.json new file mode 100644 index 0000000..783b9a1 --- /dev/null +++ b/plugins/agentkit/mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "scalekit": { + "type": "http", + "url": "https://mcp.scalekit.com" + } + } +} diff --git a/plugins/agent-auth/references/byoc.md b/plugins/agentkit/references/byoc.md similarity index 88% rename from plugins/agent-auth/references/byoc.md rename to plugins/agentkit/references/byoc.md index b88d0f6..83460ac 100644 --- a/plugins/agent-auth/references/byoc.md +++ b/plugins/agentkit/references/byoc.md @@ -1,6 +1,6 @@ # 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. +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 +33,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 +53,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/code-samples.md b/plugins/agentkit/references/code-samples.md similarity index 94% rename from plugins/agent-auth/code-samples.md rename to plugins/agentkit/references/code-samples.md index 537ca57..314152e 100644 --- a/plugins/agent-auth/code-samples.md +++ b/plugins/agentkit/references/code-samples.md @@ -1,6 +1,6 @@ # Code Samples -This reference provides implementation examples for integrating Scalekit Agent Auth across different frameworks, languages, and use cases. +This reference provides implementation examples for integrating Scalekit AgentKit across different frameworks, languages, and use cases. ## Quick Start Guide @@ -36,7 +36,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:** @@ -50,17 +50,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** @@ -129,7 +129,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 @@ -143,7 +143,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:** @@ -157,21 +157,21 @@ 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( +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") ) # Generate OAuth authorization link -auth = client.actions.get_authorization_link( +auth = sk_client.actions.get_authorization_link( identifier=identifier, connection_name=connection_name ) @@ -181,7 +181,7 @@ print(f"🔗 Visit this URL to authorize the gmail connection:\n\n {auth.link} input("✅ Press Enter after authorization...") # Generate Gmail tools with authenticated access -gmail_tools = client.actions.google.get_tools( +gmail_tools = sk_client.actions.google.get_tools( providers=["GMAIL"], identifier=identifier, page_size=100 @@ -498,10 +498,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"), ) ``` @@ -532,6 +532,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/agent-auth/references/connected-accounts.md b/plugins/agentkit/references/connected-accounts.md similarity index 96% rename from plugins/agent-auth/references/connected-accounts.md rename to plugins/agentkit/references/connected-accounts.md index c32f761..902e820 100644 --- a/plugins/agent-auth/references/connected-accounts.md +++ b/plugins/agentkit/references/connected-accounts.md @@ -1,6 +1,6 @@ # 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). +Connected accounts in AgentKit 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). ## What are connected accounts? @@ -51,7 +51,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) @@ -255,7 +255,7 @@ const refreshed = await agentConnect.accounts.refreshTokens('account_id'); const tokenStatus = await agentConnect.accounts.getTokenStatus('account_id'); ``` -For detailed token management including automatic refresh, error handling, and security, see [token-management.md](token-management.md). +Connected accounts automatically handle token refresh. Scalekit re-fetches tokens when they expire, so always call `get_connected_account` before making API calls to get the latest valid token. ### Account status monitoring diff --git a/plugins/agent-auth/connections.md b/plugins/agentkit/references/connections.md similarity index 93% rename from plugins/agent-auth/connections.md rename to plugins/agentkit/references/connections.md index 118dc1f..5f0ef42 100644 --- a/plugins/agent-auth/connections.md +++ b/plugins/agentkit/references/connections.md @@ -1,6 +1,6 @@ # 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. +Connections in AgentKit 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. ## Table of Contents @@ -18,7 +18,7 @@ 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 providers. They contain: - **Authentication credentials** (OAuth client ID/secret, API keys, etc.) - **Configuration settings** (scopes, permissions, endpoints) @@ -28,7 +28,7 @@ Connections serve as the bridge between your Agent Auth setup and third-party pr ## 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,7 +101,7 @@ For providers with unique authentication requirements: ### Using the dashboard -1. **Navigate to connections** in your Agent Auth dashboard +1. **Navigate to connections** in your AgentKit dashboard 2. **Select provider** from the list of available providers 3. **Choose connection type** based on your authentication method 4. **Configure credentials** by entering your API keys or OAuth settings @@ -111,7 +111,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:** @@ -427,7 +427,7 @@ Use templates for common connection patterns: } ``` -Next, learn how to create and manage [Connected accounts](/agent-auth/connected-accounts) that use these connections to authenticate and execute tools for your users. +Next, learn how to create and manage [Connected accounts](connected-accounts.md) that use these connections to authenticate and execute tools for your users. ## Related documentation diff --git a/plugins/agent-auth/providers.md b/plugins/agentkit/references/connectors.md similarity index 66% rename from plugins/agent-auth/providers.md rename to plugins/agentkit/references/connectors.md index 5a4270b..fcb73d0 100644 --- a/plugins/agent-auth/providers.md +++ b/plugins/agentkit/references/connectors.md @@ -1,47 +1,47 @@ -# 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. +Connectors in AgentKit represent third-party applications that your users can connect to and interact with through Scalekit's unified API. Each connector offers a set of tools and capabilities that can be executed on behalf of connected users. -## What are providers? +## What are connectors? -Providers are pre-configured integrations with popular third-party applications that enable your users to: +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 | +| Category | Connectors | |---|---| -| **Google Workspace** | Gmail, Google Calendar, Google Drive, Google Docs, Google Sheets, Google Forms, Google Meet, Google Ads | +| **Google Workspace** | Gmail, Google Calendar, Google Drive, Google Docs, Google Sheets, Google Slides, Google Forms, Google Meet, Google Ads | | **Microsoft 365** | Outlook, OneDrive, SharePoint, Microsoft Teams, Microsoft Excel, Microsoft Word, OneNote | | **Communication** | Slack, Zoom | | **Project Management** | Jira, Asana, Trello, Monday.com, ClickUp, Linear, Confluence | -| **CRM & Sales** | Salesforce, HubSpot, Zendesk, Freshdesk, Intercom, Gong | +| **CRM & Sales** | Salesforce, HubSpot, Zendesk, Freshdesk, Intercom, Gong, Attention, Chorus, Clari Copilot | | **Development** | GitHub | | **Productivity** | Notion, Airtable, Dropbox | | **Data & Analytics** | BigQuery, Snowflake, Fathom | | **Service Management** | ServiceNow | -For per-connector tool specifications, see [agent-connectors/README.md](agent-connectors/README.md). +For per-connector tool specifications, see the [AgentKit connectors catalog](https://docs.scalekit.com/agentkit/connectors/). -## 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 -- **OAuth 2.0**: Standard method for all supported providers +- **OAuth 2.0**: Standard method for all supported connectors ### 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:** @@ -53,25 +53,25 @@ Providers expose various tools that can be executed through Agent Auth: ### Rate limits and quotas -Each provider has different rate limits and quotas: +Each connector has different rate limits and quotas: - **API rate limits**: Requests per minute/hour limitations - **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 +5. **Test connection** to verify connector setup -### Provider settings +### Connector settings -Each provider can be configured with: +Each connector can be configured with: **Authentication settings:** - OAuth client credentials (if using custom OAuth apps) @@ -83,11 +83,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 — the workflow (create account → authorize → fetch token → call API) is identical for all connectors. Only the downstream API call changes: ```python # Step 3: Fetch token (always call this immediately before the API call) @@ -98,7 +98,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 +106,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 +122,7 @@ Agent Auth normalizes provider-specific errors into consistent error responses: } ``` -## Provider-specific considerations +## Connector-specific considerations ### Google Workspace @@ -167,23 +167,24 @@ Agent Auth normalizes provider-specific errors into consistent error responses: ## Monitoring and analytics -### Provider health +### Connector health -- **API uptime**: Track provider API availability +- **API uptime**: Track connector API availability - **Response times**: Monitor latency for different operations -- **Error rates**: Track errors by provider and tool type +- **Error rates**: Track errors by connector and tool type - **Rate limit usage**: Monitor quota consumption ### Usage analytics -- **Popular providers**: Which providers are used most +- **Popular connectors**: Which connectors are used most - **Tool usage**: Which tools are executed most frequently -- **User adoption**: How many users connect to each provider -- **Error patterns**: Common failure modes by provider +- **User adoption**: How many users connect to each connector +- **Error patterns**: Common failure modes by connector ## 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 +- [AgentKit connectors catalog](https://docs.scalekit.com/agentkit/connectors/) — detailed API tools for each connector +- [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/redirects.md b/plugins/agentkit/references/redirects.md similarity index 100% rename from plugins/agent-auth/redirects.md rename to plugins/agentkit/references/redirects.md diff --git a/plugins/agentkit/references/tool-discovery.md b/plugins/agentkit/references/tool-discovery.md new file mode 100644 index 0000000..c58da4a --- /dev/null +++ b/plugins/agentkit/references/tool-discovery.md @@ -0,0 +1,83 @@ +# Tool Discovery + +## Overview + +In AgentKit, the live tool metadata is the source of truth for: + +- current connector coverage +- tool names +- `input_schema` +- `output_schema` + +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 + +- `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 Cursor + +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 +- 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 + +For per-connector tool specifications, see the [AgentKit connectors catalog](https://docs.scalekit.com/agentkit/connectors/). + +## 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: + +- 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 diff --git a/plugins/agentkit/rules/live-metadata-first.mdc b/plugins/agentkit/rules/live-metadata-first.mdc new file mode 100644 index 0000000..8513204 --- /dev/null +++ b/plugins/agentkit/rules/live-metadata-first.mdc @@ -0,0 +1,25 @@ +--- +description: Treat live AgentKit metadata as the source of truth for tool names, schemas, and connector coverage. +globs: +alwaysApply: true +--- + +# 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/agentkit/rules/terminology.mdc b/plugins/agentkit/rules/terminology.mdc new file mode 100644 index 0000000..1f2a962 --- /dev/null +++ b/plugins/agentkit/rules/terminology.mdc @@ -0,0 +1,18 @@ +--- +description: Use consistent AgentKit terminology in all user-facing content and generated code. +globs: +alwaysApply: true +--- + +# Terminology + +- `connector`: the integration (Gmail, Slack, Salesforce, Notion, etc.) +- `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. diff --git a/plugins/agentkit/rules/tool-selection.mdc b/plugins/agentkit/rules/tool-selection.mdc new file mode 100644 index 0000000..0f0ca5e --- /dev/null +++ b/plugins/agentkit/rules/tool-selection.mdc @@ -0,0 +1,28 @@ +--- +description: Keep the tool set as small as possible before handing it to an LLM. +globs: +alwaysApply: true +--- + +# 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/agentkit/skills/discovering-connector-tools/SKILL.md b/plugins/agentkit/skills/discovering-connector-tools/SKILL.md new file mode 100644 index 0000000..6cbe8e3 --- /dev/null +++ b/plugins/agentkit/skills/discovering-connector-tools/SKILL.md @@ -0,0 +1,101 @@ +--- +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 Connector Tools + +Use live AgentKit metadata as the source of truth for tool names, required inputs, and output schemas. + +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 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` +4. Recommend the smallest useful tool set for the workflow. + +## Live tool discovery (Python) + +```python +from scalekit import ScalekitClient +import os +from dotenv import load_dotenv +load_dotenv() + +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 = 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}") + print(f" Input schema: {tool.input_schema}") + print(f" Output schema: {tool.output_schema}") + +# Get a specific tool by name +tool = sk_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_ENVIRONMENT_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 + +- `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. + +## 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 + +**If `get_tools` returns empty:** verify the connector is configured in the dashboard and the connection name matches exactly. + +## Deep reference + +- 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/agent-auth/skills/building-agent-mcp-server/SKILL.md b/plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md similarity index 69% rename from plugins/agent-auth/skills/building-agent-mcp-server/SKILL.md rename to plugins/agentkit/skills/exposing-agentkit-via-mcp/SKILL.md index b8e85b7..0f04789 100644 --- a/plugins/agent-auth/skills/building-agent-mcp-server/SKILL.md +++ b/plugins/agentkit/skills/exposing-agentkit-via-mcp/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. +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-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-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:** Agent Auth MCP servers only support Streamable HTTP transport. +> **Note:** AgentKit MCP servers only support Streamable HTTP transport. ## What you'll build @@ -18,17 +18,17 @@ Scalekit lets you build MCP servers that manage authentication, create personali ## 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: > -> 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 @@ -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 @@ -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 @@ -156,3 +156,16 @@ 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 + +- 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/agent-auth/skills/agent-auth/SKILL.md b/plugins/agentkit/skills/integrating-agentkit/SKILL.md similarity index 72% rename from plugins/agent-auth/skills/agent-auth/SKILL.md rename to plugins/agentkit/skills/integrating-agentkit/SKILL.md index 05bf999..bc44ca8 100644 --- a/plugins/agent-auth/skills/agent-auth/SKILL.md +++ b/plugins/agentkit/skills/integrating-agentkit/SKILL.md @@ -1,13 +1,13 @@ --- -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. +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. --- -# 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. -**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 @@ -16,7 +16,7 @@ Install the SDK and initialize the client: > **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** +> To set up a connector: **Scalekit Dashboard → AgentKit → Connections → + Create Connection → Select connector → Set Connection Name → Save** @@ -25,28 +25,29 @@ 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** ```bash -npm install @scalekit-sdk/node@2.2.0-beta.1 +npm install @scalekit-sdk/node ``` ```typescript 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! ); @@ -55,35 +56,14 @@ 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:** +> **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. -> 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 - -Copy this checklist and check off steps as you complete them: +Copy this checklist: ``` -Agent Auth Integration Progress: +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) @@ -159,7 +139,7 @@ refresh_token = tokens["refresh_token"] ```typescript const accountResponse = await connectedAccounts.getConnectedAccountByIdentifier({ connector: 'gmail', - identifier: 'user@example.com', + identifier: 'user_123', }); const authDetails = accountResponse?.connectedAccount?.authorizationDetails; const accessToken = authDetails?.details?.case === 'oauthToken' @@ -223,7 +203,7 @@ for (const msg of messages) { 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 [CONNECTORS.md](CONNECTORS.md). +For connector-specific API details, see the [Scalekit Connectors catalog](https://docs.scalekit.com/agentkit/connectors/). ## Building agents @@ -289,16 +269,20 @@ agent = 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). +For more examples and framework-specific patterns, see the [AgentKit code samples](https://docs.scalekit.com/agentkit/code-samples/). ## 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). +- 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/) -For comprehensive token management including refresh, security, and monitoring, see [token-management.md](../references/token-management.md). +## When to switch skills -For configuring your own OAuth credentials per connector (whitelabeling, dedicated quotas), see [byoc.md](../references/byoc.md). +- 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. +- 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/agent-auth/skills/production-readiness-scalekit/SKILL.md b/plugins/agentkit/skills/production-readiness-agentkit/SKILL.md similarity index 53% rename from plugins/agent-auth/skills/production-readiness-scalekit/SKILL.md rename to plugins/agentkit/skills/production-readiness-agentkit/SKILL.md index 152ffd3..d28d40d 100644 --- a/plugins/agent-auth/skills/production-readiness-scalekit/SKILL.md +++ b/plugins/agentkit/skills/production-readiness-agentkit/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. +name: production-readiness-agentkit +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 Agent Auth Production Readiness +# Scalekit AgentKit Production Readiness Work through each section in order — earlier sections are blockers for later ones. @@ -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_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_ENVIRONMENT_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,8 +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. + +## Final smoke test + +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/setup/SKILL.md b/plugins/agentkit/skills/setup/SKILL.md new file mode 100644 index 0000000..43b49b8 --- /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 Cursor, Claude Desktop, 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_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. + +The Scalekit MCP server (`https://mcp.scalekit.com`) is pre-configured in `mcp.json`. Cursor 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/full-stack-auth/.cursor-plugin/plugin.json b/plugins/full-stack-auth/.cursor-plugin/plugin.json deleted file mode 100644 index 61b9f05..0000000 --- a/plugins/full-stack-auth/.cursor-plugin/plugin.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "full-stack-auth", - "displayName": "Full Stack Auth", - "description": "Production-ready authentication flows (sign-up, login, logout, sessions) using Scalekit full-stack auth across common stacks.", - "version": "1.0.0", - "author": { - "name": "Scalekit", - "email": "hi@scalekit.com" - }, - "homepage": "https://docs.scalekit.com", - "repository": "https://github.com/scalekit-inc/cursor-authstack", - "license": "MIT", - "keywords": ["authentication", "oauth", "full-stack", "sessions"], - "skills": "./skills", - "agents": "./agents", - "rules": "./rules", - "mcpServers": "./.mcp.json" -} diff --git a/plugins/full-stack-auth/.mcp.json b/plugins/full-stack-auth/.mcp.json deleted file mode 100644 index 36dbbf4..0000000 --- a/plugins/full-stack-auth/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "scalekit": { - "command": "npx", - "args": ["-y", "mcp-remote", "https://mcp.scalekit.com"] - } - } -} diff --git a/plugins/full-stack-auth/README.md b/plugins/full-stack-auth/README.md deleted file mode 100644 index 1c0ae87..0000000 --- a/plugins/full-stack-auth/README.md +++ /dev/null @@ -1,96 +0,0 @@ -# full-stack-auth - -Production-ready authentication flows using Scalekit full-stack auth across common stacks. - -## Purpose - -This plugin adds complete authentication to B2B and AI apps — sign-up, login, logout, sessions, RBAC, admin portal, API key auth, and more. One Scalekit integration unlocks social sign-in, magic links, passkeys, enterprise SSO, workspaces, MCP authentication, and SCIM provisioning. - -**Non-goals:** This plugin does not cover MCP server auth (see `mcp-auth`) or agent-to-service OAuth (see `agent-auth`). For apps with existing user management that only need SSO, see `modular-sso`. - ---- - -## Install - -Clone or install the cursor-authstack repository and activate the `full-stack-auth` plugin from the Cursor plugin panel. - -Required environment variables (add to `.env`): - -```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:3000/auth/callback -``` - -Get credentials from [app.scalekit.com](https://app.scalekit.com) → Developers → Settings → API Credentials. - ---- - -## Skills - -### full-stack-auth -Core auth flow: authorization URL, token exchange, session management, logout. Supports Node.js, Python, Go, and Java. - -### Framework-specific skills - -| Skill | Framework | -|-------|-----------| -| `implementing-scalekit-nextjs-auth` | Next.js App Router | -| `implementing-scalekit-django-auth` | Django | -| `implementing-scalekit-fastapi-auth` | FastAPI | -| `implementing-scalekit-flask-auth` | Flask | -| `implementing-scalekit-go-auth` | Go (Gin) | -| `implementing-scalekit-springboot-auth` | Spring Boot 3.x | -| `implementing-scalekit-laravel-auth` | Laravel | - -### Additional skills - -| Skill | Purpose | -|-------|---------| -| `implement-logout` | Complete logout flows (OIDC end-session) | -| `implementing-access-control` | RBAC and permission checks from access tokens | -| `implementing-admin-portal` | Self-serve SSO/SCIM customer portal (iframe embed) | -| `adding-api-key-auth` | API key creation, validation, and revocation | -| `adding-oauth2-to-apis` | OAuth 2.0 client-credentials for machine-to-machine auth | -| `manage-user-sessions` | Secure session storage and transparent token refresh | -| `migrating-to-scalekit-auth` | Incremental migration from existing auth systems | -| `production-readiness-scalekit` | Pre-launch production readiness checklist | - ---- - -## Commands - -### dryrun - -Runs the Scalekit dryrun tool to verify your auth configuration end-to-end. - -``` -/dryrun fsa -``` - ---- - -## Configuration - -The `.mcp.json` connects to the Scalekit hosted MCP server. The `SCALEKIT_REDIRECT_URI` must exactly match the callback URL registered in your Scalekit dashboard. - ---- - -## Troubleshooting - -**"Invalid redirect_uri"**: The callback URL in your code must exactly match what is registered in Dashboard → Authentication → Redirect URLs. - -**"invalid_grant" on token refresh**: The refresh token expired or was revoked. Clear the session and redirect to login. - -**SameSite cookie issues**: Use `SameSite=Lax` (not Strict) — the OAuth callback is a cross-site redirect that drops cookies with Strict mode. - ---- - -## Security notes - -- Store all tokens in HttpOnly cookies, not localStorage -- Always validate access tokens server-side before trusting claims -- Use the `offline_access` scope to receive refresh tokens -- Set `Secure: true` on all cookies in production (HTTPS) -- Never commit credentials to version control diff --git a/plugins/full-stack-auth/agents/scalekit-mcp-helper.md b/plugins/full-stack-auth/agents/scalekit-mcp-helper.md deleted file mode 100644 index 39a155b..0000000 --- a/plugins/full-stack-auth/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/full-stack-auth/agents/sdk-version-advisor.md b/plugins/full-stack-auth/agents/sdk-version-advisor.md deleted file mode 100644 index 404b8f5..0000000 --- a/plugins/full-stack-auth/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/full-stack-auth/agents/setup-auth.md b/plugins/full-stack-auth/agents/setup-auth.md deleted file mode 100644 index d5c7679..0000000 --- a/plugins/full-stack-auth/agents/setup-auth.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: setup-auth -description: Guide users through initial auth setup including environment variables, SDK installation, and credential verification. ---- - -# Auth Setup Agent - -You are an authentication setup specialist. - -## Workflow -1. Determine the auth type (SSO, SCIM, MCP, etc.) -2. Confirm required environment variables -3. Install and initialize the appropriate SDK -4. Verify credentials with a minimal test -5. Route to the correct skill for detailed implementation - -## Hard rules -- NEVER ask users to paste secrets into chat -- Always use environment variables for credentials -- Create local verification scripts when helpful diff --git a/plugins/full-stack-auth/commands/deploy-auth.md b/plugins/full-stack-auth/commands/deploy-auth.md deleted file mode 100644 index 9401d8d..0000000 --- a/plugins/full-stack-auth/commands/deploy-auth.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -name: deploy-auth -description: Deploy authentication configuration and verify production readiness. ---- - -# Deploy Auth - -## Deployment checklist -1. Review environment-specific configuration -2. Verify all secrets are set in production -3. Test auth flows in staging environment -4. Run smoke tests on critical auth paths -5. Monitor for errors after deployment - -## Steps -1. Validate configuration in target environment -2. Run integration tests -3. Deploy configuration changes -4. Verify health endpoints -5. Check logs for any auth errors diff --git a/plugins/full-stack-auth/commands/dryrun.md b/plugins/full-stack-auth/commands/dryrun.md deleted file mode 100644 index 3f121d4..0000000 --- a/plugins/full-stack-auth/commands/dryrun.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: dryrun -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/full-stack-auth/hooks/hooks.json b/plugins/full-stack-auth/hooks/hooks.json deleted file mode 100644 index f6a9da5..0000000 --- a/plugins/full-stack-auth/hooks/hooks.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "description": "Usage beacon for Scalekit full-stack-auth plugin", - "hooks": { - "PostToolUse": [ - { - "matcher": ".*", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh full-stack-auth post_tool", - "timeout": 10 - } - ] - } - ], - "Stop": [ - { - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh full-stack-auth stop", - "timeout": 10 - } - ] - } - ] - } -} diff --git a/plugins/full-stack-auth/references/redirects.md b/plugins/full-stack-auth/references/redirects.md deleted file mode 100644 index dbe1977..0000000 --- a/plugins/full-stack-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/full-stack-auth/rules/web-auth-security.mdc b/plugins/full-stack-auth/rules/web-auth-security.mdc deleted file mode 100644 index 48670c3..0000000 --- a/plugins/full-stack-auth/rules/web-auth-security.mdc +++ /dev/null @@ -1,21 +0,0 @@ ---- -description: Web application authentication security standards. -alwaysApply: true -globs: ["**/*.{js,ts,jsx,tsx,py}"] ---- - -web-auth-security: - -- Use secure, HTTP-only cookies for session tokens -- Implement CSRF protection for state-changing operations -- Set proper security headers (X-Frame-Options, CSP, etc.) -- Rate-limit authentication endpoints -- Log authentication failures (without exposing passwords) -- Implement proper password hashing (bcrypt, argon2) -- Validate all inputs to prevent injection attacks - -Session management: -- Set appropriate cookie expiration times -- Implement proper logout/session invalidation -- Use signed JWT tokens with proper expiration -- Store session data securely (server-side when possible) 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/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-admin-portal/SKILL.md b/plugins/full-stack-auth/skills/implementing-admin-portal/SKILL.md deleted file mode 100644 index cf0d039..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` | -| Go | `go get github.com/scalekit/scalekit-go` | -| 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 32e4703..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-dotenv pydantic-settings starlette -``` - -Add to `requirements.txt`: -``` -scalekit-sdk>=0.1.0 -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 743a74b..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-dotenv flask -``` - -Add to `requirements.txt`: -``` -scalekit-sdk>=0.1.0 -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-go-auth/SKILL.md b/plugins/full-stack-auth/skills/implementing-scalekit-go-auth/SKILL.md deleted file mode 100644 index 8364c1c..0000000 --- a/plugins/full-stack-auth/skills/implementing-scalekit-go-auth/SKILL.md +++ /dev/null @@ -1,441 +0,0 @@ ---- -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 -manually implement four handlers: **authorize → callback → session → logout**. Use `scalekit-sdk-go/v2`. - -## 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 variables - -```bash -SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.dev -SCALEKIT_CLIENT_ID=your_client_id -SCALEKIT_CLIENT_SECRET=your_client_secret -PORT=8080 -``` - -Never commit secrets. Load with `godotenv` or equivalent. - -## Global client — initialize once - -Use `sync.Once` so the client is created exactly once across all requests: - -```go -var ( - globalClient scalekit.Scalekit - clientOnce sync.Once - clientErr error -) - -func GetScaleKitClient() (scalekit.Scalekit, error) { - clientOnce.Do(func() { - envURL := os.Getenv("SCALEKIT_ENVIRONMENT_URL") - id := os.Getenv("SCALEKIT_CLIENT_ID") - secret := os.Getenv("SCALEKIT_CLIENT_SECRET") - globalClient = scalekit.NewScalekitClient(envURL, id, secret) - }) - return globalClient, clientErr -} -``` - -Call `GetScaleKitClient()` once at startup to fail fast on bad credentials. - -## Auth flow at a glance - -``` -GET /api/authorize - → GetAuthorizationUrl() → 302 to Scalekit - -GET /api/scalekit/callback?code=... - → AuthenticateWithCode() → redirect to /dashboard or /onboarding - -GET /api/session (every page load) - → ValidateAccessToken() → refresh if expired → return user JSON - -GET /api/logout - → GetLogoutUrl() → clear cookies → 302 to Scalekit end-session -``` - -## Handler: Authorize - -Builds the authorization URL and redirects the browser: - -```go -func AuthorizeHandler(c *gin.Context) { - sc, _ := GetScaleKitClient() - - stateBytes, _ := json.Marshal(map[string]any{ - "next": c.Query("next"), - "csrf": randomString(12), - }) - state := base64.StdEncoding.EncodeToString(stateBytes) - - opts := scalekit.AuthorizationUrlOptions{ - 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 } - - authURL, err := sc.GetAuthorizationUrl(callbackURL(c), opts) - if err != nil { - c.JSON(500, gin.H{"error": "Failed to build authorization URL"}) - return - } - c.Redirect(http.StatusFound, authURL.String()) -} - -func callbackURL(c *gin.Context) string { - proto := "https" - if strings.Contains(c.Request.Host, "localhost") { proto = "http" } - return proto + "://" + c.Request.Host + "/api/scalekit/callback" -} -``` - -## Handler: Callback - -Exchange the authorization code for tokens; set httpOnly cookies: - -```go -func CallbackHandler(c *gin.Context) { - if e := c.Query("error"); e != "" { - c.JSON(400, gin.H{"error": c.Query("error_description")}) - return - } - - sc, _ := GetScaleKitClient() - resp, err := sc.AuthenticateWithCode( - c.Request.Context(), - c.Query("code"), - callbackURL(c), - scalekit.AuthenticationOptions{}, - ) - if err != nil { - c.JSON(500, gin.H{"error": "Token exchange failed"}) - return - } - - 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: no org in token → new user needs onboarding - claims, _ := decodeJWTPayload(resp.AccessToken) - redirect := "/onboarding" - if _, hasOrg := claims["xoid"]; hasOrg { - redirect = "/dashboard" - } - c.Redirect(http.StatusFound, getUIBaseURL(c)+redirect) -} -``` - -`resp` fields: `AccessToken`, `RefreshToken`, `IdToken`, `User` (email, name, etc.). - -## Handler: Session - -Validate on every authenticated page load; silently refresh expired tokens: - -```go -func SessionHandler(c *gin.Context) { - accessToken, _ := c.Cookie("auth_access_token") - refreshToken, _ := c.Cookie("auth_refresh_token") - - sc, _ := GetScaleKitClient() - - valid, err := sc.ValidateAccessToken(c.Request.Context(), accessToken) - if err != nil || !valid { - refreshed, err := sc.RefreshAccessToken(c.Request.Context(), refreshToken) - if err != nil { - LogoutHandler(c) // force re-login - return - } - c.SetCookie("auth_access_token", refreshed.AccessToken, 86400, "/", "", false, true) - c.SetCookie("auth_refresh_token", refreshed.RefreshToken, 2592000, "/", "", false, true) - accessToken = refreshed.AccessToken - } - - claims, _ := decodeJWTPayload(accessToken) - userID, _ := getStringClaim(claims, "sub") - - userResp, _ := sc.User().GetUser(context.Background(), userID) - c.JSON(200, gin.H{ - "authenticated": true, - "user": gin.H{ - "id": userResp.User.Id, - "email": userResp.User.Email, - "first_name": userResp.User.UserProfile.FirstName, - "last_name": userResp.User.UserProfile.LastName, - }, - }) -} -``` - -## Handler: Logout - -Clear all cookies and redirect to Scalekit's end-session endpoint: - -```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()) -} -``` - -## Handler: IDP-initiated login (enterprise SSO) - -When an IdP starts the login (e.g. Okta tile click), Scalekit sends a signed JWT: - -```go -func IdpInitiatedLoginHandler(c *gin.Context) { - sc, _ := GetScaleKitClient() - claims, err := sc.GetIdpInitiatedLoginClaims( - c.Request.Context(), - c.Query("idp_initiated_login"), - ) - if err != nil { - c.JSON(400, gin.H{"error": "invalid idp_initiated_login token"}) - return - } - opts := scalekit.AuthorizationUrlOptions{ - Scopes: []string{"openid", "profile", "email", "offline_access"}, - } - if claims.OrganizationID != "" { opts.OrganizationId = claims.OrganizationID } - if claims.ConnectionID != "" { opts.ConnectionId = claims.ConnectionID } - if claims.LoginHint != "" { opts.LoginHint = claims.LoginHint } - - authURL, _ := sc.GetAuthorizationUrl(callbackURL(c), opts) - c.Redirect(http.StatusFound, authURL.String()) -} -``` - -## 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 { - return nil, fmt.Errorf("invalid JWT format") - } - payload, err := base64.RawURLEncoding.DecodeString(parts[1]) - if err != nil { - return nil, err - } - var claims map[string]interface{} - return claims, json.Unmarshal(payload, &claims) -} - -func getStringClaim(claims map[string]interface{}, key string) (string, error) { - v, ok := claims[key].(string) - if !ok || v == "" { - return "", fmt.Errorf("claim %q missing or empty", key) - } - return v, nil -} -``` - -## Scalekit JWT claims reference - -| Claim | Meaning | Notes | -|---|---|---| -| `sub` | Scalekit user ID | Always present | -| `xoid` | External org ID (e.g. `wspace_abc`) | Absent → user has no org yet → send to `/onboarding` | -| `xuid` | Your app's user DB ID | Absent → create user locally, then call `UpdateUser` to write it back | -| `permissions` | User permissions in org | Check before authorizing sensitive actions | -| `roles` | User roles in org | Derive `is_admin` from role names | - -## 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) -``` - -## CORS — required for cookie-based auth - -```go -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 - MaxAge: 12 * time.Hour, -})) -``` - -## Implementation checklist - -``` -- [ ] Step 1: go get scalekit-sdk-go/v2, gin, cors, jwt/v5 -- [ ] Step 2: Set SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET in .env -- [ ] Step 3: Create handlers/client.go — sync.Once singleton -- [ ] Step 4: Create handlers/utils.go — decodeJWTPayload, getStringClaim, callbackURL, getUIBaseURL -- [ ] Step 5: Implement AuthorizeHandler → GetAuthorizationUrl → redirect -- [ ] Step 6: Implement CallbackHandler → AuthenticateWithCode → set cookies → redirect -- [ ] Step 7: Implement SessionHandler → ValidateAccessToken → RefreshAccessToken if expired -- [ ] Step 8: Implement LogoutHandler → GetLogoutUrl → clear cookies → redirect -- [ ] Step 9: Register all four routes under /api -- [ ] Step 10: Configure CORS with AllowCredentials: true -- [ ] Step 11: Register callback URI in Scalekit dashboard -- [ ] Step 12: Test: login → /dashboard → GET /api/session → logout -``` - -## 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. - -**`xoid` missing**: The user has no organization. This is expected for new signups — route to `/onboarding` to create or join a workspace. - -**CORS / cookie not sent**: Ensure `AllowCredentials: true` is set in CORS config. Without it, the browser strips cookies from cross-origin requests. - -**`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. - -## Reference - -- Full working example: [scalekit-inc/coffee-desk-demo](https://github.com/scalekit-inc/coffee-desk-demo) -- Scalekit Go SDK: [scalekit-inc/scalekit-sdk-go](https://github.com/scalekit-inc/scalekit-sdk-go) -- Scalekit docs: https://docs.scalekit.com - -## Tactics - -### SameSite=Lax — set explicitly on each cookie - -Gin's `c.SetCookie` does not expose a `SameSite` parameter. Use `http.SetCookie` directly for full control: - -```go -http.SetCookie(c.Writer, &http.Cookie{ - Name: "auth_access_token", - Value: resp.AccessToken, - Path: "/", - MaxAge: 86400, - HttpOnly: true, - SameSite: http.SameSiteLaxMode, // Required — Strict drops cookie on OAuth redirect back - 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: - -```go -func isSecure(c *gin.Context) bool { - return !strings.Contains(c.Request.Host, "localhost") -} -``` - -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`. - -### Deep link preservation via state - -The state JSON already includes `"next"`. After a successful callback, extract it and redirect: - -```go -next := stateData["next"] -if next == "" || !strings.HasPrefix(next, "/") { - next = "/dashboard" // prevent open redirect -} -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") - // ... -} -``` - -### 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. - -### 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: - -```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 - } - // ... -} -``` 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-springboot-auth/SKILL.md b/plugins/full-stack-auth/skills/implementing-scalekit-springboot-auth/SKILL.md deleted file mode 100644 index fb6b34a..0000000 --- a/plugins/full-stack-auth/skills/implementing-scalekit-springboot-auth/SKILL.md +++ /dev/null @@ -1,287 +0,0 @@ ---- -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 -authorization code flow — no custom filters needed. - -## Required dependencies - -Add to `pom.xml` (Spring Boot 3.2+, Java 17+): - -```xml - - org.springframework.boot - spring-boot-starter-oauth2-client - - - org.springframework.boot - spring-boot-starter-security - - - - com.scalekit - scalekit-sdk-java - 2.0.4 - -``` - -## Configuration - -`src/main/resources/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 - client-name: Scalekit - provider: - scalekit: - issuer-uri: ${scalekit.env-url} - authorization-uri: ${scalekit.env-url}/oauth/authorize - token-uri: ${scalekit.env-url}/oauth/token - user-info-uri: ${scalekit.env-url}/userinfo - jwk-set-uri: ${scalekit.env-url}/keys - user-name-attribute: sub -``` - -For local dev, use `application-local.properties` — never commit secrets. - -## 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 - -```java -@Configuration -@EnableWebSecurity -public class SecurityConfig { - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http, - ClientRegistrationRepository clientRegistrationRepository) throws Exception { - http - .authorizeHttpRequests(authz -> authz - .requestMatchers("/", "/login", "/error", "/css/**", "/js/**").permitAll() - .anyRequest().authenticated() - ) - .oauth2Login(oauth2 -> oauth2 - .loginPage("/login") - .defaultSuccessUrl("/dashboard", true) - ) - .logout(logout -> logout - .logoutSuccessHandler(oidcLogoutSuccessHandler(clientRegistrationRepository)) - .invalidateHttpSession(true) - .clearAuthentication(true) - ); - return http.build(); - } - - private LogoutSuccessHandler oidcLogoutSuccessHandler( - ClientRegistrationRepository clientRegistrationRepository) { - OidcClientInitiatedLogoutSuccessHandler handler = - new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository); - handler.setPostLogoutRedirectUri("{baseUrl}"); - return handler; - } -} -``` - -## Accessing user identity in controllers - -```java -@GetMapping("/dashboard") -public String dashboard(@AuthenticationPrincipal OidcUser oidcUser, Model model) { - model.addAttribute("name", oidcUser.getFullName()); - model.addAttribute("email", oidcUser.getEmail()); - model.addAttribute("subject", oidcUser.getSubject()); - model.addAttribute("claims", oidcUser.getClaims()); - return "dashboard"; -} -``` - -Key `OidcUser` accessors: `getFullName()`, `getEmail()`, `getSubject()`, `getClaims()`, -`getAuthorities()`. - -## Application routes - -| Route | Auth? | Notes | -|---|---|---| -| `/` | No | Home page | -| `/login` | No | Custom login page | -| `/dashboard` | Yes | Protected; redirects to login | -| `/oauth2/authorization/scalekit` | No | Starts OIDC flow | -| `/auth/callback` | No | Handled by Spring Security automatically | -| `/logout` | Yes | Triggers OIDC end-session | - -## Scalekit Dashboard setup checklist - -``` -- [ ] Get Environment URL (e.g., https://your-env.scalekit.dev) -- [ ] Get Client ID and Client Secret from Settings > API Credentials -- [ ] Add allowed redirect URI: http://localhost:8080/login/oauth2/code/scalekit -- [ ] Optionally add post-logout redirect: http://localhost:8080 -``` - -## Workflows - -### Add Scalekit auth to an existing Spring Boot app - -``` -Progress: -- [ ] Step 1: Add Maven dependencies -- [ ] Step 2: Add application.yml OAuth2 provider/registration config -- [ ] Step 3: Create ScalekitConfig bean -- [ ] Step 4: Create SecurityConfig filter chain -- [ ] Step 5: Inject @AuthenticationPrincipal OidcUser in protected controllers -- [ ] Step 6: Configure redirect URIs in Scalekit dashboard -- [ ] Step 7: Run app and verify login → dashboard → logout flow -``` - -## 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). - -**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`: - -```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 - http-only: true - secure: true # Set to true in production (HTTPS) -``` - -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: - -```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. - -### 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 - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); - return source; -} -``` - -### 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 - .authenticationEntryPoint((request, response, authException) -> { - String accept = request.getHeader("Accept"); - if (accept != null && accept.contains("application/json")) { - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication required"); - } else { - response.sendRedirect("/login"); - } - })) -``` - -### 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. diff --git a/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/AUDIT-CHECKLIST.md b/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/AUDIT-CHECKLIST.md deleted file mode 100644 index 5b54ef9..0000000 --- a/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/AUDIT-CHECKLIST.md +++ /dev/null @@ -1,33 +0,0 @@ -# Auth Code Audit Checklist - -## Flows to locate and document -- [ ] Sign-up endpoint and validation logic -- [ ] Login endpoint (password, OAuth, SSO paths) -- [ ] Session middleware (where tokens are validated per request) -- [ ] Refresh token handling -- [ ] RBAC enforcement points (middleware, decorators, guards) -- [ ] Email verification trigger and callback -- [ ] Logout + session invalidation - -## Data to export -- [ ] Users table (email, name, email_verified, created_at) -- [ ] Organizations / tenants table -- [ ] Role definitions and user-role join table -- [ ] OAuth / SSO provider configs (client IDs, domains) - -## Format for export -Produce a JSON array per entity type: - -```json -[ - { - "email": "user@example.com", - "external_id": "usr_001", - "first_name": "Jane", - "last_name": "Doe", - "email_verified": true, - "org_external_id": "org_123", - "roles": ["admin"] - } -] -``` diff --git a/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/IMPORT-SAMPLES.md b/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/IMPORT-SAMPLES.md deleted file mode 100644 index 6b149b5..0000000 --- a/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/IMPORT-SAMPLES.md +++ /dev/null @@ -1,91 +0,0 @@ -# Import Code Samples - -## Python — Create organization -```python -from scalekit.v1.organizations.organizations_pb2 import CreateOrganization - -result = scalekit_client.organization.create_organization( - CreateOrganization( - display_name="Megasoft Inc", - external_id="org_123", - metadata={"plan": "enterprise"} - ) -) -``` - -## Python — Create user in organization -```python -from scalekit.v1.users.users_pb2 import CreateUser -from scalekit.v1.commons.commons_pb2 import UserProfile - -user_msg = CreateUser( - email="user@example.com", - external_id="usr_987", - metadata={"department": "engineering"}, - user_profile=UserProfile(first_name="John", last_name="Doe") -) -create_resp, _ = scalekit_client.user.create_user_and_membership("org_123", user_msg) -``` - -## Go — Create organization -```go -result, err := scalekit.Organization.CreateOrganization(ctx, "Megasoft Inc", - scalekit.CreateOrganizationOptions{ExternalID: "org_123"}) -``` - -## Go — Create user in organization -```go -createUser := &usersv1.CreateUser{ - Email: "user@example.com", - ExternalId: "usr_987", - UserProfile: &usersv1.CreateUserProfile{ - FirstName: "John", - LastName: "Doe", - }, -} -resp, err := scalekitClient.User().CreateUserAndMembership(ctx, "org_123", createUser) -``` - -## Java — Create organization -```java -CreateOrganization createOrg = CreateOrganization.newBuilder() - .setDisplayName("Megasoft Inc") - .setExternalId("org_123") - .build(); -scalekitClient.organizations().createOrganization(createOrg); -``` - -## Java — Create user -```java -CreateUser createUser = CreateUser.newBuilder() - .setEmail("user@example.com") - .setExternalId("usr_987") - .setUserProfile(CreateUserProfile.newBuilder().setFirstName("John").setLastName("Doe").build()) - .build(); -scalekitClient.users().createUserAndMembership("org_123", createUser); -``` - -## cURL — Create organization -```sh -curl -L -X POST '/api/v1/organizations' \ - -H 'Content-Type: application/json' \ - -H 'Authorization: Bearer ' \ - -d '{ - "display_name": "Megasoft Inc", - "external_id": "org_123", - "metadata": { "plan": "enterprise" } - }' -``` - -## cURL — Create user in organization -```sh -curl -L -X POST '/api/v1/organizations//users' \ - -H 'Content-Type: application/json' \ - -H 'Authorization: Bearer ' \ - -d '{ - "email": "user@example.com", - "external_id": "usr_987", - "send_invitation_email": false, - "user_profile": { "first_name": "John", "last_name": "Doe" } - }' -``` 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/.cursor-plugin/plugin.json b/plugins/mcp-auth/.cursor-plugin/plugin.json deleted file mode 100644 index 859d673..0000000 --- a/plugins/mcp-auth/.cursor-plugin/plugin.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "mcp-auth", - "displayName": "MCP Auth", - "description": "OAuth 2.1 authorization for MCP servers using Scalekit to protect tools used by AI IDEs and agents.", - "version": "1.0.0", - "author": { - "name": "Scalekit", - "email": "hi@scalekit.com" - }, - "homepage": "https://docs.scalekit.com", - "repository": "https://github.com/scalekit-inc/cursor-authstack", - "license": "MIT", - "keywords": ["oauth", "mcp", "authentication", "scalekit"], - "skills": "./skills", - "agents": "./agents", - "rules": "./rules", - "mcpServers": "./.mcp.json" -} diff --git a/plugins/mcp-auth/.mcp.json b/plugins/mcp-auth/.mcp.json deleted file mode 100644 index 36dbbf4..0000000 --- a/plugins/mcp-auth/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "scalekit": { - "command": "npx", - "args": ["-y", "mcp-remote", "https://mcp.scalekit.com"] - } - } -} diff --git a/plugins/mcp-auth/README.md b/plugins/mcp-auth/README.md deleted file mode 100644 index 3e21033..0000000 --- a/plugins/mcp-auth/README.md +++ /dev/null @@ -1,112 +0,0 @@ -# mcp-auth - -OAuth 2.1 authorization for MCP servers using Scalekit as the authorization server. - -## Purpose - -This plugin adds production-ready OAuth 2.1 authorization to any MCP server. It serves the `/.well-known/oauth-protected-resource` discovery endpoint, validates Bearer tokens in middleware, and enforces per-tool scope checks — so MCP clients like Claude Desktop, Cursor, and VS Code can automatically discover and authenticate with your server. - -**Non-goals:** This plugin does not cover user-facing authentication flows (see `full-stack-auth`) or agent-to-service OAuth (see `agent-auth`). - ---- - -## Install - -Clone or install the cursor-authstack repository and activate the `mcp-auth` plugin from the Cursor plugin panel. - -Required environment variables (add to `.env`): - -```env -SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com -SCALEKIT_CLIENT_ID=your_client_id -SCALEKIT_CLIENT_SECRET=your_client_secret -``` - -Get credentials from [app.scalekit.com](https://app.scalekit.com) → Developers → Settings → API Credentials. - ---- - -## Skills - -### mcp-auth (add-mcp-auth) - -Adds OAuth 2.1 authorization to any MCP server. Implements the discovery endpoint, Bearer token validation middleware, and scope enforcement. Supports Node.js (Express) and Python (FastAPI). - -**Example invocations:** -- "Add OAuth 2.1 auth to my MCP server" -- "Protect my MCP tools with Scalekit token validation" -- "Set up the OAuth discovery endpoint for my MCP server" - -### mcp-auth-expressjs-scalekit - -Implements a complete Express.js MCP server with OAuth 2.1 authorization using Scalekit. - -**Example invocations:** -- "Build an Express MCP server with OAuth auth" -- "Add Scalekit token validation to my Express MCP server" - -### mcp-auth-fastapi-fastmcp-scalekit - -Implements OAuth 2.1 authorization for FastAPI + FastMCP servers using Scalekit. - -**Example invocations:** -- "Add auth to my FastAPI MCP server" -- "Protect my FastMCP tools with Scalekit OAuth" - -### mcp-auth-fastmcp-scalekit (add-auth-fastmcp) - -Adds OAuth 2.1 authorization to FastMCP servers using the Scalekit provider plugin for minimal setup. - -**Example invocations:** -- "Add Scalekit OAuth to my FastMCP server with minimal code" -- "Use the Scalekit FastMCP provider plugin" - -### production-readiness-scalekit - -Walks through a structured production readiness checklist for Scalekit MCP authentication implementations. - -**Example invocations:** -- "Run a production readiness check on my MCP auth setup" -- "What do I need to verify before going live with MCP auth?" - ---- - -## Agents - -### scalekit-setup - -Sets up Scalekit env vars, installs/initializes the SDK, and verifies credentials. Use when the user asks to set up, install, initialize, or configure Scalekit for an MCP server. - -### mcp-auth-troubleshooter - -Diagnoses and fixes common MCP authentication issues: discovery endpoint problems, token validation failures, scope errors, and client configuration issues. - ---- - -## Configuration - -The `.mcp.json` connects to the Scalekit hosted MCP server at `https://mcp.scalekit.com`. No additional configuration is required beyond the environment variables above. - -The `RESOURCE_ID` used in token validation must match the Server URL registered in the Scalekit dashboard under MCP Servers → your server. - ---- - -## Troubleshooting - -**MCP client cannot discover auth server**: The `/.well-known/oauth-protected-resource` endpoint must be publicly accessible without any authentication. Verify it returns the correct `authorization_servers` URL from your Scalekit dashboard. - -**"Token validation failed"**: Ensure `SCALEKIT_ENVIRONMENT_URL` is set correctly and the `audience` in `validateToken()` matches the Server URL registered in the Scalekit dashboard. - -**"insufficient_scope" errors**: Check that the required scopes are configured in Dashboard → MCP Servers → your server → Scopes, and that the client is requesting those scopes during authorization. - -**FastMCP `resource` must use base URL with trailing slash**: When using FastMCP, the `RESOURCE_ID` must be the base URL with a trailing slash (e.g., `https://mcp.yourapp.com/`). - ---- - -## Security notes - -- Never authenticate the `/.well-known/oauth-protected-resource` endpoint — it must be public -- Always validate `aud`, `iss`, `exp`, and `scope` claims using the Scalekit SDK, never manually -- Return HTTP 401 with a `WWW-Authenticate` header on token validation failure -- Return HTTP 403 (not 401) when scopes are insufficient — authorization is valid but permissions are not -- Store `SCALEKIT_CLIENT_SECRET` in environment variables only, never in source code diff --git a/plugins/mcp-auth/agents/mcp-auth-troubleshooter.md b/plugins/mcp-auth/agents/mcp-auth-troubleshooter.md deleted file mode 100644 index 759b2d9..0000000 --- a/plugins/mcp-auth/agents/mcp-auth-troubleshooter.md +++ /dev/null @@ -1,138 +0,0 @@ ---- -name: mcp-auth-troubleshooter -description: Troubleshoot Scalekit OAuth authentication issues on MCP servers. Invoke when a user reports MCP connection failures, 401 errors, redirect_uri mismatches, CORS errors, token validation failures, scope errors, or client-specific issues with Claude Desktop, Cursor, VS Code, or MCP Inspector. ---- - -# MCP Auth Troubleshooter (Scalekit) - -You are a specialist in diagnosing and resolving OAuth 2.1 authentication issues for MCP servers secured with Scalekit. You have deep knowledge of the MCP protocol auth flow, the .well-known/oauth-protected-resource discovery mechanism, Bearer token validation, and common client-specific quirks. - -## Your behavior - -When invoked, always start by asking ONE clarifying question to identify the failure category before suggesting fixes. Do not dump all possible causes at once. Work through the issue diagnostically, like a senior engineer doing triage. - -The failure categories are: -1. Connection / handshake failure (MCP Inspector can't connect, no 401, no WWW-Authenticate) -2. redirect_uri mismatch (OAuth error during token exchange) -3. CORS errors (visible in browser network tab or MCP Inspector logs) -4. Token validation failure (401 after token is issued) -5. Scope / insufficient_permissions error (403 or tool returns error object) -6. Client-specific issue (Claude Desktop, Cursor, VS Code, MCP Inspector) -7. Browser not launching during auth flow - ---- - -## Diagnosis flow - -### Step 1: Identify the failure point -Ask the user: "At what stage does the error occur?" -- Before or during Connect in MCP Inspector → likely handshake / metadata / CORS -- After connecting, when calling a tool → likely token validation or scope -- Browser never opens → OS permission issue -- Works in Inspector but fails in Claude Desktop / Cursor / VS Code → client-specific - -### Step 2: Confirm basic server health -If the stage is unclear, ask the user to run this check first: -``` -curl -i http://localhost:/ -``` -Expected: HTTP 401 with a `www-authenticate` header like: -``` -WWW-Authenticate: Bearer realm="OAuth", resource_metadata="http://localhost:/.well-known/oauth-protected-resource" -``` -If 401 + WWW-Authenticate is missing → the server is not responding correctly. Escalate to server config. -If 401 + WWW-Authenticate is present → the server is healthy. Move to metadata check. - -### Step 3: Validate metadata endpoint -``` -curl http://localhost:/.well-known/oauth-protected-resource -``` -Expected: valid JSON matching what's in Scalekit Dashboard > MCP Servers > Your Server > Metadata JSON. -Common failures: -- 500 → PROTECTED_RESOURCE_METADATA env var is missing or malformed -- authorization_servers URL wrong → copied from wrong environment -- resource field doesn't match EXPECTED_AUDIENCE → token audience mismatch - ---- - -## Issue playbooks - -### Handshake / connection failure -1. Confirm server returns 401 + WWW-Authenticate on root request (see Step 2). -2. Confirm /.well-known/oauth-protected-resource returns valid JSON (see Step 3). -3. If both pass, ask: "Are there CORS errors in the browser console or MCP Inspector logs?" -4. If yes → go to CORS playbook. - -### redirect_uri mismatch -Cause: MCP client cached an old domain and is sending auth requests to a stale URL. -Fix by client: -- MCP-Remote: delete `~/.mcp-auth/mcp-remote-` then reconnect. -- VS Code: open Command Palette → "Authentication: Remove Dynamic Authentication Provider" → reconnect. -- Claude Desktop: clearing cached auth is NOT currently supported. Workaround: use a different domain/subdomain. - -### CORS errors (MCP Inspector) -Cause: Scalekit is rejecting the callback origin. -Fix: -1. Go to Dashboard > Authentication > Redirect URLs > Allowed Callback URLs. -2. Add `http://localhost:6274/` for MCP Inspector development. -3. Add your production callback URL if deploying. -4. Retry connection. - -### Token validation failure (401 after auth completes) -Ask: "What framework is the MCP server using — FastMCP standalone, FastAPI+FastMCP, or Express?" -Then check the most likely cause per framework: - -**FastMCP (ScalekitProvider)**: -- SCALEKIT_RESOURCE_ID: confirm it starts with `res_` and matches the resource ID in the dashboard. -- MCP_URL: confirm it has trailing slash and matches the Server URL registered in Scalekit. -- After changing these, restart the server (FastMCP caches auth server details on boot). - -**FastAPI + FastMCP / Express**: -- EXPECTED_AUDIENCE: must exactly match the Server URL in Scalekit (including trailing slash). - e.g. `http://localhost:3002/` not `http://localhost:3002` -- SK_ENV_URL: must match the environment issuing the tokens. Confirm in Dashboard > Settings > API Credentials. -- If using `validate_access_token` (Python), confirm it returns True; if using `validateToken` (Node), confirm no exception is thrown. - -### Scope / insufficient_permissions error -Ask: "Which tool is failing and what scopes are configured in the Scalekit dashboard?" -Steps: -1. Confirm the scope (e.g. `todo:read`, `todo:write`) exists in Dashboard > MCP Servers > Your Server > Scopes. -2. Confirm the MCP client requested that scope during authentication. -3. Inspect the token: decode the JWT (jwt.io or similar) and check the `scope` claim. -4. If the scope is missing from the token, the user didn't authorize it → reconnect and grant the scope. - -### Client-specific issues - -**Claude Desktop + custom port (not 443)**: -Claude Desktop only supports standard HTTPS on port 443. If the server runs on a custom port: -- Use a reverse proxy (Nginx, Caddy) to listen on 443 and forward to the custom port. -- Do NOT expect Claude Desktop to connect directly to non-443 ports. - -**Multiple auth tabs / duplicate flows (MCP-Remote + Claude Desktop)**: -- Claude Desktop now has a built-in Custom Connector feature. Disable MCP-Remote and use the built-in connector. -- Never run both simultaneously — they produce duplicate auth flows. - -**Browser not launching during auth**: -macOS: System Preferences > Security & Privacy > App Management → allow the MCP client to open apps. -Windows: Settings > Privacy > App permissions → enable "Allow apps to manage your default app settings". -Linux: confirm `xdg-open` is installed → `which xdg-open`. -Always restart the MCP client after updating OS permissions. - ---- - -## Best practices to surface proactively - -If the user resolves their issue, always close by checking: -- Are they using separate Scalekit environments for dev and prod? -- Are their Server URLs environment-specific (`mcp-dev.yourdomain.com` vs `mcp.yourdomain.com`)? -- Is EXPECTED_AUDIENCE (or MCP_URL) pointing to the correct environment? -- Are callback URLs registered for both environments in the Scalekit dashboard? -- Are they monitoring Dashboard > Authentication > Logs for future failures? - ---- - -## What you do NOT do -- Do not suggest editing Scalekit's internal token signing or JWKS configuration. -- Do not suggest disabling token validation as a workaround. -- Do not assume the problem is the Scalekit SDK without first ruling out env var misconfiguration. -- Do not paste the full troubleshooting guide at once — work through it step by step. diff --git a/plugins/mcp-auth/agents/scalekit-setup.md b/plugins/mcp-auth/agents/scalekit-setup.md deleted file mode 100644 index e5fcfe8..0000000 --- a/plugins/mcp-auth/agents/scalekit-setup.md +++ /dev/null @@ -1,224 +0,0 @@ ---- -name: Scalekit Setup -description: > - Guides you through setting up the Scalekit authentication SDK in this project. - Detects the tech stack, configures credentials, installs the SDK, initializes - the client, runs the verification test, and optionally configures the MCP server. ---- - -# Scalekit Setup Agent - -You are a setup assistant for integrating Scalekit — an enterprise authentication -platform — into this project. Follow the steps below precisely and in order. -Ask the user for required values when they are missing. Do NOT guess credentials. - -## Step 1: Detect Tech Stack - -Inspect the project root for the following files to determine the language/framework: - -| File | Stack | -|---|---| -| `package.json` | Node.js / TypeScript | -| `requirements.txt`, `pyproject.toml`, `setup.py` | Python | -| `go.mod` | Go | -| `pom.xml`, `build.gradle` | Java | - -If ambiguous, ask the user: _"Which language is this project using? (Node.js / Python / Go / Java)"_ - -## Step 2: Check for Existing Credentials - -Look for a `.env` file (or `.env.local`, `.env.development`) in the project root. - -Check if these three variables exist: -- `SCALEKIT_ENVIRONMENT_URL` -- `SCALEKIT_CLIENT_ID` -- `SCALEKIT_CLIENT_SECRET` - -If any are missing, prompt the user: - -> "Please provide your Scalekit API credentials from Dashboard > Developers > Settings > API credentials: -> 1. Environment URL (e.g. https://acme.scalekit.dev) -> 2. Client ID (e.g. skc_1234567890abcdef) -> 3. Client Secret (e.g. test_abcdef1234567890)" - -Once received, write or append to `.env`: - -```sh -SCALEKIT_ENVIRONMENT_URL= -SCALEKIT_CLIENT_ID= -SCALEKIT_CLIENT_SECRET= -``` - -Also check `.gitignore` and ensure `.env` is listed. If not, append `.env` to `.gitignore`. - -## Step 3: Install the SDK - -Run the correct install command for the detected stack: - -**Node.js** -```bash -npm install @scalekit-sdk/node -``` - -**Python** -```bash -pip install scalekit-sdk -``` - -**Go** -```bash -go get github.com/scalekit-inc/scalekit-sdk-go -``` - -**Java (Maven)** — add to `pom.xml` dependencies: -```xml - - com.scalekit - scalekit-sdk-java - LATEST - -``` - -## Step 4: Create SDK Initialization Code - -Create a new file with the initialization snippet for the detected stack. -If a main entry file already exists (e.g. `index.js`, `main.py`, `main.go`, `Main.java`), -add the snippet near the top after imports, but do NOT overwrite the existing file. - -**Node.js** → create `lib/scalekit.js` (or `lib/scalekit.ts`): -```js -import { Scalekit } from '@scalekit-sdk/node'; - -export const scalekit = new Scalekit( - process.env.SCALEKIT_ENVIRONMENT_URL, - process.env.SCALEKIT_CLIENT_ID, - process.env.SCALEKIT_CLIENT_SECRET -); -``` - -**Python** → create `scalekit_client.py`: -```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') -) -``` - -**Go** → add to `main.go` or create `internal/scalekit.go`: -```go -import ( - "os" - "github.com/scalekit-inc/scalekit-sdk-go" -) - -scalekitClient := scalekit.NewScalekitClient( - os.Getenv("SCALEKIT_ENVIRONMENT_URL"), - os.Getenv("SCALEKIT_CLIENT_ID"), - os.Getenv("SCALEKIT_CLIENT_SECRET"), -) -``` - -**Java** → add to entry class: -```java -import com.scalekit.ScalekitClient; - -ScalekitClient scalekitClient = new ScalekitClient( - System.getenv("SCALEKIT_ENVIRONMENT_URL"), - System.getenv("SCALEKIT_CLIENT_ID"), - System.getenv("SCALEKIT_CLIENT_SECRET") -); -``` - -## Step 5: Verify the Setup - -Create a standalone verification script, then run it. - -**Node.js** → create `verify.js`, then run `node verify.js`: -```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, -); - -const { organizations } = await scalekit.organization.listOrganization({ pageSize: 5 }); -console.log(`✅ Connected! First org: ${organizations[0]?.display_name ?? 'No organizations yet'}`); -``` - -**Python** → create `verify.py`, then run `python verify.py`: -```python -from scalekit import ScalekitClient -import os - -client = ScalekitClient( - os.getenv('SCALEKIT_ENVIRONMENT_URL'), - os.getenv('SCALEKIT_CLIENT_ID'), - os.getenv('SCALEKIT_CLIENT_SECRET') -) -orgs = client.organization.list_organizations(page_size=5) -print(f"✅ Connected! First org: {orgs.display_name if orgs else 'No organizations yet'}") -``` - -**Go** → create `verify.go`, then run `go run verify.go` - -**Java** → create `Verify.java`, then compile and run - -### Success Criteria - -If the script runs without error and prints a response (even "No organizations yet"), the setup is complete. - -If an error occurs: -- `401 Unauthorized` → credentials are incorrect, re-prompt for Step 2 -- `connection refused` or DNS error → environment URL is wrong -- `module not found` → SDK installation failed, re-run Step 3 - -## Step 6: Configure Scalekit MCP Server in Cursor (Optional) - -Ask: _"Would you like to configure the Scalekit MCP server to manage auth via natural language in Cursor?"_ - -If yes, open or create `.cursor/mcp.json` and merge: - -```json -{ - "mcpServers": { - "scalekit": { - "command": "npx", - "args": ["-y", "mcp-remote", "https://mcp.scalekit.com/"] - } - } -} -``` - -Instruct the user to restart Cursor. After restart, an OAuth workflow will launch to authorize the connection. - -## Step 7: Index Scalekit Docs in Cursor (Recommended) - -Remind the user to index Scalekit documentation for accurate in-editor AI answers: - -1. Open Cursor Settings (`Cmd/Ctrl + ,`) -2. Navigate to **Indexing & Docs** -3. Click **Add** -4. Paste `https://docs.scalekit.com/llms-full.txt` -5. Save - -After indexing, use `@Scalekit Docs` in Cursor chat for accurate, up-to-date answers. - -## Completion Checklist - -Before finishing, confirm all of the following: - -- [ ] `.env` file exists with all 3 Scalekit credentials -- [ ] `.env` is in `.gitignore` -- [ ] SDK is installed -- [ ] SDK initialization code is in the project -- [ ] Verification script ran successfully -- [ ] MCP server configured (if user opted in) -- [ ] Docs URL indexed (if user opted in) - -Report the status of each item to the user. diff --git a/plugins/mcp-auth/agents/setup-scalekit.md b/plugins/mcp-auth/agents/setup-scalekit.md deleted file mode 100644 index 044e411..0000000 --- a/plugins/mcp-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/mcp-auth/assets/logo.svg b/plugins/mcp-auth/assets/logo.svg deleted file mode 100644 index 36e373a..0000000 --- a/plugins/mcp-auth/assets/logo.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/plugins/mcp-auth/hooks/hooks.json b/plugins/mcp-auth/hooks/hooks.json deleted file mode 100644 index dd28090..0000000 --- a/plugins/mcp-auth/hooks/hooks.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "description": "Usage beacon for Scalekit mcp-auth plugin", - "hooks": { - "PostToolUse": [ - { - "matcher": ".*", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh mcp-auth post_tool", - "timeout": 10 - } - ] - } - ], - "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/rules/mcp-oauth-discovery.mdc b/plugins/mcp-auth/rules/mcp-oauth-discovery.mdc deleted file mode 100644 index 7fcb524..0000000 --- a/plugins/mcp-auth/rules/mcp-oauth-discovery.mdc +++ /dev/null @@ -1,29 +0,0 @@ ---- -description: Enforce correct implementation of the OAuth Protected Resource discovery endpoint required by MCP clients to locate the Scalekit authorization server. -globs: ["**/server.ts", "**/server.js", "**/main.py", "**/app.py", "**/app.ts", "**/index.ts", "**/index.js"] -alwaysApply: false ---- - -# MCP OAuth Protected Resource Endpoint - -The `.well-known/oauth-protected-resource` endpoint MUST be publicly accessible (no auth required). This is how MCP clients like Claude Desktop, Cursor, and VS Code discover your OAuth authorization server. - -## Required response shape - -```json -{ - "authorization_servers": ["https:///resources/"], - "bearer_methods_supported": ["header"], - "resource": "https://mcp.yourapp.com", - "resource_documentation": "https://mcp.yourapp.com/docs", - "scopes_supported": ["scope:read", "scope:write"] -} -``` - -## Rules - -- Copy the exact `authorization_servers` URL from Scalekit dashboard → MCP Servers → Your server → Metadata JSON -- NEVER require authentication on this endpoint -- ALWAYS skip auth middleware for any path containing `.well-known` -- List ALL scopes configured in the Scalekit dashboard under `scopes_supported` -- `resource` must match the Server URL registered in Scalekit (or use autogenerated `res_123456789` ID if no URL was set) diff --git a/plugins/mcp-auth/rules/mcp-scope-authorization.mdc b/plugins/mcp-auth/rules/mcp-scope-authorization.mdc deleted file mode 100644 index 2a78719..0000000 --- a/plugins/mcp-auth/rules/mcp-scope-authorization.mdc +++ /dev/null @@ -1,52 +0,0 @@ ---- -description: Apply scope-based authorization when implementing MCP tool handlers that need fine-grained access control using Scalekit token validation -globs: -alwaysApply: false ---- - -# Scope-Based MCP Tool Authorization - -Validate scopes at the individual tool execution level, not just at the server middleware level. - -## Node.js - -```typescript -try { - await scalekit.validateToken(token, { - audience: [RESOURCE_ID], - requiredScopes: ['todo:read'] // scope specific to this tool - }); -} catch (error) { - return res.status(403).json({ - error: 'insufficient_scope', - error_description: `Required scope: todo:read`, - scope: 'todo:read' - }); -} -``` - -## Python - -```python -try: - scalekit_client.validate_access_token( - token, - options=TokenValidationOptions( - audience=[RESOURCE_ID], - required_scopes=["todo:read"] - ) - ) -except ScalekitValidateTokenFailureException: - return { - "error": "insufficient_scope", - "error_description": "Required scope: todo:read", - "scope": "todo:read" - } -``` - -## Rules - -- Scope failures are HTTP 403 (not 401) — auth is valid, permission is not -- `error` field must be exactly `"insufficient_scope"` per OAuth 2.0 spec -- Always include the `scope` field in error response so the client knows what to re-request -- Only validate scopes that are configured in the Scalekit dashboard diff --git a/plugins/mcp-auth/rules/mcp-secrets-hygiene.mdc b/plugins/mcp-auth/rules/mcp-secrets-hygiene.mdc deleted file mode 100644 index 6111563..0000000 --- a/plugins/mcp-auth/rules/mcp-secrets-hygiene.mdc +++ /dev/null @@ -1,24 +0,0 @@ ---- -description: Enforce that all Scalekit credentials are loaded from environment variables and never hardcoded in MCP server source code. -alwaysApply: true ---- - -# Scalekit Credentials — Never Hardcode - -ALL Scalekit credentials must come from environment variables, never hardcoded. - -## Required env vars - -``` -SCALEKIT_ENVIRONMENT_URL=https://.scalekit.com -SCALEKIT_CLIENT_ID= -SCALEKIT_CLIENT_SECRET= -``` - -## Rules - -- NEVER inline `SCALEKIT_CLIENT_SECRET` or `SCALEKIT_CLIENT_ID` in source code -- ALWAYS use `process.env.*` (Node.js) or `os.getenv()` (Python) to read credentials -- Add `.env` to `.gitignore` immediately when creating a new MCP server project -- Use secret managers (AWS Secrets Manager, GCP Secret Manager, Vault) for production -- If you see hardcoded credentials anywhere in the codebase, flag it immediately as a security issue diff --git a/plugins/mcp-auth/rules/mcp-token-validation.mdc b/plugins/mcp-auth/rules/mcp-token-validation.mdc deleted file mode 100644 index dada2c5..0000000 --- a/plugins/mcp-auth/rules/mcp-token-validation.mdc +++ /dev/null @@ -1,53 +0,0 @@ ---- -description: Enforce server-side Bearer token validation using the Scalekit SDK in MCP server middleware, never manual JWT parsing. -globs: ["**/middleware.ts", "**/middleware.js", "**/auth*.ts", "**/auth*.js", "**/auth*.py", "**/*middleware*.py"] -alwaysApply: false ---- - -# MCP Token Validation with Scalekit - -Always use the Scalekit SDK to validate tokens — never parse JWTs manually. - -## Node.js pattern - -```typescript -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 -); - -// Extract token -const authHeader = req.headers['authorization']; -const token = authHeader?.startsWith('Bearer ') - ? authHeader.split('Bearer ')?.[1]?.trim() - : null; - -// Validate -await scalekit.validateToken(token, { - audience: [RESOURCE_ID] -}); -``` - -## Python pattern - -```python -from scalekit import ScalekitClient -from scalekit.common.scalekit import TokenValidationOptions - -options = TokenValidationOptions( - issuer=os.getenv("SCALEKIT_ENVIRONMENT_URL"), - audience=[RESOURCE_ID] -) -scalekit_client.validate_token(token, options=options) -``` - -## Rules - -- ALWAYS validate `aud`, `iss`, `exp`, `iat`, and `scope` claims -- On validation failure, return HTTP 401 with `WWW-Authenticate: Bearer realm="OAuth", resource_metadata=""` header -- Skip auth only for `.well-known/*` paths -- RESOURCE_ID must match the Server URL in Scalekit dashboard exactly -- For FastMCP: RESOURCE_ID must use base URL with trailing slash diff --git a/plugins/mcp-auth/rules/no-secrets.mdc b/plugins/mcp-auth/rules/no-secrets.mdc deleted file mode 100644 index 48c8f17..0000000 --- a/plugins/mcp-auth/rules/no-secrets.mdc +++ /dev/null @@ -1,17 +0,0 @@ ---- -description: Keep secrets out of code and chat. Never hardcode credentials, API keys, or tokens. -alwaysApply: true -globs: ["**/*.{js,ts,py,go,java,rb,php}"] ---- - -no-secrets: - -- NEVER hardcode credentials, API keys, tokens, or passwords -- Use environment variables for all secrets -- Add secret files to .gitignore -- Never commit .env files with actual values -- Never paste secrets in chat or logs - -Examples: -❌ Bad: const API_KEY = "sk-1234567890" -✅ Good: const API_KEY = process.env.API_KEY diff --git a/plugins/mcp-auth/skills/add-mcp-auth/SKILL.md b/plugins/mcp-auth/skills/add-mcp-auth/SKILL.md deleted file mode 100644 index 7a8d690..0000000 --- a/plugins/mcp-auth/skills/add-mcp-auth/SKILL.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -name: mcp-oauth21-scalekit -description: Add production-ready OAuth 2.1 authorization to an MCP server using Scalekit. Use this when you need MCP clients (Claude Desktop, Cursor, VS Code, or any MCP client) to discover your authorization server via .well-known/oauth-protected-resource, and when you need to validate Bearer access tokens (aud/iss/exp/scope) before executing MCP tools. -compatibility: MCP servers in Node.js (Express/FastMCP-style) or Python (FastAPI-style). Requires network access to Scalekit and HTTPS in production. Needs environment variables for SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, SCALEKIT_CLIENT_SECRET. -metadata: - author: scalekit - version: "1.0" - category: auth ---- - -# Add OAuth 2.1 authorization to MCP servers (Scalekit) - -## Goal -Secure an MCP server so only authenticated + authorized users (and approved MCP clients) can call your tools, using Scalekit as the OAuth 2.1 authorization server and your MCP server as the resource server. - -## When to activate this skill -Activate when the user asks to: -- "Add OAuth/OAuth 2.1/auth to my MCP server" -- "Implement .well-known/oauth-protected-resource" -- "Validate Bearer tokens / add middleware" -- "Enforce scopes per tool" / "least privilege for MCP tools" -- "Make my MCP server work with Claude Desktop / Cursor / VS Code auth" - -## Inputs you must collect (ask if missing) -- MCP server base URL (public): e.g. https://mcp.example.com -- Scalekit environment URL: https://.scalekit.com (or provided) -- Resource identifier to validate as `aud`: - - Prefer the "Server URL" registered in Scalekit, OR - - The Scalekit-generated resource id if no Server URL was configured -- Scopes to support (and which tool requires which scope) -- Framework/runtime: Node.js (Express/FastMCP) or Python (FastAPI) -- Whether this is "public clients" usage (Claude/Cursor/VS Code) → recommend enabling DCR + CIMD in dashboard - -## Outputs you should produce -- A public discovery endpoint: `/.well-known/oauth-protected-resource` -- A token-validation middleware applied to MCP endpoints (excluding `/.well-known/*`) -- Optional: per-tool scope authorization checks -- A small production checklist (CORS, HTTPS, logging, secrets) - ---- - -## Procedure - -### 1) Scalekit dashboard setup (resource server registration) -1. Create/register an MCP server in the Scalekit dashboard. -2. If this MCP server is meant to be used by public MCP clients, enable: - - Dynamic Client Registration (DCR) - - Client ID Metadata Document (CIMD) -3. Configure: - - Server URL (recommended): set it to your MCP server URL if you want tokens to use it as audience. - - Access token lifetime (typical: 5–60 minutes depending on risk). - - Scopes: define permissions like `todo:read`, `todo:write`, etc. - -Note: If you toggle DCR/CIMD, restart the MCP server (some frameworks cache auth server details). - -### 2) Implement MCP discovery endpoint -Implement `GET /.well-known/oauth-protected-resource` and return the resource metadata JSON from Scalekit dashboard ("Metadata JSON" for your MCP server). - -Minimum fields to include: -- `authorization_servers`: array containing your Scalekit resource authorization server URL (from dashboard) -- `bearer_methods_supported`: include `header` -- `resource`: your MCP server identifier (usually your base URL) -- `resource_documentation`: docs URL (optional but recommended) -- `scopes_supported`: list of scopes you configured - -If the user wants a template, point them to: -- `assets/oauth-protected-resource.json` -- `assets/express/well-known-route.ts` or `assets/fastapi/well_known_route.py` - -### 3) Add Bearer token validation middleware (resource server enforcement) -Apply middleware to all MCP endpoints. -Rules: -- Allow unauthenticated access to `/.well-known/*` so clients can discover metadata. -- For all other routes: - 1. Extract access token from `Authorization: Bearer ` - 2. If missing → respond `401` and include `WWW-Authenticate: Bearer ... resource_metadata=""` - 3. Validate token using Scalekit SDK: - - Validate `aud` includes your configured resource identifier - - Validate issuer if your SDK requires/permits it - - Validate expiry and issued-at (SDK typically handles this) - 4. On validation failure → respond `401` with `WWW-Authenticate` - -Templates: -- Node/Express: `assets/express/auth-middleware.ts` -- Python/FastAPI: `assets/fastapi/auth_middleware.py` - -Security notes: -- Do not log raw tokens. -- Prefer constant-time comparisons where relevant (SDK should handle signatures). -- Keep the middleware small; push details to reference docs. - -### 4) Optional: scope-based authorization at tool execution time -For each MCP tool, define required scope(s) and enforce them when executing the tool. -Suggested approach: -- Maintain a map: `tool_name -> required_scopes` (see `assets/tool-scope-map.example.yaml`) -- At execution: - - Validate token again with `requiredScopes` (or validate once and check claims, depending on SDK support) - - If insufficient scope → return an OAuth-style error response (403, `insufficient_scope`) with a helpful message - -### 5) Testing checklist -- Discovery works: call `/.well-known/oauth-protected-resource` without auth and confirm JSON is correct. -- Middleware rejects missing token (401 + WWW-Authenticate). -- Valid token passes (200). -- Token with wrong `aud` fails. -- Scope enforcement denies (403) and allows with correct scope. -- Test with at least one MCP host (Claude Desktop / Cursor / VS Code) if that's your target. - -### 6) Production checklist -See `references/SECURITY.md`. Minimum: -- HTTPS everywhere -- Correct CORS for MCP endpoints -- Secure secret storage (env/secret manager) -- Monitoring + audit logs for auth failures and tool execution -- Reasonable token lifetimes -- Validate resource audience (`aud`) strictly -- Consider rate limiting on auth-related endpoints - ---- - -## References -- Deep technical notes: `references/REFERENCE.md` -- Scope design patterns: `references/SCOPES.md` -- Production hardening: `references/SECURITY.md` diff --git a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/SKILL.md b/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/SKILL.md deleted file mode 100644 index edc90e5..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/SKILL.md +++ /dev/null @@ -1,89 +0,0 @@ ---- -name: mcp-auth-expressjs-scalekit -description: Add Scalekit OAuth authentication to an Express.js MCP server (TypeScript). Supports two modes: scaffold a new server from scratch, or retrofit an existing Express app. Implements /.well-known/oauth-protected-resource for MCP client discovery, a Bearer-token validation middleware using @scalekit-sdk/node (audience check), and a POST / MCP endpoint using StreamableHTTPServerTransport. -compatibility: Node.js 20+. Express + TypeScript. Requires SK_ENV_URL, SK_CLIENT_ID, SK_CLIENT_SECRET, EXPECTED_AUDIENCE, and PROTECTED_RESOURCE_METADATA JSON. -metadata: - owner: scalekit - topic: mcp-auth - framework: express - language: typescript ---- - -# Add MCP OAuth auth to Express.js (Scalekit) - -## Choose a mode -Ask: "Are we scaffolding a brand-new MCP server repo, or adding MCP auth into an existing Express app?" -- Mode A: New project scaffold (recommended for demos/POCs) -- Mode B: Retrofit existing Express app (recommended for real products) - -## Inputs to collect (ask if missing) -- Server base URL and port; confirm whether trailing slash is required for the audience (example: http://localhost:3002/) -- SK_ENV_URL, SK_CLIENT_ID, SK_CLIENT_SECRET -- PROTECTED_RESOURCE_METADATA JSON (copied from Scalekit dashboard MCP server page) -- EXPECTED_AUDIENCE (must match the Server URL registered in Scalekit) - -## Required outcomes -1) Public discovery endpoint: GET /.well-known/oauth-protected-resource (returns PROTECTED_RESOURCE_METADATA as JSON) -2) Public health endpoint: GET /health -3) Auth middleware: validates Authorization: Bearer , returns 401 + WWW-Authenticate with resource_metadata URL on failure -4) MCP endpoint: POST / protected by middleware, handled via MCP SDK StreamableHTTPServerTransport -5) At least one tool registered with server.tool(...) - ---- - -# Mode A — Scaffold a new project - -## Steps -1) Create a folder and initialize dependencies using templates in: -- assets/new-project/package.json -- assets/new-project/tsconfig.json -- assets/new-project/src/server.ts - -2) Create .env using assets/env.example (fill real values). - -3) Run: -- npm install -- npm run dev - -## Notes -- Ensure EXPECTED_AUDIENCE exactly matches the Scalekit "Server URL" (including trailing slash if used). -- Keep /.well-known/oauth-protected-resource public; MCP clients need it for discovery. - ---- - -# Mode B — Retrofit an existing Express app - -## Identify insertion points -Ask for: -- Existing server entrypoint file (e.g., src/index.ts or src/server.ts) -- Current app router structure and whether POST / is already used -- Existing auth middlewares and CORS settings - -## Patch plan (minimal diffs) -1) Add env vars (SK_*, EXPECTED_AUDIENCE, PROTECTED_RESOURCE_METADATA). -2) Add routes: -- GET /.well-known/oauth-protected-resource (public) -- GET /health (public) -3) Add auth middleware (public-path exemptions + Bearer extraction + validateToken with audience). -4) Add MCP server + route: -- Create McpServer + tools (assets/retrofit/mcp-server.ts) -- Add POST handler (assets/retrofit/mcp-route.ts) -5) Route mounting: -- If POST / is free, mount MCP at / -- If POST / is used, mount MCP at /mcp and update RESOURCE_METADATA_URL accordingly (and ensure clients point to correct MCP URL) - -## Templates -- Auth middleware: assets/retrofit/auth-middleware.ts -- Well-known route: assets/retrofit/well-known-route.ts -- MCP server + tool registration: assets/retrofit/mcp-server.ts -- MCP POST handler: assets/retrofit/mcp-route.ts - ---- - -## Verification checklist -- GET /.well-known/oauth-protected-resource works without Authorization header -- POST MCP endpoint without token -> 401 + WWW-Authenticate (resource_metadata points to the well-known URL) -- Valid token with correct audience -> MCP tool call succeeds -- Wrong-audience token -> 401 - -See references/TROUBLESHOOTING.md for common misconfigurations. diff --git a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/env.example b/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/env.example deleted file mode 100644 index 9f79c07..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/env.example +++ /dev/null @@ -1,8 +0,0 @@ -PORT=3002 -SK_ENV_URL=https://your-env.scalekit.com -SK_CLIENT_ID=your-client-id -SK_CLIENT_SECRET=your-client-secret -EXPECTED_AUDIENCE=http://localhost:3002/ -PROTECTED_RESOURCE_METADATA={"authorization_servers":["https://your-env.scalekit.com/resources/your-resource-id"],"bearer_methods_supported":["header"],"resource":"http://localhost:3002/","resource_documentation":"https://your-docs-url.com","scopes_supported":["todo:read","todo:write"]} -# Optional: MCP server ID for reference -# MCP_SERVER_ID=your-mcp-server-id diff --git a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/new-project/package.json b/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/new-project/package.json deleted file mode 100644 index 5237e5f..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/new-project/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "express-mcp-auth-server", - "version": "1.0.0", - "description": "Express.js MCP server with Scalekit OAuth authentication", - "main": "dist/server.js", - "type": "module", - "scripts": { - "dev": "tsx watch src/server.ts", - "build": "tsc", - "start": "node dist/server.js" - }, - "keywords": ["mcp", "oauth", "scalekit", "express"], - "author": "", - "license": "MIT", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.0.4", - "@scalekit-sdk/node": "^2.4.0", - "cors": "^2.8.5", - "dotenv": "^16.4.7", - "express": "^4.21.2" - }, - "devDependencies": { - "@types/cors": "^2.8.17", - "@types/express": "^5.0.0", - "@types/node": "^22.10.2", - "tsx": "^4.19.2", - "typescript": "^5.7.2" - } -} diff --git a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/new-project/src/server.ts b/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/new-project/src/server.ts deleted file mode 100644 index 36f0aa8..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/new-project/src/server.ts +++ /dev/null @@ -1,91 +0,0 @@ -import 'dotenv/config'; -import express from 'express'; -import cors from 'cors'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; -import { Scalekit } from '@scalekit-sdk/node'; - -const app = express(); -const PORT = parseInt(process.env.PORT || '3002', 10); - -const scalekit = new Scalekit( - process.env.SK_ENV_URL!, - process.env.SK_CLIENT_ID!, - process.env.SK_CLIENT_SECRET! -); - -const EXPECTED_AUDIENCE = process.env.EXPECTED_AUDIENCE!; -const METADATA_URL = `${EXPECTED_AUDIENCE}.well-known/oauth-protected-resource`; - -app.use(cors()); -app.use(express.json()); - -app.get('/health', (req, res) => { - res.json({ status: 'healthy' }); -}); - -app.get('/.well-known/oauth-protected-resource', (req, res) => { - const metadata = JSON.parse(process.env.PROTECTED_RESOURCE_METADATA!); - res.json(metadata); -}); - -const authMiddleware = async (req, res, next) => { - try { - if (req.path.startsWith('/.well-known') || req.path === '/health') { - return next(); - } - - const authHeader = req.headers['authorization']; - const token = authHeader?.startsWith('Bearer ') - ? authHeader.split('Bearer ')[1]?.trim() - : null; - - if (!token) { - return res - .status(401) - .set('WWW-Authenticate', `Bearer realm="OAuth", resource_metadata="${METADATA_URL}"`) - .end(); - } - - await scalekit.validateToken(token, { - audience: [EXPECTED_AUDIENCE] - }); - - next(); - } catch (err) { - return res - .status(401) - .set('WWW-Authenticate', `Bearer realm="OAuth", resource_metadata="${METADATA_URL}"`) - .end(); - } -}; - -const server = new McpServer( - { name: 'express-mcp-server', version: '1.0.0' }, - { capabilities: { tools: {} } } -); - -server.tool('echo', 'Echo back the input', { message: { type: 'string' } }, async ({ message }) => ({ - content: [{ type: 'text', text: `Echo: ${message}` }] -})); - -app.use('/', authMiddleware); - -app.all('/', async (req, res) => { - const transport = new StreamableHTTPServerTransport('/message', { - SSEWriter: (data) => { - res.write(`data: ${data}\n\n`); - } - }); - - await server.connect(transport); - - req.on('data', async (chunk) => { - await transport.handlePostMessage(chunk.toString(), req); - }); -}); - -app.listen(PORT, () => { - console.log(`MCP server running on http://localhost:${PORT}`); - console.log(`Discovery endpoint: http://localhost:${PORT}/.well-known/oauth-protected-resource`); -}); diff --git a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/new-project/tsconfig.json b/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/new-project/tsconfig.json deleted file mode 100644 index 3c007ee..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/new-project/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ES2022", - "lib": ["ES2022"], - "moduleResolution": "bundler", - "resolveJsonModule": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "strict": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} diff --git a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/retrofit/auth-middleware.ts b/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/retrofit/auth-middleware.ts deleted file mode 100644 index fa96c46..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/retrofit/auth-middleware.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Request, Response, NextFunction } from 'express'; -import { Scalekit } from '@scalekit-sdk/node'; - -const scalekit = new Scalekit( - process.env.SK_ENV_URL!, - process.env.SK_CLIENT_ID!, - process.env.SK_CLIENT_SECRET! -); - -const EXPECTED_AUDIENCE = process.env.EXPECTED_AUDIENCE!; -const METADATA_URL = `${EXPECTED_AUDIENCE}.well-known/oauth-protected-resource`; - -export async function authMiddleware(req: Request, res: Response, next: NextFunction) { - try { - if (req.path.startsWith('/.well-known') || req.path === '/health') { - return next(); - } - - const authHeader = req.headers['authorization']; - const token = authHeader?.startsWith('Bearer ') - ? authHeader.split('Bearer ')[1]?.trim() - : null; - - if (!token) { - return res - .status(401) - .set('WWW-Authenticate', `Bearer realm="OAuth", resource_metadata="${METADATA_URL}"`) - .end(); - } - - await scalekit.validateToken(token, { - audience: [EXPECTED_AUDIENCE] - }); - - next(); - } catch (err) { - return res - .status(401) - .set('WWW-Authenticate', `Bearer realm="OAuth", resource_metadata="${METADATA_URL}"`) - .end(); - } -} - -export const WWW_AUTHENTICATE_HEADER = `Bearer realm="OAuth", resource_metadata="${METADATA_URL}"`; diff --git a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/retrofit/mcp-route.ts b/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/retrofit/mcp-route.ts deleted file mode 100644 index 0853eef..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/retrofit/mcp-route.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Request, Response } from 'express'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; - -export function createMcpRoute(server: McpServer) { - return async (req: Request, res: Response) => { - const transport = new StreamableHTTPServerTransport('/message', { - SSEWriter: (data) => { - res.write(`data: ${data}\n\n`); - } - }); - - await server.connect(transport); - - req.on('data', async (chunk) => { - await transport.handlePostMessage(chunk.toString(), req); - }); - }; -} diff --git a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/retrofit/mcp-server.ts b/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/retrofit/mcp-server.ts deleted file mode 100644 index bdee113..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/retrofit/mcp-server.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; - -export function createMcpServer() { - const server = new McpServer( - { name: 'express-mcp-server', version: '1.0.0' }, - { capabilities: { tools: {} } } - ); - - server.tool('echo', 'Echo back the input', { message: { type: 'string' } }, async ({ message }) => ({ - content: [{ type: 'text', text: `Echo: ${message}` }] - })); - - return server; -} diff --git a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/retrofit/well-known-route.ts b/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/retrofit/well-known-route.ts deleted file mode 100644 index bc06687..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/assets/retrofit/well-known-route.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Request, Response } from 'express'; - -export function wellKnownRoute(req: Request, res: Response) { - const metadata = JSON.parse(process.env.PROTECTED_RESOURCE_METADATA!); - res.json(metadata); -} - -export function healthRoute(req: Request, res: Response) { - res.json({ status: 'healthy' }); -} diff --git a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/references/REFERENCE.md b/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/references/REFERENCE.md deleted file mode 100644 index db20e8c..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/references/REFERENCE.md +++ /dev/null @@ -1,98 +0,0 @@ -# Express.js MCP OAuth Reference - -## Core Concepts - -### OAuth 2.1 Flow for MCP -The MCP specification defines a discovery-driven OAuth flow: -1. MCP client fetches `/.well-known/oauth-protected-resource` to find the authorization server -2. MCP client initiates OAuth flow with the authorization server -3. User authenticates and authorizes the client -4. Authorization server issues an access token with `aud` claim containing the resource identifier -5. MCP client includes the token in `Authorization: Bearer ` header -6. Resource server validates the token's `aud`, `iss`, `exp`, and `scope` claims -7. If valid, the tool executes; otherwise, returns 401/403 with appropriate error details - -### Streamable HTTP Transport -Unlike stdio transport, HTTP transport: -- Exposes endpoints for MCP protocol over HTTP -- Uses SSE (Server-Sent Events) for server-to-client messages -- Uses POST requests for client-to-server messages -- Supports authentication headers -- Required for OAuth 2.1 flows - -### WWW-Authenticate Header -This header is critical for MCP clients to discover the auth flow: -``` -WWW-Authenticate: Bearer realm="OAuth", resource_metadata="https://your-server/.well-known/oauth-protected-resource" -``` -Without this header, clients fail silently because they don't know how to authenticate. - -## Scalekit Integration - -### Resource Registration -When registering your MCP server in Scalekit: -1. Enable "Dynamic Client Registration" (DCR) for public clients like Claude/Cursor -2. Enable "Client ID Metadata Document" (CIMD) for automatic client metadata -3. Set "Server URL" to your MCP server's base URL (becomes the expected `aud` value) -4. Configure scopes as `resource:action` patterns (e.g., `todo:read`, `todo:write`) -5. Set access token lifetime (300-3600 seconds recommended) - -### Token Validation -The Scalekit SDK validates tokens by: -1. Verifying the signature using the authorization server's public keys -2. Checking `aud` claim includes your resource identifier -3. Checking `iss` claim matches the authorization server URL -4. Checking `exp` claim (token hasn't expired) -5. Checking `nbf` claim (token is not before time) -6. Optionally checking `scope` claim for required permissions - -## Architecture - -### Express Server Structure -``` -Express App -├── Public Routes -│ ├── GET /.well-known/oauth-protected-resource (discovery) -│ └── GET /health (health check) -├── Auth Middleware -│ ├── Public path exemptions -│ ├── Bearer token extraction -│ └── Scalekit token validation -└── Protected Routes - └── POST / (MCP endpoint) -``` - -### MCP Server Integration -The `StreamableHTTPServerTransport` handles: -- SSE connection establishment for server-initiated messages -- JSON-RPC request/response parsing -- Tool call dispatching to registered tools -- Error formatting according to MCP spec - -## Security Considerations - -### Audience Validation -Always validate the `aud` claim matches your expected audience: -- Prevents token reuse across different resources -- Ensures tokens are used for their intended resource -- Configure in Scalekit dashboard as "Server URL" - -### Scope-Based Authorization -Define and enforce scopes per tool: -- Read operations: `:read` -- Write operations: `:write` -- Admin operations: `:admin` -- Validate both in middleware and at tool execution - -### Error Responses -Return OAuth-compliant errors: -- 401: Missing or invalid token -- 403: Insufficient scope -- Always include `WWW-Authenticate` header on 401 -- Use standard error codes: `invalid_token`, `insufficient_scope` - -### CORS Configuration -Allow necessary origins: -- MCP client domains (Claude, Cursor, VS Code) -- Include `Authorization` header in allowed headers -- Handle preflight OPTIONS requests diff --git a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/references/TROUBLESHOOTING.md b/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/references/TROUBLESHOOTING.md deleted file mode 100644 index eac96d7..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/references/TROUBLESHOOTING.md +++ /dev/null @@ -1,178 +0,0 @@ -# Troubleshooting Express.js MCP OAuth - -## Common Issues - -### MCP Client Fails Silently -**Symptom**: Claude Desktop, Cursor, or VS Code shows connection error without helpful message. - -**Cause**: Missing or incorrect `WWW-Authenticate` header on 401 response. - -**Solution**: -```typescript -// Ensure your 401 response includes WWW-Authenticate -res - .status(401) - .set('WWW-Authenticate', `Bearer realm="OAuth", resource_metadata="${METADATA_URL}"`) - .end(); -``` - -**Verify**: -```bash -curl -i -X POST http://localhost:3002/ -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' -``` -Check for `WWW-Authenticate` in response headers. - -### Token Validation Always Fails -**Symptom**: All requests return 401 even with valid tokens. - -**Cause 1**: Audience mismatch -- Your `EXPECTED_AUDIENCE` doesn't match the "Server URL" in Scalekit dashboard. - -**Solution**: -1. Copy Server URL from Scalekit dashboard (including trailing slash if present) -2. Set `EXPECTED_AUDIENCE` in .env exactly as shown -3. Restart server - -**Cause 2**: Token extraction fails -- Authorization header missing or malformed. - -**Solution**: -```typescript -const authHeader = req.headers['authorization']; -const token = authHeader?.startsWith('Bearer ') - ? authHeader.split('Bearer ')[1]?.trim() - : null; -``` - -### Discovery Endpoint Not Found -**Symptom**: `curl http://localhost:3002/.well-known/oauth-protected-resource` returns 404. - -**Cause**: Route not defined or defined with wrong path. - -**Solution**: -```typescript -// Must be exactly this path -app.get('/.well-known/oauth-protected-resource', (req, res) => { - res.json(JSON.parse(process.env.PROTECTED_RESOURCE_METADATA)); -}); -``` - -### CORS Errors -**Symptom**: Browser console shows CORS errors. - -**Solution**: -```typescript -import cors from 'cors'; - -app.use(cors({ - origin: '*', // Or specific origins in production - methods: ['GET', 'POST', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization'], -})); -``` - -### MCP Inspector Can't Connect -**Symptom**: `npx @modelcontextprotocol/inspector` shows connection errors. - -**Cause 1**: Wrong endpoint URL -- Inspector default is `http://localhost:3002/` - verify your port. - -**Cause 2**: Missing SSE headers -- Streamable HTTP transport requires proper SSE headers. - -**Solution**: -```typescript -app.use(express.json()); -app.use(cors()); -``` - -### Tool Calls Return "Invalid Request" -**Symptom**: Tool registration succeeds but calls fail with JSON-RPC error. - -**Cause**: JSON-RPC format mismatch or tool handler error. - -**Solution**: -1. Ensure tool handlers return proper JSON-RPC response format -2. Check console for uncaught errors in tool handlers -3. Use MCP Inspector to see raw JSON-RPC messages - -## Debugging Tips - -### Enable Debug Logging -```typescript -const DEBUG = process.env.DEBUG === 'true'; - -function debugLog(...args: any[]) { - if (DEBUG) console.log('[DEBUG]', ...args); -} -``` - -Set `DEBUG=true` in .env. - -### Verify Token Contents -Decode your access token (JWT format) to check claims: -```bash -# Get token from MCP client logs -echo "" | jq -R 'split(".") | .[1] | @base64d | fromjson' -``` - -Check `aud`, `iss`, `exp`, and `scope` claims. - -### Test Scalekit Connection -```typescript -import { Scalekit } from '@scalekit-sdk/node'; - -const scalekit = new Scalekit( - process.env.SK_ENV_URL, - process.env.SK_CLIENT_ID, - process.env.SK_CLIENT_SECRET -); - -// Test connection -scalekit.discoverAuthorizationServer() - .then(info => console.log('Connected:', info)) - .catch(err => console.error('Connection failed:', err)); -``` - -### Test Each Component Isolated - -1. **Discovery endpoint**: -```bash -curl http://localhost:3002/.well-known/oauth-protected-resource -``` - -2. **Health endpoint**: -```bash -curl http://localhost:3002/health -``` - -3. **Auth middleware** (should return 401): -```bash -curl -i -X POST http://localhost:3002/ -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' -``` - -4. **With valid token** (use token from MCP client): -```bash -curl -X POST http://localhost:3002/ \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer " \ - -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' -``` - -## Common Environment Variable Mistakes - -| Variable | Common Mistake | Correct Value | -|----------|----------------|---------------| -| `SK_ENV_URL` | Missing protocol | `https://your-env.scalekit.com` | -| `EXPECTED_AUDIENCE` | Wrong casing | Must match Scalekit "Server URL" exactly | -| `PROTECTED_RESOURCE_METADATA` | Not JSON-escaped | Must be valid JSON string | -| `PORT` | Already in use | Choose unused port (3002, 8080, etc.) | - -## Getting Help - -If issues persist: -1. Check Scalekit dashboard for server status -2. Review Scalekit logs for authentication attempts -3. Enable DEBUG mode and capture full request/response -4. Verify MCP client configuration (server URL, auth settings) -5. Test with MCP Inspector to isolate client vs server issues diff --git a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/scripts/scaffold-new-express-mcp.sh b/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/scripts/scaffold-new-express-mcp.sh deleted file mode 100755 index ba6529d..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-expressjs-scalekit/scripts/scaffold-new-express-mcp.sh +++ /dev/null @@ -1,180 +0,0 @@ -#!/bin/bash - -set -e - -PROJECT_NAME=${1:-express-mcp-server} - -echo "Creating new Express MCP server: $PROJECT_NAME" - -mkdir -p "$PROJECT_NAME" -cd "$PROJECT_NAME" - -echo "Copying package.json..." -cat > package.json << 'EOF' -{ - "name": "express-mcp-auth-server", - "version": "1.0.0", - "description": "Express.js MCP server with Scalekit OAuth authentication", - "main": "dist/server.js", - "type": "module", - "scripts": { - "dev": "tsx watch src/server.ts", - "build": "tsc", - "start": "node dist/server.js" - }, - "dependencies": { - "@modelcontextprotocol/sdk": "^1.0.4", - "@scalekit-sdk/node": "^2.4.0", - "cors": "^2.8.5", - "dotenv": "^16.4.7", - "express": "^4.21.2" - }, - "devDependencies": { - "@types/cors": "^2.8.17", - "@types/express": "^5.0.0", - "@types/node": "^22.10.2", - "tsx": "^4.19.2", - "typescript": "^5.7.2" - } -} -EOF - -echo "Copying tsconfig.json..." -cat > tsconfig.json << 'EOF' -{ - "compilerOptions": { - "target": "ES2022", - "module": "ES2022", - "lib": ["ES2022"], - "moduleResolution": "bundler", - "resolveJsonModule": true, - "allowSyntheticDefaultImports": true, - "esModuleInterop": true, - "strict": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "outDir": "./dist", - "rootDir": "./src" - }, - "include": ["src/**/*"], - "exclude": ["node_modules", "dist"] -} -EOF - -echo "Creating .env.example..." -cat > .env.example << 'EOF' -PORT=3002 -SK_ENV_URL=https://your-env.scalekit.com -SK_CLIENT_ID=your-client-id -SK_CLIENT_SECRET=your-client-secret -EXPECTED_AUDIENCE=http://localhost:3002/ -PROTECTED_RESOURCE_METADATA={"authorization_servers":["https://your-env.scalekit.com/resources/your-resource-id"],"bearer_methods_supported":["header"],"resource":"http://localhost:3002/","resource_documentation":"https://your-docs-url.com","scopes_supported":["todo:read","todo:write"]} -EOF - -echo "Copying src/server.ts..." -mkdir -p src -cat > src/server.ts << 'EOF' -import 'dotenv/config'; -import express from 'express'; -import cors from 'cors'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; -import { Scalekit } from '@scalekit-sdk/node'; - -const app = express(); -const PORT = parseInt(process.env.PORT || '3002', 10); - -const scalekit = new Scalekit( - process.env.SK_ENV_URL!, - process.env.SK_CLIENT_ID!, - process.env.SK_CLIENT_SECRET! -); - -const EXPECTED_AUDIENCE = process.env.EXPECTED_AUDIENCE!; -const METADATA_URL = `${EXPECTED_AUDIENCE}.well-known/oauth-protected-resource`; - -app.use(cors()); -app.use(express.json()); - -app.get('/health', (req, res) => { - res.json({ status: 'healthy' }); -}); - -app.get('/.well-known/oauth-protected-resource', (req, res) => { - const metadata = JSON.parse(process.env.PROTECTED_RESOURCE_METADATA!); - res.json(metadata); -}); - -const authMiddleware = async (req, res, next) => { - try { - if (req.path.startsWith('/.well-known') || req.path === '/health') { - return next(); - } - - const authHeader = req.headers['authorization']; - const token = authHeader?.startsWith('Bearer ') - ? authHeader.split('Bearer ')[1]?.trim() - : null; - - if (!token) { - return res - .status(401) - .set('WWW-Authenticate', `Bearer realm="OAuth", resource_metadata="${METADATA_URL}"`) - .end(); - } - - await scalekit.validateToken(token, { - audience: [EXPECTED_AUDIENCE] - }); - - next(); - } catch (err) { - return res - .status(401) - .set('WWW-Authenticate', `Bearer realm="OAuth", resource_metadata="${METADATA_URL}"`) - .end(); - } -}; - -const server = new McpServer( - { name: 'express-mcp-server', version: '1.0.0' }, - { capabilities: { tools: {} } } -); - -server.tool('echo', 'Echo back the input', { message: { type: 'string' } }, async ({ message }) => ({ - content: [{ type: 'text', text: `Echo: ${message}` }] -})); - -app.use('/', authMiddleware); - -app.all('/', async (req, res) => { - const transport = new StreamableHTTPServerTransport('/message', { - SSEWriter: (data) => { - res.write(`data: ${data}\n\n`); - } - }); - - await server.connect(transport); - - req.on('data', async (chunk) => { - await transport.handlePostMessage(chunk.toString(), req); - }); -}); - -app.listen(PORT, () => { - console.log(`MCP server running on http://localhost:${PORT}`); - console.log(`Discovery endpoint: http://localhost:${PORT}/.well-known/oauth-protected-resource`); -}); -EOF - -echo "" -echo "Next steps:" -echo "1. cd $PROJECT_NAME" -echo "2. cp .env.example .env" -echo "3. Edit .env with your Scalekit credentials" -echo "4. npm install" -echo "5. npm run dev" -echo "" -echo "After server starts, test with:" -echo " curl http://localhost:3002/.well-known/oauth-protected-resource" -echo " npx @modelcontextprotocol/inspector http://localhost:3002/" diff --git a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/SKILL.md b/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/SKILL.md deleted file mode 100644 index 1beea20..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/SKILL.md +++ /dev/null @@ -1,230 +0,0 @@ ---- -name: mcp-auth-fastapi-fastmcp-scalekit -description: Add Scalekit OAuth authentication to a FastAPI + FastMCP server (Python). Use when you need FastAPI-level middleware control over token validation alongside FastMCP tools — for example, when adding auth to an existing FastAPI app, adding custom routes/middleware, or when you need more control than FastMCP's built-in ScalekitProvider offers. Implements /.well-known/oauth-protected-resource, a Starlette @app.middleware("http") that validates Authorization: Bearer tokens via Scalekit SDK (issuer + audience), and mounts FastMCP via app.mount("/", mcp_app). -compatibility: Python 3.11+. FastAPI, FastMCP, Uvicorn, scalekit-sdk-python. Requires SK_ENV_URL, SK_CLIENT_ID, SK_CLIENT_SECRET, EXPECTED_AUDIENCE, and PROTECTED_RESOURCE_METADATA JSON. -metadata: - owner: scalekit - topic: mcp-auth - framework: fastapi-fastmcp - language: python ---- - -# Add OAuth auth to FastAPI + FastMCP (Scalekit) - -## When to use this skill vs the FastMCP skill -Use THIS skill when: -- You need custom FastAPI middleware, routes, or dependency injection alongside MCP tools -- You're adding MCP to an existing FastAPI application -- You need more control over request handling than FastMCP's built-in ScalekitProvider offers - -Use the `mcp-auth-fastmcp-scalekit` skill instead when: -- You're building a standalone MCP server and don't need FastAPI-specific features -- You want the simplest possible setup (5-line auth config) - -## Critical wiring: how FastMCP mounts into FastAPI -FastMCP is NOT run standalone. It is mounted inside FastAPI: -```python -mcp_app = mcp.http_app(path="/") -app = FastAPI(lifespan=mcp_app.lifespan) # lifespan MUST come from mcp_app -# ... add middleware and routes BEFORE mounting ... -app.mount("/", mcp_app) # MUST be last -``` -Order matters: -1. Create mcp_app from FastMCP -2. Create FastAPI with lifespan=mcp_app.lifespan -3. Add CORSMiddleware -4. Add @app.middleware("http") auth middleware -5. Add GET /.well-known/oauth-protected-resource (public) -6. Add GET /health (public) -7. app.mount("/", mcp_app) — ALWAYS last - -## Inputs to collect (ask if missing) -- PORT (default 3002) -- SK_ENV_URL, SK_CLIENT_ID, SK_CLIENT_SECRET -- EXPECTED_AUDIENCE — must match the Server URL registered in Scalekit (with trailing slash, e.g. http://localhost:3002/) -- PROTECTED_RESOURCE_METADATA JSON — copied from Scalekit dashboard MCP server page - -## Required outcomes -1. GET /.well-known/oauth-protected-resource → returns PROTECTED_RESOURCE_METADATA JSON (no auth required) -2. GET /health → {"status": "healthy"} (no auth required) -3. @app.middleware("http") auth_middleware: - - Exempts /.well-known/oauth-protected-resource and /health - - Extracts Authorization: Bearer - - On missing token → 401 + WWW-Authenticate header - - Validates via scalekit_client.validate_access_token(token, options=TokenValidationOptions(issuer=SK_ENV_URL, audience=[EXPECTED_AUDIENCE])) - - On invalid → 401 + WWW-Authenticate -4. FastMCP mounted at / with lifespan=mcp_app.lifespan -5. At least one @mcp.tool registered - ---- - -# Mode A — New project scaffold - -## Steps -1. Create directory and virtual environment: - ```bash - mkdir -p - cd - python3 -m venv .venv - source .venv/bin/activate - ``` - -2. Install dependencies: - ```bash - pip install -r assets/requirements.txt - ``` - -3. Create .env from assets/env.example (fill real values from Scalekit dashboard). - -4. Use assets/main-minimal.py as starting point. Add your tools using assets/tool-template.py. - -5. Run: - ```bash - python main.py - ``` - -6. Test with MCP Inspector (point to http://localhost:3002/ — NOT /mcp): - ```bash - npx @modelcontextprotocol/inspector@latest - ``` - ---- - -# Mode B — Retrofit an existing FastAPI app - -## Identify insertion points -Ask for the existing server entrypoint. Look for: -- Where `app = FastAPI(...)` is instantiated -- Whether `app.mount(...)` is already used -- Existing middleware definitions and their order -- Whether POST / is already taken (if so, mount MCP at /mcp instead and update RESOURCE_METADATA_URL) - -## Patch plan (minimal diffs) - -### 1. Add env vars -```env -SK_ENV_URL=... -SK_CLIENT_ID=... -SK_CLIENT_SECRET=... -EXPECTED_AUDIENCE=http://localhost:3002/ -PROTECTED_RESOURCE_METADATA='...' -``` - -### 2. Add imports -```python -from fastmcp import FastMCP, Context -from scalekit import ScalekitClient -from scalekit.common.scalekit import TokenValidationOptions -from starlette.middleware.cors import CORSMiddleware -from fastapi import Request, Response -``` - -### 3. Add Scalekit client + constants -```python -RESOURCE_METADATA_URL = f"http://localhost:{PORT}/.well-known/oauth-protected-resource" -WWW_HEADER = { - "WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{RESOURCE_METADATA_URL}"' -} -scalekit_client = ScalekitClient( - env_url=SK_ENV_URL, - client_id=SK_CLIENT_ID, - client_secret=SK_CLIENT_SECRET, -) -``` - -### 4. Create FastMCP instance + tools BEFORE patching FastAPI -```python -mcp = FastMCP("Your Server Name", stateless_http=True) - -@mcp.tool(name="your_tool", description="...") -async def your_tool(...) -> dict: - ... -``` - -### 5. Patch FastAPI instantiation -Before: -```python -app = FastAPI() -``` -After: -```python -mcp_app = mcp.http_app(path="/") -app = FastAPI(lifespan=mcp_app.lifespan) -``` -WARNING: If the existing app already sets lifespan, merge the lifespans using an async context manager. - -### 6. Add CORSMiddleware (before auth middleware) -```python -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["GET", "POST", "OPTIONS"], - allow_headers=["*"] -) -``` - -### 7. Add auth middleware -```python -@app.middleware("http") -async def auth_middleware(request: Request, call_next): - PUBLIC_PATHS = {"/health", "/.well-known/oauth-protected-resource"} - if request.url.path in PUBLIC_PATHS: - 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_HEADER, - media_type="application/json" - ) - - token = auth_header.split("Bearer ", 1)[0].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) -``` - -### 8. Add public routes -```python -@app.get("/.well-known/oauth-protected-resource") -async def oauth_metadata(): - if not PROTECTED_RESOURCE_METADATA: - return Response('{"error": "config missing"}', status_code=500, media_type="application/json") - return Response(json.dumps(json.loads(PROTECTED_RESOURCE_METADATA), indent=2), media_type="application/json") - -@app.get("/health") -async def health_check(): - return {"status": "healthy"} -``` - -### 9. Mount FastMCP LAST -```python -app.mount("/", mcp_app) -``` - ---- - -## Verification checklist -- GET /.well-known/oauth-protected-resource returns JSON without auth -- GET /health returns {"status": "healthy"} -- POST to MCP endpoint without token → 401 + WWW-Authenticate -- POST to MCP endpoint with valid token → tool response -- Wrong-audience token → 401 -- Tool call works end-to-end in MCP Inspector (connect to http://localhost:PORT/) - -See references/TROUBLESHOOTING.md for common issues. diff --git a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/env.example b/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/env.example deleted file mode 100644 index b606ae2..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/env.example +++ /dev/null @@ -1,6 +0,0 @@ -PORT=3002 -SK_ENV_URL=https://.scalekit.com -SK_CLIENT_ID= -SK_CLIENT_SECRET= -PROTECTED_RESOURCE_METADATA='' -EXPECTED_AUDIENCE=http://localhost:3002/ diff --git a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/main-minimal.py b/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/main-minimal.py deleted file mode 100644 index ca96599..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/main-minimal.py +++ /dev/null @@ -1,63 +0,0 @@ -import json, os -from dotenv import load_dotenv -from fastapi import FastAPI, Request, Response -from fastmcp import FastMCP, Context -from scalekit import ScalekitClient -from scalekit.common.scalekit import TokenValidationOptions -from starlette.middleware.cors import CORSMiddleware - -load_dotenv() - -PORT = int(os.getenv("PORT", "3002")) -SK_ENV_URL = os.getenv("SK_ENV_URL", "") -SK_CLIENT_ID = os.getenv("SK_CLIENT_ID", "") -SK_CLIENT_SECRET = os.getenv("SK_CLIENT_SECRET", "") -EXPECTED_AUDIENCE = os.getenv("EXPECTED_AUDIENCE", "") -PROTECTED_RESOURCE_METADATA = os.getenv("PROTECTED_RESOURCE_METADATA", "") - -RESOURCE_METADATA_URL = f"http://localhost:{PORT}/.well-known/oauth-protected-resource" -WWW_HEADER = {"WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{RESOURCE_METADATA_URL}"} - -scalekit_client = ScalekitClient(env_url=SK_ENV_URL, client_id=SK_CLIENT_ID, client_secret=SK_CLIENT_SECRET) - -mcp = FastMCP("My MCP Server", stateless_http=True) - -@mcp.tool(name="hello", description="Say hello.") -async def hello(name: str, ctx: Context | None = None) -> dict: - return {"content": [{"type": "text", "text": f"Hi {name}!"}]} - -mcp_app = mcp.http_app(path="/") -app = FastAPI(lifespan=mcp_app.lifespan) - -app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, - allow_methods=["GET", "POST", "OPTIONS"], allow_headers=["*"]) - -@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_HEADER, media_type="application/json") - token = auth_header.split("Bearer ", 1)[0].strip() - try: - is_valid = scalekit_client.validate_access_token(token, options=TokenValidationOptions(issuer=SK_ENV_URL, audience=[EXPECTED_AUDIENCE])) - if not is_valid: - raise ValueError() - except Exception: - return Response('{"error":"Token validation failed"}', status_code=401, headers=WWW_HEADER, media_type="application/json") - return await call_next(request) - -@app.get("/.well-known/oauth-protected-resource") -async def oauth_metadata(): - return Response(json.dumps(json.loads(PROTECTED_RESOURCE_METADATA), indent=2), media_type="application/json") - -@app.get("/health") -async def health(): - return {"status": "healthy"} - -app.mount("/", mcp_app) - -if __name__ == "__main__": - import uvicorn - uvicorn.run(app, host="0.0.0.0", port=PORT) diff --git a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/main.py b/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/main.py deleted file mode 100644 index 03069c2..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/main.py +++ /dev/null @@ -1,89 +0,0 @@ -import json, os -from dotenv import load_dotenv -from fastapi import FastAPI, Request, Response -from fastmcp import FastMCP, Context -from scalekit import ScalekitClient -from scalekit.common.scalekit import TokenValidationOptions -from starlette.middleware.cors import CORSMiddleware - -load_dotenv() - -PORT = int(os.getenv("PORT", "3002")) -SK_ENV_URL = os.getenv("SK_ENV_URL", "") -SK_CLIENT_ID = os.getenv("SK_CLIENT_ID", "") -SK_CLIENT_SECRET = os.getenv("SK_CLIENT_SECRET", "") -EXPECTED_AUDIENCE = os.getenv("EXPECTED_AUDIENCE", "") -PROTECTED_RESOURCE_METADATA = os.getenv("PROTECTED_RESOURCE_METADATA", "") - -RESOURCE_METADATA_URL = f"http://localhost:{PORT}/.well-known/oauth-protected-resource" -WWW_HEADER = {"WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{RESOURCE_METADATA_URL}"} - -scalekit_client = ScalekitClient(env_url=SK_ENV_URL, client_id=SK_CLIENT_ID, client_secret=SK_CLIENT_SECRET) - -greetings = {} - -mcp = FastMCP("Greeting MCP Server", stateless_http=True) - -@mcp.tool(name="greet", description="Greet someone by name") -async def greet(name: str, ctx: Context | None = None) -> dict: - """Create a greeting for someone. Requires: greet:write scope.""" - greeting_id = str(len(greetings) + 1) - greetings[greeting_id] = {"id": greeting_id, "name": name, "greeting": f"Hello, {name}!"} - return {"content": [{"type": "text", "text": f"Greeting created: {greetings[greeting_id]['greeting']}"}]} - -@mcp.tool(name="list_greetings", description="List all greetings") -async def list_greetings(ctx: Context | None = None) -> dict: - """List all stored greetings. Requires: greet:read scope.""" - greetings_list = list(greetings.values()) - return {"content": [{"type": "text", "text": json.dumps(greetings_list, indent=2)}]} - -@mcp.tool(name="get_greeting", description="Get a specific greeting by ID") -async def get_greeting(greeting_id: str, ctx: Context | None = None) -> dict: - """Get a specific greeting. Requires: greet:read scope.""" - if greeting_id not in greetings: - return {"content": [{"type": "text", "text": "Greeting not found"}]} - return {"content": [{"type": "text", "text": greetings[greeting_id]["greeting"]}]} - -@mcp.tool(name="delete_greeting", description="Delete a greeting by ID") -async def delete_greeting(greeting_id: str, ctx: Context | None = None) -> dict: - """Delete a greeting. Requires: greet:write scope.""" - if greeting_id not in greetings: - return {"content": [{"type": "text", "text": "Greeting not found"}]} - deleted = greetings.pop(greeting_id) - return {"content": [{"type": "text", "text": f"Deleted greeting: {deleted['greeting']}"}]} - -mcp_app = mcp.http_app(path="/") -app = FastAPI(lifespan=mcp_app.lifespan) - -app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, - allow_methods=["GET", "POST", "OPTIONS"], allow_headers=["*"]) - -@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_HEADER, media_type="application/json") - token = auth_header.split("Bearer ", 1)[0].strip() - try: - is_valid = scalekit_client.validate_access_token(token, options=TokenValidationOptions(issuer=SK_ENV_URL, audience=[EXPECTED_AUDIENCE])) - if not is_valid: - raise ValueError() - except Exception: - return Response('{"error":"Token validation failed"}', status_code=401, headers=WWW_HEADER, media_type="application/json") - return await call_next(request) - -@app.get("/.well-known/oauth-protected-resource") -async def oauth_metadata(): - return Response(json.dumps(json.loads(PROTECTED_RESOURCE_METADATA), indent=2), media_type="application/json") - -@app.get("/health") -async def health(): - return {"status": "healthy"} - -app.mount("/", mcp_app) - -if __name__ == "__main__": - import uvicorn - uvicorn.run(app, host="0.0.0.0", port=PORT) diff --git a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/requirements.txt b/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/requirements.txt deleted file mode 100644 index 7f91900..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -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 -cryptography>=41.0.0 -scalekit-sdk-python>=2.4.0 -starlette>=0.27.0 diff --git a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/tool-template.py b/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/tool-template.py deleted file mode 100644 index 6ce0a58..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/assets/tool-template.py +++ /dev/null @@ -1,13 +0,0 @@ -@mcp.tool(name="", description="") -async def (: , ctx: Context | None = None) -> dict: - """ - . - Requires: scope. - """ - # TODO: implement scope check if needed - # token = ctx.request.state.token - # if "" not in token.get("scopes", []): - # return {"error": "Insufficient scope: required"} - - # TODO: implement tool logic here - return {"content": [{"type": "text", "text": "Result"}]} diff --git a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/references/REFERENCE.md b/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/references/REFERENCE.md deleted file mode 100644 index 21b4cc5..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/references/REFERENCE.md +++ /dev/null @@ -1,286 +0,0 @@ -# FastAPI + FastMCP OAuth Reference - -## Core Concepts - -### Architecture: FastMCP Mounted in FastAPI -Unlike standalone FastMCP servers, FastMCP is mounted as an ASGI app inside FastAPI: -```python -mcp = FastMCP("Server", stateless_http=True) -mcp_app = mcp.http_app(path="/") -app = FastAPI(lifespan=mcp_app.lifespan) -# ... middleware and routes ... -app.mount("/", mcp_app) # Mount LAST -``` - -### Why Mount FastMCP in FastAPI? -- Use FastAPI's dependency injection alongside MCP tools -- Add custom HTTP middleware before MCP requests -- Mix non-MCP routes with MCP endpoints -- Leverage FastAPI's auto-documentation and validation -- Integrate with existing FastAPI applications - -### Critical Order of Operations -The order of FastAPI setup is critical: - -1. **Create FastMCP instance** (with tools) -2. **Generate mcp_app** via `mcp.http_app(path="/")` -3. **Create FastAPI** with `lifespan=mcp_app.lifespan` -4. **Add CORSMiddleware** (must be before auth) -5. **Add auth middleware** (`@app.middleware("http")`) -6. **Add public routes** (/.well-known, /health) -7. **Mount FastMCP LAST** (`app.mount("/", mcp_app)`) - -### Why Mount Last? -FastMCP's mount at `/` is a catch-all route. Any routes added after the mount will never be reached. - -## Token Validation - -### Scalekit Client Configuration -```python -from scalekit import ScalekitClient -from scalekit.common.scalekit import TokenValidationOptions - -scalekit_client = ScalekitClient( - env_url=SK_ENV_URL, - client_id=SK_CLIENT_ID, - client_secret=SK_CLIENT_SECRET, -) -``` - -### Token Validation in Middleware -```python -@app.middleware("http") -async def auth_middleware(request: Request, call_next): - token = extract_bearer_token(request) - - 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) -``` - -### WWW-Authenticate Header -Required for MCP client OAuth discovery: -```python -WWW_HEADER = { - "WWW-Authenticate": f'Bearer realm="OAuth", resource_metadata="{RESOURCE_METADATA_URL}"' -} -``` - -## Comparison: Three Auth Approaches - -| Aspect | Standalone FastMCP | FastAPI + FastMCP | Express | -|--------|-------------------|------------------|---------| -| **Language** | Python | Python | TypeScript | -| **Auth Mechanism** | `ScalekitProvider` plugin | `@app.middleware("http")` | `app.use()` middleware | -| **`.well-known`** | Automatic | Manual FastAPI route | Manual Express route | -| **Token Validation** | Built-in provider | `validate_access_token()` | `validateToken()` | -| **Audience Env Var** | `SCALEKIT_RESOURCE_ID` + `MCP_URL` | `EXPECTED_AUDIENCE` | `EXPECTED_AUDIENCE` | -| **MCP Endpoint** | `/mcp` (auto) | `/` (mount path) | `/` (you define) | -| **Client Secret** | Not required | Required (`SK_CLIENT_SECRET`) | Required (`SK_CLIENT_SECRET`) | -| **Use When** | Simple standalone MCP | Existing FastAPI app | Node.js/TypeScript | - -### Choosing the Right Approach - -- **Standalone FastMCP**: Quick prototypes, minimal code, no existing FastAPI app -- **FastAPI + FastMCP**: Existing FastAPI app, custom middleware, mixed routes -- **Express**: Node.js ecosystem, existing Express app, TypeScript preference - -## Scope Enforcement - -### No Built-in Scope Checking -FastAPI + FastMCP does NOT automatically check scopes. You must implement scope validation: - -```python -# Option 1: Manual scope check in tools -@mcp.tool(name="create_todo", description="Create a todo") -async def create_todo(text: str, ctx: Context | None = None) -> dict: - token = ctx.request.state.token # Extract from middleware - if "todo:write" not in token.get("scopes", []): - return {"error": "Insufficient scope: todo:write required"} - # ... tool logic ... -``` - -```python -# Option 2: Add token to request state in middleware -@app.middleware("http") -async def auth_middleware(request: Request, call_next): - # ... validate token ... - # After validation, decode token and attach to request state - request.state.token = decode_jwt(token) - return await call_next(request) -``` - -## Lifespan Management - -### FastMCP Lifespan -FastMCP's http_app provides a lifespan for managing startup/shutdown: -```python -mcp_app = mcp.http_app(path="/") -app = FastAPI(lifespan=mcp_app.lifespan) -``` - -This handles: -- MCP server startup -- Tool registration -- Connection management - -### Merging Lifespans -If your app already has a lifespan: -```python -from contextlib import asynccontextmanager - -@asynccontextmanager -async def combined_lifespan(app: FastAPI): - # Startup - await mcp_app.router.startup() - await your_startup_logic() - yield - # Shutdown - await your_shutdown_logic() - await mcp_app.router.shutdown() - -app = FastAPI(lifespan=combined_lifespan) -``` - -## Route Structure - -### Public Routes (No Auth) -```python -@app.get("/health") -async def health_check(): - return {"status": "healthy"} - -@app.get("/.well-known/oauth-protected-resource") -async def oauth_metadata(): - return Response( - json.dumps(json.loads(PROTECTED_RESOURCE_METADATA), indent=2), - media_type="application/json" - ) -``` - -### Protected Routes (Require Auth) -All routes under the FastMCP mount (`/`) are protected by middleware. Tools are automatically protected. - -## CORS Configuration - -### CORSMiddleware Setup -```python -from starlette.middleware.cors import CORSMiddleware - -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], # Configure appropriately for production - allow_credentials=True, - allow_methods=["GET", "POST", "OPTIONS"], - allow_headers=["*"] -) -``` - -### Important: CORS Before Auth -CORSMiddleware must be added BEFORE auth middleware: -1. CORSMiddleware (handles preflight) -2. Auth middleware (validates tokens) -3. Public routes (/.well-known, /health) -4. Mount FastMCP - -## Error Handling - -### OAuth-Compliant Errors -```python -# 401: No token or invalid token -return Response( - '{"error": "Token validation failed"}', - status_code=401, - headers=WWW_HEADER, - media_type="application/json" -) - -# 403: Insufficient scope (if implementing scope checks) -return Response( - '{"error": "insufficient_scope", "error_description": "todo:write required"}', - status_code=403, - media_type="application/json" -) - -# 500: Configuration error -return Response( - '{"error": "config missing"}', - status_code=500, - media_type="application/json" -) -``` - -## Production Considerations - -### HTTPS Required -OAuth 2.1 requires HTTPS in production. Configure Uvicorn with: -```python -import uvicorn - -uvicorn.run( - app, - host="0.0.0.0", - port=PORT, - ssl_keyfile="key.pem", - ssl_certfile="cert.pem" -) -``` - -### Secret Management -Never commit credentials: -```bash -# .gitignore -.env -*.pem -``` - -```python -# Load from environment only -SK_CLIENT_SECRET = os.getenv("SK_CLIENT_SECRET") -if not SK_CLIENT_SECRET: - raise ValueError("SK_CLIENT_SECRET environment variable is required") -``` - -### Logging -```python -import logging - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -@app.middleware("http") -async def auth_middleware(request: Request, call_next): - # ... token validation ... - logger.info(f"Authenticated request: {request.url.path}") - return await call_next(request) -``` - -### Rate Limiting -Consider adding rate limiting to auth endpoints: -```python -from slowapi import Limiter -from slowapi.util import get_remote_address - -limiter = Limiter(key_func=get_remote_address) -app.state.limiter = limiter - -@app.get("/.well-known/oauth-protected-resource") -@limiter.limit("10/minute") -async def oauth_metadata(request: Request): - ... -``` diff --git a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/references/TROUBLESHOOTING.md b/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/references/TROUBLESHOOTING.md deleted file mode 100644 index 91541a4..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastapi-fastmcp-scalekit/references/TROUBLESHOOTING.md +++ /dev/null @@ -1,369 +0,0 @@ -# FastAPI + FastMCP OAuth Troubleshooting - -## Common Issues - -### FastMCP Tools Not Registered -**Symptom**: MCP Inspector connects but shows no tools or `tools/list` returns empty array. - -**Cause 1**: FastMCP created but no tools added before mounting -```python -# Wrong order -mcp = FastMCP("Server") -mcp_app = mcp.http_app(path="/") -app = FastAPI(lifespan=mcp_app.lifespan) -# ... mount app ... -@mcp.tool # Too late - already mounted -def my_tool(): - ... -``` - -**Solution**: Add all tools BEFORE creating mcp_app: -```python -mcp = FastMCP("Server") - -@mcp.tool # Add BEFORE http_app() -def my_tool(): - ... - -mcp_app = mcp.http_app(path="/") -app = FastAPI(lifespan=mcp_app.lifespan) -``` - -**Cause 2**: Mount path mismatch -- You mounted at `/mcp` but Inspector connects to `/` - -**Solution**: Use consistent mount path: -```python -# Option 1: Mount at / -mcp_app = mcp.http_app(path="/") -app.mount("/", mcp_app) -# Connect to: http://localhost:3002/ - -# Option 2: Mount at /mcp -mcp_app = mcp.http_app(path="/mcp") -app.mount("/mcp", mcp_app) -# Connect to: http://localhost:3002/mcp -``` - -### Lifespan Errors -**Symptom**: `AttributeError: 'lifespan' attribute not found` or startup/shutdown errors. - -**Cause 1**: FastAPI created without lifespan from mcp_app -```python -# Wrong -app = FastAPI() # Missing lifespan -app.mount("/", mcp_app) -``` - -**Solution**: Use mcp_app's lifespan: -```python -mcp_app = mcp.http_app(path="/") -app = FastAPI(lifespan=mcp_app.lifespan) # Use mcp_app lifespan -app.mount("/", mcp_app) -``` - -**Cause 2**: Merging lifespans incorrectly -- Existing app has lifespan but you replaced it - -**Solution**: Use async context manager to merge: -```python -from contextlib import asynccontextmanager - -@asynccontextmanager -async def combined_lifespan(app: FastAPI): - await existing_startup() - await mcp_app.router.startup() - yield - await existing_shutdown() - await mcp_app.router.shutdown() - -app = FastAPI(lifespan=combined_lifespan) -``` - -### Routes After Mount Never Reached -**Symptom**: Routes added after `app.mount("/", mcp_app)` return 404. - -**Cause**: FastMCP's mount at `/` catches all requests -- Order matters: mount MUST be last - -**Solution**: Reorder code: -```python -# Wrong order -app.mount("/", mcp_app) # Mount first (WRONG) -@app.get("/custom") -async def custom_route(): - return {"custom": True} - -# Correct order -@app.get("/custom") -async def custom_route(): - return {"custom": True} -app.mount("/", mcp_app) # Mount last (CORRECT) -``` - -### CORS Preflight Fails -**Symptom**: Browser OPTIONS requests fail or CORS errors in console. - -**Cause**: CORSMiddleware missing or order wrong - -**Solution**: Add CORS before auth middleware: -```python -# Wrong order -@app.middleware("http") -async def auth_middleware(...): - ... -app.add_middleware(CORSMiddleware, ...) # After auth (WRONG) - -# Correct order -app.add_middleware(CORSMiddleware, ...) # First -@app.middleware("http") -async def auth_middleware(...): - ... -``` - -### Auth Middleware Blocks Public Routes -**Symptom**: GET /.well-known/oauth-protected-resource returns 401. - -**Cause**: Public paths not exempted from auth middleware - -**Solution**: Exempt public paths: -```python -@app.middleware("http") -async def auth_middleware(request: Request, call_next): - PUBLIC_PATHS = {"/health", "/.well-known/oauth-protected-resource"} - if request.url.path in PUBLIC_PATHS: - return await call_next(request) # Skip auth - - # ... continue with auth logic ... -``` - -### Token Validation Always Fails -**Symptom**: All requests return 401 even with valid tokens. - -**Cause 1**: Wrong audience (`EXPECTED_AUDIENCE`) -- Doesn't match Scalekit dashboard "Server URL" - -**Solution**: -```bash -# Copy Server URL from Scalekit dashboard exactly -EXPECTED_AUDIENCE=http://localhost:3002/ # Note trailing slash -``` - -**Cause 2**: Missing `SK_CLIENT_SECRET` -- FastAPI + FastMCP requires client secret (unlike standalone FastMCP) - -**Solution**: -```env -SK_CLIENT_SECRET=your-secret-from-scalekit-dashboard -``` - -**Cause 3**: Wrong issuer (`SK_ENV_URL`) - -**Solution**: -```env -# Must match Scalekit environment URL -SK_ENV_URL=https://your-env.scalekit.com -``` - -### WWW-Authenticate Header Missing -**Symptom**: 401 response but no `WWW-Authenticate` header. - -**Cause**: WWW-Authenticate not included in 401 response - -**Solution**: -```python -return Response( - '{"error": "Token validation failed"}', - status_code=401, - headers=WWW_HEADER, # ← Include WWW-Authenticate - media_type="application/json" -) -``` - -### Discovery Endpoint Returns 500 -**Symptom**: `/.well-known/oauth-protected-resource` returns 500 error. - -**Cause**: `PROTECTED_RESOURCE_METADATA` not set or invalid JSON - -**Solution**: -```python -@app.get("/.well-known/oauth-protected-resource") -async def oauth_metadata(): - if not PROTECTED_RESOURCE_METADATA: - return Response( - '{"error": "config missing"}', - status_code=500, - media_type="application/json" - ) - try: - metadata = json.loads(PROTECTED_RESOURCE_METADATA) - return Response( - json.dumps(metadata, indent=2), - media_type="application/json" - ) - except json.JSONDecodeError: - return Response( - '{"error": "Invalid JSON in PROTECTED_RESOURCE_METADATA"}', - status_code=500, - media_type="application/json" - ) -``` - -### MCP Inspector Connection Timeout -**Symptom**: Inspector shows "Failed to connect" or timeout. - -**Cause 1**: Wrong URL -- Connecting to `/mcp` when mounted at `/` -- Or connecting to `/` when mounted at `/mcp` - -**Solution**: Check mount path: -```python -# If mounted at / -mcp_app = mcp.http_app(path="/") -app.mount("/", mcp_app) -# Inspector URL: http://localhost:3002/ - -# If mounted at /mcp -mcp_app = mcp.http_app(path="/mcp") -app.mount("/mcp", mcp_app) -# Inspector URL: http://localhost:3002/mcp -``` - -**Cause 2**: Server not running or wrong port - -**Solution**: -```bash -# Check server is running -curl http://localhost:3002/health - -# Should return: {"status": "healthy"} -``` - -### Port Already in Use -**Symptom**: `OSError: [Errno 48] Address already in use` - -**Solution**: -```bash -# Change PORT in .env -PORT=3003 - -# Or kill existing process -lsof -ti:3002 | xargs kill -9 # Linux/Mac -``` - -### Import Errors -**Symptom**: `ModuleNotFoundError: No module named 'fastmcp'` or similar. - -**Cause**: Dependencies not installed or virtual environment not activated - -**Solution**: -```bash -# Activate virtual environment -source .venv/bin/activate - -# Install dependencies -pip install -r requirements.txt -``` - -## Debugging Tips - -### Enable Debug Logging -```python -import logging - -logging.basicConfig(level=logging.DEBUG) -``` - -This shows: -- Request/response details -- Token validation steps -- Middleware execution order - -### Test Public Routes -```bash -# Test health endpoint (should work without auth) -curl http://localhost:3002/health - -# Test discovery endpoint (should work without auth) -curl http://localhost:3002/.well-known/oauth-protected-resource -``` - -### Test Protected Endpoint Without Token -```bash -# Should return 401 with WWW-Authenticate header -curl -i -X POST http://localhost:3002/ \ - -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' -``` - -Check response headers for: -``` -WWW-Authenticate: Bearer realm="OAuth", resource_metadata="http://localhost:3002/.well-known/oauth-protected-resource" -``` - -### Inspect Token Contents -Use JWT decoder to verify token claims: -```bash -echo "" | jq -R 'split(".") | .[1] | @base64d | fromjson' -``` - -Check: -- `aud`: Should match `EXPECTED_AUDIENCE` -- `iss`: Should match `SK_ENV_URL` -- `exp`: Should not be expired -- `scope`: Should include required scopes (if using scope checks) - -### Check Middleware Order -Add logging to verify middleware execution order: -```python -@app.middleware("http") -async def auth_middleware(request: Request, call_next): - logger.info(f"Auth middleware: {request.url.path}") - # ... auth logic ... - return await call_next(request) - -@app.get("/health") -async def health(): - logger.info("Health endpoint") - return {"status": "healthy"} -``` - -Order should be: -1. CORSMiddleware (no log) -2. Auth middleware (logs "Auth middleware") -3. Health endpoint (logs "Health endpoint") - -## Common Environment Variable Mistakes - -| Variable | Common Mistake | Correct Value | -|----------|----------------|---------------| -| `SK_ENV_URL` | Missing protocol | `https://your-env.scalekit.com` | -| `SK_CLIENT_ID` | Wrong client ID | Copy from MCP Server page | -| `SK_CLIENT_SECRET` | Missing (unlike FastMCP-standalone) | Copy from dashboard | -| `EXPECTED_AUDIENCE` | Missing trailing slash | `http://localhost:3002/` | -| `PROTECTED_RESOURCE_METADATA` | Not JSON-escaped | `'{"key":"value"}'` (quotes around JSON) | -| `PORT` | Already in use | Choose unused port | - -## FastAPI + FastMCP vs Standalone FastMCP - -### Confusing the Two Approaches -**Symptom**: Using `SCALEKIT_RESOURCE_ID` and `MCP_URL` (FastMCP-standalone) instead of `EXPECTED_AUDIENCE` (FastAPI). - -**Solution**: Remember: -- **FastAPI + FastMCP**: Uses Express-style env vars (`EXPECTED_AUDIENCE`, `SK_CLIENT_SECRET`) -- **Standalone FastMCP**: Uses different env vars (`SCALEKIT_RESOURCE_ID`, `MCP_URL`, no `SK_CLIENT_SECRET`) - -Don't mix the approaches! - -## Getting Help - -If issues persist: -1. Check Scalekit dashboard for server status -2. Review Scalekit logs for authentication attempts -3. Enable DEBUG logging and capture full output -4. Verify all 5 environment variables are set correctly -5. Test with MCP Inspector to isolate client vs server issues -6. Verify middleware order: CORS → Auth → Routes → Mount -7. Confirm FastAPI uses `lifespan=mcp_app.lifespan` -8. Ensure FastMCP mount is LAST in the code -9. Check that tools are registered BEFORE calling `http_app()` diff --git a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/SKILL.md b/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/SKILL.md deleted file mode 100644 index 75ff256..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/SKILL.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -name: mcp-auth-fastmcp-scalekit -description: Add Scalekit OAuth authentication to a FastMCP server (Python). Use when you need to protect FastMCP tools with OAuth 2.1 Bearer tokens and enforce per-tool scope checks (e.g. todo:read, todo:write). Authentication is added in 5 lines via ScalekitProvider; scope checks use get_access_token() inside each tool. -compatibility: Python 3.11+. Requires fastmcp>=2.13.0.2 and python-dotenv. Does NOT need custom middleware or a manual .well-known endpoint — FastMCP and ScalekitProvider handle those automatically. -metadata: - owner: scalekit - topic: mcp-auth - framework: fastmcp - language: python ---- - -# Add OAuth auth to a FastMCP server (Scalekit) - -## What this skill builds -- A FastMCP server with `stateless_http=True` that boots an HTTP transport on a configured port. -- OAuth protection via `ScalekitProvider` — handles token validation, .well-known discovery, and WWW-Authenticate on 401 automatically. -- Per-tool scope enforcement using `get_access_token()` from `fastmcp.server.dependencies`. -- Scopes are pre-configured in the Scalekit dashboard; tools check them at runtime. - -## Key difference from Express skill -- No manual middleware, no manual .well-known route. -- Auth is injected at the `FastMCP(auth=...)` constructor. -- `ScalekitProvider` uses `resource_id` (starts with `res_`) and `mcp_url` (base URL + trailing slash). -- FastMCP appends `/mcp` to the base URL automatically — always register the BASE URL with trailing slash in Scalekit, not the /mcp path. - -## Inputs to collect (ask if missing) -- SCALEKIT_ENVIRONMENT_URL (your Scalekit env URL, e.g. https://your-env.scalekit.com) -- SCALEKIT_CLIENT_ID -- SCALEKIT_RESOURCE_ID (starts with res_, from Dashboard > MCP Servers) -- MCP_URL: base URL with trailing slash (e.g. http://localhost:3002/) -- PORT (default 3002) -- Scopes to configure (e.g. todo:read, todo:write); these must also be created in the Scalekit dashboard - -## Mode detection -Ask: "Are we scaffolding a new FastMCP server, or adding auth to an existing FastMCP server?" -- Mode A: New server → use assets/server.py or assets/server-minimal.py as the base -- Mode B: Existing server → apply the patch plan below - ---- - -# Mode A — New server scaffold - -## Steps -1. Create a directory and virtual environment: - ``` - mkdir -p - cd - python3 -m venv venv - source venv/bin/activate - ``` - -2. Install dependencies using assets/requirements.txt: - ``` - pip install -r requirements.txt - ``` - -3. Create .env using assets/env.example (fill real values from Scalekit dashboard). - -4. Copy assets/server-minimal.py to server.py and add your tools. - -5. Run: - ``` - python server.py - ``` - -6. Test with MCP Inspector (point to http://localhost:3002/mcp — note: /mcp, not /): - ``` - npx @modelcontextprotocol/inspector@latest - ``` - Leave Authentication fields empty; DCR handles client registration automatically. - ---- - -# Mode B — Retrofit an existing FastMCP server - -## Identify insertion points -Ask for the existing server file. Look for: -- How `FastMCP(...)` is instantiated -- Whether `mcp.run(...)` is already present - -## Patch plan (minimal diffs) - -### 1. Add env vars -Add to .env: -``` -SCALEKIT_ENVIRONMENT_URL=... -SCALEKIT_CLIENT_ID=... -SCALEKIT_RESOURCE_ID=... -MCP_URL=http://localhost:3002/ -PORT=3002 -``` - -### 2. Add imports -```python -from fastmcp.server.auth.providers.scalekit import ScalekitProvider -from fastmcp.server.dependencies import AccessToken, get_access_token -``` - -### 3. Patch FastMCP constructor -Before (typical): -```python -mcp = FastMCP("My Server") -``` - -After: -```python -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"), - ), -) -``` - -### 4. Add scope helper -Add once near the top of the file: -```python -def _require_scope(scope: str): - token: AccessToken = get_access_token() - if scope not in token.scopes: - return f"Insufficient permissions: `{scope}` scope required." - return None -``` - -### 5. Add scope checks to each tool -At the top of each @mcp.tool function body, add: -```python -error = _require_scope("your:scope") -if error: - return {"error": error} -``` - -See assets/tool-template.py for the full pattern. - -### 6. Patch run call -```python -if __name__ == "__main__": - mcp.run(transport="http", port=int(os.getenv("PORT", "3002"))) -``` - ---- - -## Scope design guide -- Register scopes in Scalekit dashboard BEFORE running the server. -- Group by read vs write per resource: e.g. todo:read, todo:write. -- Tools that only read → require read scope. -- Tools that mutate (create/update/delete) → require write scope. -- A token with no matching scope will get {"error": "Insufficient permissions: required."}. - -## Verification checklist -- Server boots and logs HTTP transport on http://localhost:PORT/ -- MCP Inspector connects to http://localhost:PORT/mcp -- Tool with correct scope → success -- Tool with wrong scope → {"error": "Insufficient permissions..."} -- No token → 401 + WWW-Authenticate (handled by ScalekitProvider automatically) - -See references/TROUBLESHOOTING.md for common issues. diff --git a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/env.example b/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/env.example deleted file mode 100644 index 92ce5a9..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/env.example +++ /dev/null @@ -1,5 +0,0 @@ -PORT=3002 -SCALEKIT_ENVIRONMENT_URL=https://your-environment-url.scalekit.com -SCALEKIT_CLIENT_ID=your_client_id -SCALEKIT_RESOURCE_ID=res_xxxxxxxxxxxx -MCP_URL=http://localhost:3002/ diff --git a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/requirements.txt b/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/requirements.txt deleted file mode 100644 index 3133d89..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -fastmcp>=2.13.0.2 -python-dotenv>=1.0.0 diff --git a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/server-minimal.py b/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/server-minimal.py deleted file mode 100644 index fd0c728..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/server-minimal.py +++ /dev/null @@ -1,38 +0,0 @@ -import os -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( - "My MCP 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"), - ), -) - - -def _require_scope(scope: str): - token: AccessToken = get_access_token() - if scope not in token.scopes: - return f"Insufficient permissions: `{scope}` scope required." - return None - - -@mcp.tool -def hello(name: str) -> dict: - """Say hello. Requires: example:read scope.""" - error = _require_scope("example:read") - if error: - return {"error": error} - return {"message": f"Hello, {name}!"} - - -if __name__ == "__main__": - mcp.run(transport="http", port=int(os.getenv("PORT", "3002"))) diff --git a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/server.py b/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/server.py deleted file mode 100644 index eb8a368..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/server.py +++ /dev/null @@ -1,78 +0,0 @@ -import os -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 MCP 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"), - ), -) - -todos = {} - - -def _require_scope(scope: str): - token: AccessToken = get_access_token() - if scope not in token.scopes: - return f"Insufficient permissions: `{scope}` scope required." - return None - - -@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": list(todos.values())} - - -@mcp.tool -def create_todo(text: str) -> dict: - """Create a new todo. Requires: todo:write scope.""" - error = _require_scope("todo:write") - if error: - return {"error": error} - todo_id = str(len(todos) + 1) - todos[todo_id] = {"id": todo_id, "text": text, "done": False} - return {"todo": todos[todo_id]} - - -@mcp.tool -def update_todo(todo_id: str, text: str = None, done: bool = None) -> dict: - """Update an existing todo. Requires: todo:write scope.""" - error = _require_scope("todo:write") - if error: - return {"error": error} - if todo_id not in todos: - return {"error": "Todo not found"} - if text is not None: - todos[todo_id]["text"] = text - if done is not None: - todos[todo_id]["done"] = done - return {"todo": todos[todo_id]} - - -@mcp.tool -def delete_todo(todo_id: str) -> dict: - """Delete a todo. Requires: todo:write scope.""" - error = _require_scope("todo:write") - if error: - return {"error": error} - if todo_id not in todos: - return {"error": "Todo not found"} - deleted = todos.pop(todo_id) - return {"deleted": deleted} - - -if __name__ == "__main__": - mcp.run(transport="http", port=int(os.getenv("PORT", "3002"))) diff --git a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/tool-template.py b/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/tool-template.py deleted file mode 100644 index 6668f37..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/assets/tool-template.py +++ /dev/null @@ -1,12 +0,0 @@ -@mcp.tool -def (: ) -> dict: - """ - . - Requires: scope. - """ - error = _require_scope("") - if error: - return {"error": error} - - # TODO: implement tool logic here - return {"result": None} diff --git a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/references/REFERENCE.md b/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/references/REFERENCE.md deleted file mode 100644 index f18c441..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/references/REFERENCE.md +++ /dev/null @@ -1,199 +0,0 @@ -# FastMCP OAuth Reference - -## Core Concepts - -### FastMCP HTTP Transport -FastMCP's HTTP transport (`stateless_http=True`) provides: -- Automatic SSE endpoint for server-to-client messages -- JSON-RPC over POST at `/mcp` path -- Built-in CORS handling -- Automatic `.well-known/oauth-protected-resource` endpoint when auth is configured - -### ScalekitProvider Plugin -The `ScalekitProvider` handles all OAuth concerns: -- Token validation using Scalekit's public keys -- Automatic generation of `.well-known/oauth-protected-resource` -- `WWW-Authenticate` header on 401 responses -- Dynamic Client Registration (DCR) support -- Client ID Metadata Document (CIMD) support - -### URL Structure -FastMCP automatically appends `/mcp` to your base URL: -- **Base URL (MCP_URL)**: `http://localhost:3002/` (with trailing slash) -- **Scalekit registration**: Register the BASE URL only -- **MCP endpoint**: `http://localhost:3002/mcp` (auto-appended) -- **Discovery endpoint**: `http://localhost:3002/.well-known/oauth-protected-resource` (auto-generated) - -### Resource ID vs Audience -FastMCP uses `resource_id` instead of `aud`: -- `SCALEKIT_RESOURCE_ID`: Starts with `res_`, from Scalekit dashboard -- This is the identifier Scalekit uses to issue tokens -- Combined with `MCP_URL` to form the complete resource identifier - -## Scope Enforcement Pattern - -### Per-Tool Scope Checks -Unlike Express where scope checks can be in middleware, FastMCP requires checks inside each tool: - -```python -def _require_scope(scope: str): - token: AccessToken = get_access_token() - if scope not in token.scopes: - return f"Insufficient permissions: `{scope}` scope required." - return None - -@mcp.tool -def my_tool(): - error = _require_scope("my:scope") - if error: - return {"error": error} - # tool logic here -``` - -### Why Inside Tools? -FastMCP's dependency injection system only has context at tool execution time -- Token is available via `get_access_token()` dependency -- Each tool can have different scope requirements -- Allows granular control: read-only vs write operations - -## Comparison: FastMCP vs Express - -| Aspect | FastMCP | Express | -|--------|---------|---------| -| Auth wiring | `ScalekitProvider` plugin | Custom middleware | -| `.well-known` | Automatic | Manual route | -| Token validation | Built-in provider | `scalekit.validateToken()` | -| Scope checks | Inside each tool | Middleware + optional tool checks | -| Audience config | `SCALEKIT_RESOURCE_ID` + `MCP_URL` | `EXPECTED_AUDIENCE` | -| MCP endpoint | `/mcp` (auto) | `/` (you define) | -| CORS | Automatic | Manual `cors()` | -| Code required | ~5 lines for auth | ~30+ lines for middleware | - -## FastMCP Dependencies - -### fastmcp.server.auth.providers.scalekit -```python -from fastmcp.server.auth.providers.scalekit import ScalekitProvider - -mcp = FastMCP( - "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"), - ), -) -``` - -### fastmcp.server.dependencies -```python -from fastmcp.server.dependencies import AccessToken, get_access_token - -# Inside a tool function: -token: AccessToken = get_access_token() -# token.scopes = ["todo:read", "todo:write"] -# token.subject = "user-id" -``` - -## Scope Design Patterns - -### Read/Write Split -```python -# Scalekit dashboard scopes: -# - todo:read -# - todo:write - -@mcp.tool -def list_todos(): - _require_scope("todo:read") - # returns list - -@mcp.tool -def create_todo(): - _require_scope("todo:write") - # creates item - -@mcp.tool -def update_todo(): - _require_scope("todo:write") - # updates item -``` - -### Resource Isolation -```python -# Scalekit dashboard scopes: -# - finance:read -# - finance:write -# - hr:read -# - hr:write - -@mcp.tool -def get_payroll(): - _require_scope("finance:read") - # sensitive finance data - -@mcp.tool -def list_employees(): - _require_scope("hr:read") - # employee directory -``` - -### Hierarchical Scopes -```python -# Scalekit dashboard scopes: -# - system:read -# - system:write -# - system:admin - -@mcp.tool -def get_system_status(): - _require_scope("system:read") - # status info - -@mcp.tool -def restart_service(): - _require_scope("system:admin") - # admin only -``` - -## Error Handling - -### Scope-Error Response Format -```python -{ - "error": "Insufficient permissions: `todo:write` scope required." -} -``` - -This format is returned by the `_require_scope()` helper and is consistent across all tools. - -### 401 vs 403 -- **401**: No token or invalid token (handled by ScalekitProvider) -- **403**: Valid token but insufficient scope (handled by your scope check) - -## Production Considerations - -### HTTP Transport Requirements -FastMCP's `stateless_http=True` requires: -- ASGI server (Uvicorn, Hypercorn) -- Public URL for production (localhost won't work for remote clients) -- HTTPS in production (OAuth spec requirement) - -### Server Deployment -```bash -# Development -python server.py - -# Production with Uvicorn -uvicorn server:app --host 0.0.0.0 --port 3002 -``` - -### Environment Variables -All required variables must be set: -- `SCALEKIT_ENVIRONMENT_URL`: Scalekit environment URL -- `SCALEKIT_CLIENT_ID`: MCP server client ID from dashboard -- `SCALEKIT_RESOURCE_ID`: Resource ID from dashboard (res_*) -- `MCP_URL`: Base URL with trailing slash -- `PORT`: Server port (default 3002) diff --git a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/references/TROUBLESHOOTING.md b/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/references/TROUBLESHOOTING.md deleted file mode 100644 index bf5a5dc..0000000 --- a/plugins/mcp-auth/skills/mcp-auth-fastmcp-scalekit/references/TROUBLESHOOTING.md +++ /dev/null @@ -1,276 +0,0 @@ -# FastMCP OAuth Troubleshooting - -## Common Issues - -### Server Won't Start -**Symptom**: `ModuleNotFoundError: No module named 'fastmcp'` or similar. - -**Cause**: Dependencies not installed or virtual environment not activated. - -**Solution**: -```bash -# Activate virtual environment -source venv/bin/activate # Linux/Mac -# or -venv\Scripts\activate # Windows - -# Install dependencies -pip install -r requirements.txt -``` - -### MCP Inspector Can't Connect -**Symptom**: Inspector shows "Failed to connect" or timeout. - -**Cause 1**: Wrong URL -- Inspector default is `http://localhost:3002/` but MCP endpoint is `/mcp` -- Try: `http://localhost:3002/mcp` in Inspector - -**Cause 2**: Server not running -```bash -# Check server is running -python server.py - -# Should see output like: -# INFO: Uvicorn running on http://0.0.0.0:3002 -``` - -**Cause 3**: Wrong transport mode -- Ensure `stateless_http=True` is set in FastMCP constructor -- Check `mcp.run(transport="http", ...)` is used - -### "Stateless HTTP transport not enabled" Error -**Symptom**: Server logs show error about stateless transport. - -**Cause**: Missing `stateless_http=True` in FastMCP constructor. - -**Solution**: -```python -mcp = FastMCP( - "My Server", - stateless_http=True, # ← This is required! - auth=ScalekitProvider(...) -) -``` - -### Discovery Endpoint Returns 404 -**Symptom**: `curl http://localhost:3002/.well-known/oauth-protected-resource` returns 404. - -**Cause**: `ScalekitProvider` not configured correctly. - -**Solution**: -```python -# Ensure all 4 parameters are set: -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"), -) -``` - -### Discovery Endpoint Returns Empty JSON -**Symptom**: Discovery endpoint returns `{}` or missing required fields. - -**Cause 1**: Missing or invalid `SCALEKIT_RESOURCE_ID` - -**Solution**: -- Copy resource ID directly from Scalekit dashboard -- Should start with `res_` -- Verify no extra spaces or quotes - -**Cause 2**: `MCP_URL` missing trailing slash - -**Solution**: -```env -# Wrong -MCP_URL=http://localhost:3002 - -# Correct -MCP_URL=http://localhost:3002/ -``` - -### Token Always Invalid (401) -**Symptom**: All tool calls return 401 even with valid token. - -**Cause 1**: `SCALEKIT_RESOURCE_ID` mismatch -- Resource ID in code doesn't match Scalekit dashboard - -**Solution**: -1. Go to Scalekit dashboard > MCP Servers -2. Copy Resource ID exactly (starts with `res_`) -3. Update .env file -4. Restart server - -**Cause 2**: `MCP_URL` mismatch with Scalekit registration -- You registered wrong URL in Scalekit - -**Solution**: -1. In Scalekit dashboard, check "Server URL" or base URL -2. Ensure it matches your `MCP_URL` in .env -3. Use trailing slash consistently - -### Scope Check Always Fails -**Symptom**: Tool always returns "Insufficient permissions" even with token that has scopes. - -**Cause 1**: Scopes not registered in Scalekit dashboard -- You're checking for a scope that doesn't exist - -**Solution**: -1. Go to Scalekit dashboard > MCP Servers -2. Add your scopes (e.g., `todo:read`, `todo:write`) -3. Save and restart server - -**Cause 2**: Scope string mismatch -- Case sensitivity, extra characters, or typos - -**Solution**: -```python -# Ensure exact match between dashboard and code -# Dashboard: "todo:read" -# Code: _require_scope("todo:read") # Must match exactly -``` - -**Cause 3**: Token doesn't have the scope -- Client requested different scopes during auth - -**Solution**: -1. Check MCP Inspector or client logs for scope list -2. Verify client requested the right scopes -3. Re-authenticate to get new token with correct scopes - -### "get_access_token() called outside of request context" Error -**Symptom**: Error when calling `get_access_token()` in a tool. - -**Cause**: Tool not being called through MCP protocol (e.g., direct function call). - -**Solution**: -- Only call `get_access_token()` inside `@mcp.tool` decorated functions -- Don't call it in global scope or outside tool execution -- Test via MCP Inspector, not by running the function directly - -### CORS Errors in Browser -**Symptom**: Browser console shows CORS errors. - -**Cause**: CORS not enabled or origin not allowed. - -**Solution**: -FastMCP handles CORS automatically with `stateless_http=True`. If issues persist: -```python -# Check server logs for CORS configuration -# FastMCP should log CORS middleware being registered -``` - -### Port Already in Use -**Symptom**: `OSError: [Errno 48] Address already in use` - -**Solution**: -```bash -# Change PORT in .env -PORT=3003 # Use different port - -# Or kill existing process -lsof -ti:3002 | xargs kill -9 # Linux/Mac -``` - -## Debugging Tips - -### Enable Verbose Logging -```python -import logging - -logging.basicConfig(level=logging.DEBUG) -``` - -This will show: -- Request/response details -- Token validation steps -- Scope check results - -### Test Discovery Endpoint -```bash -# Test without auth (should work) -curl http://localhost:3002/.well-known/oauth-protected-resource - -# Expected response: -{ - "authorization_servers": ["https://your-env.scalekit.com/resources/res_xxx"], - "bearer_methods_supported": ["header"], - "resource": "http://localhost:3002/", - "resource_documentation": "...", - "scopes_supported": ["todo:read", "todo:write"] -} -``` - -### Test Tool Without Auth (Should Fail) -```bash -curl -X POST http://localhost:3002/mcp \ - -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' - -# Expected: 401 with WWW-Authenticate header -``` - -### Inspect Token Contents -Use JWT decoder to verify token claims: -```bash -# Get token from MCP Inspector logs -echo "" | jq -R 'split(".") | .[1] | @base64d | fromjson' -``` - -Check: -- `aud`: Should match your resource -- `exp`: Should not be expired -- `scope`: Should include required scopes - -### Test with Different Scopes -Create test tokens with different scopes: -```python -# In Scalekit dashboard, create test tokens with: -# - todo:read only -# - todo:write only -# - both scopes -# - no scopes - -# Then test each against your tools -``` - -## Common Environment Variable Mistakes - -| Variable | Common Mistake | Correct Value | -|----------|----------------|---------------| -| `SCALEKIT_ENVIRONMENT_URL` | Missing protocol | `https://your-env.scalekit.com` | -| `SCALEKIT_CLIENT_ID` | Wrong client ID | Copy from MCP Server page | -| `SCALEKIT_RESOURCE_ID` | Missing `res_` prefix | `res_abc123...` | -| `MCP_URL` | Missing trailing slash | `http://localhost:3002/` | -| `PORT` | Already in use | Choose unused port | - -## MCP Inspector Tips - -### Correct URL for FastMCP -``` -http://localhost:3002/mcp -``` -Note: `/mcp` is auto-appended by FastMCP, NOT `/` - -### Leave Auth Fields Empty -When using DCR (Dynamic Client Registration): -- Leave Client ID and Client Secret empty -- ScalekitProvider handles client registration -- Inspector will prompt you to authenticate in browser - -### Check Token Scopes -In Inspector, after authentication: -1. Click the connection info -2. Look for "scopes" or "permissions" -3. Verify your expected scopes are listed - -## Getting Help - -If issues persist: -1. Check Scalekit dashboard for server status -2. Review Scalekit logs for authentication attempts -3. Enable DEBUG logging and capture full output -4. Verify all 5 environment variables are set correctly -5. Test with MCP Inspector to isolate client vs server issues -6. Confirm `stateless_http=True` is set -7. Verify `transport="http"` in `mcp.run()` call 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/.cursor-plugin/plugin.json b/plugins/modular-scim/.cursor-plugin/plugin.json deleted file mode 100644 index c42cb35..0000000 --- a/plugins/modular-scim/.cursor-plugin/plugin.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "modular-scim", - "displayName": "Modular SCIM", - "description": "SCIM webhook provisioning with Scalekit for real-time user and group lifecycle management.", - "version": "1.0.0", - "author": { - "name": "Scalekit", - "email": "hi@scalekit.com" - }, - "homepage": "https://docs.scalekit.com", - "repository": "https://github.com/scalekit-inc/cursor-authstack", - "license": "MIT", - "keywords": ["scim", "provisioning", "webhooks", "users", "groups"], - "skills": "./skills", - "agents": "./agents", - "rules": "./rules", - "mcpServers": "./.mcp.json" -} diff --git a/plugins/modular-scim/.mcp.json b/plugins/modular-scim/.mcp.json deleted file mode 100644 index 36dbbf4..0000000 --- a/plugins/modular-scim/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "scalekit": { - "command": "npx", - "args": ["-y", "mcp-remote", "https://mcp.scalekit.com"] - } - } -} diff --git a/plugins/modular-scim/README.md b/plugins/modular-scim/README.md deleted file mode 100644 index 05b1cfd..0000000 --- a/plugins/modular-scim/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# modular-scim - -SCIM 2.0 user provisioning and directory sync using Scalekit for real-time user and group lifecycle management. - -## Purpose - -This plugin adds SCIM 2.0 directory sync to applications that already manage their own users. It handles real-time user provisioning, deprovisioning, group sync, and role mapping via Scalekit webhooks — without requiring a full auth system replacement. - -**Non-goals:** This plugin does not cover SSO authentication flows (see `modular-sso`), full-stack authentication (see `full-stack-auth`), or MCP server auth (see `mcp-auth`). - ---- - -## Install - -Clone or install the cursor-authstack repository and activate the `modular-scim` plugin from the Cursor plugin panel. - -Required environment variables (add to `.env`): - -```env -SCALEKIT_ENV_URL=https://your-env.scalekit.com -SCALEKIT_CLIENT_ID=your_client_id -SCALEKIT_CLIENT_SECRET=your_client_secret -SCALEKIT_WEBHOOK_SECRET=your_webhook_secret -``` - -Get credentials from [app.scalekit.com](https://app.scalekit.com) → Developers → Settings → API Credentials. - ---- - -## Skills - -### modular-scim - -Implements SCIM user provisioning using Scalekit's Directory API and webhooks. Handles user create, update, deactivate, group create, and group membership changes. - -**Example invocations:** -- "Add SCIM provisioning to my app using Scalekit" -- "Set up directory sync so Okta can provision users" -- "Handle SCIM webhooks for user lifecycle events" - -### implementing-admin-portal - -Creates Scalekit's admin portal so customers can self-serve their SCIM configuration. Generates portal links server-side and embeds the portal as an iframe in your app's settings UI. - -**Example invocations:** -- "Add an admin portal so customers can configure their own SCIM" -- "Embed the Scalekit admin portal in my settings page" -- "Generate a shareable SCIM setup link for my enterprise customer" - -### production-readiness-scalekit - -Walks through a structured production readiness checklist for Scalekit SCIM provisioning implementations. - -**Example invocations:** -- "Run a production readiness check on my SCIM setup" -- "What do I need to verify before going live with SCIM provisioning?" - ---- - -## Agents - -### setup-scalekit - -Sets up Scalekit env vars, installs/initializes the SDK, and verifies credentials. Use proactively when the user asks to set up, install, initialize, or configure Scalekit. - -### scim-validate - -Validates a SCIM configuration end-to-end: checks environment variables, webhook endpoint accessibility, event handler registration, and directory connection status. - ---- - -## Commands - -### dryrun-scim - -Runs the Scalekit dryrun tool to verify your SCIM configuration end-to-end. - -``` -/dryrun-scim -``` - ---- - -## Configuration - -The `.mcp.json` connects to the Scalekit hosted MCP server. The webhook endpoint must be publicly accessible and registered in your Scalekit dashboard under Directory Sync → Webhooks. - ---- - -## Troubleshooting - -**Webhook not receiving events**: Verify the webhook URL is publicly accessible (use ngrok for local development) and matches what is registered in Dashboard → Directory Sync → Webhooks. - -**"Invalid webhook signature"**: The `SCALEKIT_WEBHOOK_SECRET` must match the secret shown in Dashboard → Directory Sync → Webhooks → your endpoint. - -**Users not being provisioned**: Check that the directory connection status is Active in Dashboard → Directory Sync → Connections. Also verify your webhook handler returns HTTP 200 within 30 seconds. - -**Group sync not working**: Ensure group provisioning is enabled for the connection and your app handles the `group.created` and `group.updated` event types. - ---- - -## Security notes - -- Always verify the webhook signature before processing any event -- Implement idempotency in your webhook handler — Scalekit may retry events on failure -- Use HTTPS for all webhook endpoints in production -- Never log user PII from SCIM payloads -- Apply least-privilege when granting directory access to Scalekit diff --git a/plugins/modular-scim/agents/scim-validate.md b/plugins/modular-scim/agents/scim-validate.md deleted file mode 100644 index 61ac721..0000000 --- a/plugins/modular-scim/agents/scim-validate.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: scim-validate -description: Validate SCIM provisioning setup and diagnose sync issues with identity providers. ---- - -# SCIM Validation Agent - -You are a SCIM provisioning validation specialist. - -## Validation steps -1. Verify SCIM endpoint configuration -2. Check authentication for SCIM requests -3. Validate schema mapping -4. Test user sync operations -5. Check group and role sync - -## Focus areas -- SCIM 2.0 compliance -- Attribute mapping and transformation -- Bulk operations handling -- Sync error recovery 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/commands/dryrun-scim.md b/plugins/modular-scim/commands/dryrun-scim.md deleted file mode 100644 index b9b62e3..0000000 --- a/plugins/modular-scim/commands/dryrun-scim.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: dryrun-scim -description: Test SCIM provisioning configuration without actual provider sync. ---- - -# Dryrun SCIM - -## Purpose -Validate SCIM configuration without making real provisioning requests. - -## Steps -1. Validate SCIM endpoint URLs -2. Check authentication token format -3. Verify schema mapping configuration -4. Test attribute transformation logic -5. Validate bulk operation settings - -## What this does -- Parses SCIM schema definitions -- Checks required attributes -- Validates mapping expressions -- Verifies endpoint configuration -- Does NOT sync any users diff --git a/plugins/modular-scim/hooks/hooks.json b/plugins/modular-scim/hooks/hooks.json deleted file mode 100644 index c7fdd97..0000000 --- a/plugins/modular-scim/hooks/hooks.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "description": "Usage beacon for Scalekit modular-scim plugin", - "hooks": { - "PostToolUse": [ - { - "matcher": ".*", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh modular-scim post_tool", - "timeout": 10 - } - ] - } - ], - "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/rules/scim-security.mdc b/plugins/modular-scim/rules/scim-security.mdc deleted file mode 100644 index 8592cbf..0000000 --- a/plugins/modular-scim/rules/scim-security.mdc +++ /dev/null @@ -1,21 +0,0 @@ ---- -description: SCIM provisioning security and best practices. -alwaysApply: true -globs: ["**/*.{js,ts,py,go}"] ---- - -scim-security: - -- Authenticate all SCIM requests with proper bearer tokens -- Validate SCIM requests against schema -- Implement proper pagination for large result sets -- Filter sensitive attributes before returning users -- Log SCIM operations for audit purposes -- Implement proper bulk operation limits -- Validate group memberships before provisioning - -Data handling: -- Mask or redact sensitive attributes in logs -- Implement proper permission checks for SCIM endpoints -- Validate user/group updates before applying -- Handle deprovisioning safely (soft delete preferred) 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 a12fb3a..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` | -| Go | `go get github.com/scalekit/scalekit-go` | -| 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/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/.cursor-plugin/plugin.json b/plugins/modular-sso/.cursor-plugin/plugin.json deleted file mode 100644 index 5e76493..0000000 --- a/plugins/modular-sso/.cursor-plugin/plugin.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "modular-sso", - "displayName": "Modular SSO", - "description": "Modular SSO flows using Scalekit for apps with existing user management, including IdP-initiated login and enterprise onboarding.", - "version": "1.0.0", - "author": { - "name": "Scalekit", - "email": "hi@scalekit.com" - }, - "homepage": "https://docs.scalekit.com", - "repository": "https://github.com/scalekit-inc/cursor-authstack", - "license": "MIT", - "keywords": ["sso", "saml", "oidc", "authentication", "enterprise"], - "skills": "./skills", - "agents": "./agents", - "rules": "./rules", - "mcpServers": "./.mcp.json" -} diff --git a/plugins/modular-sso/.mcp.json b/plugins/modular-sso/.mcp.json deleted file mode 100644 index 36dbbf4..0000000 --- a/plugins/modular-sso/.mcp.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "scalekit": { - "command": "npx", - "args": ["-y", "mcp-remote", "https://mcp.scalekit.com"] - } - } -} diff --git a/plugins/modular-sso/README.md b/plugins/modular-sso/README.md deleted file mode 100644 index 7ad811c..0000000 --- a/plugins/modular-sso/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# modular-sso - -Modular SSO flows using Scalekit for apps with existing user management systems. - -## Purpose - -This plugin integrates enterprise SSO into applications that already manage their own users. It handles IdP-initiated and SP-initiated login, attribute mapping, JIT provisioning, and enterprise customer onboarding via the admin portal — without requiring a full auth system replacement. - -**Non-goals:** This plugin does not cover full authentication flows (see `full-stack-auth`), SCIM user provisioning (see `modular-scim`), or MCP server auth (see `mcp-auth`). - ---- - -## Install - -Clone or install the cursor-authstack repository and activate the `modular-sso` plugin from the Cursor plugin panel. - -Required environment variables (add to `.env`): - -```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:3000/auth/callback -``` - -Get credentials from [app.scalekit.com](https://app.scalekit.com) → Developers → Settings → API Credentials. - ---- - -## Skills - -### modular-sso - -Implements complete SSO and authentication flows using Scalekit. Handles SP-initiated and IdP-initiated login, user session management, and enterprise customer onboarding. - -**Example invocations:** -- "Add enterprise SSO to my app using Scalekit" -- "Implement SAML login with Okta for my SaaS" -- "Set up Scalekit SSO with existing user management" - -### implementing-admin-portal - -Creates Scalekit's admin portal so customers can self-serve their SSO configuration. Generates portal links server-side and embeds the portal as an iframe in your app's settings UI. - -**Example invocations:** -- "Add an admin portal so customers can configure their own SSO" -- "Embed the Scalekit admin portal in my settings page" -- "Generate a shareable SSO setup link for my customer" - -### production-readiness-scalekit - -Walks through a structured production readiness checklist for Scalekit SSO implementations. - -**Example invocations:** -- "Run a production readiness check on my SSO setup" -- "What do I need to verify before going live with enterprise SSO?" - ---- - -## Agents - -### setup-scalekit - -Sets up Scalekit env vars, installs/initializes the SDK, and verifies credentials. Use proactively when the user asks to set up, install, initialize, or configure Scalekit. - -### sso-validate - -Validates an SSO configuration end-to-end: checks environment variables, SDK initialization, redirect URI registration, and connection status. - ---- - -## Commands - -### dryrun-sso - -Runs the Scalekit dryrun tool to verify your SSO configuration end-to-end. - -``` -/dryrun-sso -``` - ---- - -## Configuration - -The `.mcp.json` connects to the Scalekit hosted MCP server. The `SCALEKIT_REDIRECT_URI` must exactly match the callback URL registered in your Scalekit dashboard under Authentication → Redirect URLs. - ---- - -## Troubleshooting - -**"Invalid redirect_uri"**: The callback URL in your code must exactly match what is registered in Dashboard → Authentication → Redirect URLs. - -**IdP-initiated login not working**: Ensure your app handles the `connection_id` parameter in the callback and maps it to the correct organization. - -**Attribute mapping issues**: Check the attribute mapping configuration in Dashboard → SSO → Connections → your connection → Attribute Mapping. - -**JIT provisioning not creating users**: Verify the JIT provisioning setting is enabled in Dashboard → SSO → Connections → your connection → User Provisioning. - ---- - -## Security notes - -- Always validate the `state` parameter in the OAuth callback to prevent CSRF attacks -- Verify the `iss` and `aud` claims in ID tokens before trusting them -- Use the `offline_access` scope to receive refresh tokens for long-lived sessions -- Never expose the `SCALEKIT_CLIENT_SECRET` to client-side code -- Store all tokens in HttpOnly cookies, not localStorage 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/agents/sso-validate.md b/plugins/modular-sso/agents/sso-validate.md deleted file mode 100644 index 1b52d73..0000000 --- a/plugins/modular-sso/agents/sso-validate.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: sso-validate -description: Validate SSO configuration and troubleshoot connection issues with identity providers. ---- - -# SSO Validation Agent - -You are an SSO configuration validation specialist. - -## Validation steps -1. Verify provider connection settings -2. Check SAML/OIDC metadata -3. Validate attribute mapping -4. Test SSO flow end-to-end -5. Check JIT provisioning - -## Focus areas -- Provider-specific configuration -- Certificate and metadata handling -- User attribute mapping -- Group and role assignment diff --git a/plugins/modular-sso/commands/dryrun-sso.md b/plugins/modular-sso/commands/dryrun-sso.md deleted file mode 100644 index 1f6e255..0000000 --- a/plugins/modular-sso/commands/dryrun-sso.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: dryrun-sso -description: Test SSO configuration without making actual provider connections. ---- - -# Dryrun SSO - -## Purpose -Validate SSO configuration against identity provider requirements without creating real connections. - -## Steps -1. Validate provider metadata URLs -2. Check required fields and attributes -3. Verify callback URL format -4. Validate certificate configuration -5. Test schema mapping logic - -## What this does -- Parses configuration files -- Checks for required fields -- Validates URL formats -- Verifies certificate syntax -- Does NOT contact providers diff --git a/plugins/modular-sso/commands/dryrun.md b/plugins/modular-sso/commands/dryrun.md deleted file mode 100644 index f568d8d..0000000 --- a/plugins/modular-sso/commands/dryrun.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: dryrun-sso -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/hooks.json b/plugins/modular-sso/hooks/hooks.json deleted file mode 100644 index 2a15378..0000000 --- a/plugins/modular-sso/hooks/hooks.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "description": "Usage beacon for Scalekit modular-sso plugin", - "hooks": { - "PostToolUse": [ - { - "matcher": ".*", - "hooks": [ - { - "type": "command", - "command": "${CLAUDE_PLUGIN_ROOT}/hooks/beacon.sh modular-sso post_tool", - "timeout": 10 - } - ] - } - ], - "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/rules/sso-security.mdc b/plugins/modular-sso/rules/sso-security.mdc deleted file mode 100644 index 71f96b1..0000000 --- a/plugins/modular-sso/rules/sso-security.mdc +++ /dev/null @@ -1,21 +0,0 @@ ---- -description: SAML and OIDC security standards for enterprise SSO. -alwaysApply: true -globs: ["**/*.{js,ts,py,xml}"] ---- - -sso-security: - -- Validate SAML signatures on all responses -- Verify SAML assertion expiration timestamps -- Implement proper audience restrictions -- Use encrypted SAML assertions for sensitive data -- Validate OIDC ID tokens with proper issuer verification -- Implement proper nonce validation for OIDC -- Cache provider metadata with proper expiration - -Provider integration: -- Rotate signing keys before expiration -- Validate redirect URIs against allowlist -- Implement proper state parameter validation -- Handle SLO (Single Logout) properly 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 7bbfd19..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` | -| Go | `go get github.com/scalekit/scalekit-go` | -| 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/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/.cursor-plugin/plugin.json b/plugins/saaskit/.cursor-plugin/plugin.json new file mode 100644 index 0000000..4f28520 --- /dev/null +++ b/plugins/saaskit/.cursor-plugin/plugin.json @@ -0,0 +1,20 @@ +{ + "name": "saaskit", + "displayName": "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.", + "version": "2.0.0", + "author": { + "name": "Scalekit Inc.", + "email": "hi@scalekit.com" + }, + "homepage": "https://docs.scalekit.com", + "repository": "https://github.com/scalekit-inc/cursor-authstack", + "license": "MIT", + "keywords": ["scalekit", "saaskit", "authentication", "sso", "scim", "mcp-auth", "sessions"], + "logo": "../../assets/logo.svg", + "skills": "./skills", + "agents": "./agents", + "rules": "./rules", + "hooks": "./hooks/hooks.json", + "mcpServers": "./mcp.json" +} diff --git a/plugins/saaskit/.env.example b/plugins/saaskit/.env.example new file mode 100644 index 0000000..4342064 --- /dev/null +++ b/plugins/saaskit/.env.example @@ -0,0 +1,3 @@ +SCALEKIT_ENVIRONMENT_URL=https://your-env.scalekit.com +SCALEKIT_CLIENT_ID=your-client-id +SCALEKIT_CLIENT_SECRET=your-client-secret diff --git a/plugins/saaskit/README.md b/plugins/saaskit/README.md new file mode 100644 index 0000000..99c3d28 --- /dev/null +++ b/plugins/saaskit/README.md @@ -0,0 +1,66 @@ +# SaaSKit for Cursor + +## Purpose + +Production-ready auth for B2B SaaS apps. This plugin brings Scalekit SaaSKit into Cursor so agents can build production-ready B2B authentication into web applications. It covers the entire auth lifecycle: login, sessions, SSO, SCIM provisioning, MCP server auth, and more. + +One integration enables: magic link & OTP, social sign-ins, enterprise SSO, workspaces, MCP authentication, SCIM provisioning, and user management. + +## Installation + +1. Run the bootstrap installer in your terminal: + +```bash +curl -fsSL https://raw.githubusercontent.com/scalekit-inc/cursor-authstack/main/install.sh | bash +``` + +2. Open Cursor, then run the plugin install command from the Command Palette: + +``` +> Plugins: Install Plugin +``` + +Select **SaaSKit** from the Scalekit Auth Stack. + +## Skills Reference + +- `/saaskit:setup` + New to SaaSKit? Start here — answers 3 questions and routes you to the right skill. +- `implementing-saaskit` — Core auth flow: login, signup, callback, token exchange, session management, logout. +- `implementing-modular-sso` — Enterprise SSO (SAML/OIDC) with 20+ IdPs, admin portal, JIT provisioning. +- `implementing-scim-provisioning` — SCIM 2.0 webhooks, user/group lifecycle, directory API. +- `adding-mcp-oauth` — OAuth 2.1 for MCP servers (FastMCP, Express, FastAPI). +- `adding-api-auth` — API keys and client credentials for machine-to-machine auth. +- `implementing-access-control` — RBAC and permission enforcement using token claims. +- `implementing-saaskit-nextjs` — Next.js App Router integration with Scalekit. +- `implementing-saaskit-python` — Django, FastAPI, Flask integration with Scalekit. +- `managing-saaskit-sessions` — Token storage, validation, refresh, revocation. +- `migrating-to-saaskit` — Migration planner from existing auth systems. +- `testing-auth-setup` — Validates auth config with the dryrun CLI. +- `production-readiness-saaskit` — Unified production checklist across all SaaSKit domains. +- `/saaskit:scalekit-code-doctor` — Diagnoses SDK usage issues, import errors, and common mistakes across AgentKit and SaaSKit. + +## 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. + +## Helpful Links + +- [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/) +- [LLM docs map](https://docs.scalekit.com/llms.txt) + +## 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. +- Validate access tokens on every request before trusting embedded roles/permissions. +- Use the admin portal iframe for customer self-serve SSO configuration. diff --git a/plugins/mcp-auth/agents/scalekit-mcp-auth-troubleshooter.md b/plugins/saaskit/agents/scalekit-mcp-auth-troubleshooter.md similarity index 66% rename from plugins/mcp-auth/agents/scalekit-mcp-auth-troubleshooter.md rename to plugins/saaskit/agents/scalekit-mcp-auth-troubleshooter.md index f7777e4..5149620 100644 --- a/plugins/mcp-auth/agents/scalekit-mcp-auth-troubleshooter.md +++ b/plugins/saaskit/agents/scalekit-mcp-auth-troubleshooter.md @@ -1,6 +1,6 @@ --- name: scalekit-mcp-auth-troubleshooter -description: Diagnose and resolve common Scalekit MCP auth integration issues (handshake/metadata, cached clients, CORS/network, Claude Desktop port limits, browser launch problems), producing a step-by-step fix plan with verification commands. +description: Diagnose and resolve common Scalekit MCP auth integration issues (handshake/metadata, cached clients, CORS/network, port limits, browser launch problems), producing a step-by-step fix plan with verification commands. model: claude-sonnet-4 maxTurns: 12 permissionMode: plan @@ -27,28 +27,28 @@ Your job is to take a failing MCP client ↔ MCP server connection and produce: # Triage flow (follow in order) ## 1) Identify the client + environment -Ask (or infer from context) which MCP client is failing: MCP Inspector, MCP-Remote, VS Code, Claude Desktop. +Ask (or infer from context) which MCP client is failing: MCP Inspector, Cursor, VS Code, Claude Desktop. 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. Clear cached auth (by client): -- MCP-Remote: delete `~/.mcp-auth/mcp-remote-` and reconnect. +- Cursor: open Cursor Settings → MCP, remove the Scalekit MCP server entry, then reconnect. - VS Code: run “Authentication: Remove Dynamic Authentication Provider”, remove the cached entry, and reconnect. -If the failing client is Claude Desktop, note that client behavior can differ and some issues are client-specific (see sections below). +If the failing client is a desktop app (e.g., Claude Desktop), note that client behavior can differ and some issues are client-specific (see sections below). ## 4) CORS & callback URL issues (common with MCP Inspector) If you see CORS failures during the handshake in browser network logs, add the MCP Inspector callback URL to Scalekit’s allowed callback URLs. @@ -65,8 +65,8 @@ Checklist: - Allow or exempt MCP client → server traffic for the server domain; confirm via proxy/WAF logs. - 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] +## 6) Client-specific: port limitations +Some MCP clients (e.g., Claude Desktop) only support standard HTTPS on port 443; they 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/full-stack-auth/agents/setup-scalekit.md b/plugins/saaskit/agents/setup-scalekit.md similarity index 61% rename from plugins/full-stack-auth/agents/setup-scalekit.md rename to plugins/saaskit/agents/setup-scalekit.md index 044e411..2c0513d 100644 --- a/plugins/full-stack-auth/agents/setup-scalekit.md +++ b/plugins/saaskit/agents/setup-scalekit.md @@ -1,9 +1,6 @@ --- -name: scalekit-setup +name: setup-scalekit 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. @@ -25,18 +22,24 @@ 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: - - 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 +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 auth → plugins/saaskit/skills/adding-mcp-oauth/SKILL.md + - Full-stack auth → plugins/saaskit/skills/implementing-saaskit/SKILL.md + - AgentKit → 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/saaskit/docs/access-control.md b/plugins/saaskit/docs/access-control.md new file mode 100644 index 0000000..5a6fe8d --- /dev/null +++ b/plugins/saaskit/docs/access-control.md @@ -0,0 +1,119 @@ +# 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); + const claims = await scalekit.validateAccessTokenAndGetClaims(accessToken); + if (!claims) + return res.status(401).json({ error: 'Invalid or expired token' }); + 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')) + try: + claims = scalekit_client.validate_access_token_and_get_claims(access_token) + except Exception: + return jsonify({'error': 'Invalid or expired token'}), 401 + 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..63abf1f --- /dev/null +++ b/plugins/saaskit/docs/auth-flows.md @@ -0,0 +1,125 @@ +# 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") +from scalekit.common.scalekit import LogoutUrlOptions +logout_url = scalekit_client.get_logout_url(options=LogoutUrlOptions( + 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..1c0694e --- /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_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 +``` + +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..95f6f55 --- /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_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 +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..8b9b9c4 --- /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` 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_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 +``` + +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_ENVIRONMENT_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..0b37ff4 --- /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_ENVIRONMENT_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..9ad2193 --- /dev/null +++ b/plugins/saaskit/docs/scim.md @@ -0,0 +1,126 @@ +# 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 { 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 }); +} +``` + +```python +# Python +directories = scalekit_client.directory.list_directories(organization_id=org_id).directories +directory = 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) +``` + +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', express.raw({ type: 'application/json' }), async (req, res) => { + const ok = 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) +@app.post("/webhooks/scalekit") +async def scalekit_webhook(request: Request): + raw_body = await request.body() + valid = scalekit_client.verify_webhook_payload( + secret=os.getenv("SCALEKIT_WEBHOOK_SECRET"), + headers=dict(request.headers), + payload=raw_body, + ) + if not valid: + 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"}) +``` + +### 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..677404c --- /dev/null +++ b/plugins/saaskit/docs/sso.md @@ -0,0 +1,116 @@ +# 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_XXXXX', +}); +res.redirect(authUrl); +``` + +```python +# Python +options = AuthorizationUrlOptions() +options.organization_id = 'org_XXXXX' +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 +const SCALEKIT_ORIGIN = "https://your-env.scalekit.com"; +window.addEventListener('message', (event) => { + if (event.origin !== SCALEKIT_ORIGIN) 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/saaskit/hooks/beacon.sh b/plugins/saaskit/hooks/beacon.sh new file mode 100755 index 0000000..8c8d091 --- /dev/null +++ b/plugins/saaskit/hooks/beacon.sh @@ -0,0 +1,20 @@ +#!/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\":\"cursor\",\"hook\":\"${HOOK}\",\"tool_name\":\"${TOOL_NAME}\"}}" \ No newline at end of file diff --git a/plugins/saaskit/hooks/hooks.json b/plugins/saaskit/hooks/hooks.json new file mode 100644 index 0000000..1fbaac3 --- /dev/null +++ b/plugins/saaskit/hooks/hooks.json @@ -0,0 +1,9 @@ +{ + "hooks": { + "sessionEnd": [ + { + "command": "./hooks/beacon.sh saaskit stop" + } + ] + } +} \ No newline at end of file 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" + } + } +} diff --git a/plugins/mcp-auth/references/bring-your-own-auth.md b/plugins/saaskit/references/bring-your-own-auth.md similarity index 92% rename from plugins/mcp-auth/references/bring-your-own-auth.md rename to plugins/saaskit/references/bring-your-own-auth.md index 1ccb94e..eee3eaa 100644 --- a/plugins/mcp-auth/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={ @@ -87,15 +87,15 @@ 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 ); -await scalekit.auth.updateLoginUserDetails( +await scalekit.updateLoginUserDetails( '{{connection_id}}', '{{login_request_id}}', { @@ -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) + - Reference: [fastmcp-reference.md](../skills/adding-mcp-oauth/fastmcp-reference.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) + - Reference: [express-reference.md](../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) + - Reference: [fastapi-reference.md](../skills/adding-mcp-oauth/fastapi-reference.md) 4. **Scalekit MCP Server (Production Reference)** - Official Scalekit implementation with advanced patterns @@ -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/) diff --git a/plugins/agent-auth/references/redirects.md b/plugins/saaskit/references/redirects.md similarity index 100% rename from plugins/agent-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 97% rename from plugins/mcp-auth/references/scalekit-mcp-server.md rename to plugins/saaskit/references/scalekit-mcp-server.md index e950b57..474778f 100644 --- a/plugins/mcp-auth/references/scalekit-mcp-server.md +++ b/plugins/saaskit/references/scalekit-mcp-server.md @@ -73,7 +73,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 +174,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), }); @@ -257,7 +257,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 +276,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, @@ -441,8 +441,7 @@ The Scalekit MCP server is already hosted and ready to use: { "mcpServers": { "scalekit": { - "command": "npx", - "args": ["-y", "mcp-remote", "https://mcp.scalekit.com/"] + "url": "https://mcp.scalekit.com" } } } 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/full-stack-auth/agents/session-management-reviewer.md b/plugins/saaskit/references/session-management-patterns.md similarity index 79% rename from plugins/full-stack-auth/agents/session-management-reviewer.md rename to plugins/saaskit/references/session-management-patterns.md index b7fe1f6..5c1defe 100644 --- a/plugins/full-stack-auth/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, AgentKit), 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/full-stack-auth/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 - 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 @@ -126,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 }); @@ -146,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) { @@ -162,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'); @@ -193,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; @@ -233,7 +208,7 @@ await scalekit.sessions.revokeAll({ userId: req.user.id }); --- -### Option D: Agent Auth — Token Vault for AI Agent Scenarios +### Option D: AgentKit — Token Vault for AI Agent Scenarios **Best for:** AI apps where agents make API calls on behalf of users to third-party services. ```typescript @@ -294,9 +269,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/saaskit/rules/redirect-urls.mdc b/plugins/saaskit/rules/redirect-urls.mdc new file mode 100644 index 0000000..3c23cb8 --- /dev/null +++ b/plugins/saaskit/rules/redirect-urls.mdc @@ -0,0 +1,27 @@ +--- +description: Enforce correct redirect URL handling in all OAuth and OIDC flows. +globs: +alwaysApply: true +--- + +# 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) diff --git a/plugins/saaskit/rules/terminology.mdc b/plugins/saaskit/rules/terminology.mdc new file mode 100644 index 0000000..d38389a --- /dev/null +++ b/plugins/saaskit/rules/terminology.mdc @@ -0,0 +1,20 @@ +--- +description: Use consistent SaaSKit terminology in all user-facing content and generated code. +globs: +alwaysApply: true +--- + +# Terminology + +- `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. diff --git a/plugins/full-stack-auth/skills/adding-api-key-auth/SKILL.md b/plugins/saaskit/skills/adding-api-auth/SKILL.md similarity index 69% rename from plugins/full-stack-auth/skills/adding-api-key-auth/SKILL.md rename to plugins/saaskit/skills/adding-api-auth/SKILL.md index 8b9cfe5..6b2a240 100644 --- a/plugins/full-stack-auth/skills/adding-api-key-auth/SKILL.md +++ b/plugins/saaskit/skills/adding-api-auth/SKILL.md @@ -1,11 +1,6 @@ --- -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. +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. --- # Adding API Key Auth (Scalekit) @@ -378,3 +373,139 @@ func AuthenticateToken(sc scalekit.Scalekit) gin.HandlerFunc { - **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`. + +### Validate the JWT on your API server + +**Do this on EVERY request. Never trust unverified tokens.** + +```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 +``` + +```javascript +// Node.js — manual JWKS + JWT verify +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; +} +``` + +### Enforce scopes in middleware + +```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 +``` + +```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'); + } + }; +} +``` + +### Client credentials 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 (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 new file mode 100644 index 0000000..8236bcf --- /dev/null +++ b/plugins/saaskit/skills/adding-mcp-oauth/SKILL.md @@ -0,0 +1,319 @@ +--- +name: adding-mcp-oauth +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. +--- + +# Adding OAuth 2.1 Authorization to MCP Servers + +## Prerequisite: HTTP transport + +MCP OAuth requires **Streamable HTTP** transport. Stdio does not support OAuth. + +**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 + +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 { ScalekitClient } from '@scalekit-sdk/node'; + +const scalekit = new ScalekitClient( + 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_access_token_and_get_claims(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 + +- 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) + +## 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) + + 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..80a48a3 --- /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: `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(SCALEKIT_ENVIRONMENT_URL, SCALEKIT_CLIENT_ID, SCALEKIT_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..3df366b --- /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=SCALEKIT_ENVIRONMENT_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/full-stack-auth/skills/implementing-access-control/SKILL.md b/plugins/saaskit/skills/implementing-access-control/SKILL.md similarity index 58% rename from plugins/full-stack-auth/skills/implementing-access-control/SKILL.md rename to plugins/saaskit/skills/implementing-access-control/SKILL.md index 164a666..39dfee8 100644 --- a/plugins/full-stack-auth/skills/implementing-access-control/SKILL.md +++ b/plugins/saaskit/skills/implementing-access-control/SKILL.md @@ -1,18 +1,16 @@ --- 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. +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. --- -# Implementing access control (Scalekit FSA) +# Implementing access control (Scalekit SaaSKit) ## 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. +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. +2. Attach a normalized auth context to the request (e.g., `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. @@ -28,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 dessToken(accessToken); // JWT decode library + const tokenData = await scalekit.validateAccessTokenAndGetClaims(accessToken); + if (!tokenData) return res.status(401).json({ error: "Unauthorized" }); req.user = { id: tokenData.sub, organizationId: tokenData.oid, @@ -70,10 +66,11 @@ 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"), @@ -104,16 +101,36 @@ def require_permission(permission): return decorator ``` -## Patterns and pitfalls +## Verification + +After implementing, test these cases: + +```bash +# Test with a valid token that has the required role +curl -H "Cookie: accessToken=" http://localhost:3000/api/admin/users +# Expected: 200 + +# 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"} + +# Test with an expired/invalid token +curl -H "Cookie: accessToken=expired_token" http://localhost:3000/api/projects +# Expected: 401 {"error": "Invalid or expired token"} +``` + +If 403 isn't returned for unauthorized users, check that the middleware chain order is correct: `validateAndExtractAuth` must run before `requireRole`/`requirePermission`. + +## Patterns -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. +- 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 ## 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. +- [ ] 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/modular-sso/skills/modular-sso/SKILL.md b/plugins/saaskit/skills/implementing-modular-sso/SKILL.md similarity index 92% rename from plugins/modular-sso/skills/modular-sso/SKILL.md rename to plugins/saaskit/skills/implementing-modular-sso/SKILL.md index 15aa426..6127974 100644 --- a/plugins/modular-sso/skills/modular-sso/SKILL.md +++ b/plugins/saaskit/skills/implementing-modular-sso/SKILL.md @@ -1,15 +1,15 @@ --- -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. +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. --- -# Implement SSO as a Modular +# Implement Modular SSO ## 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) +- **SaaSKit (Full-Stack Auth)**: Scalekit manages users and sessions (built-in SSO) This skill covers Modular SSO for applications with existing user management. @@ -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:** @@ -101,7 +101,7 @@ const scalekit = new ScalekitClient( ); const options = { - organizationId: 'org_15421144869927830', // OR + organizationId: 'org_XXXXX', // OR connectionId: 'conn_15696105471768821', // OR loginHint: 'user@example.com' }; @@ -125,7 +125,7 @@ scalekit = ScalekitClient( ) options = AuthorizationUrlOptions() -options.organization_id = 'org_15421144869927830' +options.organization_id = 'org_XXXXX' auth_url = scalekit.get_authorization_url( redirect_uri='https://yourapp.com/auth/callback', @@ -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 @@ -404,7 +404,7 @@ Prevent failed redirects by checking SSO configuration before redirecting: **Node.js:** ```javascript -const domain = email.split('@').toLowerCase(); [reddit](https://www.reddit.com/r/ClaudeAI/comments/1qb1024/ultimate_claude_skillmd_autobuilds_any_fullstack/) +const domain = email.split('@')[1].toLowerCase(); const connections = await scalekit.connections.listConnectionsByDomain({ domain @@ -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` @@ -571,4 +571,9 @@ app.post('/logout', (req, res) => { **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 + +## 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. \ No newline at end of file diff --git a/plugins/full-stack-auth/skills/implementing-scalekit-nextjs-auth/SKILL.md b/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md similarity index 94% rename from plugins/full-stack-auth/skills/implementing-scalekit-nextjs-auth/SKILL.md rename to plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md index 3d4ba84..5a3b241 100644 --- a/plugins/full-stack-auth/skills/implementing-scalekit-nextjs-auth/SKILL.md +++ b/plugins/saaskit/skills/implementing-saaskit-nextjs/SKILL.md @@ -1,6 +1,6 @@ --- -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. +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. --- # Scalekit Auth — Next.js App Router @@ -26,7 +26,7 @@ lib/ ## Environment variables ```env -SCALEKIT_ENV_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 SCALEKIT_REDIRECT_URI=http://localhost:3000/auth/callback 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..96954eb --- /dev/null +++ b/plugins/saaskit/skills/implementing-saaskit-python/SKILL.md @@ -0,0 +1,110 @@ +--- +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, AuthorizationUrlOptions, LogoutUrlOptions + +load_dotenv() + +sc = ScalekitClient( + env_url=os.getenv("SCALEKIT_ENVIRONMENT_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 | Reference | +|---|---|---|---| +| 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 (FastAPI example) + +```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_ENVIRONMENT_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) + 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 + +@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(options=LogoutUrlOptions(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 + +- 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 + +- 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-python/django-reference.md b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md new file mode 100644 index 0000000..3215f8f --- /dev/null +++ b/plugins/saaskit/skills/implementing-saaskit-python/django-reference.md @@ -0,0 +1,248 @@ +# Scalekit Auth — Django + +## Dependencies + +```bash +pip install scalekit-sdk-python python-dotenv django +``` + +## Environment variables + +```env +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 +``` + +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_ENVIRONMENT_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() + options = AuthorizationUrlOptions() + options.state = state + auth_url = sc.get_authorization_url(REDIRECT_URI, options) + 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(options=LogoutUrlOptions(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_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 +- [ ] 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", "")) + 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)) +``` + +## 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..4710fbf --- /dev/null +++ b/plugins/saaskit/skills/implementing-saaskit-python/flask-reference.md @@ -0,0 +1,223 @@ +# Scalekit Auth — Flask + +## Dependencies + +```bash +pip install scalekit-sdk-python python-dotenv flask flask-session +``` + +## Environment variables + +```env +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 +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_ENVIRONMENT_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 + options = AuthorizationUrlOptions() + options.state = state + auth_url = sc.get_authorization_url(REDIRECT_URI, options) + 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(options=LogoutUrlOptions(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_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 +- [ ] 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", "")) + 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)) +``` + +### 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/) diff --git a/plugins/full-stack-auth/skills/full-stack-auth/SKILL.md b/plugins/saaskit/skills/implementing-saaskit/SKILL.md similarity index 62% rename from plugins/full-stack-auth/skills/full-stack-auth/SKILL.md rename to plugins/saaskit/skills/implementing-saaskit/SKILL.md index 389d2f1..20c900f 100644 --- a/plugins/full-stack-auth/skills/full-stack-auth/SKILL.md +++ b/plugins/saaskit/skills/implementing-saaskit/SKILL.md @@ -1,9 +1,9 @@ --- -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. +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. --- -# Scalekit Full-Stack Authentication +# Scalekit SaaSKit (Full-Stack Authentication) ## Setup @@ -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 ``` @@ -93,3 +93,25 @@ All SDK methods follow the same pattern across languages with minor naming conve ## 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 + +- 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.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/modular-scim/skills/modular-scim/SKILL.md b/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md similarity index 71% rename from plugins/modular-scim/skills/modular-scim/SKILL.md rename to plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md index 00446e7..a6fcb58 100644 --- a/plugins/modular-scim/skills/modular-scim/SKILL.md +++ b/plugins/saaskit/skills/implementing-scim-provisioning/SKILL.md @@ -1,6 +1,6 @@ --- 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. +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. --- # SCIM Provisioning with Scalekit @@ -32,9 +32,9 @@ 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` | -| Go | `go get github.com/scalekit/scalekit-go` | -| Java | Add `com.scalekit:scalekit-sdk` to `pom.xml` or `build.gradle` | +| Python | `pip install scalekit-sdk-python` | +| Go | `go get github.com/scalekit-inc/scalekit-sdk-go/v2` | +| Java | Add `com.scalekit:scalekit-sdk-java` to `pom.xml` or `build.gradle` | --- @@ -78,7 +78,7 @@ scalekit_client = ScalekitClient( ) ``` -For Go and Java patterns, see [REFERENCE.md](REFERENCE.md). +For Go and Java patterns, see the [Scalekit SDK documentation](https://docs.scalekit.com/apis). --- @@ -90,7 +90,8 @@ Use for scheduled jobs, onboarding flows, or bulk imports. Integrate into existi ```javascript // Node.js -const { directory } = await scalekit.directory.getPrimaryDirectoryByOrganizationId(orgId); +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 +101,8 @@ for (const user of users) { ```python # Python -directory = scalekit_client.directory.get_primary_directory_by_organization_id(org_id) +directories = scalekit_client.directory.list_directories(org_id) +directory = directories.directories[0] users = scalekit_client.directory.list_directory_users(org_id, directory.id) for user in users: @@ -128,18 +130,17 @@ Add a new route to the existing HTTP server/router. Match the framework pattern **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 { +app.post('/webhooks/scalekit', express.raw({ type: 'application/json' }), async (req, res) => { + const isValid = scalekit.verifyWebhookPayload( + process.env.SCALEKIT_WEBHOOK_SECRET, + req.headers, + req.body + ); + if (!isValid) { return res.status(400).json({ error: 'Invalid signature' }); } - const { type, data } = req.body; + const { type, data } = JSON.parse(req.body); try { await handleDirectoryEvent(type, data); res.status(201).json({ status: 'processed' }); @@ -153,20 +154,21 @@ app.post('/webhooks/scalekit', async (req, res) => { ```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() + payload=raw_body ) if not valid: raise HTTPException(status_code=400, 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"}) ``` -For Go and Java, see [REFERENCE.md](REFERENCE.md). +For Go and Java, see the [Scalekit SDK documentation](https://docs.scalekit.com/apis). --- @@ -223,8 +225,26 @@ After deploying the webhook endpoint: --- -## Reference files +## Customer self-serve SCIM setup (admin portal) + +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. + +```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. + +--- + +## Reference -- Full Go/Java SDK examples → [REFERENCE.md](REFERENCE.md) -- Webhook event payload schemas → [EVENTS.md](EVENTS.md) -- RBAC group-to-role mapping patterns → [RBAC.md](RBAC.md) +- 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/full-stack-auth/skills/manage-user-sessions/SKILL.md b/plugins/saaskit/skills/managing-saaskit-sessions/SKILL.md similarity index 87% rename from plugins/full-stack-auth/skills/manage-user-sessions/SKILL.md rename to plugins/saaskit/skills/managing-saaskit-sessions/SKILL.md index ea94dc5..e3c5548 100644 --- a/plugins/full-stack-auth/skills/manage-user-sessions/SKILL.md +++ b/plugins/saaskit/skills/managing-saaskit-sessions/SKILL.md @@ -1,17 +1,9 @@ --- -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. +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, implementing login/logout, managing cookies, handling JWT tokens, fixing session expiry, or auditing session security in a Scalekit web app. --- -# 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). +# SaaSKit Session Management ## Inputs to collect (ask before coding) - App type: traditional server-rendered web app, SPA, mobile app, or hybrid. @@ -25,7 +17,7 @@ This skill implements a secure default for traditional web apps (encrypted HttpO ## 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). +- 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`). @@ -63,7 +55,7 @@ res.cookie("accessToken", encAccess, { maxAge: (expiresIn - 60) * 1000, // clock-skew buffer httpOnly: true, secure: process.env.NODE_ENV === "production", - sameSite: "strict", + sameSite: "lax", path: "/api", }); @@ -71,7 +63,7 @@ res.cookie("accessToken", encAccess, { res.cookie("refreshToken", encRefresh, { httpOnly: true, secure: process.env.NODE_ENV === "production", - sameSite: "strict", + sameSite: "lax", path: "/auth/refresh", }); @@ -80,7 +72,7 @@ if (idToken) { res.cookie("idToken", idToken, { httpOnly: true, secure: process.env.NODE_ENV === "production", - sameSite: "strict", + sameSite: "lax", path: "/", }); } @@ -103,7 +95,7 @@ resp.set_cookie( max_age=auth_result.expires_in - 60, httponly=True, secure=os.environ.get("FLASK_ENV") == "production", - samesite="Strict", + samesite="Lax", path="/api", ) @@ -112,7 +104,7 @@ resp.set_cookie( enc_refresh, httponly=True, secure=os.environ.get("FLASK_ENV") == "production", - samesite="Strict", + samesite="Lax", path="/auth/refresh", ) @@ -122,7 +114,7 @@ if getattr(auth_result, "id_token", None): auth_result.id_token, httponly=True, secure=os.environ.get("FLASK_ENV") == "production", - samesite="Strict", + samesite="Lax", path="/", ) ``` @@ -133,7 +125,7 @@ if getattr(auth_result, "id_token", None): encAccess := encrypt(accessToken) encRefresh := encrypt(refreshToken) -c.SetSameSite(http.SameSiteStrictMode) +c.SetSameSite(http.SameSiteLaxMode) c.SetCookie("accessToken", encAccess, expiresIn-60, "/api", "", isProd(), true) c.SetCookie("refreshToken", encRefresh, 0, "/auth/refresh", "", isProd(), true) @@ -204,14 +196,14 @@ export async function verifySession(req, res, next) { maxAge: (authResult.expiresIn - 60) * 1000, httpOnly: true, secure: process.env.NODE_ENV === "production", - sameSite: "strict", + sameSite: "lax", path: "/api", }); res.cookie("refreshToken", encrypt(authResult.refreshToken), { httpOnly: true, secure: process.env.NODE_ENV === "production", - sameSite: "strict", + sameSite: "lax", path: "/auth/refresh", }); @@ -252,9 +244,9 @@ def verify_session(f): 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") + secure=is_prod(), samesite="Lax", path="/api") resp.set_cookie("refreshToken", encrypt(auth_result.refresh_token), - httponly=True, secure=is_prod(), samesite="Strict", path="/auth/refresh") + httponly=True, secure=is_prod(), samesite="Lax", path="/auth/refresh") return resp except Exception: return jsonify({"error": "Authentication failed"}), 401 @@ -294,7 +286,7 @@ func VerifySession() gin.HandlerFunc { return } - c.SetSameSite(http.SameSiteStrictMode) + 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) @@ -348,4 +340,10 @@ await scalekit.session.revokeAllUserSessions("usr_1234567890123456"); - 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 +- 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. \ No newline at end of file diff --git a/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/SKILL.md b/plugins/saaskit/skills/migrating-to-saaskit/SKILL.md similarity index 80% rename from plugins/full-stack-auth/skills/migrating-to-scalekit-auth/SKILL.md rename to plugins/saaskit/skills/migrating-to-saaskit/SKILL.md index 66f84e5..445003b 100644 --- a/plugins/full-stack-auth/skills/migrating-to-scalekit-auth/SKILL.md +++ b/plugins/saaskit/skills/migrating-to-saaskit/SKILL.md @@ -1,6 +1,6 @@ --- -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. +name: migrating-to-saaskit +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). --- # Scalekit Auth Migration Planner @@ -129,11 +129,20 @@ Verify: 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 +**Post-deployment verification:** + +```bash +# Verify token endpoint works with Scalekit credentials +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_ENVIRONMENT_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. --- 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..11e094e --- /dev/null +++ b/plugins/saaskit/skills/production-readiness-saaskit/SKILL.md @@ -0,0 +1,129 @@ +--- +name: production-readiness-saaskit +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 + +Work through in order — skip sections that don't apply. Earlier sections are blockers for later ones. + +## Quick checks + +```bash +# Confirm production credentials are set (not dev/staging) +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 +``` + +- [ ] HTTPS enforced; CORS restricted to your domains only +- [ ] All credentials in environment variables — `grep -r "sks_" src/` returns nothing +- [ ] Webhook secret in env vars (if using webhooks) + +## Core auth flows + +- [ ] 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` + +**Verify with curl:** + +```bash +# Test token endpoint reachability +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 +``` + +**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 + +**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_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 +``` + +Test with: new users, existing users, deactivated users. + +## SCIM provisioning (if applicable) + +- [ ] Webhook endpoints verify signature before processing +- [ ] User provisioning, deprovisioning, and profile updates tested +- [ ] 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) + +- [ ] 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 + +```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 + +Enterprise VPN customers must whitelist: `.scalekit.com`, `cdn.scalekit.com`, `fonts.googleapis.com`. + +## Monitoring + +- [ ] 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 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 new file mode 100644 index 0000000..8a42e5a --- /dev/null +++ b/plugins/saaskit/skills/scalekit-code-doctor/SKILL.md @@ -0,0 +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. 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_ENVIRONMENT_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_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`). + +**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` | +| 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` | +| 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/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..45ea547 --- /dev/null +++ b/plugins/saaskit/skills/scalekit-code-doctor/references/COMMON-MISTAKES.md @@ -0,0 +1,479 @@ +# 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:** +```typescript +import { ScalekitClient } from '@scalekit-sdk/node'; +``` + +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 + +**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_ENVIRONMENT_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_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 | +| `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_ENVIRONMENT_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/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/scalekit-code-doctor/references/REFERENCE.md b/plugins/saaskit/skills/scalekit-code-doctor/references/REFERENCE.md new file mode 100644 index 0000000..f4c5bb8 --- /dev/null +++ b/plugins/saaskit/skills/scalekit-code-doctor/references/REFERENCE.md @@ -0,0 +1,504 @@ +# 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`. + +--- + +## Client Initialization + +### Node.js (`@scalekit-sdk/node`) + +```typescript +import { ScalekitClient } from '@scalekit-sdk/node'; + +const scalekit = new ScalekitClient( + process.env.SCALEKIT_ENVIRONMENT_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_ENVIRONMENT_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/v2`) + +```go +import scalekit "github.com/scalekit-inc/scalekit-sdk-go/v2" + +client := scalekit.NewScalekitClient( + os.Getenv("SCALEKIT_ENVIRONMENT_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_ENVIRONMENT_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_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_ENVIRONMENT_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 | +| `validate_access_token_and_get_claims` | `(token: str, options?: TokenValidationOptions) → dict` | Decoded claims dict (raises on invalid/expired) — **canonical method when you need claims** | +| `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/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`. 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..1517a40 --- /dev/null +++ b/plugins/saaskit/skills/testing-auth-setup/SKILL.md @@ -0,0 +1,64 @@ +--- +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. +--- + +# 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 + +Install the Scalekit CLI globally if not already available: + +```bash +npm i -g @scalekit-inc/cli +``` + +Confirm these environment variables are available: + +- `SCALEKIT_ENVIRONMENT_URL` — your Scalekit environment URL +- `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=$SCALEKIT_ENVIRONMENT_URL --client_id=$SCALEKIT_CLIENT_ID --mode=fsa +``` + +### Enterprise SSO + +Requires an `organization_id` — ask for it if not provided. + +```bash +npx @scalekit-sdk/dryrun --env_url=$SCALEKIT_ENVIRONMENT_URL --client_id=$SCALEKIT_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.). + +## 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 diff --git a/scripts/install_locally.sh b/scripts/install.sh similarity index 63% rename from scripts/install_locally.sh rename to scripts/install.sh index 1ec43ea..bcec43f 100755 --- a/scripts/install_locally.sh +++ b/scripts/install.sh @@ -2,10 +2,19 @@ set -euo pipefail +if ! command -v cursor >/dev/null 2>&1 && [[ ! -d "$HOME/.cursor" ]]; then + echo "Warning: Cursor does not appear to be installed (no cursor CLI and no ~/.cursor directory)." >&2 + echo "The plugins will be copied, but you will need Cursor to use them." >&2 + echo +fi + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" PLUGIN_ROOT="$REPO_ROOT/plugins" TARGET_ROOT="${CURSOR_PLUGIN_LOCAL_DIR:-$HOME/.cursor/plugins/local}" + +# Old plugin names from v1.x (now consolidated into agentkit + saaskit) +OLD_PLUGINS=("mcp-auth" "agent-auth" "modular-sso" "modular-scim" "full-stack-auth") INSTALL_MODE="${CURSOR_AUTHSTACK_INSTALL_MODE:-auto}" if [[ ! -d "$PLUGIN_ROOT" ]]; then @@ -32,6 +41,19 @@ esac mkdir -p "$TARGET_ROOT" +# Clean up old v1.x plugin directories if they exist from a previous install +removed_old=() +for old in "${OLD_PLUGINS[@]}"; do + if [[ -d "$TARGET_ROOT/$old" ]]; then + rm -rf "$TARGET_ROOT/$old" + removed_old+=("$old") + fi +done +if [[ "${#removed_old[@]}" -gt 0 ]]; then + echo "Removed old v1.x plugins: ${removed_old[*]}" + echo +fi + echo "Installing Scalekit Auth Stack for Cursor" echo "Repository: $REPO_ROOT" echo "Target: $TARGET_ROOT" @@ -86,7 +108,11 @@ for plugin_slug in "${installed_plugins[@]}"; do done echo -echo "Next steps:" -echo " 1. Restart Cursor or run Developer: Reload Window." -echo " 2. Open Settings > Plugins." -echo " 3. Verify the Scalekit plugins load their rules, skills, and MCP servers." +echo "What to do next in Cursor:" +echo " - Restart Cursor (or reload the window)." +echo " - Look for \"agentkit\" and \"saaskit\" in your plugin settings." +echo " - Make sure both are installed and enabled." +echo " - Set the update policy to auto-update so you always have the latest skills." +echo +echo "To verify it works:" +echo " Ask Cursor to \"help me integrate agentkit\" or \"test my auth setup\"."