@apify/mcpc 0.1.11-beta.2 → 0.1.11-beta.4
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 +70 -1
- package/README.md +188 -190
- package/dist/bridge/index.js +190 -13
- package/dist/bridge/index.js.map +1 -1
- package/dist/cli/commands/auth.d.ts +2 -0
- package/dist/cli/commands/auth.d.ts.map +1 -1
- package/dist/cli/commands/auth.js +11 -1
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/logging.d.ts.map +1 -1
- package/dist/cli/commands/logging.js +1 -4
- package/dist/cli/commands/logging.js.map +1 -1
- package/dist/cli/commands/sessions.d.ts +3 -1
- package/dist/cli/commands/sessions.d.ts.map +1 -1
- package/dist/cli/commands/sessions.js +47 -8
- package/dist/cli/commands/sessions.js.map +1 -1
- package/dist/cli/commands/tasks.d.ts +5 -0
- package/dist/cli/commands/tasks.d.ts.map +1 -0
- package/dist/cli/commands/tasks.js +53 -0
- package/dist/cli/commands/tasks.js.map +1 -0
- package/dist/cli/commands/tools.d.ts +2 -0
- package/dist/cli/commands/tools.d.ts.map +1 -1
- package/dist/cli/commands/tools.js +88 -2
- package/dist/cli/commands/tools.js.map +1 -1
- package/dist/cli/helpers.d.ts +1 -5
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/helpers.js +25 -169
- package/dist/cli/helpers.js.map +1 -1
- package/dist/cli/index.js +426 -200
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/output.d.ts +3 -1
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +71 -3
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/parser.d.ts +11 -8
- package/dist/cli/parser.d.ts.map +1 -1
- package/dist/cli/parser.js +91 -65
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/shell.d.ts.map +1 -1
- package/dist/cli/shell.js +30 -3
- package/dist/cli/shell.js.map +1 -1
- package/dist/core/mcp-client.d.ts +10 -2
- package/dist/core/mcp-client.d.ts.map +1 -1
- package/dist/core/mcp-client.js +165 -0
- package/dist/core/mcp-client.js.map +1 -1
- package/dist/lib/auth/keychain.d.ts.map +1 -1
- package/dist/lib/auth/keychain.js +15 -8
- package/dist/lib/auth/keychain.js.map +1 -1
- package/dist/lib/auth/oauth-flow.d.ts +4 -1
- package/dist/lib/auth/oauth-flow.d.ts.map +1 -1
- package/dist/lib/auth/oauth-flow.js +21 -6
- package/dist/lib/auth/oauth-flow.js.map +1 -1
- package/dist/lib/auth/oauth-provider.d.ts +5 -0
- package/dist/lib/auth/oauth-provider.d.ts.map +1 -1
- package/dist/lib/auth/oauth-provider.js +16 -1
- package/dist/lib/auth/oauth-provider.js.map +1 -1
- package/dist/lib/bridge-client.d.ts +1 -1
- package/dist/lib/bridge-client.d.ts.map +1 -1
- package/dist/lib/bridge-client.js +17 -3
- package/dist/lib/bridge-client.js.map +1 -1
- package/dist/lib/bridge-manager.d.ts +1 -0
- package/dist/lib/bridge-manager.d.ts.map +1 -1
- package/dist/lib/bridge-manager.js +39 -17
- package/dist/lib/bridge-manager.js.map +1 -1
- package/dist/lib/errors.js +2 -2
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/file-lock.d.ts.map +1 -1
- package/dist/lib/file-lock.js +4 -2
- package/dist/lib/file-lock.js.map +1 -1
- package/dist/lib/session-client.d.ts +12 -2
- package/dist/lib/session-client.d.ts.map +1 -1
- package/dist/lib/session-client.js +113 -14
- package/dist/lib/session-client.js.map +1 -1
- package/dist/lib/sessions.d.ts.map +1 -1
- package/dist/lib/sessions.js +9 -4
- package/dist/lib/sessions.js.map +1 -1
- package/dist/lib/types.d.ts +30 -5
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/utils.d.ts +0 -2
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +1 -19
- package/dist/lib/utils.js.map +1 -1
- package/docs/TODOs.md +89 -35
- package/package.json +3 -2
- package/renovate.json +2 -1
package/dist/cli/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { EnvHttpProxyAgent, setGlobalDispatcher } from 'undici';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { setVerbose, setJsonMode, closeFileLogger } from '../lib/index.js';
|
|
5
|
-
import { isMcpError, formatHumanError,
|
|
5
|
+
import { isMcpError, formatHumanError, ClientError } from '../lib/index.js';
|
|
6
6
|
import { formatJson, formatJsonError, rainbow } from './output.js';
|
|
7
7
|
import * as tools from './commands/tools.js';
|
|
8
8
|
import * as resources from './commands/resources.js';
|
|
@@ -11,12 +11,16 @@ import * as sessions from './commands/sessions.js';
|
|
|
11
11
|
import * as logging from './commands/logging.js';
|
|
12
12
|
import * as utilities from './commands/utilities.js';
|
|
13
13
|
import * as auth from './commands/auth.js';
|
|
14
|
+
import * as tasks from './commands/tasks.js';
|
|
14
15
|
import { handleX402Command } from './commands/x402.js';
|
|
15
16
|
import { clean } from './commands/clean.js';
|
|
16
|
-
import {
|
|
17
|
+
import { extractOptions, getVerboseFromEnv, getJsonFromEnv, validateOptions, validateArgValues, parseServerArg, hasSubcommand, optionTakesValue, KNOWN_COMMANDS, KNOWN_SESSION_COMMANDS, } from './parser.js';
|
|
17
18
|
import { createRequire } from 'module';
|
|
18
19
|
const { version: mcpcVersion } = createRequire(import.meta.url)('../../package.json');
|
|
19
|
-
|
|
20
|
+
{
|
|
21
|
+
const insecure = process.argv.includes('--insecure');
|
|
22
|
+
setGlobalDispatcher(new EnvHttpProxyAgent(insecure ? { connect: { rejectUnauthorized: false } } : {}));
|
|
23
|
+
}
|
|
20
24
|
function getOptionsFromCommand(command) {
|
|
21
25
|
const opts = command.optsWithGlobals ? command.optsWithGlobals() : command.opts();
|
|
22
26
|
const verbose = opts.verbose || getVerboseFromEnv();
|
|
@@ -28,25 +32,31 @@ function getOptionsFromCommand(command) {
|
|
|
28
32
|
const options = {
|
|
29
33
|
outputMode: (json ? 'json' : 'human'),
|
|
30
34
|
};
|
|
31
|
-
if (opts.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
if (opts.timeout) {
|
|
36
|
+
const timeout = parseInt(opts.timeout, 10);
|
|
37
|
+
if (isNaN(timeout) || timeout <= 0) {
|
|
38
|
+
throw new Error(`Invalid --timeout value: "${opts.timeout}". Must be a positive number (seconds).`);
|
|
39
|
+
}
|
|
40
|
+
options.timeout = timeout;
|
|
41
|
+
}
|
|
42
|
+
if (opts.profile === false) {
|
|
43
|
+
options.noProfile = true;
|
|
35
44
|
}
|
|
36
|
-
if (opts.
|
|
37
|
-
options.timeout = parseInt(opts.timeout, 10);
|
|
38
|
-
if (opts.profile)
|
|
45
|
+
else if (opts.profile) {
|
|
39
46
|
options.profile = opts.profile;
|
|
47
|
+
}
|
|
40
48
|
if (verbose)
|
|
41
49
|
options.verbose = verbose;
|
|
42
50
|
if (opts.x402)
|
|
43
51
|
options.x402 = true;
|
|
52
|
+
if (opts.insecure)
|
|
53
|
+
options.insecure = true;
|
|
44
54
|
if (opts.schema)
|
|
45
55
|
options.schema = opts.schema;
|
|
46
56
|
if (opts.schemaMode) {
|
|
47
57
|
const mode = opts.schemaMode;
|
|
48
58
|
if (mode !== 'strict' && mode !== 'compatible' && mode !== 'ignore') {
|
|
49
|
-
throw new Error(`Invalid schema
|
|
59
|
+
throw new Error(`Invalid --schema-mode value: "${mode}". Valid modes are: strict, compatible, ignore`);
|
|
50
60
|
}
|
|
51
61
|
options.schemaMode = mode;
|
|
52
62
|
}
|
|
@@ -78,7 +88,7 @@ async function main() {
|
|
|
78
88
|
return;
|
|
79
89
|
}
|
|
80
90
|
if (args.includes('--help') || args.includes('-h')) {
|
|
81
|
-
const program =
|
|
91
|
+
const program = createTopLevelProgram();
|
|
82
92
|
await program.parseAsync(process.argv);
|
|
83
93
|
return;
|
|
84
94
|
}
|
|
@@ -90,34 +100,23 @@ async function main() {
|
|
|
90
100
|
console.error(formatHumanError(error, false));
|
|
91
101
|
process.exit(1);
|
|
92
102
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
console.error(formatHumanError(error, false));
|
|
107
|
-
process.exit(1);
|
|
103
|
+
let firstNonOption;
|
|
104
|
+
let firstNonOptionIndex = -1;
|
|
105
|
+
for (let i = 0; i < args.length; i++) {
|
|
106
|
+
const arg = args[i];
|
|
107
|
+
if (!arg)
|
|
108
|
+
continue;
|
|
109
|
+
if (arg.startsWith('-')) {
|
|
110
|
+
if (optionTakesValue(arg) && !arg.includes('=') && i + 1 < args.length) {
|
|
111
|
+
i++;
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
108
114
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
profiles: cleanTypes.includes('profiles'),
|
|
113
|
-
logs: cleanTypes.includes('logs'),
|
|
114
|
-
all: cleanTypes.includes('all'),
|
|
115
|
-
});
|
|
116
|
-
await closeFileLogger();
|
|
117
|
-
return;
|
|
115
|
+
firstNonOption = arg;
|
|
116
|
+
firstNonOptionIndex = i;
|
|
117
|
+
break;
|
|
118
118
|
}
|
|
119
|
-
|
|
120
|
-
if (!targetInfo) {
|
|
119
|
+
if (!firstNonOption) {
|
|
121
120
|
const { json } = extractOptions(args);
|
|
122
121
|
if (json)
|
|
123
122
|
setJsonMode(true);
|
|
@@ -128,240 +127,421 @@ async function main() {
|
|
|
128
127
|
await closeFileLogger();
|
|
129
128
|
return;
|
|
130
129
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
130
|
+
if (firstNonOption.startsWith('@')) {
|
|
131
|
+
const session = firstNonOption;
|
|
132
|
+
const modifiedArgs = [
|
|
133
|
+
...process.argv.slice(0, 2),
|
|
134
|
+
...args.slice(0, firstNonOptionIndex),
|
|
135
|
+
...args.slice(firstNonOptionIndex + 1),
|
|
136
|
+
];
|
|
137
|
+
try {
|
|
138
|
+
await handleSessionCommands(session, modifiedArgs);
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (isMcpError(error)) {
|
|
142
|
+
const opts = extractOptions(args);
|
|
143
|
+
const outputMode = opts.json ? 'json' : 'human';
|
|
144
|
+
if (outputMode === 'json') {
|
|
145
|
+
console.error(formatJsonError(error, error.code));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
console.error(formatHumanError(error, opts.verbose));
|
|
149
|
+
}
|
|
150
|
+
process.exit(error.code);
|
|
152
151
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
await closeFileLogger();
|
|
156
|
+
}
|
|
157
|
+
await flushStdout();
|
|
158
|
+
process.exit(0);
|
|
159
|
+
}
|
|
160
|
+
if (KNOWN_COMMANDS.includes(firstNonOption)) {
|
|
161
|
+
if (firstNonOption === 'x402') {
|
|
162
|
+
const x402Args = args.slice(firstNonOptionIndex + 1);
|
|
163
|
+
await handleX402Command(x402Args);
|
|
164
|
+
await closeFileLogger();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
const program = createTopLevelProgram();
|
|
169
|
+
await program.parseAsync(process.argv);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (isMcpError(error)) {
|
|
173
|
+
const opts = extractOptions(args);
|
|
174
|
+
const outputMode = opts.json ? 'json' : 'human';
|
|
175
|
+
if (outputMode === 'json') {
|
|
176
|
+
console.error(formatJsonError(error, error.code));
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
console.error(formatHumanError(error, opts.verbose));
|
|
159
180
|
}
|
|
181
|
+
process.exit(error.code);
|
|
160
182
|
}
|
|
161
|
-
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
await closeFileLogger();
|
|
162
187
|
}
|
|
163
|
-
|
|
188
|
+
return;
|
|
164
189
|
}
|
|
165
|
-
|
|
166
|
-
|
|
190
|
+
const opts = extractOptions(args);
|
|
191
|
+
const outputMode = opts.json ? 'json' : 'human';
|
|
192
|
+
const allCommands = [...KNOWN_COMMANDS, ...KNOWN_SESSION_COMMANDS];
|
|
193
|
+
if (allCommands.includes(firstNonOption)) {
|
|
194
|
+
if (outputMode === 'json') {
|
|
195
|
+
console.error(formatJsonError(new Error(`Missing session target for command: ${firstNonOption}`), 1));
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
console.error(`Error: Missing session target for command: ${firstNonOption}`);
|
|
199
|
+
console.error(`\nDid you mean: mcpc <@session> ${firstNonOption}`);
|
|
200
|
+
console.error(`Run "mcpc --help" for usage information.\n`);
|
|
201
|
+
}
|
|
167
202
|
}
|
|
168
|
-
|
|
169
|
-
if (
|
|
170
|
-
|
|
203
|
+
else {
|
|
204
|
+
if (outputMode === 'json') {
|
|
205
|
+
console.error(formatJsonError(new Error(`Unknown command: ${firstNonOption}`), 1));
|
|
171
206
|
}
|
|
172
207
|
else {
|
|
173
|
-
|
|
174
|
-
|
|
208
|
+
console.error(`Error: Unknown command: ${firstNonOption}`);
|
|
209
|
+
console.error(`Run "mcpc --help" for usage information.\n`);
|
|
175
210
|
}
|
|
176
|
-
}
|
|
177
|
-
|
|
211
|
+
}
|
|
212
|
+
await closeFileLogger();
|
|
213
|
+
process.exit(1);
|
|
178
214
|
}
|
|
179
|
-
function
|
|
215
|
+
function createTopLevelProgram() {
|
|
180
216
|
const program = new Command();
|
|
181
217
|
program.configureOutput({
|
|
182
218
|
outputError: (str, write) => write(str),
|
|
183
219
|
getOutHelpWidth: () => 100,
|
|
184
220
|
getErrHelpWidth: () => 100,
|
|
185
221
|
});
|
|
222
|
+
program.configureHelp({
|
|
223
|
+
subcommandTerm: (cmd) => `${cmd.name()} ${cmd.usage()}`.replace(/^\[options\]\s*|\s*\[options\]/g, '').trim(),
|
|
224
|
+
});
|
|
225
|
+
const docsUrl = process.stdout.isTTY
|
|
226
|
+
? `https://github.com/apify/mcpc/tree/v${mcpcVersion}`
|
|
227
|
+
: `https://raw.githubusercontent.com/apify/mcpc/v${mcpcVersion}/README.md`;
|
|
186
228
|
program
|
|
187
229
|
.name('mcpc')
|
|
188
230
|
.description(`${rainbow('Universal')} command-line client for the Model Context Protocol (MCP).`)
|
|
189
|
-
.usage('[options]
|
|
190
|
-
.helpOption('-h, --help', 'Display general help')
|
|
231
|
+
.usage('[options] [<@session>] [<command>]')
|
|
191
232
|
.option('-j, --json', 'Output in JSON format for scripting')
|
|
192
|
-
.option('-c, --config <file>', 'Path to MCP config JSON file (e.g. ".vscode/mcp.json")')
|
|
193
|
-
.option('-H, --header <header>', 'HTTP header for remote MCP server (can be repeated)')
|
|
194
|
-
.version(mcpcVersion, '-v, --version', 'Output the version number')
|
|
195
233
|
.option('--verbose', 'Enable debug logging')
|
|
196
234
|
.option('--profile <name>', 'OAuth profile for the server ("default" if not provided)')
|
|
197
235
|
.option('--schema <file>', 'Validate tool/prompt schema against expected schema')
|
|
198
236
|
.option('--schema-mode <mode>', 'Schema validation mode: strict, compatible (default), ignore')
|
|
199
237
|
.option('--timeout <seconds>', 'Request timeout in seconds (default: 300)')
|
|
200
|
-
.option('--
|
|
201
|
-
.
|
|
202
|
-
.
|
|
203
|
-
.option('--clean[=types]', 'Clean up mcpc data (types: sessions, logs, profiles, all)');
|
|
204
|
-
const docsUrl = process.stdout.isTTY
|
|
205
|
-
? `https://github.com/apify/mcpc/tree/v${mcpcVersion}`
|
|
206
|
-
: `https://raw.githubusercontent.com/apify/mcpc/v${mcpcVersion}/README.md`;
|
|
238
|
+
.option('--insecure', 'Skip TLS certificate verification (for self-signed certs)')
|
|
239
|
+
.version(mcpcVersion, '-v, --version', 'Output the version number')
|
|
240
|
+
.helpOption('-h, --help', 'Display help');
|
|
207
241
|
program.addHelpText('after', `
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
242
|
+
MCP session commands (after connecting):
|
|
243
|
+
<@session> Show MCP server info and capabilities
|
|
244
|
+
<@session> tools-list List MCP tools
|
|
245
|
+
<@session> tools-get <name>
|
|
246
|
+
<@session> tools-call <name> [arg:=val ... | <json> | <stdin]
|
|
247
|
+
<@session> prompts-list
|
|
248
|
+
<@session> prompts-get <name> [arg:=val ... | <json> | <stdin]
|
|
249
|
+
<@session> resources-list
|
|
250
|
+
<@session> resources-read <uri>
|
|
251
|
+
<@session> resources-subscribe <uri>
|
|
252
|
+
<@session> resources-unsubscribe <uri>
|
|
253
|
+
<@session> resources-templates-list
|
|
254
|
+
<@session> tasks-list
|
|
255
|
+
<@session> tasks-get <taskId>
|
|
256
|
+
<@session> tasks-cancel <taskId>
|
|
257
|
+
<@session> logging-set-level <level>
|
|
258
|
+
<@session> ping
|
|
212
259
|
|
|
213
|
-
|
|
214
|
-
login Create OAuth profile with credentials for remote server
|
|
215
|
-
logout Remove OAuth profile for remote server
|
|
216
|
-
connect @<session> Connect to server and create named persistent session
|
|
217
|
-
restart Kill and restart a session
|
|
218
|
-
close Close a session
|
|
219
|
-
|
|
220
|
-
MCP server commands:
|
|
221
|
-
help Show server info ("help" can be omitted)
|
|
222
|
-
shell Open interactive shell
|
|
223
|
-
tools-list [--full] Send "tools/list" MCP request...
|
|
224
|
-
tools-get <tool-name>
|
|
225
|
-
tools-call <tool-name> [arg1:=val1 arg2:=val2 ... | <args-json> | <stdin]
|
|
226
|
-
prompts-list
|
|
227
|
-
prompts-get <prompt-name> [arg1:=val1 arg2:=val2 ... | <args-json> | <stdin]
|
|
228
|
-
resources
|
|
229
|
-
resources-list
|
|
230
|
-
resources-read <uri>
|
|
231
|
-
resources-subscribe <uri>
|
|
232
|
-
resources-unsubscribe <uri>
|
|
233
|
-
resources-templates-list
|
|
234
|
-
logging-set-level <level>
|
|
235
|
-
ping
|
|
236
|
-
|
|
237
|
-
x402 payment commands (no target needed):
|
|
238
|
-
x402 init Create a new x402 wallet
|
|
239
|
-
x402 import <key> Import wallet from private key
|
|
240
|
-
x402 info Show wallet info
|
|
241
|
-
x402 sign -r <base64> Sign payment from PAYMENT-REQUIRED header
|
|
242
|
-
x402 remove Remove the wallet
|
|
243
|
-
|
|
244
|
-
Run "mcpc" without <target> to show available sessions and profiles.
|
|
260
|
+
Run "mcpc" without arguments to show active sessions and OAuth profiles.
|
|
245
261
|
|
|
246
262
|
Full docs: ${docsUrl}`);
|
|
263
|
+
program
|
|
264
|
+
.command('connect [server] [@session]')
|
|
265
|
+
.usage('<server> <@session>')
|
|
266
|
+
.description('Connect to an MCP server and start a new named @session')
|
|
267
|
+
.option('-H, --header <header>', 'HTTP header (can be repeated)')
|
|
268
|
+
.option('--profile <name>', 'OAuth profile to use ("default" if skipped)')
|
|
269
|
+
.option('--no-profile', 'Skip OAuth profile (connect anonymously)')
|
|
270
|
+
.option('--proxy <[host:]port>', 'Start proxy MCP server for session')
|
|
271
|
+
.option('--proxy-bearer-token <token>', 'Require authentication for access to proxy server')
|
|
272
|
+
.option('--x402', 'Enable x402 auto-payment using the configured wallet')
|
|
273
|
+
.addHelpText('after', `
|
|
274
|
+
Server formats:
|
|
275
|
+
mcp.apify.com Remote HTTP server (https:// added automatically)
|
|
276
|
+
~/.vscode/mcp.json:puppeteer Config file entry (file:entry)
|
|
277
|
+
`)
|
|
278
|
+
.action(async (server, sessionName, opts, command) => {
|
|
279
|
+
if (!server) {
|
|
280
|
+
throw new ClientError('Missing required argument: server\n\nExample: mcpc connect mcp.apify.com @myapp');
|
|
281
|
+
}
|
|
282
|
+
if (!sessionName) {
|
|
283
|
+
throw new ClientError('Missing required argument: @session\n\nExample: mcpc connect mcp.apify.com @myapp');
|
|
284
|
+
}
|
|
285
|
+
const globalOpts = getOptionsFromCommand(command);
|
|
286
|
+
const parsed = parseServerArg(server);
|
|
287
|
+
const headers = opts.header
|
|
288
|
+
? Array.isArray(opts.header)
|
|
289
|
+
? opts.header
|
|
290
|
+
: [opts.header]
|
|
291
|
+
: undefined;
|
|
292
|
+
if (!parsed) {
|
|
293
|
+
throw new ClientError(`Invalid server: "${server}"\n\n` +
|
|
294
|
+
`Expected a URL (e.g. mcp.apify.com) or a config file entry (e.g. ~/.vscode/mcp.json:filesystem)`);
|
|
295
|
+
}
|
|
296
|
+
if (parsed.type === 'config') {
|
|
297
|
+
await sessions.connectSession(parsed.entry, sessionName, {
|
|
298
|
+
...globalOpts,
|
|
299
|
+
...(headers && { headers }),
|
|
300
|
+
config: parsed.file,
|
|
301
|
+
proxy: opts.proxy,
|
|
302
|
+
proxyBearerToken: opts.proxyBearerToken,
|
|
303
|
+
x402: opts.x402,
|
|
304
|
+
...(globalOpts.insecure && { insecure: true }),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
await sessions.connectSession(server, sessionName, {
|
|
309
|
+
...globalOpts,
|
|
310
|
+
...(headers && { headers }),
|
|
311
|
+
proxy: opts.proxy,
|
|
312
|
+
proxyBearerToken: opts.proxyBearerToken,
|
|
313
|
+
x402: opts.x402,
|
|
314
|
+
...(globalOpts.insecure && { insecure: true }),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
program
|
|
319
|
+
.command('close [@session]')
|
|
320
|
+
.usage('<@session>')
|
|
321
|
+
.description('Close a session')
|
|
322
|
+
.action(async (sessionName, _opts, command) => {
|
|
323
|
+
if (!sessionName) {
|
|
324
|
+
throw new ClientError('Missing required argument: @session\n\nExample: mcpc close @myapp');
|
|
325
|
+
}
|
|
326
|
+
await sessions.closeSession(sessionName, getOptionsFromCommand(command));
|
|
327
|
+
});
|
|
328
|
+
program
|
|
329
|
+
.command('restart [@session]')
|
|
330
|
+
.usage('<@session>')
|
|
331
|
+
.description('Restart a session (losing all state)')
|
|
332
|
+
.action(async (sessionName, _opts, command) => {
|
|
333
|
+
if (!sessionName) {
|
|
334
|
+
throw new ClientError('Missing required argument: @session\n\nExample: mcpc restart @myapp');
|
|
335
|
+
}
|
|
336
|
+
await sessions.restartSession(sessionName, getOptionsFromCommand(command));
|
|
337
|
+
});
|
|
338
|
+
program
|
|
339
|
+
.command('shell [@session]')
|
|
340
|
+
.usage('<@session>')
|
|
341
|
+
.description('Open interactive shell for a session')
|
|
342
|
+
.action(async (sessionName) => {
|
|
343
|
+
if (!sessionName) {
|
|
344
|
+
throw new ClientError('Missing required argument: @session\n\nExample: mcpc shell @myapp');
|
|
345
|
+
}
|
|
346
|
+
await sessions.openShell(sessionName);
|
|
347
|
+
});
|
|
348
|
+
program
|
|
349
|
+
.command('login [server]')
|
|
350
|
+
.usage('<server>')
|
|
351
|
+
.description('Interactively login to a server using OAuth and save profile')
|
|
352
|
+
.option('--profile <name>', 'Profile name (default: "default")')
|
|
353
|
+
.option('--scope <scopes>', 'OAuth scopes to request, quoted and space-separated (e.g. --scope "read write")')
|
|
354
|
+
.option('--client-id <id>', 'OAuth client ID (for servers without dynamic client registration)')
|
|
355
|
+
.option('--client-secret <secret>', 'OAuth client secret (for servers without dynamic client registration)')
|
|
356
|
+
.action(async (server, opts, command) => {
|
|
357
|
+
if (!server) {
|
|
358
|
+
throw new ClientError('Missing required argument: server\n\nExample: mcpc login mcp.apify.com');
|
|
359
|
+
}
|
|
360
|
+
await auth.login(server, {
|
|
361
|
+
profile: opts.profile,
|
|
362
|
+
scope: opts.scope,
|
|
363
|
+
clientId: opts.clientId,
|
|
364
|
+
clientSecret: opts.clientSecret,
|
|
365
|
+
...getOptionsFromCommand(command),
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
program
|
|
369
|
+
.command('logout [server]')
|
|
370
|
+
.usage('<server>')
|
|
371
|
+
.description('Delete an authentication profile for a server')
|
|
372
|
+
.option('--profile <name>', 'Profile name (default: "default")')
|
|
373
|
+
.action(async (server, opts, command) => {
|
|
374
|
+
if (!server) {
|
|
375
|
+
throw new ClientError('Missing required argument: server\n\nExample: mcpc logout mcp.apify.com');
|
|
376
|
+
}
|
|
377
|
+
await auth.logout(server, {
|
|
378
|
+
profile: opts.profile,
|
|
379
|
+
...getOptionsFromCommand(command),
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
program
|
|
383
|
+
.command('clean [resources...]')
|
|
384
|
+
.description('Clean up mcpc data (sessions, profiles, logs, all)')
|
|
385
|
+
.addHelpText('after', `
|
|
386
|
+
Resources:
|
|
387
|
+
sessions Remove stale/crashed session records
|
|
388
|
+
profiles Remove authentication profiles
|
|
389
|
+
logs Remove bridge log files
|
|
390
|
+
all Remove all of the above
|
|
391
|
+
|
|
392
|
+
Without arguments, performs safe cleanup of stale data only.
|
|
393
|
+
`)
|
|
394
|
+
.action(async (resources, _opts, command) => {
|
|
395
|
+
const globalOpts = getOptionsFromCommand(command);
|
|
396
|
+
const VALID_CLEAN_TYPES = ['sessions', 'profiles', 'logs', 'all'];
|
|
397
|
+
for (const r of resources) {
|
|
398
|
+
if (!VALID_CLEAN_TYPES.includes(r)) {
|
|
399
|
+
throw new ClientError(`Invalid clean resource: "${r}". Valid resources are: ${VALID_CLEAN_TYPES.join(', ')}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
await clean({
|
|
403
|
+
outputMode: globalOpts.outputMode,
|
|
404
|
+
sessions: resources.includes('sessions'),
|
|
405
|
+
profiles: resources.includes('profiles'),
|
|
406
|
+
logs: resources.includes('logs'),
|
|
407
|
+
all: resources.includes('all'),
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
program
|
|
411
|
+
.command('x402 [subcommand] [args...]')
|
|
412
|
+
.description('Configure an x402 payment wallet (EXPERIMENTAL)')
|
|
413
|
+
.addHelpText('after', `
|
|
414
|
+
Subcommands:
|
|
415
|
+
init Create a new x402 wallet
|
|
416
|
+
import <key> Import wallet from private key
|
|
417
|
+
info Show wallet info
|
|
418
|
+
sign -r <b64> Sign payment from PAYMENT-REQUIRED header
|
|
419
|
+
remove Remove the wallet
|
|
420
|
+
`)
|
|
421
|
+
.action(() => { });
|
|
422
|
+
program
|
|
423
|
+
.command('help [command]')
|
|
424
|
+
.description('Show help for a specific command')
|
|
425
|
+
.action((cmdName) => {
|
|
426
|
+
if (!cmdName) {
|
|
427
|
+
program.outputHelp();
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const topLevelCmd = program.commands.find((c) => c.name() === cmdName || c.aliases().includes(cmdName));
|
|
431
|
+
if (topLevelCmd) {
|
|
432
|
+
topLevelCmd.outputHelp();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const dummyProgram = new Command();
|
|
436
|
+
registerSessionCommands(dummyProgram, '@dummy');
|
|
437
|
+
const sessionCmd = dummyProgram.commands.find((c) => c.name() === cmdName || c.aliases().includes(cmdName));
|
|
438
|
+
if (sessionCmd) {
|
|
439
|
+
sessionCmd.outputHelp();
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
console.error(`Unknown command: ${cmdName}`);
|
|
443
|
+
console.error(`Run "mcpc --help" for usage information.`);
|
|
444
|
+
process.exit(1);
|
|
445
|
+
});
|
|
446
|
+
program.action(async () => {
|
|
447
|
+
const opts = program.opts();
|
|
448
|
+
const json = opts.json || getJsonFromEnv();
|
|
449
|
+
if (json)
|
|
450
|
+
setJsonMode(true);
|
|
451
|
+
await sessions.listSessionsAndAuthProfiles({ outputMode: json ? 'json' : 'human' });
|
|
452
|
+
if (!json) {
|
|
453
|
+
console.log('\nRun "mcpc --help" for usage information.\n');
|
|
454
|
+
}
|
|
455
|
+
});
|
|
247
456
|
return program;
|
|
248
457
|
}
|
|
249
|
-
|
|
250
|
-
const program = createProgram();
|
|
251
|
-
program.argument('<target>', 'Target (session @name, MCP config entry, or server URL)');
|
|
252
|
-
if (!hasCommandAfterTarget(args)) {
|
|
253
|
-
const options = extractOptions(args);
|
|
254
|
-
if (options.verbose)
|
|
255
|
-
setVerbose(true);
|
|
256
|
-
if (options.json)
|
|
257
|
-
setJsonMode(true);
|
|
258
|
-
await sessions.showServerDetails(target, {
|
|
259
|
-
outputMode: options.json ? 'json' : 'human',
|
|
260
|
-
...(options.verbose && { verbose: true }),
|
|
261
|
-
...(options.config && { config: options.config }),
|
|
262
|
-
...(options.headers && { headers: options.headers }),
|
|
263
|
-
...(options.timeout !== undefined && { timeout: options.timeout }),
|
|
264
|
-
});
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
458
|
+
function registerSessionCommands(program, session) {
|
|
267
459
|
program
|
|
268
460
|
.command('help')
|
|
269
461
|
.description('Show server instructions and available capabilities')
|
|
270
462
|
.action(async (_options, command) => {
|
|
271
|
-
await sessions.showHelp(
|
|
463
|
+
await sessions.showHelp(session, getOptionsFromCommand(command));
|
|
272
464
|
});
|
|
273
465
|
program
|
|
274
466
|
.command('shell')
|
|
275
|
-
.description('Interactive shell for the
|
|
467
|
+
.description('Interactive shell for the session')
|
|
276
468
|
.action(async () => {
|
|
277
|
-
await sessions.openShell(
|
|
469
|
+
await sessions.openShell(session);
|
|
278
470
|
});
|
|
279
471
|
program
|
|
280
|
-
.command('close')
|
|
472
|
+
.command('close', { hidden: true })
|
|
281
473
|
.description('Close the session')
|
|
282
474
|
.action(async (_options, command) => {
|
|
283
|
-
await sessions.closeSession(
|
|
475
|
+
await sessions.closeSession(session, getOptionsFromCommand(command));
|
|
284
476
|
});
|
|
285
477
|
program
|
|
286
478
|
.command('restart')
|
|
287
479
|
.description('Restart the session (stop and start the bridge)')
|
|
288
480
|
.action(async (_options, command) => {
|
|
289
|
-
await sessions.restartSession(
|
|
290
|
-
});
|
|
291
|
-
program
|
|
292
|
-
.command('connect <name>')
|
|
293
|
-
.description('Create or reconnect a named session to an MCP server')
|
|
294
|
-
.action(async (name, _options, command) => {
|
|
295
|
-
const opts = command.optsWithGlobals();
|
|
296
|
-
await sessions.connectSession(name, target, {
|
|
297
|
-
...getOptionsFromCommand(command),
|
|
298
|
-
proxy: opts.proxy,
|
|
299
|
-
proxyBearerToken: opts.proxyBearerToken,
|
|
300
|
-
x402: opts.x402,
|
|
301
|
-
});
|
|
302
|
-
});
|
|
303
|
-
program
|
|
304
|
-
.command('login')
|
|
305
|
-
.description('Login to a server using OAuth and save authentication profile')
|
|
306
|
-
.option('--profile <name>', 'Profile name (default: default)')
|
|
307
|
-
.option('--scope <scope>', 'OAuth scope(s) to request')
|
|
308
|
-
.action(async (options, command) => {
|
|
309
|
-
await auth.login(target, {
|
|
310
|
-
profile: options.profile,
|
|
311
|
-
scope: options.scope,
|
|
312
|
-
...getOptionsFromCommand(command),
|
|
313
|
-
});
|
|
314
|
-
});
|
|
315
|
-
program
|
|
316
|
-
.command('logout')
|
|
317
|
-
.description('Delete an authentication profile')
|
|
318
|
-
.option('--profile <name>', 'Profile name (default: default)')
|
|
319
|
-
.action(async (options, command) => {
|
|
320
|
-
await auth.logout(target, {
|
|
321
|
-
profile: options.profile,
|
|
322
|
-
...getOptionsFromCommand(command),
|
|
323
|
-
});
|
|
481
|
+
await sessions.restartSession(session, getOptionsFromCommand(command));
|
|
324
482
|
});
|
|
325
483
|
program
|
|
326
484
|
.command('tools')
|
|
327
485
|
.description('List available tools (shorthand for tools-list)')
|
|
328
486
|
.option('--full', 'Show full tool details including complete input schema')
|
|
329
487
|
.action(async (_options, command) => {
|
|
330
|
-
await tools.listTools(
|
|
488
|
+
await tools.listTools(session, getOptionsFromCommand(command));
|
|
331
489
|
});
|
|
332
490
|
program
|
|
333
491
|
.command('tools-list')
|
|
334
492
|
.description('List available tools')
|
|
335
493
|
.option('--full', 'Show full tool details including complete input schema')
|
|
336
494
|
.action(async (_options, command) => {
|
|
337
|
-
await tools.listTools(
|
|
495
|
+
await tools.listTools(session, getOptionsFromCommand(command));
|
|
338
496
|
});
|
|
339
497
|
program
|
|
340
498
|
.command('tools-get <name>')
|
|
341
499
|
.description('Get information about a specific tool')
|
|
342
500
|
.action(async (name, _options, command) => {
|
|
343
|
-
await tools.getTool(
|
|
501
|
+
await tools.getTool(session, name, getOptionsFromCommand(command));
|
|
344
502
|
});
|
|
345
503
|
program
|
|
346
504
|
.command('tools-call <name> [args...]')
|
|
347
505
|
.description('Call a tool with arguments (key:=value pairs or JSON)')
|
|
348
|
-
.
|
|
349
|
-
|
|
506
|
+
.option('--async', 'Use async task execution (experimental)')
|
|
507
|
+
.option('--detach', 'Start async task and return immediately with task ID (implies --async)')
|
|
508
|
+
.action(async (name, args, options, command) => {
|
|
509
|
+
await tools.callTool(session, name, {
|
|
350
510
|
args,
|
|
511
|
+
async: options.async,
|
|
512
|
+
detach: options.detach,
|
|
351
513
|
...getOptionsFromCommand(command),
|
|
352
514
|
});
|
|
353
515
|
});
|
|
516
|
+
program
|
|
517
|
+
.command('tasks-list')
|
|
518
|
+
.description('List active tasks')
|
|
519
|
+
.action(async (_options, command) => {
|
|
520
|
+
await tasks.listTasks(session, getOptionsFromCommand(command));
|
|
521
|
+
});
|
|
522
|
+
program
|
|
523
|
+
.command('tasks-get <taskId>')
|
|
524
|
+
.description('Get status of a specific task')
|
|
525
|
+
.action(async (taskId, _options, command) => {
|
|
526
|
+
await tasks.getTask(session, taskId, getOptionsFromCommand(command));
|
|
527
|
+
});
|
|
528
|
+
program
|
|
529
|
+
.command('tasks-cancel <taskId>')
|
|
530
|
+
.description('Cancel a running task')
|
|
531
|
+
.action(async (taskId, _options, command) => {
|
|
532
|
+
await tasks.cancelTask(session, taskId, getOptionsFromCommand(command));
|
|
533
|
+
});
|
|
354
534
|
program
|
|
355
535
|
.command('resources')
|
|
356
536
|
.description('List available resources (shorthand for resources-list)')
|
|
357
537
|
.action(async (_options, command) => {
|
|
358
|
-
await resources.listResources(
|
|
538
|
+
await resources.listResources(session, getOptionsFromCommand(command));
|
|
359
539
|
});
|
|
360
540
|
program
|
|
361
541
|
.command('resources-list')
|
|
362
542
|
.description('List available resources')
|
|
363
543
|
.action(async (_options, command) => {
|
|
364
|
-
await resources.listResources(
|
|
544
|
+
await resources.listResources(session, getOptionsFromCommand(command));
|
|
365
545
|
});
|
|
366
546
|
program
|
|
367
547
|
.command('resources-read <uri>')
|
|
@@ -369,9 +549,8 @@ async function handleCommands(target, args) {
|
|
|
369
549
|
.option('-o, --output <file>', 'Write resource to file')
|
|
370
550
|
.option('--max-size <bytes>', 'Maximum resource size in bytes')
|
|
371
551
|
.action(async (uri, options, command) => {
|
|
372
|
-
await resources.getResource(
|
|
552
|
+
await resources.getResource(session, uri, {
|
|
373
553
|
output: options.output,
|
|
374
|
-
raw: options.raw,
|
|
375
554
|
maxSize: options.maxSize,
|
|
376
555
|
...getOptionsFromCommand(command),
|
|
377
556
|
});
|
|
@@ -380,37 +559,37 @@ async function handleCommands(target, args) {
|
|
|
380
559
|
.command('resources-subscribe <uri>')
|
|
381
560
|
.description('Subscribe to resource updates')
|
|
382
561
|
.action(async (uri, _options, command) => {
|
|
383
|
-
await resources.subscribeResource(
|
|
562
|
+
await resources.subscribeResource(session, uri, getOptionsFromCommand(command));
|
|
384
563
|
});
|
|
385
564
|
program
|
|
386
565
|
.command('resources-unsubscribe <uri>')
|
|
387
566
|
.description('Unsubscribe from resource updates')
|
|
388
567
|
.action(async (uri, _options, command) => {
|
|
389
|
-
await resources.unsubscribeResource(
|
|
568
|
+
await resources.unsubscribeResource(session, uri, getOptionsFromCommand(command));
|
|
390
569
|
});
|
|
391
570
|
program
|
|
392
571
|
.command('resources-templates-list')
|
|
393
572
|
.description('List available resource templates')
|
|
394
573
|
.action(async (_options, command) => {
|
|
395
|
-
await resources.listResourceTemplates(
|
|
574
|
+
await resources.listResourceTemplates(session, getOptionsFromCommand(command));
|
|
396
575
|
});
|
|
397
576
|
program
|
|
398
577
|
.command('prompts')
|
|
399
578
|
.description('List available prompts (shorthand for prompts-list)')
|
|
400
579
|
.action(async (_options, command) => {
|
|
401
|
-
await prompts.listPrompts(
|
|
580
|
+
await prompts.listPrompts(session, getOptionsFromCommand(command));
|
|
402
581
|
});
|
|
403
582
|
program
|
|
404
583
|
.command('prompts-list')
|
|
405
584
|
.description('List available prompts')
|
|
406
585
|
.action(async (_options, command) => {
|
|
407
|
-
await prompts.listPrompts(
|
|
586
|
+
await prompts.listPrompts(session, getOptionsFromCommand(command));
|
|
408
587
|
});
|
|
409
588
|
program
|
|
410
589
|
.command('prompts-get <name> [args...]')
|
|
411
590
|
.description('Get a prompt by name with arguments (key:=value pairs or JSON)')
|
|
412
591
|
.action(async (name, args, _options, command) => {
|
|
413
|
-
await prompts.getPrompt(
|
|
592
|
+
await prompts.getPrompt(session, name, {
|
|
414
593
|
args,
|
|
415
594
|
...getOptionsFromCommand(command),
|
|
416
595
|
});
|
|
@@ -419,14 +598,50 @@ async function handleCommands(target, args) {
|
|
|
419
598
|
.command('logging-set-level <level>')
|
|
420
599
|
.description('Set server logging level (debug, info, notice, warning, error, critical, alert, emergency)')
|
|
421
600
|
.action(async (level, _options, command) => {
|
|
422
|
-
await logging.setLogLevel(
|
|
601
|
+
await logging.setLogLevel(session, level, getOptionsFromCommand(command));
|
|
423
602
|
});
|
|
424
603
|
program
|
|
425
604
|
.command('ping')
|
|
426
605
|
.description('Ping the MCP server to check if it is alive')
|
|
427
606
|
.action(async (_options, command) => {
|
|
428
|
-
await utilities.ping(
|
|
607
|
+
await utilities.ping(session, getOptionsFromCommand(command));
|
|
429
608
|
});
|
|
609
|
+
}
|
|
610
|
+
function createSessionProgram() {
|
|
611
|
+
const program = new Command();
|
|
612
|
+
program.configureOutput({
|
|
613
|
+
outputError: (str, write) => write(str),
|
|
614
|
+
getOutHelpWidth: () => 100,
|
|
615
|
+
getErrHelpWidth: () => 100,
|
|
616
|
+
});
|
|
617
|
+
program
|
|
618
|
+
.name('mcpc <@session>')
|
|
619
|
+
.helpOption('-h, --help', 'Display help')
|
|
620
|
+
.option('-j, --json', 'Output in JSON format for scripting and code mode')
|
|
621
|
+
.option('--verbose', 'Enable debug logging')
|
|
622
|
+
.option('--profile <name>', 'OAuth profile override')
|
|
623
|
+
.option('--schema <file>', 'Validate tool/prompt schema against expected schema')
|
|
624
|
+
.option('--schema-mode <mode>', 'Schema validation mode: strict, compatible (default), ignore')
|
|
625
|
+
.option('--timeout <seconds>', 'Request timeout in seconds (default: 300)')
|
|
626
|
+
.option('--insecure', 'Skip TLS certificate verification (for self-signed certs)');
|
|
627
|
+
return program;
|
|
628
|
+
}
|
|
629
|
+
async function handleSessionCommands(session, args) {
|
|
630
|
+
if (!hasSubcommand(args)) {
|
|
631
|
+
const options = extractOptions(args);
|
|
632
|
+
if (options.verbose)
|
|
633
|
+
setVerbose(true);
|
|
634
|
+
if (options.json)
|
|
635
|
+
setJsonMode(true);
|
|
636
|
+
await sessions.showServerDetails(session, {
|
|
637
|
+
outputMode: options.json ? 'json' : 'human',
|
|
638
|
+
...(options.verbose && { verbose: true }),
|
|
639
|
+
...(options.timeout !== undefined && { timeout: options.timeout }),
|
|
640
|
+
});
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
const program = createSessionProgram();
|
|
644
|
+
registerSessionCommands(program, session);
|
|
430
645
|
try {
|
|
431
646
|
await program.parseAsync(args);
|
|
432
647
|
}
|
|
@@ -448,6 +663,17 @@ async function handleCommands(target, args) {
|
|
|
448
663
|
process.exit(1);
|
|
449
664
|
}
|
|
450
665
|
}
|
|
666
|
+
async function flushStdout() {
|
|
667
|
+
await new Promise((resolve) => {
|
|
668
|
+
if (process.stdout.writableFinished) {
|
|
669
|
+
resolve();
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
process.stdout.once('finish', resolve);
|
|
673
|
+
process.stdout.end();
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
}
|
|
451
677
|
main().catch(async (error) => {
|
|
452
678
|
console.error('Fatal error:', error);
|
|
453
679
|
await closeFileLogger();
|