Toolbelt lets you turn any chatbot with MCP support into a fully fledged coding agent.
It decouples your agentic infrastructure from the chatbot itself. Your tools, permissions, and infrastructure live in the MCP server, not baked into a vendor's product. Swap between web-based chatbots, terminal-based agents, and even chatbot apps on your iPhone without extensive work maintaining your setup.
It's a single MCP server bundling shell, filesystem, web search (Exa), library docs (Context7), and skills discovery tools. Supports two transport modes:
- Local (stdio) — spawned directly by LM Studio or any stdio MCP client. No network exposure.
- Remote (HTTP) — serves over Streamable HTTP behind a Cloudflare Quick Tunnel with bearer-token auth and full OAuth 2.0 support.
| Tool | Description |
|---|---|
bash |
Run a shell command and return combined stdout/stderr |
read_file |
Read a file from disk |
write_file |
Write/overwrite a file (creates parent directories) |
glob |
Glob filesystem search (recursive with **) |
grep |
Search file contents with ripgrep (falls back to grep -rn) |
list_dir |
List directory entries |
list_skills |
Discover SKILL.md files with YAML frontmatter from a skills directory |
web_search |
Search the web via Exa (titles, URLs, snippets) |
web_crawl |
Fetch the full text content of a URL via Exa |
context7_resolve |
Resolve a library name to a Context7-compatible library ID |
context7_docs |
Fetch up-to-date library docs from Context7 |
Toggle any tool on/off in tools.toml — set enabled = false to disable. Restart the server to pick up changes. See Custom tools for how to add your own.
Pick whichever method suits you:
pip install git+https://github.com/dawsonamf/toolbelt.gitcurl -fsSL https://raw.githubusercontent.com/dawsonamf/toolbelt/main/install.sh | bashgit clone https://github.com/dawsonamf/toolbelt.git
cd toolbelt
pip install -e .pip install toolbeltSet API keys (add to your shell profile to make them permanent):
export EXA_API_KEY=... # required for web_search / web_crawl
export CONTEXT7_API_KEY=... # optional — avoids rate limitsThen run:
toolbelt --local # local/stdio mode
toolbelt # remote mode (requires cloudflared)Run with --local to use stdio transport — no ports, no network:
python server.py --localEdit ~/.lmstudio/mcp.json:
{
"mcpServers": {
"toolbelt": {
"command": "toolbelt",
"args": ["--local"],
"env": {
"EXA_API_KEY": "your_exa_key",
"CONTEXT7_API_KEY": "your_context7_key"
}
}
}
}LM Studio spawns the process over stdio — fully localhost, no sockets.
Run without --local to start a Streamable HTTP server tunneled through a Cloudflare Quick Tunnel (no account required):
python server.pyOn first run a bearer token is auto-generated and saved to ~/.toolbelt/token (mode 600). The tunnel URL and MCP client config snippet are printed to the console:
───────────────────────────────────────────────────────
Toolbelt running (remote)
Add to the "mcpServers" section of your MCP client config:
"toolbelt": {
"type": "streamableHttp",
"url": "https://<random>.trycloudflare.com/mcp",
"headers": {
"Authorization": "Bearer <your-token>"
}
}
OAuth (Claude Desktop / OAuth-only clients):
MCP URL: https://<random>.trycloudflare.com/mcp
Client ID: <auto-generated>
Client Secret: <auto-generated>
───────────────────────────────────────────────────────
Requires cloudflared (brew install cloudflared on macOS).
Rotate all auth credentials (bearer token, OAuth client ID, and client secret) in one shot:
toolbelt --rotate-authThis deletes and regenerates ~/.toolbelt/token, client_id, and client_secret, prints the new values, and exits. Any MCP clients using the old credentials will need to be updated with the new ones.
Remote mode exposes full OAuth 2.0 endpoints for clients that require them (e.g. Claude Desktop):
| Endpoint | Purpose |
|---|---|
/.well-known/oauth-protected-resource |
RFC 9728 Protected Resource Metadata |
/.well-known/oauth-authorization-server |
RFC 8414 Authorization Server Metadata |
/oauth/authorize |
Authorization endpoint (code grant) |
/oauth/token |
Token endpoint |
Supported grant types: authorization_code (with PKCE / S256) and client_credentials. OAuth client ID and secret are auto-generated on first run and stored in ~/.toolbelt/.
By default, tools operate relative to the process's current directory. You can set a fixed working directory so all path-based tools (bash, read_file, write_file, glob, grep, list_dir) resolve relative paths against it.
In order of priority (highest first):
- CLI flag —
toolbelt --cwd /path/to/project - Environment variable —
export WORK_DIR=/path/to/project - config.json —
{ "work_dir": "/path/to/project" }
A common pattern is to launch with the current shell directory:
toolbelt --local --cwd .Absolute paths always pass through unchanged regardless of the WORK_DIR setting.
The Cloudflare Quick Tunnel generates a random URL on every startup. If you want a stable, permanent URL, you can host the server on any VPS provider. The server already works over HTTP — just run it on a VPS with a fixed public address and configure your bearer token once. No code changes needed.
Lives next to server.py. Toggle individual tools:
[tools.bash]
enabled = true
[tools.list_skills]
enabled = falseOverride settings like the skills directory or OAuth credentials:
{
"skills_dir": "/path/to/skills",
"client_id": "custom-id",
"client_secret": "custom-secret"
}| Variable | Description |
|---|---|
EXA_API_KEY |
Exa API key (required for web_search / web_crawl) |
CONTEXT7_API_KEY |
Context7 API key (optional, avoids rate limits) |
TOOLBELT_CONFIG |
Override path to tools.toml |
CONFIG_FILE |
Override path to config.json |
SKILLS_DIR |
Override skills directory (default ~/.claude/skills) |
WORK_DIR |
Default working directory for path-based tools |
CLIENT_ID_FILE |
Override path to OAuth client ID file |
CLIENT_SECRET_FILE |
Override path to OAuth client secret file |
The list_skills tool scans a directory for SKILL.md files and returns their paths and YAML frontmatter. The default directory is ~/.claude/skills, configurable via the SKILLS_DIR env var or skills_dir in config.json.
All tools are defined in toolbelt_server/__init__.py. Each tool is a plain Python function decorated with @mcp.tool(), optionally gated by the enabled() helper so it can be toggled in tools.toml.
- Open
toolbelt_server/__init__.pyand add your function anywhere after themcp = FastMCP("toolbelt")line (the# -- custom tools --comment marks a good spot):
if enabled("my_tool"):
@mcp.tool()
def my_tool(arg: str) -> str:
"""Description shown to the LLM."""
return f"got {arg}"- Add a toggle entry in
tools.toml:
[tools.my_tool]
enabled = true- Restart the server.
mcpis a FastMCP server instance.@mcp.tool()registers the function as an MCP tool — the function name becomes the tool name, the docstring becomes its description, and the type-annotated parameters become the tool's input schema.enabled("name")readstools.tomland returnsFalsewhen a tool is explicitly disabled, so the decorator is never called and the tool is never registered.- Tools that operate on file paths should use
resolve_path(path)to respect theWORK_DIRsetting.
import datetime
if enabled("timestamp"):
@mcp.tool()
def timestamp() -> str:
"""Return the current UTC timestamp."""
return datetime.datetime.now(datetime.timezone.utc).isoformat()[tools.timestamp]
enabled = truepip install -e ".[dev]" # install with dev deps (pytest, ruff)
make test # run tests
make lint # lint with ruff
make fmt # auto-format with ruff
make run # start in remote mode
make run-local # start in local/stdio mode
make setup # create an isolated .venv and install deps
make clean # remove .venv, caches, bytecodepytest tests/Tests cover tool registration, config toggling, auth middleware, OAuth flows, and server startup.
