mcpflare 0.2.1
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 +68 -0
- package/LICENSE +22 -0
- package/README.md +371 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +1617 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +19 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/mcp-handler.d.ts +34 -0
- package/dist/server/mcp-handler.d.ts.map +1 -0
- package/dist/server/mcp-handler.js +1524 -0
- package/dist/server/mcp-handler.js.map +1 -0
- package/dist/server/metrics-collector.d.ts +30 -0
- package/dist/server/metrics-collector.d.ts.map +1 -0
- package/dist/server/metrics-collector.js +85 -0
- package/dist/server/metrics-collector.js.map +1 -0
- package/dist/server/schema-converter.d.ts +9 -0
- package/dist/server/schema-converter.d.ts.map +1 -0
- package/dist/server/schema-converter.js +82 -0
- package/dist/server/schema-converter.js.map +1 -0
- package/dist/server/worker-manager.d.ts +48 -0
- package/dist/server/worker-manager.d.ts.map +1 -0
- package/dist/server/worker-manager.js +1746 -0
- package/dist/server/worker-manager.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/mcp.d.ts +495 -0
- package/dist/types/mcp.d.ts.map +1 -0
- package/dist/types/mcp.js +80 -0
- package/dist/types/mcp.js.map +1 -0
- package/dist/types/worker.d.ts +35 -0
- package/dist/types/worker.d.ts.map +1 -0
- package/dist/types/worker.js +2 -0
- package/dist/types/worker.js.map +1 -0
- package/dist/utils/config-manager.d.ts +64 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +556 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/env-selector.d.ts +4 -0
- package/dist/utils/env-selector.d.ts.map +1 -0
- package/dist/utils/env-selector.js +127 -0
- package/dist/utils/env-selector.js.map +1 -0
- package/dist/utils/errors.d.ts +19 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +37 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +27 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/mcp-registry.d.ts +108 -0
- package/dist/utils/mcp-registry.d.ts.map +1 -0
- package/dist/utils/mcp-registry.js +298 -0
- package/dist/utils/mcp-registry.js.map +1 -0
- package/dist/utils/progress-indicator.d.ts +14 -0
- package/dist/utils/progress-indicator.d.ts.map +1 -0
- package/dist/utils/progress-indicator.js +82 -0
- package/dist/utils/progress-indicator.js.map +1 -0
- package/dist/utils/settings-manager.d.ts +19 -0
- package/dist/utils/settings-manager.d.ts.map +1 -0
- package/dist/utils/settings-manager.js +78 -0
- package/dist/utils/settings-manager.js.map +1 -0
- package/dist/utils/token-calculator.d.ts +34 -0
- package/dist/utils/token-calculator.d.ts.map +1 -0
- package/dist/utils/token-calculator.js +167 -0
- package/dist/utils/token-calculator.js.map +1 -0
- package/dist/utils/validation.d.ts +4 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +36 -0
- package/dist/utils/validation.js.map +1 -0
- package/dist/utils/wrangler-formatter.d.ts +37 -0
- package/dist/utils/wrangler-formatter.d.ts.map +1 -0
- package/dist/utils/wrangler-formatter.js +302 -0
- package/dist/utils/wrangler-formatter.js.map +1 -0
- package/dist/worker/runtime.d.ts +34 -0
- package/dist/worker/runtime.d.ts.map +1 -0
- package/dist/worker/runtime.js +166 -0
- package/dist/worker/runtime.js.map +1 -0
- package/package.json +83 -0
|
@@ -0,0 +1,1617 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as readline from 'node:readline';
|
|
3
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
4
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
5
|
+
import dotenv from 'dotenv';
|
|
6
|
+
import { MetricsCollector } from '../server/metrics-collector.js';
|
|
7
|
+
import { WorkerManager } from '../server/worker-manager.js';
|
|
8
|
+
import { ExecuteCodeRequestSchema, isCommandBasedConfig, LoadMCPRequestSchema, } from '../types/mcp.js';
|
|
9
|
+
import { ConfigManager } from '../utils/config-manager.js';
|
|
10
|
+
import { selectEnvVarsInteractively } from '../utils/env-selector.js';
|
|
11
|
+
import logger from '../utils/logger.js';
|
|
12
|
+
import { createDefaultConfig, loadSettings, upsertMCPConfig, } from '../utils/mcp-registry.js';
|
|
13
|
+
import { ProgressIndicator } from '../utils/progress-indicator.js';
|
|
14
|
+
import { invalidateMetricsCache, loadTokenMetrics, saveTokenMetrics, } from '../utils/settings-manager.js';
|
|
15
|
+
import { assessCommandBasedMCP, calculatePercentage, calculateTokenSavings, formatTokens, } from '../utils/token-calculator.js';
|
|
16
|
+
import { validateInput, validateTypeScriptCode } from '../utils/validation.js';
|
|
17
|
+
import { formatExecutionResult } from '../utils/wrangler-formatter.js';
|
|
18
|
+
dotenv.config();
|
|
19
|
+
process.env.CLI_MODE = 'true';
|
|
20
|
+
const verbose = process.argv.includes('--verbose') || process.argv.includes('-v');
|
|
21
|
+
if (verbose) {
|
|
22
|
+
process.env.LOG_LEVEL = 'debug';
|
|
23
|
+
logger.level = 'debug';
|
|
24
|
+
}
|
|
25
|
+
const rl = readline.createInterface({
|
|
26
|
+
input: process.stdin,
|
|
27
|
+
output: process.stdout,
|
|
28
|
+
prompt: 'mcpflare> ',
|
|
29
|
+
});
|
|
30
|
+
const workerManager = new WorkerManager();
|
|
31
|
+
const metricsCollector = new MetricsCollector();
|
|
32
|
+
const configManager = new ConfigManager();
|
|
33
|
+
let isExiting = false;
|
|
34
|
+
function question(query) {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
rl.question(query, resolve);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async function loadMCP() {
|
|
40
|
+
try {
|
|
41
|
+
const savedConfigs = configManager.getSavedConfigs();
|
|
42
|
+
const savedNames = Object.keys(savedConfigs);
|
|
43
|
+
if (savedNames.length > 0) {
|
|
44
|
+
const sourceName = configManager.getConfigSourceDisplayName();
|
|
45
|
+
console.log(`\nš¾ Saved MCP configurations found (${sourceName}):`);
|
|
46
|
+
savedNames.forEach((name, index) => {
|
|
47
|
+
console.log(` ${index + 1}. ${name}`);
|
|
48
|
+
});
|
|
49
|
+
console.log(` ${savedNames.length + 1}. Load new MCP configuration\n`);
|
|
50
|
+
const useSaved = await question('Use saved config? Enter number or name (or "new" for new config): ');
|
|
51
|
+
const useSavedLower = useSaved.trim().toLowerCase();
|
|
52
|
+
if (useSavedLower !== 'new' &&
|
|
53
|
+
useSavedLower !== String(savedNames.length + 1)) {
|
|
54
|
+
let selectedName = null;
|
|
55
|
+
const selectedNum = parseInt(useSavedLower, 10);
|
|
56
|
+
if (!Number.isNaN(selectedNum) &&
|
|
57
|
+
selectedNum >= 1 &&
|
|
58
|
+
selectedNum <= savedNames.length) {
|
|
59
|
+
selectedName = savedNames[selectedNum - 1];
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
selectedName =
|
|
63
|
+
savedNames.find((name) => name.toLowerCase() === useSavedLower) ||
|
|
64
|
+
null;
|
|
65
|
+
}
|
|
66
|
+
if (selectedName) {
|
|
67
|
+
const savedConfig = configManager.getSavedConfig(selectedName);
|
|
68
|
+
if (savedConfig) {
|
|
69
|
+
console.log(`\nš Loading saved config: ${selectedName}`);
|
|
70
|
+
const startTime = Date.now();
|
|
71
|
+
const instance = await workerManager.loadMCP(selectedName, savedConfig);
|
|
72
|
+
const loadTime = Date.now() - startTime;
|
|
73
|
+
metricsCollector.recordMCPLoad(instance.mcp_id, loadTime);
|
|
74
|
+
console.log(`\nā
${instance.mcp_name} loaded with ${instance.tools.length} ${instance.tools.length === 1 ? 'tool' : 'tools'}!`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const mcpName = await question('MCP name: ');
|
|
81
|
+
const command = await question('Command (e.g., npx): ');
|
|
82
|
+
const argsInput = await question('Args (comma-separated, or press Enter for none): ');
|
|
83
|
+
const args = argsInput.trim()
|
|
84
|
+
? argsInput.split(',').map((s) => s.trim())
|
|
85
|
+
: [];
|
|
86
|
+
const allSavedConfigs = configManager.getSavedConfigs();
|
|
87
|
+
const conflictingMCP = allSavedConfigs[mcpName];
|
|
88
|
+
if (conflictingMCP) {
|
|
89
|
+
const sourceName = configManager.getConfigSourceDisplayName();
|
|
90
|
+
console.log(`\nā ļø Warning: An MCP named "${mcpName}" already exists in your ${sourceName} configuration.`);
|
|
91
|
+
console.log(` If you're using mcpflare, consider disabling "${mcpName}" in your IDE's MCP settings`);
|
|
92
|
+
console.log(` to avoid confusion. The IDE will use the real MCP, while mcpflare uses the sandboxed version.`);
|
|
93
|
+
const proceed = await question('\nContinue anyway? (y/N): ');
|
|
94
|
+
if (proceed.trim().toLowerCase() !== 'y') {
|
|
95
|
+
console.log('Cancelled.');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
let env = {};
|
|
100
|
+
try {
|
|
101
|
+
env = await selectEnvVarsInteractively(rl);
|
|
102
|
+
}
|
|
103
|
+
catch (_error) {
|
|
104
|
+
console.log('\nā ļø Interactive selector failed, falling back to manual input.');
|
|
105
|
+
const envInput = await question('Environment variables as JSON (or press Enter for none): ');
|
|
106
|
+
if (envInput.trim()) {
|
|
107
|
+
try {
|
|
108
|
+
env = JSON.parse(envInput.trim());
|
|
109
|
+
}
|
|
110
|
+
catch (_parseError) {
|
|
111
|
+
console.error('ā Invalid JSON. Proceeding without env vars.');
|
|
112
|
+
env = {};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const config = {
|
|
117
|
+
command,
|
|
118
|
+
args: args.length > 0 ? args : undefined,
|
|
119
|
+
env: Object.keys(env).length > 0 ? env : undefined,
|
|
120
|
+
};
|
|
121
|
+
const validated = validateInput(LoadMCPRequestSchema, {
|
|
122
|
+
mcp_name: mcpName,
|
|
123
|
+
mcp_config: config,
|
|
124
|
+
});
|
|
125
|
+
const resolvedConfig = configManager.resolveEnvVarsInObject(validated.mcp_config);
|
|
126
|
+
console.log('\nLoading MCP server...');
|
|
127
|
+
const startTime = Date.now();
|
|
128
|
+
const instance = await workerManager.loadMCP(validated.mcp_name, resolvedConfig);
|
|
129
|
+
const loadTime = Date.now() - startTime;
|
|
130
|
+
metricsCollector.recordMCPLoad(instance.mcp_id, loadTime);
|
|
131
|
+
try {
|
|
132
|
+
configManager.saveConfig(validated.mcp_name, validated.mcp_config);
|
|
133
|
+
const configPath = configManager.getCursorConfigPath();
|
|
134
|
+
const sourceName = configManager.getConfigSourceDisplayName();
|
|
135
|
+
console.log(`\nš¾ Configuration saved to ${sourceName}: ${configPath}`);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
console.warn(`\nā ļø Warning: Failed to save configuration: ${error instanceof Error ? error.message : String(error)}`);
|
|
139
|
+
}
|
|
140
|
+
console.log(`\nā
${instance.mcp_name} loaded with ${instance.tools.length} ${instance.tools.length === 1 ? 'tool' : 'tools'}!`);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error('\nā Error loading MCP:', error instanceof Error ? error.message : String(error));
|
|
144
|
+
if (error && typeof error === 'object' && 'details' in error) {
|
|
145
|
+
console.error('Details:', JSON.stringify(error.details, null, 2));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function generateToolCallCode(toolName, args) {
|
|
150
|
+
const argsJson = JSON.stringify(args);
|
|
151
|
+
return `const result = await mcp.${toolName}(${argsJson});
|
|
152
|
+
console.log('Result:', JSON.stringify(result, null, 2));
|
|
153
|
+
return result;`;
|
|
154
|
+
}
|
|
155
|
+
async function selectToolFromInstance(tools) {
|
|
156
|
+
console.log('\nš Available Tools:');
|
|
157
|
+
tools.forEach((tool, index) => {
|
|
158
|
+
console.log(` ${index + 1}. ${tool.name}`);
|
|
159
|
+
if (tool.description) {
|
|
160
|
+
console.log(` ${tool.description}`);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
while (true) {
|
|
164
|
+
const selection = await question('\nSelect tool by number or name (or "exit" to quit): ');
|
|
165
|
+
const trimmed = selection.trim();
|
|
166
|
+
if (trimmed.toLowerCase() === 'exit') {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const num = parseInt(trimmed, 10);
|
|
170
|
+
if (!Number.isNaN(num) && num >= 1 && num <= tools.length) {
|
|
171
|
+
return tools[num - 1];
|
|
172
|
+
}
|
|
173
|
+
const tool = tools.find((t) => t.name.toLowerCase() === trimmed.toLowerCase());
|
|
174
|
+
if (tool) {
|
|
175
|
+
return tool;
|
|
176
|
+
}
|
|
177
|
+
console.log('ā Invalid selection. Please try again.');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function getRequiredProperties(schema) {
|
|
181
|
+
if (!schema || typeof schema !== 'object' || !('properties' in schema)) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
const required = schema.required;
|
|
185
|
+
if (!Array.isArray(required)) {
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
return required.filter((v) => typeof v === 'string');
|
|
189
|
+
}
|
|
190
|
+
function parseValue(value, type) {
|
|
191
|
+
if (type === 'number') {
|
|
192
|
+
return parseFloat(value);
|
|
193
|
+
}
|
|
194
|
+
else if (type === 'boolean') {
|
|
195
|
+
return value.toLowerCase() === 'true' || value.toLowerCase() === 'yes';
|
|
196
|
+
}
|
|
197
|
+
else if (type === 'array') {
|
|
198
|
+
if (value.trim().startsWith('[')) {
|
|
199
|
+
try {
|
|
200
|
+
return JSON.parse(value);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return value.split(',').map((v) => v.trim());
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
return value.split(',').map((v) => v.trim());
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else if (type === 'object') {
|
|
211
|
+
if (value.trim().startsWith('{')) {
|
|
212
|
+
try {
|
|
213
|
+
return JSON.parse(value);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
throw new Error('Invalid JSON object');
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
throw new Error('Object type must be JSON');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
return value;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function collectToolArguments(tool) {
|
|
228
|
+
const args = {};
|
|
229
|
+
const schema = tool.inputSchema;
|
|
230
|
+
if (!schema?.properties || Object.keys(schema.properties).length === 0) {
|
|
231
|
+
console.log("\nš” This tool doesn't require any arguments.");
|
|
232
|
+
const useJson = await question('Enter arguments as JSON (or press Enter to skip): ');
|
|
233
|
+
if (useJson.trim()) {
|
|
234
|
+
try {
|
|
235
|
+
return JSON.parse(useJson.trim());
|
|
236
|
+
}
|
|
237
|
+
catch (_e) {
|
|
238
|
+
console.error('ā Invalid JSON. Using empty arguments.');
|
|
239
|
+
return {};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return {};
|
|
243
|
+
}
|
|
244
|
+
console.log('\nš Enter tool arguments:');
|
|
245
|
+
console.log(' āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
246
|
+
console.log(' š” Press Enter to use defaults or skip optional fields');
|
|
247
|
+
console.log(' š” Type "json" to enter full JSON object at once');
|
|
248
|
+
console.log(' āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
249
|
+
console.log('');
|
|
250
|
+
const properties = schema.properties;
|
|
251
|
+
const required = getRequiredProperties(schema);
|
|
252
|
+
const allKeys = Object.keys(properties);
|
|
253
|
+
const requiredKeys = allKeys.filter((key) => required.includes(key));
|
|
254
|
+
const optionalKeys = allKeys.filter((key) => !required.includes(key));
|
|
255
|
+
const orderedKeys = [...requiredKeys, ...optionalKeys];
|
|
256
|
+
for (const key of orderedKeys) {
|
|
257
|
+
const prop = properties[key];
|
|
258
|
+
const propSchema = prop;
|
|
259
|
+
const isRequired = required.includes(key);
|
|
260
|
+
const type = propSchema.type || 'string';
|
|
261
|
+
const hasDefault = propSchema.default !== undefined;
|
|
262
|
+
const defaultValue = propSchema.default;
|
|
263
|
+
while (true) {
|
|
264
|
+
let promptText = ` ${key}${isRequired ? ' (required)' : ''}${propSchema.description ? ` - ${propSchema.description}` : ''}${type ? ` [${type}]` : ''}`;
|
|
265
|
+
if (hasDefault) {
|
|
266
|
+
const defaultDisplay = typeof defaultValue === 'string'
|
|
267
|
+
? `"${defaultValue}"`
|
|
268
|
+
: JSON.stringify(defaultValue);
|
|
269
|
+
promptText += ` (default: ${defaultDisplay})`;
|
|
270
|
+
}
|
|
271
|
+
promptText += ': ';
|
|
272
|
+
const value = await question(promptText);
|
|
273
|
+
if (!value.trim()) {
|
|
274
|
+
if (hasDefault) {
|
|
275
|
+
args[key] = defaultValue;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
else if (isRequired) {
|
|
279
|
+
console.log(' ā ļø This field is required and has no default.');
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (value.trim().toLowerCase() === 'json') {
|
|
287
|
+
const jsonInput = await question(' Enter full JSON object: ');
|
|
288
|
+
try {
|
|
289
|
+
return JSON.parse(jsonInput.trim());
|
|
290
|
+
}
|
|
291
|
+
catch (_e) {
|
|
292
|
+
console.error(' ā Invalid JSON. Please try again.');
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
args[key] = parseValue(value.trim(), String(type));
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
catch (e) {
|
|
301
|
+
console.error(` ā ${e instanceof Error ? e.message : String(e)}. Please try again.`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return args;
|
|
306
|
+
}
|
|
307
|
+
async function testTool() {
|
|
308
|
+
try {
|
|
309
|
+
const savedConfigs = configManager.getSavedConfigs();
|
|
310
|
+
const loadedInstances = workerManager.listInstances();
|
|
311
|
+
const allMCPs = [];
|
|
312
|
+
for (const [name, entry] of Object.entries(savedConfigs)) {
|
|
313
|
+
const loadedInstance = workerManager.getMCPByName(name);
|
|
314
|
+
allMCPs.push({
|
|
315
|
+
name,
|
|
316
|
+
isLoaded: !!loadedInstance,
|
|
317
|
+
instance: loadedInstance,
|
|
318
|
+
config: entry.config,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
for (const instance of loadedInstances) {
|
|
322
|
+
if (!savedConfigs[instance.mcp_name]) {
|
|
323
|
+
allMCPs.push({
|
|
324
|
+
name: instance.mcp_name,
|
|
325
|
+
isLoaded: true,
|
|
326
|
+
instance,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (allMCPs.length === 0) {
|
|
331
|
+
console.log('\nš No MCP configurations found. Please load an MCP first using the "load" command.');
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
console.log('\nš Available MCP Servers:');
|
|
335
|
+
allMCPs.forEach((mcp, index) => {
|
|
336
|
+
const status = mcp.isLoaded
|
|
337
|
+
? `ā
Loaded (${mcp.instance?.status || 'active'})`
|
|
338
|
+
: 'ā³ Not loaded';
|
|
339
|
+
console.log(` ${index + 1}. ${mcp.name} - ${status}`);
|
|
340
|
+
});
|
|
341
|
+
const selection = await question('\nSelect MCP by number or enter MCP name: ');
|
|
342
|
+
let selectedMCP = null;
|
|
343
|
+
const selectionNum = parseInt(selection.trim(), 10);
|
|
344
|
+
if (!Number.isNaN(selectionNum) &&
|
|
345
|
+
selectionNum >= 1 &&
|
|
346
|
+
selectionNum <= allMCPs.length) {
|
|
347
|
+
selectedMCP = allMCPs[selectionNum - 1];
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
const searchTerm = selection.trim().toLowerCase();
|
|
351
|
+
const found = allMCPs.find((mcp) => mcp.name.toLowerCase() === searchTerm);
|
|
352
|
+
if (!found) {
|
|
353
|
+
console.error(`\nā MCP not found: ${selection}`);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
selectedMCP = found;
|
|
357
|
+
}
|
|
358
|
+
if (!selectedMCP) {
|
|
359
|
+
console.error(`\nā MCP not found: ${selection}`);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
let selectedInstance = selectedMCP.instance;
|
|
363
|
+
if (!selectedMCP.isLoaded) {
|
|
364
|
+
if (!selectedMCP.config) {
|
|
365
|
+
console.error(`\nā No configuration found for ${selectedMCP.name}. Please load it first using the "load" command.`);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
console.log(`\nā³ Loading ${selectedMCP.name}...`);
|
|
369
|
+
try {
|
|
370
|
+
const resolvedConfig = configManager.resolveEnvVarsInObject(selectedMCP.config);
|
|
371
|
+
const startTime = Date.now();
|
|
372
|
+
selectedInstance = await workerManager.loadMCP(selectedMCP.name, resolvedConfig);
|
|
373
|
+
const loadTime = Date.now() - startTime;
|
|
374
|
+
metricsCollector.recordMCPLoad(selectedInstance.mcp_id, loadTime);
|
|
375
|
+
console.log(`\nā
${selectedInstance.mcp_name} loaded with ${selectedInstance.tools.length} ${selectedInstance.tools.length === 1 ? 'tool' : 'tools'}!`);
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
console.error(`\nā Error loading MCP: ${error instanceof Error ? error.message : String(error)}`);
|
|
379
|
+
if (error && typeof error === 'object' && 'details' in error) {
|
|
380
|
+
console.error('Details:', JSON.stringify(error.details, null, 2));
|
|
381
|
+
}
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else if (selectedInstance) {
|
|
386
|
+
console.log(`\nā
Using already loaded: ${selectedInstance.mcp_name} (${selectedInstance.mcp_id})`);
|
|
387
|
+
}
|
|
388
|
+
if (!selectedInstance) {
|
|
389
|
+
console.error('\nā Failed to load MCP instance');
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
while (true) {
|
|
393
|
+
const selectedTool = await selectToolFromInstance(selectedInstance.tools);
|
|
394
|
+
if (!selectedTool) {
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
console.log(`\nš§ Selected tool: ${selectedTool.name}`);
|
|
398
|
+
if (selectedTool.description) {
|
|
399
|
+
console.log(` ${selectedTool.description}`);
|
|
400
|
+
}
|
|
401
|
+
const args = await collectToolArguments(selectedTool);
|
|
402
|
+
const code = generateToolCallCode(selectedTool.name, args);
|
|
403
|
+
console.log(`\nš Generated TypeScript code:`);
|
|
404
|
+
console.log('ā'.repeat(60));
|
|
405
|
+
console.log(code);
|
|
406
|
+
console.log('ā'.repeat(60));
|
|
407
|
+
console.log('');
|
|
408
|
+
const timeout = 15000;
|
|
409
|
+
console.log('\nš Executing through WorkerManager (Wrangler)...\n');
|
|
410
|
+
try {
|
|
411
|
+
validateTypeScriptCode(code);
|
|
412
|
+
const result = await workerManager.executeCode(selectedInstance.mcp_id, code, timeout);
|
|
413
|
+
metricsCollector.recordExecution(selectedInstance.mcp_id, result.execution_time_ms, result.success, result.metrics?.mcp_calls_made ?? 0);
|
|
414
|
+
console.log('\nā
Execution result:');
|
|
415
|
+
console.log(formatExecutionResult(result));
|
|
416
|
+
console.log('');
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
console.error('\nā Execution failed:');
|
|
420
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
421
|
+
if (error && typeof error === 'object' && 'details' in error) {
|
|
422
|
+
console.error('Details:', JSON.stringify(error.details, null, 2));
|
|
423
|
+
}
|
|
424
|
+
console.log('');
|
|
425
|
+
}
|
|
426
|
+
const continueChoice = await question('Test another tool? (Y/n): ');
|
|
427
|
+
if (continueChoice.trim().toLowerCase() === 'n') {
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
console.error('\nā Error testing tool:', error instanceof Error ? error.message : String(error));
|
|
434
|
+
if (error && typeof error === 'object' && 'details' in error) {
|
|
435
|
+
console.error('Details:', JSON.stringify(error.details, null, 2));
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function formatDirectToolResult(result) {
|
|
440
|
+
try {
|
|
441
|
+
const jsonStr = JSON.stringify(result, null, 2);
|
|
442
|
+
if (jsonStr.length > 2000) {
|
|
443
|
+
return (jsonStr.substring(0, 2000) +
|
|
444
|
+
`\n... (truncated, ${jsonStr.length - 2000} more characters)`);
|
|
445
|
+
}
|
|
446
|
+
return jsonStr;
|
|
447
|
+
}
|
|
448
|
+
catch (_e) {
|
|
449
|
+
return String(result);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
async function testDirect() {
|
|
453
|
+
try {
|
|
454
|
+
const savedConfigs = configManager.getSavedConfigs();
|
|
455
|
+
const savedNames = Object.keys(savedConfigs);
|
|
456
|
+
if (savedNames.length === 0) {
|
|
457
|
+
console.log('\nš No saved MCP configurations found. Please load an MCP first using the "load" command.');
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
console.log('\nš Available MCP Configurations:');
|
|
461
|
+
savedNames.forEach((name, index) => {
|
|
462
|
+
console.log(` ${index + 1}. ${name}`);
|
|
463
|
+
});
|
|
464
|
+
const selection = await question('\nSelect MCP by number or name (or "exit" to quit): ');
|
|
465
|
+
const trimmed = selection.trim();
|
|
466
|
+
if (trimmed.toLowerCase() === 'exit') {
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
let selectedName = null;
|
|
470
|
+
const selectionNum = parseInt(trimmed, 10);
|
|
471
|
+
if (!Number.isNaN(selectionNum) &&
|
|
472
|
+
selectionNum >= 1 &&
|
|
473
|
+
selectionNum <= savedNames.length) {
|
|
474
|
+
selectedName = savedNames[selectionNum - 1];
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
selectedName =
|
|
478
|
+
savedNames.find((name) => name.toLowerCase() === trimmed.toLowerCase()) || null;
|
|
479
|
+
}
|
|
480
|
+
if (!selectedName) {
|
|
481
|
+
console.error(`\nā MCP not found: ${selection}`);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const savedConfig = configManager.getSavedConfig(selectedName);
|
|
485
|
+
if (!savedConfig) {
|
|
486
|
+
console.error(`\nā Configuration not found for: ${selectedName}`);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const resolvedConfig = configManager.resolveEnvVarsInObject(savedConfig);
|
|
490
|
+
if (!('command' in resolvedConfig)) {
|
|
491
|
+
console.error('\nā URL-based MCP configurations are not supported for direct testing.');
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
console.log(`\nš Testing ${selectedName} directly (bypassing Wrangler)...\n`);
|
|
495
|
+
console.log('Configuration:');
|
|
496
|
+
console.log(` Command: ${resolvedConfig.command}`);
|
|
497
|
+
console.log(` Args: ${resolvedConfig.args?.join(' ') || 'none'}`);
|
|
498
|
+
const envKeys = Object.keys(resolvedConfig.env || {});
|
|
499
|
+
console.log(` Env keys: ${envKeys.join(', ') || 'none'}`);
|
|
500
|
+
console.log('');
|
|
501
|
+
const transport = new StdioClientTransport({
|
|
502
|
+
command: resolvedConfig.command,
|
|
503
|
+
args: resolvedConfig.args || [],
|
|
504
|
+
env: resolvedConfig.env,
|
|
505
|
+
});
|
|
506
|
+
const client = new Client({ name: 'mcpflare-cli-direct-test', version: '1.0.0' }, { capabilities: {} });
|
|
507
|
+
try {
|
|
508
|
+
const progress = new ProgressIndicator();
|
|
509
|
+
progress.steps = [
|
|
510
|
+
{ name: 'CLI', status: 'pending' },
|
|
511
|
+
{ name: 'MCP SDK Client', status: 'pending' },
|
|
512
|
+
{ name: 'Target MCP', status: 'pending' },
|
|
513
|
+
];
|
|
514
|
+
console.log('š” Connecting to MCP server...');
|
|
515
|
+
progress.updateStep(0, 'running');
|
|
516
|
+
progress.updateStep(1, 'running');
|
|
517
|
+
await client.connect(transport, { timeout: 10000 });
|
|
518
|
+
progress.updateStep(0, 'success');
|
|
519
|
+
progress.updateStep(1, 'success');
|
|
520
|
+
progress.updateStep(2, 'running');
|
|
521
|
+
progress.showFinal();
|
|
522
|
+
console.log('ā
Connected successfully!\n');
|
|
523
|
+
console.log('š Fetching available tools...');
|
|
524
|
+
const toolsResponse = await client.listTools();
|
|
525
|
+
const tools = toolsResponse.tools;
|
|
526
|
+
progress.updateStep(2, 'success');
|
|
527
|
+
progress.showFinal();
|
|
528
|
+
console.log(`ā
Found ${tools.length} tools\n`);
|
|
529
|
+
while (true) {
|
|
530
|
+
const selectedTool = await selectToolFromInstance(tools);
|
|
531
|
+
if (!selectedTool) {
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
console.log(`\nš§ Selected tool: ${selectedTool.name}`);
|
|
535
|
+
if (selectedTool.description) {
|
|
536
|
+
console.log(` ${selectedTool.description}`);
|
|
537
|
+
}
|
|
538
|
+
const args = await collectToolArguments(selectedTool);
|
|
539
|
+
console.log(`\nš Executing tool with arguments:`);
|
|
540
|
+
console.log(JSON.stringify(args, null, 2));
|
|
541
|
+
console.log('');
|
|
542
|
+
const execProgress = new ProgressIndicator();
|
|
543
|
+
execProgress.steps = [
|
|
544
|
+
{ name: 'CLI', status: 'pending' },
|
|
545
|
+
{ name: 'MCP SDK Client', status: 'pending' },
|
|
546
|
+
{ name: 'Target MCP', status: 'pending' },
|
|
547
|
+
];
|
|
548
|
+
execProgress.updateStep(0, 'success');
|
|
549
|
+
execProgress.updateStep(1, 'running');
|
|
550
|
+
execProgress.updateStep(2, 'running');
|
|
551
|
+
try {
|
|
552
|
+
const result = await client.callTool({
|
|
553
|
+
name: selectedTool.name,
|
|
554
|
+
arguments: args,
|
|
555
|
+
});
|
|
556
|
+
execProgress.updateStep(1, 'success');
|
|
557
|
+
execProgress.updateStep(2, 'success');
|
|
558
|
+
execProgress.showFinal();
|
|
559
|
+
console.log('\nā
Tool execution result:');
|
|
560
|
+
console.log(formatDirectToolResult(result));
|
|
561
|
+
console.log('');
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
execProgress.updateStep(1, 'failed');
|
|
565
|
+
execProgress.updateStep(2, 'failed');
|
|
566
|
+
execProgress.showFinal(2);
|
|
567
|
+
console.error('\nā Tool execution failed:');
|
|
568
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
569
|
+
if (error instanceof Error && error.stack) {
|
|
570
|
+
console.error(`\nStack trace:\n${error.stack}`);
|
|
571
|
+
}
|
|
572
|
+
console.log('');
|
|
573
|
+
}
|
|
574
|
+
const continueChoice = await question('Test another tool? (Y/n): ');
|
|
575
|
+
if (continueChoice.trim().toLowerCase() === 'n') {
|
|
576
|
+
break;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
await transport.close();
|
|
580
|
+
console.log('\nā
Test session completed!\n');
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
console.error('\nā Error testing MCP:');
|
|
584
|
+
console.error(` ${error instanceof Error ? error.message : String(error)}`);
|
|
585
|
+
if (error instanceof Error && error.stack) {
|
|
586
|
+
console.error(`\nStack trace:\n${error.stack}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
console.error('\nā Error:', error instanceof Error ? error.message : String(error));
|
|
592
|
+
if (error && typeof error === 'object' && 'details' in error) {
|
|
593
|
+
console.error('Details:', JSON.stringify(error.details, null, 2));
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
async function executeCode() {
|
|
598
|
+
try {
|
|
599
|
+
const instances = workerManager.listInstances();
|
|
600
|
+
if (instances.length === 0) {
|
|
601
|
+
console.log('\nš No MCP servers loaded. Please load an MCP first using the "load" command.');
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
console.log('\nš Available MCP Servers:');
|
|
605
|
+
instances.forEach((instance, index) => {
|
|
606
|
+
console.log(` ${index + 1}. ${instance.mcp_name} - Status: ${instance.status}`);
|
|
607
|
+
});
|
|
608
|
+
const selection = await question('\nSelect MCP by number or enter MCP ID/name: ');
|
|
609
|
+
let selectedInstance;
|
|
610
|
+
const selectionNum = parseInt(selection.trim(), 10);
|
|
611
|
+
if (!Number.isNaN(selectionNum) &&
|
|
612
|
+
selectionNum >= 1 &&
|
|
613
|
+
selectionNum <= instances.length) {
|
|
614
|
+
selectedInstance = instances[selectionNum - 1];
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
const searchTerm = selection.trim().toLowerCase();
|
|
618
|
+
selectedInstance = instances.find((inst) => inst.mcp_id.toLowerCase() === searchTerm ||
|
|
619
|
+
inst.mcp_name.toLowerCase() === searchTerm);
|
|
620
|
+
if (!selectedInstance) {
|
|
621
|
+
console.error(`\nā MCP not found: ${selection}`);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
console.log(`\nā
Selected: ${selectedInstance.mcp_name} (${selectedInstance.mcp_id})`);
|
|
626
|
+
console.log('Enter TypeScript code (end with a blank line):');
|
|
627
|
+
const lines = [];
|
|
628
|
+
while (true) {
|
|
629
|
+
const line = await question('');
|
|
630
|
+
if (line.trim() === '' && lines.length > 0) {
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
if (line.trim() !== '') {
|
|
634
|
+
lines.push(line);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const code = lines.join('\n');
|
|
638
|
+
const timeoutInput = await question('Timeout (ms, default 30000): ');
|
|
639
|
+
const timeout = timeoutInput.trim() ? parseInt(timeoutInput, 10) : 30000;
|
|
640
|
+
const validated = validateInput(ExecuteCodeRequestSchema, {
|
|
641
|
+
mcp_id: selectedInstance.mcp_id,
|
|
642
|
+
code,
|
|
643
|
+
timeout_ms: timeout,
|
|
644
|
+
});
|
|
645
|
+
validateTypeScriptCode(validated.code);
|
|
646
|
+
if (!validated.mcp_id) {
|
|
647
|
+
throw new Error('mcp_id is required in CLI mode');
|
|
648
|
+
}
|
|
649
|
+
console.log('\nExecuting code...\n');
|
|
650
|
+
const result = await workerManager.executeCode(validated.mcp_id, validated.code, validated.timeout_ms);
|
|
651
|
+
metricsCollector.recordExecution(validated.mcp_id, result.execution_time_ms, result.success, result.metrics?.mcp_calls_made ?? 0);
|
|
652
|
+
console.log(formatExecutionResult(result));
|
|
653
|
+
}
|
|
654
|
+
catch (error) {
|
|
655
|
+
console.error('\nā Error executing code:', error instanceof Error ? error.message : String(error));
|
|
656
|
+
if (error && typeof error === 'object' && 'details' in error) {
|
|
657
|
+
console.error('Details:', JSON.stringify(error.details, null, 2));
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
async function listMCPs() {
|
|
662
|
+
const instances = workerManager.listInstances();
|
|
663
|
+
const savedConfigs = configManager.getSavedConfigs();
|
|
664
|
+
const disabledMCPs = configManager.getDisabledMCPNames();
|
|
665
|
+
if (instances.length === 0) {
|
|
666
|
+
console.log('\nš No MCP servers loaded.');
|
|
667
|
+
if (Object.keys(savedConfigs).length > 0) {
|
|
668
|
+
const guardedCount = disabledMCPs.length;
|
|
669
|
+
if (guardedCount > 0) {
|
|
670
|
+
console.log(`\nš” ${guardedCount} MCP${guardedCount === 1 ? '' : 's'} configured for guarding: ${disabledMCPs.join(', ')}`);
|
|
671
|
+
console.log(` Run 'load' to load an MCP, then 'savings' to see token savings`);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
console.log('\nš Loaded MCP Servers:');
|
|
677
|
+
instances.forEach((instance) => {
|
|
678
|
+
const isGuarded = disabledMCPs.includes(instance.mcp_name);
|
|
679
|
+
const guardStatus = isGuarded ? 'š”ļø Guarded' : 'ā ļø Unguarded';
|
|
680
|
+
console.log(JSON.stringify({
|
|
681
|
+
mcp_id: instance.mcp_id,
|
|
682
|
+
mcp_name: instance.mcp_name,
|
|
683
|
+
status: instance.status,
|
|
684
|
+
guard_status: guardStatus,
|
|
685
|
+
uptime_ms: instance.uptime_ms,
|
|
686
|
+
tools_count: instance.tools.length,
|
|
687
|
+
created_at: instance.created_at.toISOString(),
|
|
688
|
+
}, null, 2));
|
|
689
|
+
});
|
|
690
|
+
const allMCPs = Object.entries(savedConfigs).map(([name]) => ({
|
|
691
|
+
name,
|
|
692
|
+
isGuarded: disabledMCPs.includes(name),
|
|
693
|
+
metrics: tokenMetricsCache.get(name),
|
|
694
|
+
toolCount: workerManager.getMCPByName(name)?.tools.length,
|
|
695
|
+
}));
|
|
696
|
+
const summary = calculateTokenSavings(allMCPs);
|
|
697
|
+
if (summary.guardedMCPs > 0 || summary.tokensSaved > 0) {
|
|
698
|
+
console.log('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
699
|
+
console.log('Token Savings Summary:');
|
|
700
|
+
if (summary.tokensSaved > 0) {
|
|
701
|
+
const savingsPercent = calculatePercentage(summary.tokensSaved, summary.totalTokensWithoutGuard);
|
|
702
|
+
console.log(` š° Saving ~${formatTokens(summary.tokensSaved)} tokens (${savingsPercent}% reduction)`);
|
|
703
|
+
console.log(` š”ļø ${summary.guardedMCPs} MCP${summary.guardedMCPs === 1 ? '' : 's'} guarded`);
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
console.log(` ā ļø No token savings yet - run 'guard --all' to protect MCPs`);
|
|
707
|
+
}
|
|
708
|
+
console.log(`\n Run 'savings' for detailed breakdown`);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
async function getSchema() {
|
|
712
|
+
try {
|
|
713
|
+
const instances = workerManager.listInstances();
|
|
714
|
+
if (instances.length === 0) {
|
|
715
|
+
console.log('\nš No MCP servers loaded. Please load an MCP first using the "load" command.');
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
console.log('\nš Available MCP Servers:');
|
|
719
|
+
instances.forEach((instance, index) => {
|
|
720
|
+
console.log(` ${index + 1}. ${instance.mcp_name} - Status: ${instance.status}`);
|
|
721
|
+
});
|
|
722
|
+
const selection = await question('\nSelect MCP by number or enter MCP ID/name: ');
|
|
723
|
+
let selectedInstance;
|
|
724
|
+
const selectionNum = parseInt(selection.trim(), 10);
|
|
725
|
+
if (!Number.isNaN(selectionNum) &&
|
|
726
|
+
selectionNum >= 1 &&
|
|
727
|
+
selectionNum <= instances.length) {
|
|
728
|
+
selectedInstance = instances[selectionNum - 1];
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
const searchTerm = selection.trim().toLowerCase();
|
|
732
|
+
selectedInstance = instances.find((inst) => inst.mcp_id.toLowerCase() === searchTerm ||
|
|
733
|
+
inst.mcp_name.toLowerCase() === searchTerm);
|
|
734
|
+
if (!selectedInstance) {
|
|
735
|
+
console.error(`\nā MCP not found: ${selection}`);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
const instance = workerManager.getInstance(selectedInstance.mcp_id);
|
|
740
|
+
if (!instance) {
|
|
741
|
+
console.error(`\nā MCP instance not found: ${selectedInstance.mcp_id}`);
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
console.log(`\nā
Selected: ${instance.mcp_name} (${instance.mcp_id})`);
|
|
745
|
+
console.log('\nš TypeScript API:');
|
|
746
|
+
console.log(instance.typescript_api);
|
|
747
|
+
console.log('\nš§ Available Tools:');
|
|
748
|
+
instance.tools.forEach((tool) => {
|
|
749
|
+
console.log(` - ${tool.name}: ${tool.description || 'No description'}`);
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
catch (error) {
|
|
753
|
+
console.error('\nā Error:', error instanceof Error ? error.message : String(error));
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
async function unloadMCP() {
|
|
757
|
+
try {
|
|
758
|
+
const instances = workerManager.listInstances();
|
|
759
|
+
if (instances.length === 0) {
|
|
760
|
+
console.log('\nš No MCP servers loaded. Nothing to unload.');
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
console.log('\nš Available MCP Servers:');
|
|
764
|
+
instances.forEach((instance, index) => {
|
|
765
|
+
console.log(` ${index + 1}. ${instance.mcp_name} - Status: ${instance.status}`);
|
|
766
|
+
});
|
|
767
|
+
const selection = await question('\nSelect MCP to unload by number or enter MCP ID/name: ');
|
|
768
|
+
let selectedInstance;
|
|
769
|
+
const selectionNum = parseInt(selection.trim(), 10);
|
|
770
|
+
if (!Number.isNaN(selectionNum) &&
|
|
771
|
+
selectionNum >= 1 &&
|
|
772
|
+
selectionNum <= instances.length) {
|
|
773
|
+
selectedInstance = instances[selectionNum - 1];
|
|
774
|
+
}
|
|
775
|
+
else {
|
|
776
|
+
const searchTerm = selection.trim().toLowerCase();
|
|
777
|
+
selectedInstance = instances.find((inst) => inst.mcp_id.toLowerCase() === searchTerm ||
|
|
778
|
+
inst.mcp_name.toLowerCase() === searchTerm);
|
|
779
|
+
if (!selectedInstance) {
|
|
780
|
+
console.error(`\nā MCP not found: ${selection}`);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
const removeFromSaved = await question(`\nAlso remove ${selectedInstance.mcp_name} from saved configs? (y/N): `);
|
|
785
|
+
const shouldRemove = removeFromSaved.trim().toLowerCase() === 'y';
|
|
786
|
+
console.log(`\nā ļø Unloading: ${selectedInstance.mcp_name} (${selectedInstance.mcp_id})`);
|
|
787
|
+
await workerManager.unloadMCP(selectedInstance.mcp_id);
|
|
788
|
+
if (shouldRemove) {
|
|
789
|
+
try {
|
|
790
|
+
const removed = configManager.deleteConfig(selectedInstance.mcp_name);
|
|
791
|
+
if (removed) {
|
|
792
|
+
console.log(`\nš¾ Configuration removed from saved configs.`);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
catch (error) {
|
|
796
|
+
console.warn(`\nā ļø Warning: Failed to remove from saved configs: ${error instanceof Error ? error.message : String(error)}`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
console.log(`\nā
MCP server ${selectedInstance.mcp_name} unloaded successfully.`);
|
|
800
|
+
}
|
|
801
|
+
catch (error) {
|
|
802
|
+
console.error('\nā Error unloading MCP:', error instanceof Error ? error.message : String(error));
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
async function getMetrics() {
|
|
806
|
+
const metrics = metricsCollector.getMetrics();
|
|
807
|
+
console.log('\nš Metrics:');
|
|
808
|
+
console.log(JSON.stringify(metrics, null, 2));
|
|
809
|
+
}
|
|
810
|
+
async function listSavedConfigs() {
|
|
811
|
+
const savedConfigs = configManager.getSavedConfigs();
|
|
812
|
+
const configPath = configManager.getCursorConfigPath();
|
|
813
|
+
const sourceName = configManager.getConfigSourceDisplayName();
|
|
814
|
+
if (Object.keys(savedConfigs).length === 0) {
|
|
815
|
+
console.log('\nš No saved MCP configurations found.');
|
|
816
|
+
if (configPath) {
|
|
817
|
+
console.log(` Config file location: ${configPath}`);
|
|
818
|
+
}
|
|
819
|
+
else {
|
|
820
|
+
console.log(` ${sourceName} config file not found. Configs will be saved when you load an MCP.`);
|
|
821
|
+
}
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
console.log(`\nš¾ Saved MCP Configurations (${sourceName}):`);
|
|
825
|
+
if (configPath) {
|
|
826
|
+
console.log(` Config file: ${configPath}\n`);
|
|
827
|
+
}
|
|
828
|
+
Object.entries(savedConfigs).forEach(([name, entry], index) => {
|
|
829
|
+
console.log(` ${index + 1}. ${name}`);
|
|
830
|
+
const config = entry.config;
|
|
831
|
+
if (isCommandBasedConfig(config)) {
|
|
832
|
+
console.log(` Command: ${config.command}`);
|
|
833
|
+
if (config.args) {
|
|
834
|
+
console.log(` Args: ${config.args.join(' ')}`);
|
|
835
|
+
}
|
|
836
|
+
if (config.env) {
|
|
837
|
+
const envKeys = Object.keys(config.env);
|
|
838
|
+
console.log(` Env vars: ${envKeys.length} variable(s)`);
|
|
839
|
+
envKeys.forEach((key) => {
|
|
840
|
+
const value = config.env?.[key];
|
|
841
|
+
if (value?.startsWith('${') && value.endsWith('}')) {
|
|
842
|
+
console.log(` ${key}: ${value}`);
|
|
843
|
+
}
|
|
844
|
+
else {
|
|
845
|
+
console.log(` ${key}: [hidden]`);
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
else {
|
|
851
|
+
console.log(` URL: ${config.url}`);
|
|
852
|
+
}
|
|
853
|
+
console.log('');
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
async function checkIDEConflicts() {
|
|
857
|
+
const savedConfigs = configManager.getSavedConfigs();
|
|
858
|
+
const loadedInstances = workerManager.listInstances();
|
|
859
|
+
const sourceName = configManager.getConfigSourceDisplayName();
|
|
860
|
+
if (Object.keys(savedConfigs).length === 0) {
|
|
861
|
+
console.log('\nš No MCP configurations found in your IDE.');
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
console.log(`\nš Checking for potential IDE MCP conflicts (${sourceName}):\n`);
|
|
865
|
+
const conflicts = [];
|
|
866
|
+
for (const [name] of Object.entries(savedConfigs)) {
|
|
867
|
+
const inIsolate = loadedInstances.some((inst) => inst.mcp_name === name);
|
|
868
|
+
conflicts.push({
|
|
869
|
+
name,
|
|
870
|
+
inIDE: true,
|
|
871
|
+
inIsolate,
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
for (const instance of loadedInstances) {
|
|
875
|
+
if (!savedConfigs[instance.mcp_name]) {
|
|
876
|
+
conflicts.push({
|
|
877
|
+
name: instance.mcp_name,
|
|
878
|
+
inIDE: false,
|
|
879
|
+
inIsolate: true,
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (conflicts.length === 0) {
|
|
884
|
+
console.log('ā
No conflicts detected.');
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
let hasConflicts = false;
|
|
888
|
+
conflicts.forEach((conflict) => {
|
|
889
|
+
if (conflict.inIDE && conflict.inIsolate) {
|
|
890
|
+
hasConflicts = true;
|
|
891
|
+
console.log(`ā ļø "${conflict.name}" is configured in both your IDE and mcpflare`);
|
|
892
|
+
console.log(` Recommendation: Disable "${conflict.name}" in your IDE's MCP settings`);
|
|
893
|
+
console.log(` to avoid confusion. The IDE will use the real MCP, while mcpflare`);
|
|
894
|
+
console.log(` uses the sandboxed version.\n`);
|
|
895
|
+
}
|
|
896
|
+
else if (conflict.inIDE && !conflict.inIsolate) {
|
|
897
|
+
console.log(`ā¹ļø "${conflict.name}" is configured in your IDE but not loaded in mcpflare`);
|
|
898
|
+
console.log(` This is fine - they won't conflict unless you load it here.\n`);
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
if (hasConflicts) {
|
|
902
|
+
const conflictName = conflicts.find((c) => c.inIDE && c.inIsolate)?.name;
|
|
903
|
+
console.log('š” Tip: To disable an MCP in your IDE, use the "guard" command:');
|
|
904
|
+
console.log(` guard ${conflictName}`);
|
|
905
|
+
console.log('');
|
|
906
|
+
console.log(` This moves the MCP to a disabled section while keeping it discoverable by MCPflare.`);
|
|
907
|
+
console.log(` ā ļø Do NOT manually remove or comment out MCP entries - MCPflare won't be able to find them.\n`);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
async function deleteSavedConfig() {
|
|
911
|
+
const savedConfigs = configManager.getSavedConfigs();
|
|
912
|
+
const savedNames = Object.keys(savedConfigs);
|
|
913
|
+
if (savedNames.length === 0) {
|
|
914
|
+
console.log('\nš No saved MCP configurations to delete.');
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
console.log('\nš¾ Saved MCP Configurations:');
|
|
918
|
+
savedNames.forEach((name, index) => {
|
|
919
|
+
console.log(` ${index + 1}. ${name}`);
|
|
920
|
+
});
|
|
921
|
+
const selection = await question('\nSelect config to delete by number or name: ');
|
|
922
|
+
let selectedName = null;
|
|
923
|
+
const selectionNum = parseInt(selection.trim(), 10);
|
|
924
|
+
if (!Number.isNaN(selectionNum) &&
|
|
925
|
+
selectionNum >= 1 &&
|
|
926
|
+
selectionNum <= savedNames.length) {
|
|
927
|
+
selectedName = savedNames[selectionNum - 1];
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
selectedName =
|
|
931
|
+
savedNames.find((name) => name.toLowerCase() === selection.trim().toLowerCase()) || null;
|
|
932
|
+
}
|
|
933
|
+
if (!selectedName) {
|
|
934
|
+
console.error(`\nā Config not found: ${selection}`);
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
const confirmed = await question(`\nā ļø Delete saved config "${selectedName}"? (y/N): `);
|
|
938
|
+
if (confirmed.trim().toLowerCase() !== 'y') {
|
|
939
|
+
console.log('Cancelled.');
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
try {
|
|
943
|
+
const deleted = configManager.deleteConfig(selectedName);
|
|
944
|
+
if (deleted) {
|
|
945
|
+
console.log(`\nā
Configuration "${selectedName}" deleted successfully.`);
|
|
946
|
+
}
|
|
947
|
+
else {
|
|
948
|
+
console.error(`\nā Failed to delete configuration "${selectedName}".`);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
catch (error) {
|
|
952
|
+
console.error(`\nā Error deleting config: ${error instanceof Error ? error.message : String(error)}`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
const tokenMetricsCache = loadTokenMetrics();
|
|
956
|
+
async function showSavings() {
|
|
957
|
+
try {
|
|
958
|
+
const savedConfigs = configManager.getSavedConfigs();
|
|
959
|
+
const loadedInstances = workerManager.listInstances();
|
|
960
|
+
if (Object.keys(savedConfigs).length === 0 &&
|
|
961
|
+
loadedInstances.length === 0) {
|
|
962
|
+
console.log('\nš No MCP configurations found. Load an MCP first using the "load" command.');
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
const allMCPs = [];
|
|
966
|
+
const disabledMCPs = configManager.getDisabledMCPNames();
|
|
967
|
+
for (const [name, entry] of Object.entries(savedConfigs)) {
|
|
968
|
+
const isGuarded = disabledMCPs.includes(name);
|
|
969
|
+
const instance = workerManager.getMCPByName(name);
|
|
970
|
+
let metrics = tokenMetricsCache.get(name);
|
|
971
|
+
if (!metrics && isGuarded) {
|
|
972
|
+
console.log(`\nAssessing ${name}...`);
|
|
973
|
+
const assessedMetrics = await assessCommandBasedMCP(name, entry.config);
|
|
974
|
+
if (assessedMetrics) {
|
|
975
|
+
metrics = assessedMetrics;
|
|
976
|
+
tokenMetricsCache.set(name, metrics);
|
|
977
|
+
saveTokenMetrics(tokenMetricsCache);
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
allMCPs.push({
|
|
981
|
+
name,
|
|
982
|
+
isGuarded,
|
|
983
|
+
metrics,
|
|
984
|
+
toolCount: instance?.tools.length,
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
for (const instance of loadedInstances) {
|
|
988
|
+
if (!savedConfigs[instance.mcp_name]) {
|
|
989
|
+
allMCPs.push({
|
|
990
|
+
name: instance.mcp_name,
|
|
991
|
+
isGuarded: disabledMCPs.includes(instance.mcp_name),
|
|
992
|
+
toolCount: instance.tools.length,
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
const summary = calculateTokenSavings(allMCPs);
|
|
997
|
+
console.log('\nš Token Savings Analysis');
|
|
998
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
999
|
+
console.log(` Without MCPflare: ${formatTokens(summary.totalTokensWithoutGuard)} tokens`);
|
|
1000
|
+
console.log(` With MCPflare: ${formatTokens(summary.mcpflareTokens)} tokens (MCPflare's ${summary.mcpflareTokens} tools)`);
|
|
1001
|
+
console.log(' āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
1002
|
+
if (summary.tokensSaved > 0) {
|
|
1003
|
+
const savingsPercent = calculatePercentage(summary.tokensSaved, summary.totalTokensWithoutGuard);
|
|
1004
|
+
console.log(` Net Savings: ${formatTokens(summary.tokensSaved)} tokens (${savingsPercent}% reduction)`);
|
|
1005
|
+
}
|
|
1006
|
+
else {
|
|
1007
|
+
console.log(` Net Savings: 0 tokens (no MCPs guarded)`);
|
|
1008
|
+
}
|
|
1009
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
1010
|
+
const guardedMCPs = summary.mcpBreakdown.filter((m) => m.isGuarded);
|
|
1011
|
+
if (guardedMCPs.length > 0) {
|
|
1012
|
+
console.log('\nGuarded MCPs:');
|
|
1013
|
+
for (const mcp of guardedMCPs) {
|
|
1014
|
+
const assessed = mcp.isAssessed ? '' : ' (estimated)';
|
|
1015
|
+
const tools = mcp.toolCount > 0 ? ` (${mcp.toolCount} tools)` : '';
|
|
1016
|
+
console.log(` ā ${mcp.name}: ~${formatTokens(mcp.tokens)} tokens${tools}${assessed}`);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
const unguardedMCPs = summary.mcpBreakdown.filter((m) => !m.isGuarded);
|
|
1020
|
+
if (unguardedMCPs.length > 0) {
|
|
1021
|
+
console.log('\nUnguarded MCPs:');
|
|
1022
|
+
for (const mcp of unguardedMCPs) {
|
|
1023
|
+
const mcpMetrics = tokenMetricsCache.get(mcp.name);
|
|
1024
|
+
const tokens = mcpMetrics
|
|
1025
|
+
? ` (~${formatTokens(mcpMetrics.estimatedTokens)} tokens)`
|
|
1026
|
+
: '';
|
|
1027
|
+
const tools = mcp.toolCount > 0 ? ` (${mcp.toolCount} tools)` : '';
|
|
1028
|
+
console.log(` ā ${mcp.name}${tools}${tokens} - Run 'guard ${mcp.name}' to save tokens`);
|
|
1029
|
+
}
|
|
1030
|
+
console.log(`\nš” Tip: Run 'guard --all' to guard all MCPs and maximize token savings`);
|
|
1031
|
+
}
|
|
1032
|
+
if (summary.hasEstimates) {
|
|
1033
|
+
console.log('\nš” Note: Some MCPs are using estimated tokens. Assessments happen automatically when you load them.');
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
catch (error) {
|
|
1037
|
+
console.error('\nā Error calculating token savings:', error instanceof Error ? error.message : String(error));
|
|
1038
|
+
if (error instanceof Error && error.stack && verbose) {
|
|
1039
|
+
console.error(error.stack);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
async function guardMCP(mcpName, shouldGuard) {
|
|
1044
|
+
try {
|
|
1045
|
+
const savedConfigs = configManager.getSavedConfigs();
|
|
1046
|
+
const disabledMCPs = configManager.getDisabledMCPNames();
|
|
1047
|
+
if (mcpName === '--all') {
|
|
1048
|
+
const allNames = Object.keys(savedConfigs);
|
|
1049
|
+
if (allNames.length === 0) {
|
|
1050
|
+
console.log('\nš No MCP configurations found.');
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
if (shouldGuard) {
|
|
1054
|
+
let guardedCount = 0;
|
|
1055
|
+
for (const name of allNames) {
|
|
1056
|
+
if (!disabledMCPs.includes(name)) {
|
|
1057
|
+
configManager.disableMCP(name);
|
|
1058
|
+
guardedCount++;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
console.log(`\nā Guarding all ${allNames.length} MCPs...`);
|
|
1062
|
+
console.log(` ${allNames.join(', ')}`);
|
|
1063
|
+
if (guardedCount > 0) {
|
|
1064
|
+
console.log(`\nš” Run 'savings' to see token savings estimate`);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
else {
|
|
1068
|
+
let unguardedCount = 0;
|
|
1069
|
+
for (const name of allNames) {
|
|
1070
|
+
if (disabledMCPs.includes(name)) {
|
|
1071
|
+
configManager.enableMCP(name);
|
|
1072
|
+
unguardedCount++;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
console.log(`\nā Removed MCPflare protection from all ${unguardedCount} MCPs`);
|
|
1076
|
+
console.log(` All MCPs now have direct access to your system`);
|
|
1077
|
+
}
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
if (!savedConfigs[mcpName]) {
|
|
1081
|
+
console.error(`\nā MCP not found: ${mcpName}`);
|
|
1082
|
+
console.log('\nAvailable MCPs:');
|
|
1083
|
+
Object.keys(savedConfigs).forEach((name) => {
|
|
1084
|
+
console.log(` - ${name}`);
|
|
1085
|
+
});
|
|
1086
|
+
return;
|
|
1087
|
+
}
|
|
1088
|
+
const isCurrentlyGuarded = disabledMCPs.includes(mcpName);
|
|
1089
|
+
if (shouldGuard) {
|
|
1090
|
+
if (isCurrentlyGuarded) {
|
|
1091
|
+
console.log(`\n${mcpName} is already guarded`);
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
configManager.disableMCP(mcpName);
|
|
1095
|
+
console.log(`\nā ${mcpName} moved to MCPflare protection`);
|
|
1096
|
+
console.log(` Network: Isolated (use 'configure ${mcpName}' to allow domains)`);
|
|
1097
|
+
console.log(` Filesystem: Isolated (use 'configure ${mcpName}' to allow paths)`);
|
|
1098
|
+
const config = savedConfigs[mcpName].config;
|
|
1099
|
+
const metrics = await assessCommandBasedMCP(mcpName, config);
|
|
1100
|
+
if (metrics) {
|
|
1101
|
+
tokenMetricsCache.set(mcpName, metrics);
|
|
1102
|
+
saveTokenMetrics(tokenMetricsCache);
|
|
1103
|
+
console.log(` Token savings: ~${formatTokens(metrics.estimatedTokens)} tokens`);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
else {
|
|
1107
|
+
if (!isCurrentlyGuarded) {
|
|
1108
|
+
console.log(`\n${mcpName} is not currently guarded`);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
configManager.enableMCP(mcpName);
|
|
1112
|
+
invalidateMetricsCache(mcpName);
|
|
1113
|
+
console.log(`\nā ${mcpName} removed from MCPflare protection`);
|
|
1114
|
+
console.log(` This MCP now has direct access to your system`);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
catch (error) {
|
|
1118
|
+
console.error(`\nā Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
1119
|
+
if (error instanceof Error && error.stack && verbose) {
|
|
1120
|
+
console.error(error.stack);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
async function diagnoseMCP() {
|
|
1125
|
+
try {
|
|
1126
|
+
const savedConfigs = configManager.getSavedConfigs();
|
|
1127
|
+
const savedNames = Object.keys(savedConfigs);
|
|
1128
|
+
if (savedNames.length === 0) {
|
|
1129
|
+
console.log('\nš No MCP configurations found.');
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
console.log('\nš Available MCP Configurations:');
|
|
1133
|
+
savedNames.forEach((name, index) => {
|
|
1134
|
+
console.log(` ${index + 1}. ${name}`);
|
|
1135
|
+
});
|
|
1136
|
+
const selection = await question('\nSelect MCP to diagnose by number or name (or "exit" to quit): ');
|
|
1137
|
+
const trimmed = selection.trim();
|
|
1138
|
+
if (trimmed.toLowerCase() === 'exit') {
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
let selectedName = null;
|
|
1142
|
+
const selectionNum = parseInt(trimmed, 10);
|
|
1143
|
+
if (!Number.isNaN(selectionNum) &&
|
|
1144
|
+
selectionNum >= 1 &&
|
|
1145
|
+
selectionNum <= savedNames.length) {
|
|
1146
|
+
selectedName = savedNames[selectionNum - 1];
|
|
1147
|
+
}
|
|
1148
|
+
else {
|
|
1149
|
+
selectedName =
|
|
1150
|
+
savedNames.find((name) => name.toLowerCase() === trimmed.toLowerCase()) || null;
|
|
1151
|
+
}
|
|
1152
|
+
if (!selectedName) {
|
|
1153
|
+
console.error(`\nā MCP not found: ${selection}`);
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
const savedConfig = configManager.getSavedConfig(selectedName);
|
|
1157
|
+
if (!savedConfig) {
|
|
1158
|
+
console.error(`\nā Configuration not found for: ${selectedName}`);
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
const resolvedConfig = configManager.resolveEnvVarsInObject(savedConfig);
|
|
1162
|
+
console.log(`\nš Diagnosing ${selectedName}...`);
|
|
1163
|
+
console.log('');
|
|
1164
|
+
console.log('[1/4] Validate Configuration');
|
|
1165
|
+
if ('command' in resolvedConfig) {
|
|
1166
|
+
console.log(` ā Command: ${resolvedConfig.command}`);
|
|
1167
|
+
if (resolvedConfig.args) {
|
|
1168
|
+
console.log(` Args: ${resolvedConfig.args.join(' ')}`);
|
|
1169
|
+
}
|
|
1170
|
+
const envKeys = Object.keys(resolvedConfig.env || {});
|
|
1171
|
+
if (envKeys.length > 0) {
|
|
1172
|
+
console.log(` Env vars: ${envKeys.join(', ')}`);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
else if ('url' in resolvedConfig) {
|
|
1176
|
+
console.log(` ā URL: ${resolvedConfig.url}`);
|
|
1177
|
+
if (resolvedConfig.headers) {
|
|
1178
|
+
const headerKeys = Object.keys(resolvedConfig.headers);
|
|
1179
|
+
console.log(` Headers: ${headerKeys.join(', ')}`);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
else {
|
|
1183
|
+
console.log(' ā No command or URL configured');
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
console.log('');
|
|
1187
|
+
console.log('[2/4] Test MCP Connection');
|
|
1188
|
+
if ('command' in resolvedConfig) {
|
|
1189
|
+
console.log(' Testing command-based MCP...');
|
|
1190
|
+
const transport = new StdioClientTransport({
|
|
1191
|
+
command: resolvedConfig.command,
|
|
1192
|
+
args: resolvedConfig.args || [],
|
|
1193
|
+
env: resolvedConfig.env,
|
|
1194
|
+
});
|
|
1195
|
+
const client = new Client({ name: 'mcpflare-cli-diagnose', version: '1.0.0' }, { capabilities: {} });
|
|
1196
|
+
try {
|
|
1197
|
+
const progress = new ProgressIndicator();
|
|
1198
|
+
progress.steps = [
|
|
1199
|
+
{ name: 'CLI', status: 'pending' },
|
|
1200
|
+
{ name: 'MCP SDK Client', status: 'pending' },
|
|
1201
|
+
{ name: 'Target MCP', status: 'pending' },
|
|
1202
|
+
];
|
|
1203
|
+
progress.updateStep(0, 'running');
|
|
1204
|
+
progress.updateStep(1, 'running');
|
|
1205
|
+
await client.connect(transport, { timeout: 10000 });
|
|
1206
|
+
progress.updateStep(0, 'success');
|
|
1207
|
+
progress.updateStep(1, 'success');
|
|
1208
|
+
progress.updateStep(2, 'running');
|
|
1209
|
+
progress.showFinal();
|
|
1210
|
+
console.log(' ā Connected successfully\n');
|
|
1211
|
+
console.log('[3/4] Fetch Tools List');
|
|
1212
|
+
const toolsResponse = await client.listTools();
|
|
1213
|
+
const tools = toolsResponse.tools;
|
|
1214
|
+
progress.updateStep(2, 'success');
|
|
1215
|
+
progress.showFinal();
|
|
1216
|
+
console.log(` ā Found ${tools.length} tools\n`);
|
|
1217
|
+
console.log('[4/4] Summary');
|
|
1218
|
+
console.log(` ā MCP "${selectedName}" is working correctly`);
|
|
1219
|
+
console.log(` ā Available tools: ${tools.length}`);
|
|
1220
|
+
if (tools.length > 0) {
|
|
1221
|
+
console.log('\n Top tools:');
|
|
1222
|
+
tools.slice(0, 5).forEach((tool) => {
|
|
1223
|
+
console.log(` - ${tool.name}${tool.description ? `: ${tool.description}` : ''}`);
|
|
1224
|
+
});
|
|
1225
|
+
if (tools.length > 5) {
|
|
1226
|
+
console.log(` ... and ${tools.length - 5} more`);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
await transport.close();
|
|
1230
|
+
}
|
|
1231
|
+
catch (error) {
|
|
1232
|
+
console.log(` ā Connection failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
1233
|
+
console.log('[3/4] Troubleshooting');
|
|
1234
|
+
console.log(' Possible issues:');
|
|
1235
|
+
console.log(' - Command not found or not executable');
|
|
1236
|
+
console.log(' - Missing dependencies (npm packages, etc.)');
|
|
1237
|
+
console.log(' - Incorrect environment variables');
|
|
1238
|
+
console.log(' - MCP server crashed on startup');
|
|
1239
|
+
console.log('\n Try:');
|
|
1240
|
+
console.log(` 1. Run the command manually: ${resolvedConfig.command} ${resolvedConfig.args?.join(' ') || ''}`);
|
|
1241
|
+
console.log(' 2. Check MCP server logs for errors');
|
|
1242
|
+
console.log(' 3. Verify all required environment variables are set');
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
else if ('url' in resolvedConfig) {
|
|
1246
|
+
console.log(' ā ļø URL-based MCP diagnostics not yet supported in CLI');
|
|
1247
|
+
console.log(` URL: ${resolvedConfig.url}`);
|
|
1248
|
+
console.log('\n To test URL-based MCPs, use the VSCode extension or test-direct command');
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
catch (error) {
|
|
1252
|
+
console.error('\nā Error:', error instanceof Error ? error.message : String(error));
|
|
1253
|
+
if (error instanceof Error && error.stack && verbose) {
|
|
1254
|
+
console.error(error.stack);
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
async function configureMCP() {
|
|
1259
|
+
try {
|
|
1260
|
+
const savedConfigs = configManager.getSavedConfigs();
|
|
1261
|
+
const disabledMCPs = configManager.getDisabledMCPNames();
|
|
1262
|
+
const savedNames = Object.keys(savedConfigs);
|
|
1263
|
+
if (savedNames.length === 0) {
|
|
1264
|
+
console.log('\nš No MCP configurations found.');
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
console.log('\nš Available MCP Configurations:');
|
|
1268
|
+
savedNames.forEach((name, index) => {
|
|
1269
|
+
const isGuarded = disabledMCPs.includes(name);
|
|
1270
|
+
const guardStatus = isGuarded ? 'š”ļø Guarded' : 'ā ļø Unguarded';
|
|
1271
|
+
console.log(` ${index + 1}. ${name} ${guardStatus}`);
|
|
1272
|
+
});
|
|
1273
|
+
const selection = await question('\nSelect MCP to configure by number or name (or "exit" to quit): ');
|
|
1274
|
+
const trimmed = selection.trim();
|
|
1275
|
+
if (trimmed.toLowerCase() === 'exit') {
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
let selectedName = null;
|
|
1279
|
+
const selectionNum = parseInt(trimmed, 10);
|
|
1280
|
+
if (!Number.isNaN(selectionNum) &&
|
|
1281
|
+
selectionNum >= 1 &&
|
|
1282
|
+
selectionNum <= savedNames.length) {
|
|
1283
|
+
selectedName = savedNames[selectionNum - 1];
|
|
1284
|
+
}
|
|
1285
|
+
else {
|
|
1286
|
+
selectedName =
|
|
1287
|
+
savedNames.find((name) => name.toLowerCase() === trimmed.toLowerCase()) || null;
|
|
1288
|
+
}
|
|
1289
|
+
if (!selectedName) {
|
|
1290
|
+
console.error(`\nā MCP not found: ${selection}`);
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
const isGuarded = disabledMCPs.includes(selectedName);
|
|
1294
|
+
console.log(`\nāļø Configuration: ${selectedName}`);
|
|
1295
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
1296
|
+
console.log(` Status: ${isGuarded ? 'š”ļø Guarded (Protected by MCPflare)' : 'ā ļø Unguarded (Direct access)'}`);
|
|
1297
|
+
console.log('');
|
|
1298
|
+
if (!isGuarded) {
|
|
1299
|
+
console.log(' ā ļø This MCP is not guarded.');
|
|
1300
|
+
console.log(' Network: Direct access (no isolation)');
|
|
1301
|
+
console.log(' Filesystem: Direct access (no isolation)');
|
|
1302
|
+
console.log('');
|
|
1303
|
+
console.log(` Run 'guard ${selectedName}' to enable MCPflare protection.`);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
console.log(' Current Settings (CLI defaults):');
|
|
1307
|
+
console.log(' Network: Isolated (no external network access)');
|
|
1308
|
+
console.log(' Filesystem: Isolated (no filesystem access)');
|
|
1309
|
+
console.log(' Resource Limits:');
|
|
1310
|
+
console.log(' - Max execution time: 30000ms');
|
|
1311
|
+
console.log(' - Max memory: 128MB');
|
|
1312
|
+
console.log(' - Max MCP calls: 100');
|
|
1313
|
+
console.log('');
|
|
1314
|
+
console.log(' ā¹ļø Advanced Configuration:');
|
|
1315
|
+
console.log(' Network allowlists, filesystem paths, and custom resource');
|
|
1316
|
+
console.log(' limits can be configured using the VSCode extension.');
|
|
1317
|
+
console.log('');
|
|
1318
|
+
console.log(' For now, the CLI uses secure defaults:');
|
|
1319
|
+
console.log(' ⢠Complete network isolation');
|
|
1320
|
+
console.log(' ⢠No filesystem access');
|
|
1321
|
+
console.log(' ⢠Standard resource limits');
|
|
1322
|
+
console.log('');
|
|
1323
|
+
console.log(' Quick Actions:');
|
|
1324
|
+
console.log(` ⢠unguard ${selectedName} - Remove protection (not recommended)`);
|
|
1325
|
+
console.log(` ⢠test ${selectedName} - Test this MCP's tools`);
|
|
1326
|
+
console.log(` ⢠diagnose ${selectedName} - Test connection`);
|
|
1327
|
+
}
|
|
1328
|
+
catch (error) {
|
|
1329
|
+
console.error('\nā Error:', error instanceof Error ? error.message : String(error));
|
|
1330
|
+
if (error instanceof Error && error.stack && verbose) {
|
|
1331
|
+
console.error(error.stack);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
async function showStatus() {
|
|
1336
|
+
try {
|
|
1337
|
+
const savedConfigs = configManager.getSavedConfigs();
|
|
1338
|
+
const loadedInstances = workerManager.listInstances();
|
|
1339
|
+
const disabledMCPs = configManager.getDisabledMCPNames();
|
|
1340
|
+
const sourceName = configManager.getConfigSourceDisplayName();
|
|
1341
|
+
const totalMCPs = Object.keys(savedConfigs).length;
|
|
1342
|
+
const guardedCount = disabledMCPs.length;
|
|
1343
|
+
const unguardedCount = totalMCPs - guardedCount;
|
|
1344
|
+
const loadedCount = loadedInstances.length;
|
|
1345
|
+
console.log('\nMCPflare Status');
|
|
1346
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
1347
|
+
const globalEnabled = true;
|
|
1348
|
+
console.log(` Global Protection: ${globalEnabled ? 'ā ENABLED' : 'ā DISABLED'}`);
|
|
1349
|
+
console.log(` Total MCPs: ${totalMCPs} (${guardedCount} guarded, ${unguardedCount} unguarded)`);
|
|
1350
|
+
console.log(` Loaded MCPs: ${loadedCount}`);
|
|
1351
|
+
const allMCPs = Object.entries(savedConfigs).map(([name]) => ({
|
|
1352
|
+
name,
|
|
1353
|
+
isGuarded: disabledMCPs.includes(name),
|
|
1354
|
+
metrics: tokenMetricsCache.get(name),
|
|
1355
|
+
}));
|
|
1356
|
+
const summary = calculateTokenSavings(allMCPs);
|
|
1357
|
+
if (summary.tokensSaved > 0) {
|
|
1358
|
+
const savingsPercent = calculatePercentage(summary.tokensSaved, summary.totalTokensWithoutGuard);
|
|
1359
|
+
console.log(` Token Savings: ~${formatTokens(summary.tokensSaved)} tokens (${savingsPercent}% reduction)`);
|
|
1360
|
+
}
|
|
1361
|
+
else {
|
|
1362
|
+
console.log(` Token Savings: 0 tokens (no MCPs guarded)`);
|
|
1363
|
+
}
|
|
1364
|
+
console.log(` IDE Config: ${sourceName}`);
|
|
1365
|
+
console.log('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā');
|
|
1366
|
+
console.log('\nQuick Actions:');
|
|
1367
|
+
if (unguardedCount > 0) {
|
|
1368
|
+
console.log(' ⢠guard --all - Protect all MCPs');
|
|
1369
|
+
}
|
|
1370
|
+
console.log(' ⢠savings - View detailed token analysis');
|
|
1371
|
+
if (totalMCPs > 0) {
|
|
1372
|
+
console.log(' ⢠list - List all loaded MCPs');
|
|
1373
|
+
}
|
|
1374
|
+
if (guardedCount > 0) {
|
|
1375
|
+
const firstGuarded = disabledMCPs[0];
|
|
1376
|
+
console.log(` ⢠test ${firstGuarded} - Test a guarded MCP`);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
catch (error) {
|
|
1380
|
+
console.error('\nā Error:', error instanceof Error ? error.message : String(error));
|
|
1381
|
+
if (error instanceof Error && error.stack && verbose) {
|
|
1382
|
+
console.error(error.stack);
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
function showHelp() {
|
|
1387
|
+
console.log(`
|
|
1388
|
+
Available commands:
|
|
1389
|
+
status - Show at-a-glance MCPflare status (counts, token savings, quick actions)
|
|
1390
|
+
savings - Detailed token savings analysis with per-MCP breakdown
|
|
1391
|
+
guard <mcp> - Enable MCPflare protection for an MCP (use --all for all MCPs)
|
|
1392
|
+
unguard <mcp> - Disable MCPflare protection for an MCP (use --all for all MCPs)
|
|
1393
|
+
network <mcp> on|off - Enable/disable Worker outbound network for a guarded MCP
|
|
1394
|
+
allowhost <mcp> add|remove <h> - Add/remove an allowed host (e.g., api.github.com)
|
|
1395
|
+
allowlocalhost <mcp> on|off - Allow/deny localhost (localhost/127.0.0.1) access
|
|
1396
|
+
configure - View security configuration for an MCP
|
|
1397
|
+
diagnose - Step-by-step connection diagnostics for an MCP
|
|
1398
|
+
load - Load an MCP server (shows saved configs, auto-saves new ones)
|
|
1399
|
+
test - Interactively test MCP tools (select tool, enter args, execute via Wrangler)
|
|
1400
|
+
test-direct - Test MCP directly without Wrangler/Worker isolation (uses saved configs)
|
|
1401
|
+
execute - Execute custom TypeScript code against a loaded MCP
|
|
1402
|
+
list - List all loaded MCP servers
|
|
1403
|
+
saved - List all saved MCP configurations
|
|
1404
|
+
delete - Delete a saved MCP configuration
|
|
1405
|
+
schema - Get TypeScript API schema for an MCP
|
|
1406
|
+
unload - Unload an MCP server
|
|
1407
|
+
conflicts - Check for IDE MCP configuration conflicts
|
|
1408
|
+
metrics - Show performance metrics
|
|
1409
|
+
help - Show this help message
|
|
1410
|
+
exit - Exit the CLI
|
|
1411
|
+
|
|
1412
|
+
Common workflows:
|
|
1413
|
+
status - Quick overview of your setup
|
|
1414
|
+
guard --all - Protect all MCPs and maximize token savings
|
|
1415
|
+
savings - See how many tokens you're saving
|
|
1416
|
+
diagnose - Troubleshoot MCP connection issues
|
|
1417
|
+
test github - Test a specific MCP's tools interactively
|
|
1418
|
+
|
|
1419
|
+
Usage:
|
|
1420
|
+
npm run cli - Run CLI (quiet mode, only warnings/errors)
|
|
1421
|
+
npm run cli -- --verbose - Run CLI with detailed logs
|
|
1422
|
+
npm run cli -- -v - Short form for verbose mode
|
|
1423
|
+
`);
|
|
1424
|
+
}
|
|
1425
|
+
async function handleCommand(command) {
|
|
1426
|
+
const input = command.trim();
|
|
1427
|
+
const [cmd, ...args] = input.toLowerCase().split(/\s+/);
|
|
1428
|
+
switch (cmd) {
|
|
1429
|
+
case 'status':
|
|
1430
|
+
await showStatus();
|
|
1431
|
+
break;
|
|
1432
|
+
case 'savings':
|
|
1433
|
+
await showSavings();
|
|
1434
|
+
break;
|
|
1435
|
+
case 'configure':
|
|
1436
|
+
await configureMCP();
|
|
1437
|
+
break;
|
|
1438
|
+
case 'diagnose':
|
|
1439
|
+
await diagnoseMCP();
|
|
1440
|
+
break;
|
|
1441
|
+
case 'guard':
|
|
1442
|
+
if (args.length === 0) {
|
|
1443
|
+
console.log('\nā Usage: guard <mcp-name> or guard --all');
|
|
1444
|
+
console.log('Example: guard github');
|
|
1445
|
+
console.log('Example: guard --all');
|
|
1446
|
+
}
|
|
1447
|
+
else {
|
|
1448
|
+
await guardMCP(args[0], true);
|
|
1449
|
+
}
|
|
1450
|
+
break;
|
|
1451
|
+
case 'unguard':
|
|
1452
|
+
if (args.length === 0) {
|
|
1453
|
+
console.log('\nā Usage: unguard <mcp-name> or unguard --all');
|
|
1454
|
+
console.log('Example: unguard github');
|
|
1455
|
+
console.log('Example: unguard --all');
|
|
1456
|
+
}
|
|
1457
|
+
else {
|
|
1458
|
+
await guardMCP(args[0], false);
|
|
1459
|
+
}
|
|
1460
|
+
break;
|
|
1461
|
+
case 'load':
|
|
1462
|
+
await loadMCP();
|
|
1463
|
+
break;
|
|
1464
|
+
case 'execute':
|
|
1465
|
+
await executeCode();
|
|
1466
|
+
break;
|
|
1467
|
+
case 'test':
|
|
1468
|
+
await testTool();
|
|
1469
|
+
break;
|
|
1470
|
+
case 'test-direct':
|
|
1471
|
+
case 'testdirect':
|
|
1472
|
+
await testDirect();
|
|
1473
|
+
break;
|
|
1474
|
+
case 'list':
|
|
1475
|
+
await listMCPs();
|
|
1476
|
+
break;
|
|
1477
|
+
case 'saved':
|
|
1478
|
+
await listSavedConfigs();
|
|
1479
|
+
break;
|
|
1480
|
+
case 'delete':
|
|
1481
|
+
await deleteSavedConfig();
|
|
1482
|
+
break;
|
|
1483
|
+
case 'schema':
|
|
1484
|
+
await getSchema();
|
|
1485
|
+
break;
|
|
1486
|
+
case 'unload':
|
|
1487
|
+
await unloadMCP();
|
|
1488
|
+
break;
|
|
1489
|
+
case 'conflicts':
|
|
1490
|
+
await checkIDEConflicts();
|
|
1491
|
+
break;
|
|
1492
|
+
case 'metrics':
|
|
1493
|
+
await getMetrics();
|
|
1494
|
+
break;
|
|
1495
|
+
case 'network': {
|
|
1496
|
+
if (args.length < 2) {
|
|
1497
|
+
console.log('\nā Usage: network <mcp-name> on|off');
|
|
1498
|
+
console.log('Example: network github on');
|
|
1499
|
+
console.log('Example: network github off');
|
|
1500
|
+
break;
|
|
1501
|
+
}
|
|
1502
|
+
const [mcpName, mode] = args;
|
|
1503
|
+
await updateNetworkEnabled(mcpName, mode === 'on');
|
|
1504
|
+
break;
|
|
1505
|
+
}
|
|
1506
|
+
case 'allowhost': {
|
|
1507
|
+
if (args.length < 3) {
|
|
1508
|
+
console.log('\nā Usage: allowhost <mcp-name> add|remove <host>');
|
|
1509
|
+
console.log('Example: allowhost github add api.github.com');
|
|
1510
|
+
console.log('Example: allowhost github remove api.github.com');
|
|
1511
|
+
break;
|
|
1512
|
+
}
|
|
1513
|
+
const [mcpName, action, host] = args;
|
|
1514
|
+
await updateAllowedHost(mcpName, action, host);
|
|
1515
|
+
break;
|
|
1516
|
+
}
|
|
1517
|
+
case 'allowlocalhost': {
|
|
1518
|
+
if (args.length < 2) {
|
|
1519
|
+
console.log('\nā Usage: allowlocalhost <mcp-name> on|off');
|
|
1520
|
+
console.log('Example: allowlocalhost github on');
|
|
1521
|
+
console.log('Example: allowlocalhost github off');
|
|
1522
|
+
break;
|
|
1523
|
+
}
|
|
1524
|
+
const [mcpName, mode] = args;
|
|
1525
|
+
await updateAllowLocalhost(mcpName, mode === 'on');
|
|
1526
|
+
break;
|
|
1527
|
+
}
|
|
1528
|
+
case 'help':
|
|
1529
|
+
showHelp();
|
|
1530
|
+
break;
|
|
1531
|
+
case 'exit':
|
|
1532
|
+
case 'quit':
|
|
1533
|
+
isExiting = true;
|
|
1534
|
+
console.log('\nš Goodbye!');
|
|
1535
|
+
rl.close();
|
|
1536
|
+
process.exit(0);
|
|
1537
|
+
break;
|
|
1538
|
+
case '':
|
|
1539
|
+
break;
|
|
1540
|
+
default:
|
|
1541
|
+
console.log(`\nā Unknown command: ${cmd}`);
|
|
1542
|
+
console.log('Type "help" for available commands.');
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
async function updateNetworkEnabled(mcpName, enabled) {
|
|
1546
|
+
const config = getOrCreateSecurityConfig(mcpName);
|
|
1547
|
+
config.network.enabled = enabled;
|
|
1548
|
+
upsertMCPConfig(config);
|
|
1549
|
+
console.log(`\nā
Network access for "${mcpName}" set to ${enabled ? 'ON' : 'OFF'}.`);
|
|
1550
|
+
if (enabled) {
|
|
1551
|
+
console.log(` Allowed hosts: ${config.network.allowlist.length > 0 ? config.network.allowlist.join(', ') : '(none - blocks all external)'}`);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
async function updateAllowLocalhost(mcpName, allow) {
|
|
1555
|
+
const config = getOrCreateSecurityConfig(mcpName);
|
|
1556
|
+
config.network.enabled = true;
|
|
1557
|
+
config.network.allowLocalhost = allow;
|
|
1558
|
+
upsertMCPConfig(config);
|
|
1559
|
+
console.log(`\nā
Allow localhost for "${mcpName}" set to ${allow ? 'ON' : 'OFF'}.`);
|
|
1560
|
+
}
|
|
1561
|
+
async function updateAllowedHost(mcpName, action, host) {
|
|
1562
|
+
const normalizedHost = host.trim().toLowerCase();
|
|
1563
|
+
if (!normalizedHost) {
|
|
1564
|
+
console.log('\nā Host is required.');
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
const config = getOrCreateSecurityConfig(mcpName);
|
|
1568
|
+
config.network.enabled = true;
|
|
1569
|
+
const current = new Set(config.network.allowlist.map((h) => h.toLowerCase()));
|
|
1570
|
+
if (action === 'add') {
|
|
1571
|
+
current.add(normalizedHost);
|
|
1572
|
+
}
|
|
1573
|
+
else if (action === 'remove' || action === 'rm' || action === 'delete') {
|
|
1574
|
+
current.delete(normalizedHost);
|
|
1575
|
+
}
|
|
1576
|
+
else {
|
|
1577
|
+
console.log('\nā Usage: allowhost <mcp-name> add|remove <host>');
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1580
|
+
config.network.allowlist = Array.from(current).sort();
|
|
1581
|
+
upsertMCPConfig(config);
|
|
1582
|
+
console.log(`\nā
Updated allowlist for "${mcpName}": ${config.network.allowlist.length > 0 ? config.network.allowlist.join(', ') : '(none - blocks all external)'}`);
|
|
1583
|
+
}
|
|
1584
|
+
function getOrCreateSecurityConfig(mcpName) {
|
|
1585
|
+
const settings = loadSettings();
|
|
1586
|
+
const existing = settings.mcpConfigs.find((c) => c.mcpName === mcpName);
|
|
1587
|
+
if (existing)
|
|
1588
|
+
return existing;
|
|
1589
|
+
return createDefaultConfig(mcpName);
|
|
1590
|
+
}
|
|
1591
|
+
async function main() {
|
|
1592
|
+
const verbose = process.argv.includes('--verbose') || process.argv.includes('-v');
|
|
1593
|
+
console.log(`
|
|
1594
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1595
|
+
ā MCPflare - Interactive CLI ā
|
|
1596
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1597
|
+
|
|
1598
|
+
Type "help" for available commands.
|
|
1599
|
+
Type "exit" to quit.
|
|
1600
|
+
${verbose ? '\nš Verbose logging enabled. Use --quiet to disable.\n' : '\nš” Tip: Use --verbose or -v for detailed logs.\n'}`);
|
|
1601
|
+
rl.prompt();
|
|
1602
|
+
rl.on('line', async (input) => {
|
|
1603
|
+
await handleCommand(input);
|
|
1604
|
+
rl.prompt();
|
|
1605
|
+
});
|
|
1606
|
+
rl.on('close', () => {
|
|
1607
|
+
if (!isExiting) {
|
|
1608
|
+
console.log('\nš Goodbye!');
|
|
1609
|
+
}
|
|
1610
|
+
process.exit(0);
|
|
1611
|
+
});
|
|
1612
|
+
}
|
|
1613
|
+
main().catch((error) => {
|
|
1614
|
+
console.error('Fatal error:', error);
|
|
1615
|
+
process.exit(1);
|
|
1616
|
+
});
|
|
1617
|
+
//# sourceMappingURL=index.js.map
|