# Design note: who decides which cluster an agent's query runs on

Written 2026-08-03 for a second opinion. It states a problem we hit, four ways to answer it, and
which one I would pick. I have tried to argue against my own choice in the last section, because
the useful thing to get back is where the reasoning is weak, not agreement.

## The system, briefly

`tidbcloud-mcp` is an MCP server for TiDB Cloud. It has two shapes of use in one codebase.

**Exploration.** An engineer or agent in an IDE lists clusters, runs SQL, inspects schemas. Many
clusters are in scope and the caller chooses per call. Every tool takes a `clusterId` parameter.

**Templates.** An operator registers named, parameterized SQL statements in a file. Each becomes a
tool `tidb_tpl_<name>`. The caller supplies values and can never supply a query. `--templates-only`
removes free-form query, execute, schema and management tools, leaving the registered statements
plus read-only documentation and capability tools that need no cluster at all. This is what a
no-code platform or an agent product consumes.

Credentials: normally the server mints a short-lived SQL user per cluster through the management
API, which is why one server can serve many clusters. A deployment can instead be given one fixed
SQL user, in which case it is bound to whatever cluster that user belongs to.

## What happened

We connected a Workato Genie (an LLM agent) to a templates-only deployment with four skills:
search resolved tickets, get account context, check repeat contact, record a resolution.

First question asked of it: _"A customer says their integration platform cannot complete the TLS
handshake. What do we know about this?"_

The agent picked the right skill and extracted the parameters correctly:

```json
{
  "clusterId": "system",
  "query_text": "integration platform cannot complete TLS handshake",
  "product_filter": "",
  "top_k": 5
}
```

Everything except `clusterId` is right. `"system"` is invented. The server refused it:

```
Template search_known_issues failed: Cluster "system" is not permitted by this server's
cluster allow/blocklist.
```

The agent then asked the user for a cluster id and offered `CLUS-1234` as the format. Real ids are
20-digit numbers. So it invented a value, was refused, and then invented a format.

## Why it happened, and the part that makes it a design question

The obvious reading is "the agent needs to be told the cluster id". Three facts make that the
wrong reading.

**The server already knew.** This deployment runs with `--cluster-allow <one id>`. That is what
refused the invented value. The server was asking a question it had the answer to.

**The credential already bound it.** This deployment holds one fixed SQL user belonging to one
cluster. It could not have connected to any other cluster if the agent had named one.

**The template already names its database.** Every statement in the file is written against a
specific schema:

```sql
SELECT ... FROM workato_poc.tickets WHERE status = 'resolved' ...
```

So the tool asks which cluster, while the SQL it will run has already decided which database. A
template that names `workato_poc.tickets` and then asks the caller where that lives is incoherent
on its own terms.

**And we already answered this question the other way elsewhere.** The same product has a REST
transport for the same templates. There, the host and database are deployment configuration and
the actions take only a table name. Two transports over one definition currently disagree about
whether the target is part of the request.

## The question

Is `clusterId` part of the request, or part of the deployment?

For exploration it is clearly part of the request: browsing many clusters is the point.

For templates I think it is deployment configuration, and the argument is that a template is a
statement about a schema. Registering one is an act of configuration, not of asking a question.

## Options

**A. Default it when the allowlist names exactly one cluster.** Implemented and deployed. The
parameter stays in the schema, marked optional; an omitted value gets the allowed cluster. A wrong
value is still refused. No caller breaks.

**B. Remove `clusterId` from template tools entirely.** The server resolves the cluster from
configuration and refuses to start if it cannot. Templates mode then requires a single resolvable
cluster. One deployment serves one cluster; three clusters means three deployments.

**C. Leave the code and put the cluster id in the agent's instructions.** No change. Every customer
repeats it, and a 20-digit identifier sits in a prompt.

**D. Bind the cluster to the connection rather than the deployment.** One deployment, cluster
carried in the MCP URL path or a header, resolved per connection. Tool surface has no `clusterId`.
Requires new routing and per-connection credential resolution, which the credential manager can in
principle already do.

## What I would do

**B now, D as the stated direction.**

B costs us nothing today because we have one cluster, and it removes the failure rather than
softening it. Under A the parameter is still in the schema, so an agent can still see it, still
fill it in, and still be refused; we have only made the common path work.

The reason to prefer B over A even at equal cost today is that **B and D present the same tool
surface.** Neither has `clusterId`. So B is a step along the way to D, and moving from B to D later
changes deployment topology without changing the contract agents are written against. A is a side
road: going from A to D removes a parameter that callers may have started passing.

B's cost is that a customer wanting the same templates against several clusters runs several
deployments. Compute is trivial; the real cost is operational, N URLs and N tokens and N upgrades.
The common shape of that case is dev/staging/prod, where a support agent wanting all three at once
is unusual and arguably undesirable. Multi-region with one agent over several clusters is the case
where B genuinely hurts, and D is the answer to it.

## Where I am least confident

Listed so a second reader has somewhere to push.

**I may be over-reading `--templates-only` as a product boundary.** It exists as a lockdown flag.
Treating it as "this is a different product with different rules about targeting" is my
interpretation, not something the code says. It also does not draw the line as cleanly as my
argument wants: it still ships the documentation and capability tools. Those happen to need no
cluster, so they do not contradict the proposal directly, but the mode is "fewer tools" rather
than "a different product", and I am the one calling it the latter.

**The database-connector analogy may be doing too much work.** "Every database connector binds the
connection, not the call" is true and rhetorically strong, and MCP servers are not database
connectors. An MCP server is closer to a toolbox, and a toolbox that can only reach one cluster may
be the wrong default for a product whose whole surface elsewhere is multi-cluster.

**I have not verified that D is buildable the way I describe.** Per-connection cluster binding
assumes MCP clients handle a URL that carries configuration, and that Workato's connection model
maps onto it. Neither is checked. If D is not reachable, B's cost is permanent rather than
temporary, and that changes the comparison.

**I may be wrong that agents should never choose infrastructure.** The evidence I have is one agent
inventing one value. A `tidb_list_clusters` tool plus a better description might let an agent choose
correctly, and that would preserve one deployment for many clusters without new routing. My
instinct is that "which cluster holds this customer's data" is not a question a model should answer
from a support ticket, but that is an instinct.

**The breaking-change surface of B is not fully mapped.** Our retrieval eval script passes
`clusterId` to template tools and would need editing; it is the only caller I found in this
project, and I have not searched outside it.
