hacienda-cr MCP Server
hacienda-cr — 哥斯达黎加电子发票
最完整的哥斯达黎加电子发票开源工具包。
SDK + CLI + MCP服务器,用于向财政部(Ministerio de Hacienda)的v4.4 API签发电子凭证。
为什么选择 hacienda-cr?
在哥斯达黎加签发电子发票不应该是一件头疼的事。在OAuth2认证、生成带有特定命名空间的XML、XAdES-EPES数字签名、50位数字密钥以及状态轮询之间……有太多偶然的复杂性。
hacienda-cr 用一个工具包解决了所有这些问题:
SDK — 具有严格类型的TypeScript库:认证、XML、数字签名、增值税计算、发送和查询。
CLI — 命令行工具
hacienda,用于在终端中签发、签名、验证和查询。MCP服务器 — 模型上下文协议(Model Context Protocol)服务器,让AI助手(如Claude等)为您签发发票。
支持全部7种凭证类型 + 接收方消息。兼容沙箱和生产环境。
Related MCP server: mcp-sii
2分钟快速开始
选项1:SDK(面向开发者)
npm install @dojocoding/hacienda-sdkimport { HaciendaClient, DocumentType, Situation } from "@dojocoding/hacienda-sdk";
// 1. Crear el cliente
const client = new HaciendaClient({
environment: "sandbox",
credentials: {
idType: "02", // Cédula Jurídica
idNumber: "3101234567",
password: process.env.HACIENDA_PASSWORD!,
},
});
// 2. Autenticarse
await client.authenticate();
// 3. Generar la clave numérica
const clave = client.buildClave({
date: new Date(),
taxpayerId: "3101234567",
documentType: DocumentType.FACTURA_ELECTRONICA,
sequence: 1,
situation: Situation.NORMAL,
});
// 4. Construir XML, firmar y enviar (ver ejemplo completo abajo)选项2:CLI(从终端开票)
npm install -g @dojocoding/hacienda-cli
# Autenticarse
hacienda auth login --cedula-type 02 --cedula 3101234567
# Crear borrador interactivo
hacienda draft --interactive
# Validar antes de enviar
hacienda validate factura.json
# Enviar (vista previa primero)
hacienda submit factura.json --dry-run
# Consultar contribuyente
hacienda lookup 3101234567选项3:MCP服务器(面向AI助手)
npm install -g @dojocoding/hacienda-mcp
hacienda-mcp您可以对Claude说:"为Mi Empresa S.A.(税号3101234567)向Cliente S.R.L.(税号3109876543)创建一张发票,内容为2小时咨询服务,每小时₡50,000,含13%的增值税。"
支持的凭证类型
代码 | 凭证类型 | SDK构建器 |
| 电子发票 |
|
| 电子借记通知 |
|
| 电子贷记通知 |
|
| 电子小票 |
|
| 采购电子发票 |
|
| 出口电子发票 |
|
| 电子付款收据 |
|
— | 接收方消息(接受/拒绝) |
|
目录
SDK — 完整文档
HaciendaClient
主要入口点。协调认证、密钥生成和API操作。
import { HaciendaClient } from "@dojocoding/hacienda-sdk";
const client = new HaciendaClient({
// Requerido
environment: "sandbox", // "sandbox" | "production"
credentials: {
idType: "02", // "01"=Física, "02"=Jurídica, "03"=DIMEX, "04"=NITE
idNumber: "3101234567", // Cédula de 9-12 dígitos
password: process.env.HACIENDA_PASSWORD!,
},
// Opcional
p12Path: "/ruta/al/certificado.p12", // Para firma digital
p12Pin: process.env.HACIENDA_P12_PIN, // PIN del .p12
fetchFn: customFetch, // Implementación fetch personalizada
});选项在实例化时使用Zod进行验证。如果有问题,会抛出带有清晰细节的ValidationError。
OAuth2认证
Hacienda使用OAuth2 ROPC(资源所有者密码凭据)。SDK自动处理令牌的整个生命周期。
// Autenticarse (obtiene access + refresh token)
await client.authenticate();
// Verificar estado
console.log(client.isAuthenticated); // true
// Obtener token válido (refresca automáticamente si expiró)
const token = await client.getAccessToken();
// Forzar re-autenticación
client.invalidate();
await client.authenticate();令牌生命周期:
访问令牌约5分钟过期(缓存在内存中,提前30秒刷新)
刷新令牌持续约10小时
getAccessToken()透明地处理刷新
Hacienda环境:
环境 | API基础URL | IDP Realm | Client ID |
|
|
|
|
|
|
|
|
创建文档
电子发票的完整示例 — 其他类型的流程相同:
import {
buildFacturaXml,
calculateLineItemTotals,
calculateInvoiceSummary,
buildClave,
DocumentType,
Situation,
} from "@dojocoding/hacienda-sdk";
import type { LineItemInput } from "@dojocoding/hacienda-sdk";
// 1. Definir las líneas de detalle
const lineas: LineItemInput[] = [
{
numeroLinea: 1,
codigoCabys: "8310100000000", // Código CABYS (13 dígitos)
cantidad: 2,
unidadMedida: "Unid",
detalle: "Servicios de desarrollo web",
precioUnitario: 50000,
esServicio: true,
impuesto: [
{
codigo: "01", // IVA
codigoTarifaIVA: "08", // Tarifa general 13%
tarifa: 13,
},
],
},
{
numeroLinea: 2,
codigoCabys: "4321000000000",
cantidad: 1,
unidadMedida: "Unid",
detalle: "Laptop",
precioUnitario: 500000,
esServicio: false,
impuesto: [
{
codigo: "01",
codigoTarifaIVA: "08",
tarifa: 13,
},
],
descuento: [
{
montoDescuento: 25000,
codigoDescuento: "01",
naturalezaDescuento: "Descuento por volumen",
},
],
},
];
// 2. Calcular totales por línea (agrega montoTotal, subTotal, impuestoNeto, etc.)
const lineasCalculadas = lineas.map(calculateLineItemTotals);
// 3. Calcular resumen de factura (ResumenFactura)
const resumen = calculateInvoiceSummary(lineasCalculadas);
// 4. Generar la clave numérica
const clave = buildClave({
date: new Date(),
taxpayerId: "3101234567",
documentType: DocumentType.FACTURA_ELECTRONICA,
sequence: 1,
situation: Situation.NORMAL,
});
// 5. Consecutivo
const numeroConsecutivo = "00100001010000000001";
// 6. Armar la factura y generar XML
const factura = {
clave,
proveedorSistemas: "3101234567", // Cédula del proveedor de sistemas (v4.4)
codigoActividadEmisor: "620100",
numeroConsecutivo,
fechaEmision: new Date().toISOString(),
emisor: {
nombre: "Mi Empresa S.A.",
identificacion: { tipo: "02", numero: "3101234567" },
ubicacion: {
provincia: "1",
canton: "01",
distrito: "01",
otrasSenas: "100m norte del parque central",
},
correoElectronico: "facturacion@miempresa.co.cr",
},
receptor: {
nombre: "Cliente S.R.L.",
identificacion: { tipo: "02", numero: "3109876543" },
correoElectronico: "pagos@cliente.co.cr",
},
condicionVenta: "01", // Contado
detalleServicio: lineasCalculadas,
resumenFactura: {
...resumen,
// v4.4: los medios de pago van dentro del ResumenFactura, con monto
medioPago: [{ tipoMedioPago: "01", totalMedioPago: resumen.totalComprobante }],
},
};
const xml = buildFacturaXml(factura);XML验证:
import { validateFacturaInput } from "@dojocoding/hacienda-sdk";
const resultado = validateFacturaInput(datosFactura);
if (!resultado.valid) {
for (const err of resultado.errors) {
console.error(`${err.path}: ${err.message}`);
}
}增值税计算
用于计算税费、每行总额和汇总的工具,符合Hacienda的规定。所有金额四舍五入到5位小数。
import { round5, calculateLineItemTotals, calculateInvoiceSummary } from "@dojocoding/hacienda-sdk";
import type { LineItemInput, CalculatedLineItem, InvoiceSummary } from "@dojocoding/hacienda-sdk";
const item: LineItemInput = {
numeroLinea: 1,
codigoCabys: "8310100000000",
cantidad: 3,
unidadMedida: "Sp",
detalle: "Horas de consultoría",
precioUnitario: 75000,
esServicio: true,
impuesto: [{ codigo: "01", codigoTarifaIVA: "08", tarifa: 13 }],
};
const calculado: CalculatedLineItem = calculateLineItemTotals(item);
// calculado.montoTotal = 225000 (3 × ₡75.000)
// calculado.subTotal = 225000 (sin descuentos)
// calculado.impuestoNeto = 29250 (₡225.000 × 13%)
// calculado.montoTotalLinea = 254250 (₡225.000 + ₡29.250)
const resumen: InvoiceSummary = calculateInvoiceSummary([calculado]);
// resumen.totalServGravados = 225000
// resumen.totalImpuesto = 29250
// resumen.totalComprobante = 254250增值税豁免:
const itemExonerado: LineItemInput = {
// ...campos base
impuesto: [
{
codigo: "01",
codigoTarifaIVA: "08",
tarifa: 13,
exoneracion: {
tipoDocumento: "01",
numeroDocumento: "AL-001-2025",
nombreInstitucion: "99", // código de institución (Nota v4.4)
fechaEmision: "2025-01-01T00:00:00",
tarifaExonerada: 13, // puntos de tarifa exonerados
},
},
],
};支持的增值税税率: 0%、0.5%、1%、2%、4%、8%、13%(v4.4中的代码01-11)
数字密钥
每张电子凭证都需要一个唯一的50位数字密钥。SDK自动生成并解析它。
结构: [506][DDMMYY][12位税号][3位分支机构][5位终端][2位文档类型][10位连续号][1位情况][8位安全码]
import { buildClave, parseClave, DocumentType, Situation } from "@dojocoding/hacienda-sdk";
// Generar clave
const clave = buildClave({
date: new Date("2025-07-15"),
taxpayerId: "3101234567",
documentType: DocumentType.FACTURA_ELECTRONICA,
sequence: 42,
situation: Situation.NORMAL,
branch: "001", // Opcional, default "001"
pos: "00001", // Opcional, default "00001"
});
// => "50615072500310123456700100001010000000042112345678"
// Parsear clave existente
const parsed = parseClave(clave);
// parsed.countryCode => "506"
// parsed.date => Date(2025-07-15)
// parsed.taxpayerId => "003101234567"
// parsed.documentType => "01"
// parsed.sequence => 42
// parsed.situation => "1"
// parsed.securityCode => "12345678"情况代码:
1正常(标准在线发送)2应急(Hacienda系统故障)3无网络(离线)
XAdES-EPES数字签名
发送给Hacienda的每个XML都必须使用纳税人的.p12证书(RSA 2048 + SHA-256)进行XAdES-EPES签名。SDK处理整个签名过程。
import { readFileSync } from "node:fs";
import { signXml, signAndEncode, loadP12 } from "@dojocoding/hacienda-sdk";
const p12Buffer = readFileSync("/ruta/al/certificado.p12");
const pin = process.env.HACIENDA_P12_PIN!;
// Firmar XML (retorna XML firmado como string)
const xmlFirmado = await signXml(xml, p12Buffer, pin);
// Firmar y codificar en Base64 (listo para enviar a la API)
const xmlBase64 = await signAndEncode(xml, p12Buffer, pin);
// Cargar .p12 para inspeccionar el certificado
const credenciales = await loadP12(p12Buffer, pin);
// credenciales.privateKey — CryptoKey para firma
// credenciales.certificateDer — Certificado codificado en DER发送和状态查询
简化选项 — submitAndWait(推荐):
发送文档并等待Hacienda处理。自动处理轮询。
import { submitAndWait, HttpClient } from "@dojocoding/hacienda-sdk";
const httpClient = new HttpClient({
baseUrl: "https://api.comprobanteselectronicos.go.cr/recepcion-sandbox/v1",
getToken: () => client.getAccessToken(),
});
const resultado = await submitAndWait(
httpClient,
{
clave: "50601...",
fecha: new Date().toISOString(),
emisor: {
tipoIdentificacion: "02",
numeroIdentificacion: "3101234567",
},
comprobanteXml: xmlBase64Firmado,
},
{
pollIntervalMs: 3000, // Consultar cada 3 segundos (default)
timeoutMs: 60000, // Timeout a 60 segundos (default)
onPoll: (status, intento) => {
console.log(`Intento ${intento}: ${status.status}`);
},
},
);
if (resultado.accepted) {
console.log("¡Comprobante aceptado por Hacienda!");
} else {
console.log("Rechazado:", resultado.rejectionReason);
}细粒度选项 — 完全控制:
import { submitDocument, getStatus, isTerminalStatus } from "@dojocoding/hacienda-sdk";
// Enviar
const response = await submitDocument(httpClient, solicitud);
// Consultar estado
const status = await getStatus(httpClient, "50601...");
if (isTerminalStatus(status.status)) {
console.log("Estado final:", status.status);
}列出和查询凭证:
import { listComprobantes, getComprobante } from "@dojocoding/hacienda-sdk";
const lista = await listComprobantes(httpClient, {
offset: 0,
limit: 10,
fechaEmisionDesde: "2025-01-01",
fechaEmisionHasta: "2025-12-31",
});
const detalle = await getComprobante(httpClient, "50601...");指数退避重试:
import { withRetry } from "@dojocoding/hacienda-sdk";
const resultado = await withRetry(() => submitDocument(httpClient, solicitud), {
maxAttempts: 3,
delayMs: 1000,
backoff: "exponential",
});纳税人查询
使用Hacienda的公共经济活动API查询任何纳税人的信息(无需认证):
import { lookupTaxpayer } from "@dojocoding/hacienda-sdk";
const info = await lookupTaxpayer("3101234567");
console.log(info.nombre); // "MI EMPRESA S.A."
console.log(info.tipoIdentificacion); // "02"
for (const actividad of info.actividades) {
console.log(`${actividad.codigo}: ${actividad.descripcion} (${actividad.estado})`);
}配置管理
配置存储在~/.hacienda-cr/config.toml中,支持多个配置文件(例如:沙箱、生产、不同公司)。
import {
loadConfig,
saveConfig,
listProfiles,
deleteProfile,
getNextSequence,
resetSequence,
} from "@dojocoding/hacienda-sdk";
// Guardar un perfil
await saveConfig(
{
environment: "sandbox",
cedula_type: "02",
cedula: "3101234567",
p12_path: "/ruta/al/certificado.p12",
},
"miempresa",
);
// Cargar un perfil
const config = await loadConfig("miempresa");
// Listar perfiles
const perfiles = await listProfiles();
// Eliminar un perfil
await deleteProfile("perfil-viejo");
// Gestión de consecutivos (numeración automática)
const consecutivo = await getNextSequence("02", "3101234567", "01", "001", "00001");
await resetSequence("02", "3101234567", "01", "001", "00001");安全性: 密码和PIN从不存储在配置文件中。它们始终通过环境变量传递:
HACIENDA_PASSWORD— IDP密码HACIENDA_P12_PIN— .p12证书的PIN
结构化日志
内置日志器,具有可配置级别和JSON支持(适合生产环境)。
import { Logger, LogLevel, noopLogger } from "@dojocoding/hacienda-sdk";
const logger = new Logger({
level: LogLevel.DEBUG, // DEBUG, INFO, WARN, ERROR, SILENT
format: "text", // "text" | "json"
context: "mi-app",
});
logger.debug("Token refrescado", { expiresIn: 300 });
logger.info("Comprobante enviado", { clave: "50601..." });
logger.warn("Rate limit acercándose");
logger.error("Envío falló", { statusCode: 500 });
// Logger silencioso (suprime toda salida)
const silencioso = noopLogger;错误处理
SDK的所有错误都扩展自HaciendaError,以实现统一处理:
import {
HaciendaError,
ValidationError,
ApiError,
AuthenticationError,
SigningError,
} from "@dojocoding/hacienda-sdk";
try {
await client.authenticate();
const xml = buildFacturaXml(factura);
const firmado = await signAndEncode(xml, p12, pin);
const resultado = await submitAndWait(httpClient, solicitud);
} catch (err) {
if (err instanceof ValidationError) {
// Fallo de validación (esquema Zod o reglas de negocio)
console.error("Validación:", err.message, err.details);
} else if (err instanceof AuthenticationError) {
// Fallo de autenticación o ciclo de vida del token
console.error("Auth:", err.message);
} else if (err instanceof SigningError) {
// Fallo de firma XAdES-EPES (certificado malo, PIN incorrecto, etc.)
console.error("Firma:", err.message);
} else if (err instanceof ApiError) {
// Error HTTP/red de la API de Hacienda
console.error("API:", err.message, err.statusCode, err.responseBody);
} else if (err instanceof HaciendaError) {
// Cualquier otro error del SDK
console.error(`[${err.code}]`, err.message);
}
}错误代码(HaciendaErrorCode):
代码 | 描述 |
| Zod验证或业务规则失败 |
| Hacienda REST API返回错误或无法访问 |
| 认证或令牌生命周期失败 |
| XAdES-EPES签名操作失败 |
| 意外的内部错误 |
CLI — 命令参考
npm install -g @dojocoding/hacienda-cli所有命令都支持--json以输出机器可读的结果。
hacienda auth login
使用Hacienda的IDP进行认证并保存配置文件。
hacienda auth login \
--cedula-type 02 \
--cedula 3101234567 \
--environment sandbox \
--profile default
# Contraseña por variable de entorno (recomendado)
export HACIENDA_PASSWORD="tu-contraseña"
hacienda auth login --cedula-type 02 --cedula 3101234567参数 | 描述 |
|
|
| 身份证号码 |
| IDP密码(或使用 |
|
|
| 配置文件名(默认: |
hacienda auth status
显示当前认证状态。
hacienda auth status
hacienda auth status --profile produccion
hacienda auth status --jsonhacienda auth switch
切换认证配置文件。
hacienda auth switch # Listar perfiles disponibles
hacienda auth switch produccion # Cambiar a un perfil específicohacienda submit
向Hacienda发送电子凭证。
hacienda submit factura.json --dry-run # Vista previa del XML
hacienda submit factura.json # Enviar de verdad
hacienda submit factura.json --json # Salida JSONhacienda status
通过密钥查询凭证的处理状态。
hacienda status 50601012400310123456700100001010000000001199999999hacienda list
从Hacienda列出最近的凭证。
hacienda list
hacienda list --limit 50 --offset 0
hacienda list --jsonhacienda get
通过密钥获取凭证的完整详情。
hacienda get 50601012400310123456700100001010000000001199999999hacienda sign
使用.p12证书(XAdES-EPES)对XML文档进行签名。
hacienda sign factura.xml --p12 cert.p12 --pin 1234 --output firmado.xml
hacienda sign factura.xml --p12 cert.p12 --pin 1234 # stdout
# Con variables de entorno
export HACIENDA_P12_PATH=/ruta/al/cert.p12
export HACIENDA_P12_PIN=1234
hacienda sign factura.xml --output firmado.xmlhacienda validate
根据模式和业务规则验证发票文件(JSON或XML)。
hacienda validate factura.json
hacienda validate documento.xml
hacienda validate factura.json --jsonhacienda lookup
通过税号查询纳税人的经济活动(无需认证)。
hacienda lookup 3101234567
hacienda lookup 3101234567 --jsonhacienda draft
交互式创建用于发送的JSON发票草稿。
hacienda draft # Modo interactivo
hacienda draft --no-interactive # Plantilla en blanco
hacienda draft --template nota-credito --output nc.json模板: factura(默认)、nota-credito、nota-debito、tiquete
环境变量
变量 | 描述 |
| 用于认证的IDP密码 |
| .p12证书文件的PIN |
| .p12证书文件的路径 |
MCP服务器 — AI集成
@dojocoding/hacienda-mcp包将SDK暴露为MCP服务器(模型上下文协议),允许AI助手以对话方式签发电子发票。
使用Claude Desktop配置
将此添加到claude_desktop_config.json:
{
"mcpServers": {
"hacienda-cr": {
"command": "npx",
"args": ["-y", "@dojocoding/hacienda-mcp"]
}
}
}可用工具
工具 | 描述 |
| 从结构化数据创建电子发票。计算税费、生成密钥并构建XML。 |
| 通过50位数字密钥查询处理状态。 |
| 列出最近的电子凭证,支持可选过滤器。 |
| 通过密钥获取凭证的完整详情。 |
| 通过税号查询纳税人信息。 |
| 生成带有默认值的发票草稿。 |
可用资源
URI | 描述 |
| 用于创建发票的 JSON 模式 |
| 凭证类型、代码和描述 |
| 税码、增值税税率和计量单位 |
| 身份类型和验证规则 |
开发
前提条件
Node.js 22+(使用原生
fetch和crypto.subtle)pnpm 9+
开始
git clone https://github.com/DojoCodingLabs/hacienda-cr.git
cd hacienda-cr
pnpm install
pnpm build
pnpm test
pnpm lint
pnpm typecheck项目结构
hacienda-cr/
├── packages/
│ ├── sdk/ # @dojocoding/hacienda-sdk — Core: auth, XML, firma, API
│ ├── cli/ # @dojocoding/hacienda-cli — Binario `hacienda` (citty)
│ └── mcp/ # @dojocoding/hacienda-mcp — Servidor MCP
├── shared/ # @dojocoding/hacienda-shared — Tipos, constantes, enums compartidos
├── turbo.json # Configuración de Turborepo
├── vitest.workspace.ts
└── pnpm-workspace.yaml构建单个包
pnpm --filter @dojocoding/hacienda-sdk build
pnpm --filter @dojocoding/hacienda-sdk test
pnpm --filter @dojocoding/hacienda-sdk test clave.spec.ts技术栈
工具 | 用途 |
TypeScript (strict) | 语言 |
pnpm workspaces + Turborepo | 单体仓库管理 |
tsup | 构建(零配置) |
Vitest | 测试(780+ 个测试) |
ESLint + Prettier | 代码检查与格式化 |
Zod | 运行时验证 + 类型推断 |
fast-xml-parser | XML 生成与解析 |
citty | CLI 框架 |
@modelcontextprotocol/sdk | MCP 框架 |
xadesjs / xmldsigjs | XAdES-EPES 数字签名 |
贡献
Fork 仓库
创建分支(
git checkout -b feature/mi-feature)进行修改并附带测试
运行
pnpm test && pnpm lint && pnpm typecheck提交 pull request
约定:
文件:
kebab-case.ts类型/类:
PascalCase函数/变量:
camelCase常量:
UPPER_SNAKE_CASE
致谢
本项目建立在哥斯达黎加开源社区的开创性工作之上:
CRLibre/API_Hacienda — 哥斯达黎加电子发票的原始开源 API(PHP)。其文档、流程图和社区资源是理解 Hacienda API 的宝贵参考。感谢整个 CRLibre 社区让哥斯达黎加开发者能够轻松使用电子发票功能。
CRLibre/fe-hacienda-cr-misc — 哥斯达黎加电子发票的共享资源和文档。
许可证
This server cannot be deployed
Maintenance
Related MCP Connectors
Costa Rica Hacienda v4.4: AI agents submit and query electronic invoices, stateless BYO.
Official Timbrix MCP server: stamp, cancel, and query Mexican CFDI 4.0 invoices from AI agents.
- ZapierOAuthcom.zapier
Hosted MCP server connecting AI assistants to 9,000+ apps and 40,000+ actions via Zapier.
MCP server unifying ERPs, CRMs, APIs and knowledge base for Claude, ChatGPT and Gemini.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAn MCP server that integrates with the FacturaScripts ERP system, providing resources and tools to manage clients, products, invoices, accounting entries, and business analytics through natural language.10-
- AlicenseAqualityBmaintenanceOpen-source MCP server for Chile's SII free invoicing system, enabling AI agents to query issued and received tax documents.124MIT
- AlicenseNot gradedqualityAmaintenanceMCP server enabling AI assistants to manage invoices, contacts, products, and other accounting data through the Bukku API.9MIT
- AlicenseNot gradedqualityCmaintenanceMCP server for SRI electronic invoicing in Ecuador, enabling AI agents to emit invoices, credit notes, retention documents, and more via natural language through the Cobra API.MIT