GST e-invoice extraction
Convierte facturas fiscales GST de la India, en PDF o escaneadas, en el payload JSON INV-01 del gobierno. Cada valor que devuelve el modelo se verifica contra el texto del documento, de modo que un campo que no puede corroborar se reporta como ausente en lugar de inventado; un campo obligatorio faltante significa que no hay payload en absoluto. La procedencia por campo y la confianza del OCR viajan junto al payload, nunca dentro de él.
Documentación
gst-einvoice-mcp 0.1.5
pip install gst-einvoice-mcp==0.1.5
Extracción de facturas electrónicas GST
Convierte una factura fiscal GST india en el payload JSON INV-01 del gobierno, y te dice exactamente qué no pudo leer.
Se distribuye como un servidor MCP con tres herramientas, para que un agente pueda analizar un documento, verificar un GSTIN o revalidar un payload que ya tenga.
Produce un payload listo para enviar, no una factura presentada. Aquí no hay IRN. Un Número de Referencia de Factura lo emite el Portal de Registro de Facturas del gobierno después de que envíes el payload. Nada en este repositorio se comunica con el IRP.
La idea
Una herramienta de extracción que adivina en silencio es peor que una que dice que no puede leer un campo. Un dígito alucinado en un GSTIN que aún así pasa su suma de verificación, o una línea de artículo que nunca estuvo en la página, es el fallo que le cuesta dinero real a un contador — y es invisible precisamente porque parece correcto.
Así que el diseño tiene una regla: el modelo puede estructurar texto, nunca puede inventar valores. Eso se aplica dos veces. El prompt lo dice, y luego cada valor que devuelve el modelo se verifica contra el texto del documento antes de conservarse. Un valor sin fuente en el documento se reemplaza con null y se reporta, por plausible que parezca. Si ese campo es obligatorio en INV-01, no se produce ningún payload.
Todo lo que la herramienta sabe sobre su propio trabajo — cómo se leyó cada página, de dónde vino cada campo, qué le generaba incertidumbre — viaja junto al payload en extraction_meta, nunca dentro de él. El payload se mantiene estrictamente puro según la especificación, porque la API del gobierno rechaza claves desconocidas.
Lee LIMITATIONS.md antes de confiar en la salida. Es específico sobre lo que la herramienta no puede corroborar y qué te cuesta eso.
Instalación
Requiere Python 3.11 o superior y Tesseract OCR como dependencia del sistema.
# Tesseract (Windows)
winget install UB-Mannheim.TesseractOCR
# Tesseract (Debian/Ubuntu)
sudo apt-get install -y tesseract-ocr
# Tesseract (macOS)
brew install tesseract
# From PyPI -- installs the \`gst-einvoice-mcp\` command the MCP client configuration below names
pip install gst-einvoice-mcp
# Or from a clone, for development (editable)
python -m venv .venv
.venv/Scripts/activate # Windows
# source .venv/bin/activate # Linux / macOS
pip install -e .
Tesseract no necesita estar en PATH: el módulo OCR lo busca allí primero, luego en la ubicación estándar de instalación de Windows, y genera un error accionable que nombra ambas si ninguna funciona.
Entorno
| Variable | Requerida | Propósito |
|---|---|---|
GROQ_API_KEY | sí | La etapa 2 lee la tabla de líneas de artículo a través de Groq |
GST_MCP_MODEL | recomendada | Fija el modelo al que tu clave puede acceder |
GST_MCP_TRANSPORT | no | stdio (predeterminado), sse o streamable-http |
El modelo predeterminado es openai/gpt-oss-120b, que es contra el que se validó esta versión. Funciona sin configuración.
Aun así, establece
GST_MCP_MODELen un despliegue. Un identificador de modelo codificado expira silenciosamente cuando el proveedor lo retira, y el fallo llega como un HTTP 404 que parece una clave incorrecta en lugar de una constante obsoleta. Fija el modelo al que tienes acceso y verifícalo contra los avisos de desaprobación de Groq.
Configuración del cliente MCP
pip install coloca un comando gst-einvoice-mcp en tu PATH, así que un cliente solo necesita nombrarlo:
{
"mcpServers": {
"gst-einvoice": {
"command": "gst-einvoice-mcp",
"env": {
"GROQ_API_KEY": "your-key-here",
"GST_MCP_MODEL": "openai/gpt-oss-120b"
}
}
}
}
¿Ejecutando desde un clon en lugar de una instalación? Apunta command al intérprete dentro de tu entorno virtual e invoca el módulo directamente:
{
"mcpServers": {
"gst-einvoice": {
"command": "/absolute/path/to/.venv/bin/python",
"args": ["-m", "gst_einvoice.server"],
"env": {
"GROQ_API_KEY": "your-key-here"
}
}
}
}
En Windows, esa ruta de intérprete termina en \.venv\Scripts\python.exe.
Herramientas
| Herramienta | Recibe | Devuelve |
|---|---|---|
parse_invoice | una ruta de archivo, tolerancia opcional | el payload INV-01, campos faltantes, rechazos y extraction_meta |
validate_gstin | un GSTIN | estructura, suma de verificación, código de estado, PAN y el motivo del fallo |
validate_payload | un payload INV-01 | las cuatro verificaciones de consistencia sobre un payload que ya tienes |
parse_invoice tiene tres resultados normales, y solo el primero te da un payload:
- Un payload más advertencias. Utilizable, pero las advertencias indican qué campos se leyeron con baja confianza de OCR, cuáles se asignaron por posición en lugar de por una etiqueta, y cuáles se derivaron en lugar de imprimirse.
- Sin payload,
missing_fieldspoblado. Un campo obligatorio no pudo leerse. No se inventó nada para llenar el vacío, que es por qué no hay payload. - Sin payload,
refusalspoblado. Las facturas de exportación, SEZ y moneda extranjera se rechazan por diseño, con un mensaje que indica qué se detectó y qué hacer en su lugar.
Ejemplo práctico
La factura, como PDF con capa de texto:
TAX INVOICE
Seller: Nimbus Components Pvt Ltd
GSTIN 27AAPFU0939F1ZV
Plot 14 MIDC Andheri East
Mumbai 400093
Invoice No: INV-2026-0042
Invoice Date: 17/04/2026
Buyer: Kanchan Electricals LLP
GSTIN 27AABCB5507N1ZJ
221 Laxmi Road Shivajinagar
Pune 411005
Sl No 01 Laptop Stand
Qty 4 NOS Rate 1500.00
Taxable 6000.00 GST 18%
CGST 540.00 SGST 540.00 IGST 0.00
HSN/SAC: 8471
Goods once sold will not be taken back or exchanged
Total Invoice Value 7080.00
from gst_einvoice.extract_llm import make_client
from gst_einvoice.pipeline import extract_invoice
# model defaults to openai/gpt-oss-120b; pass model=... to override
result = extract_invoice("sample_invoice.pdf", client=make_client())
El payload
{
"Version": "1.1",
"TranDtls": { "TaxSch": "GST", "SupTyp": "B2B" },
"DocDtls": { "Typ": "INV", "No": "INV-2026-0042", "Dt": "17/04/2026" },
"SellerDtls": {
"Gstin": "27AAPFU0939F1ZV",
"LglNm": "Nimbus Components Pvt Ltd",
"Addr1": "Plot 14 MIDC Andheri East",
"Loc": "Mumbai",
"Pin": 400093,
"Stcd": "27"
},
"BuyerDtls": {
"Gstin": "27AABCB5507N1ZJ",
"LglNm": "Kanchan Electricals LLP",
"Addr1": "221 Laxmi Road Shivajinagar",
"Loc": "Pune",
"Pin": 411005,
"Stcd": "27",
"Pos": "27"
},
"ItemList": [
{
"SlNo": "01",
"PrdDesc": "Laptop Stand",
"IsServc": "N",
"HsnCd": "8471",
"Qty": 4.0,
"Unit": "NOS",
"UnitPrice": 1500.0,
"TotAmt": 6000.0,
"Discount": 0.0,
"AssAmt": 6000.0,
"GstRt": 18.0,
"CgstAmt": 540.0,
"SgstAmt": 540.0,
"IgstAmt": 0.0,
"CesAmt": 0.0,
"StateCesAmt": 0.0,
"OthChrg": 0.0,
"TotItemVal": 7080.0
}
],
"ValDtls": {
"AssVal": 6000.0, "CgstVal": 540.0, "SgstVal": 540.0, "IgstVal": 0.0,
"CesVal": 0.0, "StCesVal": 0.0, "RndOffAmt": 0.0, "TotInvVal": 7080.0
}
}
El bloque de advertencias
Esta es la mitad que la mayoría de las herramientas no te dan. Seis entradas, de la ejecución anterior, todas info:
[info] extract_llm ItemList[0].HsnCd
"8471" is not printed as a code of its own in this row's text: it was grounded by
the HSN/SAC codes stage 1 confirmed, or by a longer number elsewhere on the page.
Which code belongs to which row is therefore the model's judgement and could not
be corroborated against the document. A code on the wrong row changes that row's
tax classification, so confirm it against the invoice.
[info] extract_llm ItemList[0].Discount
ItemList[0].Discount was not found in the document: the model returned no value
for it, so it is left empty rather than filled with a guess.
[info] extract_llm ItemList[0].CesAmt (same wording)
[info] extract_llm ValDtls.CesVal (same wording)
[info] extract_llm ValDtls.RndOffAmt (same wording)
[info] pipeline BuyerDtls.Pos
BuyerDtls.Pos (place of supply) was not read from the document -- build 2 does
not extract it -- so it was assumed equal to the buyer's registered state code
(27). A genuine bill-to/ship-to supply, where the goods go to a different state
from the one the buyer is registered in, has a different place of supply, and
the CGST/SGST-versus-IGST split follows the place of supply. Confirm it against
the document before filing.
Nada en esa lista significa que el payload esté mal. Cada una nombra algo que la herramienta no pudo corroborar, para que sepas dónde mirar. Los cuatro validadores aritméticos no generaron nada, que es lo que significa su silencio.
En una factura cuya plantilla omite una columna — una factura intraestatal sin columna IGST, o una que imprime un valor gravable pero no un total bruto separado — también verás una nota pipeline que indica que el campo se derivó en lugar de leerse, y field_provenance lo registrará como "source": "derived". Cinco reglas pueden hacer esto: el encabezado de impuesto que no puede aplicarse se establece en cero a partir de los dos códigos de estado; el total bruto de una fila se completa desde su valor gravable y descuento, y el valor gravable de una fila desde su total bruto, cada uno el inverso exacto del otro y nunca ambos en una misma fila; el total por línea en una factura de una sola línea que imprime su total solo al pie se completa desde la identidad del artículo INV-01; y un total de documento que el modelo omitió se completa desde su contraparte de fila, nuevamente solo en una factura de una sola línea. Las dos últimas se activan solo cuando el resultado concilia con el ValDtls.TotInvVal impreso, y un valor derivado nunca alimenta otra derivación a través del límite fila/totales. En una factura de varias líneas, un TotItemVal o total de documento faltante aún se reporta como faltante y no se produce ningún payload — consulta LIMITATIONS.md para saber por qué.
Procedencia
extraction_meta.field_provenance lleva una entrada para cada campo en el payload — 45 para esta factura — que indica qué etapa lo produjo y, para una página escaneada, la confianza de OCR del texto del que se leyó:
{
"SellerDtls.Gstin": { "source": "regex", "ocr_confidence": null },
"SellerDtls.Stcd": { "source": "derived", "ocr_confidence": null },
"ItemList[0].PrdDesc": { "source": "llm", "ocr_confidence": null },
"ItemList[0].IsServc": { "source": "derived", "ocr_confidence": null },
"BuyerDtls.Pos": { "source": "assumed", "ocr_confidence": null }
}
En una página escaneada, los mismos campos llevan números reales — 0.86 a 0.96 en una renderización limpia de 300 dpi — y la confianza más baja entre las palabras de un campo es la que se registra.
source | Significado |
|---|---|
regex | Confirmado determinísticamente, estructuralmente seguro |
llm | Estructurado por el modelo, luego verificado contra el texto del documento |
derived | Sigue por regla a partir de valores que se leyeron; no impreso en la página |
assumed | Ni leído ni derivado — una suposición que la herramienta nombra explícitamente |
Desarrollo
pytest -q -W error
1644 pruebas en diez módulos, pasando con advertencias tratadas como errores. La etapa LLM toma un cliente inyectado, así que toda la suite se ejecuta sin clave API y sin red.
La suite pasa tanto en 3.11 como en 3.13; 3.11 es el mínimo porque es la versión más baja en la que se resuelve todo el conjunto de dependencias, y se verificó ejecutando la suite allí en lugar de asumirlo.
Pruebas de cambios locales a través de un cliente MCP
El servidor MCP ejecuta lo que está instalado en site-packages, no tu árbol de trabajo. Un cliente MCP lanza el servidor a través del punto de entrada gst-einvoice-mcp, que se resuelve a la distribución instalada. Editar un archivo en el repositorio no cambia nada de lo que el servidor sirve hasta que reinstales, y no hay ningún error que te lo diga — la llamada a la herramienta tiene éxito y devuelve el comportamiento anterior.
Es fácil perder una hora en esto. Durante el trabajo de 0.1.2, el repositorio llevaba una nueva regla de derivación mientras el servidor aún servía 0.1.1, así que una llamada a la herramienta contra el código nuevo ejercitaba silenciosamente la ruta antigua y parecía mostrar que el cambio no había funcionado.
O reinstala después de cada cambio:
pip install -e . # editable, so later edits are picked up on server restart
o apunta el command del cliente MCP al virtualenv del propio repositorio en lugar de una instalación a nivel de usuario, para que el servidor y las pruebas ejecuten el mismo código:
// in your MCP client config
"command": "C:\\path\\to\\repo\\.venv\\Scripts\\gst-einvoice-mcp.exe"
De cualquier manera, reinicia el servidor MCP después de cambiar código — un servidor en ejecución mantiene los módulos que importó al inicio. Si un cambio parece no tener efecto, verifica qué copia se está sirviendo antes de buscar el error en tu código.
| Módulo | Qué hace |
|---|---|
gstin.py | Estructura y suma de verificación mod-36 |
state_codes.py | Tabla de códigos de estado, incluidos el descontinuado 25 y el heredado 28 |
schema.py | Modelos pydantic INV-01, extra="forbid" en todo |
validators.py | Las cuatro verificaciones aritméticas y de división de impuestos |
ingest.py | Enrutamiento por página y las reglas de detectar y rechazar |
ocr.py | Tesseract con confianza por palabra mapeada a intervalos de caracteres |
extract_rules.py | Extracción determinística: GSTIN, partes, número, fecha, HSN |
extract_llm.py | La etapa LLM y la verificación de fundamentación |
pipeline.py | Ensamblaje de extremo a extremo |
server.py | El servidor MCP |
Nota de licencia
Este proyecto depende de PyMuPDF, que es AGPL-3.0. Esa es una elección deliberada, hecha porque PyMuPDF abre archivos de imagen directamente como documentos de una página y dio una detección de capa de texto más confiable que las alternativas. Si tienes la intención de distribuir esta herramienta como parte de un producto de código cerrado, revisa esa licencia primero.
Fechas clave
Datos de PyPI
1 mantenedor
Datos de PyPI
Descarga el archivo para tu plataforma. Si no estás seguro de cuál elegir, aprende más sobre instalación de paquetes.
Distribución de fuente
gst_einvoice_mcp-0.1.5.tar.gz (187.3 kB ver detalles)
Distribución compilada
Si no estás seguro sobre el formato del nombre de archivo, aprende más sobre nombres de archivos wheel.
gst_einvoice_mcp-0.1.5-py3-none-any.whl (90.4 kB ver detalles)
Detalles para el archivo gst_einvoice_mcp-0.1.5.tar.gz.
Metadatos del archivo
- URL de descarga: gst_einvoice_mcp-0.1.5.tar.gz
- Fecha de carga: 16 de septiembre de 2026
- Tamaño: 187.3 kB
- Etiquetas: Fuente
- Cargado usando Publicación de Confianza? No
- Cargado vía:
twine/7.0.0 CPython/3.13.4
Hashes del archivo
| Algoritmo | Resumen hash | |
|---|---|---|
| SHA256 | aa8a2410dc8f55095385f946d05de488a167cad8fe69303dbaaa9eb0953947ee | Copiar |
| MD5 | df88853d70fce10ce69e2306e37aa293 | Copiar |
| BLAKE2b-256 | 46b015ed04dbe6c63661d03201ed40ba2403ea96e0dd10cc5a472656c7e2ac27 | Copiar |
Ver más detalles sobre el uso de hashes aquí.
Detalles para el archivo gst_einvoice_mcp-0.1.5-py3-none-any.whl.
Metadatos del archivo
- URL de descarga: gst_einvoice_mcp-0.1.5-py3-none-any.whl
- Fecha de carga: 16 de septiembre de 2026
- Tamaño: 90.4 kB
- Etiquetas: Python 3
- Cargado usando Publicación de Confianza? No
- Cargado vía:
twine/7.0.0 CPython/3.13.4
Hashes del archivo
| Algoritmo | Resumen hash | |
|---|---|---|
| SHA256 | 556679fc6d332720ff7e2d69729a7941ca8c4463b2ad42aeaf2e19a614362690 | Copiar |
| MD5 | 2442ea9ce546f9e3e233508ceabb2641 | Copiar |
| BLAKE2b-256 | 825504b5a974af3ef69eaa4b395c2e0805b8e2c88d7b36f7caa8294e5003aae5 | Copiar |