From 9c88e3077127294e4d03830aa9619f4d2ed0b2a6 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 17:30:56 -0400 Subject: [PATCH 1/5] fix(scripts): escape backslashes before pipes in sweep table cells (#2546) dependabot-alerts.mjs (twice) and sdk-watch.mjs escaped | in Markdown table cells but not \, so a value ending in a backslash re-exposed the pipe and broke the row (CodeQL js/incomplete-sanitization #74-#76). One shared helper, scripts/lib/markdown-cell.mjs, escapes backslashes first, then pipes; both scripts import it. Its test covers a trailing backslash and checks every pipe in the output sits behind an odd run of backslashes. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- scripts/dependabot-alerts.mjs | 6 ++---- scripts/lib/markdown-cell.mjs | 19 ++++++++++++++++++ scripts/lib/markdown-cell.test.mjs | 31 ++++++++++++++++++++++++++++++ scripts/sdk-watch.mjs | 3 +-- 4 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 scripts/lib/markdown-cell.mjs create mode 100644 scripts/lib/markdown-cell.test.mjs diff --git a/scripts/dependabot-alerts.mjs b/scripts/dependabot-alerts.mjs index 26f7f5390..51de7e3b8 100644 --- a/scripts/dependabot-alerts.mjs +++ b/scripts/dependabot-alerts.mjs @@ -56,6 +56,7 @@ import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; import semver from "semver"; +import { escapeTableCell as cell } from "./lib/markdown-cell.mjs"; /** Board #28 (v2). The project and field node ids are stable; option ids are not. */ export const PROJECT_ID = "PVT_kwDOCt2Azc4BJVxt"; @@ -431,9 +432,6 @@ export function buildIssueTitle(group) { return `chore(deps): bump \`${group.package}\` to \`${group.fixedIn}\` in \`${group.manifestPath}\` (${n} ${n === 1 ? "advisory" : "advisories"})`; } -/** Escape a value going into a Markdown table cell. */ -const cell = (value) => String(value).replace(/\|/g, "\\|"); - const PLACEMENT_DOC = "https://github.com/modelcontextprotocol/inspector/blob/v2/main/AGENTS.md#dependency-placement"; @@ -676,7 +674,7 @@ export function buildNewAdvisoryComment(group, added) { .filter((a) => added.includes(a.ghsa)) .map( (a) => - `| [${a.ghsa}](${a.url}) | ${a.severity} | ${a.summary.replace(/\|/g, "\\|")} |`, + `| [${a.ghsa}](${a.url}) | ${a.severity} | ${cell(a.summary)} |`, ) .join("\n"); return [ diff --git a/scripts/lib/markdown-cell.mjs b/scripts/lib/markdown-cell.mjs new file mode 100644 index 000000000..c97d2d0a2 --- /dev/null +++ b/scripts/lib/markdown-cell.mjs @@ -0,0 +1,19 @@ +/** + * Escape a value for a Markdown table cell (#2546). + * + * The issue-filing sweeps (`dependabot-alerts.mjs`, `sdk-watch.mjs`) build + * table rows from text they do not control: advisory summaries and upstream + * release data. A `|` in that text ends the cell early, so it is escaped as + * `\|`. That alone is incomplete: a value that itself ends in a backslash, + * such as `abc\`, would become `abc\\|`, where the backslash pair cancels and + * the pipe is live again, breaking the row. So backslashes are escaped + * **first**, then pipes (CodeQL js/incomplete-sanitization, alerts #74–#76). + * + * One helper for both scripts, so the order cannot drift between copies. + * + * @param {unknown} value + * @returns {string} + */ +export function escapeTableCell(value) { + return String(value).replace(/\\/g, "\\\\").replace(/\|/g, "\\|"); +} diff --git a/scripts/lib/markdown-cell.test.mjs b/scripts/lib/markdown-cell.test.mjs new file mode 100644 index 000000000..c9b760dc9 --- /dev/null +++ b/scripts/lib/markdown-cell.test.mjs @@ -0,0 +1,31 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { escapeTableCell } from "./markdown-cell.mjs"; + +test("leaves plain text alone", () => { + assert.equal(escapeTableCell("plain text"), "plain text"); +}); + +test("escapes a pipe so it cannot end the cell", () => { + assert.equal(escapeTableCell("a|b"), "a\\|b"); +}); + +test("escapes a trailing backslash before a following pipe can pair with it", () => { + // `abc\` + an escaped pipe must not read as `abc\\|` (a live pipe). + assert.equal(escapeTableCell("abc\\"), "abc\\\\"); + assert.equal(escapeTableCell("abc\\|d"), "abc\\\\\\|d"); +}); + +test("coerces non-strings", () => { + assert.equal(escapeTableCell(42), "42"); + assert.equal(escapeTableCell(null), "null"); +}); + +test("every pipe in the output is escaped by an odd run of backslashes", () => { + for (const input of ["a|b", "a\\|b", "a\\\\|b", "\\", "|", "x\\"]) { + const out = escapeTableCell(input); + for (const m of out.matchAll(/(\\*)\|/g)) { + assert.equal(m[1].length % 2, 1, `${JSON.stringify(input)} → ${out}`); + } + } +}); diff --git a/scripts/sdk-watch.mjs b/scripts/sdk-watch.mjs index 0fffdc75d..ae7d68515 100644 --- a/scripts/sdk-watch.mjs +++ b/scripts/sdk-watch.mjs @@ -67,6 +67,7 @@ import { spawnSync } from "node:child_process"; import { appendFileSync, readFileSync } from "node:fs"; import semver from "semver"; +import { escapeTableCell as cell } from "./lib/markdown-cell.mjs"; /** The branch this repo ships from, and whose manifests are read. */ export const TARGET_BRANCH = "v2/main"; @@ -445,8 +446,6 @@ export function buildIssueTitle(state) { return `chore(deps): upgrade the ${state.group.label} to ${state.target}`; } -const cell = (value) => String(value).replace(/\|/g, "\\|"); - /** * Does adopting `target` require editing the root manifest, or only the lockfile? * From 7a2c154eed9663304c1d0fe095701785ce3bbe23 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 17:32:14 -0400 Subject: [PATCH 2/5] docs(env): document mcpdo's daemon variables and add it to Read by (#2640) docs/environment-variables.md claims every runtime variable but never mentioned mcpdo, which ships for the first time in 2.10.0. - A new 'mcpdo connection daemon' section documents MCP_INSPECTOR_DAEMON_DIR, MCP_INSPECTOR_DAEMON_TOKEN and MCP_ALLOW_DEFAULT_CONNECTION from daemon/paths.ts, daemon/auth.ts and connection/dispatch.ts; they were only in the spec. - mcpdo is named in the Read-by legend and in the 16 rows it reads through core/: each name was checked against the built mcpdo bundle (the proxy rows through EnvHttpProxyAgent in core/mcp/node/proxyFetch). Raised by Copilot on the v2.10.0 milestone merge (#2637). Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- docs/environment-variables.md | 104 +++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/docs/environment-variables.md b/docs/environment-variables.md index a8b3f2a60..a09502a1a 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -2,7 +2,7 @@ Every environment variable that changes how the Inspector behaves at runtime, in one place: the Inspector's own variables, plus the standard system and Node variables it reads (`HOME`, the proxy variables, the Node TLS variables). Set them in the shell that launches `mcp-inspector` (or with `-e` for the [Docker image](./docker.md)). -The **Read by** column names the client whose process reads the variable: **web** is the Node backend that `--web` starts (the browser itself reads no environment), **CLI** and **TUI** are those clients, and **launcher** is the `mcp-inspector` bin that picks one of them. A variable read in shared `core/` code is marked with every client that reaches it. +The **Read by** column names the client whose process reads the variable: **web** is the Node backend that `--web` starts (the browser itself reads no environment), **CLI**, **TUI** and **mcpdo** (the connection CLI, and the daemon it starts) are those clients, and **launcher** is the `mcp-inspector` bin that picks one of them. A variable read in shared `core/` code is marked with every client that reaches it. ⚠️ **Unset a variable rather than setting it to an empty string.** The two are not interchangeable: `HOST=""` is read as an all-interfaces bind and refused, and an empty path variable such as `MCP_STORAGE_DIR=` or `MCP_INSPECTOR_LOG_DIR=` can resolve relative to the working directory instead of falling back to the default. A row says so explicitly where an empty value is treated as unset. @@ -12,22 +12,22 @@ A `~` in a default below means the home directory as described under [Home direc These guard the web backend, which spawns processes on request. Read [Host binding and the origin allow-list](../clients/web/README.md#host-binding--the-origin-allow-list) before widening any of them. -| Variable | Read by | Default | Effect | -| --------------------------------- | -------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MCP_INSPECTOR_API_TOKEN` | web, CLI | a random token per launch | Bearer token guarding every `/api/*` route (`x-mcp-remote-auth: Bearer `). Set it to use a known token instead of the generated one printed in the launch banner. The CLI reads it only to fill the `autoConnect` parameter of the deep link it emits. | -| `MCP_PROXY_AUTH_TOKEN` | web | — | **Deprecated** v1 name for `MCP_INSPECTOR_API_TOKEN`, used only when the new name is unset. | -| `DANGEROUSLY_OMIT_AUTH` | web | unset | Disables the API token entirely when set to `true` or `1` (trimmed, case-insensitive). Any other value — including `false`, `0` and empty — keeps auth on. | -| `HOST` | web, CLI | `127.0.0.1` | Address the web server binds. An all-interfaces host (`0.0.0.0`, `::`, an empty string, and equivalent spellings) is **refused** unless `DANGEROUSLY_BIND_ALL_INTERFACES` is enabled. The CLI reads it only to build its deep link. | -| `DANGEROUSLY_BIND_ALL_INTERFACES` | web | off | Opts in to an all-interfaces `HOST`. Only `true` or `1` (case-insensitive) enable it, so `false` reads as off. The Docker image sets it. | +| Variable | Read by | Default | Effect | +| --------------------------------- | -------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `MCP_INSPECTOR_API_TOKEN` | web, CLI | a random token per launch | Bearer token guarding every `/api/*` route (`x-mcp-remote-auth: Bearer `). Set it to use a known token instead of the generated one printed in the launch banner. The CLI reads it only to fill the `autoConnect` parameter of the deep link it emits. | +| `MCP_PROXY_AUTH_TOKEN` | web | — | **Deprecated** v1 name for `MCP_INSPECTOR_API_TOKEN`, used only when the new name is unset. | +| `DANGEROUSLY_OMIT_AUTH` | web | unset | Disables the API token entirely when set to `true` or `1` (trimmed, case-insensitive). Any other value — including `false`, `0` and empty — keeps auth on. | +| `HOST` | web, CLI | `127.0.0.1` | Address the web server binds. An all-interfaces host (`0.0.0.0`, `::`, an empty string, and equivalent spellings) is **refused** unless `DANGEROUSLY_BIND_ALL_INTERFACES` is enabled. The CLI reads it only to build its deep link. | +| `DANGEROUSLY_BIND_ALL_INTERFACES` | web | off | Opts in to an all-interfaces `HOST`. Only `true` or `1` (case-insensitive) enable it, so `false` reads as off. The Docker image sets it. | | `ALLOWED_ORIGINS` | web | derived from `HOST` | Comma-separated origins allowed to call the API. Unset, the list follows `HOST` at `CLIENT_PORT`: the loopback origins for a loopback host, the loopback origins plus `http://0.0.0.0` and `http://[::]` for an all-interfaces bind, and otherwise only the configured host's own origin (so binding a LAN address does **not** also allow `localhost`). **Replaces** the default list rather than adding to it, so list every form you browse from. Each entry must include the scheme (`http://localhost:6274`). The same list is the MCP Apps sandbox proxy's embedder allow-list (its `frame-ancestors` header and its referrer check), so a public Inspector origin must be listed here for the Apps tab to render. | ## Ports -| Variable | Read by | Default | Effect | -| --------------------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Variable | Read by | Default | Effect | +| --------------------- | -------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `CLIENT_PORT` | web, CLI | `6274` | Web UI port. Must be a **fixed** integer in 1–65535: `0` (an OS-assigned port) is rejected at startup, because the origin allow-list and the MCP Apps sandbox CSP are derived from it. | -| `MCP_SANDBOX_PORT` | web, CLI | `6275` | Port of the MCP Apps sandbox server, 0–65535. `0` asks the OS for a free port. An invalid value is ignored with a warning. | -| `SERVER_PORT` | web | — | v1's proxy port, now only a fallback for the sandbox port: used whenever `MCP_SANDBOX_PORT` does not yield a valid port — unset, empty, **or invalid**. | +| `MCP_SANDBOX_PORT` | web, CLI | `6275` | Port of the MCP Apps sandbox server, 0–65535. `0` asks the OS for a free port. An invalid value is ignored with a warning. | +| `SERVER_PORT` | web | — | v1's proxy port, now only a fallback for the sandbox port: used whenever `MCP_SANDBOX_PORT` does not yield a valid port — unset, empty, **or invalid**. | | `MCP_APP_ORIGIN_PORT` | web, CLI | `6278` | Port of the dedicated app-origin server, used only by an MCP App whose UI resource declares `_meta.ui.domain`; 0–65535, where `0` asks the OS for a free port. An invalid value is ignored with a warning. Pin it if your app's backend allowlists that origin. | The sandbox port resolves as: a valid `MCP_SANDBOX_PORT`, else a valid `SERVER_PORT`, else `6275`. The CLI reads `CLIENT_PORT`, `MCP_SANDBOX_PORT`, `MCP_APP_ORIGIN_PORT` and `HOST` only to build the deep link and port list it hands to a web session; it binds none of them. It normalizes `HOST` (an all-interfaces host becomes `localhost`, any other host is canonicalized), but it validates none of the three **port** variables: any non-empty port value is copied into the URLs and port-forwarding command as-is, with no range check, no warning, and no `SERVER_PORT` fallback, so a malformed value produces a broken hand-off rather than an error. @@ -36,38 +36,48 @@ The sandbox port resolves as: a valid `MCP_SANDBOX_PORT`, else a valid `SERVER_P The sandbox and app-origin servers advertise a URL built from their own bind (`http://localhost:6275/sandbox` under a wildcard bind). Behind a reverse proxy or an ingress, where the browser reaches them at a public hostname, set the address the browser should use instead. Neither changes what is bound: the process still listens on the port above, and routing the public address to it is the proxy's job. See [Behind a reverse proxy](../clients/web/README.md#host-binding--the-origin-allow-list). -| Variable | Read by | Default | Effect | -| ----------------------------- | ------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MCP_SANDBOX_FULL_ADDRESS` | web | `http://:/sandbox` | Public URL of the MCP Apps sandbox proxy, returned as `sandboxUrl` by `GET /api/config` and printed in the banner (e.g. `https://inspector-sandbox.example.com/sandbox`). A bare origin gets `/sandbox` appended. An empty value counts as unset. | -| `MCP_APP_ORIGIN_FULL_ADDRESS` | web | `http://:` | Public origin that `_meta.ui.domain` app documents are published under (e.g. `https://inspector-apps.example.com`). **Origin only** — a path is refused, since documents are served at `/app-document/`. An empty value counts as unset. | +| Variable | Read by | Default | Effect | +| ----------------------------- | ------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MCP_SANDBOX_FULL_ADDRESS` | web | `http://:/sandbox` | Public URL of the MCP Apps sandbox proxy, returned as `sandboxUrl` by `GET /api/config` and printed in the banner (e.g. `https://inspector-sandbox.example.com/sandbox`). A bare origin gets `/sandbox` appended. An empty value counts as unset. | +| `MCP_APP_ORIGIN_FULL_ADDRESS` | web | `http://:` | Public origin that `_meta.ui.domain` app documents are published under (e.g. `https://inspector-apps.example.com`). **Origin only** — a path is refused, since documents are served at `/app-document/`. An empty value counts as unset. | Both are **refused** — ignored with a warning, keeping the bind-derived address — when the value is not an absolute `http(s)` URL, carries credentials, a query string, a fragment or a wildcard, or is a bracketed IPv6 literal. ⚠️ **Each needs its own origin.** The MCP Apps spec requires the sandbox origin to differ from the Inspector's, so a sandbox address sharing an `ALLOWED_ORIGINS` origin is refused (`https://inspector.example.com/sandbox` behind the same hostname as the UI is the common case), as is an app origin equal to the Inspector's or the sandbox's. Neither is used unless its listener is on a fixed port — not when the port is `0`, and not when the pinned port was taken at startup (or collided with another Inspector port) and the server fell back to an OS-assigned one — since the proxy has no stable port to route it to. A plain-`http` address while `ALLOWED_ORIGINS` lists an `https` origin is used, but warned about: the browser blocks it as mixed content. ## Behavior -| Variable | Read by | Default | Effect | -| ------------------------ | ------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MCP_AUTO_OPEN_ENABLED` | web, CLI | see effect | Whether a browser is opened for you. `false` never opens one; `true` always does. Unset (or any other value), the web client opens the UI at launch, and the CLI opens the OAuth authorization page only when stderr is a TTY. In the CLI, `true` also lets interactive OAuth **start** when neither stdin nor stderr is a TTY; otherwise that case fails with an auth-required error pointing at `--stored-auth-only`. **The TUI does not read it** and always opens the OAuth page. | -| `MCP_CATALOG_PATH` | web, CLI, TUI | `~/.mcp-inspector/mcp.json` | Default writable catalog, used when no `--catalog` is given. The CLI honors it only when no ad-hoc target (positional command, `--server-url`, or `--transport`) is given. See [MCP server configuration](./mcp-server-configuration.md). | -| `MCP_OAUTH_CALLBACK_URL` | CLI, TUI | `http://127.0.0.1:6276/oauth/callback` | Loopback redirect URL for the CLI/TUI OAuth flow. `--callback-url` takes precedence. | -| `NO_COLOR` | CLI | unset | Any non-empty value disables ANSI styling in the CLI's human-readable output. An empty `NO_COLOR=` counts as unset. | +| Variable | Read by | Default | Effect | +| ------------------------ | -------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MCP_AUTO_OPEN_ENABLED` | web, CLI, mcpdo | see effect | Whether a browser is opened for you. `false` never opens one; `true` always does. Unset (or any other value), the web client opens the UI at launch, and the CLI opens the OAuth authorization page only when stderr is a TTY. In the CLI, `true` also lets interactive OAuth **start** when neither stdin nor stderr is a TTY; otherwise that case fails with an auth-required error pointing at `--stored-auth-only`. **The TUI does not read it** and always opens the OAuth page. | +| `MCP_CATALOG_PATH` | web, CLI, TUI, mcpdo | `~/.mcp-inspector/mcp.json` | Default writable catalog, used when no `--catalog` is given. The CLI honors it only when no ad-hoc target (positional command, `--server-url`, or `--transport`) is given. See [MCP server configuration](./mcp-server-configuration.md). | +| `MCP_OAUTH_CALLBACK_URL` | CLI, TUI, mcpdo | `http://127.0.0.1:6276/oauth/callback` | Loopback redirect URL for the CLI/TUI OAuth flow. `--callback-url` takes precedence. | +| `NO_COLOR` | CLI, mcpdo | unset | Any non-empty value disables ANSI styling in the CLI's human-readable output. An empty `NO_COLOR=` counts as unset. | ## Storage and state -| Variable | Read by | Default | Effect | -| -------------------------------- | ------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MCP_STORAGE_DIR` | web, CLI, TUI | `~/.mcp-inspector/storage` | Storage directory. Relocates the OAuth state file (`oauth.json`) and the secrets file (`secrets.json`) for every client. For the **web** backend it also relocates `client.json`; the CLI and TUI find `client.json` through `MCP_CLIENT_CONFIG_PATH` instead. | -| `MCP_INSPECTOR_OAUTH_STATE_PATH` | CLI, TUI | `~/.mcp-inspector/storage/oauth.json` | Names the OAuth state file outright. Lookup order: this variable, then `/oauth.json`, then `~/.mcp-inspector/storage/oauth.json`. ⚠️ Setting `MCP_STORAGE_DIR` alone does not isolate a CLI or TUI run if this variable is also exported. **The web backend does not read it** — it always uses `/oauth.json`. Each state file keeps its own secret-store entries — they are scoped by a namespace stamped into the file — so per-profile state paths stay isolated even on a shared keychain (see [Where secrets are stored](./secret-storage.md)). | -| `MCP_CLIENT_CONFIG_PATH` | CLI, TUI | `~/.mcp-inspector/storage/client.json` | Install-level client config (CIMD, enterprise IdP). `--client-config` takes precedence. | +| Variable | Read by | Default | Effect | +| -------------------------------- | -------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MCP_STORAGE_DIR` | web, CLI, TUI, mcpdo | `~/.mcp-inspector/storage` | Storage directory. Relocates the OAuth state file (`oauth.json`) and the secrets file (`secrets.json`) for every client. For the **web** backend it also relocates `client.json`; the CLI and TUI find `client.json` through `MCP_CLIENT_CONFIG_PATH` instead. | +| `MCP_INSPECTOR_OAUTH_STATE_PATH` | CLI, TUI, mcpdo | `~/.mcp-inspector/storage/oauth.json` | Names the OAuth state file outright. Lookup order: this variable, then `/oauth.json`, then `~/.mcp-inspector/storage/oauth.json`. ⚠️ Setting `MCP_STORAGE_DIR` alone does not isolate a CLI or TUI run if this variable is also exported. **The web backend does not read it** — it always uses `/oauth.json`. Each state file keeps its own secret-store entries — they are scoped by a namespace stamped into the file — so per-profile state paths stay isolated even on a shared keychain (see [Where secrets are stored](./secret-storage.md)). | +| `MCP_CLIENT_CONFIG_PATH` | CLI, TUI, mcpdo | `~/.mcp-inspector/storage/client.json` | Install-level client config (CIMD, enterprise IdP). `--client-config` takes precedence. | ### Home directory Every default above that starts with `~` is built from the home directory the process sees, not from the OS account database: -| Variable | Read by | Effect | -| ------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `HOME` | web, CLI, TUI | Base of `~/.mcp-inspector`: the default catalog, storage directory (`oauth.json`, `client.json`), secrets file, and TUI log directory. | -| `USERPROFILE` | web, CLI, TUI | Used in place of `HOME` when `HOME` is unset or empty — the normal case on Windows. ⚠️ If **neither** is set, as under some service managers, those defaults resolve against the **current working directory** instead. Set `HOME` or the specific path variables above. | +| Variable | Read by | Effect | +| ------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `HOME` | web, CLI, TUI, mcpdo | Base of `~/.mcp-inspector`: the default catalog, storage directory (`oauth.json`, `client.json`), secrets file, and TUI log directory. | +| `USERPROFILE` | web, CLI, TUI, mcpdo | Used in place of `HOME` when `HOME` is unset or empty — the normal case on Windows. ⚠️ If **neither** is set, as under some service managers, those defaults resolve against the **current working directory** instead. Set `HOME` or the specific path variables above. | + +## mcpdo connection daemon + +mcpdo runs its connections in a local daemon (`mcpdod`) that it starts on first use. These select which daemon a command talks to, and how a command picks its connection. + +| Variable | Read by | Default | Effect | +| ------------------------------ | ------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MCP_INSPECTOR_DAEMON_DIR` | mcpdo | `MCP_STORAGE_DIR` if set, else `~/.mcp-inspector` | Directory that holds the daemon's socket (`mcpdod.sock`), lock and published token. Takes precedence over `MCP_STORAGE_DIR`. `eval "$(mcpdo private)"` sets it to a fresh `0700` directory under the system temp directory, which gives that shell a daemon of its own. | +| `MCP_INSPECTOR_DAEMON_TOKEN` | mcpdo | unset | Bearer token every request to the daemon must carry. Unset, the daemon generates one at startup and publishes it to `mcpdod.token` in the daemon directory for same-user clients to read, so the shared daemon is still authenticated. `mcpdo private` sets it alongside `MCP_INSPECTOR_DAEMON_DIR`. Neither is a boundary against other processes running as your user (see the [mcpdo README](../clients/mcpdo/README.md)). | +| `MCP_ALLOW_DEFAULT_CONNECTION` | mcpdo | unset | When stdin is not a terminal, commands that act on a connection require an explicit `@name` or `--connection`. Set it to `1` to let them fall back to the most recently used connection, as they do interactively. Any other value keeps the requirement. | ## Secret store @@ -76,31 +86,31 @@ Where the Inspector's secrets (OAuth client secrets, the enterprise IdP client s > [!WARNING] > On a host with no OS keychain (Linux without libsecret or a Secret Service, headless or SSH sessions, Termux), the Inspector **automatically** stores secrets in a file that is **plaintext** unless `MCP_INSPECTOR_SECRET_KEY_FILE` or `MCP_INSPECTOR_SECRET_KEY` is set. See [the warning in Where secrets are stored](./secret-storage.md#how-the-store-is-chosen). -| Variable | Read by | Default | Effect | -| ---------------------------- | ------------- | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MCP_INSPECTOR_SECRET_STORE` | web, CLI, TUI | probe the OS keychain | `keyring`, `file`, or `memory` (case-insensitive) picks the store outright and skips the probe. An empty or whitespace-only value counts as unset and silently runs automatic selection; any other value is ignored with a warning and also falls back to automatic selection. | -| `MCP_INSPECTOR_SECRET_FILE` | web, CLI, TUI | `~/.mcp-inspector/secrets.json` | Path of the file store. Lookup order: this variable, then `secrets.json` in `MCP_STORAGE_DIR` when that is set, then `~/.mcp-inspector/secrets.json`. ⚠️ The default sits **beside** the storage directory, not inside it. | -| `MCP_INSPECTOR_SECRET_KEY` | web, CLI, TUI | unset (file is plaintext, `0600`) | Passphrase that encrypts the file store; an empty or whitespace-only value counts as unset. Use a generated, high-entropy value. ⚠️ Changing or losing it makes the existing file unreadable; see [Where secrets are stored](./secret-storage.md#encryption) before rotating it. | -| `MCP_INSPECTOR_SECRET_KEY_FILE` | web, CLI, TUI | unset | Path of a file holding the passphrase; trailing line breaks are removed. Use this for Docker or Compose secrets, so the key stays out of the environment. Setting it together with a non-blank `MCP_INSPECTOR_SECRET_KEY` is an error, and so is setting it to an empty value. ⚠️ If the file is missing, unreadable or empty, or is the secrets file itself, the file store refuses to read or write rather than fall back to plaintext. | -| `MCP_INSPECTOR_PERSIST_TOKENS` | web, CLI, TUI | `all` | Which **acquired OAuth tokens** are persisted to the secret store: `all` (access + refresh tokens and IdP session tokens), `access` (access and ID tokens, but no refresh tokens), or `none` (no acquired tokens outlive the process; expect to re-authorize each run). Applies on write only — already-persisted tokens still load, and the next save under a stricter policy removes them from the store. Client secrets are registration credentials, not acquired tokens, and are persisted regardless. An empty value counts as unset; any other value is ignored with a warning and treated as `all`. | +| Variable | Read by | Default | Effect | +| ------------------------------- | -------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MCP_INSPECTOR_SECRET_STORE` | web, CLI, TUI, mcpdo | probe the OS keychain | `keyring`, `file`, or `memory` (case-insensitive) picks the store outright and skips the probe. An empty or whitespace-only value counts as unset and silently runs automatic selection; any other value is ignored with a warning and also falls back to automatic selection. | +| `MCP_INSPECTOR_SECRET_FILE` | web, CLI, TUI, mcpdo | `~/.mcp-inspector/secrets.json` | Path of the file store. Lookup order: this variable, then `secrets.json` in `MCP_STORAGE_DIR` when that is set, then `~/.mcp-inspector/secrets.json`. ⚠️ The default sits **beside** the storage directory, not inside it. | +| `MCP_INSPECTOR_SECRET_KEY` | web, CLI, TUI, mcpdo | unset (file is plaintext, `0600`) | Passphrase that encrypts the file store; an empty or whitespace-only value counts as unset. Use a generated, high-entropy value. ⚠️ Changing or losing it makes the existing file unreadable; see [Where secrets are stored](./secret-storage.md#encryption) before rotating it. | +| `MCP_INSPECTOR_SECRET_KEY_FILE` | web, CLI, TUI, mcpdo | unset | Path of a file holding the passphrase; trailing line breaks are removed. Use this for Docker or Compose secrets, so the key stays out of the environment. Setting it together with a non-blank `MCP_INSPECTOR_SECRET_KEY` is an error, and so is setting it to an empty value. ⚠️ If the file is missing, unreadable or empty, or is the secrets file itself, the file store refuses to read or write rather than fall back to plaintext. | +| `MCP_INSPECTOR_PERSIST_TOKENS` | web, CLI, TUI, mcpdo | `all` | Which **acquired OAuth tokens** are persisted to the secret store: `all` (access + refresh tokens and IdP session tokens), `access` (access and ID tokens, but no refresh tokens), or `none` (no acquired tokens outlive the process; expect to re-authorize each run). Applies on write only — already-persisted tokens still load, and the next save under a stricter policy removes them from the store. Client secrets are registration credentials, not acquired tokens, and are persisted regardless. An empty value counts as unset; any other value is ignored with a warning and treated as `all`. | When no store is configured, the choice also depends on whether the Inspector is running in a container, which it detects from `KUBERNETES_SERVICE_HOST` (or Docker's and Podman's marker files). That variable is set by the orchestrator, not by you. ## Logging and debugging -| Variable | Read by | Default | Effect | -| ----------------------- | ------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Variable | Read by | Default | Effect | +| ----------------------- | ------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `MCP_DEBUG` / `DEBUG` | launcher, TUI | off | Prints the full stack trace when the process exits on an error — the launcher for any `--web`/`--tui` failure, and the standalone TUI entry point for a startup failure. Any value other than empty, `0` or `false` (case-insensitive) turns it on. | -| `MCP_LOG_FILE` | web | unset (no log) | Appends the web backend's structured (pino, JSON lines) log to this file, creating its directory if needed. An empty value counts as unset. | -| `MCP_INSPECTOR_LOG_DIR` | TUI | `~/.mcp-inspector` | Directory of the TUI's `auth.log`. The TUI logs to a file so its output does not corrupt the terminal UI. | -| `LOG_LEVEL` | TUI | `info` | Level of the TUI's `auth.log`: one of `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `silent`. An empty value is not replaced by `info`. | +| `MCP_LOG_FILE` | web | unset (no log) | Appends the web backend's structured (pino, JSON lines) log to this file, creating its directory if needed. An empty value counts as unset. | +| `MCP_INSPECTOR_LOG_DIR` | TUI | `~/.mcp-inspector` | Directory of the TUI's `auth.log`. The TUI logs to a file so its output does not corrupt the terminal UI. | +| `LOG_LEVEL` | TUI | `info` | Level of the TUI's `auth.log`: one of `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `silent`. An empty value is not replaced by `info`. | ## Outbound proxy -| Variable | Read by | Default | Effect | -| ---------------------------- | ------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `HTTPS_PROXY` / `HTTP_PROXY` | web, CLI, TUI | unset | Route connections to remote HTTP/SSE servers, including OAuth discovery and token requests, through a proxy. Lowercase forms are honored too. See [HTTP proxy support](../clients/cli/README.md#http-proxy-support). | -| `NO_PROXY` | web, CLI, TUI | unset | Hosts exempted from the proxy. | +| Variable | Read by | Default | Effect | +| ---------------------------- | -------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `HTTPS_PROXY` / `HTTP_PROXY` | web, CLI, TUI, mcpdo | unset | Route connections to remote HTTP/SSE servers, including OAuth discovery and token requests, through a proxy. Lowercase forms are honored too. See [HTTP proxy support](../clients/cli/README.md#http-proxy-support). | +| `NO_PROXY` | web, CLI, TUI, mcpdo | unset | Hosts exempted from the proxy. | ## Node.js variables From 4195f6ad4d7a7035ff8d843ae815aa181ab4241e Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 17:39:16 -0400 Subject: [PATCH 3/5] style(scripts): prettier reflow after the cell() change (#2546) Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- scripts/dependabot-alerts.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/dependabot-alerts.mjs b/scripts/dependabot-alerts.mjs index 51de7e3b8..3e8d621a0 100644 --- a/scripts/dependabot-alerts.mjs +++ b/scripts/dependabot-alerts.mjs @@ -673,8 +673,7 @@ export function buildNewAdvisoryComment(group, added) { const rows = group.advisories .filter((a) => added.includes(a.ghsa)) .map( - (a) => - `| [${a.ghsa}](${a.url}) | ${a.severity} | ${cell(a.summary)} |`, + (a) => `| [${a.ghsa}](${a.url}) | ${a.severity} | ${cell(a.summary)} |`, ) .join("\n"); return [ From 0c7648a0b4300c3b35e3b632b7646424dbc30cde Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 17:49:16 -0400 Subject: [PATCH 4/5] docs(env): drop mcpdo from the proxy rows; qualify the CLI/TUI-only flags (#2640) Copilot on #2641, verified in source: - mcpdo does not honour HTTPS_PROXY/HTTP_PROXY/NO_PROXY. Its two InspectorClient environments (connection/authorize.ts, daemon/connections.ts) omit fetch, so InspectorClient wraps global fetch (inspectorClient.ts:846) and the transport never reaches createProxyFetch(). The variable names are in the mcpdo bundle, but that code is never called on these paths. - mcpdo has no --callback-url or --client-config flag; say the flag overrides apply to the CLI and TUI only. Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- docs/environment-variables.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/environment-variables.md b/docs/environment-variables.md index a09502a1a..2c0e6d22f 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -49,7 +49,7 @@ Both are **refused** — ignored with a warning, keeping the bind-derived addres | ------------------------ | -------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `MCP_AUTO_OPEN_ENABLED` | web, CLI, mcpdo | see effect | Whether a browser is opened for you. `false` never opens one; `true` always does. Unset (or any other value), the web client opens the UI at launch, and the CLI opens the OAuth authorization page only when stderr is a TTY. In the CLI, `true` also lets interactive OAuth **start** when neither stdin nor stderr is a TTY; otherwise that case fails with an auth-required error pointing at `--stored-auth-only`. **The TUI does not read it** and always opens the OAuth page. | | `MCP_CATALOG_PATH` | web, CLI, TUI, mcpdo | `~/.mcp-inspector/mcp.json` | Default writable catalog, used when no `--catalog` is given. The CLI honors it only when no ad-hoc target (positional command, `--server-url`, or `--transport`) is given. See [MCP server configuration](./mcp-server-configuration.md). | -| `MCP_OAUTH_CALLBACK_URL` | CLI, TUI, mcpdo | `http://127.0.0.1:6276/oauth/callback` | Loopback redirect URL for the CLI/TUI OAuth flow. `--callback-url` takes precedence. | +| `MCP_OAUTH_CALLBACK_URL` | CLI, TUI, mcpdo | `http://127.0.0.1:6276/oauth/callback` | Loopback redirect URL for the CLI, TUI and mcpdo OAuth flow. In the CLI and TUI, `--callback-url` takes precedence; mcpdo has no such flag and reads only this variable. | | `NO_COLOR` | CLI, mcpdo | unset | Any non-empty value disables ANSI styling in the CLI's human-readable output. An empty `NO_COLOR=` counts as unset. | ## Storage and state @@ -58,7 +58,7 @@ Both are **refused** — ignored with a warning, keeping the bind-derived addres | -------------------------------- | -------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `MCP_STORAGE_DIR` | web, CLI, TUI, mcpdo | `~/.mcp-inspector/storage` | Storage directory. Relocates the OAuth state file (`oauth.json`) and the secrets file (`secrets.json`) for every client. For the **web** backend it also relocates `client.json`; the CLI and TUI find `client.json` through `MCP_CLIENT_CONFIG_PATH` instead. | | `MCP_INSPECTOR_OAUTH_STATE_PATH` | CLI, TUI, mcpdo | `~/.mcp-inspector/storage/oauth.json` | Names the OAuth state file outright. Lookup order: this variable, then `/oauth.json`, then `~/.mcp-inspector/storage/oauth.json`. ⚠️ Setting `MCP_STORAGE_DIR` alone does not isolate a CLI or TUI run if this variable is also exported. **The web backend does not read it** — it always uses `/oauth.json`. Each state file keeps its own secret-store entries — they are scoped by a namespace stamped into the file — so per-profile state paths stay isolated even on a shared keychain (see [Where secrets are stored](./secret-storage.md)). | -| `MCP_CLIENT_CONFIG_PATH` | CLI, TUI, mcpdo | `~/.mcp-inspector/storage/client.json` | Install-level client config (CIMD, enterprise IdP). `--client-config` takes precedence. | +| `MCP_CLIENT_CONFIG_PATH` | CLI, TUI, mcpdo | `~/.mcp-inspector/storage/client.json` | Install-level client config (CIMD, enterprise IdP). In the CLI and TUI, `--client-config` takes precedence; mcpdo has no such flag and reads only this variable. | ### Home directory @@ -107,10 +107,10 @@ When no store is configured, the choice also depends on whether the Inspector is ## Outbound proxy -| Variable | Read by | Default | Effect | -| ---------------------------- | -------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `HTTPS_PROXY` / `HTTP_PROXY` | web, CLI, TUI, mcpdo | unset | Route connections to remote HTTP/SSE servers, including OAuth discovery and token requests, through a proxy. Lowercase forms are honored too. See [HTTP proxy support](../clients/cli/README.md#http-proxy-support). | -| `NO_PROXY` | web, CLI, TUI, mcpdo | unset | Hosts exempted from the proxy. | +| Variable | Read by | Default | Effect | +| ---------------------------- | ------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `HTTPS_PROXY` / `HTTP_PROXY` | web, CLI, TUI | unset | Route connections to remote HTTP/SSE servers, including OAuth discovery and token requests, through a proxy. Lowercase forms are honored too. See [HTTP proxy support](../clients/cli/README.md#http-proxy-support). | +| `NO_PROXY` | web, CLI, TUI | unset | Hosts exempted from the proxy. | ## Node.js variables From 337d5b1bfe9c9ddb22bf773a014815b986a6a5ac Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 7 Oct 2026 18:16:05 -0400 Subject: [PATCH 5/5] docs(env): mcpdo's non-TTY OAuth hand-off, and its daemon-dir home fallback (#2640) Copilot round 2 on #2641, verified in source: - MCP_AUTO_OPEN_ENABLED: with no TTY and no --stored-auth-only, mcpdo connect returns a pending connection and authUrl instead of the CLI's auth-required error; true keeps the blocking flow (connection/mcp.ts). - USERPROFILE: the daemon directory falls back to os.homedir(), not the working directory, when neither HOME nor USERPROFILE is set (daemon/paths.ts). Co-Authored-By: Claude Opus 5.5 Signed-off-by: cliffhall --- docs/environment-variables.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 2c0e6d22f..392abcbfd 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -45,12 +45,12 @@ Both are **refused** — ignored with a warning, keeping the bind-derived addres ## Behavior -| Variable | Read by | Default | Effect | -| ------------------------ | -------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `MCP_AUTO_OPEN_ENABLED` | web, CLI, mcpdo | see effect | Whether a browser is opened for you. `false` never opens one; `true` always does. Unset (or any other value), the web client opens the UI at launch, and the CLI opens the OAuth authorization page only when stderr is a TTY. In the CLI, `true` also lets interactive OAuth **start** when neither stdin nor stderr is a TTY; otherwise that case fails with an auth-required error pointing at `--stored-auth-only`. **The TUI does not read it** and always opens the OAuth page. | -| `MCP_CATALOG_PATH` | web, CLI, TUI, mcpdo | `~/.mcp-inspector/mcp.json` | Default writable catalog, used when no `--catalog` is given. The CLI honors it only when no ad-hoc target (positional command, `--server-url`, or `--transport`) is given. See [MCP server configuration](./mcp-server-configuration.md). | -| `MCP_OAUTH_CALLBACK_URL` | CLI, TUI, mcpdo | `http://127.0.0.1:6276/oauth/callback` | Loopback redirect URL for the CLI, TUI and mcpdo OAuth flow. In the CLI and TUI, `--callback-url` takes precedence; mcpdo has no such flag and reads only this variable. | -| `NO_COLOR` | CLI, mcpdo | unset | Any non-empty value disables ANSI styling in the CLI's human-readable output. An empty `NO_COLOR=` counts as unset. | +| Variable | Read by | Default | Effect | +| ------------------------ | -------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MCP_AUTO_OPEN_ENABLED` | web, CLI, mcpdo | see effect | Whether a browser is opened for you. `false` never opens one; `true` always does. Unset (or any other value), the web client opens the UI at launch, and the CLI opens the OAuth authorization page only when stderr is a TTY. In the CLI, `true` also lets interactive OAuth **start** when neither stdin nor stderr is a TTY; otherwise that case fails with an auth-required error pointing at `--stored-auth-only`. mcpdo differs there: with no TTY on stdin or stderr and no `--stored-auth-only`, `connect` exits 0 with a pending connection and an `authUrl` to relay, and `true` keeps the blocking interactive flow instead. **The TUI does not read it** and always opens the OAuth page. | +| `MCP_CATALOG_PATH` | web, CLI, TUI, mcpdo | `~/.mcp-inspector/mcp.json` | Default writable catalog, used when no `--catalog` is given. The CLI honors it only when no ad-hoc target (positional command, `--server-url`, or `--transport`) is given. See [MCP server configuration](./mcp-server-configuration.md). | +| `MCP_OAUTH_CALLBACK_URL` | CLI, TUI, mcpdo | `http://127.0.0.1:6276/oauth/callback` | Loopback redirect URL for the CLI, TUI and mcpdo OAuth flow. In the CLI and TUI, `--callback-url` takes precedence; mcpdo has no such flag and reads only this variable. | +| `NO_COLOR` | CLI, mcpdo | unset | Any non-empty value disables ANSI styling in the CLI's human-readable output. An empty `NO_COLOR=` counts as unset. | ## Storage and state @@ -64,10 +64,10 @@ Both are **refused** — ignored with a warning, keeping the bind-derived addres Every default above that starts with `~` is built from the home directory the process sees, not from the OS account database: -| Variable | Read by | Effect | -| ------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `HOME` | web, CLI, TUI, mcpdo | Base of `~/.mcp-inspector`: the default catalog, storage directory (`oauth.json`, `client.json`), secrets file, and TUI log directory. | -| `USERPROFILE` | web, CLI, TUI, mcpdo | Used in place of `HOME` when `HOME` is unset or empty — the normal case on Windows. ⚠️ If **neither** is set, as under some service managers, those defaults resolve against the **current working directory** instead. Set `HOME` or the specific path variables above. | +| Variable | Read by | Effect | +| ------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `HOME` | web, CLI, TUI, mcpdo | Base of `~/.mcp-inspector`: the default catalog, storage directory (`oauth.json`, `client.json`), secrets file, and TUI log directory. | +| `USERPROFILE` | web, CLI, TUI, mcpdo | Used in place of `HOME` when `HOME` is unset or empty — the normal case on Windows. ⚠️ If **neither** is set, as under some service managers, those defaults resolve against the **current working directory** instead. Set `HOME` or the specific path variables above. The mcpdo daemon directory is the exception: with neither set, it falls back to the OS account's home directory (`os.homedir()`), not the working directory. | ## mcpdo connection daemon