gst-einvoice-mcp 0.1.0__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 (31) hide show
  1. gst_einvoice_mcp-0.1.0/PKG-INFO +14 -0
  2. gst_einvoice_mcp-0.1.0/README.md +302 -0
  3. gst_einvoice_mcp-0.1.0/gst_einvoice/__init__.py +1 -0
  4. gst_einvoice_mcp-0.1.0/gst_einvoice/extract_llm.py +1215 -0
  5. gst_einvoice_mcp-0.1.0/gst_einvoice/extract_rules.py +1831 -0
  6. gst_einvoice_mcp-0.1.0/gst_einvoice/gstin.py +95 -0
  7. gst_einvoice_mcp-0.1.0/gst_einvoice/ingest.py +290 -0
  8. gst_einvoice_mcp-0.1.0/gst_einvoice/ocr.py +237 -0
  9. gst_einvoice_mcp-0.1.0/gst_einvoice/pipeline.py +767 -0
  10. gst_einvoice_mcp-0.1.0/gst_einvoice/schema.py +161 -0
  11. gst_einvoice_mcp-0.1.0/gst_einvoice/server.py +223 -0
  12. gst_einvoice_mcp-0.1.0/gst_einvoice/state_codes.py +122 -0
  13. gst_einvoice_mcp-0.1.0/gst_einvoice/validators.py +172 -0
  14. gst_einvoice_mcp-0.1.0/gst_einvoice_mcp.egg-info/PKG-INFO +14 -0
  15. gst_einvoice_mcp-0.1.0/gst_einvoice_mcp.egg-info/SOURCES.txt +29 -0
  16. gst_einvoice_mcp-0.1.0/gst_einvoice_mcp.egg-info/dependency_links.txt +1 -0
  17. gst_einvoice_mcp-0.1.0/gst_einvoice_mcp.egg-info/entry_points.txt +2 -0
  18. gst_einvoice_mcp-0.1.0/gst_einvoice_mcp.egg-info/requires.txt +10 -0
  19. gst_einvoice_mcp-0.1.0/gst_einvoice_mcp.egg-info/top_level.txt +1 -0
  20. gst_einvoice_mcp-0.1.0/pyproject.toml +33 -0
  21. gst_einvoice_mcp-0.1.0/setup.cfg +4 -0
  22. gst_einvoice_mcp-0.1.0/tests/test_extract_llm.py +1898 -0
  23. gst_einvoice_mcp-0.1.0/tests/test_extract_rules.py +2766 -0
  24. gst_einvoice_mcp-0.1.0/tests/test_gstin.py +249 -0
  25. gst_einvoice_mcp-0.1.0/tests/test_ingest.py +643 -0
  26. gst_einvoice_mcp-0.1.0/tests/test_ocr.py +638 -0
  27. gst_einvoice_mcp-0.1.0/tests/test_pipeline.py +1889 -0
  28. gst_einvoice_mcp-0.1.0/tests/test_schema.py +664 -0
  29. gst_einvoice_mcp-0.1.0/tests/test_server.py +443 -0
  30. gst_einvoice_mcp-0.1.0/tests/test_state_codes.py +339 -0
  31. gst_einvoice_mcp-0.1.0/tests/test_validators.py +571 -0
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: gst-einvoice-mcp
3
+ Version: 0.1.0
4
+ Summary: GST e-invoice extraction: Indian tax invoices in, INV-01 JSON plus validation warnings out
5
+ Requires-Python: >=3.13
6
+ Requires-Dist: pydantic>=2.0
7
+ Requires-Dist: pymupdf>=1.24
8
+ Requires-Dist: pillow>=10.0
9
+ Requires-Dist: numpy>=1.26
10
+ Requires-Dist: pytesseract>=0.3.10
11
+ Requires-Dist: groq>=0.11
12
+ Requires-Dist: mcp>=2.0
13
+ Provides-Extra: test
14
+ Requires-Dist: pytest>=8; extra == "test"
@@ -0,0 +1,302 @@
1
+ # GST e-invoice extraction
2
+
3
+ Turns an Indian GST tax invoice into the government's **INV-01 JSON payload**, and tells
4
+ you exactly what it could not read.
5
+
6
+ Ships as an [MCP](https://modelcontextprotocol.io) server with three tools, so an agent can
7
+ parse a document, check a GSTIN, or re-validate a payload it already holds.
8
+
9
+ > **It produces a submission-ready payload, not a filed invoice.** There is no IRN here. An
10
+ > Invoice Reference Number is issued by the government's Invoice Registration Portal after
11
+ > you submit the payload to it. Nothing in this repository talks to the IRP.
12
+
13
+ ---
14
+
15
+ ## The idea
16
+
17
+ An extraction tool that quietly guesses is worse than one that says it cannot read a field.
18
+ A hallucinated digit in a GSTIN that still passes its checksum, or a line item that was
19
+ never on the page, is the failure that costs an accountant real money — and it is invisible
20
+ precisely because it looks right.
21
+
22
+ So the design has one rule: **the model may structure text, it may never invent values.**
23
+ That is enforced twice. The prompt says it, and then every value the model returns is
24
+ checked back against the document text before it is kept. A value with no source in the
25
+ document is replaced with null and reported, however plausible it looks. If that field is
26
+ mandatory in INV-01, no payload is produced at all.
27
+
28
+ Everything the tool knows about its own work — how each page was read, where each field came
29
+ from, what it was unsure about — travels beside the payload in `extraction_meta`, never
30
+ inside it. The payload stays strictly spec-pure, because the government API rejects unknown
31
+ keys.
32
+
33
+ **Read [LIMITATIONS.md](LIMITATIONS.md) before trusting the output.** It is specific about
34
+ what the tool cannot corroborate, and what that costs you.
35
+
36
+ ---
37
+
38
+ ## Install
39
+
40
+ Requires **Python 3.13** and **Tesseract OCR** as a system dependency.
41
+
42
+ ```bash
43
+ # Tesseract (Windows)
44
+ winget install UB-Mannheim.TesseractOCR
45
+
46
+ # Tesseract (Debian/Ubuntu)
47
+ sudo apt-get install -y tesseract-ocr
48
+
49
+ # Tesseract (macOS)
50
+ brew install tesseract
51
+ ```
52
+
53
+ ```bash
54
+ python -m venv .venv
55
+ .venv/Scripts/activate # Windows
56
+ # source .venv/bin/activate # Linux / macOS
57
+ pip install -e .
58
+ ```
59
+
60
+ Tesseract does not need to be on `PATH`: the OCR module looks there first, then at the
61
+ standard Windows install location, and raises an actionable error naming both if neither
62
+ works.
63
+
64
+ ### Environment
65
+
66
+ | Variable | Required | Purpose |
67
+ |---|---|---|
68
+ | `GROQ_API_KEY` | yes | Stage 2 reads the line-item table through Groq |
69
+ | `GST_MCP_MODEL` | recommended | Pin the model your key can reach |
70
+ | `GST_MCP_TRANSPORT` | no | `stdio` (default), `sse`, or `streamable-http` |
71
+
72
+ The default model is `openai/gpt-oss-120b`, which is what this release was validated
73
+ against. It works without configuration.
74
+
75
+ > **Still set `GST_MCP_MODEL` in a deployment.** A hard-coded model identifier expires
76
+ > silently when the provider retires it, and the failure arrives as an HTTP 404 that reads
77
+ > like a bad key rather than a stale constant. Pin the model you have access to, and check
78
+ > it against Groq's deprecation notices.
79
+
80
+ ---
81
+
82
+ ## MCP client configuration
83
+
84
+ ```json
85
+ {
86
+ "mcpServers": {
87
+ "gst-einvoice": {
88
+ "command": "C:/Shrish/GST MCP/.venv/Scripts/python.exe",
89
+ "args": ["-m", "gst_einvoice.server"],
90
+ "env": {
91
+ "GROQ_API_KEY": "your-key-here",
92
+ "GST_MCP_MODEL": "openai/gpt-oss-120b"
93
+ }
94
+ }
95
+ }
96
+ }
97
+ ```
98
+
99
+ On Linux or macOS use `.venv/bin/python` instead.
100
+
101
+ ### Tools
102
+
103
+ | Tool | Takes | Gives back |
104
+ |---|---|---|
105
+ | `parse_invoice` | a file path, optional tolerance | the INV-01 payload, missing fields, refusals, and `extraction_meta` |
106
+ | `validate_gstin` | a GSTIN | structure, checksum, state code, PAN, and the reason on failure |
107
+ | `validate_payload` | an INV-01 payload | the four consistency checks over a payload you already hold |
108
+
109
+ `parse_invoice` has **three normal outcomes**, and only the first gives you a payload:
110
+
111
+ 1. **A payload plus warnings.** Usable, but the warnings say which fields were read at low
112
+ OCR confidence, which were assigned by position rather than by a label, and which were
113
+ derived rather than printed.
114
+ 2. **No payload, `missing_fields` populated.** A mandatory field could not be read. Nothing
115
+ was invented to fill the gap, which is why there is no payload.
116
+ 3. **No payload, `refusals` populated.** Export, SEZ and foreign-currency invoices are
117
+ refused by design, with a message saying what was detected and what to do instead.
118
+
119
+ ---
120
+
121
+ ## Worked example
122
+
123
+ The invoice, as a text-layer PDF:
124
+
125
+ ```
126
+ TAX INVOICE
127
+ Seller: Nimbus Components Pvt Ltd
128
+ GSTIN 27AAPFU0939F1ZV
129
+ Plot 14 MIDC Andheri East
130
+ Mumbai 400093
131
+ Invoice No: INV-2026-0042
132
+ Invoice Date: 17/04/2026
133
+ Bill To: Kanchan Electricals LLP
134
+ GSTIN 27AABCB5507N1ZJ
135
+ 18 Connaught Place
136
+ Pune 411005
137
+ Sl 1 Laptop Stand HSN/SAC: 8471 Qty 4 NOS Rate 1500.00
138
+ Taxable 6000.00 GST 18% CGST 540.00 SGST 540.00 IGST 0.00 Line Total 7080.00
139
+ Taxable 6000.00 CGST 540.00 SGST 540.00 IGST 0.00
140
+ Total Invoice Value 7080.00
141
+ ```
142
+
143
+ ```python
144
+ from gst_einvoice.extract_llm import make_client
145
+ from gst_einvoice.pipeline import extract_invoice
146
+
147
+ # model defaults to openai/gpt-oss-120b; pass model=... to override
148
+ result = extract_invoice("invoice.pdf", client=make_client())
149
+ ```
150
+
151
+ ### The payload
152
+
153
+ ```json
154
+ {
155
+ "Version": "1.1",
156
+ "TranDtls": { "TaxSch": "GST", "SupTyp": "B2B" },
157
+ "DocDtls": { "Typ": "INV", "No": "INV-2026-0042", "Dt": "17/04/2026" },
158
+ "SellerDtls": {
159
+ "Gstin": "27AAPFU0939F1ZV",
160
+ "LglNm": "Nimbus Components Pvt Ltd",
161
+ "Addr1": "Plot 14 MIDC Andheri East",
162
+ "Loc": "Mumbai",
163
+ "Pin": 400093,
164
+ "Stcd": "27"
165
+ },
166
+ "BuyerDtls": {
167
+ "Gstin": "27AABCB5507N1ZJ",
168
+ "LglNm": "Kanchan Electricals LLP",
169
+ "Addr1": "18 Connaught Place",
170
+ "Loc": "Pune",
171
+ "Pin": 411005,
172
+ "Stcd": "27",
173
+ "Pos": "27"
174
+ },
175
+ "ItemList": [
176
+ {
177
+ "SlNo": "1",
178
+ "PrdDesc": "Laptop Stand",
179
+ "IsServc": "N",
180
+ "HsnCd": "8471",
181
+ "Qty": 4.0,
182
+ "Unit": "NOS",
183
+ "UnitPrice": 1500.0,
184
+ "TotAmt": 6000.0,
185
+ "Discount": 0.0,
186
+ "AssAmt": 6000.0,
187
+ "GstRt": 18.0,
188
+ "CgstAmt": 540.0,
189
+ "SgstAmt": 540.0,
190
+ "IgstAmt": 0.0,
191
+ "CesAmt": 0.0,
192
+ "StateCesAmt": 0.0,
193
+ "OthChrg": 0.0,
194
+ "TotItemVal": 7080.0
195
+ }
196
+ ],
197
+ "ValDtls": {
198
+ "AssVal": 6000.0, "CgstVal": 540.0, "SgstVal": 540.0, "IgstVal": 0.0,
199
+ "CesVal": 0.0, "StCesVal": 0.0, "RndOffAmt": 0.0, "TotInvVal": 7080.0
200
+ }
201
+ }
202
+ ```
203
+
204
+ ### The warnings block
205
+
206
+ This is the half most tools do not give you. Seven entries, from the run above, one
207
+ `warning` and six `info`:
208
+
209
+ ```
210
+ [warning] extract_llm ItemList[0].SlNo
211
+ "1" is a single character. It is printed as a token of its own in the document,
212
+ which is why ItemList[0].SlNo was kept rather than dropped, but one character
213
+ matches almost any page by accident, so the corroboration is weak. Confirm it
214
+ against the invoice by eye — on a line item, that means the row numbering.
215
+
216
+ [info] extract_llm ItemList[0].HsnCd
217
+ "8471" is not printed as a code of its own in this row's text: it was grounded by
218
+ the HSN/SAC codes stage 1 confirmed, or by a longer number elsewhere on the page.
219
+ Which code belongs to which row is the model's judgement, which the grounding
220
+ check cannot corroborate.
221
+
222
+ [info] extract_llm ItemList[0].Discount
223
+ ItemList[0].Discount was not found in the document: the model returned no value
224
+ for it, so it is left empty rather than filled with a guess.
225
+
226
+ [info] extract_llm ItemList[0].CesAmt (same wording)
227
+ [info] extract_llm ValDtls.CesVal (same wording)
228
+ [info] extract_llm ValDtls.RndOffAmt (same wording)
229
+
230
+ [info] pipeline BuyerDtls.Pos
231
+ BuyerDtls.Pos (place of supply) was not read from the document — build 2 does not
232
+ extract it — so it was assumed equal to the buyer's registered state code (27).
233
+ A genuine bill-to/ship-to supply, where the goods go to a different state from
234
+ the one the buyer is registered in, has a different place of supply, and the
235
+ CGST/SGST-versus-IGST split follows the place of supply.
236
+ ```
237
+
238
+ Nothing in that list means the payload is wrong. Each one names something the tool could
239
+ not corroborate, so you know where to look. The four arithmetic validators raised nothing,
240
+ which is what silence from them means.
241
+
242
+ On an invoice whose template omits a column — an intra-state invoice with no IGST column,
243
+ or one that prints a taxable value but no separate gross — you will also see a `pipeline`
244
+ note saying the field was derived rather than read, and `field_provenance` will record it
245
+ as `"source": "derived"`.
246
+
247
+ ### Provenance
248
+
249
+ `extraction_meta.field_provenance` carries an entry for **every** field in the payload — 45
250
+ for this invoice — saying which stage produced it and, for a scanned page, the OCR
251
+ confidence of the text it was read from:
252
+
253
+ ```json
254
+ {
255
+ "SellerDtls.Gstin": { "source": "regex", "ocr_confidence": null },
256
+ "SellerDtls.Stcd": { "source": "derived", "ocr_confidence": null },
257
+ "ItemList[0].PrdDesc": { "source": "llm", "ocr_confidence": null },
258
+ "ItemList[0].IsServc": { "source": "derived", "ocr_confidence": null },
259
+ "BuyerDtls.Pos": { "source": "assumed", "ocr_confidence": null }
260
+ }
261
+ ```
262
+
263
+ On a scanned page the same fields carry real numbers — 0.86 to 0.96 on a clean 300 dpi
264
+ render — and the **lowest** confidence across a field's words is the one recorded.
265
+
266
+ | `source` | Meaning |
267
+ |---|---|
268
+ | `regex` | Confirmed deterministically, structurally certain |
269
+ | `llm` | Structured by the model, then verified against the document text |
270
+ | `derived` | Follows by rule from values that were read; not printed on the page |
271
+ | `assumed` | Neither read nor derived — an assumption the tool names explicitly |
272
+
273
+ ---
274
+
275
+ ## Development
276
+
277
+ ```bash
278
+ .venv/Scripts/python.exe -m pytest -q -W error
279
+ ```
280
+
281
+ 1610 tests across ten modules, passing with warnings treated as errors. The LLM stage takes
282
+ an injected client, so the whole suite runs with no API key and no network.
283
+
284
+ | Module | What it does |
285
+ |---|---|
286
+ | `gstin.py` | Structure and mod-36 checksum |
287
+ | `state_codes.py` | State code table, including discontinued 25 and legacy 28 |
288
+ | `schema.py` | INV-01 pydantic models, `extra="forbid"` throughout |
289
+ | `validators.py` | The four arithmetic and tax-split checks |
290
+ | `ingest.py` | Per-page routing and the detect-and-refuse rules |
291
+ | `ocr.py` | Tesseract with per-word confidence mapped onto character spans |
292
+ | `extract_rules.py` | Deterministic extraction: GSTINs, parties, number, date, HSN |
293
+ | `extract_llm.py` | The LLM stage and the grounding check |
294
+ | `pipeline.py` | End-to-end assembly |
295
+ | `server.py` | The MCP server |
296
+
297
+ ## Licence note
298
+
299
+ This project depends on **PyMuPDF**, which is AGPL-3.0. That is a deliberate choice, made
300
+ because PyMuPDF opens image files directly as one-page documents and gave more reliable
301
+ text-layer detection than the alternatives. If you intend to distribute this tool as part
302
+ of a closed-source product, check that licence first.
@@ -0,0 +1 @@
1
+ """GST e-invoice extraction: Indian tax invoices in, INV-01 JSON plus validation warnings out."""