mcpbeat

Ops Package

davepoon/ops-package

Ship parcels via any configured carrier — MyParcel, Sendcloud, DHL Parcel NL, PostNL, DPD, UPS, FedEx. Auto-selects the first carrier whose credentials are configured, or pass --carrier <name> to override. Verbs: ship, label, track, list, carriers.

16k tokens
context cost
the whole folder, loaded on every use
10
files
ships runnable scripts
0
copies elsewhere
how many repositories repackaged it
3248
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/davepoon/buildwithclaude --skill ops-package

What comes with it

56 576 bytes besides the instruction
lib/carriers/dhl.sh
lib/carriers/dpd.sh
lib/carriers/fedex.sh
lib/carriers/myparcel.sh
lib/carriers/postnl.sh
lib/carriers/sendcloud.sh
lib/carriers/ups.sh
lib/common.sh
ops-package.sh

The instruction itself

12 sections, as written by the author

OPS ► PACKAGE — multi-carrier shipping

One skill, seven carriers. The router picks the carrier automatically based on which credentials are configured.

Which carrier do I get?

| Condition | Selected carrier |

| -------------------------------------------------------------------- | ------------------------- |

| --carrier <name> flag passed | That carrier (validated) |

| Exactly one carrier has credentials | That carrier |

| Multiple carriers have credentials | First match in this order |

| No carrier has credentials | Exit 2 with setup help |

Preference order: myparcelsendclouddhlpostnldpdupsfedex.

Credential resolution

Each carrier resolves its credential(s) from, in order:

  • Environment variable(s) listed below.
  • preferences.json key (lowercase of the env var name) at ${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json.
  • Doppler: doppler secrets get <NAME> --plain.

| Carrier | Env vars required | Docs |

| ---------- | ---------------------------------------------------------------------- | --------------------------------------------------- |

| MyParcel | MYPARCEL_API_KEY | https://developer.myparcel.nl/api-reference/ |

| Sendcloud | SENDCLOUD_PUBLIC_KEY + SENDCLOUD_PRIVATE_KEY | https://api.sendcloud.dev/docs/ |

| DHL NL | DHL_PARCEL_USER_ID + DHL_PARCEL_KEY | https://api-gw.dhlparcel.nl/docs |

| PostNL | POSTNL_API_KEY + POSTNL_CUSTOMER_CODE + POSTNL_CUSTOMER_NUMBER | https://developer.postnl.nl/ |

| DPD | DPD_DELIS_ID + DPD_PASSWORD | https://esolutions.dpd.com/ |

| UPS | UPS_CLIENT_ID + UPS_CLIENT_SECRET + UPS_SHIPPER_NUMBER | https://developer.ups.com/api/reference/shipping |

| FedEx | FEDEX_CLIENT_ID + FEDEX_CLIENT_SECRET + FEDEX_ACCOUNT_NUMBER | https://developer.fedex.com/api/en-us/catalog/ |

Routing

All API calls are delegated to ${CLAUDE_PLUGIN_ROOT}/skills/ops-package/ops-package.sh. Do not re-implement curl logic inline — pass args through.

| First token | Action |

| ----------- | ------------------------------------------------------------------ |

| carriers | Show configured vs unconfigured carriers |

| ship | Create a shipment |

| label | Download + open label PDF |

| track | Show status + tracking barcode |

| list | List last 10 shipments (MyParcel / Sendcloud; others unsupported) |

| (empty) | Show usage |

ship

Required: --to "<address>". Address format:

"Person / Company, Street 12A, 1011AB Amsterdam, NL"
  • / Company segment is optional.
  • Postcode accepts with/without space; NL postcodes are normalised to uppercase without space.
  • Country defaults to NL; common names (Netherlands, Belgium, Germany, France, UK, USA) map to ISO codes.

Flags (not every carrier honours every flag — unsupported flags are safely ignored per adapter):

  • --from "<address>" override sender; default is the account's configured sender.
  • --weight <grams> integer grams.
  • --package-type 1|2|3 1=parcel (default), 2=mailbox, 3=letter (MyParcel only).
  • --signature require signature on delivery.
  • --insurance <EUR> integer EUR; 0 disables (MyParcel only).
  • --description "<text>" label reference (carriers differ; ~45 char max).
  • --pickup request home pickup at sender (MyParcel only).

Invocation

${CLAUDE_PLUGIN_ROOT}/skills/ops-package/ops-package.sh \
  [--carrier myparcel|sendcloud|dhl|postnl|dpd|ups|fedex] \
  ship --to "$TO" [--from "$FROM"] [--weight "$W"] \
       [--signature] [--insurance "$INS"] [--description "$DESC"] \
       [--package-type "$TYPE"] [--pickup]

Returns:

{"carrier": "<name>", "shipment_id": "<id>", "response": { /* raw carrier JSON */ }}

Summarise to the user as:

Shipment created — <carrier> #<id>
Next: /ops:ops-package label <id>  to download the PDF.

Offer via AskUserQuestion (≤4 options):

  • [Download label now] — call label <id> immediately
  • [Track it] — call track <id>
  • [Ship another]
  • [Done]

label <shipment-id>

${CLAUDE_PLUGIN_ROOT}/skills/ops-package/ops-package.sh label "$ID"

Behaviour per carrier:

  • MyParcel: PDF saved to /tmp/myparcel_label_<id>.pdf and opened on macOS. If unpaid, returns {"status":"payment_required","payment_url":"..."} and opens the MultiSafepay URL.
  • Sendcloud: Fetches the CDN PDF URL (v2 /labels/<id>) and saves locally.
  • DHL NL: Fetches /labels/<id>?format=PDF&printerType=A4.
  • DPD: Fetches /v1/parcellabelnumber/<id>?paperFormat=A4.
  • PostNL / UPS / FedEx: PDF is returned inline with the ship call and cached to /tmp/<carrier>_label_<id>.pdf. Calling label re-opens that cached file. If the cache is gone, re-run ship (none of these carriers expose a stable label-recovery endpoint here).

track <shipment-id>

Returns a normalised object across all carriers:

{"carrier":"<n>","id":"<id>","status":"<s>","barcode":"<b>","tracking_url":"<u>","recipient":<o>,"updated":"<ts>"}

list

MyParcel returns the last 10 shipments. Sendcloud returns the last 10 parcels. All other carriers return {"shipments":[],"note":"..."} because their APIs don't expose a customer-facing list endpoint — track by id instead.

carriers

Prints which carriers are configured () vs which are missing credentials (·). Use this when deciding which --carrier to pass.

Error handling

  • No carrier configured → script exits 2 with a checklist of envs. Surface it verbatim, then via AskUserQuestion:

[Paste key now] [Open carriers doc] [Skip — configure later]

On "Paste key now", collect via AskUserQuestion free-text and write to preferences.json.

  • 4xx from carrier → dump the JSON error body and stop. Do not retry silently.
  • Address parser looks wrong → re-prompt the user with the parsed breakdown and ask for corrections before POSTing.

Verification status

| Carrier | Status |

| ---------- | ------------------------------------------------------------------ |

| MyParcel | Verified against api.myparcel.nl v1.1 (same working reference) |

| Sendcloud | Verified request/response shapes against Sendcloud Panel API v3 |

| DHL NL | UNVERIFIED — modelled on My DHL Parcel Swagger, needs live account |

| PostNL | UNVERIFIED — modelled on Send API v2.2 docs, needs live account |

| DPD | UNVERIFIED — modelled on DPD eSolutions REST, needs live account |

| UPS | UNVERIFIED — modelled on UPS v2403 Ship/Track REST, needs account |

| FedEx | UNVERIFIED — modelled on FedEx Ship v1 + Track v1 REST |

Unverified adapters are tagged with # UNVERIFIED - pending live test with account in the source. Payloads match the documented contract; adjustments may be needed when tested against live accounts.

How to use it

Copy the folder

Take davepoon/ops-package from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.