@apify/mcpc 0.3.0 → 0.3.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 +37 -2
- package/README.md +171 -48
- package/dist/bridge/index.js +32 -40
- package/dist/bridge/index.js.map +1 -1
- package/dist/cli/commands/clean.d.ts.map +1 -1
- package/dist/cli/commands/clean.js +3 -2
- package/dist/cli/commands/clean.js.map +1 -1
- package/dist/cli/commands/connect.d.ts +63 -0
- package/dist/cli/commands/connect.d.ts.map +1 -0
- package/dist/cli/commands/connect.js +655 -0
- package/dist/cli/commands/connect.js.map +1 -0
- package/dist/cli/commands/logs.d.ts +8 -0
- package/dist/cli/commands/logs.d.ts.map +1 -0
- package/dist/cli/commands/logs.js +143 -0
- package/dist/cli/commands/logs.js.map +1 -0
- package/dist/cli/commands/sessions.d.ts +6 -59
- package/dist/cli/commands/sessions.d.ts.map +1 -1
- package/dist/cli/commands/sessions.js +37 -690
- package/dist/cli/commands/sessions.js.map +1 -1
- package/dist/cli/commands/skills.d.ts +20 -0
- package/dist/cli/commands/skills.d.ts.map +1 -0
- package/dist/cli/commands/skills.js +160 -0
- package/dist/cli/commands/skills.js.map +1 -0
- package/dist/cli/commands/x402.d.ts.map +1 -1
- package/dist/cli/commands/x402.js +14 -4
- package/dist/cli/commands/x402.js.map +1 -1
- package/dist/cli/index.js +117 -31
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/output.d.ts +13 -0
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +113 -20
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/parser.d.ts +1 -1
- package/dist/cli/parser.d.ts.map +1 -1
- package/dist/cli/parser.js +25 -2
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/shell.js.map +1 -1
- package/dist/core/capabilities.d.ts +3 -0
- package/dist/core/capabilities.d.ts.map +1 -0
- package/dist/core/capabilities.js +14 -0
- package/dist/core/capabilities.js.map +1 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/mcp-client.d.ts +4 -0
- package/dist/core/mcp-client.d.ts.map +1 -1
- package/dist/core/mcp-client.js +36 -12
- package/dist/core/mcp-client.js.map +1 -1
- package/dist/lib/bridge-client.d.ts +4 -1
- package/dist/lib/bridge-client.d.ts.map +1 -1
- package/dist/lib/bridge-client.js +29 -3
- package/dist/lib/bridge-client.js.map +1 -1
- package/dist/lib/bridge-manager.d.ts +3 -3
- package/dist/lib/bridge-manager.d.ts.map +1 -1
- package/dist/lib/bridge-manager.js +36 -20
- package/dist/lib/bridge-manager.js.map +1 -1
- package/dist/lib/cleanup.d.ts.map +1 -1
- package/dist/lib/cleanup.js +20 -19
- package/dist/lib/cleanup.js.map +1 -1
- package/dist/lib/config.d.ts +14 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +41 -27
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/errors.d.ts +0 -1
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +2 -4
- package/dist/lib/errors.js.map +1 -1
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +1 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/log-reader.d.ts +28 -0
- package/dist/lib/log-reader.d.ts.map +1 -0
- package/dist/lib/log-reader.js +278 -0
- package/dist/lib/log-reader.js.map +1 -0
- package/dist/lib/session-client.d.ts +1 -1
- package/dist/lib/session-client.d.ts.map +1 -1
- package/dist/lib/session-client.js +6 -8
- package/dist/lib/session-client.js.map +1 -1
- package/dist/lib/stderr-tail.d.ts +10 -0
- package/dist/lib/stderr-tail.d.ts.map +1 -0
- package/dist/lib/stderr-tail.js +28 -0
- package/dist/lib/stderr-tail.js.map +1 -0
- package/dist/lib/types.d.ts +6 -1
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +1 -0
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +12 -2
- package/dist/lib/utils.js.map +1 -1
- package/dist/lib/x402/fetch-middleware.d.ts +3 -2
- package/dist/lib/x402/fetch-middleware.d.ts.map +1 -1
- package/dist/lib/x402/fetch-middleware.js +37 -24
- package/dist/lib/x402/fetch-middleware.js.map +1 -1
- package/dist/lib/x402/signer.d.ts +7 -1
- package/dist/lib/x402/signer.d.ts.map +1 -1
- package/dist/lib/x402/signer.js +249 -5
- package/dist/lib/x402/signer.js.map +1 -1
- package/docs/README.md +1 -0
- package/docs/claude-skill/SKILL.md +150 -117
- package/docs/examples/company-lookup.sh +6 -6
- package/docs/vhs/README.md +79 -0
- package/docs/vhs/grep.tape +56 -0
- package/docs/vhs/mcpc-demo.tape +179 -0
- package/docs/vhs/proxy.tape +69 -0
- package/docs/vhs/quickstart.tape +59 -0
- package/docs/vhs/scripting.tape +69 -0
- package/docs/vhs/tools.tape +66 -0
- package/package.json +20 -20
|
@@ -1,347 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isValidSessionName, generateSessionName, normalizeServerUrl, validateProfileName, isProcessAlive, getServerHost, getLogsDir, redactHeaders, } from '../../lib/index.js';
|
|
1
|
+
import { isProcessAlive, getServerHost, redactHeaders, ClientError, } from '../../lib/index.js';
|
|
3
2
|
import { DISCONNECTED_THRESHOLD_MS } from '../../lib/types.js';
|
|
4
3
|
import { formatOutput, formatSuccess, formatWarning, formatError, formatSessionLine, formatServerDetails, theme, } from '../output.js';
|
|
5
|
-
import { withMcpClient,
|
|
4
|
+
import { withMcpClient, resolveAuthProfile } from '../helpers.js';
|
|
6
5
|
import { listAuthProfiles } from '../../lib/auth/profiles.js';
|
|
7
|
-
import { sessionExists, deleteSession,
|
|
6
|
+
import { sessionExists, deleteSession, updateSession, consolidateSessions, getSession, } from '../../lib/sessions.js';
|
|
8
7
|
import { startBridge, stopBridge, reconnectCrashedSessions, } from '../../lib/bridge-manager.js';
|
|
9
|
-
import { storeKeychainSessionHeaders, storeKeychainProxyBearerToken, } from '../../lib/auth/keychain.js';
|
|
10
|
-
import { AuthError, ClientError, isAuthenticationError, createServerAuthError, } from '../../lib/index.js';
|
|
11
|
-
import { getWallet } from '../../lib/wallets.js';
|
|
12
8
|
import chalk from 'chalk';
|
|
13
9
|
import { createLogger } from '../../lib/logger.js';
|
|
14
|
-
import {
|
|
15
|
-
import { loadConfig, listServers, isStdioEntry, discoverMcpConfigFiles, getStandardMcpConfigPaths, } from '../../lib/config.js';
|
|
10
|
+
import { getBridgeLogPath } from '../../lib/log-reader.js';
|
|
16
11
|
const logger = createLogger('sessions');
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
resolve(false);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
server.once('listening', () => {
|
|
29
|
-
server.close(() => {
|
|
30
|
-
resolve(true);
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
server.listen(port, host);
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
async function findMatchingSession(parsed, options) {
|
|
37
|
-
const storage = await loadSessions();
|
|
38
|
-
const sessions = Object.values(storage.sessions);
|
|
39
|
-
if (sessions.length === 0)
|
|
40
|
-
return undefined;
|
|
41
|
-
const effectiveProfile = options.noProfile ? undefined : (options.profile ?? 'default');
|
|
42
|
-
for (const session of sessions) {
|
|
43
|
-
if (!session.server)
|
|
44
|
-
continue;
|
|
45
|
-
if (parsed.type === 'url') {
|
|
46
|
-
if (!session.server.url)
|
|
47
|
-
continue;
|
|
48
|
-
try {
|
|
49
|
-
const existingUrl = normalizeServerUrl(session.server.url);
|
|
50
|
-
const newUrl = normalizeServerUrl(parsed.url);
|
|
51
|
-
if (existingUrl !== newUrl)
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
continue;
|
|
60
|
-
}
|
|
61
|
-
const sessionProfile = session.profileName ?? 'default';
|
|
62
|
-
if (effectiveProfile !== sessionProfile)
|
|
63
|
-
continue;
|
|
64
|
-
const existingHeaderKeys = Object.keys(session.server.headers || {}).sort();
|
|
65
|
-
const newHeaderKeys = (options.headers || [])
|
|
66
|
-
.map((h) => h.split(':')[0]?.trim() || '')
|
|
67
|
-
.filter(Boolean)
|
|
68
|
-
.sort();
|
|
69
|
-
if (existingHeaderKeys.join(',') !== newHeaderKeys.join(','))
|
|
70
|
-
continue;
|
|
71
|
-
return session.name;
|
|
72
|
-
}
|
|
73
|
-
return undefined;
|
|
74
|
-
}
|
|
75
|
-
export async function resolveSessionName(parsed, options) {
|
|
76
|
-
const existingName = await findMatchingSession(parsed, options);
|
|
77
|
-
if (existingName) {
|
|
78
|
-
return existingName;
|
|
79
|
-
}
|
|
80
|
-
const candidateName = generateSessionName(parsed);
|
|
81
|
-
const storage = await loadSessions();
|
|
82
|
-
if (!(candidateName in storage.sessions)) {
|
|
83
|
-
if (options.outputMode === 'human') {
|
|
84
|
-
console.log(theme.cyan(`Using session name: ${candidateName}`));
|
|
85
|
-
}
|
|
86
|
-
return candidateName;
|
|
87
|
-
}
|
|
88
|
-
for (let i = 2; i <= 99; i++) {
|
|
89
|
-
const suffixed = `${candidateName}-${i}`;
|
|
90
|
-
if (isValidSessionName(suffixed) && !(suffixed in storage.sessions)) {
|
|
91
|
-
if (options.outputMode === 'human') {
|
|
92
|
-
console.log(theme.cyan(`Using session name: ${suffixed}`));
|
|
93
|
-
}
|
|
94
|
-
return suffixed;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
throw new ClientError(`Cannot auto-generate session name: too many sessions for this server.\n` +
|
|
98
|
-
`Specify a name explicitly: mcpc connect ${parsed.type === 'url' ? parsed.url : `${parsed.file}:${parsed.entry}`} @my-session`);
|
|
99
|
-
}
|
|
100
|
-
async function buildConnectResultEntry(sessionName, status, options) {
|
|
101
|
-
return await withMcpClient(sessionName, {
|
|
102
|
-
outputMode: 'json',
|
|
103
|
-
hideTarget: true,
|
|
104
|
-
...(options.verbose && { verbose: options.verbose }),
|
|
105
|
-
...(options.timeout !== undefined && { timeout: options.timeout }),
|
|
106
|
-
}, async (client, context) => {
|
|
107
|
-
const serverDetails = await client.getServerDetails();
|
|
108
|
-
const tools = (await client.listAllTools()).tools;
|
|
109
|
-
const server = context.serverConfig
|
|
110
|
-
? {
|
|
111
|
-
...context.serverConfig,
|
|
112
|
-
...(context.serverConfig.headers && {
|
|
113
|
-
headers: redactHeaders(context.serverConfig.headers),
|
|
114
|
-
}),
|
|
115
|
-
}
|
|
116
|
-
: undefined;
|
|
117
|
-
return {
|
|
118
|
-
_mcpc: {
|
|
119
|
-
sessionName: context.sessionName ?? sessionName,
|
|
120
|
-
...(context.profileName && { profileName: context.profileName }),
|
|
121
|
-
...(server && { server }),
|
|
122
|
-
...(options.configFile && { configFile: options.configFile }),
|
|
123
|
-
...(options.entry && { entry: options.entry }),
|
|
124
|
-
status,
|
|
125
|
-
},
|
|
126
|
-
...(serverDetails.protocolVersion && { protocolVersion: serverDetails.protocolVersion }),
|
|
127
|
-
...(serverDetails.capabilities && { capabilities: serverDetails.capabilities }),
|
|
128
|
-
...(serverDetails.serverInfo && { serverInfo: serverDetails.serverInfo }),
|
|
129
|
-
...(serverDetails.instructions && { instructions: serverDetails.instructions }),
|
|
130
|
-
...(tools.length > 0 && { toolNames: tools.map((t) => t.name) }),
|
|
131
|
-
};
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
export async function connectSession(target, name, options) {
|
|
135
|
-
if (!isValidSessionName(name)) {
|
|
136
|
-
throw new ClientError(`Invalid session name: ${name}\n` +
|
|
137
|
-
`Session names must start with @ and be followed by 1-64 characters, alphanumeric with hyphens or underscores only (e.g., @my-session).`);
|
|
138
|
-
}
|
|
139
|
-
if (options.profile) {
|
|
140
|
-
validateProfileName(options.profile);
|
|
141
|
-
}
|
|
142
|
-
let proxyConfig;
|
|
143
|
-
if (options.proxy) {
|
|
144
|
-
proxyConfig = parseProxyArg(options.proxy);
|
|
145
|
-
logger.debug(`Proxy config: ${proxyConfig.host}:${proxyConfig.port}`);
|
|
146
|
-
const portAvailable = await checkPortAvailable(proxyConfig.host, proxyConfig.port);
|
|
147
|
-
if (!portAvailable) {
|
|
148
|
-
throw new ClientError(`Port ${proxyConfig.port} is already in use on ${proxyConfig.host}. ` +
|
|
149
|
-
`Choose a different port with --proxy [host:]port`);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
if (options.proxyBearerToken && !options.proxy) {
|
|
153
|
-
throw new ClientError('--proxy-bearer-token requires --proxy to be specified');
|
|
154
|
-
}
|
|
155
|
-
const existingSession = await getSession(name);
|
|
156
|
-
if (existingSession) {
|
|
157
|
-
const bridgeStatus = getBridgeStatus(existingSession);
|
|
158
|
-
if (bridgeStatus === 'live') {
|
|
159
|
-
if (options.outputMode === 'human' && !options.quiet) {
|
|
160
|
-
console.log(formatSuccess(`Session ${name} is already active`));
|
|
161
|
-
}
|
|
162
|
-
if (!options.skipDetails) {
|
|
163
|
-
if (options.outputMode === 'json') {
|
|
164
|
-
const entry = await buildConnectResultEntry(name, 'active', {
|
|
165
|
-
...(options.verbose && { verbose: options.verbose }),
|
|
166
|
-
...(options.timeout !== undefined && { timeout: options.timeout }),
|
|
167
|
-
});
|
|
168
|
-
console.log(formatOutput([entry], 'json'));
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
await showServerDetails(name, { ...options, hideTarget: false });
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
if (options.outputMode === 'human' && !options.quiet) {
|
|
177
|
-
console.log(theme.yellow(`Session ${name} exists but bridge is ${bridgeStatus}, reconnecting...`));
|
|
178
|
-
}
|
|
179
|
-
try {
|
|
180
|
-
await stopBridge(name);
|
|
181
|
-
}
|
|
182
|
-
catch {
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
const serverConfig = await resolveTarget(target, options);
|
|
186
|
-
const hasExplicitAuthHeader = serverConfig.headers?.Authorization !== undefined;
|
|
187
|
-
const hasExplicitProfile = options.profile !== undefined;
|
|
188
|
-
if (hasExplicitAuthHeader && hasExplicitProfile) {
|
|
189
|
-
throw new ClientError(`Cannot combine --profile with --header "Authorization: ...".\n\n` +
|
|
190
|
-
`Use either:\n` +
|
|
191
|
-
` --profile ${options.profile} (OAuth authentication via saved profile)\n` +
|
|
192
|
-
` --header "Authorization: Bearer <token>" (static bearer token)`);
|
|
193
|
-
}
|
|
194
|
-
let profileName;
|
|
195
|
-
if (serverConfig.url) {
|
|
196
|
-
if (options.noProfile) {
|
|
197
|
-
logger.debug('Skipping OAuth profile: --no-profile specified');
|
|
198
|
-
}
|
|
199
|
-
else if (hasExplicitAuthHeader) {
|
|
200
|
-
logger.debug('Skipping OAuth profile auto-detection: explicit Authorization header provided via --header');
|
|
201
|
-
}
|
|
202
|
-
else if (options.x402 && !options.profile) {
|
|
203
|
-
logger.debug('Skipping OAuth profile auto-detection: --x402 specified');
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
profileName = await resolveAuthProfile(serverConfig.url, target, options.profile, {
|
|
207
|
-
sessionName: name,
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
let headers;
|
|
212
|
-
if (Object.keys(serverConfig.headers || {}).length > 0) {
|
|
213
|
-
headers = { ...serverConfig.headers };
|
|
214
|
-
if (Object.keys(headers).length > 0) {
|
|
215
|
-
logger.debug(`Storing ${Object.keys(headers).length} headers for session ${name} in keychain`);
|
|
216
|
-
await storeKeychainSessionHeaders(name, headers);
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
headers = undefined;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
if (options.proxyBearerToken) {
|
|
223
|
-
logger.debug(`Storing proxy bearer token for session ${name} in keychain`);
|
|
224
|
-
await storeKeychainProxyBearerToken(name, options.proxyBearerToken);
|
|
225
|
-
}
|
|
226
|
-
if (options.x402) {
|
|
227
|
-
const wallet = await getWallet();
|
|
228
|
-
if (!wallet) {
|
|
229
|
-
throw new ClientError('x402 wallet not found. Create one with: mcpc x402 init');
|
|
230
|
-
}
|
|
231
|
-
logger.debug(`Using x402 wallet: ${wallet.address}`);
|
|
232
|
-
}
|
|
233
|
-
const isReconnect = !!existingSession;
|
|
234
|
-
const { headers: _originalHeaders, ...baseTransportConfig } = serverConfig;
|
|
235
|
-
const sessionTransportConfig = {
|
|
236
|
-
...baseTransportConfig,
|
|
237
|
-
...(headers && { headers: redactHeaders(headers) }),
|
|
238
|
-
};
|
|
239
|
-
const sessionUpdate = {
|
|
240
|
-
server: sessionTransportConfig,
|
|
241
|
-
...(profileName && { profileName }),
|
|
242
|
-
...(proxyConfig && { proxy: proxyConfig }),
|
|
243
|
-
...(options.x402 && { x402: true }),
|
|
244
|
-
...(options.insecure && { insecure: true }),
|
|
245
|
-
...(isReconnect && { status: 'active' }),
|
|
246
|
-
};
|
|
247
|
-
if (isReconnect) {
|
|
248
|
-
await updateSession(name, sessionUpdate);
|
|
249
|
-
logger.debug(`Session record updated for reconnect: ${name}`);
|
|
250
|
-
}
|
|
251
|
-
else {
|
|
252
|
-
await saveSession(name, {
|
|
253
|
-
server: sessionTransportConfig,
|
|
254
|
-
createdAt: new Date().toISOString(),
|
|
255
|
-
status: 'connecting',
|
|
256
|
-
lastConnectionAttemptAt: new Date().toISOString(),
|
|
257
|
-
...sessionUpdate,
|
|
258
|
-
});
|
|
259
|
-
logger.debug(`Initial session record created for: ${name}`);
|
|
260
|
-
}
|
|
261
|
-
try {
|
|
262
|
-
const bridgeOptions = {
|
|
263
|
-
sessionName: name,
|
|
264
|
-
serverConfig: serverConfig,
|
|
265
|
-
verbose: options.verbose || false,
|
|
266
|
-
};
|
|
267
|
-
if (headers) {
|
|
268
|
-
bridgeOptions.headers = headers;
|
|
269
|
-
}
|
|
270
|
-
if (profileName) {
|
|
271
|
-
bridgeOptions.profileName = profileName;
|
|
272
|
-
}
|
|
273
|
-
if (proxyConfig) {
|
|
274
|
-
bridgeOptions.proxyConfig = proxyConfig;
|
|
275
|
-
}
|
|
276
|
-
if (options.x402) {
|
|
277
|
-
bridgeOptions.x402 = true;
|
|
278
|
-
}
|
|
279
|
-
if (options.insecure) {
|
|
280
|
-
bridgeOptions.insecure = true;
|
|
281
|
-
}
|
|
282
|
-
const { pid } = await startBridge(bridgeOptions);
|
|
283
|
-
await updateSession(name, { pid, status: 'active' });
|
|
284
|
-
logger.debug(`Session ${name} updated with bridge PID: ${pid}`);
|
|
285
|
-
}
|
|
286
|
-
catch (error) {
|
|
287
|
-
logger.debug(`Bridge start failed, cleaning up session ${name}`);
|
|
288
|
-
if (!isReconnect) {
|
|
289
|
-
try {
|
|
290
|
-
await deleteSession(name);
|
|
291
|
-
}
|
|
292
|
-
catch {
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
throw error;
|
|
296
|
-
}
|
|
297
|
-
if (options.skipDetails) {
|
|
298
|
-
if (options.outputMode === 'human' && !options.quiet) {
|
|
299
|
-
console.log(formatSuccess(`Session ${name} ${isReconnect ? 'reconnected' : 'created'}`));
|
|
300
|
-
}
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
try {
|
|
304
|
-
if (options.outputMode === 'json') {
|
|
305
|
-
const entry = await buildConnectResultEntry(name, 'created', {
|
|
306
|
-
...(options.verbose && { verbose: options.verbose }),
|
|
307
|
-
...(options.timeout !== undefined && { timeout: options.timeout }),
|
|
308
|
-
});
|
|
309
|
-
console.log(formatOutput([entry], 'json'));
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
await showServerDetails(name, {
|
|
313
|
-
...options,
|
|
314
|
-
hideTarget: false,
|
|
315
|
-
});
|
|
316
|
-
console.log(formatSuccess(`Session ${name} ${isReconnect ? 'reconnected' : 'created'}`));
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
catch (detailsError) {
|
|
320
|
-
if (detailsError instanceof AuthError) {
|
|
321
|
-
throw detailsError;
|
|
322
|
-
}
|
|
323
|
-
if (detailsError instanceof Error && isAuthenticationError(detailsError.message)) {
|
|
324
|
-
const logPath = `${getLogsDir()}/bridge-${name}.log`;
|
|
325
|
-
throw createServerAuthError(serverConfig.url || target, { sessionName: name, logPath });
|
|
326
|
-
}
|
|
327
|
-
const errorMsg = detailsError instanceof Error ? detailsError.message : String(detailsError);
|
|
328
|
-
if (options.outputMode === 'json') {
|
|
329
|
-
const failed = {
|
|
330
|
-
_mcpc: {
|
|
331
|
-
sessionName: name,
|
|
332
|
-
status: 'failed',
|
|
333
|
-
error: errorMsg,
|
|
334
|
-
},
|
|
335
|
-
};
|
|
336
|
-
console.log(formatOutput([failed], 'json'));
|
|
337
|
-
}
|
|
338
|
-
else {
|
|
339
|
-
console.log(formatWarning(`Session ${name} created but server is not responding: ${errorMsg}\n` +
|
|
340
|
-
` The session will auto-recover when the server becomes available.\n` +
|
|
341
|
-
` Check status with: mcpc ${name}`));
|
|
342
|
-
}
|
|
343
|
-
logger.debug(`showServerDetails failed for new session ${name}: ${errorMsg}`);
|
|
344
|
-
}
|
|
12
|
+
export function statelessField(connectionMode) {
|
|
13
|
+
if (connectionMode === 'stateless')
|
|
14
|
+
return { stateless: true };
|
|
15
|
+
if (connectionMode === 'stateful')
|
|
16
|
+
return { stateless: false };
|
|
17
|
+
return { stateless: null };
|
|
345
18
|
}
|
|
346
19
|
export function getBridgeStatus(session) {
|
|
347
20
|
if (session.status === 'unauthorized') {
|
|
@@ -402,9 +75,12 @@ export function formatTimeAgo(isoDate) {
|
|
|
402
75
|
return 'yesterday';
|
|
403
76
|
if (diffDays < 7)
|
|
404
77
|
return `${diffDays} days ago`;
|
|
405
|
-
if (diffDays < 30)
|
|
406
|
-
|
|
407
|
-
|
|
78
|
+
if (diffDays < 30) {
|
|
79
|
+
const weeks = Math.floor(diffDays / 7);
|
|
80
|
+
return `${weeks} ${weeks === 1 ? 'week' : 'weeks'} ago`;
|
|
81
|
+
}
|
|
82
|
+
const months = Math.floor(diffDays / 30);
|
|
83
|
+
return `${months} ${months === 1 ? 'month' : 'months'} ago`;
|
|
408
84
|
}
|
|
409
85
|
export async function listSessionsAndAuthProfiles(options) {
|
|
410
86
|
const consolidateResult = await consolidateSessions(false);
|
|
@@ -412,9 +88,10 @@ export async function listSessionsAndAuthProfiles(options) {
|
|
|
412
88
|
reconnectCrashedSessions(consolidateResult.sessionsToRestart);
|
|
413
89
|
const profiles = await listAuthProfiles();
|
|
414
90
|
if (options.outputMode === 'json') {
|
|
415
|
-
const sessionsWithStatus = sessions.map((session) => ({
|
|
91
|
+
const sessionsWithStatus = sessions.map(({ connectionMode, ...session }) => ({
|
|
416
92
|
...session,
|
|
417
93
|
status: getBridgeStatus(session),
|
|
94
|
+
...statelessField(connectionMode),
|
|
418
95
|
}));
|
|
419
96
|
console.log(formatOutput({
|
|
420
97
|
sessions: sessionsWithStatus,
|
|
@@ -478,6 +155,7 @@ export async function listSessionsAndAuthProfiles(options) {
|
|
|
478
155
|
}
|
|
479
156
|
}
|
|
480
157
|
}
|
|
158
|
+
return { hasSessions: sessions.length > 0 };
|
|
481
159
|
}
|
|
482
160
|
export async function closeSession(name, options) {
|
|
483
161
|
try {
|
|
@@ -513,7 +191,7 @@ export async function closeSession(name, options) {
|
|
|
513
191
|
export async function showServerDetails(target, options) {
|
|
514
192
|
await withMcpClient(target, options, async (client, context) => {
|
|
515
193
|
const serverDetails = await client.getServerDetails();
|
|
516
|
-
const { serverInfo, capabilities, instructions, protocolVersion } = serverDetails;
|
|
194
|
+
const { serverInfo, capabilities, instructions, protocolVersion, connectionMode } = serverDetails;
|
|
517
195
|
const cachedToolsResult = await client.listAllTools();
|
|
518
196
|
const tools = cachedToolsResult.tools;
|
|
519
197
|
if (options.outputMode === 'human') {
|
|
@@ -526,11 +204,17 @@ export async function showServerDetails(target, options) {
|
|
|
526
204
|
headers: redactHeaders(context.serverConfig.headers),
|
|
527
205
|
}),
|
|
528
206
|
};
|
|
207
|
+
let logPath;
|
|
208
|
+
if (target.startsWith('@')) {
|
|
209
|
+
logPath = getBridgeLogPath(target);
|
|
210
|
+
}
|
|
529
211
|
console.log(formatOutput({
|
|
530
212
|
_mcpc: {
|
|
531
213
|
sessionName: context.sessionName,
|
|
532
214
|
profileName: context.profileName,
|
|
533
215
|
server,
|
|
216
|
+
...statelessField(connectionMode),
|
|
217
|
+
...(logPath && { logPath }),
|
|
534
218
|
},
|
|
535
219
|
protocolVersion,
|
|
536
220
|
capabilities,
|
|
@@ -561,14 +245,6 @@ export async function restartSession(name, options) {
|
|
|
561
245
|
}
|
|
562
246
|
const { readKeychainSessionHeaders } = await import('../../lib/auth/keychain.js');
|
|
563
247
|
const headers = await readKeychainSessionHeaders(name);
|
|
564
|
-
const bridgeOptions = {
|
|
565
|
-
sessionName: name,
|
|
566
|
-
serverConfig: { ...serverConfig, ...(headers && { headers }) },
|
|
567
|
-
verbose: options.verbose || false,
|
|
568
|
-
};
|
|
569
|
-
if (headers) {
|
|
570
|
-
bridgeOptions.headers = headers;
|
|
571
|
-
}
|
|
572
248
|
const hasExplicitAuthHeader = headers?.Authorization !== undefined;
|
|
573
249
|
let profileName = session.profileName;
|
|
574
250
|
if (!profileName && serverConfig.url && !hasExplicitAuthHeader && !session.x402) {
|
|
@@ -580,18 +256,16 @@ export async function restartSession(name, options) {
|
|
|
580
256
|
await updateSession(name, { profileName });
|
|
581
257
|
}
|
|
582
258
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
bridgeOptions.insecure = session.insecure;
|
|
594
|
-
}
|
|
259
|
+
const bridgeOptions = {
|
|
260
|
+
sessionName: name,
|
|
261
|
+
serverConfig: { ...serverConfig, ...(headers && { headers }) },
|
|
262
|
+
verbose: options.verbose || false,
|
|
263
|
+
...(headers && { headers }),
|
|
264
|
+
...(profileName && { profileName }),
|
|
265
|
+
...(session.proxy && { proxyConfig: session.proxy }),
|
|
266
|
+
...(session.x402 && { x402: session.x402 }),
|
|
267
|
+
...(session.insecure && { insecure: session.insecure }),
|
|
268
|
+
};
|
|
595
269
|
const { pid } = await startBridge(bridgeOptions);
|
|
596
270
|
await updateSession(name, { pid, status: 'active' });
|
|
597
271
|
logger.debug(`Session ${name} restarted with bridge PID: ${pid}`);
|
|
@@ -618,335 +292,8 @@ export async function restartSession(name, options) {
|
|
|
618
292
|
throw error;
|
|
619
293
|
}
|
|
620
294
|
}
|
|
621
|
-
async function bulkConnectEntries(entries, options) {
|
|
622
|
-
const liveSet = new Set();
|
|
623
|
-
for (const { sessionName } of entries) {
|
|
624
|
-
const session = await getSession(sessionName);
|
|
625
|
-
if (session && getBridgeStatus(session) === 'live') {
|
|
626
|
-
liveSet.add(sessionName);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
const settled = await Promise.allSettled(entries.map(async ({ entry, sessionName, configFile }) => connectSession(entry, sessionName, {
|
|
630
|
-
...options,
|
|
631
|
-
config: configFile,
|
|
632
|
-
skipDetails: true,
|
|
633
|
-
quiet: true,
|
|
634
|
-
})));
|
|
635
|
-
const results = settled.map((outcome, i) => {
|
|
636
|
-
const base = entries[i];
|
|
637
|
-
if (outcome.status === 'fulfilled') {
|
|
638
|
-
return { ...base, status: liveSet.has(base.sessionName) ? 'active' : 'created' };
|
|
639
|
-
}
|
|
640
|
-
const error = outcome.reason instanceof Error ? outcome.reason.message : String(outcome.reason);
|
|
641
|
-
return { ...base, status: 'failed', error };
|
|
642
|
-
});
|
|
643
|
-
if (options.outputMode === 'human') {
|
|
644
|
-
for (const r of results) {
|
|
645
|
-
const name = theme.cyan(r.sessionName);
|
|
646
|
-
switch (r.status) {
|
|
647
|
-
case 'created':
|
|
648
|
-
console.log(` ${theme.yellow('●')} ${name} ${theme.yellow('connecting')}`);
|
|
649
|
-
break;
|
|
650
|
-
case 'active':
|
|
651
|
-
console.log(` ${theme.green('●')} ${name} ${chalk.dim('already active')}`);
|
|
652
|
-
break;
|
|
653
|
-
case 'failed':
|
|
654
|
-
console.log(` ${theme.red('●')} ${name} ${theme.red('failed')}${r.error ? chalk.dim(` — ${r.error}`) : ''}`);
|
|
655
|
-
break;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
return results;
|
|
660
|
-
}
|
|
661
|
-
function printBulkConnectSummary(results, options) {
|
|
662
|
-
const active = results.filter((r) => r.status === 'active').length;
|
|
663
|
-
const connecting = results.filter((r) => r.status === 'created').length;
|
|
664
|
-
const failed = results.filter((r) => r.status === 'failed').length;
|
|
665
|
-
if (options.outputMode === 'human' && results.length > 1) {
|
|
666
|
-
const parts = [];
|
|
667
|
-
if (active > 0)
|
|
668
|
-
parts.push(`${active} already active`);
|
|
669
|
-
if (connecting > 0)
|
|
670
|
-
parts.push(`${connecting} connecting`);
|
|
671
|
-
if (failed > 0)
|
|
672
|
-
parts.push(`${failed} failed`);
|
|
673
|
-
const summary = parts.join(', ');
|
|
674
|
-
if (failed === 0) {
|
|
675
|
-
console.log(formatSuccess(summary));
|
|
676
|
-
}
|
|
677
|
-
else if (active + connecting > 0) {
|
|
678
|
-
console.log(formatWarning(summary));
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
return { active, connecting, failed };
|
|
682
|
-
}
|
|
683
|
-
export async function connectAllFromConfig(configFile, options) {
|
|
684
|
-
const config = loadConfig(configFile);
|
|
685
|
-
const allNames = listServers(config);
|
|
686
|
-
if (allNames.length === 0) {
|
|
687
|
-
throw new ClientError(`No servers found in config file: ${configFile}`);
|
|
688
|
-
}
|
|
689
|
-
const stdioSkipped = [];
|
|
690
|
-
const serverNames = allNames.filter((name) => {
|
|
691
|
-
if (!options.stdio && isStdioEntry(config, name)) {
|
|
692
|
-
stdioSkipped.push(name);
|
|
693
|
-
return false;
|
|
694
|
-
}
|
|
695
|
-
return true;
|
|
696
|
-
});
|
|
697
|
-
if (serverNames.length === 0) {
|
|
698
|
-
if (options.outputMode === 'json') {
|
|
699
|
-
const skippedEntries = stdioSkipped.map((entry) => ({
|
|
700
|
-
_mcpc: {
|
|
701
|
-
sessionName: generateSessionName({ type: 'config', file: configFile, entry }),
|
|
702
|
-
configFile,
|
|
703
|
-
entry,
|
|
704
|
-
status: 'skipped',
|
|
705
|
-
skipReason: 'stdio',
|
|
706
|
-
},
|
|
707
|
-
}));
|
|
708
|
-
console.log(formatOutput(skippedEntries, 'json'));
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
throw new ClientError(`All ${allNames.length} server${allNames.length === 1 ? '' : 's'} in ${configFile} use stdio transport.\n` +
|
|
712
|
-
`Pass --stdio to include them: mcpc connect ${configFile} --stdio`);
|
|
713
|
-
}
|
|
714
|
-
if (options.outputMode === 'human') {
|
|
715
|
-
console.log(theme.cyan(`Connecting ${serverNames.length} server${serverNames.length === 1 ? '' : 's'} from ${configFile}...`));
|
|
716
|
-
if (stdioSkipped.length > 0) {
|
|
717
|
-
console.log(chalk.dim(` skipping ${stdioSkipped.length} stdio server${stdioSkipped.length === 1 ? '' : 's'} ` +
|
|
718
|
-
`(${stdioSkipped.join(', ')}), pass --stdio to include`));
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
const entries = serverNames.map((entry) => ({
|
|
722
|
-
configFile,
|
|
723
|
-
entry,
|
|
724
|
-
sessionName: generateSessionName({ type: 'config', file: configFile, entry }),
|
|
725
|
-
}));
|
|
726
|
-
const results = await bulkConnectEntries(entries, options);
|
|
727
|
-
if (options.outputMode === 'json') {
|
|
728
|
-
const resultEntries = await buildBulkConnectEntries(results, options);
|
|
729
|
-
const skippedEntries = stdioSkipped.map((entry) => ({
|
|
730
|
-
_mcpc: {
|
|
731
|
-
sessionName: generateSessionName({ type: 'config', file: configFile, entry }),
|
|
732
|
-
configFile,
|
|
733
|
-
entry,
|
|
734
|
-
status: 'skipped',
|
|
735
|
-
skipReason: 'stdio',
|
|
736
|
-
},
|
|
737
|
-
}));
|
|
738
|
-
console.log(formatOutput([...resultEntries, ...skippedEntries], 'json'));
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
const { active, connecting, failed } = printBulkConnectSummary(results, options);
|
|
742
|
-
if (active + connecting === 0 && failed > 0) {
|
|
743
|
-
throw new ClientError(`Failed to connect any servers from ${configFile}`);
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
async function buildBulkConnectEntries(results, options) {
|
|
747
|
-
return await Promise.all(results.map(async (r) => {
|
|
748
|
-
if (r.status === 'failed') {
|
|
749
|
-
return {
|
|
750
|
-
_mcpc: {
|
|
751
|
-
sessionName: r.sessionName,
|
|
752
|
-
configFile: r.configFile,
|
|
753
|
-
entry: r.entry,
|
|
754
|
-
status: 'failed',
|
|
755
|
-
...(r.error && { error: r.error }),
|
|
756
|
-
},
|
|
757
|
-
};
|
|
758
|
-
}
|
|
759
|
-
try {
|
|
760
|
-
return await buildConnectResultEntry(r.sessionName, r.status, {
|
|
761
|
-
...(options.verbose && { verbose: options.verbose }),
|
|
762
|
-
...(options.timeout !== undefined && { timeout: options.timeout }),
|
|
763
|
-
configFile: r.configFile,
|
|
764
|
-
entry: r.entry,
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
catch (err) {
|
|
768
|
-
return {
|
|
769
|
-
_mcpc: {
|
|
770
|
-
sessionName: r.sessionName,
|
|
771
|
-
configFile: r.configFile,
|
|
772
|
-
entry: r.entry,
|
|
773
|
-
status: 'failed',
|
|
774
|
-
error: err instanceof Error ? err.message : String(err),
|
|
775
|
-
},
|
|
776
|
-
};
|
|
777
|
-
}
|
|
778
|
-
}));
|
|
779
|
-
}
|
|
780
|
-
function aggregateDiscoveredEntries(discovered, options) {
|
|
781
|
-
const entries = [];
|
|
782
|
-
const skippedDuplicates = [];
|
|
783
|
-
const skippedStdio = [];
|
|
784
|
-
const seenNames = new Set();
|
|
785
|
-
for (const d of discovered) {
|
|
786
|
-
for (const entry of Object.keys(d.config.mcpServers)) {
|
|
787
|
-
const sessionName = generateSessionName({ type: 'config', file: d.path, entry });
|
|
788
|
-
if (!options.stdio && isStdioEntry(d.config, entry)) {
|
|
789
|
-
skippedStdio.push({ configFile: d.path, entry, sessionName });
|
|
790
|
-
continue;
|
|
791
|
-
}
|
|
792
|
-
if (seenNames.has(sessionName)) {
|
|
793
|
-
skippedDuplicates.push({ configFile: d.path, entry, sessionName });
|
|
794
|
-
continue;
|
|
795
|
-
}
|
|
796
|
-
seenNames.add(sessionName);
|
|
797
|
-
entries.push({
|
|
798
|
-
configFile: d.path,
|
|
799
|
-
entry,
|
|
800
|
-
sessionName,
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
return { entries, skippedDuplicates, skippedStdio };
|
|
805
|
-
}
|
|
806
|
-
export async function connectAllFromStandardConfigs(options) {
|
|
807
|
-
const discovered = discoverMcpConfigFiles();
|
|
808
|
-
const hasApifyToken = !!process.env.APIFY_API_TOKEN;
|
|
809
|
-
if (discovered.length === 0 && !hasApifyToken) {
|
|
810
|
-
if (options.outputMode === 'json') {
|
|
811
|
-
console.log(formatOutput([], 'json'));
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
const searchPaths = getStandardMcpConfigPaths()
|
|
815
|
-
.map((c) => ` ${c.path}`)
|
|
816
|
-
.join('\n');
|
|
817
|
-
throw new ClientError(`No MCP config files found in standard locations.\n\n` +
|
|
818
|
-
`Searched:\n${searchPaths}\n\n` +
|
|
819
|
-
`Connect a specific server: mcpc connect mcp.example.com\n` +
|
|
820
|
-
`Connect from a specific file: mcpc connect /path/to/mcp.json`);
|
|
821
|
-
}
|
|
822
|
-
if (discovered.length === 0) {
|
|
823
|
-
await maybeConnectApify([], [], options);
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
826
|
-
const { entries, skippedDuplicates, skippedStdio } = aggregateDiscoveredEntries(discovered, {
|
|
827
|
-
...(options.stdio && { stdio: true }),
|
|
828
|
-
});
|
|
829
|
-
if (options.outputMode === 'human') {
|
|
830
|
-
const totalEntries = entries.length + skippedDuplicates.length + skippedStdio.length;
|
|
831
|
-
console.log(theme.cyan(`Found ${discovered.length} MCP config file${discovered.length === 1 ? '' : 's'} ` +
|
|
832
|
-
`with ${totalEntries} server${totalEntries === 1 ? '' : 's'}:`));
|
|
833
|
-
for (const d of discovered) {
|
|
834
|
-
console.log(` ${d.path} ${chalk.dim(`(${d.serverCount} server${d.serverCount === 1 ? '' : 's'})`)}`);
|
|
835
|
-
for (const entryName of Object.keys(d.config.mcpServers)) {
|
|
836
|
-
const sessionName = generateSessionName({ type: 'config', file: d.path, entry: entryName });
|
|
837
|
-
const serverCfg = d.config.mcpServers[entryName];
|
|
838
|
-
const target = serverCfg?.url ?? [serverCfg?.command, ...(serverCfg?.args ?? [])].join(' ');
|
|
839
|
-
const truncated = target && target.length > 72 ? target.slice(0, 72) + '…' : target;
|
|
840
|
-
const isStdio = skippedStdio.some((s) => s.configFile === d.path && s.entry === entryName);
|
|
841
|
-
const isDuplicate = skippedDuplicates.some((s) => s.configFile === d.path && s.entry === entryName);
|
|
842
|
-
if (isStdio) {
|
|
843
|
-
console.log(` ${theme.cyan(sessionName)} → ${chalk.dim(truncated ?? entryName)} ${theme.yellow('○ skipped (stdio)')}`);
|
|
844
|
-
}
|
|
845
|
-
else if (isDuplicate) {
|
|
846
|
-
console.log(` ${theme.cyan(sessionName)} → ${chalk.dim(truncated ?? entryName)} ${chalk.dim('○ skipped (duplicate)')}`);
|
|
847
|
-
}
|
|
848
|
-
else {
|
|
849
|
-
console.log(` ${theme.cyan(sessionName)} → ${chalk.dim(truncated ?? entryName)}`);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
if (entries.length === 0 && !hasApifyToken) {
|
|
854
|
-
throw new ClientError(`All servers in discovered config files use stdio transport.\n` +
|
|
855
|
-
`Pass --stdio to include them: mcpc connect --stdio`);
|
|
856
|
-
}
|
|
857
|
-
const parts = [];
|
|
858
|
-
if (entries.length > 0) {
|
|
859
|
-
parts.push(`Connecting ${entries.length} server${entries.length === 1 ? '' : 's'}`);
|
|
860
|
-
}
|
|
861
|
-
if (skippedStdio.length > 0) {
|
|
862
|
-
parts.push(`skipped ${skippedStdio.length} stdio server${skippedStdio.length === 1 ? '' : 's'}, pass --stdio to include`);
|
|
863
|
-
}
|
|
864
|
-
if (parts.length > 0) {
|
|
865
|
-
console.log(theme.cyan(`\n${parts.join('. ')}.`));
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
const skippedJsonEntries = [
|
|
869
|
-
...skippedStdio.map((s) => ({
|
|
870
|
-
_mcpc: {
|
|
871
|
-
sessionName: s.sessionName,
|
|
872
|
-
configFile: s.configFile,
|
|
873
|
-
entry: s.entry,
|
|
874
|
-
status: 'skipped',
|
|
875
|
-
skipReason: 'stdio',
|
|
876
|
-
},
|
|
877
|
-
})),
|
|
878
|
-
...skippedDuplicates.map((s) => ({
|
|
879
|
-
_mcpc: {
|
|
880
|
-
sessionName: s.sessionName,
|
|
881
|
-
configFile: s.configFile,
|
|
882
|
-
entry: s.entry,
|
|
883
|
-
status: 'skipped',
|
|
884
|
-
skipReason: 'duplicate',
|
|
885
|
-
},
|
|
886
|
-
})),
|
|
887
|
-
];
|
|
888
|
-
if (entries.length === 0) {
|
|
889
|
-
if (!hasApifyToken && options.outputMode === 'json') {
|
|
890
|
-
console.log(formatOutput(skippedJsonEntries, 'json'));
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
await maybeConnectApify([], [], options);
|
|
894
|
-
return;
|
|
895
|
-
}
|
|
896
|
-
const results = await bulkConnectEntries(entries, options);
|
|
897
|
-
if (options.outputMode === 'json') {
|
|
898
|
-
const resultEntries = await buildBulkConnectEntries(results, options);
|
|
899
|
-
console.log(formatOutput([...resultEntries, ...skippedJsonEntries], 'json'));
|
|
900
|
-
return;
|
|
901
|
-
}
|
|
902
|
-
const { active, connecting, failed } = printBulkConnectSummary(results, options);
|
|
903
|
-
await maybeConnectApify(entries, results, options);
|
|
904
|
-
if (active + connecting === 0 && failed > 0) {
|
|
905
|
-
throw new ClientError(`Failed to connect any servers from discovered config files`);
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
const APIFY_MCP_URL = 'https://mcp.apify.com';
|
|
909
|
-
const APIFY_SESSION_NAME = '@apify';
|
|
910
|
-
async function maybeConnectApify(configEntries, configResults, options) {
|
|
911
|
-
const token = process.env.APIFY_API_TOKEN;
|
|
912
|
-
if (!token)
|
|
913
|
-
return;
|
|
914
|
-
if (configEntries.some((e) => e.sessionName === APIFY_SESSION_NAME))
|
|
915
|
-
return;
|
|
916
|
-
if (configResults.some((r) => r.sessionName === APIFY_SESSION_NAME))
|
|
917
|
-
return;
|
|
918
|
-
const existing = await getSession(APIFY_SESSION_NAME);
|
|
919
|
-
const isLive = existing && getBridgeStatus(existing) === 'live';
|
|
920
|
-
if (options.outputMode === 'human') {
|
|
921
|
-
console.log(theme.cyan(`\nAPIFY_API_TOKEN detected, connecting to ${APIFY_MCP_URL}...`));
|
|
922
|
-
}
|
|
923
|
-
if (isLive) {
|
|
924
|
-
if (options.outputMode === 'human') {
|
|
925
|
-
console.log(` ${theme.green('●')} ${theme.cyan(APIFY_SESSION_NAME)} ${chalk.dim('already active')}`);
|
|
926
|
-
}
|
|
927
|
-
return;
|
|
928
|
-
}
|
|
929
|
-
try {
|
|
930
|
-
await connectSession(APIFY_MCP_URL, APIFY_SESSION_NAME, {
|
|
931
|
-
outputMode: options.outputMode,
|
|
932
|
-
...(options.verbose && { verbose: true }),
|
|
933
|
-
headers: [`Authorization: Bearer ${token}`],
|
|
934
|
-
skipDetails: true,
|
|
935
|
-
quiet: true,
|
|
936
|
-
noProfile: true,
|
|
937
|
-
});
|
|
938
|
-
if (options.outputMode === 'human') {
|
|
939
|
-
console.log(` ${theme.yellow('●')} ${theme.cyan(APIFY_SESSION_NAME)} ${theme.yellow('connecting')}`);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
catch (error) {
|
|
943
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
944
|
-
if (options.outputMode === 'human') {
|
|
945
|
-
console.log(` ${theme.red('●')} ${theme.cyan(APIFY_SESSION_NAME)} ${theme.red('failed')}${chalk.dim(` — ${msg}`)}`);
|
|
946
|
-
}
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
295
|
export async function openShell(target) {
|
|
296
|
+
console.error(formatWarning('The "shell" command is deprecated and will be removed in a future release.'));
|
|
950
297
|
const { startShell } = await import('../shell.js');
|
|
951
298
|
await startShell(target);
|
|
952
299
|
}
|