Skip to content

dmcp: the Deemwar MCP CLI

dmcp is the Deemwar MCP CLI. It does two jobs: author and publish an API as MCP, and expose a herdr agent. Its herdr command takes ONE live herdr coding agent and turns it into an MCP server. Claude.ai, Claude mobile (voice too), ChatGPT, Codex, Claude Code and other agents can then ask it questions.

From install to working takes four steps: install, dmcp login, dmcp terminal serve <name> --publish, paste the URL. See Terminals for the product overview and pricing.

Terminal window
curl -fsSL https://mcp.deemwar.com/install.sh | sh # macOS, Linux: ~/.local/bin/dmcp, sha256-checked, no sudo

Windows 10/11: download install.cmd and double-click it (or run it from cmd.exe). It uses only built-in tools (curl.exe, certutil, reg; no PowerShell), installs %LOCALAPPDATA%\dmcp\bin\dmcp.exe, checks its SHA-256, adds the folder to your user PATH (no admin) and waits for a key before closing. Then open a new terminal. Windows notes:

  • Config lives in %USERPROFILE%\.config\dmcp (the same layout as macOS/Linux; DMCP_CONFIG_DIR overrides it). It is protected by your user profile’s permissions; Unix file modes do not apply on Windows.
  • dmcp terminal setup installs klark.exe or herdr.exe next to dmcp.exe. dmcp never runs PowerShell.
  • To keep dmcp terminal serve <agent> --publish running, add it to Task Scheduler (“At log on”) or put a shortcut to it in shell:startup. Ctrl-C stops it.
  • Alpha: the Windows build is tested in CI (unit tests, version, doctor, setup downloads, install.cmd); a live publish from Windows has not been tried yet.

The script detects your OS and CPU, verifies the binary against the release’s checksums.txt, and is safe to run again. dmcp version shows your version and the latest release; dmcp update installs the latest (checksum-verified, replaced in place; restart a running serve to pick it up). Releases live at https://mcp.deemwar.com/dl/dmcp/<version>/.

Terminal window
dmcp login # opens your browser: sign in to the console, click Allow
dmcp terminal setup # installs herdr (or klark) if you have neither
dmcp terminal serve ceo # local only
dmcp terminal serve ceo --publish # public: https://<tenant>-terminal.deemwar.com/mcp
dmcp terminal connect # what to paste into ChatGPT, Grok, Claude, Codex, Cursor

First publish needs no setup. If your tenant has no MCP project yet, the first --publish creates a “Terminal sign-in” project for you: callers sign in with your Deemwar account (email and console password), and only your tenant’s admin email (plus Deemwar super admins) is accepted.

Alpha limits. One publishing machine per tenant (the newest one gets the URL); single person (only the tenant admin signs in); our relay sees questions, answers and pane text in plaintext (TLS on the wire, not end-to-end encrypted); a question in flight during a relay restart has to be asked again.

Tool What it does
ask(question) Types the question into the agent’s terminal, waits, and returns only the new answer as plain text: no terminal chrome, no colours, so it reads well aloud. It is refused, and nothing is sent, if the agent is busy or blocked waiting for its owner.
get_answer() Hosted clients give up after about 60 seconds. If the agent is still working at about 50 seconds, ask says so, and get_answer returns the answer once the agent is idle.

Every agent describes itself. When dmcp serve starts, dmcp asks the agent through herdr who it is and what it can answer, in the first person, removes anything that looks like a secret, email or file path, and uses that as the server instructions and the ask description. It asks again every hour (--refresh), so the description follows what the agent is doing. To use your own wording instead: dmcp describe --agent ceo "..."; to go back: dmcp describe --agent ceo --auto.

dmcp serve --all (local only) serves every live agent instead, adding list_agents; then ask and get_answer take an agent argument.

Install dmcp, then run one command:

Terminal window
dmcp terminal setup # installs herdr if neither herdr nor klark is there (--mux klark for klark), then signs you in
dmcp doctor # ✓ or ✗ for sign-in, tenant, herdr/klark (and its version), the port and the control plane, each with its fix

Downloads are the official release binaries, checked against the publisher’s SHA-256 before they’re made executable. They go into ~/.local/bin (or ~/.config/dmcp/bin when that isn’t on your PATH; dmcp says so). dmcp never uses sudo and never overwrites a binary you already have. dmcp serve offers the same install when no multiplexer is found (--yes to skip the question).

Then start your agent inside herdr (run herdr, then claude or codex in a pane), check dmcp terminal agent list, and run dmcp terminal serve <name> --publish.

Every terminal command lives under dmcp terminal (dmcp terminal --help): setup, doctor, agent list, serve, connect, status, log, describe, sessions, unpublish. The older top-level forms (dmcp serve --agent <name>, dmcp connect, …) keep working.

dmcp terminal serve <name> (alias: dmcp serve --agent <name>) listens on http://127.0.0.1:8765/mcp. It accepts a bearer token that is created once and stays the same across restarts. It prints ready-to-paste lines:

Terminal window
claude mcp add --transport http ceo http://127.0.0.1:8765/mcp --header "Authorization: Bearer <token>"
export DMCP_TOKEN=<token> && codex mcp add ceo --url http://127.0.0.1:8765/mcp --bearer-token-env-var DMCP_TOKEN

This is how a Claude Code session talks to a Codex agent, and the other way round.

--publish asks the control plane for your hostname, https://<tenant>-terminal.deemwar.com/mcp. There is one per tenant and it can expose several agents (choose them in the console Terminals page or with dmcp terminal expose / hide <agent>). dmcp dials out to the Deemwar relay with one outbound connection and reconnects by itself: no open ports, no tunnel, no Cloudflare account. (--via cloudflare still uses a Cloudflare Tunnel as a fallback and fetches cloudflared only then.)

  • Sign-in is built in. The endpoint serves OAuth protected-resource metadata (RFC 9728) and answers unauthenticated calls with 401 and a WWW-Authenticate pointer. Your project’s own authorization server at https://<tenant>-mcp.deemwar.com handles dynamic client registration, PKCE, and the Claude and ChatGPT callbacks. Tokens are bound to https://<tenant>-terminal.deemwar.com/mcp, so they don’t work on your API’s MCP endpoint, and the reverse holds too. Only people who can sign in to your project get through. A request without a valid token never reaches the agent.
  • Stable URL. Your tenant always gets the same URL, and sign-ins stay valid (also when you publish a different agent: it is the same tenant’s people). After a restart or a laptop sleep, nobody has to paste the URL again.
  • One publishing machine per tenant (alpha). MCP calls go to the machine that published most recently; agents on other machines are not listed.
  • Live while serve runs. When dmcp terminal serve stops (Ctrl-C, the terminal closes, a crash), the URL answers that the terminal is offline until it reconnects. dmcp unpublish ends it at once.

Paste the URL:

  • Claude.ai / Claude mobile: Settings > Connectors > Add custom connector.
  • ChatGPT: Settings > Connectors > Create (developer mode), authentication OAuth.
  • Claude Code: claude mcp add --transport http ceo https://<tenant>-terminal.deemwar.com/mcp, then /mcp to sign in.
  • Codex: codex mcp add ceo --url https://<tenant>-terminal.deemwar.com/mcp && codex mcp login ceo.

Publishing signs people in through your MCP project, so the tenant needs at least one project with sign-in set up (Sign-in methods).

Command
dmcp login Browser sign-in (below). --key-stdin reads a tenant API key instead, for fully non-interactive machines.
dmcp tenants Tenants you can work in: all of them for a super admin, your own otherwise.
dmcp tenant [use <slug>] Show or switch the active tenant (below).
dmcp profile [list | use <name>] Keep several logins side by side.
dmcp token [--json] Print the current bearer token for scripts (warning on stderr). Same as dmcp login --token.
dmcp serve --agent <name> [--publish] [--port N] [--refresh 1h] [--describe "..."] Serve one agent. --agent is required (or --all, local only).
dmcp describe --agent <name> "<summary>" Set what the agent works on, live.
dmcp status Sign-in, the running server, and the live public exposure.
dmcp log [-n 20] Every question: when, who, which agent, the outcome.
dmcp unpublish Stop the public herdr exposure now.
dmcp projects, api add, auth set, tools, docs, publish, usage, cli, widget Author and publish an API.

Every command takes --json, --tenant <slug> (this command only) and --profile <name> (or $DMCP_PROFILE).

dmcp login works like gh auth login. It starts a listener on 127.0.0.1 on a random port and opens the console at https://mcpconsole.deemwar.com/cli-login. It also prints that URL, in case the browser doesn’t open:

Opening your browser… If it didn't open, visit:
https://mcpconsole.deemwar.com/cli-login?port=…&state=…&code_challenge=…
Or paste the code shown in the browser:

Sign in there the usual way (password or email link). The console asks Authorize dmcp on this computer?. Click Allow and the console hands a code to dmcp’s local listener, and the tab says you can close it. On SSH or another machine the callback can’t reach dmcp, so open the URL anywhere and paste the code the console shows at the prompt. Whichever arrives first wins.

The code works once, expires after 60 seconds, and is bound to dmcp’s PKCE (S256) challenge, so a code seen by someone else is useless without the verifier that never leaves your machine. The console only ever redirects to http://127.0.0.1:<port>/callback, and dmcp checks the state it sent.

After dmcp login you hold a console session (dms_…, valid 30 days; dmcp logout ends it) that works on the control-plane API:

Terminal window
T=$(dmcp token 2>/dev/null) # stdout is the token alone; the warning goes to stderr
curl -H "Authorization: Bearer $T" https://mcpconsoleapi.deemwar.com/v1/me
dmcp token --json # {"token", "expires_at", "api_base_url"}

The token acts as you until it expires. Keep it out of logs, shell history and chat.

dmcp status shows who is signed in and the active tenant. A super admin can work in any tenant: dmcp tenants lists them all, and dmcp tenant use <slug> makes one active. From then on every call sends X-Act-As-Tenant, and the control plane audits every change made that way. --tenant <slug> does the same for one command without changing the saved choice. Anyone else sees only their own tenant, and switching to another one fails with a clear error.

A profile is a separate login with its own config. dmcp profile use work switches the default, --profile work (or DMCP_PROFILE=work) picks one for a single command, and dmcp profile list shows each profile and who is signed in to it. The default profile is the config you already have.

apps/herdr-mcp/skill/SKILL.md (Claude Code plugin in apps/herdr-mcp/plugin) teaches an agent to expose itself. It summarises first: the live agents, which one, local or public, the URL, and who can sign in. Then it runs dmcp serve --agent <name> and hands out the URL with the lines to paste. It publishes only when you asked for public access.

POST /v1/tunnels {agent, port} creates the exposure, GET /v1/tunnels returns the live one, and DELETE /v1/tunnels[?agent=] removes it. All three use tenant auth. See Control plane API.

The same steps as the console wizard, from the terminal:

Terminal window
dmcp login
dmcp projects create "Acme Tasks" --url https://api.acme.com --purpose "Projects and tasks"
dmcp api add https://api.acme.com/openapi.json # or a local file
dmcp auth set --from auth.json # login URL, JWKS, issuer, audience, sign-in methods, roles
echo "$TEST_PASSWORD" | dmcp tools test --user cy --password-stdin
dmcp tools list # dmcp tools enable|disable <op>...
dmcp docs add https://api.acme.com/docs --wait # publishing needs the docs wiki
dmcp publish # https://<tenant>-mcp.deemwar.com/mcp
dmcp usage # points
dmcp cli # install lines for your branded CLI
dmcp widget --origins https://app.acme.com # the script tag

auth.json holds {"auth": {...}, "roles": {...}} in the shape of the draft config (see Sign-in methods and Roles). Use --project <id|name> (or --server <name>) on any command to act on a project other than the selected one.

Each published project is an MCP server: its own URL and its own sign-in for its users. dmcp server manages them, with a default you switch like a profile:

Terminal window
dmcp server new "Acme Tasks" --url https://api.acme.com # create it; it becomes the default
dmcp server use "Acme Tasks" # switch the default
dmcp server list # * marks the default
dmcp server status # state, URL, tool count, what to do next
dmcp server show # the same plus every tool
dmcp server add api https://api.acme.com/openapi.json --docs ./docs --code ./ # import and cross-check (see below)
dmcp server preview # what the AI will see, before anything is live
dmcp server add docs ./handbook https://acme.com/faq # documents -> a cited wiki; with no API it's a knowledge server
dmcp server test --user cy --password-stdin
dmcp server publish # live at https://<server>-mcp.deemwar.com/mcp
dmcp server unpublish # offline within a minute; everything kept, publish brings it back
dmcp server remove # asks for the name (or --yes); the URL is never reused

--server <name> acts on another server for one command.

dmcp server add docs <files|folder|URLs> turns documents (Markdown, text, HTML, PDF) into a cited wiki that assistants query with ask_docs. On a server with no API it makes a knowledge server: no upstream URL, no operations, just answers from your documents with citations. dmcp waits for the wiki to build, then runs the same understanding pass over the text files to say what the documents cover and what they don’t. dmcp server preview shows it, and dmcp server publish puts it live. On an API server, add docs adds to that API’s docs instead.

Terminal window
export ORDERS_DB_URL='postgres://mcp_ro:[email protected]:5432/app?sslmode=require'
dmcp server new "Orders DB"
dmcp server add system postgres --dsn-env ORDERS_DB_URL --schema public # or --dsn-stdin, or --from-sec ORDERS_DB
dmcp server preview
dmcp server publish
  • The connection string is a secret. dmcp reads it from an env var, stdin or your sec vault, never from the command line, and never prints it. The control plane seals it; only the data plane can open it, to run queries.
  • TLS is required: sslmode=require, verify-ca or verify-full.
  • Read-only: use a read-only role. dmcp checks the role when you add it and warns loudly if it can write. Every query runs in a READ ONLY transaction with a time limit (5 s) and a row limit (200), one statement at a time.
  • Never your data to the model: the description pass sees table and column names, types, comments, keys and row estimates, never row values.
  • Sensitive columns (emails, phones, passwords, tokens, card or tax numbers, addresses, birth dates…) are marked and left out of query results, unless you add --show-sensitive.
  • Tools: assistants get list_tables, describe_table and query. The data model also goes into the docs wiki, so ask_docs can explain it.
  • Network: Deemwar connects only to public addresses. Allow-list our servers’ outbound IPs, 78.46.65.254 and 195.201.109.89. (65.109.42.99 is our inbound load balancer; it doesn’t connect to your database.)
Terminal window
export EXP_AK=... EXP_SK=...
dmcp server new "Exports"
dmcp server add data s3 my-bucket/exports/ --keys-env EXP_AK,EXP_SK # AWS (add --region)
dmcp server add data s3 my-bucket/exports/ --endpoint https://<account>.r2.cloudflarestorage.com --region auto --from-sec R2_AK,R2_SK
dmcp server add data s3 open-data-bucket --public # a public bucket, no keys

Works with AWS S3 and S3-compatible stores (Cloudflare R2, Hetzner, MinIO). dmcp lists the bucket or prefix (file types, sizes, folders) and reads only the column names of each CSV, TSV, JSON Lines or JSON file, never the values. Documents (Markdown, text, HTML, PDF; up to 20) go to the docs wiki for ask_docs. The keys are sealed like every other secret; use read-only keys.

Assistants get list_files, read_file (the start of a text file, up to 256 KB) and read_table (rows of a CSV/TSV/JSON Lines file, filtered by column values, at most 200 rows). Everything stays inside the prefix. Sensitive columns are left out, can’t be filtered on, and data files that have them (or weren’t inventoried) can’t be read raw with read_file. Parquet files are listed but not read yet.

Database and S3 servers use the same roles as API operations (the role claim from your sign-in). Next to operation ids, a role can list:

  • table:public.orders, table:sales.* or table:*
  • prefix:exports/public/ or prefix:*
  • * for everything
{"roles": {"claim_path": "role", "default_role": "viewer",
"roles": {"viewer": ["table:public.orders", "prefix:exports/public/"], "analyst": ["table:*", "prefix:*"]}}}

Once any role has a table: (or prefix:) entry, the rules apply to every role. A role sees only its tables and files: others don’t appear in list_tables, describe_table or list_files, and query, read_file and read_table refuse them. A query’s tables are taken from Postgres’s own plan, so views and subqueries over a hidden table are refused too. Calls to your own SQL functions are refused while table rules apply, because a function can read tables the plan doesn’t show. Servers without these entries keep full access. Table and column names still appear in the data model that ask_docs reads.

With --docs (markdown files, a folder or a URL; repeatable) and --code (a folder or a git URL), dmcp sends the docs and the files that define your routes to one model pass. That pass cross-checks them against the spec and writes into the draft:

  • a description of the server, and what it is and is not (“Does not handle billing; cannot delete accounts”);
  • the order calls must happen in, and side effects and limits;
  • a better description for each operation that needs one;
  • flags where the spec and the docs or code disagree (a “delete” that only archives, an endpoint the docs never mention).

Annotations only ever get safer: an operation can be marked destructive (and then asks before running), never the reverse. dmcp server preview shows all of it, plus the SKILL.md this server will ship. Assistants also see the is / is-not list in the overview tool. It uses the project’s AI settings, or Deemwar’s model if none are set.

dmcp server publish and dmcp terminal serve end with a Connect it block for every client: Claude, ChatGPT and Grok (public URL only), plus Claude Code, Codex, Cursor, VS Code and opencode (Grok needs Client ID grok, an empty secret, the server’s /authorize and /token endpoints and Token Auth Method “none (PKCE)”). dmcp server connect and dmcp terminal connect print it again; --client <id> picks one, --json is for scripts. When output isn’t a terminal (a service log), the local token shows as <run: dmcp token --local>. Full guides: Connect.

Everything lives in ~/.config/dmcp (or $DMCP_CONFIG_DIR):

  • config.json: your sign-in and the active tenant.
  • sessions/<agent>/: one folder per agent you have served: its folder in herdr (home), its description, the question log and the tunnel log.
  • sessions/last: the session dmcp log, dmcp describe and dmcp status use when you don’t pass --agent. It is the one you ran last; switch with dmcp session use <agent>, list them with dmcp sessions.

Run dmcp serve inside a herdr pane without --agent and it serves that pane’s agent, and says so. When herdr restarts and the agent loses its name, dmcp finds it again by its folder and carries on.