OHMS
by crashzero9
README.md
# OHMS - Order Hub Management System
Flauraly Flowers and Plants - Python FastMCP server hosted on Replit Reserved VM.
## Purpose
OHMS exposes a small, hardened set of order- and inventory-related tools to
Violet (and any other authorized MCP client) over the Model Context Protocol.
It centralizes Shopify Admin REST access behind a Bearer-auth gate so agents
never touch raw Shopify credentials.
## Architecture
```
+-----------------+ Bearer +------------------------------+
| MCP Client | ---------------> | OHMS (Replit Reserved VM) |
| (Violet, etc.) | /mcp or /sse | |
+-----------------+ | Starlette parent app |
| +-- /health (open) |
| +-- /mcp (Streamable HTTP)|
| +-- /sse (SSE fallback) |
| +-- BearerAuthMiddleware |
+--------------+---------------+
|
v
+------------------------------+
| Shopify Admin REST API |
+------------------------------+
```
Both `/mcp` (Streamable HTTP) and `/sse` (Server-Sent Events fallback) are
mounted simultaneously so any MCP client transport profile works.
## Environment Variables
OHMS authenticates to Shopify via the OAuth 2.0 **client_credentials** grant.
The server never holds a long-lived `SHOPIFY_ACCESS_TOKEN`; instead it holds a
`SHOPIFY_CLIENT_ID` + `SHOPIFY_CLIENT_SECRET` pair and mints a short-lived
access token on demand against `https://{shop}.myshopify.com/admin/oauth/access_token`.
The minted token is cached in-memory with a 5-minute clock-skew buffer and
re-minted automatically (or on a 401/403 from any subsequent call). All values
are read via `os.environ.get(...)`. Nothing is hardcoded.
| Var | Purpose |
|---|---|
| `PORT` | TCP port to bind (Replit injects this; defaults to 8080). |
| `OHMS_API_TOKEN` | Static bearer token required on every non-`/health` request (client to OHMS auth - separate from Shopify). |
| `SHOPIFY_STORE_URL` | Shop domain, e.g. `flauraly.myshopify.com`. |
| `SHOPIFY_CLIENT_ID` | Shopify app client ID (used for OAuth client_credentials grant). |
| `SHOPIFY_CLIENT_SECRET` | Shopify app client secret (used for OAuth client_credentials grant). Rotate per `Secrets_Registry.md` schedule. |
| `SHOPIFY_API_VERSION` | Pinned Shopify API version, e.g. `2025-01`. |
| `PRINTER_IP` | Local network IP of the receipt printer (stub uses this). |
See `.env.example` for the placeholder template.
## Local Dev (Windows)
OHMS reads `.env` only when running locally (via `python-dotenv`). Bootstrap
your `.env` from the Windows DPAPI-protected secrets store rather than typing
secrets in plaintext:
```powershell
# 1. Pull each secret from DPAPI into the local .env (PowerShell pseudocode)
$secrets = @("OHMS_API_TOKEN","SHOPIFY_STORE_URL","SHOPIFY_CLIENT_ID","SHOPIFY_CLIENT_SECRET","SHOPIFY_API_VERSION","PRINTER_IP")
foreach ($k in $secrets) {
$v = Unprotect-DpapiSecret -Name $k # your local helper
Add-Content .env "$k=$v"
}
# 2. Run the server
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txt
python main.py
```
The server listens on `http://0.0.0.0:8080` by default. Probe with:
```bash
curl http://localhost:8080/health
# => OHMS OK
```
## Replit Deploy
1. Create a Replit project and import this folder.
2. In the Secrets pane, set every variable from `.env.example` (using the real
values - never paste them into any committed file).
3. Confirm `.replit` shows `deploymentTarget = "reserved_vm"` and port
`8080 -> 80`.
4. Deploy. The public URL is `https://ohms-server.crashzero9.replit.app`.
5. Verify both transports:
```bash
curl https://ohms-server.crashzero9.replit.app/health
curl -H "Authorization: Bearer $OHMS_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}' \
https://ohms-server.crashzero9.replit.app/mcp
```
## Tool Registry
| Tool | Source | Notes |
|---|---|---|
| `get_order(order_id)` | Shopify Admin REST | `GET /orders/{id}.json` |
| `list_pending_orders()` | Shopify Admin REST | `GET /orders.json?status=open&limit=50` |
| `update_order_status(order_id, status)` | Shopify Admin REST | `PUT /orders/{id}.json` (sets tags) |
| `get_inventory_snapshot()` | Shopify Admin REST | `GET /inventory_levels.json?limit=50` |
| `get_doordash_orders_via_browser()` | Stub | Returns routing dict for browser-automation handoff. |
| `print_order_ticket(order_id)` | Stub | Reads `PRINTER_IP`; returns queued status. Driver pending. |
## Phase Status
- **Phase 1 (current):** MVP scaffold, Bearer auth, six tools (4 live + 2
stubs), Replit Reserved VM deploy, basic pytest suite.
- **Phase 2 (planned):** OAuth 2.1 replacing static bearer, full DoorDash
driver, real network printer driver, structured logging with secret
redaction, rate limiting.
## Security Notes
- No secret values appear in any committed file.
- `Authorization` headers and full Shopify response bodies are never logged.
- `/health` returns only the literal string `OHMS OK` - no version, env, or
path info.
- All `httpx` calls have an explicit 30-second timeout.
This server cannot be deployed