Skip to main content
Glama

@qodalis/mcp-cli

A resilient Model Context Protocol server that gives Claude Code a Qodalis CLI server: its CLI commands, files, data explorer, jobs and plugin tools.

Every Qodalis CLI server (.NET, Node or Python) can expose an MCP endpoint at <server>/api/qcli/mcp. mcp-cli runs on your machine as a stdio MCP server and proxies to that endpoint: Claude Code sees exactly the server's tools, with the permissions of the account you configure. It handles sign-in, reconnects after network drops and lets you narrow what the assistant may use.

Highlights

  • No tool-specific code. Tools come from the server's tools/list and are passed through unchanged (names, schemas, annotations); calls are forwarded. New commands on the server show up without updating the proxy.

  • Token or password. Use an API token (QCLI_TOKEN), or sign in with QCLI_USER / QCLI_PASSWORD; an expired session is renewed automatically on the next call.

  • Read-only mode (QCLI_READ_ONLY=true) exposes only the tools the server marks read-only, and refuses calls to anything else.

  • Client-side allow and deny lists (QCLI_TOOLS, QCLI_EXCLUDE, trailing * allowed) on top of the server's own permissions.

  • Self-healing connection. It connects on first use, never caches a failed connect, retries once when the connection drops, and fails fast (8 s) when the server is unreachable, with a message that says what to check.

  • Clear errors: a rejected token, MCP not enabled on the server, a missing permission or an unreachable host each come back as one actionable sentence.

Related MCP server: Claude Code Manager WAN

Quick start

npx -y @qodalis/mcp-cli --help

The server must have MCP enabled (mcp option; see MCP endpoint). Default ports: .NET 8046, Node 8047, Python 8048.

Claude Code

claude mcp add qcli -- npx -y @qodalis/mcp-cli

then set the environment in ~/.claude.json:

{
  "mcpServers": {
    "qcli": {
      "command": "npx",
      "args": ["-y", "@qodalis/mcp-cli"],
      "env": {
        "QCLI_URL": "http://localhost:8047",
        "QCLI_TOKEN": "qcli_pat_..."
      }
    }
  }
}

or sign in with a user name and password instead of a token:

"env": {
  "QCLI_URL": "http://localhost:8047",
  "QCLI_USER": "ops",
  "QCLI_PASSWORD": "********"
}

The same in one command, with -e flags:

claude mcp add qcli -e QCLI_URL=http://localhost:8047 -e QCLI_TOKEN=qcli_pat_... -- npx -y @qodalis/mcp-cli

Add --scope user to make it available in every project. With --scope project the entry goes into the project's .mcp.json, which is usually committed: keep the secret out of it with "QCLI_TOKEN": "${QCLI_TOKEN}" and set the variable in your shell. A .env file works too:

{ "command": "npx", "args": ["-y", "@qodalis/mcp-cli", "--env-file", "/path/to/.env"] }

Pin a version in shared configs (@qodalis/mcp-cli@0.1.0) so an upgrade is a deliberate change.

Getting a token

In the web CLI connected to the server (users plugin), signed in as the account the assistant should act as:

token create claude --expires=90
# token create claude --permissions=commands:execute,files:read   (narrower scope)

The token (qcli_pat_…) is shown once. Without the web CLI, sign in and create one with curl:

SESSION=$(curl -s http://localhost:8047/api/v1/qcli/sessions \
  -H 'Content-Type: application/json' -d '{"username":"ops","password":"..."}' | jq -r .token)
curl -s http://localhost:8047/api/v1/qcli/tokens -H "Authorization: Bearer $SESSION" \
  -H 'Content-Type: application/json' -d '{"name":"claude","expiresInDays":90}' | jq -r .token
curl -s -X DELETE http://localhost:8047/api/v1/qcli/sessions/current -H "Authorization: Bearer $SESSION"

A server without the users plugin protects its endpoint with a static token (its mcp.token option): put that value in QCLI_TOKEN. If it sets none, the endpoint is open and no auth variable is needed.

Configuration

Every setting is an environment variable. QODALIS_CLI_* names are accepted as aliases for every key.

Variable

Default

Description

QCLI_URL

required

Server base URL, e.g. https://cli.example.com (a pasted …/api/qcli/mcp URL is accepted).

QCLI_TOKEN

none

API token (token create in the web CLI) or the server's static mcp.token.

QCLI_USER

none

Sign in with this user name instead of a token (needs QCLI_PASSWORD).

QCLI_PASSWORD

none

Password for QCLI_USER. Never logged.

QCLI_READ_ONLY

false

Expose and allow only tools annotated readOnlyHint: true. See read-only mode.

QCLI_TOOLS

all

Comma-separated allow-list of tool names; a trailing * matches a prefix (fs_*).

QCLI_EXCLUDE

none

Comma-separated deny-list, same syntax, applied after QCLI_TOOLS.

QCLI_CONNECTION_TIMEOUT

8000

Milliseconds to wait for the TCP/TLS connection before reporting the server unreachable.

QCLI_REQUEST_TIMEOUT

60000

Milliseconds a single request (tools/list, tools/call) may take.

QCLI_TRUST_CERT

false

Accept a self-signed TLS certificate.

QCLI_ENV_FILE

none

Path to a .env file (same as --env-file). Variables already set in the environment win.

Set either QCLI_TOKEN or QCLI_USER + QCLI_PASSWORD, not both.

Tools

The tools are whatever the server exposes to your account. A typical server offers:

Tools

Available when

server_info

always: version, enabled features

one tool per CLI command (weather, weather_current, ...)

always

fs_list, fs_stat, fs_read, fs_write, fs_mkdir, fs_delete

the server's filesystem API is enabled

data_sources, data_schema, data_query

the data explorer is enabled

jobs_list, jobs_get, jobs_history, jobs_trigger, ...

the jobs plugin is installed

plugin tools

a server plugin contributes them

Each one is limited by the account's permissions on the server (for example commands:execute, files:read / files:write, data:query, jobs:manage), and by the token's scope when it was created with --permissions.

Read-only mode

With QCLI_READ_ONLY=true the proxy lists only the tools the server annotates readOnlyHint: true (such as server_info, fs_list, fs_read, data_schema, jobs_list) and refuses calls to any other tool before they reach the server. Command tools carry no read-only annotation, so they are hidden in this mode; add the ones you want with a separate, non-read-only entry and QCLI_TOOLS.

The filter is defence in depth. The real boundary is the account: give the assistant a user (or a token scope) with only the permissions it should have.

Several servers

Add one entry per server (and per access level) with a distinct name:

{
  "mcpServers": {
    "qcli-dev": {
      "command": "npx",
      "args": ["-y", "@qodalis/mcp-cli"],
      "env": { "QCLI_URL": "http://localhost:8047", "QCLI_TOKEN": "qcli_pat_dev..." }
    },
    "qcli-prod": {
      "command": "npx",
      "args": ["-y", "@qodalis/mcp-cli"],
      "env": {
        "QCLI_URL": "https://cli.example.com",
        "QCLI_TOKEN": "qcli_pat_prod...",
        "QCLI_READ_ONLY": "true"
      }
    },
    "qcli-prod-jobs": {
      "command": "npx",
      "args": ["-y", "@qodalis/mcp-cli"],
      "env": {
        "QCLI_URL": "https://cli.example.com",
        "QCLI_TOKEN": "qcli_pat_prod...",
        "QCLI_TOOLS": "jobs_*,server_info",
        "QCLI_EXCLUDE": "jobs_cancel"
      }
    }
  }
}

Troubleshooting

claude mcp list shows whether each server connects; inside a session /mcp shows its status and tools and lets you reconnect. The proxy logs to stderr (claude --debug shows it).

Message

What to do

… answered 401 (not authenticated): QCLI_TOKEN is wrong, expired or revoked

Create a new token (token create claude) and update QCLI_TOKEN. token list in the web CLI shows expiry dates.

… rejected QCLI_USER / QCLI_PASSWORD

Check the credentials; the account may be disabled or locked out after failed sign-ins.

… answered 403 …

The account lacks commands:execute, or the server rejected the request origin.

MCP is not enabled on this server (enable the mcp option)

Turn on the server's mcp option and restart it; check QCLI_URL (base URL, not a page of the web app).

Cannot reach … Check QCLI_URL / VPN.

The server is down, the URL or port is wrong, or the VPN is disconnected. Retry the call once it is back: the proxy reconnects by itself.

… did not answer within 60000 ms

A slow command; raise QCLI_REQUEST_TIMEOUT.

A tool is missing

The account (or token scope) lacks its permission, the feature is not enabled on the server, or QCLI_READ_ONLY / QCLI_TOOLS / QCLI_EXCLUDE hide it.

QCLI_URL … is required at startup

The env block is missing or the variable name is misspelled; npx -y @qodalis/mcp-cli --help lists every variable.

How reconnection works

  1. The proxy connects to the endpoint on the first tools/list or tools/call, not at startup, so Claude Code starts even when the server is down.

  2. A failed connect is not cached: the next call tries again.

  3. A connection-level error (refused, reset, socket closed, DNS failure) drops the connection and retries the request once on a fresh one. A connect timeout is not retried, so an unreachable host fails within QCLI_CONNECTION_TIMEOUT.

  4. With QCLI_USER / QCLI_PASSWORD, a 401 signs in again and retries once.

  5. The tool list is cached for 60 s; a call to a tool the proxy does not know yet refreshes it first, so new server commands work without a restart.

Security notes

  • Tokens and passwords are read from the environment, sent only to QCLI_URL, and never written to logs or tool results.

  • Prefer an API token with a narrow --permissions scope and an expiry over a password; revoke it with token revoke <id> when it is no longer needed.

  • Use https:// for anything but localhost. QCLI_TRUST_CERT=true disables certificate verification for this process; use it only for self-signed certificates you control.

  • Nothing is written to stdout except the MCP protocol stream.

See SECURITY.md for reporting vulnerabilities.

Library use

The pieces are exported for embedding:

import { createProxyServer, loadConfig, RemoteServer } from '@qodalis/mcp-cli';

const config = loadConfig(process.env);
const remote = new RemoteServer(config, { clientInfo: { name: 'my-app', version: '1.0.0' } });
const server = createProxyServer(remote, config); // a low-level MCP Server: connect any transport

Development

npm install
npm run build
npm test             # unit tests and stdio end-to-end tests against a fake server (build first)
npm run typecheck
npm run check-format
npm run dev -- --env-file .env   # run from source

See CONTRIBUTING.md.

License

MIT © Qodalis Solutions SRL

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables AI agents to interact programmatically with Claude Code CLI, managing sessions, streaming outputs, and handling permission requests.
    7
    7 npm
    1
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables ISLI agents and MCP clients to dispatch natural-language coding and terminal tasks to a locally-installed Claude Code CLI, supporting both one-shot execution and persistent sessions with workspace and security controls.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    Lets Codex delegate coding and repository work to an installed Claude Code CLI with permission-aware inspect/write access, model and effort selection, resumable and cloud-attached sessions, and durable synchronous or asynchronous jobs.
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Bridges MCP clients such as Claude Code and Cursor to independent Codex app-server sessions, letting them start, poll, steer, review, compact, cancel, and answer prompts for asynchronous Codex tasks over local stdio or a remote authenticated WebSocket. Runs read-only by default, with workspace-write sandboxing only when a user explicitly enables it, and reports job status, model listing, and progress back to the client.
    10
    1
    MIT