Title: [FEATURE] MCP: fix empty-result + nullable-render bugs, add registry discovery/pull tools · Issue #661 · stackql/stackql · GitHub
Open Graph Title: [FEATURE] MCP: fix empty-result + nullable-render bugs, add registry discovery/pull tools · Issue #661 · stackql/stackql
X Title: [FEATURE] MCP: fix empty-result + nullable-render bugs, add registry discovery/pull tools · Issue #661 · stackql/stackql
Description: Two fixes and two new tools, bundled because they form one coherent unit: the fixes harden the result path that the new tools depend on, and together they close the discover -> pull -> query loop for agents (today an agent can only use p...
Open Graph Description: Two fixes and two new tools, bundled because they form one coherent unit: the fixes harden the result path that the new tools depend on, and together they close the discover -> pull -> query loop f...
X Description: Two fixes and two new tools, bundled because they form one coherent unit: the fixes harden the result path that the new tools depend on, and together they close the discover -> pull -> query ...
Opengraph URL: https://github.com/stackql/stackql/issues/661
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"[FEATURE] MCP: fix empty-result + nullable-render bugs, add registry discovery/pull tools","articleBody":"Two fixes and two new tools, bundled because they form one coherent unit: the\nfixes harden the result path that the new tools depend on, and together they\nclose the discover -\u003e pull -\u003e query loop for agents (today an agent can only\nuse providers a human has already pulled from a shell).\n\n### Fix 1 - empty result sets reported as extraction failures\n`extractQueryResults` (`internal/stackql/mcpbackend/mcp_service_stackql.go`)\nreturns `(rv, ok \u0026\u0026 len(rv) \u003e 0)`, and the caller maps a false second return to\n`fmt.Errorf(\"failed to extract query results\")`. A zero-row result is a valid\nempty list. Any `list_*` / `run_select_query` returning no rows errors instead\nof rendering empty. Repro: fresh approot, no providers pulled, `list_providers`\n-\u003e \"failed to extract query results\".\nFix: return `(rv, ok)`; let the renderer's `**no results**` branch handle empty.\n\n### Fix 2 - literal/expression columns render as Go nullable wrappers\n`RenderTable`/`dataRow` and `RenderKV` (`pkg/mcp_server/render/render.go`)\nformat with `fmt.Sprintf(\"%v\", v)` assuming scalars. Literal columns carry a\npointer-to-nullable-wrapper, so cells render as `\u0026{ok true}`. Repro:\n`run_select_query` `SELECT 1 as n, 'ok' as status` -\u003e `| \u0026{ok true} | \u0026{1 true} |`.\nProvider-backed columns are unaffected (unwrapped upstream).\nFix: unwrap helper (check valid, take value) applied before formatting in\n`dataRow` and the `RenderKV` loop.\n\n### Feature 1 - list_registry tool\nLets an agent see providers available in the registry to pull, distinct from\nlist_providers (which shows only pulled/installed providers).\n- Renderer: Table. Inputs: optional `provider` (lists versions for that\n provider; omitted lists all available).\n- New interrogator method `GetRegistryList(provider string)` building\n `REGISTRY LIST [\u003cprovider\u003e]`; backend `ListRegistry` routes through\n `runPreprocessedQueryJSON`.\n- Registered with `selectGate(\"list_registry\")` - read-only, allowed in all\n modes.\n\n### Feature 2 - pull_providers tool\nLets an agent install a provider into the approot cache so subsequent queries\nresolve.\n- Renderer: KV ({messages, timestamp}). Inputs: `provider` (required),\n `version` (optional).\n- New interrogator method `GetRegistryPull(provider, version string)` building\n `REGISTRY PULL \u003cprovider\u003e [\u003cversion\u003e]`; backend `PullProvider` routes through\n `execQuery`.\n- Gating decision (flagged for review): pulling fetches remote content and\n writes local state but does not touch any cloud control/data plane. Proposed\n default: allow in all modes (it is not a cloud mutation). If a human-in-the-\n loop is wanted, classify it as a new `registry_pull` query class gated like\n lifecycle (refused in read_only, elicitation-prompted in safe). Defaulting to\n allow keeps autonomous agent flows unblocked.\n\n### Why CI didn't catch the fixes\nNo test asserts on an empty result set, and query/metadata tests assert on\nstructuredContent JSON + row counts, never on rendered cell values; every test\nSELECT hits a real provider table, so no test runs the literal/no-FROM shape\nthat exposes the wrapper.\n\n### Tests added (test/robot/functional/mcp.robot + render_test.go)\n- Empty-result `list_providers`/SELECT renders cleanly, no error.\n- Literal SELECT cell equals `ok`, does not contain `\u0026{`.\n- Unit: nullable wrapper value renders as unwrapped scalar.\n- `list_registry` returns a non-empty available-provider set (against the test\n registry).\n- `pull_providers` for a known provider returns a timestamp; the provider is\n then visible via `list_providers`.\n\n### Impact / risk\nLow. Fixes are additive on the read path; provider-backed output unchanged. New\ntools are additive. Only open decision is the pull_providers gating class.","author":{"url":"https://github.com/jeffreyaven","@type":"Person","name":"jeffreyaven"},"datePublished":"2026-05-29T06:15:23.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":12},"url":"https://github.com/661/stackql/issues/661"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:70a87652-739f-68b7-7883-d08d34197be0 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | BC86:384DDA:8775C4:BA056F:6A4D49CF |
| html-safe-nonce | 10c72f19e39202e9c017610d1cd1d3c86e3ea909dc7555d90513143cb35597f9 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJCQzg2OjM4NEREQTo4Nzc1QzQ6QkEwNTZGOjZBNEQ0OUNGIiwidmlzaXRvcl9pZCI6IjEzNDk2NTk0MjgzNDU2OTQ2NzEiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 4a1988d0561b6daa4fc977e23976adca36116cb6a912fd0df62f77330c40c4c6 |
| hovercard-subject-tag | issue:4546156112 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/stackql/stackql/661/issue_layout |
| twitter:image | https://opengraph.githubassets.com/d12b05e6f781425bf0f06a67c3a4af33b7caa68d7d2c786da78fe79d684b31b5/stackql/stackql/issues/661 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/d12b05e6f781425bf0f06a67c3a4af33b7caa68d7d2c786da78fe79d684b31b5/stackql/stackql/issues/661 |
| og:image:alt | Two fixes and two new tools, bundled because they form one coherent unit: the fixes harden the result path that the new tools depend on, and together they close the discover -> pull -> query loop f... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | jeffreyaven |
| hostname | github.com |
| expected-hostname | github.com |
| None | 92571a8944142227b7e19cd10918b1ddd06e5066c1ad5bc7e4769cf6140a87e6 |
| turbo-cache-control | no-preview |
| go-import | github.com/stackql/stackql git https://github.com/stackql/stackql.git |
| octolytics-dimension-user_id | 95105302 |
| octolytics-dimension-user_login | stackql |
| octolytics-dimension-repository_id | 443987542 |
| octolytics-dimension-repository_nwo | stackql/stackql |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 443987542 |
| octolytics-dimension-repository_network_root_nwo | stackql/stackql |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 56fc8347865a14e2ec811533d68f929cf4e0ec19 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width