@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 +4 -0
- package/dist/index.mjs +51 -8
- package/package.json +1 -1
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.
|
|
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
|
|
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.
|
|
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",
|