The HTTP API is the layer underneath MCP. Every MCP tool call lands here eventually. If you want to script ToolPiper without an AI editor in the loop, you call this API directly. Shell, Python, Node, Swift, anywhere with HTTP support.
What's the base URL and shape?
Base URL is http://127.0.0.1:9998. Endpoints are organized by category (/chat, /audio/*, /vision/*, /browser/*, /system/*, etc.). Requests are JSON. Responses are JSON. Errors come back as HTTP 4xx/5xx with JSON error bodies. Standard REST conventions throughout.
A working example with curl, calling /chat against a local LLM:
curl -s http://127.0.0.1:9998/chat \
-H 'Content-Type: application/json' \
-d '{
"messages": [
{"role": "user", "content": "Summarize today\u0027s top HN story."}
]
}'Response shape:
{
"content": "...",
"model": "...",
"usage": { ... }
}Every endpoint follows the same pattern. POST to a path, send JSON, receive JSON. There's no special framing, no streaming-required envelopes, no proprietary protocol.
How do I authenticate?
Loopback calls just work with zero config: ToolPiper mints a short-lived ambient bearer token on disk and trusts 127.0.0.1 at the socket level. Off-machine calls (a LAN device or a PiperMesh peer) must send a scoped Authorization: Bearer tp_<64 hex> token with the correct audience.
The split is straightforward. Loopback requests skip the paste-a-key step entirely. On every launch ToolPiper writes an ambient token to ~/Library/Application Support/ToolPiper/.toolpiper-token (file mode 0600, rotated each launch) and accepts loopback connections at the socket level, so a local script or curl call needs no header at all. Off-machine clients are different: they need a scoped bearer token of 64 hex characters of CSPRNG entropy, carrying the right audience, included as Authorization: Bearer tp_.... Treat that token as a secret. ToolPiper never writes it into ~/.claude/ or any client config you don't control.
Sample LAN call:
curl -s http://192.168.1.10:9998/chat \
-H 'Authorization: Bearer tp_abc123...' \
-H 'Content-Type: application/json' \
-d '{ "messages": [{"role": "user", "content": "Hello"}] }'If you forget the token on an off-machine call, the request returns 401 Unauthorized. Loopback calls never need it.
You can see and control who is connected without leaving the app. The Connected Apps pane lists every authenticated client as a labeled row showing its MCP-tools access state, and a one-click revoke kills that bearer immediately — the row stays visible, marked revoked, so state is never hidden. The Tool governance overlay is restrict-only: you can deny specific tools (say, system_run_command) for every client, or flip the built-in tool marketplace from open browse to an explicit allow-list of servers and tools. It only ever narrows what a tier already permits, never widens it. Today that overlay is a per-device, locally-edited policy; the same shape is built so an organization can push it later (controls lock with a "Managed by your organization" badge), but org-managed governance is planned, not a shipping admin console yet.
How do I find the available endpoints?
ToolPiper publishes a full OpenAPI spec at GET /v1/openapi.json. Load it into any OpenAPI client (Postman, Insomnia, Bruno, or codegen tools) to get the endpoint list with request and response schemas.
Endpoints group into broad categories. The naming pattern is POST /<category>/<action>:
- Inference.
POST /chat,POST /audio/transcribe,POST /audio/speak,POST /vision/ocr,POST /text/embed,POST /text/analyze,POST /image/analyze. - Browser.
POST /browser/launch,POST /browser/snapshot,POST /browser/action,POST /browser/assert,POST /browser/console,POST /browser/network, more. - System control.
POST /system/window/list,POST /system/input/click,POST /system/input/type,POST /system/clipboard/read,POST /system/audio/state, more (160+ system endpoints across 26 macOS domains). - Web.
POST /web/scrape,POST /web/search,POST /web/api/discover. - Vision.
POST /vision/screenshot,POST /vision/ocr,POST /vision/color/pick,POST /vision/camera/capture. - Files.
POST /file/read,POST /file/write,POST /file/list. - RAG.
POST /rag/ingest,POST /rag/query,POST /rag/collection/list. - OAuth.
POST /oauth/connect,POST /oauth/status,POST /oauth/disconnect,POST /gsc/*.
What does a real script look like?
A Python script that screenshots, OCRs, summarizes, and posts the result to Slack in 20 lines. The HTTP API makes each tool a single function call.
import requests
BASE = "http://127.0.0.1:9998"
def call(path, body):
r = requests.post(f"{BASE}{path}", json=body)
r.raise_for_status()
return r.json()
shot = call("/vision/screenshot", {"mode": "full"})
ocr = call("/vision/ocr", {"path": shot["path"]})
summary = call("/chat", {
"messages": [
{"role": "system", "content": "Summarize in one sentence."},
{"role": "user", "content": ocr["text"]},
]
})
print(summary["content"])That's the whole thing. No MCP, no AI editor in the loop, no orchestration framework. You decide the order. ToolPiper provides the tools.
How does the HTTP API relate to MCP?
MCP dispatches every tool call to the same handlers the HTTP API uses. Both surfaces share implementations and behavior. The wire formats differ: MCP is JSON-RPC, the HTTP API is REST. Pick the surface based on the caller, not on the underlying tool.
When does each shine? MCP wins when you want an AI model to decide which tool to call. The HTTP API wins when you already know which tool you want. Both work concurrently against the same ToolPiper instance. There's no resource contention or state divergence.
What happens when ToolPiper isn't running?
HTTP requests return connection refused. The API is hosted inside the ToolPiper app, not as a separate daemon. If you need ToolPiper to start automatically at login, enable that in Preferences -> General. Otherwise, scripts that depend on the API should check ToolPiper's /health endpoint first.
# Sanity check before running a script
if ! curl -sf http://127.0.0.1:9998/health > /dev/null; then
echo "ToolPiper isn't running; launching..."
open -a ToolPiper
sleep 3
fiThe /health endpoint returns JSON with "status": "ok", the build number, and the uptime. It's the cheapest call ToolPiper supports, designed for liveness checks.
