PostalForm MCP

Mail real letters from agents: PDF → checkout → status.

PostalForm MCP Developer Docs

PostalForm runs a streamable HTTP MCP server that lets agents create draft mail orders, take payment, and track fulfillment.

UCP (Universal Commerce Protocol)

PostalForm also supports the UCP Checkout capability for platforms that integrate via the UCP MCP binding.

UCP checkout sessions are dynamically priced based on PDF page count and print options. PostalForm expects the PDF and address data in metadata.postalform (pdf can be { download_url, file_id }, { upload_token }, a data URL, or an allowlisted https URL). UCP tool calls must include a platform profile in _meta.ucp.profile.

Note: UCP currently supports PDF-based checkouts with Loqate Address IDs (*_address_type="Address") only. For manual addresses, letter text, and workflow forms, use the PostalForm MCP tools on /mcp.

Example UCP create_checkout:

{
  "name": "create_checkout",
  "arguments": {
    "_meta": {
      "ucp": {
        "profile": "https://platform.example/profiles/v2026-01/shopping-agent.json"
      }
    },
    "currency": "USD",
    "line_items": [{ "item": { "id": "postalform_mail_pdf_bw_double" }, "quantity": 1 }],
    "payment": {},
    "metadata": {
      "postalform": {
        "pdf": {
          "download_url": "https://example.oaiusercontent.com/file.pdf",
          "file_id": "file_abc123"
        },
        "file_name": "letter.pdf",
        "sender_name": "Sender Example",
        "sender_address_id": "US|LP|Pz0_Qj4_bGJg|16074807|13_ENG",
        "sender_address_type": "Address",
        "sender_address_text": "123 Sender St, Springfield, IL 62701",
        "recipient_name": "Recipient Example",
        "recipient_address_id": "US|LP|Pz0_Qj4_bGJg|199825276|99_ENG",
        "recipient_address_type": "Address",
        "recipient_address_text": "456 Recipient Ave, Springfield, IL 62701",
        "double_sided": true,
        "color": false
      }
    }
  }
}

Machine payments (x402 private preview)

PostalForm supports x402-based machine payments for direct API order creation.

  • Create/pay endpoint: POST https://postalform.com/api/machine/orders
  • Validate/quote endpoint (no payment side effects): POST https://postalform.com/api/machine/orders/validate
  • Status endpoint: GET https://postalform.com/api/machine/orders/:id
  • Flow: unpaid request returns 402 + PAYMENT-REQUIRED, client pays and retries with PAYMENT-SIGNATURE, server returns 202 + PAYMENT-RESPONSE

Required request fields for POST /api/machine/orders:

  • request_id (UUID)
  • buyer_name
  • buyer_email (required; set on Stripe PaymentIntent as receipt_email)
  • pdf (recommended canonical format: { "upload_token": "..." }; also accepts { download_url, file_id }, data URL, or allowlisted https URL)
  • sender/recipient names + addresses (Loqate: *_address_type="Address" + *_address_id + *_address_text, or Manual: *_address_type="Manual" + *_address_manual with { line1, line2?, city, state, zip })

Common options:

  • double_sided (default true)
  • color (default false)
  • mail_class (standard, priority, express)
  • certified (default false)
  • mailpiece_type (letter or postcard; default letter)
  • postcard_size (4x6, 6x9, 11x6; required when mailpiece_type="postcard")

Postcard machine orders use the same endpoints and payment flow. For postcards, send pdf as the final composed postcard PDF and set mailpiece_type: "postcard" plus postcard_size.

Postcard PDF requirements:

  • Use a 2-page PDF.
  • Page 1 is the artwork side.
  • Page 2 is the mailing side. You can place backside artwork or a non-address message there, but do not place sender/recipient names, return or delivery addresses, indicia, or barcode data in the PDF. PostalForm fills the mailing block automatically.
  • Match the exact bleed canvas for the selected size. The canonical spec and templates live at postcard PDF guidelines:

PostalForm normalizes postcard print options server-side to standard mail, full color, double-sided, and no certified/signature add-ons.

MCP endpoint

  • https://postalform.com/mcp
  • Methods: POST/GET/DELETE with the streamable HTTP transport
  • Sessions: initialize once, then include the mcp-session-id header on all subsequent requests
  • Auth: no authentication is required today (contact [email protected] for allowlisting)
  • Transport: JSON-RPC payloads over POST

Integration quickstart

  1. Point your MCP client at /mcp and initialize a session.
  2. Choose addresses: use Loqate IDs via postalform.search_addresses, or use manual addresses with *_address_type="Manual" + *_address_manual.
  3. Create a draft:
    • Letter text: postalform.create_letter_order_draft (recommended for ChatGPT apps).
    • Workflow forms: postalform.list_forms -> postalform.get_form_schema -> postalform.create_form_order_draft.
    • PDF upload: postalform.create_order_draft (ChatGPT file params, upload_token, data URL, or allowlisted https URL).
  4. Send the customer to checkout_url, or use checkout_session for ChatGPT Instant Checkout.
  5. Poll order status as fulfillment progresses.
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'

const transport = new StreamableHTTPClientTransport(new URL('https://postalform.com/mcp'))
const client = new Client({ name: 'my-agent', version: '1.0.0' })

await client.connect(transport)
await client.listTools()

const draft = await client.callTool({
  name: 'postalform.create_letter_order_draft',
  arguments: {
    letter: {
      title: 'Demand for payment',
      body: 'Hello...\\n\\nThis is my letter body.\\n\\nSincerely,\\n',
      signature: 'Sender Example',
    },
    sender_name: 'Sender Example',
    sender_address_type: 'Manual',
    sender_address_manual: {
      line1: '123 Sender St',
      city: 'Springfield',
      state: 'IL',
      zip: '62701',
    },
    recipient_name: 'Recipient Example',
    recipient_address_type: 'Manual',
    recipient_address_manual: {
      line1: '456 Recipient Ave',
      city: 'Springfield',
      state: 'IL',
      zip: '62701',
    },
  },
})

console.log(draft.structuredContent?.checkout_url)

Payment and checkout

External checkout (any MCP client)

  • Use checkout_url from postalform.create_order_draft to send customers to the hosted PostalForm payment page.
  • Best default for non-ChatGPT MCP clients.
  • Open the URL in a browser (or window.openai.openExternal inside ChatGPT).
  • Poll postalform.get_order_status to confirm payment and fulfillment.

ChatGPT Instant Checkout (Stripe Agentic Commerce)

  • PostalForm returns an ACP checkout_session in the draft response that ChatGPT uses to show Instant Checkout.
  • When the buyer confirms, ChatGPT calls complete_checkout with a Stripe Shared Payment Token (spt_...).
  • Use window.openai.requestCheckout(checkout_session) in your UI.
  • Use checkout_url as a fallback if window.openai.requestCheckout is unavailable.

Tools

  • postalform.list_forms: List published workflow-based forms available to agents.
  • postalform.get_form_schema: Fetch a workflow schema (fields, dependencies, attachments). May include checkout_flow metadata (home or forms_order) for PostalForm web routing; MCP clients/agents can treat this as informational.
  • postalform.create_letter_order_draft: Create a draft order from letter text (server renders a PDF).
  • postalform.create_form_order_draft: Create a draft order from a workflow form JSON submission (server fills template PDF).
  • postalform.create_pdf_upload: Create a short-lived PDF upload URL + upload_token for agents that cannot pass ChatGPT file params. Upload the PDF, then call postalform.create_order_draft with pdf: { upload_token: "..." }.
  • postalform.search_addresses: Search US address suggestions. Input: query (min 3 chars), optional container (for drilling into Container results), optional target. Output: address suggestions with ids/text/type. If type is Container, call search again with container=id to get Address results for order drafts.
  • postalform.create_order_draft: Create a draft order and receive a checkout URL. Input: pdf (ChatGPT file object { download_url, file_id }, or { upload_token } from postalform.create_pdf_upload, or a data:application/pdf;base64,... URL, or an https URL on an allowlisted host), sender/recipient names, and either Loqate addresses (*_address_type="Address" + *_address_id) or manual addresses (*_address_type="Manual" + *_address_manual). Output: order_id, price_usd, checkout_url, checkout_session.
  • complete_checkout: Finalize a ChatGPT Instant Checkout session with a payment token. Input: checkout_session_id (order*id), buyer (first_name, last_name optional, email), payment_data.token (Stripe shared payment token, spt*...), provider=stripe. Output: checkout session response with status and order permalink.
  • postalform.get_order_status: Fetch the latest order status details. Input: order_id. Output: found, is_paid, current_step (for example: payment_received, receiver_address_verified, sender_address_verified, pdf_normalized, letter_created, email_sent, canceled, refunded, abandoned).
  • postalform.ping: Health check for the MCP server. Input: none. Output: ping payload for connectivity checks.

Tool payload examples

postalform.list_forms

Request:

{
  "name": "postalform.list_forms",
  "arguments": { "q": "IRS", "limit": 10 }
}

postalform.get_form_schema

Request:

{
  "name": "postalform.get_form_schema",
  "arguments": { "slug": "8822" }
}

Note: the response may include checkout_flow. This is workflow metadata for PostalForm web checkout routing and does not change MCP tool-call sequencing.

postalform.search_addresses

Request:

{
  "name": "postalform.search_addresses",
  "arguments": {
    "target": "recipient",
    "query": "123 Main St"
  }
}

Response:

{
  "structuredContent": {
    "view": "address_suggestions",
    "target": "recipient",
    "query": "123 Main St",
    "suggestions": [
      {
        "id": "US|LP|Pz0_Qj4_bGJg|16074807|13_ENG",
        "text": "123 Main St, Springfield, IL 62701",
        "type": "Address",
        "description": ""
      }
    ]
  }
}

postalform.create_letter_order_draft

Request:

{
  "name": "postalform.create_letter_order_draft",
  "arguments": {
    "request_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "letter": {
      "title": "Payment demand letter",
      "body": "Hello,\\n\\nThis is the letter body.\\n\\nSincerely,\\n",
      "signature": "Sender Example"
    },
    "sender_name": "Sender Example",
    "sender_address_type": "Manual",
    "sender_address_manual": {
      "line1": "123 Sender St",
      "city": "Springfield",
      "state": "IL",
      "zip": "62701"
    },
    "recipient_name": "Recipient Example",
    "recipient_address_type": "Manual",
    "recipient_address_manual": {
      "line1": "456 Recipient Ave",
      "city": "Springfield",
      "state": "IL",
      "zip": "62701"
    }
  }
}

postalform.create_form_order_draft

Request:

{
  "name": "postalform.create_form_order_draft",
  "arguments": {
    "request_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "slug": "8822",
    "fields": {
      "name_line_1": "Sender Example",
      "old_address_line_1": "123 Old St",
      "old_city": "Springfield",
      "old_state": "IL",
      "old_zip": "62701",
      "new_address_line_1": "456 New Ave",
      "new_city": "Springfield",
      "new_state": "IL",
      "new_zip": "62701"
    },
    "sender_name": "Sender Example",
    "sender_address_type": "Address",
    "sender_address_id": "US|LP|Pz0_Qj4_bGJg|16074807|13_ENG",
    "sender_address_text": "123 Sender St, Springfield, IL 62701",
    "recipient_name": "IRS",
    "recipient_address_type": "Manual",
    "recipient_address_manual": {
      "line1": "Internal Revenue Service",
      "city": "Kansas City",
      "state": "MO",
      "zip": "64999"
    },
    "use_workflow_recipient": false
  }
}

postalform.create_pdf_upload

Request:

{
  "name": "postalform.create_pdf_upload",
  "arguments": {
    "file_name": "letter.pdf",
    "content_type": "application/pdf",
    "content_length": 1234567
  }
}

Response:

{
  "structuredContent": {
    "upload_url": "https://postalform.com/api/mcp/pdf-uploads/pfu_...",
    "upload_token": "pfu_...",
    "expires_at": "2026-01-17T12:00:00Z",
    "max_bytes": 104857600,
    "required_headers": {
      "Content-Type": "multipart/form-data"
    }
  }
}

Upload the PDF with multipart/form-data using the file field (aliases pdf and pdfFile are also accepted), then pass upload_token to postalform.create_order_draft.

postalform.create_order_draft

Note: pdf accepts a ChatGPT file object, an upload_token, or a base64 data URL (data:application/pdf;base64,...). If you pass a URL, it must be https and hosted on an allowlisted domain (default: *.oaiusercontent.com). Request:

{
  "name": "postalform.create_order_draft",
  "arguments": {
    "request_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "pdf": { "upload_token": "pfu_..." },
    "file_name": "letter.pdf",
    "sender_name": "Sender Example",
    "sender_address_id": "US|LP|Pz0_Qj4_bGJg|16074807|13_ENG",
    "sender_address_type": "Address",
    "sender_address_text": "123 Sender St, Springfield, IL 62701",
    "recipient_name": "Recipient Example",
    "recipient_address_id": "US|LP|Pz0_Qj4_bGJg|199825276|99_ENG",
    "recipient_address_type": "Address",
    "recipient_address_text": "456 Recipient Ave, Springfield, IL 62701",
    "double_sided": true,
    "color": false
  }
}

Response:

{
  "structuredContent": {
    "view": "order_draft",
    "order_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "page_count": 2,
    "price_usd": 2.99,
    "checkout_url": "https://postalform.com/payment?orderId=8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "sender_name": "Sender Example",
    "sender_address_text": "123 Sender St, Springfield, IL 62701",
    "recipient_name": "Recipient Example",
    "recipient_address_text": "456 Recipient Ave, Springfield, IL 62701",
    "checkout_session": {
      "id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
      "payment_provider": {
        "provider": "stripe",
        "merchant_id": "profile_123",
        "supported_payment_methods": ["card", "apple_pay", "google_pay"]
      },
      "status": "ready_for_payment",
      "currency": "usd",
      "line_items": [
        {
          "id": "line_item_8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
          "item": { "id": "postalform_mail_pdf", "quantity": 1 },
          "base_amount": 299,
          "discount": 0,
          "subtotal": 299,
          "tax": 0,
          "total": 299
        }
      ],
      "totals": [
        { "type": "items_base_amount", "display_text": "Items", "amount": 299 },
        { "type": "subtotal", "display_text": "Subtotal", "amount": 299 },
        { "type": "tax", "display_text": "Tax", "amount": 0 },
        { "type": "total", "display_text": "Total", "amount": 299 }
      ],
      "links": [
        { "type": "terms_of_use", "value": "https://postalform.com/terms" },
        { "type": "privacy_policy", "value": "https://postalform.com/privacy" }
      ],
      "payment_mode": "test"
    }
  }
}

complete_checkout

Request:

{
  "name": "complete_checkout",
  "arguments": {
    "checkout_session_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "buyer": {
      "first_name": "Jane",
      "last_name": "Doe",
      "email": "[email protected]"
    },
    "payment_data": {
      "token": "spt_test_123",
      "provider": "stripe"
    }
  }
}

Response:

{
  "structuredContent": {
    "id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "buyer": {
      "first_name": "Jane",
      "last_name": "Doe",
      "email": "[email protected]"
    },
    "status": "completed",
    "currency": "usd",
    "line_items": [
      {
        "id": "line_item_8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
        "item": { "id": "postalform_mail_pdf", "quantity": 1 },
        "base_amount": 299,
        "discount": 0,
        "subtotal": 299,
        "tax": 0,
        "total": 299
      }
    ],
    "fulfillment_options": [
      {
        "type": "shipping",
        "id": "postalform_standard",
        "title": "First Class mail",
        "subtitle": "USPS First Class",
        "carrier": "USPS",
        "carrier_info": "USPS",
        "earliest_delivery_time": "2025-01-01T00:00:00.000Z",
        "latest_delivery_time": "2025-01-06T00:00:00.000Z",
        "subtotal": 0,
        "tax": 0,
        "total": 0
      }
    ],
    "fulfillment_option_id": "postalform_standard",
    "totals": [
      { "type": "items_base_amount", "display_text": "Items", "amount": 299 },
      { "type": "subtotal", "display_text": "Subtotal", "amount": 299 },
      { "type": "tax", "display_text": "Tax", "amount": 0 },
      { "type": "total", "display_text": "Total", "amount": 299 }
    ],
    "order": {
      "id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
      "checkout_session_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
      "permalink_url": "https://postalform.com/order/complete?order_id=8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b"
    },
    "messages": [],
    "links": [
      { "type": "terms_of_use", "value": "https://postalform.com/terms" },
      { "type": "privacy_policy", "value": "https://postalform.com/privacy" }
    ]
  }
}

postalform.get_order_status

Response:

{
  "structuredContent": {
    "view": "order_status",
    "found": true,
    "order_id": "8c1a1b58-2c8f-4f4f-9c46-2c1ac32d7a1b",
    "is_paid": true,
    "current_step": "letter_created"
  }
}

Error response example

{
  "isError": true,
  "content": [
    {
      "type": "text",
      "text": "recipient_address_manual is required when recipient_address_type is Manual."
    }
  ]
}

Integration notes

  • Idempotency: use request_id on draft creation tools (postalform.create_order_draft, postalform.create_letter_order_draft, postalform.create_form_order_draft) and reuse the same value on retries. The server will return the existing draft.
  • Errors: tool calls that fail validation return isError=true with a human-readable message. For Instant Checkout, complete_checkout can also return ACP messages with error codes like payment_declined or requires_3ds.
  • Instant Checkout fallback: if window.openai.requestCheckout is unavailable or checkout_session.payment_provider lacks merchant_id, use checkout_url instead.
  • Order status steps: payment_received, receiver_address_verified, sender_address_verified, pdf_normalized, letter_created, email_sent, canceled, refunded, abandoned.

Limits and requirements

  • All orders are printed from PDFs. For postalform.create_order_draft you supply the PDF. For postalform.create_letter_order_draft and postalform.create_form_order_draft PostalForm generates the PDF server-side. PDFs are sanitized before printing.
  • Max 199 pages and 100 MB per file.
  • download_url must be HTTPS and hosted on an allowlisted domain (ChatGPT attachments use oaiusercontent.com by default). The URL must be publicly reachable without auth headers. If you cannot provide an allowlisted URL, use postalform.create_pdf_upload to get an upload_token, or pass a data:application/pdf;base64,... URL instead.
  • US addresses only. Use Loqate address ids returned by postalform.search_addresses, or use manual address input with *_address_type="Manual" and *_address_manual.
  • Address suggestions can include type="Container" (buildings/complexes). Call postalform.search_addresses again with container=<id> and a refined query (suite, unit, PO Box) to get type="Address" results. Only type="Address" (not Container) is valid for Loqate-based order drafts.

Related Servers

NotebookLM Web Importer

Import web pages and YouTube videos to NotebookLM with one click. Trusted by 200,000+ users.

Install Chrome Extension