CLI tool
openclaw/mcporter avatar
openclaw/mcporter

mcporter: one MCP client for terminals, scripts, and agents

Call MCPs via TypeScript, masquerading as simple TypeScript API. Or package them as cli.

5,119 stars365 forksTypeScriptMIT

At a glance

What is it?
mcporter is a TypeScript runtime and CLI for discovering and calling Model Context Protocol servers, published as version 0.16.1. Its interesting decisions are behavioural: it rejects a placeholder syntax other MCP clients accept, it declines interactive prompts when it runs headless, and it never guesses a relay port.
Who is it for?
mcporter suits anyone who wants one MCP tool surface reachable from a shell, a script, and a generated CLI, and who can live with its rules: Node 24 or newer, an explicit precedence model for discovered config, and a fail-closed stance on browser relay authentication. It does not suit a workflow that depends on server-issued form or URL approvals while running headless, because those calls are declined rather than answered.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • openclaw/mcporter5,119 ★ · +284
Star history
Repository2026-07-282026-11-14
openclaw/mcporter4.8k5.1k

284 Stars in the last 109 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The published package is dist plus three files and two export paths

The npm artifact is narrow. `package.json` names the package mcporter, the `mcporter` binary maps to `dist/cli.js`, and the published files are `dist`, `README.md`, and `LICENSE`. Two export paths exist: the package root resolves to `./dist/index.js` with `./dist/index.d.ts` for types, and `./cli` resolves to `./dist/cli.js` with its own types, so a consumer that only needs the command line does not have to import the runtime. The package is `type: module`, MIT licensed, authored by OpenClaw, and version 0.16.1, matching the newest tagged release. Anything not built into `dist` never reaches a user, and the build step is explicit about that: `tsc -p tsconfig.build.json` followed by a `chmodSync` on `dist/cli.js` to make it executable. Packaging is checked by a script rather than a convention, with `test:package` building and then running `bash scripts/test-npm-package.sh`.

Node 24 is the install floor and Node 25 sits outside the matrix

npm installs need Node 24 or newer, and the development setup is narrower than that floor. CI checks Node 24 on Linux, macOS, and Windows, plus Node 26 on Linux, and development happens on Node 24 or 26. The gap is Node 25, which Vitest 5 excludes as a non-LTS line. The runtime requirement stays at Node 24 or newer, so nothing stops you from installing on Node 25, but no CI run covers it. The local commands mirror that matrix:

sh
pnpm install --frozen-lockfile
pnpm check
pnpm test
pnpm docs:site

`pnpm check` chains format checking, linting, and typechecking, and the lint step passes `--deny-warnings --max-warnings=0`, so a warning ends the run instead of scrolling past. Tests build first and then hand off to `node scripts/test-runner.js`. Live hosted-server tests are opt-in through `MCP_LIVE_TESTS=1 vitest run tests/live`, which is why a default `pnpm test` does not depend on somebody else's server being up.

Config takes JSONC and three placeholder forms and refuses a fourth

A minimal project config is a single object in `config/mcporter.json`:

json
{
  "mcpServers": {
    "context7": {
      "url": "https://mcp.context7.com/mcp"
    }
  }
}

Beyond that shape, config files accept JSONC, HTTP and stdio definitions, OAuth settings, tool filters, and lifecycle policy. Placeholders come in three accepted forms: `${VAR}`, `${VAR:-fallback}`, and a whole-value `$env:VAR`. The fourth form is the interesting one. `${env:VAR}` is the syntax some other MCP clients use, and mcporter does not support it: the file is rejected with the accepted alternatives reported instead of the literal text being sent onward. So a config copied out of another client is rejected instead of the placeholder being sent literally. The tree also carries a `mcporter.schema.json` file at the top level.

Discovery imports six client formats on top of your own config

mcporter reads project config and user config, then imports MCP servers from Cursor, Claude Code and Desktop, Codex, Windsurf, OpenCode, and VS Code. Those imports come on top of whatever you wrote yourself. Because the sources stack rather than replace one another, precedence becomes the question that matters, and that is what docs/config.md exists to settle, with docs/import.md holding the list of every discovered client format. The practical consequence shows up on any machine with several editors installed: `mcporter list` shows servers you never configured, and when a server name resolves to a URL you did not expect, the reason is precedence rather than a typo. Check what discovery picked up before concluding that your own config is being ignored.

Seven command groups, and a quick start that needs no credentials

The command surface is grouped by what you are trying to do:

| Goal | Command or API | Details | | ----------------------------------------- | ------------------------------------ | ------------------------------------------ | | Discover servers and tools | `mcporter list` | [CLI reference](docs/cli-reference.md) | | Call tools and read resources | `mcporter call`, `mcporter resource` | [Call syntax](docs/call-syntax.md) | | Connect to a one-off URL or stdio command | `--http-url`, `--stdio` | [Ad-hoc servers](docs/adhoc.md) | | Complete or seed OAuth | `mcporter auth`, `mcporter vault` | [Configuration](docs/config.md) | | Generate a focused CLI | `mcporter generate-cli` | [CLI generator](docs/cli-generator.md) | | Generate TypeScript types or clients | `mcporter emit-ts` | [Typed clients](docs/emit-ts.md) | | Capture a reproducible MCP session | `mcporter record`, `mcporter replay` | [Record and replay](docs/record-replay.md) |

The two-command quick start runs against a public server, with `npx` standing in for an install:

sh
npx mcporter --version
npx mcporter list https://mcp.context7.com/mcp --brief
npx mcporter call https://mcp.context7.com/mcp.resolve-library-id \
  query="React hooks docs" libraryName=react

`--brief` prints the server's tool signatures in a TypeScript-like shape, and `call` takes arguments as `key=value` pairs after the tool address, which is why the tool name and its arguments share one line. Neither command needs local configuration or credentials. Output is human-readable on stdout by default, and JSON exists for the case where a program or an agent needs a stable result. Per-command flags live behind `mcporter <command> --help`.

Headless calls decline the form and URL requests a terminal answers

mcporter connects over stdio, Streamable HTTP, and legacy SSE. It negotiates the current `2026-07-28` protocol revision or a legacy revision per server, and legacy connections advertise client elicitation capabilities. That last detail has a consequence you feel the moment you automate something. An interactive CLI call can answer form requests and URL requests raised by a server. A headless call and a daemon-managed call decline them and hand back an actionable hint instead. If you are wiring mcporter into an agent or a CI job and the server wants confirmation, the call neither hangs nor guesses: it returns declined. Plan the tool list you expose to non-interactive callers with that in mind, or you will lose time to a refusal that is really a missing terminal. The repository keeps modern and legacy fixture servers for both generations, and CI exercises representative fixture paths end to end over stdio and Streamable HTTP.

createRuntime, callOnce, and createServerProxy solve different problems

The runtime API has three entry points, and using the wrong one costs work. `callOnce()` handles a single configured call and its cleanup. `createRuntime()` is for a process that needs explicit server definitions, connection reuse, or several calls:

ts
import { createRuntime } from 'mcporter';

const runtime = await createRuntime({
  servers: [{ name: 'context7', command: { kind: 'http', url: new URL('https://mcp.context7.com/mcp') } }],
});
try {
  console.log((await runtime.listTools('context7')).map((tool) => tool.name));
} finally {
  await runtime.close();
}

The `finally` block is the point. A runtime holds connections, so closing it is not optional in a long-lived process, and the example shows that as part of the contract rather than as an afterthought. `createServerProxy()` is the ergonomic layer above it, mapping MCP tool names to callable camelCase properties and wrapping results with text, Markdown, JSON, image, and raw-content helpers. The renaming is the tradeoff to know about: a tool whose name contains dashes becomes a differently spelled property in your code. The tree carries examples such as `examples/context7-headlines.ts` and `examples/headless-pooling-demo.ts`.

Chrome relay discovery gets 20 seconds and never guesses a port

Chrome DevTools definitions that use `--autoConnect` can control Chrome through a paired OpenClaw extension relay. mcporter discovers the active relay endpoint within a 20-second cold-start budget, and a fixed endpoint can be supplied through `MCPORTER_CHROME_DEVTOOLS_RELAY_URL` in the canonical Chrome definition's `env` map. When discovery fails, it reports the cause and does not fall back to a guessed default port, which is the behaviour you want on a machine with several browsers running.

The authentication model is strict by design. It uses Browser Relay Authentication v2 over one retained loopback socket, keeps the host key out of the network and out of the child process, and gives `chrome-devtools-mcp` a credential-free loopback URL plus ephemeral authorization through an OS-protected preload handoff. Old relay authentication is never retried. Under shared ownership, both `prefer` and `require` fail closed, while `off` requires matching canonical configuration, and shell-level relay controls from the caller do not override the daemon's canonical Chrome configuration. The practical reading is short: on a shared machine expect `prefer` and `require` to refuse rather than quietly degrade, and if you turn the relay off you have to make your own configuration agree with it.

Editorial conclusion

mcporter suits anyone who wants one MCP tool surface reachable from a shell, a script, and a generated CLI, and who can live with its rules: Node 24 or newer, an explicit precedence model for discovered config, and a fail-closed stance on browser relay authentication. It does not suit a workflow that depends on server-issued form or URL approvals while running headless, because those calls are declined rather than answered. Before adopting it, check your Node version, run `mcporter list <url> --brief` against a server you already trust, and read docs/config.md for precedence plus docs/import.md for what gets discovered.

Frequently asked questions

What is mcporter?

mcporter is a TypeScript runtime and command-line tool for discovering and calling Model Context Protocol servers. It is meant for developers and coding agents that need the same MCP tools from a terminal, from a script, or from a generated standalone CLI.

How do I install mcporter?

Run `npx mcporter --version` to try it without installing, then `brew install steipete/tap/mcporter` or `npm install -g mcporter` for repeated use. npm installs need Node 24 or newer, and the install guide covers signed macOS binaries and the other supported paths.

How do I use mcporter to list and call tools?

Run `npx mcporter list https://mcp.context7.com/mcp --brief` to print tool signatures, then `npx mcporter call` with the tool address followed by `key=value` arguments. Output is human-readable on stdout by default, and JSON is available when another program needs a stable result.

How does mcporter connect to Chrome through the OpenClaw relay?

Chrome DevTools definitions using `--autoConnect` can control Chrome through a paired OpenClaw extension relay. mcporter discovers the active relay endpoint within a 20-second cold-start budget, and `MCPORTER_CHROME_DEVTOOLS_RELAY_URL` can set one explicitly in the canonical Chrome definition's `env` map.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/openclaw-mcporter.svg)](https://hysenlabs.com/projects/openclaw-mcporter)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/openclaw-mcporter.svg)](https://hysenlabs.com/projects/openclaw-mcporter)

Was this useful?