mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-07 01:46:53 +01:00
Add new action skills for AL testing and documentation
- Introduced `al-test-writer` to generate AL test codeunits for production objects based on TDD principles. - Added `al-userguide-test-writer` to create test codeunits from user guide steps, mapping actions and assertions. - Implemented `bc-extension-test-guide` to generate a comprehensive TEST_GUIDE.md for Business Central extensions, covering various categories. - Created `bc-webclient-runner` to automate UI testing of the Business Central web client, capturing screenshots and asserting UI states. - Developed `page-scripting-e2e` to produce a recording plan for Page Scripting, ensuring a structured approach to browser-level testing.
This commit is contained in:
parent
822cae1b27
commit
07140e2223
76 changed files with 4353 additions and 6 deletions
|
|
@ -0,0 +1,60 @@
|
|||
// Anti-pattern: an unstable, over-broad agent surface. The version is not a
|
||||
// fixed contract, the entity is exposed through a part page the agent cannot
|
||||
// see, and every write is left open. Demonstration-only.
|
||||
|
||||
// Smell 1: a ListPart can never surface as an agent tool. Only top-level API
|
||||
// pages are picked up, so this entity is silently unreachable.
|
||||
page 50121 "Agent Customer Part"
|
||||
{
|
||||
PageType = ListPart;
|
||||
SourceTable = Customer;
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
repeater(Group)
|
||||
{
|
||||
field(no; Rec."No.") { }
|
||||
field(name; Rec.Name) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Smell 2: a top-level API page that is over-broad and unstable.
|
||||
page 50122 "Agent Vendor API"
|
||||
{
|
||||
PageType = API;
|
||||
SourceTable = Vendor;
|
||||
APIPublisher = 'contoso';
|
||||
APIGroup = 'purchasing';
|
||||
// The version is bumped in place on each change instead of adding a new one,
|
||||
// so every tool the agent discovered against v1.0 stops resolving.
|
||||
APIVersion = 'v2.0';
|
||||
EntityName = 'vendor';
|
||||
EntitySetName = 'vendors';
|
||||
|
||||
// Nothing is locked down. A read-only agent workflow still gets create,
|
||||
// modify, and delete tools, so a mistaken or prompt-injected agent can
|
||||
// mutate or delete vendors it had no business touching.
|
||||
// InsertAllowed / ModifyAllowed / DeleteAllowed left at permissive defaults.
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
repeater(Group)
|
||||
{
|
||||
// The entire table sprayed out, far beyond what the agent needs,
|
||||
// widening the schema and the write blast radius for no reason.
|
||||
field(no; Rec."No.") { }
|
||||
field(name; Rec.Name) { }
|
||||
field(blocked; Rec.Blocked) { }
|
||||
field(balance; Rec."Balance (LCY)") { }
|
||||
field(iban; Rec.IBAN) { }
|
||||
field(paymentTerms; Rec."Payment Terms Code") { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// Best practice: a top-level API page whose entity names and version form a
|
||||
// stable contract for an external agent, shaped read-only because the agent's
|
||||
// workflow only needs to read. Demonstration-only; not a deployed object.
|
||||
|
||||
page 50120 "Agent Customer API"
|
||||
{
|
||||
// Only top-level PageType = API pages surface as agent tools. A ListPart or
|
||||
// CardPart here would be silently unreachable by the agent.
|
||||
PageType = API;
|
||||
SourceTable = Customer;
|
||||
|
||||
// These five properties are the contract. They are fixed literals, so the
|
||||
// route and the tool names the agent binds to never shift under it.
|
||||
APIPublisher = 'contoso';
|
||||
APIGroup = 'sales';
|
||||
APIVersion = 'v1.0';
|
||||
// EntityName and EntitySetName become the singular/plural tool names. They
|
||||
// use the agent's domain vocabulary, not the table's internal captions.
|
||||
EntityName = 'customer';
|
||||
EntitySetName = 'customers';
|
||||
|
||||
// The agent only reads, so the whole surface is locked to read. A read tool
|
||||
// can never mutate, no matter what the prompt asks for.
|
||||
Editable = false;
|
||||
InsertAllowed = false;
|
||||
ModifyAllowed = false;
|
||||
DeleteAllowed = false;
|
||||
|
||||
layout
|
||||
{
|
||||
area(Content)
|
||||
{
|
||||
repeater(Group)
|
||||
{
|
||||
// A narrow, deliberately chosen field set: exactly what the
|
||||
// agent reasons over, named for the agent, nothing more.
|
||||
field(number; Rec."No.") { Caption = 'number'; }
|
||||
field(displayName; Rec.Name) { Caption = 'displayName'; }
|
||||
field(city; Rec.City) { Caption = 'city'; }
|
||||
field(balanceDue; Rec."Balance Due (LCY)") { Caption = 'balanceDue'; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: api
|
||||
keywords: [api-page, external-agent, mcp, copilot-studio, entity, api-version, top-level-page]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Expose BC entities as API pages for external agents
|
||||
|
||||
## Description
|
||||
|
||||
An external agent (Copilot Studio, Claude, ChatGPT, or a custom agent) can only reach Business Central data and operations that are modelled as API pages. The agent never sees the table directly: it sees the API pages the tenant chooses to expose, each becoming a tool with a name derived from its entity and operations. Deciding which entities and which operations are reachable is therefore a design step, not an afterthought, because the published surface is simultaneously the agent's capability list and its blast radius. Stable entity names and explicit API versions matter because they are the contract the agent's tools are built on, and a rename or version bump silently breaks every tool the agent already discovered against the old names.
|
||||
|
||||
The mechanism is the page's metadata. A top-level page with `PageType = API` and a fixed `APIPublisher`, `APIGroup`, and `APIVersion` is addressable at a stable route, and its `EntityName` and `EntitySetName` become the singular and plural tool names the agent binds to. Those five properties are the contract. The fields in the repeater are the schema the agent reasons over, so they should be named for the agent's domain vocabulary, not for the underlying table's field captions.
|
||||
|
||||
## Best Practice
|
||||
|
||||
For each entity an external agent must reach, define a top-level API page with a deliberate `EntityName`, `EntitySetName`, `APIPublisher`, `APIGroup`, and `APIVersion`, and treat those names and the version as a frozen contract: add a new `APIVersion` for breaking changes instead of mutating the existing one, so old tools keep resolving. Model only the operations the agent's workflow needs, setting `Editable = false` and `InsertAllowed`, `ModifyAllowed`, and `DeleteAllowed` to false when the agent only reads, so a read tool can never mutate. Keep the field set narrow and named in the agent's vocabulary so tool discovery is predictable. Choose entities and operations to match one specific agent audience rather than publishing a single broad surface for every possible client. See `expose-bc-entities-as-api-pages-for-external-agents.good.al` for a stable read-shaped API page and `expose-bc-entities-as-api-pages-for-external-agents.bad.al` for the unstable, over-broad form.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Pointing an external agent at a `ListPart` or `CardPart` page, or at a non-API page, and expecting it to surface as a tool: only top-level API pages are picked up, so the entity is silently unreachable. The fix is a top-level API page wrapping the same source table. Other smells: renaming an exposed entity or bumping its `APIVersion` in place, which breaks the agent's existing tools; or exposing a sprawling set of entities and write operations the agent does not use. The detection signal: an external-agent integration that depends on a part-subtype API page, an API page whose `EntityName` or `APIVersion` is parameterised or computed rather than a fixed literal, or a read-only agent pointed at a page that leaves `ModifyAllowed` and `DeleteAllowed` at their permissive defaults.
|
||||
|
||||
## See also
|
||||
|
||||
- `scope-mcp-server-tools-to-least-privilege.md`
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
bc-version: [all]
|
||||
domain: api
|
||||
keywords: [mcp, mcp-server, least-privilege, allow-create, unblock-edit-tools, read-only, configuration]
|
||||
technologies: [al]
|
||||
countries: [w1]
|
||||
application-area: [all]
|
||||
---
|
||||
|
||||
# Scope MCP server tools to least privilege
|
||||
|
||||
## Description
|
||||
|
||||
Business Central's product MCP server exposes selected API pages as tools to outside AI clients. Each configuration controls which API pages an agent sees and what it may do with them: read, create, modify, delete, and bound actions. Because every operation runs as the signed-in user's identity and lands in the audit trail under that name, the tool surface is a privilege surface, and the agent inherits exactly the permission set of whoever signed it in. An agent can never do less than its configuration allows but never more than the user can do; the configuration is the ceiling and the user's permissions are the floor.
|
||||
|
||||
The mechanism has two gates. A newly added page is read-only by default, and turning on any write requires both the configuration-level `Unblock Edit Tools` master switch and the specific per-page create, modify, or delete permission. Least privilege means leaving both gates shut except on the exact pages and operations the agent's workflow actually exercises, so the published tool surface is the smallest set that still lets the workflow succeed.
|
||||
|
||||
## Best Practice
|
||||
|
||||
Create one configuration per intended audience (for example a sales configuration and a warehouse configuration) rather than a single broad configuration shared by every client, so each audience's surface can be reasoned about and revoked on its own. Leave every API page read-only by default and enable create, modify, or delete one entity at a time, only when the agent's workflow requires it, setting both `Unblock Edit Tools` and the per-page permission deliberately rather than as a blanket flip. Document each configuration's audience and intended use, and review quarterly who has access and what is enabled, pruning any write that the workflow no longer exercises. Turn on Dynamic Tool Mode for any configuration that grows large so the surface stays within client tool caps without widening permissions.
|
||||
|
||||
## Anti Pattern
|
||||
|
||||
Building one mega-configuration that exposes many entities with write enabled "just in case", or flipping `Unblock Edit Tools` on at the configuration level with broad per-page create, modify, and delete permissions the agent never uses. Because the agent acts as the signed-in user, an over-broad surface lets a prompt-injected or mistaken agent modify or delete data it had no business touching, all under that user's identity in the audit log. The signal to look for: an MCP configuration with write operations enabled on pages the documented agent workflow does not require, or a single configuration serving multiple unrelated audiences.
|
||||
|
||||
## See also
|
||||
|
||||
- `expose-bc-entities-as-api-pages-for-external-agents.md`
|
||||
Loading…
Add table
Add a link
Reference in a new issue