bc-webclient-mcp
Version:
Model Context Protocol (MCP) server for Microsoft Dynamics 365 Business Central via WebUI protocol. Enables AI assistants to interact with BC through the web client protocol, supporting Card, List, and Document pages with full line item support and server
211 lines • 6.22 kB
TypeScript
/**
* FormState Service
*
* Manages BC form metadata, control tree parsing, field indexing,
* and field/button resolution for CRUD operations.
*
* Critical requirements:
* - LoadForm MUST be called after FormToShow before field interactions
* - Field resolution uses multi-index (Caption, ScopedCaption, SourceExpr, Name)
* - oldValue for SaveValue comes from FormState.node.value.formatted
* - Dialog buttons are resolved semantically by intent (yes/no/ok/cancel)
*/
import { FormState, FieldResolveOptions, FieldResolveResult, ButtonIntent, ButtonSelectResult, FormStateCacheConfig } from '../types/form-state.js';
/**
* BC Form structure from FormToShow event
*/
interface FormToShowData {
ServerId?: string;
Caption?: string;
DesignName?: string;
Children?: BcControl[];
children?: BcControl[];
}
/**
* BC Control structure for form tree
*/
interface BcControl {
Caption?: string;
caption?: string;
Name?: string;
name?: string;
SourceExpr?: string;
sourceExpr?: string;
SourceExpression?: string;
Kind?: string;
kind?: string;
Type?: string;
type?: string;
Editable?: boolean;
Visible?: boolean;
IsPrimary?: boolean;
isPrimary?: boolean;
IsDefault?: boolean;
isDefault?: boolean;
Value?: unknown;
FormattedValue?: string;
Metadata?: Record<string, unknown>;
Controls?: BcControl[];
controls?: BcControl[];
Children?: BcControl[];
children?: BcControl[];
}
/**
* BC Change object from LogicalClientChangeHandler
*/
interface BcChange {
t?: string;
type?: string;
ControlReference?: ControlReference;
controlReference?: ControlReference;
Properties?: Record<string, unknown>;
properties?: Record<string, unknown>;
Caption?: string;
Name?: string;
Editable?: boolean;
Visible?: boolean;
Value?: unknown;
FormattedValue?: string;
Control?: BcControl;
ParentPath?: string;
RowChanges?: unknown[];
rowChanges?: unknown[];
Controls?: BcControl[];
controls?: BcControl[];
RootControls?: BcControl[];
rootControls?: BcControl[];
}
/**
* Control reference for identifying controls in changes
*/
interface ControlReference {
controlPath?: string;
ControlPath?: string;
}
/**
* FormState Service - manages form metadata and field resolution
*/
export declare class FormStateService {
private formStates;
private config;
constructor(config?: Partial<FormStateCacheConfig>);
/**
* Create empty FormState for a new form
*/
createFormState(formId: string): FormState;
/**
* Get FormState for a form
*/
getFormState(formId: string): FormState | undefined;
/**
* Get or create FormState
*/
getOrCreateFormState(formId: string): FormState;
/**
* Delete FormState (e.g., on FormClosed)
*/
deleteFormState(formId: string): boolean;
/**
* Initialize FormState from FormToShow data (from OpenForm response)
*
* FormToShow contains the complete form structure in parameters[1]:
* - ServerId: form ID
* - Children: array of top-level controls
* - Caption, DesignName, etc.
*
* This must be called BEFORE applyChanges() to establish the control tree.
*/
initFromFormToShow(formId: string, formToShowData: FormToShowData | null | undefined): void;
/**
* Clear all FormStates (e.g., on sessionKey rotation)
*/
clearAll(): void;
/**
* Apply changes from DN.LogicalClientChangeHandler to FormState
*
* This is the critical function that parses LoadForm responses
* and builds the control tree.
*/
applyChanges(formId: string, changes: BcChange | BcChange[] | null | undefined): void;
/**
* Apply a single change object to FormState
*/
private applySingleChange;
/**
* Apply property changes to existing controls
*/
private applyPropertyChanges;
/**
* Apply control change (add/modify control)
*/
private applyControlChange;
/**
* Apply data refresh (field values, repeater data)
*/
private applyDataRefresh;
/**
* Apply full form update (initial structure)
*/
private applyFullUpdate;
/**
* Parse array of controls into control tree
*/
private parseControls;
/**
* Parse single control into ControlNode
*/
private parseControl;
/**
* Resolve control path from ControlReference object
*/
private resolveControlPath;
/**
* Build field indices after LoadForm completes
*
* MUST be called after all DN.LogicalClientChangeHandler messages
* for the LoadForm request have been processed.
*/
buildIndices(formId: string): void;
/**
* Index a single control node (recursive DFS)
*/
private indexNode;
/**
* Add to index with duplicate tracking
*/
private addToIndex;
/** Try to resolve by SourceExpr (e.g., [Customer.Email]) */
private tryResolveBySourceExpr;
/** Try to resolve by scoped caption (e.g., "General > Name") */
private tryResolveByScopedCaption;
/** Try to resolve by unscoped caption with duplicate handling */
private tryResolveByCaption;
/** Try to resolve by control name */
private tryResolveByName;
/**
* Resolve field name/caption to control path
*
* Supports:
* - Unscoped caption: "Email"
* - Scoped caption: "General > Name" or "Address/City"
* - SourceExpr override: "[Customer.Email]"
* - Control name: field name from metadata
*/
resolveField(formId: string, userKey: string, options?: FieldResolveOptions): FieldResolveResult | null;
/**
* Filter candidates based on heuristics
*/
private filterCandidates;
/**
* Select a dialog button by semantic intent
*
* Used for confirmation dialogs (delete, save, etc.)
*/
selectDialogButton(formId: string, intent: ButtonIntent): ButtonSelectResult | null;
/**
* Evict old FormState entries when cache size exceeds limit
*/
private evictOldEntries;
}
export {};
//# sourceMappingURL=form-state-service.d.ts.map