Documentation
MCP

Confirmed actions

How FloPay MCP runs mutating tools, from a preview that changes nothing to explicit confirmation, safe retries, and what to do when an outcome is unclear.

Confirmed actions

A mutating tool never changes anything on its first call. FloPay checks the request, returns a preview of exactly what would happen, and waits. The action runs only when your client sends back the operator's explicit acceptance, and FloPay checks everything again before it writes.

Each tool's class, risk, and retry policy are in the Tool reference. Actions FloPay has not made available to agents, including every high-risk action, are listed on Unavailable operations.

Which clients see mutating tools

A mutating tool appears in tools/list and search results, and can be called, only when all of these hold for the request:

  • The access token carries the mcp:write scope.
  • The operator's role on the merchant is owner or admin.
  • The request declares form elicitation in io.modelcontextprotocol/clientCapabilities: "elicitation": { "form": {} }, or an elicitation object that lists no mode. A client that declares only URL elicitation stays read-only.

Otherwise the tool is hidden, and calling it returns JSON-RPC error -32602 with Unknown or unavailable tool. Declare the same capabilities on the preview call and the confirmation call.

Preview

Call the tool with its arguments, exactly as you call a read-only tool, using the headers from Connect any MCP client with Mcp-Name: ProductBrand_create:

{
  "jsonrpc": "2.0",
  "id": 10,
  "method": "tools/call",
  "params": {
    "name": "ProductBrand_create",
    "arguments": { "code": "summer-range", "name": "Summer range" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": { "elicitation": { "form": {} } }
    }
  }
}

FloPay runs the same validation and permission checks the action itself would run, then stops before writing, so the preview does not change anything. FloPay MCP answers with an input_required result that holds a confirmation form:

{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "inputRequests": {
      "flopay/confirmation": {
        "method": "elicitation/create",
        "params": {
          "mode": "form",
          "message": "Review the authoritative FloPay preview and explicitly accept to execute: {\"operationId\":\"ProductBrand_create\",\"inputHash\":\"<input hash>\",\"actionPrincipal\":{\"actorId\":\"3c6f1e2a-8b4d-4f7e-9a1c-5d2e8b7f4a3c\",\"authMethod\":\"oauth-bearer\",\"clientApplicationId\":\"7d0c6c2e-4b1f-4f7a-9c1e-2f8b7a1d0e5c\",\"merchantId\":\"0f8e4c1a-6d2b-4e7f-8a9c-3b5d7e1f2a4c\",\"role\":\"admin\",\"scopes\":[\"mcp:read\",\"mcp:write\"]},\"authorization\":{\"role\":\"admin\",\"scopes\":[\"mcp:read\",\"mcp:write\"],\"permissions\":{\"roles\":[\"owner\",\"admin\"],\"scopes\":[\"mcp:write\"]},\"policyHash\":\"<policy hash>\"},\"targets\":[{\"type\":\"product-brand-code\",\"id\":\"summer-range\",\"version\":\"absent\"}],\"effects\":[{\"type\":\"create-product-brand\",\"targetId\":\"summer-range\",\"details\":{\"before\":null,\"after\":{\"code\":\"summer-range\",\"name\":\"Summer range\",\"description\":null}}}],\"warnings\":[\"Product brand id and timestamps are assigned only when the confirmed action executes.\"],\"normalizedProposedValues\":{\"code\":\"summer-range\",\"name\":\"Summer range\",\"description\":null},\"validationOutcome\":{\"valid\":true,\"checks\":[\"dto\",\"product-brand-code-available\"]},\"expectedResult\":{\"responseType\":\"ProductBrandDto\",\"projected\":{\"code\":\"summer-range\",\"name\":\"Summer range\",\"description\":null},\"serverAssignedFields\":[\"id\",\"createdAt\",\"updatedAt\"]},\"expiresAt\":\"2026-09-14T15:05:00.000Z\"}",
          "requestedSchema": {
            "type": "object",
            "properties": {
              "idempotencyKey": {
                "type": "string",
                "title": "Idempotency key",
                "description": "A client-generated retry identity for this action."
              }
            },
            "required": ["idempotencyKey"]
          }
        }
      }
    },
    "requestState": "<confirmation state>",
    "_meta": {
      "flopay/contractRevision": "<contract revision>",
      "flopay/httpStatus": 201,
      "flopay/confirmationPhase": "preview",
      "io.modelcontextprotocol/serverInfo": { "name": "flopay-mcp", "version": "<server version>" }
    },
    "resultType": "input_required"
  }
}

The form message carries the preview from the FloPay API. Show the operator what it says before asking for a decision:

Preview fieldWhat it tells the operator
effectsEach change, with the values before and after it.
targetsThe records the action touches, with the version the preview saw.
warningsAnything the operator should know first.
validationOutcomeWhether the input passed validation, and which checks ran.
expectedResultThe kind of record the action returns, and which fields FloPay assigns when it runs.
expiresAtWhen the confirmation stops working.
actionPrincipal, authorizationWho the action runs as, and the role and scopes that allow it.

requestState is an opaque confirmation token. Keep it as it is and never show it to the model or the operator. If FloPay rejects the preview, because the arguments are invalid or the operator can no longer run the action, FloPay MCP answers JSON-RPC error -32603 with Backend action preview is invalid. Check the arguments against the tool's entry in the Tool reference before trying again.

Confirm or decline

To run the action, call the same tool again with the same name and arguments, echo requestState, and send the operator's decision in inputResponses:

{
  "jsonrpc": "2.0",
  "id": 11,
  "method": "tools/call",
  "params": {
    "name": "ProductBrand_create",
    "arguments": { "code": "summer-range", "name": "Summer range" },
    "requestState": "<confirmation state>",
    "inputResponses": {
      "flopay/confirmation": {
        "action": "accept",
        "content": { "idempotencyKey": "5b7e2c90-3d4f-4a8b-9c1d-6e2f7a8b9c0d" }
      }
    },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": { "elicitation": { "form": {} } }
    }
  }
}

The idempotency key is a value your client generates once for this intended action, such as a UUID. The result is the FloPay API response for the action, in the shape the tool's entry in the reference documents.

If the operator declines or dismisses the form, send decline or cancel as the action instead. FloPay MCP answers FloPay action was not executed. and does not contact the FloPay API.

A confirmation:

  • expires five minutes after the preview,
  • works for one execution only,
  • is bound to the operator, the merchant, the client application, the tool, the exact arguments, the operator's role and scopes, and the version of every record the preview targeted.

If anything the confirmation is bound to changes after the preview, for example someone else edits the same record, FloPay refuses to run the action. Call the tool again for a fresh preview.

An action that reaches the FloPay API but does not complete returns a tool result with isError: true, the status in flopay/httpStatus, and the API response in structuredContent:

StatusMessageWhat to do
409MCP action confirmation is expired, changed, forged, or already used.Call the tool again for a new preview, and ask the operator to confirm it.
409Idempotency-Key was already used for a different MCP action binding.The key belongs to another action. Generate a new key for this one.
409The MCP action outcome is not yet authoritative.An earlier attempt with this key has not finished or did not complete. Read the target record before doing anything else.
503The MCP action outcome is ambiguous.FloPay cannot tell whether the change was written. Read the target record to find out.
503The MCP action completed without a durable authoritative result record.The action ran, but FloPay could not keep its result for retries. Read the target record instead of retrying.
403The live action principal does not satisfy the reviewed MCP action policy.The operator's role or the token's scopes changed since the preview. Reconnect with the access the action needs.

The responses about unclear outcomes also carry a reconciliation field with the same instruction: do not retry automatically, and check the record first.

Retry an action

Retries are safe only when you know what happened to the first attempt. Use the same idempotency key, and only for the same action:

  • The call timed out or the connection dropped. Send the same confirmation call again, with the same requestState and the same idempotency key. If the first attempt completed, FloPay returns its original result instead of running the action twice. FloPay keeps that result for at least 24 hours.
  • The result was an error. Read the target record with a read-only tool. If the change did not happen, start again with a new preview and a new idempotency key.
  • The result was 409 or 503 with a reconciliation field. Do not retry automatically. Check the target record, and let the operator decide what to do next.

An idempotency key can be up to 200 characters. Never reuse a key for a different action: FloPay refuses it with 409.

Some tools declare the manual-reconciliation retry policy, because their outcome cannot be safely repeated once FloPay starts writing. For these tools, a failure after writing starts answers 503 with The MCP action outcome is ambiguous., and you must read the record before deciding anything:

  • Session_createSetup returns one-time card capture credentials. They are issued once: a repeated call returns only a reference to the session, never the credentials again.
  • User_updateUser changes a customer's details. Read the customer record to see whether the change was applied.

Tools with the same-idempotency-key policy can be retried with the same idempotency key as described above. Do not retry automatically when FloPay says a result is not yet authoritative.

On this page