F&O Studio AI Companion
A Visual Studio extension (VSIX) + MCP server that gives GitHub Copilot full D365FO X++ awareness — language knowledge, project context, and 25 metadata tools that create, read, update, validate, search, and manage X++ objects through the official MetaModel API. It is the Visual Studio sibling of F&O Studio for VS Code and shares its AI tools.
Requirements
| Requirement |
Why |
| Visual Studio 2022 17.14 or later, or Visual Studio 2026 (Community, Professional or Enterprise) |
Copilot agent mode with MCP servers arrived in 17.14 |
| GitHub Copilot (Visual Studio component, with a Copilot subscription) |
The tools are used from Copilot Chat in agent mode. The installer requires the component |
| Finance and Operations (Dynamics 365) tools for Visual Studio |
All metadata tools run through its MetaModel API. Install it from your environment (Power Platform Tools: connect and download the Finance and Operations assets) or use a cloud-hosted development VM. It is not on the Marketplace, so the installer cannot check it: the extension shows a warning in Visual Studio when it is missing |
| .NET Framework 4.8 |
Runtime of the extension and the MCP server |
| Cross-reference database (optional) |
xpp_find_references, xpp_find_examples and where-used checks need a build with Build cross reference data |
Problem Statement
GitHub Copilot has no built-in knowledge of X++. When a D365FO developer uses Copilot in Visual Studio, it:
- Does not understand X++ syntax, data types, or keywords (
ttsbegin, next, select forupdate, etc.)
- Has no concept of the five D365FO extensibility mechanisms (metadata extensions, CoC, events, extension methods, plug-in/factory)
- Does not know that Microsoft standard objects are read-only and must be extended, never modified directly
- Has no awareness of model descriptors, package dependencies, or ownership rules that determine whether to extend or modify directly
- Cannot reason about where to place new objects or which model/package they belong in
- Has no awareness of the active project's model, metadata structure, or naming conventions
- Cannot create, modify, or inspect D365FO metadata objects programmatically
The result is that Copilot generates invalid or dangerous code — modifying base objects, missing next calls in CoC, using C# syntax instead of X++, or failing to check model dependencies.
Solution
This extension solves the problem at three levels:
A standalone MCP (Model Context Protocol) server (XppMcpServer.exe) exposes 25 tools that Copilot can call directly. These tools use the D365FO MetaModel API — the same API that Visual Studio uses internally — to manipulate metadata with full type safety.
| Tool |
Description |
xpp_create_object |
Create any of 19 base object types, extensions (table, enum, EDT, form, menu, data entity, security), composite data entities, label files and menu extensions, with strongly-typed JSON metadata. All-or-nothing: relations are checked before saving, a failed metadata apply rolls the object back, an existing name is never overwritten, the object is added to the active project and the response ends with a Validation: line; a failed validation returns an error |
xpp_read_object |
Read object declaration, methods, and typed metadata (properties, fields, indexes, etc.). Objects over 25,000 characters return a summary first; sections returns only the parts you need |
xpp_update_object |
Update declaration, methods, or metadata on existing custom objects (every supported type, including menu items, services, tiles and configuration keys) |
xpp_validate_object |
Validate that an object exists and has the expected metadata; project membership is required only for objects of the project's model |
xpp_find_object |
Search for objects by name pattern and type; security roles, duties and privileges also by their label text (closest labels when no exact wording matches) |
xpp_list_objects |
Look up a handful of objects by a specific name substring |
xpp_list_model_objects |
List what one model contains (counts per type, then paged names) — reads the model index on disk, outside VS |
xpp_find_references |
Query the cross-reference database for all objects that use/reference a given object; includeCode also shows the calling code lines |
xpp_find_examples |
Standard implementations to reuse or copy: for a task ("batch job with a contract", "number sequence", forms with a pattern) or objects similar to a given one |
xpp_search_code |
Text or regex search over the X++ code of every package (classes, tables, forms, views, entities, queries, maps); model takes a model or a package |
xpp_analyze_security |
Security and licensing from the metadata: the license a role, duty or privilege needs (from the menu items' View/Maintain user license) and what drives it, who can access a menu item, form, table or entity, security gaps in your models, segregation-of-duties conflicts (built-in or your own rules) and a Markdown/CSV sign-off report |
xpp_get_object_type_schema |
Return the generated JSON schema for a metadata type via reflective type inspection |
xpp_get_form_layout |
Show a form's data sources and control tree with all extensions merged and attributed — the survey step before placing form-extension controls |
xpp_get_model_info |
Get model descriptor (layer, references, dependencies) |
xpp_list_models |
List all installed models with editability status |
xpp_read_label |
Read a label by ID and language |
xpp_create_label |
Create labels: one, or many in one call (labels array), each with translations; each label file is written once; per-label result (created / unchanged / failed) |
xpp_add_to_project |
Add an existing object to the active VS project |
xpp_list_project_items |
List items in the active VS project |
xpp_get_environment |
Get D365FO environment info (metadata folders, active model) |
xpp_search_docs |
Search Microsoft D365FO documentation (Microsoft Learn semantic search; pages returned as markdown) |
xpp_delete_object |
Delete an object of your own model completely (metadata file and project entry) and remove its own cross-reference entries. Refused for standard/ISV objects, and while other objects still reference it unless force=true |
xpp_remove_from_project |
Exclude an object from the active project; it stays in the model |
xpp_select_project |
List the D365FO projects of the open solution, or make one active (added to the solution if needed, selected and set as startup project) |
xpp_build |
Build a custom model (LabelC + xppc with cross-references) like Dynamics 365 > Build, and return BUILD SUCCEEDED/FAILED with every error, fatal error and warning; scope=project reports only the active project's objects. Runs off the VS UI thread |
Supported Object Types (19)
All 19 base types are created via the typed MetaModel API:
| Object Type |
Read |
Create |
Update |
Validate |
Status |
| AxClass |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxTable |
✅ |
✅ |
✅ |
✅ |
Fully tested. Change own fields with updateFields (EDT, label, help text, properties; the base type never changes), remove fields/indexes/field groups/relations; best-practice warnings after every save |
| AxView |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxDataEntityView |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxEdt |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxEnum |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxForm |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxMenu |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxMenuItemAction |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxMenuItemDisplay |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxMenuItemOutput |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxQuery |
✅ |
✅ |
✅ |
✅ |
Data sources and ranges (class declaration added automatically); same code as the VS Code extension, where it is regression-tested |
| AxSecurityPrivilege |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxSecurityDuty |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxSecurityRole |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxService |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxServiceGroup |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxConfigurationKey |
✅ |
✅ |
✅ |
✅ |
Fully tested |
| AxTile |
✅ |
✅ |
✅ |
✅ |
Fully tested |
All 19 types support read → create → validate → compare.
Object Extensions
IMetaModelService can only read extensions, so extension writes go through the D365FO metadata provider (IMetaModelProviders.CurrentMetadataProvider). Extensions are named <BaseObject>.<Suffix> and can only be created in custom models.
| Extension Type |
Read |
Create |
Update |
Validate |
What can be changed |
| AxTableExtension |
✅ |
✅ |
✅ |
✅ |
Add fields, indexes, field groups, relations; property modifications; change or remove its own fields, indexes, field groups and relations (updateFields, remove*) |
| AxEnumExtension |
✅ |
✅ |
✅ |
✅ |
Add enum values; property modifications |
| AxEdtExtension |
✅ |
✅ |
✅ |
✅ |
Property modifications |
| AxFormExtension |
✅ |
✅ |
✅ |
✅ |
Add controls with explicit placement (parent + begin/end/after), data sources; control property modifications (base/other-extension controls, D365FO-modifiable properties only); update, move and remove the extension's own controls (updateControls, removeControls, removeControlModifications) |
| AxDataEntityViewExtension |
✅ |
✅ |
✅ |
✅ |
Add mapped fields (entityFields or typedMetadata.Fields with $type), also when the data source is another entity or a view; the staging table is extended with the underlying table field |
| AxCompositeDataEntityView |
✅ |
✅ |
✅ |
✅ |
rootDataEntities: root + embedded entity references with their relation; staging tables checked (RowId/ParentRowId, parent relation) |
Form extensions are survey-first: xpp_get_form_layout shows the base form with all existing extensions merged (each extension control and data source is attributed) and the child structure each container's pattern expects. New controls must name a parent and a position; placement is validated against that merged form before anything is saved, and re-verified after saving. New controls get the property values their form pattern requires, and the compiler's own form-extension validators run after saving: any error the change would add to the build restores the previous version and is returned with the build's text.
All create/update/read operations use strongly-typed JSON parameters instead of raw XML. This ensures reliable round-trip metadata fidelity:
properties — Key-value pairs for scalar metadata (Label, IsExtensible, TableGroup, etc.) with automatic type coercion (string → bool/int/long/enum)
enumValues — [{ "name": "None", "value": 0, "label": "None" }, ...]
fields — [{ "name": "CustId", "fieldType": "String", "extendedDataType": "CustAccount", "label": "Customer" }, ...]
indexes — [{ "name": "CustIdx", "allowDuplicates": false, "fields": ["CustId"] }, ...]
fieldGroups — [{ "name": "AutoReport", "label": "Report", "fields": ["CustId", "Name"] }, ...]
relations — [{ "name": "CustTableRel", "relatedTable": "CustTable", "constraints": [{ "field": "CustId", "relatedField": "AccountNum" }] }, ...]
entryPoints — [{ "name": "Maintain", "objectType": "MenuItemDisplay", "objectName": "CustTable" }, ...]
Read results return the same JSON format, so Copilot can use a read result directly as a template for creating or modifying objects.
Other behaviour worth knowing:
- EDTs: set
properties.EdtType (String, Int, Int64, Real, Enum, Date, UtcDateTime, Time, Guid, Container) or properties.Extends; the read shows the kind.
- Label files:
xpp_create_object with AxLabelFile and properties.Languages (e.g. en-US,fr-CA) creates one label file per language; xpp_create_label accepts translations and a labels array.
typedMetadata: items of polymorphic collections take "$type" (as xpp_read_object writes them); unknown keys are refused, so nothing is dropped silently. Query data sources default to dynamicFields true and accept fields.
- Menu extensions:
AxMenuExtension with menuElements (menu item, type, parent submenu, position).
methods: plain X++ source strings, or objects with source.
- Reads: data entities include data sources, entity fields and the staging table;
methodNames returns only the requested methods; only non-default properties are shown.
- Validation compares only the attributes you passed, treats Yes/True and No/False alike, and checks project membership for objects of the project's model.
- Find/list reject unknown types, find data entities and security roles, and say
0 matches when nothing matches; xpp_list_model_objects explains package names that contain several models.
- Where-used marks objects from packages that are not installed locally (typically test packages).
2. Embedded X++ Knowledge (System Prompt Injection)
A comprehensive X++ language reference and decision-making framework is compiled into the VSIX assembly as an embedded resource. It covers:
- Ownership-first decision logic: Determines whether to modify directly or extend, based on model descriptors and package ownership
- Model & package awareness: Model descriptor format,
ModuleReferences, ModelModule, dependency rules
- Five extensibility mechanisms with decision trees:
- Metadata extensions (table/form/enum/EDT extensions — add fields, controls, relations)
- Chain of Command (CoC) — wrapping existing methods (class, table, form, data source, data field, control, data entity)
- Events & event handlers — table data events, form control/data source/data field events, delegate subscriptions, EventHandlerResult patterns
- Extension methods — adding new methods to existing types via
_Extension suffix classes
- Plug-in / factory — SysExtension and SysPlugin frameworks for strategy pattern variants
- X++ syntax: Data types, classes, method modifiers, data access (
select, while select, set-based operations, Query framework)
- Display & edit methods: On tables, as extension methods on standard tables, form-level
- Object type properties: Semantic descriptions of all object types with their valid property values
- Transaction control, exception handling, config key guards
- Naming conventions and best practices
This prompt is injected into every Copilot interaction — both in the context payload and the AI service system message. It travels with the VSIX binary, so it works in any D365FO project regardless of the repository.
3. Dynamic Project Context Pipeline
The extension crawls the open solution's metadata files in real time, scores them by relevance to the developer's current task, and feeds a token-budgeted selection into Copilot's context. This gives Copilot awareness of:
- What objects exist in the current project
- The active document's content
- Related classes, tables, EDTs, and enums
- Whether objects are custom (editable) or Microsoft reference (extend only)
Architecture
For detailed architecture diagrams, design decisions, data flow, reliability mechanisms, and file structure see ARCHITECTURE.md.
Installation
Prerequisites
See Requirements. Building from source also needs the Visual Studio extension development workload.
Install from the Marketplace
In Visual Studio: Extensions > Manage Extensions, search for F&O Studio AI Companion, install, and restart Visual Studio.
Build from Source
- Clone this repository
- Open
DynDevTools.sln in Visual Studio 2022 or later (or run vsix-companion\build.cmd Release; it finds MSBuild with vswhere)
- Ensure the Visual Studio extension development workload is installed
- Build the solution (
Ctrl+Shift+B)
- The VSIX will be in
vsix-companion/bin/Release/XppAiCopilotCompanion.vsix
- The MCP server will be in
vsix-companion/mcp-server/bin/Release/XppMcpServer.exe
Install the VSIX
- Close all Visual Studio instances
- Double-click
XppAiCopilotCompanion.vsix
- Follow the installer prompts
- Restart Visual Studio
Upgrading: On the first VS startup after installing a new version, the extension automatically detects the version mismatch, kills the old MCP server, cleans the companion folder, and starts fresh. No manual cleanup needed.
Deploy to a D365FO Dev VM
Copy the .vsix file to the dev VM and double-click to install. The extension activates automatically when a solution is opened. The MCP server starts and stops automatically.
Usage
Go to Tools > Options > X++ AI Copilot > General and set the Environment Type to match your D365FO environment:
| Environment Type |
When to use |
PackagesLocalDirectory |
| UDE (default) |
Unified Development Environment / remote dev box |
Auto-detected from registry |
| Onebox |
Local all-in-one install (e.g. demo VM) |
C:\AOSService\PackagesLocalDirectory (default) |
| CHE |
Cloud-Hosted Environment (local install) |
K:\AOSService\PackagesLocalDirectory (default) |
For UDE, metadata paths are auto-detected from the active D365FO VS configuration:
- Registry —
HKCU\Software\Microsoft\Dynamics\AX7\Development\Configurations → CurrentMetadataConfig
- JSON config file —
%LOCALAPPDATA%\Microsoft\Dynamics365\XPPConfig → ModelStoreFolder / ReferencePackagesPaths
For Onebox / CHE, the default PackagesLocalDirectory paths are used automatically. Override them in the Packages Local Directory field if your installation uses a non-standard path.
The Metadata Roots fields (Custom and Reference) can be used for fully custom path overrides on any environment type. Leave them empty to use the environment-driven auto-detection.
MCP Server Registration
The MCP server is registered automatically when the VSIX loads. You can also use:
- Tools > X++ AI: Register MCP Server — Manually register/restart the MCP server
- Tools > X++ AI: MCP Diagnostics — Check MCP server health and tool availability
Using with Copilot Chat
Once the MCP server is registered, Copilot Chat in Visual Studio can call the 16 xpp_* tools directly. Examples:
- "Create an AxEnum called MyStatus with values None, Active, and Closed"
- "Read the CustTable and show me its fields and indexes"
- "Add a String field called MyCustomField to my table"
- "Find all classes that start with 'Cust'"
- "What models are installed?"
Copilot uses the same MetaModel API that Visual Studio uses, so objects are properly registered, cross-referenced, and added to the active project.
Menu Commands
All commands are under Tools in the main menu:
| Command |
Description |
| X++ AI: Refresh Context |
Builds the context payload and displays it in the Output window |
| X++ AI: Generate Code |
Prompts for a description → AI generates X++ → applies to active document |
| X++ AI: Create Object |
Prompts for type/name/description → creates via MetaModel API → adds to project |
| X++ AI: Register MCP Server |
Registers or restarts the MCP server for Copilot |
| X++ AI: MCP Diagnostics |
Shows MCP server health, port status, and tool list |
Configuration Reference
Environment
| Setting |
Default |
Description |
| Environment Type |
UDE |
UDE = remote dev box (registry auto-detects paths). Onebox = local all-in-one. CHE = Cloud-Hosted Environment. |
| Packages Local Directory |
(env default) |
Override path to PackagesLocalDirectory. Leave blank to use the default for the selected environment type. Ignored when Environment Type is UDE. |
| Setting |
Default |
Description |
| Custom Metadata Roots |
(auto-detect) |
Semicolon-separated paths to your model's metadata directories. Leave empty to use environment-driven auto-detection. |
| Reference (MS) Metadata Roots |
(auto-detect) |
Semicolon-separated paths to Microsoft reference metadata. Leave empty to use environment-driven auto-detection. |
Context Budget
| Setting |
Default |
Description |
| Max Context Tokens |
6000 |
Total token budget for the AI context payload |
| Max Snippet Tokens Per Object |
350 |
Token limit per individual object in context |
| Max Active Document Tokens |
1000 |
Token budget for the currently open file |
Selection
| Setting |
Default |
Description |
| Top N Objects |
20 |
Maximum relevant objects to include |
| Min Custom Objects |
3 |
Minimum custom objects guaranteed in context |
| Min Reference Objects |
3 |
Minimum MS reference objects guaranteed in context |
| Include Samples |
false |
Include files from \samples\ directories |
Limitations
- VS Copilot MCP discovery: Visual Studio's Copilot Chat does not always discover or use the registered MCP endpoint automatically. When this happens, you can guide Copilot by explicitly referencing the tools in your prompt (e.g. "use xpp_read_object to read CustTable") or by invoking tools via a PowerShell HTTP call to
http://127.0.0.1:21329/ to verify the server is responsive and then retrying in Copilot Chat. This is a known VS Copilot MCP integration limitation.
- VS Copilot language model API: Currently preview/internal in VS 2022. The extension falls back to prompt composition until the API stabilizes.
- No label support for extension types: Label files can be read/created for base types, but not yet for extensions.
- Single model scope: Context pipeline doesn't yet distinguish between multiple models in a multi-model solution.
- AccessGrant on security entry points: The
grant field on entryPoints is not yet fully supported — EntryPointType and ObjectName are set, but granular access grants (Read, Update, Delete, Invoke) require a future enhancement.
- UDE config auto-detection depends on D365FO extension: The registry key and JSON config are written by Microsoft's D365FO VS extension. If that extension is not installed or not yet configured, set Environment Type to Onebox or CHE (or set Metadata Roots manually).
| |