summaryrefslogtreecommitdiff
path: root/mcp_server/client.py
blob: d2b9ff090dab6c638216f6c0bed8ecd0b3b9998e (plain)
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