A curl-like CLI for Model Context Protocol (MCP) servers. Query tools, resources, and prompts using simple REST-like URLs.
LLM-friendly: compact JSON output, NDJSON streaming, structured errors to stderr, semantic exit codes. Built for agents to call from shell.
brew install turlockmike/murl/murlpip install mcp-curlcurl -sSL https://raw.githubusercontent.com/turlockmike/murl/master/install.sh | bashTo upgrade: brew upgrade turlockmike/murl/murl or murl --upgrade
# List tools on a server (NDJSON output — one JSON object per line)
murl https://mcp.deepwiki.com/mcp/tools | jq -r '.name'
# Call a tool — response is auto-unwrapped from the MCP text envelope
murl https://remote.mcpservers.org/fetch/mcp/tools/fetch -d url=https://example.com
# Paginated tools auto-follow nextCursor and emit NDJSON
murl https://your-server/mcp/tools/list_things -d filter=active > all.ndjsonPublic demo servers:
https://mcp.deepwiki.com/mcp— GitHub repository docshttps://remote.mcpservers.org/fetch/mcp— fetch web content
murl <url> [options]| URL Path | MCP Method |
|---|---|
/tools |
tools/list |
/tools/<name> |
tools/call |
/resources |
resources/list |
/resources/<path> |
resources/read |
/prompts |
prompts/list |
/prompts/<name> |
prompts/get |
tools/call responses are post-processed by default so the common case
needs zero flags:
- Auto-unwrap the text envelope. MCP tool responses are returned as
[{type:"text", text:"<stringified JSON>"}]. murl detects this shape and emits the inner JSON directly. - Auto-follow
nextCursor. When the unwrapped body carries{hasMore: true, nextCursor}, murl follows the cursor and emits the aggregated items as NDJSON to stdout. Capped at--max-pages(default 1000) with cursor-cycle protection. - Detect tool-level auth failures. When the response body carries
{error: {code: "invalid_token"}}or anAUTH_FAILEDmarker (HTTP 200 but the downstream provider rejected the token), murl exits with a structuredAUTH_FAILEDerror to stderr.
Use --raw to disable all three and return the protocol response verbatim.
# List tools
murl http://localhost:3000/tools
# Call a tool (response is auto-unwrapped to inner JSON)
murl http://localhost:3000/tools/echo -d message=hello
# Paginated tool — get every page as NDJSON, no manual cursor loop
murl http://localhost:3000/tools/list_things -d filter=active > all.ndjson
# Cap pagination
murl http://localhost:3000/tools/list_things --max-pages 5
# Multiple arguments (auto type-coerced)
murl http://localhost:3000/tools/weather -d city=Paris -d metric=true
# JSON data
murl http://localhost:3000/tools/config -d '{"settings": {"theme": "dark"}}'
# Custom headers
murl http://localhost:3000/tools -H "Authorization: Bearer token123"
# Raw protocol response (no unwrap, no cursor follow)
murl http://localhost:3000/tools/echo -d message=hi --raw
# Verbose mode (pretty-prints output, shows request debug info)
murl http://localhost:3000/tools -v| Flag | Description |
|---|---|
-d, --data |
Add key=value, JSON, @file, or @- (stdin). Repeatable. |
-H, --header |
Add HTTP header. Repeatable. |
-v, --verbose |
Pretty-print output, show request debug info. |
--raw |
Skip auto-unwrap and cursor follow; emit the protocol response verbatim. |
--max-pages <N> |
Cap auto-pagination on tools/call responses (default 1000). |
--format toon |
Output in TOON format. |
--login |
Force OAuth re-authentication. |
--no-auth |
Skip all authentication. |
--client-id <id> |
Pre-registered OAuth client ID (skips dynamic registration). |
--client-secret <s> |
Pre-registered OAuth client secret (or MURL_CLIENT_SECRET). |
--callback-port <n> |
Fixed port for the OAuth callback redirect URI. |
--scope <scopes> |
OAuth scope to request. |
--no-mcp-config |
Skip .mcp.json discovery for OAuth client defaults. |
--version |
Show version info. |
--upgrade |
Upgrade to latest version. |
Use --format toon for TOON output, which uses fewer tokens than JSON when feeding results into LLM contexts. Requires the optional python-toon package:
pip install mcp-curl[toon]
# List tools in TOON format — produces fewer tokens than JSON for structured data
murl https://mcp.deepwiki.com/mcp/tools --format toonmurl supports OAuth 2.0 with Dynamic Client Registration (RFC 7591) and PKCE. Tokens are cached automatically.
# First call triggers browser-based OAuth flow
murl https://example.com/mcp/tools
# Skip auth for public servers
murl https://example.com/mcp/tools --no-auth
# Force re-authentication
murl https://example.com/mcp/tools --login
# Pre-registered OAuth client (skip Dynamic Client Registration)
murl --client-id 0oax4xo --callback-port 8080 https://example.com/mcp/toolsmurl walks up from the current directory looking for a sibling .mcp.json
and uses it to fill in OAuth defaults — same convention Claude Code uses.
When the requested base URL exactly matches an entry's url field (after
URL decoding and trailing-slash normalization), murl picks up:
oauth.clientId→ fills--client-idif not passedoauth.callbackPort→ fills--callback-portif not passed
{
"mcpServers": {
"myserver": {
"type": "http",
"url": "https://example.com/mcp",
"oauth": {
"clientId": "0oax4xo",
"callbackPort": 8080
}
}
}
}Matching is exact (not prefix): a caller URL with extra path segments
or a different query string is treated as a different runtime and falls
through to dynamic client registration. Use --no-mcp-config to disable
discovery entirely.
- Output & Exit Codes — NDJSON format, structured errors, exit codes
- MCP Server Setup — mcp-proxy, Streamable HTTP, local servers
- Contributing — development setup, testing, releasing
- CHANGELOG — release history and breaking changes
- Python 3.10+
MIT
