DEV Community

gentic news
gentic news

Posted on • Originally published at gentic.news

Fix MCP Server .env Loading: Stop Depending on cwd with __file__-Relative Paths

Learn why MCP servers must use file-relative .env paths. Claude Desktop's config lacks cwd, so relative paths break. Fix: os.path.join(os.path.dirname(os.path.abspath(__file__)), '.env').

Key Takeaways

  • Learn why MCP servers must use file-relative .env paths.
  • Claude Desktop's config lacks cwd, so relative paths break.
  • Fix: os.path.join(os.path.dirname(os.path.abspath(__file__)), '.env').

The Problem: MCP Servers and Unpredictable Working Directories

Introducing the Model Context Protocol (MCP) with Server-Sent Events ...

When you build an MCP server and configure it in Claude Desktop or another client, you typically specify command and args — but rarely cwd. The client launches your server as a subprocess, and that subprocess inherits the client's working directory, not the directory where your script lives.

This is a silent trap. If your server loads a .env file with a relative path like ".env", you're assuming the process's current working directory is the repo root. Nothing in your code enforces that, and nothing in the client config guarantees it.

The Fix: Resolve .env Relative to file

The robust solution is to anchor your .env path to the server script's own location. In Python, that looks like this:

import os

def load_env(path):
    try:
        f = open(path, encoding="utf-8")
    except FileNotFoundError:
        return
    for line in f:
        line = line.strip()
        if "=" in line and not line.startswith("#"):
            k, v = line.split("=", 1)
            os.environ.setdefault(k, v.strip().strip('"').strip("'"))

here = os.path.dirname(os.path.abspath(__file__))
load_env(os.path.join(here, ".env"))
Enter fullscreen mode Exit fullscreen mode

Note the key line: here = os.path.dirname(os.path.abspath(__file__)). This works regardless of where the client launches the process from.

Why This Matters for Claude Code Users

You likely use Claude Code to build and manage MCP servers. If you're developing a custom MCP server that reads API keys from .env, you've probably tested it by running python server.py from your repo root. It worked — so you shipped it. But Claude Desktop (or any MCP client) might launch it from ~ or /Applications or anywhere else. The moment that happens, your server silently fails to load credentials.

The failure is especially insidious: the server starts fine, but every tool that needs an API key returns an auth error. You debug for hours before noticing the env vars are missing.

Try It Now: Audit Your MCP Servers

  1. Find all your MCP server scripts — look for Python (or Node) files that read .env or process.env.
  2. Check their .env loading code — does it use a relative path like ".env"? If so, fix it.
  3. For Node.js, use path.join(__dirname, '.env') instead of '.env'.
  4. Test from a different directory: cd /tmp && python /path/to/your/server.py — does it still load env? If not, you've found the bug.

The Deeper Lesson: Don't Trust Implicit Assumptions

This bug is a symptom of a broader principle: never let your code rely on the environment's current working directory. MCP servers are especially vulnerable because they're launched by external clients you don't control.

Beyond .env, audit your server for other relative path usage — file logging, SQLite databases, temp directories. All of them should be anchored to __file__ or explicitly configured.

Conclusion

One line of code — os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env") — saves you from a whole class of "works on my machine" failures. Apply it to every MCP server you build, and you'll never lose a session to a missing API key again.


Source: dev.to


Originally published on gentic.news

Top comments (0)