@mcpcloud/cli 0.1.0 → 0.1.2

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.
Files changed (3) hide show
  1. package/README.md +40 -10
  2. package/dist/index.js +111 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,10 +4,14 @@ The official command-line interface for [MCPCloud](https://mcpcloud.sh) — mana
4
4
 
5
5
  ```sh
6
6
  npm install -g @mcpcloud/cli
7
+ export MCPCLOUD_BASE_URL=https://your-deployment.example.com # ask your admin
7
8
  mcpsh login
8
9
  mcpsh whoami
9
10
  ```
10
11
 
12
+ > **Note**
13
+ > MCPCloud is in private beta. The CLI does **not** ship with a default API base URL — set `MCPCLOUD_BASE_URL` (or `mcpsh config set-url <url>`) using the URL provided by your MCPCloud admin. A built-in default will be added when the platform launches publicly.
14
+
11
15
  The package installs two equivalent binaries: `mcpsh` (short) and `mcpcloud` (full). Use whichever you prefer.
12
16
 
13
17
  ## Requirements
@@ -27,20 +31,39 @@ npx -y @mcpcloud/cli whoami
27
31
  bunx --bun @mcpcloud/cli whoami
28
32
  ```
29
33
 
30
- ## Authentication
34
+ ## Configuration
35
+
36
+ The CLI needs **two** values to talk to your MCPCloud deployment: an API base URL and an API key. Either or both can come from environment variables, the saved config file, or per-invocation flags.
37
+
38
+ ### 1. Set the API base URL
31
39
 
32
- You can authenticate two ways. The CLI prefers the environment variable when both are set.
40
+ The CLI does not assume any default base URL. Use whichever method fits your environment:
33
41
 
34
42
  ```sh
35
- # 1. Save a key to ~/.mcpcloud/config.json (mode 0600)
43
+ # environment variable (recommended for CI / scripts)
44
+ export MCPCLOUD_BASE_URL=https://your-deployment.example.com
45
+
46
+ # persist for your user account (writes to ~/.mcpcloud/config.json)
47
+ mcpsh config set-url https://your-deployment.example.com
48
+
49
+ # one-off override
50
+ mcpsh --base-url https://your-deployment.example.com whoami
51
+ ```
52
+
53
+ The lookup order is: `--base-url` flag → `MCPCLOUD_BASE_URL` env var → saved `baseUrl` in `~/.mcpcloud/config.json`. If none are set the CLI exits with code `3` and a clear message.
54
+
55
+ ### 2. Authenticate
56
+
57
+ ```sh
58
+ # Save a key to ~/.mcpcloud/config.json (mode 0600)
36
59
  mcpsh login # interactive prompt (recommended)
37
60
  mcpsh login --key mck_xxx # non-interactive (avoid: writes to shell history)
38
61
 
39
- # 2. Use an environment variable (recommended for CI)
62
+ # Or use an environment variable (recommended for CI)
40
63
  export MCPCLOUD_API_KEY=mck_xxx
41
64
  ```
42
65
 
43
- `mcpsh logout` removes the saved key.
66
+ `mcpsh logout` removes the saved key. `mcpsh config show` prints the current resolved config (with the API key redacted).
44
67
 
45
68
  ## Quick start
46
69
 
@@ -75,7 +98,7 @@ Run `mcpsh <command> --help` on any command for the full option list.
75
98
  | Variable | Description |
76
99
  |---|---|
77
100
  | `MCPCLOUD_API_KEY` | API key used for authentication (overrides the saved config). |
78
- | `MCPCLOUD_BASE_URL` | Override the API base URL. Default: `https://mcpcloud.sh`. |
101
+ | `MCPCLOUD_BASE_URL` | API base URL. **Required** — there is no built-in default. |
79
102
  | `MCPCLOUD_ORG_ID` | Default organization ID for commands that require one. |
80
103
 
81
104
  The organization for each command is resolved in this order:
@@ -87,12 +110,18 @@ The organization for each command is resolved in this order:
87
110
 
88
111
  ## Commands
89
112
 
90
- ### Authentication
113
+ ### Authentication & config
91
114
 
92
115
  ```sh
93
116
  mcpsh login [--key <secret>] # save an API key locally
94
117
  mcpsh logout # remove the saved key
95
118
  mcpsh whoami # show current key, base URL, and accessible orgs
119
+
120
+ mcpsh config show # print resolved config (key redacted)
121
+ mcpsh config set-url <url> # persist the API base URL
122
+ mcpsh config clear-url # remove the saved base URL
123
+ mcpsh config set-org <orgId> # persist a default organization
124
+ mcpsh config clear-org # remove the saved default organization
96
125
  ```
97
126
 
98
127
  ### Projects
@@ -158,6 +187,7 @@ mcpsh --json skills connect skl_123 --agent cursor \
158
187
  | `0` | Success. |
159
188
  | `1` | Generic failure (API error, validation error, network error). |
160
189
  | `2` | No API key configured. Run `mcpsh login` or set `MCPCLOUD_API_KEY`. |
190
+ | `3` | No API base URL configured. Set `MCPCLOUD_BASE_URL` or run `mcpsh config set-url <url>`. |
161
191
 
162
192
  When an API call fails, the CLI prints the response code, status, and request id (when present) to make debugging easier:
163
193
 
@@ -173,9 +203,9 @@ The CLI stores its config at `~/.mcpcloud/config.json` with mode `0600`. Recogni
173
203
 
174
204
  ```jsonc
175
205
  {
176
- "apiKey": "mck_...", // saved by `mcpsh login`
177
- "baseUrl": "https://staging.mcpcloud.sh", // overrides the default
178
- "defaultOrganizationId": "org_..." // used when --org is omitted
206
+ "apiKey": "mck_...", // saved by `mcpsh login`
207
+ "baseUrl": "https://your-deployment...", // saved by `mcpsh config set-url`
208
+ "defaultOrganizationId": "org_..." // saved by `mcpsh config set-org`
179
209
  }
180
210
  ```
181
211
 
package/dist/index.js CHANGED
@@ -1878,7 +1878,6 @@ var {
1878
1878
  import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from "node:fs";
1879
1879
  import { homedir } from "node:os";
1880
1880
  import { join } from "node:path";
1881
- var DEFAULT_BASE_URL = "https://mcpcloud.sh";
1882
1881
  function configDir() {
1883
1882
  return join(homedir(), ".mcpcloud");
1884
1883
  }
@@ -1892,6 +1891,13 @@ class MissingApiKeyError extends Error {
1892
1891
  this.name = "MissingApiKeyError";
1893
1892
  }
1894
1893
  }
1894
+
1895
+ class MissingBaseUrlError extends Error {
1896
+ constructor() {
1897
+ super("No API base URL configured. Set MCPCLOUD_BASE_URL, pass --base-url, or save one with `mcpsh config set-url <url>`.");
1898
+ this.name = "MissingBaseUrlError";
1899
+ }
1900
+ }
1895
1901
  function readConfig() {
1896
1902
  if (!existsSync(configFile()))
1897
1903
  return {};
@@ -1923,7 +1929,10 @@ function setBaseUrlOverride(url) {
1923
1929
  baseUrlOverride = url;
1924
1930
  }
1925
1931
  function getBaseUrl() {
1926
- return baseUrlOverride ?? process.env["MCPCLOUD_BASE_URL"] ?? readConfig().baseUrl ?? DEFAULT_BASE_URL;
1932
+ const url = baseUrlOverride ?? process.env["MCPCLOUD_BASE_URL"] ?? readConfig().baseUrl;
1933
+ if (!url)
1934
+ throw new MissingBaseUrlError;
1935
+ return url;
1927
1936
  }
1928
1937
  function requireApiKey() {
1929
1938
  const key = getApiKey();
@@ -1968,6 +1977,9 @@ function buildUrl(path, params) {
1968
1977
  function shouldRetry(attempt, err) {
1969
1978
  if (attempt >= MAX_RETRIES)
1970
1979
  return false;
1980
+ if (err instanceof Error && (err.name === "MissingApiKeyError" || err.name === "MissingBaseUrlError")) {
1981
+ return false;
1982
+ }
1971
1983
  if (err instanceof McpCloudNetworkError)
1972
1984
  return true;
1973
1985
  if (err instanceof McpCloudApiError && RETRYABLE_STATUSES.has(err.status))
@@ -2032,9 +2044,10 @@ async function performRequest(method, path, options) {
2032
2044
  init.body = JSON.stringify(options.body);
2033
2045
  }
2034
2046
  try {
2047
+ const url = buildUrl(path, options.params);
2035
2048
  let res;
2036
2049
  try {
2037
- res = await fetch(buildUrl(path, options.params), init);
2050
+ res = await fetch(url, init);
2038
2051
  } catch (err) {
2039
2052
  if (controller.signal.aborted && !externalSignal?.aborted) {
2040
2053
  throw new McpCloudNetworkError(`Request to ${path} timed out after ${timeoutMs}ms.`, err);
@@ -2144,6 +2157,9 @@ function describeError(err) {
2144
2157
  if (err instanceof MissingApiKeyError) {
2145
2158
  return { message: err.message, code: "missing_api_key" };
2146
2159
  }
2160
+ if (err instanceof MissingBaseUrlError) {
2161
+ return { message: err.message, code: "missing_base_url" };
2162
+ }
2147
2163
  if (err instanceof Error) {
2148
2164
  return { message: err.message };
2149
2165
  }
@@ -2165,7 +2181,11 @@ function handleError(err) {
2165
2181
  if (info.docsUrl)
2166
2182
  console.error(` docs: ${info.docsUrl}`);
2167
2183
  }
2168
- process.exit(info.code === "missing_api_key" ? 2 : 1);
2184
+ if (info.code === "missing_api_key")
2185
+ process.exit(2);
2186
+ if (info.code === "missing_base_url")
2187
+ process.exit(3);
2188
+ process.exit(1);
2169
2189
  }
2170
2190
 
2171
2191
  // src/lib/version.ts
@@ -2812,29 +2832,112 @@ function registerApiKeyCommands(program2) {
2812
2832
  }));
2813
2833
  }
2814
2834
 
2835
+ // src/commands/config.ts
2836
+ import { homedir as homedir2 } from "node:os";
2837
+ import { join as join3 } from "node:path";
2838
+ function configFilePath() {
2839
+ return join3(homedir2(), ".mcpcloud", "config.json");
2840
+ }
2841
+ function previewKey2(key) {
2842
+ if (!key)
2843
+ return;
2844
+ if (key.length <= 12)
2845
+ return `${key.slice(0, 4)}…`;
2846
+ return `${key.slice(0, 8)}…${key.slice(-4)}`;
2847
+ }
2848
+ function registerConfigCommands(program2) {
2849
+ const config = program2.command("config").description("Manage CLI configuration in ~/.mcpcloud/config.json");
2850
+ config.command("show").description("Print the current saved config (API key is redacted)").action(runAction(() => {
2851
+ const c = readConfig();
2852
+ const safe = {
2853
+ path: configFilePath(),
2854
+ baseUrl: c.baseUrl ?? null,
2855
+ defaultOrganizationId: c.defaultOrganizationId ?? null,
2856
+ apiKeyPreview: previewKey2(c.apiKey) ?? null
2857
+ };
2858
+ if (isJsonMode()) {
2859
+ printJson(safe);
2860
+ return;
2861
+ }
2862
+ printKeyValue({
2863
+ path: safe.path,
2864
+ "base url": safe.baseUrl ?? "—",
2865
+ "default org": safe.defaultOrganizationId ?? "—",
2866
+ "api key": safe.apiKeyPreview ?? "—"
2867
+ });
2868
+ }));
2869
+ config.command("set-url <url>").description("Save the API base URL to ~/.mcpcloud/config.json").action(runAction((url) => {
2870
+ try {
2871
+ new URL(url);
2872
+ } catch {
2873
+ throw new Error(`Invalid URL: ${url}`);
2874
+ }
2875
+ const c = readConfig();
2876
+ c.baseUrl = url;
2877
+ writeConfig(c);
2878
+ if (isJsonMode()) {
2879
+ printJson({ baseUrl: url });
2880
+ return;
2881
+ }
2882
+ printSuccess(`Saved base URL: ${url}`);
2883
+ }));
2884
+ config.command("clear-url").description("Remove the saved base URL (falls back to MCPCLOUD_BASE_URL)").action(runAction(() => {
2885
+ const c = readConfig();
2886
+ delete c.baseUrl;
2887
+ writeConfig(c);
2888
+ if (isJsonMode()) {
2889
+ printJson({ baseUrl: null });
2890
+ return;
2891
+ }
2892
+ printSuccess("Cleared saved base URL.");
2893
+ }));
2894
+ config.command("set-org <organizationId>").description("Save a default organization ID").action(runAction((organizationId) => {
2895
+ const c = readConfig();
2896
+ c.defaultOrganizationId = organizationId;
2897
+ writeConfig(c);
2898
+ if (isJsonMode()) {
2899
+ printJson({ defaultOrganizationId: organizationId });
2900
+ return;
2901
+ }
2902
+ printSuccess(`Saved default organization: ${organizationId}`);
2903
+ }));
2904
+ config.command("clear-org").description("Remove the saved default organization ID").action(runAction(() => {
2905
+ const c = readConfig();
2906
+ delete c.defaultOrganizationId;
2907
+ writeConfig(c);
2908
+ if (isJsonMode()) {
2909
+ printJson({ defaultOrganizationId: null });
2910
+ return;
2911
+ }
2912
+ printSuccess("Cleared saved default organization.");
2913
+ }));
2914
+ }
2915
+
2815
2916
  // src/create-program.ts
2816
2917
  function createProgram() {
2817
2918
  const program2 = new Command;
2818
- program2.name("mcpsh").description("The official CLI for MCPCloud — manage projects, MCP servers, skills, and API keys from the terminal").version(getCliVersion(), "-v, --version", "Print the CLI version").option("--json", "Output raw JSON (useful for scripting)").option("--base-url <url>", "Override the API base URL (defaults to MCPCLOUD_BASE_URL or https://mcpcloud.sh)").hook("preAction", (thisCommand) => {
2919
+ program2.name("mcpsh").description("The official CLI for MCPCloud — manage projects, MCP servers, skills, and API keys from the terminal").version(getCliVersion(), "-v, --version", "Print the CLI version").option("--json", "Output raw JSON (useful for scripting)").option("--base-url <url>", "API base URL for this invocation. Falls back to MCPCLOUD_BASE_URL or `mcpsh config set-url`.").hook("preAction", (thisCommand) => {
2819
2920
  const opts = thisCommand.optsWithGlobals();
2820
2921
  setJsonMode(Boolean(opts.json));
2821
2922
  setBaseUrlOverride(opts.baseUrl);
2822
2923
  }).addHelpText("after", [
2823
2924
  "",
2824
2925
  "Environment variables:",
2825
- " MCPCLOUD_API_KEY API key used for authentication",
2826
- " MCPCLOUD_BASE_URL Override the API base URL",
2926
+ " MCPCLOUD_API_KEY API key used for authentication (required)",
2927
+ " MCPCLOUD_BASE_URL API base URL (required — no default is shipped)",
2827
2928
  " MCPCLOUD_ORG_ID Default organization ID",
2828
2929
  "",
2829
2930
  "Examples:",
2931
+ " $ export MCPCLOUD_BASE_URL=https://your-deployment.example.com",
2932
+ " $ mcpsh config set-url https://your-deployment.example.com # persist it",
2830
2933
  " $ mcpsh login",
2831
2934
  " $ mcpsh whoami",
2832
- " $ mcpsh projects list",
2833
2935
  " $ mcpsh skills connect skill_123 --agent claude-code --apply",
2834
2936
  " $ mcpsh --json servers list"
2835
2937
  ].join(`
2836
2938
  `));
2837
2939
  registerAuthCommands(program2);
2940
+ registerConfigCommands(program2);
2838
2941
  registerProjectCommands(program2);
2839
2942
  registerServerCommands(program2);
2840
2943
  registerToolCommands(program2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcpcloud/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "The official CLI for MCPCloud — manage projects, servers, skills, and API keys from the terminal",
5
5
  "type": "module",
6
6
  "bin": {