@meldocio/mcp-stdio-proxy 1.0.22 → 1.0.24

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,328 @@
1
+ /**
2
+ * MCP Installation Orchestrator
3
+ *
4
+ * Unified installer for all MCP clients (Claude Desktop, Cursor, Claude Code, Local).
5
+ * Consolidates installation logic to eliminate code duplication.
6
+ */
7
+
8
+ const logger = require('../core/logger');
9
+ const { getConfigPath, getClientDisplayName } = require('./config-paths');
10
+ const { getConfigTemplate, configsEqual, mergeClaudeDesktopConfig, mergeCursorConfig, mergeClaudeCodeConfig, removeMeldocConfig, removeMeldocClaudeCodeConfig } = require('./templates');
11
+ const { readConfigSafe, writeConfig, fileExists } = require('./config-manager');
12
+ const path = require('path');
13
+
14
+ /**
15
+ * Installation result codes
16
+ */
17
+ const INSTALL_RESULTS = {
18
+ SUCCESS: 'success',
19
+ ALREADY_CONFIGURED: 'already_configured',
20
+ UPDATED: 'updated',
21
+ ERROR: 'error'
22
+ };
23
+
24
+ /**
25
+ * Unified MCP Installer
26
+ * Handles installation for all supported clients
27
+ */
28
+ class Installer {
29
+ /**
30
+ * Create an installer for a specific client
31
+ * @param {string} client - Client type: 'claude-desktop', 'cursor', 'claude-code', 'local'
32
+ * @param {string} [scope='project'] - Installation scope: 'global', 'project', 'user', 'local'
33
+ */
34
+ constructor(client, scope = 'project') {
35
+ this.client = client;
36
+ this.scope = scope;
37
+ this.clientName = getClientDisplayName(client);
38
+ this.configPath = getConfigPath(client, scope);
39
+ this.expectedConfig = getConfigTemplate(client);
40
+ }
41
+
42
+ /**
43
+ * Get the server key name based on client type
44
+ * @returns {string} Server key ('meldoc' or 'meldoc-mcp')
45
+ */
46
+ getServerKey() {
47
+ return this.client === 'claude-code' ? 'meldoc-mcp' : 'meldoc';
48
+ }
49
+
50
+ /**
51
+ * Check if Meldoc is already configured
52
+ * @returns {Object} { configured: boolean, isEqual: boolean, existingConfig: Object|null }
53
+ */
54
+ checkExistingConfig() {
55
+ const config = readConfigSafe(this.configPath);
56
+ const serverKey = this.getServerKey();
57
+
58
+ if (!config.mcpServers || !config.mcpServers[serverKey]) {
59
+ return {
60
+ configured: false,
61
+ isEqual: false,
62
+ existingConfig: null
63
+ };
64
+ }
65
+
66
+ const existingConfig = config.mcpServers[serverKey];
67
+ const isEqual = configsEqual(existingConfig, this.expectedConfig);
68
+
69
+ return {
70
+ configured: true,
71
+ isEqual,
72
+ existingConfig
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Install Meldoc MCP configuration
78
+ * @returns {Object} { result: string, message: string, configPath: string }
79
+ */
80
+ install() {
81
+ try {
82
+ logger.section(`🚀 Installing Meldoc MCP for ${this.clientName}`);
83
+ console.log();
84
+
85
+ logger.info(`Config file location: ${logger.highlight(this.configPath)}`);
86
+ console.log();
87
+
88
+ // Check existing configuration
89
+ const { configured, isEqual, existingConfig } = this.checkExistingConfig();
90
+
91
+ if (configured && isEqual) {
92
+ logger.success('Meldoc MCP is already configured correctly!');
93
+ console.log();
94
+ logger.info('Current configuration:');
95
+ console.log(' ' + logger.highlight(JSON.stringify(existingConfig, null, 2)));
96
+ console.log();
97
+ this.printNextSteps();
98
+
99
+ return {
100
+ result: INSTALL_RESULTS.ALREADY_CONFIGURED,
101
+ message: 'Configuration already exists and is correct',
102
+ configPath: this.configPath
103
+ };
104
+ }
105
+
106
+ if (configured && !isEqual) {
107
+ logger.warn('Meldoc MCP is already configured, but with different settings');
108
+ console.log();
109
+ logger.info('Current configuration:');
110
+ console.log(' ' + logger.highlight(JSON.stringify(existingConfig, null, 2)));
111
+ console.log();
112
+ logger.info('Expected configuration:');
113
+ console.log(' ' + logger.highlight(JSON.stringify(this.expectedConfig, null, 2)));
114
+ console.log();
115
+ logger.info('To update the configuration, run:');
116
+ console.log(' ' + logger.highlight('npx @meldocio/mcp-stdio-proxy uninstall'));
117
+ console.log(' ' + logger.highlight('npx @meldocio/mcp-stdio-proxy install'));
118
+ console.log();
119
+
120
+ return {
121
+ result: INSTALL_RESULTS.ALREADY_CONFIGURED,
122
+ message: 'Configuration exists but differs from expected',
123
+ configPath: this.configPath
124
+ };
125
+ }
126
+
127
+ // Read existing config or create new
128
+ const config = readConfigSafe(this.configPath);
129
+ const configExists = fileExists(this.configPath);
130
+
131
+ if (configExists) {
132
+ logger.info('Found existing configuration file');
133
+ } else {
134
+ logger.info('Configuration file does not exist, will create it');
135
+ }
136
+
137
+ // Merge Meldoc configuration
138
+ const { merged } = this.mergeConfig(config, this.expectedConfig);
139
+
140
+ // Create directory if needed
141
+ const configDir = path.dirname(this.configPath);
142
+ if (!fileExists(configDir)) {
143
+ logger.info(`Creating directory: ${configDir}`);
144
+ }
145
+
146
+ // Write config
147
+ writeConfig(this.configPath, merged);
148
+
149
+ logger.success('Configuration added successfully!');
150
+ console.log();
151
+
152
+ // Show what was added
153
+ logger.info('Added configuration:');
154
+ console.log(' ' + logger.highlight(JSON.stringify(this.expectedConfig, null, 2)));
155
+ console.log();
156
+
157
+ this.printNextSteps();
158
+
159
+ return {
160
+ result: INSTALL_RESULTS.SUCCESS,
161
+ message: 'Configuration installed successfully',
162
+ configPath: this.configPath
163
+ };
164
+
165
+ } catch (error) {
166
+ logger.error(`Installation failed: ${error.message}`);
167
+ return {
168
+ result: INSTALL_RESULTS.ERROR,
169
+ message: error.message,
170
+ configPath: this.configPath
171
+ };
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Merge Meldoc config into existing config based on client type
177
+ * @param {Object} config - Existing configuration
178
+ * @param {Object} newServer - New Meldoc server configuration
179
+ * @returns {Object} { merged: Object, changed: boolean }
180
+ */
181
+ mergeConfig(config, newServer) {
182
+ switch (this.client) {
183
+ case 'claude-desktop':
184
+ return mergeClaudeDesktopConfig(config, newServer);
185
+ case 'cursor':
186
+ return mergeCursorConfig(config, newServer);
187
+ case 'claude-code':
188
+ return mergeClaudeCodeConfig(config, newServer);
189
+ case 'local':
190
+ return mergeCursorConfig(config, newServer); // Uses same format as Cursor
191
+ default:
192
+ throw new Error(`Unknown client type: ${this.client}`);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Uninstall Meldoc MCP configuration
198
+ * @returns {Object} { result: string, message: string, configPath: string }
199
+ */
200
+ uninstall() {
201
+ try {
202
+ logger.section(`🗑️ Uninstalling Meldoc MCP from ${this.clientName}`);
203
+ console.log();
204
+
205
+ logger.info(`Config file location: ${logger.highlight(this.configPath)}`);
206
+ console.log();
207
+
208
+ // Check if config exists
209
+ if (!fileExists(this.configPath)) {
210
+ logger.info('Configuration file does not exist');
211
+ logger.success('Nothing to uninstall');
212
+ console.log();
213
+ return {
214
+ result: INSTALL_RESULTS.SUCCESS,
215
+ message: 'No configuration found',
216
+ configPath: this.configPath
217
+ };
218
+ }
219
+
220
+ // Read config
221
+ const config = readConfigSafe(this.configPath);
222
+ const serverKey = this.getServerKey();
223
+
224
+ // Check if Meldoc is configured
225
+ if (!config.mcpServers || !config.mcpServers[serverKey]) {
226
+ logger.info('Meldoc MCP is not configured');
227
+ logger.success('Nothing to uninstall');
228
+ console.log();
229
+ return {
230
+ result: INSTALL_RESULTS.SUCCESS,
231
+ message: 'Meldoc not configured',
232
+ configPath: this.configPath
233
+ };
234
+ }
235
+
236
+ // Remove Meldoc configuration
237
+ const { merged } = this.client === 'claude-code'
238
+ ? removeMeldocClaudeCodeConfig(config)
239
+ : removeMeldocConfig(config);
240
+
241
+ // Write updated config
242
+ writeConfig(this.configPath, merged);
243
+
244
+ logger.success('Configuration removed successfully!');
245
+ console.log();
246
+ logger.info(`Remember to restart ${this.clientName} to apply changes`);
247
+ console.log();
248
+
249
+ return {
250
+ result: INSTALL_RESULTS.SUCCESS,
251
+ message: 'Configuration uninstalled successfully',
252
+ configPath: this.configPath
253
+ };
254
+
255
+ } catch (error) {
256
+ logger.error(`Uninstallation failed: ${error.message}`);
257
+ return {
258
+ result: INSTALL_RESULTS.ERROR,
259
+ message: error.message,
260
+ configPath: this.configPath
261
+ };
262
+ }
263
+ }
264
+
265
+ /**
266
+ * Print next steps after installation
267
+ */
268
+ printNextSteps() {
269
+ logger.info('✅ Next steps:');
270
+ console.log();
271
+
272
+ if (this.client === 'claude-desktop') {
273
+ console.log(' 1. Restart Claude Desktop (if you haven\'t already)');
274
+ console.log(' 2. Run: ' + logger.highlight('npx @meldocio/mcp-stdio-proxy auth login'));
275
+ } else if (this.client === 'cursor') {
276
+ console.log(` 1. Restart Cursor (if you haven't already)`);
277
+ console.log(' 2. Run: ' + logger.highlight('npx @meldocio/mcp-stdio-proxy auth login'));
278
+ } else if (this.client === 'claude-code') {
279
+ console.log(' 1. Restart Claude Code (if you haven\'t already)');
280
+ console.log(' 2. Run: ' + logger.highlight('npx @meldocio/mcp-stdio-proxy auth login'));
281
+ } else {
282
+ console.log(' 1. Restart your MCP client');
283
+ console.log(' 2. Run: ' + logger.highlight('npx @meldocio/mcp-stdio-proxy auth login'));
284
+ }
285
+
286
+ console.log();
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Factory function to create installer
292
+ * @param {string} client - Client type
293
+ * @param {string} [scope='project'] - Installation scope
294
+ * @returns {Installer} Installer instance
295
+ */
296
+ function createInstaller(client, scope = 'project') {
297
+ return new Installer(client, scope);
298
+ }
299
+
300
+ /**
301
+ * Quick install function
302
+ * @param {string} client - Client type
303
+ * @param {string} [scope='project'] - Installation scope
304
+ * @returns {Object} Installation result
305
+ */
306
+ function install(client, scope = 'project') {
307
+ const installer = createInstaller(client, scope);
308
+ return installer.install();
309
+ }
310
+
311
+ /**
312
+ * Quick uninstall function
313
+ * @param {string} client - Client type
314
+ * @param {string} [scope='project'] - Installation scope
315
+ * @returns {Object} Uninstallation result
316
+ */
317
+ function uninstall(client, scope = 'project') {
318
+ const installer = createInstaller(client, scope);
319
+ return installer.uninstall();
320
+ }
321
+
322
+ module.exports = {
323
+ Installer,
324
+ createInstaller,
325
+ install,
326
+ uninstall,
327
+ INSTALL_RESULTS
328
+ };
@@ -0,0 +1,266 @@
1
+ /**
2
+ * MCP Configuration Templates
3
+ *
4
+ * Standard configuration templates for different MCP clients.
5
+ * Includes comparison and merging utilities.
6
+ */
7
+
8
+ /**
9
+ * Get expected Meldoc MCP configuration for Claude Desktop
10
+ * @returns {Object} Claude Desktop MCP configuration
11
+ */
12
+ function getClaudeDesktopConfig() {
13
+ return {
14
+ command: 'npx',
15
+ args: ['-y', '@meldocio/mcp-stdio-proxy@latest']
16
+ };
17
+ }
18
+
19
+ /**
20
+ * Get expected Meldoc MCP configuration for Cursor
21
+ * @returns {Object} Cursor MCP configuration
22
+ */
23
+ function getCursorConfig() {
24
+ return {
25
+ command: 'npx',
26
+ args: ['-y', '@meldocio/mcp-stdio-proxy@latest']
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Get expected Meldoc MCP configuration for Claude Code
32
+ * @returns {Object} Claude Code MCP configuration
33
+ */
34
+ function getClaudeCodeConfig() {
35
+ return {
36
+ type: 'stdio',
37
+ command: 'npx',
38
+ args: ['-y', '@meldocio/mcp-stdio-proxy@latest']
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Get expected Meldoc MCP configuration for local (generic) installation
44
+ * @returns {Object} Local MCP configuration
45
+ */
46
+ function getLocalConfig() {
47
+ return {
48
+ type: 'stdio',
49
+ command: 'npx',
50
+ args: ['-y', '@meldocio/mcp-stdio-proxy@latest']
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Get configuration template by client type
56
+ * @param {string} client - Client type: 'claude-desktop', 'cursor', 'claude-code', 'local'
57
+ * @returns {Object} Configuration template
58
+ * @throws {Error} If client type is unknown
59
+ */
60
+ function getConfigTemplate(client) {
61
+ switch (client) {
62
+ case 'claude-desktop':
63
+ return getClaudeDesktopConfig();
64
+ case 'cursor':
65
+ return getCursorConfig();
66
+ case 'claude-code':
67
+ return getClaudeCodeConfig();
68
+ case 'local':
69
+ return getLocalConfig();
70
+ default:
71
+ throw new Error(`Unknown client type: ${client}`);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Deep equality comparison for configuration objects
77
+ * @param {any} a - First value
78
+ * @param {any} b - Second value
79
+ * @returns {boolean} True if values are deeply equal
80
+ */
81
+ function deepEqual(a, b) {
82
+ // Handle null/undefined
83
+ if (a === b) return true;
84
+ if (a == null || b == null) return false;
85
+ if (typeof a !== typeof b) return false;
86
+
87
+ // Handle non-objects
88
+ if (typeof a !== 'object') return a === b;
89
+
90
+ // Handle arrays
91
+ if (Array.isArray(a) && Array.isArray(b)) {
92
+ if (a.length !== b.length) return false;
93
+ return a.every((val, idx) => deepEqual(val, b[idx]));
94
+ }
95
+
96
+ // Handle objects
97
+ if (Array.isArray(a) || Array.isArray(b)) return false;
98
+
99
+ const keysA = Object.keys(a);
100
+ const keysB = Object.keys(b);
101
+
102
+ if (keysA.length !== keysB.length) return false;
103
+
104
+ return keysA.every(key => {
105
+ if (!Object.prototype.hasOwnProperty.call(b, key)) return false;
106
+ return deepEqual(a[key], b[key]);
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Check if two configurations are equal
112
+ * @param {Object} config1 - First configuration
113
+ * @param {Object} config2 - Second configuration
114
+ * @returns {boolean} True if configurations are equal
115
+ */
116
+ function configsEqual(config1, config2) {
117
+ return deepEqual(config1, config2);
118
+ }
119
+
120
+ /**
121
+ * Merge Meldoc server configuration into existing config
122
+ * For Claude Desktop format (mcpServers.meldoc)
123
+ * @param {Object} existingConfig - Existing configuration object
124
+ * @param {Object} newServer - New Meldoc server configuration
125
+ * @returns {Object} { merged: Object, changed: boolean }
126
+ */
127
+ function mergeClaudeDesktopConfig(existingConfig, newServer) {
128
+ const merged = { ...existingConfig };
129
+
130
+ // Ensure mcpServers exists
131
+ if (!merged.mcpServers) {
132
+ merged.mcpServers = {};
133
+ }
134
+
135
+ // Check if meldoc server exists
136
+ if (!merged.mcpServers.meldoc) {
137
+ merged.mcpServers.meldoc = newServer;
138
+ return { merged, changed: true };
139
+ }
140
+
141
+ // Check if configs are equal
142
+ if (configsEqual(merged.mcpServers.meldoc, newServer)) {
143
+ return { merged, changed: false };
144
+ }
145
+
146
+ // Update if different
147
+ merged.mcpServers.meldoc = newServer;
148
+ return { merged, changed: true };
149
+ }
150
+
151
+ /**
152
+ * Merge Meldoc server configuration into existing config
153
+ * For Cursor/Local format (mcpServers.meldoc)
154
+ * @param {Object} existingConfig - Existing configuration object
155
+ * @param {Object} newServer - New Meldoc server configuration
156
+ * @returns {Object} { merged: Object, changed: boolean }
157
+ */
158
+ function mergeCursorConfig(existingConfig, newServer) {
159
+ const merged = { ...existingConfig };
160
+
161
+ // Ensure mcpServers exists
162
+ if (!merged.mcpServers) {
163
+ merged.mcpServers = {};
164
+ }
165
+
166
+ // Check if meldoc server exists
167
+ if (!merged.mcpServers.meldoc) {
168
+ merged.mcpServers.meldoc = newServer;
169
+ return { merged, changed: true };
170
+ }
171
+
172
+ // Check if configs are equal
173
+ if (configsEqual(merged.mcpServers.meldoc, newServer)) {
174
+ return { merged, changed: false };
175
+ }
176
+
177
+ // Update if different
178
+ merged.mcpServers.meldoc = newServer;
179
+ return { merged, changed: true };
180
+ }
181
+
182
+ /**
183
+ * Merge Meldoc server configuration into existing config
184
+ * For Claude Code format (mcpServers.meldoc-mcp)
185
+ * @param {Object} existingConfig - Existing configuration object
186
+ * @param {Object} newServer - New Meldoc server configuration
187
+ * @returns {Object} { merged: Object, changed: boolean }
188
+ */
189
+ function mergeClaudeCodeConfig(existingConfig, newServer) {
190
+ const merged = { ...existingConfig };
191
+
192
+ // Ensure mcpServers exists
193
+ if (!merged.mcpServers) {
194
+ merged.mcpServers = {};
195
+ }
196
+
197
+ // Check if meldoc-mcp server exists (Claude Code uses different key)
198
+ if (!merged.mcpServers['meldoc-mcp']) {
199
+ merged.mcpServers['meldoc-mcp'] = newServer;
200
+ return { merged, changed: true };
201
+ }
202
+
203
+ // Check if configs are equal
204
+ if (configsEqual(merged.mcpServers['meldoc-mcp'], newServer)) {
205
+ return { merged, changed: false };
206
+ }
207
+
208
+ // Update if different
209
+ merged.mcpServers['meldoc-mcp'] = newServer;
210
+ return { merged, changed: true };
211
+ }
212
+
213
+ /**
214
+ * Remove Meldoc server from configuration
215
+ * For Claude Desktop/Cursor format
216
+ * @param {Object} config - Configuration object
217
+ * @returns {Object} { merged: Object, changed: boolean }
218
+ */
219
+ function removeMeldocConfig(config) {
220
+ const merged = { ...config };
221
+
222
+ if (!merged.mcpServers || !merged.mcpServers.meldoc) {
223
+ return { merged, changed: false };
224
+ }
225
+
226
+ delete merged.mcpServers.meldoc;
227
+ return { merged, changed: true };
228
+ }
229
+
230
+ /**
231
+ * Remove Meldoc server from Claude Code configuration
232
+ * @param {Object} config - Configuration object
233
+ * @returns {Object} { merged: Object, changed: boolean }
234
+ */
235
+ function removeMeldocClaudeCodeConfig(config) {
236
+ const merged = { ...config };
237
+
238
+ if (!merged.mcpServers || !merged.mcpServers['meldoc-mcp']) {
239
+ return { merged, changed: false };
240
+ }
241
+
242
+ delete merged.mcpServers['meldoc-mcp'];
243
+ return { merged, changed: true };
244
+ }
245
+
246
+ module.exports = {
247
+ // Template getters
248
+ getClaudeDesktopConfig,
249
+ getCursorConfig,
250
+ getClaudeCodeConfig,
251
+ getLocalConfig,
252
+ getConfigTemplate,
253
+
254
+ // Comparison utilities
255
+ deepEqual,
256
+ configsEqual,
257
+
258
+ // Merge utilities
259
+ mergeClaudeDesktopConfig,
260
+ mergeCursorConfig,
261
+ mergeClaudeCodeConfig,
262
+
263
+ // Remove utilities
264
+ removeMeldocConfig,
265
+ removeMeldocClaudeCodeConfig
266
+ };