@grebyn/toolflow-mcp-server
Version:
MCP server for managing other MCP servers - discover, install, organize into bundles, and automate with workflows. Uses StreamableHTTP transport with dual OAuth/API key authentication.
170 lines ⢠8.05 kB
JavaScript
/**
* Write Client Config Tool
* Write an updated MCP configuration to a client using the toolflow CLI
*/
import { exec } from 'child_process';
import { promisify } from 'util';
const execAsync = promisify(exec);
export const writeClientConfigTool = {
name: 'write_client_config',
description: 'Write an updated MCP configuration to a client. Uses the CLIENT environment variable by default, or a custom path if provided. **CRITICAL: Always preserve existing working MCP servers unless explicitly asked to remove them.** First read the current config, then modify it to add/update/remove only the requested changes. The CLI automatically creates backups. If servers require API keys/tokens (indicated by env properties), guide the user to obtain credentials.',
inputSchema: {
type: 'object',
properties: {
config: {
type: 'object',
description: 'Complete configuration object to write. MUST include all existing servers you want to keep, plus any new/modified servers. Do NOT remove existing servers unless explicitly requested.',
additionalProperties: true
},
config_path: {
type: 'string',
description: 'Custom configuration file path (overrides CLIENT environment variable)'
}
},
required: ['config']
},
async execute(args, context) {
try {
// Validate config is provided
if (!args.config || typeof args.config !== 'object') {
throw new Error('Invalid configuration: config object is required');
}
// Escape the JSON for shell command
const configJson = JSON.stringify(args.config);
const escapedConfig = configJson.replace(/"/g, '\\"').replace(/\$/g, '\\$');
let command;
let configTarget;
if (args.config_path) {
// Custom path override
command = `npx @grebyn/toolflow-cli@latest write "${escapedConfig}" --path "${args.config_path}"`;
configTarget = `custom path: ${args.config_path}`;
}
else {
// Use CLIENT environment variable
const client = process.env.CLIENT;
if (!client) {
throw new Error('CLIENT environment variable not set. Please configure your MCP client connection with CLIENT environment variable.');
}
command = `npx @grebyn/toolflow-cli@latest write ${client} "${escapedConfig}"`;
configTarget = `client: ${client}`;
}
// Execute the CLI command
const { stdout, stderr } = await execAsync(command, {
timeout: 30000, // 30 second timeout
maxBuffer: 1024 * 1024 // 1MB buffer
});
if (stderr && stderr.includes('Error')) {
throw new Error(`CLI error: ${stderr}`);
}
// Count servers in the written config
let serverCount = 0;
const config = args.config;
if (config.mcpServers) {
serverCount = Object.keys(config.mcpServers).length;
}
else if (config.context_servers) {
serverCount = Object.keys(config.context_servers).length;
}
else if (config.servers) {
serverCount = Object.keys(config.servers).length;
}
// Check for environment variables in config
const envInfo = hasEnvironmentVariables(config);
return {
content: [
{
type: 'text',
text: formatWriteResult(configTarget, serverCount, stdout, envInfo)
}
]
};
}
catch (error) {
console.error(`Failed to write client config: ${error.message}`);
const errorMessage = error.message;
let troubleshooting = '';
if (errorMessage.includes('CLIENT environment variable not set')) {
troubleshooting = `\n\n**Setup Required:**\n- Ensure your MCP client configuration includes: \`"env": { "CLIENT": "your-client-name" }\`\n- Supported clients: claude-desktop, vscode, cursor, and others`;
}
else if (errorMessage.includes('permission') || errorMessage.includes('EACCES')) {
troubleshooting = `\n\n**Possible Issues:**\n- File permission problems\n- Client configuration file may be locked\n- Try closing the client application before writing`;
}
else if (errorMessage.includes('Invalid configuration')) {
troubleshooting = `\n\n**Configuration Issues:**\n- Ensure the config object is valid JSON\n- Check that the configuration format matches the client type`;
}
return {
content: [
{
type: 'text',
text: `â **Failed to Write Client Configuration**\n\nError: ${errorMessage}${troubleshooting}\n\n**Recovery:** The CLI automatically creates backups before writing. Check the backup if needed.`
}
]
};
}
}
};
/**
* Check if configuration contains servers with environment variables
*/
function hasEnvironmentVariables(config) {
const serversWithEnv = [];
// Check different config formats
const serverSections = [config.mcpServers, config.servers, config.context_servers];
for (const servers of serverSections) {
if (servers && typeof servers === 'object') {
for (const [serverName, serverConfig] of Object.entries(servers)) {
const server = serverConfig;
if (server.env && typeof server.env === 'object' && Object.keys(server.env).length > 0) {
serversWithEnv.push(serverName);
}
}
}
}
return {
hasEnv: serversWithEnv.length > 0,
serverNames: serversWithEnv
};
}
/**
* Format write result for display
*/
function formatWriteResult(configTarget, serverCount, cliOutput, envInfo) {
let output = `đž **Configuration Successfully Written**\n\n`;
output += `**Target**: ${configTarget}\n`;
output += `**Servers Configured**: ${serverCount}\n`;
// Include any relevant CLI output
if (cliOutput && cliOutput.includes('Backup created')) {
const backupMatch = cliOutput.match(/Backup created: (.+)/);
if (backupMatch) {
output += `**Backup Created**: ${backupMatch[1]}\n`;
}
}
// Check for environment variables
if (envInfo.hasEnv) {
output += `\nđ **Environment Variables Required**\n`;
output += `The following servers use environment variables: ${envInfo.serverNames.join(', ')}\n`;
output += `â ď¸ **Ensure these environment variables are properly configured for the servers to work.**\n`;
}
// Note about restart requirements
const clientName = process.env.CLIENT || 'the client';
const requiresRestart = !['cursor'].includes(clientName.toLowerCase());
if (requiresRestart) {
output += `\nâ ď¸ **Restart Required**: ${clientName} must be restarted for changes to take effect.\n`;
}
else {
output += `\nâ
**No Restart Required**: Configuration changes should take effect immediately.\n`;
}
output += `\n**Next Steps:**\n`;
let stepNum = 1;
if (requiresRestart) {
output += `${stepNum}. Restart ${clientName} to load the new configuration\n`;
stepNum++;
}
if (envInfo.hasEnv) {
output += `${stepNum}. **Configure environment variables** for: ${envInfo.serverNames.join(', ')}\n`;
stepNum++;
}
output += `${stepNum}. Use \`test_mcp_server\` to verify servers are working correctly\n`;
return output;
}
//# sourceMappingURL=write-client-config.js.map