Initial commit: ag-bids MCP server
Exposes live + historical ag-bids commodity data (from the ag-monitor service at agbids.paul.farm) as MCP tools, sitting behind MetaMCP at https://mcp.jpaul.io/metamcp/ag-bids/mcp. Pattern mirrors zerto-docs-rag with one addition: HTTP Basic auth in front of the streamable-HTTP transport so namespace guessers can't reach the tools. Stdio transport is unaffected (used by local Claude Desktop dev). Tools (markdown returns, ~15 LOC each): best_local_bid(commodity) — where to sell corn/soy/wheat today, for the current calendar month only current_lime_price() — latest lime quotes ($/ton) current_input_price(product?) — MAP / Potash / Lime latest_prices(...) — filtered snapshot price_history(...) — per-(source,delivery) trend list_sources / list_commodities / list_deliveries source_health() — healthy / stale / down buckets todays_summary() — same shape as morning brief snapshot Data path: ag-bids-mcp -> X-API-Key -> /api/data/* on ag-monitor (reuses BRIEF_API_KEY). Tests: 24 covering the httpx client, markdown formatters, HTTP Basic middleware (401/200), and JSONL usage logging. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
# Deploying `ag-bids-mcp` behind MetaMCP
|
||||
|
||||
This runs on the **MetaMCP host (192.168.0.2)** alongside `zerto-docs-mcp`.
|
||||
It joins the same `mcp` Docker network so MetaMCP can proxy to it by container
|
||||
DNS name (`http://ag-bids-mcp:8000/mcp`).
|
||||
|
||||
## One-time setup (on 192.168.0.2)
|
||||
|
||||
### 1. Add env vars
|
||||
|
||||
Edit `/home/justin/zerto-docs-rag/deploy/.env`:
|
||||
|
||||
```ini
|
||||
# Copy this from ag-monitor's .env on 192.168.0.126 (the BRIEF_API_KEY value).
|
||||
# Same key powers /api/data/* and /api/brief/*.
|
||||
AG_BIDS_API_KEY=<paste>
|
||||
|
||||
# Credentials MetaMCP will send to the MCP via Basic auth. Generate two
|
||||
# random values — they're not user-facing, only MetaMCP knows them.
|
||||
AG_BIDS_MCP_USER=agbids-svc
|
||||
AG_BIDS_MCP_PASS=<generate: python -c "import secrets; print(secrets.token_urlsafe(32))">
|
||||
```
|
||||
|
||||
### 2. Paste the service block into compose
|
||||
|
||||
Open `/home/justin/zerto-docs-rag/deploy/docker-compose.yml` and append the
|
||||
`ag-bids-mcp:` block from [docker-compose.snippet.yml](docker-compose.snippet.yml)
|
||||
inside `services:`, alongside `zerto-docs-mcp:`. Keep the same indentation
|
||||
(2 spaces).
|
||||
|
||||
### 3. Build + push the image
|
||||
|
||||
On any dev machine with `docker` and the Gitea registry login:
|
||||
|
||||
```bash
|
||||
cd ~/github/ag-bids-mcp
|
||||
docker login git.jpaul.io # use your Gitea PAT
|
||||
docker build -t git.jpaul.io/justin/ag-bids-mcp:latest .
|
||||
docker push git.jpaul.io/justin/ag-bids-mcp:latest
|
||||
```
|
||||
|
||||
### 4. Start the container
|
||||
|
||||
```bash
|
||||
cd /home/justin/zerto-docs-rag/deploy
|
||||
docker compose pull ag-bids-mcp
|
||||
docker compose up -d ag-bids-mcp
|
||||
docker compose logs -f ag-bids-mcp # confirm "starting ag-bids MCP on streamable-http://0.0.0.0:8000 (Basic auth enforced)"
|
||||
```
|
||||
|
||||
Watchtower (already running on the host) will auto-pull updates every 5
|
||||
minutes from this point forward.
|
||||
|
||||
### 5. Register the namespace in MetaMCP
|
||||
|
||||
In the MetaMCP web UI at `https://mcp.jpaul.io`:
|
||||
|
||||
1. **Create namespace** → name: `ag-bids`
|
||||
2. **Add upstream MCP server** to that namespace:
|
||||
- **Transport:** Streamable HTTP
|
||||
- **URL:** `http://ag-bids-mcp:8000/mcp`
|
||||
- **Authentication:** Basic
|
||||
- **Username:** matches `AG_BIDS_MCP_USER`
|
||||
- **Password:** matches `AG_BIDS_MCP_PASS`
|
||||
3. Save.
|
||||
|
||||
Public endpoint becomes:
|
||||
**`https://mcp.jpaul.io/metamcp/ag-bids/mcp`**
|
||||
|
||||
### 6. Smoke test from the LAN
|
||||
|
||||
```bash
|
||||
# 401 without creds
|
||||
curl -i http://192.168.0.2:8000/mcp 2>&1 | head -3
|
||||
# 200 with creds (Initialize handshake will succeed; the MCP doesn't have
|
||||
# a plain GET, so a curl probe just confirms auth)
|
||||
curl -i -u "$AG_BIDS_MCP_USER:$AG_BIDS_MCP_PASS" http://192.168.0.2:8000/mcp 2>&1 | head -3
|
||||
```
|
||||
|
||||
Then in a real MCP client (Claude Desktop / OpenWebUI / etc.) configured against
|
||||
`https://mcp.jpaul.io/metamcp/ag-bids/mcp`, try:
|
||||
|
||||
- **"What's the best place to sell corn today?"** → calls `best_local_bid("corn")`
|
||||
- **"What's the current price of lime?"** → calls `current_lime_price()`
|
||||
- **"Are any sources down?"** → calls `source_health()`
|
||||
|
||||
## Rotating credentials
|
||||
|
||||
To rotate the Basic password: change `AG_BIDS_MCP_PASS` in the `.env` on
|
||||
192.168.0.2 → `docker compose up -d ag-bids-mcp` to restart with the new
|
||||
value → update the MetaMCP namespace's upstream Basic password to match.
|
||||
|
||||
To rotate the upstream API key: change `BRIEF_API_KEY` in ag-monitor's `.env`
|
||||
on 192.168.0.126 + restart `api` there, then update `AG_BIDS_API_KEY` on
|
||||
192.168.0.2 + restart `ag-bids-mcp`.
|
||||
|
||||
## Observability
|
||||
|
||||
- Per-tool-call usage logs: `/home/justin/zerto-docs-rag/deploy/ag-bids-mcp-logs/usage-YYYY-MM-DD.jsonl`
|
||||
- Container stdout: `docker compose logs ag-bids-mcp`
|
||||
- Successful auth → no log line; failed auth → INFO line with the offending path
|
||||
@@ -0,0 +1,38 @@
|
||||
# Paste this service block into /home/justin/zerto-docs-rag/deploy/docker-compose.yml
|
||||
# on 192.168.0.2 (the MetaMCP host), alongside zerto-docs-mcp. It joins the
|
||||
# same `mcp` Docker network so MetaMCP can reach it by container DNS name.
|
||||
#
|
||||
# Required env vars (set in the same .env that already powers the rest of the
|
||||
# MetaMCP stack):
|
||||
# AG_BIDS_API_KEY — copy from ag-monitor's .env (BRIEF_API_KEY)
|
||||
# AG_BIDS_MCP_USER — username MetaMCP will send in Basic auth
|
||||
# AG_BIDS_MCP_PASS — password MetaMCP will send in Basic auth
|
||||
|
||||
ag-bids-mcp:
|
||||
container_name: ag-bids-mcp
|
||||
image: git.jpaul.io/justin/ag-bids-mcp:latest
|
||||
pull_policy: always
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MCP_TRANSPORT: streamable-http
|
||||
MCP_HOST: 0.0.0.0
|
||||
MCP_PORT: "8000"
|
||||
# Behind a Docker DNS name, FastMCP's localhost-only rebinding-protection
|
||||
# would 421 every call from MetaMCP. Disable it; the mcp network is private.
|
||||
MCP_DISABLE_DNS_REBINDING_PROTECTION: "1"
|
||||
# --- upstream ag-monitor (Cloudflare Tunnel from .0.126) ---
|
||||
AG_BIDS_API_URL: https://agbids.paul.farm
|
||||
AG_BIDS_API_KEY: ${AG_BIDS_API_KEY}
|
||||
AG_BIDS_API_TIMEOUT_SECS: "20"
|
||||
# --- HTTP Basic auth in front of this MCP ---
|
||||
AG_BIDS_MCP_USER: ${AG_BIDS_MCP_USER}
|
||||
AG_BIDS_MCP_PASS: ${AG_BIDS_MCP_PASS}
|
||||
# --- per-tool-call JSONL usage log ---
|
||||
USAGE_LOG_DIR: /app/var/logs
|
||||
USAGE_LOG_KEEP_DAYS: "90"
|
||||
volumes:
|
||||
# Survive container recreates (Watchtower rolls this every ~5 min).
|
||||
- ./ag-bids-mcp-logs:/app/var/logs
|
||||
networks: [mcp]
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
Reference in New Issue
Block a user