Repository navigation
reload-credentials-redesign - #761
Merged
Merged
Conversation
…ontexts, changed flag, actionable credential errors The reload_credentials MCP tool read the lazily populated auth context registry, so an unscoped call before any query returned no rows and a scoped call for an installed, credentialed provider errored with "cannot find AUTH context" (after the env re-source had already run). The handler is now three ordered phases: re-source the configured env file (a configured file that has gone missing is an explicit error naming the path), invalidate auth contexts registered lazily from provider documents (--auth contexts are retained; the cached provider object is dropped with its context because doc auth only registers on provider creation), then report every installed provider against the now-current environment. Scoping is a filter; an unknown provider name is the only scoped error. Each row carries a new `changed` field: a sha256 comparison of the resolved credential material (successor chain included) before and after the re-source, never emitted. Query tools: credential resolution failures on run_select_query, run_mutation_query and run_lifecycle_operation now name the provider (parsed from the statement) and the recovery sequence (fix the env file, call reload_credentials, retry). execQuery propagates statement errors, which mutation, lifecycle and pull_provider previously swallowed into an empty success payload. Tool description rewritten to forbid preemptive calls and to make `changed: false` after a failure the stop signal. Tests: scripted stdio harness (run_stdio_credential_script) driving the stackql_auth_testing dummy-credential provider; robot scenarios for fresh-session scoped/unscoped/unknown, rotation after a registered context (stale key rejected by the mock, new key used post-reload), rotation before first query, recovery from failed resolution (select and mutation error text, no-op reload), missing env file. The pull_provider scenario now runs against a mocked HTTP registry server (9927) so the install is real rather than a swallowed local-mode error. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Tool descriptions were inline Go string literals spread across server.go and query_library.go while instructions, prompts and resources were already authored as markdown under pkg/mcp_server/content. Move the 16 descriptions to content/tools/<tool_name>.md (frontmatter `name`, body is the description prose; soft line breaks collapse to spaces, blank lines keep paragraph breaks) and load them through the same embedded content framework. addToolWithGate attaches the mastered description and returns an error when a registered tool has none, so registration failures surface at server construction like prompt and resource failures do. Published bytes are unchanged for every tool. The build-time gate test now checks coverage in both directions: every published tool has a file and every file names a published tool. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…lishes Reads content/tools/run_select_query.md from disk independently of the loader, strips the frontmatter and joins the wrapped lines, then compares with the description returned by an in-process tools/list. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…TP server The 2026-07-28 stateless roundtrip now opens with server/discover, the SEP-2575 replacement for the initialize handshake, and the 9926 scenario asserts the response lists the revision and carries the embedded content/instructions text. Closes the black-box gap where the new-revision instructions path was only covered by the in-process unit test. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
server_info and reload_credentials are on-demand recovery and diagnostic tools, never session-start steps; the instructions said otherwise. Session guidance now states the defaults (sqlite3, safe), that credentials resolve at query time with no preceding step, and the fail -> fix file -> reload -> retry sequence with changed: false as the stop signal. The 401 recovery rule gates the retry on changed: true. Discovery points at the query library before the drill-down, dialect rules describe validate_select_query as an on-demand pre-flight rather than a routine step, and the mutation shapes gain the EXEC statement form behind run_lifecycle_operation. validate_select_query: the "no credentials required" claim was checked against the binary and is false (EXPLAIN resolves credentials and fails before reporting a bad column when they are missing); the description now says so. Trailing newlines restored on the tool files. The stateless discover scenario anchors on the "# Discovery workflow" heading rather than a sentence that the rewrite reworded. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… scripted driver The Sources Env File Mid Session scenario now runs through run_stdio_credential_script like the other reload scenarios, and the superseded run_stdio_credential_reload_roundtrip helper is removed. This also clears the CodeQL clear-text-storage alert (pre-existing on main) that the helper's secret_value write carried. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Redesigns the
reload_credentialsMCP tool around a demonstrated bug, moves MCP tool descriptions out of Go literals intopkg/mcp_server/content/tools/*.md, and aligns the server instructions and tool descriptions so agents spend zero turns on auth in the happy path and at most two calls (reload + retry) in any failure path.The bug
Found on a fresh stdio session (0.11.669, Windows, aws provider installed, valid creds in the process env):
reload_credentialsunscoped, before any query:no resultsreload_credentialsprovideraws, before any aws query:cannot find AUTH context for provider = 'aws'run_select_queryagainstaws.s3.buckets: 75 rowsreload_credentialsprovideraws, after the query:aws / aws_signing_v4 / env:AWS_SECRET_ACCESS_KEY / okRoot cause: the report read the auth context registry, which is populated lazily on a provider's first query. The env re-source ran before the failing lookup, so the shipped behaviour was a silently working reload with a misleading failure report.
reload_credentials design
The handler is now three ordered phases, and the report is a pure function of post-reload state.
--authare user configuration, not a cache, and are retained.list_providersset) against the now-current env; theproviderarg is a filter and an unknown name is the only scoped error.Each row gains a
changedfield: a sha256 comparison of the resolved credential material (successor chain included) before and after the re-source. Values are compared, never emitted.changed: falseafter a failure is the agent's stop signal.Invariants: an installed provider always produces a row; the report is identical before and after a provider's first query; a second reload against an unchanged file is idempotent (
changed: falsethroughout); no secret values in any output or error text.Finding that changed the design
The work order assumed "lazy registration rebuilds on next query". It does not: doc-default auth registers only when the provider object is first created (
GetProvider!okbranch). Dropping a context alone leaves a cached provider with no auth and every later query fails.InvalidateAuthContextstherefore drops the cached provider object together with its doc-registered context, tracked via a newdocAuthProvidersset on the handler context, and never touches--authcontexts. Lock order matchesGetProvider(providers mutex outer, auth mutex inner).Query tool errors
Credential resolution failures on
run_select_query,run_mutation_queryandrun_lifecycle_operationnow read:The provider is parsed from the statement (
queryProviderName, first provider-qualified table orEXECmethod).execQuerynow propagates statement errors: mutation, lifecycle andpull_providerpreviously collapsed any error into{messages: [], timestamp}, which read as success.Tool descriptions mastered as markdown
Tool descriptions were inline Go strings across
server.goandquery_library.gowhile instructions, prompts and resources already lived underpkg/mcp_server/content. They now live atcontent/tools/<tool_name>.md(frontmatternamemust match the file stem; body is the prose; soft line breaks collapse to spaces, blank lines keep paragraph breaks) and load through the same embedded content framework behind a privatetoolDescriptionscarrier.addToolWithGateattaches the description and returns an error for a registered tool with no file, so registration failures surface at server construction like prompt and resource failures. Published bytes were verified identical to the removed literals for all 16 tools.The descriptions were then reworded for turn economy:
server_infoandreload_credentialsare on-demand, never session-start or pre-query steps;list_methodsis for composing from scratch, not for executing a vetted library template;validate_select_queryis a pre-flight for expensive or failing queries, not routine. The server instructions were updated to say the same thing (defaultssqlite3andsafe, credentials resolve at query time, fail -> fix file -> reload -> retry, 401 retry gated onchanged: true, query library before drill-down,EXECstatement shape).Design notes to debate once
ok/unresolved/not_checked. The work order's table saidnot_resolved; existing docs, tests and the plugin smoke test useunresolved, andchangedis the field that actually answers the rotation question.execQueryerror propagation is a behaviour change beyond credentials: any failing mutation, lifecycle op or provider pull now returns an error instead of an empty success payload. Required for the error text, and honest.cloud_audit,cost_cleanup,drift_report,iam_access_review,public_exposure_scan,getting_startedandcreate_deploy_stackstill callreload_credentialsat session setup as a multi-cloud status probe. That is the work order's scenario 4 (idempotent status check when the user asks which providers resolve), not the preemptive anti-pattern, so left as is.validate_select_queryrequires credentials. A draft description claimed otherwise; checked against the binary,EXPLAINresolves credentials and reports the credential error before a bad column. The description says so now.toolDescriptions/newToolDescriptions). No new package-level globals.Test plan
Robot (
test/robot/functional/mcp.robot), all driven through a new scripted stdio harnessrun_stdio_credential_scriptinmcp_stdio_client.pyagainst the existingstackql_auth_testingdummy-credential provider (custom auth viaDD_API_KEY/DD_APPLICATION_KEY; the static_auth mock rejects any other key value):MCP Stdio Reload Credentials Reports Installed Providers On Fresh Session- unscoped and scoped before any query, unknown provider is the only scoped error, no secret values.MCP Stdio Reload Credentials Rotates Key After Auth Context Registered- stale key rejected by the mock, rotate the file, one reload reportschanged: true, next query uses the new key, no-op reload showschanged: false, deleted file is an explicit error naming the path.MCP Stdio Reload Credentials Rotates Key Before First Query- full report pre-query, first query uses the rotated key.MCP Stdio Reload Credentials Recovers From Failed Resolution- select and mutation error text name the provider and the recovery, unchanged reload showschanged: false, fixed reloadchanged: true, retry succeeds.MCP Stdio Reload Credentials Sources Env File Mid Session- existing scenario, assertions tightened to the new error text and row shape.MCP HTTP Pull Provider Installs Known Provider- was passing vacuously (local-modeREGISTRY PULLerror swallowed); now runs against a new full_access server on 9927 backed by the mocked HTTP registry with a throwaway approot and assertssuccessfully installed.MCP HTTP Current Revision Client Runs Without Handshake Or Session- the stateless roundtrip now opens withserver/discoverand asserts the 2026-07-28 revision and the embedded instructions are served.Removed:
MCP Stdio Reload Credentials Without Auth Contexts(asserted the empty report that was the bug) and its helper.Go unit tests:
credentialFingerprint,queryProviderName, error text;loadToolDescriptionsFromvalidation and prose collapsing; two-way coverage between published tools andcontent/toolsfiles; a disk-read check that the markdown prose is whattools/listpublishes.Local run: full Mcp robot suite 64/69 with the 4 known version-stamp environmental failures on an unstamped binary, plus the retargeted pull scenario passing separately.
golangci-lintclean on touched packages.Docs
docs/mcp.mdandpkg/mcp_server/README.md: reload semantics,changedfield, per-process reload note for replicated stateless HTTP deployments (rotation there belongs at the deployment layer), and thecontent/toolsdirectory in the embedded content section.Out of scope
query_library_searchreporting emptyrequired_paramsforaws/s3/buckets-list(library index metadata, likely the query library repo).Implementationversion is hardcodedv0.1.1atserver.go; wiring the real build version through is a separate one-liner.