@casys/mcp-einvoice 0.1.0 → 0.1.1

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.
Files changed (2) hide show
  1. package/mcp-einvoice.mjs +120 -33
  2. package/package.json +2 -2
package/mcp-einvoice.mjs CHANGED
@@ -35907,6 +35907,37 @@ async function loadYamlAuth(path) {
35907
35907
  var MCP_APP_MIME_TYPE = "text/html;profile=mcp-app";
35908
35908
  var MCP_APP_URI_SCHEME = "ui:";
35909
35909
 
35910
+ // node_modules/@casys/mcp-server/src/ui/viewer-utils.ts
35911
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["shared", "dist", "node_modules", ".cache", ".vite"]);
35912
+ function fileUrlToPath(url2) {
35913
+ const decoded = decodeURIComponent(url2.pathname);
35914
+ if (/^\/[A-Za-z]:\//.test(decoded)) return decoded.slice(1);
35915
+ if (url2.host.length > 0) return `//${url2.host}${decoded}`;
35916
+ return decoded;
35917
+ }
35918
+ function resolveViewerDistPath(moduleUrl, viewerName, exists) {
35919
+ const candidates = [
35920
+ fileUrlToPath(new URL(`./src/ui/dist/${viewerName}/index.html`, moduleUrl)),
35921
+ fileUrlToPath(new URL(`./ui-dist/${viewerName}/index.html`, moduleUrl))
35922
+ ];
35923
+ for (const candidate of candidates) {
35924
+ if (exists(candidate)) return candidate;
35925
+ }
35926
+ return null;
35927
+ }
35928
+ function discoverViewers(uiDir, fs) {
35929
+ const entries = fs.readDir(uiDir);
35930
+ const viewers = [];
35931
+ for (const entry of entries) {
35932
+ if (!entry.isDirectory) continue;
35933
+ if (entry.name.startsWith(".")) continue;
35934
+ if (SKIP_DIRS.has(entry.name)) continue;
35935
+ if (!fs.hasIndexHtml(uiDir, entry.name)) continue;
35936
+ viewers.push(entry.name);
35937
+ }
35938
+ return viewers.sort();
35939
+ }
35940
+
35910
35941
  // node_modules/@casys/mcp-server/src/security/csp.ts
35911
35942
  function buildCspHeader(options = {}) {
35912
35943
  const allowInline = options.allowInline !== false;
@@ -36748,6 +36779,63 @@ var ConcurrentMCPServer = class _ConcurrentMCPServer {
36748
36779
  }
36749
36780
  this.log(`Registered ${resources.length} resources`);
36750
36781
  }
36782
+ /**
36783
+ * Register MCP Apps viewers with automatic dist path resolution.
36784
+ *
36785
+ * Replaces the manual pattern of: enumerate viewers → resolve paths → register resources.
36786
+ * Each viewer gets a `ui://{prefix}/{viewerName}` resource URI.
36787
+ *
36788
+ * Viewers whose dist is not found are skipped with a warning (not an error),
36789
+ * so that the server can start in dev without building UIs first.
36790
+ *
36791
+ * @returns Summary of registered and skipped viewers
36792
+ */
36793
+ registerViewers(config2) {
36794
+ if (!config2.prefix) {
36795
+ throw new Error("[ConcurrentMCPServer] registerViewers: prefix is required");
36796
+ }
36797
+ let viewerNames;
36798
+ if (config2.viewers) {
36799
+ viewerNames = config2.viewers;
36800
+ } else if (config2.discover) {
36801
+ viewerNames = discoverViewers(config2.discover.uiDir, config2.discover);
36802
+ } else {
36803
+ viewerNames = [];
36804
+ }
36805
+ const humanNameFn = config2.humanName ?? defaultHumanName;
36806
+ const registered = [];
36807
+ const skipped = [];
36808
+ for (const viewerName of viewerNames) {
36809
+ const distPath = resolveViewerDistPath(config2.moduleUrl, viewerName, config2.exists);
36810
+ if (!distPath) {
36811
+ this.log(
36812
+ `Warning: UI not built for ui://${config2.prefix}/${viewerName}. Run the UI build step first.`
36813
+ );
36814
+ skipped.push(viewerName);
36815
+ continue;
36816
+ }
36817
+ const resourceUri = `ui://${config2.prefix}/${viewerName}`;
36818
+ const readFile3 = config2.readFile;
36819
+ const currentDistPath = distPath;
36820
+ this.registerResource(
36821
+ {
36822
+ uri: resourceUri,
36823
+ name: humanNameFn(viewerName),
36824
+ description: `MCP App: ${viewerName}`,
36825
+ mimeType: MCP_APP_MIME_TYPE
36826
+ },
36827
+ async () => {
36828
+ const html = await Promise.resolve(readFile3(currentDistPath));
36829
+ return { uri: resourceUri, mimeType: MCP_APP_MIME_TYPE, text: html };
36830
+ }
36831
+ );
36832
+ registered.push(viewerName);
36833
+ }
36834
+ if (registered.length > 0) {
36835
+ this.log(`Registered ${registered.length} viewer(s): ${registered.join(", ")}`);
36836
+ }
36837
+ return { registered, skipped };
36838
+ }
36751
36839
  /**
36752
36840
  * Start the MCP server with stdio transport
36753
36841
  */
@@ -37609,6 +37697,9 @@ data: ${JSON.stringify(message2)}
37609
37697
  }
37610
37698
  }
37611
37699
  };
37700
+ function defaultHumanName(name) {
37701
+ return name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
37702
+ }
37612
37703
 
37613
37704
  // src/generated-store.ts
37614
37705
  var EXPIRY_MS = 10 * 60 * 1e3;
@@ -37634,6 +37725,13 @@ function getGenerated(id) {
37634
37725
  }
37635
37726
 
37636
37727
  // src/tools/invoice.ts
37728
+ function uint8ToBase64(data) {
37729
+ let binary = "";
37730
+ for (let i = 0; i < data.length; i += 8192) {
37731
+ binary += String.fromCharCode(...data.subarray(i, i + 8192));
37732
+ }
37733
+ return btoa(binary);
37734
+ }
37637
37735
  function normalizeInvoiceForGenerate(inv) {
37638
37736
  const normalized = { ...inv };
37639
37737
  for (const party of ["seller", "buyer"]) {
@@ -37694,6 +37792,7 @@ function mapToViewerPreview(inv) {
37694
37792
  direction: "sent"
37695
37793
  };
37696
37794
  }
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: "..." }]';
37697
37796
  var invoiceTools = [
37698
37797
  // ── Emit ────────────────────────────────────────────────
37699
37798
  {
@@ -37737,7 +37836,12 @@ var invoiceTools = [
37737
37836
  if (!lower.endsWith(".pdf") && !lower.endsWith(".xml")) {
37738
37837
  throw new Error("[einvoice_invoice_emit] filename must end in .pdf or .xml");
37739
37838
  }
37740
- const binaryString = atob(input.file_base64);
37839
+ let binaryString;
37840
+ try {
37841
+ binaryString = atob(input.file_base64);
37842
+ } catch {
37843
+ throw new Error("[einvoice_invoice_emit] 'file_base64' is not valid base64");
37844
+ }
37741
37845
  const bytes = new Uint8Array(binaryString.length);
37742
37846
  for (let i = 0; i < binaryString.length; i++) {
37743
37847
  bytes[i] = binaryString.charCodeAt(i);
@@ -37905,12 +38009,7 @@ var invoiceTools = [
37905
38009
  throw new Error("[einvoice_invoice_download] 'id' is required");
37906
38010
  }
37907
38011
  const { data, contentType } = await ctx.adapter.downloadInvoice(input.id);
37908
- let binary = "";
37909
- for (let i = 0; i < data.length; i += 8192) {
37910
- binary += String.fromCharCode(...data.subarray(i, i + 8192));
37911
- }
37912
- const base643 = btoa(binary);
37913
- return { content_type: contentType, data_base64: base643, size_bytes: data.length };
38012
+ return { content_type: contentType, data_base64: uint8ToBase64(data), size_bytes: data.length };
37914
38013
  }
37915
38014
  },
37916
38015
  // ── Download readable ───────────────────────────────────
@@ -37930,12 +38029,7 @@ var invoiceTools = [
37930
38029
  throw new Error("[einvoice_invoice_download_readable] 'id' is required");
37931
38030
  }
37932
38031
  const { data, contentType } = await ctx.adapter.downloadReadable(input.id);
37933
- let binary = "";
37934
- for (let i = 0; i < data.length; i += 8192) {
37935
- binary += String.fromCharCode(...data.subarray(i, i + 8192));
37936
- }
37937
- const base643 = btoa(binary);
37938
- return { content_type: contentType, data_base64: base643, size_bytes: data.length };
38032
+ return { content_type: contentType, data_base64: uint8ToBase64(data), size_bytes: data.length };
37939
38033
  }
37940
38034
  },
37941
38035
  // ── Invoice Files ─────────────────────────────────────────
@@ -37993,12 +38087,7 @@ var invoiceTools = [
37993
38087
  throw new Error("[einvoice_invoice_download_file] 'file_id' is required");
37994
38088
  }
37995
38089
  const { data, contentType } = await ctx.adapter.downloadFile(input.file_id);
37996
- let binary = "";
37997
- for (let i = 0; i < data.length; i += 8192) {
37998
- binary += String.fromCharCode(...data.subarray(i, i + 8192));
37999
- }
38000
- const base643 = btoa(binary);
38001
- return { content_type: contentType, data_base64: base643, size_bytes: data.length };
38090
+ return { content_type: contentType, data_base64: uint8ToBase64(data), size_bytes: data.length };
38002
38091
  }
38003
38092
  },
38004
38093
  // ── Mark as seen ────────────────────────────────────────
@@ -38072,7 +38161,7 @@ var invoiceTools = [
38072
38161
  properties: {
38073
38162
  invoice: {
38074
38163
  type: "object",
38075
- 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: "..." }]'
38164
+ description: INVOICE_SCHEMA_DESCRIPTION
38076
38165
  },
38077
38166
  flavor: {
38078
38167
  type: "string",
@@ -38112,7 +38201,7 @@ var invoiceTools = [
38112
38201
  properties: {
38113
38202
  invoice: {
38114
38203
  type: "object",
38115
- 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: "..." }]'
38204
+ description: INVOICE_SCHEMA_DESCRIPTION
38116
38205
  },
38117
38206
  flavor: {
38118
38207
  type: "string",
@@ -38152,7 +38241,7 @@ var invoiceTools = [
38152
38241
  properties: {
38153
38242
  invoice: {
38154
38243
  type: "object",
38155
- 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: "..." }]'
38244
+ description: INVOICE_SCHEMA_DESCRIPTION
38156
38245
  },
38157
38246
  flavor: {
38158
38247
  type: "string",
@@ -38774,7 +38863,10 @@ var IopoleClient = class {
38774
38863
  }
38775
38864
  // ─── Generic Request ────────────────────────────────────
38776
38865
  async request(method, path, options) {
38777
- const url2 = new URL(`${this.config.baseUrl}${path}`);
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}`);
38778
38870
  if (options?.query) {
38779
38871
  for (const [key, value] of Object.entries(options.query)) {
38780
38872
  if (value !== void 0) {
@@ -38827,17 +38919,12 @@ var IopoleClient = class {
38827
38919
  }
38828
38920
  /**
38829
38921
  * GET request against the v1.1 API endpoint.
38830
- * Replaces /v1 with /v1.1 in the base URL for endpoints that require it
38831
- * (e.g. /v1.1/invoice/search). Safer than path traversal tricks.
38922
+ * Builds a v1.1 URL without mutating this.config.baseUrl,
38923
+ * so concurrent calls (e.g. getV11 + get) cannot interfere.
38832
38924
  */
38833
38925
  async getV11(path, query) {
38834
- const originalBase = this.config.baseUrl;
38835
- try {
38836
- this.config.baseUrl = originalBase.replace(/\/v1\b/, "/v1.1");
38837
- return await this.request("GET", path, { query });
38838
- } finally {
38839
- this.config.baseUrl = originalBase;
38840
- }
38926
+ const baseV11 = this.config.baseUrl.replace(/\/v1\b/, "/v1.1");
38927
+ return this.requestWithBase(baseV11, "GET", path, { query });
38841
38928
  }
38842
38929
  async post(path, body) {
38843
38930
  return this.request("POST", path, { body });
@@ -39152,7 +39239,7 @@ async function main() {
39152
39239
  );
39153
39240
  const server = new ConcurrentMCPServer({
39154
39241
  name: "mcp-einvoice",
39155
- version: "0.1.0",
39242
+ version: "0.1.1",
39156
39243
  maxConcurrent: 10,
39157
39244
  backpressureStrategy: "queue",
39158
39245
  validateSchema: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@casys/mcp-einvoice",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "PA-agnostic MCP server for French e-invoicing (Iopole, Chorus Pro...)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "repository": {
30
30
  "type": "git",
31
- "url": "https://github.com/Casys-AI/casys-pml-cloud"
31
+ "url": "https://github.com/Casys-AI/mcp-einvoice"
32
32
  },
33
33
  "license": "MIT"
34
34
  }