# Mode: stdio

For MCP servers that run as a local subprocess. mcporter spawns the command, passes JSON-RPC over its stdio, and tears it down at end of call (or keeps it warm in daemon mode).

## When to pick this mode

- The MCP runs locally, not over HTTP. Common for: official Anthropic example MCPs, `npx -y @vendor/mcp-server-foo`, `uvx mcp-server-bar`, or a binary you built yourself.
- The MCP communicates via stdin/stdout (the default for `@modelcontextprotocol/sdk` server boilerplate).

If the MCP runs as an HTTP service (even if locally hosted), use one of the HTTP modes — stdio mode is specifically for subprocess-spawn MCPs.

## Required inputs

In addition to the universal `<slug>` and `--out <path>` (covered in [SKILL.md § "Step 2"](../SKILL.md)):

- **Command and args** — what mcporter spawns. Two common shapes:
  - **Direct binary**: `mcp-server-time` (already on PATH). Pass via `--command mcp-server-time`.
  - **Package runner**: `npx -y @vendor/mcp-server`. Pass via `--command npx --args -y --args @vendor/mcp-server` (or accept the full command string and parse).
- **Optional env vars** the stdio MCP needs (commonly an API token via env): pass `<HEADER>=<ENV_VAR>` style if the upstream MCP reads `process.env.X`. mcporter merges these into the spawned process's env via `mcporter.json`'s `env` field.
- **Required-on-PATH binary** — what binary must be available at agent runtime for the spawn to succeed. For `npx -y @vendor/...`, that's `npx` (which means Node must be installed). For a direct binary, that's the binary itself. This goes in `requires.bins` so OpenClaw filters the skill out if the binary is missing.
- **How is the spawn binary distributed?** — the `install` spec needs to know. For direct binaries, ask the user: brew formula, npm global, cargo, prebuilt download? For `npx -y <pkg>` and `uvx <pkg>` patterns, the package is identifiable from args. See [§ "Install spec — how the spawn binary gets installed"](#install-spec--how-the-spawn-binary-gets-installed) below.

If the user gives a high-level description ("set up the time MCP") without specifying the command, ask for the exact command. Don't guess — `mcp-server-time`, `mcp-time`, `time-mcp` are all plausible and only one is right.

## Files to generate

Standard non-OAuth shape — see [`conventions.md` § "Files generated by scaffolding"](conventions.md#files-generated-by-scaffolding). No `scripts/` directory and no install hook script. Binary provisioning is delegated to OpenClaw via the `install` spec in SKILL.md frontmatter — see [§ "Install spec — how the spawn binary gets installed"](#install-spec--how-the-spawn-binary-gets-installed) below for what to put in that array.

## Template — `<slug>/SKILL.md`

The `<SPAWN_INSTALL_ARRAY>` placeholder in the template below is a multi-entry JSON5 array — substitute it with the entries derived from the "Install spec" decision tree above. The first entry is always the `mcporter` node-install; the second (and possibly third) entry covers the spawn binary. Worked example for `command: "npx", args: ["-y", "@vendor/mcp-server-foo"]`:

```jsonc
[
  { "id": "node-mcporter", "kind": "node", "package": "mcporter",
    "bins": ["mcporter"], "label": "Install mcporter (node)" },
  { "id": "node-stdio", "kind": "node", "package": "@vendor/mcp-server-foo",
    "bins": ["@vendor/mcp-server-foo"], "label": "Install @vendor/mcp-server-foo (node)" }
]
```

If the user said the spawn binary has no canonical installer, emit only the `mcporter` entry (single-element array).

````markdown
---
name: <SLUG>
description: "TODO: rewrite before publishing — durable purpose framing for this MCP integration."
homepage: "TODO: replace with upstream MCP docs URL"
metadata:
  {
    "openclaw":
      {
        "emoji": "🔌",
        "requires":
          {
            "bins": ["mcporter", "<RUNTIME_BIN>"],
            "env": [<EACH_ENV_VAR_NAME_PASSED_TO_STDIO_MCP>],
          },
        "primaryEnv": "<MOST_REPRESENTATIVE_ENV_VAR_OR_OMIT_IF_NONE>",
        "install": <SPAWN_INSTALL_ARRAY>,
      },
  }
---

# <SLUG>

## How to use this skill

This skill is a thin pass-through to the local stdio MCP `<COMMAND_DESCRIPTION>`. mcporter spawns it as a subprocess on each call (unless daemon mode is keeping it warm). The live MCP is the source of truth for what tools exist, what they're called, what arguments they take, and any per-server instructions it publishes.

**Step 1 — Discover the live tool catalog and any server-published usage instructions.** Always run this first; do not rely on tool names from memory:

```sh
mcporter --config {baseDir}/mcporter.json list <SLUG> --schema
```

The output includes the server's `Instructions:` field (read it) and a JSON Schema for every tool's parameters. Treat this as the authoritative reference for the rest of the session.

**Step 2 — Call any tool from the catalog** using the form `<SLUG>.<tool>`:

```sh
mcporter --config {baseDir}/mcporter.json call <SLUG>.<tool> <arg>=<value> ...
```

Add `--output json` for structured output (also surfaces transport errors as JSON envelopes):

```sh
mcporter --config {baseDir}/mcporter.json call --output json <SLUG>.<tool> ...
```

First run pays a cold-start cost (typically <1s for binaries, several seconds for `npx`/`uvx` package runners) while mcporter spawns the subprocess. Subsequent calls within a session may amortize this if daemon mode is enabled — see § "Performance" below.

## Authentication

This skill expects the following env vars to be set before invocation:

- **`<ENV_VAR_NAME>`** — <one-line description of what this credential is for>

mcporter passes them through to the spawned subprocess via `mcporter.json`'s `env` field. The stdio MCP reads them from its own `process.env`.

## Data flow

Tool calls run **locally** — mcporter spawns the stdio MCP as a subprocess on the same host as the agent. Whatever the MCP does with your inputs (network calls, file access, etc.) happens on that host with the host's network and filesystem permissions. Read the upstream MCP's docs to understand what it does before exposing it to the agent.

## Performance

By default, mcporter spawns a fresh subprocess per call. If you make many calls in sequence, the cold-start cost adds up — particularly for `npx`/`uvx`-runner MCPs. Enable daemon mode to keep the subprocess warm:

```sh
mcporter daemon start
```

(See [mcporter's daemon docs](https://github.com/openclaw/mcporter/blob/main/docs/daemon.md). Daemon state is per-user; the skill itself doesn't manage it.)
````

**Substitutions to make before writing the SKILL.md.** Substitute `<SLUG>` and `<COMMAND_DESCRIPTION>` (a short phrase naming the stdio MCP, e.g. `mcp-server-time` or `npx -y @vendor/mcp-server-foo`) throughout. The Authentication block contains a single example bullet (`<ENV_VAR_NAME>`); for each env var passed to the stdio MCP, *duplicate that bullet* with the env var's name and a one-line description substituted (this counts as required expansion, not an edit).

**Editability rules** for the Discovery flow and Authentication block live in [`conventions.md` § "Editability — Discovery flow and Authentication block"](conventions.md#editability--discovery-flow-and-authentication-block). Read it before publishing.

**If the stdio MCP requires no env vars at all**, replace the entire Authentication block above with the two-line form below (same fenced shape, no other changes):

```markdown
## Authentication

None. This stdio MCP requires no credentials.
```

**Optional sections.** Between the discovery flow and the Authentication block, you *may* insert `## Conventions worth knowing` and/or `## Safety` sections if they apply to this MCP. Their templates and inclusion criteria live in [`conventions.md` § "Optional sections — Conventions worth knowing & Safety"](conventions.md#optional-sections--conventions-worth-knowing--safety). For stdio mode, the Safety section (tool-surface read/write split) is *additional* to the "Data flow" section above (local-subprocess concerns) — keep the two distinct, and omit Safety only if the stdio MCP is fully read-only.

## Template — `<slug>/mcporter.json`

For a direct binary:

```json
{
  "imports": [],
  "mcpServers": {
    "<SLUG>": {
      "command": "<BINARY>",
      "args": [],
      "env": {
        "<ENV_VAR_NAME_1>": "${<ENV_VAR_NAME_1>}"
      }
    }
  }
}
```

For an `npx` package runner:

```json
{
  "imports": [],
  "mcpServers": {
    "<SLUG>": {
      "command": "npx",
      "args": ["-y", "@vendor/mcp-server-foo"],
      "env": {}
    }
  }
}
```

The `env` field passes through env vars to the spawned subprocess. Use `${VAR}` syntax to interpolate from the agent's process env (mcporter expands these per spawn). Omit the `env` field entirely if no env vars are passed.

## `requires.bins` for stdio

stdio mode is the only mode where `requires.bins` extends beyond `["mcporter"]`. The agent invokes `mcporter`, but mcporter then spawns whatever you put in `command`. If that binary isn't on PATH at agent runtime, the spawn fails on every call.

Examples:
- Command `mcp-server-time` → `requires.bins: ["mcporter", "mcp-server-time"]`.
- Command `npx -y @vendor/foo` → `requires.bins: ["mcporter", "npx"]`. (Don't put `@vendor/foo` in `requires.bins`; that's an npm package, not a PATH binary. `npx -y` will fetch it on first run.)
- Command `uvx mcp-server-bar` → `requires.bins: ["mcporter", "uvx"]`.

## Install spec — how the spawn binary gets installed

`requires.bins` *gates* on a binary's presence; `install` *provisions* it. For HTTP modes a single `install` entry for `mcporter` is enough, because that's the only runtime binary the agent invokes. For stdio mode, the `install` array should additionally cover the spawn binary so OpenClaw's UI / `skills.install` RPC can put it on PATH automatically.

Per [OpenClaw skills.md § "Installer specs"](https://github.com/openclaw/openclaw/blob/main/docs/tools/skills.md#installer-specs), supported `kind` values are `brew`, `node`, `go`, `uv`, and `download`. OpenClaw picks one preferred installer when multiple are listed (preference: brew → uv → node → go → download).

### Decision tree by spawn command

When generating the bundle, derive `install` entries from the user-supplied `command`/`args`:

**Case 1 — Package runner (`npx`/`uvx`):**

The runner itself is the PATH binary in `requires.bins`. The package being run can ship as an `install` entry of the matching kind, so OpenClaw can pre-install the package globally and skip the `npx -y` fetch on first call. Or you can leave it un-pre-installed and rely on `npx -y` / `uvx`'s fetch-on-demand behavior.

```jsonc
// command: "npx", args: ["-y", "@vendor/mcp-server-foo"]
"install": [
  { "id": "node-mcporter", "kind": "node", "package": "mcporter",
    "bins": ["mcporter"], "label": "Install mcporter (node)" },
  { "id": "node-stdio", "kind": "node", "package": "@vendor/mcp-server-foo",
    "bins": ["@vendor/mcp-server-foo"], "label": "Install @vendor/mcp-server-foo (node)" }
]
```

For `uvx <pkg>`, swap to `"kind": "uv"` and `"package": "<pkg>"`.

**Case 2 — Direct binary (`command: "<binary>"`, no `npx`/`uvx`):**

The binary itself goes on PATH. Ask the user how it's distributed; do not guess. **Default to listing every plausible installer kind**, not just one — single-kind `install` arrays silently exclude users on platforms the chosen kind doesn't reach (a `brew`-only entry filters the skill out for Linux users without Homebrew). OpenClaw's installer-preference order (brew → uv → node → go → download) picks the first available; extra entries are harmless when the host has the preferred one, but they're the difference between "skill works on my platform" and "skill is invisible" when it doesn't.

For a binary distributed via both Homebrew and npm (the typical case for cross-platform MCPs):

```jsonc
// command: "mcp-server-time"
"install": [
  { "id": "node-mcporter", "kind": "node", "package": "mcporter",
    "bins": ["mcporter"], "label": "Install mcporter (node)" },
  { "id": "brew-stdio", "kind": "brew", "formula": "mcp-server-time",
    "bins": ["mcp-server-time"], "label": "Install mcp-server-time (brew)" },
  { "id": "node-stdio", "kind": "node", "package": "<npm-package-name>",
    "bins": ["mcp-server-time"], "label": "Install mcp-server-time (node)" }
]
```

OpenClaw on a macOS-with-brew host picks the `brew` entry; on Linux without brew it falls through to `node`. Both work; the bundle isn't filtered out anywhere.

**For binaries with truly one distribution path** (e.g. a cargo crate with no Homebrew formula, a prebuilt `download`-only release), emit a single entry of the matching kind — but only after confirming with the user that no other path exists. If you're unsure, ask; a too-narrow `install` array is a worse failure than asking one extra question.

For `download` distribution (prebuilt artifact at a stable URL), use `kind: "download"` with `url`, optional `archive` (`tar.gz`/`zip`), and `targetDir`. Pair this with `brew`/`node` entries when the binary also ships through those channels.

**Case 3 — User can't say how the binary is installed:**

Ship `install` with only the `mcporter` entry and tell the user (in the Reporting step) that they're responsible for putting the spawn binary on PATH. The `requires.bins` gate still works — if the binary is absent, the skill is filtered out for the agent rather than failing mid-call.

### Installer-preference order

OpenClaw picks one preferred installer when multiple are listed: **brew → uv → node → go → download** ([skills.md installer selection rules](https://github.com/openclaw/openclaw/blob/main/docs/tools/skills.md#installer-specs)). The order matters when the same binary is reachable via multiple kinds — the bundle author doesn't pick which one runs, OpenClaw does, based on the host. List every plausible kind; let OpenClaw choose.

## Mode-specific validation

In addition to the universal checks in `validation.md`:

- [ ] `mcpServers.<slug>.command` is set (string, not empty).
- [ ] `mcpServers.<slug>.args` is an array (may be empty).
- [ ] `mcpServers.<slug>` has **no** `baseUrl`, `transport`, `auth`, `bearerTokenEnv`, or `headers` fields. (Cross-pollination with HTTP modes.)
- [ ] `requires.bins` includes both `mcporter` and the spawn binary (`command` value, or `npx`/`uvx` for package runners).
- [ ] Every `${VAR}` referenced in `mcpServers.<slug>.env` appears in `requires.env`.
- [ ] No env var appears in `requires.env` that isn't referenced in the spawn `env`. (Stale requirements.)
- [ ] No `scripts/` directory exists.
- [ ] **Every non-`mcporter` binary in `requires.bins` is covered by either an `install` entry OR an explicit user-facing note.** Concretely: if `requires.bins` is `["mcporter", "<binary>"]`, then `metadata.openclaw.install` should contain an entry whose `bins` array includes `<binary>` — UNLESS the user said the binary has no canonical installer, in which case the Reporting step must tell them they're responsible for putting it on PATH. A binary in `requires.bins` with no `install` entry and no surfaced manual-install note silently filters the skill out for end users on systems where the binary is absent.
- [ ] **For direct-binary stdio MCPs, `install` lists every plausible installer kind, not just one.** A binary distributed via Homebrew + npm should have both a `brew` and a `node` entry; a binary distributed via cargo + prebuilt download should have both. Single-kind `install` arrays are correct only when the user confirmed there's truly one distribution path. Default failure mode of multi-kind: harmless redundancy. Default failure mode of single-kind: skill silently filtered out on platforms the chosen kind doesn't reach.

## Reporting back to the user

Follow the universal shape in [`conventions.md` § "Reporting back to the user"](conventions.md#reporting-back-to-the-user). stdio-specific notes:

- When you split auto-install vs manual binaries (per the universal table), name the auto-install path explicitly: `openclaw skills install <slug>` or the macOS Skills UI. For the manual path, suggest the install command the user mentioned (brew formula, cargo crate, prebuilt download URL).
- Mention that the smoke test's first run may pay a cold-start cost while the package fetches (for `npx`/`uvx` modes).

## References

- [Model Context Protocol spec — stdio transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#stdio) — the transport this mode targets; defines stdin/stdout JSON-RPC framing
- [openclaw/mcporter — `docs/config.md`](https://github.com/openclaw/mcporter/blob/main/docs/config.md) — `command`/`args`/`env` config fields for stdio servers, including `${VAR}` interpolation in the `env` field
- [openclaw/mcporter — `docs/daemon.md`](https://github.com/openclaw/mcporter/blob/main/docs/daemon.md) — daemon mode for keeping stdio subprocesses warm across calls (relevant when cold-start cost matters)
- [openclaw/mcporter — `docs/adhoc.md`](https://github.com/openclaw/mcporter/blob/main/docs/adhoc.md) — `--stdio` and `--stdio-arg` ad-hoc flag reference (mirrors the config-file shape)
- [OpenClaw skills reference § Sandboxing notes](https://github.com/openclaw/openclaw/blob/main/docs/tools/skills.md#sandboxing-notes) — `requires.bins` checks happen on the host AND inside any sandbox container, so the spawn binary must exist in both
