mcpick 0.0.17 → 0.0.18
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 +8 -0
- package/dist/cli/commands/clone.js +108 -0
- package/dist/cli/commands/dev.js +7 -1
- package/dist/cli/commands/list.js +2 -1
- package/dist/cli/index.js +1 -0
- package/dist/core/config.js +54 -0
- package/dist/core/dev-override.js +2 -57
- package/dist/index.js +2 -0
- package/dist/utils/claude-cli.js +4 -2
- package/dist/utils/redact.js +28 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# mcpick
|
|
2
2
|
|
|
3
|
+
## 0.0.18
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 58ff00f: Add clone command and skip redundant stdio transport flag
|
|
8
|
+
- 7f277da: redact env keys when listing
|
|
9
|
+
- b52fcbd: Show available CLI commands hint in TUI intro
|
|
10
|
+
|
|
3
11
|
## 0.0.17
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { defineCommand } from 'citty';
|
|
2
|
+
import { detect_server_scope, find_server_in_scope, } from '../../core/config.js';
|
|
3
|
+
import { add_server_to_registry } from '../../core/registry.js';
|
|
4
|
+
import { validate_mcp_server } from '../../core/validation.js';
|
|
5
|
+
import { add_mcp_via_cli } from '../../utils/claude-cli.js';
|
|
6
|
+
import { redact_server } from '../../utils/redact.js';
|
|
7
|
+
import { error, output } from '../output.js';
|
|
8
|
+
export default defineCommand({
|
|
9
|
+
meta: {
|
|
10
|
+
name: 'clone',
|
|
11
|
+
description: 'Clone an existing MCP server config with a new name, optionally overriding command/args',
|
|
12
|
+
},
|
|
13
|
+
args: {
|
|
14
|
+
source: {
|
|
15
|
+
type: 'positional',
|
|
16
|
+
description: 'Source server name to clone from',
|
|
17
|
+
required: true,
|
|
18
|
+
},
|
|
19
|
+
name: {
|
|
20
|
+
type: 'positional',
|
|
21
|
+
description: 'New server name',
|
|
22
|
+
required: true,
|
|
23
|
+
},
|
|
24
|
+
command: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Override command (e.g. "node" for local dev)',
|
|
27
|
+
},
|
|
28
|
+
args: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description: 'Override comma-separated arguments',
|
|
31
|
+
},
|
|
32
|
+
scope: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'Scope for new server (default: same as source)',
|
|
35
|
+
},
|
|
36
|
+
json: {
|
|
37
|
+
type: 'boolean',
|
|
38
|
+
description: 'Output as JSON',
|
|
39
|
+
default: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
async run({ args }) {
|
|
43
|
+
if (args.scope &&
|
|
44
|
+
!['local', 'project', 'user'].includes(args.scope)) {
|
|
45
|
+
error(`Invalid scope: ${args.scope}. Use local, project, or user.`);
|
|
46
|
+
}
|
|
47
|
+
// Find the source server
|
|
48
|
+
const scope = args.scope;
|
|
49
|
+
let found;
|
|
50
|
+
if (scope) {
|
|
51
|
+
found = await find_server_in_scope(args.source, scope);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
found = await detect_server_scope(args.source);
|
|
55
|
+
}
|
|
56
|
+
if (!found) {
|
|
57
|
+
error(`Server '${args.source}' not found${scope ? ` in ${scope} scope` : ' in any scope'}`);
|
|
58
|
+
}
|
|
59
|
+
const target_scope = args.scope || found.scope;
|
|
60
|
+
// Clone the config, applying overrides
|
|
61
|
+
const cloned = {
|
|
62
|
+
...found.server,
|
|
63
|
+
name: args.name,
|
|
64
|
+
};
|
|
65
|
+
if (args.command) {
|
|
66
|
+
cloned.command = args.command;
|
|
67
|
+
// When overriding command, clear url/type if switching from http/sse to stdio
|
|
68
|
+
delete cloned.url;
|
|
69
|
+
if (cloned.type === 'sse' || cloned.type === 'http') {
|
|
70
|
+
delete cloned.type;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (args.args) {
|
|
74
|
+
cloned.args = args.args.split(',');
|
|
75
|
+
}
|
|
76
|
+
// Validate
|
|
77
|
+
let server;
|
|
78
|
+
try {
|
|
79
|
+
server = validate_mcp_server(cloned);
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
error(`Invalid cloned config: ${err instanceof Error ? err.message : 'validation failed'}`);
|
|
83
|
+
}
|
|
84
|
+
// Add to registry
|
|
85
|
+
await add_server_to_registry(server);
|
|
86
|
+
// Enable via CLI
|
|
87
|
+
const result = await add_mcp_via_cli(server, target_scope);
|
|
88
|
+
if (args.json) {
|
|
89
|
+
output({
|
|
90
|
+
cloned: server.name,
|
|
91
|
+
from: args.source,
|
|
92
|
+
scope: target_scope,
|
|
93
|
+
server: redact_server(server),
|
|
94
|
+
cli: result.success,
|
|
95
|
+
error: result.error,
|
|
96
|
+
}, true);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
if (result.success) {
|
|
100
|
+
console.log(`Cloned '${args.source}' → '${server.name}' (scope: ${target_scope})`);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
console.log(`Cloned '${args.source}' → '${server.name}' to registry but CLI failed: ${result.error}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=clone.js.map
|
package/dist/cli/commands/dev.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
2
|
import { apply_dev_override, list_dev_overrides, restore_all_dev_overrides, restore_dev_override, } from '../../core/dev-override.js';
|
|
3
|
+
import { redact_server_base } from '../../utils/redact.js';
|
|
3
4
|
import { error, output } from '../output.js';
|
|
4
5
|
const apply = defineCommand({
|
|
5
6
|
meta: {
|
|
@@ -126,7 +127,12 @@ const list = defineCommand({
|
|
|
126
127
|
async run({ args }) {
|
|
127
128
|
const overrides = await list_dev_overrides();
|
|
128
129
|
if (args.json) {
|
|
129
|
-
|
|
130
|
+
const redacted = overrides.map((o) => ({
|
|
131
|
+
...o,
|
|
132
|
+
original: redact_server_base(o.original),
|
|
133
|
+
dev: redact_server_base(o.dev),
|
|
134
|
+
}));
|
|
135
|
+
output(redacted, true);
|
|
130
136
|
return;
|
|
131
137
|
}
|
|
132
138
|
if (overrides.length === 0) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineCommand } from 'citty';
|
|
2
2
|
import { get_enabled_servers_for_scope } from '../../core/config.js';
|
|
3
3
|
import { get_all_available_servers } from '../../core/registry.js';
|
|
4
|
+
import { redact_server } from '../../utils/redact.js';
|
|
4
5
|
import { error, output } from '../output.js';
|
|
5
6
|
export default defineCommand({
|
|
6
7
|
meta: {
|
|
@@ -38,7 +39,7 @@ export default defineCommand({
|
|
|
38
39
|
for (const scope of scopes) {
|
|
39
40
|
status[scope] = enabled_by_scope[scope].includes(server.name);
|
|
40
41
|
}
|
|
41
|
-
const { name, ...rest } = server;
|
|
42
|
+
const { name, ...rest } = redact_server(server);
|
|
42
43
|
return { name, ...status, ...rest };
|
|
43
44
|
});
|
|
44
45
|
output(data, true);
|
package/dist/cli/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const main = defineCommand({
|
|
|
11
11
|
remove: () => import('./commands/remove.js').then((m) => m.default),
|
|
12
12
|
add: () => import('./commands/add.js').then((m) => m.default),
|
|
13
13
|
'add-json': () => import('./commands/add-json.js').then((m) => m.default),
|
|
14
|
+
clone: () => import('./commands/clone.js').then((m) => m.default),
|
|
14
15
|
get: () => import('./commands/get.js').then((m) => m.default),
|
|
15
16
|
'reset-project-choices': () => import('./commands/reset-project-choices.js').then((m) => m.default),
|
|
16
17
|
backup: () => import('./commands/backup.js').then((m) => m.default),
|
package/dist/core/config.js
CHANGED
|
@@ -42,6 +42,60 @@ export function create_config_from_servers(selected_servers) {
|
|
|
42
42
|
});
|
|
43
43
|
return { mcpServers: mcp_servers };
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Find a server's full config in a specific scope.
|
|
47
|
+
*/
|
|
48
|
+
export async function find_server_in_scope(name, scope) {
|
|
49
|
+
if (scope === 'user' || scope === 'local') {
|
|
50
|
+
const config_path = get_claude_config_path();
|
|
51
|
+
try {
|
|
52
|
+
await access(config_path);
|
|
53
|
+
const content = await readFile(config_path, 'utf-8');
|
|
54
|
+
const parsed = JSON.parse(content);
|
|
55
|
+
if (scope === 'user') {
|
|
56
|
+
const server = parsed.mcpServers?.[name];
|
|
57
|
+
if (server)
|
|
58
|
+
return { server, scope: 'user' };
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const cwd = get_current_project_path();
|
|
62
|
+
const server = parsed.projects?.[cwd]?.mcpServers?.[name];
|
|
63
|
+
if (server)
|
|
64
|
+
return { server, scope: 'local' };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// File doesn't exist
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else if (scope === 'project') {
|
|
72
|
+
const mcp_path = get_project_mcp_json_path();
|
|
73
|
+
try {
|
|
74
|
+
await access(mcp_path);
|
|
75
|
+
const content = await readFile(mcp_path, 'utf-8');
|
|
76
|
+
const parsed = JSON.parse(content);
|
|
77
|
+
const server = parsed.mcpServers?.[name];
|
|
78
|
+
if (server)
|
|
79
|
+
return { server, scope: 'project' };
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// File doesn't exist
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Auto-detect which scope a server lives in.
|
|
89
|
+
* Searches local → project → user.
|
|
90
|
+
*/
|
|
91
|
+
export async function detect_server_scope(name) {
|
|
92
|
+
for (const scope of ['local', 'project', 'user']) {
|
|
93
|
+
const result = await find_server_in_scope(name, scope);
|
|
94
|
+
if (result)
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
45
99
|
/**
|
|
46
100
|
* Read full Claude config including projects section
|
|
47
101
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { atomic_json_write } from '../utils/atomic-write.js';
|
|
3
3
|
import { get_claude_config_path, get_current_project_path, get_dev_overrides_path, get_project_mcp_json_path, } from '../utils/paths.js';
|
|
4
|
+
import { detect_server_scope, find_server_in_scope, } from './config.js';
|
|
4
5
|
const EMPTY_OVERRIDES = {
|
|
5
6
|
version: 1,
|
|
6
7
|
overrides: {},
|
|
@@ -17,62 +18,6 @@ export async function read_dev_overrides() {
|
|
|
17
18
|
async function write_dev_overrides(data) {
|
|
18
19
|
await atomic_json_write(get_dev_overrides_path(), () => data);
|
|
19
20
|
}
|
|
20
|
-
/**
|
|
21
|
-
* Read full config for a given scope and return the server entry if found.
|
|
22
|
-
* Returns { config, server, scope } or null.
|
|
23
|
-
*/
|
|
24
|
-
async function find_server_in_scope(name, scope) {
|
|
25
|
-
if (scope === 'user' || scope === 'local') {
|
|
26
|
-
const config_path = get_claude_config_path();
|
|
27
|
-
try {
|
|
28
|
-
await access(config_path);
|
|
29
|
-
const content = await readFile(config_path, 'utf-8');
|
|
30
|
-
const parsed = JSON.parse(content);
|
|
31
|
-
if (scope === 'user') {
|
|
32
|
-
const server = parsed.mcpServers?.[name];
|
|
33
|
-
if (server)
|
|
34
|
-
return { server, scope: 'user' };
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
// local scope: projects[cwd].mcpServers
|
|
38
|
-
const cwd = get_current_project_path();
|
|
39
|
-
const server = parsed.projects?.[cwd]?.mcpServers?.[name];
|
|
40
|
-
if (server)
|
|
41
|
-
return { server, scope: 'local' };
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
// File doesn't exist
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
else if (scope === 'project') {
|
|
49
|
-
const mcp_path = get_project_mcp_json_path();
|
|
50
|
-
try {
|
|
51
|
-
await access(mcp_path);
|
|
52
|
-
const content = await readFile(mcp_path, 'utf-8');
|
|
53
|
-
const parsed = JSON.parse(content);
|
|
54
|
-
const server = parsed.mcpServers?.[name];
|
|
55
|
-
if (server)
|
|
56
|
-
return { server, scope: 'project' };
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
// File doesn't exist
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Auto-detect which scope a server lives in.
|
|
66
|
-
* Searches local → project → user.
|
|
67
|
-
*/
|
|
68
|
-
async function detect_server_scope(name) {
|
|
69
|
-
for (const scope of ['local', 'project', 'user']) {
|
|
70
|
-
const result = await find_server_in_scope(name, scope);
|
|
71
|
-
if (result)
|
|
72
|
-
return result;
|
|
73
|
-
}
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
21
|
/**
|
|
77
22
|
* Write a server config into the appropriate scope config file.
|
|
78
23
|
*/
|
package/dist/index.js
CHANGED
|
@@ -165,6 +165,7 @@ async function main() {
|
|
|
165
165
|
return;
|
|
166
166
|
}
|
|
167
167
|
intro('MCPick - MCP Server Configuration Manager');
|
|
168
|
+
log.info('CLI: mcpick <command> --help | Commands: list, add, clone, enable, disable, remove, get, dev, profile, plugins, hooks, backup, restore');
|
|
168
169
|
while (true) {
|
|
169
170
|
try {
|
|
170
171
|
const action = await select({
|
|
@@ -295,6 +296,7 @@ const SUBCOMMANDS = new Set([
|
|
|
295
296
|
'remove',
|
|
296
297
|
'add',
|
|
297
298
|
'add-json',
|
|
299
|
+
'clone',
|
|
298
300
|
'get',
|
|
299
301
|
'reset-project-choices',
|
|
300
302
|
'hooks',
|
package/dist/utils/claude-cli.js
CHANGED
|
@@ -34,9 +34,11 @@ function build_add_command(server, scope) {
|
|
|
34
34
|
const parts = ['claude', 'mcp', 'add'];
|
|
35
35
|
// Server name
|
|
36
36
|
parts.push(shell_escape(server.name));
|
|
37
|
-
// Transport type
|
|
37
|
+
// Transport type (only specify if non-default)
|
|
38
38
|
const transport = server.type || 'stdio';
|
|
39
|
-
|
|
39
|
+
if (transport !== 'stdio') {
|
|
40
|
+
parts.push('--transport', transport);
|
|
41
|
+
}
|
|
40
42
|
// Scope
|
|
41
43
|
parts.push('--scope', scope);
|
|
42
44
|
// Handle different transport types
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redact sensitive values (env, headers) from a server config.
|
|
3
|
+
* Shows key names but replaces values with "***".
|
|
4
|
+
*/
|
|
5
|
+
export function redact_server_base(server) {
|
|
6
|
+
const redacted = { ...server };
|
|
7
|
+
if ('env' in redacted && redacted.env) {
|
|
8
|
+
redacted.env = redact_record(redacted.env);
|
|
9
|
+
}
|
|
10
|
+
if ('headers' in redacted && redacted.headers) {
|
|
11
|
+
redacted.headers = redact_record(redacted.headers);
|
|
12
|
+
}
|
|
13
|
+
return redacted;
|
|
14
|
+
}
|
|
15
|
+
export function redact_server(server) {
|
|
16
|
+
return {
|
|
17
|
+
...redact_server_base(server),
|
|
18
|
+
name: server.name,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function redact_record(record) {
|
|
22
|
+
const redacted = {};
|
|
23
|
+
for (const key of Object.keys(record)) {
|
|
24
|
+
redacted[key] = '***';
|
|
25
|
+
}
|
|
26
|
+
return redacted;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=redact.js.map
|