> ## Documentation Index
> Fetch the complete documentation index at: https://docs.usefused.com/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.

## 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.

## Validate before you apply

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

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

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="/app/unified/call">
  Targets, selectors, and reading the results envelope.
</Card>
