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 bypasses the oversized-list guard, on the theory that you are clearly already slimming it.

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 too, or you just moved the wall of text one line down.

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 no matter how the caller phrases it:

"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 wrapping travel with the config, so the tool behaves the same on your 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 across projects within one 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

Every error message names the next action (unknown tool "x" — see: mduct tools srv).

Clone this wiki locally