storedge-mcp
# storedge-mcp
[]()
[]()
[]()
[]()
**The first MCP server for StorEdge (Storable Edge) - the self-storage management platform that 5,000+ storage facilities rely on.**
Read and write tenants, units, leads, ledgers, move-ins/outs, insurance, tasks, and surcharge calculations for a self-storage facility - straight from Claude, Cursor, or any MCP client.
---
## Why this exists
StorEdge is the operations backbone for tens of thousands of self-storage facilities (under the Storable / Red Nova Labs umbrella). Its REST API at `api.storedgefms.com` powers tenant management, lead tracking, billing, insurance enrollment, task workflows, and move-in/out pipelines.
The existing integration options all require building OAuth 1.0 signing + paginated list handling + typed-error mapping from scratch every time:
- **Tray.ai connector** - locked inside Tray's walled garden, no MCP, no local-first.
- **Twilio integration** - single-purpose, voice-channel only.
- **Java/PHP/.NET sample clients** (`rednovalabs/storedge-api-client-*`) - boilerplate code, no Claude tool surface.
- **No first-party MCP server** - StorEdge has not shipped one.
`storedge-mcp` is the open MCP surface. One stdio call away, agents can answer "what 10x10 units are available right now?", "create a reservation for unit A-12", "show me who's past due this week", and "calculate the surcharge for a $125 Visa payment".
---
## Quick start
```bash
pip install -e .
export STOREDGE_CONSUMER_KEY=... # from StorEdge dashboard
export STOREDGE_CONSUMER_SECRET=... # from StorEdge dashboard
export STOREDGE_FACILITY_ID=<uuid> # optional; tools accept it as arg too
storedge-mcp
```
In Claude Desktop (`~/Library/Application Support/Claude/claude_desktop_config.json`):
```json
{
"mcpServers": {
"storedge": {
"command": "storedge-mcp",
"env": {
"STOREDGE_CONSUMER_KEY": "...",
"STOREDGE_CONSUMER_SECRET": "...",
"STOREDGE_FACILITY_ID": "..."
}
}
}
}
```
---
## Tools (26)
### Diagnostic
- `health_check(facility_id?)` - verify credentials by listing one tenant. Returns fast-fail if OAuth signing is wrong.
### Tenants
- `list_tenants(facility_id?, per_page?, page?, created_after?, created_before?, updated_since?, only_ids?)`
- `list_current_tenants(facility_id?, per_page?, page?)` - only currently-active tenants
- `search_tenants(account_code?, phone_number?, facility_id?)` - by account code OR phone (exactly one)
- `get_tenant(tenant_id, facility_id?)`
- `get_tenant_ledgers(tenant_id, facility_id?)` - list of a tenant's rental agreements
- `get_tenant_notes(tenant_id, facility_id?)` - operator notes attached to a tenant
### Units
- `list_units(facility_id?, per_page?, page?, only_ids?)` - every unit (rented + vacant + offline)
- `list_available_units(facility_id?, per_page?, page?)` - only units available for move-in
- `get_unit(unit_id, facility_id?)`
- `list_unit_groups(facility_id?, per_page?, page?)` - size buckets (5x5, 10x10, etc.)
- `list_unit_types(facility_id?, per_page?, page?)` - Parking, Climate, Drive-Up, etc.
### Leads
- `list_leads(view="current", facility_id?, per_page?, page?)` - view ∈ {current, reservations, inquiries, follow_up, waitlist, missed_move_ins}
- `get_lead_counts(facility_id?)` - open lead counts by status
- `get_lead(lead_id, facility_id?)`
- `create_lead(lead, facility_id?)` - create a reservation or inquiry
- `update_lead(lead_id, lead, facility_id?)` - PATCH an existing lead
### Ledgers (rental agreements)
- `list_current_ledgers(facility_id?, per_page?, page?)`
- `get_ledger(ledger_id, facility_id?)`
- `get_ledger_delinquency(ledger_id, facility_id?)` - overdue balance + days late
- `get_ledger_invoices(ledger_id, facility_id?)`
### Insurance
- `get_insurance_summary(facility_id?)` - counts + revenue rollup
- `get_insurance_activity(facility_id?, per_page?, page?)`
### Tasks
- `list_tasks(category_id?, facility_id?, per_page?, page?)`
- `list_task_categories(facility_id?)`
- `create_task(task, facility_id?)`
- `complete_task(task_id, facility_id?)`
### Move-in / move-out pipeline
- `list_move_ins(facility_id?, per_page?, page?)` - in-progress move-ins
- `list_move_outs(facility_id?, per_page?, page?)` - scheduled move-outs
### Surcharge + invoices
- `calculate_surcharge(amount_cents, card_type?, facility_id?)` - credit-card convenience fee quote
- `list_invoices_and_ledgers(facility_id?, per_page?, page?)`
---
## Auth
OAuth 1.0 one-legged (RFC 5849). Get your consumer key + secret from
**StorEdge dashboard → Corporate → Settings → API**.
`storedge-mcp` ships its own OAuth 1.0 signer - not authlib's - because authlib's stock `OAuth1Auth` strips JSON request bodies during signing (silently breaking `POST`/`PATCH`/`PUT`). Our signer computes `oauth_body_hash` for non-form bodies and includes it in the signature per RFC 5849 §3.4.1.3.
---
## Architecture
Built on industry-leading patterns from `encode/httpx`, `stripe-python`, `lepture/authlib`, `boto3`, `pytest-dev/pytest`, and `astral-sh/ruff`:
- **Shared `httpx.AsyncClient`** with pooling + transport-level retries.
- **Typed exception hierarchy** (`StoredgeAuthError`, `StoredgeNotFoundError`, `StoredgeRateLimitError`, `StoredgeAPIError`, `StoredgeConnectionError`) carrying structured `error_code` (from StorEdge's `meta.error_code` envelope) and `request_id`.
- **Application-level retry** with exponential backoff + full jitter on 429/5xx, honoring `Retry-After`.
- **isError-compliance** - every tool re-raises on failure so FastMCP sets `isError: true` on the wire (Blackwell audit / MCPTox).
- **JSONL audit logging** via `STOREDGE_AUDIT_LOG` env var (or stderr default) - every tool call emits a structured record with redaction for `password`/`api_key`/`token` fields.
- **Property-based tests** (Hypothesis) for JSON round-trips.
- **respx** for httpx mocking - no live API calls in tests.
See `engineering-playbook.md` for the full pattern reference.
---
## Development
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
# Lint + type check + test
ruff check src tests
mypy src tests
pytest
# Coverage
pytest --cov=storedge_mcp --cov-report=term-missing
```
CI matrix (Python 3.10-3.13 on ubuntu/macos/windows) lives in `.github/workflows/ci.yml`.
---
## Sister projects
sanjibani publishes MCP servers for one niche vertical SaaS per day. See the [full list](https://github.com/sanjibani?q=-mcp) for the rest:
- `hawksoft-mcp` - independent insurance agencies
- `open-dental-mcp` - dental practice mgmt
- `ezyvet-mcp` - veterinary
- `jobber-mcp` - home service / field service
- `practicepanther-mcp` - legal practice (solo/small)
- `realm-mp-mcp` - church / nonprofit
- `fieldroutes-mcp` - pest control / lawn care
- `cox-automotive-mcp` - auto dealership
- `qualia-mcp` - title and escrow
- `campspot-mcp` - outdoor hospitality (campgrounds)
- `cleancloud-mcp` - laundry / dry cleaning
- `playmetrics-mcp` - youth sports clubs
- `foreup-mcp` - golf course management
Need a custom MCP for your vertical SaaS stack? → [mcp-services.landing](https://sanjibani.github.io/mcp-services/).
---
## License
MIT
---
## References
- Storable Edge API docs: <https://api.storedgefms.com/>
- Storable Edge Swagger JSON: <https://api.storedgefms.com/apidoc/schema_swagger_json_v1.json>
- OAuth 1.0 RFC: <https://datatracker.ietf.org/doc/html/rfc5849>TDQS
Scored across 31 tools
Each tool targets a distinct entity or action: leads, tenants, units, ledgers, invoices, tasks, insurance, moves, and health. There is no ambiguity; even similar tools like list_current_tenants vs list_tenants vs search_tenants serve different scopes.
All tools follow a consistent verb_noun snake_case pattern (create_lead, get_ledger, list_units, etc.). Even health_check and calculate_surcharge fit the pattern. No mixing of conventions.
31 tools cover a broad storage facility management domain. While slightly above the typical ideal range (3-15), each tool serves a clear purpose and the count is justified by the complexity of the domain.
The set covers many read operations and a few creates/updates, but lacks update/delete for several entities (tenants, units, leads) and misses write endpoints for insurance and moves. Some obvious lifecycle gaps exist.