You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor(mcp): address review on db_execute_query result limiting
Incorporate review feedback on the result-limiting change:
- Drain instead of cancel on truncation, preserving the implicit
transaction so writes and later statements still run, and reporting
the true row count from the command tag.
- Remove the hard 10000-row ceiling and the per-call max_rows parameter;
mcp_max_rows config is now the single authoritative row cap.
- Trim duplicative guidance text in the tool description and notice.
- Show mcp_max_rows in `tiger config show` table output.
- Fix the rows_affected schema description, which contradicted itself
after rows_affected became the true total.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-`mcp_max_rows` - Default maximum number of rows the `db_execute_query` MCP tool returns per result set before truncating, to limit how much data lands in an AI agent's context. The tool's `max_rows` parameter overrides this per call, and both are hard-capped at 10000. Only applies to the MCP tool, not CLI commands. Default: `100`
246
+
-`mcp_max_rows` - Maximum number of rows the `db_execute_query` MCP tool returns per result set before truncating, to limit how much data lands in an AI agent's context. Only applies to the MCP tool, not CLI commands. Default: `100`
247
247
-`output` - Output format: `json`, `yaml`, or `table` (default: `table`)
-`read_only` - When `true`, mutating operations are refused: the `tiger service create`/`fork`/`start`/`stop`/`resize`/`update-password`/`delete` CLI commands and their MCP equivalents return an error, and `tiger db connect`, `tiger db connection-string`, and the `db_execute_query` MCP tool open the database session in Tiger Cloud's immutable read-only mode (writes and DDL are rejected by the server). Read commands/tools are unaffected. Default: `false`.
Copy file name to clipboardExpand all lines: internal/tiger/mcp/db_tools.go
+31-61Lines changed: 31 additions & 61 deletions
Original file line number
Diff line number
Diff line change
@@ -19,37 +19,22 @@ import (
19
19
)
20
20
21
21
const (
22
-
// mcpMaxRowsCeiling is the hard upper bound on rows returned per result set,
23
-
// regardless of the configured or per-call max_rows.
24
-
mcpMaxRowsCeiling=10000
25
-
26
-
// mcpMaxResponseBytes caps the total serialized row data across a response,
27
-
// guarding the model's context against a few very wide rows that the row cap
28
-
// alone would miss. Not user-configurable.
22
+
// mcpMaxResponseBytes caps total serialized row data per response, catching a
23
+
// few very wide rows the row cap alone would miss. Not user-configurable.
29
24
mcpMaxResponseBytes=256*1024
30
25
)
31
26
32
-
// resolveMaxRows returns the effective per-result-set row cap: the per-call
33
-
// value if > 0, else the configured value, else the default, clamped to
34
-
// mcpMaxRowsCeiling. It also sanitizes non-positive config-file/env values
35
-
// (which bypass `tiger config set` validation) to the default.
36
-
funcresolveMaxRows(configured, requestedint) int {
37
-
n:=configured
38
-
ifrequested>0 {
39
-
n=requested
40
-
}
41
-
ifn<=0 {
42
-
n=config.DefaultMCPMaxRows
27
+
// resolveMaxRows returns the row cap from mcp_max_rows, falling back to the
28
+
// default for non-positive config-file/env values (which skip set validation).
29
+
funcresolveMaxRows(configuredint) int {
30
+
ifconfigured<=0 {
31
+
returnconfig.DefaultMCPMaxRows
43
32
}
44
-
ifn>mcpMaxRowsCeiling {
45
-
n=mcpMaxRowsCeiling
46
-
}
47
-
returnn
33
+
returnconfigured
48
34
}
49
35
50
-
// approxRowSize estimates the serialized size, in bytes, of a single row's
51
-
// values. It is used to enforce the overall response byte budget. The estimate
52
-
// mirrors how the row is ultimately serialized to JSON for the client.
36
+
// approxRowSize estimates a row's serialized size in bytes for the byte budget,
37
+
// mirroring how it is ultimately marshaled to JSON for the client.
53
38
funcapproxRowSize(values []any) int {
54
39
ifb, err:=json.Marshal(values); err==nil {
55
40
returnlen(b)
@@ -61,7 +46,7 @@ func approxRowSize(values []any) int {
61
46
// truncationNotice builds the actionable guidance returned to the model when a
62
47
// response is truncated.
63
48
functruncationNotice(maxRowsint) string {
64
-
returnfmt.Sprintf("Results were truncated to limit the amount of data returned (max_rows=%d per result set, plus an overall response size cap). More rows exist. Do the work in the database instead of re-running this query: aggregate (GROUP BY, COUNT, SUM, AVG), filter (WHERE), or paginate (LIMIT/OFFSET). To pull more raw rows in a single call, set max_rows (up to %d).", maxRows, mcpMaxRowsCeiling)
49
+
returnfmt.Sprintf("Results were truncated to limit the amount of data returned (the configured mcp_max_rows=%d per result set, plus an overall response size cap). More rows exist. Do the work in the database instead of re-running this query: aggregate (GROUP BY, COUNT, SUM, AVG), filter (WHERE), or paginate (LIMIT/OFFSET).", maxRows)
65
50
}
66
51
67
52
// DBExecuteQueryInput represents input for db_execute_query
@@ -72,7 +57,6 @@ type DBExecuteQueryInput struct {
// No schema Default: it would be injected by the SDK and shadow the
104
-
// configured mcp_max_rows fallback when max_rows is omitted.
105
-
schema.Properties["max_rows"].Description=fmt.Sprintf("Maximum number of rows to return per result set. When the query produces more, the result set is truncated (the response indicates this) and the in-flight query is aborted to avoid streaming and buffering data the model won't use. Defaults to the configured mcp_max_rows (%d). Hard-capped at %d. Prefer aggregating or filtering in SQL over raising this.", config.DefaultMCPMaxRows, mcpMaxRowsCeiling)
resultSetSchema.Properties["rows"].Description="Result rows as arrays of values. Omitted for commands that don't return rows (INSERT, UPDATE, DELETE, etc.)"
resultSetSchema.Properties["rows_affected"].Description="Number of rows affected. For SELECT, this is the number of rows returned (which equals the number of rows in this response; when truncated is true, more rows existed but were not returned). For INSERT/UPDATE/DELETE, this is the number of rows modified. Returns 0 for statements that don't return or modify rows (e.g. CREATE TABLE)."
134
+
resultSetSchema.Properties["rows_affected"].Description="Number of rows affected. For SELECT, this is the total number of rows the query produced; when truncated is true this exceeds the number of rows actually returned in this response. For INSERT/UPDATE/DELETE, this is the number of rows modified. Returns 0 for statements that don't return or modify rows (e.g. CREATE TABLE)."
resultSetSchema.Properties["truncated"].Description="True when this result set was capped (by max_rows or the overall response size limit) and additional rows exist that were not returned. Refine the query in SQL to get the data you need."
137
+
resultSetSchema.Properties["truncated"].Description="True when this result set was capped (by the configured mcp_max_rows row limit or the overall response size limit) and additional rows exist that were not returned. Refine the query in SQL to get the data you need."
160
138
161
139
schema.Properties["execution_time"].Description="Execution time as a human-readable duration string"
@@ -179,9 +157,7 @@ Connects to a PostgreSQL database service in Tiger Cloud and executes the provid
179
157
180
158
Multi-statement queries (semicolon-separated) are supported when no parameters are provided. All result sets are returned. By default, statements execute in an implicit transaction that automatically commits on success or rolls back on error. Explicit transactions (opened with BEGIN) must be explicitly committed with COMMIT, or they roll back when the connection closes.
181
159
182
-
DO THE WORK IN THE DATABASE. PostgreSQL is far more efficient at processing data than fetching raw rows and computing in your context. Push computation into SQL: aggregate with GROUP BY / COUNT / SUM / AVG / MIN / MAX, filter with WHERE, sort and take the top N with ORDER BY ... LIMIT, and join/transform server-side. Avoid SELECT * on large tables; project only the columns you need.
183
-
184
-
RESULTS ARE CAPPED. Each result set returns at most max_rows rows (default 100, configurable via mcp_max_rows), and the total response size is bounded. If a result set is truncated, the response sets "truncated": true and includes a "notice" — refine the query in SQL (aggregate, filter, or paginate with LIMIT/OFFSET) rather than re-running it to pull everything. Only raise max_rows when you genuinely need more raw rows in a single call.
160
+
Process data in the database, not in your context: aggregate, filter, sort/limit, and join in SQL rather than fetching raw rows. Results are capped per result set (default 100 rows, configurable via mcp_max_rows) and by total size; a truncated response sets "truncated": true with a "notice" on how to refine the query.
185
161
186
162
WARNING: Can execute any SQL statement including INSERT, UPDATE, DELETE, and DDL commands. Always review queries before execution.`,
-`mcp_max_rows` - Default maximum rows the `db_execute_query` MCP tool returns per result set before truncating, to limit data placed in an AI agent's context. Overridable per call via the tool's `max_rows` parameter; both are hard-capped at 10000. MCP-only (does not affect CLI commands). (default: 100). See `specs/spec_mcp.md` for details.
49
+
-`mcp_max_rows` - Maximum rows the `db_execute_query` MCP tool returns per result set before truncating, to limit data placed in an AI agent's context. MCP-only (does not affect CLI commands). (default: 100). See `specs/spec_mcp.md` for details.
50
50
-`output` - Output format: json, yaml, or table (default: table)
-`read_only` - When `true`, mutating operations are refused: `tiger service create`/`fork`/`start`/`stop`/`resize`/`update-password`/`delete` and their MCP equivalents return an error, and `tiger db connect`/`connection-string`/`db_execute_query` open against an immutable read-only database connection regardless of `--read-only` (default: false). See `specs/spec_mcp.md` for details.
0 commit comments