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 +14 -0
- package/dist/cli/commands/disable.js +3 -0
- package/dist/cli/commands/remove.js +12 -8
- package/dist/core/config.js +6 -15
- package/dist/core/plugin-cache.js +3 -2
- package/dist/core/registry.js +26 -0
- package/dist/core/settings.js +8 -15
- package/dist/utils/atomic-write.js +27 -0
- package/dist/utils/claude-cli.js +6 -1
- package/package.json +1 -1
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
|
|
21
|
-
|
|
26
|
+
if (index >= 0) {
|
|
27
|
+
registry.servers.splice(index, 1);
|
|
28
|
+
await write_server_registry(registry);
|
|
22
29
|
}
|
|
23
|
-
// Remove
|
|
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}'
|
|
32
|
+
console.log(`Removed '${args.server}'`);
|
|
29
33
|
},
|
|
30
34
|
});
|
|
31
35
|
//# sourceMappingURL=remove.js.map
|
package/dist/core/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { access, readFile
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
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
|
|
23
|
+
await atomic_json_write(get_installed_plugins_path(), () => data);
|
|
23
24
|
}
|
|
24
25
|
export async function read_known_marketplaces() {
|
|
25
26
|
try {
|
package/dist/core/registry.js
CHANGED
|
@@ -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) {
|
package/dist/core/settings.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { access, readFile
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
existing
|
|
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
|
package/dist/utils/claude-cli.js
CHANGED
|
@@ -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
|
}
|