@dojocoding/hacienda-mcp 0.4.1 → 0.4.2

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 CHANGED
@@ -1,5 +1,14 @@
1
1
  # @dojocoding/hacienda-mcp
2
2
 
3
+ ## 0.4.2
4
+
5
+ ### Patch Changes
6
+
7
+ - b6b25b4: Correct shared fiscal document codes, preserve current official schema provenance, enforce document identity and tax consistency, and use Costa Rica calendar dates in claves. Scope MCP invoice numbering by issuer/environment and invalidate authenticated clients after profile or credential changes. Preserve existing HTTP cancellation and redirect protections, make token deadlines configurable, disable mutation replays by default, and redact diagnostic secrets. Add regression coverage and document actual sandbox reception failures separately from offline test success. Live acceptance of all document families remains a release gate.
8
+ - Updated dependencies [b6b25b4]
9
+ - @dojocoding/hacienda-sdk@0.4.2
10
+ - @dojocoding/hacienda-shared@0.4.2
11
+
3
12
  ## 0.4.1
4
13
 
5
14
  ### Patch Changes
package/README.md CHANGED
@@ -204,3 +204,11 @@ See the [MCP reference](https://github.com/DojoCodingLabs/hacienda-cr/blob/main/
204
204
  ## Migrating to 0.4.0
205
205
 
206
206
  Read the [migration guide](https://github.com/DojoCodingLabs/hacienda-cr/blob/main/packages/sdk/MIGRATION-v4.4.md) before upgrading.
207
+
208
+ ### Issuer numbering and credential rotation
209
+
210
+ `create_invoice` accepts `environment` (defaults to `sandbox`) and stores local counters under `issuer-sequences/<environment>/<sha256 identity>/sequences.json` in the configuration directory. The digest covers environment, identification type and identification number. Branch, terminal and document type remain separate counters within that file. This is local-process/file coordination, not a distributed transactional counter.
211
+
212
+ If an old global `sequences.json` contains positive counters, creating a new unseeded issuer series fails closed. Stop writers, back up the old file, establish the issuer/environment that owns each series, and seed each corresponding scoped file with the last reserved number before resuming. Do not copy a mixed-company global file blindly, delete counters, or restart a production series at one. There is no automatic migration because the old file does not identify its owner.
213
+
214
+ Authenticated clients are invalidated when the resolved profile, environment, issuer configuration, certificate path, password or PIN changes. Replacing certificate bytes at the same path still requires restarting the process or clearing the cache. Configure one profile per identity/environment.
@@ -2,6 +2,36 @@
2
2
  import { readFileSync } from "fs";
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
 
5
+ // src/tools/issuer-sequence.ts
6
+ import { createHash } from "crypto";
7
+ import { readFile, access } from "fs/promises";
8
+ import { join } from "path";
9
+ import { getConfigDir, getNextSequence } from "@dojocoding/hacienda-sdk";
10
+ function issuerSequenceDirectory(environment, idType, idNumber, configDir) {
11
+ const identity = createHash("sha256").update(JSON.stringify([environment, idType, idNumber])).digest("hex");
12
+ return join(getConfigDir(configDir), "issuer-sequences", environment, identity);
13
+ }
14
+ async function nextIssuerSequence(environment, idType, idNumber, documentType, branch, pos, configDir) {
15
+ const destination = issuerSequenceDirectory(environment, idType, idNumber, configDir);
16
+ try {
17
+ const legacy = JSON.parse(
18
+ await readFile(join(getConfigDir(configDir), "sequences.json"), "utf8")
19
+ );
20
+ if (legacy && typeof legacy === "object" && Object.values(legacy).some((v) => typeof v === "number" && v > 0)) {
21
+ try {
22
+ await access(join(destination, "sequences.json"));
23
+ } catch {
24
+ throw new Error(
25
+ "Legacy numbering exists. Migrate and seed the issuer/environment series before creating a document."
26
+ );
27
+ }
28
+ }
29
+ } catch (error) {
30
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
31
+ }
32
+ return getNextSequence(documentType, branch, pos, { configDir: destination });
33
+ }
34
+
5
35
  // src/tools/create-invoice.ts
6
36
  import { z } from "zod";
7
37
  import {
@@ -13,7 +43,7 @@ import {
13
43
  Situation,
14
44
  calculateLineItemTotals,
15
45
  calculateInvoiceSummary,
16
- getNextSequence,
46
+ sanitizeLogString,
17
47
  DEFAULT_BRANCH,
18
48
  DEFAULT_POS
19
49
  } from "@dojocoding/hacienda-sdk";
@@ -62,6 +92,7 @@ var ReceptorInputSchema = z.object({
62
92
  correoElectronico: z.string().optional().describe("Email address")
63
93
  });
64
94
  var CreateInvoiceInputSchema = z.object({
95
+ environment: z.enum(["sandbox", "production"]).default("sandbox"),
65
96
  emisor: EmisorInputSchema.describe("Invoice issuer (emisor)"),
66
97
  receptor: ReceptorInputSchema.describe("Invoice receiver (receptor)"),
67
98
  proveedorSistemas: z.string().regex(/^\d{9,12}$/).describe("Actual invoicing-system provider identification number (ProveedorSistemas)"),
@@ -78,6 +109,7 @@ function registerCreateInvoiceTool(server) {
78
109
  "create_invoice",
79
110
  "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
111
  {
112
+ environment: CreateInvoiceInputSchema.shape.environment,
81
113
  emisor: EmisorInputSchema,
82
114
  receptor: ReceptorInputSchema,
83
115
  proveedorSistemas: CreateInvoiceInputSchema.shape.proveedorSistemas,
@@ -183,7 +215,14 @@ function registerCreateInvoiceTool(server) {
183
215
  let xml = buildFacturaXml(factura);
184
216
  const xmlValidation = await validateDocumentXml(xml);
185
217
  if (!xmlValidation.valid) throw new Error(xmlValidation.issues.join("; "));
186
- const allocatedSequence = await getNextSequence(docTypeCode, branch, pos);
218
+ const allocatedSequence = await nextIssuerSequence(
219
+ args.environment,
220
+ args.emisor.identificacion.tipo,
221
+ taxpayerId,
222
+ docTypeCode,
223
+ branch,
224
+ pos
225
+ );
187
226
  clave = buildClave({
188
227
  date: now,
189
228
  taxpayerId,
@@ -214,7 +253,7 @@ function registerCreateInvoiceTool(server) {
214
253
  ]
215
254
  };
216
255
  } catch (error) {
217
- const message = error instanceof Error ? error.message : String(error);
256
+ const message = sanitizeLogString(error instanceof Error ? error.message : String(error));
218
257
  return {
219
258
  content: [
220
259
  {
@@ -230,6 +269,7 @@ function registerCreateInvoiceTool(server) {
230
269
  }
231
270
 
232
271
  // src/tools/document-tools.ts
272
+ import { sanitizeLogString as sanitizeLogString2 } from "@dojocoding/hacienda-sdk";
233
273
  import { z as z2 } from "zod";
234
274
  import {
235
275
  parseClave,
@@ -241,17 +281,33 @@ import {
241
281
  import { DOCUMENT_TYPE_NAMES } from "@dojocoding/hacienda-shared";
242
282
 
243
283
  // src/tools/api-client.ts
244
- import { bootstrapClient } from "@dojocoding/hacienda-sdk";
284
+ import { createHash as createHash2 } from "crypto";
285
+ import { bootstrapClient, loadConfig } from "@dojocoding/hacienda-sdk";
245
286
  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;
287
+ function fingerprint(config) {
288
+ return createHash2("sha256").update(JSON.stringify([config.profileName, config.profile, config.password, config.p12Pin])).digest("hex");
289
+ }
290
+ async function createMcpApiClient(profileName = "default") {
291
+ const env = {
292
+ HACIENDA_USERNAME: process.env.HACIENDA_USERNAME,
293
+ HACIENDA_PASSWORD: process.env.HACIENDA_PASSWORD,
294
+ HACIENDA_P12_PIN: process.env.HACIENDA_P12_PIN
295
+ };
296
+ const config = await loadConfig(profileName, { env });
297
+ const identity = fingerprint(config);
298
+ const cached = clientCache.get(profileName);
299
+ if (cached?.fingerprint === identity) return cached.promise;
300
+ const promise = bootstrapClient({ profileName, configOptions: { env } }).then(({ httpClient, config: authenticatedConfig }) => {
301
+ if (clientCache.get(profileName) === entry)
302
+ entry.fingerprint = fingerprint(authenticatedConfig);
303
+ return httpClient;
304
+ }).catch((error) => {
305
+ if (clientCache.get(profileName) === entry) clientCache.delete(profileName);
306
+ throw error;
307
+ });
308
+ const entry = { fingerprint: identity, promise };
309
+ clientCache.set(profileName, entry);
310
+ return entry.promise;
255
311
  }
256
312
 
257
313
  // src/tools/document-tools.ts
@@ -294,7 +350,7 @@ function registerCheckStatusTool(server) {
294
350
  ]
295
351
  };
296
352
  } catch (error) {
297
- const message = error instanceof Error ? error.message : String(error);
353
+ const message = sanitizeLogString2(error instanceof Error ? error.message : String(error));
298
354
  return {
299
355
  content: [
300
356
  {
@@ -360,7 +416,7 @@ function registerListDocumentsTool(server) {
360
416
  ]
361
417
  };
362
418
  } catch (error) {
363
- const message = error instanceof Error ? error.message : String(error);
419
+ const message = sanitizeLogString2(error instanceof Error ? error.message : String(error));
364
420
  return {
365
421
  content: [
366
422
  {
@@ -426,7 +482,7 @@ ${xmlContent}` : null
426
482
  ]
427
483
  };
428
484
  } catch (error) {
429
- const message = error instanceof Error ? error.message : String(error);
485
+ const message = sanitizeLogString2(error instanceof Error ? error.message : String(error));
430
486
  return {
431
487
  content: [
432
488
  {
@@ -443,7 +499,7 @@ ${xmlContent}` : null
443
499
 
444
500
  // src/tools/lookup-tools.ts
445
501
  import { z as z3 } from "zod";
446
- import { lookupTaxpayer } from "@dojocoding/hacienda-sdk";
502
+ import { lookupTaxpayer, sanitizeLogString as sanitizeLogString3 } from "@dojocoding/hacienda-sdk";
447
503
  import { IDENTIFICATION_TYPE_NAMES } from "@dojocoding/hacienda-shared";
448
504
  function registerLookupTaxpayerTool(server) {
449
505
  server.tool(
@@ -474,7 +530,7 @@ function registerLookupTaxpayerTool(server) {
474
530
  ]
475
531
  };
476
532
  } catch (error) {
477
- const message = error instanceof Error ? error.message : String(error);
533
+ const message = sanitizeLogString3(error instanceof Error ? error.message : String(error));
478
534
  return {
479
535
  content: [
480
536
  {
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createServer
4
- } from "./chunk-Q2UJO3PQ.js";
4
+ } from "./chunk-A5SYNMZP.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createServer
3
- } from "./chunk-Q2UJO3PQ.js";
3
+ } from "./chunk-A5SYNMZP.js";
4
4
 
5
5
  // src/index.ts
6
6
  var PACKAGE_NAME = "@dojocoding/hacienda-mcp";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dojocoding/hacienda-mcp",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "MCP Server for Costa Rica electronic invoicing (AI-accessible tools)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -28,8 +28,8 @@
28
28
  "model-context-protocol"
29
29
  ],
30
30
  "dependencies": {
31
- "@dojocoding/hacienda-sdk": "0.4.1",
32
- "@dojocoding/hacienda-shared": "0.4.1",
31
+ "@dojocoding/hacienda-sdk": "0.4.2",
32
+ "@dojocoding/hacienda-shared": "0.4.2",
33
33
  "@modelcontextprotocol/sdk": "^1.32.0",
34
34
  "xmllint-wasm": "^5.3.0",
35
35
  "zod": "^4.6.5"