> ## Documentation Index
> Fetch the complete documentation index at: https://usefused.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Declare a unified operation

> Turn several provider calls into one SDK or MCP operation, with dependencies and rollback.

A unified operation is one logical SDK or MCP operation that calls several provider operations, waits for declared dependencies, and compensates the successful ones when a later step fails. The caller makes one call; the ordering, mapping, and unwinding happen in the Engine.

<Note>
  Unified operations work in **TypeScript or Python** `kind: sdk` configs and in `kind: mcp` configs. Go SDK generation does not support them. MCP exposes each token-authorized authored name through `search_docs` and executes it through the existing `execute` tool.
</Note>

They wrap operations already selected in `services`. They grant no new scope and carry no credentials.

<Tip>
  To draft a sequential operation from a goal, use [prompt](/docs/app/from-a-prompt#describe-a-sequential-operation). It resolves exact provider operations and shows the proposed order, inputs, mappings, and output before apply. You can still author the full configuration directly below.
</Tip>

## The shape

`unified_operations` sits at the top level, beside `services`:

```yaml theme={null}
apiVersion: fused/v1
kind: sdk
name: issues
version: "1.0.0"
language: typescript
services:
  github: {operations: [createIssue, deleteIssue]}
  gitlab: {operations: [createIssue]}

unified_operations:
  issues.create:
    input:
      type: object
      required: [title]
      properties: {title: {type: string}}
    bindings:
      github:
        operation: createIssue
        input: {title: "${input.title}"}
        rollback: {operation: deleteIssue, input: {id: "${response.github.id}"}}
      gitlab: createIssue
```

For MCP, use `kind: mcp`, omit `language`, and keep the same `unified_operations` block.

In an SDK, dot-separated names become the generated namespace: `issues.create` produces `sdk.unified.issues.create`. MCP preserves `issues.create` as the exact operation name returned by `search_docs` and accepted by `execute`.

## Operation properties

| Property      | Required | What it does                                                            |
| ------------- | -------- | ----------------------------------------------------------------------- |
| `input`       | Yes      | JSON Schema for the logical operation's public input                    |
| `bindings`    | Yes      | Map of step names to selected provider operations; max 16               |
| `output`      | No       | Final projection. Its constructed object becomes the exact return value |
| `description` | No       | Metadata retained with the immutable definition                         |

An SDK or MCP app version may declare at most 64 unified operations.

## Binding properties

A binding is either a bare operation ID or an expanded object.

```yaml theme={null}
bindings:
  gitlab: createIssue          # compact: step key also selects the service
  github:                      # expanded
    service: github
    operation: createIssue
    input: {title: "${input.title}"}
    depends_on: [gitlab]
    rollback:
      operation: deleteIssue
      input: {id: "${response.github.id}"}
```

| Property             | What it does                                                                 |
| -------------------- | ---------------------------------------------------------------------------- |
| `service`            | The configured service key. Omitted, it defaults to the binding key          |
| `operation`          | Exact case-sensitive `operationId`, already selected for that service        |
| `input`              | Maps unified input and direct dependency responses into the provider request |
| `depends_on`         | Makes this step wait for the named steps                                     |
| `rollback.operation` | An operation on the same service that compensates this one                   |
| `output`             | Optional projection for this step's result                                   |

Each binding key is a unique step name used by `depends_on`, response references, caller `targets`, and the returned `result.target`. It does not have to match a service key — several steps may use one service, and they stay separate steps with separate results.

Quote a provider-qualified service that starts with `@`, like `"@acme/github"`.

## How execution runs

Steps run as their dependencies settle, several at a time. Once every forward call has finished, rollback runs in reverse dependency order.

A failed step compensates only its **successful direct dependencies that declare rollback**.

<Warning>
  An output-mapping error happens *after* the provider call succeeded, so it neither triggers rollback nor blocks dependants.
</Warning>

`depends_on` values must be exact binding keys — self-dependencies, duplicates, unknown targets and cycles are all rejected. There is no `on_failure`; application code owns fallback.

## Mapping values

Values in `bindings.<step>.input`, `rollback.input`, and output projections can reference the operation's input and earlier responses. Operation-level `input` is plain JSON Schema.

A complete reference keeps its JSON type — `` `${response.drive.files}` `` stays an array — while a reference mixed into a string interpolates. Two operators are available: `??` takes the first non-null value, and a trailing `?` omits a missing one.

What each location may read:

| Location                | Can reference                                                      |
| ----------------------- | ------------------------------------------------------------------ |
| `bindings.<step>.input` | `input.*`, `target`, and `response.<direct-dependency>.*`          |
| `rollback.input`        | `input.*`, `target`, and that step's own `response.<step>.*`       |
| Operation `output`      | `input.*` and `response.<declared-target>.*` after bindings settle |
| Binding `output`        | `input.*`, `target`, and that binding's raw `response.<step>.*`    |

## Shaping the return value

Without `output`, the call returns the all-settled `{results, rollbacks}` envelope. With it, your constructed object *is* the return value — no `data` wrapper, no envelope.

```yaml theme={null}
    output:
      type: object
      required: [primary_id, title, provider_ids]
      properties:
        primary_id: "${response.github.id}"
        title: "${response.github.issue.title}"
        provider_ids:
          type: array
          value: ["${response.github.id}", "${response.gitlab.iid}"]
          items: {type: string}
```

Binding outputs run first and become the response values the operation output reads. Scalar shorthand infers its type from the authored JSON scalar; expanded scalars use `{type, value}`. Arrays take one `value` plus optional schema-only `items`.

The `{schema, mapping}` form is invalid — use only the recursive output tree.

## Plan before you apply

Plan validates the local config before its first Engine request, so one command covers both the recursive output contract and the remote change preview:

```bash theme={null}
# SDK
fused-cli sdk plan -f .fused/sdks/issues.yaml --no-input --json

# MCP
fused-cli mcp plan -f .fused/mcps/issues.yaml --no-input --json
```

Use the standalone `validate` commands only for offline checks that must not contact an Engine.

Unified operations live inside an immutable SDK or MCP app version like everything else. Changing one under an existing version returns `app_version_immutable` — publish a new version.

<Card title="Call a unified operation" icon="play" href="/docs/app/unified/call">
  Targets, selectors, and reading the results envelope.
</Card>
