metabase / metabase/metabase

MCP: search and read_resource payloads make agent retrieval unusable — one documented call crashes the transport

Open
#79,400 1 comment 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

.Needs Triage .Team/Metabot Priority:P3 Type:Bug
Dominant language
Clojure
Stars
49.3k
Forks
6.8k
Avg merge
1d 13h
Merged PRs (30d)
653

Description

### Describe the bug

### Summary

We use the Metabase MCP server as the data-access layer for an internal AI agent. Four defects in search and read_resource make the documented retrieval path unusable for agents: three of the four calls in a normal lookup exceed the client's context limit, and one documented call pattern crashes the MCP transport outright.

The ranking defect is the most consequential and the least obvious, so it's written up separately in (2) below. The transport crash in (1) is the most urgent.

All figures measured against Metabase Cloud on 2026-08-03/04 via the MCP server. Entity names are genericized; every number is verbatim from our measurements.

### **1. read_resource on two metric URIs returns ~4.8 MB and kills the transport**

Severity: this does not degrade, it fails. No partial content, no truncation, no recoverable error — the call dies and takes the session's transport with it.

```
read_resource(["metabase://metric/{id_1}", "metabase://metric/{id_2}"])

→ 4,782,988 characters
→ Failed to parse SSE message: Invalid JSON: EOF while parsing a string
at line 1 column 4782988
```

Two URIs. The tool's own description permits "Up to 5 URIs per call", and [docs/ai/mcp.md](https://github.com/metabase/metabase/blob/master/docs/ai/mcp.md) documents that same limit with no accompanying response-size limit. So following the documented guidance can take the transport down — and at 5 URIs the payload would be over twice what already broke it.

Expected: a server-side response size limit that truncates or errors cleanly, so a client can recover and retry with fewer URIs. Any bounded failure is better than an unparseable stream.

Also affected — same root cause, different endpoint. metabase://table/{id}/fields eagerly expands FK-target tables:

read_resource(["metabase://table/{id}/fields"])
→ 152,087 characters (~60K tokens) — exceeds client context limit

That table has ~21 columns of its own. The payload is large because the endpoint inlines the full field list of every FK target, including all ~120 columns of one dimension table, each with its full description. There is no way to ask for just the local columns.

Requested: a depth or projection parameter on read_resource (e.g. depth=0 for local fields only, or fields=[...]), plus a hard server-side response cap.

### **2. search ranks a metric below the saved questions derived from it**

This is the one we'd most like looked at, because it means good BI hygiene actively degrades agent retrieval.

Searching the exact name of a verified metric returns that metric at position 47 of 50, below 33 saved questions that are built on it:

```
search(term_queries=["Metric A"])
→ 50 results. The verified metric "Metric A (EUR)" is at position 47,
below 33 saved questions derived from it.

search(term_queries=["Metric A (EUR)"]) # fully-qualified name
→ same metric ranked #1
```

So retrieval only succeeds when the caller already knows the exact, fully-qualified name — which defeats the purpose of search.

Why this is a defect and not a ranking preference: a metric is a composable query source; a saved question derived from it is a leaf. For an agent, the metric is strictly more useful — it can be filtered and grouped, a question can't. The current order is inverted relative to usefulness. And it gets worse as teams do the right thing: every new dashboard question built on a certified metric pushes that metric further down. Teams with the best-governed metric layer will have the worst agent retrieval.

A concrete consequence: for our canonical revenue metric — correctly modeled, described, and verified — a natural search is absent from all 50 results:

```
search(term_queries=["revenue","bookings","margin"],
semantic_queries=["total revenue this quarter","gross margin by month"])
→ canonical revenue metric absent from all 50 results
```

The 5 metrics that did return were one deprecated measure, one target, two pipeline measures, and one component of the metric we wanted. Nothing about the metric is misconfigured; retrieval is what fails.

Requested: rank metric above question when both match comparably, or expose a relevance weight per entity type.

### **3. search has no result-type filter**

search accepts only term_queries and semantic_queries. There is no way to express "metrics only".

Type breakdown of a single broad revenue search :

Image

~58% of the payload is entity types an agent cannot use as a query source. Client-side filtering doesn't help: the tokens are already spent by the time the response arrives.

Requested: types=["metric"] (or equivalent) on search.

### **4. search has no field projection**

Every result carries its full multi-paragraph description whether or not the caller needs it — descriptions average ~1,390 characters per result. Combined with a fixed 50-result response, every search costs 50–63 KB regardless of how specific the query is:

Image

fields=["name","portable_entity_id","verified"] would take the first call from ~27.4K tokens to a few hundred — those three fields are all an agent needs to pick a source and proceed.

Note that total_count was exactly 50 on every search we ran, across three different business domains, independent of query specificity. There appears to be no way to request fewer.

Requested: a fields projection parameter, and/or a limit.

### **Impact**
Together these turn a 1.1-second query into a 10–20 minute agent session. Execution itself is not the problem — once the right metric is identified, construct_query + execute_query return the correct answer in running_time: 1088 ms. The entire cost is discovery: the agent burns turns recovering from oversized responses and re-searching with progressively more specific terms until it stumbles onto the exact metric name.

We've had to build a hand-maintained mapping of metric name → portable_entity_id in our client to bypass search altogether, plus a CI job to detect when that mapping drifts. We'd much rather delete both.

**Environment**
Metabase Cloud (*.metabaseapp.com), version TODO: fill in — Admin → Troubleshooting
Plan: TODO: fill in
MCP server over streamable HTTP; client is an MCP-capable LLM agent
Warehouse: BigQuery
Measured 2026-08-03 and 2026-08-04

**What we'd prioritize**
Server-side response size limit on read_resource — stops the hard failure (1)
Type-aware ranking on search — highest retrieval-quality gain (2)
types filter and fields projection on search — largest token reduction (3, 4)
Depth/projection on read_resource table fields (1, second half)

### To Reproduce

1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

### Expected behavior

_No response_

### Logs

_No response_

### Information about your Metabase installation

```JSON
{
"browser-info": {
"language": "en-US",
"platform": "MacIntel",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36",
"vendor": "Google Inc."
},
"metabase-info": {
"databases": [
"postgres",
"bigquery-cloud-sdk",
"athena",
"sqlite"
],
"run-mode": "prod",
"plan-alias": "pro-cloud",
"version": {
"date": "2026-08-04",
"tag": "v1.63.3",
"hash": "f0c5d38"
},
"settings": {
"report-timezone": "America/Montreal"
},
"hosting-env": "unknown",
"application-database": "postgres"
}
}
```

### Severity

transport crash has no workaround

### Additional context

_No response_

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with docs/ai/mcp.md and the MCP search and read_resource entry points described in the report. Reproduce the oversized metric and table-field responses, the transport parse failure, and the ranking and filtering behavior. Done means documented calls fail or truncate recoverably and provide bounded, more targeted retrieval through the requested ranking, type, field, limit, or depth controls.

Written by the indexing model from the issue text.

Assessment

Tech stack
clojure
Domain
ai, backend-api-design, search
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.