# Toolcli

> Use when a task calls MCP tools or LLM endpoints and benefits from filtering, ranking, batching, or post-processing. Routes calls through a Python sandbox so bulk data (embeddings, file bytes, large responses) stays out of agent context; only summaries return.

- Skill: `octaviusp/toolcli` (Agent Skill)
- Install (CLI): `npx skillmds@latest add octaviusp/toolcli`
- Raw SKILL.md: https://api.skillmd.com/api/skills/octaviusp/toolcli/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: octaviusp (https://skillmd.com/u/octaviusp)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/octaviusp/toolcli

---


# toolcli — Programmatic Tool Calling

You have a CLI called `toolcli` for invoking MCP tools through small Python wrappers. Prefer it over direct one-shot MCP calls whenever the task needs filtering, ranking, batching, or any data shaping.

## 3 Rules (ignore these = silent failure)

1. **`tools` is a MAGIC injected namespace** — only exists inside `toolcli code` / `toolcli script execute`. **NEVER** write a `.py` file and run it with `python` — `tools` will be undefined.
2. **Each call is a FRESH sandbox.** Variables don't survive between calls. A multi-step pipeline = ONE wrapper. Share data across calls by writing to `cwd` (use `--cwd <dir>` to make the dir persist).
3. **When you don't know a tool's signature:**
   ```
   toolcli list --live                                          # all tools
   toolcli mcp inspect <srv>.<tool>                             # INPUT schema
   toolcli code "r=tools['x'].fn(...); result=list(r.keys())"   # PROBE OUTPUT shape
   ```
   MCP responses are **dicts with server-specific keys**. Don't guess (`resp['content']`, `resp['data']`). Probe first.

## Wrapper Anatomy — [PRE] → [IN] → [POST]

```python
# [PRE]  prepare inputs
from pathlib import Path
files = sorted(Path(payload['path']).glob('*.md'))
inputs = [f.read_text(encoding='utf-8', errors='ignore') for f in files]

# [IN]   call MCP tools
# Identifier-safe tool names → tools['srv'].method(...)
# Hyphenated tool names      → tools['srv']['hyphen-name'](...)
resp = tools['gpt'].generate_embeddings(
    inputs=inputs, model='text-embedding-3-small', full=True,
)

# [POST] heavy data → cwd, return only summary
import numpy as np
v = np.array([x['embedding'] for x in resp['items']])
np.save(cwd / 'vectors.npy', v)
result = {'count': len(files), 'dim': v.shape[1],
          'usage': resp.get('usage'),
          'artifacts': [str(cwd / 'vectors.npy')]}
```

**Injected globals:** `tools`, `payload` (dict from `--payload`), `cwd` (Path).
**Return:** assign to `result` (or last top-level expression).
**Errors:** MCP failures raise `RuntimeError` — wrap with `try/except` if needed.

## Invoke from Bash

```bash
# HEREDOC (preferred — kills quote-hell):
toolcli code --with numpy --cwd ./out <<'PY'
... wrapper body ...
PY

# Inline (short scripts only):
toolcli code "result = 1+1"

# Save reusable wrapper, run by name (description goes BEFORE code):
toolcli script add cluster "Cluster texts via embeddings" \
  -f wrapper.py --deps numpy
toolcli script execute cluster --payload '{"texts":[...]}' [--json]
```

`--json` on `code`/`script execute` emits machine-readable output for chaining (jq, sub-agents).

## Anti-Patterns (these fail silently or hang)

- `python wrapper.py` → `tools` won't exist
- `tools['srv'].hyphen-name(...)` → SyntaxError. Use `tools['srv']['hyphen-name'](...)`
- guessing keys (`resp['content']`, `resp['data']`) → MCP returns server-specific shape; **probe first** with `list(r.keys())`
- `--cwd` redirecting MCP file outputs → tools that save files have their own param (often `output_dir=str(cwd)`); pass it explicitly
- 120s default timeout on image/video calls → use `--timeout 300+`
- `gpt.generate_embeddings` without `full=True` → returns previews silently; numpy gets garbage
- `plt.show()` / interactive GUI → sandbox is headless; use `matplotlib.use("Agg")` + `fig.savefig(cwd / 'p.png')`
- `result = full_api_response` → save bulk to `cwd`, return only counts/paths/summary
- Splitting a pipeline across multiple `code` calls → state DIES between calls. ONE wrapper.
- `mcp add <name> <url> --env K=V` → `--env` is for stdio MCPs; HTTP MCPs use `--header`

## Output

ASCII (default): `OK/FAIL · STDOUT · RESULT · ARTIFACTS · STDERR`. Result is annotated with `(object)` / `(array, N items)` / `(string)` so the shape is always explicit.

JSON (`--json`): `{ok, stdout, stderr, result, artifacts, elapsedMs, exitCode, timedOut}` — single line, perfect for chaining.

## Quick Reference

```
toolcli help                                  # full agent guide
toolcli list                                  # what's wired
toolcli list --live                           # + live tool enumeration
toolcli mcp import ~/.claude/.mcp.json        # bulk-import existing MCPs
```

