Docs
Everything an agent, or the person configuring one, needs to connect.
Endpoint
The server is stateless: there is no session id and no server-initiated stream. GET returns 405. Every request is authenticated independently, so revoking a key takes effect on the agent's very next call.
Getting a key
Create an account, then generate a key from the dashboard. The key is displayed once, at creation. We store only a SHA-256 hash, so a lost key can't be recovered. Revoke it and make a new one.
Client configuration
The formats genuinely differ: mcpServers vs servers, url vs serverUrl, and whether type is required, so a config copied between clients usually won't connect. These are generated from the same definitions the Connect an agent page uses, which will fill your key in for you.
{
"mcpServers": {
"dynamics-mcp": {
"type": "http",
"url": "https://dynamics-mcp.com/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_KEY"
}
}
}
}claude mcp add --transport http dynamics-mcp https://dynamics-mcp.com/api/mcp \ --header "Authorization: Bearer YOUR_KEY"
type is required. Claude Code reads an entry with a url but no type as a stdio server and skips it. Add --scope user to the command to make it available in every project.
{
"mcpServers": {
"dynamics-mcp": {
"url": "https://dynamics-mcp.com/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_KEY"
}
}
}
}Cursor has no CLI for adding servers. Use the one-click install, or edit the file and reload Cursor. It infers the transport from url.
{
"servers": {
"dynamics-mcp": {
"type": "http",
"url": "https://dynamics-mcp.com/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_KEY"
}
}
}
}code --add-mcp "{\"name\":\"dynamics-mcp\",\"type\":\"http\",\"url\":\"https://dynamics-mcp.com/api/mcp\",\"headers\":{\"Authorization\":\"Bearer YOUR_KEY\"}}"The CLI adds it to your user profile rather than the workspace. VS Code can also hold the key in an inputs prompt instead of in the file. See its MCP docs if you'd rather not commit it.
{
"servers": {
"dynamics-mcp": {
"url": "https://dynamics-mcp.com/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_KEY"
}
}
}
}No CLI. Visual Studio 2022 17.14+ / 2026 picks the file up automatically; save it and Copilot reloads. Tools stay disabled until you enable them in the Agent-mode tool picker.
{
"mcpServers": {
"dynamics-mcp": {
"serverUrl": "https://dynamics-mcp.com/api/mcp",
"headers": {
"Authorization": "Bearer YOUR_KEY"
}
}
}
}Antigravity uses serverUrl, not url. Its docs state that url and httpUrl are not supported, so a config copied from another client silently won't connect. The IDE, CLI and 2.0 share this one file.
[mcp_servers.dynamics_mcp] url = "https://dynamics-mcp.com/api/mcp" bearer_token_env_var = "DYNAMICS_MCP_API_KEY"
export DYNAMICS_MCP_API_KEY="YOUR_KEY"
codex mcp add only supports stdio servers, so a remote server has to go in the file. Codex reads the token from the environment variable named above. Set it in your shell profile so it survives a restart.
An MCP server is only loaded when the client starts, so restart it after saving, and note that Cursor and VS Code can also be set up with a single click from the Connect page.
Local client
Writing AOT files, compiling and syncing the database can only happen on the machine that holds your PackagesLocalDirectory. That half is a small Windows executable you run alongside the hosted server, using the same key. It is published as a built binary at github.com/Laura-Minter/FinOps_Dynamics_MCP.
It requires .NET Framework 4.8, which ships with Windows 10 1903+ and Windows Server 2019+ — so on a D365FO developer VM there is normally nothing to install. One command in PowerShell puts it in C:\Tools\dynamics-mcp, after checking the download against its published SHA-256:
irm https://dynamics-mcp.com/install.ps1 | iex
If you already have Node, npx -y dynamics-mcp-client works as the command instead. Either way, add a second entry to the same config file:
{
"mcpServers": {
"dynamics-mcp-local": {
"type": "stdio",
"command": "C:\\Tools\\dynamics-mcp\\dynamics-mcp.exe",
"env": {
"DYNAMICS_MCP_API_KEY": "YOUR_KEY"
}
}
}
}That shape is for Claude Code; the Connect an agent page emits it for whichever client you use, with your key filled in. The key travels in env rather than a header, because this half is stdio rather than HTTP.
It adds write_aot_file, write_label, create_model, verify_objects, undo_last_write, build_model, sync_database, run_bp_check, run_systest, get_workspace_info and index_model. It generates nothing on its own — it holds no index and no generation rules, and writes what the hosted server produces. Every call verifies your subscription.
index_model is worth running once after setup, and again after you add objects: the hosted index knows Microsoft's models, not yours, so your own EDTs and tables are invisible to resolution until it does. It uploads object names, types, and the facts needed to resolve a field's type. It does not upload method bodies — your X++ stays on your machine.
Tools
analyze_codeLearn from the existing codebase. Choose a `mode`: • patterns → common classes/methods/dependencies for a scenario (call BEFORE generate_object(mode="pattern")). • implementations → real implementation examples of a similar method (actual code). • completeness → missing standard methods on a class (find/exist/validate gaps). • api-usage → how an API/class is initialized and called in practice.
Input schema
{
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"patterns",
"implementations",
"completeness",
"api-usage"
],
"description": "Which analysis to run."
},
"scenario": {
"type": "string",
"description": "[patterns] REQUIRED. Scenario/functionality to analyze (e.g., \"financial dimensions\", \"inventory transactions\")."
},
"classPattern": {
"type": "string",
"description": "[patterns] Optional class name pattern to filter results (e.g., \"Helper\", \"Service\")."
},
"methodName": {
"type": "string",
"description": "[implementations] REQUIRED. Name of the method to implement."
},
"parameters": {
"type": "array",
"description": "[implementations] Method parameters.",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"type": {
"type": "string"
}
},
"required": [
"name",
"type"
]
}
},
"returnType": {
"type": "string",
"default": "void",
"description": "[implementations] Method return type."
},
"className": {
"type": "string",
"description": "[implementations|completeness] REQUIRED. Class to analyze / containing the method."
},
"apiName": {
"type": "string",
"description": "[api-usage] REQUIRED. Name of the API/class to get usage patterns for."
},
"context": {
"type": "string",
"description": "[api-usage] Optional context to filter patterns (e.g., \"initialization\", \"validation\")."
},
"limit": {
"type": "number",
"description": "[patterns] Maximum number of pattern examples to return",
"default": 5
}
},
"required": [
"mode"
]
}d365fo_fileCreate, modify, or generate a D365FO AOT object. Choose an `action`: • create → write a NEW object file into PackagesLocalDirectory (UTF-8 BOM, auto-added to .rnrproj). THE WRITE STEP — incomplete until isError=false; ⚠️/❌ = failure. Extensions: objectName="Base.PrefixExtension". • modify → edit an EXISTING object. APPLIES IMMEDIATELY, no dry-run — confirm with the user first; revert with undo_last_modification. Needs `operation`. • generate → XML as TEXT only, no write (Azure/Linux fallback). Try create first. create/modify need Windows. 📖 Parameters are NOT inlined here: get_knowledge(kind="op-spec", topic="<operation>"|"<objectType>") returns the contract for the one you picked — pass its values nested in `params` (modify) / `properties` (create), along with any packageName/packagePath/solutionPath/workspacePath override. Model + prefix auto-applied. Classes: member vars inside the class { }, methods after the closing }.
Input schema
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"create",
"modify",
"generate"
],
"description": "One of the three modes described above."
},
"objectType": {
"type": "string",
"enum": [
"class",
"table",
"enum",
"form",
"query",
"view",
"data-entity",
"report",
"edt",
"table-extension",
"class-extension",
"form-extension",
"enum-extension",
"edt-extension",
"data-entity-extension",
"menu-item-display-extension",
"menu-item-action-extension",
"menu-item-output-extension",
"menu-extension",
"menu-item-display",
"menu-item-action",
"menu-item-output",
"menu",
"security-privilege",
"security-duty",
"security-role",
"security-duty-extension",
"security-role-extension",
"business-event",
"tile",
"kpi",
"map",
"service",
"service-group",
"macro",
"configuration-key",
"security-policy",
"aggregate-measurement",
"license-code"
],
"description": "Each security/menu-item type is its own AOT folder — NEVER use security-privilege for duty or role. [modify]/[generate] cover the core families + their *-extension variants."
},
"objectName": {
"type": "string",
"description": "Base name WITHOUT model prefix — the tool prepends it. Extension classes: \"{Base}_Extension\". NEVER hand-build the prefix."
},
"modelName": {
"type": "string",
"description": "Target model — auto-detected. NEVER take it from search results (those are source models)."
},
"sourceCode": {
"type": "string",
"description": "X++ source. FOR CLASSES auto-split: <Declaration> = class line + member vars; <Methods> = each method after the closing }."
},
"properties": {
"type": "object",
"additionalProperties": true,
"description": "[create] Per-objectType creation properties (label, fields[], extends, enumValues[], primaryTable, …) — NOT in this schema. Fetch yours: get_knowledge(kind=\"op-spec\", topic=\"<objectType>\")."
},
"addToProject": {
"type": "boolean",
"description": "Add to the ACTIVE .rnrproj — keep the default.",
"default": true
},
"projectPath": {
"type": "string",
"description": "Path to .rnrproj (auto-detected)."
},
"xmlContent": {
"type": "string",
"description": "Complete XML written verbatim (+overwrite=true rewrites an object)."
},
"overwrite": {
"type": "boolean",
"description": "Allow overwriting — never rewrite via PowerShell.",
"default": false
},
"groundingToken": {
"type": "string",
"description": "From prepare(change/create). Required for *-extension when GROUNDING_ENFORCE=true; object-bound."
},
"operation": {
"type": "string",
"enum": [
"add-method",
"remove-method",
"replace-code",
"add-field",
"modify-field",
"rename-field",
"replace-all-fields",
"remove-field",
"add-display-method",
"add-table-method",
"add-index",
"remove-index",
"add-full-text-index",
"remove-full-text-index",
"add-table-mapping",
"remove-table-mapping",
"add-relation",
"remove-relation",
"add-delete-action",
"remove-delete-action",
"add-field-group",
"remove-field-group",
"add-field-to-field-group",
"add-field-modification",
"add-data-source",
"add-control",
"add-enum-value",
"modify-enum-value",
"remove-enum-value",
"add-menu-item-to-menu",
"modify-property"
],
"description": "[modify] REQUIRED unless using operations[]. add-method also UPDATES in place; replace-code is the surgical oldCode→newCode path. Parameters: get_knowledge(kind=\"op-spec\", topic=\"<operation>\")."
},
"operations": {
"type": "array",
"maxItems": 20,
"description": "[modify] PREFERRED for 2+ edits to the SAME object — ONE call, not one per edit. Entries are {operation, …op-spec params}; objectType/objectName/modelName stay top-level. Applied in order, stopped at the first failure, per-operation results back. 3 fields + their field groups + an index: 7 calls flat, 1 here.",
"items": {
"type": "object",
"additionalProperties": true
}
},
"params": {
"type": "object",
"additionalProperties": true,
"description": "[modify] Operation-specific parameters as ONE nested object, per get_knowledge(kind=\"op-spec\", topic=\"<operation>\"). A missing/wrong one returns that COMPLETE spec — follow it, do not guess."
},
"createBackup": {
"type": "boolean",
"description": "[modify] Back up before modifying.",
"default": false
},
"filePath": {
"type": "string",
"description": "[modify] Absolute XML path — bypasses symbol-DB lookup. Use for objects just created."
}
},
"required": [
"action"
]
}extension_infoD365FO extensibility analyzer. Choose a `mode`: • coc → Chain of Command extensions + event subscriptions for a class/table. Use before writing a CoC extension to check for conflicts. • events → event handler subscriptions (SubscribesTo, delegate +=) for a class/table. Use before adding handlers to check for duplicates. • table-merge → all extensions of a table across models + effective merged schema (base + extension fields/indexes/methods). • points → available extension points (CoC-eligible/replaceable methods, delegates, blocked methods) and which are already extended. • strategy → recommends the best extensibility mechanism for a goal (CoC, event handler, business event, data entity, …) with reasoning, risks, alternatives, next steps.
Input schema
{
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"coc",
"events",
"table-merge",
"points",
"strategy"
],
"description": "coc/events/table-merge/points need `target`; strategy needs `goal`."
},
"target": {
"type": "string",
"description": "The base object: [coc] class/table being extended; [events] class/table whose handlers to find; [table-merge] base table; [points] class/table/form; [strategy] optional target object."
},
"method": {
"type": "string",
"description": "[coc] filter to a specific method name; [events] filter to a specific event name (e.g. onInserted)."
},
"objectType": {
"type": "string",
"enum": [
"class",
"table",
"form",
"auto"
],
"description": "[events] set \"table\" when target is a table (else class is assumed); [points] object type (default: auto-detect).",
"default": "auto"
},
"goal": {
"type": "string",
"description": "[strategy] REQUIRED. What you want to achieve — e.g. \"validate that SalesLine quantity is positive\"."
},
"scenario": {
"type": "string",
"enum": [
"data-validation",
"field-defaulting",
"field-change-reaction",
"business-logic-change",
"outbound-integration",
"inbound-data",
"ui-modification",
"document-output",
"number-sequence",
"security-access",
"batch-processing",
"custom"
],
"description": "[strategy] Scenario category (auto-detected from goal if omitted). field-defaulting = set defaults on NEW records (initValue); field-change-reaction = react when a user/code CHANGES a field (modifiedField)."
},
"handlerType": {
"type": "string",
"enum": [
"static",
"delegate",
"all"
],
"description": "[events] Filter by handler type (default: all).",
"default": "all"
},
"includeEventHandlers": {
"type": "boolean",
"description": "[coc] Also find static event subscriptions (SubscribesTo) (default: true).",
"default": true
},
"includeEffectiveSchema": {
"type": "boolean",
"description": "[table-merge] Merge base + extension counts (default: true).",
"default": true
},
"showExistingExtensions": {
"type": "boolean",
"description": "[points] Show which extension points are already extended (default: true).",
"default": true
}
},
"required": [
"mode"
]
}find_referencesFind all references (where-used) to a class, method, field, table, enum, or LABEL. Essential for impact analysis before refactoring. For a method, SCOPE it to its declaring type — pass "Owner.method" (e.g. "SalesTable.initFromSalesQuotationTable"), set ownerName alongside a bare method name, or pass an AOT path ("/Tables/SalesTable/Methods/initFromSalesQuotationTable"). A bare method name (no owner) matches that name on every type and over-reports. For a label, pass the label id as targetName (e.g. "@WAX2194" or "@MyLabelFile:MyLabel"); results span every referencing object type (tables, forms, EDTs, enums, reports, menu items, …), not just code, and require the xref database (DYNAMICSXREFDB, full server mode).
Input schema
{
"type": "object",
"properties": {
"targetName": {
"type": "string",
"description": "Target name. Method where-used: qualify as \"Owner.method\" or pass an AOT path \"/Tables/<Table>/Methods/<method>\" for a result scoped to one declaring type (matches Visual Studio xref). A bare method name is name-only and over-reports. Label where-used: pass the label id exactly as written — old format \"@WAX2194\" or new format \"@LabelFile:LabelId\" (e.g. \"@ApplicationPlatform:AbortButtonText\")."
},
"targetType": {
"type": "string",
"enum": [
"class",
"method",
"field",
"table",
"enum",
"edt",
"form",
"query",
"view",
"report",
"label",
"all"
],
"description": "Type of the target to search for. Use \"label\" for label where-used (or just pass an \"@…\" / \"/Labels/@…\" targetName).",
"default": "all"
},
"ownerName": {
"type": "string",
"description": "Declaring table/class/form that owns the method, when targetName is the bare method name. Scopes the where-used to that single type."
},
"limit": {
"type": "number",
"description": "Maximum number of references to return",
"default": 50
}
},
"required": [
"targetName"
]
}generate_objectGenerate X++/AOT code. Choose a `mode`: • pattern → a named X++ skeleton from the pattern enum (text only, no write). Call analyze_code(mode="patterns") first, then generate_object(mode="pattern"), then d365fo_file(action="create"). • scaffold → pattern-aware whole-object generation (table/form/report) with intelligent field/index/relation or form-pattern suggestions; set objectType. • find-methods → find()/findRecId()/exists() for a table (text), keyed on its primary/unique index. • relation-xpp → a table's relation(s) → X++ select + QueryBuildRange (text). • fields → field names → AxTableField XML with auto-resolved EDTs + optional field group. • table-relation → EDT-referencing fields → AxTableRelation XML (inverse of relation-xpp). 📖 Mode parameters are NOT inlined here: get_knowledge(kind="op-spec", topic="<mode>") — "scaffold:table"/"scaffold:form"/"scaffold:report" for the scaffolds — returns the contract; pass its values nested in `params`. For a single existing object definition's XML use d365fo_file(action="generate") instead.
Input schema
{
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"pattern",
"scaffold",
"find-methods",
"relation-xpp",
"fields",
"table-relation"
],
"description": "pattern = X++ skeleton; scaffold = whole table/form/report (set objectType); find-methods/relation-xpp/fields/table-relation = X++/XML helpers for an existing table."
},
"name": {
"type": "string",
"description": "REQUIRED. [pattern] element name (extensions: base element; form-datasource/control-extension: the FORM name). [scaffold] object name WITHOUT model prefix. [other modes] the existing table."
},
"modelName": {
"type": "string",
"description": "Model name (auto-detected). NEVER use placeholders like \"MyModel\"."
},
"pattern": {
"type": "string",
"enum": [
"class",
"runnable",
"form-handler",
"data-entity",
"batch-job",
"table-extension",
"sysoperation",
"event-handler",
"security-privilege",
"menu-item",
"class-extension",
"ssrs-report-full",
"lookup-form",
"dialog-box",
"dimension-controller",
"number-seq-handler",
"display-menu-controller",
"data-entity-staging",
"service-class-ais",
"form-datasource-extension",
"form-control-extension",
"map-extension"
],
"description": "[pattern] REQUIRED. CoC skeletons: class/table-extension, form-handler, form-datasource-extension, form-control-extension, map-extension. ssrs-report-full = Contract+DP+Controller; service-class-ais = CRUD service + contract."
},
"objectType": {
"type": "string",
"enum": [
"table",
"form",
"report"
],
"description": "[scaffold] REQUIRED. Kind of object to generate."
},
"params": {
"type": "object",
"additionalProperties": true,
"description": "Mode-specific parameters as ONE nested object (label, fields[], fieldsHint, cloneFrom, tableMapping, formPattern, contractParams[], keyFields[], style, fieldGroup, …). Get the contract from get_knowledge(kind=\"op-spec\", topic=\"<mode>\"); a missing required one returns that COMPLETE spec."
}
},
"required": [
"mode"
]
}get_knowledgeX++ knowledge lookup. Choose a `kind`: • knowledge → queryable X++ rulebook: verified patterns, BP rules, AX2012→D365FO migration. Use BEFORE generating code. Topics incl.: select-statement, coc-authoring, bp-rules, sysoperation, event-handlers, workflow, number-sequences, security, sysda, form patterns. • error → diagnose a D365FO/X++ compiler or runtime error: structured root cause + step-by-step fix + corrected X++ example (TTS mismatch, UpdateConflict, CSUV1, SYS10028 missing next, overlayering, BP errors, …). Call this instead of guessing — X++ error semantics differ from C#/.NET. • op-spec → the parameter contract for ONE d365fo_file operation/objectType or ONE generate_object mode (topic = "add-index", "table", "scaffold:form", …). Those two tools deliberately do not ship their parameters inline; call this after picking the operation, before the call. Omit topic for the index of available topics. • bp-moniker → validate an exact BP-check moniker, search by scenario when you have no moniker yet, or render a _BPSuppressions.xml <Diagnostic> block. Backed by names/text extracted from a real D365FO install — never invents a moniker.
Input schema
{
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"knowledge",
"error",
"op-spec",
"bp-moniker"
],
"description": "knowledge = look up an X++ topic/rule; error = diagnose an error message; op-spec = parameter contract for a d365fo_file operation/objectType or generate_object mode; bp-moniker = validate/search a BP-check moniker or render a suppression."
},
"topic": {
"type": "string",
"description": "[knowledge] REQUIRED. Topic to query — e.g. \"batch job\", \"ttsbegin\", \"RunBase vs SysOperation\", \"set-based operations\", \"CoC\", \"data entities\", \"number sequences\", \"security\", \"temp tables\", \"today() deprecated\", \"query patterns\", \"form patterns\". [op-spec] The operation / objectType / mode to look up."
},
"format": {
"type": "string",
"enum": [
"concise",
"detailed"
],
"default": "concise",
"description": "[knowledge] concise = quick reference (default), detailed = full explanation with code examples"
},
"errorText": {
"type": "string",
"description": "[error] REQUIRED. Full error message text as displayed in the X++ compiler or event log"
},
"errorCode": {
"type": "string",
"description": "[error] Optional error code (e.g. SYS10028, CSUV1, BPUpgradeCodeToday)"
},
"action": {
"type": "string",
"enum": [
"validate",
"search",
"suppress"
],
"description": "[bp-moniker] REQUIRED. validate = confirm an exact moniker is real; search = free-text scenario query; suppress = render a <Diagnostic> block."
},
"moniker": {
"type": "string",
"description": "[bp-moniker validate/suppress] REQUIRED. Exact moniker, e.g. \"BPErrorPrivilegeNotCoveredByDuty\"."
},
"path": {
"type": "string",
"description": "[bp-moniker suppress] REQUIRED. dynamics:// path, verbatim from the finding."
},
"justification": {
"type": "string",
"description": "[bp-moniker suppress] REQUIRED. Why the warning is ignored; 95% of real entries carry one."
}
},
"required": []
}get_object_infoRead D365FO object metadata. For 2+ objects pass objects:[{objectType,objectName},…] (max 10) — ONE call, run in parallel, per-object sections back; never loop single calls. One object: {objectType, name}. Pick the kind via objectType: class, table, form, query, view, enum, edt, report, data-entity, menu-item, service, map, config-key, security-policy, macro. Extension types (table-extension, form-extension, enum-extension, edt-extension, data-entity-extension) list all extensions of a base object — pass the base object name or a full extension name (the dot suffix is stripped automatically). Type-specific flags go in options. For CLASSES, {"members":"names"} (optional {"prefix":...}) returns a fast IntelliSense-style member-name list instead of full metadata. Replaces the former get_<type>_info, code_completion, batch_get_info and get_method tools.
Input schema
{
"type": "object",
"properties": {
"objects": {
"type": "array",
"minItems": 1,
"maxItems": 10,
"description": "PREFERRED for 2+ objects: read them all in one round trip. Each entry takes the same objectType/options as the single form, with the name in objectName.",
"items": {
"type": "object",
"properties": {
"objectType": {
"type": "string",
"enum": [
"class",
"table",
"form",
"query",
"view",
"enum",
"edt",
"report",
"data-entity",
"menu-item",
"service",
"map",
"config-key",
"security-policy",
"macro",
"table-extension",
"class-extension",
"form-extension",
"enum-extension",
"edt-extension",
"data-entity-extension"
],
"description": "Kind of object to read"
},
"objectName": {
"type": "string",
"description": "Exact object name (use search first if unsure)"
},
"options": {
"type": "object",
"description": "Optional type-specific flags for this object; overrides the top-level options."
}
},
"required": [
"objectType",
"objectName"
]
}
},
"objectType": {
"type": "string",
"enum": [
"class",
"table",
"form",
"query",
"view",
"enum",
"edt",
"report",
"data-entity",
"menu-item",
"service",
"map",
"config-key",
"security-policy",
"macro",
"table-extension",
"class-extension",
"form-extension",
"enum-extension",
"edt-extension",
"data-entity-extension"
],
"description": "Kind of object to read (incl. *-extension types — pass base object name or full extension name). REQUIRED unless using objects[]."
},
"name": {
"type": "string",
"description": "Exact object name (use search first if unsure). REQUIRED unless using objects[]."
},
"options": {
"type": "object",
"description": "Type-specific reader flags: includeRdl (report), searchControl/maxControls (form), compact/methodOffset (class), fieldsOffset/fieldFilter (table), filter (macro), mode (edt), includeFields, includeOperations, modelName. On class/table/view/data-entity, {\"method\":\"validateWrite\",\"include\":\"signature\"} returns ONE method (include: signature | source | both) — required before writing a CoC extension. {\"include\":\"xml\"} returns raw AOT XML + its path (page: startLine/endLine) — never shell out to find or read a file. Applies to every objects[] entry."
}
}
}labelsUnified label operations — read and write. Choose an `action`: • search → full-text query across indexed label files. • info → all translations for a labelId; without labelId lists label files (with labelFileId: physical .label.txt path per language). • create → add a new label to an AxLabelFile across every language .label.txt (write). Label IDs describe MEANING — never add a model prefix; target the model's ORIGINAL label file, never an …_Extension… file. Pass createIfMissing=true to reuse an existing label instead of reporting it — one call, no search first. Bulk: pass labels:[{labelId, translations}, …] with shared labelFileId/model at top level. • update → overwrite the text of an EXISTING label; same args as create with corrected translations[] (write). • rename → rename a label ID across .label.txt + X++ + XML + index. Use dryRun=true first (write). Write plumbing (paths, languages, sortLabels, allowExtensionLabelFile…) is auto-resolved; override it via get_knowledge(kind="op-spec", topic="labels").
Input schema
{
"type": "object",
"properties": {
"params": {
"type": "object",
"additionalProperties": true,
"description": "Optional write plumbing (packagePath, projectPath, languages, sortLabels, allowExtensionLabelFile, …) — all auto-resolved when omitted. Contract: get_knowledge(kind=\"op-spec\", topic=\"labels\")."
},
"action": {
"type": "string",
"enum": [
"search",
"info",
"create",
"update",
"rename",
"list",
"list-files"
],
"description": "Label operation to perform. \"list\"/\"list-files\" are aliases of \"info\" (lists label files)."
},
"model": {
"type": "string",
"description": "[search|info|create|update|rename] Model that owns the label file (e.g. ContosoExt)."
},
"labelFileId": {
"type": "string",
"description": "[search|info|create|update|rename] AxLabelFile ID (e.g. ContosoExt, SYS). For action=info with no labelId, returns the physical .label.txt path per language. For create/update/rename use the model's ORIGINAL label file, not an extension (…_Extension…). For a NEW label file this ID is the MODEL name, never the bare EXTENSION_PREFIX."
},
"language": {
"type": "string",
"description": "[search] Language/locale (default: en-US). Examples: cs, de, sk."
},
"maxResults": {
"type": "number",
"description": "[search] Max labels listed (default 10, alias `limit`); a truncated set reports how many more matched."
},
"limit": {
"type": "number",
"description": "[search] Alias of maxResults."
},
"verbose": {
"type": "boolean",
"description": "[search] Default one line per label; true = full multi-line block."
},
"query": {
"type": [
"string",
"array"
],
"items": {
"type": "string"
},
"description": "[search] REQUIRED. Search text — matches label ID, text and developer comment. ARRAY = try several phrasings in ONE call."
},
"labelId": {
"type": "string",
"description": "[info] Label ID, any spelling: SYS67433, @SYS67433, @ContosoExt:MyLabel (paste search output). labelFileId/model optional. Omit to list label files."
},
"labels": {
"type": "array",
"description": "[create] OPTIONAL bulk mode — create several labels in one call; shared fields (labelFileId, model, languages, paths…) stay at the top level and top-level labelId/translations are ignored. A failed entry does not abort the batch.",
"items": {
"type": "object",
"properties": {
"labelId": {
"type": "string",
"description": "Label ID for this entry — alphanumeric, no model prefix."
},
"translations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"language": {
"type": "string",
"description": "Locale code, e.g. en-US, cs, de, sk"
},
"text": {
"type": "string",
"description": "Label text"
},
"comment": {
"type": "string",
"description": "Developer comment (optional)"
}
},
"required": [
"language",
"text"
]
}
}
},
"required": [
"labelId",
"translations"
]
}
},
"translations": {
"type": "array",
"description": "[create] REQUIRED for single-label create (omit when using labels[]). Translations for each language. Provide at least en-US.",
"items": {
"type": "object",
"properties": {
"language": {
"type": "string",
"description": "Locale code, e.g. en-US, cs, de, sk"
},
"text": {
"type": "string",
"description": "Label text"
},
"comment": {
"type": "string",
"description": "Developer comment (optional)"
}
},
"required": [
"language",
"text"
]
}
},
"oldLabelId": {
"type": "string",
"description": "[rename] REQUIRED. Current label ID (e.g. MyOldField)."
},
"newLabelId": {
"type": "string",
"description": "[rename] REQUIRED. New label ID — must be alphanumeric, no spaces."
},
"dryRun": {
"type": "boolean",
"description": "[rename] Preview changes without writing anything (default: false). Use this first!"
}
},
"required": [
"action"
]
}object_patternsPattern toolkit. Choose a `domain`: • table → common field types, index patterns and relation structures for D365FO tables. Filter by tableGroup (Main, Transaction, …) or similarTo a given table. • form → form-pattern toolkit; pick an `action`: - analyze → pattern advisor + usage analysis. RECOMMEND (preferred for a new form): pass recommend={entityKind, hasHeaderLines, fieldCount, usageIntent, tableName} for the right pattern via the Microsoft decision tree + reference forms to clone. Or filter by formPattern / dataSource / similarTo. - spec → full structure spec of a pattern or sub-pattern (required hierarchy/ordering, allowed children, reference forms, lifecycle). Call after analyze, before building. - validate → structural validator of AxForm XML (<50 ms, offline): container hierarchy/order, sub-patterns, PatternVersion. Returns FP001-FP010 violations. Call before action=create on d365fo_file.
Input schema
{
"type": "object",
"properties": {
"domain": {
"type": "string",
"enum": [
"table",
"form"
],
"description": "table = table field/index/relation patterns; form = form-pattern toolkit (set action). Optional — inferred from the other params (action/pattern/xml/formName → form; tableGroup → table). ⚠️ This is NOT a free-form \"pattern type\": a concept like \"number-sequence\"/\"SysOperation\" belongs to get_knowledge, not here."
},
"tableGroup": {
"type": "string",
"enum": [
"Main",
"Transaction",
"Parameter",
"Group",
"Reference",
"Miscellaneous",
"WorksheetHeader",
"WorksheetLine"
],
"description": "[table] Table group type to analyze (choose one)."
},
"action": {
"type": "string",
"enum": [
"analyze",
"validate",
"spec",
"repair"
],
"description": "[form] Which form-pattern operation to run. repair = auto-fill missing required controls."
},
"formPattern": {
"type": "string",
"enum": [
"DetailsTransaction",
"ListPage",
"SimpleList",
"SimpleListDetails",
"Dialog",
"DropDialog",
"FormPart",
"Lookup"
],
"description": "[analyze] D365FO form pattern to analyze"
},
"dataSource": {
"type": "string",
"description": "[form/analyze] Table name - find forms using this table"
},
"similarTo": {
"type": "string",
"description": "[table] table name to find similar table patterns; [form/analyze] form name to find similar form patterns."
},
"recommend": {
"type": "object",
"description": "[analyze] Pattern advisor: describe requirements, get a recommended pattern + reference forms to clone.",
"properties": {
"entityKind": {
"type": "string",
"enum": [
"master",
"transaction",
"setup",
"parameters",
"inquiry",
"lookup",
"workspace",
"dialogTask"
],
"description": "Kind of entity: master (customers), transaction (orders+lines), setup (group tables), parameters, inquiry (read-only), lookup, workspace, dialogTask"
},
"hasHeaderLines": {
"type": "boolean",
"description": "True when data is a header with line items"
},
"fieldCount": {
"type": "number",
"description": "Approximate fields users see/edit per record (<10 → SimpleList, ≥10 → SimpleListDetails)"
},
"usageIntent": {
"type": "string",
"enum": [
"maintain",
"viewOnly",
"pickValue",
"quickCreate",
"dashboard",
"wizard"
],
"description": "Primary user activity on the form"
},
"tableName": {
"type": "string",
"description": "Main table — pulls field count and existing-form evidence from the index"
}
}
},
"limit": {
"type": "number",
"description": "[analyze] Maximum number of pattern examples (default: 10)",
"default": 10
},
"pattern": {
"type": "string",
"description": "[spec] REQUIRED. Pattern name (id, xmlName, or alias) — e.g. \"SimpleList\", \"DetailsMaster\", or a sub-pattern like \"FieldsFieldGroups\"."
},
"xml": {
"type": "string",
"description": "[validate] Complete AxForm XML to validate. Provide this OR formName/filePath."
},
"formName": {
"type": "string",
"description": "[validate] Name of an indexed form — XML is loaded from the metadata store."
},
"filePath": {
"type": "string",
"description": "[form/validate] Explicit path to an AxForm XML file (e.g. a freshly created form not yet indexed)."
}
},
"required": []
}prepareHosted prepare includes matching implementation skills and current user conventions in the same response. Read those before generating; no separate skill list/get is needed for guides included in full. ONE-call context aggregator + groundingToken (30-min TTL, required for extension/new-object writes when GROUNDING_ENFORCE=true). Choose a `mode`: • change → extending/modifying an EXISTING object: exact signature, existing CoC wrappers, eligibility, recommended strategy, naming, patterns. Replaces the analyze→search→info→generate loop. • create → a NEW object: collision check, naming with auto-prefix, similar objects, EDT suggestions, reusable labels, mined property defaults.
Input schema
{
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"change",
"create"
],
"description": "change = extend/modify an existing object; create = a brand-new object."
},
"goal": {
"type": "string",
"description": "One-sentence description of the intent. Example (change): \"Add CoC on CustTable.validateWrite\". Example (create): \"Parameter table for the Contoso import feature.\""
},
"objectName": {
"type": "string",
"description": "[change] Name of the object to extend/modify (e.g. \"CustTable\"). [create] Proposed BASE name WITHOUT model prefix (same value you would pass to d365fo_file create)."
},
"objectType": {
"type": "string",
"enum": [
"class",
"table",
"form",
"enum",
"edt",
"query",
"view",
"data-entity",
"report",
"map",
"menu-item-display",
"menu-item-action",
"menu-item-output",
"menu",
"security-privilege",
"security-duty",
"security-role",
"business-event",
"tile",
"kpi",
"service",
"service-group",
"macro",
"configuration-key",
"security-policy",
"aggregate-measurement",
"license-code"
],
"description": "[change] D365FO object type — auto-detected when omitted. [create] REQUIRED — type of the new object."
},
"methodName": {
"type": "string",
"description": "[change] Target method name when the change involves a specific method (CoC or event handlers). Example: \"validateWrite\"."
},
"operation": {
"type": "string",
"description": "[change] The modify operation you intend to run; its full parameter contract comes back in THIS response, so no separate op-spec call. Defaults to add-method when methodName is given."
},
"proposedName": {
"type": "string",
"description": "[change] Proposed name for the new extension class/object. When provided, naming validation runs."
},
"fieldsHint": {
"type": "array",
"items": {
"type": "string"
},
"description": "[create] For tables/views: planned field names — each gets EDT suggestions from the index."
}
},
"required": [
"mode",
"goal",
"objectName"
]
}searchSearch pre-indexed D365FO objects by name or keyword. Three modes in ONE tool: • single (default) → pass `query`; returns name, type, model. • batch → pass `queries[]` (max 10) to run searches in parallel (3× faster, with dedup + cross-reference). • extensions → set `scope:"extensions"` to restrict to custom/ISV models only (filters out Microsoft standard code). Model names in those results are SOURCE models — never use them as create/modify targets. Use get_object_info(objectType, name) when you already know the exact name and need full details.
Input schema
{
"type": "object",
"properties": {
"scope": {
"type": "string",
"enum": [
"all",
"extensions"
],
"default": "all",
"description": "[single] Search the whole index (\"all\", default) or only custom/ISV models (\"extensions\"). Ignored when `queries[]` is provided."
},
"query": {
"type": "string",
"description": "[single|extensions] Search query (class name, method name, table name, etc.). REQUIRED unless using batch `queries[]`."
},
"type": {
"type": "string",
"enum": [
"class",
"table",
"field",
"method",
"enum",
"edt",
"form",
"query",
"view",
"report",
"security-privilege",
"security-duty",
"security-role",
"menu-item-display",
"menu-item-action",
"menu-item-output",
"table-extension",
"class-extension",
"form-extension",
"enum-extension",
"edt-extension",
"data-entity-extension",
"all"
],
"description": "[single] Filter by object type (\"all\" = no filter).",
"default": "all"
},
"prefix": {
"type": "string",
"description": "[extensions] Extension prefix filter (e.g., ISV_, Custom_)."
},
"limit": {
"type": "number",
"description": "[single|extensions] Maximum results to return",
"default": 20
},
"verbose": {
"type": "boolean",
"default": false,
"description": "[single] Include related-searches/patterns/tips sections (off by default to keep responses compact)."
},
"workspacePath": {
"type": "string",
"description": "[single] Optional workspace path to search local project files in addition to external metadata"
},
"includeWorkspace": {
"type": "boolean",
"default": false,
"description": "[single] Whether to include workspace files in search results (workspace-aware search)"
},
"queries": {
"type": "array",
"description": "[batch] Array of search queries to execute in parallel (max 10). When provided, runs in batch mode and `scope`/`query` are ignored.",
"minItems": 1,
"maxItems": 10,
"items": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query (class name, method name, etc.)"
},
"type": {
"type": "string",
"default": "all",
"description": "Filter by object type — same values as the top-level `type`. Omit to inherit globalTypeFilter or default to \"all\""
},
"limit": {
"type": "number",
"default": 10,
"description": "Maximum results to return for this query"
},
"workspacePath": {
"type": "string",
"description": "Optional workspace path to search local files"
},
"includeWorkspace": {
"type": "boolean",
"default": false,
"description": "Whether to include workspace files in results"
}
},
"required": [
"query"
]
}
},
"globalTypeFilter": {
"type": "array",
"maxItems": 5,
"description": "[batch] Default type filter for queries without an explicit per-query type. E.g. [\"class\"] restricts all untyped queries to classes. Multiple values fan out each untyped query into one search per type. Values: same as the top-level `type`, except \"all\" (which means \"no filter\" — omit this instead).",
"items": {
"type": "string"
}
},
"deduplicate": {
"type": "boolean",
"default": true,
"description": "[batch] When true, symbols appearing in multiple query results are collapsed. Later occurrences are replaced with a reference to the query where they first appeared."
},
"crossReference": {
"type": "boolean",
"default": true,
"description": "[batch] Append a cross-reference summary at the end listing symbols that appeared in multiple queries. Useful for identifying the most relevant / commonly matched objects across all searches."
}
}
}security_infoD365FO security lookup. Choose a `mode`: • artifact → details + full hierarchy of a named privilege/duty/role (Role → Duties → Privileges → Entry Points). • coverage → reverse chain for an object: which privileges/duties/roles grant access (object → menu items → privileges → duties → roles).
Input schema
{
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"artifact",
"coverage"
],
"description": "artifact = look up a named privilege/duty/role; coverage = who can access an object."
},
"name": {
"type": "string",
"description": "[artifact] REQUIRED. Name of the security privilege, duty, or role"
},
"artifactType": {
"type": "string",
"enum": [
"privilege",
"duty",
"role"
],
"description": "[artifact] REQUIRED. Type of security artifact to look up"
},
"includeChain": {
"type": "boolean",
"description": "[artifact] Walk the full hierarchy (default: true)",
"default": true
},
"objectName": {
"type": "string",
"description": "[coverage] REQUIRED. Name of the form, table, class, or menu item"
},
"objectType": {
"type": "string",
"enum": [
"form",
"table",
"class",
"menu-item",
"auto"
],
"description": "[coverage] Type of the object (default: auto-detect)",
"default": "auto"
}
},
"required": [
"mode"
]
}validate_codeStatic validator for generated X++/XML (paste the text). Choose a `mode`: • syntax → offline best-practice/BP validator (no xppbp.exe). Structured violations {rule, severity, line, excerpt, fix}. Covers select, CoC, BP and table-XML rules mined from standard models. • references → semantic reference resolver (index-only): verifies every type, field, method (incl. arity), enum, label and intrinsic (tableStr/fieldStr/…) EXISTS in the indexed codebase — catches hallucinated symbols before the compiler. codeType="xml-table" checks XML refs instead: EDT/enum/relation/extends/label. Call both AFTER generating, BEFORE writes; fix errors in the same turn. Write tools run references internally when GROUNDING_ENFORCE=true.
Input schema
{
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"syntax",
"references"
],
"description": "syntax = BP/best-practice rules; references = symbol resolution against the index. Defaults to syntax."
},
"code": {
"type": "string",
"description": "X++ source code or XML metadata to validate. Paste the full generated text."
},
"codeType": {
"type": "string",
"enum": [
"xpp",
"xml-table",
"xml-any"
],
"default": "xpp",
"description": "[syntax] \"xpp\" for X++ source (default), \"xml-table\" for AxTable XML, \"xml-any\" for other XML."
},
"context": {
"type": "string",
"description": "Optional: owning class/table name, used in diagnostic messages."
}
},
"required": [
"mode",
"code"
]
}validate_object_namingValidate a proposed D365FO object name against naming conventions: extension naming, ISV prefix, type-specific suffixes, and conflict detection against the symbol index.
Input schema
{
"type": "object",
"properties": {
"proposedName": {
"type": "string",
"description": "The proposed object name to validate"
},
"objectType": {
"type": "string",
"enum": [
"class",
"table",
"form",
"enum",
"edt",
"query",
"view",
"table-extension",
"class-extension",
"form-extension",
"enum-extension",
"edt-extension",
"menu-item",
"security-privilege",
"security-duty",
"security-role",
"data-entity"
],
"description": "Type of the D365FO object"
},
"baseObjectName": {
"type": "string",
"description": "Required for extension types: name of the object being extended"
},
"modelPrefix": {
"type": "string",
"description": "Expected ISV/model prefix (2-4 uppercase letters, e.g. \"WHS\"). Auto-detected if omitted."
}
},
"required": [
"proposedName",
"objectType"
]
}skillAuthored implementation guides for D365FO object types and subsystems — the house rules for building the thing you are about to build. • list → every skill available, with a one-line description each. Cheap; use it when you need guides beyond those included by prepare. • get → the full skill, given its `name`. READ THE MATCHING SKILL BEFORE YOU GENERATE. These cover the decisions a correct implementation depends on and that no amount of reading the existing code will tell you: which pattern is right for the case, the properties that must be set and why, the extension mechanism to prefer, the validation the result has to pass. Generating first and consulting after produces something that compiles and is still wrong. One skill per call. Do not store what you get — the content is licensed for the session it was fetched for; fetch it again when you next need it.
Input schema
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"list",
"get"
],
"description": "`list` for the catalogue, `get` for one skill's text."
},
"name": {
"type": "string",
"description": "[get] REQUIRED. The skill name exactly as `list` reported it, e.g. \"d365fo-table\"."
}
},
"required": [
"action"
]
}report_backReport a problem you hit while using these tools, so the team that runs this service can fix it. Call this when a tool behaved wrongly: it returned something incorrect, misleading or malformed; it failed when it should have worked; it did something you did not ask for; or its result led you to produce code or advice you then had to undo. Call it whether or not you found a way around the problem — a workaround you found is the most useful thing you can tell us, because it tells us what the fix should look like. Reporting has no consequence for you or for the user: nothing is blocked, no work is undone, and the user is not interrupted. It records the incident for the operators of this service, who are the only people who can read it. It is not a support channel and nobody will reply to you, so after calling it, carry on with the user's task. Do not use it for your own mistakes (a typo you fixed, an argument you got wrong the first time), for a tool correctly telling you your arguments were invalid, or for anything about the user's own D365FO code. Report one problem per call, and describe it concretely enough that someone who was not in this session could reproduce it.
Input schema
{
"type": "object",
"properties": {
"activity": {
"type": "string",
"description": "What you were doing when this happened, in one or two sentences. The task, not the tool call — e.g. \"generating a vendor rating table for the user's extension model\"."
},
"problem": {
"type": "string",
"description": "What went wrong. Be concrete: what you called, what you expected, what you got. Include the exact error text if there was one."
},
"fix": {
"type": "string",
"description": "What you did about it, if anything — the workaround you found, or the approach that turned out to work instead. Leave this out if you could not get past it."
},
"fixed": {
"type": "boolean",
"description": "True only if your fix actually worked and you were able to complete what you were doing. False, or omitted, if you are still blocked or had to give up on that part of the task."
},
"severity": {
"type": "string",
"enum": [
"low",
"medium",
"high"
],
"description": "How much this cost the user. \"low\": cosmetic or easily worked around. \"medium\": you lost time or had to change approach. \"high\": you could not complete the task, or the wrong result would have reached the user unnoticed. Defaults to medium."
},
"tool": {
"type": "string",
"description": "The name of the tool that misbehaved, if one specific tool did."
}
},
"required": [
"activity",
"problem"
],
"additionalProperties": false
}Results come back as MCP content blocks written for the model to act on, so calls chain directly: search → get_object_info → generate_object → validate_code. Tools that write to disk, compile, or sync the database are not part of this endpoint. Those need your own D365FO environment.
Errors
Transport-level refusals use HTTP status codes, and they are distinct on purpose so a client can tell “fix your credential” from “top up your account”:
Errors caused by tool arguments are different: they come back as a successful JSON-RPC response with isError: true and a readable message. That's the MCP convention, and it lets the model correct itself and retry.
Limits
Calls are metered and the allowance is enforced. Each plan includes a number of tool calls per month; once those are used, further calls draw on credits, and when there are none left the endpoint returns 402. Calls inside the included allowance never consume credits. Plans also cap how many API keys can be active at once. See pricing for the figures, or Plan & usage for where your account currently stands.
For agents
A machine-readable summary lives at /llms.txt is public, with no auth required.