Skip to main content
Glama
411sst

gst-einvoice-mcp

by 411sst

GST e-invoice extraction

Turns an Indian GST tax invoice into the government's INV-01 JSON payload, and tells you exactly what it could not read.

Ships as an MCP server with three tools, so an agent can parse a document, check a GSTIN, or re-validate a payload it already holds.

It produces a submission-ready payload, not a filed invoice. There is no IRN here. An Invoice Reference Number is issued by the government's Invoice Registration Portal after you submit the payload to it. Nothing in this repository talks to the IRP.


The idea

An extraction tool that quietly guesses is worse than one that says it cannot read a field. A hallucinated digit in a GSTIN that still passes its checksum, or a line item that was never on the page, is the failure that costs an accountant real money — and it is invisible precisely because it looks right.

So the design has one rule: the model may structure text, it may never invent values. That is enforced twice. The prompt says it, and then every value the model returns is checked back against the document text before it is kept. A value with no source in the document is replaced with null and reported, however plausible it looks. If that field is mandatory in INV-01, no payload is produced at all.

Everything the tool knows about its own work — how each page was read, where each field came from, what it was unsure about — travels beside the payload in extraction_meta, never inside it. The payload stays strictly spec-pure, because the government API rejects unknown keys.

Read LIMITATIONS.md before trusting the output. It is specific about what the tool cannot corroborate, and what that costs you.


Related MCP server: Kontor MCP

Install

Requires Python 3.13 and Tesseract OCR as a system dependency.

# Tesseract (Windows)
winget install UB-Mannheim.TesseractOCR

# Tesseract (Debian/Ubuntu)
sudo apt-get install -y tesseract-ocr

# Tesseract (macOS)
brew install tesseract
python -m venv .venv
.venv/Scripts/activate        # Windows
# source .venv/bin/activate   # Linux / macOS
pip install -e .

Tesseract does not need to be on PATH: the OCR module looks there first, then at the standard Windows install location, and raises an actionable error naming both if neither works.

Environment

Variable

Required

Purpose

GROQ_API_KEY

yes

Stage 2 reads the line-item table through Groq

GST_MCP_MODEL

recommended

Pin the model your key can reach

GST_MCP_TRANSPORT

no

stdio (default), sse, or streamable-http

The default model is openai/gpt-oss-120b, which is what this release was validated against. It works without configuration.

Still set GST_MCP_MODEL in a deployment. A hard-coded model identifier expires silently when the provider retires it, and the failure arrives as an HTTP 404 that reads like a bad key rather than a stale constant. Pin the model you have access to, and check it against Groq's deprecation notices.


MCP client configuration

pip install puts a gst-einvoice-mcp command on your PATH, so a client only needs to name it:

{
  "mcpServers": {
    "gst-einvoice": {
      "command": "gst-einvoice-mcp",
      "env": {
        "GROQ_API_KEY": "your-key-here",
        "GST_MCP_MODEL": "openai/gpt-oss-120b"
      }
    }
  }
}

Running from a clone rather than an install? Point command at the interpreter inside your virtual environment and invoke the module directly:

{
  "mcpServers": {
    "gst-einvoice": {
      "command": "/absolute/path/to/.venv/bin/python",
      "args": ["-m", "gst_einvoice.server"],
      "env": {
        "GROQ_API_KEY": "your-key-here"
      }
    }
  }
}

On Windows that interpreter path ends \.venv\Scripts\python.exe.

Tools

Tool

Takes

Gives back

parse_invoice

a file path, optional tolerance

the INV-01 payload, missing fields, refusals, and extraction_meta

validate_gstin

a GSTIN

structure, checksum, state code, PAN, and the reason on failure

validate_payload

an INV-01 payload

the four consistency checks over a payload you already hold

parse_invoice has three normal outcomes, and only the first gives you a payload:

  1. A payload plus warnings. Usable, but the warnings say which fields were read at low OCR confidence, which were assigned by position rather than by a label, and which were derived rather than printed.

  2. No payload, missing_fields populated. A mandatory field could not be read. Nothing was invented to fill the gap, which is why there is no payload.

  3. No payload, refusals populated. Export, SEZ and foreign-currency invoices are refused by design, with a message saying what was detected and what to do instead.


Worked example

The invoice, as a text-layer PDF:

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
Bill To: Kanchan Electricals LLP
GSTIN 27AABCB5507N1ZJ
18 Connaught Place
Pune 411005
Sl 1 Laptop Stand HSN/SAC: 8471 Qty 4 NOS Rate 1500.00
Taxable 6000.00  GST 18%  CGST 540.00  SGST 540.00  IGST 0.00  Line Total 7080.00
Taxable 6000.00  CGST 540.00  SGST 540.00  IGST 0.00
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("invoice.pdf", client=make_client())

The 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": "18 Connaught Place",
    "Loc": "Pune",
    "Pin": 411005,
    "Stcd": "27",
    "Pos": "27"
  },
  "ItemList": [
    {
      "SlNo": "1",
      "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
  }
}

The warnings block

This is the half most tools do not give you. Seven entries, from the run above, one warning and six info:

[warning] extract_llm  ItemList[0].SlNo
    "1" is a single character. It is printed as a token of its own in the document,
    which is why ItemList[0].SlNo was kept rather than dropped, but one character
    matches almost any page by accident, so the corroboration is weak. Confirm it
    against the invoice by eye — on a line item, that means the row numbering.

[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 the model's judgement, which the grounding
    check cannot corroborate.

[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.

Nothing in that list means the payload is wrong. Each one names something the tool could not corroborate, so you know where to look. The four arithmetic validators raised nothing, which is what silence from them means.

On an invoice whose template omits a column — an intra-state invoice with no IGST column, or one that prints a taxable value but no separate gross — you will also see a pipeline note saying the field was derived rather than read, and field_provenance will record it as "source": "derived".

Provenance

extraction_meta.field_provenance carries an entry for every field in the payload — 45 for this invoice — saying which stage produced it and, for a scanned page, the OCR confidence of the text it was read from:

{
  "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 }
}

On a scanned page the same fields carry real numbers — 0.86 to 0.96 on a clean 300 dpi render — and the lowest confidence across a field's words is the one recorded.

source

Meaning

regex

Confirmed deterministically, structurally certain

llm

Structured by the model, then verified against the document text

derived

Follows by rule from values that were read; not printed on the page

assumed

Neither read nor derived — an assumption the tool names explicitly


Development

pytest -q -W error

1610 tests across ten modules, passing with warnings treated as errors. The LLM stage takes an injected client, so the whole suite runs with no API key and no network.

Module

What it does

gstin.py

Structure and mod-36 checksum

state_codes.py

State code table, including discontinued 25 and legacy 28

schema.py

INV-01 pydantic models, extra="forbid" throughout

validators.py

The four arithmetic and tax-split checks

ingest.py

Per-page routing and the detect-and-refuse rules

ocr.py

Tesseract with per-word confidence mapped onto character spans

extract_rules.py

Deterministic extraction: GSTINs, parties, number, date, HSN

extract_llm.py

The LLM stage and the grounding check

pipeline.py

End-to-end assembly

server.py

The MCP server

Licence note

This project depends on PyMuPDF, which is AGPL-3.0. That is a deliberate choice, made because PyMuPDF opens image files directly as one-page documents and gave more reliable text-layer detection than the alternatives. If you intend to distribute this tool as part of a closed-source product, check that licence first.

Available Tools

3 tools
parse_invoiceParse an Indian GST tax invoice into an INV-01 payloadA

Read one Indian GST tax invoice (PDF or image) and return the government's INV-01 JSON payload for it, together with the evidence behind every field.

Accepts .pdf, .png, .jpg, .jpeg, .tif, .tiff and .bmp. Pages that carry a text layer are read directly; scanned pages go through OCR, which is slower (budget a few seconds per scanned page).

Returns an object with four keys: invoice the INV-01 payload, or null when none could be produced missing_fields INV-01 paths that are mandatory but could not be read refusals why the document is out of scope, when it is extraction_meta per-page read method, per-field provenance, and warnings

THREE OUTCOMES ARE NORMAL, AND ONLY THE FIRST GIVES YOU A PAYLOAD.

  1. A payload plus warnings. Usable, but read the warnings: they say which fields were read at low OCR confidence, which were assigned by document position rather than by a label, and which were derived rather than printed.

  2. No payload, with missing_fields populated. A mandatory field could not be read from the document. Nothing was invented to fill the gap, which is why there is no payload at all.

  3. No payload, with refusals populated. Export and SEZ invoices and foreign-currency invoices are refused by design; the refusal message says what was detected, which field revealed it, and what to do instead.

The payload is submission-ready in shape, but this tool does NOT file it and does NOT return an IRN. An IRN is issued by the government's Invoice Registration Portal after you submit the payload there.

Requires a Groq API key in the environment; stage 2 uses an LLM to read the line-item table, and every value it returns is checked back against the document text before it is kept.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYes
toleranceNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description fully discloses behavioral traits: it explains OCR behavior for scanned pages, the LLM stage with verification, the requirement for a Groq API key, and the three possible outcomes including refusals. It also explicitly states it does not file or return an IRN, adding critical transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every section is purposeful: the opening states the core function, file types are enumerated, outcomes are clearly separated, and constraints are highlighted. It is front-loaded with the main purpose and uses bullet-like structure for readability without unnecessary repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the description is remarkably complete. It covers input formats, processing behavior, output structure (four keys), edge cases (refusals, missing fields), dependencies (API key), and limitations (no IRN). Even with an output schema present, the description explains what an agent needs to know to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It indirectly explains the 'path' parameter by listing accepted file extensions, but it does not mention the 'tolerance' parameter at all, leaving it unexplained. The description adds value for path but fails to cover tolerance, resulting in a significant gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific verb 'Read' and resource 'Indian GST tax invoice' and explicitly defines the output as the INV-01 JSON payload with evidence. It differentiates itself from sibling validation tools by focusing on extraction, making its purpose unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

While the description gives clear context on when to use it (parsing invoices) and describes the three outcomes, it does not explicitly mention the sibling tools or provide when-not-to-use guidance. However, the purpose is so distinct from validation that usage is implied; still, no explicit alternatives are stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

validate_gstinValidate a GSTIN's structure and check digitA

Check one GSTIN (the 15-character Indian GST registration number) against its structural rules and its mod-36 check character, without touching any document.

Returns is_valid, structural_ok, checksum_ok, the state code and its name, the embedded PAN, and a human-readable reason when it fails. checksum_ok is null, not false, when the structure itself failed: the checksum was never evaluated, and reporting two failures where one check ran would be misleading.

State code 25 resolves to a distinct 'discontinued' status rather than 'unknown', because a 25 means either a misread or a genuine pre-2020 record, and the caller needs to tell those apart. State code 28 is legacy-but-valid: pre-2014 Andhra Pradesh registrations remain legitimate.

IMPORTANT: a passing checksum proves the number is well-formed, NOT that the registration exists or is currently active. Only the GST portal can tell you that.

ParametersJSON Schema
NameRequiredDescriptionDefault
gstinYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full behavioral burden and does so thoroughly. It discloses the null-vs-false semantics of checksum_ok on structural failure, explains state code 25 and 28 edge cases, and clearly warns that a well-formed GSTIN does not imply an active registration.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is substantial but every sentence earns its place: core purpose, return fields, null semantics, state-code edge cases, and a necessary interpretive caveat. Important behavioral nuances are front-loaded and clearly organized.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter validation tool with an output schema present, this description is complete. It covers validation scope, failure semantics, special state-code behavior, and the critical limitation about registration existence, leaving no practical gap for an agent selecting or invoking the tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema provides only the parameter name, type, and required flag with 0% description coverage, so the description must compensate. It does by explaining that gstin is a 15-character Indian GST registration number subject to structural and mod-36 checks, which gives the agent enough context to supply an appropriate value, though no format example is given.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb and resource: 'Check one GSTIN ... against its structural rules and its mod-36 check character,' which clearly distinguishes this from parsing invoices or validating arbitrary payloads. The scope is explicit and immediately actionable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description establishes that this is a single-GSTIN structural validator that 'touches no document' and that existence or active status must be checked via the GST portal. However, it never explicitly names or contrasts sibling tools like parse_invoice or validate_payload, so the when-to-use-versus-alternatives guidance is only implied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

validate_payloadRun the arithmetic and tax-split checks over an INV-01 payloadA

Take an INV-01 payload you already have and run the same consistency checks the parser runs, without re-reading any document. Useful for a payload you assembled yourself, or one you edited after parsing.

Four checks run: each item's total against its own components; the invoice total against the value block; each value-block total against the sum of the item fields; and the CGST/SGST-versus-IGST split against the two parties' state codes.

Comparisons use a rupee tolerance (0.05 by default), never exact equality, because real invoices round to the nearest rupee and exact comparison would flag almost every genuine document.

Returns valid (true when nothing was flagged), the warning list with the exact field path for each, and schema_error when the payload does not fit INV-01 at all. The tax-split check reports itself as skipped, as an informational note rather than a warning, under reverse charge and when the place of supply differs from the buyer's registered state, because it cannot evaluate those cases.

ParametersJSON Schema
NameRequiredDescriptionDefault
invoiceYes
toleranceNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full responsibility and delivers: it enumerates all four checks, explains the rupee tolerance and its rationale, and details the return fields (valid, warning list, schema_error) and the specific conditions under which the tax-split check is skipped. This is exceptionally transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but well-structured and front-loaded with the core purpose. Every sentence adds information, and the paragraphs organize checks, tolerance, and return behavior. It is not padded, though it could be trimmed slightly without losing substance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite having an output schema, the description explicitly covers the return semantics (valid, warning list, schema_error) and the nuanced skip behavior, which the output schema might not fully convey. For a validation tool with complex checks and edge cases, nothing needed for correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must explain both parameters. It does: 'invoice' is clearly the INV-01 payload, and 'tolerance' is described as the rupee tolerance with a default of 0.05 and the reason for it. The description adds meaning well beyond the bare schema definitions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb ('run the consistency checks') over a specific resource ('an INV-01 payload you already have'), and immediately distinguishes itself from parse_invoice by noting it does so 'without re-reading any document'. The purpose is unmistakable and separate from both siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly describes the intended use case ('payload you assembled yourself, or one you edited after parsing'), which is a clear when-to-use signal. It does not explicitly name alternatives or say when not to use it, but the contrast with parse_invoice is strongly implied by the opening phrase, so the guidance is adequate though not exhaustive.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.0
    • First observedparse_invoice
    • First observedvalidate_gstin
    • First observedvalidate_payload

TDQS

A4.6/5.0

Scored across 3 tools

Disambiguation5/5

Each tool targets a distinct stage of the e-invoicing workflow: validating a GSTIN, parsing an invoice document into an INV-01 payload, and validating a payload's internal consistency. There is no functional overlap between them.

Naming Consistency5/5

All three tool names follow a consistent verb_noun pattern: validate_gstin, parse_invoice, validate_payload. The verbs and nouns clearly describe the action and resource, with no mixed conventions.

Tool Count5/5

Three tools is well-scoped for a focused GST e-invoice MCP server covering the core pipeline: GSTIN validation, invoice parsing, and payload validation. Each tool addresses a distinct need without bloat or obvious missing essentials for the stated purpose.

Completeness4/5

The tool set covers the main pre-filing workflow: validating the seller's GSTIN, extracting an INV-01 payload from an invoice document, and validating that payload before submission. It does not include submission or IRN generation by design, but that is explicitly delegated to the government portal, so the gap is intentional.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    Extracts structured JSON from receipts and invoices with Australian GST/ABN validation, per-line tax codes, confidence scores, and rationale.
    2
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to locally parse, validate, audit, explain, generate, and convert XRechnung and ZUGFeRD/Factur-X e-invoices using official rule sets, fully offline with no API keys required.
    Apache 2.0
  • A
    license
    A
    quality
    A
    maintenance
    Reads and validates any European e-invoice a business receives — XRechnung, UBL, CII, ZUGFeRD/Factur-X PDF, Peppol BIS 3, FatturaPA, KSeF FA(3) — into canonical EN 16931 JSON with plain-language fix hints in EN/DE/PL/IT/FR, plus PDF, CSV and DATEV export. Nothing is stored; works without a key on a small daily quota.
    2
    5
    MIT