mcpick 0.0.6 → 0.0.7

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,11 @@
1
1
  # mcpick
2
2
 
3
+ ## 0.0.7
4
+
5
+ ### Patch Changes
6
+
7
+ - fb70e88: add profiles feature
8
+
3
9
  ## 0.0.6
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -74,6 +74,8 @@ McPick provides an intuitive CLI menu to:
74
74
  │ ○ Backup config
75
75
  │ ○ Add MCP server
76
76
  │ ○ Restore from backup
77
+ │ ○ Load profile
78
+ │ ○ Save profile
77
79
  │ ○ Exit
78
80
 
79
81
  ```
@@ -98,14 +100,56 @@ McPick provides an intuitive CLI menu to:
98
100
  - **Easy restoration**: Restore from any previous backup with a simple
99
101
  menu
100
102
 
103
+ ### Profiles
104
+
105
+ Load predefined sets of MCP servers instantly:
106
+
107
+ ```bash
108
+ # Apply a profile
109
+ mcpick --profile database
110
+ mcpick -p database
111
+
112
+ # Save current config as a profile
113
+ mcpick --save-profile mysetup
114
+ mcpick -s mysetup
115
+
116
+ # List available profiles
117
+ mcpick --list-profiles
118
+ mcpick -l
119
+ ```
120
+
121
+ Profiles are stored in `~/.claude/mcpick/profiles/`. You can also
122
+ create them manually:
123
+
124
+ ```json
125
+ // ~/.claude/mcpick/profiles/database.json
126
+ {
127
+ "mcp-sqlite-tools": {
128
+ "type": "stdio",
129
+ "command": "npx",
130
+ "args": ["-y", "mcp-sqlite-tools"]
131
+ }
132
+ }
133
+ ```
134
+
135
+ Or use full format with `mcpServers` wrapper:
136
+
137
+ ```json
138
+ {
139
+ "mcpServers": {
140
+ "server-name": { ... }
141
+ }
142
+ }
143
+ ```
144
+
101
145
  ### Typical Workflow
102
146
 
103
- 1. **Before a coding session**: Run MCPick and enable only relevant
104
- servers (e.g., just database tools for DB work)
147
+ 1. **Before a coding session**: Run `mcpick -p <profile>` or use the
148
+ interactive menu to enable relevant servers
105
149
  2. **Launch Claude Code**: Run `claude` to start with your configured
106
150
  servers
107
- 3. **Switch contexts**: Re-run MCPick to enable different servers for
108
- different tasks
151
+ 3. **Switch contexts**: Run `mcpick -p <other-profile>` to quickly
152
+ switch server sets
109
153
 
110
154
  ### Adding New Servers
111
155
 
@@ -142,6 +186,7 @@ MCPick works with the standard Claude Code configuration format:
142
186
  - **MCPick Registry**: `~/.claude/mcpick/servers.json` (MCPick's
143
187
  server database)
144
188
  - **Backups**: `~/.claude/mcpick/backups/` (MCP configuration backups)
189
+ - **Profiles**: `~/.claude/mcpick/profiles/` (predefined server sets)
145
190
 
146
191
  > **Note**: If your MCP servers do not appear in MCPick, ensure they
147
192
  > are configured at the global level in Claude Code
@@ -169,8 +214,6 @@ McPick is actively being developed with new features planned. See the
169
214
  [claude-code-settings-schema](https://github.com/spences10/claude-code-settings-schema)
170
215
  - **Permissions Management** - Interactive tool permission
171
216
  configuration with presets (Safe Mode, Dev Mode, Review Mode)
172
- - **Configuration Profiles** - Save and switch between complete
173
- configuration snapshots for different workflows
174
217
 
175
218
  Have ideas for other features?
176
219
  [Open an issue](https://github.com/spences10/mcpick/issues) or check
@@ -0,0 +1,70 @@
1
+ import { access, readFile, readdir, writeFile, } from 'node:fs/promises';
2
+ import { ensure_directory_exists, get_profile_path, get_profiles_dir, } from '../utils/paths.js';
3
+ import { read_claude_config } from './config.js';
4
+ import { validate_claude_config } from './validation.js';
5
+ export async function load_profile(name) {
6
+ const profile_path = get_profile_path(name);
7
+ try {
8
+ await access(profile_path);
9
+ const content = await readFile(profile_path, 'utf-8');
10
+ const parsed = JSON.parse(content);
11
+ // Profile can be either full ClaudeConfig format or just mcpServers object
12
+ if (parsed.mcpServers) {
13
+ return validate_claude_config(parsed);
14
+ }
15
+ // If it's just a servers object, wrap it
16
+ return validate_claude_config({ mcpServers: parsed });
17
+ }
18
+ catch (error) {
19
+ if (error instanceof Error &&
20
+ 'code' in error &&
21
+ error.code === 'ENOENT') {
22
+ throw new Error(`Profile '${name}' not found at ${profile_path}`);
23
+ }
24
+ throw error;
25
+ }
26
+ }
27
+ export async function list_profiles() {
28
+ const profiles_dir = get_profiles_dir();
29
+ try {
30
+ await access(profiles_dir);
31
+ const files = await readdir(profiles_dir);
32
+ const json_files = files.filter((f) => f.endsWith('.json'));
33
+ const profiles = [];
34
+ for (const file of json_files) {
35
+ try {
36
+ const path = get_profile_path(file);
37
+ const content = await readFile(path, 'utf-8');
38
+ const parsed = JSON.parse(content);
39
+ const servers = parsed.mcpServers || parsed;
40
+ profiles.push({
41
+ name: file.replace('.json', ''),
42
+ path,
43
+ serverCount: Object.keys(servers).length,
44
+ });
45
+ }
46
+ catch {
47
+ // Skip invalid profiles
48
+ }
49
+ }
50
+ return profiles;
51
+ }
52
+ catch {
53
+ return [];
54
+ }
55
+ }
56
+ export async function save_profile(name) {
57
+ const config = await read_claude_config();
58
+ const servers = config.mcpServers || {};
59
+ const server_count = Object.keys(servers).length;
60
+ if (server_count === 0) {
61
+ throw new Error('No MCP servers configured to save');
62
+ }
63
+ const profiles_dir = get_profiles_dir();
64
+ await ensure_directory_exists(profiles_dir);
65
+ const profile_path = get_profile_path(name);
66
+ const content = JSON.stringify({ mcpServers: servers }, null, 2);
67
+ await writeFile(profile_path, content, 'utf-8');
68
+ return server_count;
69
+ }
70
+ //# sourceMappingURL=profile.js.map
package/dist/index.js CHANGED
@@ -1,10 +1,136 @@
1
1
  #!/usr/bin/env node
2
- import { cancel, intro, isCancel, outro, select, } from '@clack/prompts';
2
+ import { cancel, intro, isCancel, log, outro, select, text, } from '@clack/prompts';
3
3
  import { add_server } from './commands/add-server.js';
4
4
  import { backup_config } from './commands/backup.js';
5
5
  import { edit_config } from './commands/edit-config.js';
6
6
  import { restore_config } from './commands/restore.js';
7
+ import { write_claude_config } from './core/config.js';
8
+ import { list_profiles, load_profile, save_profile, } from './core/profile.js';
9
+ function parse_args() {
10
+ const args = process.argv.slice(2);
11
+ const result = {};
12
+ for (let i = 0; i < args.length; i++) {
13
+ if (args[i] === '--profile' || args[i] === '-p') {
14
+ result.profile = args[i + 1];
15
+ i++;
16
+ }
17
+ else if (args[i] === '--save-profile' || args[i] === '-s') {
18
+ result.saveProfile = args[i + 1];
19
+ i++;
20
+ }
21
+ else if (args[i] === '--list-profiles' || args[i] === '-l') {
22
+ result.listProfiles = true;
23
+ }
24
+ }
25
+ return result;
26
+ }
27
+ async function apply_profile(name) {
28
+ intro(`MCPick - Loading profile: ${name}`);
29
+ try {
30
+ const profile_config = await load_profile(name);
31
+ await write_claude_config(profile_config);
32
+ const server_count = Object.keys(profile_config.mcpServers || {}).length;
33
+ log.success(`Profile '${name}' applied (${server_count} servers)`);
34
+ outro('Done!');
35
+ }
36
+ catch (error) {
37
+ if (error instanceof Error) {
38
+ cancel(error.message);
39
+ }
40
+ else {
41
+ cancel('Failed to load profile');
42
+ }
43
+ process.exit(1);
44
+ }
45
+ }
46
+ async function show_profiles() {
47
+ intro('MCPick - Available Profiles');
48
+ const profiles = await list_profiles();
49
+ if (profiles.length === 0) {
50
+ log.warn('No profiles found in ~/.claude/mcpick/profiles/');
51
+ log.info('Create .json files there to use profiles');
52
+ }
53
+ else {
54
+ for (const p of profiles) {
55
+ log.info(`${p.name} (${p.serverCount} servers)`);
56
+ }
57
+ }
58
+ outro('');
59
+ }
60
+ async function create_profile(name) {
61
+ intro(`MCPick - Saving profile: ${name}`);
62
+ try {
63
+ const server_count = await save_profile(name);
64
+ log.success(`Profile '${name}' saved (${server_count} servers)`);
65
+ outro('Done!');
66
+ }
67
+ catch (error) {
68
+ if (error instanceof Error) {
69
+ cancel(error.message);
70
+ }
71
+ else {
72
+ cancel('Failed to save profile');
73
+ }
74
+ process.exit(1);
75
+ }
76
+ }
77
+ async function handle_load_profile() {
78
+ const profiles = await list_profiles();
79
+ if (profiles.length === 0) {
80
+ log.warn('No profiles found');
81
+ log.info('Save a profile first or create one in ~/.claude/mcpick/profiles/');
82
+ return;
83
+ }
84
+ const profile_name = await select({
85
+ message: 'Select a profile to load:',
86
+ options: profiles.map((p) => ({
87
+ value: p.name,
88
+ label: p.name,
89
+ hint: `${p.serverCount} servers`,
90
+ })),
91
+ });
92
+ if (isCancel(profile_name))
93
+ return;
94
+ const profile_config = await load_profile(profile_name);
95
+ await write_claude_config(profile_config);
96
+ const server_count = Object.keys(profile_config.mcpServers || {}).length;
97
+ log.success(`Profile '${profile_name}' applied (${server_count} servers)`);
98
+ }
99
+ async function handle_save_profile() {
100
+ const name = await text({
101
+ message: 'Profile name:',
102
+ placeholder: 'e.g. database, web-dev, minimal',
103
+ validate: (value) => {
104
+ if (!value || value.trim().length === 0) {
105
+ return 'Profile name is required';
106
+ }
107
+ if (!/^[\w-]+$/.test(value)) {
108
+ return 'Use only letters, numbers, underscores, hyphens';
109
+ }
110
+ },
111
+ });
112
+ if (isCancel(name))
113
+ return;
114
+ const server_count = await save_profile(name);
115
+ log.success(`Profile '${name}' saved (${server_count} servers)`);
116
+ }
7
117
  async function main() {
118
+ const args = parse_args();
119
+ // Handle --list-profiles
120
+ if (args.listProfiles) {
121
+ await show_profiles();
122
+ return;
123
+ }
124
+ // Handle --save-profile <name>
125
+ if (args.saveProfile) {
126
+ await create_profile(args.saveProfile);
127
+ return;
128
+ }
129
+ // Handle --profile <name>
130
+ if (args.profile) {
131
+ await apply_profile(args.profile);
132
+ return;
133
+ }
8
134
  intro('MCPick - MCP Server Configuration Manager');
9
135
  while (true) {
10
136
  try {
@@ -31,6 +157,16 @@ async function main() {
31
157
  label: 'Restore from backup',
32
158
  hint: 'Restore from a previous backup',
33
159
  },
160
+ {
161
+ value: 'load-profile',
162
+ label: 'Load profile',
163
+ hint: 'Apply a saved profile',
164
+ },
165
+ {
166
+ value: 'save-profile',
167
+ label: 'Save profile',
168
+ hint: 'Save current config as profile',
169
+ },
34
170
  {
35
171
  value: 'exit',
36
172
  label: 'Exit',
@@ -55,6 +191,12 @@ async function main() {
55
191
  case 'restore':
56
192
  await restore_config();
57
193
  break;
194
+ case 'load-profile':
195
+ await handle_load_profile();
196
+ break;
197
+ case 'save-profile':
198
+ await handle_save_profile();
199
+ break;
58
200
  case 'exit':
59
201
  outro('Goodbye!');
60
202
  process.exit(0);
@@ -1,7 +1,7 @@
1
- import { access, mkdir } from 'node:fs/promises';
2
1
  import { existsSync } from 'node:fs';
2
+ import { access, mkdir } from 'node:fs/promises';
3
3
  import { homedir } from 'node:os';
4
- import { join, dirname } from 'node:path';
4
+ import { dirname, join } from 'node:path';
5
5
  export function get_base_dir() {
6
6
  const configDir = process.env.CLAUDE_CONFIG_DIR;
7
7
  if (configDir && configDir.length > 0 && existsSync(configDir)) {
@@ -34,6 +34,14 @@ export function get_server_registry_path() {
34
34
  export function get_backups_dir() {
35
35
  return join(get_mcpick_dir(), 'backups');
36
36
  }
37
+ export function get_profiles_dir() {
38
+ return join(get_mcpick_dir(), 'profiles');
39
+ }
40
+ export function get_profile_path(name) {
41
+ // Allow .json extension or add it
42
+ const filename = name.endsWith('.json') ? name : `${name}.json`;
43
+ return join(get_profiles_dir(), filename);
44
+ }
37
45
  export function get_backup_filename() {
38
46
  const now = new Date();
39
47
  const year = now.getFullYear();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcpick",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "Dynamic MCP server configuration manager for Claude Code",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",