# Dynamics MCP > A hosted, multi-tenant MCP (Model Context Protocol) server for Microsoft Dynamics 365 Finance & Operations. Sign up, generate an API key, and point any MCP-capable AI client at it to search, read and validate X++ against a real indexed D365FO codebase. Every answer is grounded in indexed metadata — classes, tables, forms, EDTs, labels, security objects — rather than in a model's recollection of X++. This endpoint reads, analyses, validates and generates XML. Writing to disk, compiling and database sync require your own Windows D365FO environment and are not served here. ## Connecting - **Endpoint:** `https://dynamics-mcp.com/api/mcp` - **Transport:** Streamable HTTP (not SSE). Send JSON-RPC 2.0 over `POST`. - **Auth:** required on every request — `Authorization: Bearer `. There is no anonymous access and no session; a missing, invalid, or revoked key returns `401`. - **Getting a key:** sign in at https://dynamics-mcp.com/signin and create one from the dashboard. The key is shown once, at creation. - **Isolation:** every call is attributed to the key's owner, and every billable one is metered against their plan. See Plans and limits for which calls are billable. Add it to Claude Code with: ``` claude mcp add --transport http dynamics-mcp https://dynamics-mcp.com/api/mcp --header "Authorization: Bearer YOUR_KEY" ``` ## Tools - `analyze_code` — Learn from the existing codebase. - `d365fo_file` — Create, modify, or generate a D365FO AOT object. - `extension_info` — D365FO extensibility analyzer. - `find_references` — Find all references (where-used) to a class, method, field, table, enum, or LABEL. - `generate_object` — Generate X++/AOT code. - `get_knowledge` — X++ knowledge lookup. - `get_object_info` — Read D365FO object metadata. - `labels` — Unified label operations — read and write. - `object_patterns` — Pattern toolkit. - `prepare` — Hosted prepare includes matching implementation skills and current user conventions in the same response. - `search` — Search pre-indexed D365FO objects by name or keyword. - `security_info` — D365FO security lookup. - `validate_code` — Static validator for generated X++/XML (paste the text). - `validate_object_naming` — Validate a proposed D365FO object name against naming conventions: extension naming, ISV prefix, type-specific suffixes, and conflict… - `skill` — Authored implementation guides for D365FO object types and subsystems — the house rules for building the thing you are about to build. - `report_back` — Report a problem you hit while using these tools, so the team that runs this service can fix it. Tool results come back as MCP `content` blocks written for you to act on. ## The implementation sequence Start with `prepare`. It is not a formality, and it is not a lookup you can assemble out of the others: it returns the indexed context, the matching implementation guides, and the conventions this particular user has configured, in one call. An agent that starts at `search` gets facts without conventions, and produces X++ that is correct in the abstract and wrong for the codebase it is going into. 1. **`prepare`** — Start every implementation here, with the mode, the goal and the object. It returns the indexed context, the matching implementation guides and the user's own conventions in one call. Reaching for search first gets you facts without the conventions, and conventions are most of what makes generated X++ acceptable in someone else's codebase. 2. **`search`, `get_object_info`, `find_references`, `extension_info`** — Look up whatever preparation did not already resolve. X++ codebases are private and heavily customised. A guessed method signature or field name compiles in your head and nowhere else. 3. **`get_knowledge`** — Read the rulebook before using a pattern you have not verified here. Select statements, chain of command, event handlers, number sequences and form patterns each have D365FO-specific rules that do not follow from C# or SQL intuition. 4. **`generate_object`, `d365fo_file`, `labels`** — Generate the object, passing base names. The object prefix is applied server-side from the user's preference. Prepending it yourself produces it twice. 5. **`validate_code`, `validate_object_naming`** — Validate, and read which checks actually ran. A reference check that could not reach the index reports that it was unavailable, not that it passed. Treating an unavailable check as a pass is how unresolvable symbols reach a build. 6. **`write_aot_file`, `write_label`** *(local client)* — Apply the artifacts through the local client, never through your own file tool. D365FO reads AOT XML as UTF-8 WITH a byte order mark and rejects a file without one at build time, in an error that names no file. The cause then lands a long way from the symptom. 7. **`build_model`, `run_bp_check`, `verify_objects`** *(local client)* — Compile, and report what the compiler said. Generated and validated is not built. These are separate states and the user is asking about the last one. 8. **`index_model`** *(local client)* — Re-index after the model changes. This service knows Microsoft's models, not the user's. Until index_model runs, their own EDTs and tables are invisible to every lookup above — including the ones you are about to make for the next object. Steps 6 to 8 need the local client, below. If it is not in your session, stop at step 5, hand the XML back in your answer, and say it has not been written. ## Local client Writing AOT files, compiling, database sync and indexing the user's own models happen on the machine holding their `PackagesLocalDirectory`. That half is a separate stdio MCP server — a Windows executable published at https://github.com/Laura-Minter/FinOps_Dynamics_MCP — configured alongside this one under the name `dynamics-mcp-local`, taking the same API key in `DYNAMICS_MCP_API_KEY`. If those tools are absent from your session, the user has not installed it; point them at https://dynamics-mcp.com/dashboard/connect rather than trying to write files yourself. The tools it adds: - `get_workspace_info` — Report the packages root, resolved model and package, active project, prefix, the custom models on the machine, which object types this client can write, and whether the hosted index is still current for each model. - `set_workspace_target` — Change the model and/or .rnrproj later writes go to, for the rest of the session. Both are validated against disk. - `create_model` — Create a new model's package directories and Descriptor XML. - `write_aot_file` — Write generated AOT XML into PackagesLocalDirectory as UTF-8 WITH BOM and register it in the .rnrproj. This is the write step. - `write_label` — Add or update a label in a model's label file, per language, with the descriptor that makes it resolvable. - `verify_objects` — Confirm named objects exist at the correct AOT path AND are referenced by the .rnrproj. - `undo_last_write` — Reverse the last write: restore the previous contents and unregister the object from the project. - `build_model` — Compile the model with xppc and return the diagnostics. - `sync_database` — Run a D365FO database synchronization. Required after adding or changing tables, fields, indexes, views or data entities. - `run_bp_check` — Run the Microsoft best-practice checker and return its findings. - `run_systest` — Run a SysTest class. Needs an interactive console session. - `index_model` — Extract a custom model's AOT metadata and upload it so this service can resolve against it. Reports per-category coverage. Until it runs, and again after every change, hosted lookups answer from the previous upload — get_workspace_info reports which models are out of date. That list is generated from the capability manifest this repo checks against the client's own schema, so it does not promise a tool or a parameter the installed executable lacks. Pass only the arguments named in the schema you actually see. `index_model` uploads the user's object names and type facts so this endpoint can resolve against their customisations. Until it has run — and again after they add, rename or delete objects — their custom EDTs and tables are invisible here, and lookups for them correctly report nothing. A successful local write does not by itself make the object visible to this half. ## User preferences Each user configures conventions that shape what you generate. They are sent to you in the `initialize` instructions. Preferences are read-only over MCP — only the user can change them, from their dashboard. - **I'm a technical person** (`isTechnicalUser`) — Turn this off if you're a functional consultant, analyst or business user. Your agent is told which you are, and writes to you accordingly: X++ and AOT terms as they are, or plain business language. *Guidance: shapes how you should write to the user, not what you generate.* - **Translate labels into** (`translateToLanguages`) — Labels on generated objects are translated into each language you pick. Leave empty for English only. *Advisory: stated to you as a convention, but not enforced by the server yet.* - **Object prefix** (`objectPrefix`) — Prepended to the name of every object created through the MCP tools. Include your own separator: 'DM_' gives DM_Vendor, 'DM' gives DMVendor. Required, because every generated object carries it. - **A new Visual Studio project per feature** (`newProjectPerFeature`) — Each piece of work gets its own VS project instead of everything landing in one. Your agent handles the setup: it names the project, tells you the one click to make it, and registers every object it creates into it. Leave it on unless your team shares a single project. *Advisory: stated to you as a convention, but not enforced by the server yet.* - **Generate test cases after creating forms** (`generateTestCasesForForms`) — When forms are supported, automatically scaffold test cases for each new form. *Advisory: stated to you as a convention, but not enforced by the server yet.* - **Generate number sequences** (`generateNumberSequences`) — When number-sequence objects are supported, generate the sequence itself rather than only referencing it. On by default. *Advisory: stated to you as a convention, but not enforced by the server yet.* The object prefix is applied **server-side**. Pass base names — prepending it yourself produces it twice. ## Plans and limits Tool calls are metered against the key owner's plan. Prices are USD, exclusive of the tax Paddle adds at checkout. **Two tools cost nothing: `report_back` and `skill`.** They are free because they are the ones we most need you to call. The guides carry the decisions correctness depends on, so a guide that spends the budget it exists to protect is a step you would learn to skip; and a bug report filed when an account is already out of quota is the one report we would never receive. Both are still rate-limited, so neither is a hole to pour traffic through. The protocol around a tool call is not metered either — `initialize`, `tools/list`, `ping` and notifications cost nothing. A JSON array of several messages in one request costs what the messages in it cost; batching is not a discount. - **Free** — free. 500 tool calls/mo, 1 active API key, 60 calls/min ceiling. - **Solo** — $19/mo or $180/yr. 8,000 tool calls/mo, 3 active API keys, 180 calls/min ceiling. - **Team** — $149/mo or $1,415/yr. 80,000 tool calls/mo, 10 active API keys, 600 calls/min ceiling. - **Enterprise** — custom pricing. Unlimited tool calls, unlimited API keys, no per-minute ceiling. Once a month's included calls are gone, further calls draw on **credits** — one-time packs bought at 100 credits per dollar. With no included calls and no credit left, the endpoint returns `402`. Both ceilings are enforced server-side. The monthly allowance is what actually meters cost; the per-minute ceiling exists only to catch a runaway loop and sits well above real work — one chat turn is typically 10 to 20 tool calls. If you are hitting the per-minute limit you are looping, not working hard: re-read the last error instead of retrying. ## Notes for agents - `GET` on the MCP endpoint returns `405`. The server is stateless and opens no server-initiated stream — use `POST`. - Errors caused by your input come back as a successful response with `isError: true` and a human-readable message. Read it: it usually names what was wrong with the arguments, or says which piece of index data was unavailable. **Retrying the identical call repeats the identical error.** Change something the message pointed at, or ask the user for what only they can supply. - An error saying index data is unavailable is not the same as an object not existing. Unavailable means the lookup could not be performed — often because the user's own models have not been uploaded with `index_model`. Say which of the two it was; reporting "that object does not exist" when the index simply could not be read sends the user looking for a bug that is not there. - A `402` means the account is out of included calls and credit; a `403` means the account is suspended. Neither is retryable — tell the user. - A `429` is the per-minute ceiling and IS retryable, once, after the number of seconds in its `Retry-After` header. Hitting it repeatedly means a loop, not throughput. - Validation results report which checks ran. A check that could not reach the index reports itself unavailable; that is not a pass, and treating it as one is how an unresolvable symbol reaches a build. ## Links - [Home](https://dynamics-mcp.com/): what the product does - [Pricing](https://dynamics-mcp.com/pricing): plans - [Connect an agent](https://dynamics-mcp.com/dashboard/connect): per-client config snippets --- Document version: b91e99b95ef7259e. This document is generated from the running deployment — the tool list, the workflow, the plan table and the free-tool list above are read from the same code that serves and meters the endpoint, not maintained alongside it. If you are reading a saved copy, it may be older than the service. Re-fetch `https://dynamics-mcp.com/llms.txt` and compare the version line; the response carries the same value as its `ETag`, so a conditional request costs you nothing when it has not changed.