The premise, and where I think it sits wrong

Foxit's MCP server gives an agent forty tools and deliberately leaves signing out. The natural reading is that signing is too dangerous to be a tool.

That is not quite it. The agent can reach the eSign API perfectly well — it has the credentials and an HTTP client, and this project calls createfolder directly, exactly as the challenge describes. Removing a tool from a catalogue removes a convenience, not a capability.

What the agent cannot do is be the signer. So the useful statement is narrower and harder: an API boundary is not an authority boundary. request_signature is a legitimate agent capability. sign_as_this_person is not, and no catalogue decision makes it one.

What makes a signature different from every other human-in-the-loop gate

I have built human gates before, and they all freeze a hash of the artifact and call it done. For a signature that is not enough, and Foxit's own request body shows exactly why:

{
  "base64FileString": ["…"],          // the document
  "parties": [{ "emailId": "…", "sequence": 1, "permission": "…" }],
  "fields":  [{ "type": "signature", "party": 1, "required": true,
                "pageNumber": 1, "x": 119, "y": 542 }],
  "sendNow": false,
  "createEmbeddedSigningSession": true
}

Who signs, in what order, on which page, and whether a signature is required at all — none of that lives in the PDF. It lives in the request. So an envelope can be changed in every way that matters while the document stays byte-identical:

  • swap parties[0].emailId and a different person signs the same page;
  • set the signature field to required: false and the ceremony completes with nothing signed;
  • move the field, or reassign it to another party;
  • flip sendNow and it leaves by email instead of the controlled session.

A gate that freezes the document hash waves all four through. In the demo you can watch the digest stay identical while the handoff is refused.

So what a person approves here is not the document. It is a canonical envelope manifest: document digest, parties with sequence and permission, every field with page, coordinates and required flag, delivery mode, cost, expiry, nonce. The gate recomputes it immediately before createfolder and refuses on any difference.

Two things I got backwards at first

The gate belongs before createfolder, not before the sign button. createfolder is the irreversible act: it transmits the document to Foxit, spends credits, and brings a signable session into existence. A gate in front of the signature is guarding a door that is already open. For the same reason the handoff is not a safe retry — one manifest gets exactly one folderId, and that has to survive a restart, so it is written to disk before anything else can fail.

The signed document's hash is supposed to differ from the approved one. My first verification compared them and would have reported every real signature as tampering. Foxit adds the signature values, the certificate and the audit material; in the recorded run the file goes from 58,459 to 119,803 bytes. What is verified is lineage, not equality:

approved digest → envelope manifest → folderId → human signs → EXECUTED → signed PDF + activity history

What is deliberately absent

There is no code path that submits a signature on anyone's behalf. There is also none that deletes an envelope — an earlier draft had a "cleanup" that cancelled and permanently deleted a folder on error, and that is wrong: by then a person may already be looking at it, and destroying it quietly is not a tool's decision.

Things the Foxit docs do not tell you

Four of these cost me real time, so they are in the README as well:

  • eSign is not on a separate host with its own OAuth. It rides the same host and the same client credentials as PDF Services, at /esign/api/v1/…. The regional foxitesign.foxit.com token exchange in the older guides is an earlier surface and answers invalid_client to current keys.
  • pdf_from_html rejects fractional page dimensions. A4 as 595.92 × 841.92 fails with a bare VALIDATION_ERROR; 595 × 842 succeeds.
  • pageMode: SINGLE_PAGE with scalingMode: SCALE squeezed the whole document onto one 4-point-wide page. Normal pagination is MULTIPLE_PAGE.
  • createfolder does not return embeddedSigningSessions. The signing session comes from regenerateEmbeddedSigningSession, which is safe to call again — useful, because a signer who steps away needs their seat back.

Cost

The free Developer plan gives 500 shared credits a year and one envelope costs 5. Calls stop when credits run out rather than billing anything. The console carries its own budget and refuses a handoff that would exceed it, so a runaway loop cannot spend the year in an afternoon.

Scope

docs/change-order.html is a fictional change order between invented companies, marked as such on its face, with a clause that makes the signer swap mean something: only the named individual can bind the customer above USD 25,000. The digests bind an approver to an exact envelope; they are not an identity proof. Multi-party signing, reminders, templates and webhooks are all absent — this demonstrates one thesis rather than a product.

Built With

Share this project:

Updates