tkflyc-ai-cli MCP server
Commit Nov 13, 2026Run local AI CLIs and OpenAI-compatible APIs as MCP tools with background job support and durable state management.
Also known as moerasermax/tkflyc-ai-cli
- Stars
- 7
- Tools
- —
- Downloads, 30 days
- —
- Latest version
- —
- Last commit
- Nov 13, 2026
- License
- Apache-2.0
What is tkflyc-ai-cli MCP?
tkflyc-ai-cli runs local AI CLIs and OpenAI-compatible APIs as MCP tools with background job support and durable state management.
Summary generated from the project's README
About tkflyc-ai-cli MCP server
Developers can run local AI command-line interfaces and OpenAI-compatible APIs directly through an MCP client. This server turns tools like Claude, Codex, Grok, and Windows Antigravity into accessible MCP tools. It is designed for users who need to execute complex agent loops where the model can perform file operations and run bash commands.
Unlike simple completion wrappers, this system supports background jobs with durable state management. When a task is started, the run tool returns a PID immediately, allowing you to use list_processes, peek, wait, and get_result to monitor progress. It manages long-running tasks through a JobStore that retains finished jobs for 30 minutes.
Setup involves adding the server to a client like Claude Code via npx or by running the distributed files from a local source installation. For third-party services, you can configure providers in a JSON file to connect to OpenRouter, DeepSeek, Ollama, or NVIDIA hosted endpoints. The server handles complex requirements like exponential backoff for rate-limited APIs and replaying reasoning content for specific models.
What you can do with it
- 1Monitor background AI tasksTrack the progress of a long-running coding task by observing its process ID.
- 2Connect custom API endpointsIntegrate any OpenAI-compatible service like DeepSeek or a local Ollama instance into your AI workflow.
- 3Execute full agent loopsAllow an AI model to interact with your local system by reading files and executing commands.
- 4Manage API rate limitsEnsure reliable connections to shared endpoints that frequently return 429 or 5xx errors.
Written from the project's README. Every use case is checked against a quote from it.
Automated checks
Ran Nov 15, 2026Full report →Not verified yet. A server needs a score of 80, no serious finding, and enough data for us to check. How checks work
- Source repository linked and reachablemoerasermax/tkflyc-ai-cli is public and reachablegithub.com/moerasermax/tkflyc-ai-cli ↗Pass
- Package names this repository as its sourceNot checked
- Build provenance (Sigstore attestation)Not checked
- Open source license (OSI)Apache-2.0spdx.org/licenses/Apache-2.0.html ↗Pass
- Commit or release in the last yearLast commit or release 2026-11-13github.com/moerasermax/tkflyc-ai-cli ↗Pass
- OpenSSF ScorecardNot checked
- Package name not close to a popular packageNot checked
- No critical advisory in the package or its dependenciesNot checked
- No hidden instructions to the model0 tool descriptions and the README read; no override phrases, credential paths or hidden charactersPass
- Destructive tools are labelledNot checked
- Remote endpoint answers the MCP handshakeNot checked
README
View on GitHubai-cli-mcp
Optional ai-cli_kernel_panel: /kernel opens the dashboard in a new Terminal tab; /kernel side opens an embedded session pane with two-step confirmation.
Run local AI CLIs — Claude, Codex, Grok, Antigravity (agy) — and any third-party
OpenAI-compatible API as MCP tools, with background jobs. Self-maintained from
source, built on a registry architecture: adding an agent means adding one file.
Requires Node ^20.19.0 || >=22.12.0.
完整中文文件(環境變數、設定檔、每個工具的細節)見 README.zh-TW.md. The Chinese document is the exhaustive reference; this page covers the design and the parts you need to get running.
What it does
Each supported CLI becomes a tool you can call from an MCP client. Jobs run in the
background: run returns a PID immediately, and list_processes, peek, wait and
get_result observe it while it works. Two surfaces expose this — the MCP server and
the ai-cli command line — sharing durable state for Claude, Codex, Grok and Windows Antigravity.
Quick start
From npm — nothing to build:
claude mcp add ai-cli -s user -- npx -y @tkflyc/ai-cli-mcp
From source — if you intend to change it, or want auto-update:
git clone https://github.com/moerasermax/tkflyc-ai-cli
cd tkflyc-ai-cli
npm install # triggers build via the prepare script and produces dist/
claude mcp add ai-cli -s user -- node "$PWD/dist/server.js"
dist/ is not version-controlled, so the source install has to build before first
use. npm install runs it through the prepare script, so a separate
npm run build is normally unnecessary.
⚠️ The two paths differ in more than building. Auto-update requires a clone with
.git and package.json (see Auto-update below), so an npm/npx install never
updates itself — moving to a new version is something you do yourself. Pin the
version in the command (@tkflyc/ai-cli-mcp@<version>) if you need to know exactly
which one is running.
doctor reports whether this installation meets the source-update precondition
in update.supported.
Wiring into Claude Code
# bash / git bash
claude mcp add ai-cli -s user -- node "$PWD/dist/server.js"
# PowerShell
claude mcp add ai-cli -s user -- node "$PWD\dist\server.js"
Agents
Usage notes for each backend. Maintainer acceptance procedures are in tools/acceptance/README.md.
Grok CLI
Grok is a separate xAI CLI backend for coding and text work. Select grok-4.7,
grok-4.7-build-fast, grok-4.6, or grok-4.5; models and ai-cli models
query grok models with a 15-second timeout and retain successful cache values or
label the builtin fallback on failure (including an unauthenticated CLI).
GROK_CLI_NAME overrides the binary; otherwise PATH wins, with
~/.grok/bin/grok.exe (Windows) or ~/.grok/bin/grok as fallback. Doctor lists Grok.
Prompts use --prompt-file to preserve long Windows prompts. Native Windows
execution bypasses cmd.exe; --rules receives the shared WORKER_CONTEXT first,
then an optional run.system_prompt, separated by a blank line. Every resume
reinjects the lock with --resume; --no-subagents is always set. Normal mode
uses --always-approve. reasoning_effort accepts only low, medium, high, xhigh (live verified: grok 1.0.50).
Grok results with subtype: error_during_execution or stop_reason: cancelled
fail even with exit 0; the error includes both fields.
Grok uses the same durable JobStore/runner, restart recovery and 30-minute finished
job retention as Claude/Codex. Messages JSON result usage includes cache tokens in
normalized input and exposes cost_usd_nominal (vendor estimate, not a billing statement).
The strict read-only capabilities flags, their live verification and the F2 live-verification checklist are maintainer acceptance steps; see tools/acceptance/README.md. Empty and unsupported capabilities are refused.
Antigravity model list
Antigravity's top-level model list uses routable vendor results (including successful
cached results) plus agy / agy-default. Without a usable result, its fallback is
agy, agy-default, gemini-3.1-pro-high, and gemini-3.8-flash-high.
direct-api
Besides the three local CLIs there is a fourth path that starts no subprocess at all:
direct-api talks HTTP from inside the Node process, so any OpenAI-compatible
/chat/completions endpoint works — OpenRouter, DashScope, DeepSeek, NVIDIA's
hosted NIM catalog, a local Ollama, your company gateway. Adding a provider needs no
code change, only a config entry.
It is not a thin completion wrapper: it runs the full agent loop, so the model gets
read_file / write_file / bash and can actually change files and run commands.
Configuring a provider
~/.local/share/ai-cli/providers.json (override with AI_CLI_PROVIDERS_PATH):
{
"providers": {
"openrouter": { "api_key": "sk-or-v1-..." },
"local": { "base_url": "http://127.0.0.1:11434/v1", "api_key": "ollama" },
"nv": {
"base_url": "https://integrate.api.nvidia.com/v1",
"api_key": "nvapi-...",
"retry": { "max_retries": 3, "initial_delay_ms": 1000 },
"extra_body": { "max_tokens": 8192 },
"model_extra_body": {
"nvidia/nemotron-3.5-lightning-30b-a3b": { "reasoning_effort": "none" }
}
}
}
}
Address a model as <provider>-<model>, where the prefix is the key above:
or-qwen/qwen3.7-plus, nv-openai/gpt-oss-20b, local-llama3.1. Everything after
the prefix is sent verbatim, so slashes in model names are fine. openrouter and
dashscope have built-in base URLs and short prefixes (or, ds); every other key
must supply its own base_url.
This file is fail-closed as a whole. One provider missing
base_urlorapi_keymakes the entire file throw, taking the working providers down with it. Back it up before editing.
extra_body — controlling what the framework does not send
The request body is model / messages / stream / stream_options / tools and
nothing else, so a hosted model's defaults apply untouched — and those defaults can be
expensive. nvidia/nemotron-3.5-lightning-30b-a3b spends 28.0s and 318 output tokens
per tool round on its default reasoning, and 6.1s / 84 tokens with
reasoning_effort: "none".
extra_body is the provider-wide default; model_extra_body overrides it field by
field, keyed by the model name as sent to the provider.
model, messages, stream, stream_options and tools are rejected at load
time, naming the provider and the offending key — not dropped silently. Overriding
stream would hand the SSE reader a single JSON blob; overriding tools would offer
the model tools executeTool cannot run.
Note the terminology clash: the run tool's reasoning_effort argument is a
CLI flag for claude/codex and still does not apply here. What extra_body sends is
the same-named API field, which is a different thing.
max_tool_loops — tool loop budget
Each provider may set "max_tool_loops": 60 in providers.json. It must be an
integer from 1 to 200 (default: 30). This limits both tool-loop iterations and
API completion calls; HTTP retries do not count toward the limit. Reaching the
limit returns finish_reason: "tool_loop_limit" and logs the effective limit
to stderr. Invalid values report the provider name and providers.json path.
The job still ends as completed with exit code 0 and get_result does not
show that stderr line, so check agentOutput.finish_reason before treating the
reply as a finished answer.
retry — surviving a shared endpoint
Free shared endpoints throttle and shed load. NVIDIA's own troubleshooting docs say the hosted Nemotron endpoints may return 429 or 503 under high demand, and advise a short wait plus lower concurrency; before this, any non-200 simply failed the job.
429 and 5xx are retried with exponential backoff and jitter. Every other 4xx is not —
a request the server rejected as malformed is still malformed the second time, and
retrying only burns quota. Defaults are 2 retries and a 1s initial delay;
max_retries: 0 turns it off. A Retry-After header wins over the computed backoff,
capped at 60s. A kill during backoff wakes immediately rather than sleeping it out.
Each retry emits a retry event on stdout — a silent retry makes "slow" and "stuck"
indistinguishable to a caller that only sees the tool result.
Retrying covers establishing the request. Once a 200 arrives and the stream starts, content has already reached the caller, so a mid-stream failure is not retried; replaying it would emit the same answer twice.
Measured on nvidia/nemotron-3-super-120b-a12b, 10 two-round tool loops per row:
| Request pacing | Retry | Success |
|---|---|---|
| back-to-back (~66 rpm) | off | 4/10 |
| 10 rpm | off | 8/10 |
| 15 rpm | off | 9/10 |
| 15 rpm | on | 10/10 |
Slowing down alone never reached 100%; retrying did, and at the faster rate. Even pacing trades every request's latency for a few requests' success, while backoff pays only when the server actually refuses.
replay_reasoning — models that want their own thinking back
Some models require the previous assistant turn to be replayed complete, including
its reasoning. Kimi-K3's model card: "clients must pass back the complete assistant
message, including reasoning_content and tool_calls". DeepSeek V4 goes further —
with tools in play, omitting it returns 400.
"nv": { "replay_reasoning": ["moonshotai/kimi-k3"] }
true covers every model of that provider; an array names them individually.
Off by default, and deliberately not a built-in model list. reasoning_content
is not part of OpenAI's assistant message schema, and "OpenAI-compatible" does not
promise unknown fields are ignored — Azure AI Model Inference defaults extra-parameters
to error. A hardcoded allow-list would also go stale the way every other hardcoded
model list here has.
The replayed value is that turn's reasoning, not the run's accumulated text —
otherwise each round would re-append everything before it. It applies to every
assistant turn, not only the ones carrying tool calls: the final answer is written to
the session file and becomes a prior turn when you continue with the same
session_id.
Measured caveat: Kimi-K3 completed a three-round tool chain correctly without the replay, and the docs do not state what omitting it breaks. This follows the stated contract; it is not a fix for an observed failure.
Knowing what this machine can reach
models returns a directApiProviders block: each configured provider's usable
prefixes (including built-in shorthands), base URL, the models named in
model_extra_body, and a copy-pasteable example.
Without it a tool result cannot answer "what can this machine reach" — the static
direct-api list is four placeholder strings, and configured providers are local
state that lives in neither version control nor any static list.
The block never contains api_key, and never throws: an unreadable providers.json
comes back as a note, because "cannot read it" and "nothing configured" are
different answers.
Limits
- Sessions —
session_idpersists underworkFolder/.tmp/api_sessions. - Images — write
[image:C:/path/to.png]in the prompt (png/jpg/webp/gif). - Plain Q&A — prefix the prompt with
[no-tools]to drop the tool loop. - Per turn — at most 30 API calls and 30 tool-loop iterations.
- Keys — error text is redacted to
[redacted]before it is returned. - Model lists are dynamic; the framework keeps no allow-list.
Migrating from OpenCode: if
providers.jsonis absent but~/.local/share/opencode/auth.jsonexists, it is converted once, automatically.
Auto-update
Precondition: this section applies only to a source clone. The updater requires
.git and package.json in the repo root, so an npm/npx install never updates
itself and doctor reports update.supported: false.
All three MCP entry points (dist/server.js, dist/bin/ai-cli-mcp.js, and
dist/bin/ai-cli.js mcp) serve normally after the transport connects, then check in the
background ~3 seconds later and apply updates in a subprocess; the new version takes
effect on the next start. Nothing blocks startup, and no running server or job is
killed.
AI_CLI_AUTO_UPDATE |
Behavior |
|---|---|
on (default) |
Check in the background, apply automatically |
check |
Report a new version, do not apply |
off |
Never touch the network; still reads existing state |
Also honored: AI_CLI_UPDATE_CHECK_INTERVAL_SEC (default 3600),
AI_CLI_UPDATE_BRANCH, and AI_CLI_STATE_DIR (default ~/.local/state/ai-cli).
⚠️ Treat a push to master as a deployment: npm test must be green before you
push. This is a public repo, and anyone with write access can trigger that.
A source install that tracks master with auto-update on will try to fetch and apply
it on a later check. It does not always land: a dirty tree, a different tracked
branch (AI_CLI_UPDATE_BRANCH), a non-fast-forward or a held lock stops it before
the pull, and a failed install, build or doctor smoke test triggers a rollback
attempt — which can itself fail. So neither "it went everywhere" nor "it went
nowhere" is safe to assume.
Trust model: what on means
AI_CLI_AUTO_UPDATE=on (the default) means trusting every push to origin:
- Anyone who can push to
origin— or anyone who has taken over the repo owner's GitHub account or token — can get this machine to run their code on its next check, with no action from you. - "Takes effect on the next start" refers to the MCP server switching versions. The new
code already runs during the background update: when package files change,
npm installruns the lifecycle scripts of the dependencies and of this repo (such asprepare), and the build anddoctorsmoke test that follow run the new code too. - This server is powerful to begin with: a
runwithoutcapabilitiesstarts each agent with its vendor's permission bypass (--dangerously-skip-permissionsfor claude and agy,--dangerously-bypass-approvals-and-sandboxfor codex,--always-approvefor Grok), using your local CLI logins.
So:
- Use
ononly on machines where you trust whoever can write toorigin, such as your own machines tracking your own repo. - Otherwise —
originpoints at someone else's repo, the machine is shared, or you are not sure who has write access — usecheck: it only reports a new version, and you review the changes before applying them yourself. - On the maintainer side: turn on two-factor authentication for the GitHub account, give
write access only to the people who need it, and protect the
masterbranch.
npm/npx installs are unaffected; they never update themselves.
Architecture
src/
├─ agents/ one file per AI — this is the extension point
│ ├─ types.ts AgentDefinition, the core contract
│ ├─ registry.ts central registry
│ └─ claude.ts · codex.ts · grok.ts · antigravity.ts · direct-api.ts
├─ core/ the framework; untouched when adding an agent
│ ├─ command-builder.ts model routing and command assembly
│ ├─ process-service.ts MCP job management
│ ├─ file-process-service.ts file-backed job management (CLI)
│ ├─ job-store.ts · job-runner.ts shared durable store and detached runner
│ ├─ pty-runner.ts ConPTY, for CLIs that need a real TTY
│ ├─ circuit-breaker.ts start-rate and duplicate-prompt breaker
│ ├─ updater.ts background check, subprocess apply, rollback
│ └─ user-config.ts · binary-resolver.ts · peek.ts · doctor.ts
├─ models/ model catalog, aliases, provenance and disk cache
├─ plugins/ quota lookup bridge
└─ app/ mcp.ts (MCP server) · cli.ts (command line)
The split is the point: agents/ is data about each CLI, core/ is the machinery.
A new backend never requires editing the machinery.
Claude, Codex, Grok and Windows Antigravity (ConPTY) jobs run under a detached runner and outlive their MCP server or
CLI launcher. A restarted MCP server recovers only jobs dispatched by the same
principal process, verified by PID plus OS creation time; recovered results include
recovered: true. Recovery observes existing work and never reruns or retries it.
A recorded exit determines completion; a live verified runner stays running; if
both runner and worker are gone without an exit record, the job becomes lost
and its output remains available. The public PID identifies the runner.
POSIX Antigravity and direct-api remain unrecoverable across MCP
restarts and cannot be adopted. Windows ConPTY workers are held by the detached runner;
PTY stdout combines both streams and strips ANSI, while stderr holds runner errors.
direct-api starts no subprocess at all: on the CLI it runs in-process and blocks until the request
finishes, while the MCP server still returns a PID immediately.
The principal is the first ancestor outside the package launcher chain. Command-line
inspection skips npx-cli.js, npm-cli.js exec/x, and the cmd.exe or POSIX sh
wrappers invoking npx/npm exec. It retains a Node process running Claude Code itself.
This supports both direct Node registration and the npx registration shown above.
Unavailable server/principal identity is reported on stderr and retried on later checks.
Command lines are used for identification only and are never saved in job metadata.
Watching jobs across sessions
Open another terminal using the same state directory:
ai-cli jobs # One table across MCP sessions and CLI jobs
ai-cli jobs --watch # Refresh every 2 seconds; Ctrl+C exits
ai-cli jobs --json # JSON array with metadata and timing
ai-cli jobs --running # Only running jobs; combines with watch/json
ai-cli jobs --watch --json # One compact JSON array per refresh (NDJSON)
ai-cli adopt # Read-only JSON candidates for MCP session adoption
The table shows status (spinner / ✓ / ✗ / ? for lost), agent, resolved model and
reasoning effort, a 40-character task summary, elapsed m:ss, the last short event,
and the dispatching process's parent name plus the ai-cli PID. Narrow terminals
truncate columns without wrapping. Parent names can be shells or Node launchers;
if the OS cannot provide a name, the table shows unknown.
Claude/Codex/Grok and Windows agy (ConPTY) use AI_CLI_STATE_DIR/jobs/<UUID>/ (default
~/.local/state/ai-cli/jobs/). meta.json records the agent, model, effort,
40-character task summary, work folder, session and principal/server/runner/worker
identities. It contains no full prompt. The runner writes metadata and exit.json
atomically once each; the exit record includes code, signal, end time, timeout and
kill flags. For lost jobs, the writer records the first confirmed loss in
lost.json so a long-silent job's output still gets 30 minutes of retention.
Worker stdout/stderr go directly to open stdout.log/stderr.log file descriptors.
On Windows, a short-lived launcher starts the runner and exits before the job is
dispatched. The returned PID remains the runner PID. This keeps durable jobs alive
when Claude Code terminates the MCP server's entire process tree on shutdown.
POSIX keeps its existing detached runner; POSIX agy and direct-api remain unrecoverable.
A temporary stdin file carries the prompt independently of the server
and is removed on completion. Logs can contain prompts or model response excerpts.
bootstrap.json contains only startup metadata and the runner PID, so a failed meta
write does not make the job invisible. Runner identity lookup retries three times with
250/500 ms backoff; failure records a failed exit, removes stdin, and starts no worker.
A job still missing meta after 30 seconds is reported as lost. If meta cannot be saved
after spawning, the runner stops its newly spawned worker and attempts an error exit.
Grok's prompt file moves into this job directory and follows job retention; unclaimed
staging files have exit/timer cleanup and are swept by GC after six hours.
Readers retain byte offsets, parser progress and a 4 KiB tail per stream. They read
only appended bytes; verbose results read at most 8 MiB per stream on demand and return
stdoutTruncated/stderrTruncated byte counts when capped. Oversized parsed replies
also carry agentOutputTruncated. Read the disk logs for larger output. ai-cli run,
ps, result, wait and jobs use this same store, so durable jobs need no second
monitoring snapshot. CLI run waits up to 15 seconds for the metadata handshake;
timeout still returns PID/jobId, a warning and exit 0. Track that job; do not dispatch again.
jobs is read-only and also works with AI_CLI_WORKER=1.
tkflyc-ai-cli MCP questions
How can I add this to Claude Code?
You can add it using the command: claude mcp add ai-cli -s user -- npx -y @tkflyc/ai-cli-mcp.
Can I use any OpenAI-compatible API?
Yes, the direct-api path allows any OpenAI-compatible /chat/completions endpoint like OpenRouter, DeepSeek, or local Ollama to work as a tool.
Does it support local models?
Yes, you can configure local providers such as Ollama by specifying a base URL in your providers configuration.