[MCP] Server Features in MCP configuration: API Tools, Dynamic Tool Mode, Data Query Tools (Preview)#8085
[MCP] Server Features in MCP configuration: API Tools, Dynamic Tool Mode, Data Query Tools (Preview)#8085onbuyuka wants to merge 21 commits into
Conversation
…n card UX-only prototype for the upcoming AL Query MCP server. Replaces the prior AL Query Server fasttab with a generic Server Features list-part on the card (Activate / Deactivate / Configure actions per row, mirroring page 7775 "Copilot AI Capabilities") and a per-feature StandardDialog for settings. Tool Mode toggles (EnableDynamicToolMode + DiscoverReadOnlyObjects) stay on the General fasttab — they're contract modifiers, not features. System Tools sub-page now uses a push pattern (parent calls Reload(...)) instead of the prior pull/SubPageLink approach, fixing a render-collapsed bug that affected newly-visible parts. No real runtime, no platform fields, no public API, no Export/Import or telemetry round-trip, no automated tests. See al-query-mock.md "Next steps to production" for the full path to shipping. New objects: - enum 8351 "MCP Server Feature" - table 8355 "MCP Server Feature" (temp) - page 8368 "MCP Server Feature List" - page 8369 "MCP Server Feature Settings" Fixes AB#631012 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…12-al-query-mcp-config-ui
Three empty-body procedures on codeunit 8350 "MCP Config" mirror the existing EnableDynamicToolMode / EnableDiscoverReadOnlyObjects shapes for the prototype's AL Query Server feature: EnableALQueryServer, SetALQueryMaxRowsPerQuery, and SetALQueryTimeoutSeconds. Each has a // MOCK: comment in the implementation spelling out the GetBySystemId → assign → Modify → LogConfigurationModified flow to wire up once the platform adds the real fields. MCPServerFeatureSettings.SaveChanges() is restored as an empty stub with a commented-out routing-through-the-facade template, and OpenSettings re-gates the call on Action::OK so the OK/Cancel contract is preserved. Six minimal MCPConfigTest tests verify each facade procedure is callable; each is // MOCK commented and just calls the API. Replace with real state assertions once the platform-side fields exist. Fixes AB#631012 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
AL Documentation Audit
Documentation gaps were detected in the following apps:
- MCP-Test: 0% documentation coverage
- MCP: 67% documentation coverage
To generate documentation, run /al-docs init or /al-docs update using GitHub Copilot CLI or Claude Code.
This review is for awareness to help keep documentation in sync with code changes. It is okay to dismiss this request.
Removes docs/features/al-query-mcp-config-ui/design.md and src/System Application/App/MCP/docs/al-query-mock.md from the PR. The code + // MOCK: comments are the source of truth going forward. Fixes AB#631012 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…12-al-query-mcp-config-ui
…ature) - Renamed "AL Query Server" -> "AL Query Tools" everywhere (enum value identifier and caption `'AL Query Tools (Preview)'`, facade procedure EnableALQueryServer -> EnableALQueryTools, test names, comments). - Dynamic Tool Mode is back in the Server Features list as a feature row (enum ordinal 0, replacing the blank value introduced earlier). Removed EnableDynamicToolMode and DiscoverReadOnlyObjects fields from the card's General fasttab, along with the Tool Modes description block and its labels. Discover Additional Read-Only Objects is now exposed in the Configure dialog under Dynamic Tool Mode (loaded from / saved to Rec.DiscoverReadOnlyObjects on OK). The cascade "deactivating Dynamic Tool Mode clears DiscoverReadOnlyObjects" lives in the row's SetActive. LoadSystemTools tags the search/describe/invoke system tools with the Dynamic Tool Mode enum value so every System Tools row has a concrete feature label. - MCPConfigToolList caption: 'Available Tools' -> 'API Tools'. All three CUD actions (SelectTools, AddToolsByAPIGroup, AddStandardAPITools) guard on `Enabled = not IsConfigActive`. New internal SetConfigActive(IsActive) procedure accepts a push from MCPConfigCard.RefreshSubPages. The redraw is triggered by CurrPage.Update() at the end of Active.OnValidate on the card, which cascades through UpdatePropagation = Both. - System Tools list-part moved from area(Content) to area(FactBoxes). - Restored docs/features/al-query-mcp-config-ui/design.md with D21-D24, iteration entries 10-13, and lessons 10-11 covering the above changes. Fixes AB#631012 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e APIs" - enum 8351: API Tools (0), Dynamic Tool Mode (1), AL Query Tools (2) - Server Features list seeds an API Tools row; activation is a page-local mock (APIToolsActiveLocal) exposed via IsAPIToolsActive() - Dynamic Tool Mode pre-flights API Tools (APIToolsRequiredForDynamicErr) - Card: "Available APIs" sub-part gated on `not IsDefault and APIToolsActive` - page 8352 caption 'API Tools' -> 'Available APIs'; description labels updated - design.md D25 / Appendix C #14 corrected to match the code (no AllowProdChanges relocation; API Tools Configure has no sub-settings yet) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce interface "MCP Feature Handler" implemented by enum "MCP Server Feature"; each feature is a handler codeunit (MCP API Tools Feature 8369, MCP AL Query Tools Feature 8368, MCP Dyn. Tool Mode Feature 8370). The list page resolves a handler and dispatches instead of a per-feature case, and Reload walks the enum's Ordinals()/FromInteger() so adding a feature needs no page edit. - Description, activation, settings, and the Dynamic-Tool-Mode-requires- API-Tools pre-flight live in the handlers; the page holds no per-feature data - temp table gains Configurable (= Handler.HasSettings()); Configure gates on it - Dynamic Tool Mode handler is real; API Tools + AL Query are platform-pending stubs (IsActive returns false until the MCP Configuration booleans ship) - drop AL Query Tools mock sub-settings (Max Rows / Query Timeout) and the SetALQueryMaxRowsPerQuery / SetALQueryTimeoutSeconds facade + impl + 4 tests - extend MCP/app.json idRanges to 8365-8370 for the handler codeunits - design.md D26 + D27 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tem tools - rename interface "MCP Feature Handler" -> "MCP Server Features" - handlers' SetActive delegate to MCP Config Implementation (EnableAPITools added to facade + impl, mock); the "API Tools required for Dynamic Tool Mode" gate moved into impl.EnableDynamicToolMode as a PLATFORM-PENDING commented check (inactive until the API Tools field is in symbols) - add LoadSystemTools to the interface; Dyn. Tool Mode emits the real GetSystemToolsInDynamicMode() catalog, AL Query the mock tools, API Tools none; remove impl.LoadSystemTools + InsertSystemTool + the Include* flags - MCP System Tool List.Reload(ConfigId) iterates the enum and dispatches to each active feature; card calls SystemToolList.Reload(Rec.SystemId) - rename interface vars Handler -> ServerFeature design.md D28-D29. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- API Tools facade smoke tests (EnableAPITools enable/disable) - Server Features TestPage tests on MCP Config Card: lists all three features, Configure gated to Dynamic Tool Mode, Activate flips status + sets EnableDynamicToolMode, part hidden on the default configuration - fix TestDefaultConfigurationPage: drop assertions on EnableDynamicToolMode / DiscoverReadOnlyObjects fields that D22 moved off the card (stale TestPage references that blocked the test codeunit from compiling) - design.md Appendix C #19 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te/onbuyuka/631012-al-query-mcp-config-ui
Continues the Server Features work with activation persistence and UX polish. - Persistence (mock): new internal table 8360 "MCP Feature Activation" holds the API Tools / AL Query Tools activation flags keyed by config SystemId, behind four MCP Config Implementation procedures (EnableAPITools/EnableALQueryTools and IsAPIToolsEnabled/IsALQueryToolsEnabled). These flags belong on the platform-owned MCP Configuration table, which the app can't extend; every mock site is tagged "// MOCK:" and Appendix E of the design doc lists the exact steps to remove them. - Gate: EnableDynamicToolMode now requires API Tools to be enabled first (APIToolsRequiredForDynamicErr) - previously a platform-pending stub. - Dynamic Tool Mode renders as an indented sub-feature of API Tools (interface TryGetParentFeature + MCP Server Feature.Indentation). - UI polish: align the curated list with "Available APIs" terminology (action and field renames), drop the Server Feature column from the System Tools factbox, and promote Export/Import on the config list. - Tests: enable API Tools before Dynamic Tool Mode so the new gate is satisfied. - Docs: design doc D30-D32, Appendix C #20-22, Appendix D #12-13, and a new Appendix E "Productionizing - removing the mocks". D32 / Appendix D #13 record the card list-part "always collapsed" investigation (BC platform limitation, AL #6258). AB#631012 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review follow-ups on top of d71487c: - Tag the MCPConfigCard.APIToolsActive page-local with "// MOCK:" so the productionizing grep surfaces it (it reads the mock activation table). The note is comment-only - the page-local code itself stays. - Correct two stale "// MOCK:" comments in MCP Config Test that still called the EnableAPITools / EnableALQueryTools facade procedures "no-ops with no persistence" - they persist to the mock "MCP Feature Activation" table now. - Shorten the Dynamic Tool Mode setting caption "Discover Additional Read-Only Objects" -> "Discover Read-Only Objects" (the longer one truncated in the dialog); the tooltip keeps the full meaning and it matches the config list page's default caption. - Design doc Appendix E: note the card + facade mock comments are comment-only and "// MOCK:"-tagged, and drop the contradictory "not a mock" bullet for APIToolsActive. AB#631012 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8356 is contiguous with table 8355 "MCP Server Feature" (8360 left a gap) and is a free table ID within the app's 8350-8362 idRange. Every reference to the table is by name, so there are no code-wiring changes - only the table declaration and the design doc's 8360 mentions. PR description updated to match. AB#631012 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
3a3217d to
49b399f
Compare
Implements the review feedback on the MCP configuration feature plus a follow-up quality pass. Feedback: - Default field is read-only on the card; Set as Default / Clear Default actions (promoted) are back on the configuration list. - Renamed AL Query Tools -> Data Query Tools (Preview) across the code: enum value, handler codeunit (MCP Data Query Tools Feature), facade/impl procedures, the mock activation field, and tests. The mock system-tool IDs compile_al_query / run_al_query are kept. - Select APIs opens one lookup over both API pages and API queries (new temp table "MCP API Object Buffer" + page "MCP API Object Lookup", populated by MCPConfigImplementation.LookupAPIObjects). The per-row Object Id lookup shares the same path. - Configuration list shows read-only API Tools / Data Query Tools status columns instead of Dynamic Tool Mode / Discover Read-Only Objects. - Permissions: registered the recently-added tables and the new buffer in "MCP - Objects" and granted tabledata on the persistent MCP Feature Activation (R/IMD) - these were missing and broke the feature for non-SUPER users. Quality pass: - Removed the now-orphaned per-type lookups (LookupAPIPageTools / LookupAPIQueryTools, pages "MCP API Config Tool Lookup" / "MCP Query Config Tool Lookup", their test-library wrappers and two tests) in favour of the unified lookup; one TestLookupAPIObjects smoke test replaces them. - "MCP API Object Buffer" declared TableType = Temporary (matching sibling buffers); temp-record variable naming aligned to the LinterCop convention (locals Temp-prefixed, parameters not). Design doc updated throughout (D33-D34, Appendix C #23-24, Appendix E, the AL Query -> Data Query naming note, lookup page renumber 8360 -> 8376). AB#631012 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…44811 Stages the productionized versions of the mocked activation persistence + Data Query Tools catalog next to each mock, so productionizing once BC-Platform PR #44811 lands is "uncomment + delete the mock". #44811 adds MCP Configuration.EnableApiTools (field 8, InitValue = true) + .EnableAlQueryTools (field 9) and "MCP Utilities".GetSystemToolsInDataQuery(). - MCP Config Implementation: commented real versions of the four activation procedures (read/write the platform fields). - MCP Data Query Tools Feature.LoadSystemTools: commented real version using GetSystemToolsInDataQuery(). - MCP Upgrade: commented EnableApiToolsOnExistingConfigurations (+ tag, registration, call) to set EnableApiTools := true on existing configurations (the field's InitValue only covers new ones). - MCP Config Test: commented real-assertion versions of the four activation tests. - MCP Config List: commented Rec.EnableApiTools / Rec.EnableAlQueryTools field bindings next to the function-sourced status columns; trimmed the redundant facade/card mock comments. All pre-staged blocks are tagged // PLATFORM-PENDING (BC-Platform PR #44811). Design doc Appendix E + D35 updated. UX keeps "Data Query Tools"; it maps onto the platform's EnableAlQueryTools field. AB#631012 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BC-Platform PR #44811 landed (MCP Configuration.EnableApiTools field 8 InitValue = true, .EnableAlQueryTools field 9, and "MCP Utilities".GetSystemToolsInDataQuery()), so this removes every mock and wires the feature to the real platform members. - MCP Config Implementation: the four activation procedures read/write the real EnableApiTools / EnableAlQueryTools fields (writes via LogConfigurationModified). Reads fall back to Init() for a not-yet-saved config so a new config reflects the InitValue default. - MCP Data Query Tools Feature.LoadSystemTools: consumes GetSystemToolsInDataQuery(). - MCP Upgrade: EnableApiToolsOnExistingConfigurations turns API Tools on for existing configs on upgrade (InitValue only covers new ones). - MCP Config List: API Tools / Data Query Tools status columns bind to the real fields. - Deleted mock table 8356 "MCP Feature Activation" + its permission-set entries. New-config fix: the card's Name.OnValidate persists the config (CurrPage.Update when IsNullGuid(SystemId)) the moment it's named, so its Server Features can be configured without first closing the card. Export/Import: now carries enableApiTools / enableAlQueryTools, with round-trip tests asserting both the JSON keys and the imported field values. MCP Config Test: the four activation tests assert the real field state. Design doc updated (Appendix E flipped to "completed"; decision log D36-D38). AB#631012 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ade, dialog telemetry Addresses automated review feedback on #8085: - LogConfigurationModified now records EnableApiTools / EnableAlQueryTools old/new values, so toggling those features emits field-level telemetry. - Disabling API Tools now cascades Dynamic Tool Mode (and Discover Read-Only Objects) off, since Dynamic Tool Mode requires API Tools -- mirroring the existing "disable Dynamic Tool Mode clears Discover Read-Only Objects" cascade. The Server Features part reloads all rows after a toggle so the cascaded row updates too. Added TestDisableAPIToolsDisablesDynamicToolMode. - MCP Server Feature Settings.SaveChanges routes its Discover Read-Only Objects write through MCPConfigImplementation.EnableDiscoverReadOnlyObjects (logged and gated) instead of a direct Modify. AB#631012 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clustered PK change on non-temporary table needs upgrade handlingThe clustered primary key of Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
No test for Dynamic Tool Mode guard when API Tools disabledA new guard was added to Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
Pages 8353 "MCP API Config Tool Lookup" and 8367 "MCP Query Config Tool Lookup" are public System Application objects present in the baseline; D34 deleted them, which breaks dependent extensions (build error AS0029). Restored both verbatim and marked ObsoleteState = Pending + ObsoleteReason (-> the unified MCP API Object Lookup, 8376) + ObsoleteTag = '29.0', wrapped in #if not CLEAN29 ... #endif (the repo's staged-removal convention). The internal MCP Config Implementation procedures and the isTest library wrappers stay deleted -- neither is public surface. Design doc: decision log D39, Appendix C #26, Appendix D #14. AB#631012 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Import can create inconsistent feature stateThe JSON import path sets Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
| if UpgradeTag.HasDatabaseUpgradeTag(GetMCPEnableApiToolsUpgradeTag()) then | ||
| exit; | ||
|
|
||
| MCPConfiguration.ModifyAll(EnableApiTools, true); |
There was a problem hiding this comment.
Upgrade ModifyAll bypasses audit telemetry
MCPConfiguration.ModifyAll(EnableApiTools, true) bulk-updates every configuration record without calling LogConfigurationModified, so the change is invisible to the telemetry pipeline and audit log. Every user-driven toggle of API Tools is individually audited, but this upgrade silently changes potentially hundreds of production configurations with no trace.
Recommendation:
- Iterate over the configurations and call
EnableAPITools(or log viaLogConfigurationModified) for each record, or at minimum emit a single telemetry event summarising how many records were updated.
| MCPConfiguration.ModifyAll(EnableApiTools, true); | |
| if MCPConfiguration.FindSet(true) then | |
| repeat | |
| if not MCPConfiguration.EnableApiTools then begin | |
| MCPConfiguration.EnableApiTools := true; | |
| MCPConfiguration.Modify(); | |
| end; | |
| until MCPConfiguration.Next() = 0; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| Rec.Copy(MCPAPIObjectBuffer, true); | ||
| end; | ||
|
|
||
| internal procedure GetSelectedObjects(var MCPAPIObjectBuffer: Record "MCP API Object Buffer") |
There was a problem hiding this comment.
GetSelectedObjects silently drops insert failures
if MCPAPIObjectBuffer.Insert() then; swallows all insert errors without feedback. If the caller passes a pre-populated buffer (or the same object is selected twice in multi-select), the silent discard makes it impossible to distinguish between 'nothing was selected' and 'all inserts failed due to duplicates', and the return value of LookupAPIObjects will be misleadingly true.
Recommendation:
- Either clear the output buffer before populating it (so duplicate keys cannot arise) or replace the silent
Insert()with a plainInsert()call that will surface conflicts explicitly.
| internal procedure GetSelectedObjects(var MCPAPIObjectBuffer: Record "MCP API Object Buffer") | |
| internal procedure GetSelectedObjects(var MCPAPIObjectBuffer: Record "MCP API Object Buffer") | |
| begin | |
| MCPAPIObjectBuffer.Reset(); | |
| MCPAPIObjectBuffer.DeleteAll(); | |
| CurrPage.SetSelectionFilter(Rec); | |
| if Rec.FindSet() then | |
| repeat | |
| MCPAPIObjectBuffer := Rec; | |
| MCPAPIObjectBuffer.Insert(); | |
| until Rec.Next() = 0; | |
| end; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| exit; | ||
|
|
||
| if Rec.Default then | ||
| MCPConfigImplementation.SetAsDefaultConfiguration(Rec.SystemId) |
There was a problem hiding this comment.
Default field read-only with no card-level actions
The Default field was changed to Editable = false and its tooltip directs users to 'Set as Default and Clear Default actions on the configuration list'. However, no such actions were added to the card page. Users who navigate directly to a configuration card — whether via a URL, a link from a related page, or the card's own navigation — have no way to change the default designation from that context.
Recommendation:
- Add
SetAsDefaultandClearDefaultactions to the card page (mirroring those added to the list page), promoting them to the Actions bar so the experience is consistent regardless of entry point.
| MCPConfigImplementation.SetAsDefaultConfiguration(Rec.SystemId) | |
| action(SetAsDefault) | |
| { | |
| Caption = 'Set as Default'; | |
| ToolTip = 'Set this configuration as the default.'; | |
| Image = Approve; | |
| Enabled = not Rec.Default and not IsDefault; | |
| trigger OnAction() | |
| begin | |
| MCPConfigImplementation.SetAsDefaultConfiguration(Rec.SystemId); | |
| IsDefault := false; | |
| CurrPage.Update(false); | |
| end; | |
| } |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| Feature := NewFeature; | ||
| end; | ||
|
|
||
| internal procedure SaveChanges() |
There was a problem hiding this comment.
SaveChanges case silently no-ops for new features
SaveChanges contains only a case Feature of Feature::"Dynamic Tool Mode" branch. If any future feature sets HasSettings() = true and OpenSettings is invoked, the user sees a dialog, can make changes, clicks OK — and SaveChanges silently discards everything without error, because no branch matches the new feature.
Recommendation:
- Add an
elsebranch to the case statement that raises an error so that missing implementations are caught during development rather than silently failing in production.
| internal procedure SaveChanges() | |
| case Feature of | |
| Feature::"Dynamic Tool Mode": | |
| MCPConfigImplementation.EnableDiscoverReadOnlyObjects(ConfigSystemId, DiscoverReadOnlyObjectsLocal); | |
| else | |
| Error('SaveChanges is not implemented for feature %1.', Feature); | |
| end; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| } | ||
|
|
||
| trigger OnOpenPage() | ||
| begin |
There was a problem hiding this comment.
Removed OnOpenPage auto-load breaks embedders
The OnOpenPage trigger that automatically loaded system tools was deleted. The page now requires callers to invoke the new Reload(ConfigSystemId) procedure explicitly. Any existing or future code that embeds MCP System Tool List as a part without calling Reload will silently display an empty list, with no compile-time or runtime warning.
Recommendation:
- Document the mandatory
Reloadcall contract prominently in an XML doc comment on the procedure, or restore a fallback inOnOpenPagethat loads tools for the embedded-without-init case (e.g., ifParentSystemIdis null, load all active features).
| begin | |
| /// <summary> | |
| /// Must be called by the host page before the part is displayed. | |
| /// Loads the system tools for all active features of the given configuration. | |
| /// </summary> | |
| internal procedure Reload(ConfigSystemId: Guid) |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| "target": "OnPrem", | ||
| "contextSensitiveHelpUrl": "https://learn.microsoft.com/dynamics365/business-central/" | ||
| } | ||
| } No newline at end of file |
There was a problem hiding this comment.
app.json trailing newline removed
The PR removes the trailing newline from app.json (\ No newline at end of file). This is a regression — the file previously ended correctly — and will generate unnecessary noise in future diffs. Some tooling (POSIX utilities, certain CI linters) treats files without a trailing newline as malformed.
Recommendation:
- Restore the trailing newline at the end of
app.json.
| } | |
| "contextSensitiveHelpUrl": "https://learn.microsoft.com/dynamics365/business-central/" | |
| } |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
…te/onbuyuka/631012-al-query-mcp-config-ui
| end; | ||
|
|
||
| internal procedure LookupAPIQueryTools(var QueryMetadata: Record "Query Metadata"): Boolean | ||
| local procedure PopulateAPIObjects(var MCPAPIObjectBuffer: Record "MCP API Object Buffer") |
There was a problem hiding this comment.
PopulateAPIObjects scans all pages and queries on every lookup
PopulateAPIObjects issues two full-table scans of Page Metadata and Query Metadata with filters on every invocation of the API object lookup. In a large environment with hundreds of API pages and queries, this can result in noticeable latency each time the admin opens the 'Select APIs' dialog with no caching.
Recommendation:
- Consider caching the buffer within the session or using a session-scoped temporary table populated only once (checking
if not TempBuffer.IsEmpty()before re-populating). Alternatively, document the expected scale and confirm performance is acceptable.
| local procedure PopulateAPIObjects(var MCPAPIObjectBuffer: Record "MCP API Object Buffer") | |
| // Check if already populated before scanning: | |
| internal procedure LookupAPIObjects(var SelectedObjects: Record "MCP API Object Buffer"): Boolean | |
| var | |
| TempMCPAPIObjectBuffer: Record "MCP API Object Buffer"; | |
| MCPAPIObjectLookup: Page "MCP API Object Lookup"; | |
| begin | |
| if CachedAPIObjectBuffer.IsEmpty() then // re-use session cache | |
| PopulateAPIObjects(CachedAPIObjectBuffer); | |
| ... |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| LogConfigurationModified(MCPConfiguration, xMCPConfiguration); | ||
| end; | ||
|
|
||
| internal procedure IsAPIToolsEnabled(ConfigId: Guid): Boolean |
There was a problem hiding this comment.
IsEnabled helpers silently return defaults for unknown GUIDs
IsAPIToolsEnabled and IsDataQueryToolsEnabled call MCPConfiguration.Init() and return the field's InitValue when the given ConfigId is not found, without any log or signal to the caller. A stale or wrong GUID passed by a bug would silently report the wrong active state, causing feature checks to make incorrect decisions.
Recommendation:
- Only fall back to
Init()when the GUID is explicitly null (new-record scenario). Returnfalseand log a telemetry warning for non-null GUIDs that are not found, making the unexpected path visible.
| internal procedure IsAPIToolsEnabled(ConfigId: Guid): Boolean | |
| internal procedure IsAPIToolsEnabled(ConfigId: Guid): Boolean | |
| var | |
| MCPConfiguration: Record "MCP Configuration"; | |
| begin | |
| if IsNullGuid(ConfigId) then begin | |
| MCPConfiguration.Init(); | |
| exit(MCPConfiguration.EnableApiTools); | |
| end; | |
| if not MCPConfiguration.GetBySystemId(ConfigId) then | |
| exit(false); // config not found - treat as inactive | |
| exit(MCPConfiguration.EnableApiTools); | |
| end; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
New configs created with API Tools disabled by default
Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
| if not Enable and IsDefaultConfiguration(MCPConfiguration) then | ||
| Error(DynamicToolModeCannotBeDisabledErr); | ||
|
|
||
| if Enable and not IsAPIToolsEnabled(ConfigId) then |
There was a problem hiding this comment.
EnableDynamicToolMode error message lacks actionability
The new error label APIToolsRequiredForDynamicErr reads 'API Tools must be enabled before Dynamic Tool Mode can be enabled.' but doesn't tell the user where or how to enable API Tools. For admins unfamiliar with the new Server Features list, this may be confusing.
Recommendation:
- Update the label to reference the Server Features list: e.g.
'API Tools must be enabled in the Server Features list before Dynamic Tool Mode can be activated.'
| if Enable and not IsAPIToolsEnabled(ConfigId) then | |
| APIToolsRequiredForDynamicErr: Label 'API Tools must be enabled in the Server Features list before Dynamic Tool Mode can be activated.'; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| begin | ||
| SystemTools := MCPUtilities.GetSystemToolsInDataQuery(); | ||
| foreach ToolName in SystemTools.Keys() do | ||
| InsertTool(MCPSystemTool, CopyStr(ToolName, 1, MaxStrLen(MCPSystemTool."Tool Name")), CopyStr(SystemTools.Get(ToolName), 1, MaxStrLen(MCPSystemTool."Tool Description"))); |
There was a problem hiding this comment.
LoadSystemTools tool description truncation silently drops data
CopyStr(SystemTools.Get(ToolName), 1, MaxStrLen(MCPSystemTool."Tool Description")) silently truncates tool descriptions longer than the field length (250 chars). If the platform later returns longer descriptions, the truncated text will be stored without any warning, potentially making descriptions unintelligible.
Recommendation:
- Assert or log a warning when the source string exceeds the target field length, or verify with the platform that descriptions will never exceed 250 characters and add a comment documenting that contract.
| InsertTool(MCPSystemTool, CopyStr(ToolName, 1, MaxStrLen(MCPSystemTool."Tool Name")), CopyStr(SystemTools.Get(ToolName), 1, MaxStrLen(MCPSystemTool."Tool Description"))); | |
| local procedure InsertTool(var MCPSystemTool: Record "MCP System Tool"; ToolName: Text[100]; ToolDescription: Text[250]) | |
| begin | |
| MCPSystemTool."Server Feature" := MCPSystemTool."Server Feature"::"Data Query Tools"; | |
| MCPSystemTool."Tool Name" := ToolName; | |
| MCPSystemTool."Tool Description" := ToolDescription; // platform guarantees max 250 chars | |
| MCPSystemTool.Insert(); | |
| end; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
Name OnValidate fires CurrPage.Update on every keystroke for new recordsThe Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
SystemToolList FactBox always reloads on cursor move
Recommendation:
Line mapping was unavailable, so this was posted as an issue comment. 👍 useful · ❤️ especially valuable · 👎 wrong - reply with why |
| { | ||
| action(SetAsDefault) | ||
| { | ||
| Caption = 'Set as Default'; |
There was a problem hiding this comment.
SetAsDefault / ClearDefault miss active-config guard
The new SetAsDefault and ClearDefault actions call SetAsDefaultConfiguration and ClearDefaultConfiguration directly without checking if the configuration is active first. SetAsDefaultConfiguration in the implementation presumably performs this check, but if a future refactor moves the guard, the list actions have no safety net.
Recommendation:
- Add
Enabled = Rec.ActivetoSetAsDefault(only active configs should be set as default, consistent with the tooltip text) and document the active requirement explicitly.
| Caption = 'Set as Default'; | |
| action(SetAsDefault) | |
| { | |
| Caption = 'Set as Default'; | |
| ToolTip = 'Set this configuration as the default. Only active configurations can be set as the default.'; | |
| Image = Approve; | |
| AccessByPermission = tabledata "MCP Configuration" = M; | |
| Scope = Repeater; | |
| Enabled = not Rec.Default and Rec.Active; | |
| ... |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| if not MCPConfigImplementation.CheckAPIToolExists(Rec.ID, TempSelectedObjects."Object ID", Rec."Object Type"::Page) then | ||
| MCPConfig.CreateAPITool(Rec.ID, TempSelectedObjects."Object ID"); | ||
| TempSelectedObjects."Object Type"::Query: | ||
| if not MCPConfigImplementation.CheckAPIToolExists(Rec.ID, TempSelectedObjects."Object ID", Rec."Object Type"::Query) then |
There was a problem hiding this comment.
AddAPIObjects deletes current rec unconditionally after lookup
At the end of AddAPIObjects(), if not IsNullGuid(Rec.SystemId) then Rec.Delete() deletes the current record when it has a SystemId. This is inherited from the old per-type lookup logic and assumes the 'lookup from an existing row' scenario deletes that row. If an admin clicks 'Select APIs' while positioned on a valid existing tool row (non-null SystemId), that row is silently deleted after the lookup completes.
Recommendation:
- Remove or clarify the
Rec.Delete()call. The delete was originally intended only for removing a blank new-row placeholder created by theOnLookuptrigger. Use a flag or only delete when called from theOnLookuptrigger path.
| if not MCPConfigImplementation.CheckAPIToolExists(Rec.ID, TempSelectedObjects."Object ID", Rec."Object Type"::Query) then | |
| local procedure AddAPIObjects(DeleteCurrentRow: Boolean) | |
| var | |
| TempSelectedObjects: Record "MCP API Object Buffer"; | |
| begin | |
| if not MCPConfigImplementation.LookupAPIObjects(TempSelectedObjects) then | |
| exit; | |
| ... | |
| if DeleteCurrentRow and not IsNullGuid(Rec.SystemId) then | |
| Rec.Delete(); | |
| CurrPage.Update(); | |
| end; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| area(Processing) | ||
| { | ||
| action(Activate) | ||
| { |
There was a problem hiding this comment.
Activate visible for Dynamic Tool Mode when parent inactive
The Activate action is Visible = ActionsEnabled and (Rec.Status = Rec.Status::Inactive) with no check that the parent feature (API Tools) is active. A user clicking Activate on the Dynamic Tool Mode row when API Tools is inactive receives an error instead of a gracefully disabled button.
Recommendation:
- Add a computed boolean variable
ParentFeatureActiveinReloadthat is set per-row, or extend theMCP Server Featuretable with aParentActivefield, and include it in theVisible/Enabledexpression for theActivateaction.
| { | |
| action(Activate) | |
| { | |
| ... | |
| Visible = ActionsEnabled and (Rec.Status = Rec.Status::Inactive); | |
| Enabled = ActionsEnabled and (Rec.Status = Rec.Status::Inactive) and (Rec.Indentation = 0 or ParentFeatureActive); | |
| ... |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| internal procedure SaveChanges() | ||
| var | ||
| MCPConfigImplementation: Codeunit "MCP Config Implementation"; | ||
| begin |
There was a problem hiding this comment.
SaveChanges silently no-ops for unhandled features
SaveChanges() uses a case Feature of with only "Dynamic Tool Mode" handled and no else branch. Any feature that gains HasSettings() = true in a future iteration will open the settings dialog but silently discard changes when the user clicks OK.
Recommendation:
- Add an
else Error(...)branch, or an assertion, so an unhandled feature triggers a developer-visible error rather than a silent no-op.
| begin | |
| internal procedure SaveChanges() | |
| var | |
| MCPConfigImplementation: Codeunit "MCP Config Implementation"; | |
| begin | |
| case Feature of | |
| Feature::"Dynamic Tool Mode": | |
| MCPConfigImplementation.EnableDiscoverReadOnlyObjects(ConfigSystemId, DiscoverReadOnlyObjectsLocal); | |
| else | |
| Error('Unhandled feature settings for: %1', Feature); | |
| end; | |
| end; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| ConfigId := CreateMCPConfig(false, false, true, false); | ||
| MCPConfig.EnableAPITools(ConfigId, false); | ||
| MCPConfiguration.GetBySystemId(ConfigId); | ||
| Assert.IsFalse(MCPConfiguration.EnableApiTools, 'API Tools should be disabled'); |
There was a problem hiding this comment.
TestDisableAPITools missing cascade-off verification for DiscoverReadOnly
TestDisableAPITools only asserts EnableApiTools = false but doesn't verify that EnableDynamicToolMode and DiscoverReadOnlyObjects also cascade off when API Tools is disabled on a config that had them enabled. This is tested in TestDisableAPIToolsDisablesDynamicToolMode but only for Dynamic Tool Mode; a separate test (or the same test) should also cover DiscoverReadOnlyObjects.
Recommendation:
- Merge the cascade assertion into
TestDisableAPITools, or add a dedicated test that starts withDynToolMode=true, DiscoverReadOnly=true, APITools=trueand verifies all three cascade off.
| Assert.IsFalse(MCPConfiguration.EnableApiTools, 'API Tools should be disabled'); | |
| [Test] | |
| procedure TestDisableAPIToolsCascadesDiscoverReadOnly() | |
| var | |
| MCPConfiguration: Record "MCP Configuration"; | |
| ConfigId: Guid; | |
| begin | |
| ConfigId := CreateMCPConfig(false, true, true, true); | |
| MCPConfig.EnableAPITools(ConfigId, true); | |
| MCPConfig.EnableAPITools(ConfigId, false); | |
| MCPConfiguration.GetBySystemId(ConfigId); | |
| Assert.IsFalse(MCPConfiguration.EnableDynamicToolMode, 'DynToolMode should cascade off'); | |
| Assert.IsFalse(MCPConfiguration.DiscoverReadOnlyObjects, 'DiscoverReadOnly should cascade off'); | |
| end; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
| exit('MS-612454-MCPSystemDefaultAsDefault-20260216'); | ||
| end; | ||
|
|
||
| local procedure GetMCPEnableApiToolsUpgradeTag(): Text[250] |
There was a problem hiding this comment.
Upgrade tag date mismatch with merge date
The upgrade tag value is 'MS-631012-MCPEnableApiTools-20260603' (June 3) but the PR description notes that the latest merge date is June 4 (2026-06-04). While functionally harmless, upgrade tags with dates earlier than the actual deployment can be confusing during incident triage.
Recommendation:
- Update the tag date to match the actual merge/deployment date, or use a date-independent suffix (e.g., the sprint or iteration number) to avoid this discrepancy.
| local procedure GetMCPEnableApiToolsUpgradeTag(): Text[250] | |
| local procedure GetMCPEnableApiToolsUpgradeTag(): Text[250] | |
| begin | |
| exit('MS-631012-MCPEnableApiTools-20260604'); | |
| end; |
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why
Summary
Adds a Server Features surface to the MCP configuration card so admins can opt configurations into the Data Query Tools (Preview) MCP runtime, alongside the existing Dynamic Tool Mode and API Tools controls. Each feature is a self-contained handler behind an interface, so the card is data-driven and extensible. Activation persists on the real
MCP Configurationfields shipped in BC-Platform #44811; only the Data Query Tools server runtime lands separately (see Platform dependency).Feature handlers (interface-based)
interface "MCP Server Features"—SetActive/IsActive/HasSettings/OpenSettings/Description/LoadSystemTools/TryGetParentFeature.enum "MCP Server Feature"implements it, binding each value to a handler codeunit; resolvingServerFeature := Rec.Featurereplaces the old per-featurecaseblocks.MCP Server Feature ListandMCP System Tool Listpages are fully generic: each walksServerFeature.Ordinals()+FromInteger(...)(the same pattern asMCP Config Warning Type) and dispatches every action — activation, settings, descriptions, and system-tool loading — through the handler. Adding a feature is a new enum value + handler codeunit — no page edits.SetActivedelegates tocodeunit "MCP Config Implementation", which reads/writes the realMCP Configurationfields (EnableDynamicToolMode,EnableApiTools,EnableAlQueryTools);IsActivereads them back.Card UX
Server Featureslist-part with per-row Activate / Deactivate / Configure, mirroringpage 7775 "Copilot AI Capabilities"→Copilot Capabilities GA. Configure appears only for features that have settings — gated on a per-rowConfigurableflag set from the handler'sHasSettings(). Dynamic Tool Mode renders indented as a sub-feature of API Tools (TryGetParentFeature+ anIndentationcolumn on the temp table).Visible = not IsDefault and APIToolsActive), where the admin curates which API pages / queries the client can reach. No settings.Discover Read-Only Objects; activation flows toEnableDynamicToolMode, deactivation cascade-clearsDiscoverReadOnlyObjects. Enabling it requires API Tools to be enabled first —impl.EnableDynamicToolModeenforces the gate (APIToolsRequiredForDynamicErr).page 8352) — the curated API list. ItsSelect APIsaction opens a single lookup listing both API pages and API queries (new temptable "MCP API Object Buffer"+page "MCP API Object Lookup", populated byimpl.LookupAPIObjects); the per-rowObject Idlookup shares the same path. The part was renamed'Available Tools'→'API Tools'→'Available APIs'to disambiguate it from the API Tools feature row.StandardDialog(page 8369 "MCP Server Feature Settings") is used only by Dynamic Tool Mode; its handler runs it viaOpenSettings, writing on OK.LoadSystemTools.Configuration list (
page 8350)Defaultfield is read-only on the card; Set as Default / Clear Default actions (promoted) live on the configuration list instead.Rec.EnableApiTools/Rec.EnableAlQueryTools) replaced the previous Dynamic Tool Mode / Discover Read-Only Objects columns.Public API
codeunit 8350 "MCP Config"exposesEnableAPIToolsandEnableDataQueryToolsalongside the existingEnableDynamicToolMode/EnableDiscoverReadOnlyObjects. The two feature procedures read/write theMCP Configuration.EnableApiTools/.EnableAlQueryToolsfields.New objects
interface "MCP Server Features"enum 8351 "MCP Server Feature"(implements "MCP Server Features";API Tools,Dynamic Tool Mode,Data Query Tools)table 8355 "MCP Server Feature"(temporary; carriesConfigurable+Indentation)table 8357 "MCP API Object Buffer"(temporary; unifies API pages + queries for the Select APIs lookup)page 8368 "MCP Server Feature List",page 8369 "MCP Server Feature Settings",page 8376 "MCP API Object Lookup"8369 "MCP API Tools Feature",8368 "MCP Data Query Tools Feature",8370 "MCP Dyn. Tool Mode Feature"Deprecated
8353 "MCP API Config Tool Lookup"/8367 "MCP Query Config Tool Lookup"are obsoleted (ObsoleteState = Pending,ObsoleteTag = '29.0', wrapped in#if not CLEAN29) — superseded by the unifiedMCP API Object Lookup(8376). They're public System Application objects, so they're deprecated rather than deleted (removing them breaks dependent extensions —AS0029). Their internalMCP Config Implementationlookup procedures (LookupAPIPageTools/LookupAPIQueryTools) and the test-library wrappers were removed (neither is public surface).Permissions
MCP - Objectslists the new app tables (MCP Server Feature,MCP API Object Buffer).Platform dependency
BC-Platform PR #44811 has merged, and this PR is productionized against it — no mocks remain (
grep -rn "MOCK:|PLATFORM-PENDING" "src/System Application/App/MCP/"is empty).MCP Configuration.EnableApiTools(field 8,InitValue = true) /.EnableAlQueryTools(field 9), read/written through fourMCP Config Implementationprocedures. An upgrade (MCP Upgrade.EnableApiToolsOnExistingConfigurations) turns API Tools on for existing configurations (InitValueonly covers new ones)."MCP Utilities".GetSystemToolsInDataQuery().The platform uses the "AL Query Tools" field name; our UX stays "Data Query Tools" (it maps onto
EnableAlQueryTools).Test plan
MCP Config Test(codeunit 130130): activation tests asserting the realEnableApiTools/EnableAlQueryToolsfield state; aServer FeaturesTestPage set onpage 8351 "MCP Config Card"(the list shows all three features in order,Configureis enabled only on Dynamic Tool Mode, activating Dynamic Tool Mode flips its row Status and setsEnableDynamicToolMode, and the part is hidden on the default configuration; API Tools is enabled before Dynamic Tool Mode so the gate is satisfied);TestLookupAPIObjectsexercising the unified API lookup; and Export/Import round-trip tests asserting the two new fields survive.Fixes AB#631012
🤖 Generated with Claude Code