Bug: get-datasource-metadata fails on datasources with table relationships
Describe the bug
The get-datasource-metadata tool throws a Zod client-side validation error and returns no field metadata when called against a published datasource that contains multiple tables connected via Tableau relationships. The Tableau API itself returns HTTP 200 with a valid response body, so the failure occurs entirely within the MCP server's response parsing.
Single-table datasources work correctly and return full field metadata as expected.
Reproduced using Claude (claude.ai) as the MCP host.
Expected behavior
get-datasource-metadata should return full field metadata including field names, data types, roles, and aggregation defaults for all published datasources, regardless of whether they contain single or multiple related tables.
Versions
Tableau version: Tableau Server Version: 2025.3.1 (20253.25.1210.1815) 64-bit Linux
Tableau MCP server version: @tableau/mcp-server@latest 2.2.4 Public https://www.npmjs.com/package/@tableau/mcp-server
Steps to reproduce
- Publish (or use an existing) datasource on Tableau Server that contains multiple tables connected via Tableau relationships e.g. a Superstore datasource with Orders, People, and Returns tables.
- Call the
get-datasource-metadata tool with the LUID of that datasource.
- Observe the Zod validation error no field metadata is returned.
- For comparison, call
get-datasource-metadata with the LUID of a single-table datasource (e.g. a single CSV file).
- Observe that the single-table datasource returns full field metadata successfully.
Logs
Failing case: multi-table datasource with relationships (Superstore Orders + People + Returns)
Tool input:
{
"datasourceLuid": "acea80cc-a16a-4729-a513-a0daf2dca7cc"
}
Tool output / error:
Zodios: Invalid response from endpoint 'post /get-datasource-model'
status: 200
cause:
[
{
"code": "invalid_type",
"expected": "object",
"received": "undefined",
"path": ["logicalTableRelationships", 0, "expression"],
"message": "Required"
},
{
"code": "invalid_type",
"expected": "object",
"received": "undefined",
"path": ["logicalTableRelationships", 1, "expression"],
"message": "Required"
}
]
received:
{
"logicalTables": [
{ "logicalTableId": "Orders_ECFCA1FB690A41FE803BC071773BA862", "caption": "Orders" },
{ "logicalTableId": "People_D73023733B004CC1B3CB1ACF62F4A965", "caption": "People" },
{ "logicalTableId": "Returns_2AA0FE4D737A4F63970131D0E7480A03", "caption": "Returns" }
],
"logicalTableRelationships": [
{
"fromLogicalTable": { "logicalTableId": "Orders_ECFCA1FB690A41FE803BC071773BA862" },
"toLogicalTable": { "logicalTableId": "People_D73023733B004CC1B3CB1ACF62F4A965" }
},
{
"fromLogicalTable": { "logicalTableId": "Orders_ECFCA1FB690A41FE803BC071773BA862" },
"toLogicalTable": { "logicalTableId": "Returns_2AA0FE4D737A4F63970131D0E7480A03" }
}
]
}
Note: the expression field is absent from both relationship objects in the API response. The Zod schema marks it as required, causing the otherwise valid 200 response to be rejected.
Passing case: single-table datasource (HM Land Registry House Price Paid 2025)
Tool input:
{
"datasourceLuid": "20774db2-ede0-4706-a349-d3fcadbd356c"
}
Tool output (truncated for brevity):
{
"datasourceDescription": "HM Land Registry - Residential Property Price Paid Data ...",
"datasourceModel": {
"logicalTables": [
{
"logicalTableId": "pp-2025-clean.csv_35B9C215B84C44549379B9BA0E00E543",
"caption": "pp-2025-clean.csv"
}
],
"logicalTableRelationships": []
},
"fieldGroups": [
{
"logicalTableId": "pp-2025-clean.csv_35B9C215B84C44549379B9BA0E00E543",
"fields": [
{ "name": "Price", "dataType": "INTEGER", "role": "MEASURE", "defaultAggregation": "SUM", "dataCategory": "QUANTITATIVE" },
{ "name": "Date of Transfer", "dataType": "DATETIME", "role": "DIMENSION", "defaultAggregation": "YEAR", "dataCategory": "ORDINAL" },
{ "name": "Postcode", "dataType": "STRING", "role": "DIMENSION", "defaultAggregation": "COUNT", "dataCategory": "NOMINAL" }
]
}
],
"parameters": []
}
This succeeds because logicalTableRelationships is an empty array no relationship objects are present, so the missing expression property is never encountered by Zod.
Root cause
The Zod response schema for logicalTableRelationships entries marks expression as required. When a datasource uses Tableau's native relationship model and the relationship expression is implicit or not explicitly authored, the Tableau API omits the expression key entirely. The fix is to mark expression as optional in the schema:
// Before
expression: z.object({ ... })
// After
expression: z.object({ ... }).optional()
Bug:
get-datasource-metadatafails on datasources with table relationshipsDescribe the bug
The
get-datasource-metadatatool throws a Zod client-side validation error and returns no field metadata when called against a published datasource that contains multiple tables connected via Tableau relationships. The Tableau API itself returns HTTP 200 with a valid response body, so the failure occurs entirely within the MCP server's response parsing.Single-table datasources work correctly and return full field metadata as expected.
Reproduced using Claude (claude.ai) as the MCP host.
Expected behavior
get-datasource-metadatashould return full field metadata including field names, data types, roles, and aggregation defaults for all published datasources, regardless of whether they contain single or multiple related tables.Versions
Tableau version: Tableau Server Version: 2025.3.1 (20253.25.1210.1815) 64-bit Linux
Tableau MCP server version: @tableau/mcp-server@latest 2.2.4 Public https://www.npmjs.com/package/@tableau/mcp-server
Steps to reproduce
get-datasource-metadatatool with the LUID of that datasource.get-datasource-metadatawith the LUID of a single-table datasource (e.g. a single CSV file).Logs
Failing case: multi-table datasource with relationships (Superstore Orders + People + Returns)
Tool input:
{ "datasourceLuid": "acea80cc-a16a-4729-a513-a0daf2dca7cc" }Tool output / error:
Note: the
expressionfield is absent from both relationship objects in the API response. The Zod schema marks it as required, causing the otherwise valid 200 response to be rejected.Passing case: single-table datasource (HM Land Registry House Price Paid 2025)
Tool input:
{ "datasourceLuid": "20774db2-ede0-4706-a349-d3fcadbd356c" }Tool output (truncated for brevity):
{ "datasourceDescription": "HM Land Registry - Residential Property Price Paid Data ...", "datasourceModel": { "logicalTables": [ { "logicalTableId": "pp-2025-clean.csv_35B9C215B84C44549379B9BA0E00E543", "caption": "pp-2025-clean.csv" } ], "logicalTableRelationships": [] }, "fieldGroups": [ { "logicalTableId": "pp-2025-clean.csv_35B9C215B84C44549379B9BA0E00E543", "fields": [ { "name": "Price", "dataType": "INTEGER", "role": "MEASURE", "defaultAggregation": "SUM", "dataCategory": "QUANTITATIVE" }, { "name": "Date of Transfer", "dataType": "DATETIME", "role": "DIMENSION", "defaultAggregation": "YEAR", "dataCategory": "ORDINAL" }, { "name": "Postcode", "dataType": "STRING", "role": "DIMENSION", "defaultAggregation": "COUNT", "dataCategory": "NOMINAL" } ] } ], "parameters": [] }This succeeds because
logicalTableRelationshipsis an empty array no relationship objects are present, so the missingexpressionproperty is never encountered by Zod.Root cause
The Zod response schema for
logicalTableRelationshipsentries marksexpressionas required. When a datasource uses Tableau's native relationship model and the relationship expression is implicit or not explicitly authored, the Tableau API omits theexpressionkey entirely. The fix is to markexpressionas optional in the schema: