MCP setup
Expose your Dynamics GP data to AI agents over the Model Context Protocol.
ERPSpan exposes your GP data to AI agents (Claude Desktop, Claude Code, any MCP client) at one
endpoint: POST /mcp. It runs the same machinery as the REST API — the MCP tools call the
identical internal services — so an agent reads and (with a human in the loop) writes GP exactly
as the REST API does.
- Read tools —
list_connections,list_<object>/get_<object>for every read object (customers, vendors, items, accounts, ar-invoices, …), andsearch_customers/search_vendors/search_items. Every list is capped at 50 rows; page with the returnednextCursor. - Write tools — writes are always draft → commit (never a single call):
draft_<object>validates and previews without executing,commit_draftexecutes it,discard_draftcancels.
Connect a client (Claude Desktop)
Add ERPSpan to your claude_desktop_config.json. It's a remote, streamable-HTTP server
authenticated with an API key — nothing to install locally:
{
"mcpServers": {
"erpspan": {
"type": "http",
"url": "https://api.erpspan.com/mcp",
"headers": { "Authorization": "Bearer lb_live_YOUR_KEY" }
}
}
}- The key must have the
mcpscope to reach/mcp, and additionally thewritescope to use the draft/commit write tools. - Writes are off by default per connection: a
draft_/commit_is refused withWRITE_NOT_ENABLEDuntil you enable that object under Connections → Write access. Sandbox connections are always read-only.
A worked example
A typical hands-free session — the model picks each tool from its description:
| You say | The model calls | Result |
|---|---|---|
| "List my GP connections." | list_connections | your connections (id, name, status, companies) |
| "Who are our 5 biggest customers by credit limit?" | list_customers, then ranks | top-5 by creditLimit |
| "Any AR invoices past due?" | list_ar_invoices { dueBefore } | open AR docs due before today |
| "Draft a journal entry accruing $1,250 to 000-6120-00 against 000-2100-00." | draft_journal_entries | { draftId, preview } — balanced: true, willCreateUnpostedBatch: true |
| "Commit it." | commit_draft { draftId } | { data: { journalEntryNumber, batchId, status: "unposted" } } |
| "Show me the batch it created." | list_batches | the LBAPI-YYYYMMDD unposted batch |
Safety
- A
draft_call never touches GP — an unbalanced or malformed body is rejected in the draft and never reaches the tunnel. commit_draftruns the same eConnect pipeline as a REST write. The result lands in an unposted batch — a person still posts it in GP. Re-committing the samedraftIdreplays the first result (idempotent); it never double-posts.- Every write requires the
writescope and the object on the connection's whitelist and the draft→commit two-step — none can be bypassed.