Skip to content

[FEATURE] MCP: fix empty-result + nullable-render bugs, add registry discovery/pull tools #661

@jeffreyaven

Description

@jeffreyaven

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 providers a human has already pulled from a shell).

Fix 1 - empty result sets reported as extraction failures

extractQueryResults (internal/stackql/mcpbackend/mcp_service_stackql.go)
returns (rv, ok && len(rv) > 0), and the caller maps a false second return to
fmt.Errorf("failed to extract query results"). A zero-row result is a valid
empty list. Any list_* / run_select_query returning no rows errors instead
of rendering empty. Repro: fresh approot, no providers pulled, list_providers
-> "failed to extract query results".
Fix: return (rv, ok); let the renderer's **no results** branch handle empty.

Fix 2 - literal/expression columns render as Go nullable wrappers

RenderTable/dataRow and RenderKV (pkg/mcp_server/render/render.go)
format with fmt.Sprintf("%v", v) assuming scalars. Literal columns carry a
pointer-to-nullable-wrapper, so cells render as &{ok true}. Repro:
run_select_query SELECT 1 as n, 'ok' as status -> | &{ok true} | &{1 true} |.
Provider-backed columns are unaffected (unwrapped upstream).
Fix: unwrap helper (check valid, take value) applied before formatting in
dataRow and the RenderKV loop.

Feature 1 - list_registry tool

Lets an agent see providers available in the registry to pull, distinct from
list_providers (which shows only pulled/installed providers).

  • Renderer: Table. Inputs: optional provider (lists versions for that
    provider; omitted lists all available).
  • New interrogator method GetRegistryList(provider string) building
    REGISTRY LIST [<provider>]; backend ListRegistry routes through
    runPreprocessedQueryJSON.
  • Registered with selectGate("list_registry") - read-only, allowed in all
    modes.

Feature 2 - pull_providers tool

Lets an agent install a provider into the approot cache so subsequent queries
resolve.

  • Renderer: KV ({messages, timestamp}). Inputs: provider (required),
    version (optional).
  • New interrogator method GetRegistryPull(provider, version string) building
    REGISTRY PULL <provider> [<version>]; backend PullProvider routes through
    execQuery.
  • Gating decision (flagged for review): pulling fetches remote content and
    writes local state but does not touch any cloud control/data plane. Proposed
    default: allow in all modes (it is not a cloud mutation). If a human-in-the-
    loop is wanted, classify it as a new registry_pull query class gated like
    lifecycle (refused in read_only, elicitation-prompted in safe). Defaulting to
    allow keeps autonomous agent flows unblocked.

Why CI didn't catch the fixes

No test asserts on an empty result set, and query/metadata tests assert on
structuredContent JSON + row counts, never on rendered cell values; every test
SELECT hits a real provider table, so no test runs the literal/no-FROM shape
that exposes the wrapper.

Tests added (test/robot/functional/mcp.robot + render_test.go)

  • Empty-result list_providers/SELECT renders cleanly, no error.
  • Literal SELECT cell equals ok, does not contain &{.
  • Unit: nullable wrapper value renders as unwrapped scalar.
  • list_registry returns a non-empty available-provider set (against the test
    registry).
  • pull_providers for a known provider returns a timestamp; the provider is
    then visible via list_providers.

Impact / risk

Low. Fixes are additive on the read path; provider-backed output unchanged. New
tools are additive. Only open decision is the pull_providers gating class.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions