1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
"""Thin HTTP client around the Food Django REST API.
Pure HTTP — holds no Django import. Talks to the API over localhost using the
`caine` DRF token (Authorization: Token <token>). One method per endpoint the
MCP tools need; all errors surface as `FoodApiError` with a readable message.
"""
import os
import httpx
class FoodApiError(Exception):
"""The Django API returned a non-2xx response, or was unreachable."""
class FoodClient:
def __init__(self, base=None, token=None, http=None):
base = (base or os.environ.get("FOOD_API_BASE", "http://127.0.0.1:8042/api")).rstrip("/")
token = token or os.environ.get("FOOD_API_TOKEN", "")
# `http` is injectable for tests (e.g. httpx.MockTransport).
self._http = http or httpx.Client(
base_url=base,
headers={"Authorization": f"Token {token}"},
timeout=30.0,
)
# --- low level ---
def _request(self, method, path, **kwargs):
try:
resp = self._http.request(method, path, **kwargs)
except httpx.HTTPError as e:
raise FoodApiError(f"could not reach the food API: {e}") from e
if resp.status_code >= 400:
raise FoodApiError(f"{resp.status_code}: {_safe_json(resp)}")
return _safe_json(resp)
def _get(self, path, params=None):
return self._request("GET", path, params=params)
def _post(self, path, json):
return self._request("POST", path, json=json)
# --- endpoints (one per MCP tool need) ---
def pantry(self):
return self._get("/pantry/")
def set_state(self, ingredient, state, location=None):
body = {"ingredient": ingredient, "state": state}
if location:
body["location"] = location
return self._post("/pantry/set-state/", body)
def bulk_add(self, items):
return self._post("/bulk-pantry-add/", {"items": items})
def what_can_i_cook(self, servings=2):
return self._get("/what-can-i-cook/", {"servings": servings})
def meta_recipes(self):
return self._get("/meta-recipes/")
def log_cook(self, payload):
return self._post("/log-cook/", payload)
def create_meta_recipe(self, payload):
return self._post("/create-meta-recipe/", payload)
def _safe_json(resp):
try:
return resp.json()
except ValueError:
return resp.text
|