cronometer-api-mcp 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cronometer_api_mcp-0.2.2/.github/dependabot.yml +13 -0
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/Dockerfile +4 -0
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/PKG-INFO +14 -4
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/README.md +11 -0
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/pyproject.toml +9 -3
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/server.json +2 -2
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/src/cronometer_api_mcp/client.py +533 -62
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/src/cronometer_api_mcp/server.py +170 -4
- cronometer_api_mcp-0.2.2/tests/test_client.py +1009 -0
- cronometer_api_mcp-0.2.2/tests/test_server_import.py +142 -0
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/tests/test_timezone.py +1 -1
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/uv.lock +101 -52
- cronometer_api_mcp-0.2.0/tests/test_client.py +0 -336
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/.dockerignore +0 -0
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/.env.example +0 -0
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/.github/workflows/ci.yml +0 -0
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/.github/workflows/docker.yml +0 -0
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/.github/workflows/publish.yml +0 -0
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/.gitignore +0 -0
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/.python-version +0 -0
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/LICENSE +0 -0
- {cronometer_api_mcp-0.2.0 → cronometer_api_mcp-0.2.2}/src/cronometer_api_mcp/__init__.py +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "uv"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
# Edit the declared ranges, not just the lock: pyproject.toml is what PyPI
|
|
8
|
+
# consumers resolve against, so a breaking release has to surface there.
|
|
9
|
+
versioning-strategy: increase
|
|
10
|
+
open-pull-requests-limit: 5
|
|
11
|
+
groups:
|
|
12
|
+
dev-tooling:
|
|
13
|
+
dependency-type: "development"
|
|
@@ -51,6 +51,10 @@ RUN uv python install 3.14 \
|
|
|
51
51
|
# in supergateway's own node_modules to a release that covers the versions the
|
|
52
52
|
# Python `mcp` package negotiates.
|
|
53
53
|
#
|
|
54
|
+
# Python mcp 2.x still handshakes at 2025-11-25, which this Node SDK range
|
|
55
|
+
# covers; its 2026-07-28 revision is reachable only via `server/discover`.
|
|
56
|
+
# Re-check this pin only if a release adds a *handshake* version.
|
|
57
|
+
#
|
|
54
58
|
# NOTE: this is a per-image mitigation, not the durable fix. supergateway
|
|
55
59
|
# proxies `initialize` verbatim but then validates the negotiated version
|
|
56
60
|
# against its own (possibly older) SDK, so the skew recurs whenever the wrapped
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: cronometer-api-mcp
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: MCP server for Cronometer nutrition data using the mobile REST API
|
|
5
5
|
Author: Randy
|
|
6
6
|
License-Expression: MIT
|
|
@@ -11,10 +11,9 @@ Classifier: Intended Audience :: Developers
|
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Programming Language :: Python :: 3
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.14
|
|
14
|
-
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
15
14
|
Requires-Python: >=3.14
|
|
16
15
|
Requires-Dist: httpx[socks]>=0.27.0
|
|
17
|
-
Requires-Dist: mcp
|
|
16
|
+
Requires-Dist: mcp<3,>=2.0.0
|
|
18
17
|
Requires-Dist: python-dotenv>=1.0.0
|
|
19
18
|
Requires-Dist: tzdata>=2024.1
|
|
20
19
|
Description-Content-Type: text/markdown
|
|
@@ -138,6 +137,8 @@ cached session, so it also overrides a stale cached timezone.
|
|
|
138
137
|
| `add_food_entry` | Log a food serving to the diary |
|
|
139
138
|
| `remove_food_entry` | Remove one or more diary entries |
|
|
140
139
|
| `add_custom_food` | Create a custom food with specified nutrition |
|
|
140
|
+
| `add_recipe` | Create a recipe from existing foods referenced by ID and gram weight |
|
|
141
|
+
| `import_recipe` | Create a recipe from a free-text ingredient list; Cronometer matches each line to a database food and converts the amount to grams |
|
|
141
142
|
| `copy_day` | Copy all entries from the previous day |
|
|
142
143
|
| `mark_day_complete` | Mark a diary day as complete or incomplete |
|
|
143
144
|
|
|
@@ -186,6 +187,8 @@ The API uses two protocols:
|
|
|
186
187
|
- **v2 (`POST /api/v2/*`)** -- JSON-body auth, used for most operations (food search, diary read/write, nutrition, fasting, macros, biometrics)
|
|
187
188
|
- **v3 (`DELETE /api/v3/user/{id}/*`)** -- Header-based auth (`x-crono-session`), used for diary entry deletion
|
|
188
189
|
|
|
190
|
+
Recipe import is the one asynchronous operation: `import_recipe` returns a job id, and `poll_async_result` is polled until the server reports 100% progress and attaches the parsed ingredients.
|
|
191
|
+
|
|
189
192
|
## Python API
|
|
190
193
|
|
|
191
194
|
You can use the client directly:
|
|
@@ -212,6 +215,13 @@ client.add_serving(
|
|
|
212
215
|
# Get today's diary
|
|
213
216
|
diary = client.get_diary()
|
|
214
217
|
|
|
218
|
+
# Import a recipe from a free-text ingredient list
|
|
219
|
+
recipe = client.import_recipe("one hot dog\nketchup\nbun")
|
|
220
|
+
print(recipe["food_id"], recipe["ingredients"])
|
|
221
|
+
|
|
222
|
+
# Parse without saving, to review the matches first
|
|
223
|
+
preview = client.import_recipe("2 tbsp olive oil\n200g chicken", save=False)
|
|
224
|
+
|
|
215
225
|
# Get nutrition scores
|
|
216
226
|
scores = client.get_nutrition_scores()
|
|
217
227
|
```
|
|
@@ -117,6 +117,8 @@ cached session, so it also overrides a stale cached timezone.
|
|
|
117
117
|
| `add_food_entry` | Log a food serving to the diary |
|
|
118
118
|
| `remove_food_entry` | Remove one or more diary entries |
|
|
119
119
|
| `add_custom_food` | Create a custom food with specified nutrition |
|
|
120
|
+
| `add_recipe` | Create a recipe from existing foods referenced by ID and gram weight |
|
|
121
|
+
| `import_recipe` | Create a recipe from a free-text ingredient list; Cronometer matches each line to a database food and converts the amount to grams |
|
|
120
122
|
| `copy_day` | Copy all entries from the previous day |
|
|
121
123
|
| `mark_day_complete` | Mark a diary day as complete or incomplete |
|
|
122
124
|
|
|
@@ -165,6 +167,8 @@ The API uses two protocols:
|
|
|
165
167
|
- **v2 (`POST /api/v2/*`)** -- JSON-body auth, used for most operations (food search, diary read/write, nutrition, fasting, macros, biometrics)
|
|
166
168
|
- **v3 (`DELETE /api/v3/user/{id}/*`)** -- Header-based auth (`x-crono-session`), used for diary entry deletion
|
|
167
169
|
|
|
170
|
+
Recipe import is the one asynchronous operation: `import_recipe` returns a job id, and `poll_async_result` is polled until the server reports 100% progress and attaches the parsed ingredients.
|
|
171
|
+
|
|
168
172
|
## Python API
|
|
169
173
|
|
|
170
174
|
You can use the client directly:
|
|
@@ -191,6 +195,13 @@ client.add_serving(
|
|
|
191
195
|
# Get today's diary
|
|
192
196
|
diary = client.get_diary()
|
|
193
197
|
|
|
198
|
+
# Import a recipe from a free-text ingredient list
|
|
199
|
+
recipe = client.import_recipe("one hot dog\nketchup\nbun")
|
|
200
|
+
print(recipe["food_id"], recipe["ingredients"])
|
|
201
|
+
|
|
202
|
+
# Parse without saving, to review the matches first
|
|
203
|
+
preview = client.import_recipe("2 tbsp olive oil\n200g chicken", save=False)
|
|
204
|
+
|
|
194
205
|
# Get nutrition scores
|
|
195
206
|
scores = client.get_nutrition_scores()
|
|
196
207
|
```
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cronometer-api-mcp"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "MCP server for Cronometer nutrition data using the mobile REST API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -19,10 +19,9 @@ classifiers = [
|
|
|
19
19
|
"License :: OSI Approved :: MIT License",
|
|
20
20
|
"Programming Language :: Python :: 3",
|
|
21
21
|
"Programming Language :: Python :: 3.14",
|
|
22
|
-
"Topic :: Scientific/Engineering :: Medical Science Apps.",
|
|
23
22
|
]
|
|
24
23
|
dependencies = [
|
|
25
|
-
"mcp>=
|
|
24
|
+
"mcp>=2.0.0,<3",
|
|
26
25
|
"httpx[socks]>=0.27.0",
|
|
27
26
|
"python-dotenv>=1.0.0",
|
|
28
27
|
"tzdata>=2024.1",
|
|
@@ -39,3 +38,10 @@ dev = [
|
|
|
39
38
|
|
|
40
39
|
[tool.hatch.build.targets.wheel]
|
|
41
40
|
packages = ["src/cronometer_api_mcp"]
|
|
41
|
+
|
|
42
|
+
[tool.ruff.lint.per-file-ignores]
|
|
43
|
+
# Tools have to hand every failure back as a structured error string; an
|
|
44
|
+
# exception escaping into the stdio stream kills the session.
|
|
45
|
+
"src/cronometer_api_mcp/server.py" = ["BLE001"]
|
|
46
|
+
# Diary enrichment is best-effort and degrades to an unenriched diary.
|
|
47
|
+
"src/cronometer_api_mcp/client.py" = ["BLE001"]
|
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/rwestergren/cronometer-api-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.2.
|
|
9
|
+
"version": "0.2.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "pypi",
|
|
13
13
|
"identifier": "cronometer-api-mcp",
|
|
14
|
-
"version": "0.2.
|
|
14
|
+
"version": "0.2.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|