Changelog
All notable changes to MCP4Acumatica are documented here. The format is based on
Keep a Changelog, and the project follows
semantic-ish versioning. Release tags use the form 25R2-<version> (the 25R2
prefix tracks the targeted Acumatica release, 2025 R2).
[0.38.0] - 2026-07-08
Added
acumatica_query_dac— query Acumatica data access classes (DACs) directly via the OData v4 DAC endpoint (/t/{tenant}/api/odata/dac), bypassing Generic Inquiries and the contract API. Server-side$filter/$select/$orderbywith bounded pagination, plus optional$applyaggregation passthrough (graceful error when the instance rejects it). Closes the "GI too wide for the response limit" and "contract API can't filter it" data-access gaps (e.g. ARTran by TranCostFinal, GLTran by account, INTran/INCostStatus detail). Auth/identity DACs (Users, Roles, …) are blocked.acumatica_describe_dac— sample-row field schema for a DAC (mirrorsacumatica_describe_inquiry), cached 1 h.- Preflight probe for the DAC OData endpoint on
/docs/admin/preflight(WARN when the instance predates Acumatica 2023 R1 or has OData disabled).
Changed
AcumaticaClient: GI OData +$metadatafetches now share oneodataRequest()helper (behavior unchanged); newgetODataDac().
[0.37.0] - 2026-06-20
Fixed
topNwas capped at 1000 regardless ofACUMATICA_MAX_RECORDS. ThetopNparameter schema had a hardcodedmax(1000)that silently overrode the env/config cap, so raisingACUMATICA_MAX_RECORDSto 2000 had no effect for callers. The schema bound is now10000, makingACUMATICA_MAX_RECORDS(runtime-overridable via the admin console) the real governing limit.REDACT_SKIPnow exempts the value-shape (SSN/card) scan, not just name-based redaction. A 9-digitInventoryIDmatched the SSN value-shape pattern and was redacted, corrupting line data. A whitelisted field is now left entirely alone. Deployments setREDACT_SKIP=InventoryID.
Added
- Bounded pagination on
acumatica_list_entitiesandacumatica_run_inquiry(src/lib/pagination.ts). A newoffsetparameter pages via OData$skip; a full page returnspaginationSupported: truewithhasMore+nextOffset. Bounded toMAX_PAGES(5) — past that the model is told to narrow the filter or use an aggregated Generic Inquiry. Replaces the prior hard "no pagination" refusal. (Reliable paging needs a stable$orderby/GI sort order.) - Graceful oversized-response handling.
AcumaticaClient.parseJson()converts a truncated/too-large body (the "Unexpected end of JSON input" parse failure) into a clear "response too large — narrow the query or use an aggregated GI" message instead of a raw error.
[0.36.0] - 2026-06-16
Added
- Opt-in write tools —
acumatica_create_purchase_orderandacumatica_create_invoice, gated behind a newACUMATICA_WRITES_ENABLEDenv var (defaultfalse). When not set, the tools never register and the server remains entirely read-only. Enabled at deploy time viawrangler.jsoncvars or the Cloudflare dashboard. - Two-step dry-run/confirm gate. Each write tool accepts a
confirmparameter. Calling withoutconfirm="true"returns an echo of the exact request body that would be sent, with nothing written. Calling withconfirm="true"and a uniqueidempotencyKeyexecutes the create. This forces the model to show the user what it's about to do before committing. - Per-tenant+user idempotency keys (
write_idem:{tenant}:{username}:{key}in KV). A confirmed call writes apendingmarker before the Acumatica PUT, then updates it todoneon success. A retried confirmed call with the same key that findsdonereturns the original response without a second request. An unconfirmed prior attempt (pendingmarker withoutdone) refuses a blind retry and instructs the model to ask the user to verify in Acumatica first, preventing duplicate documents from network retries or model loops. AcumaticaClient.put()— new method on the shared HTTP client that wraps aPUTto the contract API. UseswrapFields()to build the wire body (inverse ofunwrapFields), now extracted tosrc/lib/wrap-fields.ts.unwrapFieldsmoved tosrc/lib/unwrap-fields.ts(re-exported from its previous location for backward compatibility).- New files:
src/lib/wrap-fields.ts,src/lib/unwrap-fields.ts,src/lib/put-init.ts,src/tools/writes/write-common.ts,src/tools/writes/create-purchase-order.ts,src/tools/writes/purchase-order-build.ts,src/tools/writes/create-invoice.ts,src/tools/writes/invoice-build.ts.
[0.35.0] - 2026-06-13
Fixed
substringof/startswith/endswithfilters silently returned[]. Acumatica's contract-REST$filterparser returns an empty set (HTTP 200, no error) when a boolean string function is compared to a literal —substringof('X', Field) eq true— but works for the bare function. Models habitually appendeq true(valid OData v3), so every partial-text/"contains" search returned zero rows.normalizeODataFilter()(src/lib/odata-filter.ts) now strips a trailingeq trueoff the three boolean functions before the request goes out, for bothacumatica_list_entitiesandacumatica_run_inquiry.eq falseis left verbatim — the only equivalent negation (not substringof(...)) is rejected by the contract API with a 500. (This was a parser quirk, not a URL-encoding bug.)
Added
- Structured errors for non-optimizable
$filterqueries. Acumatica's OData filter binder 500s when it can't apply a$filterto a complex document entity (unbound/computed/BQL-delegate field →CannotOptimizeException, a child-collection field → "not a single value", a type mismatch, or an unknown field).getFilterErrorKind()(src/lib/complex-entities.ts) classifies these andacumatica_list_entitiesreturns a structured, actionable error (filterNotApplicable: true,filterErrorKind, a key-field hint, and a pointer toacumatica_describe_entity/ a Generic Inquiry) instead of an opaque "Acumatica internal error". - False-negative guard for complex document entities. When
acumatica_list_entitiesreturns 0 rows on a non-key filter against a known complex entity (PurchaseOrder,Shipment,PhysicalInventoryCount), the response now includes apossibleFalseNegative: truewarning — Acumatica can silently drop a non-optimizable filter and return[]even when matching records exist, so the model is told not to conclude "no such record exists" and to verify with a keyed lookup or a Generic Inquiry. - Tool descriptions for
acumatica_list_entities/acumatica_run_inquirynow tell the model to write boolean functions bare (noeq true) and call out the complex-document-entity filtering limitation. - Unit-test harness — first tests in the repo (
test/, Node's built-innode --testrunner with TypeScript type-stripping, zero new dependencies), wired tonpm test. CoversnormalizeODataFilterand the filter-error classification helpers.
[0.34.2] - 2026-06-11
Fixed
- The OIDC-fallback
UserSecurityInfoidentity lookup in/callbackhardcoded the contract version25.200.001instead of usingACUMATICA_ENDPOINT_VERSION. On a re-targeted instance (e.g. 26R1) that path would 404, silently dropping users to the UUID-based key fallback and breaking token reuse across sessions. Now uses the configured endpoint version like every other contract-API URL. (Originally authored by Adam Coates in the hoser-dev fork.)
[0.34.1] - 2026-06-11
Docs
- Documented the DAC-layer stance: DAC metadata is intentionally not a tool — stock DACs are covered by Acumatica's public DAC Schema Browser (
help.acumatica.com/dacBrowser, reachable via the client's web access), custom DACs by the customization source, and API-exposed custom fields by the existing schema tools. (A DAC-via-GI customization was prototyped and dropped as redundant + high-maintenance.) - Added a "DAC-layer questions" pointer to
/docs/schema-discovery; removed a third-party-comparison aside and a stale "DAC index (planned)" item from the upgrade guide.
[0.34.0] - 2026-06-11
Added
- Schema-knowledge tools for power users building integrations and customizations:
acumatica_search_schema— find entities by name/keyword and/or "which entities contain field X".acumatica_get_schema_entity— full offline schema for one entity (fields + types, actions,$expandsub-entities).acumatica_list_schema_entities— browse/filter the entity catalog by name/module prefix.acumatica_explain_gi_xml— stateless structural summary of a pasted Generic Inquiry definition XML (tables, joins, parameters, filters, results).
- These answer from an offline schema index built from your instance's own
swagger.json(always current, includes your customizations, no third-party IP), instead of sampling live records to infer shape. The three index-backed tools register only when the index is present; the GI explainer is always available. - New platform abstraction
IBlobStore(CF implCloudflareR2BlobStore) onAppEnv.indexStore, backed by a newmcp4acumatica-indexR2 bucket (INDEX_STORE). Self-hosting story preserved. - Open-source ingestion scripts (
scripts/build-schema-index.mjs,scripts/upload-indexes.mjs) +npm run build-index;setup.shbuilds/uploads the schema index automatically after deploy whenswagger.jsonis present. - New
/docs/schema-discoverydocumentation page.
Notes
- Acumatica documentation lookups are intentionally not a tool — the public Help Wiki (https://help.acumatica.com/) is reachable via the AI client's own web search. DAC metadata and GI XML example libraries are planned as later, private-index workstreams.
[0.33.2] - 2026-06-08
Added
- This
CHANGELOG.md(full history reconstructed from git tags/commits) and a/docs/changelogpage on the documentation site.
Docs
- Commit and close-session checklists now include a changelog-update step.
[0.33.1] - 2026-06-07
Fixed
- Sessions no longer die after ~1 hour of idle. Root cause:
/authorizenever requested theoffline_accessscope, so Acumatica/IdentityServer issued no refresh token — the stored refresh token was empty and every refresh failed with400 invalid_request. Now requestsoffline_access(the Connected App must permit it). TokenManagerreadToken()reconciles DO storage vs KV by recency, so a failed callback seed can't pin a stale, already-rotated token.
Added
token_resolve_outcomediagnostic logging (reason on every non-ok token resolution).- Self-hosting guide now documents the
offline_accessrequirement and theITokenProviderserialization step.
[0.33.0] - 2026-06-07
Changed
- Per-user token-refresh serialization via a new
TokenManagerDurable Object. All token access for a user funnels through one globally-unique DO (idFromName(username)), coalescing concurrent refreshes. Eliminates the cross-isolate rotation race where concurrent sessions reused a rotated refresh token and one was spuriously evicted. - Token logic kept platform-agnostic behind a new
ITokenProviderabstraction onAppEnv(CF implDOTokenProvider; self-host = a distributed lock).
Docs
- Documented that Claude.ai authenticates via CIMD (not DCR), and recorded the Claude.ai reconnect/dead-state and
/authorize-500-on-bad-client_id findings.
[0.32.1] - 2026-06-06
Fixed
- Dead refresh tokens are now classified by HTTP status (any
4xx→ re-auth;5xx/429→ transient), not by matching theinvalid_grantstring — Acumatica's400body doesn't reliably parse to that code, which previously made the model loop on "try again shortly" instead of re-authenticating.
[0.32.0] - 2026-05-29
Added
- Transparent re-auth on a dead Acumatica refresh token: a
ReauthRequiredErrorrevokes the user's MCP grant so the client silently re-runs OAuth instead of a manual disconnect/reconnect.
[0.31.1] - 2026-05-10
Docs
- Documented the Generic Inquiry "no description metadata" gap and three potential cure paths.
[0.31.0] - 2026-05-10
Added
CONTRIBUTING.mdandSECURITY.md;.claude/gitignored; anonymized theworkers.devhostname in tracked config.
[0.30.1] - 2026-05-10
Changed
- Set
preview_urls: truein the trackedwrangler.jsoncso deploys don't flip it off on config drift.
[0.30.0] - 2026-05-10
Added
- GUI install path ("Deploy to Cloudflare" button) with
wrangler.jsoncas the tracked deploy template; one-shotsetup.shand one-lineinstall.sh. - Preflight diagnostics (
/docs/admin/preflight) and/callbackOAuth-error mapping.
Changed
- Tool-description rework (instance-specific ID wording, lookup pointers, expand/denylist/cache disclosures);
runGetterempty-string guard for required path params.
[0.29.1] - 2026-04-16
Fixed
- Persist the DO log buffer to
ctx.storageso a buffer flush survives DO eviction (alarm runs on a fresh instance).
[0.29.0] - 2026-04-16
Security
- Closed the full security-audit review (all critical/high/medium/low items).
[0.26.1] - 2026-04-16
Fixed
- Use a full UUID (not an 8-char slice) for R2 log filenames to avoid collisions.
[0.26.0] - 2026-04-16
Security
- Closed audit items M1, M2, M5, M6.
[0.25.0] - 2026-04-16
Security
- Closed audit criticals C1–C3 and mediums M3, M7.
[0.24.1] - 2026-04-16
Fixed
- Flush the audit-log buffer on a DO alarm, not only on the next log arrival.
[0.24.0] - 2026-04-16
Changed
- Removed the server-side pagination cooldown guard in favor of a structured pagination-refusal envelope (
truncated,paginationSupported: false,actionRequired). - Fixed the
acumatica_max_recordsKV override and hardenedtopNcoercion.
[0.23.2] - 2026-04-16
Changed
- Sped up the admin log viewer (streaming server-side pagination) and buffered DO logs into fewer R2 files.
[0.23.1] - 2026-04-09
Fixed
- DO tool logs weren't visible in the admin console — write them directly to R2 from the DO.
[0.23.0] - 2026-04-09
Added
- Storage abstraction layer (
IKeyValueStore+AppEnv) for platform portability; self-hosting guide.
[0.22.1] - 2026-04-09
Docs
- Added the close-session procedure to CLAUDE.md.
[0.22.0] - 2026-04-09
Added
- Admin console: log viewer, settings management, and R2 Logpush.
[0.21.0] - 2026-04-08
Added
- Pagination guard and anti-pagination tool descriptions (later superseded by the 0.24.0 refusal envelope).
[0.20.2] - 2026-04-08
Docs
- Documented access control, consent, redaction, and audit logging.
[0.20.1] - 2026-04-08
Changed
- Renamed the project to MCP4Acumatica.
[0.20.0] - 2026-04-08
Added
- KV-backed metadata cache (entity schemas 24h; GI lists/field schemas 1h) and the
acumatica_clear_cachetool.
[0.19.1] - 2026-04-08
Changed
- Clarified the
topNmax-1000 limit in tool descriptions.
[0.19.0] - 2026-04-08
Added
- Access controls: canary-GI role gate, consent interstitial, sensitive-field redaction, and enhanced audit logging.
[0.18.1] - 2026-04-08
Changed
- Filter parameterized GIs out of
acumatica_list_generic_inquiries.
[0.18.0] - 2026-04-08
Changed
- Renamed
ACUMATICA_COMPANY→ACUMATICA_TENANT; added a configurable record limit; restored GI tools over OData with OAuth 2.0 Bearer tokens.
[0.17.0] - 2026-04-07
Changed
- Removed unused code and consolidated the KV namespaces.
[0.16.0] - 2026-04-07
Added
- GI discovery tools:
acumatica_list_generic_inquiries,acumatica_describe_inquiry.
[0.15.0] - 2026-04-07
Added
- CIMD support alongside DCR; OpenID Connect discovery endpoint (for ChatGPT compatibility).
[0.14.0] - 2026-04-07
Added
- Documentation website served from
/docson the same Worker.
[0.13.0] - 2026-04-07
Added
- Schema discovery tool:
acumatica_describe_entity.
[0.12.0] - 2026-04-07
Added
- Generic list/search tool:
acumatica_list_entities.
[0.11.0] - 2026-04-06
Added
- Generic Inquiry tool:
acumatica_run_inquiry.
[0.10.0] - 2026-04-06
Added
- CRM Activity read-only tools: Email, Event, Activity, Task.
[0.9.0] - 2026-04-06
Added
- HR & Payroll read-only tools: Employee, ExpenseClaim, TimeEntry.
[0.8.0] - 2026-04-06
Added
- Shipping & Fulfillment read-only tools: Shipment, SalesInvoice.
[0.7.0] - 2026-04-06
Added
- Sales & CRM read-only tools: Contact, BusinessAccount, Opportunity, Lead, Salesperson.
[0.6.0] - 2026-04-06
Added
- Service & Field read-only tools: Case, ServiceOrder, Appointment.
[0.5.0] - 2026-04-06
Added
- Projects read-only tools: Project, ProjectTask, ProjectBudget, ProjectTransaction.
[0.4.0] - 2026-04-06
Added
- Purchasing read-only tools: PurchaseOrder, PurchaseReceipt.
[0.3.0] - 2026-04-06
Added
- Inventory & Warehouse read-only tools: StockItem, NonStockItem, availability inquiries, Warehouse, ItemClass.
[0.2.0] - 2026-04-06
Added
- Financial/Accounting read-only tools: Invoice, Bill, JournalTransaction, Payment, Account, Check.
[0.1.0] - 2026-04-06
Added
- Initial Acumatica MCP server: OAuth auth (Acumatica as sole IdP) and the first read-only tools (Customer, Vendor, SalesOrder). Microsoft Entra ID removed in favor of direct Acumatica OAuth.