@qodalis/mcp-cli 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [0.1.0] - 2026-10-15
8
+
9
+ ### Added
10
+
11
+ - First release: a stdio MCP server that proxies Claude Code (or any MCP client)
12
+ to a Qodalis CLI server's MCP endpoint (`<QCLI_URL>/api/qcli/mcp`). Tools are
13
+ taken from the server's `tools/list` and passed through unchanged; calls are
14
+ forwarded.
15
+ - Authentication with an API token or the server's static MCP token
16
+ (`QCLI_TOKEN`), or with `QCLI_USER` / `QCLI_PASSWORD` (signs in again after a
17
+ 401).
18
+ - `QCLI_READ_ONLY`, `QCLI_TOOLS` and `QCLI_EXCLUDE` client-side tool filters.
19
+ - Resilience: connect on first use, failed connects are not cached, one retry on
20
+ connection loss, fast failure (`QCLI_CONNECTION_TIMEOUT`) with actionable
21
+ messages for unreachable servers, rejected credentials, missing permissions and
22
+ MCP not enabled; 60 s tool-list cache refreshed for unknown tools.
23
+ - `QCLI_REQUEST_TIMEOUT`, `QCLI_TRUST_CERT`, `--env-file` / `QCLI_ENV_FILE`,
24
+ `QODALIS_CLI_*` aliases, `--help`, `--version`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Qodalis Solutions SRL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,274 @@
1
- # Temporary Holding Version
1
+ # @qodalis/mcp-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A resilient [Model Context Protocol](https://modelcontextprotocol.io) server that
4
+ gives [Claude Code](https://code.claude.com) a [Qodalis CLI](https://cli.qodalis.com)
5
+ server: its CLI commands, files, data explorer, jobs and plugin tools.
6
+
7
+ Every Qodalis CLI server (.NET, Node or Python) can expose an MCP endpoint at
8
+ `<server>/api/qcli/mcp`. `mcp-cli` runs on your machine as a stdio MCP server
9
+ and proxies to that endpoint: Claude Code sees exactly the server's tools, with
10
+ the permissions of the account you configure. It handles sign-in, reconnects
11
+ after network drops and lets you narrow what the assistant may use.
12
+
13
+ ## Highlights
14
+
15
+ - **No tool-specific code.** Tools come from the server's `tools/list` and are
16
+ passed through unchanged (names, schemas, annotations); calls are forwarded.
17
+ New commands on the server show up without updating the proxy.
18
+ - **Token or password.** Use an API token (`QCLI_TOKEN`), or sign in with
19
+ `QCLI_USER` / `QCLI_PASSWORD`; an expired session is renewed automatically on
20
+ the next call.
21
+ - **Read-only mode** (`QCLI_READ_ONLY=true`) exposes only the tools the server
22
+ marks read-only, and refuses calls to anything else.
23
+ - **Client-side allow and deny lists** (`QCLI_TOOLS`, `QCLI_EXCLUDE`, trailing
24
+ `*` allowed) on top of the server's own permissions.
25
+ - **Self-healing connection.** It connects on first use, never caches a failed
26
+ connect, retries once when the connection drops, and fails fast (8 s) when the
27
+ server is unreachable, with a message that says what to check.
28
+ - **Clear errors**: a rejected token, MCP not enabled on the server, a missing
29
+ permission or an unreachable host each come back as one actionable sentence.
30
+
31
+ ## Quick start
32
+
33
+ ```bash
34
+ npx -y @qodalis/mcp-cli --help
35
+ ```
36
+
37
+ The server must have MCP enabled (`mcp` option; see
38
+ [MCP endpoint](https://cli.qodalis.com/docs/server-integration#mcp-endpoint)).
39
+ Default ports: .NET `8046`, Node `8047`, Python `8048`.
40
+
41
+ ### Claude Code
42
+
43
+ ```bash
44
+ claude mcp add qcli -- npx -y @qodalis/mcp-cli
45
+ ```
46
+
47
+ then set the environment in `~/.claude.json`:
48
+
49
+ ```json
50
+ {
51
+ "mcpServers": {
52
+ "qcli": {
53
+ "command": "npx",
54
+ "args": ["-y", "@qodalis/mcp-cli"],
55
+ "env": {
56
+ "QCLI_URL": "http://localhost:8047",
57
+ "QCLI_TOKEN": "qcli_pat_..."
58
+ }
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ or sign in with a user name and password instead of a token:
65
+
66
+ ```json
67
+ "env": {
68
+ "QCLI_URL": "http://localhost:8047",
69
+ "QCLI_USER": "ops",
70
+ "QCLI_PASSWORD": "********"
71
+ }
72
+ ```
73
+
74
+ The same in one command, with `-e` flags:
75
+
76
+ ```bash
77
+ claude mcp add qcli -e QCLI_URL=http://localhost:8047 -e QCLI_TOKEN=qcli_pat_... -- npx -y @qodalis/mcp-cli
78
+ ```
79
+
80
+ Add `--scope user` to make it available in every project. With
81
+ `--scope project` the entry goes into the project's `.mcp.json`, which is
82
+ usually committed: keep the secret out of it with
83
+ `"QCLI_TOKEN": "${QCLI_TOKEN}"` and set the variable in your shell. A `.env`
84
+ file works too:
85
+
86
+ ```json
87
+ { "command": "npx", "args": ["-y", "@qodalis/mcp-cli", "--env-file", "/path/to/.env"] }
88
+ ```
89
+
90
+ Pin a version in shared configs (`@qodalis/mcp-cli@0.1.0`) so an upgrade is a
91
+ deliberate change.
92
+
93
+ ### Getting a token
94
+
95
+ In the web CLI connected to the server (users plugin), signed in as the account
96
+ the assistant should act as:
97
+
98
+ ```bash
99
+ token create claude --expires=90
100
+ # token create claude --permissions=commands:execute,files:read (narrower scope)
101
+ ```
102
+
103
+ The token (`qcli_pat_…`) is shown once. Without the web CLI, sign in and create
104
+ one with curl:
105
+
106
+ ```bash
107
+ SESSION=$(curl -s http://localhost:8047/api/v1/qcli/sessions \
108
+ -H 'Content-Type: application/json' -d '{"username":"ops","password":"..."}' | jq -r .token)
109
+ curl -s http://localhost:8047/api/v1/qcli/tokens -H "Authorization: Bearer $SESSION" \
110
+ -H 'Content-Type: application/json' -d '{"name":"claude","expiresInDays":90}' | jq -r .token
111
+ curl -s -X DELETE http://localhost:8047/api/v1/qcli/sessions/current -H "Authorization: Bearer $SESSION"
112
+ ```
113
+
114
+ A server **without** the users plugin protects its endpoint with a static token
115
+ (its `mcp.token` option): put that value in `QCLI_TOKEN`. If it sets none, the
116
+ endpoint is open and no auth variable is needed.
117
+
118
+ ## Configuration
119
+
120
+ Every setting is an environment variable. `QODALIS_CLI_*` names are accepted as
121
+ aliases for every key.
122
+
123
+ | Variable | Default | Description |
124
+ | ------------------------- | -------- | -------------------------------------------------------------------------------------------------- |
125
+ | `QCLI_URL` | required | Server base URL, e.g. `https://cli.example.com` (a pasted `…/api/qcli/mcp` URL is accepted). |
126
+ | `QCLI_TOKEN` | none | API token (`token create` in the web CLI) or the server's static `mcp.token`. |
127
+ | `QCLI_USER` | none | Sign in with this user name instead of a token (needs `QCLI_PASSWORD`). |
128
+ | `QCLI_PASSWORD` | none | Password for `QCLI_USER`. Never logged. |
129
+ | `QCLI_READ_ONLY` | `false` | Expose and allow only tools annotated `readOnlyHint: true`. See [read-only mode](#read-only-mode). |
130
+ | `QCLI_TOOLS` | all | Comma-separated allow-list of tool names; a trailing `*` matches a prefix (`fs_*`). |
131
+ | `QCLI_EXCLUDE` | none | Comma-separated deny-list, same syntax, applied after `QCLI_TOOLS`. |
132
+ | `QCLI_CONNECTION_TIMEOUT` | `8000` | Milliseconds to wait for the TCP/TLS connection before reporting the server unreachable. |
133
+ | `QCLI_REQUEST_TIMEOUT` | `60000` | Milliseconds a single request (`tools/list`, `tools/call`) may take. |
134
+ | `QCLI_TRUST_CERT` | `false` | Accept a self-signed TLS certificate. |
135
+ | `QCLI_ENV_FILE` | none | Path to a `.env` file (same as `--env-file`). Variables already set in the environment win. |
136
+
137
+ Set either `QCLI_TOKEN` or `QCLI_USER` + `QCLI_PASSWORD`, not both.
138
+
139
+ ## Tools
140
+
141
+ The tools are whatever the server exposes to your account. A typical server
142
+ offers:
143
+
144
+ | Tools | Available when |
145
+ | -------------------------------------------------------------------- | -------------------------------------- |
146
+ | `server_info` | always: version, enabled features |
147
+ | one tool per CLI command (`weather`, `weather_current`, ...) | always |
148
+ | `fs_list`, `fs_stat`, `fs_read`, `fs_write`, `fs_mkdir`, `fs_delete` | the server's filesystem API is enabled |
149
+ | `data_sources`, `data_schema`, `data_query` | the data explorer is enabled |
150
+ | `jobs_list`, `jobs_get`, `jobs_history`, `jobs_trigger`, ... | the jobs plugin is installed |
151
+ | plugin tools | a server plugin contributes them |
152
+
153
+ Each one is limited by the account's permissions on the server (for example
154
+ `commands:execute`, `files:read` / `files:write`, `data:query`, `jobs:manage`),
155
+ and by the token's scope when it was created with `--permissions`.
156
+
157
+ ## Read-only mode
158
+
159
+ With `QCLI_READ_ONLY=true` the proxy lists only the tools the server annotates
160
+ `readOnlyHint: true` (such as `server_info`, `fs_list`, `fs_read`,
161
+ `data_schema`, `jobs_list`) and refuses calls to any other tool before they
162
+ reach the server. Command tools carry no read-only annotation, so they are
163
+ hidden in this mode; add the ones you want with a separate, non-read-only entry
164
+ and `QCLI_TOOLS`.
165
+
166
+ The filter is defence in depth. The real boundary is the account: give the
167
+ assistant a user (or a token scope) with only the permissions it should have.
168
+
169
+ ## Several servers
170
+
171
+ Add one entry per server (and per access level) with a distinct name:
172
+
173
+ ```json
174
+ {
175
+ "mcpServers": {
176
+ "qcli-dev": {
177
+ "command": "npx",
178
+ "args": ["-y", "@qodalis/mcp-cli"],
179
+ "env": { "QCLI_URL": "http://localhost:8047", "QCLI_TOKEN": "qcli_pat_dev..." }
180
+ },
181
+ "qcli-prod": {
182
+ "command": "npx",
183
+ "args": ["-y", "@qodalis/mcp-cli"],
184
+ "env": {
185
+ "QCLI_URL": "https://cli.example.com",
186
+ "QCLI_TOKEN": "qcli_pat_prod...",
187
+ "QCLI_READ_ONLY": "true"
188
+ }
189
+ },
190
+ "qcli-prod-jobs": {
191
+ "command": "npx",
192
+ "args": ["-y", "@qodalis/mcp-cli"],
193
+ "env": {
194
+ "QCLI_URL": "https://cli.example.com",
195
+ "QCLI_TOKEN": "qcli_pat_prod...",
196
+ "QCLI_TOOLS": "jobs_*,server_info",
197
+ "QCLI_EXCLUDE": "jobs_cancel"
198
+ }
199
+ }
200
+ }
201
+ }
202
+ ```
203
+
204
+ ## Troubleshooting
205
+
206
+ `claude mcp list` shows whether each server connects; inside a session `/mcp`
207
+ shows its status and tools and lets you reconnect. The proxy logs to stderr
208
+ (`claude --debug` shows it).
209
+
210
+ | Message | What to do |
211
+ | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
212
+ | `… 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. |
213
+ | `… rejected QCLI_USER / QCLI_PASSWORD` | Check the credentials; the account may be disabled or locked out after failed sign-ins. |
214
+ | `… answered 403 …` | The account lacks `commands:execute`, or the server rejected the request origin. |
215
+ | `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). |
216
+ | `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. |
217
+ | `… did not answer within 60000 ms` | A slow command; raise `QCLI_REQUEST_TIMEOUT`. |
218
+ | 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. |
219
+ | `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. |
220
+
221
+ ## How reconnection works
222
+
223
+ 1. The proxy connects to the endpoint on the first `tools/list` or
224
+ `tools/call`, not at startup, so Claude Code starts even when the server is
225
+ down.
226
+ 2. A failed connect is not cached: the next call tries again.
227
+ 3. A connection-level error (refused, reset, socket closed, DNS failure) drops
228
+ the connection and retries the request once on a fresh one. A connect timeout
229
+ is not retried, so an unreachable host fails within `QCLI_CONNECTION_TIMEOUT`.
230
+ 4. With `QCLI_USER` / `QCLI_PASSWORD`, a 401 signs in again and retries once.
231
+ 5. The tool list is cached for 60 s; a call to a tool the proxy does not know
232
+ yet refreshes it first, so new server commands work without a restart.
233
+
234
+ ## Security notes
235
+
236
+ - Tokens and passwords are read from the environment, sent only to `QCLI_URL`,
237
+ and never written to logs or tool results.
238
+ - Prefer an API token with a narrow `--permissions` scope and an expiry over a
239
+ password; revoke it with `token revoke <id>` when it is no longer needed.
240
+ - Use `https://` for anything but `localhost`. `QCLI_TRUST_CERT=true` disables
241
+ certificate verification for this process; use it only for self-signed
242
+ certificates you control.
243
+ - Nothing is written to stdout except the MCP protocol stream.
244
+
245
+ See [SECURITY.md](SECURITY.md) for reporting vulnerabilities.
246
+
247
+ ## Library use
248
+
249
+ The pieces are exported for embedding:
250
+
251
+ ```ts
252
+ import { createProxyServer, loadConfig, RemoteServer } from '@qodalis/mcp-cli';
253
+
254
+ const config = loadConfig(process.env);
255
+ const remote = new RemoteServer(config, { clientInfo: { name: 'my-app', version: '1.0.0' } });
256
+ const server = createProxyServer(remote, config); // a low-level MCP Server: connect any transport
257
+ ```
258
+
259
+ ## Development
260
+
261
+ ```bash
262
+ npm install
263
+ npm run build
264
+ npm test # unit tests and stdio end-to-end tests against a fake server (build first)
265
+ npm run typecheck
266
+ npm run check-format
267
+ npm run dev -- --env-file .env # run from source
268
+ ```
269
+
270
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
271
+
272
+ ## License
273
+
274
+ [MIT](LICENSE) © Qodalis Solutions SRL
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ import { serveStdio } from '@modelcontextprotocol/server/stdio';
3
+ import { ConfigError, describeConfig, ENV_FILE_VARIABLES, loadConfig } from './config.js';
4
+ import { applyEnvFile } from './env-file.js';
5
+ import { RemoteServer } from './remote.js';
6
+ import { createProxyServer, SERVER_NAME, SERVER_VERSION } from './server.js';
7
+ const SHUTDOWN_GRACE_MS = 5000;
8
+ const usage = `Proxies Claude Code (or any MCP client) to a Qodalis CLI server's MCP endpoint
9
+ (<QCLI_URL>/api/qcli/mcp). Configuration is read from the environment (QCLI_* or
10
+ QODALIS_CLI_* names):
11
+ QCLI_URL (required) server base URL, e.g. http://localhost:8047
12
+ QCLI_TOKEN API token (token create in the web CLI) or the server's mcp token
13
+ QCLI_USER, QCLI_PASSWORD sign in with a password instead (signs in again on 401)
14
+ QCLI_READ_ONLY (false) only tools the server marks read-only
15
+ QCLI_TOOLS comma-separated allow-list of tool names (trailing * allowed)
16
+ QCLI_EXCLUDE comma-separated deny-list, applied after QCLI_TOOLS
17
+ QCLI_CONNECTION_TIMEOUT (8000 ms) QCLI_REQUEST_TIMEOUT (60000 ms)
18
+ QCLI_TRUST_CERT (false) accept a self-signed TLS certificate
19
+ QCLI_ENV_FILE path to a .env file (same as --env-file)
20
+ `;
21
+ const log = (message) => {
22
+ // Anything written to stdout corrupts the JSON-RPC stream: all logging goes to stderr.
23
+ process.stderr.write(`[${SERVER_NAME}] ${message}\n`);
24
+ };
25
+ const parseArgs = (argv) => {
26
+ const result = {};
27
+ for (let i = 0; i < argv.length; i++) {
28
+ const arg = argv[i];
29
+ if (arg === '--help' || arg === '-h') {
30
+ process.stdout.write(`Usage: mcp-cli [--env-file <path>] [--help] [--version]\n\n${usage}`);
31
+ process.exit(0);
32
+ }
33
+ if (arg === '--version' || arg === '-v') {
34
+ process.stdout.write(`${SERVER_VERSION}\n`);
35
+ process.exit(0);
36
+ }
37
+ if (arg === '--env-file') {
38
+ const value = argv[i + 1];
39
+ if (!value) {
40
+ throw new ConfigError('--env-file requires a path.');
41
+ }
42
+ result.envFile = value;
43
+ i++;
44
+ continue;
45
+ }
46
+ throw new ConfigError(`Unknown argument "${arg}".`);
47
+ }
48
+ return result;
49
+ };
50
+ const main = () => {
51
+ try {
52
+ const args = parseArgs(process.argv.slice(2));
53
+ const envFile = args.envFile ?? ENV_FILE_VARIABLES.map((name) => process.env[name]).find(Boolean);
54
+ if (envFile) {
55
+ applyEnvFile(envFile, process.env);
56
+ }
57
+ const config = loadConfig(process.env);
58
+ if (config.trustCert) {
59
+ log('QCLI_TRUST_CERT=true: TLS certificates are not verified');
60
+ }
61
+ const remote = new RemoteServer(config, {
62
+ clientInfo: { name: SERVER_NAME, version: SERVER_VERSION },
63
+ log,
64
+ });
65
+ const handle = serveStdio(() => createProxyServer(remote, config), {
66
+ onerror: (error) => log(`transport error: ${error.message}`),
67
+ });
68
+ log(`v${SERVER_VERSION} ready for ${describeConfig(config)}; connects on first use`);
69
+ let shuttingDown = false;
70
+ const shutdown = async (reason) => {
71
+ if (shuttingDown) {
72
+ return;
73
+ }
74
+ shuttingDown = true;
75
+ log(`shutting down (${reason})`);
76
+ setTimeout(() => process.exit(0), SHUTDOWN_GRACE_MS).unref();
77
+ await handle.close().catch(() => undefined);
78
+ await remote.close();
79
+ process.exit(0);
80
+ };
81
+ process.once('SIGINT', () => void shutdown('SIGINT'));
82
+ process.once('SIGTERM', () => void shutdown('SIGTERM'));
83
+ process.stdin.once('end', () => void shutdown('stdin closed'));
84
+ }
85
+ catch (error) {
86
+ const message = error instanceof Error ? error.message : String(error);
87
+ log(`fatal: ${message}`);
88
+ process.exit(1);
89
+ }
90
+ };
91
+ main();
92
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Environment-driven configuration. Primary names use `QCLI_`; `QODALIS_CLI_`
3
+ * is accepted as an alias for every key.
4
+ */
5
+ export type Env = Record<string, string | undefined>;
6
+ export declare class ConfigError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ /** How the proxy authenticates to the Qodalis CLI server. */
10
+ export type AuthConfig = {
11
+ kind: 'none';
12
+ } | {
13
+ kind: 'token';
14
+ token: string;
15
+ } | {
16
+ kind: 'password';
17
+ password: string;
18
+ user: string;
19
+ };
20
+ export type Config = {
21
+ auth: AuthConfig;
22
+ connectionTimeoutMs: number;
23
+ /** Client-side deny-list (trailing `*` allowed), applied after `tools` */
24
+ exclude: string[];
25
+ /** Expose and allow only tools annotated `readOnlyHint: true` */
26
+ readOnly: boolean;
27
+ requestTimeoutMs: number;
28
+ /** Client-side allow-list (trailing `*` allowed); empty = every tool */
29
+ tools: string[];
30
+ /** Accept self-signed TLS certificates */
31
+ trustCert: boolean;
32
+ /** Server base URL, e.g. `https://cli.example.com` (no trailing slash) */
33
+ url: string;
34
+ };
35
+ export declare const PREFIXES: string[];
36
+ /** `QCLI_<suffix>` plus the `QODALIS_CLI_<suffix>` alias. */
37
+ export declare const names: (suffix: string) => string[];
38
+ export declare const ENV_FILE_VARIABLES: string[];
39
+ export type EnvReader = {
40
+ boolean: (names: string[], fallback: boolean) => boolean;
41
+ list: (names: string[]) => string[];
42
+ optional: (names: string[]) => string | undefined;
43
+ positiveInteger: (names: string[], fallback: number) => number;
44
+ required: (names: string[]) => string;
45
+ };
46
+ /** Typed readers over an environment map; the first non-empty name wins. */
47
+ export declare const createEnvReader: (env: Env) => EnvReader;
48
+ /**
49
+ * Canonical server URL: scheme and host, optional base path, no trailing slash.
50
+ * A pasted endpoint URL (`…/api/qcli/mcp`) is accepted and reduced to the base.
51
+ */
52
+ export declare const normalizeUrl: (input: string) => string;
53
+ export declare const loadConfig: (env?: Env) => Config;
54
+ /** One-line summary for the startup log (never contains secrets). */
55
+ export declare const describeConfig: (config: Config) => string;
56
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAErD,qBAAa,WAAY,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI9B;AAED,6DAA6D;AAC7D,MAAM,MAAM,UAAU,GAChB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,MAAM,MAAM,MAAM,GAAG;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,0EAA0E;IAC1E,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,0CAA0C;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,QAAQ,UAA0B,CAAC;AAEhD,6DAA6D;AAC7D,eAAO,MAAM,KAAK,GAAI,QAAQ,MAAM,KAAG,MAAM,EAAqD,CAAC;AAEnG,eAAO,MAAM,kBAAkB,UAAoB,CAAC;AAKpD,MAAM,MAAM,SAAS,GAAG;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,OAAO,KAAK,OAAO,CAAC;IACzD,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,CAAC;IACpC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,GAAG,SAAS,CAAC;IAClD,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/D,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC;CACzC,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,eAAe,GAAI,KAAK,GAAG,KAAG,SAoD1C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,MAY5C,CAAC;AAqBF,eAAO,MAAM,UAAU,GAAI,MAAK,GAAiB,KAAG,MAYnD,CAAC;AAEF,qEAAqE;AACrE,eAAO,MAAM,cAAc,GAAI,QAAQ,MAAM,KAAG,MAe/C,CAAC"}
@@ -0,0 +1,130 @@
1
+ export class ConfigError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'ConfigError';
5
+ }
6
+ }
7
+ export const PREFIXES = ['QCLI', 'QODALIS_CLI'];
8
+ /** `QCLI_<suffix>` plus the `QODALIS_CLI_<suffix>` alias. */
9
+ export const names = (suffix) => PREFIXES.map((prefix) => `${prefix}_${suffix}`);
10
+ export const ENV_FILE_VARIABLES = names('ENV_FILE');
11
+ const TRUE_VALUES = new Set(['1', 'true', 'yes', 'on']);
12
+ const FALSE_VALUES = new Set(['0', 'false', 'no', 'off']);
13
+ /** Typed readers over an environment map; the first non-empty name wins. */
14
+ export const createEnvReader = (env) => {
15
+ const optional = (names) => {
16
+ for (const name of names) {
17
+ const value = env[name]?.trim();
18
+ if (value) {
19
+ return value;
20
+ }
21
+ }
22
+ return undefined;
23
+ };
24
+ const describe = (names) => names.length > 1
25
+ ? `${names[0]} (or its alias ${names.slice(1).join(' / ')})`
26
+ : (names[0] ?? '');
27
+ const required = (names) => {
28
+ const value = optional(names);
29
+ if (!value) {
30
+ throw new ConfigError(`${describe(names)} is required.`);
31
+ }
32
+ return value;
33
+ };
34
+ const boolean = (names, fallback) => {
35
+ const value = optional(names);
36
+ if (value === undefined) {
37
+ return fallback;
38
+ }
39
+ const normalized = value.toLowerCase();
40
+ if (TRUE_VALUES.has(normalized)) {
41
+ return true;
42
+ }
43
+ if (FALSE_VALUES.has(normalized)) {
44
+ return false;
45
+ }
46
+ throw new ConfigError(`${names[0]} must be a boolean (true/false), got "${value}".`);
47
+ };
48
+ const positiveInteger = (names, fallback) => {
49
+ const value = optional(names);
50
+ if (value === undefined) {
51
+ return fallback;
52
+ }
53
+ const parsed = Number(value);
54
+ if (!Number.isInteger(parsed) || parsed <= 0) {
55
+ throw new ConfigError(`${names[0]} must be a positive integer, got "${value}".`);
56
+ }
57
+ return parsed;
58
+ };
59
+ const list = (names) => (optional(names) ?? '')
60
+ .split(',')
61
+ .map((entry) => entry.trim())
62
+ .filter((entry) => entry.length > 0);
63
+ return { boolean, list, optional, positiveInteger, required };
64
+ };
65
+ /**
66
+ * Canonical server URL: scheme and host, optional base path, no trailing slash.
67
+ * A pasted endpoint URL (`…/api/qcli/mcp`) is accepted and reduced to the base.
68
+ */
69
+ export const normalizeUrl = (input) => {
70
+ let url;
71
+ try {
72
+ url = new URL(input);
73
+ }
74
+ catch {
75
+ throw new ConfigError(`QCLI_URL must be an http(s) URL, got "${input}".`);
76
+ }
77
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
78
+ throw new ConfigError(`QCLI_URL must be an http(s) URL, got "${input}".`);
79
+ }
80
+ const path = url.pathname.replace(/\/+$/, '').replace(/\/api\/qcli\/mcp$/i, '');
81
+ return `${url.protocol}//${url.host}${path}`;
82
+ };
83
+ const loadAuth = (read) => {
84
+ const token = read.optional(names('TOKEN'));
85
+ const user = read.optional(names('USER'));
86
+ const password = read.optional(names('PASSWORD'));
87
+ if (token && (user || password)) {
88
+ throw new ConfigError('Set either QCLI_TOKEN or QCLI_USER + QCLI_PASSWORD, not both.');
89
+ }
90
+ if (token) {
91
+ return { kind: 'token', token };
92
+ }
93
+ if (user || password) {
94
+ if (!user || !password) {
95
+ throw new ConfigError('QCLI_USER and QCLI_PASSWORD must be set together.');
96
+ }
97
+ return { kind: 'password', password, user };
98
+ }
99
+ return { kind: 'none' };
100
+ };
101
+ export const loadConfig = (env = process.env) => {
102
+ const read = createEnvReader(env);
103
+ return {
104
+ auth: loadAuth(read),
105
+ connectionTimeoutMs: read.positiveInteger(names('CONNECTION_TIMEOUT'), 8000),
106
+ exclude: read.list(names('EXCLUDE')),
107
+ readOnly: read.boolean(names('READ_ONLY'), false),
108
+ requestTimeoutMs: read.positiveInteger(names('REQUEST_TIMEOUT'), 60000),
109
+ tools: read.list(names('TOOLS')),
110
+ trustCert: read.boolean(names('TRUST_CERT'), false),
111
+ url: normalizeUrl(read.required(names('URL'))),
112
+ };
113
+ };
114
+ /** One-line summary for the startup log (never contains secrets). */
115
+ export const describeConfig = (config) => {
116
+ const auth = config.auth.kind === 'token'
117
+ ? 'token auth'
118
+ : config.auth.kind === 'password'
119
+ ? `signs in as ${config.auth.user}`
120
+ : 'no auth';
121
+ const filters = [
122
+ config.readOnly ? 'read-only tools' : 'all tools',
123
+ config.tools.length ? `allow ${config.tools.join(',')}` : '',
124
+ config.exclude.length ? `exclude ${config.exclude.join(',')}` : '',
125
+ ]
126
+ .filter(Boolean)
127
+ .join(', ');
128
+ return `${config.url} (${auth}; ${filters})`;
129
+ };
130
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A small `.env` parser (no dependency): `KEY=value`, optional `export`,
3
+ * `#` comments, single/double quotes with `\n`/`\t`/`\\`/`\"` escapes inside
4
+ * double quotes, and inline comments after unquoted values.
5
+ */
6
+ export declare const parseEnvFile: (content: string) => Record<string, string>;
7
+ /** Loads a .env file into `env` without overriding keys that are already set. */
8
+ export declare const applyEnvFile: (path: string, env: Record<string, string | undefined>) => void;
9
+ //# sourceMappingURL=env-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-file.d.ts","sourceRoot":"","sources":["../../src/env-file.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,KAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAgBnE,CAAC;AAmBF,iFAAiF;AACjF,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,KAAG,IAapF,CAAC"}