Skip to content
Back to skills

Mcp Stdio Liveness Audit Macos

ASecurity

Verify stdio MCP server liveness/attachment on macOS without inferring from age or fd state - five lsof/awk methodology traps, when fd-join is the wrong tool, the global-orphan host-recognition gap, the Codex MCP-duplication mechanism tiers, and the fd-not-idle / alive-not-legitimate / recommendation-not-authorization / absence-not-absence discipline

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 10, 2026
securitygoshellbashsqlnodegit

Works with

  • claude code
  • mcp

Security analysis

A100/100

Scanned September 10, 2026

npx -y skills add nexuslinkproductions/yuri-os --skill mcp-stdio-liveness-audit-macos --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Mcp Stdio Liveness Audit Macos?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Mcp Stdio Liveness Audit Macos
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/nexuslinkproductions-mcp-stdio-liveness-audit-macos-yuri-os/badge)](https://www.skillsdirectory.com/skills/nexuslinkproductions-mcp-stdio-liveness-audit-macos-yuri-os)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: mcp-stdio-liveness-audit-macos
description: "Verify stdio MCP server liveness/attachment on macOS without inferring from age or fd state - five lsof/awk methodology traps, when fd-join is the wrong tool, the global-orphan host-recognition gap, the Codex MCP-duplication mechanism tiers, and the fd-not-idle / alive-not-legitimate / recommendation-not-authorization / absence-not-absence discipline"
---

# MCP stdio liveness audit (macOS)

Use when auditing whether stdio MCP server processes are live/attached vs orphaned/leaked - e.g. duplicate `npm exec` MCP servers under one Codex/OMP/Claude host.

## The five traps

### 1. lsof PIPE peer is in `$NF`, not `$9`
macOS `lsof` PIPE rows have a **blank NODE column**, so whitespace-collapse puts the peer pointer (`->0x...`) in the trailing NAME field, not column 9. Parsing `peer=$9` silently yields empty and every join returns 0.
```awk
local=$6; peer=$NF; sub(/^->/,"",peer)
```

### 2. Don't iterate `total[]` - seed the known leaf set
`for (p in total)` only visits PIDs that had at least 1 PIPE row at fd 0/1/2. A fully-detached leaf (stdio to /dev/null, no PIPE fds) **vanishes** instead of printing `0/N`, hiding orphans. Seed awk with the enumerated descendant list and iterate THAT:
```awk
BEGIN { n=split(leaves,a,","); for(i=1;i<=n;i++) known[a[i]]=1 }
$5=="PIPE" { pid=$2; fd=$4; local=$6; peer=$NF; sub(/^->/,"",peer);
  if (pid==ROOT) root[local]=1;
  if (fd=="0"||fd=="1"||fd=="2") { total[pid]++; if (peer in root) hit[pid]++; }
}
END { for (p in known) printf "%d %d/%d\n", p, hit[p]+0, total[p]+0 }   # 0/0 = no stdio PIPE at all
```

### 3. Classification: require `t==3 && h==3` for "fully attached"
Do NOT use `h==t` - it miscounts a leaf with only 1/1 or 2/2 matching PIPE fds as fully attached. "Fully attached" across stdin/stdout/stderr requires exactly 3 PIPE-type stdio fds AND all 3 peers in the root set. Report `t<3` and `t==0` as separate categories (a `t==0` leaf has stdio redirected to CHR/unix, not PIPE). NOTE: "fully fd-attached" establishes the pipe is open; it does NOT establish "idle" or "active" - protocol activity / which set is in use is a separate, fd-invisible question (see Discipline).

### 4. fd-join only works for DIRECT-stdio hosts - OMP transport is UNRESOLVED
The leaf-stdio-peer to root-pipe-node join proves attachment **only when the host itself holds stdio PIPEs**. True for **Codex** (`codex resume`). For **OMP** (`bun omp --auto-approve`), a root-only PIPE fd-join returns 0/N - **method-insufficient, NOT a detachment verdict**. OMP MCP transport is **UNRESOLVED**: a `bun __omp_worker_daemon_broker` child + unix sockets were observed, mcpvault stdio was confirmed to be a **unix socketpair** (not PIPE), but no leaf socket peer has been matched to a specific intermediary, and the broker holds ZERO pipe fds. Do **not** claim broker-mediation or any specific OMP mechanism as fact. Treat 0/N OMP result as "method-insufficient, topology unresolved," never "detached." To settle: trace socketpair inodes / KQUEUE peers, or read OMP internal state.

### 5. Global orphan scan: recognize ALL MCP-owning hosts, not just codex-resume/omp
A descendant-scoped audit (`getdesc <root>`) covers ONE tree; a launchd-reparented MCP process from a dead session is invisible. For a GLOBAL orphan verdict, enumerate ALL MCP-type procs system-wide and walk each one's ancestry. BUT the walk must recognize EVERY MCP-owning host pattern, not just `codex resume` and `bun omp`. Observed live: **ChatGPT.app runs its own MCP servers via `codex -c features.code_mode_host=true`** (a different codex invocation); Claude Code, IDE extensions, and Electron apps (Obsidian, ChatGPT.app) can also own MCP servers. A walkroot matching only codex-resume/omp will **MISLABEL** those as "orphan to launchd" when they are actually under a live (different) host. Distinguish: (a) **TRUE orphan** = chain reaches launchd with NO live app/host in the chain (dead session); (b) **live non-canonical host** = chain reaches launchd THROUGH a live app. Label the top ancestor explicitly; do not collapse both into "orphan." (Live finding: PID 1553 obsidian-mcp-tools scanned as orphan-to-launchd but was actually under ChatGPT.app/668 via codex code-mode-host 1301.)

## Codex MCP-duplication signature (mechanism tiers)
When N complete MCP sets appear as direct children of one `codex resume` session, all fd-ATTACHED (no orphans WITHIN the tree; protocol activity / which set is in active use UNKNOWN - fd-attachment does NOT establish idle or active):

- **CONFIRMED (observed):** the duplication is CORRELATED with subagent `thread_spawn` - one parent startup + one MCP set per spawned subagent thread (observed with named MURE role threads Lagrange/Volta/Sartre), each fork adding +6-7 MCP procs parented to the main codex ppid. The ps/lsof process-tree evidence shows the duplication tracks the forks.
- **NOT EVIDENCED (not "ruled out"):** MCP-layer reconnect/EOF. The inspected `connection_manager` logs show NO reconnect/EOF entries, so reconnect is NOT EVIDENCED by that surface - but absence in ONE logger is not absence of the thing. A reconnect on a different log surface or an unlogged retry remains POSSIBLE. Do not harden "no reconnect logged" into "reconnect ruled out."
- **PLAUSIBLE (inferred, not code-proven):** the internal mechanism "Codex rollouts do not share stdio handles across forks" - inferred from process-tree + lsof evidence, NOT from source. Keep it PLAUSIBLE until code inspection confirms.
- **Config:** has NO respawn/watch/restart/auto-reconnect directives - so the duplication is not config-driven from the inspected files.
- **OPEN (leak vs expected overhead):** are the spawned subagents STILL ACTIVE (per-subagent MCP sets in use = legitimate MURE fan-out overhead) or FINISHED-with-unreaped-MCPs (persistent ppid-codex sets = teardown leak)? The all-attached fd-join is consistent with EITHER (it cannot distinguish in-use from unreaped). Settle by querying codex `logs_*.sqlite` for per-thread `thread.status` transitions and mapping to wave ps-pids.
- **Wave-1 npm ENOENT asymmetry** (a server present in waves 2-N but absent in wave 1): PLAUSIBLE cause is npx `_npx` cache eviction, NOT verified; do not assume "config changed between launches" without a config-mtime or spawn-log check.

Cleanup: **restart the codex session** tears down all per-subagent MCP sets regardless of leak-vs-expected. Do NOT surgically kill old-wave leaves while the host holds their pipes (restart is a RECOMMENDED boundary, not authorization - read-only audits recommend; only the owner authorizes the mutation). Structural fix: HTTP-transport MCPs eliminate per-fork duplication at the source. Resident set grows over time (observed 23 to 33 leaves in ~50min) while staying all-attached; re-run the fd-join at cleanup time.

## Discipline (every audit)
- **fd-attachment is not active JSON-RPC use - and not "idle" either.** An open pipe proves the host still holds it; it proves NEITHER active use NOR idleness. Protocol activity / which set is in use is fd-invisible; label it UNKNOWN or NEEDS-VERIFICATION. Do not write "fd-attached-but-idle" - "idle" is the same unproven inference as "active," just inverted.
- **Alive + owned is NOT legitimate / intended / unsuperseded.** A live parent proves ownership and aliveness only; it does not prove the host is intended, active, or not a stale duplicate. For live-vs-superseded classification (often the audit's actual purpose), each host's session and activity must be independently checked. Do not call a host "legitimate" from aliveness alone.
- **Recommendation is NOT authorization.** A read-only audit task (constraint: "do not stop/edit/delete") produces a recommended remediation boundary (e.g. "restart session X"); peer agent acceptance does not authorize the mutation, and the read-only constraint binds until the OWNER separately grants it. Frame recommendations as "recommended boundary," never as authorized/in-progress/completed.
- **Absence-of-evidence is not evidence-of-absence.** "No X logged in the inspected surface" supports "X not evidenced," NOT "X ruled out." Hardening the former into the latter is a recurring overclaim failure mode.
- Each claim tier (CONFIRMED / PLAUSIBLE / NEEDS-VERIFICATION) needs its OWN evidence. Never promote inference from age, PPID, process count, or partial fd evidence to CONFIRMED.
- **Scope claims explicitly.** "Zero detached descendants in tree X" is descendant-scoped, NOT "zero orphaned MCP servers system-wide." A global orphan verdict requires a system-wide scan with full host taxonomy (trap 5), and even then is bounded by the regex of server types scanned - state the scanned-type boundary and defer unconditional completeness to an independent critic.
- State churns: rerun scans at report time. Observed 23 to 33 leaves under one codex session in ~50min while staying all-attached; processes die/respawn across the audit window.
- Secrets: config files (`~/.codex/config.toml`, `.mcp.json`) carry inline tokens. Grep TOML headers (`^\[mcp_servers\.`) for KEY names only; `jq -r '.mcpServers|keys[]'` for JSON keys; never cat/dump values.

## Scout delegation caution
A weak scout will misuse `read(bash://...)`, `write(/dev/stdin)`, and `hub(start)` as shell-execution paths and loop without yielding. If a scout stalls on tool errors, cancel and run the check inline. Liveness config-reads and fd-joins are small enough to own directly; reserve scouts for genuinely independent broad sweeps and adversarial cross-checks (e.g. a completeness critic that re-runs the global scan with an independent host taxonomy).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…