TypeScript
Wrap a CLI as an MCP tool when there is no API
The thing you want an agent to drive only has a CLI. execFile it, hand back stdout, and put bounds on the call so it cannot eat your process.
25 Jun 2026
No HTTP API, just a binary over SSH. Promisify execFile, set the limits up front, return the text.
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const run = promisify(execFile);
interface SshTarget {
keyPath: string;
user: string;
host: string;
}
export async function dokku(t: SshTarget, args: string[]): Promise<string> {
const { stdout } = await run(
"ssh",
[
"-i",
t.keyPath,
"-o",
"StrictHostKeyChecking=accept-new",
`${t.user}@${t.host}`,
"dokku",
...args,
],
{ maxBuffer: 5 * 1024 * 1024, timeout: 60_000 },
);
return stdout;
}
// Want structured data out of a REPL? Make the REPL emit JSON.
export async function mongoJson(uri: string, expr: string): Promise<unknown> {
const { stdout } = await run(
"mongosh",
[uri, "--quiet", "--eval", `JSON.stringify(${expr})`],
{ maxBuffer: 5 * 1024 * 1024, timeout: 60_000 },
);
return JSON.parse(stdout);
}Gotchas
Set maxBuffer and timeout or large output and hung connections will bite you. The default buffer is small, so a chatty command throws ENOBUFS mid-stream and you lose the output you wanted; without a timeout a stalled SSH session hangs the tool call forever. For a REPL like mongosh, --quiet plus JSON.stringify(...) is what turns a human-shaped dump into something JSON.parse can read.
- Giving agents real handsA fleet of thirteen small servers that give agents real hands. What they wrap, the two house styles I build them in, and why they all start read-only.Musing
- mcp-dokkuAn MCP server that drives a Dokku PaaS over SSH.Tool
- Small tool surfaces beat fat APIsA marketing API exposes 115 operations; my server hands the agent six tools. The boundary is set by token budget and model focus, not REST purity.Musing
- Guardrails for agents in productionA catalogue of the guards I actually ship: typed confirmations, blast-radius escalation, pay-to-play gating, ordered workflows, and read-only by default.Musing
- mcp-search-consoleAn MCP server for Google Search Console properties, sitemaps, and analytics.Tool