@bicon/mcp-composer 0.1.0 → 0.3.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 (3) hide show
  1. package/README.md +191 -75
  2. package/dist/index.mjs +2821 -241
  3. package/package.json +11 -3
package/README.md CHANGED
@@ -23,110 +23,226 @@ via API keys is a separate, security-reviewed phase.
23
23
 
24
24
  | Name | What it does |
25
25
  |---|---|
26
- | resource `initiative://schema` / tool `get_initiative_schema` | The field catalog: required fields, enum values, the four dimensions with their named sub-dimensions (EN/DE labels + help text), confidence scale, optional sections, and the handoff steps. **Read this first.** |
27
- | `assemble_initiative` | Compact spec (metadata + named sub-ratings 0–100 + confidence per dimension) → fully-formed initiative validated against the API contract (`CreateInitiativeBody` from `@workspace/api-zod`). Mints id/timestamps, **derives** the four dimension scores (mean of subs, like the app), returns `{ok, initiative, derivedScores, warnings, errors}` with precise per-field errors. |
28
- | `export_initiative_file` | Wraps an assembled initiative in the app's single-initiative JSON envelope; returns `{filename, json, importInstructions, derivedScores}`. |
29
-
30
- The score derivation is a test-pinned copy of the app's `scoring.ts`
31
- (golden test against the "Advisor Copilot" demo seed → 77 / 61 / 73 / 90),
32
- and a catalog-drift test fails the build if the OpenAPI contract's enums or
33
- sub-dimension keys change without a catalog update.
34
-
35
- ## Install for users (no clone, no build)
36
-
37
- The package is published to npm as `@bicon/mcp-composer` — a single
38
- self-contained bundle whose only runtime dependency is the MCP SDK. Any user
39
- with Node ≥ 20 adds this to Claude Desktop's `claude_desktop_config.json`
40
- (Settings → Developer → Edit Config) and restarts:
26
+ | resource `initiative://schema` / tool `get_initiative_schema` | The field catalog: required fields, enum values, the four dimensions with their named sub-dimensions (EN/DE labels + help text, plus the wording the app uses in digital / innovation portfolios), the exact shape of every optional section, the EU-AI-Act vocabularies, and the handoff steps. **Read this first.** |
27
+ | `assemble_initiative` | Compact spec (metadata + named sub-ratings 0–100 + confidence per dimension + optional sections, optionally the organisation's `context`) → fully-formed initiative validated against the API contract (`CreateInitiativeBody` from `@workspace/api-zod`). Mints the initiative id and every nested row id (decision-log entries, status reports, incidents — a client never invents ids), **derives** the four dimension scores (the app's own scoring function), accepts dates as dd/mm/yyyy, dd.mm.yyyy or yyyy-mm-dd, returns `{ok, initiative, derivedScores, warnings, errors, contextId}`. Pass `id` (an existing initiative's) only to **update** that initiative. |
28
+ | `export_initiative_file` | Takes the **same compact spec**, assembles it and wraps the result in the app's single-initiative JSON envelope; returns `{ok, filename, json, importInstructions, derivedScores, warnings}`. There is one pipeline — a hand-edited initiative object is not an input, so client-supplied scores or unknown keys can never bypass assembly. |
29
+ | `read_portfolio_context` | Takes the organisation's **portfolio-context file** (see below) and returns the interview plan for that organisation — its own labels, help texts and rating notes per sub-dimension, which sub-dimensions are switched off, its business units, categories, tags, skills (id + name) and non-archived initiatives (id + name), currency, discount rate, whether the EU-AI-Act module is on — plus a short **contextId** that stands in for the file in the two other tools. |
30
+ | `composer_version` | Returns `{composerVersion, capabilities}`. Every *successful* tool result also carries `composerVersion`. |
31
+
32
+ ### The portfolio context — how the Composer learns an organisation's settings
33
+
34
+ The Composer holds no credentials and calls no API, so on its own it cannot
35
+ know how an organisation configured its portfolio. The app therefore exports
36
+ a small **portfolio-context** JSON (Import/Export → the AI-assistant card →
37
+ *Download portfolio context*): the active portfolio mode, which evaluation
38
+ sliders are switched off, the organisation's own slider wording and rating
39
+ notes, business units, categories, tags, the skill roster and the live
40
+ initiatives (id + name), currency, discount rate and the AI-Act module switch.
41
+ The user hands that file to the assistant at the start; the assistant calls
42
+ `read_portfolio_context` once and then passes the returned **contextId**
43
+ (`ctx_<16 hex>`, a content hash — or the whole file again) as `context` to
44
+ `assemble_initiative` / `export_initiative_file`, so a 5–40 KB document is
45
+ not re-emitted three times per initiative. The id resolves against a
46
+ per-process content cache; an unknown id (server restarted) is a clear error
47
+ naming the fix, never a wrong answer. Effects:
48
+
49
+ - the interview uses the organisation's wording (org label › mode overlay ›
50
+ base) and skips switched-off sub-dimensions;
51
+ - switched-off sub-dimensions are optional in the spec, stored as 0 (the app's
52
+ blank default — a value sent anyway is zeroed with a warning) and
53
+ **excluded from the dimension score** — the app's own derivation, so the
54
+ score does not change on the first save in Evaluate; a dimension with every
55
+ sub-dimension off needs only `{ confidence }`;
56
+ - `businessUnit` and `aiCategory` **must be on the organisation's lists** (no
57
+ inventing — matched case-insensitively and normalised to the organisation's
58
+ spelling; new ones are created in the app's Settings); `dependsOn`,
59
+ `requiredSkills[].skillId` and an update `id` must be ids that exist (any
60
+ id format; Retired initiatives count, archived ones do not);
61
+ - every EU-AI-Act field the app strips is warned about while the
62
+ organisation's AI-Act module is off; the export file defaults to the
63
+ context's mode and language; a file newer than the Composer knows is flagged.
64
+
65
+ Without the file everything falls back to the built-in catalog: business unit
66
+ and category are free text, dependency / update ids must at least be UUID-shaped
67
+ (zod's rule, the same the API uses), and scores are derived over all 30
68
+ sub-dimensions. Export the file again after changing settings — the plan
69
+ reports its export date.
70
+
71
+ ### How validation works (and why it lives at the wire)
72
+
73
+ The MCP SDK validates every call with the registered schema **and hands the
74
+ handler the parsed value** — whatever the schema strips, the server never sees.
75
+ So the spec schema is registered as a full **strict** object, with every
76
+ optional section a **deep-strict copy of the API contract**: an unknown key
77
+ anywhere is rejected by the SDK with its exact path
78
+ (`Unrecognized key(s) in object: 'literacy' at aiAct`), enum values, dates
79
+ (dd/mm/yyyy, dd.mm.yyyy or yyyy-mm-dd — real calendar dates, normalised to
80
+ yyyy-mm-dd) and blank names are caught with field paths, and the advertised JSON Schema
81
+ (`additionalProperties: false`, no `$ref` pointers) means what it says. A
82
+ section may also be sent as a **JSON string** (clients working from a stale
83
+ tool descriptor did that); it is parsed and validated identically. What a JSON
84
+ Schema cannot express — the 30 named ratings, the AI-Act controlled
85
+ vocabularies (`aiActVocabularies` in the descriptor; the app drops unknown
86
+ obligation keys and never shows unknown artifact keys), the score derivation —
87
+ is checked in the assembler, which reports **all** problems of a call at once
88
+ with the allowed values in the message (id shapes included). Row ids of
89
+ decision-log entries, status reports and incidents are minted by the
90
+ Composer — a client-supplied UUID is kept, anything else replaced — because
91
+ the app keys those rows per initiative and a re-used id from another
92
+ initiative is exactly what collided on import once.
93
+
94
+ Errors the SDK raises before a handler runs (`Input validation error: …`) carry
95
+ no `composerVersion` — that is normal. A *successful* result without it means an
96
+ old build is answering (see next section).
97
+
98
+ ### Stale instances (reinstall is not enough — quit the app)
99
+
100
+ Claude Desktop runs an extension in **two** places: a chat-UI instance, which
101
+ every reinstall restarts, and a session-facing instance
102
+ (`LocalMcpServerManager` in Desktop's `main.log`) that serves Claude Code,
103
+ Cowork and remote claude.ai chats reaching the device. The session-facing
104
+ instance is started once at app launch and is only replaced on a **full app
105
+ quit** — reinstalling or disabling/enabling the extension leaves it running
106
+ the old code. Observed on 2026-08-18: five reinstalls, the chat-UI copy was
107
+ current every time, and every session still got the original build.
108
+
109
+ Tell: a successful tool result without `composerVersion`, or
110
+ `composer_version` missing from the tool list. Fix: install the new `.mcpb`,
111
+ then **quit Claude Desktop completely** (tray icon → Quit — closing the window
112
+ is not enough), reopen, and check with `composer_version`.
113
+
114
+ ### One source for the domain
115
+
116
+ The Composer imports the app's own domain leaf, `@workspace/initiative-model`
117
+ (types and value lists incl. the AI-Act vocabularies, the score derivation, the
118
+ portfolio-mode wording overlays, the backup-format version) — the same package
119
+ the web app renders and validates with — and derives its enum lists from the
120
+ generated API contract. The 30 sub-dimension keys are the shared leaf's too;
121
+ the only hand-listed part is their base wording, and `catalog-drift.test.ts`
122
+ pins the keys to the contract in both directions. `server.test.ts` drives the real server through
123
+ the real SDK client over an in-memory transport, so what is asserted is what a
124
+ client sees.
125
+
126
+ ## Install for users
127
+
128
+ ### Claude Desktop — the extension (recommended, no Node needed)
129
+
130
+ Download **`bicon-composer.mcpb`** from the BICon app (Import/Export → *Set
131
+ up initiatives with an AI assistant*, or directly at
132
+ `https://www.bicon.digital/downloads/bicon-composer.mcpb`) and open it —
133
+ double-click, or Claude Desktop → Settings → Extensions → Install. Claude
134
+ Desktop runs the server on its own built-in Node runtime, so there is nothing
135
+ else to install and no configuration file to edit. Then **quit Claude Desktop
136
+ completely and reopen** (see *Stale instances*), start a **new chat** and ask:
137
+ *"Use the BICon Initiative Composer to help me set up a new initiative."*
138
+
139
+ Why the extension is primary: current Claude Desktop builds manage local MCP
140
+ servers as Desktop Extensions and treat `claude_desktop_config.json` as their
141
+ own preferences store — a hand-pasted `mcpServers` block there is silently
142
+ overwritten. There is no supported config-file route for Claude Desktop.
143
+
144
+ ### Other MCP clients — the npm package
145
+
146
+ `@bicon/mcp-composer` on npm is a single self-contained bundle whose only
147
+ runtime dependency is the MCP SDK. Any MCP-capable client with Node ≥ 20 can
148
+ run it; for a client that reads a `mcpServers` config, add this and restart
149
+ the client:
41
150
 
42
151
  ```json
43
152
  {
44
153
  "mcpServers": {
45
154
  "bicon-composer": {
46
155
  "command": "npx",
47
- "args": ["-y", "@bicon/mcp-composer"]
156
+ "args": ["-y", "@bicon/mcp-composer@latest"]
48
157
  }
49
158
  }
50
159
  }
51
160
  ```
52
161
 
53
- That's the whole install. `npx -y` fetches (and caches) the published version;
54
- no repository, no terminal work.
162
+ `@latest` matters: an unpinned `npx` spec keeps serving whatever it cached
163
+ first. Check what you got with the `composer_version` tool — the registry
164
+ version and the extension version are released together (see *Publishing*);
165
+ if npm is behind, use the extension.
166
+
167
+ **Prerequisite:** Node.js ≥ 20 on the **same operating system as the client**.
168
+ On Windows with Node only inside WSL, launch the server *through* WSL:
169
+ `"command": "wsl.exe", "args": ["-d", "Ubuntu", "--", "/path/to/npx", "-y",
170
+ "@bicon/mcp-composer@latest"]` (replace `/path/to/npx` with the output of
171
+ `which npx` in WSL). Restart the client afterwards.
55
172
 
56
173
  ## Build & run from source (contributors)
57
174
 
58
175
  ```bash
59
- pnpm --filter @bicon/mcp-composer run build # → dist/index.mjs (single ESM bundle)
60
- pnpm --filter @bicon/mcp-composer run test # vitest
61
- node lib/integrations/mcp/dist/index.mjs # stdio server (used by the client, not by hand)
176
+ pnpm --filter @bicon/mcp-composer run build # → dist/index.mjs (npm bundle) + dist/mcpb/composer-<v>.mjs (extension bundle)
177
+ pnpm --filter @bicon/mcp-composer run test # vitest, incl. the SDK-boundary suite (builds the bundles itself)
178
+ pnpm --filter @bicon/mcp-composer run build:mcpb # → dist/bicon-composer.mcpb, copied to the web app's public/downloads
179
+ node lib/integrations/mcp/dist/index.mjs # stdio server (used by a client, not by hand)
62
180
  ```
63
181
 
64
- Point Claude Desktop at the local build with the **absolute path**:
65
-
66
- ```json
67
- {
68
- "mcpServers": {
69
- "bicon-composer": {
70
- "command": "node",
71
- "args": ["/ABSOLUTE/PATH/TO/lib/integrations/mcp/dist/index.mjs"]
72
- }
73
- }
74
- }
75
- ```
76
-
77
- On Windows with the repo in WSL, run the server **inside WSL** so it sees the
78
- Linux `node`:
79
-
80
- ```json
81
- {
82
- "mcpServers": {
83
- "bicon-composer": {
84
- "command": "wsl.exe",
85
- "args": ["-d", "Ubuntu", "--", "/home/<user>/.nvm/versions/node/<version>/bin/node",
86
- "/home/<user>/projects/ai-portfolio-steering/lib/integrations/mcp/dist/index.mjs"]
87
- }
88
- }
89
- }
90
- ```
91
-
92
- Restart Claude Desktop; the tools appear under the tools menu.
182
+ For a client that reads a `mcpServers` config, point it at the local build
183
+ with an absolute path (`"command": "node", "args": ["/ABSOLUTE/PATH/TO/lib/integrations/mcp/dist/index.mjs"]`;
184
+ on Windows with the repo in WSL, run through `wsl.exe` with the WSL `node`).
185
+ For Claude Desktop, build and install the extension instead.
186
+
187
+ `build:mcpb` stages the manifest (version, entry point and launch args written
188
+ from `package.json` — the checked-in `mcpb/manifest.json` carries
189
+ placeholders), adds the single self-contained bundle, and — before zipping —
190
+ boots the staged server and drives it through the protocol: `initialize`
191
+ (version), `tools/list` (five tools, no `$ref`), `composer_version`,
192
+ `resources/list`, and an `assemble_initiative` call with an unknown AI-Act key
193
+ that must be rejected by name. It then copies the `.mcpb` to
194
+ `artifacts/bicon-portfolio/public/downloads/` and writes
195
+ `src/lib/composer-extension.json` (version + path), which the Import/Export
196
+ card imports. Requires `python3` for the zip container.
93
197
 
94
198
  ## Using it (the interview → import flow)
95
199
 
96
- 1. Ask Claude: *"Help me set up a new initiative in BICon."* It reads
97
- `initiative://schema` and interviews you — metadata first, then the 30
98
- sub-dimensions with the app's own help text, then a confidence per dimension.
99
- 2. Claude calls `assemble_initiative` and shows you the **derived scores** as a
100
- sanity check (fix and re-run if anything looks off).
101
- 3. Claude calls `export_initiative_file` → save the returned JSON under the
102
- suggested filename (`initiative-<name>-<date>.json`).
103
- 4. In the app (signed in to the target organisation): **Import/Export →
104
- Single initiative** card → drop the file → the preview should show
105
- **0 warnings** → **Add as new** → **Confirm**.
106
- 5. The initiative appears in the Inventory with exactly the scores Claude
200
+ 1. (Recommended) In the app, Import/Export → *Download portfolio context*,
201
+ and give that file to Claude first — it then interviews with your
202
+ organisation's own wording, skips switched-off sliders and only accepts
203
+ your business units, categories, skills and initiatives.
204
+ 2. Ask Claude: *"Help me set up a new initiative in BICon."* It reads
205
+ `initiative://schema` (and your context) and interviews you — metadata
206
+ first, then the sub-dimensions with the app's help text, then a confidence
207
+ per dimension. Dates are dd/mm/yyyy in conversation.
208
+ 3. Claude calls `assemble_initiative` and shows you the **derived scores** and
209
+ any warnings as a sanity check (fix and re-run if anything looks off).
210
+ 4. Claude calls `export_initiative_file` with the same spec → save the returned
211
+ JSON under the suggested filename (`initiative-<name>-<date>.json`). To
212
+ **update** an existing initiative, name it — Claude passes its id from the
213
+ context and the app offers "Update"; a re-export without the id is
214
+ recognised by name and offered as an update too.
215
+ 5. In the app (signed in to the target organisation): if the initiative carries
216
+ EU-AI-Act data, make sure the organisation's **EU-AI-Act module is switched
217
+ on** (Settings › AI Act — the app strips AI-Act fields on import while it is
218
+ off; the file's `sourceMode` is informational and cannot override that).
219
+ Then **Import/Export → Single initiative** card → drop the file → the
220
+ preview should show **0 warnings** → **Add as new** (or **Update**, which
221
+ keeps the existing initiative's id, creation date and links) → **Confirm**.
222
+ 6. The initiative appears in the Inventory with exactly the scores Claude
107
223
  reported.
108
224
 
109
- `sourceMode` (default `ai`) must match the target portfolio's mode, otherwise the
110
- importer strips the AI-Act fields.
111
-
112
225
  ## Publishing (maintainers)
113
226
 
114
- The bundle inlines `zod` and `@workspace/api-zod` (a workspace-only
115
- devDependency the registry cannot resolve), so what ships is
116
- `dist/index.mjs` + this README with `@modelcontextprotocol/sdk` as the sole
117
- runtime dependency — `publish-shape.test.ts` guards exactly that. Publish under
118
- the BICon npm account (business identity, 2FA on):
227
+ Release = extension + npm together, from one version:
119
228
 
120
229
  ```bash
121
230
  cd lib/integrations/mcp
122
- pnpm run pack:check # builds, then `npm pack --dry-run` — inspect the file list
123
- npm login # once, as the BICon npm account
124
- npm version patch|minor # bumps package.json version + creates a git tag
125
- npm publish # prepack runs the build; publishConfig.access is public
231
+ npm version patch|minor # bumps package.json — the ONLY place the version is written
232
+ pnpm run test # incl. the SDK-boundary suite
233
+ pnpm run build:mcpb # bundles, ship-gate smoke, copies the .mcpb + version JSON into the web app
234
+ npm login # once, as the BICon npm account (business identity, 2FA on)
235
+ npm publish # prepack builds; publishConfig.access is public
126
236
  ```
127
237
 
128
- Then verify from a clean machine: `npx -y @bicon/mcp-composer` should start
129
- (it waits for stdio input; Ctrl-C to exit).
238
+ Then commit (the `.mcpb` and `composer-extension.json` are part of the
239
+ release commit) and verify from a clean machine:
240
+ `npx -y @bicon/mcp-composer@latest` starts and `composer_version` reports the
241
+ new version. The bundle inlines `zod`, `@workspace/api-zod` and
242
+ `@workspace/initiative-model` (workspace-only packages the registry cannot
243
+ resolve), so what ships is `dist/index.mjs` + this README with
244
+ `@modelcontextprotocol/sdk` as the sole runtime dependency —
245
+ `publish-shape.test.ts` guards exactly that.
130
246
 
131
247
  ## Not in scope (Phase 2)
132
248