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.
Install
Section titled “Install”curl -fsSL https://mcp.deemwar.com/install.sh | sh # macOS, Linux: ~/.local/bin/dmcp, sha256-checked, no sudoWindows 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_DIRoverrides it). It is protected by your user profile’s permissions; Unix file modes do not apply on Windows. dmcp terminal setupinstallsklark.exeorherdr.exenext todmcp.exe. dmcp never runs PowerShell.- To keep
dmcp terminal serve <agent> --publishrunning, add it to Task Scheduler (“At log on”) or put a shortcut to it inshell: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>/.
dmcp login # opens your browser: sign in to the console, click Allowdmcp terminal setup # installs herdr (or klark) if you have neitherdmcp terminal serve ceo # local onlydmcp terminal serve ceo --publish # public: https://<tenant>-terminal.deemwar.com/mcpdmcp terminal connect # what to paste into ChatGPT, Grok, Claude, Codex, CursorFirst 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:
dmcp terminal setup # installs herdr if neither herdr nor klark is there (--mux klark for klark), then signs you indmcp doctor # ✓ or ✗ for sign-in, tenant, herdr/klark (and its version), the port and the control plane, each with its fixDownloads 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:
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_TOKENThis is how a Claude Code session talks to a Codex agent, and the other way round.
Public
Section titled “Public”--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
401and aWWW-Authenticatepointer. Your project’s own authorization server athttps://<tenant>-mcp.deemwar.comhandles dynamic client registration, PKCE, and the Claude and ChatGPT callbacks. Tokens are bound tohttps://<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 servestops (Ctrl-C, the terminal closes, a crash), the URL answers that the terminal is offline until it reconnects.dmcp unpublishends 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/mcpto 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).
Commands
Section titled “Commands”| 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).
Signing in
Section titled “Signing in”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.
Calling the API with your session
Section titled “Calling the API with your session”After dmcp login you hold a console session (dms_…, valid 30 days; dmcp logout ends it) that works on the control-plane API:
- Base URL:
https://mcpconsoleapi.deemwar.com - Header:
Authorization: Bearer <token> - Reference: https://mcpconsoleapi.deemwar.com/docs
T=$(dmcp token 2>/dev/null) # stdout is the token alone; the warning goes to stderrcurl -H "Authorization: Bearer $T" https://mcpconsoleapi.deemwar.com/v1/medmcp 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.
Tenants and profiles
Section titled “Tenants and profiles”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.
Skill and plugin
Section titled “Skill and plugin”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.
Control plane API
Section titled “Control plane API”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.
Author and publish an API
Section titled “Author and publish an API”The same steps as the console wizard, from the terminal:
dmcp logindmcp 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 filedmcp auth set --from auth.json # login URL, JWKS, issuer, audience, sign-in methods, rolesecho "$TEST_PASSWORD" | dmcp tools test --user cy --password-stdindmcp tools list # dmcp tools enable|disable <op>...dmcp docs add https://api.acme.com/docs --wait # publishing needs the docs wikidmcp publish # https://<tenant>-mcp.deemwar.com/mcpdmcp usage # pointsdmcp cli # install lines for your branded CLIdmcp widget --origins https://app.acme.com # the script tagauth.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.
Servers
Section titled “Servers”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:
dmcp server new "Acme Tasks" --url https://api.acme.com # create it; it becomes the defaultdmcp server use "Acme Tasks" # switch the defaultdmcp server list # * marks the defaultdmcp server status # state, URL, tool count, what to do nextdmcp server show # the same plus every tooldmcp 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 livedmcp server add docs ./handbook https://acme.com/faq # documents -> a cited wiki; with no API it's a knowledge serverdmcp server test --user cy --password-stdindmcp server publish # live at https://<server>-mcp.deemwar.com/mcpdmcp server unpublish # offline within a minute; everything kept, publish brings it backdmcp server remove # asks for the name (or --yes); the URL is never reused--server <name> acts on another server for one command.
Knowledge servers
Section titled “Knowledge servers”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.
Database servers (Postgres)
Section titled “Database servers (Postgres)”dmcp server new "Orders DB"dmcp server add system postgres --dsn-env ORDERS_DB_URL --schema public # or --dsn-stdin, or --from-sec ORDERS_DBdmcp server previewdmcp server publish- The connection string is a secret. dmcp reads it from an env var, stdin or your
secvault, 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-caorverify-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 ONLYtransaction 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_tableandquery. The data model also goes into the docs wiki, soask_docscan explain it. - Network: Deemwar connects only to public addresses. Allow-list our servers’ outbound IPs,
78.46.65.254and195.201.109.89. (65.109.42.99is our inbound load balancer; it doesn’t connect to your database.)
Data servers (S3)
Section titled “Data servers (S3)”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_SKdmcp server add data s3 open-data-bucket --public # a public bucket, no keysWorks 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.
Who sees which tables and files
Section titled “Who sees which tables and files”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.*ortable:*prefix:exports/public/orprefix:**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.
A server is more than its spec
Section titled “A server is more than its spec”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.
Where dmcp keeps things
Section titled “Where dmcp keeps things”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 sessiondmcp log,dmcp describeanddmcp statususe when you don’t pass--agent. It is the one you ran last; switch withdmcp session use <agent>, list them withdmcp 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.