Skip to content

mcp: Respect cluster USAGE in MCP data product discovery (DEX-66) - #37495

Merged
bobbyiliev merged 4 commits into
MaterializeInc:mainfrom
bobbyiliev:dex-66-cluster-usage
Jul 20, 2026
Merged

mcp: Respect cluster USAGE in MCP data product discovery (DEX-66)#37495
bobbyiliev merged 4 commits into
MaterializeInc:mainfrom
bobbyiliev:dex-66-cluster-usage

Conversation

@bobbyiliev

@bobbyiliev bobbyiliev commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Fixes DEX-66: get_data_products advertised an object's index/compute cluster even when the role lacked USAGE on it. The MCP data-product views now null the cluster unless the role has USAGE (the product stays listed and readable from a serving cluster), and read_data_product falls back to the session's serving cluster instead of erroring.

@bobbyiliev bobbyiliev changed the title environmentd: Respect cluster USAGE in MCP data product discovery (DEX-66) mcp: Respect cluster USAGE in MCP data product discovery (DEX-66) Jul 7, 2026
@bobbyiliev
bobbyiliev force-pushed the dex-66-cluster-usage branch 5 times, most recently from c5cbdc9 to 4cd8e47 Compare July 7, 2026 19:12

@bobbyiliev bobbyiliev left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heads-up, the diff looks large, but most of it is regenerated .slt snapshots, the docs markdown, and comment updates. The actual logic change is small. If it helps when reviewing, the main things are the two view SQL edits in mz_internal.rs and the read_data_product routing in mcp.rs (null cluster → falls back to the session's serving cluster instead of erroring).

@bobbyiliev
bobbyiliev force-pushed the dex-66-cluster-usage branch from 4cd8e47 to dade6e2 Compare July 8, 2026 13:30
@bobbyiliev
bobbyiliev marked this pull request as ready for review July 8, 2026 13:39
@bobbyiliev
bobbyiliev requested review from a team as code owners July 8, 2026 13:39
@bobbyiliev
bobbyiliev requested review from ggevay, jubrad and sjwiesman July 8, 2026 13:39
@sjwiesman

sjwiesman commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Can we added the expected rules for what counts as a data product to the description in the docs. And if there are multiple clusters what we advertise?

@bobbyiliev

Copy link
Copy Markdown
Contributor Author

@sjwiesman I just updated the docs with the exact rules.

That said, these rules just document what the view currently does. If we don't agree this is the right definition of a data product (e.g. should a comment be required? should non-indexed matviews qualify?), now is the time to change the logic, while the MCP server is still in public preview and we can redefine it without breaking anyone. Let me know what you think.

@sjwiesman

Copy link
Copy Markdown
Contributor

looks great, thank you!

@ggevay ggevay left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments from me and Claude.

| ------------- | -------- | ---------------------------------------------------------------------------------------- |
| `object_name` | [`text`] | Fully qualified object name (database.schema.name). |
| `cluster` | [`text`] | Cluster where the object computes or its index is hosted. Reads from any cluster work, but only reads on this cluster benefit from the index. |
| `cluster` | [`text`] | Cluster hosting the object's index or compute, shown only when your role has USAGE on it (otherwise null). Reads still work from any cluster you can use, but only reads on this cluster benefit from the index. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd reorder the 3 pieces of information in this description. Currently:

  1. Cluster hosting the object's index or compute
  2. shown only when your role has USAGE on it (otherwise null)
  3. Reads still work from any cluster you can use, but only reads on this cluster benefit from the index.

I'd say 1. and 3. belong together, and 2. is currently intruding into the reading flow. So, I'd order these as 1., 3., 2.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, just reordered to 1, 3, 2 in both mz_internal.md and mz_internal.rs column_comments so the reading flow reads naturally.

| ------------- | -------- | ---------------------------------------------------------------------------------------- |
| `object_name` | [`text`] | Fully qualified object name (database.schema.name). |
| `cluster` | [`text`] | Cluster where the object computes or its index is hosted. Reads from any cluster work, but only reads on this cluster benefit from the index. |
| `cluster` | [`text`] | Cluster hosting the object's index or compute, shown only when your role has USAGE on it (otherwise null). Reads still work from any cluster you can use, but only reads on this cluster benefit from the index. |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(same here)

with a named cluster and with a null cluster when the role can use only some
of its clusters. When reading a data product, the MCP server routes the read
to an advertised cluster so it benefits from the index, and falls back to the
session's default cluster when no advertised cluster is usable.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When reading a data product, the MCP server routes the read
to an advertised cluster so it benefits from the index, and falls back to the
session's default cluster when no advertised cluster is usable.

This is only by default, right? Maybe say it explicitly that a specific cluster can also be specified in read_data_product.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, a potential bigger issue is that this fallback might be to a cluster where there is no index for a view, right? In that case, we run into the full recomputation issue, which we decided earlier to avoid. Is this intentional?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm yes, updated it to call out that read_data_product also takes an explicit cluster argument to override the default routing. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed. The view now excludes plain views when the role has no USAGE on any of their index clusters, same reason we already exclude non-indexed views. MVs still appear with a null cluster because they serve from persist. Added a negative test and updated the read_data_product doc comment.

Comment thread src/environmentd/src/http/mcp.rs Outdated
/// when the role has USAGE on it, so reads of indexed objects hit the index's
/// in-memory arrangement. That column is null when the role lacks USAGE on the
/// object's cluster (DEX-66); in that case, and absent an override, the read
/// runs on the session's default (serving) cluster instead. That still works

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude:

The agent-facing strings still describe the old routing: the endpoint instructions (~line 692) and the read_data_product cluster param description (~line 828) say the read runs on "the cluster recorded in the data product catalog", without saying what a null cluster in discovery means. The sharpest case is the query-tool-only config (enable_mcp_agent_read_data_product_tool off): its instructions say to pass "the cluster from get_data_product_details", the query tool requires cluster, and the agent endpoint exposes no catalog tool, so a null advertised cluster leaves the agent with no way to find a usable cluster name. A sentence in each place would cover it, e.g. "a null cluster means your role lacks USAGE on the object's cluster; read_data_product without an override then reads on your session's cluster".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a sentence to both agent-facing strings explaining null cluster semantics

simple conn=agent_restricted,user=agent
SELECT object_name, cluster FROM mz_internal.mz_mcp_data_products WHERE object_name = '"materialize"."agent_objects"."transfer_windows"';
----
"materialize"."agent_objects"."transfer_windows",quickstart

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude:

Could we also add coverage for an object advertised under both a usable and an unusable cluster (e.g. a view indexed on both quickstart and agent_compute)? That would pin the "appears both with a named cluster and with a null cluster" behavior the docs promise, and the ORDER BY dp.cluster NULLS LAST preference in read_data_product's lookup. If that ordering regressed, reads would silently fall back to the serving cluster even when a usable index exists, which no current test would catch.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the test!

@bobbyiliev
bobbyiliev requested a review from ggevay July 20, 2026 16:45

@ggevay ggevay left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@bobbyiliev
bobbyiliev force-pushed the dex-66-cluster-usage branch from 6532472 to ac22108 Compare July 20, 2026 19:40
@bobbyiliev
bobbyiliev merged commit 2ba3150 into MaterializeInc:main Jul 20, 2026
127 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants