@intentface/just-mcp-to-cli
v0.2.0
Published
Turn any MCP server into native bash commands for just-bash
Readme
@intentface/just-mcp-to-cli
Turn any MCP server into native bash commands for just-bash.
An agent types slack send-message --channel "#dev" --text "deployed" and it just works. No SDK. No MCP knowledge. Just bash.
Install
npm install @intentface/just-mcp-to-cli just-bashQuick Start
import { 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"');How It Works
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 stdoutDiscovery & Help
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.).
Nested Parameters
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]Unix Pipes
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.jsonMultiple Servers
import { 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 });Configuration
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,
});API
mcpCommand(name, url, options?)
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: [...] }).
mcpCommands(servers)
Register multiple servers at once.
| Param | Type | Description |
|-------|------|-------------|
| servers | Record<string, string \| ServerConfig> | Map of name → URL or config |
Returns Promise<Command[]>.
MCPTransport
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();Utility Exports
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";Reserved Flags
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 |
Error Handling
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.
Developing
Prerequisites
- Bun v1.0+
Setup
git clone https://github.com/intentface/just-mcp-to-cli.git
cd just-mcp-to-cli
bun installCommands
# 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-runProject Structure
src/
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 REPLArchitecture
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.
Running Examples
# 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.tsPublishing
npm login --scope=@intentface
npm publish --access publicLicense
MIT
