googleapis/mcp-toolbox · error

failed to read manual PRM file at startup: %w

Error message

failed to read manual PRM file at startup: %w

What it means

When --prm-file (s.mcpPrmFile) is provided, NewServer reads that file at startup to pre-cache Protected Resource Metadata and strictly validate its JSON schema. A missing, unreadable, or permission-denied file aborts server startup with this wrapped os.ReadFile error.

Source

Thrown at internal/server/server.go:561

	r.Use(hostCheck(allowedHostsMap))

	// Host OAuth Protected Resource Metadata endpoint
	mcpAuthEnabled := false
	for _, authSvc := range s.PrimitiveMgr.AuthServices() {
		if mSvc, ok := authSvc.(auth.MCPAuthService); ok && mSvc.IsMCPEnabled() {
			mcpAuthEnabled = true
			break
		}
	}

	// Manual PRM override
	var cachedPrmBytes []byte
	var prmConfig ProtectedResourceMetadata
	if s.mcpPrmFile != "" {
		var err error
		cachedPrmBytes, err = os.ReadFile(s.mcpPrmFile)
		if err != nil {
			return nil, fmt.Errorf("failed to read manual PRM file at startup: %w", err)
		}
		// Unmarshal into the struct to strictly validate the schema
		if err := json.Unmarshal(cachedPrmBytes, &prmConfig); err != nil {
			return nil, fmt.Errorf("manual PRM file does not match expected schema: %w", err)
		}
	}

	// Register route if auth is enabled or a manual file is provided
	if mcpAuthEnabled || s.mcpPrmFile != "" {
		r.Get(prmURL.Path, func(w http.ResponseWriter, req *http.Request) {
			// Serve from memory if file was loaded
			if s.mcpPrmFile != "" {
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusOK)
				if _, err := w.Write(cachedPrmBytes); err != nil {
					s.logger.ErrorContext(req.Context(), "failed to write manual PRM file response", "error", err)
				}
				return

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Verify the path passed to --prm-file exists and is a regular file (ls -l) inside the container/working directory.
  2. Use an absolute path for the PRM file in containerized deployments.
  3. Check file permissions/ownership so the toolbox process user can read it.
  4. Confirm the volume/secret mount succeeded before the process starts.

Example fix

// before
toolbox serve --prm-file ./config/prm.json ...
// (running from a different cwd in the container)

// after
toolbox serve --prm-file /etc/toolbox/prm.json ...
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(prmFilePath)
if err != nil {
    return fmt.Errorf("PRM file %q not accessible: %w", prmFilePath, err)
}
if info.IsDir() {
    return fmt.Errorf("PRM path %q is a directory", prmFilePath)
}
if _, err := os.ReadFile(prmFilePath); err != nil {
    return fmt.Errorf("PRM file unreadable: %w", err)
}

Try / catch

if _, err := server.NewServer(ctx, cfg); err != nil {
    var pathErr *os.PathError
    if errors.As(err, &pathErr) {
        log.Printf("check PRM file path/permissions: %s", pathErr.Path)
    }
    return err
}

Prevention

When it happens

Trigger: Starting the server with a PRM file path that does not exist, points to a directory, or is unreadable by the process user (os.ReadFile error) in NewServer.

Common situations: K8s/Docker mounts where the secret file path is wrong or the volume failed to mount, relative paths resolved against a different working directory in containers, or wrong file permissions after mounting.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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