Skip to content

get-datasource-metadata fails on datasources with table relationships #364

@wjsutton

Description

@wjsutton

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

  1. 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.
  2. Call the get-datasource-metadata tool with the LUID of that datasource.
  3. Observe the Zod validation error no field metadata is returned.
  4. For comparison, call get-datasource-metadata with the LUID of a single-table datasource (e.g. a single CSV file).
  5. 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()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions