googleapis/mcp-toolbox · error

unable to get logger from ctx: %s

Error message

unable to get logger from ctx: %s

What it means

vacuumTool.models retrieves the structured logger from the request context via util.LoggerFromContext(ctx). The MCP server normally injects a logger into the context before invoking a tool; if it is absent (or the stored value fails the type assertion to the expected logger type), the tool aborts the models vacuum step and returns this wrapped error. Without the logger the tool cannot emit progress events during vacuuming.

Source

Thrown at internal/tools/looker/lookerhealthvacuum/lookerhealthvacuum.go:207

}

// =================================================================================================================
// END MCP SERVER CORE LOGIC
// =================================================================================================================

// =================================================================================================================
// START LOOKER HEALTH VACUUM CORE LOGIC
// =================================================================================================================
type vacuumTool struct {
	SdkClient  *v4.LookerSDK
	timeframe  int
	minQueries int
}

func (t *vacuumTool) models(ctx context.Context, project, model string) ([]map[string]interface{}, error) {
	logger, err := util.LoggerFromContext(ctx)
	if err != nil {
		return nil, fmt.Errorf("unable to get logger from ctx: %s", err)
	}
	logger.InfoContext(ctx, "Vacuuming models...")

	usedModels, err := t.getUsedModels(ctx)
	if err != nil {
		return nil, err
	}

	lookmlModels, err := t.SdkClient.AllLookmlModels(v4.RequestAllLookmlModels{}, nil)
	if err != nil {
		return nil, fmt.Errorf("error fetching LookML models: %w", err)
	}

	var results []map[string]interface{}
	for _, m := range lookmlModels {
		if (project == "" || (m.ProjectName != nil && *m.ProjectName == project)) &&
			(model == "" || (m.Name != nil && *m.Name == model)) {

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Call the tool through the normal MCP server Invoke path, which attaches the logger to the context.
  2. In tests, attach a logger first, e.g. ctx = util.WithLogger(ctx, logger) (or the project's helper) before invoking.
  3. If using custom middleware, ensure it derives from the incoming ctx rather than replacing it with context.Background().

Example fix

// before
err := tool.Invoke(context.Background(), src, params, token)

// after
ctx := util.WithLogger(context.Background(), slog.Default())
err := tool.Invoke(ctx, src, params, token)
Defensive patterns

Strategy: validation

Validate before calling

if _, err := util.LoggerFromContext(ctx); err != nil {
    ctx = util.WithLogger(ctx, slog.Default())
}

Type guard

func ensureLogger(ctx context.Context) context.Context {
    if _, err := util.LoggerFromContext(ctx); err != nil {
        return util.WithLogger(ctx, slog.Default())
    }
    return ctx
}

Try / catch

models, err := vt.models(ctx, project, model)
if err != nil {
    if strings.Contains(err.Error(), "unable to get logger from ctx") {
        ctx = util.WithLogger(ctx, slog.Default())
        models, err = vt.models(ctx, project, model)
    }
    return err
}

Prevention

When it happens

Trigger: Calling vacuumTool.models (directly in tests, or via Invoke) with a bare context.Context that never had a logger attached — e.g. context.Background() in unit tests, or a code path that strips context values before calling the tool.

Common situations: Unit/integration tests constructing the tool and calling Invoke with ctx := context.Background(); custom harnesses that rebuild the request context and drop the server-injected logger; middleware that replaces the context instead of adding to it.

Related errors


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