Available for contract work

I build MCP servers that make internal systems safely agent-callable.

Model Context Protocol servers, Claude API integrations, and the unglamorous production plumbing around them — auth, rate limiting, provider failover, and webhook fulfillment that holds up under real traffic.

What I build

MCP servers

Custom tools with real input schemas, so Claude and other agents can call your systems directly instead of guessing at them.

LLM integration

Claude API work with the parts people skip: provider failover, spend caps, prompt-injection-resistant input handling.

API hardening

Rate limiting, origin enforcement, webhook signature verification, and kill switches for endpoints that burn money when abused.

Selected work

Production MCP server — 42 tools

2,269 LOC 42 tools 21 general-purpose

A single well-scoped MCP server rather than a scatter of thin ones. Of 42 registered tools, 21 are general-purpose utilities an agent would reach for against any codebase — diff_text, cron_explain, unit_convert, json_to_ts, csv_to_markdown, wcag_contrast, regex_test, jwt_decode, hash_text, word_freq. The rest are host and catalog operations specific to the system it serves.

The design principle was that every tool had to be one an agent would reach for unprompted. An earlier iteration shipped servers whose only tool was a stub; those were cut. What survives are tools with real input schemas and real work behind them.

{
  "name": "cron_explain",
  "description": "Explain a cron expression in plain English",
  "inputSchema": {
    "type": "object",
    "properties": { "expression": { "type": "string" } },
    "required": ["expression"]
  }
}
Node.js · MCP SDK · Vercel serverless

Public AI endpoint hardening

326 LOC guard layer 6 controls

A public button that calls a paid LLM API is an open invitation to burn someone else's API budget. This layer exists to make that expensive and boring for an attacker.

ControlBehavior
Kill switchSingle env var returns 503 globally; free features keep working
Origin requiredRequests without a first-party Origin get 403 — no bare curl
CORS allowlistExplicit host list, never wildcard
Secret rejectionInput matching key patterns (sk-, ghp_, private keys) refused
Tiered limitsPer-IP sliding windows: per-minute, per-hour, per-day
Body capsHard payload ceiling

Serverless rate limits are best-effort and are not a global wallet, so provider-side hard spend caps back all of it. That distinction is the difference between a control that feels safe and one that is.

Node.js · Vercel · sliding-window rate limiting

Multi-provider LLM failover

5 providers Cost-ordered

A generation endpoint that degrades instead of failing. Providers are tried in cost order — smallest capable model first, escalating only on failure — across Moonshot, NVIDIA, xAI, OpenAI, and Groq, with per-task routing so cheap tasks never touch expensive models.

Most of the engineering is in the unhappy paths: distinguishing a rate-limit from an outage from a malformed response, and making sure a single wedged provider cannot stall the request.

Node.js · Moonshot/Kimi · NVIDIA NIM · xAI · OpenAI · Groq

Stripe checkout & entitlement system

Webhook fulfillment Durable ledger

End-to-end purchase flow: dynamic per-product Checkout Sessions, signature-verified webhooks, and entitlement verification that treats Stripe as source of truth rather than trusting the client.

Entitlements persist to a durable ledger with a reconciliation script that re-syncs from the Stripe API, so a missed webhook degrades into a delay rather than a customer who paid and got nothing.

Stripe API · webhooks · SQLite · HMAC sessions

JSON → TypeScript interface generator

Paste JSON, get typed interfaces. Handles nested objects, arrays of mixed shape, and optional-field inference across sample records. The conversion runs entirely in the browser — the pasted JSON never leaves the page, which matters when what someone is pasting is a production API response.

TypeScript · client-side only

Home Assistant LLM automation

107KB generator Live deployment

A generated multi-panel dashboard for a live Home Assistant install, plus a conversational layer that routes natural-language household queries to an LLM with fallback handling when providers are unreachable.

Included the genuinely fiddly parts: entity-state reconciliation, template sensor generation, and a probe harness for verifying fallback behavior without waiting for a real outage.

Python · Home Assistant API · Jinja2 templates

How I work

I scope a first milestone small enough that you can judge the work before committing to the rest. For MCP projects that usually means two or three working tools against your actual data, deployed where you can call them — not a document describing what I would build.

I write things down. You get the schema decisions and the failure modes in writing, because the part that bites later is always the unhappy path nobody specified.