@ebowwa/claude-code-config-mcp 1.0.0

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.
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ // ============================================================================
3
+ // Path Utilities - Cross-platform path handling for Claude Code config
4
+ // ============================================================================
5
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
6
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
7
+ if (ar || !(i in from)) {
8
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
9
+ ar[i] = from[i];
10
+ }
11
+ }
12
+ return to.concat(ar || Array.prototype.slice.call(from));
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.getHomeDir = getHomeDir;
16
+ exports.getClaudeConfigDir = getClaudeConfigDir;
17
+ exports.getClaudeAppConfigDir = getClaudeAppConfigDir;
18
+ exports.getClaudeCliConfigPath = getClaudeCliConfigPath;
19
+ exports.getProjectClaudeDir = getProjectClaudeDir;
20
+ exports.getConfigPaths = getConfigPaths;
21
+ exports.validatePath = validatePath;
22
+ exports.pathExists = pathExists;
23
+ exports.getDisplayPath = getDisplayPath;
24
+ exports.getMCPConfigPaths = getMCPConfigPaths;
25
+ var node_path_1 = require("node:path");
26
+ var node_os_1 = require("node:os");
27
+ var node_fs_1 = require("node:fs");
28
+ /**
29
+ * Get home directory cross-platform
30
+ */
31
+ function getHomeDir() {
32
+ return (0, node_os_1.homedir)();
33
+ }
34
+ /**
35
+ * Get Claude Code global config directory (~/.claude)
36
+ */
37
+ function getClaudeConfigDir() {
38
+ return (0, node_path_1.join)(getHomeDir(), '.claude');
39
+ }
40
+ /**
41
+ * Get Claude Code app config directory (~/.config/claude-code)
42
+ * This is where MCP servers are configured for the Claude Code app
43
+ */
44
+ function getClaudeAppConfigDir() {
45
+ return (0, node_path_1.join)(getHomeDir(), '.config', 'claude-code');
46
+ }
47
+ /**
48
+ * Get Claude CLI config file path (~/.claude.json)
49
+ * This is where MCP servers are configured for the claude CLI
50
+ */
51
+ function getClaudeCliConfigPath() {
52
+ return (0, node_path_1.join)(getHomeDir(), '.claude.json');
53
+ }
54
+ /**
55
+ * Get project-local Claude Code config directory (.claude)
56
+ */
57
+ function getProjectClaudeDir(cwd) {
58
+ if (cwd === void 0) { cwd = process.cwd(); }
59
+ return (0, node_path_1.join)(cwd, '.claude');
60
+ }
61
+ /**
62
+ * Resolve Claude config file paths
63
+ */
64
+ function getConfigPaths(cwd) {
65
+ if (cwd === void 0) { cwd = process.cwd(); }
66
+ var claudeDir = getClaudeConfigDir();
67
+ var claudeAppDir = getClaudeAppConfigDir();
68
+ var projectClaudeDir = getProjectClaudeDir(cwd);
69
+ return {
70
+ // Global config files
71
+ globalClaudeMd: (0, node_path_1.join)(claudeDir, 'CLAUDE.md'),
72
+ keybindings: (0, node_path_1.join)(claudeDir, 'keybindings.json'),
73
+ settings: (0, node_path_1.join)(claudeDir, 'settings.json'),
74
+ claudeJson: (0, node_path_1.join)(claudeDir, 'claude.json'),
75
+ mcpServers: (0, node_path_1.join)(claudeDir, 'mcp_servers.json'),
76
+ hooksDir: (0, node_path_1.join)(claudeDir, 'hooks'),
77
+ pluginsDir: (0, node_path_1.join)(claudeDir, 'plugins'),
78
+ // App config file (where MCP servers are actually configured)
79
+ appConfig: (0, node_path_1.join)(claudeAppDir, 'config.json'),
80
+ // Project-local config files
81
+ projectClaudeMd: (0, node_path_1.join)(projectClaudeDir, 'CLAUDE.md'),
82
+ };
83
+ }
84
+ /**
85
+ * Allowed base paths for security (whitelist)
86
+ */
87
+ var ALLOWED_BASES = [
88
+ getClaudeConfigDir(),
89
+ getProjectClaudeDir(),
90
+ getClaudeAppConfigDir(),
91
+ getClaudeCliConfigPath(),
92
+ ];
93
+ /**
94
+ * Validate and sanitize a file path
95
+ * Prevents path traversal attacks and ensures path is within allowed directories
96
+ */
97
+ function validatePath(userPath, cwd) {
98
+ if (cwd === void 0) { cwd = process.cwd(); }
99
+ // Normalize path
100
+ var normalized = (0, node_path_1.normalize)(userPath.replace(/\0/g, ''));
101
+ // Resolve to absolute path
102
+ var absolute = (0, node_path_1.isAbsolute)(normalized)
103
+ ? (0, node_path_1.resolve)(normalized)
104
+ : (0, node_path_1.resolve)(cwd, normalized);
105
+ // Check for path traversal attempts
106
+ if (normalized.includes('..')) {
107
+ throw new Error('Path traversal detected');
108
+ }
109
+ // Build allowed bases including the current project directory
110
+ var allowedBases = __spreadArray(__spreadArray([], ALLOWED_BASES, true), [
111
+ getClaudeConfigDir(),
112
+ getProjectClaudeDir(cwd),
113
+ ], false);
114
+ // Check if path is within allowed bases
115
+ // For files that don't exist yet, check the parent directory
116
+ var checkPath = absolute;
117
+ while (checkPath !== '/' && checkPath !== cwd && checkPath !== getHomeDir()) {
118
+ var isAllowed = allowedBases.some(function (base) {
119
+ var resolvedBase = (0, node_path_1.resolve)(base);
120
+ return (checkPath === resolvedBase ||
121
+ checkPath.startsWith(resolvedBase + '/') ||
122
+ resolvedBase.startsWith(checkPath + '/'));
123
+ });
124
+ if (isAllowed) {
125
+ return absolute;
126
+ }
127
+ // Move up one directory to check
128
+ checkPath = (0, node_path_1.dirname)(checkPath);
129
+ }
130
+ // If we get here, the path is not allowed
131
+ throw new Error("Path not allowed: ".concat(userPath));
132
+ }
133
+ /**
134
+ * Check if a path exists
135
+ */
136
+ function pathExists(filePath) {
137
+ try {
138
+ return (0, node_fs_1.existsSync)(filePath);
139
+ }
140
+ catch (_a) {
141
+ return false;
142
+ }
143
+ }
144
+ /**
145
+ * Get relative path from home directory for display
146
+ */
147
+ function getDisplayPath(filePath) {
148
+ var home = getHomeDir();
149
+ if (filePath.startsWith(home)) {
150
+ return filePath.replace(home, '~');
151
+ }
152
+ return filePath;
153
+ }
154
+ /**
155
+ * Get MCP config file paths based on target
156
+ * @param target - Which config(s) to get ('cli', 'app', or 'both')
157
+ * @returns Object with the requested config paths
158
+ */
159
+ function getMCPConfigPaths(target) {
160
+ if (target === void 0) { target = 'both'; }
161
+ var result = {};
162
+ if (target === 'cli' || target === 'both') {
163
+ result.cli = getClaudeCliConfigPath();
164
+ }
165
+ if (target === 'app' || target === 'both') {
166
+ result.app = getConfigPaths().appConfig;
167
+ }
168
+ return result;
169
+ }
@@ -0,0 +1,173 @@
1
+ // ============================================================================
2
+ // Path Utilities - Cross-platform path handling for Claude Code config
3
+ // ============================================================================
4
+
5
+ import { resolve, normalize, isAbsolute, join, dirname } from 'node:path';
6
+ import { homedir } from 'node:os';
7
+ import { existsSync, realpathSync } from 'node:fs';
8
+
9
+ /**
10
+ * Get home directory cross-platform
11
+ */
12
+ export function getHomeDir(): string {
13
+ return homedir();
14
+ }
15
+
16
+ /**
17
+ * Get Claude Code global config directory (~/.claude)
18
+ */
19
+ export function getClaudeConfigDir(): string {
20
+ return join(getHomeDir(), '.claude');
21
+ }
22
+
23
+ /**
24
+ * Get Claude Code app config directory (~/.config/claude-code)
25
+ * This is where MCP servers are configured for the Claude Code app
26
+ */
27
+ export function getClaudeAppConfigDir(): string {
28
+ return join(getHomeDir(), '.config', 'claude-code');
29
+ }
30
+
31
+ /**
32
+ * Get Claude CLI config file path (~/.claude.json)
33
+ * This is where MCP servers are configured for the claude CLI
34
+ */
35
+ export function getClaudeCliConfigPath(): string {
36
+ return join(getHomeDir(), '.claude.json');
37
+ }
38
+
39
+ /**
40
+ * Get project-local Claude Code config directory (.claude)
41
+ */
42
+ export function getProjectClaudeDir(cwd: string = process.cwd()): string {
43
+ return join(cwd, '.claude');
44
+ }
45
+
46
+ /**
47
+ * Resolve Claude config file paths
48
+ */
49
+ export function getConfigPaths(cwd: string = process.cwd()) {
50
+ const claudeDir = getClaudeConfigDir();
51
+ const claudeAppDir = getClaudeAppConfigDir();
52
+ const projectClaudeDir = getProjectClaudeDir(cwd);
53
+
54
+ return {
55
+ // Global config files
56
+ globalClaudeMd: join(claudeDir, 'CLAUDE.md'),
57
+ keybindings: join(claudeDir, 'keybindings.json'),
58
+ settings: join(claudeDir, 'settings.json'),
59
+ claudeJson: join(claudeDir, 'claude.json'),
60
+ mcpServers: join(claudeDir, 'mcp_servers.json'),
61
+ hooksDir: join(claudeDir, 'hooks'),
62
+ pluginsDir: join(claudeDir, 'plugins'),
63
+
64
+ // App config file (where MCP servers are actually configured)
65
+ appConfig: join(claudeAppDir, 'config.json'),
66
+
67
+ // Project-local config files
68
+ projectClaudeMd: join(projectClaudeDir, 'CLAUDE.md'),
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Allowed base paths for security (whitelist)
74
+ */
75
+ const ALLOWED_BASES = [
76
+ getClaudeConfigDir(),
77
+ getProjectClaudeDir(),
78
+ getClaudeAppConfigDir(),
79
+ getClaudeCliConfigPath(),
80
+ ];
81
+
82
+ /**
83
+ * Validate and sanitize a file path
84
+ * Prevents path traversal attacks and ensures path is within allowed directories
85
+ */
86
+ export function validatePath(userPath: string, cwd: string = process.cwd()): string {
87
+ // Normalize path
88
+ const normalized = normalize(userPath.replace(/\0/g, ''));
89
+
90
+ // Resolve to absolute path
91
+ const absolute = isAbsolute(normalized)
92
+ ? resolve(normalized)
93
+ : resolve(cwd, normalized);
94
+
95
+ // Check for path traversal attempts
96
+ if (normalized.includes('..')) {
97
+ throw new Error('Path traversal detected');
98
+ }
99
+
100
+ // Build allowed bases including the current project directory
101
+ const allowedBases = [
102
+ ...ALLOWED_BASES,
103
+ getClaudeConfigDir(),
104
+ getProjectClaudeDir(cwd),
105
+ ];
106
+
107
+ // Check if path is within allowed bases
108
+ // For files that don't exist yet, check the parent directory
109
+ let checkPath = absolute;
110
+ while (checkPath !== '/' && checkPath !== cwd && checkPath !== getHomeDir()) {
111
+ const isAllowed = allowedBases.some(base => {
112
+ const resolvedBase = resolve(base);
113
+ return (
114
+ checkPath === resolvedBase ||
115
+ checkPath.startsWith(resolvedBase + '/') ||
116
+ resolvedBase.startsWith(checkPath + '/')
117
+ );
118
+ });
119
+
120
+ if (isAllowed) {
121
+ return absolute;
122
+ }
123
+
124
+ // Move up one directory to check
125
+ checkPath = dirname(checkPath);
126
+ }
127
+
128
+ // If we get here, the path is not allowed
129
+ throw new Error(`Path not allowed: ${userPath}`);
130
+ }
131
+
132
+ /**
133
+ * Check if a path exists
134
+ */
135
+ export function pathExists(filePath: string): boolean {
136
+ try {
137
+ return existsSync(filePath);
138
+ } catch {
139
+ return false;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Get relative path from home directory for display
145
+ */
146
+ export function getDisplayPath(filePath: string): string {
147
+ const home = getHomeDir();
148
+ if (filePath.startsWith(home)) {
149
+ return filePath.replace(home, '~');
150
+ }
151
+ return filePath;
152
+ }
153
+
154
+ /**
155
+ * Get MCP config file paths based on target
156
+ * @param target - Which config(s) to get ('cli', 'app', or 'both')
157
+ * @returns Object with the requested config paths
158
+ */
159
+ export function getMCPConfigPaths(target: 'cli' | 'app' | 'both' = 'both'): {
160
+ cli?: string;
161
+ app?: string;
162
+ } {
163
+ const result: { cli?: string; app?: string } = {};
164
+
165
+ if (target === 'cli' || target === 'both') {
166
+ result.cli = getClaudeCliConfigPath();
167
+ }
168
+ if (target === 'app' || target === 'both') {
169
+ result.app = getConfigPaths().appConfig;
170
+ }
171
+
172
+ return result;
173
+ }
package/test/test.js ADDED
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env bun
2
+ "use strict";
3
+ // Test script for Claude Code Config MCP Server
4
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
5
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
6
+ return new (P || (P = Promise))(function (resolve, reject) {
7
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
8
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
11
+ });
12
+ };
13
+ var __generator = (this && this.__generator) || function (thisArg, body) {
14
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
15
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
16
+ function verb(n) { return function (v) { return step([n, v]); }; }
17
+ function step(op) {
18
+ if (f) throw new TypeError("Generator is already executing.");
19
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
20
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
21
+ if (y = 0, t) op = [op[0] & 2, t.value];
22
+ switch (op[0]) {
23
+ case 0: case 1: t = op; break;
24
+ case 4: _.label++; return { value: op[1], done: false };
25
+ case 5: _.label++; y = op[1]; op = [0]; continue;
26
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
27
+ default:
28
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
29
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
30
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
31
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
32
+ if (t[2]) _.ops.pop();
33
+ _.trys.pop(); continue;
34
+ }
35
+ op = body.call(thisArg, _);
36
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
37
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
38
+ }
39
+ };
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ var index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
42
+ var stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
43
+ function main() {
44
+ return __awaiter(this, void 0, void 0, function () {
45
+ var transport, client, listResult, readResult, content, paths, _i, paths_1, pathType, pathResult, data, hooksResult, hooks, error_1;
46
+ var _a, _b, _c;
47
+ return __generator(this, function (_d) {
48
+ switch (_d.label) {
49
+ case 0:
50
+ console.log('Starting Claude Code Config MCP Server test...\n');
51
+ transport = new stdio_js_1.StdioClientTransport({
52
+ command: 'bun',
53
+ args: ['run', 'src/index.ts'],
54
+ cwd: '/Users/ebowwa/Desktop/codespaces/MCP/packages/claude-code-config',
55
+ });
56
+ client = new index_js_1.Client({ name: 'test-client', version: '1.0.0' }, { capabilities: {} });
57
+ _d.label = 1;
58
+ case 1:
59
+ _d.trys.push([1, 10, 11, 13]);
60
+ // Connect to server
61
+ return [4 /*yield*/, client.connect(transport)];
62
+ case 2:
63
+ // Connect to server
64
+ _d.sent();
65
+ console.log('✅ Connected to server\n');
66
+ // Test 1: List config files
67
+ console.log('📋 Test 1: List config files');
68
+ return [4 /*yield*/, client.callTool({
69
+ name: 'list_config_files',
70
+ arguments: { includeProject: true },
71
+ })];
72
+ case 3:
73
+ listResult = _d.sent();
74
+ console.log(JSON.stringify(listResult, null, 2));
75
+ console.log('');
76
+ // Test 2: Read global CLAUDE.md
77
+ console.log('📄 Test 2: Read global CLAUDE.md');
78
+ return [4 /*yield*/, client.callTool({
79
+ name: 'read_global_claude_md',
80
+ arguments: {},
81
+ })];
82
+ case 4:
83
+ readResult = _d.sent();
84
+ content = (_a = readResult.content[0]) === null || _a === void 0 ? void 0 : _a.text;
85
+ console.log("Content preview (".concat(content.length, " chars):"));
86
+ console.log(content.substring(0, 200) + '...\n');
87
+ // Test 3: Get config paths
88
+ console.log('🔍 Test 3: Get config paths');
89
+ paths = ['CLAUDE_MD_GLOBAL', 'KEYBINDINGS', 'SETTINGS'];
90
+ _i = 0, paths_1 = paths;
91
+ _d.label = 5;
92
+ case 5:
93
+ if (!(_i < paths_1.length)) return [3 /*break*/, 8];
94
+ pathType = paths_1[_i];
95
+ return [4 /*yield*/, client.callTool({
96
+ name: 'get_config_path',
97
+ arguments: { fileType: pathType },
98
+ })];
99
+ case 6:
100
+ pathResult = _d.sent();
101
+ data = JSON.parse((_b = pathResult.content[0]) === null || _b === void 0 ? void 0 : _b.text);
102
+ console.log(" ".concat(pathType, ": ").concat(data.path, " (exists: ").concat(data.exists, ")"));
103
+ _d.label = 7;
104
+ case 7:
105
+ _i++;
106
+ return [3 /*break*/, 5];
107
+ case 8:
108
+ console.log('');
109
+ // Test 4: List hooks
110
+ console.log('🪝 Test 4: List hooks');
111
+ return [4 /*yield*/, client.callTool({
112
+ name: 'list_hooks',
113
+ arguments: {},
114
+ })];
115
+ case 9:
116
+ hooksResult = _d.sent();
117
+ hooks = JSON.parse((_c = hooksResult.content[0]) === null || _c === void 0 ? void 0 : _c.text);
118
+ console.log(" Found ".concat(hooks.length, " hooks"));
119
+ console.log('');
120
+ console.log('✅ All tests passed!');
121
+ return [3 /*break*/, 13];
122
+ case 10:
123
+ error_1 = _d.sent();
124
+ console.error('❌ Test failed:', error_1);
125
+ process.exit(1);
126
+ return [3 /*break*/, 13];
127
+ case 11: return [4 /*yield*/, client.close()];
128
+ case 12:
129
+ _d.sent();
130
+ return [7 /*endfinally*/];
131
+ case 13: return [2 /*return*/];
132
+ }
133
+ });
134
+ });
135
+ }
136
+ main();
package/test/test.ts ADDED
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env bun
2
+ // Test script for Claude Code Config MCP Server
3
+
4
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
6
+
7
+ async function main() {
8
+ console.log('Starting Claude Code Config MCP Server test...\n');
9
+
10
+ // Create client
11
+ const transport = new StdioClientTransport({
12
+ command: 'bun',
13
+ args: ['run', 'src/index.ts'],
14
+ cwd: '/Users/ebowwa/Desktop/codespaces/MCP/packages/claude-code-config',
15
+ });
16
+
17
+ const client = new Client(
18
+ { name: 'test-client', version: '1.0.0' },
19
+ { capabilities: {} }
20
+ );
21
+
22
+ try {
23
+ // Connect to server
24
+ await client.connect(transport);
25
+ console.log('✅ Connected to server\n');
26
+
27
+ // Test 1: List config files
28
+ console.log('📋 Test 1: List config files');
29
+ const listResult = await client.callTool({
30
+ name: 'list_config_files',
31
+ arguments: { includeProject: true },
32
+ });
33
+ console.log(JSON.stringify(listResult, null, 2));
34
+ console.log('');
35
+
36
+ // Test 2: Read global CLAUDE.md
37
+ console.log('📄 Test 2: Read global CLAUDE.md');
38
+ const readResult = await client.callTool({
39
+ name: 'read_global_claude_md',
40
+ arguments: {},
41
+ });
42
+ const content = readResult.content[0]?.text as string;
43
+ console.log(`Content preview (${content.length} chars):`);
44
+ console.log(content.substring(0, 200) + '...\n');
45
+
46
+ // Test 3: Get config paths
47
+ console.log('🔍 Test 3: Get config paths');
48
+ const paths = ['CLAUDE_MD_GLOBAL', 'KEYBINDINGS', 'SETTINGS'] as const;
49
+ for (const pathType of paths) {
50
+ const pathResult = await client.callTool({
51
+ name: 'get_config_path',
52
+ arguments: { fileType: pathType },
53
+ });
54
+ const data = JSON.parse(pathResult.content[0]?.text as string);
55
+ console.log(` ${pathType}: ${data.path} (exists: ${data.exists})`);
56
+ }
57
+ console.log('');
58
+
59
+ // Test 4: List hooks
60
+ console.log('🪝 Test 4: List hooks');
61
+ const hooksResult = await client.callTool({
62
+ name: 'list_hooks',
63
+ arguments: {},
64
+ });
65
+ const hooks = JSON.parse(hooksResult.content[0]?.text as string);
66
+ console.log(` Found ${hooks.length} hooks`);
67
+ console.log('');
68
+
69
+ console.log('✅ All tests passed!');
70
+
71
+ } catch (error) {
72
+ console.error('❌ Test failed:', error);
73
+ process.exit(1);
74
+ } finally {
75
+ await client.close();
76
+ }
77
+ }
78
+
79
+ main();