npm package: @intentface/just-mcp-to-cli
Description: Turn any MCP server into native bash commands for just-bash
Latest version: 0.2.0

# @intentface/just-mcp-to-cli

Turn any MCP server into native bash commands for [just-bash](https://github.com/vercel-labs/just-bash).

An agent types `slack send-message --channel "#dev" --text "deployed"` and it just works. No SDK. No MCP knowledge. Just bash.

## Install

```bash
npm install @intentface/just-mcp-to-cli just-bash
```

## Quick Start

```typescript
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](https://github.com/vercel-labs/just-bash). 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
```

## Discovery & Help

Every registered server and tool gets auto-generated `--help`:

```bash
# 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 details
```

Help 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:

```bash
# 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:

```bash
# 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.json
```

## Multiple Servers

```typescript
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

```typescript
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:

```typescript
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:

```typescript
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](https://bun.sh) v1.0+

### Setup

```bash
git clone https://github.com/intentface/just-mcp-to-cli.git
cd just-mcp-to-cli
bun install
```

### Commands

```bash
# 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-run
```

### Project 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 REPL
```

### Architecture

The library has a simple pipeline:

1. **Transport** — thin JSON-RPC client using `fetch()`. Supports Streamable HTTP (primary) and SSE (fallback). No `@modelcontextprotocol/sdk` dependency.
2. **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.
3. **Help generator** — produces GNU-style `--help` output from the arg definitions, showing types, constraints, enums, defaults, and nested object structure.
4. **Output formatter** — transforms MCP `tools/call` results into text, JSON, or raw format for stdout.
5. **Command factory** — ties it all together into a `defineCommand()` handler that routes subcommands, parses args, calls the MCP server, and formats output.

### Running Examples

```bash
# 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.ts
```

### Publishing

```bash
npm login --scope=@intentface
npm publish --access public
```

## License

MIT