Merge stable into main: catch up bc-mcp static tool mode + MS Learn MCP

This commit is contained in:
Michael Dieringer 2026-07-31 20:22:46 +02:00
commit 73144817d6
4 changed files with 138 additions and 75 deletions

View file

@ -1,7 +1,7 @@
---
bc-version: [all]
domain: architecture
keywords: [dependency, source, add-repo, github, curabis, closed-source, test, symbol, black-box]
keywords: [dependency, source, reference-repos, clone, github, curabis, closed-source, test, symbol, black-box]
technologies: [al]
countries: [w1]
application-area: [all]
@ -17,10 +17,31 @@ customer project imports one of these apps, it typically arrives as a compiled
This makes the app look like a closed external dependency. It is not.
Before treating any CURABIS-owned dependency app as a black box, the agent
must check whether its source is available via `add_repo` (GitHub). Reverse-
engineering compiled symbol packages (`SymbolReference.json`, `.app` manifest
inspection) is always an inferior substitute for reading the actual production
source, and produces lower-confidence tests and code reviews.
must check whether its source is available on GitHub and read it directly.
Reverse-engineering compiled symbol packages (`SymbolReference.json`, `.app`
manifest inspection) is always an inferior substitute for reading the actual
production source, and produces lower-confidence tests and code reviews.
**How to actually get the source (2026-07-30 — was previously the underspecified
`add_repo <repo>`, which is not a real tool in most Claude Code sessions):**
- **Claude Code CLI** (this is the common case): maintain a persistent,
periodically-refreshed clone at `~/.claude/reference-repos/<org>/<repo>/` —
same pattern as BCQuality's own channel clone. Before reading, check if it
exists:
- Missing: `git clone --depth 1 <url> "$env:USERPROFILE\.claude\reference-repos\<org>\<repo>"`
(shallow — you need current source, not history)
- Exists: `git -C "$env:USERPROFILE\.claude\reference-repos\<org>\<repo>" pull --depth 1`
(refresh before trusting it — a stale clone from a prior session is a
silent source of wrong answers)
- Then `Read`/`Grep`/`Glob` it like any other local path.
- **Claude Code on web** (claude.ai/code): use the session's repo picker /
"add repository" action instead, if the UI offers one — there is no shell
to clone into in that environment.
Verify which of these your actual session supports before assuming either
works — do not silently fall back to symbol inspection if neither succeeds;
flag the limitation explicitly instead (see "When cloning fails" below).
## Known CURABIS app repos
@ -29,7 +50,7 @@ source, and produces lower-confidence tests and code reviews.
| Contract Management 365 app | https://github.com/Curabis/ContractMgmt365app.git | Main contract engine; depended on by most CURABIS customer projects |
| Project Management 365 app | https://github.com/Curabis/ProjectMgmt365app.git | |
| Cross Channel Management 365 app | https://github.com/Curabis/WebStore.git | |
| Summatim | https://github.com/MichaelDieringer/-summatim.git | Currently restricted — only mid has access; add_repo will fail for other team members |
| Summatim | https://github.com/MichaelDieringer/-summatim.git | Currently restricted — only mid has access; the clone will fail for other team members |
*Expand this table when new CURABIS apps are created. If an app is not listed here,
ask `mid` whether source is available before reverting to symbol inspection.*
@ -42,10 +63,12 @@ available at:
- https://github.com/microsoft/BCApps
Use `add_repo microsoft/BCApps` when you need to understand internals of
Microsoft standard codeunits (e.g. Sales-Post, Gen. Jnl.-Post Line, Copy Document
Mgt.) that are referenced by event subscribers but whose source is not visible in
the current project.
Clone it to `~/.claude/reference-repos/microsoft/BCApps/` (see the mechanism
above) when you need to understand internals of Microsoft standard codeunits
(e.g. Sales-Post, Gen. Jnl.-Post Line, Copy Document Mgt.) that are referenced
by event subscribers but whose source is not visible in the current project.
It is a large public monorepo — a shallow clone is still the right call, and
worth refreshing rather than re-cloning once it exists.
## Anti Pattern
@ -56,8 +79,9 @@ the current project.
## Best Practice
// CORRECT: add the source repo and read it directly
add_repo Curabis/ContractMgmt365app
// CORRECT: clone the source repo and read it directly
git clone --depth 1 https://github.com/Curabis/ContractMgmt365app.git `
"$env:USERPROFILE\.claude\reference-repos\Curabis\ContractMgmt365app"
// Then read the actual table definitions, codeunits, and any Test Library
// codeunits that may already exist in the repo's own test app.
@ -75,5 +99,8 @@ Apply at the start of any task involving:
- Building GIVEN helpers for a CURABIS-owned app's tables
- Code-reviewing changes to codeunits that extend CURABIS-owned apps
If `add_repo` fails due to access restrictions (see table above), flag the
limitation explicitly rather than silently falling back to symbol inspection.
## When cloning fails
If the clone fails due to access restrictions (see table above) or your
session has no shell to clone into, flag the limitation explicitly rather
than silently falling back to symbol inspection.

View file

@ -212,13 +212,14 @@ If it does NOT exist:
Everything machine-global beyond the bridge and BC secret — the knowledge
mirror, the 19 roster agent files (18 to `~/.claude/curabis-agents/` +
Florence to `~/.claude/agents/florence.md`), `~/.claude/find-altool.ps1`, and
the `al`/`businesscentral` MCP registrations — is deployed by ONE script,
`sync-bcquality-knowledge.ps1`. None of it is ever committed to a project
repository (BCQuality rule `bcquality-knowledge-must-mirror-to-machine-not-repo`,
extended in v24 to `roster-agents-live-on-machine-not-in-repo`). Rationale:
developers switch between many repos daily — N per-repo copies are
permanently out of sync with each other, while one machine copy needs
exactly one sync per upstream change.
the `al`/`businesscentral`/`microsoft-learn` MCP registrations — is deployed
by ONE script, `sync-bcquality-knowledge.ps1`. None of it is ever committed
to a project repository (BCQuality rule
`bcquality-knowledge-must-mirror-to-machine-not-repo`, extended in v24 to
`roster-agents-live-on-machine-not-in-repo`). Rationale: developers switch
between many repos daily — N per-repo copies are permanently out of sync
with each other, while one machine copy needs exactly one sync per upstream
change.
1. Fetch `{BASE}/sync-bcquality-knowledge.ps1` → write AS RAW BYTES
(`Invoke-WebRequest -OutFile`, never via string content — re-encoding
@ -234,15 +235,20 @@ exactly one sync per upstream change.
- `~/.claude/curabis-agents/*.agent.md` (18 files)
- `~/.claude/agents/florence.md` (Florence, as a real subagent)
- `~/.claude/find-altool.ps1`
- `al` + `businesscentral` registered at user MCP scope (idempotent — a
server that already exists is reported, not re-added or overwritten)
- `al` + `businesscentral` + `microsoft-learn` registered at user MCP scope
(idempotent — a server that already exists is reported, not re-added or
overwritten). `microsoft-learn` is `https://learn.microsoft.com/api/mcp`,
HTTP transport, no auth — the same official documentation search Mode C
already gives support users; v24 closes the gap where developers had
only the static `microsoft/` knowledge-file snapshot and no live search
of Microsoft's own docs.
3. If a v6-era `.github/.agents/bcquality-knowledge/` exists in THIS repo,
add it to `.gitignore` so no future session can accidentally commit it
(see the v6-cleanup step in Mode B for full removal — this step just
prevents new commits).
4. Confirm: "Maskine-opsætning synkroniseret — bcquality-knowledge [antal]
filer, curabis-agents 18 filer, Florence, find-altool.ps1, MCP (al,
businesscentral)."
businesscentral, microsoft-learn)."
This machine setup is what the global `~/.claude/CLAUDE.md` roster section
and the project CLAUDE.md's session-start line both depend on. Without this
@ -543,7 +549,7 @@ these are shared across every CURABIS repo on the machine:
| `~/.claude/curabis-agents/*.agent.md` (18 files) | Re-run the sync script |
| `~/.claude/agents/florence.md` | Re-run the sync script |
| `~/.claude/find-altool.ps1` | Re-run the sync script |
| `al` + `businesscentral` MCP servers (user scope) | Re-run the sync script — idempotent: registers if missing, does NOT touch an existing registration (a developer's personal-scope config is not policed the way repo-shared `.mcp.json` used to be) |
| `al` + `businesscentral` + `microsoft-learn` MCP servers (user scope) | Re-run the sync script — idempotent: registers if missing, does NOT touch an existing registration (a developer's personal-scope config is not policed the way repo-shared `.mcp.json` used to be) |
| `.github/.agents/bcquality-knowledge/` + `.github/.agents/sync-bcquality-knowledge.ps1` | v6-era repo-local mirror: propose removal (see below) |
### bcquality-knowledge — machine re-sync (Mode B)

View file

@ -198,3 +198,27 @@ Ensure-UserMcpServer -Name 'al' -CommandAndArgs @(
'-File', '${USERPROFILE}\.claude\find-altool.ps1',
'launchmcpserver', 'auto', '--transport', 'stdio'
)
# --- 9. Microsoft Learn MCP (HTTP, ingen auth) - samme adgang udviklere faar som
# Mode C support-brugere allerede har. Verificeret 2026-07-30: offentlig, ingen
# nogen creds noedvendige, stdio-mekanismen ovenfor gaelder ikke - HTTP-transport
# bruger et andet flag-sæt (--transport http, ingen '--' kommando-adskiller).
function Ensure-UserMcpHttpServer {
param([string]$Name, [string]$Url)
$prevEap = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
$output = & claude mcp add --scope user --transport http $Name $Url 2>&1
$exitCode = $LASTEXITCODE
$ErrorActionPreference = $prevEap
if ($exitCode -ne 0) {
if ($output -match 'already exists') {
Write-Host "MCP-server '$Name' er allerede registreret paa user scope."
} else {
throw "claude mcp add fejlede for '$Name': $output"
}
} else {
Write-Host "MCP-server '$Name' registreret paa user scope."
}
}
Ensure-UserMcpHttpServer -Name 'microsoft-learn' -Url 'https://learn.microsoft.com/api/mcp'

View file

@ -1,9 +1,9 @@
---
kind: action-skill
id: curabis-bc-mcp
version: 1
version: 2
title: CURABIS Business Central MCP usage
description: How to use the CURABIS Business Central MCP server to read project-management work from BC and write GitHub dev status back. Company-default workflow for syncing Claude Code / GitHub work with BC tasks.
description: How to use the CURABIS Business Central MCP server to read project-management work from BC and write GitHub dev status back. Company-default workflow for syncing Claude Code / GitHub work with BC tasks. v2 (2026-07-30) - BC MCP switched from Dynamic to Static Tool Mode; 14 directly-named tools replace the old search/describe/invoke indirection.
inputs: [project-no, task-no, branch, dev-status, comment]
outputs: [task-list, updated-task, posted-comment]
bc-version: [all]
@ -60,52 +60,51 @@ branch / status / a note back to BC.
so attribute work to a developer yourself (see "Developer identity" below).
- If the server is not connected, say so and stop. Do not invent task data.
## Session start: pre-load the three MCP tools
## Session start: pre-load the tools you'll need
Before producing any user-visible output, load the three tool schemas by exact name:
**2026-07-30: BC MCP switched from Dynamic Tool Mode to Static Tool Mode** (BC's
"Konfiguration af MCP-server" — CURABIS_DEV). The generic `bc_actions_search` /
`bc_actions_describe` / `bc_actions_invoke` tools **no longer exist**. Every BC
action is now its own directly-named, directly-typed MCP tool — confirmed live
via the tool panel after a reconnect (a stale connection cached the old three-tool
list for a while after the BC-side toggle; a full reconnect is what surfaced the
real list). There is nothing left to search or describe — the tool names below
are the actual MCP tool names, not a guessed convention.
ToolSearch query: select:mcp__businesscentral__bc_actions_search,mcp__businesscentral__bc_actions_invoke,mcp__businesscentral__bc_actions_describe
Before producing any user-visible output, load the tools this task actually needs
by exact name, e.g. for the standard dev workflow:
ToolSearch query: select:mcp__businesscentral__List_ActiveTasks_PAG6102900,mcp__businesscentral__Modify_ActiveTask_PAG6102900,mcp__businesscentral__Create_TaskComment_PAG6102902
Do this first, silently. It must complete before you respond to the user - loading it
mid-task means the user hits unexpected latency at the moment they expect an action,
not setup.
not setup. Load only what the task needs — see the table below for the full set.
## Tools (BC MCP, Dynamic Tool Mode OFF)
## Tools (BC MCP, Static Tool Mode)
The `businesscentral` MCP server exposes exactly **three** callable tools:
`bc_actions_search`, `bc_actions_describe`, `bc_actions_invoke`. There is no static
tool list to browse - BC entity/action names (`List_Projects_PAG6102901` and so on)
are not separate MCP tools; they only exist as results of `bc_actions_search`.
The `businesscentral` MCP server exposes **14 directly-named tools**, one per
BC action, each with its own typed schema reflecting exactly which fields that
page allows you to write. No discovery step, no per-call reverification — call
the tool by name directly, matching the convention `List_<Entity>_PAG<id>` (read),
`Modify_<Entity>_PAG<id>` (update, **singular** entity name), `Create_<Entity>_PAG<id>`
(create). All are marked `destructive` except the `List_*` reads, which are `read-only`.
**Do not use the general-purpose `ToolSearch` to look for BC entity/action names.**
`ToolSearch` only resolves this session's own deferred client-side tools (the three
above) - it does not search Business Central's action catalog and will return
irrelevant matches from unrelated servers. To find an action:
1. `bc_actions_search` with `SearchMode: keyword` and a few field/entity keywords
(e.g. `"project, repository, gitHubRepository"`), filtered by `ActionType` if known.
2. `bc_actions_describe` on the exact name it returns, to get the callable schema.
3. `bc_actions_invoke` with matching `RequestParameters`.
Expected naming convention - **reverify per call with `bc_actions_search`, do not
assume it holds**: `List_<Entity>_PAG<id>` (read), `Modify_<Entity>_PAG<id>` (update,
**singular** entity name - e.g. `Modify_ProjectRepository_PAG6102904`, not
`ModifyProjectRepositories` or `ListUpdate...`), `Create_<Entity>_PAG<id>` (create).
| Entity (page) | Read | Write you MAY do | Never |
| Entity (page) | Tools | Write you MAY do | Never |
| --- | --- | --- | --- |
| projects (6102901) | active projects, `Status = Started` | **read-only for the agent** | any field — humans manage projects |
| projectRepositories (6102904) | project + gitHubRepository | `gitHubRepository` | all other fields |
| activeTasks (6102900) | active sub-tasks, `Accepted` / `In progress` | `gitHubDevStatus`, `gitHubBranch` | other fields, create, delete |
| newTasks (6102905) | pending sub-tasks, `Created` (awaiting customer approval) | create new task | `status` — always Created on insert, never change it |
| taskComments (6102902) | comment lines for a task | create a comment, edit `comment`/`date`/`lineType` | delete |
| consultants (PAG50009) | CURABIS employees: `userID`, `name`, `employeeCode`, `production`, `costPrHourLCY` | **read-only** | any write |
| projects (6102901) | `List_Projects_PAG6102901` | **read-only for the agent** | any field — humans manage projects; no Modify tool exists |
| projectRepositories (6102904) | `List_ProjectRepositories_PAG6102904`, `Modify_ProjectRepository_PAG6102904` | `gitHubRepository` | all other fields |
| activeTasks (6102900) | `List_ActiveTasks_PAG6102900`, `Modify_ActiveTask_PAG6102900` | `gitHubDevStatus`, `gitHubBranch`, `taskResponsible` (added 2026-07-30 — task reassignment between consultants, same category as the two GitHub fields) | other fields, create, delete |
| newTasks (6102905) | `List_NewTasks_PAG6102905`, `Create_NewTask_PAG6102905` | create new task | `status` — always Created on insert, never change it |
| taskComments (6102902) | `List_TaskComments_PAG6102902`, `Create_TaskComment_PAG6102902`, `Modify_TaskComment_PAG6102902` | create a comment, edit `comment`/`date`/`lineType` | delete |
| consultants (PAG50009) | `List_Consultants_PAG50009` | **read-only** | any write; no Modify/Create tool exists |
| projectAIScores (6102906) | `List_ProjectAIScores_PAG6102906`, `Create_ProjectAIScore_PAG6102906` | **Edison only** — insert one score entry per eval iteration | modify, delete (immutable posting table — no such tool exists); any other agent inserting here |
| projectWeberScores (6102908) | `List_ProjectWeberScores_PAG6102908`, `Create_ProjectWeberScore_PAG6102908` | **Weber only** — insert one classification per sub-task coached | modify, delete (immutable posting table — no such tool exists); any other agent inserting here |
`consultants` was previously documented here as `users (6102903)` — that entity does not exist
in the BC MCP action catalog under any name or search term. The real page is
`List_Consultants_PAG50009` (corrected 2026-07-24 after `users`/`employee` searches returned
nothing). Reverify with `bc_actions_search` before relying on either name — this correction
itself may drift. `costPrHourLCY` is an internal billing-rate field — never surface it to a
in the BC MCP action catalog under any name. The real page is `List_Consultants_PAG50009`
(corrected 2026-07-24). Now that tool names are static and directly listed above, this class
of drift cannot recur — the name IS the tool, not a search result to reverify. `costPrHourLCY`
(on `List_Consultants_PAG50009`) is an internal billing-rate field — never surface it to a
customer, and include it in internal summaries only when specifically relevant.
`gitHubDevStatus` uses enum **CUR GitHub Dev Status**: `Backlog`, `In Progress`, `Done`,
@ -116,12 +115,12 @@ Moving to `Accepted` requires `Starting date`, `Estimated time` and `Expected De
## Standard workflow
1. **Find the work.** Read `activeTasks` (filter by `projectNo` or `gitHubRepository`). Use
`gitHubRepository` on the project to confirm you are in the right repo.
2. **Claim it.** When you start, set `gitHubBranch` to the working branch and
`gitHubDevStatus = In Progress` on the task (search `bc_actions_search` for the
modify action on `activeTasks` - expect `Modify_ActiveTask_PAG6102900`, reverify).
3. **Record progress.** Post a status note with `Create taskComments`
1. **Find the work.** Call `List_ActiveTasks_PAG6102900` (filter by `projectNo` or
`gitHubRepository`). Use `gitHubRepository` on the project to confirm you are in
the right repo.
2. **Claim it.** When you start, call `Modify_ActiveTask_PAG6102900` to set
`gitHubBranch` to the working branch and `gitHubDevStatus = In Progress`.
3. **Record progress.** Call `Create_TaskComment_PAG6102902`
(`projectNo` + `subTaskNo` scope it to one task). Keep notes short and factual.
4. **Finish.** Set `gitHubDevStatus = Done` automatically when branch is merged to main.
Set `On Hold` if the branch is parked.
@ -131,8 +130,9 @@ Moving to `Accepted` requires `Starting date`, `Estimated time` and `Expected De
Use `Create_NewTask_PAG6102905` when a developer wants to register a new task from VS Code.
Follow ALL steps — do not skip any:
1. **Duplicate check.** Search `activeTasks` and `newTasks` for similar descriptions on the same
project. If a match is found, show it and ask the developer to confirm it is truly a new task.
1. **Duplicate check.** Call `List_ActiveTasks_PAG6102900` and `List_NewTasks_PAG6102905` for
similar descriptions on the same project. If a match is found, show it and ask the developer
to confirm it is truly a new task.
2. **Ask clarifying questions.** Before estimating, ask: What is the expected outcome? What is
the scope? Are there dependencies or unknowns? Summarise the answers as line-level comments.
3. **Propose an estimate.** Based on the summary, suggest estimated hours with reasoning.
@ -140,8 +140,8 @@ Follow ALL steps — do not skip any:
4. **Link to repo.** Set `gitHubRepository` from `git remote get-url origin`. Verify it matches
the project's `gitHubRepository` via `projectRepositories`.
5. **Set responsible.** Resolve the developer's `employeeCode` from `consultants` via `git config user.email`.
6. **Create.** POST to `newTasks` with: `projectNo`, `description`, `taskType`, `taskResponsible`,
`estimatedTime`, `startingDate`, `expectedDelivery`, `customerPriority`.
6. **Create.** Call `Create_NewTask_PAG6102905` with: `projectNo`, `description`, `taskType`,
`taskResponsible`, `estimatedTime`, `startingDate`, `expectedDelivery`, `customerPriority`.
Status is always `Created` — the page enforces this.
7. **Inform.** Tell the developer the task is created and awaiting customer approval in BC
before work can begin.
@ -165,9 +165,15 @@ If no matching user is found, say so - do not guess whose tasks these are.
## Safety rules
CURABIS-BCMCP-001 Write only `gitHubBranch` / `gitHubDevStatus` on active tasks, and task comments.
Never write BC sub-task `status` — it controls time registration and invoicing. Never modify
any other field, never create/delete projects, never delete tasks or comments.
CURABIS-BCMCP-001 Write only `gitHubBranch` / `gitHubDevStatus` / `taskResponsible` on active
tasks, and task comments. Never write BC sub-task `status` — it controls time registration
and invoicing. Never modify any other field, never create/delete projects, never delete
tasks or comments.
CURABIS-BCMCP-008 `projectAIScores` and `projectWeberScores` are immutable posting logs —
insert-only (no Modify/Delete tool exists for either). Only Edison inserts to
`projectAIScores`; only Weber inserts to `projectWeberScores`. An agent acting in any
other capacity must not call `Create_ProjectAIScore_PAG6102906` or
`Create_ProjectWeberScore_PAG6102908`.
CURABIS-BCMCP-006 Never start a task that is not `Accepted`. Before setting `gitHubDevStatus =
In Progress`, verify the task appears in `activeTasks` (Status = Accepted or In progress).
A task in `newTasks` (Status = Created) has not been approved — do not begin work on it.