Model provider · localhost only
Claude Code as a model, on your own machine.
clyde serves the Claude Code CLI as an OpenAI-compatible provider on localhost. Point
anything that speaks POST /v1/chat/completions at it and the replies come
from claude -p running under your own Claude Code login. No API key, no
tools, no MCP servers: the thing on the other end behaves like a model, not like an agent.
$ uv run clyde serve --port 8127
$ curl localhost:8127/v1/chat/completions \
-H 'content-type: application/json' \
-d '{"model":"sonnet","messages":[{"role":"user","content":"say ok"}]}'
{
"object": "chat.completion",
"model": "sonnet",
"choices": [{"index": 0,
"message": {"role": "assistant", "content": "ok"},
"finish_reason": "stop"}],
"usage": {"prompt_tokens": …, "completion_tokens": …, "total_tokens": …}
}
claude -p process, one turn./ready reports what it saw.How it works
One chat-completions request becomes one claude -p run.
clyde is small, and most of it is there to keep one promise: the model on the other end can reach nothing. The flags are the security posture, and a test asserts each one literally.
The request is translated
System messages become the system prompt. A single user message is sent as it is; a longer conversation is rendered into one <conversation> of <turn role="…"> blocks. A request carrying tools is refused with a 400.
The CLI runs locked down
Every call passes --tools "", --disable-slash-commands and an empty --mcp-config with --strict-mcp-config, in an empty working directory, with one turn. A JSON-schema call gets four, because the CLI spends turns handing a structured answer back.
The reply comes back in OpenAI's shape
The text lands in choices[0].message.content, structured output included, as a string. Usage carries cached tokens under prompt_tokens_details. An empty reply is a 502, never a finished turn.
Routes
Four routes, and nothing else listening.
Errors use OpenAI's envelope, {"error": {"message", "type", "code"}}, and the message is a sentence you can act on: not_logged_in, claude_not_found, not_locked_down, tools_unsupported, claude_failed, timeout. Every refusal is also written to clyde's log.
Is the process up?
{"status": "ok", "version": …} whenever the process is up. It checks nothing.
Would a call work?
200 when it would, otherwise 503. checks.claude says whether the binary was found; checks.lockdown lists the tools and MCP servers the startup probe saw load, and is ok only when both lists are empty.
Which models?
The aliases the CLI accepts, in OpenAI's list shape: sonnet, opus, haiku, fable.
One completion
Answered by one claude -p run. response_format with a json_schema asks for structured output; stream: true sends the same reply as server-sent events, in one piece, because the CLI returns the whole reply at once.
Configured by environment
Host, port, the path to claude, the default model, a timeout, how many processes may run at once. A CLYDE_* name no setting declares stops startup, so a typo is a crash rather than a setting that silently did nothing.
Used by the Lucy hub
The Lucy hub knows clyde as its clyde provider at http://localhost:8127/v1, so Lucy can run on your own Claude Code login. clyde is built for the LUCY family but is not one of its keyring-verifying services.
Run it
A signed-in CLI, a sync, and a port.
You need Python 3.12 or 3.13, uv, and the Claude Code CLI signed in. clyde uses that login and never needs an API key.
- Install the Claude Code CLI, then run
claudeonce in a terminal and sign in.npm install -g @anthropic-ai/claude-code - Clone clyde and install it.
git clone https://github.com/tochi-mba/clyde.git && cd clyde && uv sync - Serve it on loopback. Startup runs
claudeonce under the same flags as every call, which takes a few seconds and at most two minutes.uv run clyde serve --port 8127 - Ask it something.
curl localhost:8127/v1/chat/completions -H 'content-type: application/json' -d '{"model":"sonnet","messages":[{"role":"user","content":"say ok"}]}'
clyde looks for claude on PATH, then in the places npm usually puts a global binary. A service manager often runs with a shorter PATH than your shell, so set CLYDE_CLAUDE_BINARY when it cannot be found; a missing binary does not stop the server, /ready says what is wrong. The README lists every CLYDE_* setting.
What it is not
A model, not an agent. A local runtime, not a hosted API.
Is this a hosted API, or does it have keys of its own?
Neither. clyde binds to loopback and authenticates nobody, which is the same posture as any other local runtime on the same machine. It holds no API key and no credential: the replies come from the Claude Code CLI you signed into, under its own login. Do not expose the port; that would hand a signed-in Claude Code to whoever finds it.
Can the model read my files or call tools?
No. Every call runs with no tools, no MCP servers, no skills and an empty working directory, and with one turn. That is checked, not assumed: at startup the CLI runs once under the same flags, /ready reports what it loaded, and every call is refused with a 503 unless that was nothing.
Does it support OpenAI tool calling?
No. A request carrying tools is refused with a 400 tools_unsupported. Ask for structured output through response_format instead; the JSON arrives as a string in choices[0].message.content, which is where a chat-completions client looks for it. A reply that comes back as tool-call syntax is asked for again, once.
Does it stream token by token?
Not today. stream: true sends the reply as server-sent events, one chunk with the text, one with the finish reason, one with usage, then data: [DONE], but claude -p returns the whole reply at once, so it arrives in one piece.
Docs
Written for the person who has to run it.
README
Install, run, call and configure it: every route, every error code, every CLYDE_* setting.
Why the flags are the flags
The measurements that chose the lockdown: token counts, costs and the tools each configuration offered.
Working notes
Where things are, the invariants, the gates, and how a commit is written.