Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 107 additions & 14 deletions .agents/skills/add-integration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,79 @@ Before writing any code:
1. Use Context7 to find official documentation: `mcp__context7__resolve-library-id`, then fetch with `mcp__context7__query-docs`
2. Or use WebFetch to read API docs directly
3. Identify:
- Authentication method (OAuth, API Key, both)
- Supported authentication grants, who owns the app, and which permissions each operation needs
- Available operations (CRUD, search, etc.)
- Required vs optional parameters
- Response structures

### Choose the connection flow before building tools

Read the provider's current authentication documentation first. “OAuth” describes a protocol;
it does not imply a browser redirect or a deployment-wide client ID and secret. Compare the
supported paths and choose the simplest supported setup for the intended user:

| Provider method | Sim connection pattern | Verify in the provider docs |
| --- | --- | --- |
| Authorization code | Shared OAuth app and consent flow | Partner approval, redirect URIs, tenant consent, scopes, refresh-token rotation |
| Customer-owned client credentials | Saved `service_account` credential with a client ID and secret | Internal-app eligibility, grant activation, scope syntax, token lifetime and revocation behavior |
| API token or personal access token | Saved token service account when reusable connections are useful | Token permissions, identity verification, expiry and rotation |
| Private key, certificate, or service-account JSON | Existing key-based service-account framework | Signing algorithm, audience, subject, tenant binding and key rotation |

Do not present customer-owned credentials as a way around a provider's approval requirements
for a shared public integration. Explain the supported account/app type in the setup docs. Keep
existing OAuth connections usable when other users depend on them, unless a migration or removal is explicitly authorized.

For client credentials, extend the existing descriptors and minter registry under
`apps/sim/lib/credentials/client-credential-accounts/`; for token credentials, use
`token-service-accounts/`. Reuse the connect modal, encrypted storage, authorized credential
operations, and execution-time token resolver. Do not collect reusable secrets on every block,
invent a credential route, or store a short-lived access token as if it were permanent.

Verify the complete lifecycle, including connect-time verification, concurrent executions on
different workers, expiry, secret rotation, and permission changes. Some providers invalidate the
previous token whenever another is minted. Those providers require shared coordination keyed by
the provider application identity, including across duplicate saved credentials; a process-local
or per-scope token cache is insufficient. A cache hit must never authenticate a wrong secret or
silently grant broader permissions. Bound token responses and retries, prevent credential-bearing
redirects, and never include provider response bodies or secrets in errors.

Use explicit permission choices when the supported operations have different access needs. Keep
region and permission selections on reconnect unless the user changes them. Verify documented
identity endpoints rather than guessing which person a client-credentials token represents.

### Pair saved credentials with block and resource selectors

`oauth-input` is the shared saved-credential picker, including for service accounts that never
redirect to OAuth. Give its basic/advanced pair one `canonicalParamId: 'oauthCredential'`, with
the correct `serviceId`, and wire tool OAuth metadata to that same service. Tokens and trusted
API origins are hidden execution inputs; the model receives a credential ID, never a secret.
Use `credentialKind: 'service-account'` when only service accounts are supported, including in
tool OAuth metadata. Use `credentialKind: 'any'` on the picker when both browser OAuth and saved
service accounts are supported; omitting it defaults the connect action to browser OAuth. Per-connection scope
choices belong in the descriptor and encrypted credential, not an all-permissions block
`requiredScopes` array that would reject read-only connections.
Comment thread
waleedlatif1 marked this conversation as resolved.

When a documented list endpoint makes a resource ID discoverable, pair the saved credential
with a dynamic resource selector using the `add-selector` and `validate-selector` skills:

- Declare the credential subblock and any parent resource in `dependsOn`, and give the resource
selector and its manual advanced input the same canonical parameter.
- Register browser-safe selector metadata and a server attachment through the shared selector
framework. Resolve the credential with the expected provider binding and use a fixed or
credential-bound destination; selectors and execution must use the same auth/region policy.
- Exercise switching credentials, parent resources, pagination, expired tokens, denied access,
and the advanced environment-reference path. Check that stale choices cannot survive a change
of account. Do not fetch provider data or mint tokens in the browser.
- Keep a manual ID path when the provider cannot enumerate a resource. Do not invent an endpoint
solely to provide a dropdown.

For an existing integration, inspect persisted workflow serialization as well as the visible
form before removing old auth fields. Establish whether existing users need a migration or
compatibility path; do not add permanent legacy branches speculatively when removal is authorized.
When compatibility is needed, prove that old values survive serialization and execution; hiding
a field is not proof. If the user authorizes a production usage check, query only the aggregate usage/auth-shape evidence
needed and keep identities, secrets, and local evidence out of commits and PRs.

### Hard Rule: No Guessed Response Schemas

If the official docs do not clearly show the response JSON shape for an endpoint, you MUST stop and tell the user exactly which outputs are unknown.
Expand Down Expand Up @@ -274,18 +342,28 @@ export const TRIGGER_REGISTRY: TriggerRegistry = {

## Step 7: Configure Deployment Availability

Do this for every visible OAuth integration. API-key and unauthenticated integrations do not need
an OAuth client capability.
Do this for every integration that uses the shared credential picker. Only a connection that
depends on deployment-wide OAuth client fields needs an OAuth client capability; a customer-owned
service account must remain available without those fields.

The block's `oauth-input.serviceId` is the canonical link between the generated integration catalog,
the OAuth service configuration, deployment availability, and the setup CLI.

1. Ensure the block has exactly one distinct OAuth `serviceId` and that it matches the canonical
service entry in `apps/sim/lib/oauth/oauth.ts`.
2. Confirm `resolveOAuthClientCapabilityId(serviceId)` resolves to the intended provider entry in
1. Set `authMode: AuthMode.OAuth` for integrations using browser OAuth or customer-owned OAuth
client credentials. Token-only service accounts can retain `AuthMode.ApiKey` with the shared
picker, as Coda does; `oauth-input` alone does not determine the authentication protocol.
Comment thread
waleedlatif1 marked this conversation as resolved.
Register a new token-only service ID and block type in `tokenCredentialIntegrationTypes` in
`packages/deployment-config/src/integration-availability.ts` so availability and integration
policy recognize the saved credential path.
For OAuth integrations, this value lets the catalog discover the connection flow instead of
routing "Add to Sim" to chat. Ensure the block has exactly one distinct OAuth `serviceId`
matching the canonical service in `apps/sim/lib/oauth/oauth.ts`. The canonical service's
`authType` selects browser OAuth or the service-account modal. Verify the resulting catalog
and block connection actions for the chosen authentication method.
2. For browser OAuth, confirm `resolveOAuthClientCapabilityId(serviceId)` resolves to the intended provider entry in
`OAUTH_CLIENT_CAPABILITIES` in `packages/deployment-config/src/env-capabilities.ts`. Google and
Microsoft service IDs deliberately share provider-level capabilities.
3. For a new OAuth provider, add the required client fields to `OAUTH_CLIENT_CAPABILITIES`, add
3. For a new browser OAuth provider, add the required client fields to `OAUTH_CLIENT_CAPABILITIES`, add
every referenced field to the env schema in `apps/sim/lib/core/config/env.ts`, and add the
matching `text` or `secret` entries to `OAUTH_CLIENT_SETUP_FIELDS` in
`packages/sim-setup/src/capability-config.ts`. Do not create integration-specific setup logic or
Expand All @@ -298,9 +376,13 @@ the OAuth service configuration, deployment availability, and the setup CLI.
- no `deploymentRequirement` when the service-account path works independently of OAuth client fields;
- `'oauth-client'` when it requires the same deployment OAuth client fields;
- `'preview-gated'` when availability is controlled by the service-account preview block.
For a service-account-only default, set the canonical service's `authType: 'service_account'`
and `serviceAccountProviderId`. Verify both block availability and the connect modal with no
deployment OAuth credentials configured. An existing browser OAuth path may remain for legacy
credentials without becoming a prerequisite for the new path.

Never add a permissive fallback for missing capability metadata. A visible OAuth integration without
a resolvable capability must fail validation.
Never add a permissive fallback for missing capability metadata. A browser OAuth connection without
a resolvable capability must fail validation; an independent service account uses its own metadata.

## Step 8: Generate and Validate the Catalog

Expand Down Expand Up @@ -389,7 +471,8 @@ If creating V2 versions (API-aligned outputs):
- [ ] Set `integrationType` to the correct `IntegrationType` enum value
- [ ] `{Service}BlockMeta.tags` lists every applicable `IntegrationTag` (tags live on the meta, not the block)
- [ ] Defined operation dropdown with all operations
- [ ] Added credential field with `requiredScopes: getScopesForService('{service}')`
- [ ] Added the saved-credential picker with the supported `credentialKind`; browser OAuth scopes
use `getScopesForService('{service}')`, while variable service-account permissions stay on the credential
- [ ] Added conditional fields per operation
- [ ] Every `short-input`, `long-input`, `code`, and selector subBlock has a `placeholder`
- [ ] Set up dependsOn for cascading selectors
Expand All @@ -407,15 +490,25 @@ If creating V2 versions (API-aligned outputs):
- [ ] `canvasPresentation.sentences` covers every operation; `bun run apps/sim/scripts/check-canvas-sentences.ts --block={service}` passes
- [ ] `{Service}BlockMeta` also sets `url` (verified external homepage) and `skills` (grounded in `tools.access`, sourced from real use cases) — see add-block → BlockMeta

### OAuth Scopes (if OAuth service)
### Authentication and saved credentials
- [ ] Compared documented authorization code, client credentials, token, and key-based methods
- [ ] Chosen app ownership and approval requirements match the intended user
- [ ] Saved credential picker, tools, and resource selectors share the same provider/region binding
- [ ] Connect verification, expiry, concurrent workers, secret rotation, and scope changes are sound
- [ ] Existing usage and the migration/removal decision are established; any required compatibility is verified through serialization and execution
- [ ] New connection and reconnect flows verified in the running UI

### Browser OAuth Scopes (if authorization-code flow is supported)
- [ ] Defined scopes in `lib/oauth/oauth.ts` under `OAUTH_PROVIDERS`
- [ ] Added scope descriptions in `SCOPE_DESCRIPTIONS` within `lib/oauth/utils.ts`
- [ ] Used `getCanonicalScopesForProvider()` in `lib/auth/connectors/providers.ts` (never hardcode)
- [ ] Used `getScopesForService()` in block `requiredScopes` (never hardcode)
- [ ] Used `getScopesForService()` for the browser OAuth permissions the block needs (never hardcode)
- [ ] A picker that also accepts service accounts does not require broader scopes than every supported
connection needs; per-connection service-account permissions are validated by the descriptor/minter

### Deployment Availability (if OAuth service)
### Deployment Availability (if using the saved-credential picker)
- [ ] Block declares exactly one distinct `oauth-input.serviceId`
- [ ] `resolveOAuthClientCapabilityId(serviceId)` resolves to the intended `OAUTH_CLIENT_CAPABILITIES` entry
- [ ] Browser OAuth resolves to the intended `OAUTH_CLIENT_CAPABILITIES` entry; independent service accounts work without deployment OAuth fields
- [ ] Every new OAuth capability field exists in `apps/sim/lib/core/config/env.ts`
- [ ] Runtime OAuth fields live in `OAUTH_CLIENT_CAPABILITIES`; matching CLI input modes live in the exhaustively checked `OAUTH_CLIENT_SETUP_FIELDS`
- [ ] If `serviceAccountProviderId` is configured, `SERVICE_ACCOUNT_METADATA_BY_OAUTH_SERVICE_ID` has the matching projection and deployment requirement
Expand Down
5 changes: 2 additions & 3 deletions apps/docs/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3577,9 +3577,8 @@ export function IntuneIcon(props: SVGProps<SVGSVGElement>) {

export function RampIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
<rect x='2' y='4' width='20' height='16' rx='3' stroke='currentColor' strokeWidth='2' />
<path d='M2 9h20M6 15h4' stroke='currentColor' strokeWidth='2' strokeLinecap='round' />
<svg {...props} viewBox='0 0 69.5 59' fill='currentColor' xmlns='http://www.w3.org/2000/svg'>
<path d='M69.5,58.7V59l-37.9,0v-0.3c5.5-3.1,9.2-6.2,12.6-9.5h15.6L69.5,58.7z M60.2,9.4L50.6,0h-0.3c0,0,0.2,17.5-16,33.5C18.5,49.1,0,49.2,0,49.2v0.3L9.8,59c0,0,18.3,0.2,34.4-15.7C60.3,27.6,60.2,9.4,60.2,9.4z' />
</svg>
)
}
Expand Down
1 change: 1 addition & 0 deletions apps/docs/content/docs/cli/credentials.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ Update Credential (OAuth login or personal API key required)
| `--certificate-id <value>` | No | Provider certificate mapping identifier. |
| `--org-id <value>` | No | Provider organization ID. |
| `--data-center <value>` | No | Provider data center. |
| `--scope <value>` | No | Provider permissions; preserved on reconnect when omitted. |
| `--auth-method <value>` | No | Provider authentication method. |
| `--private-key <value>` | No | Write-only PEM private key. |
| `--username <value>` | No | Provider run-as username. |
Expand Down
1 change: 1 addition & 0 deletions apps/docs/content/docs/cli/reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,7 @@ sim credentials update <credentialId> [options]
| `--certificate-id <value>` | No | Provider certificate mapping identifier. |
| `--org-id <value>` | No | Provider organization ID. |
| `--data-center <value>` | No | Provider data center. |
| `--scope <value>` | No | Provider permissions; preserved on reconnect when omitted. |
| `--auth-method <value>` | No | Provider authentication method. |
| `--private-key <value>` | No | Write-only PEM private key. |
| `--username <value>` | No | Provider run-as username. |
Expand Down
6 changes: 5 additions & 1 deletion apps/docs/content/docs/integrations/ramp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ With the Ramp block, you can:
- **Prepare finance reviews**: Read bills and reimbursements with date, status, and employee filters.
- **Document expenses**: Read and add memos for transaction records.

Connect a Ramp account through OAuth. Self-hosted deployments require `RAMP_CLIENT_ID` and `RAMP_CLIENT_SECRET` from a Ramp developer app with Authorization Code and Refresh Token grants, plus a matching HTTPS callback at `/api/auth/oauth2/callback/ramp`. Ramp requires review before a public app can connect production businesses other than its own.
Save your Ramp developer app's client ID and client secret as a credential, then select it in the Ramp block. Use a production app owned by your business with the **Client Credentials** grant enabled. This connection accesses your own business's Ramp data; sandbox credentials are not supported.

Enable these scopes on your app: `business:read`, `users:read`, `transactions:read`, `cards:read`, `vendors:read`, `bills:read`, `reimbursements:read`, `memos:read`, and `memos:write`. Sim requests these scopes when it obtains an access token and automatically obtains a new token when needed. You do not need a redirect URI or a Sim-owned OAuth application. See [Ramp's authorization guide](https://docs.ramp.com/developer-api/v1/authorization) for app setup and permissions.

The OAuth deployment settings below apply to the optional browser consent flow. They are not required when you save your own app's client credentials.

List operations return one page at a time, with 2–100 results per page. Pass `nextCursor` as the pagination cursor to continue. Money outputs preserve Ramp's currency and minor-unit fields; avoid combining different currencies.

Expand Down
Loading
Loading