Skip to main content
Glama
ryanmichaeljames

Dataverse MCP Server

dataverse_get_attribute_change_history

Read-onlyIdempotent

Retrieve the audit trail for a single column of a specific record to see who changed that field and when. Includes audit configuration diagnostics when no changes are found.

Instructions

Retrieve the audit trail for ONE COLUMN of ONE RECORD — who changed this field.

The column-scoped sibling of dataverse_retrieve_record_change_history, which returns every change to the record across all audited columns. Use this one when the question is about a single field ('when did this account's creditlimit last change, and to what?'); it answers from the server rather than making you filter a whole record's history client-side.

MIND THE SINGULAR/PLURAL SPLIT — this tool takes the SAME TABLE TWICE, under two different names, and they are NOT interchangeable:

  • entity_set_name — the PLURAL collection name ('accounts'). This is the only one sent to the function, inside the target EntityReference.

  • table_logical_name — the SINGULAR logical name ('account'). Never sent to the function; used only by the audit-configuration probes described below, which address table metadata by LogicalName. Both are required because the plural cannot be derived from the singular (or vice versa: 'webresource' -> 'webresourceset'), and it cannot be looked up either — $filter on the root EntityDefinitions collection is refused with HTTP 400 [0x80060888]. Use dataverse_get_entity_sets to confirm the plural.

ON A 404, READ THE ERROR CODE — DO NOT ASSUME WHICH FAILURE IT IS. Live-confirmed on this function, and the older warning that a missing record and a wrong entity set were indistinguishable was wrong:

  • a well-formed but NONEXISTENT record id with the CORRECT plural entity set returns HTTP 200, not a 404. The function did not check that the record exists, so a bogus GUID (or a deleted record) simply yields zero changes and the audit_configuration diagnosis below. A successful empty answer is therefore never proof that the record is there.

  • a VALID id with the WRONG (singular) entity set returns HTTP 404 [0x80060888] "Resource not found for the segment ''" — which NAMES the bad segment. That message is the singular-for-plural slip, not a missing record: fix the entity set name rather than hunting a deleted row. Do NOT read those two as an exhaustive split. On the sibling function RetrieveRecordChangeHistory, a sweep of 15 entity sets found a THIRD outcome — [0x80048d02] from a CORRECT plural entity set ('audits'), meaning the row really was absent, i.e. some entity sets DO validate the target. That sweep was run against the record-scoped function, not this one, so it is not confirmed here; it is reason enough to read the code that actually came back rather than trust a two-case rule. THE ALL-ZERO GUID IS HANDLED DIFFERENTLY BY THE TWO FUNCTIONS, live-confirmed. Passing 00000000-0000-0000-0000-000000000000 as record_id returns HTTP 200 with zero changes HERE, but the record-scoped dataverse_retrieve_record_change_history rejects the same id with HTTP 400 [0x80040203] "Expected non-empty Guid." That is Dataverse's own inconsistency between the two messages, not this server's. So an empty, successful answer from this tool can mean the caller passed a placeholder id — check the id before reading zero changes as a fact about the record.

AN EMPTY RESULT IS AMBIGUOUS, AND THIS TOOL RESOLVES IT. Audit rows are written only where auditing is enabled at organization AND table AND column level, so zero changes cannot by itself distinguish "nothing ever changed" from "auditing was never switched on". ONLY when there are zero changes, three probes fire concurrently and an audit_configuration block is attached carrying organization_audit_enabled / table_audit_enabled / column_audit_enabled and a diagnosis naming the OUTERMOST disabled level:

  • auditing_off_at_organization / auditing_off_at_table / auditing_off_at_column

  • auditing_enabled_no_changes_recorded — all three on, so the empty answer is real (auditing still only records changes made after it was switched on)

  • undetermined — a probe failed or returned an unreadable shape; the level is reported as null with the reason in probe_errors, and NOTHING is guessed.

NOT EVERY ENTRY IS A RESULT. Dataverse MAY add org-level audit-CONFIGURATION rows (auditing itself switched on or off) to a response. They arrive when an audit-configuration change falls inside the TARGET RECORD'S history window, so their presence and count VARY BY TARGET — a record created after the last such change gets none, while older records on the same org got four each, live-measured. audit_configuration_events_count: 0 is a normal, expected answer. They can arrive anywhere in the list and are identified by their SHAPE — no @odata.type, AuditRecord and nothing else, and an all-zero AuditRecord._objectid_value — never by their position. They are split out into audit_configuration_events (with audit_configuration_events_count) and are NOT counted as changes: audit_details, count and has_more cover this column's own changes only.

ENTRIES ARE POLYMORPHIC. Each change is returned verbatim, so read its @odata.type: a column-scoped call is expected to yield AttributeAuditDetail (AuditRecord, OldValue, NewValue, InvalidNewValueAttributes, plus AuditRecord.attributemask naming the changed columns) but nothing guarantees it — AuditDetail has several subtypes. An entry with an UNRECOGNIZED @odata.type is reported as a change, never quietly dropped; only the typeless AuditRecord-only shape with an all-zero objectid is treated as configuration. detail_types counts the @odata.type values actually present on the returned page, and unclassified_typeless_count reports how many entries carrying NO @odata.type were kept as changes — 0 on every response observed so far, and a non-zero value means this tool met an entry it could not name rather than that anything was lost.

RESPONSE SHAPE IS CHECKED, NOT ASSUMED. The entries sit TWO levels down (AuditDetailCollection -> AuditDetails). If that container is absent or is not a list, the tool returns normalized: false with the raw body — a missing container is NOT reported as "no changes".

PagingInfo is not sent, so the changes are trimmed client-side to top and has_more reports whether anything was cut. total_record_count appears ONLY when Dataverse supplied a real count: it is live-confirmed to arrive as -1 here both with and without PagingInfo, and a negative count is suppressed rather than passed on. It is never substituted with the number of returned entries.

URL form: GET /api/data/v9.2/RetrieveAttributeChangeHistory( Target=@t,AttributeLogicalName=@a) ?@t={"@odata.id":"accounts()"}&@a='name'

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
paramsYes

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
resultYes
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Even with annotations declaring readOnlyHint, openWorldHint, idempotentHint, and destructiveHint=false, the description goes far beyond them. It discloses that a nonexistent record ID returns HTTP 200 with zero changes, that a wrong singular entity set returns a specific 404, that empty results trigger audit-configuration probes, and that the response shape is checked rather than assumed. These are rich behavioral details not present in annotations and critical for correct use.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every section earns its place: each bolded segment addresses a distinct, verified behavior or pitfall. It is front-loaded with the purpose, then structured by topic (singular/plural, error codes, empty results, entry polymorphism, response shape, paging). The formatting with bullets and code snippets improves scannability without waste.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex tool with a nested input schema and an output schema, the description covers all critical aspects: the exact URL form, how to identify configuration events vs real changes, polymorphic entries, paging behavior, and the handling of ambiguous empty results. It even notes the differences from the sibling function. This leaves little to guess even for a caller unfamiliar with Dataverse audit APIs.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is reported as 0%, so the description must carry the weight, and it does. It devotes a full section to the singular/plural split, explaining that entity_set_name is the plural, sent to the function, while table_logical_name is the singular, never sent and used only for probes. It also explains record_id semantics (must be well-formed GUID, all-zero GUID behavior) and column_logical_name, all adding meaning beyond the raw schema field names.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise statement: 'Retrieve the audit trail for ONE COLUMN of ONE RECORD — who changed this field.' It then distinguishes itself from the sibling dataverse_retrieve_record_change_history by calling itself the 'column-scoped sibling,' making the scope and resource unmistakable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says 'Use this one when the question is about a single field' and contrasts it with the record-scoped alternative. It also points to dataverse_get_entity_sets as a helper to confirm the plural entity set name, and gives detailed instructions on how to interpret 404 vs 200 responses, which is when-to-use guidance beyond the basics.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/ryanmichaeljames/dataverse-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server