parcel-mcp
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
parcel-mcp
A small MCP server that exposes your Parcel app's deliveries to any MCP client — ChatGPT, Claude, other agents — deployed as a Cloudflare Worker. The Worker is its own OAuth 2.1 authorization server. Cloudflare Access is used only to prove that the owner is the person approving a grant.
It lets an authenticated client ask "what packages are on their way?" and add a tracking number Parcel didn't pick up on its own. It never exposes your Parcel API key and never logs delivery contents.
An agent that calls the server regularly does not need a scheduled sign-in. Access tokens last one hour. Each refresh rotates the refresh token and moves its expiry to 90 days after that use. The grant ends only after 90 days without a successful refresh.
Tools
Tool | Arguments | What it does |
| none | Reports the identity that approved the grant, and whether the server is configured. Reveals no deliveries and no credentials. |
| none | Returns the account's active and recently completed deliveries — carrier, description, tracking number, expected dates, status codes and labels, and up to 30 events each. Parcel serves cached data; this does not poll carriers. |
|
| Adds one delivery to the account. Parcel allows 20 add requests per day including failed ones, so clients should check |
The first two are annotated read-only. add_delivery is the only write path, and it can only create — nothing can modify or delete a delivery. A grant carries active_deliveries_read and add_delivery. Clients that omit scope receive both. The legacy identifier active_deliveries_read now authorizes the recent view; it is retained so existing read grants continue to work. Consent describes that access explicitly.
list_deliveries always requests Parcel's recent view and accepts no filter argument.
This is not a full delivery archive: Parcel does not document the recent retention window,
and responses are capped at 200 deliveries with a truncated flag. A completed delivery
has status_code: 0 and status_label: "completed"; unknown statuses are labeled
unknown. Disappearance from the list is not proof of delivery. Use the returned status
and events rather than inferring completion from absence.
Version 2 replaces list_active_deliveries with list_deliveries; clients must refresh
tool discovery and update instructions that name the old tool. Existing grants remain
valid; no reauthorization is required solely for this change.
Related MCP server: ZVA Demo MCP Server
How it works
MCP client ──HTTPS──> Worker
├─ /.well-known/oauth-* discovery
├─ /register dynamic client registration
├─ /oauth/token code exchange, refresh, revocation
├─ /authorize owner approval (Cloudflare Access, then a consent page)
└─ /mcp Parcel tools, bearer access token only
└──GET/POST──> api.parcel.appThe Worker is the OAuth 2.1 authorization server (MCP authorization): authorization-code + PKCE (
S256), refresh tokens, and dynamic client registration. Grants, clients, and tokens are stored in a KV namespace bound asOAUTH_KV. The library is@cloudflare/workers-oauth-provider./authorizeis the only path behind Cloudflare Access. The Access login proves who the approver is. The Worker checks theCf-Access-Jwt-AssertionJWT (team JWKS, issuer, audience) and then shows a consent page naming the client and the host that will receive tokens. Allow issues the grant./mcpreads only the bearer access token this Worker issued./mcpaccepts only an access token this Worker issued. A missing, invalid, or expired token gets401with aWWW-Authenticatechallenge pointing at the protected-resource metadata. An old Access session or service token is not a credential for/mcp.The Parcel API key stays a Worker secret. The Worker calls Parcel with it and returns a filtered result. The key is not a client credential.
Token lifetimes live in src/oauth.ts: ACCESS_TOKEN_TTL_SECONDS (1 hour) and REFRESH_GRANT_TTL_SECONDS (90 days). The same 90-day value is the new grant's lifetime and the idle lifetime. Every successful refresh rotates the refresh token and starts the 90 days over. The immediately previous refresh token stays valid until the replacement is used, so a client that lost the refresh response can retry with it. Once the replacement is used, the older token is rejected.
Security model
Access is the owner's login. The consent page is the grant. Anyone can register a client at
/register. Nobody receives a token unless the owner passes Access on/authorizeand chooses Allow. The page shows the client's name and the redirect host. Names from dynamic registration are not verified. There is no user allow-list and no admin UI in the Worker; the Access policy on/authorizedecides who may see that page.A link is not enough. Access would otherwise send the browser back to a crafted
/authorizeURL after login and an automatic grant would mint a 90-day refresh token. The consent handle is single-use and bound to the browser that opened the page.Never serve this on
*.workers.dev. The issuer and the resource URL are the request origin.workers_devis off; serve one hostname only. A second route would be a second issuer.The Parcel API key is a Worker secret. It never appears in source, logs, responses, or client configuration. The server redacts the key from any upstream text before returning it, and classifies upstream errors without echoing their bodies.
Fail closed. If the Access variables are missing,
/authorizerefuses every grant. If the access token has no identity, tool calls are refused. If the key or the rate limiter is unavailable, data calls return an error rather than leaking or proceeding.Refresh grants have no absolute end date. A stolen refresh token can keep working while it is used at least every 90 days. Use the targeted revocation and containment procedures below; removing an Access session does not revoke Worker-issued grants.
KV is eventually consistent. A stale replica can temporarily accept a rotated or deleted token; revocation is not globally instantaneous.
Client ID Metadata Documents are enabled, so a client may use an HTTPS URL as its
client_id. The Worker fetches that document. Theglobal_fetch_strictly_publiccompatibility flag keeps that fetch on the public internet. Dynamic registration remains available for clients that do not use a metadata document.
Rate limiting
The read throttle uses a KV key to target one upstream attempt every three minutes. A visible reservation returns retry_at without calling Parcel. KV is eventually consistent and its read/write pair is not atomic: concurrent calls can admit multiple attempts. This is a best-effort throttle, not a strict quota. Parcel's own 429 response remains authoritative. Deployments sharing a Parcel API key should share the RATE_LIMIT namespace; OAUTH_KV is separate.
A native Cloudflare rate limiter also protects public requests before OAuth dispatch, targeting 60 requests per minute per client IP in each Cloudflare location. Shared egress IPs share this allowance. Cloudflare's counters are approximate; this is abuse mitigation, not a global quota. Denial returns 429 with Retry-After; an unavailable binding returns 503. Token and consent POST bodies are limited to 64 KiB; registration retains the provider's 1 MiB limit and MCP requests retain their 8 KiB limit.
Privacy
Delivery data — tracking numbers, descriptions, events — is personal data. The Worker does not log it, and you should not add logging that does. OAuth error logs record only the HTTP status and the OAuth error code. Query strings are redacted in Worker observability; traces are sampled at 10%. The approving user's email is stored in the encrypted token props, not in the grant's plaintext metadata.
Deploy your own
Prerequisites: a Cloudflare account, a domain on Cloudflare, a Parcel API key (Parcel's API docs), and Node.js 22 or newer.
npm ci
npm test # offline tests; no network or credentials
npm run typecheckCreate separate
RATE_LIMITandOAUTH_KVnamespaces in Cloudflare. The first holds read reservations; the second holds clients, grants, and tokens.Configure the GitHub
default-branchenvironment, restricted tomain, with secretCLOUDFLARE_API_TOKEN(Workers Scripts: Edit for your account) and variablesCLOUDFLARE_ACCOUNT_ID,RATE_LIMIT_KV_NAMESPACE_ID,OAUTH_KV_NAMESPACE_ID, andPUBLIC_RATE_LIMIT_NAMESPACE_ID. The workflow fills namespace IDs by binding name; do not commit deployment IDs. Choose an unused positive integer forPUBLIC_RATE_LIMIT_NAMESPACE_ID; bindings with the same ID share counters. The public config uses1only as a local-development placeholder.Deploy through the GitHub workflow, then set the Worker secret
PARCEL_API_KEYin the Cloudflare dashboard. It is never a client credential. Attach exactly one custom hostname, such asparcel.example.com; keepworkers_devdisabled.Create a self-hosted Cloudflare Access application for exactly that hostname's
authorizepath, with an Allow policy restricted to the owner. Do not useauthorize/*, a whole-host application, or a Service Auth policy. Leave Managed OAuth disabled: the Worker provides OAuth discovery and tokens. See Access paths and Managed OAuth.Set the Worker's plaintext variables
ACCESS_TEAM_DOMAIN(for exampleyour-team.cloudflareaccess.com, withouthttps://) andACCESS_AUD(the Access application's AUD tag).keep_varspreserves them across deploys. Missing values prevent new grants.Verify discovery returns JSON with your hostname as
issuer, unauthenticated/mcpreturns 401 with protected-resource metadata in its challenge, and/authorizerequires the owner's Access login. Then connect only the clients you intend to authorize.
Delivery and releases
.github/workflows/deploy.yml runs on every push to main. A credential-free job installs the lockfile and runs the existing Vitest suite and TypeScript check. Both deployment and source release depend on its success. These are deployment gates; the repository's shared PR check remains its required merge check.
Deployment uses the configured GitHub environment and Cloudflare's SHA-pinned Wrangler action. The serialized workflow also runs the local tag-package-release.sh, creating an immutable annotated parcel-mcp--v<version> tag and a GitHub source release. It does not publish an npm package. Source release and Worker deployment have separate job results: verify both before declaring a version shipped.
For a new release, update package.json, the root package entry and version in package-lock.json, the MCP serverInfo.version in src/mcp.ts, and CHANGELOG.md. Existing tags are never moved. Dependency or documentation changes that leave the version unchanged do not create another release. Forks must supply their own deployment settings; estate-specific shared PR governance is separate from this Worker delivery workflow.
Operations and recovery
After deployment, confirm the workflow's verification and deployment jobs succeeded, inspect the active Worker version in Cloudflare, and call
get_connection_statususing an already authorized client. This does not spend Parcel's read allowance. Check source release separately. No delivery write is needed for a routine smoke check.A client can revoke its own grant using an RFC 7009 form POST to
/oauth/tokenwith itsclient_id,token(refresh token), andtoken_type_hint=refresh_token, plus its registered client authentication method if applicable. Use the client's secure credential storage; never put tokens into command history or logs. Revoking the refresh token removes that grant and its access tokens. There is no operator revocation UI.If a credential is compromised and the client cannot revoke it, contain access at Cloudflare first. Replacing
OAUTH_KVwith an empty namespace and redeploying invalidates all clients and grants, requires fresh registration and consent, and must be a deliberate account-wide recovery decision. Do not clear OAuth storage for an ordinary sign-in error. Access policy changes alone do not invalidate existing Worker tokens.For a defective code release, revert the offending change through a PR to redeploy the corrected source. For urgent recovery, an authorized operator can roll back the Worker deployment in Cloudflare, then reconcile
mainbefore the next deploy. Worker rollback does not restore KV data or revoke grants. See rollback limitations.Rotate a compromised Parcel API key in Parcel and update the Worker secret through the dashboard. Do not expose its value in a ticket or client configuration.
If
add_deliverytimes out or reports an uncertain upstream failure, inspect Parcel before retrying. The upstream operation may have succeeded; blindly retrying can duplicate a delivery or spend its daily allowance.
Connect a client
Point the client at https://<your-hostname>/mcp (Streamable HTTP).
The client reads
/.well-known/oauth-protected-resource/mcp, then the authorization-server metadata on the same origin.It registers at
/register, or presents a Client ID Metadata Document.It opens the browser at
/authorizewith PKCE. You sign in through Access, read the client name and the redirect host, and choose Allow.The client stores the refresh token. Later calls refresh on their own. You sign in again only to approve a new client, or after 90 days in which that client never successfully refreshed.
Redirect URLs must be https, loopback http (localhost, 127.0.0.1, ::1), or a private-use scheme such as cursor:// for a native app. The consent page shows where the code will be sent. Remote http is rejected.
get_connection_status then reports authenticated_user_id and authenticated_user_email from the Access login that approved the grant.
Project layout
Path | Role |
| Transport-agnostic MCP core: protocol handling, the tools, response filtering, key redaction, safe error classification. The caller passes the approved identity, fetch, clock, and rate reservation. |
| OAuth provider wiring, token lifetimes, |
| Verifies the Cloudflare Access JWT on |
| The KV-backed Parcel read throttle. |
| Worker entry point. Wires Access, KV, and the Parcel secret. |
| Mock-only tests. No real Parcel key, Access tenant, or network. |
| Worker configuration. Generic: deployment settings supply the KV and public rate-limit namespace IDs. |
License
MIT — see LICENSE.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
Track parcels + ocean containers across 549 total carriers via REST and four existing MCP tools.
MCP server for EasyPost — rate shipments, buy & refund labels, track packages, verify addresses.
Configure parcelLab journeys, tracking pages and returns portals, and query delivery analytics.
A paid remote MCP for ShipSwift, built to return verdicts, receipts, usage logs, and audit-ready JSO
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA Cloudflare Worker that provides API access to Postman collections and environments via the Claude AI MCP interface, enabling Claude to assist with API testing, documentation, and management tasks.43 npm3ISC
- FlicenseNot gradedqualityDmaintenanceA Cloudflare Worker that provides MCP tools for querying order details, customer accounts, and product inventory from a D1 database, and also exposes a REST API for integration with Zoom Virtual Agent flows.-
- AlicenseNot gradedqualityDmaintenanceEnables secure access to Vikunja projects and tasks via MCP tools, protected by Cloudflare Access and designed for remote clients like ChatGPT.2MIT
- AlicenseNot gradedqualityCmaintenanceEnables OAuth-authenticated MCP clients to list registered SwitchBot devices, read their status, and switch plugs on or off via a Cloudflare Workers deployment. Access is limited to an allowlisted set of devices, and turning off a PC plug requires the caller to confirm a clean shutdown first.MIT