refigure 0.3.0__tar.gz → 0.3.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.
Files changed (74) hide show
  1. {refigure-0.3.0 → refigure-0.3.2}/.gitignore +1 -0
  2. refigure-0.3.0/README.md → refigure-0.3.2/PKG-INFO +118 -2
  3. refigure-0.3.0/PKG-INFO → refigure-0.3.2/README.md +78 -34
  4. {refigure-0.3.0 → refigure-0.3.2}/pyproject.toml +9 -1
  5. {refigure-0.3.0 → refigure-0.3.2}/refigure/mcp/server.py +3 -1
  6. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/test_http.py +29 -0
  7. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/test_tools.py +13 -0
  8. {refigure-0.3.0 → refigure-0.3.2}/ATTRIBUTION.md +0 -0
  9. {refigure-0.3.0 → refigure-0.3.2}/LICENSE +0 -0
  10. {refigure-0.3.0 → refigure-0.3.2}/NOTICE +0 -0
  11. {refigure-0.3.0 → refigure-0.3.2}/refigure/__init__.py +0 -0
  12. {refigure-0.3.0 → refigure-0.3.2}/refigure/__main__.py +0 -0
  13. {refigure-0.3.0 → refigure-0.3.2}/refigure/_io.py +0 -0
  14. {refigure-0.3.0 → refigure-0.3.2}/refigure/api.py +0 -0
  15. {refigure-0.3.0 → refigure-0.3.2}/refigure/cli.py +0 -0
  16. {refigure-0.3.0 → refigure-0.3.2}/refigure/core/__init__.py +0 -0
  17. {refigure-0.3.0 → refigure-0.3.2}/refigure/core/chart_data.py +0 -0
  18. {refigure-0.3.0 → refigure-0.3.2}/refigure/core/chart_render.py +0 -0
  19. {refigure-0.3.0 → refigure-0.3.2}/refigure/core/zipsafe.py +0 -0
  20. {refigure-0.3.0 → refigure-0.3.2}/refigure/docx/__init__.py +0 -0
  21. {refigure-0.3.0 → refigure-0.3.2}/refigure/docx_groups.py +0 -0
  22. {refigure-0.3.0 → refigure-0.3.2}/refigure/mcp/__init__.py +0 -0
  23. {refigure-0.3.0 → refigure-0.3.2}/refigure/mcp/_lru.py +0 -0
  24. {refigure-0.3.0 → refigure-0.3.2}/refigure/mcp/auth.py +0 -0
  25. {refigure-0.3.0 → refigure-0.3.2}/refigure/mcp/cli.py +0 -0
  26. {refigure-0.3.0 → refigure-0.3.2}/refigure/mcp/exceptions.py +0 -0
  27. {refigure-0.3.0 → refigure-0.3.2}/refigure/mcp/state.py +0 -0
  28. {refigure-0.3.0 → refigure-0.3.2}/refigure/mcp/vlm_cache.py +0 -0
  29. {refigure-0.3.0 → refigure-0.3.2}/refigure/py.typed +0 -0
  30. {refigure-0.3.0 → refigure-0.3.2}/refigure/vlm/__init__.py +0 -0
  31. {refigure-0.3.0 → refigure-0.3.2}/refigure/vlm/cache.py +0 -0
  32. {refigure-0.3.0 → refigure-0.3.2}/refigure/vlm/client.py +0 -0
  33. {refigure-0.3.0 → refigure-0.3.2}/refigure/xlsx/__init__.py +0 -0
  34. {refigure-0.3.0 → refigure-0.3.2}/refigure/xlsx/charts.py +0 -0
  35. {refigure-0.3.0 → refigure-0.3.2}/tests/__init__.py +0 -0
  36. {refigure-0.3.0 → refigure-0.3.2}/tests/support.py +0 -0
  37. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/__init__.py +0 -0
  38. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/core/__init__.py +0 -0
  39. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/core/test_chart_data.py +0 -0
  40. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/core/test_chart_render.py +0 -0
  41. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/core/test_chart_render_missing_mermaidx.py +0 -0
  42. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/core/test_chart_render_visual.py +0 -0
  43. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/docx/__init__.py +0 -0
  44. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/docx/test_docx.py +0 -0
  45. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/__init__.py +0 -0
  46. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/conftest.py +0 -0
  47. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/test_auth.py +0 -0
  48. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/test_batch.py +0 -0
  49. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/test_bridge.py +0 -0
  50. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/test_cli.py +0 -0
  51. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/test_lru.py +0 -0
  52. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/test_prompts.py +0 -0
  53. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/test_resources.py +0 -0
  54. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/test_schema_pin.py +0 -0
  55. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/test_state.py +0 -0
  56. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/mcp/test_vlm_cache.py +0 -0
  57. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/test_cli.py +0 -0
  58. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/test_concurrency.py +0 -0
  59. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/test_docx_chart_group_coexistence.py +0 -0
  60. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/test_docx_groups.py +0 -0
  61. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/test_io.py +0 -0
  62. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/test_optional_dependency_guards.py +0 -0
  63. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/test_property_based.py +0 -0
  64. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/test_robustness.py +0 -0
  65. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/test_vlm_max_markers.py +0 -0
  66. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/test_xml_security.py +0 -0
  67. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/vlm/__init__.py +0 -0
  68. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/vlm/test_soffice_profile_isolation.py +0 -0
  69. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/vlm/test_vlm.py +0 -0
  70. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/vlm/test_vlm_cache.py +0 -0
  71. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/vlm/test_vlm_client.py +0 -0
  72. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/xlsx/__init__.py +0 -0
  73. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/xlsx/test_xlsx.py +0 -0
  74. {refigure-0.3.0 → refigure-0.3.2}/tests/unit/xlsx/test_xlsx_charts.py +0 -0
@@ -54,6 +54,7 @@ CLAUDE.local.md
54
54
  docs/*
55
55
  !docs/assets/
56
56
  CLAUDE.md
57
+ BACKLOG.md
57
58
 
58
59
  # Real-document integration test fixtures (2026-08-05): 26/27 are checked
59
60
  # in — clean, well-documented redistribution licenses (see ATTRIBUTION.md
@@ -1,3 +1,43 @@
1
+ Metadata-Version: 2.5
2
+ Name: refigure
3
+ Version: 0.3.2
4
+ Summary: DOCX/XLSX -> Markdown conversion with native OOXML chart-data extraction (no rasterize/OCR/VLM needed) + optional VLM interpretation for figures with no native chart data
5
+ Project-URL: Homepage, https://github.com/HelgDemidov/refigure
6
+ Project-URL: Repository, https://github.com/HelgDemidov/refigure
7
+ Author-email: HelgDemidov <helg.demidov88@gmail.com>
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
10
+ License-File: NOTICE
11
+ Classifier: Development Status :: 2 - Pre-Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: lxml<7,>=6.1
18
+ Provides-Extra: docx
19
+ Requires-Dist: mammoth<2,>=1.12; extra == 'docx'
20
+ Requires-Dist: markdownify<2,>=1.2; extra == 'docx'
21
+ Provides-Extra: full
22
+ Requires-Dist: anthropic<1,>=0.120; extra == 'full'
23
+ Requires-Dist: mammoth<2,>=1.12; extra == 'full'
24
+ Requires-Dist: markdownify<2,>=1.2; extra == 'full'
25
+ Requires-Dist: mcp<3,>=2.0; extra == 'full'
26
+ Requires-Dist: openai<4,>=3.1.0; extra == 'full'
27
+ Requires-Dist: openpyxl<4,>=3.1; extra == 'full'
28
+ Requires-Dist: pdfplumber<0.12,>=0.11; extra == 'full'
29
+ Provides-Extra: mcp
30
+ Requires-Dist: mcp<3,>=2.0; extra == 'mcp'
31
+ Provides-Extra: vlm
32
+ Requires-Dist: pdfplumber<0.12,>=0.11; extra == 'vlm'
33
+ Provides-Extra: vlm-direct
34
+ Requires-Dist: anthropic<1,>=0.120; extra == 'vlm-direct'
35
+ Requires-Dist: openai<4,>=3.1.0; extra == 'vlm-direct'
36
+ Requires-Dist: pdfplumber<0.12,>=0.11; extra == 'vlm-direct'
37
+ Provides-Extra: xlsx
38
+ Requires-Dist: openpyxl<4,>=3.1; extra == 'xlsx'
39
+ Description-Content-Type: text/markdown
40
+
1
41
  # refigure
2
42
 
3
43
  **Converters where figures survive.**
@@ -7,6 +47,8 @@
7
47
  [![License: Apache 2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
8
48
  [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](pyproject.toml)
9
49
 
50
+ <!-- mcp-name: io.github.HelgDemidov/refigure -->
51
+
10
52
  DOCX / XLSX → Markdown converters that treat embedded charts, composite
11
53
  diagrams and infographics as single semantic objects instead of silently
12
54
  dropping or fragmenting them: native OOXML chart-data extraction (no
@@ -68,6 +110,12 @@ print(result.markdown)
68
110
  print(f"{result.charts_found} charts, {result.groups_found} composite figures")
69
111
  ```
70
112
 
113
+ Or without a permanent install, via [uv](https://docs.astral.sh/uv/)/`uvx`:
114
+
115
+ ```bash
116
+ uvx --from "refigure[docx,xlsx]" refigure report.docx
117
+ ```
118
+
71
119
  Optional VLM interpretation, for a composite figure the chart engine can't
72
120
  reconstruct on its own (see Features below):
73
121
 
@@ -103,6 +151,9 @@ refigure report.docx --vlm # needs the system soffice/LibreOf
103
151
  - **MCP server included** — `refigure-mcp` console command (`[mcp]` extra),
104
152
  stdio or Streamable HTTP, tools/resources/prompts, batch conversion with
105
153
  per-file isolation (see below).
154
+ - **Docker image** — `ghcr.io/helgdemidov/refigure`, both console commands
155
+ on `PATH`, `soffice`/LibreOffice baked in — the VLM composite-figure
156
+ path works turnkey, no manual LibreOffice install (see below).
106
157
 
107
158
  ## CLI
108
159
 
@@ -156,6 +207,22 @@ refigure-mcp # stdio — the MCP client launches it
156
207
  }
157
208
  ```
158
209
 
210
+ Or point the client at `uvx` instead, with no permanent install at all:
211
+
212
+ ```json
213
+ {
214
+ "mcpServers": {
215
+ "refigure": {
216
+ "command": "uvx",
217
+ "args": ["--from", "refigure[mcp,docx,xlsx,vlm-direct]", "refigure-mcp"]
218
+ }
219
+ }
220
+ }
221
+ ```
222
+
223
+ `refigure[full]` is a shortcut for `refigure[mcp,docx,xlsx,vlm-direct]` —
224
+ every tool, both formats, every VLM provider, one extras string.
225
+
159
226
  Three tools — `convert_docx`, `convert_xlsx`, and `convert_batch` (multiple
160
227
  files in one call: one bad file reports its own error without aborting the
161
228
  rest) — each registered only if its format extra is actually installed.
@@ -179,6 +246,52 @@ fairness soft-cap once 2+ callers are configured; `refigure-mcp --help`
179
246
  covers every tuning flag (concurrency, timeouts, resource-store limits,
180
247
  batch size, VLM ceiling).
181
248
 
249
+ ## Docker
250
+
251
+ One image, both surfaces — `refigure` and `refigure-mcp` are already on
252
+ `PATH`, no separate CLI/MCP builds to choose between. The one thing this
253
+ format buys over `pip`/`uvx` that neither can: the system `soffice`/
254
+ LibreOffice binary the VLM composite-figure path needs is baked in, not a
255
+ manual install.
256
+
257
+ ```bash
258
+ docker pull ghcr.io/helgdemidov/refigure:0.3.1
259
+ ```
260
+
261
+ CLI, via a bind mount (the image's working directory is already `/data`):
262
+
263
+ ```bash
264
+ docker run --rm -v "$PWD:/data:ro" ghcr.io/helgdemidov/refigure:0.3.1 \
265
+ refigure /data/report.docx
266
+ ```
267
+
268
+ MCP, stdio — the client launches the container itself:
269
+
270
+ ```json
271
+ {
272
+ "mcpServers": {
273
+ "refigure": {
274
+ "command": "docker",
275
+ "args": ["run", "-i", "--rm", "ghcr.io/helgdemidov/refigure:0.3.1", "refigure-mcp"]
276
+ }
277
+ }
278
+ }
279
+ ```
280
+
281
+ MCP, Streamable HTTP — `--mcp-http-host 0.0.0.0` is required here, not
282
+ optional: the default `127.0.0.1` bind is unreachable through `-p` port
283
+ publishing (Docker's NAT reaches the container's external network
284
+ interface, not its loopback), so the "obvious" invocation without this
285
+ flag would silently never respond:
286
+
287
+ ```bash
288
+ echo "sk-... = alice" > tokens.txt
289
+ docker run --rm -p 8000:8000 -v "$PWD/tokens.txt:/data/tokens.txt:ro" \
290
+ ghcr.io/helgdemidov/refigure:0.3.1 \
291
+ refigure-mcp --transport http --mcp-http-host 0.0.0.0 \
292
+ --mcp-auth-token-file /data/tokens.txt
293
+ ```
294
+
182
295
  ## Real examples
183
296
 
184
297
  Full `convert()` output on real, openly-licensed documents — not
@@ -243,8 +356,11 @@ else:
243
356
  markdown = refigure.xlsx.convert(path).markdown
244
357
  ```
245
358
 
246
- `v0.1.0` published via trusted publishing (GitHub↔PyPI, no stored tokens).
247
- `refigure-md` is a reserved alternate name, not an active release.
359
+ `v0.3.1` published via trusted publishing (GitHub↔PyPI, no stored tokens),
360
+ also on GHCR as `ghcr.io/helgdemidov/refigure` and on the official
361
+ [MCP Registry](https://registry.modelcontextprotocol.io) as
362
+ `io.github.HelgDemidov/refigure`. `refigure-md` is a reserved alternate
363
+ name, not an active release.
248
364
 
249
365
  ## License
250
366
 
@@ -1,35 +1,3 @@
1
- Metadata-Version: 2.5
2
- Name: refigure
3
- Version: 0.3.0
4
- Summary: DOCX/XLSX -> Markdown conversion with native OOXML chart-data extraction (no rasterize/OCR/VLM needed) + optional VLM interpretation for figures with no native chart data
5
- Project-URL: Homepage, https://github.com/HelgDemidov/refigure
6
- Project-URL: Repository, https://github.com/HelgDemidov/refigure
7
- Author-email: HelgDemidov <helg.demidov88@gmail.com>
8
- License-Expression: Apache-2.0
9
- License-File: LICENSE
10
- License-File: NOTICE
11
- Classifier: Development Status :: 2 - Pre-Alpha
12
- Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: Apache Software License
14
- Classifier: Programming Language :: Python :: 3
15
- Classifier: Topic :: Text Processing :: Markup :: Markdown
16
- Requires-Python: >=3.10
17
- Requires-Dist: lxml<7,>=6.1
18
- Provides-Extra: docx
19
- Requires-Dist: mammoth<2,>=1.12; extra == 'docx'
20
- Requires-Dist: markdownify<2,>=1.2; extra == 'docx'
21
- Provides-Extra: mcp
22
- Requires-Dist: mcp<3,>=2.0; extra == 'mcp'
23
- Provides-Extra: vlm
24
- Requires-Dist: pdfplumber<0.12,>=0.11; extra == 'vlm'
25
- Provides-Extra: vlm-direct
26
- Requires-Dist: anthropic<1,>=0.120; extra == 'vlm-direct'
27
- Requires-Dist: openai<4,>=3.1.0; extra == 'vlm-direct'
28
- Requires-Dist: pdfplumber<0.12,>=0.11; extra == 'vlm-direct'
29
- Provides-Extra: xlsx
30
- Requires-Dist: openpyxl<4,>=3.1; extra == 'xlsx'
31
- Description-Content-Type: text/markdown
32
-
33
1
  # refigure
34
2
 
35
3
  **Converters where figures survive.**
@@ -39,6 +7,8 @@ Description-Content-Type: text/markdown
39
7
  [![License: Apache 2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
40
8
  [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](pyproject.toml)
41
9
 
10
+ <!-- mcp-name: io.github.HelgDemidov/refigure -->
11
+
42
12
  DOCX / XLSX → Markdown converters that treat embedded charts, composite
43
13
  diagrams and infographics as single semantic objects instead of silently
44
14
  dropping or fragmenting them: native OOXML chart-data extraction (no
@@ -100,6 +70,12 @@ print(result.markdown)
100
70
  print(f"{result.charts_found} charts, {result.groups_found} composite figures")
101
71
  ```
102
72
 
73
+ Or without a permanent install, via [uv](https://docs.astral.sh/uv/)/`uvx`:
74
+
75
+ ```bash
76
+ uvx --from "refigure[docx,xlsx]" refigure report.docx
77
+ ```
78
+
103
79
  Optional VLM interpretation, for a composite figure the chart engine can't
104
80
  reconstruct on its own (see Features below):
105
81
 
@@ -135,6 +111,9 @@ refigure report.docx --vlm # needs the system soffice/LibreOf
135
111
  - **MCP server included** — `refigure-mcp` console command (`[mcp]` extra),
136
112
  stdio or Streamable HTTP, tools/resources/prompts, batch conversion with
137
113
  per-file isolation (see below).
114
+ - **Docker image** — `ghcr.io/helgdemidov/refigure`, both console commands
115
+ on `PATH`, `soffice`/LibreOffice baked in — the VLM composite-figure
116
+ path works turnkey, no manual LibreOffice install (see below).
138
117
 
139
118
  ## CLI
140
119
 
@@ -188,6 +167,22 @@ refigure-mcp # stdio — the MCP client launches it
188
167
  }
189
168
  ```
190
169
 
170
+ Or point the client at `uvx` instead, with no permanent install at all:
171
+
172
+ ```json
173
+ {
174
+ "mcpServers": {
175
+ "refigure": {
176
+ "command": "uvx",
177
+ "args": ["--from", "refigure[mcp,docx,xlsx,vlm-direct]", "refigure-mcp"]
178
+ }
179
+ }
180
+ }
181
+ ```
182
+
183
+ `refigure[full]` is a shortcut for `refigure[mcp,docx,xlsx,vlm-direct]` —
184
+ every tool, both formats, every VLM provider, one extras string.
185
+
191
186
  Three tools — `convert_docx`, `convert_xlsx`, and `convert_batch` (multiple
192
187
  files in one call: one bad file reports its own error without aborting the
193
188
  rest) — each registered only if its format extra is actually installed.
@@ -211,6 +206,52 @@ fairness soft-cap once 2+ callers are configured; `refigure-mcp --help`
211
206
  covers every tuning flag (concurrency, timeouts, resource-store limits,
212
207
  batch size, VLM ceiling).
213
208
 
209
+ ## Docker
210
+
211
+ One image, both surfaces — `refigure` and `refigure-mcp` are already on
212
+ `PATH`, no separate CLI/MCP builds to choose between. The one thing this
213
+ format buys over `pip`/`uvx` that neither can: the system `soffice`/
214
+ LibreOffice binary the VLM composite-figure path needs is baked in, not a
215
+ manual install.
216
+
217
+ ```bash
218
+ docker pull ghcr.io/helgdemidov/refigure:0.3.1
219
+ ```
220
+
221
+ CLI, via a bind mount (the image's working directory is already `/data`):
222
+
223
+ ```bash
224
+ docker run --rm -v "$PWD:/data:ro" ghcr.io/helgdemidov/refigure:0.3.1 \
225
+ refigure /data/report.docx
226
+ ```
227
+
228
+ MCP, stdio — the client launches the container itself:
229
+
230
+ ```json
231
+ {
232
+ "mcpServers": {
233
+ "refigure": {
234
+ "command": "docker",
235
+ "args": ["run", "-i", "--rm", "ghcr.io/helgdemidov/refigure:0.3.1", "refigure-mcp"]
236
+ }
237
+ }
238
+ }
239
+ ```
240
+
241
+ MCP, Streamable HTTP — `--mcp-http-host 0.0.0.0` is required here, not
242
+ optional: the default `127.0.0.1` bind is unreachable through `-p` port
243
+ publishing (Docker's NAT reaches the container's external network
244
+ interface, not its loopback), so the "obvious" invocation without this
245
+ flag would silently never respond:
246
+
247
+ ```bash
248
+ echo "sk-... = alice" > tokens.txt
249
+ docker run --rm -p 8000:8000 -v "$PWD/tokens.txt:/data/tokens.txt:ro" \
250
+ ghcr.io/helgdemidov/refigure:0.3.1 \
251
+ refigure-mcp --transport http --mcp-http-host 0.0.0.0 \
252
+ --mcp-auth-token-file /data/tokens.txt
253
+ ```
254
+
214
255
  ## Real examples
215
256
 
216
257
  Full `convert()` output on real, openly-licensed documents — not
@@ -275,8 +316,11 @@ else:
275
316
  markdown = refigure.xlsx.convert(path).markdown
276
317
  ```
277
318
 
278
- `v0.1.0` published via trusted publishing (GitHub↔PyPI, no stored tokens).
279
- `refigure-md` is a reserved alternate name, not an active release.
319
+ `v0.3.1` published via trusted publishing (GitHub↔PyPI, no stored tokens),
320
+ also on GHCR as `ghcr.io/helgdemidov/refigure` and on the official
321
+ [MCP Registry](https://registry.modelcontextprotocol.io) as
322
+ `io.github.HelgDemidov/refigure`. `refigure-md` is a reserved alternate
323
+ name, not an active release.
280
324
 
281
325
  ## License
282
326
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "refigure"
7
- version = "0.3.0"
7
+ version = "0.3.2"
8
8
  description = "DOCX/XLSX -> Markdown conversion with native OOXML chart-data extraction (no rasterize/OCR/VLM needed) + optional VLM interpretation for figures with no native chart data"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -64,6 +64,14 @@ vlm-direct = ["refigure[vlm]", "openai>=3.1.0,<4", "anthropic>=0.120,<1"]
64
64
  # registered — see docs/mcp-server/mcp-server-architecture/
65
65
  # mcp-server-architecture.md §2.
66
66
  mcp = ["mcp>=2.0,<3"]
67
+ # Convenience meta-extra for one-shot `uvx --from 'refigure[full]' ...`
68
+ # invocations — union of every capability extra, nothing of its own. Carries
69
+ # no independent isolation contract: if any component subset breaks, the
70
+ # per-extra CI legs above catch it first. Exists specifically because [mcp]
71
+ # alone registers 0 tools (see comment above) and [vlm-direct] alone has no
72
+ # format to convert — a useful refigure-mcp needs all four together, and
73
+ # spelling that out by hand in every doc/README invocation got repetitive.
74
+ full = ["refigure[mcp,docx,xlsx,vlm-direct]"]
67
75
 
68
76
  [project.scripts]
69
77
  refigure = "refigure.cli:main"
@@ -45,6 +45,7 @@ from mcp.types import (
45
45
  )
46
46
  from pydantic import AnyHttpUrl
47
47
 
48
+ from .. import __version__
48
49
  from ..api import (
49
50
  Config,
50
51
  ConversionResult,
@@ -1221,6 +1222,7 @@ def build_server(
1221
1222
  if token_map is not None:
1222
1223
  mcp = MCPServer(
1223
1224
  "refigure",
1225
+ version=__version__,
1224
1226
  token_verifier=_StaticTokenVerifier(token_map),
1225
1227
  auth=AuthSettings(
1226
1228
  issuer_url=AnyHttpUrl("http://localhost/"),
@@ -1228,7 +1230,7 @@ def build_server(
1228
1230
  ),
1229
1231
  )
1230
1232
  else:
1231
- mcp = MCPServer("refigure")
1233
+ mcp = MCPServer("refigure", version=__version__)
1232
1234
  server_ctx = _ServerContext(
1233
1235
  limiter=anyio.CapacityLimiter(max_concurrent),
1234
1236
  vlm_client=vlm_client,
@@ -70,6 +70,35 @@ def _build_http_server(**kwargs):
70
70
  return build_server(transport="http", **kwargs)
71
71
 
72
72
 
73
+ async def test_server_reports_its_own_version_over_http_too() -> None:
74
+ """Same regression as test_tools.py's stdio-side version, exercised
75
+ against the OTHER build_server() construction branch (token_map is
76
+ not None -> the token_verifier/AuthSettings path) — both branches
77
+ pass version=__version__ as a separate literal, line coverage alone
78
+ doesn't prove this one's value is right."""
79
+ import refigure
80
+
81
+ mcp_server = _build_http_server()
82
+ app = mcp_server.streamable_http_app()
83
+
84
+ async with app.router.lifespan_context(app):
85
+ http_client = httpx.AsyncClient(
86
+ transport=httpx.ASGITransport(app=app),
87
+ base_url=_BASE_URL,
88
+ headers={"Authorization": "Bearer tok-alice"},
89
+ )
90
+ async with http_client:
91
+ async with streamable_http_client(url=f"{_BASE_URL}/mcp", http_client=http_client) as (
92
+ read,
93
+ write,
94
+ ):
95
+ async with ClientSession(read, write) as session:
96
+ init = await session.initialize()
97
+
98
+ assert init.server_info.version == refigure.__version__
99
+ assert init.server_info.version != ""
100
+
101
+
73
102
  async def test_valid_bearer_token_converts_successfully() -> None:
74
103
  mcp_server = _build_http_server()
75
104
  app = mcp_server.streamable_http_app()
@@ -182,6 +182,19 @@ async def test_list_tools_registers_both_convert_tools(client: Client) -> None:
182
182
  assert tool.annotations.read_only_hint is True
183
183
 
184
184
 
185
+ async def test_server_reports_its_own_version_in_the_mcp_handshake(client: Client) -> None:
186
+ """Regression: MCPServer("refigure") was built with no version= kwarg
187
+ at all, so a real client's initialize() handshake saw an empty string
188
+ instead of refigure's actual version — found live via a real
189
+ subprocess stdio_client() round trip (this repo's first, every other
190
+ test here uses the in-memory Client), not by code review."""
191
+ import refigure
192
+
193
+ assert client.server_info is not None
194
+ assert client.server_info.version == refigure.__version__
195
+ assert client.server_info.version != ""
196
+
197
+
185
198
  async def test_corrupt_zip_bytes_are_rejected_as_corrupt_archive(client: Client) -> None:
186
199
  """A structurally-valid zip with no OOXML content still degrades to
187
200
  CorruptArchiveError, not something more exotic — belt-and-suspenders
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes