mcpick 0.0.11 → 0.0.13

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # mcpick
2
2
 
3
+ ## 0.0.13
4
+
5
+ ### Patch Changes
6
+
7
+ - c985c28: Headers lost on disable/enable: pass -H flags in CLI, sync
8
+ config→registry before disable
9
+
10
+ ## 0.0.12
11
+
12
+ ### Patch Changes
13
+
14
+ - b4f38d5: fix: discover externally-added servers and use atomic JSON
15
+ writes
16
+
3
17
  ## 0.0.11
4
18
 
5
19
  ### Patch Changes
@@ -1,4 +1,5 @@
1
1
  import { defineCommand } from 'citty';
2
+ import { get_all_available_servers } from '../../core/registry.js';
2
3
  import { remove_mcp_via_cli } from '../../utils/claude-cli.js';
3
4
  import { error } from '../output.js';
4
5
  export default defineCommand({
@@ -23,6 +24,8 @@ export default defineCommand({
23
24
  if (!['local', 'project', 'user'].includes(scope)) {
24
25
  error(`Invalid scope: ${scope}. Use local, project, or user.`);
25
26
  }
27
+ // Sync config→registry before removing so headers/env are preserved
28
+ await get_all_available_servers();
26
29
  const result = await remove_mcp_via_cli(args.server);
27
30
  if (!result.success) {
28
31
  error(result.error || 'Failed to disable server');
@@ -1,5 +1,5 @@
1
1
  import { defineCommand } from 'citty';
2
- import { read_server_registry, write_server_registry, } from '../../core/registry.js';
2
+ import { get_all_available_servers, read_server_registry, write_server_registry, } from '../../core/registry.js';
3
3
  import { remove_mcp_via_cli } from '../../utils/claude-cli.js';
4
4
  import { error } from '../output.js';
5
5
  export default defineCommand({
@@ -15,17 +15,21 @@ export default defineCommand({
15
15
  },
16
16
  },
17
17
  async run({ args }) {
18
+ const all_servers = await get_all_available_servers();
19
+ const found = all_servers.find((s) => s.name === args.server);
20
+ if (!found) {
21
+ error(`Server '${args.server}' not found. Run 'mcpick list' to see available servers.`);
22
+ }
23
+ // Remove from registry if present
18
24
  const registry = await read_server_registry();
19
25
  const index = registry.servers.findIndex((s) => s.name === args.server);
20
- if (index < 0) {
21
- error(`Server '${args.server}' not found in registry. Run 'mcpick list' to see available servers.`);
26
+ if (index >= 0) {
27
+ registry.servers.splice(index, 1);
28
+ await write_server_registry(registry);
22
29
  }
23
- // Remove from registry
24
- registry.servers.splice(index, 1);
25
- await write_server_registry(registry);
26
- // Also disable via CLI (best effort)
30
+ // Remove via CLI
27
31
  await remove_mcp_via_cli(args.server);
28
- console.log(`Removed '${args.server}' from registry`);
32
+ console.log(`Removed '${args.server}'`);
29
33
  },
30
34
  });
31
35
  //# sourceMappingURL=remove.js.map
@@ -1,4 +1,5 @@
1
- import { access, readFile, writeFile } from 'node:fs/promises';
1
+ import { access, readFile } from 'node:fs/promises';
2
+ import { atomic_json_write } from '../utils/atomic-write.js';
2
3
  import { get_claude_config_path, get_current_project_path, get_project_mcp_json_path, } from '../utils/paths.js';
3
4
  import { validate_claude_config } from './validation.js';
4
5
  export async function read_claude_config() {
@@ -19,20 +20,10 @@ export async function read_claude_config() {
19
20
  }
20
21
  }
21
22
  export async function write_claude_config(config) {
22
- const config_path = get_claude_config_path();
23
- // Read the entire existing file to preserve all other sections
24
- let existing_config = {};
25
- try {
26
- const existing_content = await readFile(config_path, 'utf-8');
27
- existing_config = JSON.parse(existing_content);
28
- }
29
- catch (error) {
30
- // If file doesn't exist or is invalid, start with empty object
31
- }
32
- // Only update the mcpServers section, preserve everything else
33
- existing_config.mcpServers = config.mcpServers;
34
- const config_content = JSON.stringify(existing_config, null, 2);
35
- await writeFile(config_path, config_content, 'utf-8');
23
+ await atomic_json_write(get_claude_config_path(), (existing) => {
24
+ existing.mcpServers = config.mcpServers;
25
+ return existing;
26
+ });
36
27
  }
37
28
  export function get_enabled_servers(config) {
38
29
  if (!config.mcpServers) {
@@ -1,7 +1,8 @@
1
1
  import { exec } from 'node:child_process';
2
- import { readdir, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { readdir, readFile, rm } from 'node:fs/promises';
3
3
  import { join, resolve } from 'node:path';
4
4
  import { promisify } from 'node:util';
5
+ import { atomic_json_write } from '../utils/atomic-write.js';
5
6
  import { get_installed_plugins_path, get_known_marketplaces_path, get_marketplace_manifest_path, get_plugin_cache_dir, } from '../utils/paths.js';
6
7
  const execAsync = promisify(exec);
7
8
  const EMPTY_INSTALLED = {
@@ -19,7 +20,7 @@ export async function read_installed_plugins() {
19
20
  }
20
21
  }
21
22
  export async function write_installed_plugins(data) {
22
- await writeFile(get_installed_plugins_path(), JSON.stringify(data, null, 2), 'utf-8');
23
+ await atomic_json_write(get_installed_plugins_path(), () => data);
23
24
  }
24
25
  export async function read_known_marketplaces() {
25
26
  try {
@@ -40,7 +40,33 @@ export async function add_server_to_registry(server) {
40
40
  await write_server_registry(registry);
41
41
  }
42
42
  export async function get_all_available_servers() {
43
+ const { get_enabled_servers, read_claude_config } = await import('./config.js');
43
44
  const registry = await read_server_registry();
45
+ const config = await read_claude_config();
46
+ const config_servers = get_enabled_servers(config);
47
+ // Merge: config is the live truth, so update registry entries with config data
48
+ const config_by_name = new Map(config_servers.map((s) => [s.name, s]));
49
+ const known_names = new Set();
50
+ let registry_updated = false;
51
+ for (let i = 0; i < registry.servers.length; i++) {
52
+ const name = registry.servers[i].name;
53
+ known_names.add(name);
54
+ const config_server = config_by_name.get(name);
55
+ if (config_server) {
56
+ registry.servers[i] = config_server;
57
+ registry_updated = true;
58
+ }
59
+ }
60
+ for (const server of config_servers) {
61
+ if (!known_names.has(server.name)) {
62
+ registry.servers.push(server);
63
+ registry_updated = true;
64
+ }
65
+ }
66
+ // Persist updated data back to registry so it survives disable/enable cycles
67
+ if (registry_updated) {
68
+ await write_server_registry(registry);
69
+ }
44
70
  return registry.servers;
45
71
  }
46
72
  export async function sync_servers_to_registry(servers) {
@@ -1,4 +1,5 @@
1
- import { access, readFile, writeFile } from 'node:fs/promises';
1
+ import { access, readFile } from 'node:fs/promises';
2
+ import { atomic_json_write } from '../utils/atomic-write.js';
2
3
  import { get_claude_settings_path } from '../utils/paths.js';
3
4
  export async function read_claude_settings() {
4
5
  const settings_path = get_claude_settings_path();
@@ -17,20 +18,12 @@ export async function read_claude_settings() {
17
18
  }
18
19
  }
19
20
  export async function write_claude_settings(updates) {
20
- const settings_path = get_claude_settings_path();
21
- let existing = {};
22
- try {
23
- const content = await readFile(settings_path, 'utf-8');
24
- existing = JSON.parse(content);
25
- }
26
- catch {
27
- // Start with empty if file doesn't exist
28
- }
29
- // Merge only the keys we're updating
30
- for (const [key, value] of Object.entries(updates)) {
31
- existing[key] = value;
32
- }
33
- await writeFile(settings_path, JSON.stringify(existing, null, 2), 'utf-8');
21
+ await atomic_json_write(get_claude_settings_path(), (existing) => {
22
+ for (const [key, value] of Object.entries(updates)) {
23
+ existing[key] = value;
24
+ }
25
+ return existing;
26
+ });
34
27
  }
35
28
  /**
36
29
  * Parse enabledPlugins into structured list.
@@ -0,0 +1,27 @@
1
+ import { readFile, rename, writeFile } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ /**
4
+ * Atomically write a JSON file with fresh-read merging.
5
+ *
6
+ * 1. Re-reads the file right before writing to pick up concurrent changes
7
+ * 2. Applies the merge function to the freshest data
8
+ * 3. Writes to a temp file, then renames (atomic on same filesystem)
9
+ */
10
+ export async function atomic_json_write(file_path, merge) {
11
+ // Read the freshest version right before writing
12
+ let existing = {};
13
+ try {
14
+ const content = await readFile(file_path, 'utf-8');
15
+ existing = JSON.parse(content);
16
+ }
17
+ catch {
18
+ // File doesn't exist or invalid — start fresh
19
+ }
20
+ const merged = merge(existing);
21
+ const content = JSON.stringify(merged, null, 2);
22
+ // Write to temp file then rename for atomicity
23
+ const tmp_path = join(dirname(file_path), `.${Date.now()}.tmp`);
24
+ await writeFile(tmp_path, content, 'utf-8');
25
+ await rename(tmp_path, file_path);
26
+ }
27
+ //# sourceMappingURL=atomic-write.js.map
@@ -59,10 +59,15 @@ function build_add_command(server, scope) {
59
59
  }
60
60
  }
61
61
  else {
62
- // HTTP or SSE transport
62
+ // HTTP or SSE transport — URL must come before header flags
63
63
  if ('url' in server && server.url) {
64
64
  parts.push(shell_escape(server.url));
65
65
  }
66
+ if ('headers' in server && server.headers) {
67
+ for (const [key, value] of Object.entries(server.headers)) {
68
+ parts.push('-H', shell_escape(`${key}: ${value}`));
69
+ }
70
+ }
66
71
  }
67
72
  return parts.join(' ');
68
73
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcpick",
3
- "version": "0.0.11",
3
+ "version": "0.0.13",
4
4
  "description": "Dynamic MCP server and plugin configuration manager for Claude Code",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",