@casys/mcp-einvoice 0.1.1 → 0.1.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/mcp-einvoice.mjs CHANGED
@@ -8205,6 +8205,7 @@ var require_stringify = __commonJS({
8205
8205
  nullStr: "null",
8206
8206
  simpleKeys: false,
8207
8207
  singleQuote: null,
8208
+ trailingComma: false,
8208
8209
  trueStr: "true",
8209
8210
  verifyAliasOrder: true
8210
8211
  }, doc.schema.toStringOptions, options);
@@ -8722,12 +8723,19 @@ ${indent}${line}` : "\n";
8722
8723
  if (comment)
8723
8724
  reqNewline = true;
8724
8725
  let str = stringify.stringify(item, itemCtx, () => comment = null);
8725
- if (i < items.length - 1)
8726
+ reqNewline || (reqNewline = lines.length > linesAtValue || str.includes("\n"));
8727
+ if (i < items.length - 1) {
8726
8728
  str += ",";
8729
+ } else if (ctx.options.trailingComma) {
8730
+ if (ctx.options.lineWidth > 0) {
8731
+ reqNewline || (reqNewline = lines.reduce((sum, line) => sum + line.length + 2, 2) + (str.length + 2) > ctx.options.lineWidth);
8732
+ }
8733
+ if (reqNewline) {
8734
+ str += ",";
8735
+ }
8736
+ }
8727
8737
  if (comment)
8728
8738
  str += stringifyComment.lineComment(str, itemIndent, commentString(comment));
8729
- if (!reqNewline && (lines.length > linesAtValue || str.includes("\n")))
8730
- reqNewline = true;
8731
8739
  lines.push(str);
8732
8740
  linesAtValue = lines.length;
8733
8741
  }
@@ -11741,17 +11749,22 @@ var require_compose_node = __commonJS({
11741
11749
  case "block-map":
11742
11750
  case "block-seq":
11743
11751
  case "flow-collection":
11744
- node = composeCollection.composeCollection(CN, ctx, token, props, onError);
11745
- if (anchor)
11746
- node.anchor = anchor.source.substring(1);
11752
+ try {
11753
+ node = composeCollection.composeCollection(CN, ctx, token, props, onError);
11754
+ if (anchor)
11755
+ node.anchor = anchor.source.substring(1);
11756
+ } catch (error2) {
11757
+ const message2 = error2 instanceof Error ? error2.message : String(error2);
11758
+ onError(token, "RESOURCE_EXHAUSTION", message2);
11759
+ }
11747
11760
  break;
11748
11761
  default: {
11749
11762
  const message2 = token.type === "error" ? token.message : `Unsupported token (type: ${token.type})`;
11750
11763
  onError(token, "UNEXPECTED_TOKEN", message2);
11751
- node = composeEmptyNode(ctx, token.offset, void 0, null, props, onError);
11752
11764
  isSrcToken = false;
11753
11765
  }
11754
11766
  }
11767
+ node ?? (node = composeEmptyNode(ctx, token.offset, void 0, null, props, onError));
11755
11768
  if (anchor && node.anchor === "")
11756
11769
  onError(anchor, "BAD_ALIAS", "Anchor cannot be an empty string");
11757
11770
  if (atKey && ctx.options.stringKeys && (!identity.isScalar(node) || typeof node.value !== "string" || node.tag && node.tag !== "tag:yaml.org,2002:str")) {
@@ -35496,7 +35509,7 @@ function isCloudflareWorkers() {
35496
35509
  var USER_AGENT;
35497
35510
  if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) {
35498
35511
  const NAME = "jose";
35499
- const VERSION2 = "v6.2.1";
35512
+ const VERSION2 = "v6.2.2";
35500
35513
  USER_AGENT = `${NAME}/${VERSION2}`;
35501
35514
  }
35502
35515
  var customFetch = Symbol();
@@ -37701,6 +37714,97 @@ function defaultHumanName(name) {
37701
37714
  return name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
37702
37715
  }
37703
37716
 
37717
+ // node_modules/@casys/mcp-server/src/inspector/launcher.ts
37718
+ async function launchInspector(serverCommand, serverArgs, options) {
37719
+ const port = options?.port ?? 6274;
37720
+ const shouldOpen = options?.open ?? true;
37721
+ const filteredArgs = serverArgs.filter((a) => a !== "--inspect");
37722
+ const inspectorEnv = {
37723
+ ...options?.env,
37724
+ CLIENT_PORT: String(port)
37725
+ };
37726
+ console.error(`[mcp-inspector] Starting inspector on http://localhost:${port}`);
37727
+ console.error(`[mcp-inspector] Server: ${serverCommand} ${filteredArgs.join(" ")}`);
37728
+ const command = new Deno.Command("npx", {
37729
+ args: [
37730
+ "-y",
37731
+ "@modelcontextprotocol/inspector",
37732
+ serverCommand,
37733
+ ...filteredArgs
37734
+ ],
37735
+ env: {
37736
+ ...Deno.env.toObject(),
37737
+ ...inspectorEnv
37738
+ },
37739
+ stdin: "inherit",
37740
+ stdout: "inherit",
37741
+ stderr: "inherit"
37742
+ });
37743
+ const process4 = command.spawn();
37744
+ if (shouldOpen) {
37745
+ setTimeout(() => {
37746
+ openBrowser(`http://localhost:${port}`).catch(() => {
37747
+ });
37748
+ }, 2e3);
37749
+ }
37750
+ const status = await process4.status;
37751
+ if (!status.success) {
37752
+ console.error(`[mcp-inspector] Inspector exited with code ${status.code}`);
37753
+ Deno.exit(status.code);
37754
+ }
37755
+ }
37756
+ async function openBrowser(url2) {
37757
+ const os = Deno.build.os;
37758
+ let cmd;
37759
+ if (os === "darwin") {
37760
+ cmd = ["open", url2];
37761
+ } else if (os === "windows") {
37762
+ cmd = ["cmd", "/c", "start", url2];
37763
+ } else {
37764
+ cmd = ["xdg-open", url2];
37765
+ }
37766
+ const process4 = new Deno.Command(cmd[0], {
37767
+ args: cmd.slice(1),
37768
+ stdin: "null",
37769
+ stdout: "null",
37770
+ stderr: "null"
37771
+ });
37772
+ await process4.spawn().status;
37773
+ }
37774
+
37775
+ // src/adapters/shared/errors.ts
37776
+ var NotSupportedError = class extends Error {
37777
+ constructor(adapter, method, alternative) {
37778
+ super(`[${adapter}] ${method} is not supported. ${alternative}`);
37779
+ this.name = "NotSupportedError";
37780
+ }
37781
+ };
37782
+ var AdapterAPIError = class extends Error {
37783
+ constructor(adapter, message2, status, body) {
37784
+ super(message2);
37785
+ this.status = status;
37786
+ this.body = body;
37787
+ this.name = `${adapter}APIError`;
37788
+ }
37789
+ };
37790
+
37791
+ // src/tools/error-mapper.ts
37792
+ function einvoiceErrorMapper(error2, toolName) {
37793
+ if (error2 instanceof NotSupportedError) {
37794
+ return error2.message;
37795
+ }
37796
+ if (error2 instanceof AdapterAPIError) {
37797
+ return `[${toolName}] API error ${error2.status}: ${error2.message.slice(0, 300)}`;
37798
+ }
37799
+ if (error2 instanceof Error) {
37800
+ if (error2.message.includes("is required") || error2.message.includes("must ")) {
37801
+ return error2.message;
37802
+ }
37803
+ return `[${toolName}] ${error2.message.slice(0, 300)}`;
37804
+ }
37805
+ return null;
37806
+ }
37807
+
37704
37808
  // src/generated-store.ts
37705
37809
  var EXPIRY_MS = 10 * 60 * 1e3;
37706
37810
  var store = /* @__PURE__ */ new Map();
@@ -37724,7 +37828,7 @@ function getGenerated(id) {
37724
37828
  return { file: entry.file, filename: entry.filename };
37725
37829
  }
37726
37830
 
37727
- // src/tools/invoice.ts
37831
+ // src/adapters/shared/encoding.ts
37728
37832
  function uint8ToBase64(data) {
37729
37833
  let binary = "";
37730
37834
  for (let i = 0; i < data.length; i += 8192) {
@@ -37732,72 +37836,52 @@ function uint8ToBase64(data) {
37732
37836
  }
37733
37837
  return btoa(binary);
37734
37838
  }
37735
- function normalizeInvoiceForGenerate(inv) {
37736
- const normalized = { ...inv };
37737
- for (const party of ["seller", "buyer"]) {
37738
- if (normalized[party] && !normalized[party].postalAddress) {
37739
- normalized[party] = {
37740
- ...normalized[party],
37741
- postalAddress: { country: normalized[party].country ?? "FR" }
37742
- };
37743
- }
37744
- }
37745
- for (const party of ["seller", "buyer"]) {
37746
- const p = normalized[party];
37747
- if (p && !p.electronicAddress && p.siren && p.siret) {
37748
- normalized[party] = {
37749
- ...p,
37750
- electronicAddress: `0225:${p.siren}_${p.siret}`,
37751
- identifiers: p.identifiers ?? [
37752
- { type: "ELECTRONIC_ADDRESS", value: `${p.siren}_${p.siret}`, scheme: "0225" },
37753
- { type: "PARTY_LEGAL_IDENTIFIER", value: p.siren, scheme: "0002" }
37754
- ]
37755
- };
37756
- }
37757
- }
37758
- if (Array.isArray(normalized.paymentTerms)) {
37759
- normalized.paymentTerms = normalized.paymentTerms.map((t) => t.description ?? t).join("; ");
37760
- }
37761
- return normalized;
37839
+ function encodePathSegment(s) {
37840
+ return encodeURIComponent(s);
37762
37841
  }
37842
+
37843
+ // src/tools/invoice.ts
37763
37844
  function mapToViewerPreview(inv) {
37764
37845
  const lines = (inv.lines ?? []).map((l) => {
37765
37846
  const line = l;
37766
37847
  return {
37767
- description: line.item?.name ?? line.description,
37848
+ description: line.item?.name ?? line.description ?? line.name,
37768
37849
  quantity: line.billedQuantity?.quantity ?? line.quantity,
37769
- unit_price: line.price?.netAmount?.amount ?? line.unit_price,
37770
- tax_rate: line.taxDetail?.percent ?? line.tax_rate,
37850
+ unit_price: line.price?.netAmount?.amount ?? line.unitPrice ?? line.unit_price,
37851
+ tax_rate: line.taxDetail?.percent ?? line.taxRate ?? line.tax_rate,
37771
37852
  amount: line.totalAmount?.amount ?? line.amount
37772
37853
  };
37773
37854
  });
37774
37855
  return {
37775
37856
  id: "(aper\xE7u)",
37776
- invoice_number: inv.invoiceId,
37777
- issue_date: inv.invoiceDate,
37778
- due_date: inv.invoiceDueDate,
37779
- invoice_type: inv.detailedType?.value ?? inv.type,
37780
- sender_name: inv.seller?.name,
37781
- sender_id: inv.seller?.siret ?? inv.seller?.siren,
37782
- sender_vat: inv.seller?.vatNumber,
37783
- receiver_name: inv.buyer?.name,
37784
- receiver_id: inv.buyer?.siret ?? inv.buyer?.siren,
37785
- receiver_vat: inv.buyer?.vatNumber,
37786
- currency: inv.monetary?.invoiceCurrency ?? "EUR",
37787
- total_ht: inv.monetary?.taxBasisTotalAmount?.amount ?? inv.monetary?.lineTotalAmount?.amount,
37788
- total_tax: inv.monetary?.taxTotalAmount?.amount,
37789
- total_ttc: inv.monetary?.invoiceAmount?.amount ?? inv.monetary?.payableAmount?.amount,
37857
+ invoice_number: inv.invoiceId ?? inv.invoice_id ?? inv.invoiceNumber,
37858
+ issue_date: inv.invoiceDate ?? inv.issue_date ?? inv.issueDate,
37859
+ due_date: inv.invoiceDueDate ?? inv.due_date ?? inv.dueDate,
37860
+ invoice_type: inv.detailedType?.value ?? inv.type ?? inv.invoiceType,
37861
+ sender_name: inv.seller?.name ?? inv.senderName,
37862
+ sender_id: inv.seller?.siret ?? inv.seller?.siren ?? inv.senderId,
37863
+ sender_vat: inv.seller?.vatNumber ?? inv.senderVat,
37864
+ receiver_name: inv.buyer?.name ?? inv.receiverName,
37865
+ receiver_id: inv.buyer?.siret ?? inv.buyer?.siren ?? inv.receiverId,
37866
+ receiver_vat: inv.buyer?.vatNumber ?? inv.receiverVat,
37867
+ currency: inv.monetary?.invoiceCurrency ?? inv.currency ?? "EUR",
37868
+ total_ht: inv.monetary?.taxBasisTotalAmount?.amount ?? inv.monetary?.lineTotalAmount?.amount ?? inv.totalHt,
37869
+ total_tax: inv.monetary?.taxTotalAmount?.amount ?? inv.totalTax,
37870
+ total_ttc: inv.monetary?.invoiceAmount?.amount ?? inv.monetary?.payableAmount?.amount ?? inv.totalTtc ?? inv.total_amount,
37790
37871
  items: lines,
37791
37872
  status: "aper\xE7u",
37792
37873
  direction: "sent"
37793
37874
  };
37794
37875
  }
37795
- var INVOICE_SCHEMA_DESCRIPTION = 'Invoice data in Iopole format. Required fields: invoiceId (string, max 20 chars): invoice number e.g. "CASYS-001"; invoiceDate (string, YYYY-MM-DD): issue date; type (number): invoice type code, usually 380 for commercial invoice; processType (string): e.g. "B1" for goods; invoiceDueDate (string, YYYY-MM-DD): due date; seller (object): { name, siren, siret, country, vatNumber, electronicAddress (format "0225:siren_siret"), identifiers: [{ type: "ELECTRONIC_ADDRESS", value: "siren_siret", scheme: "0225" }] }; buyer (object): same structure as seller; monetary (object): { invoiceCurrency: "EUR", invoiceAmount: { amount }, payableAmount: { amount }, taxTotalAmount: { amount, currency: "EUR" }, lineTotalAmount: { amount }, taxBasisTotalAmount: { amount } }; taxDetails (array): [{ percent, taxType: "VAT", categoryCode: "S", taxableAmount: { amount }, taxAmount: { amount } }]; lines (array): [{ id: "1", item: { name }, billedQuantity: { quantity, unitCode: "DAY"|"C62" }, price: { netAmount: { amount }, baseQuantity: { quantity: 1, unitCode } }, totalAmount: { amount }, taxDetail: { percent, taxType: "VAT", categoryCode: "S" } }]; paymentTerms (string, optional): payment conditions text; notes (array, optional): [{ type: { code: "PMT" }, content: "..." }]';
37876
+ var GENERATE_HINT = "Before generating, call einvoice_config_entities_list to verify the seller is registered. After generating, the user can submit directly via the viewer button. ";
37877
+ var INVOICE_SCHEMA_DESCRIPTION = 'Invoice data (the adapter normalizes format-specific fields internally). Required fields: invoiceId (string): invoice number; invoiceDate (string, YYYY-MM-DD): issue date; type (number): invoice type code (380 = commercial invoice); invoiceDueDate (string, YYYY-MM-DD): due date; seller (object): { name, siren, siret, country, vatNumber }; buyer (object): same structure as seller; monetary (object): { invoiceCurrency, invoiceAmount: { amount }, taxTotalAmount: { amount }, lineTotalAmount: { amount }, taxBasisTotalAmount: { amount } }; taxDetails (array): [{ percent, taxType: "VAT", categoryCode: "S", taxableAmount: { amount }, taxAmount: { amount } }]; lines (array): [{ id, item: { name }, billedQuantity: { quantity, unitCode }, price: { netAmount: { amount } }, totalAmount: { amount }, taxDetail: { percent } }]';
37796
37878
  var invoiceTools = [
37797
37879
  // ── Emit ────────────────────────────────────────────────
37798
37880
  {
37799
- name: "einvoice_invoice_emit",
37800
- description: "Emit (send) an invoice via the e-invoicing platform. Provide EITHER a generated_id (from a generate preview) OR file_base64 + filename. Asynchronous \u2014 returns a GUID to track the request.",
37881
+ name: "einvoice_invoice_submit",
37882
+ annotations: { destructiveHint: true },
37883
+ requires: ["emitInvoice"],
37884
+ description: "Submit an invoice to the e-invoicing platform. Usually triggered by the viewer's Submit button \u2014 do not call manually after a generate preview. Accepts generated_id OR file_base64 + filename.",
37801
37885
  category: "invoice",
37802
37886
  inputSchema: {
37803
37887
  type: "object",
@@ -37821,26 +37905,26 @@ var invoiceTools = [
37821
37905
  const stored = getGenerated(input.generated_id);
37822
37906
  if (!stored) {
37823
37907
  throw new Error(
37824
- "[einvoice_invoice_emit] Generated file expired or not found. Regenerate the invoice first."
37908
+ "[einvoice_invoice_submit] Generated file expired or not found. Regenerate the invoice first."
37825
37909
  );
37826
37910
  }
37827
37911
  return await ctx.adapter.emitInvoice(stored);
37828
37912
  }
37829
37913
  if (!input.file_base64 || !input.filename) {
37830
37914
  throw new Error(
37831
- "[einvoice_invoice_emit] Provide either 'generated_id' or both 'file_base64' and 'filename'"
37915
+ "[einvoice_invoice_submit] Provide either 'generated_id' or both 'file_base64' and 'filename'"
37832
37916
  );
37833
37917
  }
37834
37918
  const filename = input.filename;
37835
37919
  const lower = filename.toLowerCase();
37836
37920
  if (!lower.endsWith(".pdf") && !lower.endsWith(".xml")) {
37837
- throw new Error("[einvoice_invoice_emit] filename must end in .pdf or .xml");
37921
+ throw new Error("[einvoice_invoice_submit] filename must end in .pdf or .xml");
37838
37922
  }
37839
37923
  let binaryString;
37840
37924
  try {
37841
37925
  binaryString = atob(input.file_base64);
37842
37926
  } catch {
37843
- throw new Error("[einvoice_invoice_emit] 'file_base64' is not valid base64");
37927
+ throw new Error("[einvoice_invoice_submit] 'file_base64' is not valid base64");
37844
37928
  }
37845
37929
  const bytes = new Uint8Array(binaryString.length);
37846
37930
  for (let i = 0; i < binaryString.length; i++) {
@@ -37852,69 +37936,103 @@ var invoiceTools = [
37852
37936
  // ── Search ──────────────────────────────────────────────
37853
37937
  {
37854
37938
  name: "einvoice_invoice_search",
37939
+ annotations: { readOnlyHint: true },
37855
37940
  _meta: { ui: { resourceUri: "ui://mcp-einvoice/doclist-viewer" } },
37856
- description: "Search invoices using a query string. Returns paginated results. The query uses Lucene-like syntax (e.g. 'status:accepted AND direction:received'). Use expand to include additional data in results.",
37941
+ requires: ["searchInvoices"],
37942
+ description: "Search invoices. Use direction and status to filter. Query searches by sender name, receiver name, or invoice number. Use the direction and status parameters for filtering \u2014 not the query.",
37857
37943
  category: "invoice",
37858
37944
  inputSchema: {
37859
37945
  type: "object",
37860
37946
  properties: {
37861
37947
  q: {
37862
37948
  type: "string",
37863
- description: "Search query (Lucene-like syntax). Examples: 'status:accepted', 'direction:received', 'senderName:Acme'."
37949
+ description: "Search query (e.g. company name, invoice number). Omit to list all."
37950
+ },
37951
+ direction: {
37952
+ type: "string",
37953
+ description: "Filter by direction",
37954
+ enum: ["received", "sent"]
37864
37955
  },
37865
- expand: {
37956
+ status: {
37866
37957
  type: "string",
37867
- description: "Comma-separated list of fields to expand in results (default: 'businessData' \u2014 needed for formatted output)"
37958
+ description: "Filter by lifecycle status (after enrichment)",
37959
+ enum: [
37960
+ "SUBMITTED",
37961
+ "ISSUED",
37962
+ "MADE_AVAILABLE",
37963
+ "DELIVERED",
37964
+ "IN_HAND",
37965
+ "APPROVED",
37966
+ "PARTIALLY_APPROVED",
37967
+ "REFUSED",
37968
+ "DISPUTED",
37969
+ "SUSPENDED",
37970
+ "PAYMENT_SENT",
37971
+ "PAYMENT_RECEIVED",
37972
+ "COMPLETED",
37973
+ "WRONG_ROUTING",
37974
+ "INVALID",
37975
+ "DUPLICATED"
37976
+ ]
37868
37977
  },
37869
37978
  offset: { type: "number", description: "Result offset (default 0)" },
37870
- limit: { type: "number", description: "Max results to return (default 50, max 200)" }
37979
+ limit: { type: "number", description: "Max results (default 50, max 200)" }
37871
37980
  }
37872
37981
  },
37873
37982
  handler: async (input, ctx) => {
37874
37983
  let q = input.q;
37875
37984
  if (q != null && /^\s*\*?\s*$/.test(q)) q = void 0;
37876
- const result = await ctx.adapter.searchInvoices({
37985
+ const dirFilter = input.direction;
37986
+ const statusFilter = input.status;
37987
+ const { rows: rawRows, count } = await ctx.adapter.searchInvoices({
37877
37988
  q,
37878
- expand: input.expand ?? "businessData",
37989
+ direction: dirFilter,
37990
+ status: statusFilter,
37879
37991
  offset: input.offset,
37880
37992
  limit: input.limit
37881
37993
  });
37882
- const meta3 = result.meta;
37883
- if (meta3?.count != null && result.count == null) result.count = meta3.count;
37884
- const data = result.data;
37885
- if (Array.isArray(data)) {
37886
- result.data = data.map((row) => {
37887
- const m = row.metadata ?? {};
37888
- const bd = row.businessData ?? {};
37889
- const dateRaw = bd.invoiceDate ?? m.createDate?.split("T")[0];
37890
- const amount = bd.monetary?.invoiceAmount?.amount;
37891
- const currency = bd.monetary?.invoiceCurrency ?? "EUR";
37892
- return {
37893
- _id: m.invoiceId,
37894
- "N\xB0": bd.invoiceId ?? "\u2014",
37895
- "Statut": m.state,
37896
- "Direction": m.direction === "INBOUND" ? "Re\xE7ue" : m.direction === "OUTBOUND" ? "\xC9mise" : m.direction,
37897
- "\xC9metteur": bd.seller?.name ?? "\u2014",
37898
- "Destinataire": bd.buyer?.name ?? "\u2014",
37899
- "Date": dateRaw ? new Date(dateRaw).toLocaleDateString("fr-FR", { day: "numeric", month: "short", year: "numeric" }) : "\u2014",
37900
- "Montant": amount != null ? `${amount.toLocaleString("fr-FR")} ${currency}` : "\u2014"
37901
- };
37902
- });
37903
- }
37904
- return {
37905
- ...result,
37994
+ let rows = rawRows;
37995
+ if (dirFilter) rows = rows.filter((r) => r.direction === dirFilter);
37996
+ if (statusFilter) rows = rows.filter((r) => r.status === statusFilter);
37997
+ const data = rows.map((r) => {
37998
+ const shortDate = r.date ? new Date(r.date).toLocaleDateString("fr-FR", { day: "numeric", month: "short" }) : "";
37999
+ const tiers = r.direction === "sent" ? r.receiverName : r.senderName;
38000
+ return {
38001
+ _id: r.id,
38002
+ _direction: r.direction,
38003
+ "Direction": r.direction === "received" ? "Entrante" : r.direction === "sent" ? "Sortante" : "\u2014",
38004
+ "N\xB0": r.invoiceNumber ?? "\u2014",
38005
+ "Statut": r.status ?? "\u2014",
38006
+ "Tiers": tiers ?? "\u2014",
38007
+ "Date": shortDate || "\u2014",
38008
+ "Montant": r.amount != null ? `${Number(r.amount).toLocaleString("fr-FR")} ${r.currency ?? "EUR"}` : "\u2014"
38009
+ };
38010
+ });
38011
+ const dirLabel = dirFilter === "received" ? "re\xE7ues" : dirFilter === "sent" ? "envoy\xE9es" : "";
38012
+ const statusLabel = statusFilter ?? "";
38013
+ const titleParts = ["Factures", dirLabel, statusLabel ? `(${statusLabel})` : ""].filter(Boolean);
38014
+ const viewerData = {
38015
+ data,
38016
+ count: rows.length !== rawRows.length ? rows.length : count ?? rows.length,
38017
+ _title: titleParts.join(" "),
37906
38018
  _rowAction: {
37907
38019
  toolName: "einvoice_invoice_get",
37908
38020
  idField: "_id",
37909
38021
  argName: "id"
37910
38022
  }
37911
38023
  };
38024
+ return {
38025
+ content: `${rows.length} ${titleParts.join(" ")} trouv\xE9es`,
38026
+ structuredContent: viewerData
38027
+ };
37912
38028
  }
37913
38029
  },
37914
38030
  // ── Get by ID ───────────────────────────────────────────
37915
38031
  {
37916
38032
  name: "einvoice_invoice_get",
38033
+ annotations: { readOnlyHint: true },
37917
38034
  _meta: { ui: { resourceUri: "ui://mcp-einvoice/invoice-viewer" } },
38035
+ requires: ["getInvoice"],
37918
38036
  description: "Get a single invoice by its ID. Returns full invoice details including status history, sender/receiver info, and line items.",
37919
38037
  category: "invoice",
37920
38038
  inputSchema: {
@@ -37928,73 +38046,48 @@ var invoiceTools = [
37928
38046
  if (!input.id) {
37929
38047
  throw new Error("[einvoice_invoice_get] 'id' is required");
37930
38048
  }
37931
- const raw2 = await ctx.adapter.getInvoice(input.id);
37932
- const inv = Array.isArray(raw2) ? raw2[0] : raw2;
37933
- if (!inv?.businessData) {
37934
- return {
37935
- id: inv?.invoiceId,
37936
- status: inv?.state ?? inv?.status ?? "UNKNOWN",
37937
- direction: (() => {
37938
- const d = inv?.way ?? inv?.metadata?.direction;
37939
- if (!d) return void 0;
37940
- if (d === "RECEIVED" || d === "INBOUND") return "received";
37941
- if (d === "SENT" || d === "EMITTED" || d === "OUTBOUND") return "sent";
37942
- return d.toLowerCase();
37943
- })(),
37944
- format: inv?.originalFormat,
37945
- network: inv?.originalNetwork,
37946
- issue_date: inv?.date
37947
- };
37948
- }
37949
- const bd = inv.businessData;
37950
- const lines = (bd.lines ?? []).map((l) => {
37951
- const line = l;
37952
- return {
37953
- description: line.item?.name,
37954
- quantity: line.billedQuantity?.quantity,
37955
- unit_price: line.price?.netAmount?.amount,
37956
- tax_rate: line.taxDetail?.percent,
37957
- amount: line.totalAmount?.amount
37958
- };
37959
- });
37960
- return {
37961
- id: inv.invoiceId,
37962
- invoice_number: bd.invoiceId,
37963
- status: inv.state ?? inv.status,
37964
- direction: (() => {
37965
- const d = inv.way ?? inv.metadata?.direction;
37966
- if (!d) return void 0;
37967
- if (d === "RECEIVED" || d === "INBOUND") return "received";
37968
- if (d === "SENT" || d === "EMITTED" || d === "OUTBOUND") return "sent";
37969
- return d.toLowerCase();
37970
- })(),
37971
- format: inv.originalFormat,
37972
- network: inv.originalNetwork,
37973
- invoice_type: bd.detailedType?.value,
37974
- sender_name: bd.seller?.name,
37975
- sender_id: bd.seller?.siret ?? bd.seller?.siren,
37976
- sender_vat: bd.seller?.vatNumber,
37977
- receiver_name: bd.buyer?.name,
37978
- receiver_id: bd.buyer?.siret ?? bd.buyer?.siren,
37979
- receiver_vat: bd.buyer?.vatNumber,
37980
- issue_date: bd.invoiceDate,
37981
- due_date: bd.invoiceDueDate,
37982
- receipt_date: bd.invoiceReceiptDate,
37983
- currency: bd.monetary?.invoiceCurrency ?? "EUR",
37984
- total_ht: bd.monetary?.taxBasisTotalAmount?.amount,
37985
- total_tax: bd.monetary?.taxTotalAmount?.amount,
37986
- total_ttc: bd.monetary?.invoiceAmount?.amount,
37987
- items: lines,
37988
- notes: (bd.notes ?? []).map((n) => {
37989
- const note = n;
37990
- return note.content;
37991
- }).filter(Boolean)
38049
+ const id = input.id;
38050
+ const inv = await ctx.adapter.getInvoice(id);
38051
+ const isTerminal2 = ["REFUSED", "COMPLETED", "CANCELLED", "PAYMENT_RECEIVED", "UNKNOWN"].includes(inv.status ?? "");
38052
+ const viewerData = {
38053
+ id: inv.id,
38054
+ invoice_number: inv.invoiceNumber,
38055
+ status: inv.status,
38056
+ direction: inv.direction,
38057
+ format: inv.format,
38058
+ network: inv.network,
38059
+ invoice_type: inv.invoiceType,
38060
+ sender_name: inv.senderName,
38061
+ sender_id: inv.senderId,
38062
+ sender_vat: inv.senderVat,
38063
+ receiver_name: inv.receiverName,
38064
+ receiver_id: inv.receiverId,
38065
+ receiver_vat: inv.receiverVat,
38066
+ issue_date: inv.issueDate,
38067
+ due_date: inv.dueDate,
38068
+ receipt_date: inv.receiptDate,
38069
+ currency: inv.currency,
38070
+ total_ht: inv.totalHt,
38071
+ total_tax: inv.totalTax,
38072
+ total_ttc: inv.totalTtc,
38073
+ items: inv.lines?.map((l) => ({
38074
+ description: l.description,
38075
+ quantity: l.quantity,
38076
+ unit_price: l.unitPrice,
38077
+ tax_rate: l.taxRate,
38078
+ amount: l.amount
38079
+ })),
38080
+ notes: inv.notes,
38081
+ ...!isTerminal2 && inv.direction !== "received" ? { refreshRequest: { toolName: "einvoice_invoice_get", arguments: { id } } } : {}
37992
38082
  };
38083
+ const summary = `Invoice ${inv.invoiceNumber ?? inv.id} \u2014 ${inv.status ?? "unknown"}, ${inv.direction ?? ""}, ${inv.totalTtc != null ? inv.totalTtc + " " + (inv.currency ?? "EUR") : "no amount"}`;
38084
+ return { content: summary, structuredContent: viewerData };
37993
38085
  }
37994
38086
  },
37995
38087
  // ── Download ────────────────────────────────────────────
37996
38088
  {
37997
38089
  name: "einvoice_invoice_download",
38090
+ requires: ["downloadInvoice"],
37998
38091
  description: "Download the source file of an invoice (original CII/UBL/Factur-X XML). Returns base64-encoded content.",
37999
38092
  category: "invoice",
38000
38093
  inputSchema: {
@@ -38009,12 +38102,16 @@ var invoiceTools = [
38009
38102
  throw new Error("[einvoice_invoice_download] 'id' is required");
38010
38103
  }
38011
38104
  const { data, contentType } = await ctx.adapter.downloadInvoice(input.id);
38012
- return { content_type: contentType, data_base64: uint8ToBase64(data), size_bytes: data.length };
38105
+ return {
38106
+ content: `Downloaded invoice source (${contentType}, ${data.length} bytes)`,
38107
+ structuredContent: { content_type: contentType, data_base64: uint8ToBase64(data), size_bytes: data.length }
38108
+ };
38013
38109
  }
38014
38110
  },
38015
38111
  // ── Download readable ───────────────────────────────────
38016
38112
  {
38017
38113
  name: "einvoice_invoice_download_readable",
38114
+ requires: ["downloadReadable"],
38018
38115
  description: "Download a human-readable PDF version of an invoice. Returns base64-encoded PDF.",
38019
38116
  category: "invoice",
38020
38117
  inputSchema: {
@@ -38029,12 +38126,16 @@ var invoiceTools = [
38029
38126
  throw new Error("[einvoice_invoice_download_readable] 'id' is required");
38030
38127
  }
38031
38128
  const { data, contentType } = await ctx.adapter.downloadReadable(input.id);
38032
- return { content_type: contentType, data_base64: uint8ToBase64(data), size_bytes: data.length };
38129
+ return {
38130
+ content: `Downloaded readable PDF (${data.length} bytes)`,
38131
+ structuredContent: { content_type: contentType, data_base64: uint8ToBase64(data), size_bytes: data.length }
38132
+ };
38033
38133
  }
38034
38134
  },
38035
38135
  // ── Invoice Files ─────────────────────────────────────────
38036
38136
  {
38037
38137
  name: "einvoice_invoice_files",
38138
+ requires: ["getInvoiceFiles"],
38038
38139
  description: "Get metadata of ALL related files for an invoice (source XML, readable PDF, attachments). Use einvoice_invoice_attachments for only business attachments.",
38039
38140
  category: "invoice",
38040
38141
  inputSchema: {
@@ -38054,6 +38155,7 @@ var invoiceTools = [
38054
38155
  // ── Attachments ─────────────────────────────────────────
38055
38156
  {
38056
38157
  name: "einvoice_invoice_attachments",
38158
+ requires: ["getAttachments"],
38057
38159
  description: "Get only business attachments (supporting documents, purchase orders, etc.) for an invoice. Use einvoice_invoice_files for ALL related files including source XML and PDF.",
38058
38160
  category: "invoice",
38059
38161
  inputSchema: {
@@ -38073,6 +38175,7 @@ var invoiceTools = [
38073
38175
  // ── Download File ───────────────────────────────────────
38074
38176
  {
38075
38177
  name: "einvoice_invoice_download_file",
38178
+ requires: ["downloadFile"],
38076
38179
  description: "Download a specific file by its file ID. Returns base64-encoded content.",
38077
38180
  category: "invoice",
38078
38181
  inputSchema: {
@@ -38087,74 +38190,22 @@ var invoiceTools = [
38087
38190
  throw new Error("[einvoice_invoice_download_file] 'file_id' is required");
38088
38191
  }
38089
38192
  const { data, contentType } = await ctx.adapter.downloadFile(input.file_id);
38090
- return { content_type: contentType, data_base64: uint8ToBase64(data), size_bytes: data.length };
38091
- }
38092
- },
38093
- // ── Mark as seen ────────────────────────────────────────
38094
- {
38095
- name: "einvoice_invoice_mark_seen",
38096
- description: "Mark an invoice as seen/read. Useful for tracking which invoices have been processed.",
38097
- category: "invoice",
38098
- inputSchema: {
38099
- type: "object",
38100
- properties: {
38101
- id: { type: "string", description: "Invoice ID" }
38102
- },
38103
- required: ["id"]
38104
- },
38105
- handler: async (input, ctx) => {
38106
- if (!input.id) {
38107
- throw new Error("[einvoice_invoice_mark_seen] 'id' is required");
38108
- }
38109
- return await ctx.adapter.markInvoiceSeen(input.id);
38110
- }
38111
- },
38112
- // ── Not seen ────────────────────────────────────────────
38113
- {
38114
- name: "einvoice_invoice_not_seen",
38115
- _meta: { ui: { resourceUri: "ui://mcp-einvoice/doclist-viewer" } },
38116
- description: "Get invoices that have not been marked as seen (PULL mode). Useful for polling new incoming invoices.",
38117
- category: "invoice",
38118
- inputSchema: {
38119
- type: "object",
38120
- properties: {
38121
- offset: { type: "number", description: "Result offset (default 0)" },
38122
- limit: { type: "number", description: "Max results (default 50)" }
38123
- }
38124
- },
38125
- handler: async (input, ctx) => {
38126
- const result = await ctx.adapter.getUnseenInvoices({
38127
- offset: input.offset,
38128
- limit: input.limit
38129
- });
38130
- const data = result.data;
38131
- if (Array.isArray(data)) {
38132
- result.data = data.map((row) => {
38133
- const m = row.metadata ?? row;
38134
- const dateRaw = m.createDate?.split("T")[0];
38135
- return {
38136
- _id: m.invoiceId,
38137
- "Statut": m.state,
38138
- "Direction": m.direction === "INBOUND" ? "Re\xE7ue" : m.direction === "OUTBOUND" ? "\xC9mise" : m.direction,
38139
- "Date": dateRaw ? new Date(dateRaw).toLocaleDateString("fr-FR", { day: "numeric", month: "short", year: "numeric" }) : "\u2014"
38140
- };
38141
- });
38142
- }
38143
38193
  return {
38144
- ...result,
38145
- _rowAction: {
38146
- toolName: "einvoice_invoice_get",
38147
- idField: "_id",
38148
- argName: "id"
38149
- }
38194
+ content: `Downloaded file (${contentType}, ${data.length} bytes)`,
38195
+ structuredContent: { content_type: contentType, data_base64: uint8ToBase64(data), size_bytes: data.length }
38150
38196
  };
38151
38197
  }
38152
38198
  },
38199
+ // ── seen/notSeen tools removed ──────────────────────────
38200
+ // Iopole's seen/notSeen mechanism is opaque: `seen` is not exposed in
38201
+ // search or getInvoice, and `notSeen` always returns empty in PUSH mode
38202
+ // (active webhook). Removed in v0.2.0 — see docs/CHANGELOG.md.
38153
38203
  // ── Generate CII ────────────────────────────────────────
38154
38204
  {
38155
38205
  name: "einvoice_invoice_generate_cii",
38156
38206
  _meta: { ui: { resourceUri: "ui://mcp-einvoice/invoice-viewer" } },
38157
- description: "Generate a CII invoice preview. Validates and converts to CII XML. Returns a preview for review. Use einvoice_invoice_emit with the generated_id to send. Requires a flavor (e.g. EN16931).",
38207
+ requires: ["generateCII"],
38208
+ description: GENERATE_HINT + "Generate a CII invoice preview. Validates and converts to CII XML. Returns a preview for review. Use einvoice_invoice_submit with the generated_id to send. Requires a flavor (e.g. EN16931).",
38158
38209
  category: "invoice",
38159
38210
  inputSchema: {
38160
38211
  type: "object",
@@ -38165,7 +38216,8 @@ var invoiceTools = [
38165
38216
  },
38166
38217
  flavor: {
38167
38218
  type: "string",
38168
- description: "CII profile flavor (e.g. EN16931, MINIMUM, BASIC_WL, BASIC, EXTENDED)"
38219
+ description: "CII profile flavor",
38220
+ enum: ["EN16931", "EXTENDED"]
38169
38221
  }
38170
38222
  },
38171
38223
  required: ["invoice", "flavor"]
@@ -38174,19 +38226,21 @@ var invoiceTools = [
38174
38226
  if (!input.invoice || !input.flavor) {
38175
38227
  throw new Error("[einvoice_invoice_generate_cii] 'invoice' and 'flavor' are required");
38176
38228
  }
38177
- const inv = normalizeInvoiceForGenerate(input.invoice);
38178
- const result = await ctx.adapter.generateCII({
38229
+ const inv = input.invoice;
38230
+ const xml = await ctx.adapter.generateCII({
38179
38231
  invoice: inv,
38180
38232
  flavor: input.flavor
38181
38233
  });
38182
- const raw2 = typeof result === "string" ? result : JSON.stringify(result);
38183
- const bytes = new TextEncoder().encode(raw2);
38234
+ const bytes = new TextEncoder().encode(xml);
38184
38235
  const filename = `${inv.invoiceId ?? "invoice"}.xml`;
38185
38236
  const generated_id = storeGenerated(bytes, filename);
38186
38237
  return {
38187
- generated_id,
38188
- filename,
38189
- preview: mapToViewerPreview(input.invoice)
38238
+ content: `Invoice preview generated (${filename}). The user can review and submit via the viewer button.`,
38239
+ structuredContent: {
38240
+ generated_id,
38241
+ filename,
38242
+ preview: mapToViewerPreview(inv)
38243
+ }
38190
38244
  };
38191
38245
  }
38192
38246
  },
@@ -38194,7 +38248,8 @@ var invoiceTools = [
38194
38248
  {
38195
38249
  name: "einvoice_invoice_generate_ubl",
38196
38250
  _meta: { ui: { resourceUri: "ui://mcp-einvoice/invoice-viewer" } },
38197
- description: "Generate a UBL invoice preview. Validates and converts to UBL XML. Returns a preview for review. Use einvoice_invoice_emit with the generated_id to send. Requires a flavor (e.g. EN16931).",
38251
+ requires: ["generateUBL"],
38252
+ description: GENERATE_HINT + "Generate a UBL invoice preview. Validates and converts to UBL XML. Returns a preview for review. Use einvoice_invoice_submit with the generated_id to send. Requires a flavor (e.g. EN16931).",
38198
38253
  category: "invoice",
38199
38254
  inputSchema: {
38200
38255
  type: "object",
@@ -38205,7 +38260,8 @@ var invoiceTools = [
38205
38260
  },
38206
38261
  flavor: {
38207
38262
  type: "string",
38208
- description: "UBL profile flavor (e.g. EN16931, MINIMUM, BASIC_WL, BASIC, EXTENDED)"
38263
+ description: "UBL profile flavor",
38264
+ enum: ["EN16931", "PEPPOL_BIS_3"]
38209
38265
  }
38210
38266
  },
38211
38267
  required: ["invoice", "flavor"]
@@ -38214,19 +38270,21 @@ var invoiceTools = [
38214
38270
  if (!input.invoice || !input.flavor) {
38215
38271
  throw new Error("[einvoice_invoice_generate_ubl] 'invoice' and 'flavor' are required");
38216
38272
  }
38217
- const inv = normalizeInvoiceForGenerate(input.invoice);
38218
- const result = await ctx.adapter.generateUBL({
38273
+ const inv = input.invoice;
38274
+ const xml = await ctx.adapter.generateUBL({
38219
38275
  invoice: inv,
38220
38276
  flavor: input.flavor
38221
38277
  });
38222
- const raw2 = typeof result === "string" ? result : JSON.stringify(result);
38223
- const bytes = new TextEncoder().encode(raw2);
38278
+ const bytes = new TextEncoder().encode(xml);
38224
38279
  const filename = `${inv.invoiceId ?? "invoice"}.xml`;
38225
38280
  const generated_id = storeGenerated(bytes, filename);
38226
38281
  return {
38227
- generated_id,
38228
- filename,
38229
- preview: mapToViewerPreview(input.invoice)
38282
+ content: `Invoice preview generated (${filename}). The user can review and submit via the viewer button.`,
38283
+ structuredContent: {
38284
+ generated_id,
38285
+ filename,
38286
+ preview: mapToViewerPreview(inv)
38287
+ }
38230
38288
  };
38231
38289
  }
38232
38290
  },
@@ -38234,7 +38292,8 @@ var invoiceTools = [
38234
38292
  {
38235
38293
  name: "einvoice_invoice_generate_facturx",
38236
38294
  _meta: { ui: { resourceUri: "ui://mcp-einvoice/invoice-viewer" } },
38237
- description: "Generate a Factur-X invoice preview. Creates a hybrid PDF/XML. Returns a preview for review. Use einvoice_invoice_emit with the generated_id to send. Requires a flavor (e.g. EN16931). Optionally accepts a language (FRENCH, ENGLISH, GERMAN).",
38295
+ requires: ["generateFacturX"],
38296
+ description: GENERATE_HINT + "Generate a Factur-X invoice preview. Creates a hybrid PDF/XML. Returns a preview for review. Use einvoice_invoice_submit with the generated_id to send. Requires a flavor (e.g. EN16931). Optionally accepts a language (FRENCH, ENGLISH, GERMAN).",
38238
38297
  category: "invoice",
38239
38298
  inputSchema: {
38240
38299
  type: "object",
@@ -38245,7 +38304,8 @@ var invoiceTools = [
38245
38304
  },
38246
38305
  flavor: {
38247
38306
  type: "string",
38248
- description: "Factur-X profile flavor (e.g. EN16931, MINIMUM, BASIC_WL, BASIC, EXTENDED)"
38307
+ description: "Factur-X profile flavor",
38308
+ enum: ["BASICWL", "EN16931", "EXTENDED"]
38249
38309
  },
38250
38310
  language: {
38251
38311
  type: "string",
@@ -38259,20 +38319,21 @@ var invoiceTools = [
38259
38319
  if (!input.invoice || !input.flavor) {
38260
38320
  throw new Error("[einvoice_invoice_generate_facturx] 'invoice' and 'flavor' are required");
38261
38321
  }
38262
- const inv = normalizeInvoiceForGenerate(input.invoice);
38263
- const result = await ctx.adapter.generateFacturX({
38322
+ const inv = input.invoice;
38323
+ const { data: bytes } = await ctx.adapter.generateFacturX({
38264
38324
  invoice: inv,
38265
38325
  flavor: input.flavor,
38266
38326
  language: input.language
38267
38327
  });
38268
- const raw2 = typeof result === "string" ? result : JSON.stringify(result);
38269
- const bytes = new TextEncoder().encode(raw2);
38270
38328
  const filename = `${inv.invoiceId ?? "invoice"}.pdf`;
38271
38329
  const generated_id = storeGenerated(bytes, filename);
38272
38330
  return {
38273
- generated_id,
38274
- filename,
38275
- preview: mapToViewerPreview(input.invoice)
38331
+ content: `Invoice preview generated (${filename}). The user can review and submit via the viewer button.`,
38332
+ structuredContent: {
38333
+ generated_id,
38334
+ filename,
38335
+ preview: mapToViewerPreview(inv)
38336
+ }
38276
38337
  };
38277
38338
  }
38278
38339
  }
@@ -38283,44 +38344,20 @@ var ENTITY_TYPE_LABELS = {
38283
38344
  LEGAL_UNIT: "Entit\xE9 juridique",
38284
38345
  OFFICE: "\xC9tablissement"
38285
38346
  };
38286
- function formatDirectoryFrRow(row) {
38287
- const ci = row.countryIdentifier ?? {};
38288
- const siren = ci.siren ?? row.siren;
38289
- const siret = ci.siret ?? row.siret;
38290
- const country = ci.country ?? row.country ?? "FR";
38291
- return {
38292
- _id: row.businessEntityId,
38293
- _identifiers: row.identifiers,
38294
- "Nom": row.name ?? "\u2014",
38295
- "Type": ENTITY_TYPE_LABELS[row.type] ?? row.type ?? "\u2014",
38296
- "SIREN": siren ?? "\u2014",
38297
- "SIRET": siret ?? "\u2014",
38298
- "Pays": country
38299
- };
38300
- }
38301
- function autoWrapDirectoryQuery(q) {
38302
- const trimmed = q.trim();
38303
- if (/^\d{14}$/.test(trimmed)) return `siret:"${trimmed}"`;
38304
- if (/^\d{9}$/.test(trimmed)) return `siren:"${trimmed}"`;
38305
- if (/^FR\d{11}$/i.test(trimmed)) return `vatNumber:"${trimmed.toUpperCase()}"`;
38306
- if (trimmed.length >= 3 && !/^\d+$/.test(trimmed) && !trimmed.includes(":")) {
38307
- return `name:"*${trimmed}*"`;
38308
- }
38309
- return trimmed;
38310
- }
38311
38347
  var directoryTools = [
38312
38348
  // ── French Directory ────────────────────────────────────
38313
38349
  {
38314
38350
  name: "einvoice_directory_fr_search",
38315
- _meta: { ui: { resourceUri: "ui://mcp-einvoice/doclist-viewer" } },
38316
- description: `Search the French PPF directory (Portail Public de Facturation). Find companies registered for e-invoicing in France. Raw SIRET (14 digits), SIREN (9 digits), TVA (FR + 11 digits), or company names are auto-detected and converted to Lucene syntax. You can also pass explicit Lucene syntax (e.g. siret:"43446637100011") if needed. Returns the company's registered platform (PDP) and routing information.`,
38351
+ _meta: { ui: { resourceUri: "ui://mcp-einvoice/directory-list" } },
38352
+ requires: ["searchDirectoryFr"],
38353
+ description: "Search the French PPF directory (Portail Public de Facturation). Find companies registered for e-invoicing in France. Search by SIRET (14 digits), SIREN (9 digits), TVA (FR + 11 digits), or company name. Format is auto-detected. Returns the company's registered platform (PDP) and routing information.",
38317
38354
  category: "directory",
38318
38355
  inputSchema: {
38319
38356
  type: "object",
38320
38357
  properties: {
38321
38358
  q: {
38322
38359
  type: "string",
38323
- description: 'Search query (required). Can be a raw SIRET, SIREN, VAT number, or company name \u2014 auto-converted to Lucene syntax. You can also pass explicit Lucene syntax (e.g. siret:"43446637100011").'
38360
+ description: "Search query (required). SIRET, SIREN, VAT number, or company name."
38324
38361
  },
38325
38362
  offset: { type: "number", description: "Result offset (default 0)" },
38326
38363
  limit: { type: "number", description: "Max results (default 50, max 200)" }
@@ -38331,24 +38368,47 @@ var directoryTools = [
38331
38368
  if (!input.q) {
38332
38369
  throw new Error("[einvoice_directory_fr_search] 'q' query is required");
38333
38370
  }
38334
- const result = await ctx.adapter.searchDirectoryFr({
38335
- q: autoWrapDirectoryQuery(input.q),
38371
+ const { rows, count } = await ctx.adapter.searchDirectoryFr({
38372
+ q: input.q,
38336
38373
  offset: input.offset,
38337
38374
  limit: input.limit
38338
38375
  });
38339
- const meta3 = result.meta;
38340
- if (meta3?.count != null && result.count == null) result.count = meta3.count;
38341
- const data = result.data;
38342
- if (Array.isArray(data)) {
38343
- result.data = data.map(formatDirectoryFrRow);
38344
- }
38345
- return result;
38376
+ const viewerData = {
38377
+ data: rows.map((r) => {
38378
+ const typeLabel = r.type ? ENTITY_TYPE_LABELS[r.type] ?? r.type : void 0;
38379
+ return {
38380
+ _id: r.entityId,
38381
+ _detail: {
38382
+ name: r.name,
38383
+ type: typeLabel,
38384
+ siren: r.siren,
38385
+ siret: r.siret,
38386
+ country: r.country,
38387
+ directory: r.directory,
38388
+ status: r.status,
38389
+ createdAt: r.createdAt,
38390
+ identifiers: r.identifiers
38391
+ },
38392
+ "Nom": r.name ?? "\u2014",
38393
+ "Type": typeLabel ?? "\u2014",
38394
+ "SIRET": r.siret ?? "\u2014",
38395
+ "Pays": r.country ?? "\u2014"
38396
+ };
38397
+ }),
38398
+ count,
38399
+ _title: "Annuaire fran\xE7ais"
38400
+ };
38401
+ return {
38402
+ content: `${rows.length} entities found for "${input.q}"`,
38403
+ structuredContent: viewerData
38404
+ };
38346
38405
  }
38347
38406
  },
38348
38407
  // ── International Directory ─────────────────────────────
38349
38408
  {
38350
38409
  name: "einvoice_directory_int_search",
38351
- _meta: { ui: { resourceUri: "ui://mcp-einvoice/doclist-viewer" } },
38410
+ _meta: { ui: { resourceUri: "ui://mcp-einvoice/directory-list" } },
38411
+ requires: ["searchDirectoryInt"],
38352
38412
  description: "Search the international Peppol directory. Find companies registered on the Peppol network across 40+ countries. Search by participant identifier value (e.g. VAT number, GLN, DUNS).",
38353
38413
  category: "directory",
38354
38414
  inputSchema: {
@@ -38377,6 +38437,7 @@ var directoryTools = [
38377
38437
  // ── Check Peppol Participant ────────────────────────────
38378
38438
  {
38379
38439
  name: "einvoice_directory_peppol_check",
38440
+ requires: ["checkPeppolParticipant"],
38380
38441
  description: "Verify whether a specific Peppol participant exists in the international directory. Checks by scheme and identifier value.",
38381
38442
  category: "directory",
38382
38443
  inputSchema: {
@@ -38412,7 +38473,9 @@ var statusTools = [
38412
38473
  // ── Send Status ─────────────────────────────────────────
38413
38474
  {
38414
38475
  name: "einvoice_status_send",
38415
- description: "Send a lifecycle status update for an invoice. Uses Iopole status codes: IN_HAND, APPROVED, PARTIALLY_APPROVED, DISPUTED, SUSPENDED, COMPLETED, REFUSED, PAYMENT_SENT, PAYMENT_RECEIVED. Asynchronous \u2014 returns a GUID to track the request.",
38476
+ annotations: { destructiveHint: true },
38477
+ requires: ["sendStatus"],
38478
+ description: "Send a lifecycle status update for an invoice. Uses CDAR lifecycle codes: IN_HAND (204), APPROVED (205), REFUSED (210), DISPUTED (207), SUSPENDED (208), PAYMENT_SENT (211), PAYMENT_RECEIVED (212). Asynchronous \u2014 returns a confirmation.",
38416
38479
  category: "status",
38417
38480
  inputSchema: {
38418
38481
  type: "object",
@@ -38464,7 +38527,9 @@ var statusTools = [
38464
38527
  // ── Get Status History ────────────────────────────────────
38465
38528
  {
38466
38529
  name: "einvoice_status_history",
38530
+ annotations: { readOnlyHint: true },
38467
38531
  _meta: { ui: { resourceUri: "ui://mcp-einvoice/status-timeline" } },
38532
+ requires: ["getStatusHistory"],
38468
38533
  description: "Get the status history for an invoice. Returns all status changes in chronological order.",
38469
38534
  category: "status",
38470
38535
  inputSchema: {
@@ -38478,64 +38543,14 @@ var statusTools = [
38478
38543
  if (!input.invoice_id) {
38479
38544
  throw new Error("[einvoice_status_history] 'invoice_id' is required");
38480
38545
  }
38481
- const raw2 = await ctx.adapter.getStatusHistory(input.invoice_id);
38482
- if (Array.isArray(raw2)) return { entries: raw2 };
38483
- if (raw2 && typeof raw2 === "object") {
38484
- const obj = raw2;
38485
- if (Array.isArray(obj.data)) return { entries: obj.data };
38486
- if (Array.isArray(obj.entries)) return raw2;
38487
- if (Array.isArray(obj.history)) return { entries: obj.history };
38488
- }
38489
- return { entries: [] };
38490
- }
38491
- },
38492
- // ── Not Seen ────────────────────────────────────────────
38493
- {
38494
- name: "einvoice_status_not_seen",
38495
- _meta: { ui: { resourceUri: "ui://mcp-einvoice/doclist-viewer" } },
38496
- description: "Get status updates that have not been marked as seen. Useful for polling new incoming status changes on invoices.",
38497
- category: "status",
38498
- inputSchema: {
38499
- type: "object",
38500
- properties: {
38501
- offset: { type: "number", description: "Result offset (default 0)" },
38502
- limit: { type: "number", description: "Max results (default 50)" }
38503
- }
38504
- },
38505
- handler: async (input, ctx) => {
38506
- const result = await ctx.adapter.getUnseenStatuses({
38507
- offset: input.offset,
38508
- limit: input.limit
38509
- });
38546
+ const result = await ctx.adapter.getStatusHistory(input.invoice_id);
38510
38547
  return {
38511
- ...result,
38512
- _rowAction: {
38513
- toolName: "einvoice_status_history",
38514
- idField: "invoiceId",
38515
- argName: "invoice_id"
38516
- }
38548
+ content: `${result.entries.length} status entries for invoice ${input.invoice_id}`,
38549
+ structuredContent: result
38517
38550
  };
38518
38551
  }
38519
- },
38520
- // ── Mark as Seen ────────────────────────────────────────
38521
- {
38522
- name: "einvoice_status_mark_seen",
38523
- description: "Mark a status update as seen/processed.",
38524
- category: "status",
38525
- inputSchema: {
38526
- type: "object",
38527
- properties: {
38528
- status_id: { type: "string", description: "Status ID" }
38529
- },
38530
- required: ["status_id"]
38531
- },
38532
- handler: async (input, ctx) => {
38533
- if (!input.status_id) {
38534
- throw new Error("[einvoice_status_mark_seen] 'status_id' is required");
38535
- }
38536
- return await ctx.adapter.markStatusSeen(input.status_id);
38537
- }
38538
38552
  }
38553
+ // ── seen/notSeen tools removed — see invoice.ts comment and docs/CHANGELOG.md
38539
38554
  ];
38540
38555
 
38541
38556
  // src/tools/reporting.ts
@@ -38543,6 +38558,7 @@ var reportingTools = [
38543
38558
  // ── Invoice Transaction Reporting ───────────────────────
38544
38559
  {
38545
38560
  name: "einvoice_reporting_invoice_transaction",
38561
+ requires: ["reportInvoiceTransaction"],
38546
38562
  description: "Report an invoice transaction to the French tax authority (e-reporting). Required for B2C and international invoice transactions. Asynchronous \u2014 returns a GUID.",
38547
38563
  category: "reporting",
38548
38564
  inputSchema: {
@@ -38550,7 +38566,7 @@ var reportingTools = [
38550
38566
  properties: {
38551
38567
  transaction: {
38552
38568
  type: "object",
38553
- description: "Invoice transaction data for DGFiP e-reporting. Typical fields: invoiceReference (string): the invoice number; transactionDate (string, YYYY-MM-DD): date of the transaction; transactionType (string): e.g. 'B2C', 'INTERNATIONAL'; totalAmount (number): total TTC amount; taxDetails (array): [{ vatRate: number, taxableAmount: number, taxAmount: number }]; counterparty (object): { name, country, identifier }; currency (string, default 'EUR'). Exact schema depends on the PA provider \u2014 check Iopole docs for the full specification."
38569
+ description: "Invoice transaction data for DGFiP e-reporting. Typical fields: invoiceReference (string): the invoice number; transactionDate (string, YYYY-MM-DD): date of the transaction; transactionType (string): e.g. 'B2C', 'INTERNATIONAL'; totalAmount (number): total TTC amount; taxDetails (array): [{ vatRate: number, taxableAmount: number, taxAmount: number }]; counterparty (object): { name, country, identifier }; currency (string, default 'EUR'). Exact schema depends on the PA provider."
38554
38570
  }
38555
38571
  },
38556
38572
  required: ["transaction"]
@@ -38569,6 +38585,7 @@ var reportingTools = [
38569
38585
  // ── Non-Invoice Transaction Reporting ───────────────────
38570
38586
  {
38571
38587
  name: "einvoice_reporting_transaction",
38588
+ requires: ["reportTransaction"],
38572
38589
  description: "Report a non-invoice transaction to the French tax authority (e-reporting). Covers payment data, B2C cash transactions, etc. Requires the business entity ID. Asynchronous \u2014 returns a GUID.",
38573
38590
  category: "reporting",
38574
38591
  inputSchema: {
@@ -38580,7 +38597,7 @@ var reportingTools = [
38580
38597
  },
38581
38598
  transaction: {
38582
38599
  type: "object",
38583
- description: "Non-invoice transaction data for DGFiP e-reporting. Typical fields: transactionDate (string, YYYY-MM-DD); transactionType (string): e.g. 'CASH_PAYMENT', 'PAYMENT_DATA'; totalAmount (number): total TTC amount; taxDetails (array): [{ vatRate: number, taxableAmount: number, taxAmount: number }]; periodicity (string): 'MONTHLY' or 'QUARTERLY'; currency (string, default 'EUR'). Exact schema depends on the PA provider \u2014 check Iopole docs for the full specification."
38600
+ description: "Non-invoice transaction data for DGFiP e-reporting. Typical fields: transactionDate (string, YYYY-MM-DD); transactionType (string): e.g. 'CASH_PAYMENT', 'PAYMENT_DATA'; totalAmount (number): total TTC amount; taxDetails (array): [{ vatRate: number, taxableAmount: number, taxAmount: number }]; periodicity (string): 'MONTHLY' or 'QUARTERLY'; currency (string, default 'EUR'). Exact schema depends on the PA provider."
38584
38601
  }
38585
38602
  },
38586
38603
  required: ["business_entity_id", "transaction"]
@@ -38607,6 +38624,7 @@ var webhookTools = [
38607
38624
  _meta: { ui: { resourceUri: "ui://mcp-einvoice/doclist-viewer" } },
38608
38625
  description: "List all configured webhooks for your account.",
38609
38626
  category: "webhook",
38627
+ requires: ["listWebhooks"],
38610
38628
  inputSchema: {
38611
38629
  type: "object",
38612
38630
  properties: {}
@@ -38620,6 +38638,7 @@ var webhookTools = [
38620
38638
  name: "einvoice_webhook_get",
38621
38639
  description: "Get a single webhook configuration by its ID.",
38622
38640
  category: "webhook",
38641
+ requires: ["getWebhook"],
38623
38642
  inputSchema: {
38624
38643
  type: "object",
38625
38644
  properties: {
@@ -38637,6 +38656,7 @@ var webhookTools = [
38637
38656
  // ── Create ──────────────────────────────────────────────
38638
38657
  {
38639
38658
  name: "einvoice_webhook_create",
38659
+ requires: ["createWebhook"],
38640
38660
  description: "Create a new webhook to receive real-time notifications. Specify the target URL and which events to subscribe to (e.g. invoice.received, status.changed).",
38641
38661
  category: "webhook",
38642
38662
  inputSchema: {
@@ -38681,6 +38701,7 @@ var webhookTools = [
38681
38701
  name: "einvoice_webhook_update",
38682
38702
  description: "Update an existing webhook configuration.",
38683
38703
  category: "webhook",
38704
+ requires: ["updateWebhook"],
38684
38705
  inputSchema: {
38685
38706
  type: "object",
38686
38707
  properties: {
@@ -38713,6 +38734,7 @@ var webhookTools = [
38713
38734
  name: "einvoice_webhook_delete",
38714
38735
  description: "Delete a webhook configuration.",
38715
38736
  category: "webhook",
38737
+ requires: ["deleteWebhook"],
38716
38738
  inputSchema: {
38717
38739
  type: "object",
38718
38740
  properties: {
@@ -38729,160 +38751,1496 @@ var webhookTools = [
38729
38751
  }
38730
38752
  ];
38731
38753
 
38732
- // src/tools/mod.ts
38733
- var toolsByCategory = {
38734
- invoice: invoiceTools,
38735
- directory: directoryTools,
38736
- status: statusTools,
38737
- reporting: reportingTools,
38738
- webhook: webhookTools
38754
+ // src/tools/config.ts
38755
+ var ENTITY_TYPE_LABELS2 = {
38756
+ LEGAL_UNIT: "Entit\xE9 juridique",
38757
+ OFFICE: "\xC9tablissement"
38739
38758
  };
38740
- var allTools = [
38741
- ...invoiceTools,
38742
- ...directoryTools,
38743
- ...statusTools,
38744
- ...reportingTools,
38745
- ...webhookTools
38746
- ];
38747
- function getToolsByCategory(category) {
38748
- return toolsByCategory[category] ?? [];
38749
- }
38750
-
38751
- // src/client.ts
38752
- var EInvoiceToolsClient = class {
38753
- tools;
38754
- constructor(options) {
38755
- if (options?.categories) {
38756
- this.tools = options.categories.flatMap((cat) => getToolsByCategory(cat));
38757
- } else {
38758
- this.tools = allTools;
38759
+ var configTools = [
38760
+ // ── Customer ID ──────────────────────────────────────
38761
+ {
38762
+ name: "einvoice_config_customer_id",
38763
+ requires: ["getCustomerId"],
38764
+ description: "Get the current operator customer ID. This is your unique operator identifier on the e-invoicing platform.",
38765
+ category: "config",
38766
+ inputSchema: {
38767
+ type: "object",
38768
+ properties: {}
38769
+ },
38770
+ handler: async (_input, ctx) => {
38771
+ return await ctx.adapter.getCustomerId();
38759
38772
  }
38760
- }
38761
- /** List available tools */
38762
- listTools() {
38763
- return this.tools;
38764
- }
38765
- /** Convert tools to MCP wire format (for server registration) */
38766
- toMCPFormat() {
38767
- return this.tools.map((t) => {
38768
- const wire = {
38769
- name: t.name,
38770
- description: t.description,
38771
- inputSchema: t.inputSchema
38772
- };
38773
- if (t._meta) wire._meta = t._meta;
38774
- return wire;
38775
- });
38776
- }
38777
- /**
38778
- * Build a handlers Map for ConcurrentMCPServer.registerTools().
38779
- * Each handler wraps the tool to inject the adapter context.
38780
- */
38781
- buildHandlersMap(adapter) {
38782
- const handlers = /* @__PURE__ */ new Map();
38783
- for (const tool of this.tools) {
38773
+ },
38774
+ // ── List Business Entities ───────────────────────────
38775
+ {
38776
+ name: "einvoice_config_entities_list",
38777
+ _meta: { ui: { resourceUri: "ui://mcp-einvoice/doclist-viewer" } },
38778
+ requires: ["listBusinessEntities"],
38779
+ description: "List all business entities managed by your operator account. Shows which companies/offices are enrolled for e-invoicing under your management. If this list is empty, you need to enroll entities before sending invoices.",
38780
+ category: "config",
38781
+ inputSchema: {
38782
+ type: "object",
38783
+ properties: {}
38784
+ },
38785
+ handler: async (_input, ctx) => {
38786
+ const { rows, count } = await ctx.adapter.listBusinessEntities();
38787
+ return {
38788
+ data: rows.map((r) => ({
38789
+ _id: r.entityId,
38790
+ "Nom": r.name ?? "\u2014",
38791
+ "SIRET": r.siret ?? "\u2014",
38792
+ "Type": ENTITY_TYPE_LABELS2[r.type ?? ""] ?? r.type ?? "\u2014"
38793
+ })),
38794
+ count,
38795
+ _title: "Entit\xE9s op\xE9rateur",
38796
+ _rowAction: {
38797
+ toolName: "einvoice_config_entity_get",
38798
+ idField: "_id",
38799
+ argName: "id"
38800
+ }
38801
+ };
38802
+ }
38803
+ },
38804
+ // ── Get Business Entity ──────────────────────────────
38805
+ {
38806
+ name: "einvoice_config_entity_get",
38807
+ _meta: { ui: { resourceUri: "ui://mcp-einvoice/directory-card" } },
38808
+ requires: ["getBusinessEntity"],
38809
+ description: "Get details of a specific business entity managed by your operator. Shows registration info, identifiers, and enrollment status.",
38810
+ category: "config",
38811
+ inputSchema: {
38812
+ type: "object",
38813
+ properties: {
38814
+ id: { type: "string", description: "Business entity ID" }
38815
+ },
38816
+ required: ["id"]
38817
+ },
38818
+ handler: async (input, ctx) => {
38819
+ if (!input.id) {
38820
+ throw new Error("[einvoice_config_entity_get] 'id' is required");
38821
+ }
38822
+ return await ctx.adapter.getBusinessEntity(input.id);
38823
+ }
38824
+ },
38825
+ // ── Create Legal Unit ────────────────────────────────
38826
+ {
38827
+ name: "einvoice_config_entity_create_legal",
38828
+ requires: ["createLegalUnit"],
38829
+ description: "Create a new legal unit (company) under your operator account. A legal unit represents a legally registered entity with a unique SIREN. After creating the legal unit, create an office (establishment) with a SIRET, then enroll it for e-invoicing.",
38830
+ category: "config",
38831
+ inputSchema: {
38832
+ type: "object",
38833
+ properties: {
38834
+ siren: {
38835
+ type: "string",
38836
+ description: "SIREN number (9 digits) of the company to register"
38837
+ },
38838
+ name: {
38839
+ type: "string",
38840
+ description: "Company name"
38841
+ },
38842
+ country: {
38843
+ type: "string",
38844
+ description: "Country code (default: FR)"
38845
+ },
38846
+ scope: {
38847
+ type: "string",
38848
+ description: "Entity scope",
38849
+ enum: ["PRIVATE_TAX_PAYER", "PUBLIC", "PRIMARY", "SECONDARY"]
38850
+ }
38851
+ },
38852
+ required: ["siren"]
38853
+ },
38854
+ handler: async (input, ctx) => {
38855
+ if (!input.siren) {
38856
+ throw new Error("[einvoice_config_entity_create_legal] 'siren' is required");
38857
+ }
38858
+ return await ctx.adapter.createLegalUnit({
38859
+ identifierScheme: "0002",
38860
+ identifierValue: input.siren,
38861
+ name: input.name,
38862
+ country: input.country ?? "FR",
38863
+ scope: input.scope ?? "PRIMARY"
38864
+ });
38865
+ }
38866
+ },
38867
+ // ── Create Office ────────────────────────────────────
38868
+ {
38869
+ name: "einvoice_config_entity_create_office",
38870
+ requires: ["createOffice"],
38871
+ description: "Create a new office (establishment) for an existing legal unit. An office represents a physical location with a unique SIRET. The legal unit must exist first (use einvoice_config_entity_create_legal). After creating, enroll the office with einvoice_config_enroll_fr.",
38872
+ category: "config",
38873
+ inputSchema: {
38874
+ type: "object",
38875
+ properties: {
38876
+ siret: {
38877
+ type: "string",
38878
+ description: "SIRET number (14 digits) of the establishment"
38879
+ },
38880
+ legalUnitId: {
38881
+ type: "string",
38882
+ description: "Business entity ID of the parent legal unit"
38883
+ },
38884
+ name: {
38885
+ type: "string",
38886
+ description: "Office name"
38887
+ },
38888
+ scope: {
38889
+ type: "string",
38890
+ description: "Entity scope",
38891
+ enum: ["PRIVATE_TAX_PAYER", "PUBLIC", "PRIMARY", "SECONDARY"]
38892
+ }
38893
+ },
38894
+ required: ["siret", "legalUnitId"]
38895
+ },
38896
+ handler: async (input, ctx) => {
38897
+ if (!input.siret || !input.legalUnitId) {
38898
+ throw new Error("[einvoice_config_entity_create_office] 'siret' and 'legalUnitId' are required");
38899
+ }
38900
+ return await ctx.adapter.createOffice({
38901
+ identifierScheme: "0009",
38902
+ identifierValue: input.siret,
38903
+ legalBusinessEntityId: input.legalUnitId,
38904
+ name: input.name,
38905
+ scope: input.scope ?? "PRIMARY"
38906
+ });
38907
+ }
38908
+ },
38909
+ // ── Enroll French Entity ─────────────────────────────
38910
+ {
38911
+ name: "einvoice_config_enroll_fr",
38912
+ _meta: { ui: { resourceUri: "ui://mcp-einvoice/action-result" } },
38913
+ requires: ["enrollFrench"],
38914
+ description: "Enroll a French business entity for e-invoicing on the PPF (Portail Public de Facturation). REQUIRED before an entity can send or receive invoices. If invoice emission returns WRONG_ROUTING, the sender entity needs enrollment. Provide the SIRET of the entity to enroll. The entity must first exist under your operator (use einvoice_config_entities_list to check).",
38915
+ category: "config",
38916
+ inputSchema: {
38917
+ type: "object",
38918
+ properties: {
38919
+ siret: {
38920
+ type: "string",
38921
+ description: "SIRET (14 digits) of the entity to enroll"
38922
+ },
38923
+ siren: {
38924
+ type: "string",
38925
+ description: "SIREN (9 digits, first 9 digits of SIRET). If omitted, extracted from SIRET automatically."
38926
+ }
38927
+ },
38928
+ required: ["siret"]
38929
+ },
38930
+ handler: async (input, ctx) => {
38931
+ if (!input.siret) {
38932
+ throw new Error("[einvoice_config_enroll_fr] 'siret' is required");
38933
+ }
38934
+ const siret = input.siret;
38935
+ const siren = input.siren ?? siret.slice(0, 9);
38936
+ return await ctx.adapter.enrollFrench({ siret, siren });
38937
+ }
38938
+ },
38939
+ // ── Claim Business Entity ────────────────────────────
38940
+ {
38941
+ name: "einvoice_config_entity_claim",
38942
+ requires: ["claimBusinessEntityByIdentifier"],
38943
+ description: "Claim management of a business entity by its identifier (SIRET scheme 0009). Use this to take over management of an entity that exists in the directory but is not yet under your operator account.",
38944
+ category: "config",
38945
+ inputSchema: {
38946
+ type: "object",
38947
+ properties: {
38948
+ scheme: {
38949
+ type: "string",
38950
+ description: "Identifier scheme (e.g. '0009' for SIRET)"
38951
+ },
38952
+ value: {
38953
+ type: "string",
38954
+ description: "Identifier value (e.g. SIRET number)"
38955
+ }
38956
+ },
38957
+ required: ["scheme", "value"]
38958
+ },
38959
+ handler: async (input, ctx) => {
38960
+ if (!input.scheme || !input.value) {
38961
+ throw new Error("[einvoice_config_entity_claim] 'scheme' and 'value' are required");
38962
+ }
38963
+ return await ctx.adapter.claimBusinessEntityByIdentifier(
38964
+ input.scheme,
38965
+ input.value,
38966
+ {}
38967
+ );
38968
+ }
38969
+ },
38970
+ // ── Delete Business Entity ───────────────────────────
38971
+ {
38972
+ name: "einvoice_config_entity_delete",
38973
+ requires: ["deleteBusinessEntity"],
38974
+ description: "Remove a business entity from your operator account. This does not delete the entity from the national directory, only removes it from your management.",
38975
+ category: "config",
38976
+ inputSchema: {
38977
+ type: "object",
38978
+ properties: {
38979
+ id: { type: "string", description: "Business entity ID to remove" }
38980
+ },
38981
+ required: ["id"]
38982
+ },
38983
+ handler: async (input, ctx) => {
38984
+ if (!input.id) {
38985
+ throw new Error("[einvoice_config_entity_delete] 'id' is required");
38986
+ }
38987
+ return await ctx.adapter.deleteBusinessEntity(input.id);
38988
+ }
38989
+ },
38990
+ // ── Register on Network ──────────────────────────────
38991
+ {
38992
+ name: "einvoice_config_network_register",
38993
+ _meta: { ui: { resourceUri: "ui://mcp-einvoice/action-result" } },
38994
+ requires: ["registerNetwork"],
38995
+ description: "Register a business entity identifier on an e-invoicing network (DOMESTIC_FR or PEPPOL_INTERNATIONAL). REQUIRED for invoice routing. An entity can be enrolled but still get WRONG_ROUTING if its identifier is not registered on the target network. Use einvoice_config_entities_list to find the identifier ID, then register it here.",
38996
+ category: "config",
38997
+ inputSchema: {
38998
+ type: "object",
38999
+ properties: {
39000
+ identifier_id: {
39001
+ type: "string",
39002
+ description: "Business entity identifier ID (UUID, from the identifiers array in entity details)"
39003
+ },
39004
+ network: {
39005
+ type: "string",
39006
+ description: "Network to register on: DOMESTIC_FR (French PPF) or PEPPOL_INTERNATIONAL",
39007
+ enum: ["DOMESTIC_FR", "PEPPOL_INTERNATIONAL"]
39008
+ }
39009
+ },
39010
+ required: ["identifier_id", "network"]
39011
+ },
39012
+ handler: async (input, ctx) => {
39013
+ if (!input.identifier_id || !input.network) {
39014
+ throw new Error("[einvoice_config_network_register] 'identifier_id' and 'network' are required");
39015
+ }
39016
+ return await ctx.adapter.registerNetwork(
39017
+ input.identifier_id,
39018
+ input.network
39019
+ );
39020
+ }
39021
+ },
39022
+ // ── Register on Network by Scheme/Value ──────────────
39023
+ {
39024
+ name: "einvoice_config_network_register_by_id",
39025
+ _meta: { ui: { resourceUri: "ui://mcp-einvoice/action-result" } },
39026
+ requires: ["registerNetworkByScheme"],
39027
+ description: "Register an entity on an e-invoicing network using its identifier scheme and value directly. Shortcut when you know the SIRET but not the identifier UUID. Example: scheme='0009', value='43446637100011', network='DOMESTIC_FR'. Use scheme 0009 for SIRET, 0002 for SIREN.",
39028
+ category: "config",
39029
+ inputSchema: {
39030
+ type: "object",
39031
+ properties: {
39032
+ scheme: {
39033
+ type: "string",
39034
+ description: "Identifier scheme: '0009' for SIRET, '0002' for SIREN, '0225' for SIREN_SIRET"
39035
+ },
39036
+ value: {
39037
+ type: "string",
39038
+ description: "Identifier value (e.g. SIRET number)"
39039
+ },
39040
+ network: {
39041
+ type: "string",
39042
+ description: "Network: DOMESTIC_FR or PEPPOL_INTERNATIONAL",
39043
+ enum: ["DOMESTIC_FR", "PEPPOL_INTERNATIONAL"]
39044
+ }
39045
+ },
39046
+ required: ["scheme", "value", "network"]
39047
+ },
39048
+ handler: async (input, ctx) => {
39049
+ if (!input.scheme || !input.value || !input.network) {
39050
+ throw new Error("[einvoice_config_network_register_by_id] 'scheme', 'value', and 'network' are required");
39051
+ }
39052
+ return await ctx.adapter.registerNetworkByScheme(
39053
+ input.scheme,
39054
+ input.value,
39055
+ input.network
39056
+ );
39057
+ }
39058
+ },
39059
+ // ── Create Identifier ────────────────────────────────
39060
+ {
39061
+ name: "einvoice_config_identifier_create",
39062
+ _meta: { ui: { resourceUri: "ui://mcp-einvoice/action-result" } },
39063
+ requires: ["createIdentifier"],
39064
+ description: "Add a new identifier to a business entity. Identifiers are how entities are found in the directory and how invoices are routed. Common schemes: '0009' (SIRET), '0002' (SIREN), '0225' (SIREN_SIRET). After creating, register the identifier on a network (DOMESTIC_FR) for invoice routing.",
39065
+ category: "config",
39066
+ inputSchema: {
39067
+ type: "object",
39068
+ properties: {
39069
+ entity_id: { type: "string", description: "Business entity ID to add the identifier to" },
39070
+ scheme: {
39071
+ type: "string",
39072
+ description: "Identifier scheme: '0009' (SIRET), '0002' (SIREN), '0225' (SIREN_SIRET)"
39073
+ },
39074
+ value: { type: "string", description: "Identifier value (e.g. SIRET number)" },
39075
+ type: {
39076
+ type: "string",
39077
+ description: "Identifier type",
39078
+ enum: ["ROUTING_CODE", "SUFFIX"]
39079
+ }
39080
+ },
39081
+ required: ["entity_id", "scheme", "value", "type"]
39082
+ },
39083
+ handler: async (input, ctx) => {
39084
+ if (!input.entity_id || !input.scheme || !input.value || !input.type) {
39085
+ throw new Error("[einvoice_config_identifier_create] 'entity_id', 'scheme', 'value', and 'type' are required");
39086
+ }
39087
+ return await ctx.adapter.createIdentifier(input.entity_id, {
39088
+ scheme: input.scheme,
39089
+ value: input.value,
39090
+ type: input.type
39091
+ });
39092
+ }
39093
+ },
39094
+ // ── Create Identifier by Scheme/Value ──────────────────
39095
+ {
39096
+ name: "einvoice_config_identifier_create_by_scheme",
39097
+ _meta: { ui: { resourceUri: "ui://mcp-einvoice/action-result" } },
39098
+ requires: ["createIdentifierByScheme"],
39099
+ description: "Add a new identifier to a business entity, looking up the entity by an existing identifier. Shortcut when you know a SIRET/SIREN but not the entity UUID.",
39100
+ category: "config",
39101
+ inputSchema: {
39102
+ type: "object",
39103
+ properties: {
39104
+ lookup_scheme: { type: "string", description: "Scheme to find the entity (e.g. '0009' for SIRET)" },
39105
+ lookup_value: { type: "string", description: "Value to find the entity (e.g. SIRET number)" },
39106
+ new_scheme: { type: "string", description: "Scheme of the new identifier to add" },
39107
+ new_value: { type: "string", description: "Value of the new identifier to add" }
39108
+ },
39109
+ required: ["lookup_scheme", "lookup_value", "new_scheme", "new_value"]
39110
+ },
39111
+ handler: async (input, ctx) => {
39112
+ if (!input.lookup_scheme || !input.lookup_value || !input.new_scheme || !input.new_value) {
39113
+ throw new Error("[einvoice_config_identifier_create_by_scheme] all fields are required");
39114
+ }
39115
+ return await ctx.adapter.createIdentifierByScheme(
39116
+ input.lookup_scheme,
39117
+ input.lookup_value,
39118
+ { scheme: input.new_scheme, value: input.new_value }
39119
+ );
39120
+ }
39121
+ },
39122
+ // ── Delete Identifier ──────────────────────────────────
39123
+ {
39124
+ name: "einvoice_config_identifier_delete",
39125
+ requires: ["deleteIdentifier"],
39126
+ description: "Remove an identifier from a business entity. WARNING: if the identifier is registered on a network, unregister it first. Use the identifier UUID from the entity's identifiers array (einvoice_config_entity_get).",
39127
+ category: "config",
39128
+ inputSchema: {
39129
+ type: "object",
39130
+ properties: {
39131
+ identifier_id: { type: "string", description: "Identifier UUID to delete" }
39132
+ },
39133
+ required: ["identifier_id"]
39134
+ },
39135
+ handler: async (input, ctx) => {
39136
+ if (!input.identifier_id) {
39137
+ throw new Error("[einvoice_config_identifier_delete] 'identifier_id' is required");
39138
+ }
39139
+ return await ctx.adapter.deleteIdentifier(input.identifier_id);
39140
+ }
39141
+ },
39142
+ // ── Configure Business Entity ──────────────────────────
39143
+ {
39144
+ name: "einvoice_config_entity_configure",
39145
+ _meta: { ui: { resourceUri: "ui://mcp-einvoice/action-result" } },
39146
+ requires: ["configureBusinessEntity"],
39147
+ description: "Configure a business entity's settings (e.g. VAT regime). Use einvoice_config_entity_get to see current configuration first.",
39148
+ category: "config",
39149
+ inputSchema: {
39150
+ type: "object",
39151
+ properties: {
39152
+ entity_id: { type: "string", description: "Business entity ID" },
39153
+ vat_regime: {
39154
+ type: "string",
39155
+ description: "VAT regime: REAL_MONTHLY_TAX_REGIME, REAL_QUARTERLY_TAX_REGIME, SIMPLIFIED_TAX_REGIME, or VAT_EXEMPTION_REGIME",
39156
+ enum: ["REAL_MONTHLY_TAX_REGIME", "REAL_QUARTERLY_TAX_REGIME", "SIMPLIFIED_TAX_REGIME", "VAT_EXEMPTION_REGIME"]
39157
+ }
39158
+ },
39159
+ required: ["entity_id"]
39160
+ },
39161
+ handler: async (input, ctx) => {
39162
+ if (!input.entity_id) {
39163
+ throw new Error("[einvoice_config_entity_configure] 'entity_id' is required");
39164
+ }
39165
+ const config2 = {};
39166
+ if (input.vat_regime) config2.vatRegime = input.vat_regime;
39167
+ return await ctx.adapter.configureBusinessEntity(input.entity_id, config2);
39168
+ }
39169
+ },
39170
+ // ── Delete Claim ───────────────────────────────────────
39171
+ {
39172
+ name: "einvoice_config_claim_delete",
39173
+ requires: ["deleteClaim"],
39174
+ description: "Remove your operator's claim on a business entity. The entity remains in the national directory but is no longer managed by your operator. WARNING: after removing the claim, you lose management of this entity. Use einvoice_config_entity_claim to reclaim it later if needed.",
39175
+ category: "config",
39176
+ inputSchema: {
39177
+ type: "object",
39178
+ properties: {
39179
+ entity_id: { type: "string", description: "Business entity ID to unclaim" }
39180
+ },
39181
+ required: ["entity_id"]
39182
+ },
39183
+ handler: async (input, ctx) => {
39184
+ if (!input.entity_id) {
39185
+ throw new Error("[einvoice_config_claim_delete] 'entity_id' is required");
39186
+ }
39187
+ return await ctx.adapter.deleteClaim(input.entity_id);
39188
+ }
39189
+ },
39190
+ // ── Unregister from Network ──────────────────────────
39191
+ {
39192
+ name: "einvoice_config_network_unregister",
39193
+ requires: ["unregisterNetwork"],
39194
+ description: "Unregister an entity from a network. Removes the directory entry. After unregistration, the entity will no longer receive invoices on that network. Use the directoryId from the entity's networksRegistered array.",
39195
+ category: "config",
39196
+ inputSchema: {
39197
+ type: "object",
39198
+ properties: {
39199
+ directory_id: {
39200
+ type: "string",
39201
+ description: "Directory entry ID (UUID from the networksRegistered array in entity identifiers)"
39202
+ }
39203
+ },
39204
+ required: ["directory_id"]
39205
+ },
39206
+ handler: async (input, ctx) => {
39207
+ if (!input.directory_id) {
39208
+ throw new Error("[einvoice_config_network_unregister] 'directory_id' is required");
39209
+ }
39210
+ return await ctx.adapter.unregisterNetwork(input.directory_id);
39211
+ }
39212
+ }
39213
+ ];
39214
+
39215
+ // src/tools/mod.ts
39216
+ var toolsByCategory = {
39217
+ invoice: invoiceTools,
39218
+ directory: directoryTools,
39219
+ status: statusTools,
39220
+ reporting: reportingTools,
39221
+ webhook: webhookTools,
39222
+ config: configTools
39223
+ };
39224
+ var allTools = [
39225
+ ...invoiceTools,
39226
+ ...directoryTools,
39227
+ ...statusTools,
39228
+ ...reportingTools,
39229
+ ...webhookTools,
39230
+ ...configTools
39231
+ ];
39232
+ function getToolsByCategory(category) {
39233
+ return toolsByCategory[category] ?? [];
39234
+ }
39235
+
39236
+ // src/client.ts
39237
+ var EInvoiceToolsClient = class {
39238
+ tools;
39239
+ constructor(options) {
39240
+ if (options?.categories) {
39241
+ this.tools = options.categories.flatMap((cat) => getToolsByCategory(cat));
39242
+ } else {
39243
+ this.tools = allTools;
39244
+ }
39245
+ }
39246
+ /** List available tools */
39247
+ listTools() {
39248
+ return this.tools;
39249
+ }
39250
+ /** Filter tools to only those supported by the given adapter's capabilities. */
39251
+ supportedTools(adapter) {
39252
+ return this.tools.filter(
39253
+ (t) => !t.requires || t.requires.every((m) => adapter.capabilities.has(m))
39254
+ );
39255
+ }
39256
+ /** Convert tools to MCP wire format, filtered by adapter capabilities. */
39257
+ toMCPFormat(adapter) {
39258
+ return this.supportedTools(adapter).map((t) => {
39259
+ const wire = {
39260
+ name: t.name,
39261
+ description: t.description,
39262
+ inputSchema: t.inputSchema
39263
+ };
39264
+ if (t._meta) wire._meta = t._meta;
39265
+ if (t.annotations) wire.annotations = t.annotations;
39266
+ return wire;
39267
+ });
39268
+ }
39269
+ /**
39270
+ * Build a handlers Map for ConcurrentMCPServer.registerTools().
39271
+ * Each handler wraps the tool to inject the adapter context.
39272
+ * Only includes tools supported by the adapter's capabilities.
39273
+ */
39274
+ buildHandlersMap(adapter) {
39275
+ const handlers = /* @__PURE__ */ new Map();
39276
+ for (const tool of this.supportedTools(adapter)) {
38784
39277
  handlers.set(tool.name, async (args) => {
38785
39278
  return await tool.handler(args, { adapter });
38786
39279
  });
38787
39280
  }
38788
- return handlers;
39281
+ return handlers;
39282
+ }
39283
+ /** Execute a tool by name */
39284
+ async execute(name, args, adapter) {
39285
+ const tool = this.tools.find((t) => t.name === name);
39286
+ if (!tool) {
39287
+ throw new Error(
39288
+ `[EInvoiceToolsClient] Unknown tool: "${name}". Available: ${this.tools.map((t) => t.name).join(", ")}`
39289
+ );
39290
+ }
39291
+ return await tool.handler(args, { adapter });
39292
+ }
39293
+ /** Get tool count */
39294
+ get count() {
39295
+ return this.tools.length;
39296
+ }
39297
+ };
39298
+
39299
+ // src/adapters/base-adapter.ts
39300
+ var BaseAdapter = class {
39301
+ notSupported(method, reason = "Not implemented by this adapter.") {
39302
+ return Promise.reject(new NotSupportedError(this.name, method, reason));
39303
+ }
39304
+ // ─── Invoice ───────────────────────────────────────────
39305
+ emitInvoice(_req) {
39306
+ return this.notSupported("emitInvoice");
39307
+ }
39308
+ searchInvoices(_filters) {
39309
+ return this.notSupported("searchInvoices");
39310
+ }
39311
+ getInvoice(_id) {
39312
+ return this.notSupported("getInvoice");
39313
+ }
39314
+ downloadInvoice(_id) {
39315
+ return this.notSupported("downloadInvoice");
39316
+ }
39317
+ downloadReadable(_id) {
39318
+ return this.notSupported("downloadReadable");
39319
+ }
39320
+ getInvoiceFiles(_id) {
39321
+ return this.notSupported("getInvoiceFiles");
39322
+ }
39323
+ getAttachments(_id) {
39324
+ return this.notSupported("getAttachments");
39325
+ }
39326
+ downloadFile(_fileId) {
39327
+ return this.notSupported("downloadFile");
39328
+ }
39329
+ markInvoiceSeen(_id) {
39330
+ return this.notSupported("markInvoiceSeen");
39331
+ }
39332
+ getUnseenInvoices(_pagination) {
39333
+ return this.notSupported("getUnseenInvoices");
39334
+ }
39335
+ generateCII(_req) {
39336
+ return this.notSupported("generateCII");
39337
+ }
39338
+ generateUBL(_req) {
39339
+ return this.notSupported("generateUBL");
39340
+ }
39341
+ generateFacturX(_req) {
39342
+ return this.notSupported("generateFacturX");
39343
+ }
39344
+ // ─── Directory ────────────────────────────────────────
39345
+ searchDirectoryFr(_filters) {
39346
+ return this.notSupported("searchDirectoryFr");
39347
+ }
39348
+ searchDirectoryInt(_filters) {
39349
+ return this.notSupported("searchDirectoryInt");
39350
+ }
39351
+ checkPeppolParticipant(_scheme, _value) {
39352
+ return this.notSupported("checkPeppolParticipant");
39353
+ }
39354
+ // ─── Status ────────────────────────────────────────────
39355
+ sendStatus(_req) {
39356
+ return this.notSupported("sendStatus");
39357
+ }
39358
+ getStatusHistory(_invoiceId) {
39359
+ return this.notSupported("getStatusHistory");
39360
+ }
39361
+ getUnseenStatuses(_pagination) {
39362
+ return this.notSupported("getUnseenStatuses");
39363
+ }
39364
+ markStatusSeen(_statusId) {
39365
+ return this.notSupported("markStatusSeen");
39366
+ }
39367
+ // ─── Reporting ─────────────────────────────────────────
39368
+ reportInvoiceTransaction(_transaction) {
39369
+ return this.notSupported("reportInvoiceTransaction");
39370
+ }
39371
+ reportTransaction(_businessEntityId, _transaction) {
39372
+ return this.notSupported("reportTransaction");
39373
+ }
39374
+ // ─── Webhooks ──────────────────────────────────────────
39375
+ listWebhooks() {
39376
+ return this.notSupported("listWebhooks");
39377
+ }
39378
+ getWebhook(_id) {
39379
+ return this.notSupported("getWebhook");
39380
+ }
39381
+ createWebhook(_req) {
39382
+ return this.notSupported("createWebhook");
39383
+ }
39384
+ updateWebhook(_id, _req) {
39385
+ return this.notSupported("updateWebhook");
39386
+ }
39387
+ deleteWebhook(_id) {
39388
+ return this.notSupported("deleteWebhook");
39389
+ }
39390
+ // ─── Operator Config ───────────────────────────────────
39391
+ getCustomerId() {
39392
+ return this.notSupported("getCustomerId");
39393
+ }
39394
+ listBusinessEntities() {
39395
+ return this.notSupported("listBusinessEntities");
39396
+ }
39397
+ getBusinessEntity(_id) {
39398
+ return this.notSupported("getBusinessEntity");
39399
+ }
39400
+ createLegalUnit(_data) {
39401
+ return this.notSupported("createLegalUnit");
39402
+ }
39403
+ createOffice(_data) {
39404
+ return this.notSupported("createOffice");
39405
+ }
39406
+ deleteBusinessEntity(_id) {
39407
+ return this.notSupported("deleteBusinessEntity");
39408
+ }
39409
+ configureBusinessEntity(_id, _data) {
39410
+ return this.notSupported("configureBusinessEntity");
39411
+ }
39412
+ claimBusinessEntity(_id, _data) {
39413
+ return this.notSupported("claimBusinessEntity");
39414
+ }
39415
+ claimBusinessEntityByIdentifier(_scheme, _value, _data) {
39416
+ return this.notSupported("claimBusinessEntityByIdentifier");
39417
+ }
39418
+ enrollFrench(_data) {
39419
+ return this.notSupported("enrollFrench");
39420
+ }
39421
+ enrollInternational(_data) {
39422
+ return this.notSupported("enrollInternational");
39423
+ }
39424
+ registerNetwork(_identifierId, _network) {
39425
+ return this.notSupported("registerNetwork");
39426
+ }
39427
+ registerNetworkByScheme(_scheme, _value, _network) {
39428
+ return this.notSupported("registerNetworkByScheme");
39429
+ }
39430
+ unregisterNetwork(_directoryId) {
39431
+ return this.notSupported("unregisterNetwork");
39432
+ }
39433
+ // ─── Identifier Management ─────────────────────────────
39434
+ createIdentifier(_entityId, _data) {
39435
+ return this.notSupported("createIdentifier");
39436
+ }
39437
+ createIdentifierByScheme(_scheme, _value, _data) {
39438
+ return this.notSupported("createIdentifierByScheme");
39439
+ }
39440
+ deleteIdentifier(_identifierId) {
39441
+ return this.notSupported("deleteIdentifier");
39442
+ }
39443
+ // ─── Claim Management ──────────────────────────────────
39444
+ deleteClaim(_entityId) {
39445
+ return this.notSupported("deleteClaim");
39446
+ }
39447
+ };
39448
+
39449
+ // src/adapters/shared/oauth2.ts
39450
+ function createOAuth2TokenProvider(config2) {
39451
+ let cachedToken;
39452
+ let expiresAt = 0;
39453
+ let inflight;
39454
+ const REFRESH_MARGIN_MS = 6e4;
39455
+ async function fetchToken() {
39456
+ const body = new URLSearchParams({
39457
+ grant_type: "client_credentials",
39458
+ client_id: config2.clientId,
39459
+ client_secret: config2.clientSecret
39460
+ });
39461
+ const response = await fetch(config2.authUrl, {
39462
+ method: "POST",
39463
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
39464
+ body: body.toString(),
39465
+ signal: AbortSignal.timeout(15e3)
39466
+ });
39467
+ if (!response.ok) {
39468
+ const text = await response.text();
39469
+ throw new Error(
39470
+ `[OAuth2] Token request failed: ${response.status} \u2014 ${text.slice(0, 500)}`
39471
+ );
39472
+ }
39473
+ const data = await response.json();
39474
+ if (!data.access_token) {
39475
+ throw new Error("[OAuth2] Token response missing access_token");
39476
+ }
39477
+ cachedToken = data.access_token;
39478
+ expiresAt = Date.now() + (data.expires_in ?? 600) * 1e3 - REFRESH_MARGIN_MS;
39479
+ return cachedToken;
39480
+ }
39481
+ return async () => {
39482
+ if (cachedToken && Date.now() < expiresAt) {
39483
+ return cachedToken;
39484
+ }
39485
+ if (!inflight) {
39486
+ inflight = fetchToken().finally(() => {
39487
+ inflight = void 0;
39488
+ });
39489
+ }
39490
+ return inflight;
39491
+ };
39492
+ }
39493
+
39494
+ // src/adapters/iopole/client.ts
39495
+ var IopoleAPIError = class extends Error {
39496
+ constructor(message2, status, body) {
39497
+ super(message2);
39498
+ this.status = status;
39499
+ this.body = body;
39500
+ this.name = "IopoleAPIError";
39501
+ }
39502
+ };
39503
+ var IopoleClient = class {
39504
+ config;
39505
+ constructor(config2) {
39506
+ this.config = config2;
39507
+ }
39508
+ // ─── Generic Request ────────────────────────────────────
39509
+ async request(method, path, options) {
39510
+ return this.requestWithBase(this.config.baseUrl, method, path, options);
39511
+ }
39512
+ async requestWithBase(baseUrl, method, path, options) {
39513
+ const url2 = new URL(`${baseUrl}${path}`);
39514
+ if (options?.query) {
39515
+ for (const [key, value] of Object.entries(options.query)) {
39516
+ if (value !== void 0) {
39517
+ url2.searchParams.set(key, String(value));
39518
+ }
39519
+ }
39520
+ }
39521
+ const token = await this.config.getToken();
39522
+ const headers = {
39523
+ Authorization: `Bearer ${token}`,
39524
+ "customer-id": this.config.customerId,
39525
+ Accept: "application/json",
39526
+ ...options?.headers
39527
+ };
39528
+ if (options?.body) {
39529
+ headers["Content-Type"] = "application/json";
39530
+ }
39531
+ const controller = new AbortController();
39532
+ const timeout = setTimeout(
39533
+ () => controller.abort(),
39534
+ this.config.timeoutMs ?? 3e4
39535
+ );
39536
+ try {
39537
+ const response = await fetch(url2.toString(), {
39538
+ method,
39539
+ headers,
39540
+ body: options?.body ? JSON.stringify(options.body) : void 0,
39541
+ signal: controller.signal
39542
+ });
39543
+ if (!response.ok) {
39544
+ const body = await response.text();
39545
+ throw new IopoleAPIError(
39546
+ `[IopoleClient] ${method} ${path} \u2192 ${response.status}: ${body.slice(0, 500)}`,
39547
+ response.status,
39548
+ body
39549
+ );
39550
+ }
39551
+ const contentType = response.headers.get("content-type") ?? "";
39552
+ if (contentType.includes("application/json")) {
39553
+ return await response.json();
39554
+ }
39555
+ return await response.text();
39556
+ } finally {
39557
+ clearTimeout(timeout);
39558
+ }
39559
+ }
39560
+ // ─── Convenience Methods ────────────────────────────────
39561
+ async get(path, query) {
39562
+ return this.request("GET", path, { query });
39563
+ }
39564
+ /**
39565
+ * GET request against the v1.1 API endpoint.
39566
+ * Builds a v1.1 URL without mutating this.config.baseUrl,
39567
+ * so concurrent calls (e.g. getV11 + get) cannot interfere.
39568
+ */
39569
+ async getV11(path, query) {
39570
+ const baseV11 = this.config.baseUrl.replace(/\/v1\b/, "/v1.1");
39571
+ return this.requestWithBase(baseV11, "GET", path, { query });
39572
+ }
39573
+ async post(path, body) {
39574
+ return this.request("POST", path, { body });
39575
+ }
39576
+ async put(path, body) {
39577
+ return this.request("PUT", path, { body });
39578
+ }
39579
+ async delete(path) {
39580
+ return this.request("DELETE", path);
39581
+ }
39582
+ /**
39583
+ * Upload a file via multipart/form-data.
39584
+ * Used for POST /invoice (emitInvoice) — the Swagger spec requires
39585
+ * Content-Type: multipart/form-data with a `file` field (binary, PDF or XML).
39586
+ */
39587
+ async upload(path, file2, filename) {
39588
+ const url2 = `${this.config.baseUrl}${path}`;
39589
+ const token = await this.config.getToken();
39590
+ const controller = new AbortController();
39591
+ const timeout = setTimeout(
39592
+ () => controller.abort(),
39593
+ this.config.timeoutMs ?? 3e4
39594
+ );
39595
+ try {
39596
+ const form = new FormData();
39597
+ form.append("file", new Blob([file2]), filename);
39598
+ const response = await fetch(url2, {
39599
+ method: "POST",
39600
+ headers: {
39601
+ Authorization: `Bearer ${token}`,
39602
+ "customer-id": this.config.customerId,
39603
+ Accept: "application/json"
39604
+ // Do NOT set Content-Type — fetch sets it with the multipart boundary
39605
+ },
39606
+ body: form,
39607
+ signal: controller.signal
39608
+ });
39609
+ if (!response.ok) {
39610
+ const body = await response.text();
39611
+ throw new IopoleAPIError(
39612
+ `[IopoleClient] POST ${path} (upload) \u2192 ${response.status}: ${body.slice(0, 500)}`,
39613
+ response.status,
39614
+ body
39615
+ );
39616
+ }
39617
+ const contentType = response.headers.get("content-type") ?? "";
39618
+ if (contentType.includes("application/json")) {
39619
+ return await response.json();
39620
+ }
39621
+ return await response.text();
39622
+ } finally {
39623
+ clearTimeout(timeout);
39624
+ }
39625
+ }
39626
+ /**
39627
+ * POST with query parameters.
39628
+ * Used for /tools/{cii,ubl}/generate which return text (XML).
39629
+ */
39630
+ async postWithQuery(path, body, query) {
39631
+ return this.request("POST", path, { body, query });
39632
+ }
39633
+ /**
39634
+ * POST with query parameters, returning raw binary.
39635
+ * Used for /tools/facturx/generate which returns a PDF (binary).
39636
+ * Using request() would corrupt binary data by treating it as text.
39637
+ */
39638
+ async postBinary(path, body, query) {
39639
+ const url2 = new URL(`${this.config.baseUrl}${path}`);
39640
+ for (const [key, value] of Object.entries(query)) {
39641
+ if (value !== void 0) url2.searchParams.set(key, String(value));
39642
+ }
39643
+ const token = await this.config.getToken();
39644
+ const controller = new AbortController();
39645
+ const timeout = setTimeout(() => controller.abort(), this.config.timeoutMs ?? 6e4);
39646
+ try {
39647
+ const response = await fetch(url2.toString(), {
39648
+ method: "POST",
39649
+ headers: {
39650
+ Authorization: `Bearer ${token}`,
39651
+ "customer-id": this.config.customerId,
39652
+ "Content-Type": "application/json",
39653
+ Accept: "*/*"
39654
+ },
39655
+ body: JSON.stringify(body),
39656
+ signal: controller.signal
39657
+ });
39658
+ if (!response.ok) {
39659
+ const errBody = await response.text();
39660
+ throw new IopoleAPIError(`[IopoleClient] POST ${path} \u2192 ${response.status}: ${errBody.slice(0, 500)}`, response.status, errBody);
39661
+ }
39662
+ const data = new Uint8Array(await response.arrayBuffer());
39663
+ const contentType = response.headers.get("content-type") ?? "application/octet-stream";
39664
+ return { data, contentType };
39665
+ } finally {
39666
+ clearTimeout(timeout);
39667
+ }
39668
+ }
39669
+ /**
39670
+ * Download a binary resource (invoice PDF, attachment, etc.)
39671
+ * Returns the raw Response for streaming.
39672
+ */
39673
+ async download(path) {
39674
+ const url2 = `${this.config.baseUrl}${path}`;
39675
+ const token = await this.config.getToken();
39676
+ const controller = new AbortController();
39677
+ const timeout = setTimeout(
39678
+ () => controller.abort(),
39679
+ this.config.timeoutMs ?? 3e4
39680
+ );
39681
+ try {
39682
+ const response = await fetch(url2, {
39683
+ method: "GET",
39684
+ headers: {
39685
+ Authorization: `Bearer ${token}`,
39686
+ "customer-id": this.config.customerId
39687
+ },
39688
+ signal: controller.signal
39689
+ });
39690
+ if (!response.ok) {
39691
+ const body = await response.text();
39692
+ throw new IopoleAPIError(
39693
+ `[IopoleClient] GET ${path} \u2192 ${response.status}`,
39694
+ response.status,
39695
+ body
39696
+ );
39697
+ }
39698
+ const data = new Uint8Array(await response.arrayBuffer());
39699
+ const contentType = response.headers.get("content-type") ?? "application/octet-stream";
39700
+ return { data, contentType };
39701
+ } finally {
39702
+ clearTimeout(timeout);
39703
+ }
38789
39704
  }
38790
- /** Execute a tool by name */
38791
- async execute(name, args, adapter) {
38792
- const tool = this.tools.find((t) => t.name === name);
38793
- if (!tool) {
38794
- throw new Error(
38795
- `[EInvoiceToolsClient] Unknown tool: "${name}". Available: ${this.tools.map((t) => t.name).join(", ")}`
38796
- );
38797
- }
38798
- return await tool.handler(args, { adapter });
39705
+ };
39706
+
39707
+ // src/runtime.ts
39708
+ import { statSync as fsStatSync } from "node:fs";
39709
+ import { readFile as readFile2 } from "node:fs/promises";
39710
+ function env2(key) {
39711
+ return process.env[key];
39712
+ }
39713
+ async function readTextFile2(path) {
39714
+ return await readFile2(path, "utf-8");
39715
+ }
39716
+ function statSync(path) {
39717
+ try {
39718
+ fsStatSync(path);
39719
+ return true;
39720
+ } catch {
39721
+ return false;
38799
39722
  }
38800
- /** Get tool count */
38801
- get count() {
38802
- return this.tools.length;
39723
+ }
39724
+ function getArgs() {
39725
+ return process.argv.slice(2);
39726
+ }
39727
+ function exit(code) {
39728
+ process.exit(code);
39729
+ }
39730
+ function onSignal(signal, handler) {
39731
+ process.on(signal, handler);
39732
+ }
39733
+
39734
+ // src/adapters/shared/env.ts
39735
+ function requireEnv(adapter, name, hint) {
39736
+ const value = env2(name);
39737
+ if (!value) {
39738
+ throw new Error(`[${adapter}] ${name} is required. ${hint}`);
38803
39739
  }
38804
- };
39740
+ return value;
39741
+ }
38805
39742
 
38806
- // src/api/iopole-client.ts
38807
- function createOAuth2TokenProvider(config2) {
38808
- let cachedToken;
38809
- let expiresAt = 0;
38810
- let inflight;
38811
- const REFRESH_MARGIN_MS = 6e4;
38812
- async function fetchToken() {
38813
- const body = new URLSearchParams({
38814
- grant_type: "client_credentials",
38815
- client_id: config2.clientId,
38816
- client_secret: config2.clientSecret
39743
+ // src/adapters/shared/direction.ts
39744
+ var RECEIVED = /* @__PURE__ */ new Set(["received", "inbound", "in"]);
39745
+ var SENT = /* @__PURE__ */ new Set(["sent", "emitted", "outbound", "out"]);
39746
+ function normalizeDirection(raw2) {
39747
+ if (!raw2) return void 0;
39748
+ const l = raw2.toLowerCase();
39749
+ if (RECEIVED.has(l)) return "received";
39750
+ if (SENT.has(l)) return "sent";
39751
+ return void 0;
39752
+ }
39753
+
39754
+ // src/adapters/iopole/adapter.ts
39755
+ var IOPOLE_DEFAULT_AUTH_URL = "https://auth.ppd.iopole.fr/realms/iopole/protocol/openid-connect/token";
39756
+ var IopoleAdapter = class extends BaseAdapter {
39757
+ name = "iopole";
39758
+ capabilities = /* @__PURE__ */ new Set([
39759
+ "emitInvoice",
39760
+ "searchInvoices",
39761
+ "getInvoice",
39762
+ "downloadInvoice",
39763
+ "downloadReadable",
39764
+ "getInvoiceFiles",
39765
+ "getAttachments",
39766
+ "downloadFile",
39767
+ "markInvoiceSeen",
39768
+ "getUnseenInvoices",
39769
+ "generateCII",
39770
+ "generateUBL",
39771
+ "generateFacturX",
39772
+ "searchDirectoryFr",
39773
+ "searchDirectoryInt",
39774
+ "checkPeppolParticipant",
39775
+ "sendStatus",
39776
+ "getStatusHistory",
39777
+ "getUnseenStatuses",
39778
+ "markStatusSeen",
39779
+ "reportInvoiceTransaction",
39780
+ "reportTransaction",
39781
+ "listWebhooks",
39782
+ "getWebhook",
39783
+ "createWebhook",
39784
+ "updateWebhook",
39785
+ "deleteWebhook",
39786
+ "getCustomerId",
39787
+ "listBusinessEntities",
39788
+ "getBusinessEntity",
39789
+ "createLegalUnit",
39790
+ "createOffice",
39791
+ "deleteBusinessEntity",
39792
+ "configureBusinessEntity",
39793
+ "claimBusinessEntity",
39794
+ "claimBusinessEntityByIdentifier",
39795
+ "enrollFrench",
39796
+ "enrollInternational",
39797
+ "registerNetwork",
39798
+ "registerNetworkByScheme",
39799
+ "unregisterNetwork",
39800
+ "createIdentifier",
39801
+ "createIdentifierByScheme",
39802
+ "deleteIdentifier",
39803
+ "deleteClaim"
39804
+ ]);
39805
+ client;
39806
+ constructor(client) {
39807
+ super();
39808
+ this.client = client;
39809
+ }
39810
+ // ─── Invoice Operations ───────────────────────────────
39811
+ async emitInvoice(req) {
39812
+ return await this.client.upload("/invoice", req.file, req.filename);
39813
+ }
39814
+ async searchInvoices(filters) {
39815
+ const raw2 = await this.client.getV11("/invoice/search", {
39816
+ q: filters.q,
39817
+ expand: filters.expand ?? "businessData",
39818
+ offset: filters.offset ?? 0,
39819
+ limit: filters.limit ?? 50
38817
39820
  });
38818
- const response = await fetch(config2.authUrl, {
38819
- method: "POST",
38820
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
38821
- body: body.toString()
39821
+ const data = raw2.data ?? [];
39822
+ const count = raw2.meta?.count ?? raw2.count ?? data.length;
39823
+ const rows = data.map((row) => {
39824
+ const m = row.metadata ?? {};
39825
+ const bd = row.businessData ?? {};
39826
+ return {
39827
+ id: m.invoiceId ?? "",
39828
+ invoiceNumber: bd.invoiceId,
39829
+ status: m.state,
39830
+ // will be enriched below
39831
+ direction: normalizeDirection(m.direction),
39832
+ senderName: bd.seller?.name,
39833
+ receiverName: bd.buyer?.name,
39834
+ date: bd.invoiceDate ?? m.createDate?.split("T")[0],
39835
+ amount: bd.monetary?.invoiceAmount?.amount,
39836
+ currency: bd.monetary?.invoiceCurrency ?? "EUR"
39837
+ };
38822
39838
  });
38823
- if (!response.ok) {
38824
- const text = await response.text();
38825
- throw new Error(
38826
- `[IopoleOAuth2] Token request failed: ${response.status} \u2014 ${text.slice(0, 500)}`
38827
- );
38828
- }
38829
- const data = await response.json();
38830
- if (!data.access_token) {
38831
- throw new Error(
38832
- "[IopoleOAuth2] Token response missing access_token"
38833
- );
39839
+ const CONCURRENCY = 5;
39840
+ for (let i = 0; i < rows.length; i += CONCURRENCY) {
39841
+ const batch = rows.slice(i, i + CONCURRENCY);
39842
+ await Promise.all(batch.map(async (row) => {
39843
+ if (!row.id) return;
39844
+ try {
39845
+ const history = await this.getStatusHistory(row.id);
39846
+ if (history.entries.length > 0) {
39847
+ const sorted = [...history.entries].sort(
39848
+ (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()
39849
+ );
39850
+ row.status = sorted[0].code;
39851
+ }
39852
+ } catch {
39853
+ }
39854
+ }));
39855
+ }
39856
+ return { rows, count };
39857
+ }
39858
+ async getInvoice(id) {
39859
+ const [raw2, history] = await Promise.all([
39860
+ this.client.get(`/invoice/${encodePathSegment(id)}`, { expand: "businessData" }),
39861
+ this.getStatusHistory(id).catch(() => ({ entries: [] }))
39862
+ ]);
39863
+ const inv = Array.isArray(raw2) ? raw2[0] : raw2;
39864
+ const latestStatus = history.entries.length > 0 ? [...history.entries].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())[0].code : void 0;
39865
+ const bd = inv?.businessData;
39866
+ return {
39867
+ id: inv?.invoiceId ?? id,
39868
+ invoiceNumber: bd?.invoiceId,
39869
+ status: latestStatus ?? inv?.state ?? inv?.status ?? "UNKNOWN",
39870
+ direction: normalizeDirection(inv?.way ?? inv?.metadata?.direction),
39871
+ format: inv?.originalFormat,
39872
+ network: inv?.originalNetwork,
39873
+ invoiceType: bd?.detailedType?.value,
39874
+ senderName: bd?.seller?.name,
39875
+ senderId: bd?.seller?.siret ?? bd?.seller?.siren,
39876
+ senderVat: bd?.seller?.vatNumber,
39877
+ receiverName: bd?.buyer?.name,
39878
+ receiverId: bd?.buyer?.siret ?? bd?.buyer?.siren,
39879
+ receiverVat: bd?.buyer?.vatNumber,
39880
+ issueDate: bd?.invoiceDate,
39881
+ dueDate: bd?.invoiceDueDate,
39882
+ receiptDate: bd?.invoiceReceiptDate,
39883
+ currency: bd?.monetary?.invoiceCurrency ?? "EUR",
39884
+ totalHt: bd?.monetary?.taxBasisTotalAmount?.amount,
39885
+ totalTax: bd?.monetary?.taxTotalAmount?.amount,
39886
+ totalTtc: bd?.monetary?.invoiceAmount?.amount,
39887
+ lines: bd?.lines?.map((l) => {
39888
+ const line = l;
39889
+ return {
39890
+ description: line.item?.name,
39891
+ quantity: line.billedQuantity?.quantity,
39892
+ unitPrice: line.price?.netAmount?.amount,
39893
+ taxRate: line.taxDetail?.percent,
39894
+ amount: line.totalAmount?.amount
39895
+ };
39896
+ }),
39897
+ notes: bd?.notes?.map((n) => n.content).filter(Boolean)
39898
+ };
39899
+ }
39900
+ async downloadInvoice(id) {
39901
+ return await this.client.download(`/invoice/${encodePathSegment(id)}/download`);
39902
+ }
39903
+ async downloadReadable(id) {
39904
+ return await this.client.download(`/invoice/${encodePathSegment(id)}/download/readable`);
39905
+ }
39906
+ async getInvoiceFiles(id) {
39907
+ return await this.client.get(`/invoice/${encodePathSegment(id)}/files`);
39908
+ }
39909
+ async getAttachments(id) {
39910
+ return await this.client.get(`/invoice/${encodePathSegment(id)}/files/attachments`);
39911
+ }
39912
+ async downloadFile(fileId) {
39913
+ return await this.client.download(`/invoice/file/${encodePathSegment(fileId)}/download`);
39914
+ }
39915
+ async markInvoiceSeen(id) {
39916
+ return await this.client.put(`/invoice/${encodePathSegment(id)}/markAsSeen`);
39917
+ }
39918
+ async getUnseenInvoices(pagination) {
39919
+ return await this.client.get("/invoice/notSeen", {
39920
+ offset: pagination.offset,
39921
+ limit: pagination.limit
39922
+ });
39923
+ }
39924
+ async generateCII(req) {
39925
+ return await this.client.postWithQuery("/tools/cii/generate", normalizeForIopole(req.invoice), {
39926
+ flavor: req.flavor
39927
+ });
39928
+ }
39929
+ async generateUBL(req) {
39930
+ return await this.client.postWithQuery("/tools/ubl/generate", normalizeForIopole(req.invoice), {
39931
+ flavor: req.flavor
39932
+ });
39933
+ }
39934
+ async generateFacturX(req) {
39935
+ return await this.client.postBinary("/tools/facturx/generate", normalizeForIopole(req.invoice), {
39936
+ flavor: req.flavor,
39937
+ language: req.language
39938
+ });
39939
+ }
39940
+ // ─── Directory ────────────────────────────────────────
39941
+ async searchDirectoryFr(filters) {
39942
+ const q = autoWrapDirectoryQuery(filters.q);
39943
+ const raw2 = await this.client.get("/directory/french", {
39944
+ q,
39945
+ offset: filters.offset,
39946
+ limit: filters.limit
39947
+ });
39948
+ const data = raw2.data ?? [];
39949
+ const count = raw2.meta?.count ?? raw2.count ?? data.length;
39950
+ const rows = data.map((row) => {
39951
+ const ci = row.countryIdentifier ?? {};
39952
+ return {
39953
+ entityId: row.businessEntityId ?? "",
39954
+ name: row.name,
39955
+ type: row.type,
39956
+ siren: ci.siren ?? row.siren,
39957
+ siret: ci.siret ?? row.siret,
39958
+ country: ci.country ?? row.country ?? "FR",
39959
+ identifiers: row.identifiers
39960
+ };
39961
+ });
39962
+ return { rows, count };
39963
+ }
39964
+ async searchDirectoryInt(filters) {
39965
+ return await this.client.get("/directory/international", {
39966
+ value: filters.value,
39967
+ offset: filters.offset,
39968
+ limit: filters.limit
39969
+ });
39970
+ }
39971
+ async checkPeppolParticipant(scheme, value) {
39972
+ return await this.client.get(
39973
+ `/directory/international/check/scheme/${encodePathSegment(scheme)}/value/${encodePathSegment(value)}`
39974
+ );
39975
+ }
39976
+ // ─── Status ───────────────────────────────────────────
39977
+ async sendStatus(req) {
39978
+ return await this.client.post(`/invoice/${encodePathSegment(req.invoiceId)}/status`, {
39979
+ code: req.code,
39980
+ message: req.message,
39981
+ payment: req.payment
39982
+ });
39983
+ }
39984
+ async getStatusHistory(invoiceId) {
39985
+ const raw2 = await this.client.get(`/invoice/${encodePathSegment(invoiceId)}/status-history`);
39986
+ return normalizeStatusHistory(raw2);
39987
+ }
39988
+ async getUnseenStatuses(pagination) {
39989
+ return await this.client.get("/invoice/status/notSeen", {
39990
+ offset: pagination.offset,
39991
+ limit: pagination.limit
39992
+ });
39993
+ }
39994
+ async markStatusSeen(statusId) {
39995
+ return await this.client.put(`/invoice/status/${encodePathSegment(statusId)}/markAsSeen`);
39996
+ }
39997
+ // ─── Reporting ────────────────────────────────────────
39998
+ async reportInvoiceTransaction(transaction) {
39999
+ return await this.client.post("/reporting/fr/invoice/transaction", transaction);
40000
+ }
40001
+ async reportTransaction(businessEntityId, transaction) {
40002
+ return await this.client.post(
40003
+ `/reporting/fr/transaction/${encodePathSegment(businessEntityId)}`,
40004
+ transaction
40005
+ );
40006
+ }
40007
+ // ─── Webhooks ─────────────────────────────────────────
40008
+ async listWebhooks() {
40009
+ return await this.client.get("/config/webhook");
40010
+ }
40011
+ async getWebhook(id) {
40012
+ return await this.client.get(`/config/webhook/${encodePathSegment(id)}`);
40013
+ }
40014
+ async createWebhook(req) {
40015
+ return await this.client.post("/config/webhook", req);
40016
+ }
40017
+ async updateWebhook(id, req) {
40018
+ return await this.client.put(`/config/webhook/${encodePathSegment(id)}`, req);
40019
+ }
40020
+ async deleteWebhook(id) {
40021
+ return await this.client.delete(`/config/webhook/${encodePathSegment(id)}`);
40022
+ }
40023
+ // ─── Operator Config ───────────────────────────────────
40024
+ async getCustomerId() {
40025
+ return await this.client.get("/config/customer/id");
40026
+ }
40027
+ async listBusinessEntities() {
40028
+ const raw2 = await this.client.get("/config/business/entity");
40029
+ const data = raw2.data ?? [];
40030
+ const rows = data.map((row) => {
40031
+ const ci = row.countryIdentifier ?? {};
40032
+ return {
40033
+ entityId: row.businessEntityId ?? "",
40034
+ name: row.name,
40035
+ type: row.type,
40036
+ siren: ci.siren ?? row.siren,
40037
+ siret: ci.siret ?? row.siret,
40038
+ scope: row.scope,
40039
+ country: ci.country ?? row.country ?? "FR"
40040
+ };
40041
+ });
40042
+ return { rows, count: rows.length };
40043
+ }
40044
+ async getBusinessEntity(id) {
40045
+ return await this.client.get(`/config/business/entity/${encodePathSegment(id)}`);
40046
+ }
40047
+ async createLegalUnit(data) {
40048
+ return await this.client.post("/config/business/entity/legalunit", data);
40049
+ }
40050
+ async createOffice(data) {
40051
+ return await this.client.post("/config/business/entity/office", data);
40052
+ }
40053
+ async deleteBusinessEntity(id) {
40054
+ return await this.client.delete(`/config/business/entity/${encodePathSegment(id)}`);
40055
+ }
40056
+ async configureBusinessEntity(id, data) {
40057
+ return await this.client.post(`/config/business/entity/${encodePathSegment(id)}/configure`, data);
40058
+ }
40059
+ async claimBusinessEntity(id, data) {
40060
+ return await this.client.post(`/config/business/entity/${encodePathSegment(id)}/claim`, data);
40061
+ }
40062
+ async claimBusinessEntityByIdentifier(scheme, value, data) {
40063
+ return await this.client.post(
40064
+ `/config/business/entity/scheme/${encodePathSegment(scheme)}/value/${encodePathSegment(value)}/claim`,
40065
+ data
40066
+ );
40067
+ }
40068
+ async enrollFrench(data) {
40069
+ return await this.client.put("/config/french/enrollment", data);
40070
+ }
40071
+ async enrollInternational(data) {
40072
+ return await this.client.put("/config/international/enrollment", data);
40073
+ }
40074
+ async registerNetwork(identifierId, network) {
40075
+ return await this.client.post(
40076
+ `/config/business/entity/identifier/${encodePathSegment(identifierId)}/network/${encodePathSegment(network)}`
40077
+ );
40078
+ }
40079
+ async registerNetworkByScheme(scheme, value, network) {
40080
+ return await this.client.post(
40081
+ `/config/business/entity/identifier/scheme/${encodePathSegment(scheme)}/value/${encodePathSegment(value)}/network/${encodePathSegment(network)}`
40082
+ );
40083
+ }
40084
+ async unregisterNetwork(directoryId) {
40085
+ return await this.client.delete(
40086
+ `/config/business/entity/identifier/directory/${encodePathSegment(directoryId)}`
40087
+ );
40088
+ }
40089
+ // ─── Identifier Management ───────────────────────────────
40090
+ async createIdentifier(entityId, data) {
40091
+ return await this.client.post(`/config/business/entity/${encodePathSegment(entityId)}/identifier`, data);
40092
+ }
40093
+ async createIdentifierByScheme(scheme, value, data) {
40094
+ return await this.client.post(
40095
+ `/config/business/entity/scheme/${encodePathSegment(scheme)}/value/${encodePathSegment(value)}/identifier`,
40096
+ data
40097
+ );
40098
+ }
40099
+ async deleteIdentifier(identifierId) {
40100
+ return await this.client.delete(`/config/business/entity/identifier/${encodePathSegment(identifierId)}`);
40101
+ }
40102
+ // ─── Claim Management ────────────────────────────────────
40103
+ async deleteClaim(entityId) {
40104
+ return await this.client.delete(`/config/business/entity/${encodePathSegment(entityId)}/claim`);
40105
+ }
40106
+ };
40107
+ var normalizeForIopole = (inv) => {
40108
+ const normalized = { ...inv };
40109
+ for (const party of ["seller", "buyer"]) {
40110
+ if (normalized[party] && !normalized[party].postalAddress) {
40111
+ normalized[party] = {
40112
+ ...normalized[party],
40113
+ postalAddress: { country: normalized[party].country ?? "FR" }
40114
+ };
38834
40115
  }
38835
- cachedToken = data.access_token;
38836
- expiresAt = Date.now() + (data.expires_in ?? 600) * 1e3 - REFRESH_MARGIN_MS;
38837
- return cachedToken;
38838
40116
  }
38839
- return async () => {
38840
- if (cachedToken && Date.now() < expiresAt) {
38841
- return cachedToken;
40117
+ for (const party of ["seller", "buyer"]) {
40118
+ const p = normalized[party];
40119
+ if (p && !p.electronicAddress && p.siren && p.siret) {
40120
+ normalized[party] = {
40121
+ ...p,
40122
+ electronicAddress: `0225:${p.siren}_${p.siret}`,
40123
+ identifiers: p.identifiers ?? [
40124
+ { type: "ELECTRONIC_ADDRESS", value: `${p.siren}_${p.siret}`, scheme: "0225" },
40125
+ { type: "PARTY_LEGAL_IDENTIFIER", value: p.siren, scheme: "0002" }
40126
+ ]
40127
+ };
38842
40128
  }
38843
- if (!inflight) {
38844
- inflight = fetchToken().finally(() => {
38845
- inflight = void 0;
38846
- });
40129
+ }
40130
+ if (Array.isArray(normalized.paymentTerms)) {
40131
+ normalized.paymentTerms = normalized.paymentTerms.map((t) => t.description ?? t).join("; ");
40132
+ }
40133
+ if (normalized.monetary) {
40134
+ const m = { ...normalized.monetary };
40135
+ const currency = m.invoiceCurrency ?? "EUR";
40136
+ if (!m.payableAmount) m.payableAmount = m.invoiceAmount;
40137
+ if (m.taxTotalAmount && !m.taxTotalAmount.currency) {
40138
+ m.taxTotalAmount = { ...m.taxTotalAmount, currency };
38847
40139
  }
38848
- return inflight;
38849
- };
38850
- }
38851
- var IopoleAPIError = class extends Error {
38852
- constructor(message2, status, body) {
38853
- super(message2);
38854
- this.status = status;
38855
- this.body = body;
38856
- this.name = "IopoleAPIError";
40140
+ normalized.monetary = m;
40141
+ }
40142
+ if (Array.isArray(normalized.lines)) {
40143
+ normalized.lines = normalized.lines.map((line) => {
40144
+ if (line.taxDetail && !line.taxDetail.categoryCode) {
40145
+ return { ...line, taxDetail: { ...line.taxDetail, categoryCode: "S" } };
40146
+ }
40147
+ return line;
40148
+ });
38857
40149
  }
40150
+ return normalized;
38858
40151
  };
38859
- var IopoleClient = class {
40152
+ function autoWrapDirectoryQuery(q) {
40153
+ const trimmed = q.trim();
40154
+ if (/^\d{14}$/.test(trimmed)) return `siret:"${trimmed}"`;
40155
+ if (/^\d{9}$/.test(trimmed)) return `siren:"${trimmed}"`;
40156
+ if (/^FR\d{11}$/i.test(trimmed)) return `vatNumber:"${trimmed.toUpperCase()}"`;
40157
+ if (trimmed.length >= 3 && !/^\d+$/.test(trimmed) && !trimmed.includes(":")) {
40158
+ return `name:"*${trimmed}*"`;
40159
+ }
40160
+ return trimmed;
40161
+ }
40162
+ function normalizeStatusHistory(raw2) {
40163
+ let entries = [];
40164
+ if (Array.isArray(raw2)) {
40165
+ entries = raw2;
40166
+ } else if (raw2 && typeof raw2 === "object") {
40167
+ const obj = raw2;
40168
+ if (Array.isArray(obj.data)) entries = obj.data;
40169
+ else if (Array.isArray(obj.entries)) entries = obj.entries;
40170
+ else if (Array.isArray(obj.history)) entries = obj.history;
40171
+ }
40172
+ return {
40173
+ entries: entries.map((e) => ({
40174
+ date: e.date ?? e.createdAt ?? "",
40175
+ code: e.status?.code ?? e.code ?? e.statusCode ?? "",
40176
+ message: e.status?.message ?? e.message,
40177
+ destType: e.destType
40178
+ }))
40179
+ };
40180
+ }
40181
+ function createIopoleAdapter() {
40182
+ const baseUrl = requireEnv(
40183
+ "IopoleAdapter",
40184
+ "IOPOLE_API_URL",
40185
+ "Set it to https://api.ppd.iopole.fr/v1 (sandbox) or https://api.iopole.com/v1 (production)."
40186
+ );
40187
+ const clientId = requireEnv(
40188
+ "IopoleAdapter",
40189
+ "IOPOLE_CLIENT_ID",
40190
+ "Get your client ID from the Iopole dashboard or admin console."
40191
+ );
40192
+ const clientSecret = requireEnv(
40193
+ "IopoleAdapter",
40194
+ "IOPOLE_CLIENT_SECRET",
40195
+ "Get your client secret from the Iopole dashboard or admin console."
40196
+ );
40197
+ const customerId = requireEnv(
40198
+ "IopoleAdapter",
40199
+ "IOPOLE_CUSTOMER_ID",
40200
+ "Find it in Settings \u2192 Unique Identifier (sandbox) or admin console."
40201
+ );
40202
+ const authUrl = env2("IOPOLE_AUTH_URL") || IOPOLE_DEFAULT_AUTH_URL;
40203
+ const getToken = createOAuth2TokenProvider({ authUrl, clientId, clientSecret });
40204
+ const client = new IopoleClient({ baseUrl, customerId, getToken });
40205
+ return new IopoleAdapter(client);
40206
+ }
40207
+
40208
+ // src/adapters/shared/http-client.ts
40209
+ var BaseHttpClient = class {
38860
40210
  config;
38861
- constructor(config2) {
40211
+ adapterName;
40212
+ constructor(adapterName, config2) {
40213
+ this.adapterName = adapterName;
38862
40214
  this.config = config2;
38863
40215
  }
38864
40216
  // ─── Generic Request ────────────────────────────────────
38865
40217
  async request(method, path, options) {
38866
- return this.requestWithBase(this.config.baseUrl, method, path, options);
38867
- }
38868
- async requestWithBase(baseUrl, method, path, options) {
38869
- const url2 = new URL(`${baseUrl}${path}`);
40218
+ const url2 = new URL(`${this.config.baseUrl}${path}`);
38870
40219
  if (options?.query) {
38871
40220
  for (const [key, value] of Object.entries(options.query)) {
38872
- if (value !== void 0) {
40221
+ if (value === void 0) continue;
40222
+ if (Array.isArray(value)) {
40223
+ for (const v of value) url2.searchParams.append(key, v);
40224
+ } else {
38873
40225
  url2.searchParams.set(key, String(value));
38874
40226
  }
38875
40227
  }
38876
40228
  }
38877
- const token = await this.config.getToken();
40229
+ const authHeaders = await this.getAuthHeaders();
38878
40230
  const headers = {
38879
- Authorization: `Bearer ${token}`,
38880
- "customer-id": this.config.customerId,
40231
+ ...authHeaders,
38881
40232
  Accept: "application/json",
38882
40233
  ...options?.headers
38883
40234
  };
40235
+ let bodyPayload;
38884
40236
  if (options?.body) {
38885
- headers["Content-Type"] = "application/json";
40237
+ if (options.contentType && options.body instanceof Uint8Array) {
40238
+ headers["Content-Type"] = options.contentType;
40239
+ bodyPayload = options.body;
40240
+ } else {
40241
+ headers["Content-Type"] = options.contentType ?? "application/json";
40242
+ bodyPayload = JSON.stringify(options.body);
40243
+ }
38886
40244
  }
38887
40245
  const controller = new AbortController();
38888
40246
  const timeout = setTimeout(
@@ -38893,19 +40251,20 @@ var IopoleClient = class {
38893
40251
  const response = await fetch(url2.toString(), {
38894
40252
  method,
38895
40253
  headers,
38896
- body: options?.body ? JSON.stringify(options.body) : void 0,
40254
+ body: bodyPayload,
38897
40255
  signal: controller.signal
38898
40256
  });
38899
40257
  if (!response.ok) {
38900
40258
  const body = await response.text();
38901
- throw new IopoleAPIError(
38902
- `[IopoleClient] ${method} ${path} \u2192 ${response.status}: ${body.slice(0, 500)}`,
40259
+ throw new AdapterAPIError(
40260
+ this.adapterName,
40261
+ `[${this.adapterName}] ${method} ${path} \u2192 ${response.status}: ${body.slice(0, 500)}`,
38903
40262
  response.status,
38904
40263
  body
38905
40264
  );
38906
40265
  }
38907
- const contentType = response.headers.get("content-type") ?? "";
38908
- if (contentType.includes("application/json")) {
40266
+ const ct = response.headers.get("content-type") ?? "";
40267
+ if (ct.includes("application/json")) {
38909
40268
  return await response.json();
38910
40269
  }
38911
40270
  return await response.text();
@@ -38913,36 +40272,615 @@ var IopoleClient = class {
38913
40272
  clearTimeout(timeout);
38914
40273
  }
38915
40274
  }
38916
- // ─── Convenience Methods ────────────────────────────────
38917
- async get(path, query) {
38918
- return this.request("GET", path, { query });
40275
+ // ─── Convenience Methods ────────────────────────────────
40276
+ async get(path, query) {
40277
+ return this.request("GET", path, { query });
40278
+ }
40279
+ async post(path, body) {
40280
+ return this.request("POST", path, { body });
40281
+ }
40282
+ async put(path, body) {
40283
+ return this.request("PUT", path, { body });
40284
+ }
40285
+ async patch(path, body) {
40286
+ return this.request("PATCH", path, { body });
40287
+ }
40288
+ async delete(path) {
40289
+ return this.request("DELETE", path);
40290
+ }
40291
+ /**
40292
+ * Download a binary resource. Returns raw bytes + content type.
40293
+ */
40294
+ async download(path) {
40295
+ const url2 = `${this.config.baseUrl}${path}`;
40296
+ const authHeaders = await this.getAuthHeaders();
40297
+ const controller = new AbortController();
40298
+ const timeout = setTimeout(
40299
+ () => controller.abort(),
40300
+ this.config.timeoutMs ?? 3e4
40301
+ );
40302
+ try {
40303
+ const response = await fetch(url2, {
40304
+ method: "GET",
40305
+ headers: authHeaders,
40306
+ signal: controller.signal
40307
+ });
40308
+ if (!response.ok) {
40309
+ const body = await response.text();
40310
+ throw new AdapterAPIError(
40311
+ this.adapterName,
40312
+ `[${this.adapterName}] GET ${path} \u2192 ${response.status}`,
40313
+ response.status,
40314
+ body
40315
+ );
40316
+ }
40317
+ const data = new Uint8Array(await response.arrayBuffer());
40318
+ const contentType = response.headers.get("content-type") ?? "application/octet-stream";
40319
+ return { data, contentType };
40320
+ } finally {
40321
+ clearTimeout(timeout);
40322
+ }
40323
+ }
40324
+ };
40325
+
40326
+ // src/adapters/storecove/client.ts
40327
+ var StorecoveClient = class extends BaseHttpClient {
40328
+ apiKey;
40329
+ constructor(config2) {
40330
+ super("Storecove", { baseUrl: config2.baseUrl, timeoutMs: config2.timeoutMs });
40331
+ this.apiKey = config2.apiKey;
40332
+ }
40333
+ async getAuthHeaders() {
40334
+ return { Authorization: `Bearer ${this.apiKey}` };
40335
+ }
40336
+ };
40337
+
40338
+ // src/adapters/storecove/adapter.ts
40339
+ var StorecoveAdapter = class extends BaseAdapter {
40340
+ name = "storecove";
40341
+ /** Only methods with real Storecove API mappings. */
40342
+ capabilities = /* @__PURE__ */ new Set([
40343
+ // Invoice
40344
+ "emitInvoice",
40345
+ "getInvoice",
40346
+ "downloadInvoice",
40347
+ // Directory
40348
+ "searchDirectoryFr",
40349
+ "searchDirectoryInt",
40350
+ "checkPeppolParticipant",
40351
+ // Status
40352
+ "getStatusHistory",
40353
+ // Webhooks
40354
+ "listWebhooks",
40355
+ "deleteWebhook",
40356
+ // Config / Entities
40357
+ "getBusinessEntity",
40358
+ "createLegalUnit",
40359
+ "deleteBusinessEntity",
40360
+ "configureBusinessEntity",
40361
+ // Identifiers / Network
40362
+ "enrollInternational",
40363
+ "registerNetwork",
40364
+ "registerNetworkByScheme",
40365
+ "unregisterNetwork",
40366
+ "createIdentifier",
40367
+ "createIdentifierByScheme",
40368
+ "deleteIdentifier"
40369
+ ]);
40370
+ client;
40371
+ /** Default legal entity ID for operations that need one. */
40372
+ defaultLegalEntityId;
40373
+ constructor(client, defaultLegalEntityId) {
40374
+ super();
40375
+ this.client = client;
40376
+ this.defaultLegalEntityId = defaultLegalEntityId;
40377
+ }
40378
+ // ─── Invoice Operations ───────────────────────────────
40379
+ async emitInvoice(req) {
40380
+ const base643 = uint8ToBase64(req.file);
40381
+ const isXml = req.filename.toLowerCase().endsWith(".xml");
40382
+ return await this.client.post("/document_submissions", {
40383
+ document: {
40384
+ document_type: "invoice",
40385
+ raw_document: base643,
40386
+ raw_document_content_type: isXml ? "application/xml" : "application/pdf"
40387
+ },
40388
+ ...this.defaultLegalEntityId ? { legal_entity_id: Number(this.defaultLegalEntityId) } : {}
40389
+ });
40390
+ }
40391
+ async searchInvoices(_filters) {
40392
+ throw new NotSupportedError(
40393
+ this.name,
40394
+ "searchInvoices",
40395
+ "Storecove delivers invoices via webhooks (push) or pull queue. There is no search endpoint. Use webhook pull mode to retrieve pending documents."
40396
+ );
40397
+ }
40398
+ async getInvoice(id) {
40399
+ const doc = await this.client.get(`/received_documents/${encodePathSegment(id)}/json`);
40400
+ return {
40401
+ id,
40402
+ invoiceNumber: doc.invoiceNumber ?? doc.document?.invoiceNumber,
40403
+ status: doc.status ?? "received",
40404
+ direction: "received",
40405
+ senderName: doc.accountingSupplierParty?.party?.partyName,
40406
+ receiverName: doc.accountingCustomerParty?.party?.partyName,
40407
+ issueDate: doc.issueDate,
40408
+ dueDate: doc.dueDate,
40409
+ currency: doc.documentCurrencyCode ?? "EUR",
40410
+ totalTtc: doc.legalMonetaryTotal?.payableAmount
40411
+ };
40412
+ }
40413
+ async downloadInvoice(id) {
40414
+ return await this.client.download(`/received_documents/${encodePathSegment(id)}/original`);
40415
+ }
40416
+ async downloadReadable(_id) {
40417
+ throw new NotSupportedError(
40418
+ this.name,
40419
+ "downloadReadable",
40420
+ "Storecove does not generate readable PDFs. Use getInvoice for JSON or downloadInvoice for the original document."
40421
+ );
40422
+ }
40423
+ async getInvoiceFiles(_id) {
40424
+ throw new NotSupportedError(
40425
+ this.name,
40426
+ "getInvoiceFiles",
40427
+ "Storecove documents are atomic \u2014 no separate files list. Use getInvoice or downloadInvoice."
40428
+ );
40429
+ }
40430
+ async getAttachments(_id) {
40431
+ throw new NotSupportedError(
40432
+ this.name,
40433
+ "getAttachments",
40434
+ "Attachments are embedded in the Storecove document. Use getInvoice to access them."
40435
+ );
40436
+ }
40437
+ async downloadFile(_fileId) {
40438
+ throw new NotSupportedError(
40439
+ this.name,
40440
+ "downloadFile",
40441
+ "Storecove has no separate file download. Use downloadInvoice for the full document."
40442
+ );
40443
+ }
40444
+ async markInvoiceSeen(_id) {
40445
+ throw new NotSupportedError(
40446
+ this.name,
40447
+ "markInvoiceSeen",
40448
+ "Storecove does not track seen/unseen state via API."
40449
+ );
40450
+ }
40451
+ async getUnseenInvoices(_pagination) {
40452
+ throw new NotSupportedError(
40453
+ this.name,
40454
+ "getUnseenInvoices",
40455
+ "Use Storecove webhook pull mode to poll for new documents."
40456
+ );
40457
+ }
40458
+ async generateCII(_req) {
40459
+ throw new NotSupportedError(
40460
+ this.name,
40461
+ "generateCII",
40462
+ "Storecove auto-generates the compliant format on submission. Use emitInvoice with JSON Pure mode instead."
40463
+ );
40464
+ }
40465
+ async generateUBL(_req) {
40466
+ throw new NotSupportedError(
40467
+ this.name,
40468
+ "generateUBL",
40469
+ "Storecove auto-generates the compliant format on submission. Use emitInvoice with JSON Pure mode instead."
40470
+ );
40471
+ }
40472
+ async generateFacturX(_req) {
40473
+ throw new NotSupportedError(
40474
+ this.name,
40475
+ "generateFacturX",
40476
+ "Storecove auto-generates the compliant format on submission. Use emitInvoice with JSON Pure mode instead."
40477
+ );
40478
+ }
40479
+ // ─── Directory ────────────────────────────────────────
40480
+ async searchDirectoryFr(filters) {
40481
+ const raw2 = await this.client.post("/discovery/exists", {
40482
+ identifier: filters.q
40483
+ });
40484
+ const participant = raw2?.participant ?? raw2;
40485
+ if (!participant || !participant.identifier) {
40486
+ return { rows: [], count: 0 };
40487
+ }
40488
+ return {
40489
+ rows: [{
40490
+ entityId: participant.identifier ?? "",
40491
+ name: participant.name ?? participant.partyName,
40492
+ country: participant.country
40493
+ }],
40494
+ count: 1
40495
+ };
40496
+ }
40497
+ async searchDirectoryInt(filters) {
40498
+ return await this.client.post("/discovery/receives", {
40499
+ identifier: filters.value
40500
+ });
40501
+ }
40502
+ async checkPeppolParticipant(scheme, value) {
40503
+ return await this.client.post("/discovery/exists", {
40504
+ identifier: { scheme, identifier: value }
40505
+ });
40506
+ }
40507
+ // ─── Status ───────────────────────────────────────────
40508
+ async sendStatus(_req) {
40509
+ throw new NotSupportedError(
40510
+ this.name,
40511
+ "sendStatus",
40512
+ "Storecove status is managed by the receiving Access Point. The sender receives delivery evidence via the evidence endpoint."
40513
+ );
40514
+ }
40515
+ async getStatusHistory(invoiceId) {
40516
+ const raw2 = await this.client.get(
40517
+ `/document_submissions/${encodePathSegment(invoiceId)}/evidence/delivery`
40518
+ );
40519
+ return {
40520
+ entries: raw2 ? [{
40521
+ date: raw2.timestamp ?? raw2.date ?? "",
40522
+ code: raw2.status ?? "delivered",
40523
+ message: raw2.description
40524
+ }] : []
40525
+ };
40526
+ }
40527
+ async getUnseenStatuses(_pagination) {
40528
+ throw new NotSupportedError(
40529
+ this.name,
40530
+ "getUnseenStatuses",
40531
+ "Storecove delivers status changes via webhooks."
40532
+ );
40533
+ }
40534
+ async markStatusSeen(_statusId) {
40535
+ throw new NotSupportedError(
40536
+ this.name,
40537
+ "markStatusSeen",
40538
+ "Storecove does not track seen/unseen status via API."
40539
+ );
40540
+ }
40541
+ // ─── Reporting ────────────────────────────────────────
40542
+ async reportInvoiceTransaction(_transaction) {
40543
+ throw new NotSupportedError(
40544
+ this.name,
40545
+ "reportInvoiceTransaction",
40546
+ "Storecove handles tax reporting internally based on the destination country."
40547
+ );
40548
+ }
40549
+ async reportTransaction(_businessEntityId, _transaction) {
40550
+ throw new NotSupportedError(
40551
+ this.name,
40552
+ "reportTransaction",
40553
+ "Storecove handles tax reporting internally based on the destination country."
40554
+ );
40555
+ }
40556
+ // ─── Webhooks ─────────────────────────────────────────
40557
+ async listWebhooks() {
40558
+ throw new NotSupportedError(
40559
+ this.name,
40560
+ "listWebhooks",
40561
+ "Storecove /webhook_instances is a pull queue, not webhook config CRUD."
40562
+ );
40563
+ }
40564
+ async getWebhook(_id) {
40565
+ throw new NotSupportedError(
40566
+ this.name,
40567
+ "getWebhook",
40568
+ "Storecove has no get-by-id webhook endpoint. Use listWebhooks instead."
40569
+ );
40570
+ }
40571
+ async createWebhook(_req) {
40572
+ throw new NotSupportedError(
40573
+ this.name,
40574
+ "createWebhook",
40575
+ "Storecove webhooks are configured via the Storecove dashboard UI, not the API."
40576
+ );
40577
+ }
40578
+ async updateWebhook(_id, _req) {
40579
+ throw new NotSupportedError(
40580
+ this.name,
40581
+ "updateWebhook",
40582
+ "Storecove webhooks are configured via the Storecove dashboard UI, not the API."
40583
+ );
40584
+ }
40585
+ async deleteWebhook(_id) {
40586
+ throw new NotSupportedError(
40587
+ this.name,
40588
+ "deleteWebhook",
40589
+ "Storecove /webhook_instances is a pull queue, not webhook config CRUD."
40590
+ );
40591
+ }
40592
+ // ─── Operator Config ───────────────────────────────────
40593
+ async getCustomerId() {
40594
+ throw new NotSupportedError(
40595
+ this.name,
40596
+ "getCustomerId",
40597
+ "Storecove uses API keys, not customer IDs. Your identity is implicit in the API key."
40598
+ );
40599
+ }
40600
+ async listBusinessEntities() {
40601
+ throw new NotSupportedError(
40602
+ this.name,
40603
+ "listBusinessEntities",
40604
+ "Storecove has no list-all endpoint for legal entities. Track entity IDs locally after creation, or use getBusinessEntity with a known ID."
40605
+ );
40606
+ }
40607
+ async getBusinessEntity(id) {
40608
+ return await this.client.get(`/legal_entities/${encodePathSegment(id)}`);
40609
+ }
40610
+ async createLegalUnit(_data) {
40611
+ throw new NotSupportedError(
40612
+ this.name,
40613
+ "createLegalUnit",
40614
+ "Storecove requires address fields (line1, city, zip) not collected by the generic tool schema. Implement normalizeForStorecove when ready."
40615
+ );
40616
+ }
40617
+ async createOffice(_data) {
40618
+ throw new NotSupportedError(
40619
+ this.name,
40620
+ "createOffice",
40621
+ "Storecove has no office/establishment concept. Use createLegalUnit for all entities."
40622
+ );
40623
+ }
40624
+ async deleteBusinessEntity(id) {
40625
+ return await this.client.delete(`/legal_entities/${encodePathSegment(id)}`);
40626
+ }
40627
+ async configureBusinessEntity(_id, _data) {
40628
+ throw new NotSupportedError(
40629
+ this.name,
40630
+ "configureBusinessEntity",
40631
+ "Storecove PATCH /legal_entities uses a different model than the generic tool schema."
40632
+ );
40633
+ }
40634
+ async claimBusinessEntity(_id, _data) {
40635
+ throw new NotSupportedError(
40636
+ this.name,
40637
+ "claimBusinessEntity",
40638
+ "Storecove has no entity claim workflow. Entities are created and owned directly."
40639
+ );
40640
+ }
40641
+ async claimBusinessEntityByIdentifier(_scheme, _value, _data) {
40642
+ throw new NotSupportedError(
40643
+ this.name,
40644
+ "claimBusinessEntityByIdentifier",
40645
+ "Storecove has no entity claim workflow. Entities are created and owned directly."
40646
+ );
40647
+ }
40648
+ async enrollFrench(_data) {
40649
+ throw new NotSupportedError(
40650
+ this.name,
40651
+ "enrollFrench",
40652
+ "Use enrollInternational with Peppol identifiers for French entity registration on Storecove."
40653
+ );
40654
+ }
40655
+ async enrollInternational(data) {
40656
+ const legalEntityId = data.legalEntityId ?? this.defaultLegalEntityId;
40657
+ if (!legalEntityId) {
40658
+ throw new Error("[StorecoveAdapter] enrollInternational requires legalEntityId");
40659
+ }
40660
+ return await this.client.post(
40661
+ `/legal_entities/${encodePathSegment(String(legalEntityId))}/peppol_identifiers`,
40662
+ {
40663
+ superscheme: data.superscheme ?? "iso6523-actorid-upis",
40664
+ scheme: data.scheme,
40665
+ identifier: data.identifier ?? data.value
40666
+ }
40667
+ );
40668
+ }
40669
+ async registerNetwork(identifierId, _network) {
40670
+ return { message: `Peppol identifier ${identifierId} is registered on creation in Storecove.` };
40671
+ }
40672
+ async registerNetworkByScheme(scheme, value, _network) {
40673
+ return {
40674
+ message: `Peppol identifier ${scheme}:${value} is registered on creation in Storecove. Use enrollInternational to create the identifier.`
40675
+ };
40676
+ }
40677
+ async unregisterNetwork(directoryId) {
40678
+ const parts = directoryId.split("/");
40679
+ if (parts.length < 4) {
40680
+ throw new Error(
40681
+ "[StorecoveAdapter] unregisterNetwork expects directoryId as 'legalEntityId/superscheme/scheme/identifier'"
40682
+ );
40683
+ }
40684
+ const [legalEntityId, ...rest] = parts;
40685
+ return await this.client.delete(
40686
+ `/legal_entities/${encodePathSegment(legalEntityId)}/peppol_identifiers/${rest.map(encodePathSegment).join("/")}`
40687
+ );
40688
+ }
40689
+ // ─── Identifier Management ───────────────────────────────
40690
+ async createIdentifier(entityId, data) {
40691
+ if (data.scheme && String(data.scheme).startsWith("0")) {
40692
+ return await this.client.post(
40693
+ `/legal_entities/${encodePathSegment(entityId)}/peppol_identifiers`,
40694
+ {
40695
+ superscheme: data.superscheme ?? "iso6523-actorid-upis",
40696
+ scheme: data.scheme,
40697
+ identifier: data.value
40698
+ }
40699
+ );
40700
+ }
40701
+ return await this.client.post(
40702
+ `/legal_entities/${encodePathSegment(entityId)}/additional_tax_identifiers`,
40703
+ data
40704
+ );
40705
+ }
40706
+ async createIdentifierByScheme(_scheme, _value, _data) {
40707
+ throw new NotSupportedError(
40708
+ this.name,
40709
+ "createIdentifierByScheme",
40710
+ "Storecove cannot look up entities by scheme/value \u2014 requires legalEntityId. Implement when tool schema supports it."
40711
+ );
40712
+ }
40713
+ async deleteIdentifier(identifierId) {
40714
+ if (identifierId.includes("/")) {
40715
+ const segments = identifierId.split("/").map(encodePathSegment).join("/");
40716
+ return await this.client.delete(
40717
+ `/legal_entities/${segments}`
40718
+ );
40719
+ }
40720
+ throw new Error(
40721
+ "[StorecoveAdapter] deleteIdentifier for tax identifiers requires the full path: 'legalEntityId/additional_tax_identifiers/identifierId'"
40722
+ );
40723
+ }
40724
+ // ─── Claim Management ────────────────────────────────────
40725
+ async deleteClaim(_entityId) {
40726
+ throw new NotSupportedError(
40727
+ this.name,
40728
+ "deleteClaim",
40729
+ "Storecove has no entity claim concept. Delete the entity directly with deleteBusinessEntity."
40730
+ );
40731
+ }
40732
+ };
40733
+ function createStorecoveAdapter() {
40734
+ const baseUrl = requireEnv(
40735
+ "StorecoveAdapter",
40736
+ "STORECOVE_API_URL",
40737
+ "Set it to https://api.storecove.com/api/v2 (production/sandbox)."
40738
+ );
40739
+ const apiKey = requireEnv(
40740
+ "StorecoveAdapter",
40741
+ "STORECOVE_API_KEY",
40742
+ "Get your API key from the Storecove dashboard."
40743
+ );
40744
+ const defaultLegalEntityId = env2("STORECOVE_LEGAL_ENTITY_ID") || void 0;
40745
+ const client = new StorecoveClient({ baseUrl, apiKey });
40746
+ return new StorecoveAdapter(client, defaultLegalEntityId);
40747
+ }
40748
+
40749
+ // src/adapters/afnor/base-adapter.ts
40750
+ var AfnorBaseAdapter = class extends BaseAdapter {
40751
+ afnor;
40752
+ constructor(afnor) {
40753
+ super();
40754
+ this.afnor = afnor;
40755
+ }
40756
+ // ─── Invoice Operations (AFNOR: submitFlow, searchFlows, downloadFlow) ───
40757
+ async emitInvoice(req) {
40758
+ const afnor = this.requireAfnor("emitInvoice");
40759
+ const syntax = req.filename.toLowerCase().endsWith(".pdf") ? "Factur-X" : "CII";
40760
+ return await afnor.submitFlow(
40761
+ req.file,
40762
+ { flowSyntax: syntax, name: req.filename, processingRule: "B2B" }
40763
+ );
40764
+ }
40765
+ async searchInvoices(filters) {
40766
+ const afnor = this.requireAfnor("searchInvoices");
40767
+ const result = await afnor.searchFlows(
40768
+ {
40769
+ flowType: ["CustomerInvoice", "SupplierInvoice"],
40770
+ ...filters.q ? { trackingId: filters.q } : {}
40771
+ },
40772
+ filters.limit
40773
+ );
40774
+ const rows = (result.results ?? []).map((r) => ({
40775
+ id: r.flowId ?? "",
40776
+ status: r.ackStatus,
40777
+ direction: normalizeDirection(r.flowDirection),
40778
+ date: r.updatedAt ?? r.submittedAt
40779
+ }));
40780
+ return { rows, count: rows.length };
40781
+ }
40782
+ async getInvoice(id) {
40783
+ const afnor = this.requireAfnor("getInvoice");
40784
+ const { data, contentType } = await afnor.downloadFlow(id);
40785
+ if (contentType.includes("json")) {
40786
+ const doc = JSON.parse(new TextDecoder().decode(data));
40787
+ return {
40788
+ id,
40789
+ invoiceNumber: doc.invoiceId ?? doc.invoiceNumber,
40790
+ status: doc.ackStatus ?? doc.status,
40791
+ direction: normalizeDirection(doc.flowDirection),
40792
+ senderName: doc.seller?.name,
40793
+ receiverName: doc.buyer?.name,
40794
+ issueDate: doc.invoiceDate,
40795
+ currency: doc.currency ?? "EUR"
40796
+ };
40797
+ }
40798
+ return { id, status: "UNKNOWN" };
40799
+ }
40800
+ async downloadInvoice(id) {
40801
+ const afnor = this.requireAfnor("downloadInvoice");
40802
+ return await afnor.downloadFlow(id, "Original");
38919
40803
  }
38920
- /**
38921
- * GET request against the v1.1 API endpoint.
38922
- * Builds a v1.1 URL without mutating this.config.baseUrl,
38923
- * so concurrent calls (e.g. getV11 + get) cannot interfere.
38924
- */
38925
- async getV11(path, query) {
38926
- const baseV11 = this.config.baseUrl.replace(/\/v1\b/, "/v1.1");
38927
- return this.requestWithBase(baseV11, "GET", path, { query });
40804
+ // ─── Status (AFNOR: lifecycle flows) ───────────────────
40805
+ async sendStatus(req) {
40806
+ const afnor = this.requireAfnor("sendStatus");
40807
+ const cdarPayload = JSON.stringify({
40808
+ invoiceId: req.invoiceId,
40809
+ statusCode: req.code,
40810
+ message: req.message,
40811
+ payment: req.payment
40812
+ });
40813
+ return await afnor.submitFlow(
40814
+ new TextEncoder().encode(cdarPayload),
40815
+ { flowSyntax: "CDAR", name: `status-${req.invoiceId}.json`, processingRule: "B2B" }
40816
+ );
38928
40817
  }
38929
- async post(path, body) {
38930
- return this.request("POST", path, { body });
40818
+ async getStatusHistory(invoiceId) {
40819
+ const afnor = this.requireAfnor("getStatusHistory");
40820
+ const result = await afnor.searchFlows({
40821
+ flowType: ["CustomerInvoiceLC", "SupplierInvoiceLC"],
40822
+ trackingId: invoiceId
40823
+ });
40824
+ const entries = (result.results ?? []).map((r) => ({
40825
+ date: r.updatedAt ?? r.submittedAt ?? "",
40826
+ code: r.ackStatus ?? "",
40827
+ message: r.flowType,
40828
+ destType: r.flowDirection === "In" ? "PLATFORM" : "OPERATOR"
40829
+ }));
40830
+ return { entries };
38931
40831
  }
38932
- async put(path, body) {
38933
- return this.request("PUT", path, { body });
40832
+ // ─── Reporting (AFNOR: e-reporting flows) ──────────────
40833
+ async reportInvoiceTransaction(transaction) {
40834
+ const afnor = this.requireAfnor("reportInvoiceTransaction");
40835
+ const payload = new TextEncoder().encode(JSON.stringify(transaction));
40836
+ return await afnor.submitFlow(
40837
+ payload,
40838
+ { flowSyntax: "FRR", name: "report.json", processingRule: "B2C" }
40839
+ );
38934
40840
  }
38935
- async delete(path) {
38936
- return this.request("DELETE", path);
40841
+ async reportTransaction(businessEntityId, transaction) {
40842
+ const afnor = this.requireAfnor("reportTransaction");
40843
+ const payload = new TextEncoder().encode(JSON.stringify({ businessEntityId, ...transaction }));
40844
+ return await afnor.submitFlow(
40845
+ payload,
40846
+ { flowSyntax: "FRR", name: "report.json", processingRule: "B2C" }
40847
+ );
40848
+ }
40849
+ // All other methods inherit NotSupportedError stubs from BaseAdapter.
40850
+ // Subclasses override with native API implementations.
40851
+ // ─── Helpers ───────────────────────────────────────────
40852
+ /** Get the AFNOR client or throw if not configured. */
40853
+ requireAfnor(method) {
40854
+ if (!this.afnor) {
40855
+ throw new NotSupportedError(
40856
+ this.name,
40857
+ method,
40858
+ "AFNOR API not configured. Override this method with native implementation."
40859
+ );
40860
+ }
40861
+ return this.afnor;
40862
+ }
40863
+ };
40864
+
40865
+ // src/adapters/afnor/client.ts
40866
+ var AfnorClient = class extends BaseHttpClient {
40867
+ getToken;
40868
+ constructor(config2) {
40869
+ super("AFNOR", { baseUrl: config2.baseUrl, timeoutMs: config2.timeoutMs });
40870
+ this.getToken = config2.getToken;
40871
+ }
40872
+ async getAuthHeaders() {
40873
+ const token = await this.getToken();
40874
+ return { Authorization: `Bearer ${token}` };
38937
40875
  }
40876
+ // ─── AFNOR-Specific Methods ──────────────────────────
38938
40877
  /**
38939
- * Upload a file via multipart/form-data.
38940
- * Used for POST /invoice (emitInvoice) the Swagger spec requires
38941
- * Content-Type: multipart/form-data with a `file` field (binary, PDF or XML).
40878
+ * Submit a new flow (invoice, lifecycle event, or e-reporting).
40879
+ * POST /v1/flows — multipart: flowInfo (JSON) + file (binary).
38942
40880
  */
38943
- async upload(path, file2, filename) {
38944
- const url2 = `${this.config.baseUrl}${path}`;
38945
- const token = await this.config.getToken();
40881
+ async submitFlow(file2, flowInfo) {
40882
+ const url2 = new URL(`${this.config.baseUrl}/v1/flows`);
40883
+ const authHeaders = await this.getAuthHeaders();
38946
40884
  const controller = new AbortController();
38947
40885
  const timeout = setTimeout(
38948
40886
  () => controller.abort(),
@@ -38950,67 +40888,68 @@ var IopoleClient = class {
38950
40888
  );
38951
40889
  try {
38952
40890
  const form = new FormData();
38953
- form.append("file", new Blob([file2]), filename);
38954
- const response = await fetch(url2, {
40891
+ form.append("flowInfo", JSON.stringify(flowInfo));
40892
+ form.append("file", new Blob([file2]), flowInfo.name ?? "invoice.xml");
40893
+ const response = await fetch(url2.toString(), {
38955
40894
  method: "POST",
38956
- headers: {
38957
- Authorization: `Bearer ${token}`,
38958
- "customer-id": this.config.customerId,
38959
- Accept: "application/json"
38960
- // Do NOT set Content-Type — fetch sets it with the multipart boundary
38961
- },
40895
+ headers: { ...authHeaders, Accept: "application/json" },
38962
40896
  body: form,
38963
40897
  signal: controller.signal
38964
40898
  });
38965
40899
  if (!response.ok) {
38966
40900
  const body = await response.text();
38967
- throw new IopoleAPIError(
38968
- `[IopoleClient] POST ${path} (upload) \u2192 ${response.status}: ${body.slice(0, 500)}`,
40901
+ throw new AdapterAPIError(
40902
+ "AFNOR",
40903
+ `[AFNOR] POST /v1/flows \u2192 ${response.status}: ${body.slice(0, 500)}`,
38969
40904
  response.status,
38970
40905
  body
38971
40906
  );
38972
40907
  }
38973
- const contentType = response.headers.get("content-type") ?? "";
38974
- if (contentType.includes("application/json")) {
38975
- return await response.json();
38976
- }
38977
- return await response.text();
40908
+ return await response.json();
38978
40909
  } finally {
38979
40910
  clearTimeout(timeout);
38980
40911
  }
38981
40912
  }
38982
40913
  /**
38983
- * POST with query parameters.
38984
- * Used for /tools/{cii,ubl,facturx}/generate which require `flavor` as query param.
40914
+ * Search flows by criteria.
40915
+ * POST /v1/flows/search
38985
40916
  */
38986
- async postWithQuery(path, body, query) {
38987
- return this.request("POST", path, { body, query });
40917
+ async searchFlows(filters, limit) {
40918
+ return await this.request("POST", "/v1/flows/search", {
40919
+ body: {
40920
+ where: filters,
40921
+ ...limit ? { limit } : {}
40922
+ }
40923
+ });
38988
40924
  }
38989
40925
  /**
38990
- * Download a binary resource (invoice PDF, attachment, etc.)
38991
- * Returns the raw Response for streaming.
40926
+ * Download a flow file.
40927
+ * GET /v1/flows/{flowId}
38992
40928
  */
38993
- async download(path) {
38994
- const url2 = `${this.config.baseUrl}${path}`;
38995
- const token = await this.config.getToken();
40929
+ async downloadFlow(flowId, docType) {
40930
+ const query = docType ? { docType } : void 0;
40931
+ const url2 = `${this.config.baseUrl}/v1/flows/${flowId}`;
40932
+ const authHeaders = await this.getAuthHeaders();
38996
40933
  const controller = new AbortController();
38997
40934
  const timeout = setTimeout(
38998
40935
  () => controller.abort(),
38999
40936
  this.config.timeoutMs ?? 3e4
39000
40937
  );
39001
40938
  try {
39002
- const response = await fetch(url2, {
40939
+ const fullUrl = new URL(url2);
40940
+ if (query) {
40941
+ for (const [k, v] of Object.entries(query)) fullUrl.searchParams.set(k, v);
40942
+ }
40943
+ const response = await fetch(fullUrl.toString(), {
39003
40944
  method: "GET",
39004
- headers: {
39005
- Authorization: `Bearer ${token}`,
39006
- "customer-id": this.config.customerId
39007
- },
40945
+ headers: authHeaders,
39008
40946
  signal: controller.signal
39009
40947
  });
39010
40948
  if (!response.ok) {
39011
40949
  const body = await response.text();
39012
- throw new IopoleAPIError(
39013
- `[IopoleClient] GET ${path} \u2192 ${response.status}`,
40950
+ throw new AdapterAPIError(
40951
+ "AFNOR",
40952
+ `[AFNOR] GET /v1/flows/${flowId} \u2192 ${response.status}`,
39014
40953
  response.status,
39015
40954
  body
39016
40955
  );
@@ -39022,191 +40961,473 @@ var IopoleClient = class {
39022
40961
  clearTimeout(timeout);
39023
40962
  }
39024
40963
  }
40964
+ /**
40965
+ * Health check — GET /v1/healthcheck
40966
+ */
40967
+ async healthcheck() {
40968
+ try {
40969
+ await this.request("GET", "/v1/healthcheck");
40970
+ return true;
40971
+ } catch {
40972
+ return false;
40973
+ }
40974
+ }
39025
40975
  };
39026
40976
 
39027
- // src/runtime.ts
39028
- import { statSync as fsStatSync } from "node:fs";
39029
- import { readFile as readFile2 } from "node:fs/promises";
39030
- function env2(key) {
39031
- return process.env[key];
40977
+ // src/adapters/superpdp/client.ts
40978
+ var SuperPDPClient = class extends BaseHttpClient {
40979
+ getToken;
40980
+ constructor(config2) {
40981
+ super("SuperPDP", { baseUrl: config2.baseUrl, timeoutMs: config2.timeoutMs });
40982
+ this.getToken = config2.getToken;
40983
+ }
40984
+ async getAuthHeaders() {
40985
+ const token = await this.getToken();
40986
+ return { Authorization: `Bearer ${token}` };
40987
+ }
40988
+ /**
40989
+ * Submit XML invoice content.
40990
+ * Super PDP accepts raw XML body for invoice creation.
40991
+ */
40992
+ async postXml(path, xmlData, query) {
40993
+ return this.request("POST", path, {
40994
+ body: xmlData,
40995
+ contentType: "application/xml",
40996
+ query
40997
+ });
40998
+ }
40999
+ /**
41000
+ * Convert invoice format.
41001
+ * POST /invoices/convert with body + from/to query params.
41002
+ */
41003
+ async convert(data, from, to) {
41004
+ const contentType = from === "en16931" ? "application/json" : "application/xml";
41005
+ return this.request("POST", "/invoices/convert", {
41006
+ body: data,
41007
+ contentType,
41008
+ query: { from, to }
41009
+ });
41010
+ }
41011
+ };
41012
+
41013
+ // src/adapters/superpdp/normalize.ts
41014
+ function toDecimal(v) {
41015
+ if (v == null) return "0.00";
41016
+ const n = typeof v === "string" ? parseFloat(v) : Number(v);
41017
+ if (!Number.isFinite(n)) return "0.00";
41018
+ return n.toFixed(2);
39032
41019
  }
39033
- async function readTextFile2(path) {
39034
- return await readFile2(path, "utf-8");
41020
+ function setIfAbsent(obj, key, value) {
41021
+ if (obj[key] == null && value != null) obj[key] = value;
39035
41022
  }
39036
- function statSync(path) {
39037
- try {
39038
- fsStatSync(path);
39039
- return true;
39040
- } catch {
39041
- return false;
41023
+ function stripNulls(obj) {
41024
+ const clean = {};
41025
+ for (const [k, v] of Object.entries(obj)) {
41026
+ if (v != null) clean[k] = v;
39042
41027
  }
41028
+ return clean;
39043
41029
  }
39044
- function getArgs() {
39045
- return process.argv.slice(2);
39046
- }
39047
- function exit(code) {
39048
- process.exit(code);
41030
+ var PARTY_SOURCE_FIELDS = ["country", "address", "siret", "siretNumber", "siren", "sirenNumber", "vatNumber", "vat_number", "vatId"];
41031
+ function normalizeParty(party, requireElectronicAddress) {
41032
+ if (!party || typeof party !== "object") return party;
41033
+ const p = { ...party };
41034
+ if (!p.postal_address) {
41035
+ if (p.address && typeof p.address === "object") {
41036
+ p.postal_address = stripNulls({
41037
+ country_code: p.address.country_code ?? p.address.country ?? p.country ?? "FR",
41038
+ address_line1: p.address.street ?? p.address.address_line1 ?? p.address.line1,
41039
+ city: p.address.city,
41040
+ post_code: p.address.postal_code ?? p.address.post_code ?? p.address.zip
41041
+ });
41042
+ } else if (p.country) {
41043
+ p.postal_address = { country_code: p.country };
41044
+ }
41045
+ }
41046
+ if (!p.electronic_address && requireElectronicAddress) {
41047
+ const siret = p.siret ?? p.siretNumber;
41048
+ if (siret) {
41049
+ p.electronic_address = { scheme: "0009", value: String(siret) };
41050
+ }
41051
+ }
41052
+ if (!p.legal_registration_identifier) {
41053
+ const siren = p.siren ?? p.sirenNumber;
41054
+ const siret = p.siret ?? p.siretNumber;
41055
+ const id = siren ?? siret;
41056
+ if (id) {
41057
+ p.legal_registration_identifier = { scheme: "0002", value: String(id) };
41058
+ }
41059
+ }
41060
+ if (!p.vat_identifier) {
41061
+ const vat = p.vatNumber ?? p.vat_number ?? p.vatId;
41062
+ if (vat) p.vat_identifier = String(vat);
41063
+ }
41064
+ for (const f of PARTY_SOURCE_FIELDS) delete p[f];
41065
+ return p;
39049
41066
  }
39050
- function onSignal(signal, handler) {
39051
- process.on(signal, handler);
41067
+ var TOTALS_SOURCE_FIELDS = ["line_extension_amount", "lineTotalAmount", "tax_exclusive_amount", "taxBasisTotalAmount", "tax_inclusive_amount", "grandTotalAmount", "payableAmount"];
41068
+ function normalizeTotals(totals) {
41069
+ if (!totals || typeof totals !== "object") return totals;
41070
+ const t = { ...totals };
41071
+ setIfAbsent(t, "sum_invoice_lines_amount", t.line_extension_amount ?? t.lineTotalAmount);
41072
+ setIfAbsent(t, "total_without_vat", t.tax_exclusive_amount ?? t.taxBasisTotalAmount);
41073
+ setIfAbsent(t, "total_with_vat", t.tax_inclusive_amount ?? t.grandTotalAmount);
41074
+ setIfAbsent(t, "amount_due_for_payment", t.payableAmount);
41075
+ for (const key of ["sum_invoice_lines_amount", "total_without_vat", "total_with_vat", "amount_due_for_payment"]) {
41076
+ if (t[key] != null) t[key] = toDecimal(t[key]);
41077
+ }
41078
+ if (t.total_vat_amount != null && typeof t.total_vat_amount !== "object") {
41079
+ t.total_vat_amount = { value: toDecimal(t.total_vat_amount) };
41080
+ } else if (t.total_vat_amount?.value != null) {
41081
+ t.total_vat_amount = { ...t.total_vat_amount, value: toDecimal(t.total_vat_amount.value) };
41082
+ }
41083
+ for (const f of TOTALS_SOURCE_FIELDS) delete t[f];
41084
+ return t;
39052
41085
  }
39053
-
39054
- // src/adapters/iopole.ts
39055
- var IOPOLE_DEFAULT_AUTH_URL = "https://auth.iopole.com/realms/iopole/protocol/openid-connect/token";
39056
- var IopoleAdapter = class {
39057
- name = "iopole";
41086
+ var VAT_SOURCE_FIELDS = ["category_code", "categoryCode", "rate", "percent", "vat_rate", "taxable_amount", "taxableAmount", "tax_amount", "taxAmount"];
41087
+ function normalizeVatBreakdown(vat) {
41088
+ if (!vat || typeof vat !== "object") return vat;
41089
+ const v = { ...vat };
41090
+ setIfAbsent(v, "vat_category_code", v.category_code ?? v.categoryCode);
41091
+ setIfAbsent(v, "vat_category_rate", v.rate ?? v.percent ?? v.vat_rate);
41092
+ setIfAbsent(v, "vat_category_taxable_amount", v.taxable_amount ?? v.taxableAmount);
41093
+ setIfAbsent(v, "vat_category_tax_amount", v.tax_amount ?? v.taxAmount);
41094
+ if (v.vat_category_rate != null) v.vat_category_rate = toDecimal(v.vat_category_rate);
41095
+ if (v.vat_category_taxable_amount != null) v.vat_category_taxable_amount = toDecimal(v.vat_category_taxable_amount);
41096
+ if (v.vat_category_tax_amount != null) v.vat_category_tax_amount = toDecimal(v.vat_category_tax_amount);
41097
+ for (const f of VAT_SOURCE_FIELDS) delete v[f];
41098
+ return v;
41099
+ }
41100
+ var LINE_SOURCE_FIELDS = ["id", "name", "item_name", "description", "quantity", "billed_quantity", "unit_code", "unitCode", "net_price", "price", "unit_price", "unitPrice", "line_amount", "line_total_amount", "line_net_amount", "amount", "totalAmount", "tax_category", "vat_category_code", "line_vat_category_code", "vatCategoryCode", "tax_percent", "vat_rate", "line_vat_rate", "vatRate"];
41101
+ function normalizeLine(line) {
41102
+ if (!line || typeof line !== "object") return line;
41103
+ const l = { ...line };
41104
+ setIfAbsent(l, "identifier", l.id);
41105
+ if (!l.item_information) {
41106
+ const name = l.name ?? l.item_name ?? l.description;
41107
+ if (name) l.item_information = { name: String(name) };
41108
+ }
41109
+ setIfAbsent(l, "invoiced_quantity", l.quantity ?? l.billed_quantity);
41110
+ if (l.invoiced_quantity != null) l.invoiced_quantity = toDecimal(l.invoiced_quantity);
41111
+ setIfAbsent(l, "invoiced_quantity_code", l.unit_code ?? l.unitCode ?? "C62");
41112
+ if (!l.price_details) {
41113
+ const price = l.net_price ?? l.price ?? l.unit_price ?? l.unitPrice;
41114
+ if (price != null) l.price_details = { item_net_price: toDecimal(price) };
41115
+ }
41116
+ setIfAbsent(l, "net_amount", l.line_amount ?? l.line_total_amount ?? l.line_net_amount ?? l.amount ?? l.totalAmount);
41117
+ if (l.net_amount != null) l.net_amount = toDecimal(l.net_amount);
41118
+ if (!l.vat_information) {
41119
+ const catCode = l.tax_category ?? l.vat_category_code ?? l.line_vat_category_code ?? l.vatCategoryCode;
41120
+ const rate = l.tax_percent ?? l.vat_rate ?? l.line_vat_rate ?? l.vatRate;
41121
+ if (catCode) {
41122
+ l.vat_information = {
41123
+ invoiced_item_vat_category_code: String(catCode),
41124
+ ...rate != null ? { invoiced_item_vat_rate: toDecimal(rate) } : {}
41125
+ };
41126
+ }
41127
+ }
41128
+ for (const f of LINE_SOURCE_FIELDS) delete l[f];
41129
+ return l;
41130
+ }
41131
+ var normalizeForSuperPDP = (inv) => {
41132
+ const n = { ...inv };
41133
+ if (!n.process_control) {
41134
+ n.process_control = { specification_identifier: "urn:cen.eu:en16931:2017" };
41135
+ }
41136
+ if (!n.payment_due_date) {
41137
+ const due = n.due_date ?? n.dueDate ?? n.invoiceDueDate;
41138
+ if (due) n.payment_due_date = String(due);
41139
+ }
41140
+ for (const f of ["due_date", "dueDate", "invoiceDueDate"]) delete n[f];
41141
+ const buyerSiret = n.buyer?.siret ?? n.buyer?.siretNumber;
41142
+ if (n.seller) n.seller = normalizeParty(n.seller, true);
41143
+ if (n.buyer) n.buyer = normalizeParty(n.buyer, false);
41144
+ if (n.totals) n.totals = normalizeTotals(n.totals);
41145
+ let vatSource;
41146
+ if (Array.isArray(n.vat_break_down)) vatSource = n.vat_break_down;
41147
+ else if (Array.isArray(n.taxDetails)) vatSource = n.taxDetails;
41148
+ else if (Array.isArray(n.vatBreakdown)) vatSource = n.vatBreakdown;
41149
+ if (vatSource) {
41150
+ n.vat_break_down = vatSource.map(normalizeVatBreakdown);
41151
+ delete n.taxDetails;
41152
+ delete n.vatBreakdown;
41153
+ }
41154
+ if (Array.isArray(n.lines)) {
41155
+ n.lines = n.lines.map(normalizeLine);
41156
+ }
41157
+ if (n.payment_instructions) {
41158
+ const pi = { ...n.payment_instructions };
41159
+ if (pi.credit_transfer && !pi.credit_transfers) {
41160
+ pi.credit_transfers = Array.isArray(pi.credit_transfer) ? pi.credit_transfer : [pi.credit_transfer];
41161
+ delete pi.credit_transfer;
41162
+ }
41163
+ if (Array.isArray(pi.credit_transfers)) {
41164
+ pi.credit_transfers = pi.credit_transfers.map((ct) => {
41165
+ if (ct?.payment_account_identifier?.scheme?.toUpperCase() === "IBAN") {
41166
+ return { ...ct, payment_account_identifier: { ...ct.payment_account_identifier, scheme: "" } };
41167
+ }
41168
+ return ct;
41169
+ });
41170
+ }
41171
+ n.payment_instructions = pi;
41172
+ }
41173
+ if (!n.delivery_information) {
41174
+ n.delivery_information = { delivery_date: n.issue_date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) };
41175
+ }
41176
+ if (!n.notes || !Array.isArray(n.notes)) n.notes = [];
41177
+ const noteCodes = new Set(n.notes.map((note) => note?.subject_code));
41178
+ if (!noteCodes.has("PMT")) {
41179
+ n.notes.push({ note: "En cas de retard de paiement, indemnite forfaitaire de 40 euros pour frais de recouvrement (art. L441-10 C.com).", subject_code: "PMT" });
41180
+ }
41181
+ if (!noteCodes.has("PMD")) {
41182
+ n.notes.push({ note: "Penalites de retard : 3 fois le taux d'interet legal (art. L441-10 C.com).", subject_code: "PMD" });
41183
+ }
41184
+ if (!noteCodes.has("AAB")) {
41185
+ n.notes.push({ note: "Pas d'escompte pour paiement anticipe.", subject_code: "AAB" });
41186
+ }
41187
+ const buyerCountry = n.buyer?.postal_address?.country_code ?? n.buyer?.country;
41188
+ if (n.buyer && !n.buyer.electronic_address && buyerCountry === "FR") {
41189
+ const siret = buyerSiret ?? n.buyer.legal_registration_identifier?.value;
41190
+ if (!siret) {
41191
+ throw new Error("BR-FR-12: buyer.electronic_address is required for French buyers. Provide buyer.siret, buyer.electronic_address, or buyer.legal_registration_identifier.");
41192
+ }
41193
+ n.buyer.electronic_address = { scheme: "0009", value: siret };
41194
+ }
41195
+ return n;
41196
+ };
41197
+
41198
+ // src/adapters/superpdp/adapter.ts
41199
+ var SuperPDPAdapter = class extends AfnorBaseAdapter {
41200
+ name = "superpdp";
41201
+ capabilities = /* @__PURE__ */ new Set([
41202
+ // Native overrides
41203
+ "emitInvoice",
41204
+ "searchInvoices",
41205
+ "getInvoice",
41206
+ "downloadInvoice",
41207
+ "generateCII",
41208
+ "generateUBL",
41209
+ "sendStatus",
41210
+ "getStatusHistory",
41211
+ "getCustomerId",
41212
+ "getBusinessEntity",
41213
+ "createOffice",
41214
+ "enrollFrench",
41215
+ "registerNetwork",
41216
+ "registerNetworkByScheme",
41217
+ "unregisterNetwork",
41218
+ "createIdentifier",
41219
+ "createIdentifierByScheme",
41220
+ "deleteIdentifier",
41221
+ "searchDirectoryFr",
41222
+ // Inherited from AFNOR base
41223
+ "reportInvoiceTransaction",
41224
+ "reportTransaction"
41225
+ ]);
39058
41226
  client;
39059
- constructor(client) {
41227
+ constructor(client, afnor) {
41228
+ super(afnor);
39060
41229
  this.client = client;
39061
41230
  }
39062
- // ─── Invoice Operations ───────────────────────────────
41231
+ // ─── Invoice Operations (native) ──────────────────────
39063
41232
  async emitInvoice(req) {
39064
- return await this.client.upload("/invoice", req.file, req.filename);
41233
+ return await this.client.postXml("/invoices", req.file, {
41234
+ external_id: req.filename
41235
+ });
39065
41236
  }
39066
41237
  async searchInvoices(filters) {
39067
- return await this.client.getV11("/invoice/search", {
39068
- q: filters.q,
39069
- expand: filters.expand,
39070
- offset: filters.offset ?? 0,
39071
- limit: filters.limit ?? 50
41238
+ const direction = filters.direction === "received" ? "in" : filters.direction === "sent" ? "out" : filters.q === "in" || filters.q === "out" ? filters.q : void 0;
41239
+ const raw2 = await this.client.get("/invoices", {
41240
+ "expand[]": ["en_invoice", "events"],
41241
+ ...direction ? { direction } : {},
41242
+ ...filters.limit ? { limit: filters.limit } : {}
41243
+ // Note: SuperPDP uses cursor-based pagination (starting_after_id),
41244
+ // not offset-based. Offset is ignored — use last row ID for next page.
39072
41245
  });
41246
+ const data = Array.isArray(raw2) ? raw2 : raw2?.data ?? [];
41247
+ const rows = data.map((inv) => ({
41248
+ id: String(inv.id ?? ""),
41249
+ invoiceNumber: inv.en_invoice?.number ?? inv.external_id,
41250
+ status: lastEventCode(inv.events),
41251
+ direction: normalizeDirection(inv.direction),
41252
+ senderName: inv.en_invoice?.seller?.name,
41253
+ receiverName: inv.en_invoice?.buyer?.name,
41254
+ date: inv.en_invoice?.issue_date,
41255
+ amount: inv.en_invoice?.totals?.amount_due_for_payment,
41256
+ currency: inv.en_invoice?.currency_code ?? "EUR"
41257
+ }));
41258
+ return { rows, count: raw2?.count ?? rows.length };
39073
41259
  }
39074
41260
  async getInvoice(id) {
39075
- return await this.client.get(`/invoice/${id}`, { expand: "businessData" });
41261
+ const inv = await this.client.get(`/invoices/${encodePathSegment(id)}`);
41262
+ const en = inv.en_invoice;
41263
+ return {
41264
+ id: String(inv.id ?? id),
41265
+ invoiceNumber: en?.number ?? inv.external_id,
41266
+ status: lastEventCode(inv.events),
41267
+ direction: normalizeDirection(inv.direction),
41268
+ senderName: en?.seller?.name,
41269
+ receiverName: en?.buyer?.name,
41270
+ issueDate: en?.issue_date,
41271
+ dueDate: en?.due_date,
41272
+ currency: en?.currency_code ?? "EUR",
41273
+ totalHt: en?.totals?.tax_exclusive_amount,
41274
+ totalTtc: en?.totals?.tax_inclusive_amount
41275
+ };
39076
41276
  }
39077
41277
  async downloadInvoice(id) {
39078
- return await this.client.download(`/invoice/${id}/download`);
39079
- }
39080
- async downloadReadable(id) {
39081
- return await this.client.download(`/invoice/${id}/download/readable`);
39082
- }
39083
- async getInvoiceFiles(id) {
39084
- return await this.client.get(`/invoice/${id}/files`);
39085
- }
39086
- async getAttachments(id) {
39087
- return await this.client.get(`/invoice/${id}/files/attachments`);
39088
- }
39089
- async downloadFile(fileId) {
39090
- return await this.client.download(`/invoice/file/${fileId}/download`);
39091
- }
39092
- async markInvoiceSeen(id) {
39093
- return await this.client.put(`/invoice/${id}/markAsSeen`);
39094
- }
39095
- async getUnseenInvoices(pagination) {
39096
- return await this.client.get("/invoice/notSeen", {
39097
- offset: pagination.offset,
39098
- limit: pagination.limit
39099
- });
41278
+ return await this.client.download(`/invoices/${encodePathSegment(id)}/download`);
39100
41279
  }
41280
+ // ─── Format Conversion (native) ───────────────────────
39101
41281
  async generateCII(req) {
39102
- return await this.client.postWithQuery("/tools/cii/generate", req.invoice, {
39103
- flavor: req.flavor
39104
- });
41282
+ const invoice = normalizeForSuperPDP(req.invoice);
41283
+ const payload = new TextEncoder().encode(JSON.stringify(invoice));
41284
+ return await this.client.convert(payload, "en16931", "cii");
39105
41285
  }
39106
41286
  async generateUBL(req) {
39107
- return await this.client.postWithQuery("/tools/ubl/generate", req.invoice, {
39108
- flavor: req.flavor
39109
- });
39110
- }
39111
- async generateFacturX(req) {
39112
- return await this.client.postWithQuery("/tools/facturx/generate", req.invoice, {
39113
- flavor: req.flavor,
39114
- language: req.language
39115
- });
39116
- }
39117
- // ─── Directory ────────────────────────────────────────
39118
- async searchDirectoryFr(filters) {
39119
- return await this.client.get("/directory/french", {
39120
- q: filters.q,
39121
- offset: filters.offset,
39122
- limit: filters.limit
39123
- });
39124
- }
39125
- async searchDirectoryInt(filters) {
39126
- return await this.client.get("/directory/international", {
39127
- value: filters.value,
39128
- offset: filters.offset,
39129
- limit: filters.limit
39130
- });
39131
- }
39132
- async checkPeppolParticipant(scheme, value) {
39133
- return await this.client.get(
39134
- `/directory/international/check/scheme/${scheme}/value/${value}`
39135
- );
41287
+ const invoice = normalizeForSuperPDP(req.invoice);
41288
+ const payload = new TextEncoder().encode(JSON.stringify(invoice));
41289
+ return await this.client.convert(payload, "en16931", "ubl");
39136
41290
  }
39137
- // ─── Status ───────────────────────────────────────────
41291
+ // ─── Status / Events (native) ─────────────────────────
39138
41292
  async sendStatus(req) {
39139
- return await this.client.post(`/invoice/${req.invoiceId}/status`, {
39140
- code: req.code,
39141
- message: req.message,
39142
- payment: req.payment
41293
+ const details = [];
41294
+ if (req.message) {
41295
+ details.push({ reason: req.message });
41296
+ }
41297
+ if (req.payment) {
41298
+ const amounts = Array.isArray(req.payment.amounts) ? req.payment.amounts : [req.payment];
41299
+ details.push({ amounts });
41300
+ }
41301
+ return await this.client.post("/invoice_events", {
41302
+ invoice_id: toInvoiceId(req.invoiceId),
41303
+ status_code: req.code,
41304
+ ...details.length > 0 ? { details } : {}
39143
41305
  });
39144
41306
  }
39145
41307
  async getStatusHistory(invoiceId) {
39146
- return await this.client.get(`/invoice/${invoiceId}/status-history`);
39147
- }
39148
- async getUnseenStatuses(pagination) {
39149
- return await this.client.get("/invoice/status/notSeen", {
39150
- offset: pagination.offset,
39151
- limit: pagination.limit
41308
+ const raw2 = await this.client.get("/invoice_events", {
41309
+ invoice_id: invoiceId
39152
41310
  });
41311
+ const data = Array.isArray(raw2) ? raw2 : raw2?.data ?? [];
41312
+ return {
41313
+ // deno-lint-ignore no-explicit-any
41314
+ entries: data.map((e) => ({
41315
+ date: e.created_at ?? e.date ?? "",
41316
+ code: e.status_code ?? e.code ?? "",
41317
+ message: e.message
41318
+ }))
41319
+ };
41320
+ }
41321
+ // ─── Reporting (AFNOR — inherited) ────────────────────
41322
+ // reportInvoiceTransaction → AfnorBaseAdapter.submitFlow (FRR)
41323
+ // reportTransaction → AfnorBaseAdapter.submitFlow (FRR)
41324
+ // ─── Directory (native) ───────────────────────────────
41325
+ async searchDirectoryFr(_filters) {
41326
+ const raw2 = await this.client.get("/directory_entries");
41327
+ const data = Array.isArray(raw2) ? raw2 : raw2?.data ?? [];
41328
+ const rows = data.map((entry) => ({
41329
+ entityId: String(entry.id ?? ""),
41330
+ name: entry.company?.formal_name ?? entry.company?.trade_name ?? entry.name,
41331
+ siret: entry.identifier,
41332
+ country: entry.company?.country ?? "FR",
41333
+ directory: entry.directory,
41334
+ status: entry.status,
41335
+ createdAt: entry.created_at
41336
+ }));
41337
+ return { rows, count: rows.length };
39153
41338
  }
39154
- async markStatusSeen(statusId) {
39155
- return await this.client.put(`/invoice/status/${statusId}/markAsSeen`);
39156
- }
39157
- // ─── Reporting ────────────────────────────────────────
39158
- async reportInvoiceTransaction(transaction) {
39159
- return await this.client.post("/reporting/fr/invoice/transaction", transaction);
39160
- }
39161
- async reportTransaction(businessEntityId, transaction) {
39162
- return await this.client.post(`/reporting/fr/transaction/${businessEntityId}`, transaction);
41339
+ // ─── Operator Config (native) ─────────────────────────
41340
+ async getCustomerId() {
41341
+ return await this.client.get("/companies/me");
39163
41342
  }
39164
- // ─── Webhooks ─────────────────────────────────────────
39165
- async listWebhooks() {
39166
- return await this.client.get("/config/webhook");
41343
+ async getBusinessEntity(_id) {
41344
+ return await this.client.get("/companies/me");
39167
41345
  }
39168
- async getWebhook(id) {
39169
- return await this.client.get(`/config/webhook/${id}`);
41346
+ async createOffice(data) {
41347
+ return await this.client.post("/directory_entries", data);
39170
41348
  }
39171
- async createWebhook(req) {
39172
- return await this.client.post("/config/webhook", req);
41349
+ async enrollFrench(data) {
41350
+ return await this.client.post("/directory_entries", data);
39173
41351
  }
39174
- async updateWebhook(id, req) {
39175
- return await this.client.put(`/config/webhook/${id}`, req);
41352
+ async registerNetwork(identifierId, network) {
41353
+ return await this.client.post("/directory_entries", {
41354
+ directory: mapNetworkToDirectory(network),
41355
+ identifier: identifierId
41356
+ });
39176
41357
  }
39177
- async deleteWebhook(id) {
39178
- return await this.client.delete(`/config/webhook/${id}`);
41358
+ async registerNetworkByScheme(scheme, value, network) {
41359
+ return await this.client.post("/directory_entries", {
41360
+ directory: mapNetworkToDirectory(network),
41361
+ identifier: `${scheme}:${value}`
41362
+ });
39179
41363
  }
39180
- };
39181
- function createIopoleAdapter() {
39182
- const baseUrl = env2("IOPOLE_API_URL");
39183
- const clientId = env2("IOPOLE_CLIENT_ID");
39184
- const clientSecret = env2("IOPOLE_CLIENT_SECRET");
39185
- const customerId = env2("IOPOLE_CUSTOMER_ID");
39186
- const authUrl = env2("IOPOLE_AUTH_URL") || IOPOLE_DEFAULT_AUTH_URL;
39187
- if (!baseUrl) {
39188
- throw new Error(
39189
- "[IopoleAdapter] IOPOLE_API_URL is required. Set it to https://api.ppd.iopole.fr/v1 (sandbox) or https://api.iopole.com/v1 (production)."
39190
- );
41364
+ async unregisterNetwork(directoryId) {
41365
+ return await this.client.delete(`/directory_entries/${encodePathSegment(directoryId)}`);
39191
41366
  }
39192
- if (!clientId) {
39193
- throw new Error(
39194
- "[IopoleAdapter] IOPOLE_CLIENT_ID is required. Get your client ID from the Iopole dashboard or admin console."
39195
- );
41367
+ // ─── Identifier Management (native via directory) ─────
41368
+ async createIdentifier(_entityId, data) {
41369
+ return await this.client.post("/directory_entries", {
41370
+ directory: data.directory ?? "ppf",
41371
+ identifier: data.identifier
41372
+ });
39196
41373
  }
39197
- if (!clientSecret) {
39198
- throw new Error(
39199
- "[IopoleAdapter] IOPOLE_CLIENT_SECRET is required. Get your client secret from the Iopole dashboard or admin console."
39200
- );
41374
+ async createIdentifierByScheme(scheme, value, data) {
41375
+ return await this.client.post("/directory_entries", {
41376
+ directory: data.directory ?? "ppf",
41377
+ identifier: `${scheme}:${value}`
41378
+ });
39201
41379
  }
39202
- if (!customerId) {
39203
- throw new Error(
39204
- "[IopoleAdapter] IOPOLE_CUSTOMER_ID is required (since 2026-02-01). Find it in Settings \u2192 Unique Identifier (sandbox) or admin console."
39205
- );
41380
+ async deleteIdentifier(identifierId) {
41381
+ return await this.client.delete(`/directory_entries/${encodePathSegment(identifierId)}`);
41382
+ }
41383
+ // ─── Stubs (21 methods — inherited from AfnorBaseAdapter)
41384
+ // downloadReadable, getInvoiceFiles, getAttachments, downloadFile,
41385
+ // markInvoiceSeen, getUnseenInvoices, getUnseenStatuses, markStatusSeen,
41386
+ // listWebhooks, getWebhook, createWebhook, updateWebhook, deleteWebhook,
41387
+ // listBusinessEntities, createLegalUnit, deleteBusinessEntity,
41388
+ // configureBusinessEntity, claimBusinessEntity, claimBusinessEntityByIdentifier,
41389
+ // enrollInternational, searchDirectoryInt, checkPeppolParticipant, deleteClaim
41390
+ };
41391
+ function lastEventCode(events) {
41392
+ if (!events?.length) return void 0;
41393
+ return events[events.length - 1].status_code;
41394
+ }
41395
+ function mapNetworkToDirectory(network) {
41396
+ if (network === "DOMESTIC_FR" || network === "ppf") return "ppf";
41397
+ if (network === "PEPPOL_INTERNATIONAL" || network === "peppol") return "peppol";
41398
+ throw new Error(
41399
+ `[SuperPDP] Unknown network "${network}". Supported: "DOMESTIC_FR"/"ppf", "PEPPOL_INTERNATIONAL"/"peppol".`
41400
+ );
41401
+ }
41402
+ function toInvoiceId(id) {
41403
+ const n = Number(id);
41404
+ if (!Number.isFinite(n)) {
41405
+ throw new Error(`[SuperPDP] invoice_id must be numeric, got "${id}".`);
39206
41406
  }
41407
+ return n;
41408
+ }
41409
+ function createSuperPDPAdapter() {
41410
+ const baseUrl = requireEnv(
41411
+ "SuperPDPAdapter",
41412
+ "SUPERPDP_API_URL",
41413
+ "Set it to https://api.superpdp.tech/v1.beta"
41414
+ );
41415
+ const clientId = requireEnv(
41416
+ "SuperPDPAdapter",
41417
+ "SUPERPDP_CLIENT_ID",
41418
+ "Get your client ID from the Super PDP dashboard."
41419
+ );
41420
+ const clientSecret = requireEnv(
41421
+ "SuperPDPAdapter",
41422
+ "SUPERPDP_CLIENT_SECRET",
41423
+ "Get your client secret from the Super PDP dashboard."
41424
+ );
41425
+ const authUrl = env2("SUPERPDP_AUTH_URL") || "https://api.superpdp.tech/oauth2/token";
41426
+ const afnorUrl = env2("SUPERPDP_AFNOR_URL") || "https://api.superpdp.tech/afnor-flow";
39207
41427
  const getToken = createOAuth2TokenProvider({ authUrl, clientId, clientSecret });
39208
- const client = new IopoleClient({ baseUrl, customerId, getToken });
39209
- return new IopoleAdapter(client);
41428
+ const client = new SuperPDPClient({ baseUrl, getToken });
41429
+ const afnor = new AfnorClient({ baseUrl: afnorUrl, getToken });
41430
+ return new SuperPDPAdapter(client, afnor);
39210
41431
  }
39211
41432
 
39212
41433
  // server.ts
@@ -39216,14 +41437,22 @@ function createAdapter(adapterName) {
39216
41437
  switch (adapterName) {
39217
41438
  case "iopole":
39218
41439
  return createIopoleAdapter();
41440
+ case "storecove":
41441
+ return createStorecoveAdapter();
41442
+ case "superpdp":
41443
+ return createSuperPDPAdapter();
39219
41444
  default:
39220
41445
  throw new Error(
39221
- `${LOG_PREFIX} Unknown adapter: "${adapterName}". Available adapters: iopole`
41446
+ `${LOG_PREFIX} Unknown adapter: "${adapterName}". Available adapters: iopole, storecove, superpdp`
39222
41447
  );
39223
41448
  }
39224
41449
  }
39225
41450
  async function main() {
39226
41451
  const args = getArgs();
41452
+ if (args.includes("--inspect")) {
41453
+ await launchInspector("deno", ["run", "--allow-all", import.meta.filename]);
41454
+ return;
41455
+ }
39227
41456
  const adapterArg = args.find((arg) => arg.startsWith("--adapter="));
39228
41457
  const adapterName = adapterArg ? adapterArg.split("=")[1] : env2("EINVOICE_ADAPTER") || "iopole";
39229
41458
  const categoriesArg = args.find((arg) => arg.startsWith("--categories="));
@@ -39232,7 +41461,7 @@ async function main() {
39232
41461
  const portArg = args.find((arg) => arg.startsWith("--port="));
39233
41462
  const httpPort = portArg ? parseInt(portArg.split("=")[1], 10) : DEFAULT_HTTP_PORT;
39234
41463
  const hostnameArg = args.find((arg) => arg.startsWith("--hostname="));
39235
- const hostname3 = hostnameArg ? hostnameArg.split("=")[1] : "0.0.0.0";
41464
+ const hostname3 = hostnameArg ? hostnameArg.split("=")[1] : "localhost";
39236
41465
  const adapter = createAdapter(adapterName);
39237
41466
  const toolsClient = new EInvoiceToolsClient(
39238
41467
  categories ? { categories } : void 0
@@ -39243,15 +41472,16 @@ async function main() {
39243
41472
  maxConcurrent: 10,
39244
41473
  backpressureStrategy: "queue",
39245
41474
  validateSchema: true,
41475
+ toolErrorMapper: einvoiceErrorMapper,
39246
41476
  logger: (msg) => console.error(`${LOG_PREFIX} ${msg}`)
39247
41477
  });
39248
- const mcpTools = toolsClient.toMCPFormat();
41478
+ const mcpTools = toolsClient.toMCPFormat(adapter);
39249
41479
  const handlers = toolsClient.buildHandlersMap(adapter);
39250
41480
  server.registerTools(mcpTools, handlers);
39251
41481
  server.registerViewers({
39252
41482
  prefix: "mcp-einvoice",
39253
41483
  moduleUrl: import.meta.url,
39254
- viewers: ["invoice-viewer", "doclist-viewer", "status-timeline", "directory-card"],
41484
+ viewers: ["invoice-viewer", "doclist-viewer", "status-timeline", "directory-card", "directory-list", "action-result"],
39255
41485
  exists: statSync,
39256
41486
  readFile: readTextFile2
39257
41487
  });