MCP servers

Integrations MCP server

The Integrations MCP server's endpoint, key, tools, prompt, and resource.

Endpoint

POSThttps://www.postaged.com/api/integrations/mcp

Authenticate with an integration key (it starts with pk_int_) in the Authorization: Bearer header. Create a dedicated key with New key on your integration's page and use it only for Claude, so you can revoke it on its own.

The server takes JSON-RPC 2.0 messages over HTTP POST (MCP protocol version 2025-06-18) and introduces itself as postaged-integration. It answers every notification with 202 and an empty body. See Connect Claude to add it to your Claude client.

Claude Code

Terminal
claude mcp add --transport http postaged https://www.postaged.com/api/integrations/mcp --header "Authorization: Bearer ${POSTAGED_API_KEY}"

Tools

The server has 10 tools. Each returns one text item: JSON for every tool except get_docs, which returns the build guide as Markdown.

inspect_integration

Read this integration's name, status, whether setup is finished, any open sync, and the API limits. Call this first.

No arguments.

Call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "inspect_integration",
    "arguments": {}
  }
}
Result text
{
  "id": "int_ABCD1234",
  "name": "Dandy CRM",
  "status": "setup_needed",
  "setupComplete": false,
  "openSync": null,
  "hint": "No sync is open. Call begin_sync to start one.",
  "limits": {
    "recordsPerBatch": 1000,
    "bytesPerBatch": 4456448,
    "declaredFields": 1000
  }
}

get_schema

List the fields currently declared for this integration.

No arguments.

Call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_schema",
    "arguments": {}
  }
}
Result text
{
  "fields": [
    {
      "key": "email",
      "label": "Email",
      "type": "text",
      "semantic": "email"
    },
    {
      "key": "deal_value",
      "label": "Deal value",
      "type": "number",
      "semantic": null
    }
  ]
}

declare_schema

Declare the fields you will push. A proposal: fields the customer has already typed in Postaged keep their type and this fails naming them. Ask the customer before calling.

  • fieldsarray of objectsRequired
    Each: key, label, type (text|number|date|yes_no|choice), optional semantic.
Call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "declare_schema",
    "arguments": {
      "fields": [
        {
          "key": "email",
          "label": "Email",
          "type": "text",
          "semantic": "email"
        },
        {
          "key": "deal_value",
          "label": "Deal value",
          "type": "number"
        }
      ]
    }
  }
}
Result text
{
  "ok": true,
  "added": 1,
  "noLongerSent": []
}

validate_records

Check records against the contract and write NOTHING. Use this while building; it needs no open sync.

  • recordsarray of objectsRequired
Call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "validate_records",
    "arguments": {
      "records": [
        {
          "external_id": "crm-1042",
          "fields": { "email": "dana@example.com", "deal_value": "1500" }
        },
        { "external_id": "crm-0977", "op": "delete" }
      ]
    }
  }
}
Result text
{
  "accepted": 2,
  "rejected": 0,
  "rejections": [],
  "warnings": []
}

begin_sync

Open a sync. 'full' marks everything it does not see as removed at completion; 'delta' removes only what you delete explicitly. One open sync at a time.

  • modestringOptional
    One of full or delta.
Call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "begin_sync",
    "arguments": { "mode": "full" }
  }
}
Result text
{
  "id": "0b6f3c3e-8d2a-4f7e-9a51-2c4d8e6f1a20",
  "mode": "full",
  "expiresAt": "2026-10-04T14:05:00.000Z",
  "batchesReceived": 0,
  "recordsUpserted": 0,
  "recordsDeleted": 0,
  "recordsRejected": 0,
  "status": "open"
}

push_records

Push a batch into the open sync. Returns what was accepted, one rejection per bad row, and the values dropped from accepted rows grouped by field, each with a count and up to 5 example record ids; one bad record never stops the others.

  • recordsarray of objectsRequired
Call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "push_records",
    "arguments": {
      "records": [
        {
          "external_id": "crm-1042",
          "fields": { "email": "dana@example.com", "deal_value": "1500" }
        },
        { "external_id": "crm-0977", "op": "delete" }
      ]
    }
  }
}
Result text
{
  "accepted": 1,
  "deleted": 1,
  "rejections": [],
  "warnings": []
}

complete_sync

Close the open sync, applying a full sync's removal sweep.

No arguments.

Call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "complete_sync",
    "arguments": {}
  }
}
Result text
{
  "ok": true,
  "markedRemoved": 14,
  "nextStep": "Tell the owner to finish setup in Postaged to choose which fields to keep."
}

sync_status

Report the open sync's progress and counts, if one is open.

No arguments.

Call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "sync_status",
    "arguments": {}
  }
}
Result text
{
  "openSync": {
    "id": "0b6f3c3e-8d2a-4f7e-9a51-2c4d8e6f1a20",
    "mode": "full",
    "expiresAt": "2026-10-04T14:05:00.000Z",
    "batchesReceived": 2,
    "recordsUpserted": 1998,
    "recordsDeleted": 0,
    "recordsRejected": 2
  }
}

get_rejections

List the records a sync rejected (or, with type 'warning', the values it dropped from accepted records), one page at a time. Defaults to the latest sync. Pass nextAfter back as after for the next page.

  • syncIdstringOptional
    The sync to read. Omit for the latest sync.
  • typestringOptional
    One of rejection or warning.
  • afterstringOptional
    The nextAfter value of the previous page.
  • limitnumberOptional
    Items per page, 1 to 1000. Defaults to 100.
Call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_rejections",
    "arguments": {
      "syncId": "0b6f3c3e-8d2a-4f7e-9a51-2c4d8e6f1a20",
      "type": "rejection",
      "limit": 100
    }
  }
}
Result text
{
  "syncId": "0b6f3c3e-8d2a-4f7e-9a51-2c4d8e6f1a20",
  "type": "rejection",
  "items": [
    {
      "id": 7,
      "batch": 1,
      "index": 3,
      "externalId": "crm-1077",
      "code": "invalid_email",
      "field": "email",
      "message": "\"email\" does not look like an email address."
    }
  ],
  "nextAfter": null
}

get_docs

The full integration guide: endpoints, caps, error codes, and scheduling.

No arguments.

Call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "get_docs",
    "arguments": {}
  }
}
Result text
# Build a daily Postaged sync for Dandy CRM

You are setting up a one-way daily sync from this project's CRM into Postaged,
which prints and mails physical letters to the contacts you send.

## What you are building

(The guide continues with the schema, sync, error, and scheduling steps.)

Prompt

build_daily_sync: The full instructions for building a daily Postaged sync from this CRM.

prompts/get returns the same guide get_docs does. Build a daily CRM sync is adapted from it for a person to read.

Call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "prompts/get",
  "params": { "name": "build_daily_sync" }
}

Resource

postaged://contract (Push API contract, text/markdown): Endpoints, caps, and error codes.

resources/read returns the same guide as Markdown.

Call
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "resources/read",
  "params": { "uri": "postaged://contract" }
}

Errors

JSON-RPC errors come back with HTTP 200. Authentication, availability, and rate-limit failures use the push API's error body with HTTP 401, 403, or 429. A tool that cannot do what it was asked returns a result whose text carries error and message. See Errors for an example of each, and the Error reference for every code.

Limits

  • 4.25 MB per request body, and 1,000 records per push_records call.
  • About 120 requests per minute per source IP, counted together with the push API. Over the limit: 429 rate_limited with Retry-After: 60.