Skip to content

Cookbook

Kevin Straub edited this page Aug 1, 2026 · 6 revisions

Recipes. Paste, adjust the names, move on.

Keep a big result out of your context

The whole point. A tool that returns 20 issues returns 20 full issues. You wanted three fields.

# ~13.7k chars → ~1.1k, measured
mduct call tracker list_issues limit=20 --json | jq -c '.issues|map({id,title,status})'

--json strips the prose some servers wrap around their payload ("Found 20 issues:"), which is what makes the pipe work at all. It also skips the oversized-list guard, since you are obviously already slimming the thing.

Don't know the shape? Look once, then project:

mduct call srv some_tool --json | jq 'if type=="array" then .[0] else . end | keys'

List, then fetch each

mduct call tracker list_issues state=open --json \
  | jq -r '.[].id' \
  | while read -r id; do
      mduct call tracker get_issue id="$id" --json | jq -c '{id, title, assignee}'
    done

Project the second call as well, or you have moved the wall of text one line down and gained nothing.

Two servers in one pipeline

# every open MR whose branch no longer exists
mduct call gitlab list_merge_requests project_id=grp/proj state=opened --json \
  | jq -r '.[].source_branch' \
  | while read -r b; do
      git ls-remote --exit-code --heads origin "$b" >/dev/null 2>&1 || echo "stale: $b"
    done

Feed a file or stdin as arguments

mduct call srv create_thing --args @payload.json
jq -n '{title:"from a pipe"}' | mduct call srv create_thing --args -

A read-only server

Guards live in the daemon, so this holds however the caller phrases the request:

"prod-db": {
  "url": "https://mcp.internal/db",
  "guard": { "allow": ["list_*", "get_*", "describe_*"] }
}

Deny wins over allow. "deny": ["delete_*", "drop_*"] is the other direction when a server is mostly fine.

A throwaway instance for a script or CI

export MDUCT_PROFILE=ci        # ~/.config/mduct-ci/, own socket, own secrets, own daemon
mduct add tracker --url https://mcp.example.com/mcp
mduct call tracker list_issues --json | jq length
mduct daemon --stop            # tidy up

Nothing is shared with your default instance. Not the config, not the tokens.

Pin a CLI tool so every machine runs the same one

"playwright": {
  "run": "bunx",
  "args": ["playwright@1.61.1"],
  "check": "bunx playwright@1.61.1 --version",
  "setup": "bunx playwright@1.61.1 install chromium",
  "note": "headless browser"
}
mduct tool status              # shows "↑ update 1.61.1 → 1.62.0" when one is out
mduct tool update playwright   # bumps the pin
mduct run playwright test e2e/

The env and the wrapping travel with the config, so the tool behaves the same on a laptop and on the build box.

Move an existing Claude setup over

mduct import              # what's attached in ~/.claude* and .mcp.json
mduct import gitlab       # copy one across; literal tokens land in the secret store
mduct doctor              # anything still attached directly AND served here — you want zero
claude mcp remove gitlab  # ...then drop the direct attachment, or you pay for both

Check a server before trusting it in a script

mduct servers                       # config + connection state
mduct tools srv | head -30          # names and signatures, no schemas
mduct schema srv the_tool           # the full schema, only when you need it
mduct call srv the_tool --raw       # the MCP envelope, if a result looks wrong

Batch a set of calls without re-handshaking

Sequential calls reuse the same warm connection, so a loop is cheap:

for p in api web worker; do
  mduct call gitlab list_pipelines project_id="grp/$p" --json | jq -c "{repo:\"$p\", last:.[0].status}"
done

The daemon holds one connection per server. Parallel calls to the same server are queued, so xargs -P buys you nothing there. Parallelise across servers, or ask for more per call if the server supports it.

Make failures loud in a script

set -euo pipefail
if ! out=$(mduct call srv risky_tool id=42 --json 2>err.txt); then
  echo "call failed: $(cat err.txt)" >&2   # errors go to stderr, exit code is nonzero
  exit 1
fi
echo "$out" | jq -e '.ok' >/dev/null

Error messages name the next action, e.g. unknown tool "x" — see: mduct tools srv.

Clone this wiki locally