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)
}
returnView on GitHub (pinned to 8cc6e09de2)
Solutions
- Verify the path passed to --prm-file exists and is a regular file (ls -l) inside the container/working directory.
- Use an absolute path for the PRM file in containerized deployments.
- Check file permissions/ownership so the toolbox process user can read it.
- 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
- Use absolute paths for --prm-file in containers.
- Ensure the volume/secret mounting the PRM file is healthy before process start (init container check).
- Validate the PRM JSON schema in CI with a JSON schema check.
- Set readable file permissions (0444) on mounted secrets.
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
- unable to initialize server: %w
- unable to initialize logger: %w
- failed to construct introspection URL: %w
- failed to parse introspection URL: %w
- invalid log level
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/d079142c8d3f9ee5.
Report an issue: GitHub.