googleapis/mcp-toolbox · error

unable to get logger from ctx: %s

Error message

unable to get logger from ctx: %s

What it means

Runtime guard in ProcessQueryArgs: the request context does not carry a logger (LoggerFromContext failed), so query processing cannot proceed with structured logging. Usually indicates the call bypassed the normal server middleware chain.

Source

Thrown at internal/tools/looker/lookercommon/lookercommon.go:295

// wildcards and rejects them for unquoted parameters, so an unescaped value
// like `first_touch` is parsed as `first<single-char-wildcard>touch` and 400s
// with "The filter \"first_touch\" is not allowed." This is a no-op when no
// filters target unquoted parameters. Metadata-lookup failures are returned to
// the caller, which should log and proceed: callers without explore-read
// permission still need their non-parameter queries to succeed.
func EscapeUnquotedParameterFilters(ctx context.Context, sdk *v4.LookerSDK, wq *v4.WriteQuery, opts *rtl.ApiSettings) error {
	unquoted, err := resolveUnquotedParameterNames(ctx, sdk, wq, opts)
	if err != nil {
		return err
	}
	EscapeFiltersForUnquotedParameters(wq, unquoted)
	return nil
}

func ProcessQueryArgs(ctx context.Context, params parameters.ParamValues) (*v4.WriteQuery, error) {
	logger, err := util.LoggerFromContext(ctx)
	if err != nil {
		return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
	}

	logger.DebugContext(ctx, "params = ", params)
	paramsMap := params.AsMap()

	f, err := parameters.ConvertAnySliceToTyped(paramsMap["fields"].([]any), "string")
	if err != nil {
		return nil, fmt.Errorf("can't convert fields to array of strings: %s", err)
	}
	fields := f.([]string)
	filters := paramsMap["filters"].(map[string]any)
	// Strip a single layer of wrapping quotes from keys and string values.
	// Values matter for LookML `type: unquoted` parameters, where Looker
	// substitutes the value bare into SQL via {% parameter %}. Build a new map
	// rather than mutating during iteration, and avoid comparing `any` values
	// directly (non-comparable dynamic types like slices would panic).
	processedFilters := make(map[string]any, len(filters))
	for k, v := range filters {

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Pass the server-provided request context through to the tool invocation.
  2. Attach a logger explicitly: ctx := util.ContextWithLogger(ctx, logger).
  3. In tests, build a context with a logger before calling ProcessQueryArgs.

Example fix

// before
q, err := lookercommon.ProcessQueryArgs(context.Background(), params)
// after
ctx := util.ContextWithLogger(context.Background(), slog.Default())
q, err := lookercommon.ProcessQueryArgs(ctx, params)
Defensive patterns

Strategy: validation

Validate before calling

if _, err := util.LoggerFromContext(ctx); err != nil {
    ctx = util.ContextWithLogger(ctx, slog.Default())
}
q, err := lookercommon.ProcessQueryArgs(ctx, params)

Try / catch

q, err := lookercommon.ProcessQueryArgs(ctx, params)
if err != nil && strings.Contains(err.Error(), "unable to get logger") {
    // re-attach logger and retry once
    ctx = util.ContextWithLogger(ctx, slog.Default())
    q, err = lookercommon.ProcessQueryArgs(ctx, params)
}

Prevention

When it happens

Trigger: Calling ProcessQueryArgs with a context that was never decorated with a logger by the server middleware (bare context.Background(), new goroutine contexts, direct test calls).

Common situations: Unit tests constructing ParamValues and calling ProcessQueryArgs directly; custom dispatch paths dropping the logger; refactors that create a fresh context mid-request.

Related errors


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/5cb8da35f7431aeb. Report an issue: GitHub.