@mgsoftwarebv/mcp-server-bridge 3.5.1 → 3.5.3

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/dist/index.js CHANGED
@@ -97301,6 +97301,21 @@ var invoiceProducts = pgTable(
97301
97301
  // Array of ProductOptionGroup (see @refront/invoice/types). Null/empty for
97302
97302
  // plain catalog products.
97303
97303
  options: jsonb().$type(),
97304
+ // Structured catalog metadata for richer package/quote composition. All
97305
+ // nullable/defaulted so existing products keep working. Validated at the
97306
+ // app/MCP layer (see @refront/invoice/types constants).
97307
+ // "one_time" | "monthly" | "yearly"
97308
+ billingType: text("billing_type"),
97309
+ // "website" | "addon" | "hosting" | "support" | "other"
97310
+ category: text(),
97311
+ // List of included parts shown to the customer (e.g. "Basis SEO").
97312
+ includedItems: jsonb("included_items").$type(),
97313
+ // Whether this product is an optional add-on rather than a base package.
97314
+ optional: boolean3().default(false).notNull(),
97315
+ // Optional preset/tier label, e.g. start/growth/pro or bronze/silver/gold.
97316
+ tier: text(),
97317
+ // Manual ordering hint for catalog presentation (lower shows first).
97318
+ sortOrder: integer2("sort_order").default(0).notNull(),
97304
97319
  isActive: boolean3().default(true).notNull(),
97305
97320
  usageCount: integer2("usage_count").default(0).notNull(),
97306
97321
  lastUsedAt: timestamp("last_used_at", {
@@ -106480,7 +106495,7 @@ var TOOLS = [
106480
106495
  },
106481
106496
  {
106482
106497
  name: "get-products",
106483
- description: "List catalog products used on invoices AND quotes (the shared `invoice_products` catalog). Each entry includes its ID (UUID), name, unit price, currency, unit, active/archived flag, configurable flag, and usage stats. Editing or archiving a catalog product never changes existing invoices/quotes \u2014 those keep an immutable line-item snapshot; catalog changes only affect documents created afterwards.",
106498
+ description: "List catalog products used on invoices AND quotes (the shared `invoice_products` catalog). Each entry includes its ID (UUID), name, unit price, currency, unit, active/archived flag, configurable flag, usage stats, and structured package metadata (category, billingType, tier, optional add-on flag and includedItems). Editing or archiving a catalog product never changes existing invoices/quotes \u2014 those keep an immutable line-item snapshot; catalog changes only affect documents created afterwards.",
106484
106499
  inputSchema: {
106485
106500
  type: "object",
106486
106501
  properties: {
@@ -106506,7 +106521,7 @@ var TOOLS = [
106506
106521
  },
106507
106522
  {
106508
106523
  name: "get-product-by-id",
106509
- description: "Get a single catalog product by its ID (UUID), including name, unit price, currency, unit, active/archived flag, configurable flag, and usage stats.",
106524
+ description: "Get a single catalog product by its ID (UUID), including name, unit price, currency, unit, active/archived flag, configurable flag, usage stats, and structured package metadata (category, billingType, tier, optional add-on flag and includedItems).",
106510
106525
  inputSchema: {
106511
106526
  type: "object",
106512
106527
  properties: {
@@ -106518,7 +106533,7 @@ var TOOLS = [
106518
106533
  },
106519
106534
  {
106520
106535
  name: "create-product",
106521
- description: "Create a catalog product for use on invoices and quotes. Stored in the shared `invoice_products` catalog. This only adds a reusable catalog entry; it does not place the product on any document. Returns the created product with its ID and normalized fields.",
106536
+ description: "Create a catalog product for use on invoices and quotes. Stored in the shared `invoice_products` catalog. This only adds a reusable catalog entry; it does not place the product on any document. Supports structured package metadata (billingType, category, includedItems, optional add-on flag, tier, sortOrder) so website packages, add-ons, hosting and support subscriptions can be modelled richly. Returns the created product with its ID and normalized fields.",
106522
106537
  inputSchema: {
106523
106538
  type: "object",
106524
106539
  properties: {
@@ -106533,6 +106548,34 @@ var TOOLS = [
106533
106548
  unit: {
106534
106549
  type: "string",
106535
106550
  description: "Unit label (e.g. hour, piece, month)"
106551
+ },
106552
+ billingType: {
106553
+ type: "string",
106554
+ enum: ["one_time", "monthly", "yearly"],
106555
+ description: "Billing cadence for this product"
106556
+ },
106557
+ category: {
106558
+ type: "string",
106559
+ enum: ["website", "addon", "hosting", "support", "other"],
106560
+ description: "Product category for catalog filtering"
106561
+ },
106562
+ includedItems: {
106563
+ type: "array",
106564
+ items: { type: "string" },
106565
+ description: "Parts included in the package (e.g. ['Basis SEO', 'Contactformulier'])"
106566
+ },
106567
+ optional: {
106568
+ type: "boolean",
106569
+ description: "Optional add-on (true) vs base package (false)"
106570
+ },
106571
+ tier: {
106572
+ type: "string",
106573
+ enum: ["start", "growth", "pro", "bronze", "silver", "gold"],
106574
+ description: "Optional preset/tier label"
106575
+ },
106576
+ sortOrder: {
106577
+ type: "number",
106578
+ description: "Manual ordering hint (lower shows first)"
106536
106579
  }
106537
106580
  },
106538
106581
  required: ["name"]
@@ -106540,7 +106583,7 @@ var TOOLS = [
106540
106583
  },
106541
106584
  {
106542
106585
  name: "update-product",
106543
- description: "Update a catalog product's editable fields (name, description, price, currency, unit) or reactivate it (isActive: true). Only provided fields change. IMPORTANT: updates apply only to FUTURE invoices/quotes. Existing/sent/accepted/paid documents keep their immutable line-item snapshot and are never mutated. Find the product id via get-products.",
106586
+ description: "Update a catalog product's editable fields (name, description, price, currency, unit), its package metadata (billingType, category, includedItems, optional, tier, sortOrder), or reactivate it (isActive: true). Only provided fields change. IMPORTANT: updates apply only to FUTURE invoices/quotes. Existing/sent/accepted/paid documents keep their immutable line-item snapshot and are never mutated. Find the product id via get-products.",
106544
106587
  inputSchema: {
106545
106588
  type: "object",
106546
106589
  properties: {
@@ -106557,6 +106600,34 @@ var TOOLS = [
106557
106600
  isActive: {
106558
106601
  type: "boolean",
106559
106602
  description: "Set true to reactivate an archived product"
106603
+ },
106604
+ billingType: {
106605
+ type: ["string", "null"],
106606
+ enum: ["one_time", "monthly", "yearly", null],
106607
+ description: "Billing cadence; null clears it"
106608
+ },
106609
+ category: {
106610
+ type: ["string", "null"],
106611
+ enum: ["website", "addon", "hosting", "support", "other", null],
106612
+ description: "Product category; null clears it"
106613
+ },
106614
+ includedItems: {
106615
+ type: ["array", "null"],
106616
+ items: { type: "string" },
106617
+ description: "Parts included in the package; null/[] clears it"
106618
+ },
106619
+ optional: {
106620
+ type: "boolean",
106621
+ description: "Optional add-on (true) vs base package (false)"
106622
+ },
106623
+ tier: {
106624
+ type: ["string", "null"],
106625
+ enum: ["start", "growth", "pro", "bronze", "silver", "gold", null],
106626
+ description: "Preset/tier label; null clears it"
106627
+ },
106628
+ sortOrder: {
106629
+ type: "number",
106630
+ description: "Manual ordering hint (lower shows first)"
106560
106631
  }
106561
106632
  },
106562
106633
  required: ["productId"]
@@ -114088,6 +114159,22 @@ The project had no tickets, hours, trips, or templates. Any project-scoped confi
114088
114159
 
114089
114160
  // src/tools/products.ts
114090
114161
  var PRODUCT_STATUSES = ["active", "archived", "all"];
114162
+ var BILLING_TYPES = ["one_time", "monthly", "yearly"];
114163
+ var CATEGORIES = [
114164
+ "website",
114165
+ "addon",
114166
+ "hosting",
114167
+ "support",
114168
+ "other"
114169
+ ];
114170
+ var TIERS = [
114171
+ "start",
114172
+ "growth",
114173
+ "pro",
114174
+ "bronze",
114175
+ "silver",
114176
+ "gold"
114177
+ ];
114091
114178
  var PRODUCT_COLUMNS = {
114092
114179
  id: schema_exports.invoiceProducts.id,
114093
114180
  teamId: schema_exports.invoiceProducts.teamId,
@@ -114097,6 +114184,12 @@ var PRODUCT_COLUMNS = {
114097
114184
  currency: schema_exports.invoiceProducts.currency,
114098
114185
  unit: schema_exports.invoiceProducts.unit,
114099
114186
  isConfigurable: schema_exports.invoiceProducts.isConfigurable,
114187
+ billingType: schema_exports.invoiceProducts.billingType,
114188
+ category: schema_exports.invoiceProducts.category,
114189
+ includedItems: schema_exports.invoiceProducts.includedItems,
114190
+ optional: schema_exports.invoiceProducts.optional,
114191
+ tier: schema_exports.invoiceProducts.tier,
114192
+ sortOrder: schema_exports.invoiceProducts.sortOrder,
114100
114193
  isActive: schema_exports.invoiceProducts.isActive,
114101
114194
  usageCount: schema_exports.invoiceProducts.usageCount,
114102
114195
  lastUsedAt: schema_exports.invoiceProducts.lastUsedAt,
@@ -114113,13 +114206,34 @@ function formatPrice(p3) {
114113
114206
  function formatProduct(p3) {
114114
114207
  const flags = [p3.isActive ? "active" : "archived"];
114115
114208
  if (p3.isConfigurable) flags.push("configurable");
114209
+ if (p3.optional) flags.push("add-on");
114210
+ const meta5 = [];
114211
+ if (p3.category) meta5.push(`category=${p3.category}`);
114212
+ if (p3.billingType) meta5.push(`billing=${p3.billingType}`);
114213
+ if (p3.tier) meta5.push(`tier=${p3.tier}`);
114214
+ const included = p3.includedItems && p3.includedItems.length > 0 ? `Included: ${p3.includedItems.join(", ")}
114215
+ ` : "";
114116
114216
  return `**${p3.name}** (${flags.join(", ")})
114117
114217
  ID: ${p3.id}
114118
114218
  Price: ${formatPrice(p3)}
114119
- ${p3.description ? `Description: ${p3.description}
114219
+ ${meta5.length > 0 ? `${meta5.join(", ")}
114220
+ ` : ""}` + included + `${p3.description ? `Description: ${p3.description}
114120
114221
  ` : ""}Used: ${p3.usageCount}x${p3.lastUsedAt ? ` (last ${new Date(p3.lastUsedAt).toLocaleDateString()})` : ""}
114121
114222
  `;
114122
114223
  }
114224
+ function validateEnum(label, value, allowed) {
114225
+ if (value === void 0 || value === null) return null;
114226
+ if (typeof value !== "string" || !allowed.includes(value)) {
114227
+ return `Error: invalid ${label} "${String(value)}". Allowed: ${allowed.join(", ")}.`;
114228
+ }
114229
+ return null;
114230
+ }
114231
+ function normalizeIncludedItems(value) {
114232
+ if (value === void 0) return void 0;
114233
+ if (value === null) return null;
114234
+ const cleaned = value.map((item) => typeof item === "string" ? item.trim() : "").filter((item) => item.length > 0);
114235
+ return cleaned.length > 0 ? cleaned : null;
114236
+ }
114123
114237
  async function handleGetProducts(input) {
114124
114238
  const { q: q3, currency, pageSize = 20 } = input;
114125
114239
  const status = input.status ?? "active";
@@ -114200,6 +114314,8 @@ async function handleCreateProduct(input) {
114200
114314
  if (!name21 || name21.trim().length === 0) {
114201
114315
  return textResponse3("Error: `name` is required.");
114202
114316
  }
114317
+ const enumError = validateEnum("billingType", input.billingType, BILLING_TYPES) ?? validateEnum("category", input.category, CATEGORIES) ?? validateEnum("tier", input.tier, TIERS);
114318
+ if (enumError) return textResponse3(enumError);
114203
114319
  const resolved = await resolveTeamId(input.teamId);
114204
114320
  if (!resolved.ok) return resolved.response;
114205
114321
  const [created] = await db.insert(schema_exports.invoiceProducts).values({
@@ -114209,6 +114325,12 @@ async function handleCreateProduct(input) {
114209
114325
  price: price ?? null,
114210
114326
  currency: currency ?? null,
114211
114327
  unit: unit ?? null,
114328
+ billingType: input.billingType ?? null,
114329
+ category: input.category ?? null,
114330
+ includedItems: normalizeIncludedItems(input.includedItems) ?? null,
114331
+ optional: input.optional ?? false,
114332
+ tier: input.tier ?? null,
114333
+ sortOrder: input.sortOrder ?? 0,
114212
114334
  isActive: true,
114213
114335
  lastUsedAt: (/* @__PURE__ */ new Date()).toISOString()
114214
114336
  }).returning(PRODUCT_COLUMNS);
@@ -114230,6 +114352,8 @@ async function handleUpdateProduct(input) {
114230
114352
  `Product ${productId} not found, or it is not owned by this team.`
114231
114353
  );
114232
114354
  }
114355
+ const enumError = validateEnum("billingType", input.billingType, BILLING_TYPES) ?? validateEnum("category", input.category, CATEGORIES) ?? validateEnum("tier", input.tier, TIERS);
114356
+ if (enumError) return textResponse3(enumError);
114233
114357
  const updates = {};
114234
114358
  if (input.name !== void 0) {
114235
114359
  if (!input.name || input.name.trim().length === 0) {
@@ -114242,9 +114366,17 @@ async function handleUpdateProduct(input) {
114242
114366
  if (input.currency !== void 0) updates.currency = input.currency;
114243
114367
  if (input.unit !== void 0) updates.unit = input.unit;
114244
114368
  if (input.isActive !== void 0) updates.isActive = input.isActive;
114369
+ if (input.billingType !== void 0) updates.billingType = input.billingType;
114370
+ if (input.category !== void 0) updates.category = input.category;
114371
+ if (input.includedItems !== void 0) {
114372
+ updates.includedItems = normalizeIncludedItems(input.includedItems);
114373
+ }
114374
+ if (input.optional !== void 0) updates.optional = input.optional;
114375
+ if (input.tier !== void 0) updates.tier = input.tier;
114376
+ if (input.sortOrder !== void 0) updates.sortOrder = input.sortOrder;
114245
114377
  if (Object.keys(updates).length === 0) {
114246
114378
  return textResponse3(
114247
- "No fields to update. Provide at least one of: name, description, price, currency, unit, isActive."
114379
+ "No fields to update. Provide at least one of: name, description, price, currency, unit, isActive, billingType, category, includedItems, optional, tier, sortOrder."
114248
114380
  );
114249
114381
  }
114250
114382
  updates.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -114320,7 +114452,12 @@ function snapshotFromProduct(product, defaults, now2 = () => (/* @__PURE__ */ ne
114320
114452
  capturedAt: now2(),
114321
114453
  metadata: {
114322
114454
  isConfigurable: product.isConfigurable,
114323
- options: product.options ?? null
114455
+ options: product.options ?? null,
114456
+ billingType: product.billingType ?? null,
114457
+ category: product.category ?? null,
114458
+ includedItems: product.includedItems ?? null,
114459
+ optional: product.optional ?? false,
114460
+ tier: product.tier ?? null
114324
114461
  }
114325
114462
  };
114326
114463
  }
@@ -114444,7 +114581,12 @@ async function loadProductsInTeam(productIds, teamId) {
114444
114581
  currency: schema_exports.invoiceProducts.currency,
114445
114582
  unit: schema_exports.invoiceProducts.unit,
114446
114583
  isConfigurable: schema_exports.invoiceProducts.isConfigurable,
114447
- options: schema_exports.invoiceProducts.options
114584
+ options: schema_exports.invoiceProducts.options,
114585
+ billingType: schema_exports.invoiceProducts.billingType,
114586
+ category: schema_exports.invoiceProducts.category,
114587
+ includedItems: schema_exports.invoiceProducts.includedItems,
114588
+ optional: schema_exports.invoiceProducts.optional,
114589
+ tier: schema_exports.invoiceProducts.tier
114448
114590
  }).from(schema_exports.invoiceProducts).where(
114449
114591
  and(
114450
114592
  inArray(schema_exports.invoiceProducts.id, productIds),
@@ -114780,12 +114922,22 @@ async function handleAddProductToQuote(input) {
114780
114922
  lastUsedAt: (/* @__PURE__ */ new Date()).toISOString()
114781
114923
  }).where(eq(schema_exports.invoiceProducts.id, product.id));
114782
114924
  const snap = newItem.productSnapshot;
114925
+ const meta5 = snap.metadata;
114926
+ const metaParts = [];
114927
+ if (meta5.category) metaParts.push(`category=${meta5.category}`);
114928
+ if (meta5.billingType) metaParts.push(`billing=${meta5.billingType}`);
114929
+ if (meta5.tier) metaParts.push(`tier=${meta5.tier}`);
114930
+ if (meta5.optional) metaParts.push("optional add-on");
114931
+ if (meta5.includedItems && meta5.includedItems.length > 0) {
114932
+ metaParts.push(`included=[${meta5.includedItems.join(", ")}]`);
114933
+ }
114783
114934
  return textResponse4(
114784
114935
  `\u2705 **Product added to draft quote ${updated.quotationNumber ?? updated.id}**
114785
114936
 
114786
114937
  Line item: ${newItem.name} \xD7 ${newItem.quantity}${newItem.unit ? ` ${newItem.unit}` : ""} @ ${newItem.price} ${snap.currency}
114787
114938
  Snapshot: name="${snap.name}", unitPrice=${snap.unitPrice}, currency=${snap.currency}, vatRate=${snap.vatRate}%, unit=${snap.unit ?? "-"}
114788
-
114939
+ ${metaParts.length > 0 ? `Metadata: ${metaParts.join(", ")}
114940
+ ` : ""}
114789
114941
  New quote total: ${updated.amount} ${updated.currency} (subtotal ${updated.subtotal}, VAT ${updated.vat})
114790
114942
  The snapshot is immutable: later catalog edits won't change this quote.`
114791
114943
  );
@@ -121555,7 +121707,7 @@ function attemptedLockedFields(update) {
121555
121707
 
121556
121708
  // src/tools/trips.ts
121557
121709
  var TRIP_TYPES = ["private", "business"];
121558
- var BILLING_TYPES = TRIP_BILLING_TYPES;
121710
+ var BILLING_TYPES2 = TRIP_BILLING_TYPES;
121559
121711
  function textResponse7(text3) {
121560
121712
  return { content: [{ type: "text", text: text3 }] };
121561
121713
  }
@@ -121616,9 +121768,9 @@ async function handleGetTrips(input) {
121616
121768
  `Error: invalid tripType "${input.tripType}". Allowed: ${TRIP_TYPES.join(", ")}.`
121617
121769
  );
121618
121770
  }
121619
- if (input.billingType && !BILLING_TYPES.includes(input.billingType)) {
121771
+ if (input.billingType && !BILLING_TYPES2.includes(input.billingType)) {
121620
121772
  return textResponse7(
121621
- `Error: invalid billingType "${input.billingType}". Allowed: ${BILLING_TYPES.join(", ")}.`
121773
+ `Error: invalid billingType "${input.billingType}". Allowed: ${BILLING_TYPES2.join(", ")}.`
121622
121774
  );
121623
121775
  }
121624
121776
  const scope = await resolveTeamScope(input.teamId);
@@ -121738,9 +121890,9 @@ async function handleCreateTrip(input) {
121738
121890
  );
121739
121891
  }
121740
121892
  const billingType = input.billingType ?? "not_billable";
121741
- if (!BILLING_TYPES.includes(billingType)) {
121893
+ if (!BILLING_TYPES2.includes(billingType)) {
121742
121894
  return textResponse7(
121743
- `Error: invalid billingType "${input.billingType}". Allowed: ${BILLING_TYPES.join(", ")}.`
121895
+ `Error: invalid billingType "${input.billingType}". Allowed: ${BILLING_TYPES2.join(", ")}.`
121744
121896
  );
121745
121897
  }
121746
121898
  const resolved = await resolveTeamId(input.teamId);
@@ -121819,9 +121971,9 @@ async function handleUpdateTrip(input) {
121819
121971
  `Error: invalid tripType "${input.tripType}". Allowed: ${TRIP_TYPES.join(", ")}.`
121820
121972
  );
121821
121973
  }
121822
- if (input.billingType && !BILLING_TYPES.includes(input.billingType)) {
121974
+ if (input.billingType && !BILLING_TYPES2.includes(input.billingType)) {
121823
121975
  return textResponse7(
121824
- `Error: invalid billingType "${input.billingType}". Allowed: ${BILLING_TYPES.join(", ")}.`
121976
+ `Error: invalid billingType "${input.billingType}". Allowed: ${BILLING_TYPES2.join(", ")}.`
121825
121977
  );
121826
121978
  }
121827
121979
  const resolved = await resolveTeamId(input.teamId);
@@ -122215,13 +122367,13 @@ async function handleGetTicketById(input) {
122215
122367
  }).from(schema_exports.ticketAttachments).leftJoin(
122216
122368
  schema_exports.users,
122217
122369
  eq(schema_exports.users.id, schema_exports.ticketAttachments.userId)
122218
- ).where(eq(schema_exports.ticketAttachments.ticketId, id)).orderBy(asc(schema_exports.ticketAttachments.createdAt));
122370
+ ).where(eq(schema_exports.ticketAttachments.ticketId, resolved.id)).orderBy(asc(schema_exports.ticketAttachments.createdAt));
122219
122371
  const comments = await db.select({
122220
122372
  id: schema_exports.ticketComments.id,
122221
122373
  content: schema_exports.ticketComments.content,
122222
122374
  createdAt: schema_exports.ticketComments.createdAt,
122223
122375
  userId: schema_exports.ticketComments.userId
122224
- }).from(schema_exports.ticketComments).where(eq(schema_exports.ticketComments.ticketId, id)).orderBy(asc(schema_exports.ticketComments.createdAt));
122376
+ }).from(schema_exports.ticketComments).where(eq(schema_exports.ticketComments.ticketId, resolved.id)).orderBy(asc(schema_exports.ticketComments.createdAt));
122225
122377
  const commentUserIds = [
122226
122378
  ...new Set(
122227
122379
  comments.map((c6) => c6.userId).filter((v2) => Boolean(v2))
@@ -122284,8 +122436,8 @@ ${text3.split("\n").map((l4) => ` ${l4}`).join("\n")}`;
122284
122436
  ` : ticketRow.requester ? `Assignee: (unassigned) \u2014 use requester id ${ticketRow.requester.id} for review handoff
122285
122437
  ` : `Assignee: (unassigned)
122286
122438
  `;
122287
- const ticketTagRows = await getTagsForTickets([id]);
122288
- const ticketTags2 = ticketTagRows.get(id) ?? [];
122439
+ const ticketTagRows = await getTagsForTickets([resolved.id]);
122440
+ const ticketTags2 = ticketTagRows.get(resolved.id) ?? [];
122289
122441
  const tagsLine = ticketTags2.length > 0 ? `Tags: ${formatTagList(ticketTags2)}
122290
122442
  ` : `Tags: (none)
122291
122443
  `;
@@ -122472,7 +122624,7 @@ ${tagErrors.map((e6) => ` \u2022 ${e6}`).join("\n")}
122472
122624
  }
122473
122625
 
122474
122626
  // src/server.ts
122475
- var SERVER_VERSION = "3.5.0";
122627
+ var SERVER_VERSION = "3.5.1";
122476
122628
  function createMcpServer() {
122477
122629
  const server = new Server(
122478
122630
  {