@fractalizer/mcp-cli 0.3.18 → 1.0.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/README.md +158 -266
- package/dist/commands/connect.command.d.ts +18 -15
- package/dist/commands/connect.command.d.ts.map +1 -1
- package/dist/commands/connect.command.js +66 -40
- package/dist/commands/connect.command.js.map +1 -1
- package/dist/commands/disconnect.command.d.ts +5 -17
- package/dist/commands/disconnect.command.d.ts.map +1 -1
- package/dist/commands/disconnect.command.js +4 -15
- package/dist/commands/disconnect.command.js.map +1 -1
- package/dist/commands/doctor.command.d.ts +34 -0
- package/dist/commands/doctor.command.d.ts.map +1 -0
- package/dist/commands/doctor.command.js +321 -0
- package/dist/commands/doctor.command.js.map +1 -0
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/list.command.d.ts +4 -9
- package/dist/commands/list.command.d.ts.map +1 -1
- package/dist/commands/list.command.js +1 -5
- package/dist/commands/list.command.js.map +1 -1
- package/dist/commands/status.command.d.ts +6 -13
- package/dist/commands/status.command.d.ts.map +1 -1
- package/dist/commands/status.command.js +18 -12
- package/dist/commands/status.command.js.map +1 -1
- package/dist/commands/validate.command.d.ts +6 -11
- package/dist/commands/validate.command.d.ts.map +1 -1
- package/dist/commands/validate.command.js +15 -19
- package/dist/commands/validate.command.js.map +1 -1
- package/dist/connectors/base/base-connector.d.ts +31 -55
- package/dist/connectors/base/base-connector.d.ts.map +1 -1
- package/dist/connectors/base/base-connector.js +71 -57
- package/dist/connectors/base/base-connector.js.map +1 -1
- package/dist/connectors/base/configurable-connector.d.ts +89 -24
- package/dist/connectors/base/configurable-connector.d.ts.map +1 -1
- package/dist/connectors/base/configurable-connector.js +266 -23
- package/dist/connectors/base/configurable-connector.js.map +1 -1
- package/dist/connectors/base/connector.interface.d.ts +29 -20
- package/dist/connectors/base/connector.interface.d.ts.map +1 -1
- package/dist/connectors/base/index.d.ts +0 -1
- package/dist/connectors/base/index.d.ts.map +1 -1
- package/dist/connectors/base/index.js +0 -1
- package/dist/connectors/base/index.js.map +1 -1
- package/dist/connectors/claude-code/claude-code.connector.d.ts +171 -21
- package/dist/connectors/claude-code/claude-code.connector.d.ts.map +1 -1
- package/dist/connectors/claude-code/claude-code.connector.js +368 -43
- package/dist/connectors/claude-code/claude-code.connector.js.map +1 -1
- package/dist/connectors/connector-factory.d.ts +15 -15
- package/dist/connectors/connector-factory.d.ts.map +1 -1
- package/dist/connectors/connector-factory.js +61 -18
- package/dist/connectors/connector-factory.js.map +1 -1
- package/dist/connectors/index.d.ts +0 -4
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +2 -8
- package/dist/connectors/index.js.map +1 -1
- package/dist/connectors/registry.d.ts +20 -27
- package/dist/connectors/registry.d.ts.map +1 -1
- package/dist/connectors/registry.js +34 -34
- package/dist/connectors/registry.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/{base.types.d.ts → client.types.d.ts} +37 -30
- package/dist/types/client.types.d.ts.map +1 -0
- package/dist/types/client.types.js +6 -0
- package/dist/types/client.types.js.map +1 -0
- package/dist/types/doctor.types.d.ts +91 -0
- package/dist/types/doctor.types.d.ts.map +1 -0
- package/dist/types/doctor.types.js +12 -0
- package/dist/types/doctor.types.js.map +1 -0
- package/dist/types/launch.types.d.ts +59 -0
- package/dist/types/launch.types.d.ts.map +1 -0
- package/dist/types/launch.types.js +6 -0
- package/dist/types/launch.types.js.map +1 -0
- package/dist/types.d.ts +23 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -2
- package/dist/types.js.map +1 -1
- package/dist/utils/command-executor.d.ts +39 -3
- package/dist/utils/command-executor.d.ts.map +1 -1
- package/dist/utils/command-executor.js +95 -8
- package/dist/utils/command-executor.js.map +1 -1
- package/dist/utils/config-manager.d.ts +25 -42
- package/dist/utils/config-manager.d.ts.map +1 -1
- package/dist/utils/config-manager.js +21 -49
- package/dist/utils/config-manager.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/interactive-prompter.d.ts +16 -64
- package/dist/utils/interactive-prompter.d.ts.map +1 -1
- package/dist/utils/interactive-prompter.js +12 -65
- package/dist/utils/interactive-prompter.js.map +1 -1
- package/dist/utils/launch-spec-helpers.d.ts +38 -0
- package/dist/utils/launch-spec-helpers.d.ts.map +1 -0
- package/dist/utils/launch-spec-helpers.js +131 -0
- package/dist/utils/launch-spec-helpers.js.map +1 -0
- package/package.json +2 -2
- package/dist/connectors/base/file-based-connector.d.ts +0 -97
- package/dist/connectors/base/file-based-connector.d.ts.map +0 -1
- package/dist/connectors/base/file-based-connector.js +0 -185
- package/dist/connectors/base/file-based-connector.js.map +0 -1
- package/dist/connectors/claude-desktop/claude-desktop.connector.d.ts +0 -38
- package/dist/connectors/claude-desktop/claude-desktop.connector.d.ts.map +0 -1
- package/dist/connectors/claude-desktop/claude-desktop.connector.js +0 -68
- package/dist/connectors/claude-desktop/claude-desktop.connector.js.map +0 -1
- package/dist/connectors/codex/codex.connector.d.ts +0 -51
- package/dist/connectors/codex/codex.connector.d.ts.map +0 -1
- package/dist/connectors/codex/codex.connector.js +0 -76
- package/dist/connectors/codex/codex.connector.js.map +0 -1
- package/dist/connectors/gemini/gemini.connector.d.ts +0 -41
- package/dist/connectors/gemini/gemini.connector.d.ts.map +0 -1
- package/dist/connectors/gemini/gemini.connector.js +0 -61
- package/dist/connectors/gemini/gemini.connector.js.map +0 -1
- package/dist/connectors/qwen/qwen.connector.d.ts +0 -41
- package/dist/connectors/qwen/qwen.connector.d.ts.map +0 -1
- package/dist/connectors/qwen/qwen.connector.js +0 -61
- package/dist/connectors/qwen/qwen.connector.js.map +0 -1
- package/dist/types/base.types.d.ts.map +0 -1
- package/dist/types/base.types.js +0 -6
- package/dist/types/base.types.js.map +0 -1
|
@@ -5,32 +5,35 @@
|
|
|
5
5
|
import { InteractivePrompter } from '../utils/interactive-prompter.js';
|
|
6
6
|
import { Logger } from '../utils/logger.js';
|
|
7
7
|
/**
|
|
8
|
-
* Команда
|
|
8
|
+
* Команда подключения MCP сервера к выбранному клиенту.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* Поток:
|
|
11
|
+
* 1. Найти установленные клиенты (`registry.findInstalled`).
|
|
12
|
+
* 2. Выбрать клиент (через CLI флаг `--client` или интерактивно).
|
|
13
|
+
* 3. Загрузить сохранённую доменную конфигурацию (`configManager.load`).
|
|
14
|
+
* 4. Собрать новую доменную конфигурацию через промпты.
|
|
15
|
+
* 5. Адаптер `buildServerLaunch(config)` → {@link ServerLaunchSpec}.
|
|
16
|
+
* 6. `connector.validateLaunchSpec(spec)`; при ошибках — abort (без `connect`/`save`).
|
|
17
|
+
* 7. `connector.connect(spec)`. При исключении управление прерывается, `save` не достигается.
|
|
18
|
+
* 8. Информационный `getStatus()`.
|
|
19
|
+
* 9. После успешного connect — `configManager.save(domainConfig)` и warning про plaintext-токен.
|
|
11
20
|
*
|
|
12
21
|
* @example
|
|
13
22
|
* ```typescript
|
|
14
|
-
* const registry = new ConnectorRegistry<YourConfig>();
|
|
15
|
-
* const configManager = new ConfigManager<YourConfig>({
|
|
16
|
-
* projectName: 'your-server',
|
|
17
|
-
* safeFields: ['orgId', 'apiBase'],
|
|
18
|
-
* });
|
|
19
|
-
*
|
|
20
|
-
* const configPrompts = [
|
|
21
|
-
* { name: 'token', type: 'password', message: 'OAuth токен:' },
|
|
22
|
-
* { name: 'orgId', type: 'input', message: 'ID организации:' },
|
|
23
|
-
* ];
|
|
24
|
-
*
|
|
25
23
|
* await connectCommand({
|
|
26
24
|
* registry,
|
|
27
25
|
* configManager,
|
|
28
26
|
* configPrompts,
|
|
27
|
+
* buildServerLaunch: (cfg) => ({
|
|
28
|
+
* command: 'node',
|
|
29
|
+
* args: ['/abs/path/server.bundle.cjs'],
|
|
30
|
+
* env: { API_TOKEN: cfg.token, ORG_ID: cfg.orgId },
|
|
31
|
+
* }),
|
|
29
32
|
* });
|
|
30
33
|
* ```
|
|
31
34
|
*/
|
|
32
35
|
export async function connectCommand(options) {
|
|
33
|
-
const { registry, configManager, configPrompts,
|
|
36
|
+
const { registry, configManager, configPrompts, buildServerLaunch, cliOptions } = options;
|
|
34
37
|
Logger.header('🔌 Подключение MCP сервера');
|
|
35
38
|
Logger.newLine();
|
|
36
39
|
// 1. Найти установленные клиенты
|
|
@@ -39,11 +42,10 @@ export async function connectCommand(options) {
|
|
|
39
42
|
spinner.stop();
|
|
40
43
|
if (installedClients.length === 0) {
|
|
41
44
|
Logger.error('Не найдено установленных MCP клиентов');
|
|
42
|
-
Logger.info('
|
|
43
|
-
Logger.info('Установите хотя бы один из них для продолжения');
|
|
45
|
+
Logger.info('Установите хотя бы один поддерживаемый MCP клиент');
|
|
44
46
|
return;
|
|
45
47
|
}
|
|
46
|
-
Logger.success(`Найдено ${installedClients.length} установленных клиента(ов)`);
|
|
48
|
+
Logger.success(`Найдено ${String(installedClients.length)} установленных клиента(ов)`);
|
|
47
49
|
Logger.newLine();
|
|
48
50
|
// 2. Выбрать клиент
|
|
49
51
|
let connector;
|
|
@@ -51,6 +53,14 @@ export async function connectCommand(options) {
|
|
|
51
53
|
connector = registry.get(cliOptions.client);
|
|
52
54
|
if (!connector) {
|
|
53
55
|
Logger.error(`Клиент "${cliOptions.client}" не найден`);
|
|
56
|
+
const valid = registry.getAll();
|
|
57
|
+
if (valid.length > 0) {
|
|
58
|
+
Logger.info('Доступные клиенты:');
|
|
59
|
+
for (const c of valid) {
|
|
60
|
+
const info = c.getClientInfo();
|
|
61
|
+
Logger.info(` - ${info.name} (${info.displayName})`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
54
64
|
return;
|
|
55
65
|
}
|
|
56
66
|
const isInstalled = await connector.isInstalled();
|
|
@@ -70,49 +80,65 @@ export async function connectCommand(options) {
|
|
|
70
80
|
return;
|
|
71
81
|
}
|
|
72
82
|
Logger.newLine();
|
|
73
|
-
// 3.
|
|
83
|
+
// 3. Загрузить сохранённую доменную конфигурацию
|
|
74
84
|
const savedConfig = await configManager.load();
|
|
75
85
|
if (savedConfig) {
|
|
76
86
|
Logger.info('Найдена сохраненная конфигурация (секретные поля будут запрошены заново)');
|
|
77
87
|
}
|
|
88
|
+
// 4. Собрать доменную конфигурацию
|
|
78
89
|
const prompter = new InteractivePrompter(configPrompts);
|
|
79
|
-
const
|
|
80
|
-
// Построить полную конфигурацию
|
|
81
|
-
const config = buildConfig
|
|
82
|
-
? buildConfig(serverConfig)
|
|
83
|
-
: {
|
|
84
|
-
projectPath: process.cwd(),
|
|
85
|
-
...serverConfig,
|
|
86
|
-
};
|
|
90
|
+
const domainConfig = await prompter.promptServerConfig(savedConfig);
|
|
87
91
|
Logger.newLine();
|
|
88
|
-
//
|
|
89
|
-
const
|
|
92
|
+
// 5. Построить spec через адаптер
|
|
93
|
+
const spec = buildServerLaunch(domainConfig);
|
|
94
|
+
// 6. Валидация
|
|
95
|
+
const errors = await connector.validateLaunchSpec(spec);
|
|
90
96
|
if (errors.length > 0) {
|
|
91
|
-
Logger.error('Ошибки
|
|
97
|
+
Logger.error('Ошибки конфигурации запуска:');
|
|
92
98
|
errors.forEach((err) => Logger.error(` - ${err}`));
|
|
93
99
|
return;
|
|
94
100
|
}
|
|
95
|
-
//
|
|
101
|
+
// 7. Подключение (если бросит — save не выполняется)
|
|
96
102
|
const connectSpinner = Logger.spinner(`Подключаю к ${connector.getClientInfo().displayName}...`);
|
|
97
103
|
try {
|
|
98
|
-
await connector.connect(
|
|
104
|
+
await connector.connect(spec);
|
|
99
105
|
connectSpinner.succeed(`MCP сервер успешно подключен к ${connector.getClientInfo().displayName}!`);
|
|
100
|
-
const status = await connector.getStatus();
|
|
101
|
-
if (status.details) {
|
|
102
|
-
Logger.info(`Конфигурация: ${status.details.configPath}`);
|
|
103
|
-
}
|
|
104
106
|
}
|
|
105
107
|
catch (error) {
|
|
106
108
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
107
109
|
connectSpinner.fail(`Ошибка подключения: ${errorMessage}`);
|
|
108
110
|
return;
|
|
109
111
|
}
|
|
112
|
+
// 8. Информационный статус
|
|
113
|
+
const status = await connector.getStatus();
|
|
114
|
+
if (status.details?.configPath) {
|
|
115
|
+
Logger.info(`Конфигурация: ${status.details.configPath}`);
|
|
116
|
+
}
|
|
110
117
|
Logger.newLine();
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
// 9. Безусловно сохранить доменную конфигурацию (после успешного connect).
|
|
119
|
+
// Подключение к клиенту уже выполнено успешно; неудача save локального
|
|
120
|
+
// кэша — не критична, но информируем пользователя, чтобы при необходимости
|
|
121
|
+
// он мог разобраться (например, нет прав на ~/. dir).
|
|
122
|
+
try {
|
|
123
|
+
await configManager.save(domainConfig);
|
|
124
|
+
Logger.success(`Конфигурация сохранена: ${configManager.getConfigPath()}`);
|
|
125
|
+
}
|
|
126
|
+
catch (saveError) {
|
|
127
|
+
const errMsg = saveError instanceof Error ? saveError.message : String(saveError);
|
|
128
|
+
Logger.error(`Подключение выполнено, но локальный кэш конфига не сохранён: ${errMsg}`);
|
|
129
|
+
Logger.info(`Путь к локальному кэшу: ${configManager.getConfigPath()}`);
|
|
130
|
+
if (status.details?.configPath) {
|
|
131
|
+
Logger.info(`Путь к client config (подключение активно): ${status.details.configPath}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Предупреждение о plaintext-хранении токена в конфиге клиента
|
|
135
|
+
if (status.details?.configPath) {
|
|
136
|
+
Logger.warn(`⚠️ Токен сохранён в plaintext в ${status.details.configPath}. ` +
|
|
137
|
+
'Убедитесь, что файл недоступен другим пользователям системы.');
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
Logger.warn('⚠️ Токен сохранён в plaintext в конфиге клиента. ' +
|
|
141
|
+
'Убедитесь, что файл недоступен другим пользователям системы.');
|
|
116
142
|
}
|
|
117
143
|
Logger.newLine();
|
|
118
144
|
Logger.success('✅ Готово! Теперь вы можете использовать MCP сервер в выбранном клиенте.');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connect.command.js","sourceRoot":"","sources":["../../src/commands/connect.command.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C
|
|
1
|
+
{"version":3,"file":"connect.command.js","sourceRoot":"","sources":["../../src/commands/connect.command.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { ConnectorRegistry } from '../connectors/registry.js';
|
|
2
|
-
import type { BaseMCPServerConfig } from '../types.js';
|
|
3
2
|
/**
|
|
4
3
|
* Опции для команды disconnect
|
|
5
4
|
*/
|
|
6
|
-
export interface DisconnectCommandOptions
|
|
5
|
+
export interface DisconnectCommandOptions {
|
|
7
6
|
/** Реестр MCP коннекторов */
|
|
8
|
-
registry: ConnectorRegistry
|
|
7
|
+
registry: ConnectorRegistry;
|
|
9
8
|
/** CLI опции из командной строки */
|
|
10
9
|
cliOptions?: {
|
|
11
10
|
/** Имя клиента для отключения (опционально, если не указан - будет интерактивный выбор) */
|
|
@@ -13,24 +12,13 @@ export interface DisconnectCommandOptions<TConfig extends BaseMCPServerConfig> {
|
|
|
13
12
|
};
|
|
14
13
|
}
|
|
15
14
|
/**
|
|
16
|
-
* Команда для отключения MCP сервера от
|
|
17
|
-
*
|
|
18
|
-
* @param options - Опции команды
|
|
15
|
+
* Команда для отключения MCP сервера от клиента.
|
|
19
16
|
*
|
|
20
17
|
* @example
|
|
21
18
|
* ```typescript
|
|
22
|
-
* const registry = new ConnectorRegistry<YourConfig>();
|
|
23
|
-
* // регистрация коннекторов...
|
|
24
|
-
*
|
|
25
|
-
* // Интерактивный выбор клиента
|
|
26
19
|
* await disconnectCommand({ registry });
|
|
27
|
-
*
|
|
28
|
-
* // Или указать клиент явно
|
|
29
|
-
* await disconnectCommand({
|
|
30
|
-
* registry,
|
|
31
|
-
* cliOptions: { client: 'claude-desktop' }
|
|
32
|
-
* });
|
|
20
|
+
* await disconnectCommand({ registry, cliOptions: { client: 'claude-desktop' } });
|
|
33
21
|
* ```
|
|
34
22
|
*/
|
|
35
|
-
export declare function disconnectCommand
|
|
23
|
+
export declare function disconnectCommand(options: DisconnectCommandOptions): Promise<void>;
|
|
36
24
|
//# sourceMappingURL=disconnect.command.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"disconnect.command.d.ts","sourceRoot":"","sources":["../../src/commands/disconnect.command.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"disconnect.command.d.ts","sourceRoot":"","sources":["../../src/commands/disconnect.command.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAInE;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,6BAA6B;IAC7B,QAAQ,EAAE,iBAAiB,CAAC;IAE5B,oCAAoC;IACpC,UAAU,CAAC,EAAE;QACX,2FAA2F;QAC3F,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AA4DD;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCxF"}
|
|
@@ -6,7 +6,7 @@ import { InteractivePrompter } from '../utils/interactive-prompter.js';
|
|
|
6
6
|
async function findConnectedConnectors(registry) {
|
|
7
7
|
const statuses = await registry.checkAllStatuses();
|
|
8
8
|
return Array.from(statuses.entries())
|
|
9
|
-
.filter(([
|
|
9
|
+
.filter(([, status]) => status.connected)
|
|
10
10
|
.map(([name]) => registry.get(name))
|
|
11
11
|
.filter((c) => c !== undefined);
|
|
12
12
|
}
|
|
@@ -49,23 +49,12 @@ async function performDisconnect(connector) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
|
-
* Команда для отключения MCP сервера от
|
|
53
|
-
*
|
|
54
|
-
* @param options - Опции команды
|
|
52
|
+
* Команда для отключения MCP сервера от клиента.
|
|
55
53
|
*
|
|
56
54
|
* @example
|
|
57
55
|
* ```typescript
|
|
58
|
-
* const registry = new ConnectorRegistry<YourConfig>();
|
|
59
|
-
* // регистрация коннекторов...
|
|
60
|
-
*
|
|
61
|
-
* // Интерактивный выбор клиента
|
|
62
56
|
* await disconnectCommand({ registry });
|
|
63
|
-
*
|
|
64
|
-
* // Или указать клиент явно
|
|
65
|
-
* await disconnectCommand({
|
|
66
|
-
* registry,
|
|
67
|
-
* cliOptions: { client: 'claude-desktop' }
|
|
68
|
-
* });
|
|
57
|
+
* await disconnectCommand({ registry, cliOptions: { client: 'claude-desktop' } });
|
|
69
58
|
* ```
|
|
70
59
|
*/
|
|
71
60
|
export async function disconnectCommand(options) {
|
|
@@ -80,7 +69,7 @@ export async function disconnectCommand(options) {
|
|
|
80
69
|
Logger.newLine();
|
|
81
70
|
return;
|
|
82
71
|
}
|
|
83
|
-
Logger.success(`Найдено ${connectedConnectors.length} подключенных клиента(ов)`);
|
|
72
|
+
Logger.success(`Найдено ${String(connectedConnectors.length)} подключенных клиента(ов)`);
|
|
84
73
|
Logger.newLine();
|
|
85
74
|
const connector = await selectConnector(registry, connectedConnectors, cliOptions?.client);
|
|
86
75
|
if (!connector) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"disconnect.command.js","sourceRoot":"","sources":["../../src/commands/disconnect.command.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"disconnect.command.js","sourceRoot":"","sources":["../../src/commands/disconnect.command.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Команда самодиагностики MCP подключений.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import type { DoctorCommandOptions, DoctorReport } from '../types/doctor.types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Запустить диагностику MCP подключений.
|
|
9
|
+
*
|
|
10
|
+
* Собирает проверки в детерминированном порядке:
|
|
11
|
+
* 1. Для каждого зарегистрированного и установленного клиента
|
|
12
|
+
* (`registry.findInstalled()` в порядке `registry.getAll()`):
|
|
13
|
+
* - `isInstalled` (фактически уже пройдена — отмечается как `ok`);
|
|
14
|
+
* - `getStatus` (запрос статуса через коннектор);
|
|
15
|
+
* - `command-exists` (проверка существования `command` из spec на диске).
|
|
16
|
+
* 2. Доменные `extraChecks` в порядке передачи.
|
|
17
|
+
*
|
|
18
|
+
* Все проверки запускаются параллельно через `Promise.allSettled`. Рендеринг
|
|
19
|
+
* результатов — после завершения всех проверок, чтобы избежать чересполосицы
|
|
20
|
+
* вывода.
|
|
21
|
+
*
|
|
22
|
+
* **Robustness:** исключение внутри `check.run()` перехватывается и трактуется
|
|
23
|
+
* как `fail` с сообщением `'Исключение при выполнении проверки: <message>'`.
|
|
24
|
+
* Одна сломанная проверка не валит весь doctor.
|
|
25
|
+
*
|
|
26
|
+
* **Важно:** функция НЕ вызывает `process.exit`. Caller обязан проверить
|
|
27
|
+
* `report.summary.fail` и при необходимости вызвать `process.exit(1)` — это
|
|
28
|
+
* сохраняет тестируемость функции и оставляет policy решение на CLI entry point.
|
|
29
|
+
*
|
|
30
|
+
* @param options - Опции: `registry` + опциональные доменные `extraChecks`.
|
|
31
|
+
* @returns Агрегированный {@link DoctorReport}.
|
|
32
|
+
*/
|
|
33
|
+
export declare function doctorCommand(options: DoctorCommandOptions): Promise<DoctorReport>;
|
|
34
|
+
//# sourceMappingURL=doctor.command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.command.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.command.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,KAAK,EAGV,oBAAoB,EACpB,YAAY,EACb,MAAM,0BAA0B,CAAC;AAclC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC,CAkDxF"}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Команда самодиагностики MCP подключений.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from 'node:fs/promises';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import { resolveExecutablePath } from '../utils/launch-spec-helpers.js';
|
|
9
|
+
import { Logger } from '../utils/logger.js';
|
|
10
|
+
/**
|
|
11
|
+
* Internal: ключ группы для проверок без явной group (например, доменные
|
|
12
|
+
* extraChecks без поля group).
|
|
13
|
+
*/
|
|
14
|
+
const UNGROUPED_GROUP_KEY = '_ungrouped';
|
|
15
|
+
/**
|
|
16
|
+
* Internal: метка группы для рендера проверок без group.
|
|
17
|
+
*/
|
|
18
|
+
const UNGROUPED_GROUP_LABEL = 'Доменные проверки';
|
|
19
|
+
/**
|
|
20
|
+
* Запустить диагностику MCP подключений.
|
|
21
|
+
*
|
|
22
|
+
* Собирает проверки в детерминированном порядке:
|
|
23
|
+
* 1. Для каждого зарегистрированного и установленного клиента
|
|
24
|
+
* (`registry.findInstalled()` в порядке `registry.getAll()`):
|
|
25
|
+
* - `isInstalled` (фактически уже пройдена — отмечается как `ok`);
|
|
26
|
+
* - `getStatus` (запрос статуса через коннектор);
|
|
27
|
+
* - `command-exists` (проверка существования `command` из spec на диске).
|
|
28
|
+
* 2. Доменные `extraChecks` в порядке передачи.
|
|
29
|
+
*
|
|
30
|
+
* Все проверки запускаются параллельно через `Promise.allSettled`. Рендеринг
|
|
31
|
+
* результатов — после завершения всех проверок, чтобы избежать чересполосицы
|
|
32
|
+
* вывода.
|
|
33
|
+
*
|
|
34
|
+
* **Robustness:** исключение внутри `check.run()` перехватывается и трактуется
|
|
35
|
+
* как `fail` с сообщением `'Исключение при выполнении проверки: <message>'`.
|
|
36
|
+
* Одна сломанная проверка не валит весь doctor.
|
|
37
|
+
*
|
|
38
|
+
* **Важно:** функция НЕ вызывает `process.exit`. Caller обязан проверить
|
|
39
|
+
* `report.summary.fail` и при необходимости вызвать `process.exit(1)` — это
|
|
40
|
+
* сохраняет тестируемость функции и оставляет policy решение на CLI entry point.
|
|
41
|
+
*
|
|
42
|
+
* @param options - Опции: `registry` + опциональные доменные `extraChecks`.
|
|
43
|
+
* @returns Агрегированный {@link DoctorReport}.
|
|
44
|
+
*/
|
|
45
|
+
export async function doctorCommand(options) {
|
|
46
|
+
const { registry, extraChecks = [] } = options;
|
|
47
|
+
Logger.header('🩺 Диагностика MCP подключений');
|
|
48
|
+
// Per-run cache для getLaunchSpec — connector.getLaunchSpec() читает файл
|
|
49
|
+
// конфигурации (или вызывает CLI) — может быть дорогим. Внутри doctor мы
|
|
50
|
+
// вызываем его для двух проверок (interpretConnectionStatus и
|
|
51
|
+
// checkCommandExistsOnDisk), достаточно одного чтения.
|
|
52
|
+
const launchSpecCache = new Map();
|
|
53
|
+
const getCachedLaunchSpec = (connector) => {
|
|
54
|
+
let p = launchSpecCache.get(connector);
|
|
55
|
+
if (!p) {
|
|
56
|
+
p = connector.getLaunchSpec();
|
|
57
|
+
launchSpecCache.set(connector, p);
|
|
58
|
+
}
|
|
59
|
+
return p;
|
|
60
|
+
};
|
|
61
|
+
// 1. Сбор client-level проверок только для установленных клиентов.
|
|
62
|
+
// Порядок — как в registry.getAll() (стабильный).
|
|
63
|
+
const installed = await registry.findInstalled();
|
|
64
|
+
const installedNames = new Set(installed.map((c) => c.getClientInfo().name));
|
|
65
|
+
const clientChecks = [];
|
|
66
|
+
for (const connector of registry.getAll()) {
|
|
67
|
+
if (!installedNames.has(connector.getClientInfo().name))
|
|
68
|
+
continue;
|
|
69
|
+
clientChecks.push(...buildClientChecks(connector, getCachedLaunchSpec));
|
|
70
|
+
}
|
|
71
|
+
const allChecks = [...clientChecks, ...extraChecks];
|
|
72
|
+
// 2. Параллельное выполнение всех проверок через Promise.allSettled.
|
|
73
|
+
// Исключения внутри check.run() трактуем как fail (см. JSDoc).
|
|
74
|
+
const settled = await Promise.allSettled(allChecks.map((c) => safeRun(c)));
|
|
75
|
+
// 3. Сбор результатов в детерминированном порядке.
|
|
76
|
+
const checks = allChecks.map((check, idx) => {
|
|
77
|
+
const outcome = settled[idx];
|
|
78
|
+
const result = settledToResult(outcome);
|
|
79
|
+
return { check, result };
|
|
80
|
+
});
|
|
81
|
+
const report = buildReport(checks);
|
|
82
|
+
// 4. Рендер после завершения всех проверок.
|
|
83
|
+
renderReport(report);
|
|
84
|
+
return report;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Собрать стандартные client-level проверки для одного коннектора.
|
|
88
|
+
*/
|
|
89
|
+
function buildClientChecks(connector, getCachedLaunchSpec) {
|
|
90
|
+
const group = connector.getClientInfo().displayName;
|
|
91
|
+
return [
|
|
92
|
+
buildIsInstalledCheck(connector, group),
|
|
93
|
+
buildGetStatusCheck(connector, group),
|
|
94
|
+
buildCommandExistsCheck(connector, group, getCachedLaunchSpec),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
function buildIsInstalledCheck(connector, group) {
|
|
98
|
+
return {
|
|
99
|
+
name: 'isInstalled',
|
|
100
|
+
description: 'Проверка установки клиента в системе',
|
|
101
|
+
group,
|
|
102
|
+
run: async () => {
|
|
103
|
+
// findInstalled() уже отфильтровал по этому критерию, но для полноты
|
|
104
|
+
// явно отмечаем как ok.
|
|
105
|
+
const ok = await connector.isInstalled();
|
|
106
|
+
return ok
|
|
107
|
+
? { status: 'ok', message: 'Клиент установлен' }
|
|
108
|
+
: { status: 'fail', message: 'Клиент не установлен' };
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function buildGetStatusCheck(connector, group) {
|
|
113
|
+
return {
|
|
114
|
+
name: 'getStatus',
|
|
115
|
+
description: 'Запрос статуса подключения через коннектор',
|
|
116
|
+
group,
|
|
117
|
+
run: async () => {
|
|
118
|
+
const status = await connector.getStatus();
|
|
119
|
+
return interpretConnectionStatus(status);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function buildCommandExistsCheck(connector, group, getCachedLaunchSpec) {
|
|
124
|
+
return {
|
|
125
|
+
name: 'command-exists',
|
|
126
|
+
description: 'Существование исполняемого файла из конфигурации клиента',
|
|
127
|
+
group,
|
|
128
|
+
run: () => checkCommandExistsOnDisk(connector, getCachedLaunchSpec),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function interpretConnectionStatus(status) {
|
|
132
|
+
if (status.connected && !status.error) {
|
|
133
|
+
return { status: 'ok', message: 'Сервер подключен и отвечает' };
|
|
134
|
+
}
|
|
135
|
+
if (status.connected && status.error) {
|
|
136
|
+
return {
|
|
137
|
+
status: 'warn',
|
|
138
|
+
message: `Подключен, но с предупреждением: ${status.error}`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (status.error) {
|
|
142
|
+
return { status: 'fail', message: status.error };
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
status: 'warn',
|
|
146
|
+
message: 'Сервер не подключен к этому клиенту',
|
|
147
|
+
hint: 'Запустите команду `connect`, чтобы подключить сервер.',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Проверка существования `command` (или скрипта при `command === 'node'`)
|
|
152
|
+
* на диске. Использует {@link resolveExecutablePath} — ту же логику, что и
|
|
153
|
+
* `BaseConnector.validateLaunchSpec`/`ConfigurableConnector.commandExistsOnDisk`.
|
|
154
|
+
*
|
|
155
|
+
* Кейсы:
|
|
156
|
+
* - `spec === null` (сервер не подключен) → `skip`.
|
|
157
|
+
* - Команда — `npx`/`pipx`/`uvx` или относительная — `warn`.
|
|
158
|
+
* - Команда — абсолютный путь или `node` с абсолютным скриптом — `fs.access(R_OK)`.
|
|
159
|
+
*/
|
|
160
|
+
async function checkCommandExistsOnDisk(connector, getCachedLaunchSpec) {
|
|
161
|
+
const spec = await getCachedLaunchSpec(connector);
|
|
162
|
+
if (spec === null) {
|
|
163
|
+
return {
|
|
164
|
+
status: 'skip',
|
|
165
|
+
message: 'Сервер не подключен к этому клиенту',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const filePath = resolveExecutablePath(spec);
|
|
169
|
+
if (filePath === null) {
|
|
170
|
+
// относительная команда или `node` без скрипта в args
|
|
171
|
+
if (path.isAbsolute(spec.command)) {
|
|
172
|
+
// не должно случиться, resolveExecutablePath обрабатывает абсолютные.
|
|
173
|
+
return {
|
|
174
|
+
status: 'fail',
|
|
175
|
+
message: `Не удалось определить путь к исполняемому файлу для команды: ${spec.command}`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
status: 'warn',
|
|
180
|
+
message: `Команда '${spec.command}' разрешается через PATH; не можем проверить на диске`,
|
|
181
|
+
hint: 'Работа зависит от текущего PATH клиента. Рекомендуется указывать абсолютный путь.',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
await fs.access(filePath, fs.constants.R_OK);
|
|
186
|
+
return {
|
|
187
|
+
status: 'ok',
|
|
188
|
+
message: `Файл найден и читаем: ${filePath}`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return {
|
|
193
|
+
status: 'fail',
|
|
194
|
+
message: `Файл не найден или недоступен: ${filePath}`,
|
|
195
|
+
hint: 'Переподключите сервер командой `connect` (путь к бандлу мог измениться при обновлении пакета).',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Безопасно запустить проверку, перехватывая исключения.
|
|
201
|
+
*/
|
|
202
|
+
async function safeRun(check) {
|
|
203
|
+
try {
|
|
204
|
+
return await check.run();
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
return {
|
|
208
|
+
status: 'fail',
|
|
209
|
+
message: `Исключение при выполнении проверки: ${stringifyError(err)}`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function stringifyError(err) {
|
|
214
|
+
if (err instanceof Error)
|
|
215
|
+
return err.message;
|
|
216
|
+
return String(err);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Преобразование одного исхода `Promise.allSettled` в `DoctorCheckResult`.
|
|
220
|
+
* `safeRun` уже перехватывает исключения внутри проверки, поэтому ветка
|
|
221
|
+
* `rejected` — defence-in-depth (например, если safeRun сам кинет).
|
|
222
|
+
*/
|
|
223
|
+
function settledToResult(outcome) {
|
|
224
|
+
if (!outcome) {
|
|
225
|
+
return {
|
|
226
|
+
status: 'fail',
|
|
227
|
+
message: 'Внутренняя ошибка: отсутствует результат проверки',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
if (outcome.status === 'fulfilled') {
|
|
231
|
+
return outcome.value;
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
status: 'fail',
|
|
235
|
+
message: `Исключение при выполнении проверки: ${stringifyError(outcome.reason)}`,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function buildReport(checks) {
|
|
239
|
+
const summary = { ok: 0, warn: 0, fail: 0, skip: 0 };
|
|
240
|
+
for (const { result } of checks) {
|
|
241
|
+
summary[result.status] += 1;
|
|
242
|
+
}
|
|
243
|
+
return { checks, summary };
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Отрендерить отчёт через {@link Logger}. Группирует проверки по `group`
|
|
247
|
+
* (если задано) в порядке появления группы.
|
|
248
|
+
*/
|
|
249
|
+
function renderReport(report) {
|
|
250
|
+
// Группировка с сохранением порядка появления group.
|
|
251
|
+
const groups = new Map();
|
|
252
|
+
for (const item of report.checks) {
|
|
253
|
+
const group = item.check.group ?? UNGROUPED_GROUP_KEY;
|
|
254
|
+
let arr = groups.get(group);
|
|
255
|
+
if (!arr) {
|
|
256
|
+
arr = [];
|
|
257
|
+
groups.set(group, arr);
|
|
258
|
+
}
|
|
259
|
+
arr.push(item);
|
|
260
|
+
}
|
|
261
|
+
for (const [group, items] of groups) {
|
|
262
|
+
if (group !== UNGROUPED_GROUP_KEY) {
|
|
263
|
+
Logger.info(`\nГруппа: ${group}`);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
Logger.info(`\n${UNGROUPED_GROUP_LABEL}:`);
|
|
267
|
+
}
|
|
268
|
+
for (const { check, result } of items) {
|
|
269
|
+
renderCheck(check, result);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
Logger.newLine();
|
|
273
|
+
const { ok, warn, fail, skip } = report.summary;
|
|
274
|
+
const summaryLine = `Сводка: ${ok} ok · ${warn} warn · ${fail} fail · ${skip} skip`;
|
|
275
|
+
if (fail > 0) {
|
|
276
|
+
Logger.error(summaryLine);
|
|
277
|
+
}
|
|
278
|
+
else if (warn > 0) {
|
|
279
|
+
Logger.warn(summaryLine);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
Logger.success(summaryLine);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
function renderCheck(check, result) {
|
|
286
|
+
renderCheckLine(check, result);
|
|
287
|
+
renderCheckDetails(result);
|
|
288
|
+
renderCheckHint(result);
|
|
289
|
+
}
|
|
290
|
+
function renderCheckLine(check, result) {
|
|
291
|
+
const message = `[${check.name}] ${result.message}`;
|
|
292
|
+
switch (result.status) {
|
|
293
|
+
case 'ok':
|
|
294
|
+
Logger.success(message);
|
|
295
|
+
return;
|
|
296
|
+
case 'warn':
|
|
297
|
+
Logger.warn(message);
|
|
298
|
+
return;
|
|
299
|
+
case 'fail':
|
|
300
|
+
Logger.error(message);
|
|
301
|
+
return;
|
|
302
|
+
case 'skip':
|
|
303
|
+
Logger.info(`${message} (пропущено)`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function renderCheckDetails(result) {
|
|
308
|
+
if (!result.details || result.details.length === 0)
|
|
309
|
+
return;
|
|
310
|
+
for (const line of result.details) {
|
|
311
|
+
Logger.info(` ${line}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function renderCheckHint(result) {
|
|
315
|
+
if (!result.hint)
|
|
316
|
+
return;
|
|
317
|
+
if (result.status !== 'warn' && result.status !== 'fail')
|
|
318
|
+
return;
|
|
319
|
+
Logger.info(` Подсказка: ${result.hint}`);
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=doctor.command.js.map
|