1. Project Overview & Quickstart (openclaw/mcporter)
File: README.md
MCPorter ๐งณ โ Carry MCP tools into scripts, CLIs, and agents
MCPorter is a TypeScript runtime and command-line tool for discovering and calling Model Context Protocol servers. It is for developers and coding agents that need the same MCP tools from a terminal, a script, or a generated standalone CLI.
Install
Try the CLI without installing it:
npx mcporter --versionInstall it for repeated command-line use:
brew install steipete/tap/mcporter
# or
npm install mcporterNode 24 or newer is required for npm installs. For the TypeScript runtime, add mcporter to your project with your package manager. Signed macOS binaries and the other supported install paths are covered in the install guide.
Quick start
Inspect a public MCP server, then call one of its tools:
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=reactThe first command prints the server's TypeScript-style tool signatures. The second returns matching Context7 library IDs without requiring local configuration or credentials. See the five-minute quickstart for resources, generated CLIs, and typed clients.
Core workflows
| Goal | Command or API | Details |
|---|---|---|
| Discover servers and tools | mcporter list |
CLI reference |
| Call tools and read resources | mcporter call, mcporter resource |
Call syntax |
| Connect to a one-off URL or stdio command | --http-url, --stdio |
Ad-hoc servers |
| Complete or seed OAuth | mcporter auth, mcporter vault |
Configuration |
| Generate a focused CLI | mcporter generate-cli |
CLI generator |
| Generate TypeScript types or clients | mcporter emit-ts |
Typed clients |
| Capture a reproducible MCP session | mcporter record, mcporter replay |
Record and replay |
Human-readable output goes to stdout by default. Use JSON output when another program or agent needs a stable result, and consult mcporter <command> --help for the flags supported by that command.
Configuration and discovery
MCPorter reads project and user config, then imports MCP servers from Cursor, Claude Code and Desktop, Codex, Windsurf, OpenCode, and VS Code. A minimal config/mcporter.json looks like this:
{
"mcpServers": {
"context7": {
"url": "https://mcp.context7.com/mcp"
}
}
}Config files accept JSONC, environment placeholders, HTTP and stdio definitions, OAuth settings, tool filters, and lifecycle policy. The configuration guide defines precedence and the full schema; the import reference lists every discovered client format.
Chrome DevTools definitions using --autoConnect can control Chrome through a paired OpenClaw extension relay. MCPorter discovers OpenClaw's active extension relay endpoint, while preserving an explicit MCPORTER_CHROME_DEVTOOLS_RELAY_URL override and the legacy 127.0.0.1:18799 fallback. It uses Browser Relay Authentication v2 over one retained loopback socket, keeps the host key out of the network and 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. Routing defaults to prefer, which may fall back only to Chrome's original auto-connect path; require fails closed and off keeps original auto-connect.
TypeScript runtime
Use createRuntime() when a process needs explicit server definitions, connection reuse, or several calls:
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();
}callOnce() handles a single configured call and cleanup. createServerProxy() maps MCP tool names to callable camelCase properties and wraps results with text, Markdown, JSON, image, and raw-content helpers. See the runtime overview and tool-calling guide.
Protocols and long-lived servers
MCPorter connects to stdio, Streamable HTTP, and legacy SSE servers. It negotiates the current 2026-07-28 protocol or a legacy revision per server, while legacy connections advertise client elicitation capabilities. Interactive CLI calls can answer form and URL requests; headless and daemon-managed calls decline them with an actionable hint.
The repository's modern and legacy fixture servers cover both generations; CI exercises representative fixture paths end-to-end over stdio and Streamable HTTP. See protocols and interactive requests for negotiation controls and the keep-alive daemon for pooled servers, the MCP bridge, and Chrome DevTools integration.
Development
pnpm install --frozen-lockfile
pnpm check
pnpm test
pnpm docs:siteThe project uses the Node and pnpm versions declared in package.json. Live hosted-server tests are opt-in; see live tests and manual testing.
Related
- CodexBar keeps Codex token windows visible in the macOS menu bar.
- Trimmy flattens multi-line shell snippets for one-shot pasting.
- Oracle bundles prompts and files for multi-model runs.
- Model Context Protocol specification.
License
MIT โ see LICENSE.
File: docs/adhoc.md
summary: 'How to use mcporterโs ad-hoc HTTP/stdio flags to try MCP servers without editing config files.'
read_when:
- 'Exploring or debugging unknown MCP endpoints'
Ad-hoc MCP Servers
mcporter is gaining support for "just try it" workflows where you point the CLI at a raw MCP endpoint without first editing a config file. This doc tracks the behavior and heuristics we use to make that experience smooth while keeping the runtime predictable.
Entry Points
Two new flag sets let you describe a server on the command line:
mcporter list --http-url https://mcp.linear.app/mcp [--name linear]mcporter call --stdio "bun run ./server.ts" --name local-tools
You can also pass a bare URL as the selector (mcporter list https://mcp.linear.app/mcp) or embed the URL in a call expression (mcporter call 'https://mcp.example.com/tools.generate({ topic: "release" })').
- Add
--jsontomcporter list โฆwhen you need a machine-readable summary of status counts and per-server failures, use--output json/--output rawwithmcporter callto receive structured{ server, tool, issue }envelopes whenever a transport error occurs, and runmcporter auth โฆ --jsonto capture the same envelope if OAuth or transport setup fails.
Example: HTTP ad-hoc workflow
# Inspect the server directly via URL (no config entry needed)
mcporter list 'https://mcp.sentry.dev/mcp?agent=1'
# Call a tool by repeating the same URL + tool suffix
mcporter call 'https://mcp.sentry.dev/mcp?agent=1.use_sentry(request: "yo whats up")'Notice that the second command repeats the URL. Ad-hoc definitions are ephemeral unless you pass --persist, so there is no stored server named mcp-sentry-dev-mcp yet. Reusing the printed slug only works after you persist the definition (see "Naming & Identity" below) or add the server to your config; otherwise keep passing the URL/stdio selector each time.
Transport Detection
- HTTP(S): Providing a URL defaults to the streamable HTTP transport.
https://works out of the box;http://requires--allow-http(or the hidden alias--insecure) to acknowledge cleartext traffic. The--sseflag is a hidden alias for--http-urlto match older examples. Use repeatable--header KEY=valueflags for private servers that require headers such asAuthorization,X-API-Key, or tenant selectors. - STDIO: Supplying
--stdio(with a command string) or--stdio-bin(binary + args) selects the stdio transport. Your current shell environment is inherited automatically; use--env KEY=valueonly when you need to inject/override specific variables (and--cwdto change directories). - Conflict guard: Passing both URL and stdio flags errors out so we donโt guess.
Naming & Identity
--namewins when provided.- Otherwise we derive a slug:
- HTTP:
<host>plus a sanitized path fragment (e.g.mcp-linear-app-mcp). - STDIO: executable basename + script (
node-singlestep).
- HTTP:
- Until you persist the definition (via
--persist,mcporter config add, etc.), the slug is only used as the cache key for OAuth tokens/log prefsโyou still need to supply the ad-hoc descriptor (--http-url,--stdio, or bare URL) every time you invokemcporter list|call|auth. - Once persisted, the slug becomes a normal server name so
mcporter call <slug>.toolandmcporter auth <slug>work just like any other configured entry. - If you donโt persist the definition, run
mcporter auth https://mcp.linear.app/mcp(or supply--name linearsomcporter auth linearalso works) to finish OAuth with the same settings.
This name becomes the cache key for OAuth tokens and log preferences, so repeated ad-hoc calls still benefit from credential reuse.
OAuth Auto-Detection
Many hosted MCP servers (Supabase, Vercel, etc.) advertise OAuth capabilities but expect clients to discover this dynamically. When an ad-hoc HTTP server responds with 401/403 during the initial handshake, mcporter now:
- Promotes the definition to OAuth and spins up the default browser flowโno need to edit config or supply
auth: "oauth"manually. On headless hosts, pass--no-browser(or--browser none) to print the authorization URL instead of launching the platform browser. - Persists the change whenever you pass
--persist, so future runs remember that the endpoint requires OAuth without repeating the detection step.
The CLI still avoids surprise prompts during mcporter list; the upgrade happens the first time you run mcporter auth or any other command that allows OAuth (i.e., not in --autoAuthorize=false mode).
Auth & Persistence
- OAuth flows are allowed; successful tokens store under the inferred name just like regular definitions.
mcporter authaccepts the same--http-url/--stdioflags (and even bare URLs), so you can immediately re-runmcporter auth https://โฆafter a 401 without touching a config file.- Use
mcporter auth --no-browserfor human-in-the-loop headless OAuth. Text mode writes only the authorization URL to stdout;--json --no-browserwritesauthorizationUrlplusredirectUrlas JSON. Keep that URL out of durable CI logs and support bundles. Themcporter authprocess must keep running until the browser redirects toredirectUrl; process managers that capture stdout and then tear down the command can kill the local callback listener before tokens are saved. Use a persistent terminal,tmux, or a supervised background process such asnohupwhen completing OAuth outside an interactive shell. - When opening the URL on a different machine, remember that loopback redirect URLs point at the browser machine unless an SSH tunnel forwards the callback port back to the mcporter process. Use
oauthRedirectUrlwhen you need a predictable callback port. - Nothing is written to disk unless you pass
--persist /path/to/config.json. When set, we merge the generated definition into that file (creating it if necessary) so future runs can rely on the standard config pipeline. Ad-hoc HTTP headers are persisted with the entry, so placeholders such as--header 'Authorization=$env:MY_TOKEN'keep working through the normal config header resolver.
Safety Nets
- Non-HTTPS endpoints require
--allow-http. - For stdio commands we print a confirmation snippet the first time we see a new command unless
--yesis present. - Missing transports or malformed combinations throw descriptive errors, pointing to
docs/adhoc.mdfor guidance.
Follow-ups
- Extend
mcporter config addto leverage the same helper, making it the one-stop path from exploration to permanence. - Consider caching inference results so repeated URL calls automatically rehydrate the previous settings (env/cwd).
File: docs/agent-skills.md
summary: 'How to expose mcporter-backed MCP servers to agents through small per-server skills.'
read_when:
- 'Writing agent skill docs or wiring mcporter into an agent runtime'
- 'Triaging requests for a generic mcporter skill'
Agent Skill Pattern
Prefer one small skill per MCP server or workflow instead of a single genericmcporter skill. A focused skill keeps the agent prompt small, names the useful
tools directly, and avoids loading schemas for servers that are irrelevant to the
current task.
Recommended Flow
Add or import the MCP server:
bashnpx mcporter config add docs https://mcp.context7.com/mcp --scope homeInspect the tool surface:
bashnpx mcporter list docs --brief npx mcporter list docs --schemaWrite a skill that calls only the relevant tools via
mcporter call:markdown--- name: docs-mcp --- # Docs MCP Use `npx mcporter call docs.resolve-library-id query=<task> libraryName=<name>` to resolve a package, then call `npx mcporter call docs.query-docs ...` with the resolved ID and docs query.For repeated or shareable workflows, generate a dedicated CLI instead of
teaching the agent rawmcporter callsyntax:bashnpx mcporter generate-cli docs --bundle dist/docs-mcp.js
Why Not One Generic Skill?
A generic skill has to teach the agent how to discover, choose, and call every
configured server. That recreates the large-schema context problem MCPorter is
trying to avoid. Per-server skills stay small and let the skill author describe
the safe, useful workflows for that server.
Use allowedTools or blockedTools in mcporter.json when a server exposes
tools that should not be shown to agents.
File: docs/call-heuristic.md
summary: 'Explains mcporter callโs typo heuristics, auto-correction thresholds, and suggestion behavior.'
read_when:
- 'Tool invocations fail due to misspelled names'
Call Command Auto-Correction
mcporter call aims to help when a tool name is almost correct without hiding real mistakes.
Confident Matches โ Auto-Correct
- We normalise tool names (strip punctuation, lowercase) and compute a Levenshtein distance.
- If the distance is โค
max(2, floor(length ร 0.3)), or the names only differ by case/punctuation, we retry automatically. - A dim informational line explains the correction:
[mcporter] Auto-corrected tool call to linear.list_issues (input: linear.listIssues).
Low-Confidence Matches โ Suggest
- When the best candidate falls outside the threshold we keep the original failure.
- We still print a hint so the user learns the canonical name:
[mcporter] Did you mean linear.list_issue_statuses? - No second call is attempted in this case.
Edge Cases
- We only inspect the tool catalog if the server explicitly replied with โTool โฆ not foundโ. Other MCP errors surface untouched.
- If listing tools itself fails (auth, offline, etc.) we skip both auto-correct and hints.
- Behaviour is covered by
tests/cli-call.test.ts.
Server Selection Heuristic
mcporter list <server>now applies the same edit-distance heuristic to server names. If you typevercek, the CLI auto-corrects tovercel(and logs[mcporter] Auto-corrected server name to vercel (input: vercek).).- When the typo is too large, we keep the original failure but emit a hint:
[mcporter] Did you mean linear?followed by the usual โUnknown MCP server โฆโ line. This avoids giant stack traces while pointing to the right name. - The heuristic considers every configured server (including ad-hoc ones registered via
--http-url/--stdio). Tests covering this behaviour live intests/cli-list.test.ts. mcporter authshares the same routing logic, somcporter auth https://mcp.example.com/mcp(or evenmcporter auth vercek) will spin up the temporary definition, auto-correct close names, and launch OAuth without touching the config file.
File: docs/call-syntax.md
summary: 'Reference for mcporter call argument styles, CLI signatures, and structured output modes.'
read_when:
- 'Working on call CLI UX or documenting invocation examples'
Call Syntax Reference
mcporter call now understands two complementary styles:
| Style | Example | Notes |
|---|---|---|
| Flag-based (compatible) | mcporter call linear.create_comment --issue-id LNR-123 --body "Hi" |
Use --key value, --key=value, key=value, key:value, or key: value pairsโideal for shell scripts. |
| Function-call (expressive) | mcporter call 'linear.create_comment(issueId: "LNR-123", body: "Hi")' |
Mirrors the pseudo-TypeScript signature shown by mcporter list; unlabeled values map to schema order. |
| Structured output | mcporter call 'linear.create_comment(...)' --output json |
Successful calls emit JSON bodies; failures emit { server, tool, issue } envelopes so automation can react to auth/offline/http errors. |
Both forms share the same validation pipeline, so required parameters, enums, and formats behave identically.
Reading the CLI Signatures
mcporter list <server> prints each tool as a compact TypeScript declaration:
/**
* Create a comment on a specific Linear issue.
* @param issueId The issue ID
* @param body The content of the comment as Markdown
* @param parentId? A parent comment ID to reply to
*/
function create_comment(issueId: string, body: string, parentId?: string): Comment;
// optional (3): notifySubscribers, labelIds, mentionIds, ...Key details:
- Doc blocks use
@paramlines so every parameter description (even optional ones) stays in view. - Required parameters appear without
?; optional parameters use?and inherit enum literals (e.g."json" | "markdown"). - Known format hints are appended inline:
dueDate?: string /* ISO 8601 */(we suppress the hint when the description already calls it out). - When a tool exposes more than two optional parameters (or has โฅ4 required parameters), the default output hides the extras and replaces them with an inline summary like
// optional (8): limit, before, after, orderBy, projectId, .... - Run
mcporter list <server> --all-parameterswhenever you want the full signature; the footer repeatsOptional parameters hidden; run with --all-parameters to view all fields.any time truncation occurs. - Return types come from each toolโs output schema, so youโll see concrete names when providers include
titlemetadata (e.g.DocumentConnection). When no schema is advertised we omit the: Typesuffix entirely instead of showingunknown. - Each server concludes with a short
Examples:block that mirrors the preferred function-call syntax. - Run
mcporter list <server> --brief(or--signatures) when you only want the compact signatures and optional summaries without doc blocks or examples.
Function-Call Syntax Details
- Named arguments preferred:
issueId: "123"keeps calls self-documenting. When labels are omitted, mcporter falls back to positional order defined by the tool schema. - Optional positional fallback: omit labels when calling
mcporter 'context7.resolve-library-id("React hooks docs", "react")'โarguments map to the schema order after any explicitly named parameters. - Literals supported: strings, numbers, booleans,
null, arrays, and nested objects. For strings containing spaces or commas, wrap the entire call in single quotes to keep the shell happy. - Error feedback: invalid keys, unsupported expressions, or parser failures bubble up with actionable messages (
Unsupported argument expression: Identifier,Unable to parse call expression: โฆ). - Server selection: You can embed the server in the expression (
linear.create_comment(...)) or pass it separately (--server linear create_comment(...)).
HTTP Selectors & Ad-hoc URLs
- You can skip
--serverentirely by pasting the MCP endpoint + tool name directly:mcporter call 'https://www.shadcn.io/api/mcp.getComponent(component: "vortex")'. - Mcporter strips the
.toolsuffix to derive the base server URL, reuses any configured definition that already points at that endpoint, and only registers a new ad-hoc server when necessary (--allow-httpstill guards plain HTTP URLs). - Function-call arguments continue to work in this form; if you omit parentheses you can still append
key=valuepairs (https://.../mcp.getComponent component=vortex). - This is especially handy for copy/pasting tool listings or experimenting with anonymous HTTP MCP serversโyou get the same parsing, auto-correction, and logging pipeline as the regular
server.toolsyntax. - Protocols are optional; bare domains such as
shadcn.io/api/mcp.getComponentsautomatically assumehttps://. If you really need plain HTTP, spell outhttp://and pass--allow-http. - Hostname comparisons ignore a leading
www.sohttps://shadcn.io/api/mcpandhttps://www.shadcn.io/api/mcpresolve to the same configured server when possible.
Tips
- Use
--args '{ "issueId": "LNR-123" }',--params '{ "issueId": "LNR-123" }',--json '{ "issueId": "LNR-123" }', or--json -if you already have JSON payloads. - The new syntax respects all existing features (timeouts,
--output, auto-correction). - Required fields show by default; pass
--all-parameterswhen you want the full parameter list (or--schemafor raw JSON schemas). - When in doubt, run
mcporter list <server>to see the current signature and sample invocation.
Flag-Based Syntax Details
--key value,--key=value,key=value,key:value,key: value, andkey:=valueall map to the same named-argument handling, so you can type whichever feels most natural for your shell. Long flag keys convert kebab-case to camelCase (--save-to-drafts truebecomessaveToDrafts: true). The:=form is accepted as a compatibility alias for=.- By default, arguments keep the same validation pipeline as the function-call syntaxโenums, numbers, and booleans are coerced automatically, and missing required fields raise errors.
--args -,--params -, and--json -read a JSON object from stdin.- Named flag-style values can read exact UTF-8 text from a file with
key=@pathor--key @path. Paths resolve from the current working directory, file contents remain strings without coercion, andkey=@@literalproduces the literal value@literal. Function-call strings such asbody: "@literal"remain literal. - Bare string values supplied via long flags wrap into one-item arrays when the tool schema declares that field as an array.
- Numeric-looking
key=valuearguments are restored to their original string spelling when the tool schema declares that parameter as a string, which keeps timestamp-like IDs such as Slackthread_ts=1234567890.123456intact. --raw-stringsdisables numeric coercion for flag-style and positional values so IDs/codes stay literal strings (code=12345stays"12345").--no-coercedisables all coercion for flag-style and positional values (true,null, and JSON-like values remain strings).- Long tool flags are checked against the selected tool schema before dispatch; undeclared flags fail with guidance to use
key=valueor--args. Insert--before literal positional values that start with--. --save-images <dir>keeps stdout formatting untouched while writing image content blocks to disk when a tool response includestype: "image"entries.tool=value/tool:valueandserver=valuestill act as aliases for--tool/--serverwhen you need to override the selector.
File: docs/cli-generator.md
summary: 'Behavior and architecture of mcporter generate-cli, including outputs, runtimes, regeneration, and policy boundaries.'
read_when:
- 'Changing generate-cli behavior or bundler integrations'
CLI Generator
mcporter generate-cli produces a standalone CLI for one MCP server. Tool schemas become subcommands and schema fields become positional arguments or flags. By default, the command writes <server>.ts in the working directory. Bundling is opt-in via --bundle; Rolldown handles Node.js output, while Bunโs native bundler handles Bun output. Use --bundler to override that choice.
Notes
- Generated CLI depends on the latest
commanderfor argument parsing. - Default timeout for tool calls is 30 seconds, overridable via
--timeout. - Runtime flag remains (
--runtime bun) to tailor shebang/usage instructions, but Node.js is the default. - Generated CLI embeds the resolved server definition and always targets that snapshot (no external
--configor--serveroverrides at runtime).
Usage Examples
# Minimal: infer the name from the command URL and emit TypeScript (optionally bundle)
npx mcporter generate-cli \
--command https://mcp.context7.com/mcp \
--minify
# Provide explicit name/description and compile a Bun binary (falls back to Node if Bun missing)
npx mcporter generate-cli \
--name context7 \
--command https://mcp.context7.com/mcp \
--description "Context7 docs MCP" \
--runtime bun \
--compile
chmod +x context7
./context7
# show the embedded help + tool list
# Shareable "one weird trick" for chrome-devtools (no config required)
npx mcporter generate-cli --command "npx -y chrome-devtools-mcp@latest"--minifyshrinks the bundled output via the selected bundler (output defaults to<server>.js).--compile [path]implies bundling and invokesbun build --compileto create the native executable (Bun only). When you omit the path, the compiled binary inherits the server name.- Use
--server '{...}'when you need advanced configuration (headers, env vars, stdio commands, OAuth metadata). - Omit
--nameto let mcporter infer it from the command URL (for example,https://mcp.context7.com/mcpbecomescontext7). - When targeting an existing config entry, you can skip
--serverand pass the name as a positional argument:npx mcporter generate-cli linear --bundle dist/linear.js. - When the MCP server is a stdio command, you can also skip
--commandby quoting the inline command as the first positional argument (e.g.,npx mcporter generate-cli "npx -y chrome-devtools-mcp@latest"). - Generated CLIs preserve
lifecycle: "keep-alive"for embedded stdio servers. At runtime they create a stable generated config under~/.mcporter/generated/(or$XDG_STATE_HOME/mcporter/generated/when set), auto-start the daemon as needed, and keep the server process alive across separate generated-CLI invocations. - Narrow the CLI to a specific subset of tools with
--include-tools:npx mcporter generate-cli linear --include-tools issues_list,issues_create. - Hide debug or admin tools with
--exclude-tools:npx mcporter generate-cli linear --exclude-tools debug_tool,admin_reset.
Artifact Metadata & Regeneration
- Every generated artifact embeds its metadata (generator version, resolved server definition, invocation flags). A hidden
__mcporter_inspectsubcommand prints the payload without contacting the MCP server, so binaries remain self-describing even after being copied to another machine. mcporter inspect-cli <artifact>shells out to that embedded command and prints a human summary (pass--jsonfor raw output). The summary includes a ready-to-rungenerate-clicommand you can reuse directly.mcporter generate-cli --from <artifact>replays the stored invocation against the latest mcporter build.--server,--runtime,--timeout,--minify/--no-minify,--bundle,--compile,--output, and--dry-runlet you override specific pieces of the stored metadata when necessary.- Because the metadata lives inside the artifact, any template, bundle, or compiled binary can be refreshed after a generator upgrade without juggling sidecar files.
Policy boundary for generated CLIs
A generated CLI or typed client can be invoked independently of the MCP client
that originally configured the server. It therefore does not automatically
inherit that client's approval prompts, tool-call policies, or audit trail.
Use --include-tools to reduce the generated surface, but do not treat a static
tool list as dynamic authorization. Deployments that need per-call policy should
route the generated command through an external wrapper or policy gateway that:
- normalizes the server, tool name, and arguments;
- evaluates current policy and requests approval when required;
- blocks the call or invokes the generated CLI; and
- persists a redacted decision and result record.
Conceptual wrapper pseudocode (not a built-in mcporter API):
const operation = normalize({ server, tool, arguments });
let decision;
let outcome = 'evaluation_failed';
let result;
let failure;
try {
decision = await gateway.evaluate(operation);
if (decision.action === 'block') {
outcome = 'blocked';
failure = 'policy_block';
throw new Error(decision.reason);
}
if (decision.action === 'approve' && !(await approvals.confirm(decision))) {
outcome = 'approval_declined';
failure = 'approval_declined';
throw new Error('Approval declined');
}
try {
result = await generatedCli.call(tool, arguments);
outcome = 'succeeded';
return result;
} catch (error) {
outcome = 'execution_failed';
failure = classifyError(error);
throw error;
}
} catch (error) {
failure ??= classifyError(error);
throw error;
} finally {
await audit.append(redact({ operation, decision, outcome, result, failure }));
}The finally path records a redacted outcome for blocked requests, declined
approvals, policy-evaluation failures, execution failures, and successful calls.
The wrapper governs only calls routed through it. Direct execution of the
generated artifact bypasses that policy boundary.
File: docs/cli-reference.md
summary: 'Quick reference for mcporter subcommands, their arguments, and shared global flags.'
read_when:
- 'Need a refresher on available CLI commands'
mcporter CLI Reference
A quick reference for the primary mcporter subcommands. Each command inherits--config <file> and --root <dir> to override where servers are loaded from.
`mcporter list [server]`
- Without arguments, lists every configured server (with live discovery + brief
status). - With a server name, prints TypeScript-style signatures for each tool, doc
comments, optional summaries, and any serverinstructionsreturned during
MCP initialization. - With
server.tool, prints just that tool; combine with--schemafor a single
tool schema. - Add
--briefor--signatureswith a server orserver.tooltarget to keep
the server header/instructions and print compact signatures without doc
comments, examples, or schemas. - Add
--statuswith a server target to print only the concise status row
instead of full tool docs. - Add
--exit-codeto make the command exit 1 when any checked server is
unhealthy, or--quietto suppress output and imply--exit-code. - Hidden alias:
list-tools(kept for muscle memory; not advertised in help output). - Hidden ad-hoc flag aliases:
--ssefor--http-url,--insecurefor--allow-http(for plain HTTP testing). - Flags:
--briefโ compact single-server output; cannot be combined with--json,--schema,--verbose, or--all-parameters.--signaturesโ alias for--brief.--all-parametersโ include every optional parameter in the signature.--schemaโ pretty-print the JSON schema for each tool.--statusโ check server status only; cannot be combined with--brief,--schema, or--all-parameters.--exit-codeโ exit 1 when any checked server is unhealthy.--quietโ suppress output and exit 1 when any checked server is unhealthy.--timeout <ms>โ per-server timeout when enumerating all servers.--no-oauthโ never start an interactive OAuth flow; use cached
tokens only while keeping eligible connections pooled.
`mcporter call `
- Invokes a tool once and prints the response; supports positional arguments via
pseudo-TS syntax and--argflags. - Useful flags:
--server,--toolโ alternate way to target a tool.--args <json>,--params <json>โ provide a JSON object payload.--โ stop flag parsing so remaining tokens stay literal positional values.--timeout <ms>โ override the 60 s call timeout;MCPORTER_CALL_TIMEOUTprovides the equivalent environment override.--output text|markdown|json|rawโ choose how to render theCallResult.--save-images <dir>โ persist image content blocks to files under the specified directory.--raw-stringsโ disable numeric coercion for flag-style and positional values.--no-coerceโ disable all flag-style/positional value coercion.key=@path/--key @pathโ read a named UTF-8 string argument from a file; prefix with@@for a literal leading@.- Generic long tool flags are validated against the selected tool schema before dispatch.
--tail-logโ stream tail output when the tool returns log handles.--no-oauthโ never start an interactive OAuth flow; use cached
tokens only while keeping eligible connections pooled.
`mcporter resource [uri]`
- Lists resources exposed by a server when no URI is provided.
- Reads an MCP resource when
uriis provided and renders text, markdown, JSON,
or raw output using the same output formatter asmcporter call. - Hidden alias:
resources. - Useful flags:
--output auto|text|markdown|json|rawโ choose how to render the response.--jsonโ shortcut for--output json.--rawโ shortcut for--output raw.--no-oauthโ never start an interactive OAuth flow; use cached
tokens only while keeping eligible connections pooled.
`mcporter serve [--servers a,b,c] [--stdio | --http ]`
- Exposes daemon-managed keep-alive servers as one MCP server for clients that
consume MCP over stdio or Streamable HTTP. tools/listqueries the daemon for each selected server and publishes tools
asserver__tool;tools/callstrips the prefix and routes the call through
the daemon.- In HTTP mode,
/mcpkeeps the aggregate namespaced bridge, while/mcp/<server>exposes one selected keep-alive server with its original,
unprefixed tool names. - Only configured keep-alive servers participate. Add
"lifecycle": "keep-alive"to a server definition when you want it managed
by the daemon. - Flags:
--stdioโ serve MCP over stdio; this is the default and is the mode to
register with Claude Code, Codex, and similar clients.--httpโ serve MCP Streamable HTTP on/mcpand/mcp/<server>, bound to127.0.0.1by default.--host <host>โ override the HTTP bind host when you intentionally need a
non-local listener.--servers <csv>โ expose only the listed keep-alive server names.
`mcporter generate-cli`
- Produces a standalone CLI for a single MCP server (optionally bundling or
compiling with Bun). - Key flags:
--server <name>(or inline JSON) โ choose the server definition.--commandโ point at an ad-hoc HTTP endpoint (includehttps://or usehost/path.tool) or a stdio command (anything with spaces, e.g.,"npx -y chrome-devtools-mcp@latest"). If you omit--command, the first positional argument is inspected: whitespace โ stdio, otherwise the parser probes for HTTP/HTTPS and falls back to config names.--outputโ where to write the TypeScript template.--bundleโ emit a bundle (Node/Bun) ready forbun x.--bundler rolldown|bunโ pick the bundler implementation (defaults to Rolldown unless the runtime resolves to Bun, in which case Bunโs bundler is used automatically; still requires a local Bun install).--compileโ compile with Bun (implies--runtime bun).--include-tools <a,b,c>โ only generate subcommands for the specified tools (exact MCP tool names). It is an error if any requested tool is missing.--exclude-tools <a,b,c>โ generate subcommands for every tool except the ones listed. Unknown tool names are ignored. If all tools are excluded, generation fails.--include-toolsand--exclude-toolsare mutually exclusive.--timeout <ms>/--runtime node|bunโ shared via the generator flag
parser so defaults stay consistent.--from <artifact>โ reuse metadata from an existing CLI artifact (legacyregenerate-clibehavior, must point at an existing CLI).--dry-runโ print the resolvedmcporter generate-cli ...command without
executing (requires--from).- Positional shorthand:
npx mcporter generate-cli linearuses the configuredlineardefinition;npx mcporter generate-cli https://example.com/mcp
treats the URL as an ad-hoc server definition.
`mcporter emit-ts `
- Emits TypeScript definitions (and optionally a ready-to-use client) describing
a serverโs tools. This reuses the same formatter asmcporter listso doc
comments, signatures, and examples stay in sync. - Modes:
--mode types --out <file.d.ts>(default) โ export an interface whose
methods returnPromise<CallResult>, with doc comments and optional
summaries.--mode client --out <file.ts>โ emit both the interface (<file>.d.ts)
and a factory that wrapscreateServerProxy, returning objects whose
methods resolve toCallResult.
- Other flags:
--include-optional(alias--all-parameters) โ show every optional field.--types-out <file>โ override where the.d.tssits when using client
mode.
For more detail about configuration and OAuth flows, see docs/config.md and
the command-specific docs under docs/.
File: docs/config.md
summary: 'How mcporter discovers, merges, and mutates configuration files (project + home + imports), including OAuth and persistence.'
read_when:
- 'Working on config resolution, imports, or mcporter config subcommands'
Configuration Guide
Overview
mcporter keeps three configuration buckets in sync: repository-scoped JSON (config/mcporter.json), imported editor configs (Cursor, Claude, Codex, Windsurf, OpenCode, VS Code), and ad-hoc definitions supplied on the CLI. This guide explains how those sources merge, how to mutate them with mcporter config ..., and the safety rails around OAuth, env interpolation, and persistence.
Quick Start
- Create
config/mcporter.jsonat the repo root:Thejsonc{ "$schema": "https://raw.githubusercontent.com/openclaw/mcporter/main/mcporter.schema.json", "mcpServers": { "linear": { "description": "Linear issues", "baseUrl": "https://mcp.linear.app/mcp", "headers": { "Authorization": "Bearer ${LINEAR_API_KEY}" }, }, }, "imports": ["cursor", "claude-code", "claude-desktop", "codex", "windsurf", "opencode", "vscode"], }$schemaproperty enables IDE autocomplete and validation. Use the raw GitHub URL for the latest schema, or copymcporter.schema.jsonlocally. - Run
mcporter list linear(ormcporter config list linear) to make sure the runtime can reach it. - Use
mcporter config add shadcn https://www.shadcn.io/api/mcpto persist another server without editing JSON. - Authenticate any OAuth-backed server with either
mcporter auth <name>ormcporter config login <name>; tokens land in the shared vault (~/.mcporter/credentials.json, or$XDG_DATA_HOME/mcporter/credentials.jsonwhen set) unless you overridetokenCacheDir.
Config Resolution Order
mcporter now merges home and project config files by default so global servers stay available inside repos. The order depends on how you invoke the CLI:
- If you pass
--config <file>(or set--configprogrammatically), only that file is usedโno merging. - If
MCPORTER_CONFIGis set, only that file is usedโno merging. - Otherwise, mcporter loads both of these layers (when present):
$XDG_CONFIG_HOME/mcporter/mcporter.json[c]whenXDG_CONFIG_HOMEis set, falling back to~/.mcporter/mcporter.json[c]when no XDG mcporter config exists<root>/config/mcporter.json
Entries from the project file override entries with the same name from the home file. Each layer still pulls in its own imports before merging.
All mcporter config โฆ mutations still write back to a single file: the explicit path when provided; otherwise the project config path (<root>/config/mcporter.json). To edit the home file explicitly, run commands like mcporter config --config ~/.mcporter/mcporter.json add <name> โฆ or set MCPORTER_CONFIG in your shell profile.
mcporter honors XDG Base Directory env vars for its own paths when they are explicitly set to absolute paths:
| Kind | Env var | mcporter path | Legacy fallback |
|---|---|---|---|
| config | XDG_CONFIG_HOME |
$XDG_CONFIG_HOME/mcporter/mcporter.json[c] |
~/.mcporter/... |
| data | XDG_DATA_HOME |
$XDG_DATA_HOME/mcporter/credentials.json |
~/.mcporter/... |
| cache | XDG_CACHE_HOME |
$XDG_CACHE_HOME/mcporter/<server>/schema.json |
~/.mcporter/... |
| state | XDG_STATE_HOME |
$XDG_STATE_HOME/mcporter/daemon/... |
~/.mcporter/daemon |
Unset, empty, or relative XDG vars fall back to ~/.mcporter for backwards compatibility. For config files only, an absolute XDG_CONFIG_HOME is XDG-first but still probes ~/.mcporter/mcporter.json[c] when no XDG mcporter config exists, so embedders that sandbox unrelated tools with XDG_CONFIG_HOME do not accidentally hide the user's registry. Explicit overrides still win: --config/MCPORTER_CONFIG for config files, tokenCacheDir for per-server OAuth/schema cache directories, and MCPORTER_DAEMON_DIR for daemon files.
Discovery & Precedence
mcporter builds a merged view of all known servers before executing any command. The sources load in this order:
| Priority | Source | Notes |
|---|---|---|
| 1 | Explicit --http-url, --stdio, or bare URL passed to commands |
Highest priority, never cached unless --persist is supplied. Requires --allow-http for plain HTTP URLs. |
| 2 | config/mcporter.json (or the file passed via --config) |
Default path is <root>/config/mcporter.json; missing file returns an empty config so commands continue to work. |
| 3 | Imports listed in "imports" |
When you omit imports, mcporter loads ['cursor','claude-code','claude-desktop','codex','windsurf','opencode','vscode']. When you specify a non-empty array, mcporter appends any omitted defaults after your list so shared presets remain available. |
Rules:
- Later sources never override earlier ones. Local config always wins over imports; ad-hoc descriptors override both for the duration of a command.
- Each merged server tracks its origin (local path vs. import path), so
mcporter config get <name>can show you the path before you edit or remove the local copy withmcporter config remove <name>. - Imports remain read-only until you explicitly copy an entry via
mcporter config import <kind> --copyor runmcporter config add --copy-from claude-code:linear.
CLI Workflows
mcporter config is the entry point for reading and writing configuration files. Use the existing ad-hoc flags on mcporter list|call|auth when you want ephemeral definitions; once youโre ready to persist them, switch back to mcporter config add.
Use --scope home|project with mcporter config add to pick the write target explicitly. project is always the default (creating config/mcporter.json if needed); home writes to the XDG config path when XDG_CONFIG_HOME is set, otherwise ~/.mcporter/mcporter.json, even when a project config is present. --persist still takes precedence when you need a custom file.
`mcporter config list [filter]`
- Shows local entries by default. Pass
--source importto list imported editor configs, or--jsonfor machine output. - Always appends a summary of other config files (paths, counts, sample names) so you know where imported entries live.
filteraccepts a name, glob fragment, orsource:cursorselector.- Adds informational notes when we auto-correct names (same machinery as
mcporter list).
`mcporter config get `
- Prints the resolved definition for a single server, including the on-disk path, inherited headers/env, and transport details.
- Near-miss names are auto-corrected with the same heuristics as
mcporter list/call, and youโll see suggestions whenever ambiguity remains. - Supports ad-hoc descriptors so you can inspect a URL before persisting it.
`mcporter config add [target]`
- Persists a server into the writable config file. Accepts both positional shortcuts (
mcporter config add sentry https://mcp.sentry.dev/mcp) and flag-driven definitions:--transport http|sse|stdio--urlor--command/--stdio--env,--header,--token-cache-dir,--description,--client-name,--oauth-redirect-url--oauth-client-id,--oauth-client-secret-env,--oauth-token-endpoint-auth-methodfor pre-registered OAuth clients.--copy-from importKind:nameto clone settings from an imported entry before editing.
--dry-runshows the JSON diff without writing, while--persistoverrides the destination file.
`mcporter config remove `
- Removes the local definition. Names sourced exclusively from imports remain untouched until you copy them locally.
`mcporter config import `
- Displays (and optionally copies) entries from editor-specific configs:
cursor:.cursor/mcp.jsonin the repo, falling back to~/.config/Cursor/User/mcp.json(or%APPDATA%/Cursor/Useron Windows).claude-code:<root>/.claude/settings.local.json,<root>/.claude/settings.json,<root>/.claude/mcp.json, then~/.claude/settings.json,~/.claude/mcp.json,~/.claude.json.settings.local.jsonis meant for untracked per-developer overrides, whilesettings.jsonis the shared project config.claude-desktop: platform-specificClaude/claude_desktop_config.jsonpaths.codex:<root>/.codex/config.toml, then~/.codex/config.toml.windsurf: Codeiumโs Windsurf config under%APPDATA%/Codeium/windsurf/mcp_config.jsonor~/.codeium/windsurf/mcp_config.json.opencode: HonorsOPENCODE_CONFIGwhen set, then<root>/opencode.json(c),OPENCODE_CONFIG_DIR/opencode.json(c), and finally${XDG_CONFIG_HOME:-~/.config}/opencode/opencode.json(c)(or%APPDATA%/opencode/opencode.json(c)on Windows). Both.jsonand.jsoncextensions are supported.vscode:Code/User/mcp.json(stable + Insiders) inside the OS-appropriate config directory.
--copywrites selected entries into your local config;--filter <glob>narrows the import list;--path <file>lets you point at bespoke locations.
`mcporter config login ` / `logout`
- Mirrors
mcporter auth.logincompletes OAuth (or token provisioning) for either a named server or an ad-hoc URL. When a hosted MCP returns 401/403, mcporter automatically promotes that target to OAuth and re-runs the flow, matching the behavior documented indocs/adhoc.md. --no-browsersuppresses automatic browser launch and prints the authorization URL to stdout so it can be copied from a headless host.--browser noneis accepted as a compatibility alias, andMCPORTER_OAUTH_NO_BROWSER=1/true/yesenables the same behavior by environment.- In
--json --no-browsermode, stdout contains a JSON object withauthorizationUrlandredirectUrl; diagnostics stay off stdout so scripts can parse the result. Treat emitted authorization URLs as sensitive operational output. - A configured server name is an OAuth trust boundary. Changing its URL clears cached tokens, dynamic client registration, PKCE verifier, and callback state from the shared vault and any
tokenCacheDir; changing the URL back does not restore the old credentials, so re-authentication is required. logoutwipes the shared vault entry, legacy~/.mcporter/<name>/caches, and the customtokenCacheDirwhen present. Pass--allto clear everything.
`mcporter config doctor`
- Early validator that checks for simple issues (e.g., OAuth entries missing cache paths). Future iterations will add fixes for Accept headers, duplicate imports, and more.
Ad-hoc & Persistence
--http-urland--stdioflags live onmcporter list|call|auth, keepingmcporter configfocused on persistent config files. Ad-hoc HTTP targets also accept repeatable--header KEY=valueflags for private endpoints.- Names default to slugified hostnames or executable/script combos. Supply
--nameto improve reuse; mcporter uses that slug for OAuth caches even before persistence. --allow-httpis mandatory for cleartext endpoints so we never downgrade transport silently.- Add
--persist(defaulting toconfig/mcporter.jsonwhen omitted) to copy the ad-hoc definition into config. We reuse the same serializer as the import pipeline, so copying from Cursor โ local config produces identical structure and preserves custom env/header fields. - When an ad-hoc HTTP server returns an OAuth challenge during
list,call, orauth, the persisted entry is rewritten withauth: "oauth"so later commands use the cached OAuth path instead of retrying unauthenticated HTTP. --env KEY=VALentries merge with existingenvdictionaries if you later persist the same server; nothing is lost when you alternate between CLI flags and JSON edits.--header KEY=VALentries merge into the persisted HTTPheadersobject when used with--persist; values support the same$env:VAR,${VAR}, and${VAR:-fallback}placeholders as config-file headers.
HTTP Compatibility
HTTP MCP servers normally use Node's built-in fetch for request/response traffic. MCPorter's optional long-lived SSE receive channel uses a separate HTTP/1.1 connection so an idle stream cannot occupy the request pool and stall later tool calls. Set httpFetch: "default" to opt out of that isolation and use the built-in fetch for every request.
If a provider rejects the built-in stack entirely but accepts plain Node https.request traffic, set httpFetch: "node-http1" on the server entry to force an HTTP/1.1 fetch implementation for all Streamable HTTP and SSE requests:
{
"mcpServers": {
"sunsama": {
"baseUrl": "https://api.sunsama.com/mcp",
"headers": { "Authorization": "Bearer ${SUNSAMA_TOKEN}" },
"httpFetch": "node-http1",
},
},
}The Sunsama endpoint is auto-detected and uses the all-request compatibility path by default.
JSON Schema for IDE Support
mcporter provides a JSON Schema for config file validation and autocompletion. Add the $schema property to your config file:
{
"$schema": "https://raw.githubusercontent.com/openclaw/mcporter/main/mcporter.schema.json",
"mcpServers": { ... }
}For local development, you can reference the schema from the repo root:
{
"$schema": "../mcporter.schema.json",
"mcpServers": { ... }
}The schema is auto-generated from the Zod validation schemas using pnpm generate:schema.
Schema Reference
Top-level structure:
| Key | Type | Description |
|---|---|---|
mcpServers |
object | Map of server names โ definitions. Required even if empty. |
imports |
string[] | Optional list of import kinds. Empty array disables imports entirely; omitting the key falls back to the default list. |
Server definition fields (subset of what RawEntrySchema accepts):
| Field | Description |
|---|---|
description |
Free-form summary printed by mcporter list/config list. |
baseUrl / url / serverUrl |
HTTPS or HTTP endpoint. http:// requires --allow-http in ad-hoc mode but works in config if you explicitly set it. |
command / args |
Stdio executable definition (string or array). Arrays are preferred because they avoid shell quoting issues. |
cwd |
Working directory for stdio servers. A leading ~ is expanded to $HOME; relative paths resolve against the config file directory. Defaults to the config file directory when omitted. |
env |
Key/value pairs applied when launching stdio commands. Supports ${VAR} interpolation and ${VAR:-fallback} defaults. Existing process env values win over fallbacks. |
headers |
Request headers for HTTP/SSE transports. Values can reference $env:VAR or ${VAR} placeholders, which must be set at runtime or mcporter aborts with a helpful error. |
auth |
Recognizes oauth and refreshable_bearer. oauth runs the browser/client OAuth flow for HTTP transports; refreshable_bearer refreshes cached bearer tokens non-interactively before connecting. |
tokenCacheDir |
Directory for OAuth tokens and schema caches; still honored, but mcporter now keeps a centralized vault in ~/.mcporter/credentials.json or $XDG_DATA_HOME/mcporter/credentials.json when set (legacy per-server caches are auto-migrated). Supports ~ expansion. |
clientName |
Optional identifier some servers use for telemetry/audience segmentation. |
protocolVersion / protocol_version |
Protocol negotiation override: auto (the default), legacy for byte-compatible 2025-era connections, or 2026-07-28 to require that modern revision. |
chromeDevtoolsRelay / chrome_devtools_relay |
OpenClaw Chrome extension relay policy for eligible chrome-devtools-mcp --autoConnect stdio definitions: prefer (default), require, or off. MCPORTER_CHROME_DEVTOOLS_RELAY_POLICY overrides config. See the daemon guide. |
oauthClientId |
Pre-registered OAuth client id for providers that do not support dynamic client registration. |
oauthClientSecretEnv |
Environment variable containing the OAuth client secret. Prefer this over committing oauthClientSecret directly. |
oauthTokenEndpointAuthMethod |
Optional token endpoint auth method override, for example client_secret_post when the provider requires client credentials in the token request body. |
oauthRedirectUrl |
Override the default localhost callback. Required for many pre-registered OAuth apps because the provider must allowlist the exact redirect URI. Also useful when tunneling OAuth through Codespaces or remote dev boxes. |
oauthClientMetadataUrl |
Optional HTTPS Client ID Metadata Document URL. When the authorization server supports URL-based client identity, the SDK uses this URL as the client id; otherwise it falls back to dynamic client registration. |
oauthScope |
Optional explicit OAuth scope string. If omitted, mcporter lets the MCP SDK derive scope from server/auth metadata. Use this as an escape hatch for providers that require explicit scopes but donโt publish scopes_supported. |
oauthCommand.args |
For STDIO servers that ship a custom auth subcommand (e.g., Gmail MCP). mcporter will spawn the stdio command with these args when you run mcporter auth <name>, so you donโt need to call npx ... auth manually. |
refresh |
Explicit token refresh settings for auth: "refreshable_bearer". Supports tokenEndpoint, clientIdEnv, clientSecretEnv, clientAuthMethod, refreshSkewSeconds, and accessTokenEnv (plus snake_case aliases). |
allowedTools / allowed_tools |
Optional exact-name allowlist. Only listed tools appear in mcporter list and can be called. An empty array blocks all tools. Cannot be combined with blockedTools. |
blockedTools / blocked_tools |
Optional exact-name blocklist. Listed tools are hidden from mcporter list and rejected by mcporter call. Cannot be combined with allowedTools. |
mcporter normalizes headers to include Accept: application/json, text/event-stream automatically, matching the runtimeโs streaming expectations.
String-valued config fields support ${VAR} and ${VAR:-fallback} placeholders. Secret-bearing headers, env, and bearer-token placeholders are preserved in config get/config list output and resolved only when the transport runs; *Env fields name environment variables and are not expanded.
Top-level daemonIdleTimeoutMs (or daemon_idle_timeout_ms) shuts down the keep-alive daemon after that many milliseconds without daemon requests. Per-server lifecycle.idleTimeoutMs still controls when individual keep-alive transports are closed.
Refreshable Bearer Tokens
Use auth: "refreshable_bearer" when you already seeded OAuth tokens with mcporter vault set <server> or tokenCacheDir, and the server should receive only a fresh bearer token at runtime. HTTP servers get Authorization: Bearer <token> when no authorization header is already configured. STDIO servers require refresh.accessTokenEnv; mcporter refreshes before spawning the process and injects that env var with the raw access token.
{
"mcpServers": {
"example": {
"command": "uvx",
"args": ["example-mcp-server"],
"auth": "refreshable_bearer",
"refresh": {
"tokenEndpoint": "https://api.example.com/oauth/token",
"clientIdEnv": "EXAMPLE_CLIENT_ID",
"clientSecretEnv": "EXAMPLE_CLIENT_SECRET",
"clientAuthMethod": "client_secret_basic",
"refreshSkewSeconds": 300,
"accessTokenEnv": "EXAMPLE_ACCESS_TOKEN"
}
}
}
}For keep-alive stdio servers, refresh happens before process start. If that process cannot read updated credentials after startup, use lifecycle: "ephemeral" or restart the daemon before the injected token expires.
Imports & Conflict Resolution
pathsForImport(kind, rootDir)determines every candidate path. mcporter searches the repo first, then user-level directories, and stops at the first file that parses.- Entries pulled from imports are treated as read-only snapshots. The merge process keeps the first definition for each name; later sources with the same name are skipped until you override locally.
- To copy an imported entry, either run
mcporter config import <kind> --copy --filter nameor usemcporter config add name --copy-from kind:name. The copy operation writes through the same JSON normalization stack, so the resulting file matches our schema even if the source format was TOML (Codex) or legacy JSON shapes (serversvsmcpServers).
Project vs. Machine Layers
- Keep
config/mcporter.jsonunder version control. Encourage contributors to add sensitive data via env vars (${LINEAR_API_KEY}) rather than inline secrets. - For pre-registered OAuth apps, store the public
oauthClientIdin config and pointoauthClientSecretEnvat a local environment variable.oauthClientSecretis supported for private machine-local configs but should not be committed. - For headless deployments that already have OAuth credentials, run
mcporter vault set <server> --tokens-fileormcporter vault set <server> --stdinwith a JSON payload shaped like{ "tokens": { ... }, "clientInfo": { ... } }. This lets mcporter compute the vault key from the resolved server definition instead of duplicating that internal format in scripts. - Machine-specific additions can live in
~/.mcporter/local.jsonor$XDG_CONFIG_HOME/mcporter/local.json; pointmcporter config --config ~/.mcporter/local.json add ...there when you prefer not to touch the repo. Since the runtime only watches one config at a time, CI jobs should always pass--config config/mcporter.json(or run from the repo root) for deterministic behavior. - OAuth tokens, cached server metadata, and generated CLIs should remain outside the repo (
~/.mcporter/...or the matchingXDG_*_HOME/mcporter/..., plusdist/).
Validation & Troubleshooting
mcporter list --http-url ...refuses to auto-run OAuth to keep listing commands quick; usemcporter config login ...ormcporter auth ...to finish credential setup.- When env placeholders are missing, commands fail fast with the exact variable name. Add the variable or wrap it in
${VAR:-fallback}to provide defaults. - Use
mcporter config get <name>to confirm where a server came from. If a teammateโs Cursor config keeps overriding your local entry, reorder theimportsarray to move Cursor later or set it to[]to disable imports entirely. docs/adhoc.mdcovers deeper debugging, including tmux workflows and OAuth promotion logs.
File: docs/daemon.md
summary: 'How mcporter keeps stateful MCP servers warm and exposes them through its bridge.'
read_when:
- 'Configuring keep-alive servers, the daemon, serve mode, or Chrome DevTools'
Keep-alive daemon
MCPorter can keep stateful stdio servers alive across separate CLI invocations. A per-login daemon owns those transports, while the ordinary mcporter list and mcporter call commands route eligible servers through its local socket.
Lifecycle selection
Chrome DevTools, Mobile MCP, Playwright MCP, and CloudBase MCP definitions default to keep-alive when MCPorter recognizes their configured name or stdio command. Other servers remain ephemeral unless their config opts in:
{
"mcpServers": {
"browser": {
"command": "npx",
"args": ["-y", "chrome-devtools-mcp@latest", "--autoConnect"],
"lifecycle": "keep-alive",
},
"interactive": {
"url": "https://example.com/mcp",
"lifecycle": "ephemeral",
},
},
}MCPORTER_KEEPALIVE=name opts named servers in. MCPORTER_DISABLE_KEEPALIVE=name opts them out; both variables accept comma-separated names or *. Explicit config remains the clearest choice for project-owned definitions.
Ad-hoc targets passed with --stdio, --http-url, or a URL selector are per-process. Persist the definition into config before expecting it to participate in the shared daemon.
Daemon commands
mcporter daemon status
mcporter daemon start
mcporter daemon restart
mcporter daemon stopEligible servers start the daemon automatically when a list or call needs them. Use explicit commands to inspect, pre-warm, restart after configuration changes, or stop every managed transport.
Daemon calls are non-interactive. If a server requests elicitation, the call is declined with a hint to use an ephemeral terminal call instead. See protocols and interactive requests.
Set MCPORTER_OAUTH_NO_BROWSER=1 (also accepts true or yes) on a serve/daemon process to suppress automatic OAuth browser launches. Affected calls fail once with the server name and an mcporter auth <name> --no-browser hint instead of restarting or replaying. Changing the normalized setting replaces an already-running daemon on the next operation.
The local socket timeout is an idle liveness budget rather than a cap on the whole operation. A current daemon emits progress frames while work is in flight, allowing interactive OAuth to run to its own deadline without causing a daemon restart or a duplicate browser prompt. Mixed versions remain compatible: a current client falls back to the previous flat deadline with an older daemon, while a current daemon sends the single-response format expected by older clients unless the caller explicitly opts into progress frames.
Expose servers to another MCP client
mcporter serve turns the daemon's keep-alive servers back into an MCP server:
mcporter serve --stdio
mcporter serve --http 3000
mcporter serve --http 3000 --servers chrome-devtools,playwrightThe stdio form is suitable for clients such as Claude Code and Codex. HTTP mode binds to 127.0.0.1 by default and serves an aggregate endpoint at /mcp, with namespaced tools such as chrome-devtools__list_pages. It also serves /mcp/<server> with that server's original tool names.
Chrome DevTools through the OpenClaw extension relay
chrome-devtools-mcp --autoConnect uses Chrome's remote-debugging handshake, which can display an โAllow remote debugging?โ dialog for each session. When the OpenClaw Chrome extension relay is paired on the same host, MCPorter can use that extension-backed Chrome control path instead. Automatic rewriting supports direct chrome-devtools-mcp commands and the standard npx/bunx launch forms used by MCPorter definitions. Other shell and package-manager wrappers remain unchanged under prefer; require rejects recognizable Chrome auto-connect commands behind those unsupported wrappers instead of silently launching the legacy path.
MCPorter requires an OpenClaw relay that implements Browser Relay Authentication v2, including the connection-bound challenge, completion, authenticated /json/version, and same-connection /cdp upgrade sequence. An older OpenClaw relay is unsupported: require fails with unsupported-auth, while prefer may launch the original Chrome --autoConnect path. MCPorter never retries an old Bearer, Basic, or raw-token relay handshake, even when the v2 endpoint returns 404, 401, or 426, a proof fails, or the handshake times out. During OpenClaw's dual-stack migration window, legacy acceptance exists only for older clients; current MCPorter always uses v2.
The mode-0600, current-user relay key never enters an HTTP header, URL, WebSocket subprotocol, application frame, child command line, or child environment. MCPorter derives its non-secret keyId, connects once to a numeric loopback address, verifies the connected peer, and keeps that exact raw TCP socket from the HMAC challenge through completion, /json/version, and the /cdp WebSocket upgrade. It does not follow redirects, reconnect, re-resolve, or hand the key to the proxy. Only after both server proofs verify does MCPorter start a short-lived downstream proxy bound strictly to 127.0.0.1; that proxy wraps the already-authenticated and already-upgraded socket rather than opening another upstream connection.
The same raw client supports the protocol's separate json-list flow: it authenticates one /json/list request on the retained socket, reads a bounded response, and closes instead of upgrading.
Each downstream proxy retains the protections introduced for the child handoff: it gets a fresh 256-bit authorization bearer, MCPorter writes that ephemeral value to an exclusively created mode-0600 file inside a mode-0700 temporary directory, and only the protected file path reaches a Node preload through the child environment. The preload validates and consumes the file, removes the handoff variable, and appends --wsHeaders only to JavaScript's process.argv; the OS command line contains only the credential-free loopback WebSocket endpoint. The proxy accepts exactly one authorized downstream WebSocket, synthesizes that child's 101 response, and then bridges frames to the retained upstream socket. Losing either side retires the proxy and its authenticated connection.
The preload composes with existing NODE_OPTIONS, including MCPorter's separate Chrome compatibility preload. The proxy, handoff file, preload, and temporary directory are closed or removed on normal shutdown, setup failure, abort, and negotiation retry. On POSIX systems, ownership and permissions are checked before the handoff is consumed. On Windows, MCPorter creates the temporary directory atomically with a verified current-user-only ACL before its path exists; setup fails closed if that security descriptor cannot be established. Plaintext relay URLs remain limited to loopback, including custom URLs set with MCPORTER_CHROME_DEVTOOLS_RELAY_URL; remote HTTP relay targets and URLs containing credentials are rejected.
Choose routing with chromeDevtoolsRelay (or chrome_devtools_relay) on the server definition, or override it for the process with MCPORTER_CHROME_DEVTOOLS_RELAY_POLICY:
preferis the default. It uses the v2 extension relay when authentication and the protected child handoff succeed; otherwise it may keep only the original Chrome--autoConnectbehavior. It never falls back to legacy relay authentication.requirefails before any legacy auto-connect process is launched if the endpoint, credential, probe, authentication, extension connection, local proxy, or protected handoff is unavailable. Retries remain fail-closed.offdisables relay probing and rewriting. The olderMCPORTER_DISABLE_CHROME_DEVTOOLS_RELAY=1switch remains an alias foroff.
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["-y", "chrome-devtools-mcp@latest", "--autoConnect"],
"lifecycle": "keep-alive",
"chromeDevtoolsRelay": "require",
},
},
}Before probing, MCPorter runs the non-secret openclaw browser extension cdp --json metadata command without the legacy-bearer option. POSIX launches openclaw directly. Windows resolves an existing openclaw.cmd only from safe absolute PATH entries, rejects the current directory and command-expanding path bytes, then passes the quoted absolute shim plus constant arguments to a validated system cmd.exe. Process spawning always keeps shell: false; time and combined output are bounded, and timeout or overflow terminates the full Windows process tree through the validated system taskkill.exe. Fallback diagnostics never include stdout or stderr. MCPorter accepts only the exact loopback Browser Relay Authentication v2 CDP contract whose keyId matches the local relay credential. An explicit nonempty MCPORTER_CHROME_DEVTOOLS_RELAY_URL remains authoritative and skips metadata discovery; unavailable, timed-out, malformed, unsafe, or incompatible metadata falls back to http://127.0.0.1:18799. Relay control and OpenClaw discovery variables in a server definition's env map use the same placeholder resolution as its runtime transport and override process-level values for both routing and daemon identity.
Metadata discovery and the authenticated /json/version probe each use the relay timeout, which is 5 seconds by default. MCPORTER_CHROME_DEVTOOLS_RELAY_TIMEOUT_MS accepts milliseconds, clamped to 100โ30000; unset, zero, non-integer, and otherwise invalid values use 5000. Credential discovery follows OpenClaw: OPENCLAW_OAUTH_DIR wins, then OPENCLAW_STATE_DIR; otherwise credentials live under the effective home in .openclaw/credentials, or .openclaw-/credentials for a validated non-default OPENCLAW_PROFILE. Explicit OPENCLAW_HOME takes precedence over HOME and USERPROFILE.
Keep-alive daemons include the effective policy, discovered or explicit URL, timeout, state directory, credential directory, v2 protocol marker, and derived keyId in their runtime identity. Both daemon metadata creation and freshness checks await the same bounded asynchronous resolver; freshness never launches OpenClaw through a synchronous child process. Changing the selected OpenClaw profile or relay port, or rotating the key at the same path, therefore replaces stale daemon state on the next operation without exposing key material; no manual restart is required. mcporter daemon status shows the current or last redacted decision with route, policy, reason, safe logical upstream endpoint, and probe status/duration. Set MCPORTER_LOG_LEVEL=info to see the same structured decision during ordinary connection setup. Diagnostics distinguish unsupported v2 authentication, bad server proofs, unauthenticated server failures, replay, protocol, freshness, and sequence failures, disconnected extensions (an authenticated 503), timeouts, network errors, protected handoff failures, and success. They never print keys, proofs, nonces, stable or ephemeral bearers, handoff paths, headers, pairing strings, or child arguments.
MCPorter also applies its Chrome DevTools auto-connect compatibility patch when relevant. Set MCPORTER_DISABLE_CHROME_DEVTOOLS_COMPAT=1 to disable that separate behavior.
Logging and shutdown
Use mcporter daemon start --log or mcporter daemon restart --log to keep a daemon log. --log-file chooses the destination, and --log-servers <csv> limits per-call traces. The logging guide covers environment and per-server controls.
The daemon closes managed transports on stop. A top-level daemonIdleTimeoutMs can shut down an inactive daemon, while a keep-alive lifecycle object can set an idle timeout for one server. See configuration for both forms.
File: docs/emit-ts.md
summary: 'How to generate .d.ts files or typed client helpers with mcporter emit-ts.'
read_when:
- 'Adding new emit-ts behavior or troubleshooting generated clients'
`mcporter emit-ts`
mcporter emit-ts turns a configured MCP server into TypeScript artifacts so
agents, tests, and tooling can consume the server through strongly typed APIs.
It reuses the same buildToolDoc() data that powers mcporter list, so doc
comments, parameter hints, and signatures stay perfectly in sync. For a broader
overview of every CLI command, see docs/cli-reference.md.
mcporter emit-ts <server> --out linear-client.ts \
[--mode types|client] \
[--include-optional]--mode types(default) emits a.d.tsinterface (LinearTools) with
docblocks + promisified signatures. Missing output schemas fall back toCallResult.--mode clientemits both the interface (auto-derived.d.ts) and an
executable.tshelper that wrapscreateServerProxy. Each method returns aCallResult, and the factory exposes aclose()helper for runtimes the client
creates.--include-optionalmirrorsmcporter list --all-parameters, ensuring every
parameter is shown even when optional.
Outputs overwrite existing files automatically so you can regenerate artifacts
whenever the server schema changes.
Examples
1. Types-only header
mcporter emit-ts linear --out types/linear-tools.d.tsProduces:
import type { CallResult } from 'mcporter';
export interface LinearTools {
/**
* List comments for a specific Linear issue.
*
* @param issueId The issue ID
*/
list_comments(params: { issueId: string }): Promise<CallResult>;
}Include the file in your agent/project and you can type-check code likeconst result = await proxy.list_comments({ issueId: '...' });.
2. Client wrappers
mcporter emit-ts linear --mode client --out clients/linear.tsGenerates two files:
clients/linear.d.tsโ same interface as the types-only mode.clients/linear.tsโ importscreateRuntime,createServerProxy, andcreateCallResult, then exposes acreateLinearClient()factory:
const client = await createLinearClient({ configPath: './mcporter.json' });
const comments = await client.list_comments({ issueId: 'LIN-1234' });
console.log(comments.text());
await client.close();If you pass an existing runtime ({ runtime }), the factory reuses it; the
returned objectโs close() becomes a no-op.
Flags
| Flag | Description |
| -------------------- | ------------------------------------------------------------------------------------------ | ---------------------------------- |
| --out | Required. .d.ts target for types, .ts target for client. |
| --mode types | client | Output kind (defaults to types). |
| --types-out | Optional override for the .d.ts file when --mode client. Default: derive from --out. |
| --include-optional | Include every parameter (not just the minimum 5 + required). |
| --json | Emit a JSON summary describing the emitted file(s) instead of plain-text logs. |
Testing
tests/emit-ts.test.ts covers:
- Template rendering (doc comments,
Promise<โฆ>return types, proxy wrappers). - End-to-end CLI invocation with a stub runtime, ensuring both
.tsand.d.ts
files are written successfully.
2. Official Technical Reference & Guides (openclaw/docs)
File: README.md
openclaw-docs
Mirror repo for the published OpenClaw docs site.
Source of truth lives in openclaw/openclaw, under docs/.
How it works
- English docs are authored in
openclaw/openclaw. openclaw/openclaw/.github/workflows/docs-sync-publish.ymlmirrors the docs tree into this repo.- This repo stores the published docs tree plus generated locale output.
openclaw/docs/.github/workflows/translate-incremental.ymldebounces normal docs changes, whiletranslate-all.ymlhandles full reconciliation for glossary changes, weekly schedule, release dispatch, or manual dispatch..github/workflows/r2-pages.ymlbuilds the full unpruned static site and uploads changed objects to Cloudflare R2..github/workflows/pages.ymldeploys the small Cloudflare Worker router that preserves clean URLs and markdown negotiation while reading docs from R2.
Translation behavior
- Locale pages under
docs/<locale>/**are generated output. - Each translated page stores
x-i18n.source_hash. - The translate workflow computes a pending file list before calling the model.
- If no English source hashes changed, the workflow skips the expensive translation step entirely.
- If files changed, only the pending files are translated.
- The workflow retries transient model-format failures.
- Locale outputs are uploaded as artifacts first, then committed together by the finalizer.
- Incremental and full translation use separate concurrency lanes, so small docs edits do not cancel weekly or glossary-triggered full reconciliation.
- The weekly scheduled run uses full reconciliation mode to repair missed or flaky locale updates.
Editing rules
- Do not treat this repo as the primary place for English doc edits.
- Make English doc changes in
openclaw/openclaw, then let sync copy them here. - Locale pages under
docs/<locale>/**are generated output. .openclaw-sync/source.jsonrecords whichopenclaw/openclawcommit this mirror was synced from.
Static site build
npm run docs:buildrenders the mirrored Mintlify-flavored docs intodist/docs-site.npm run docs:build:cloudflareis the legacy Worker Static Assets fallback build.npm run docs:build:r2renders the full unpruned site and preparesdist/docs-r2-manifest.jsonfor R2 upload.npm run docs:r2:uploaduploads only changed R2 objects, reports cache hits/misses, and refuses to turn a broken remote manifest read into a full-tree reupload.- Manual R2 refreshes audit objects before upload; unchanged objects remain cache hits, and transient HEAD failures fall back to the signed manifest.
R2_UPLOAD_PUT_ALL=1is the emergency escape hatch for intentionally rewriting every object. npm run docs:smokechecks representative English and locale pages plus the Pagefind search bundle.npm run docs:checkruns both steps.- The generated site includes the language picker and static full-text search via Pagefind.
- Cloudflare deploys
workers/docs-router.ts, which serves slashless page URLs, English markdown responses for.mdpaths orAccept: text/markdown, and/api/searchthrough theDOCS_BUCKETR2 binding. - Cloudflare hosting details and limitations are documented in
CLOUDFLARE.md.
Secrets
OPENCLAW_DOCS_SYNC_TOKENlives inopenclaw/openclawand lets the source repo push into this repo.OPENCLAW_DOCS_I18N_OPENAI_API_KEYlives in this repo and powers locale translation refreshes.CLOUDFLARE_API_TOKENlives in this repo and deploys thedocs.openclaw.airouter.- R2 uploads verify
CLOUDFLARE_API_TOKEN, try temporary R2 credentials, and normally fall back to the token-derived direct S3 credential form.OPENCLAW_R2_ACCESS_KEY_ID/OPENCLAW_R2_SECRET_ACCESS_KEYare only fallback upload credentials when the Cloudflare token cannot be verified.
File: docs/index.md
summary: "OpenClaw is a multi-channel gateway for AI agents that runs on any OS."
read_when:
- Introducing OpenClaw to newcomers
title: "OpenClaw"
OpenClaw ๐ฆ
"EXFOLIATE! EXFOLIATE!" โ A space lobster, probably
Browse docs
Mobile browsers may show the section menu without the full desktop tab bar. Use
these hub links to reach the same top-level docs areas from the page body.
What is OpenClaw?
OpenClaw is a self-hosted gateway that connects your favorite chat apps โ Discord, Google Chat, iMessage, Matrix, Microsoft Teams, Signal, Slack, Telegram, WhatsApp, Zalo, and more via channel plugins โ to AI coding agents. You run a single Gateway process on your own machine (or a server), and it becomes the bridge between your messaging apps and an always-available AI assistant.
Who is it for? Developers and power users who want a personal AI assistant they can message from anywhere โ without giving up control of their data or relying on a hosted service.
What makes it different?
- Self-hosted: runs on your hardware, your rules
- Multi-channel: one Gateway serves every configured channel plugin simultaneously
- Agent-native: built for coding agents with tool use, sessions, memory, and multi-agent routing
- Open source: MIT licensed, community-driven
What do you need? Node 26 (recommended), or another supported release: Node 22.22.3+, Node 24.15+, or Node 25.9+. You also need an API key from your chosen provider and 5 minutes. For best quality and security, use the strongest latest-generation model available.
How it works
flowchart LR
A["Chat apps + plugins"] --> B["Gateway"]
B --> C["OpenClaw agent"]
B --> D["CLI"]
B --> E["Web Control UI"]
B --> F["macOS app"]
B --> G["iOS and Android nodes"]The Gateway is the single source of truth for sessions, routing, and channel connections.
Key capabilities
Quick start
```bash
npm install openclaw@latest --allow-scripts=openclaw
```
On npm 11.15 and earlier, omit `--allow-scripts=openclaw`.```bash
openclaw dashboard
```
Or connect a channel ([Telegram](/channels/telegram) is fastest) and chat from your phone.Need the full install and dev setup? See Getting Started.
Dashboard
Open the browser Control UI after the Gateway starts.
- Local default: http://127.0.0.1:18789/
- Remote access: Web surfaces and Tailscale
Configuration (optional)
Config lives at ~/.openclaw/openclaw.json.
- If you do nothing, OpenClaw uses the bundled OpenClaw agent runtime; DMs share the agent's main session, and each group chat gets its own session.
- If you want to lock it down, start with
channels.whatsapp.allowFromand (for groups) mention rules.
Example:
{
channels: {
whatsapp: {
allowFrom: ["+15555550123"],
groups: { "*": { requireMention: true } },
},
},
messages: { groupChat: { mentionPatterns: ["@openclaw"] } },
}