Skip to content
Kevin Straub edited this page Aug 2, 2026 · 6 revisions

mduct — because it glues shit together

mduct demo

One CLI in front of any number of MCP servers and plain command-line tools, with none of their schemas in your model's context.

mduct call gitlab list_issues state=opened --json | jq '.[].title'

Start here

Commands Every command and flag, with the exit codes and the edges.
Cookbook Recipes you can paste: jq pipelines, batching, CI, read-only agents, a second instance.
Configuration Every field of servers.jsonc, with defaults and what happens when you get it wrong.
Arguments & output key=value, key:=json, --args, and what lands on stdout vs stderr.
Agent integration Claude Code and Codex hooks, the prompt block, wiring anything else.
Shadowing Telling an agent a better tool exists, at the moment it reaches for the worse one.
Troubleshooting When the daemon sulks.

The 60-second version

Everything with state lives in a background daemon: MCP connections, OAuth sessions, guards. The CLI is a thin client that talks to it over a unix socket and prints text. That's the whole architecture. It is also why a stdio server isn't respawned on every call and a token isn't re-negotiated.

flowchart LR
    A["your shell<br/>or your agent"] -->|unix socket| D
    D["daemon<br/>connections · tokens · guards"] --> S1["gitlab<br/><i>stdio</i>"]
    D --> S2["notes<br/><i>http + oauth</i>"]
    D --> S3["kubectl<br/><i>plain CLI</i>"]
Loading

Your config is one JSONC file. It has two sections that behave identically from the outside:

{
  "servers": { "gitlab": { "command": "npx", "args": ["-y", "@yoda.digital/gitlab-mcp-server"] } },
  "tools":   { "kubectl": { "run": "kubectl", "check": "kubectl version --client" } }
}
mduct call gitlab list_issues state=opened   # MCP tool
mduct run kubectl get pods                   # plain binary, with its stored env

An agent sees one list and one calling convention. Whether something speaks MCP is an implementation detail nobody upstream has to know about.

Conventions in these pages

  • srv stands in for a server name you configured; there is no built-in one.
  • Examples use gitlab, notes, codeindex, kubectl, playwright. Nothing here is a real endpoint. Substitute your own.
  • Anything on stdout is meant to be piped. If a command needs to talk to a human, it uses stderr.

Clone this wiki locally