@dojocoding/hacienda-mcp 0.2.0 → 0.4.0
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.
- package/CHANGELOG.md +75 -0
- package/README.md +9 -4
- package/dist/chunk-Q2UJO3PQ.js +944 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +19 -9
- package/dist/chunk-NJFDE5LL.js +0 -61858
|
@@ -0,0 +1,944 @@
|
|
|
1
|
+
// src/server.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
|
|
5
|
+
// src/tools/create-invoice.ts
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import {
|
|
8
|
+
buildFacturaXml,
|
|
9
|
+
validateFacturaInput,
|
|
10
|
+
validateDocumentXml,
|
|
11
|
+
buildClave,
|
|
12
|
+
DocumentType,
|
|
13
|
+
Situation,
|
|
14
|
+
calculateLineItemTotals,
|
|
15
|
+
calculateInvoiceSummary,
|
|
16
|
+
getNextSequence,
|
|
17
|
+
DEFAULT_BRANCH,
|
|
18
|
+
DEFAULT_POS
|
|
19
|
+
} from "@dojocoding/hacienda-sdk";
|
|
20
|
+
import { UNITS_OF_MEASURE } from "@dojocoding/hacienda-shared";
|
|
21
|
+
var ImpuestoInputSchema = z.object({
|
|
22
|
+
codigo: z.string().describe('Tax type code (e.g. "01" for IVA)'),
|
|
23
|
+
codigoTarifaIVA: z.string().optional().describe('IVA rate code (e.g. "08" for 13%)'),
|
|
24
|
+
tarifa: z.number().describe("Tax rate percentage (e.g. 13 for 13%)")
|
|
25
|
+
});
|
|
26
|
+
var DescuentoInputSchema = z.object({
|
|
27
|
+
montoDescuento: z.number().positive().describe("Discount amount"),
|
|
28
|
+
codigoDescuento: z.string().default("01").describe('Discount code (v4.4 CodigoDescuento). Defaults to "01"'),
|
|
29
|
+
naturalezaDescuento: z.string().describe("Reason for discount")
|
|
30
|
+
});
|
|
31
|
+
var LineItemInputSchema = z.object({
|
|
32
|
+
codigoCabys: z.string().regex(/^\d{13}$/).describe("CABYS code (13 digits)"),
|
|
33
|
+
cantidad: z.number().positive().describe("Quantity"),
|
|
34
|
+
unidadMedida: z.enum(UNITS_OF_MEASURE).describe('Unit of measure from the official v4.4 catalog (e.g. "Unid", "Sp", "Kg", "h")'),
|
|
35
|
+
detalle: z.string().min(3).max(200).describe("Item description (max 200 chars)"),
|
|
36
|
+
precioUnitario: z.number().min(0).describe("Unit price before taxes"),
|
|
37
|
+
esServicio: z.boolean().optional().describe("Whether this is a service (true) or merchandise (false). Defaults to false"),
|
|
38
|
+
impuesto: z.array(ImpuestoInputSchema).max(10).optional().describe("Taxes to apply to this line item"),
|
|
39
|
+
descuento: z.array(DescuentoInputSchema).max(5).optional().describe("Discounts for this line item")
|
|
40
|
+
});
|
|
41
|
+
var IdentificacionInputSchema = z.object({
|
|
42
|
+
tipo: z.string().describe('ID type: "01"=Fisica, "02"=Juridica, "03"=DIMEX, "04"=NITE'),
|
|
43
|
+
numero: z.string().describe("ID number (digits only)")
|
|
44
|
+
});
|
|
45
|
+
var UbicacionInputSchema = z.object({
|
|
46
|
+
provincia: z.string().regex(/^[1-7]$/).describe("Province code (1-7)"),
|
|
47
|
+
canton: z.string().regex(/^\d{2}$/).describe("Canton code (2 digits)"),
|
|
48
|
+
distrito: z.string().regex(/^\d{2}$/).describe("District code (2 digits)"),
|
|
49
|
+
barrio: z.string().min(5).max(50).optional().describe("Barrio name (free text 5-50 chars in v4.4; no longer a 2-digit code)"),
|
|
50
|
+
otrasSenas: z.string().min(5).max(250).describe("Address details (5-250 chars, required in v4.4)")
|
|
51
|
+
});
|
|
52
|
+
var EmisorInputSchema = z.object({
|
|
53
|
+
nombre: z.string().describe("Issuer name (max 100 chars)"),
|
|
54
|
+
identificacion: IdentificacionInputSchema.describe("Taxpayer identification"),
|
|
55
|
+
nombreComercial: z.string().optional().describe("Commercial name"),
|
|
56
|
+
ubicacion: UbicacionInputSchema.describe("Issuer location (required in v4.4)"),
|
|
57
|
+
correoElectronico: z.string().describe("Email address")
|
|
58
|
+
});
|
|
59
|
+
var ReceptorInputSchema = z.object({
|
|
60
|
+
nombre: z.string().describe("Receiver name (max 100 chars)"),
|
|
61
|
+
identificacion: IdentificacionInputSchema.optional().describe("Taxpayer identification"),
|
|
62
|
+
correoElectronico: z.string().optional().describe("Email address")
|
|
63
|
+
});
|
|
64
|
+
var CreateInvoiceInputSchema = z.object({
|
|
65
|
+
emisor: EmisorInputSchema.describe("Invoice issuer (emisor)"),
|
|
66
|
+
receptor: ReceptorInputSchema.describe("Invoice receiver (receptor)"),
|
|
67
|
+
proveedorSistemas: z.string().regex(/^\d{9,12}$/).describe("Actual invoicing-system provider identification number (ProveedorSistemas)"),
|
|
68
|
+
codigoActividadEmisor: z.string().describe("Issuer economic activity code (6 digits)"),
|
|
69
|
+
condicionVenta: z.string().default("01").describe('Sale condition code: "01"=Cash, "02"=Credit, etc. Defaults to "01"'),
|
|
70
|
+
medioPago: z.string().default("01").describe(
|
|
71
|
+
'Payment method: "01"=Cash, "02"=Card, "04"=Transfer, "06"=SINPE Movil. Defaults to "01"'
|
|
72
|
+
),
|
|
73
|
+
lineItems: z.array(LineItemInputSchema).min(1).max(1e3).describe("Invoice line items (at least one required)"),
|
|
74
|
+
plazoCredito: z.string().optional().describe("Credit term in days (required when condicionVenta is 02)")
|
|
75
|
+
});
|
|
76
|
+
function registerCreateInvoiceTool(server) {
|
|
77
|
+
server.tool(
|
|
78
|
+
"create_invoice",
|
|
79
|
+
"Create a Factura Electronica (electronic invoice) for Costa Rica. Accepts emisor, receptor, and line items. Automatically computes taxes, totals, generates the clave numerica, and builds the XML. Returns the unsigned XML string.",
|
|
80
|
+
{
|
|
81
|
+
emisor: EmisorInputSchema,
|
|
82
|
+
receptor: ReceptorInputSchema,
|
|
83
|
+
proveedorSistemas: CreateInvoiceInputSchema.shape.proveedorSistemas,
|
|
84
|
+
codigoActividadEmisor: CreateInvoiceInputSchema.shape.codigoActividadEmisor,
|
|
85
|
+
condicionVenta: CreateInvoiceInputSchema.shape.condicionVenta,
|
|
86
|
+
medioPago: CreateInvoiceInputSchema.shape.medioPago,
|
|
87
|
+
lineItems: CreateInvoiceInputSchema.shape.lineItems,
|
|
88
|
+
plazoCredito: CreateInvoiceInputSchema.shape.plazoCredito
|
|
89
|
+
},
|
|
90
|
+
async (args) => {
|
|
91
|
+
try {
|
|
92
|
+
const lineItemInputs = args.lineItems.map((item, index) => ({
|
|
93
|
+
numeroLinea: index + 1,
|
|
94
|
+
codigoCabys: item.codigoCabys,
|
|
95
|
+
cantidad: item.cantidad,
|
|
96
|
+
unidadMedida: item.unidadMedida,
|
|
97
|
+
detalle: item.detalle,
|
|
98
|
+
precioUnitario: item.precioUnitario,
|
|
99
|
+
esServicio: item.esServicio ?? false,
|
|
100
|
+
impuesto: item.impuesto,
|
|
101
|
+
descuento: item.descuento
|
|
102
|
+
}));
|
|
103
|
+
const calculatedItems = lineItemInputs.map(calculateLineItemTotals);
|
|
104
|
+
const summary = calculateInvoiceSummary(calculatedItems);
|
|
105
|
+
const now = /* @__PURE__ */ new Date();
|
|
106
|
+
const taxpayerId = args.emisor.identificacion.numero;
|
|
107
|
+
const docTypeCode = "01";
|
|
108
|
+
const branch = DEFAULT_BRANCH;
|
|
109
|
+
const pos = DEFAULT_POS;
|
|
110
|
+
const sequence = 1;
|
|
111
|
+
let clave = buildClave({
|
|
112
|
+
date: now,
|
|
113
|
+
taxpayerId,
|
|
114
|
+
documentType: DocumentType.FACTURA_ELECTRONICA,
|
|
115
|
+
sequence,
|
|
116
|
+
situation: Situation.NORMAL
|
|
117
|
+
});
|
|
118
|
+
const seq = String(sequence).padStart(10, "0");
|
|
119
|
+
let numeroConsecutivo = `${branch}${pos}${docTypeCode}${seq}`;
|
|
120
|
+
const fechaEmision = now.toISOString();
|
|
121
|
+
const detalleServicio = calculatedItems.map((item) => ({
|
|
122
|
+
...item,
|
|
123
|
+
unidadMedida: item.unidadMedida
|
|
124
|
+
}));
|
|
125
|
+
const factura = {
|
|
126
|
+
clave,
|
|
127
|
+
proveedorSistemas: args.proveedorSistemas,
|
|
128
|
+
codigoActividadEmisor: args.codigoActividadEmisor,
|
|
129
|
+
numeroConsecutivo,
|
|
130
|
+
fechaEmision,
|
|
131
|
+
emisor: {
|
|
132
|
+
nombre: args.emisor.nombre,
|
|
133
|
+
identificacion: {
|
|
134
|
+
tipo: args.emisor.identificacion.tipo,
|
|
135
|
+
numero: args.emisor.identificacion.numero
|
|
136
|
+
},
|
|
137
|
+
...args.emisor.nombreComercial ? { nombreComercial: args.emisor.nombreComercial } : {},
|
|
138
|
+
ubicacion: args.emisor.ubicacion,
|
|
139
|
+
correoElectronico: args.emisor.correoElectronico
|
|
140
|
+
},
|
|
141
|
+
receptor: {
|
|
142
|
+
nombre: args.receptor.nombre,
|
|
143
|
+
...args.receptor.identificacion ? {
|
|
144
|
+
identificacion: {
|
|
145
|
+
tipo: args.receptor.identificacion.tipo,
|
|
146
|
+
numero: args.receptor.identificacion.numero
|
|
147
|
+
}
|
|
148
|
+
} : {},
|
|
149
|
+
...args.receptor.correoElectronico ? { correoElectronico: args.receptor.correoElectronico } : {}
|
|
150
|
+
},
|
|
151
|
+
condicionVenta: args.condicionVenta,
|
|
152
|
+
...args.plazoCredito ? { plazoCredito: args.plazoCredito } : {},
|
|
153
|
+
detalleServicio,
|
|
154
|
+
resumenFactura: {
|
|
155
|
+
totalServGravados: summary.totalServGravados,
|
|
156
|
+
totalServExentos: summary.totalServExentos,
|
|
157
|
+
...summary.totalServExonerado > 0 ? { totalServExonerado: summary.totalServExonerado } : {},
|
|
158
|
+
totalMercanciasGravadas: summary.totalMercanciasGravadas,
|
|
159
|
+
totalMercanciasExentas: summary.totalMercanciasExentas,
|
|
160
|
+
...summary.totalMercExonerada > 0 ? { totalMercExonerada: summary.totalMercExonerada } : {},
|
|
161
|
+
totalGravado: summary.totalGravado,
|
|
162
|
+
totalExento: summary.totalExento,
|
|
163
|
+
...summary.totalExonerado > 0 ? { totalExonerado: summary.totalExonerado } : {},
|
|
164
|
+
totalVenta: summary.totalVenta,
|
|
165
|
+
totalDescuentos: summary.totalDescuentos,
|
|
166
|
+
totalVentaNeta: summary.totalVentaNeta,
|
|
167
|
+
totalDesgloseImpuesto: summary.totalDesgloseImpuesto,
|
|
168
|
+
totalImpuesto: summary.totalImpuesto,
|
|
169
|
+
medioPago: [
|
|
170
|
+
{
|
|
171
|
+
tipoMedioPago: args.medioPago,
|
|
172
|
+
totalMedioPago: summary.totalComprobante
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
totalComprobante: summary.totalComprobante
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const validation = validateFacturaInput(factura);
|
|
179
|
+
if (!validation.valid)
|
|
180
|
+
throw new Error(
|
|
181
|
+
validation.errors.map((issue) => `${issue.path}: ${issue.message}`).join("; ")
|
|
182
|
+
);
|
|
183
|
+
let xml = buildFacturaXml(factura);
|
|
184
|
+
const xmlValidation = await validateDocumentXml(xml);
|
|
185
|
+
if (!xmlValidation.valid) throw new Error(xmlValidation.issues.join("; "));
|
|
186
|
+
const allocatedSequence = await getNextSequence(docTypeCode, branch, pos);
|
|
187
|
+
clave = buildClave({
|
|
188
|
+
date: now,
|
|
189
|
+
taxpayerId,
|
|
190
|
+
documentType: DocumentType.FACTURA_ELECTRONICA,
|
|
191
|
+
sequence: allocatedSequence,
|
|
192
|
+
situation: Situation.NORMAL
|
|
193
|
+
});
|
|
194
|
+
numeroConsecutivo = `${branch}${pos}${docTypeCode}${String(allocatedSequence).padStart(10, "0")}`;
|
|
195
|
+
factura.clave = clave;
|
|
196
|
+
factura.numeroConsecutivo = numeroConsecutivo;
|
|
197
|
+
xml = buildFacturaXml(factura);
|
|
198
|
+
return {
|
|
199
|
+
content: [
|
|
200
|
+
{
|
|
201
|
+
type: "text",
|
|
202
|
+
text: [
|
|
203
|
+
`Factura Electronica created successfully.`,
|
|
204
|
+
``,
|
|
205
|
+
`Clave: ${clave}`,
|
|
206
|
+
`Consecutivo: ${numeroConsecutivo}`,
|
|
207
|
+
`Total: ${summary.totalComprobante}`,
|
|
208
|
+
`Tax: ${summary.totalImpuesto}`,
|
|
209
|
+
``,
|
|
210
|
+
`--- XML (unsigned) ---`,
|
|
211
|
+
xml
|
|
212
|
+
].join("\n")
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
};
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
218
|
+
return {
|
|
219
|
+
content: [
|
|
220
|
+
{
|
|
221
|
+
type: "text",
|
|
222
|
+
text: `Error creating invoice: ${message}`
|
|
223
|
+
}
|
|
224
|
+
],
|
|
225
|
+
isError: true
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/tools/document-tools.ts
|
|
233
|
+
import { z as z2 } from "zod";
|
|
234
|
+
import {
|
|
235
|
+
parseClave,
|
|
236
|
+
getStatus,
|
|
237
|
+
extractRejectionReason,
|
|
238
|
+
listComprobantes,
|
|
239
|
+
getComprobante
|
|
240
|
+
} from "@dojocoding/hacienda-sdk";
|
|
241
|
+
import { DOCUMENT_TYPE_NAMES } from "@dojocoding/hacienda-shared";
|
|
242
|
+
|
|
243
|
+
// src/tools/api-client.ts
|
|
244
|
+
import { bootstrapClient } from "@dojocoding/hacienda-sdk";
|
|
245
|
+
var clientCache = /* @__PURE__ */ new Map();
|
|
246
|
+
async function createMcpApiClient(profileName) {
|
|
247
|
+
const key = profileName ?? "default";
|
|
248
|
+
const cached = clientCache.get(key);
|
|
249
|
+
if (cached) {
|
|
250
|
+
return cached;
|
|
251
|
+
}
|
|
252
|
+
const { httpClient } = await bootstrapClient({ profileName: key });
|
|
253
|
+
clientCache.set(key, httpClient);
|
|
254
|
+
return httpClient;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// src/tools/document-tools.ts
|
|
258
|
+
function registerCheckStatusTool(server) {
|
|
259
|
+
server.tool(
|
|
260
|
+
"check_status",
|
|
261
|
+
"Check the processing status of an electronic document by its 50-digit clave numerica. Returns the current status from Hacienda (recibido, procesando, aceptado, rechazado). Requires a configured profile (run `hacienda auth login` first).",
|
|
262
|
+
{
|
|
263
|
+
clave: z2.string().length(50).describe("The 50-digit clave numerica of the document to check"),
|
|
264
|
+
profile: z2.string().default("default").describe('Config profile name (default: "default")')
|
|
265
|
+
},
|
|
266
|
+
async (args) => {
|
|
267
|
+
try {
|
|
268
|
+
const parsed = parseClave(args.clave);
|
|
269
|
+
const docTypeName = DOCUMENT_TYPE_NAMES[parsed.documentType] ?? `Unknown (${parsed.documentType})`;
|
|
270
|
+
const httpClient = await createMcpApiClient(args.profile);
|
|
271
|
+
const status = await getStatus(httpClient, args.clave);
|
|
272
|
+
let rejectionReason;
|
|
273
|
+
if (status.responseXml) {
|
|
274
|
+
rejectionReason = extractRejectionReason(status.responseXml);
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
content: [
|
|
278
|
+
{
|
|
279
|
+
type: "text",
|
|
280
|
+
text: [
|
|
281
|
+
`Document Status`,
|
|
282
|
+
``,
|
|
283
|
+
`Clave: ${args.clave}`,
|
|
284
|
+
`Document Type: ${docTypeName}`,
|
|
285
|
+
`Taxpayer ID: ${parsed.taxpayerId}`,
|
|
286
|
+
`Date: ${parsed.dateRaw}`,
|
|
287
|
+
`Sequence: ${parsed.sequence}`,
|
|
288
|
+
``,
|
|
289
|
+
`Status: ${status.status}`,
|
|
290
|
+
status.date ? `Response Date: ${status.date}` : null,
|
|
291
|
+
rejectionReason ? `Rejection Reason: ${rejectionReason}` : null
|
|
292
|
+
].filter(Boolean).join("\n")
|
|
293
|
+
}
|
|
294
|
+
]
|
|
295
|
+
};
|
|
296
|
+
} catch (error) {
|
|
297
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
298
|
+
return {
|
|
299
|
+
content: [
|
|
300
|
+
{
|
|
301
|
+
type: "text",
|
|
302
|
+
text: `Error checking status: ${message}`
|
|
303
|
+
}
|
|
304
|
+
],
|
|
305
|
+
isError: true
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
function registerListDocumentsTool(server) {
|
|
312
|
+
server.tool(
|
|
313
|
+
"list_documents",
|
|
314
|
+
"List recent electronic documents. Optionally filter by date range, emisor, or receptor. Returns a summary list with clave, date, type, and status. Requires a configured profile (run `hacienda auth login` first).",
|
|
315
|
+
{
|
|
316
|
+
limit: z2.number().int().min(1).max(100).default(10).describe("Maximum number of documents to return (1-100, default 10)"),
|
|
317
|
+
offset: z2.number().int().min(0).default(0).describe("Pagination offset (default 0)"),
|
|
318
|
+
emisorIdentificacion: z2.string().optional().describe("Filter by issuer identification number"),
|
|
319
|
+
receptorIdentificacion: z2.string().optional().describe("Filter by receiver identification number"),
|
|
320
|
+
fechaDesde: z2.string().optional().describe("Filter by start date (ISO 8601)"),
|
|
321
|
+
fechaHasta: z2.string().optional().describe("Filter by end date (ISO 8601)"),
|
|
322
|
+
profile: z2.string().default("default").describe('Config profile name (default: "default")')
|
|
323
|
+
},
|
|
324
|
+
async (args) => {
|
|
325
|
+
try {
|
|
326
|
+
const httpClient = await createMcpApiClient(args.profile);
|
|
327
|
+
const result = await listComprobantes(httpClient, {
|
|
328
|
+
offset: args.offset,
|
|
329
|
+
limit: args.limit,
|
|
330
|
+
emisorIdentificacion: args.emisorIdentificacion,
|
|
331
|
+
receptorIdentificacion: args.receptorIdentificacion,
|
|
332
|
+
fechaEmisionDesde: args.fechaDesde,
|
|
333
|
+
fechaEmisionHasta: args.fechaHasta
|
|
334
|
+
});
|
|
335
|
+
const lines = [
|
|
336
|
+
`Document List`,
|
|
337
|
+
``,
|
|
338
|
+
`Total: ${result.totalRegistros} documents`,
|
|
339
|
+
`Showing: ${result.comprobantes.length} (offset ${result.offset})`,
|
|
340
|
+
``
|
|
341
|
+
];
|
|
342
|
+
if (result.comprobantes.length === 0) {
|
|
343
|
+
lines.push("No documents found matching the criteria.");
|
|
344
|
+
} else {
|
|
345
|
+
for (const doc of result.comprobantes) {
|
|
346
|
+
lines.push(
|
|
347
|
+
`- ${doc.clave}`,
|
|
348
|
+
` Date: ${doc.fechaEmision} | Status: ${doc.estado}`,
|
|
349
|
+
` Emisor: ${doc.emisor.numeroIdentificacion}`,
|
|
350
|
+
``
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
content: [
|
|
356
|
+
{
|
|
357
|
+
type: "text",
|
|
358
|
+
text: lines.join("\n")
|
|
359
|
+
}
|
|
360
|
+
]
|
|
361
|
+
};
|
|
362
|
+
} catch (error) {
|
|
363
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
364
|
+
return {
|
|
365
|
+
content: [
|
|
366
|
+
{
|
|
367
|
+
type: "text",
|
|
368
|
+
text: `Error listing documents: ${message}`
|
|
369
|
+
}
|
|
370
|
+
],
|
|
371
|
+
isError: true
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
function registerGetDocumentTool(server) {
|
|
378
|
+
server.tool(
|
|
379
|
+
"get_document",
|
|
380
|
+
"Get full details of an electronic document by its 50-digit clave numerica. Returns the document metadata, status, and XML content. Requires a configured profile (run `hacienda auth login` first).",
|
|
381
|
+
{
|
|
382
|
+
clave: z2.string().length(50).describe("The 50-digit clave numerica of the document to retrieve"),
|
|
383
|
+
profile: z2.string().default("default").describe('Config profile name (default: "default")')
|
|
384
|
+
},
|
|
385
|
+
async (args) => {
|
|
386
|
+
try {
|
|
387
|
+
const parsed = parseClave(args.clave);
|
|
388
|
+
const docTypeName = DOCUMENT_TYPE_NAMES[parsed.documentType] ?? `Unknown (${parsed.documentType})`;
|
|
389
|
+
const httpClient = await createMcpApiClient(args.profile);
|
|
390
|
+
const doc = await getComprobante(httpClient, args.clave);
|
|
391
|
+
let xmlContent;
|
|
392
|
+
if (doc.comprobanteXml) {
|
|
393
|
+
try {
|
|
394
|
+
xmlContent = Buffer.from(doc.comprobanteXml, "base64").toString("utf-8");
|
|
395
|
+
} catch {
|
|
396
|
+
xmlContent = "(unable to decode XML)";
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return {
|
|
400
|
+
content: [
|
|
401
|
+
{
|
|
402
|
+
type: "text",
|
|
403
|
+
text: [
|
|
404
|
+
`Document Details`,
|
|
405
|
+
``,
|
|
406
|
+
`Clave: ${doc.clave}`,
|
|
407
|
+
`Document Type: ${docTypeName}`,
|
|
408
|
+
`Taxpayer ID: ${parsed.taxpayerId}`,
|
|
409
|
+
`Emission Date: ${doc.fechaEmision}`,
|
|
410
|
+
`Status: ${doc.estado}`,
|
|
411
|
+
doc.fechaRespuesta ? `Response Date: ${doc.fechaRespuesta}` : null,
|
|
412
|
+
``,
|
|
413
|
+
`Emisor: ${doc.emisor.tipoIdentificacion} ${doc.emisor.numeroIdentificacion}`,
|
|
414
|
+
doc.receptor ? `Receptor: ${doc.receptor.tipoIdentificacion} ${doc.receptor.numeroIdentificacion}` : null,
|
|
415
|
+
``,
|
|
416
|
+
`Branch: ${parsed.branch}`,
|
|
417
|
+
`POS: ${parsed.pos}`,
|
|
418
|
+
`Sequence: ${parsed.sequence}`,
|
|
419
|
+
`Situation: ${parsed.situation}`,
|
|
420
|
+
`Security Code: ${parsed.securityCode}`,
|
|
421
|
+
xmlContent ? `
|
|
422
|
+
--- XML ---
|
|
423
|
+
${xmlContent}` : null
|
|
424
|
+
].filter(Boolean).join("\n")
|
|
425
|
+
}
|
|
426
|
+
]
|
|
427
|
+
};
|
|
428
|
+
} catch (error) {
|
|
429
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
430
|
+
return {
|
|
431
|
+
content: [
|
|
432
|
+
{
|
|
433
|
+
type: "text",
|
|
434
|
+
text: `Error getting document: ${message}`
|
|
435
|
+
}
|
|
436
|
+
],
|
|
437
|
+
isError: true
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// src/tools/lookup-tools.ts
|
|
445
|
+
import { z as z3 } from "zod";
|
|
446
|
+
import { lookupTaxpayer } from "@dojocoding/hacienda-sdk";
|
|
447
|
+
import { IDENTIFICATION_TYPE_NAMES } from "@dojocoding/hacienda-shared";
|
|
448
|
+
function registerLookupTaxpayerTool(server) {
|
|
449
|
+
server.tool(
|
|
450
|
+
"lookup_taxpayer",
|
|
451
|
+
"Look up a Costa Rica taxpayer by their identification number (cedula). Returns the taxpayer name, identification type, and registered economic activities. Useful for validating a receptor before creating an invoice.",
|
|
452
|
+
{
|
|
453
|
+
identificacion: z3.string().min(9).max(12).describe("Taxpayer identification number (9-12 digits)")
|
|
454
|
+
},
|
|
455
|
+
async (args) => {
|
|
456
|
+
try {
|
|
457
|
+
const info = await lookupTaxpayer(args.identificacion);
|
|
458
|
+
const activities = info.actividades.length > 0 ? info.actividades.map((a) => ` - [${a.codigo}] ${a.descripcion} (${a.estado})`) : [" (no activities registered)"];
|
|
459
|
+
return {
|
|
460
|
+
content: [
|
|
461
|
+
{
|
|
462
|
+
type: "text",
|
|
463
|
+
text: [
|
|
464
|
+
`Taxpayer Information`,
|
|
465
|
+
``,
|
|
466
|
+
`Name: ${info.nombre}`,
|
|
467
|
+
`Identification: ${args.identificacion}`,
|
|
468
|
+
`Type: ${info.tipoIdentificacion}`,
|
|
469
|
+
``,
|
|
470
|
+
`Economic Activities:`,
|
|
471
|
+
...activities
|
|
472
|
+
].join("\n")
|
|
473
|
+
}
|
|
474
|
+
]
|
|
475
|
+
};
|
|
476
|
+
} catch (error) {
|
|
477
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
478
|
+
return {
|
|
479
|
+
content: [
|
|
480
|
+
{
|
|
481
|
+
type: "text",
|
|
482
|
+
text: `Error looking up taxpayer: ${message}`
|
|
483
|
+
}
|
|
484
|
+
],
|
|
485
|
+
isError: true
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
function registerDraftInvoiceTool(server) {
|
|
492
|
+
server.tool(
|
|
493
|
+
"draft_invoice",
|
|
494
|
+
"Generate a draft invoice template with sensible defaults for Costa Rica electronic invoicing. Use this to quickly scaffold an invoice that can then be reviewed and submitted. Returns a JSON template that can be passed to create_invoice.",
|
|
495
|
+
{
|
|
496
|
+
emisorNombre: z3.string().describe("Issuer (emisor) name"),
|
|
497
|
+
emisorIdTipo: z3.string().default("02").describe(
|
|
498
|
+
'Issuer ID type: "01"=Fisica, "02"=Juridica, "03"=DIMEX, "04"=NITE. Defaults to "02"'
|
|
499
|
+
),
|
|
500
|
+
emisorIdNumero: z3.string().describe("Issuer identification number"),
|
|
501
|
+
emisorEmail: z3.string().describe("Issuer email address"),
|
|
502
|
+
receptorNombre: z3.string().describe("Receiver (receptor) name"),
|
|
503
|
+
receptorIdTipo: z3.string().optional().describe('Receiver ID type (optional): "01"=Fisica, "02"=Juridica, "03"=DIMEX, "04"=NITE'),
|
|
504
|
+
receptorIdNumero: z3.string().optional().describe("Receiver identification number (optional)"),
|
|
505
|
+
receptorEmail: z3.string().optional().describe("Receiver email address (optional)"),
|
|
506
|
+
codigoActividadEmisor: z3.string().default("620100").describe("Issuer economic activity code (6 digits). Defaults to 620100 (IT services)"),
|
|
507
|
+
description: z3.string().default("Servicio profesional").describe("Default line item description"),
|
|
508
|
+
amount: z3.number().default(0).describe("Default line item amount (price). Set to 0 to leave as placeholder"),
|
|
509
|
+
includeIva: z3.boolean().default(true).describe("Whether to include 13% IVA tax. Defaults to true")
|
|
510
|
+
},
|
|
511
|
+
async (args) => {
|
|
512
|
+
const lineItem = {
|
|
513
|
+
codigoCabys: "8310100000000",
|
|
514
|
+
cantidad: 1,
|
|
515
|
+
unidadMedida: "Sp",
|
|
516
|
+
detalle: args.description,
|
|
517
|
+
precioUnitario: args.amount,
|
|
518
|
+
esServicio: true
|
|
519
|
+
};
|
|
520
|
+
lineItem.impuesto = args.includeIva ? [{ codigo: "01", codigoTarifaIVA: "08", tarifa: 13 }] : [{ codigo: "01", codigoTarifaIVA: "01", tarifa: 0 }];
|
|
521
|
+
const draft = {
|
|
522
|
+
emisor: {
|
|
523
|
+
nombre: args.emisorNombre,
|
|
524
|
+
identificacion: {
|
|
525
|
+
tipo: args.emisorIdTipo,
|
|
526
|
+
numero: args.emisorIdNumero
|
|
527
|
+
},
|
|
528
|
+
ubicacion: {
|
|
529
|
+
provincia: "1",
|
|
530
|
+
canton: "01",
|
|
531
|
+
distrito: "01",
|
|
532
|
+
otrasSenas: "<<COMPLETE_ISSUER_ADDRESS>>"
|
|
533
|
+
},
|
|
534
|
+
correoElectronico: args.emisorEmail
|
|
535
|
+
},
|
|
536
|
+
receptor: {
|
|
537
|
+
nombre: args.receptorNombre,
|
|
538
|
+
...args.receptorIdTipo && args.receptorIdNumero ? {
|
|
539
|
+
identificacion: {
|
|
540
|
+
tipo: args.receptorIdTipo,
|
|
541
|
+
numero: args.receptorIdNumero
|
|
542
|
+
}
|
|
543
|
+
} : {},
|
|
544
|
+
...args.receptorEmail ? { correoElectronico: args.receptorEmail } : {}
|
|
545
|
+
},
|
|
546
|
+
proveedorSistemas: "<<SYSTEM_PROVIDER_ID>>",
|
|
547
|
+
codigoActividadEmisor: args.codigoActividadEmisor,
|
|
548
|
+
condicionVenta: "01",
|
|
549
|
+
medioPago: "01",
|
|
550
|
+
lineItems: [lineItem]
|
|
551
|
+
};
|
|
552
|
+
const idTypeName = IDENTIFICATION_TYPE_NAMES[args.emisorIdTipo] ?? args.emisorIdTipo;
|
|
553
|
+
return {
|
|
554
|
+
content: [
|
|
555
|
+
{
|
|
556
|
+
type: "text",
|
|
557
|
+
text: [
|
|
558
|
+
`Invoice Draft Template`,
|
|
559
|
+
``,
|
|
560
|
+
`Emisor: ${args.emisorNombre} (${idTypeName}: ${args.emisorIdNumero})`,
|
|
561
|
+
`Receptor: ${args.receptorNombre}`,
|
|
562
|
+
`Activity: ${args.codigoActividadEmisor}`,
|
|
563
|
+
`Sale Condition: Contado (cash)`,
|
|
564
|
+
`Payment: Efectivo (cash)`,
|
|
565
|
+
args.includeIva ? `IVA: 13% included` : `IVA: not included`,
|
|
566
|
+
``,
|
|
567
|
+
`Fill in the provider ID and issuer address placeholders before passing this JSON to create_invoice:`,
|
|
568
|
+
``,
|
|
569
|
+
"```json",
|
|
570
|
+
JSON.stringify(draft, null, 2),
|
|
571
|
+
"```",
|
|
572
|
+
``,
|
|
573
|
+
`Modify the lineItems array to add more items, change quantities,`,
|
|
574
|
+
`prices, descriptions, and tax configuration as needed.`
|
|
575
|
+
].join("\n")
|
|
576
|
+
}
|
|
577
|
+
]
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// src/tools/index.ts
|
|
584
|
+
function registerTools(server) {
|
|
585
|
+
registerCreateInvoiceTool(server);
|
|
586
|
+
registerCheckStatusTool(server);
|
|
587
|
+
registerListDocumentsTool(server);
|
|
588
|
+
registerGetDocumentTool(server);
|
|
589
|
+
registerLookupTaxpayerTool(server);
|
|
590
|
+
registerDraftInvoiceTool(server);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/resources/factura-schema.ts
|
|
594
|
+
import { z as z4 } from "zod";
|
|
595
|
+
var FACTURA_SCHEMA = {
|
|
596
|
+
...z4.toJSONSchema(CreateInvoiceInputSchema, { io: "input" }),
|
|
597
|
+
title: "Factura Electronica (Costa Rica)",
|
|
598
|
+
description: "Input schema for the create_invoice tool, including required v4.4 fields."
|
|
599
|
+
};
|
|
600
|
+
function registerFacturaSchemaResource(server) {
|
|
601
|
+
server.resource(
|
|
602
|
+
"factura-schema",
|
|
603
|
+
"hacienda://schemas/factura",
|
|
604
|
+
{
|
|
605
|
+
description: "JSON Schema for the Factura Electronica (electronic invoice) input format. Use this to understand the required fields and structure for creating invoices.",
|
|
606
|
+
mimeType: "application/json"
|
|
607
|
+
},
|
|
608
|
+
async () => ({
|
|
609
|
+
contents: [
|
|
610
|
+
{
|
|
611
|
+
uri: "hacienda://schemas/factura",
|
|
612
|
+
mimeType: "application/json",
|
|
613
|
+
text: JSON.stringify(FACTURA_SCHEMA, null, 2)
|
|
614
|
+
}
|
|
615
|
+
]
|
|
616
|
+
})
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// src/resources/document-types.ts
|
|
621
|
+
import {
|
|
622
|
+
DocumentTypeCode,
|
|
623
|
+
DOCUMENT_TYPE_NAMES as DOCUMENT_TYPE_NAMES2,
|
|
624
|
+
SituationCode,
|
|
625
|
+
MensajeReceptorCode,
|
|
626
|
+
SALE_CONDITION_NAMES,
|
|
627
|
+
PAYMENT_METHOD_NAMES
|
|
628
|
+
} from "@dojocoding/hacienda-shared";
|
|
629
|
+
var REFERENCE_DATA = {
|
|
630
|
+
documentTypes: Object.entries(DOCUMENT_TYPE_NAMES2).map(([code, name]) => ({
|
|
631
|
+
code,
|
|
632
|
+
name,
|
|
633
|
+
description: getDocTypeDescription(code)
|
|
634
|
+
})),
|
|
635
|
+
situationCodes: [
|
|
636
|
+
{ code: SituationCode.NORMAL, name: "Normal", description: "Standard submission" },
|
|
637
|
+
{
|
|
638
|
+
code: SituationCode.CONTINGENCIA,
|
|
639
|
+
name: "Contingencia",
|
|
640
|
+
description: "Contingency mode (system issues)"
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
code: SituationCode.SIN_INTERNET,
|
|
644
|
+
name: "Sin Internet",
|
|
645
|
+
description: "No internet connection at time of sale"
|
|
646
|
+
}
|
|
647
|
+
],
|
|
648
|
+
mensajeReceptorCodes: [
|
|
649
|
+
{
|
|
650
|
+
code: MensajeReceptorCode.ACEPTADO,
|
|
651
|
+
name: "Aceptado totalmente",
|
|
652
|
+
description: "Fully accepted"
|
|
653
|
+
},
|
|
654
|
+
{
|
|
655
|
+
code: MensajeReceptorCode.ACEPTADO_PARCIALMENTE,
|
|
656
|
+
name: "Aceptado parcialmente",
|
|
657
|
+
description: "Partially accepted"
|
|
658
|
+
},
|
|
659
|
+
{ code: MensajeReceptorCode.RECHAZADO, name: "Rechazado", description: "Rejected" }
|
|
660
|
+
],
|
|
661
|
+
saleConditions: Object.entries(SALE_CONDITION_NAMES).map(([code, name]) => ({
|
|
662
|
+
code,
|
|
663
|
+
name
|
|
664
|
+
})),
|
|
665
|
+
paymentMethods: Object.entries(PAYMENT_METHOD_NAMES).map(([code, name]) => ({
|
|
666
|
+
code,
|
|
667
|
+
name
|
|
668
|
+
}))
|
|
669
|
+
};
|
|
670
|
+
function getDocTypeDescription(code) {
|
|
671
|
+
switch (code) {
|
|
672
|
+
case DocumentTypeCode.FACTURA_ELECTRONICA:
|
|
673
|
+
return "Standard electronic invoice. Requires receptor. Most common document type.";
|
|
674
|
+
case DocumentTypeCode.NOTA_DEBITO_ELECTRONICA:
|
|
675
|
+
return "Electronic debit note. References an existing invoice to add charges.";
|
|
676
|
+
case DocumentTypeCode.NOTA_CREDITO_ELECTRONICA:
|
|
677
|
+
return "Electronic credit note. References an existing invoice to apply credits/returns.";
|
|
678
|
+
case DocumentTypeCode.TIQUETE_ELECTRONICO:
|
|
679
|
+
return "Simplified electronic receipt. Receptor is optional. For consumers (B2C).";
|
|
680
|
+
case DocumentTypeCode.FACTURA_ELECTRONICA_COMPRA:
|
|
681
|
+
return "Purchase invoice. Issued when buying from unregistered suppliers.";
|
|
682
|
+
case DocumentTypeCode.FACTURA_ELECTRONICA_EXPORTACION:
|
|
683
|
+
return "Export invoice. For international sales. Receptor may use foreign ID.";
|
|
684
|
+
case DocumentTypeCode.RECIBO_ELECTRONICO_PAGO:
|
|
685
|
+
return "Electronic payment receipt. New in v4.4.";
|
|
686
|
+
default:
|
|
687
|
+
return "Document type.";
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
function registerDocumentTypesResource(server) {
|
|
691
|
+
server.resource(
|
|
692
|
+
"document-types",
|
|
693
|
+
"hacienda://reference/document-types",
|
|
694
|
+
{
|
|
695
|
+
description: "Reference data for Costa Rica electronic document types, situation codes, receiver message codes, sale conditions, and payment methods.",
|
|
696
|
+
mimeType: "application/json"
|
|
697
|
+
},
|
|
698
|
+
async () => ({
|
|
699
|
+
contents: [
|
|
700
|
+
{
|
|
701
|
+
uri: "hacienda://reference/document-types",
|
|
702
|
+
mimeType: "application/json",
|
|
703
|
+
text: JSON.stringify(REFERENCE_DATA, null, 2)
|
|
704
|
+
}
|
|
705
|
+
]
|
|
706
|
+
})
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// src/resources/tax-codes.ts
|
|
711
|
+
import {
|
|
712
|
+
TaxCode,
|
|
713
|
+
IvaRateCode,
|
|
714
|
+
IVA_RATE_PERCENTAGES,
|
|
715
|
+
ExonerationType,
|
|
716
|
+
UnitOfMeasure
|
|
717
|
+
} from "@dojocoding/hacienda-shared";
|
|
718
|
+
var TAX_REFERENCE = {
|
|
719
|
+
taxCodes: [
|
|
720
|
+
{ code: TaxCode.IVA, name: "IVA", description: "Impuesto al Valor Agregado (Value Added Tax)" },
|
|
721
|
+
{
|
|
722
|
+
code: TaxCode.IMPUESTO_SELECTIVO_CONSUMO,
|
|
723
|
+
name: "Impuesto Selectivo de Consumo",
|
|
724
|
+
description: "Selective consumption tax"
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
code: TaxCode.IMPUESTO_UNICO_COMBUSTIBLES,
|
|
728
|
+
name: "Impuesto Unico a los Combustibles",
|
|
729
|
+
description: "Fuel tax"
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
code: TaxCode.IMPUESTO_BEBIDAS_ALCOHOLICAS,
|
|
733
|
+
name: "Impuesto Bebidas Alcoholicas",
|
|
734
|
+
description: "Alcoholic beverages tax"
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
code: TaxCode.IMPUESTO_BEBIDAS_SIN_ALCOHOL,
|
|
738
|
+
name: "Impuesto Bebidas sin Alcohol",
|
|
739
|
+
description: "Non-alcoholic beverages and toiletries tax"
|
|
740
|
+
},
|
|
741
|
+
{ code: TaxCode.IMPUESTO_TABACO, name: "Impuesto al Tabaco", description: "Tobacco tax" },
|
|
742
|
+
{
|
|
743
|
+
code: TaxCode.IVA_CALCULO_ESPECIAL,
|
|
744
|
+
name: "IVA (calculo especial)",
|
|
745
|
+
description: "IVA with special calculation"
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
code: TaxCode.IVA_BIENES_USADOS,
|
|
749
|
+
name: "IVA Bienes Usados",
|
|
750
|
+
description: "IVA for used goods regime (factor method)"
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
code: TaxCode.IMPUESTO_CEMENTO,
|
|
754
|
+
name: "Impuesto al Cemento",
|
|
755
|
+
description: "Cement tax"
|
|
756
|
+
},
|
|
757
|
+
{ code: TaxCode.OTROS, name: "Otros", description: "Other taxes" }
|
|
758
|
+
],
|
|
759
|
+
ivaRateCodes: Object.entries(IVA_RATE_PERCENTAGES).map(([code, rate]) => ({
|
|
760
|
+
code,
|
|
761
|
+
rate,
|
|
762
|
+
name: getIvaRateName(code)
|
|
763
|
+
})),
|
|
764
|
+
exonerationTypes: [
|
|
765
|
+
{ code: ExonerationType.COMPRAS_AUTORIZADAS, name: "Compras autorizadas" },
|
|
766
|
+
{
|
|
767
|
+
code: ExonerationType.VENTAS_EXENTAS_DIPLOMATICOS,
|
|
768
|
+
name: "Ventas exentas a diplomaticos"
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
code: ExonerationType.AUTORIZADO_LEY_ESPECIAL,
|
|
772
|
+
name: "Autorizado por ley especial"
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
code: ExonerationType.EXENCIONES_DGH,
|
|
776
|
+
name: "Exenciones de la Direccion General de Hacienda"
|
|
777
|
+
},
|
|
778
|
+
{ code: ExonerationType.TRANSITORIO_V, name: "Transitorio V" },
|
|
779
|
+
{ code: ExonerationType.TRANSITORIO_IX, name: "Transitorio IX" },
|
|
780
|
+
{ code: ExonerationType.TRANSITORIO_XVII, name: "Transitorio XVII" },
|
|
781
|
+
{ code: ExonerationType.OTROS, name: "Otros" }
|
|
782
|
+
],
|
|
783
|
+
commonUnitsOfMeasure: [
|
|
784
|
+
{ code: UnitOfMeasure.UNIDAD, name: "Unidad", description: "Unit (general merchandise)" },
|
|
785
|
+
{
|
|
786
|
+
code: UnitOfMeasure.SERVICIOS_PROFESIONALES,
|
|
787
|
+
name: "Servicios Profesionales",
|
|
788
|
+
description: "Professional services"
|
|
789
|
+
},
|
|
790
|
+
{ code: UnitOfMeasure.HORAS, name: "Horas", description: "Hours" },
|
|
791
|
+
{ code: UnitOfMeasure.KILOGRAMOS, name: "Kilogramos", description: "Kilograms" },
|
|
792
|
+
{ code: UnitOfMeasure.LITROS, name: "Litros", description: "Liters" },
|
|
793
|
+
{ code: UnitOfMeasure.METROS, name: "Metros", description: "Meters" },
|
|
794
|
+
{
|
|
795
|
+
code: UnitOfMeasure.METROS_CUADRADOS,
|
|
796
|
+
name: "Metros cuadrados",
|
|
797
|
+
description: "Square meters"
|
|
798
|
+
},
|
|
799
|
+
{ code: UnitOfMeasure.METROS_CUBICOS, name: "Metros cubicos", description: "Cubic meters" },
|
|
800
|
+
{ code: UnitOfMeasure.GALONES, name: "Galones", description: "Gallons" },
|
|
801
|
+
{ code: UnitOfMeasure.OTROS, name: "Otros", description: "Other (specify)" }
|
|
802
|
+
],
|
|
803
|
+
notes: {
|
|
804
|
+
commonUsage: "For most services, use tax code '01' (IVA) with rate code '08' (13% general rate). Unit of measure for services is typically 'Sp' (Servicios Profesionales). For merchandise, use 'Unid' (Unidad).",
|
|
805
|
+
ivaExempt: "Some goods and services are exempt from IVA (rate code '01', 0%). The reduced rates (1%, 2%, 4%) apply to specific goods like basic foodstuffs."
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
function getIvaRateName(code) {
|
|
809
|
+
switch (code) {
|
|
810
|
+
case IvaRateCode.EXENTO:
|
|
811
|
+
return "Exento (0%)";
|
|
812
|
+
case IvaRateCode.REDUCIDA_1:
|
|
813
|
+
return "Tarifa reducida 1%";
|
|
814
|
+
case IvaRateCode.REDUCIDA_2:
|
|
815
|
+
return "Tarifa reducida 2%";
|
|
816
|
+
case IvaRateCode.REDUCIDA_4:
|
|
817
|
+
return "Tarifa reducida 4%";
|
|
818
|
+
case IvaRateCode.TRANSITORIO_0:
|
|
819
|
+
return "Transitorio 0%";
|
|
820
|
+
case IvaRateCode.TRANSITORIO_4:
|
|
821
|
+
return "Transitorio 4%";
|
|
822
|
+
case IvaRateCode.TRANSITORIO_8:
|
|
823
|
+
return "Transitorio 8%";
|
|
824
|
+
case IvaRateCode.GENERAL_13:
|
|
825
|
+
return "Tarifa general 13%";
|
|
826
|
+
default:
|
|
827
|
+
return `Code ${code}`;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
function registerTaxCodesResource(server) {
|
|
831
|
+
server.resource(
|
|
832
|
+
"tax-codes",
|
|
833
|
+
"hacienda://reference/tax-codes",
|
|
834
|
+
{
|
|
835
|
+
description: "Reference data for Costa Rica tax codes, IVA rates, exoneration types, and units of measure used in electronic invoicing.",
|
|
836
|
+
mimeType: "application/json"
|
|
837
|
+
},
|
|
838
|
+
async () => ({
|
|
839
|
+
contents: [
|
|
840
|
+
{
|
|
841
|
+
uri: "hacienda://reference/tax-codes",
|
|
842
|
+
mimeType: "application/json",
|
|
843
|
+
text: JSON.stringify(TAX_REFERENCE, null, 2)
|
|
844
|
+
}
|
|
845
|
+
]
|
|
846
|
+
})
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
// src/resources/id-types.ts
|
|
851
|
+
import {
|
|
852
|
+
IdentificationType,
|
|
853
|
+
IDENTIFICATION_TYPE_NAMES as IDENTIFICATION_TYPE_NAMES2,
|
|
854
|
+
IDENTIFICATION_LENGTHS
|
|
855
|
+
} from "@dojocoding/hacienda-shared";
|
|
856
|
+
var ID_TYPES_REFERENCE = {
|
|
857
|
+
identificationTypes: [
|
|
858
|
+
{
|
|
859
|
+
code: IdentificationType.CEDULA_FISICA,
|
|
860
|
+
name: IDENTIFICATION_TYPE_NAMES2[IdentificationType.CEDULA_FISICA],
|
|
861
|
+
lengths: IDENTIFICATION_LENGTHS[IdentificationType.CEDULA_FISICA],
|
|
862
|
+
description: "Physical person identification card. 9-digit number. Used for individual taxpayers (personas fisicas).",
|
|
863
|
+
example: "101230456"
|
|
864
|
+
},
|
|
865
|
+
{
|
|
866
|
+
code: IdentificationType.CEDULA_JURIDICA,
|
|
867
|
+
name: IDENTIFICATION_TYPE_NAMES2[IdentificationType.CEDULA_JURIDICA],
|
|
868
|
+
lengths: IDENTIFICATION_LENGTHS[IdentificationType.CEDULA_JURIDICA],
|
|
869
|
+
description: "Legal entity identification. 10-digit number. Used for companies and organizations (sociedades anonimas, etc.).",
|
|
870
|
+
example: "3101234567"
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
code: IdentificationType.DIMEX,
|
|
874
|
+
name: IDENTIFICATION_TYPE_NAMES2[IdentificationType.DIMEX],
|
|
875
|
+
lengths: IDENTIFICATION_LENGTHS[IdentificationType.DIMEX],
|
|
876
|
+
description: "Foreign resident identification (Documento de Identidad Migratoria para Extranjeros). 11 or 12 digit number. Used for foreign residents in Costa Rica.",
|
|
877
|
+
example: "12345678901"
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
code: IdentificationType.NITE,
|
|
881
|
+
name: IDENTIFICATION_TYPE_NAMES2[IdentificationType.NITE],
|
|
882
|
+
lengths: IDENTIFICATION_LENGTHS[IdentificationType.NITE],
|
|
883
|
+
description: "Tax identification for foreigners without DIMEX (Numero de Identificacion Tributaria Especial). 10-digit number.",
|
|
884
|
+
example: "1234567890"
|
|
885
|
+
}
|
|
886
|
+
],
|
|
887
|
+
notes: {
|
|
888
|
+
formatting: "Identification numbers must contain only digits, no dashes or spaces. The number must match the expected length for the given type.",
|
|
889
|
+
usage: "The emisor (issuer) always requires identification. The receptor (receiver) identification is required for Factura Electronica but optional for Tiquete Electronico."
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
function registerIdTypesResource(server) {
|
|
893
|
+
server.resource(
|
|
894
|
+
"id-types",
|
|
895
|
+
"hacienda://reference/id-types",
|
|
896
|
+
{
|
|
897
|
+
description: "Reference data for Costa Rica identification types used in electronic invoicing. Includes type codes, expected lengths, descriptions, and examples.",
|
|
898
|
+
mimeType: "application/json"
|
|
899
|
+
},
|
|
900
|
+
async () => ({
|
|
901
|
+
contents: [
|
|
902
|
+
{
|
|
903
|
+
uri: "hacienda://reference/id-types",
|
|
904
|
+
mimeType: "application/json",
|
|
905
|
+
text: JSON.stringify(ID_TYPES_REFERENCE, null, 2)
|
|
906
|
+
}
|
|
907
|
+
]
|
|
908
|
+
})
|
|
909
|
+
);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// src/resources/index.ts
|
|
913
|
+
function registerResources(server) {
|
|
914
|
+
registerFacturaSchemaResource(server);
|
|
915
|
+
registerDocumentTypesResource(server);
|
|
916
|
+
registerTaxCodesResource(server);
|
|
917
|
+
registerIdTypesResource(server);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/server.ts
|
|
921
|
+
var SERVER_NAME = "hacienda-cr";
|
|
922
|
+
var SERVER_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
923
|
+
function createServer() {
|
|
924
|
+
const server = new McpServer(
|
|
925
|
+
{
|
|
926
|
+
name: SERVER_NAME,
|
|
927
|
+
version: SERVER_VERSION
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
capabilities: {
|
|
931
|
+
tools: {},
|
|
932
|
+
resources: {}
|
|
933
|
+
},
|
|
934
|
+
instructions: "Hacienda CR MCP Server \u2014 tools and reference data for Costa Rica electronic invoicing (comprobantes electronicos). Use the tools to create invoices, check document status, and look up reference data. Resources provide schemas and reference tables for document types, tax codes, and identification types."
|
|
935
|
+
}
|
|
936
|
+
);
|
|
937
|
+
registerTools(server);
|
|
938
|
+
registerResources(server);
|
|
939
|
+
return server;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
export {
|
|
943
|
+
createServer
|
|
944
|
+
};
|