MCP server for the Storno.ro e-invoicing API — enables AI tools (Claude, Cursor, Windsurf) to manage invoices, clients, companies, and the full e-Factura workflow
Server defines 9 tools with explicit schemas, descriptions, and handlers. Most tools follow verb_noun naming and have actionable descriptions (average ~250 chars, within baseline 194±198). All input schemas use Zod with typed parameters. However, several tools lack output schema documentation, and some parameter descriptions are incomplete or domain-specific without sufficient context for cross-domain agents. Tool names are action-oriented (anaf_nomenclator_*, document_*, declaration_*) and domain-appropriate for Romanian tax/legal compliance, but naming conventions mix verb-first (document_generate, declaration_build) with noun-first (anaf_nomenclator_judete), reducing consistency. Error handling is minimal, tools delegate to apiRequest() and formatResponse() with no visible recovery guidance or actionable error messages. Security is strong: no credentials in parameters, noAuth flag for public endpoints, and file I/O validation in document_generate. Composition is good: tools chain logically (nomenclators → document_generate; form_spec → declaration_build), outputs include necessary IDs for downstream calls.
ANAF county nomenclator (cod judet as used in declaration XSDs: 40 = Municipiul Bucuresti, 13 = Constanta …) with the fiscal offices (organe fiscale, ufisc codes) of each county. Public, served from Storno's local mirror of ANAF's nomenclators, no account needed.
ANAF locality nomenclator for a county: cod_localit (as required by declaration XSDs, e.g. C168 cod_localit_L), SIRUTA and town-hall codes. Optional q filters by name, diacritics-insensitive ("sector 6", "cluj"). Public, local mirror.
ANAF street nomenclator for a locality: cod_strada + name (e.g. C168 cod_strada_C). q filters by word prefix, diacritics-insensitive ("maniu" finds "Bld. Iuliu Maniu"). Streets are cached locally on first use per locality. Public.
Build an ANAF declaration from plain JSON (schema from declaration_form_spec): Storno writes the XML, applies its own rules (required fields, address codes, quotas, postal code, tenant CNP …), does the arithmetic (D212: 20 % forfait, 10 % tax, CASS tiers on the minimum wage), validates it with ANAF's DUKIntegrator and, for C168, with ANAF's online validator behind the web form (the authoritative BR-C168 rules). Returns valid, xml, issues[{level: error|warning|info, code, field, message}] (info = computed amounts to explain to the user) and validation{duk, anafOnline}. Loop: fix issues → build again until valid=true, then declaration_pdf. Public, nothing stored, 60 requests/hour per IP.
Output schemas not documented. Tools like anaf_nomenclator_judete, document_types, declaration_form_versions, and declaration_forms return unspecified structures. LLMs cannot plan downstream calls or validate response fields without documented output schemas.
Error handling delegates entirely to apiRequest/formatResponse with no visible recovery guidance. Errors appear to return raw API responses without actionable instructions. Tools should classify errors (retryable, user-fixable, fatal) and provide corrective actions.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | B | 72 | <=2025-11-25 | v2 |
| 2026-03-09 | D | 58 | - | v1 |
Everything needed to fill an ANAF declaration form correctly: the JSON input schema Storno expects (fields, required/optional, codes and their meaning, hints), how it maps to the ANAF XML (namespace, every XSD attribute with type and constraints), the business rules ANAF enforces (including the web-form rules the DUK validator misses, e.g. BR-C168-00991/0041/005911), the filing steps and a complete example. Read it before declaration_build. C168 addresses need the codes from anaf_nomenclator_*; never invent CNPs or CUIs.
ANAF's current version of every declaration form (validator J and PDF P) as DUKIntegrator's own manifest lists them, with what changed lately (changedRecently: forms Storno builds that changed in the last 30 days) and whether this Storno installation's validators lag behind ANAF (localOutdated). Check it before building or filing when a rejection mentions the form version. Public, no account.
ANAF declaration forms Storno can build from plain JSON for you (today: C168 rent contract registration/amendment/termination, D212 Declarația unică for rent income with tax and CASS computed). Returns type, title and description of each form. Public, no account.
Generate a standard legal document as PDF (and HTML) from fields: 'conventie_incetare_inchiriere' (fields: data_conventie?, locator{nume, adresa, ci_serie?, ci_numar?, cnp?}, locatar{same}, contract{numar, data, adresa_imobil, numar_inregistrare_anaf?, data_inregistrare_anaf?}, data_incetare, termen_utilitati_zile?, garantie{suma?, valuta?, termen_zile?}) or 'declaratie_incetare_contract' (locator{nume, adresa, cnp?}, locatar{nume, cnp?}, contract{numar, data, adresa_imobil, data_inceput, data_sfarsit, chirie?, valuta?, numar_inregistrare_anaf?, data_inregistrare_anaf?}, data_incetare, motiv?, motiv_detalii?, organ_fiscal?, data_declaratie?). Dates as dd.mm.yyyy. Pass outFile to save the PDF locally (then sign it with agent_sign_pdf or have it signed by hand); otherwise the PDF comes back base64. Public, nothing stored.
Standard Romanian legal documents Storno can generate from structured fields (public, nothing stored): conventie_incetare_inchiriere (rental termination agreement between locator and locatar) and declaratie_incetare_contract (the locator's sworn statement that a rental contract ended, used as the mandatory C168 attachment). Returns each type with its required fields.
Inconsistent naming convention. Tools mix verb-first (document_generate, declaration_build) with noun-first (anaf_nomenclator_*). Consistent verb_noun pattern (e.g., list_counties, get_localities, generate_document) would improve clarity and LLM parsing.
document_generate description includes extensive field specifications embedded in the description text (270+ chars). This violates the pattern of keeping descriptions 10 - 1024 chars; should migrate field specs to a separate discovery tool (document_get_schema) or parameter schema documentation.
Parameter descriptions lack format/constraint details. E.g., anaf_nomenclator_localitati accepts 'judet' (county code) but description only says 'County code, e.g. 40', no mention of format (numeric, 1-2 digits?), valid ranges, or how to discover valid values. Similar gaps in declaration_build 'input' parameter.
document_generate's 'fields' parameter is z.record(z.unknown()), completely untyped. While the description mentions field names, no schema validation occurs, and the LLM has no type information. Should provide a union of strict schemas for each document type or a discovery endpoint.
declaration_build's 'input' parameter is z.object().describe('The form input (see declaration_form_spec ...)'), deferring all validation to the handler. The schema provides zero type safety at the MCP level. Downstream API validation may reject fields without the LLM having guidance beforehand.