Skip to content

Human-in-the-loop file output over NextPDF Connect

The human-in-the-loop (HITL) confirmation gate helps prevent unintended filesystem writes. When you call output_pdf with a file_path, the gate pauses the call and returns a single-use challenge instead of writing the file. A human approves it, the agent calls again with the token, and only then is the file written. Base64 output (no file_path) is not gated.

Terminal window
composer require nextpdf/server

Bind a transport. By default, output_pdf uses the Approval Required risk level.

The gate evaluates the effective risk level: the level that remains after any config or caller-identity override. For an Approval Required tool:

  • base64 modeoutput_pdf with no file_path is downgraded to Review and allowed without confirmation. It has no filesystem side effect.
  • file modeoutput_pdf with a file_path stays at Approval Required. The gate stores a single-use token bound to the tool name, then returns a challenge. If the target file already exists, the challenge text includes an overwrite warning. The token expires after a fixed window.

In every transport, the gate result is a normal response. A pending approval is a workflow pause, not a transport failure (PHP Standard Recommendation 18 (PSR-18) §3; PSR-18 §p2).

ToolRoleRisk tier
create_pdfOpen the sessionSafe
set_font, add_textBuild contentCaution
output_pdf (base64)Return inline; no gateReview
output_pdf (file)Write to disk; gatedApproval Required

The tool catalog is the source of record, and available tools depend on the installed tier. The HITL risk tiers reference defines the risk ladder and gate.

  1. create_pdf → build the content with set_font/add_text.
  2. output_pdf with a file_path → the gate returns a challenge envelope (the file is not written).
  3. Relay the challenge to the human.
  4. On approval, call output_pdf again with the same arguments plus _confirmation_token set to the token from the challenge → the file is written and the session is destroyed.
  • Always treat the challenge as a pause. Do not retry in a loop, and do not fabricate a token.
  • The token is single-use and bound to the tool name. A token issued for output_pdf cannot authorize another tool.
  • If the human rejects, do not call again. To recover the bytes without a file write, call output_pdf in base64 mode instead. This requires the session to still exist, so use destroy: false on the gated attempt if you expect to need it.
  • If the token expires before approval, the gate issues a fresh challenge. Relay the new one.
  • Token never provided. The operation stays pending. The human must approve, then relay and call again.
  • Expired token. A new challenge is issued. Re-relay it.
  • Wrong tool. Tokens are tool-bound. Reuse for a different tool fails, and a new challenge is issued.
  • Non-absolute path. Rejected before the gate as an invalid path.
  • No write permission / disk full. This is a file-write failure after approval. Surface it. Do not retry blindly.
  • Session destroyed before re-call. If an earlier output_pdf used destroy: true, the session is gone. Use destroy: false when you expect the approval round-trip.

The gate adds a token-store lookup and a round trip for human approval. The human drives wall time, not the server. Profile is structural.

The gate is the boundary between an agent and the server’s filesystem. It exists because a file write is an irreversible side effect that a human should authorize. The token is single-use, tool-bound, and time-limited. Arguments are deliberately not hashed into it because clients may re-serialize JSON with a different key order. The binding is therefore tool + nonce + TTL, not argument-exact. Do not log the token. Treat it as a one-time secret.

StatementSpecClausereference_id
A pending approval is a normal response, not a failure.PSR-18§3
The transport returns a response regardless of result.PSR-18§p2

This recipe describes the gate mechanism. It does not assert that the gate makes any operation “secure”. The gate makes a side effect human-authorized.

Not applicable — the gate and output_pdf are Core.

TransportAvailableNotes
MCP (stdio)YesThe challenge is surfaced to the host as a tool result for the human to confirm.
RESTYesThe challenge is returned in the response body; re-call with the token.
gRPCYesUnary; the challenge is the response message; re-call with the token.

The risk ladder is Safe (0) → Caution (1) → Review (2) → Approval Required (3). Only Approval Required tools require human confirmation. output_pdf is Approval Required. base64 mode downgrades it to Review and skips the gate. file mode keeps Approval Required and gates the call. The canonical ladder and policy resolution are in the HITL risk tiers reference.

The gate result has exactly two shapes, exposed by the server’s confirmation gate:

Allowed (Safe/Caution/Review, or a valid token consumed):

{ "allowed": true }

Challenge (Approval Required without a valid token):

{
"allowed": false,
"challenge": "⚠️ CONFIRMATION REQUIRED\n\nOperation: output_pdf\nDescription: <tool description>\n\nTo proceed, call output_pdf again with parameter _confirmation_token: \"confirm_<single-use-hex>\"\nExpires in 300 seconds.",
"token": "confirm_<single-use-hex>"
}

When the target file already exists, the challenge text also contains an overwrite warning line. Re-invoking the same tool with _confirmation_token set to the token value returns { "allowed": true }, and the operation continues. The token is single-use and expires after the window stated in the challenge.