Turn any MCP server into native bash commands for just-bash.
Published on npm: @intentface/just-mcp-to-cli
An agent types slack send-message --channel "#dev" --text "deployed" and it just works. No SDK. No MCP knowledge. Just bash.
npm install @intentface/just-mcp-to-cli just-bashjust-bash is a peer dependency, so install both packages together.
# npm
npm install @intentface/just-mcp-to-cli just-bash
# pnpm
pnpm add @intentface/just-mcp-to-cli just-bash
# yarn
yarn add @intentface/just-mcp-to-cli just-bash
# bun
bun add @intentface/just-mcp-to-cli just-bashimport { Bash } from "just-bash";
import { mcpCommand } from "@intentface/just-mcp-to-cli";
const bash = new Bash({
customCommands: [
await mcpCommand("slack", "https://mcp.slack.com/mcp"),
],
});
await bash.exec('slack send-message --channel "#general" --text "hello world"');mcpCommand() connects to an MCP server, discovers its tools, and registers them as a just-bash custom command. Each MCP tool becomes a subcommand with auto-generated CLI flags from the tool's JSON Schema.
mcpCommand("slack", url)
→ connects to MCP server
→ discovers tools via tools/list
→ returns a just-bash Command
bash.exec("slack send-message --channel '#dev' --text 'hi'")
→ parses subcommand + flags
→ calls tools/call on the MCP server
→ formats result to stdout
Every registered server and tool gets auto-generated --help:
# List all tools on a server
slack --help
# slack — MCP tools
#
# Available commands:
# send_message Send a message to a channel
# list_channels List available channels
#
# Run `slack <command> --help` for details on each command.
# Tool-specific help with full type info
slack send_message --help
# Usage: slack send_message [options]
#
# Send a message to a channel
#
# Required:
# --channel <string> Channel name or ID
# --text <string> Message content [maxLength: 4000]
#
# Optional:
# --thread-ts <string> Reply to a specific thread
# --unfurl-links Unfurl links in the message
#
# Flags:
# --help Show this help message
# --json Read arguments from stdin as JSON
# --raw Output raw MCP result
# --verbose Show request/response detailsHelp output includes type hints (<string>, <number>, <string[]>), enum values, defaults, format hints (<email>, <date-time>), and constraints (min, max, pattern, etc.).
Object parameters can be passed two ways:
# Dot-notation flags
linear create-issue --title "Bug" --assignee.id "user-123" --assignee.role "owner"
# Or as a JSON string
linear create-issue --title "Bug" --assignee '{"id":"user-123","role":"owner"}'Help output shows nested properties inline:
Optional:
--assignee <json> Assignee details
Pass as JSON string, or use dot-notation flags:
--assignee.id <uuid> User ID (required)
--assignee.role <string> Team role [one of: owner, reviewer, member]
Output goes to stdout, so piping works naturally:
# Pipe between MCP servers
github list-issues --repo "acme/app" --state open \
| jq '.[] | .title' \
| xargs -I{} slack send-message --channel "#bugs" --text "Open: {}"
# Read args from stdin as JSON
echo '{"channel":"#general","text":"hi"}' | slack send-message --json
# Output to file
linear list-issues --team ENG > /tmp/issues.jsonimport { Bash } from "just-bash";
import { mcpCommands } from "@intentface/just-mcp-to-cli";
const commands = await mcpCommands({
slack: "https://mcp.slack.com/mcp",
gmail: {
url: "https://gmail.mcp.claude.com/mcp",
headers: { Authorization: "Bearer ..." },
},
linear: "https://mcp.linear.app/mcp",
});
const bash = new Bash({ customCommands: commands });await mcpCommand("slack", "https://mcp.slack.com/mcp", {
// Auth
headers: { Authorization: "Bearer xoxb-..." },
// Rename tools for shorter commands
aliases: {
send_message: "send", // slack send --channel ...
list_channels: "channels", // slack channels
},
// Filter which tools are exposed
include: ["send_message", "list_channels"],
// or: exclude: ["admin_*"],
// Output format
defaultOutput: "json", // "json" | "text" | "raw"
// Connection
timeout: 30000,
retries: 2,
});Connect to an MCP server and return a just-bash Command.
| Param | Type | Description |
|---|---|---|
name |
string |
Command name (e.g. "slack") |
url |
string |
MCP server endpoint URL |
options |
McpCommandOptions |
Optional configuration (see above) |
Returns Promise<Command> — pass to Bash({ customCommands: [...] }).
Register multiple servers at once.
| Param | Type | Description |
|---|---|---|
servers |
Record<string, string | ServerConfig> |
Map of name → URL or config |
Returns Promise<Command[]>.
Low-level MCP client if you need direct access:
import { MCPTransport } from "@intentface/just-mcp-to-cli";
const transport = new MCPTransport("https://mcp.slack.com/mcp", {
headers: { Authorization: "Bearer ..." },
timeout: 30000,
retries: 2,
});
await transport.initialize();
const tools = await transport.listTools();
const result = await transport.callTool("send_message", { channel: "#dev", text: "hi" });
await transport.close();For advanced use cases, the internals are also exported:
import {
schemaToArgs, // JSON Schema → CliArg[]
parseArgs, // argv string[] → parsed object
generateServerHelp, // tool list → help text
generateToolHelp, // single tool → help text
formatResult, // MCP result → formatted string
} from "@intentface/just-mcp-to-cli";These flags are available on every tool:
| Flag | Description |
|---|---|
--help, -h |
Show help for the server or tool |
--json |
Read arguments from stdin as JSON instead of flags |
--raw |
Output the raw MCP result envelope |
--verbose |
Show request/response details |
Follows standard Unix conventions:
| Exit Code | Meaning |
|---|---|
0 |
Success |
1 |
MCP error or connection error |
2 |
Invalid arguments (missing required, bad type, unknown flag) |
Errors go to stderr, output goes to stdout.
- Bun v1.0+
git clone https://github.com/intentface/just-mcp-to-cli.git
cd just-mcp-to-cli
bun install# Run tests
bun test
# Run tests in watch mode
bun test --watch
# Type-check
bunx tsc --noEmit
# Build (ESM + CJS + types)
bun run build
# Inspect the package before publishing
npm pack --dry-runsrc/
index.ts Public API — mcpCommand(), mcpCommands()
transport.ts MCP HTTP/SSE client (thin JSON-RPC, no SDK)
schema-to-args.ts JSON Schema ↔ CLI flags + arg parser
help-generator.ts --help text formatting with rich type info
output.ts MCP result → stdout formatting (text/json/raw)
command-factory.ts Glue — wires everything into defineCommand()
types.ts Shared TypeScript types
tests/
schema-to-args.test.ts
help-generator.test.ts
output.test.ts
transport.test.ts
command-factory.test.ts
examples/
basic.ts Connect to one MCP server
multi-server.ts Connect to multiple servers
custom-commands.ts Mix MCP tools with your own commands (runs standalone)
interactive.ts Interactive REPL
The library has a simple pipeline:
- Transport — thin JSON-RPC client using
fetch(). Supports Streamable HTTP (primary) and SSE (fallback). No@modelcontextprotocol/sdkdependency. - Schema-to-args — converts each tool's
inputSchema(JSON Schema) into CLI flag definitions. Flattens nested objects into dot-notation (--filter.status). Maps between kebab-case, snake_case, and camelCase. - Help generator — produces GNU-style
--helpoutput from the arg definitions, showing types, constraints, enums, defaults, and nested object structure. - Output formatter — transforms MCP
tools/callresults into text, JSON, or raw format for stdout. - Command factory — ties it all together into a
defineCommand()handler that routes subcommands, parses args, calls the MCP server, and formats output.
# Run without MCP server (uses only custom commands)
bun run examples/custom-commands.ts
# Connect to an MCP server
MCP_URL=https://your-server.com/mcp MCP_NAME=mytools bun run examples/basic.ts
# Interactive REPL
MCP_URL=https://your-server.com/mcp MCP_NAME=mytools bun run examples/interactive.tsnpm login --scope=@intentface
npm publish --access publicMIT