Case study · 02 / 04
BambooHR MCP Server
A custom Model Context Protocol server exposing BambooHR HR systems to Claude Desktop.
- Role
- Sole engineer
- Timeline
- 2025
- Status
- Shipped
The problem
HR data lives in BambooHR — but the questions people actually ask (“who joined this quarter”, “whose probation review is due”, “draft the offboarding email for X”) never live there. Pulling answers means switching tabs, copying values, and rephrasing them by hand.
Claude Desktop can answer those questions if it can read BambooHR — but only through a Model Context Protocol server that speaks both sides correctly: BambooHR’s API on one end, MCP’s tool/resource contract on the other.
My approach
A small, strictly-typed Node.js MCP server. Each BambooHR endpoint becomes a typed MCP tool with a Zod-validated input schema, a narrow output shape, and a usage description Claude can read at runtime. Sensitive operations are read-only by default; mutating tools are opt-in via env flag.
Authentication is handled out-of-band via BambooHR’s API key model so the server never sees user credentials. Rate limiting and retry-with-backoff are baked into the HTTP layer. Errors are translated into MCP error shapes with actionable messages so Claude can recover or ask the user for clarification rather than silently fail.
Delivered
What I shipped.
- 01
Strictly-typed MCP tool catalog
Each BambooHR endpoint exposed as a discoverable MCP tool with Zod input schemas and short, copy-edited descriptions Claude reads at session start.
- 02
Read-only by default
Mutating endpoints are gated behind an explicit flag — safe to install in personal Claude Desktop without risking accidental writes.
- 03
Resilient HTTP layer
Retry-with-backoff, request rate limiting, structured error translation into MCP error shapes.
- 04
Local-first install
npx-runnable so a non-engineer can drop one line into their Claude Desktop config and be productive in minutes.
Decisions
Worth calling out.
01Why a custom MCP over a generic connector
Generic API connectors leak schema noise into the model context — every field of every response, none labeled. A purpose-built MCP server lets you pick exactly the right slice for the questions people actually ask, name them in human language, and discard the rest. The signal-to-noise jump is the whole point.
02Errors as a feature, not a failure mode
When BambooHR returns 4xx/5xx, the server translates them into MCP error responses with “what to try next” hints. Claude then recovers in conversation instead of silently dropping the task — a small change with a big impact on perceived reliability.
Outcome
Runnable in any Claude Desktop install with a single config line. Useful as a working example of what a senior, opinionated MCP server looks like — strict types, narrow tool surface, no creepy mutation paths.
Stack
- TypeScript
- Node.js 20
- @modelcontextprotocol/sdk
- Zod
- BambooHR REST API
Working on something similar?
Free 15-minute discovery call. We’ll work out whether I’m the right person for it, and if not, I’ll point you somewhere better.
Next case study
Sakan Holiday
B2C short-term rental marketplace built and scaled over 3.5 years. Multi-country, multi-currency, multi-payment-gateway.
Read the case study