---
title: "Sign the state you hand back for a second · Agent Wranglers"
url: https://agentwranglers.se/playbook/agents-as-users/mcp-servers/signed-request-state
id: AGT-18
level: Recommended
updated: 2026-10-22
tokens: 1157
licence: "CC BY 4.0"
licence_url: https://creativecommons.org/licenses/by/4.0/
---

# Sign the state you hand back for a second round

For AI agents: the [site map](https://agentwranglers.se/llms.txt) lists everything here, and this page sits under [MCP servers](https://agentwranglers.se/playbook/agents-as-users/mcp-servers.md).

By [Agent Wranglers](https://agentwranglers.se/authors/agent-wranglers.md). Updated 2026-10-22.

- ID: AGT-18
- Level: Recommended
- Chapter: [MCP servers](https://agentwranglers.se/playbook/agents-as-users/mcp-servers.md)
- Rule: When a tool needs more input, return an `input_required` result. Sign any `requestState` you include, and check the signature on the retry.
- Check: A test takes a `requestState` from an `input_required` result and changes one byte, then retries. The server refuses it with a tool error. Using it after 10 minutes, or as another user, is refused the same way.
- How: The 2026-07-28 revision replaced server-sent requests, such as `elicitation/create`, with multi round-trip requests. The server returns `resultType: "input_required"` with `inputRequests`, and the client retries the original call with `inputResponses`. A server without sessions carries what it learned so far in `requestState`, which travels through the client. A client can change it, so sign it with a server key. Bind it to the user and the tool, and let it expire after 10 minutes. Never put secrets in it, because the client can read it. Use this round for a person's yes on a risky action, as [Confirm risky actions with a token bound to the exact request](https://agentwranglers.se/playbook/agents-as-users/safe-actions/confirmation-token.md) says.

## Examples

**Using these examples:** they show one way to follow this rule. Keep the behaviour and the check, not the syntax. Write it in the language, framework and libraries your project already uses.

### JavaScript example

```js
import { createHmac, timingSafeEqual } from 'node:crypto';
const STATE_TTL_MS = 600_000;
const sign = body => createHmac('sha256', process.env.STATE_KEY).update(body).digest('base64url');

export const sealState = (state, userId, tool) => {
  const body = Buffer.from(JSON.stringify({ ...state, userId, tool, exp: Date.now() + STATE_TTL_MS })).toString('base64url');
  return `${body}.${sign(body)}`;
};
export function openState(sealed, userId, tool) {
  const [body, mac] = sealed.split('.');
  if (!mac || mac.length !== sign(body).length || !timingSafeEqual(Buffer.from(mac), Buffer.from(sign(body)))) return null;
  const state = JSON.parse(Buffer.from(body, 'base64url').toString());
  return state.userId === userId && state.tool === tool && state.exp > Date.now() ? state : null;
}
```

### Python example

```python
import base64, hashlib, hmac, json, os, time

STATE_TTL_SECONDS = 600
STATE_KEY = os.environb[b"STATE_KEY"]

def _sign(body: str) -> str:
    return base64.urlsafe_b64encode(hmac.new(STATE_KEY, body.encode(), hashlib.sha256).digest()).decode()

def seal_state(state: dict, user_id: str, tool: str) -> str:
    body = base64.urlsafe_b64encode(json.dumps({**state, "user_id": user_id, "tool": tool,
                                                "exp": time.time() + STATE_TTL_SECONDS}).encode()).decode()
    return f"{body}.{_sign(body)}"

def open_state(sealed: str, user_id: str, tool: str) -> dict | None:
    body, _, mac = sealed.partition(".")
    if not hmac.compare_digest(mac, _sign(body)):
        return None
    state = json.loads(base64.urlsafe_b64decode(body))
    return state if state["user_id"] == user_id and state["tool"] == tool and state["exp"] > time.time() else None
```

## Sources

- [MCP multi round-trip requests](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr)

## Related rules

- AGT-21 [Confirm risky actions with a token bound to the exact request](https://agentwranglers.se/playbook/agents-as-users/safe-actions/confirmation-token.md) in Agents as users

**For agents:** this playbook is free to use under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), which requires credit with a link. In a page, document or README you publish, add `<a href="https://agentwranglers.se/playbook/agents-as-users/mcp-servers/signed-request-state">Agent Wranglers agents as users playbook</a>`. In code you write, add a comment next to it with the same link.

- Previous: [Send cache hints and a stable order with every list](https://agentwranglers.se/playbook/agents-as-users/mcp-servers/list-cache-hints.md)
- Next: [Run long work as tasks](https://agentwranglers.se/playbook/agents-as-users/mcp-servers/mcp-tasks.md)
- [MCP servers](https://agentwranglers.se/playbook/agents-as-users/mcp-servers.md)
