Local Model Context Protocol (MCP) server that lets Claude Desktop call the AfterShip API via two tools:
track-shipment– track a package by tracking number (with optional courier slug; falls back to detection)list-couriers– list available couriers
- Node.js 18+ (tested on Node 22)
- Claude Desktop (MCP enabled)
- AfterShip API key
npm iThe project uses ESM (
"type": "module") and TypeScript (tsxrunner).
Open Claude Desktop → Settings → Developer → Edit Config, and add an MCP server entry:
{
"mcpServers": {
"aftership-tracking": {
"command": "npx",
"args": ["-y", "tsx", "/absolute/path/to/repo/src/index.ts"],
"env": {
"AFTERSHIP_API_KEY": "YOUR_AFTERSHIP_API_KEY"
}
}
}
}🔐 Do not commit secrets. If this repo is shared, prefer:
"AFTERSHIP_API_KEY": "$AFTERSHIP_API_KEY"and set the variable in your shell / system keychain.
Now quit & reopen Claude Desktop (Cmd–Q on macOS) so it picks up the config.
In any chat (below or use natural language):
-
List couriers
Run the
list-courierstool (aftership-tracking). -
Track a shipment (known courier)
Run "track-shipment" (aftership-tracking) with: { "trackingNumber": "1Z999AA10123456784", "slug": "ups" } -
Track a shipment (detect courier automatically)
Run "track-shipment" (aftership-tracking) with: { "trackingNumber": "1Z999AA10123456784" }
Claude will invoke the MCP tool and show results.
src/
index.ts # MCP stdio server (tools/list & tools/call)
tools/
list-couriers.ts # Tool wrapper (calls utils.listCouriers)
track-shipment.ts # Tool: detect → fetch → create → return
utils/
aftership-api.ts # axios client + helpers (detect/get/create/list)
package.json
tsconfig.json
-
MCP transport: stdio (stdin/stdout). The server uses
@modelcontextprotocol/sdk1.18.x.- Tools are advertised via
ListToolsRequestSchema, invoked viaCallToolRequestSchema(imported from@modelcontextprotocol/sdk/types.js). - Server capabilities include
{ tools: { list: true, call: true } }.
- Tools are advertised via
-
Do not log to stdout.
stdoutmust carry only JSON-RPC.- Use
console.error(...)(stderr) for diagnostics; otherwise Claude will show “not valid JSON” errors.
-
Schemas (JSON Schema):
list-couriers:{ "type": "object", "properties": {}, "additionalProperties": false }track-shipment:{ "type": "object", "properties": { "trackingNumber": { "type": "string" }, "slug": { "type": "string", "nullable": true } }, "required": ["trackingNumber"], "additionalProperties": false }
-
Tracking logic (tool
track-shipment):- If
slugis not provided → call/couriers/detect. - Try
GET /trackings/{slug}/{tracking_number}for top candidates. - If none exists,
POST /trackingsto create one. - Return a clear status (
ok,created,no_courier_detected,error) and surface helpful messages from AfterShip.
- If
-
TypeScript / ESM:
- Use explicit
.tsextensions in relative imports (ESM rule). tsconfig.jsonsets"module": "ESNext","moduleResolution": "NodeNext".
- Use explicit
You can run the server without Claude (it will wait on stdio):
export AFTERSHIP_API_KEY=YOUR_AFTERSHIP_API_KEY # local only
npx tsx src/index.tsClaude Desktop logs (macOS): ~/Library/Logs/Claude/
mcp-server-aftership-tracking.log– your server’s stderr/stdout (stderr recommended)mcp.log– client/handshake logs
-
No wrench / tools not visible
- Restart Claude Desktop after editing config.
- Confirm
tools/listreturns both tools withinputSchema. - Capabilities must include
{ tools: { list: true, call: true } }.
-
“not valid JSON” banner
- You printed to stdout. Switch to
console.error(stderr).
- You printed to stdout. Switch to
-
400s from AfterShip
- Often caused by missing/invalid slug or an unrecognized number.
- Use detection; errors are bubbled back so Claude shows the meaningful message.
-
Missing API key
- Ensure
AFTERSHIP_API_KEYis present in the MCP server env (Claude config) or exported in your shell for local dev.
- Ensure
- Keep your API key out of source control.
- Use
.gitignoreto excludenode_modules/, logs,.env, and your localclaude_desktop_config.json. - Provide a
claude_desktop_config.example.jsonwithout secrets.
MIT (or update to your preference).
