@bicon/mcp-composer 0.3.1 → 0.3.4

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/README.md CHANGED
@@ -58,6 +58,10 @@ naming the fix, never a wrong answer. Effects:
58
58
  spelling; new ones are created in the app's Settings); `dependsOn`,
59
59
  `requiredSkills[].skillId` and an update `id` must be ids that exist (any
60
60
  id format; Retired initiatives count, archived ones do not);
61
+ - ratings must be on the organisation's **slider step** (Settings › Slider
62
+ granularity: 5, 10, 20 or 25) — the plan states it, a value off the grid is
63
+ rejected with the step in the message (files exported before 2026-08-21
64
+ carry no step and are not checked);
61
65
  - every EU-AI-Act field the app strips is warned about while the
62
66
  organisation's AI-Act module is off; the export file defaults to the
63
67
  context's mode and language; a file newer than the Composer knows is flagged.
package/dist/index.mjs CHANGED
@@ -12,7 +12,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
12
12
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
13
 
14
14
  // src/version.ts
15
- var COMPOSER_VERSION = true ? "0.3.1" : readPackageVersion();
15
+ var COMPOSER_VERSION = true ? "0.3.4" : readPackageVersion();
16
16
 
17
17
  // ../../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
18
18
  var external_exports = {};
@@ -4193,6 +4193,7 @@ var ListSnapshotsResponseItem = objectType({
4193
4193
  "initiatives": arrayType(objectType({
4194
4194
  "id": stringType(),
4195
4195
  "name": stringType(),
4196
+ "externalId": stringType().optional().describe("The organisation's own reference for the initiative (project number, Jira key, IC id, \u2026)"),
4196
4197
  "businessUnit": stringType(),
4197
4198
  "owner": stringType(),
4198
4199
  "description": stringType(),
@@ -4445,6 +4446,7 @@ var CreateSnapshotBody = objectType({
4445
4446
  "initiatives": arrayType(objectType({
4446
4447
  "id": stringType(),
4447
4448
  "name": stringType(),
4449
+ "externalId": stringType().optional().describe("The organisation's own reference for the initiative (project number, Jira key, IC id, \u2026)"),
4448
4450
  "businessUnit": stringType(),
4449
4451
  "owner": stringType(),
4450
4452
  "description": stringType(),
@@ -4702,6 +4704,7 @@ var RenameSnapshotResponse = objectType({
4702
4704
  "initiatives": arrayType(objectType({
4703
4705
  "id": stringType(),
4704
4706
  "name": stringType(),
4707
+ "externalId": stringType().optional().describe("The organisation's own reference for the initiative (project number, Jira key, IC id, \u2026)"),
4705
4708
  "businessUnit": stringType(),
4706
4709
  "owner": stringType(),
4707
4710
  "description": stringType(),
@@ -4950,6 +4953,7 @@ var DeleteSnapshotParams = objectType({
4950
4953
  var ListInitiativesResponseItem = objectType({
4951
4954
  "id": stringType(),
4952
4955
  "name": stringType(),
4956
+ "externalId": stringType().optional().describe("The organisation's own reference for the initiative (project number, Jira key, IC id, \u2026)"),
4953
4957
  "businessUnit": stringType(),
4954
4958
  "owner": stringType(),
4955
4959
  "description": stringType(),
@@ -5194,6 +5198,7 @@ var ListInitiativesResponse = arrayType(ListInitiativesResponseItem);
5194
5198
  var CreateInitiativeBody = objectType({
5195
5199
  "id": stringType(),
5196
5200
  "name": stringType(),
5201
+ "externalId": stringType().optional().describe("The organisation's own reference for the initiative (project number, Jira key, IC id, \u2026)"),
5197
5202
  "businessUnit": stringType(),
5198
5203
  "owner": stringType(),
5199
5204
  "description": stringType(),
@@ -5446,6 +5451,7 @@ var GetInitiativeParams = objectType({
5446
5451
  var GetInitiativeResponse = objectType({
5447
5452
  "id": stringType(),
5448
5453
  "name": stringType(),
5454
+ "externalId": stringType().optional().describe("The organisation's own reference for the initiative (project number, Jira key, IC id, \u2026)"),
5449
5455
  "businessUnit": stringType(),
5450
5456
  "owner": stringType(),
5451
5457
  "description": stringType(),
@@ -5692,6 +5698,7 @@ var UpdateInitiativeParams = objectType({
5692
5698
  var UpdateInitiativeBody = objectType({
5693
5699
  "id": stringType(),
5694
5700
  "name": stringType(),
5701
+ "externalId": stringType().optional().describe("The organisation's own reference for the initiative (project number, Jira key, IC id, \u2026)"),
5695
5702
  "businessUnit": stringType(),
5696
5703
  "owner": stringType(),
5697
5704
  "description": stringType(),
@@ -5941,6 +5948,7 @@ var UpdateInitiativeBody = objectType({
5941
5948
  var UpdateInitiativeResponse = objectType({
5942
5949
  "id": stringType(),
5943
5950
  "name": stringType(),
5951
+ "externalId": stringType().optional().describe("The organisation's own reference for the initiative (project number, Jira key, IC id, \u2026)"),
5944
5952
  "businessUnit": stringType(),
5945
5953
  "owner": stringType(),
5946
5954
  "description": stringType(),
@@ -8602,6 +8610,13 @@ var REQUIRED_FIELDS = [
8602
8610
  ];
8603
8611
  var OPTIONAL_SECTIONS = [
8604
8612
  { key: "tags", hint: { en: "Free-text tags", de: "Freie Schlagworte" } },
8613
+ {
8614
+ key: "externalId",
8615
+ hint: {
8616
+ en: "The organisation's own initiative reference (project number, Jira key, IC id) \u2014 the app's 'Initiative ID' field",
8617
+ de: "Eigene Referenz der Organisation (Projektnummer, Jira-Key, IC-Nummer) \u2014 das Feld \u201EInitiativen-ID\u201C der App"
8618
+ }
8619
+ },
8605
8620
  {
8606
8621
  key: "docsUrl / pmUrl",
8607
8622
  hint: {
@@ -8721,7 +8736,7 @@ function buildSchemaDescriptor(sectionShapes) {
8721
8736
  ...AI_ACT_VOCABULARIES,
8722
8737
  note: "prohibitedPractices/art50Flags: arrays of these ids. artifacts/obligations: maps keyed by these ids. annexIIICategory: one of these, or omit when no Annex III area applies (record the reasoning in classificationRationale; annexExceptionRationale is only for an Art. 6(3) exception claim). Empty is honest; invented values are rejected."
8723
8738
  },
8724
- portfolioContext: "Ask the user for the organisation's portfolio-context file first (in the app: Import/Export \u2192 'Download portfolio context'). Call read_portfolio_context on it: it returns the interview plan in the organisation's own wording, the sub-dimensions that are switched off, and the organisation's business units, categories, tags, skills and initiatives \u2014 with the file, business unit and category MUST be from those lists (no inventing) and skill / dependency ids are validated. Pass the same context to assemble_initiative and export_initiative_file. Without the file, interview with the wording below and treat business unit / category as free text.",
8739
+ portfolioContext: "Ask the user for the organisation's portfolio-context file first (in the app: Import/Export \u2192 'Download portfolio context'). Call read_portfolio_context on it: it returns the interview plan in the organisation's own wording, the sub-dimensions that are switched off, the slider step the ratings must follow, and the organisation's business units, categories, tags, skills and initiatives \u2014 with the file, business unit and category MUST be from those lists (no inventing) and skill / dependency ids are validated. Pass the same context to assemble_initiative and export_initiative_file. Without the file, interview with the wording below and treat business unit / category as free text.",
8725
8740
  handoff: "Call assemble_initiative with the compact spec (+ context), review derivedScores (and any warnings) with the user, then call export_initiative_file with the SAME spec (+ context) \u2192 save the JSON \u2192 in the app: Import/Export \u2192 Single initiative \u2192 drop the file \u2192 'Add as new' \u2192 Confirm. If the initiative carries EU-AI-Act data, the organisation's EU-AI-Act module must be switched on (Settings \u203A AI Act) when importing."
8726
8741
  };
8727
8742
  }
@@ -8825,6 +8840,7 @@ var compactSpecSchema = external_exports.object({
8825
8840
  "ONLY to UPDATE an existing initiative: its id (from read_portfolio_context \u2192 lists.initiatives, or the app). The exported file then updates that initiative in place. Omit for a new initiative \u2014 never invent an id."
8826
8841
  ),
8827
8842
  name: requiredText(),
8843
+ externalId: external_exports.preprocess((v) => typeof v === "string" && v.trim() === "" ? void 0 : v, external_exports.string().trim().min(1).optional()).describe("The organisation's own reference for the initiative (project number, Jira key, IC id, \u2026) \u2014 ask for it when the organisation uses such references"),
8828
8844
  businessUnit: requiredText().describe("With a portfolio context: one of the organisation's business units (read_portfolio_context \u2192 lists.businessUnits)"),
8829
8845
  owner: requiredText(),
8830
8846
  description: requiredText(),
@@ -8938,11 +8954,13 @@ var portfolioContextSchema = external_exports.object({
8938
8954
  currency: external_exports.string(),
8939
8955
  aiActEnabled: external_exports.boolean(),
8940
8956
  discountRatePct: external_exports.number(),
8957
+ /** Slider step (5/10/20/25). Files from before 2026-08-21 lack it → no grid check. */
8958
+ sliderGranularity: external_exports.number().int().positive().max(100).optional(),
8941
8959
  businessUnits: external_exports.array(external_exports.string()),
8942
8960
  categories: external_exports.array(external_exports.string()),
8943
8961
  suggestedTags: external_exports.array(external_exports.string()),
8944
8962
  skills: external_exports.array(external_exports.object({ id: external_exports.string().min(1), name: external_exports.string() }).passthrough()),
8945
- initiatives: external_exports.array(external_exports.object({ id: external_exports.string().min(1), name: external_exports.string(), status: external_exports.enum(STATUS_VALUES) }).passthrough()),
8963
+ initiatives: external_exports.array(external_exports.object({ id: external_exports.string().min(1), externalId: external_exports.string().trim().optional(), name: external_exports.string(), status: external_exports.enum(STATUS_VALUES) }).passthrough()),
8946
8964
  sliders: external_exports.array(sliderSchema)
8947
8965
  }).passthrough();
8948
8966
  var CONTEXT_SUPPORTED_VERSION = PORTFOLIO_CONTEXT_VERSION;
@@ -9001,6 +9019,15 @@ function checkAgainstContext(spec, ctx) {
9001
9019
  const warnings = [];
9002
9020
  const v = versionNotice(ctx);
9003
9021
  if (v) warnings.push(v);
9022
+ const specExt = spec.externalId?.trim().toLowerCase();
9023
+ if (specExt) {
9024
+ const holder = ctx.initiatives.find((x) => (x.externalId ?? "").trim().toLowerCase() === specExt && x.id !== spec.id);
9025
+ if (holder) {
9026
+ warnings.push(
9027
+ spec.id ? `externalId "${spec.externalId.trim()}" already belongs to "${holder.name}" (${holder.id}) \u2014 two initiatives would share the same organisation reference.` : `externalId "${spec.externalId.trim()}" already belongs to "${holder.name}" (${holder.id}) \u2014 pass that id as \`id\` to UPDATE it instead of creating a duplicate.`
9028
+ );
9029
+ }
9030
+ }
9004
9031
  const bus = ctx.businessUnits;
9005
9032
  let businessUnit = spec.businessUnit;
9006
9033
  const buMatch = canonical(spec.businessUnit, bus);
@@ -9053,6 +9080,10 @@ function resolveSub(base, ctxSlider, mode, lang) {
9053
9080
  if (note) out.note = note;
9054
9081
  return out;
9055
9082
  }
9083
+ function gridExample(step) {
9084
+ const pts = [0, step, 2 * step].filter((v) => v < 100);
9085
+ return `${pts.join(", ")} \u2026 100`;
9086
+ }
9056
9087
  function buildInterviewPlan(ctx, contextId) {
9057
9088
  const mode = ctx.portfolioMode;
9058
9089
  const lang = ctx.language;
@@ -9073,7 +9104,8 @@ function buildInterviewPlan(ctx, contextId) {
9073
9104
  ...allOff.length ? [`Dimension(s) with EVERY sub-dimension switched off (${allOff.join(", ")}) still need an entry in evaluation: { subs: {}, confidence } \u2014 the score is 0 there; ask only for the confidence.`] : [],
9074
9105
  ctx.businessUnits.length && ctx.categories.length ? "businessUnit MUST be one of lists.businessUnits and aiCategory one of lists.categories (matched case-insensitively, normalised to the organisation's spelling) \u2014 the Composer rejects anything else; new ones are created in the app's Settings, not in the interview." : "This organisation has no business units and/or categories configured yet \u2014 they must be added in the app's Settings and the context downloaded again before an initiative can be assembled.",
9075
9106
  "dependsOn entries must be ids from lists.initiatives; requiredSkills[].skillId must be ids from lists.skills. Never invent an id; leave the list empty when unsure.",
9076
- "To UPDATE an existing initiative instead of creating a new one, pass its id (from lists.initiatives) as `id` in the spec \u2014 the exported file then updates it in the app (Import/Export \u2192 Single initiative \u2192 'Update'). Omit id for a new initiative.",
9107
+ "To UPDATE an existing initiative instead of creating a new one, pass its id (from lists.initiatives) as `id` in the spec \u2014 the exported file then updates it in the app (Import/Export \u2192 Single initiative \u2192 'Update'). Omit id for a new initiative. Entries in lists.initiatives may carry externalId \u2014 the organisation's own reference (e.g. IC-26-118) \u2014 use it to recognise which initiative the user means. An update REPLACES the whole record: any field, section or row you omit is CLEARED \u2014 decision log, status reports and links included \u2014 so re-send everything that must survive. Exception: the organisation's externalId is carried over automatically when omitted.",
9108
+ ...ctx.sliderGranularity ? [`Ratings are in steps of ${ctx.sliderGranularity} (the organisation's slider step \u2014 Settings \u203A Slider granularity): ask for and send only multiples of ${ctx.sliderGranularity} between 0 and 100 (${gridExample(ctx.sliderGranularity)}); any other value is rejected.`] : [],
9077
9109
  "Dates: talk to the user in dd/mm/yyyy; you may send dates as dd/mm/yyyy, dd.mm.yyyy or yyyy-mm-dd \u2014 the Composer stores yyyy-mm-dd.",
9078
9110
  ctx.aiActEnabled ? "The organisation's EU-AI-Act module is ON: collect the AI-Act block and governance flags." : "The organisation's EU-AI-Act module is OFF: skip the AI-Act block and governance flags (the app would remove them on import).",
9079
9111
  `Pass \`context: "${contextId}"\` (this contextId \u2014 or the whole file again) to assemble_initiative and export_initiative_file so scores are derived over the enabled sub-dimensions and ids are validated. Amounts are in ${ctx.currency}; the default discount rate is ${ctx.discountRatePct}%.`,
@@ -9088,6 +9120,7 @@ function buildInterviewPlan(ctx, contextId) {
9088
9120
  currency: ctx.currency,
9089
9121
  aiActEnabled: ctx.aiActEnabled,
9090
9122
  discountRatePct: ctx.discountRatePct,
9123
+ ...ctx.sliderGranularity ? { sliderGranularity: ctx.sliderGranularity } : {},
9091
9124
  contextExportedAt: ctx.exportedAt
9092
9125
  },
9093
9126
  lists: {
@@ -9095,7 +9128,7 @@ function buildInterviewPlan(ctx, contextId) {
9095
9128
  categories: ctx.categories,
9096
9129
  suggestedTags: ctx.suggestedTags,
9097
9130
  skills: ctx.skills.map((s) => ({ id: s.id, name: s.name })),
9098
- initiatives: ctx.initiatives.map((i) => ({ id: i.id, name: i.name, status: i.status }))
9131
+ initiatives: ctx.initiatives.map((i) => ({ id: i.id, ...i.externalId ? { externalId: i.externalId } : {}, name: i.name, status: i.status }))
9099
9132
  },
9100
9133
  interview: {
9101
9134
  dimensions,
@@ -9110,7 +9143,7 @@ function buildInterviewPlan(ctx, contextId) {
9110
9143
  // src/assembler.ts
9111
9144
  var uuidSchema = external_exports.string().uuid();
9112
9145
  var isUuid = (v) => typeof v === "string" && uuidSchema.safeParse(v).success;
9113
- function checkSubs(dim, subs, errors, disabled) {
9146
+ function checkSubs(dim, subs, errors, disabled, step) {
9114
9147
  const expected = DIMENSION_SUBS[dim];
9115
9148
  const enabled = expected.filter((k) => !disabled.has(k));
9116
9149
  if (!subs || typeof subs !== "object") {
@@ -9122,6 +9155,7 @@ function checkSubs(dim, subs, errors, disabled) {
9122
9155
  if (v === void 0) errors.push(`evaluation.${dim}.subs.${key} is missing`);
9123
9156
  else if (typeof v !== "number" || !Number.isFinite(v)) errors.push(`evaluation.${dim}.subs.${key} must be a number`);
9124
9157
  else if (v < 0 || v > 100) errors.push(`evaluation.${dim}.subs.${key} must be within 0\u2013100 (got ${v})`);
9158
+ else if (step && v % step !== 0) errors.push(`evaluation.${dim}.subs.${key} must be a multiple of ${step} \u2014 the organisation's slider step (got ${v})`);
9125
9159
  }
9126
9160
  for (const key of Object.keys(subs)) {
9127
9161
  if (!expected.includes(key)) errors.push(`evaluation.${dim}.subs.${key} is not a known sub-dimension (expected: ${enabled.join(", ")})`);
@@ -9183,7 +9217,7 @@ function assembleInitiative(input, now = /* @__PURE__ */ new Date()) {
9183
9217
  errors.push(`evaluation.${dim} is missing`);
9184
9218
  continue;
9185
9219
  }
9186
- checkSubs(dim, d.subs, errors, disabled);
9220
+ checkSubs(dim, d.subs, errors, disabled, context?.sliderGranularity);
9187
9221
  if (!d.confidence) errors.push(`evaluation.${dim}.confidence is missing (Low | Medium | High)`);
9188
9222
  for (const key of Object.keys(d.subs ?? {})) {
9189
9223
  if (disabled.has(key)) warnings.push(`evaluation.${dim}.subs.${key}: this sub-dimension is switched off in the organisation \u2014 value ignored (stored as 0, not scored).`);
@@ -9215,11 +9249,20 @@ function assembleInitiative(input, now = /* @__PURE__ */ new Date()) {
9215
9249
  return [dim, { score: dimensionScore(subs, disabled), confidence: d.confidence, subs }];
9216
9250
  })
9217
9251
  );
9252
+ let externalId = spec.externalId;
9253
+ if (externalId === void 0 && spec.id && context) {
9254
+ const kept = context.initiatives.find((i) => i.id === spec.id)?.externalId?.trim();
9255
+ if (kept) {
9256
+ externalId = kept;
9257
+ warnings.push(`externalId "${kept}" carried over from the organisation's records \u2014 send externalId explicitly to change or clear it.`);
9258
+ }
9259
+ }
9218
9260
  const stamp = now.toISOString();
9219
9261
  const aiAct = spec.aiAct;
9220
9262
  const candidate = {
9221
9263
  ...spec,
9222
9264
  id: spec.id ?? randomUUID(),
9265
+ ...externalId !== void 0 ? { externalId } : {},
9223
9266
  businessUnit,
9224
9267
  aiCategory,
9225
9268
  tags: spec.tags ?? [],
@@ -9265,7 +9308,7 @@ var IMPORT_INSTRUCTIONS = [
9265
9308
  "2. In the BICon app (signed in to the target organisation), open Import/Export.",
9266
9309
  "3. If the initiative carries EU-AI-Act data, make sure the organisation's EU-AI-Act module is switched on (Settings \u203A AI Act) \u2014 the app strips AI-Act fields while it is off.",
9267
9310
  "4. In the 'Single initiative' card, drop the file (or Browse).",
9268
- "5. Review the preview \u2014 it should show 0 warnings \u2014 then choose 'Add as new' (or 'Update' when the file carries an existing initiative's id or the same name \u2014 the app then keeps that initiative's id and history) and Confirm.",
9311
+ "5. Review the preview \u2014 it should show 0 warnings \u2014 then choose 'Add as new' (or 'Update' when the file carries an existing initiative's id, Initiative ID or name \u2014 the app keeps that initiative's id and creation date, and REPLACES everything else with this file, decision log and status reports included) and Confirm.",
9269
9312
  "6. The initiative appears in the Inventory with the derived dimension scores."
9270
9313
  ].join("\n");
9271
9314
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bicon/mcp-composer",
3
- "version": "0.3.1",
3
+ "version": "0.3.4",
4
4
  "description": "MCP server that helps an AI client compose a valid BICon portfolio initiative and hand it back as an import file (draft-first, no auth, no API calls).",
5
5
  "license": "UNLICENSED",
6
6
  "author": "BICon Establishment",