Skip to content

Agent integration

Kevin Straub edited this page Aug 2, 2026 · 7 revisions

An agent can reach a hundred tools while carrying one line per server in its context. That line still has to get in there somehow.

The prompt block

mduct index
MCP tools via `mduct` CLI (list+args: mduct tools <server>; call: mduct call <server> <tool> key=value key:=<json>):
  gitlab       — GitLab: MRs, pipelines, issues, repos
  notes        — shared notes
CLI tools via `mduct` CLI (run: mduct run <tool> [args…]):
  kubectl      — read-only cluster access

A server with few enough tools carries its signatures here too, so the agent can see search(query, limit?) instead of only learning that a server called notes exists. Past 25 tools it collapses to a count and a pointer. The signatures come from a cache the daemon fills as it is used; mduct index --refresh fills it up front on a fresh machine.

Paste it into a system prompt, a CLAUDE.md, an AGENTS.md, whatever your harness reads. Each line is that server's note field. Write those notes for someone who has to pick a tool from this list and nothing else.

Claude Code

mduct hook install claude          # --remove to undo

Two hooks land in ~/.claude/settings.json:

SessionStart prints mduct index, so the block is there without you maintaining it. It also warns when a server is attached directly to Claude while mduct serves it too, since you would be paying for those schemas anyway.

PreToolUse matches mcp__* and answers with the replacement command, mduct call <server> <tool> key=value. Only for servers mduct actually serves. Everything else passes through untouched.

Other people's hooks in that file are left alone. Re-running the install replaces mduct's own entries and nothing else.

The installer writes the path of whichever binary you ran it with. Install first, then run mduct hook install claude from the installed one. Running it from a source checkout pins your hooks to your working tree, which is rarely what you meant.

Codex

mduct hook install codex           # --remove to undo

Codex keeps everything in ~/.codex/config.toml, hooks and MCP servers in one file, so this install is the simpler of the two. The same two hooks land there, plus [mcp_servers.mduct] when a server declares mcpCatalog.

Everything mduct writes sits between two comment markers:

# >>> mduct managed — do not edit inside this block >>>
[[hooks.SessionStart]]

# <<< mduct managed <<<

Re-running the install replaces that block and touches nothing outside it. If your config already defines [hooks] or [mcp_servers.mduct] somewhere else, the install refuses instead of writing a file TOML would reject for duplicate tables — move yours, then run it again.

The hook contract is the same as Claude's, which is why one implementation serves both. The only real difference is the name of the shell tool: Codex says shell_command or exec_command where Claude says Bash, and it passes argv instead of a string. Shadow rules are written against the command line, so a rule written once fires in both.

Everything else

There is no plugin API and nothing needs one. Any agent that can run a shell command can use mduct:

  1. Put mduct index output in the system prompt.
  2. Let the agent run mduct tools <server> when it needs the tool list, and mduct schema <server> <tool> when it needs the fields.
  3. Calls are mduct call <server> <tool> key=value.

Three commands. If your harness has a bash tool, it already supports mduct.

Teaching the pipe habit

An agent that runs mduct call srv list_things gets the whole list in its context. Run the same thing with --json | jq -c 'map({id,title})' and it gets what it asked for. That difference is real money, so the index block mentions it and mduct help carries worked examples.

warnAbove is the backstop. An oversized result prints a warning with a ready-made jq projection instead of quietly costing 40k characters.

mduct config warnAbove 25000

The tool namespace

A prompt block is prose. Tool selection happens in the tool namespace, and nothing written into a prompt reaches it. mduct mcp is a catalogue for exactly that: an MCP server whose tools/list mirrors the real tools so their names sit where an agent looks, with each description carrying the shell command to run. It never executes — calls stay in the shell, because the pipe is most of the value.

"kb": { "command": "", "mcpCatalog": true }
mduct hook install claude      # writes the hooks AND registers the catalogue
mduct hook install codex       # the same, into one TOML

Under Claude, hooks go to settings.json and MCP servers to .claude.json. The install handles both; --remove undoes both. Codex needs only the one file.

If a server declares mcpCatalog and the server is not registered, session start says so rather than leaving you with a catalogue that silently is not there.

It reloads while running: the server watches the config and its tool cache and announces a changed catalogue, so flipping mcpCatalog or calling a server for the first time updates a live session. A rewrite that produces the same catalogue stays silent.

Mirror the servers no request ever names. "Look at the GitLab MR" pulls GitLab in by itself; nobody asks for a code index by name, so that is the one that keeps losing to grep. Cost keeps the list short: an entry is roughly six times its prose line, and a 189-tool server would be 29 kB in every context.

Isolating an agent

Give it a profile and it gets its own config, secrets, OAuth tokens and daemon:

MDUCT_PROFILE=research mduct add notes --url https://mcp.example.com/mcp
MDUCT_PROFILE=research mduct call notes search q=neutrinos

One agent cannot see or use another's servers or credentials. Add a guard per server and the blast radius of a confused agent stops depending on its own good judgement.

Nudging it toward the better tool

If a server keeps losing to a habit (an index server to grep, an API to curl), it can say so at the moment of the call. See Shadowing.

Clone this wiki locally