<!-- Writing a task — Delta MCP docs · https://www.mcpdelta.com/docs/writing-a-task · updated 2026-10-07 -->

> How your AI writes a task as one program, and what Delta MCP checks before the first call: types, errors, the plan, all or nothing, read back and Undo.

# Writing a task

How your AI writes a task as one program, and what Delta MCP checks before the first call: types, errors, the plan, all or nothing, read back and Undo.

Your AI writes a task as one short program. It reads, decides in code, changes values and returns a summary. Delta MCP turns the changes into the fewest calls your connector needs, checks them, makes them together and reads them back.

## What your AI reads first

Before its first line, your AI has each connector as a typed API. These are two lines of it, for a tickets connector.

```
tickets.ticket: {  all (q ?:  { status ?:  "open"  |  "closed"  }) :  Promise<TicketListed[]>  /* one call per page */ ;  get (key :  string) :  Promise<Ticket> }
tickets.update_ticket (a: { id: string; labels ?:  string[] })
```

The first line is a **collection**. Delta MCP infers collections from a connector’s tool names and schemas (`list_tickets`, `get_ticket`…). Nothing is written by hand per connector. The second line is a tool, called as it is. Every tool is declared this way.

Argument types are exact. A return type comes from the connector’s schema, or from answers Delta MCP has seen (`as seen`). It learns those by calling a few safe read tools once, and keeps only shapes, never values.

## Reading and changing

`.all()` gives objects as the connector lists them: every page, no lines such as comments. `.get()` gives one whole object, lines included, by its key or by its name as you would say it. Independent reads can run together with `Promise.all`.

An object changes like a value: assign a field, `push` or `splice` a line, `.create({ … })`, `.delete(key)`.

```
let  n  =  0 ;
for  ( const  t  of  await  tickets.ticket.all ({ status:  "open"  })) {
  const  w  =  await  tickets.ticket.get (t.id);
  w.labels.push ( "triaged" );
  w.comments.push ({ body:  "Seen"  });
  n ++ ;
}
return  n;
```

When the program ends, Delta MCP compares each object with what it gave, reads it again as it is now, and applies only what the program changed. The answer, ids left out:

```
Returned:
1
Applied d_… · 2 call(s) · … · read back ✓
Done:
T-1:
  - status=open
  + status=open labels=triaged
  + comment "Seen"
Undo: delta_apply { "undo": "d_…" }
```

A tool also works as it is: `tickets.update_ticket({ … })`. A read returns a copy, and changing the copy writes nothing. Delta MCP says so. A write waits for the end and returns a placeholder for later writes.

## Checked before the first call

Reads run while the program runs. Changes wait. Before any is made, Delta MCP checks every tool call against its schema, every changed field, and what the connector can’t do, such as changing a comment once posted.

A tool call with a typo stops the program, with nothing changed. All the problems of that call come back together.

```
rules.rules_get: unknown argument "idx" — did you mean id?; missing id · it takes { id: string }
```

Problems in object changes come back all at once, each with its fix. This program has two typos.

```
const  t  =  await  tickets.ticket.get ( "T-1" );
t.labls  =  [ "x" ];
t.comments.push ({ bdy:  "Seen"  });
```

```
Nothing changed: 2 problem(s), found before any call.
✗ tickets.ticket T-1: no field « labls »
    → did you mean labels?
✗ tickets.ticket T-1 new comment: « bdy » is not a field of a new comment
    → did you mean body?
```

## The plan

`delta { dryRun: true }` and `delta_read { code }` plan without changing anything. A plan shows what changes, then the exact calls.

```
Delta d_… · 2 call(s) planned:
T-1:
  - status=open
  + status=closed labels=late
  + comment "Closed: fixed in 2.3"
Sends exactly:
  tickets.update_ticket { id: "T-1", status: "closed", labels: ["late"] }
  tickets.add_comment { ticket: "T-1", body: "Closed: fixed in 2.3" }
Apply: delta_apply { "delta": "d_…" }
```

Calls are minimal, named the connector’s way (`add_comment` takes `ticket`, not `id`), and ordered: a new object first, then its lines with the id it got.

## All or nothing

| What happens | Result |
| --- | --- |
| The program or a check fails | Nothing is changed. |
| You cancel the call in your AI app | None of its changes are made. |
| A mode refuses it (read only) | Nothing is changed, and the answer says why. |
| A call fails during the changes | Object changes are put back and read back as before. |
| A call gets no answer in time | It may have been made. The answer says so, and nothing is retried. |

This covers object changes. A tool called as it is can’t be rolled back by Delta MCP. If an earlier one succeeded, it stays made, and the answer lists it so your AI doesn’t repeat it.

## Read back and Undo

After applying, Delta MCP reads each changed object back and compares: `read back ✓`, or the difference. Undo is `delta_apply { undo: "d_…" }`, and it says what it can do before and after:

-   **an object changed**: put back, and read back;
-   **a creation made through a tool**: deleted by the connector’s own delete, not read back;
-   **a deleted object**: re-created, as a new copy when the connector gives its own keys;
-   **a call nothing takes back**: left as it is, and listed.

Undo goes through your mode like any change. See [Activity and Undo](https://www.mcpdelta.com/docs/activity-and-undo).

## Live mode

A browser can’t be planned: what to click depends on what the page shows. With `live: true`, each call runs at once and its result feeds the next line. A connector with browser tools also gets helpers that find elements by label. This is an illustration, with a connector named `web`.

```
await  web.goto ( "https://shop.test/form" );
await  web.fill ({ Name:  "Ana"  });
await  web.press ( "Create" );
return  "done" ;
```

In a mode that asks, you approve the program once, before its first action. A live program can’t be planned. Objects it changes are still applied together at the end.

`await world.commit()` is the middle way. It makes the object changes so far, checked, and the program goes on from the objects as they now are. A dry run stops there.

## Limits

A program may make 300 direct tool reads and 2,000 changes (200 live). `.all()` stops at 5,000 objects and asks for a narrower query. A program is stopped after 30 minutes. To keep one that worked, see [Shortcuts in code](https://www.mcpdelta.com/docs/shortcuts-in-code).
