fix: four CLI and MCP bugs (#91, #74, #87, #73) - #92
Merged
Conversation
valueToYAMLNode tagged every integer and float !!str, so any schema field
typed `integer` was unwritable through the MCP create and edit tools. A
schema marking such a field required became impossible to satisfy: the
node could not be created, and meta could not repair it afterwards
because meta validates the whole node including markdown sections.
Tag integers !!int and floats !!float, matching the !!bool case that was
already correct. Integral floats serialize as plain integers because JSON
has no integer type, so a value sent over MCP as 1 arrives as float64(1)
and must still satisfy a `type: integer` check.
Widen the MCP create tool's attrs parameter to map[string]any. A
string-typed Go map generates additionalProperties: {type: string}, which
cannot express a number by construction. The CLI --attrs flag keeps its
string map, since its values genuinely arrive as text.
Also sort nested map keys so repeated writes of the same value produce
byte-identical YAML.
Fixes #91
An unqualified `tap flight create NAME` resolved its namespace from defaultNamespace, which is the user's personal namespace. Working in an org KEG and typing a bare flight name therefore created the flight under the personal namespace instead. The result was valid, so nothing failed loudly: the flight was simply absent from the org's flights page and could later collide with an explicitly qualified one. Put the active KEG's namespace first in the precedence chain, ahead of defaultNamespace, fallbackNamespace, and the hub's own default. The selector chain matches resolveIdentity and resolveKegAdminRef, so `tap flight create` now agrees with what `tap info` reports as active. Only a namespace the KEG selector states explicitly counts. A bare keg name still falls through to defaultNamespace rather than being resolved into the personal namespace and presented as if the KEG had named it. Explicitly qualified @namespace/+slug references never reach the defaulting path and are unchanged. Fixes #74
An auth failure fell through to bare error text with no structured content, so an agent whose session lost access had no indication that reorienting is the refresh boundary and tended to treat the session as dead. Telling an agent to log in and reorient would have been false advice on its own. The credential store is loaded once per process, so a token written by `tap auth login` in a separate shell was invisible to a running MCP server and no amount of reorienting could clear the 401. Orientation already reloads configuration; credentials now reload on the same boundary, including the resolved keg cache, whose entries captured the previous resolver. Auth errors now return structured content with a code, an action, and reorientRequired. A 403 is kept distinct: the credential was accepted and the caller simply lacks the grant, so logging in again changes nothing and reorientRequired stays false rather than starting a loop that cannot succeed. Neither message carries token or credential detail. Fixes #87
`tap namespace list` queried only the selected hub, so memberships on every other configured hub were silently absent, and the output carried no hub column, leaving rows unattributable when more than one hub is configured. With no --hub, every configured remote and readonly hub is now queried and each row names its source hub, so the same namespace name on two hubs stays two distinct rows. A hub that cannot be reached becomes a warning naming that hub and the remaining hubs still list, mirroring how HubListKegs already handles aggregate mode. An explicit --hub keeps its previous single-hub behaviour and still propagates that hub's error. Local namespaces are not included. Tapper has no local hub kind: config admits only remote and readonly, and every resolver rejects anything else. Representing locally-backed namespaces needs that kind to exist first. Refs #73
This was referenced Sep 2, 2026
Exercises the create and edit paths through a real keg with the schema from the issue, rather than asserting at the metadata layer alone. Both cases fail before the fix with the exact error the issue reports. Refs #91
jlrickert
marked this pull request as ready for review
September 2, 2026 04:54
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.
Four independent fixes, one commit each.
Integer frontmatter fields were unwritable (#91)
valueToYAMLNodetagged every integer and float!!str, so a schema field typedintegercould not be satisfied through the MCPcreateoredittools. A schema marking such a field required was impossible to satisfy at all: the node could not be created, andmetacould not repair it afterwards because it validates the whole node including markdown sections.Integers now tag
!!intand floats!!float, matching the!!boolcase that was already correct. Integral floats serialize as plain integers, because JSON has no integer type and a value sent over MCP as1arrives asfloat64(1)while still needing to satisfy atype: integercheck.The
createtool'sattrsparameter is widened tomap[string]any. A string-typed Go map generatedadditionalProperties: {type: string}, which cannot express a number by construction. The CLI--attrsflag keeps its string map, since its values genuinely arrive as text.Nested map keys are now sorted, so repeated writes of the same value produce byte-identical YAML.
Unqualified flight create used the wrong namespace (#74)
tap flight create NAMEresolved its namespace fromdefaultNamespace, the user's personal namespace. Working in an org KEG and typing a bare flight name created the flight under the personal namespace instead. Nothing failed loudly: the flight was valid, simply absent from the org's flights page, and able to collide with a later explicitly qualified one.The active KEG's namespace now comes first in the precedence chain. The selector chain matches
resolveIdentityandresolveKegAdminRef, so flight creation agrees with whattap inforeports as active. Only a namespace the KEG selector states explicitly counts, so a bare keg name still falls through rather than being resolved into the personal namespace and presented as if the KEG had named it.Auth failures gave no actionable guidance (#87)
An auth failure fell through to bare error text with no structured content, so an agent whose session lost access had no indication that reorienting is the refresh boundary.
Wording alone would have been false advice. The credential store is loaded once per process, so a token written by
tap auth loginin a separate shell was invisible to a running MCP server and no amount of reorienting could clear the 401. Orientation already reloads configuration; credentials now reload on the same boundary, including the resolved keg cache whose entries captured the previous resolver.Auth errors return structured content with a code, an action, and
reorientRequired. A 403 is kept distinct: the credential was accepted and the caller simply lacks the grant, so logging in again changes nothing andreorientRequiredstays false rather than starting a loop that cannot succeed. Neither message carries token or credential detail.Namespace list covered only one hub (#73)
tap namespace listqueried only the selected hub, hiding memberships on every other configured hub, and its output carried no hub column so rows were unattributable.With no
--hub, every configured remote and readonly hub is queried and each row names its source hub, so the same namespace name on two hubs stays two distinct rows. An unreachable hub becomes a warning naming that hub while the remaining hubs still list, mirroring howHubListKegsalready handles aggregate mode. An explicit--hubkeeps its previous behavior and still propagates that hub's error.Partial. The issue also asks for local namespaces. Tapper has no local hub kind: config admits only
remoteandreadonly, and every resolver rejects anything else. Representing locally-backed namespaces needs that kind to exist first, so this is left open.Follow-up worth filing separately
Flight references resolve three different ways. Read operations search every configured hub for a unique slug match, write operations use the single-namespace default fixed here, and MCP graph selection resolves against the pinned flight root. Unifying them is a larger change than this PR should carry.
Verification
go build ./...andgo test ./...pass. New coverage: numeric and nested-map metadata round-tripping, the flight namespace precedence table, the auth error payload including the no-credential-leak criterion, credential reload after a login written by another process, and namespace aggregation across two hubs including the unreachable-hub case.Manual end-to-end checks against a running hub are still outstanding.