---
title: "What an agent can never do"
url: https://services.dev/docs/mcp/never
---
# What an agent can never do

URL: https://services.dev/docs/mcp/never

> Signing, consenting, directing, deciding, granting, attesting and raising a cap belong to a person. No tool exists for them. An agent gets the link to hand on.

An agent can do almost everything a buyer does: find your brand, ask the price, order, wait, read, accept, send back and cancel. Seven acts stay with people. An agent can never sign, consent, direct, decide, grant, attest or raise a cap.

No prompt can argue its way past that, because there's no check to argue with. Each of those acts is a row whose only callers are a person's own session or a party's own link, and no MCP client holds either. So the row renders no tool, and there's nothing to call.

## Every row MCP leaves out

```txt
# a person's own: only their session or their link can call it, and no MCP client holds either
job.approve    owner session, member session  POST /:portal/jobs/:id/approve
clock.assign   owner session                  POST /owner/clocks/:id/assign
ask.get        party link, owner session      GET /asks/:id
ask.complete   party link                     POST /asks/:id
ask.revoke     party link                     POST /asks/:id/revoke
job.export     owner session                  GET /jobs/:id/export
job.ledger     owner session                  GET /owner/jobs/:id
key.raise      party link                     POST /keys/:key

# not a call an agent makes over MCP, and what it does instead
subject.list   an agent reads a subject's clocks with list_clocks and get_clock; the subject itself is the buyer's record, read over HTTP and the CLI
subject.get    an agent reads a subject's clocks with list_clocks and get_clock; the subject itself is the buyer's record, read over HTTP and the CLI
clock.ack      only a clock's owner acts on it, with their own key or in their own app: an agent reads it with get_clock, and a call from anyone else answers needs_the_owner with the owner's app as next.hand_to_person
clock.satisfy  only a clock's owner acts on it, with their own key or in their own app: an agent reads it with get_clock, and a call from anyone else answers needs_the_owner with the owner's app as next.hand_to_person
clock.choose   only a clock's owner acts on it, with their own key or in their own app: an agent reads it with get_clock, and a call from anyone else answers needs_the_owner with the owner's app as next.hand_to_person
job.files.put  a file is bytes: send it over HTTP or the SDK, and name it by URL in an order
job.files.get  a file is bytes: send it over HTTP or the SDK, and name it by URL in an order
event.stream   a stream, not a call: an agent reads list_events after a seq, or job:// for one job
watch.create   a watch delivers to a URL your server holds, which an agent's session doesn't: it reads list_events after a seq instead
watch.list     a watch delivers to a URL your server holds, which an agent's session doesn't: it reads list_events after a seq instead
watch.delete   a watch delivers to a URL your server holds, which an agent's session doesn't: it reads list_events after a seq instead
watch.replay   a watch delivers to a URL your server holds, which an agent's session doesn't: it reads list_events after a seq instead

tools across both toolsets: 23; tools for these rows: 0

→ tools/call approve_job {"id":"job_7Hq2"}
← {"code":-32602,"message":"No tool approve_job. tools/list names them."}
```

The first group belongs to a person. The second is left out for a plainer reason, and each row says what an agent does instead. A tool an agent guesses at doesn't exist.

## The seven acts

| Act | Who does it, and where | Built on |
|---|---|---|
| sign | the party, through their link; or a credentialed person at their step, in their own session, the signature stamped on the record | `deliver.ask` `deliver.stamp` |
| consent | the party, through their link | `deliver.ask` |
| decide | the client, choosing among the step's choices | `deliver.ask` |
| direct | the client, naming who acts next | `deliver.direct` |
| grant | the client, giving a scoped permission that expires | `deliver.grant` |
| attest | a member, at their Task, in their own session | a member's acts |
| raise a cap | the buyer, at checkout, through a link | `key.raise` |

`answer` and `upload` asks are different. A party may let an agent that holds their delegated key complete those two for them, `dlg_`. On localhost the sandbox stands one in: `POST /asks/:id` with a `dlg_` value in the `x-services-test-dlg` header, and no link, completes an `answer` or `upload` ask, and the record names an agent as the one who did. A `sign` ask sent the same way is refused `needs_the_party`.

## What the agent gets instead

When a job waits on a party, its `next` is `hand_to_person`: the link only that person can use, what they're asked to do, and when it expires. `tool` is null, because no tool finishes it.

```txt
→ tools/call get_job {"id":"job_7Hq2","wait":50}
# the text an agent reads first, beside structuredContent
← job_7Hq2 · waiting · on party · $49.00 held
  next  hand http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_ to the person it asks
  next  get_job {"id":"job_7Hq2"}
  next  cancel_job {"id":"job_7Hq2"}

  next    {"do":"hand_to_person","url":"http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_","arguments":{"ask":"ask_7Hq2"},"party":"buyer","to":"answer","items":[{"id":"terms","label":"Which terms stay in English?","status":"open"}],"expires_at":"2026-11-01T14:02:11.000Z","tool":null,"http":"GET http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_"}

# the agent tries to finish the job itself
→ tools/call accept_job {"id":"job_7Hq2"}
← {
  "type": "BLOCKED",
  "category": "blocked",
  "code": "needs_the_party",
  "reason": "glossary waits on the buyer to answer. Only they can, through their own link. Hand them the link in next.",
  "param": "ask_7Hq2",
  "next": {
    "do": "hand_to_person",
    "url": "http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_",
    "arguments": {
      "ask": "ask_7Hq2"
    },
    "party": "buyer",
    "to": "answer",
    "items": [
      {
        "id": "terms",
        "label": "Which terms stay in English?",
        "status": "open"
      }
    ],
    "expires_at": "2026-11-01T14:02:11.000Z",
    "tool": null,
    "http": "GET http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_"
  },
  "doc_url": "http://localhost:8787/docs/errors/needs_the_party",
  "request_id": "req_WcR8EU",
  "simulated": true
}
```

The agent's job is to deliver the link, by whatever reached the person in the first place: email, chat, or the app they ordered from. When they act, the job moves on, and the agent's next `get_job` sees it. If the link goes astray, `reissue_ask` issues a new one, and the old one stops working at once. The ask itself, its link, expiry and reissue, has its own page: [Asks](https://services.dev/docs/service/ask.md).

## The same no, on every surface

An agent that tries anyway gets `needs_the_party`, with the same body over the CLI, HTTP, MCP and the SDK. Here the party is the client, asked to sign:

**CLI**

```txt
$ npx services.dev jobs accept job_7Hq3
sign waits on Ada Lovelace to sign. Only they can, through their own link. Hand them the link in next.
  next    hand http://localhost:8787/a/ask_7Hq2?t=cHGjprSn08ODyOp_8zLIVH to the person it asks
  docs    http://localhost:8787/docs/errors/needs_the_party
```

**API**

```txt
$ curl -X POST http://localhost:8787/jobs/job_7Hq3/accept -H 'content-type: application/json' -d '{}'
← 403 · {
  "type": "BLOCKED",
  "category": "blocked",
  "code": "needs_the_party",
  "reason": "sign waits on Ada Lovelace to sign. Only they can, through their own link. Hand them the link in next.",
  "param": "ask_7Hq2",
  "next": {
    "do": "hand_to_person",
    "url": "http://localhost:8787/a/ask_7Hq2?t=cHGjprSn08ODyOp_8zLIVH",
    "arguments": {
      "ask": "ask_7Hq2"
    },
    "party": {
      "name": "Ada Lovelace",
      "email": "ada@example.com"
    },
    "to": "sign",
    "expires_at": "2026-11-01T14:02:11.000Z",
    "tool": null,
    "http": "GET http://localhost:8787/a/ask_7Hq2?t=cHGjprSn08ODyOp_8zLIVH"
  },
  "doc_url": "http://localhost:8787/docs/errors/needs_the_party",
  "request_id": "req_SvsTKa",
  "simulated": true
}
```

**MCP**

```txt
→ tools/call accept_job {"id":"job_7Hq3"}
← isError: true, structuredContent:
← {
  "type": "BLOCKED",
  "category": "blocked",
  "code": "needs_the_party",
  "reason": "sign waits on Ada Lovelace to sign. Only they can, through their own link. Hand them the link in next.",
  "param": "ask_7Hq2",
  "next": {
    "do": "hand_to_person",
    "url": "http://localhost:8787/a/ask_7Hq2?t=cHGjprSn08ODyOp_8zLIVH",
    "arguments": {
      "ask": "ask_7Hq2"
    },
    "party": {
      "name": "Ada Lovelace",
      "email": "ada@example.com"
    },
    "to": "sign",
    "expires_at": "2026-11-01T14:02:11.000Z",
    "tool": null,
    "http": "GET http://localhost:8787/a/ask_7Hq2?t=cHGjprSn08ODyOp_8zLIVH"
  },
  "doc_url": "http://localhost:8787/docs/errors/needs_the_party",
  "request_id": "req_4Ruoeo",
  "simulated": true
}
```

**SDK**

```txt
await lingua.jobs.accept("job_7Hq3")
× throws BlockedError<'job.accept'> · 403
← {
  "type": "BLOCKED",
  "category": "blocked",
  "code": "needs_the_party",
  "reason": "sign waits on Ada Lovelace to sign. Only they can, through their own link. Hand them the link in next.",
  "param": "ask_7Hq2",
  "next": {
    "do": "hand_to_person",
    "url": "http://localhost:8787/a/ask_7Hq2?t=cHGjprSn08ODyOp_8zLIVH",
    "arguments": {
      "ask": "ask_7Hq2"
    },
    "party": {
      "name": "Ada Lovelace",
      "email": "ada@example.com"
    },
    "to": "sign",
    "expires_at": "2026-11-01T14:02:11.000Z",
    "tool": null,
    "http": "GET http://localhost:8787/a/ask_7Hq2?t=cHGjprSn08ODyOp_8zLIVH"
  },
  "doc_url": "http://localhost:8787/docs/errors/needs_the_party",
  "request_id": "req_SvsTKa",
  "simulated": true
}
```

## Why it holds

The line between an agent and a person is drawn by credential. Each operation names who may call it in the [credentials registry](https://services.dev/docs/reference/credentials.md), and every surface reads that one table, so a new surface can't open a door the table keeps shut. That's also how a Service meets licensing by design: when a step needs a credentialed person's signature, only that person's own session can give it, and the record says who did.

Worked in: [the agent's order](https://services.dev/docs/examples/sales/agent.md#what-it-can-set-and-what-it-cant), the floor handed to the seller.

---

- Up: [Your MCP server](https://services.dev/docs/mcp.md)
- Previous: [Your MCP server](https://services.dev/docs/mcp.md)
- Next: [The SDKs](https://services.dev/docs/sdk.md)
