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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "stackone-connector-builder",
"version": "1.0.0",
"description": "Interactive wizard for building generic Falcon connectors. Guides builders through provider setup, authentication, action discovery (scoped or maximal), config generation, validation, and testing with full cleanup.",
"author": {
"name": "StackOne",
"email": "engineering@stackone.com"
},
"license": "MIT",
"keywords": ["connector", "falcon", "generic", "stackone", "builder", "discovery"]
}
52 changes: 52 additions & 0 deletions .claude/plugins/stackone-connector-builder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# stackone-connector-builder

Claude Code plugin for building generic Falcon connectors that return raw provider API responses.

## When to use this plugin

Use **this plugin** when you want direct API access — actions use `actionType: custom` and return the provider's response as-is, with no schema normalisation.

Use **`stackone-unified-builder`** (via `/build-unified-connector`) when your connector needs to map provider data to a StackOne unified schema (HRIS, ATS, CRM, etc.).

## Workflow

| Step | Command | What it does |
|------|---------|--------------|
| 1 | `/setup-connector` | Provider setup, checks StackOne index, CLI pull or scaffold |
| 2 | `/configure-auth` | Auth configuration (API key, OAuth2, Basic Auth, custom) |
| 3 | `/discover-actions` | Choose scoped or maximal discovery (MCP-powered) |
| 4 | `/build-config` | Generate YAML with `actionType: custom` for all actions |
| 5 | `/validate-connector` | Validate YAML structure |
| 6 | `/test-connector` | Live test + automatic cleanup of test records |

## Quick start

```
/build-connector
```

This runs the full workflow from start to finish, guiding you through each step.

## Key features

- **Session persistence** — progress is saved to `.connector-build-session.json` so you can pause and resume at any step
- **Two discovery modes**:
- *Scoped* — describe your use case and relevant endpoints are found via vector search
- *Maximal* — discovers all provider endpoints via MCP (async, 5–15 min); use when you want full coverage
- **Test cleanup** — every record created during testing is tracked and deleted afterwards; any records that cannot be deleted are reported
- **Credential security** — `scramble_credentials` is always called after testing to invalidate live credentials

## MCP tools used

| Tool | Purpose |
|------|---------|
| `map_provider_key`, `get_provider_actions` | Check StackOne connector index |
| `discover_actions`, `get_discover_actions_task_status` | Maximal endpoint discovery (async) |
| `analyze_versioning` | Detect API versioning patterns |
| `vector_search` | Scoped discovery — find relevant endpoints by description |
| `test_actions`, `get_test_actions_task_status` | Automated live testing (async) |
| `scramble_credentials` | Invalidate credentials after testing |

---

For unified connectors (HRIS/ATS/CRM/etc), see the `stackone-unified-builder` plugin and `/build-unified-connector`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
# Actions and Steps Reference

Technical reference for writing action YAML files — covering action structure, inputs, step functions, expressions, and result mapping.

## Action Structure

### Required Fields
- `actionId`: Unique identifier
- `categories`: List of categories for StackOne UI
- `actionType`: `custom` (default for non-unified) or `list|get|create|update|delete` (unified only)
- `label`: Human-readable name
- `description`: Short description (shown in UI)
- `steps`: List of step functions
- `result`: Final output

### Optional Fields
- `details`: Longer description (tool description, rate limits, required permissions)
- `resources`: Action-specific documentation URLs
- `examples`: Input/output demonstration pairs
- `inputs`: Request parameters
- `requiredScopes`: Space-separated OAuth scopes (must be defined in `scopeDefinitions`). Use most restrictive scope.
- `entrypointUrl` / `entrypointHttpMethod`: **Unified actions ONLY** (DO NOT USE for non-unified)
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: entrypointUrl and entrypointHttpMethod are required for generic connectors in this plugin, so telling authors not to use them will produce invalid configs that fail validation.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .claude/plugins/stackone-connector-builder/references/actions-and-steps.md, line 22:

<comment>`entrypointUrl` and `entrypointHttpMethod` are required for generic connectors in this plugin, so telling authors not to use them will produce invalid configs that fail validation.</comment>

<file context>
@@ -0,0 +1,302 @@
+- `examples`: Input/output demonstration pairs
+- `inputs`: Request parameters
+- `requiredScopes`: Space-separated OAuth scopes (must be defined in `scopeDefinitions`). Use most restrictive scope.
+- `entrypointUrl` / `entrypointHttpMethod`: **Unified actions ONLY** (DO NOT USE for non-unified)
+
+## Inputs
</file context>
Fix with Cubic


## Inputs

For non-unified actions: inputs must match EXACTLY the provider's request parameters. DO NOT create inputs that don't exist in the provider API. Ignore deprecated fields.

### Supported Types
`string`, `number`, `boolean`, `datetime_string`, `object`, `enum`

**Never use `type: array`** — always use `array: true` with element type.

### Input Examples

**Basic string/number:**
```yaml
inputs:
- name: userId
description: User identifier
type: string
in: path
required: true
```

**Object:**
```yaml
inputs:
- name: filter
description: Filter object
type: object
in: body
required: true
properties:
- name: name
description: Filter by name
type: string
required: false
```

**Array:**
```yaml
inputs:
- name: userIds
description: Array of user IDs
type: string
array: true
in: body
required: true
```

**Enum:**
```yaml
inputs:
- name: status
description: Employment status
type: enum
required: true
in: query
oneOf:
values:
- active
- inactive
- terminated
```

### Input locations (`in` field)
- `query` — URL query parameter
- `body` — request body
- `path` — URL path parameter (referenced via `${inputs.fieldName}` in URL)
- `headers` — request header

## Expression Formats

### 1. JSONPath (`$.path.to.field`) — PREFERRED
For direct references without string construction:
- Credentials: `$.credentials.apiKey`
- Inputs: `$.inputs.userId`
- Step output: `$.steps.fetch_users.output.data`

### 2. String Interpolation (`${...}`)
For embedding dynamic values in strings:
- URLs: `/users/${inputs.userId}/posts/${inputs.postId}`
- Domains: `https://${credentials.domain}.api.com`

### 3. JEXL Expressions (`'{{...}}'`)
For conditional logic, transformations. **Wrap in single quotes.**
- Conditionals: `'{{present(inputs.includeInactive)}}'`
- Ternary: `'{{$.status == "active" ? "enabled" : "disabled"}}'`
- String manipulation: `'{{inputs.name.toUpperCase()}}'`

**IMPORTANT:**
- For `value` fields: prefer JSONPath `$.inputs.fieldName` for simple references. JEXL `'{{...}}'` is also supported in `value` fields when you need transformations, conditionals, or string manipulation.
- For `condition` fields: use JEXL `'{{present(inputs.fieldName)}}'`

## Step Functions

Every step MUST have a `description` field.

### `request` — Standard HTTP request

```yaml
steps:
- stepId: fetch_users
description: List users from the API
stepFunction:
functionName: request
parameters:
url: '/users'
method: get
args:
- name: showInactive
value: $.inputs.showInactive
in: query
condition: '{{present(inputs.showInactive)}}'
```

Always use `args` for parameters (never direct `body` field).

**Custom headers for `authorization.type: none`:**
```yaml
args:
- name: X-API-Key
value: $.credentials.customKey
in: headers
```

**Raw array bodies** — when API requires `[...]` instead of `{...}`:
```yaml
args:
- name: events
value: $.inputs.events
in: body
spread: true
```

**Custom error mapping:**
```yaml
customErrors:
- receivedStatus: 404
targetStatus: 400
message: 'Custom error message'
```

### `paginated_request` — Cursor-based pagination

Only use if provider supports cursor/offset pagination. Otherwise use `request`.

```yaml
steps:
- stepId: list_records
description: Fetch records with pagination
stepFunction:
functionName: paginated_request
parameters:
url: "/records"
method: get
response:
dataKey: results
nextKey: nextCursor
iterator:
key: cursor
in: query
```

### `soap_request` — SOAP API calls

```yaml
steps:
- stepId: get_employee
description: Fetch employee via SOAP
stepFunction:
functionName: soap_request
parameters:
url: /EmployeeService
method: post
soapOperation: GetEmployee
soapAction: http://example.com/soap/GetEmployee
useSoapContext: false
namespaces:
- namespaceIdentifier: emp
namespace: http://example.com/employees
args:
- name: EmployeeId
value: ${inputs.employee_id}
in: body
```

Key parameters:
- `soapOperation`: SOAP operation name
- `soapAction`: SOAP action URI
- `namespaces`: XML namespace definitions
- `useSoapContext`: Set to `false` when provider expects payload as-is
- Prefix XML attributes with `@_` (e.g., `@_xsi:type`)

### Other Step Functions
- `group_data`: Groups data from multiple steps
- `map_fields`: Maps using `fieldConfigs` (unified actions only)
- `typecast`: Applies types from `fieldConfigs` (unified actions only)

## Field Configs (Unified Actions Only)

NOT required for non-unified connectors. Maps provider response to StackOne unified schema:

```yaml
fieldConfigs:
- targetFieldKey: id
expression: $.accountId
type: string
- targetFieldKey: type
expression: $.accountType
type: enum
enumMapper:
matcher:
- matchExpression: '{{$.accountType == "atlassian"}}'
value: agent
- matchExpression: '{{$.accountType == "app"}}'
value: bot
- targetFieldKey: active
expression: $.active
type: boolean
```

## Result Mapping

```yaml
# Read response
result:
data: $.steps.fetch_users.output.data

# Write response
result:
message: Resource updated successfully
data:
id: $.inputs.id
```

## GraphQL Patterns

Reference: `linear` connector

**Input structure — always use nested `variables` object:**
```yaml
inputs:
- name: variables
description: Variables for the query
type: object
in: body
properties:
- name: first
description: Number of items
type: number
required: false
- name: filter
description: Filter object
type: object
required: false
```

**Request:**
```yaml
args:
- name: Content-Type
value: application/json
in: headers
- name: query
value: "query($first: Int) { resources(first: $first) { nodes { id name } } }"
in: body
- name: variables
in: body
condition: "{{present(inputs.variables)}}"
value:
{ first: $.inputs.variables.first }
```

**IMPORTANT for nested objects:** When querying nested objects, ONLY return the `id` field if a separate action exists to fetch the full object. Don't return full nested objects.

**Query patterns:**
- List: `query($first: Int, $after: String) { resources(first: $first, after: $after) { nodes { id name } pageInfo { hasNextPage endCursor } } }`
- Get: `query($id: String!) { resource(id: $id) { id name description } }`
- Create: `mutation($input: CreateInput!) { create(input: $input) { success resource { id } } }`
- Update: `mutation($id: String!, $input: UpdateInput!) { update(id: $id, input: $input) { success } }`
Loading
Loading