aui-agent-builder 0.3.136 → 0.3.138
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 +26 -12
- package/dist/api-client/apollo-client.d.ts +94 -0
- package/dist/api-client/apollo-client.d.ts.map +1 -1
- package/dist/api-client/apollo-client.js +27 -0
- package/dist/api-client/apollo-client.js.map +1 -1
- package/dist/commands/agents.d.ts +8 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +191 -25
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +46 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/integration-mcp-test.d.ts +60 -0
- package/dist/commands/integration-mcp-test.d.ts.map +1 -0
- package/dist/commands/integration-mcp-test.js +862 -0
- package/dist/commands/integration-mcp-test.js.map +1 -0
- package/dist/commands/integration.d.ts +10 -0
- package/dist/commands/integration.d.ts.map +1 -1
- package/dist/commands/integration.js +259 -72
- package/dist/commands/integration.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -3
- package/dist/index.js.map +1 -1
- package/dist/services/integration.service.d.ts +86 -13
- package/dist/services/integration.service.d.ts.map +1 -1
- package/dist/services/integration.service.js +152 -74
- package/dist/services/integration.service.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,862 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* `aui integration mcp-test` — execute an MCP tool and return the response.
|
|
4
|
+
*
|
|
5
|
+
* POST /apollo-api/v1/integrations/mcp/test
|
|
6
|
+
*
|
|
7
|
+
* Non-interactive when:
|
|
8
|
+
* COMPOSIO: --toolkit + --tool are present
|
|
9
|
+
* DIRECT: --url + --tool are present
|
|
10
|
+
* or when --json is set. Otherwise prompts for missing inputs.
|
|
11
|
+
*
|
|
12
|
+
* Output is always a JSON envelope.
|
|
13
|
+
*/
|
|
14
|
+
import { readFileSync } from "fs";
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
16
|
+
import { render, Box, Text } from "ink";
|
|
17
|
+
import inquirer from "inquirer";
|
|
18
|
+
import chalk from "chalk";
|
|
19
|
+
import open from "open";
|
|
20
|
+
import { getAuthenticatedSession, fetchComposioApiKey, listComposioToolkits, getComposioToolkitAuthInfo, connectComposioToolkit, waitForComposioConnection, discoverComposioTools, discoverMCPTools, resolveScopeIds, resetComposioClient, } from "../services/integration.service.js";
|
|
21
|
+
import { ApolloClient, } from "../api-client/apollo-client.js";
|
|
22
|
+
import { getConfig, loadProjectConfig, findProjectRoot, loadSession, } from "../config/index.js";
|
|
23
|
+
import { AuthenticationError, ConfigError, ValidationError, } from "../errors/index.js";
|
|
24
|
+
import { isJsonMode, outputJson, outputJsonError, stderrLog, } from "../utils/json-output.js";
|
|
25
|
+
import { Spinner, StatusLine, ErrorDisplay, } from "../ui/components/index.js";
|
|
26
|
+
// ─── Local helpers ───
|
|
27
|
+
function log(node) {
|
|
28
|
+
const { unmount } = render(node);
|
|
29
|
+
unmount();
|
|
30
|
+
}
|
|
31
|
+
function startSpinner(label) {
|
|
32
|
+
const inst = render(_jsx(Spinner, { label: label }));
|
|
33
|
+
return {
|
|
34
|
+
succeed(msg) { inst.unmount(); log(_jsx(StatusLine, { kind: "success", label: msg })); },
|
|
35
|
+
fail(msg) { inst.unmount(); log(_jsx(StatusLine, { kind: "error", label: msg })); },
|
|
36
|
+
stop() { inst.unmount(); },
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function parseJsonOption(value, flag) {
|
|
40
|
+
if (value === undefined)
|
|
41
|
+
return undefined;
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(value);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
throw new ValidationError(`Invalid JSON passed to ${flag}: ${e instanceof Error ? e.message : String(e)}`, { suggestion: `Pass valid JSON to ${flag}. Wrap it in single quotes so your shell doesn't mangle it.` });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function loadCredentialsFile(filePath) {
|
|
50
|
+
let raw;
|
|
51
|
+
try {
|
|
52
|
+
raw = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
throw new Error(`Cannot read credentials file '${filePath}': ${err instanceof Error ? err.message : String(err)}`);
|
|
56
|
+
}
|
|
57
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
58
|
+
throw new Error(`Credentials file '${filePath}' must be a JSON object.`);
|
|
59
|
+
}
|
|
60
|
+
const obj = raw;
|
|
61
|
+
const creds = {};
|
|
62
|
+
if (obj.client_id)
|
|
63
|
+
creds.clientId = String(obj.client_id);
|
|
64
|
+
if (obj.client_secret)
|
|
65
|
+
creds.clientSecret = String(obj.client_secret);
|
|
66
|
+
if (obj.bearer_token)
|
|
67
|
+
creds.bearerToken = String(obj.bearer_token);
|
|
68
|
+
if (!creds.clientId && !creds.clientSecret && !creds.bearerToken) {
|
|
69
|
+
throw new Error(`Credentials file '${filePath}' must contain at least one of: client_id, client_secret, bearer_token.`);
|
|
70
|
+
}
|
|
71
|
+
return creds;
|
|
72
|
+
}
|
|
73
|
+
function buildApolloClient() {
|
|
74
|
+
const projectRoot = findProjectRoot() || undefined;
|
|
75
|
+
const projectConfig = loadProjectConfig(projectRoot);
|
|
76
|
+
const config = getConfig(projectRoot);
|
|
77
|
+
const session = loadSession();
|
|
78
|
+
if (!config.authToken) {
|
|
79
|
+
throw new AuthenticationError("Not logged in.", { suggestion: "Run `aui login` first." });
|
|
80
|
+
}
|
|
81
|
+
const organizationId = projectConfig?.organization_id || config.organizationId;
|
|
82
|
+
if (!organizationId) {
|
|
83
|
+
throw new ConfigError("No organization could be resolved.", {
|
|
84
|
+
suggestion: "Run `aui login` or run inside an imported agent project so .auirc carries organization_id.",
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return new ApolloClient({
|
|
88
|
+
authToken: config.authToken,
|
|
89
|
+
organizationId,
|
|
90
|
+
accountId: projectConfig?.account_id || config.accountId || "",
|
|
91
|
+
userId: session?.user_id,
|
|
92
|
+
environment: config.environment,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
// ─── Shared: bundle mapping prompt ───
|
|
96
|
+
async function promptBundleMapping(options, noPrompt) {
|
|
97
|
+
if (options.raw)
|
|
98
|
+
return null;
|
|
99
|
+
const hasBundleFlags = !!(options.bundleMapping || options.responseMapping ||
|
|
100
|
+
options.parameters || options.scopeEntities);
|
|
101
|
+
// If flags are present, assemble from them directly
|
|
102
|
+
if (hasBundleFlags || noPrompt) {
|
|
103
|
+
const base = options.bundleMapping !== undefined
|
|
104
|
+
? (parseJsonOption(options.bundleMapping, "--bundle-mapping") ?? {})
|
|
105
|
+
: {};
|
|
106
|
+
const bundle = { ...base };
|
|
107
|
+
if (options.responseMapping !== undefined)
|
|
108
|
+
bundle.response_mapping = parseJsonOption(options.responseMapping, "--response-mapping");
|
|
109
|
+
if (options.parameters !== undefined)
|
|
110
|
+
bundle.parameters = parseJsonOption(options.parameters, "--parameters");
|
|
111
|
+
if (options.scopeEntities !== undefined)
|
|
112
|
+
bundle.scope_entities = parseJsonOption(options.scopeEntities, "--scope-entities");
|
|
113
|
+
return Object.keys(bundle).length > 0 ? bundle : null;
|
|
114
|
+
}
|
|
115
|
+
const { includeMapping } = await inquirer.prompt([{
|
|
116
|
+
type: "list", name: "includeMapping",
|
|
117
|
+
message: "Include bundle mapping? (returns mapped_entities alongside tool response)",
|
|
118
|
+
choices: [
|
|
119
|
+
{ name: "Yes — send bundle_mapping", value: true },
|
|
120
|
+
{ name: "No — raw tool response only", value: false },
|
|
121
|
+
],
|
|
122
|
+
}]);
|
|
123
|
+
if (!includeMapping)
|
|
124
|
+
return null;
|
|
125
|
+
const answers = await inquirer.prompt([
|
|
126
|
+
{ type: "input", name: "responseMapping", message: "Response mapping JSON (from integrations.aui.json, or Enter to skip):" },
|
|
127
|
+
{ type: "input", name: "parameters", message: "Parameters JSON (from parameters.aui.json, or Enter to skip):" },
|
|
128
|
+
{ type: "input", name: "scopeEntities", message: "Scope entities JSON (from entities.aui.json, or Enter to skip):" },
|
|
129
|
+
]);
|
|
130
|
+
const bundle = {};
|
|
131
|
+
for (const [key, raw] of [
|
|
132
|
+
["response_mapping", answers.responseMapping],
|
|
133
|
+
["parameters", answers.parameters],
|
|
134
|
+
["scope_entities", answers.scopeEntities],
|
|
135
|
+
]) {
|
|
136
|
+
if (raw?.trim()) {
|
|
137
|
+
try {
|
|
138
|
+
bundle[key] = JSON.parse(raw.trim());
|
|
139
|
+
}
|
|
140
|
+
catch { /* ignore */ }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return Object.keys(bundle).length > 0 ? bundle : null;
|
|
144
|
+
}
|
|
145
|
+
// ─── Shared: per-field argument prompting ───
|
|
146
|
+
async function promptToolArguments(tool, options, noPrompt) {
|
|
147
|
+
if (options.arguments) {
|
|
148
|
+
return parseJsonOption(options.arguments, "--arguments");
|
|
149
|
+
}
|
|
150
|
+
if (noPrompt)
|
|
151
|
+
return {};
|
|
152
|
+
const schema = (tool?.input_schema ?? tool?.inputSchema);
|
|
153
|
+
const properties = schema?.properties;
|
|
154
|
+
const required = schema?.required ?? [];
|
|
155
|
+
if (!properties || Object.keys(properties).length === 0) {
|
|
156
|
+
const { args } = await inquirer.prompt([{
|
|
157
|
+
type: "input", name: "args",
|
|
158
|
+
message: "Arguments as JSON (Enter to skip):",
|
|
159
|
+
validate: (v) => {
|
|
160
|
+
if (!v.trim())
|
|
161
|
+
return true;
|
|
162
|
+
try {
|
|
163
|
+
JSON.parse(v);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return "Must be valid JSON";
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
}]);
|
|
171
|
+
return args.trim() ? JSON.parse(args.trim()) : {};
|
|
172
|
+
}
|
|
173
|
+
const result = {};
|
|
174
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
175
|
+
const isRequired = required.includes(key);
|
|
176
|
+
const descSuffix = prop.description ? chalk.gray(` — ${prop.description}`) : "";
|
|
177
|
+
const reqMark = isRequired ? chalk.red(" (required)") : chalk.gray(" (optional)");
|
|
178
|
+
const label = `${key}${reqMark}${descSuffix}`;
|
|
179
|
+
if (prop.type === "boolean") {
|
|
180
|
+
const { val } = await inquirer.prompt([{
|
|
181
|
+
type: "list", name: "val",
|
|
182
|
+
message: label,
|
|
183
|
+
choices: [
|
|
184
|
+
{ name: "true", value: true },
|
|
185
|
+
{ name: "false", value: false },
|
|
186
|
+
...(!isRequired ? [{ name: "(skip)", value: "__skip__" }] : []),
|
|
187
|
+
],
|
|
188
|
+
}]);
|
|
189
|
+
if (val !== "__skip__")
|
|
190
|
+
result[key] = val;
|
|
191
|
+
}
|
|
192
|
+
else if (prop.type === "number" || prop.type === "integer") {
|
|
193
|
+
const { val } = await inquirer.prompt([{
|
|
194
|
+
type: "input", name: "val", message: label,
|
|
195
|
+
validate: (v) => {
|
|
196
|
+
if (!v.trim())
|
|
197
|
+
return isRequired ? `${key} is required` : true;
|
|
198
|
+
return isNaN(Number(v)) ? "Must be a number" : true;
|
|
199
|
+
},
|
|
200
|
+
}]);
|
|
201
|
+
if (val.trim())
|
|
202
|
+
result[key] = Number(val.trim());
|
|
203
|
+
}
|
|
204
|
+
else if (prop.type === "array" || prop.type === "object") {
|
|
205
|
+
const { val } = await inquirer.prompt([{
|
|
206
|
+
type: "input", name: "val", message: `${label} ${chalk.gray("(JSON)")}`,
|
|
207
|
+
validate: (v) => {
|
|
208
|
+
if (!v.trim())
|
|
209
|
+
return isRequired ? `${key} is required` : true;
|
|
210
|
+
try {
|
|
211
|
+
JSON.parse(v);
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
return "Must be valid JSON";
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
}]);
|
|
219
|
+
if (val.trim())
|
|
220
|
+
result[key] = JSON.parse(val.trim());
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
// string or unknown type
|
|
224
|
+
const { val } = await inquirer.prompt([{
|
|
225
|
+
type: "input", name: "val", message: label,
|
|
226
|
+
validate: (v) => (!v.trim() && isRequired) ? `${key} is required` : true,
|
|
227
|
+
}]);
|
|
228
|
+
if (val.trim())
|
|
229
|
+
result[key] = val.trim();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
// ─── Shared: single-tool picker ───
|
|
235
|
+
async function pickTool(tools, toolFlag, noPrompt) {
|
|
236
|
+
if (toolFlag) {
|
|
237
|
+
const found = tools.find((t) => t.name === toolFlag);
|
|
238
|
+
if (!found) {
|
|
239
|
+
const available = tools.map((t) => t.name).join(", ");
|
|
240
|
+
const msg = `Tool '${toolFlag}' not found. Available: ${available}`;
|
|
241
|
+
if (isJsonMode()) {
|
|
242
|
+
outputJsonError({ code: "VALIDATION_ERROR", message: msg });
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
log(_jsx(ErrorDisplay, { message: msg }));
|
|
246
|
+
}
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
return found;
|
|
250
|
+
}
|
|
251
|
+
if (noPrompt) {
|
|
252
|
+
const available = tools.map((t) => t.name).join(", ");
|
|
253
|
+
const msg = "Missing required option: --tool";
|
|
254
|
+
if (isJsonMode()) {
|
|
255
|
+
outputJsonError({ code: "VALIDATION_ERROR", message: msg, suggestion: `Available: ${available}` });
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
log(_jsx(ErrorDisplay, { message: msg, suggestion: `Available: ${available}` }));
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
let toolsToShow = tools;
|
|
263
|
+
if (tools.length > 15) {
|
|
264
|
+
const { filterAction } = await inquirer.prompt([{
|
|
265
|
+
type: "list", name: "filterAction",
|
|
266
|
+
message: `${tools.length} tools available. How would you like to proceed?`,
|
|
267
|
+
choices: [
|
|
268
|
+
{ name: "Browse and pick", value: "browse" },
|
|
269
|
+
{ name: "Search tools first", value: "search" },
|
|
270
|
+
],
|
|
271
|
+
}]);
|
|
272
|
+
if (filterAction === "search") {
|
|
273
|
+
const { q } = await inquirer.prompt([{
|
|
274
|
+
type: "input", name: "q", message: "Search tools by name or description:",
|
|
275
|
+
validate: (v) => v.trim().length > 0 || "Enter a search term",
|
|
276
|
+
}]);
|
|
277
|
+
const term = q.trim().toLowerCase();
|
|
278
|
+
const filtered = tools.filter((t) => t.name.toLowerCase().includes(term) ||
|
|
279
|
+
(t.description || "").toLowerCase().includes(term));
|
|
280
|
+
toolsToShow = filtered.length > 0 ? filtered : tools;
|
|
281
|
+
if (filtered.length === 0) {
|
|
282
|
+
log(_jsx(StatusLine, { kind: "warning", label: "No tools match your search \u2014 showing all tools." }));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const { selected } = await inquirer.prompt([{
|
|
287
|
+
type: "list", name: "selected",
|
|
288
|
+
message: "Select a tool to execute:",
|
|
289
|
+
choices: toolsToShow.map((t) => ({
|
|
290
|
+
name: `${t.name} ${chalk.gray(`— ${t.description || "No description"}`)}`,
|
|
291
|
+
value: t,
|
|
292
|
+
})),
|
|
293
|
+
pageSize: 20,
|
|
294
|
+
}]);
|
|
295
|
+
return selected;
|
|
296
|
+
}
|
|
297
|
+
// ─── COMPOSIO flow ───
|
|
298
|
+
async function composioFlow(session, options, noPrompt) {
|
|
299
|
+
// 1. API key
|
|
300
|
+
let apiKey = options.composioApiKey || process.env.COMPOSIO_API_KEY || "";
|
|
301
|
+
if (!apiKey) {
|
|
302
|
+
const fetched = await fetchComposioApiKey(session);
|
|
303
|
+
if (fetched) {
|
|
304
|
+
apiKey = fetched;
|
|
305
|
+
if (!isJsonMode())
|
|
306
|
+
log(_jsx(StatusLine, { kind: "success", label: "Integration configuration loaded" }));
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
const msg = "Integration API key not configured. Contact your administrator.";
|
|
310
|
+
if (noPrompt) {
|
|
311
|
+
if (isJsonMode()) {
|
|
312
|
+
outputJsonError({ code: "CONFIG_ERROR", message: msg });
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
log(_jsx(ErrorDisplay, { message: msg, suggestion: "Pass --composio-api-key or have your administrator configure it." }));
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
const { key } = await inquirer.prompt([{
|
|
320
|
+
type: "password", name: "key", message: "Integration API Key:", mask: "*",
|
|
321
|
+
validate: (v) => v.trim().length > 0 || "API key is required",
|
|
322
|
+
}]);
|
|
323
|
+
apiKey = key.trim();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// 2. Toolkit
|
|
327
|
+
let toolkitSlug = options.toolkit || "";
|
|
328
|
+
let selectedToolkit = null;
|
|
329
|
+
if (!toolkitSlug) {
|
|
330
|
+
if (noPrompt) {
|
|
331
|
+
const msg = "Missing required option: --toolkit";
|
|
332
|
+
if (isJsonMode()) {
|
|
333
|
+
outputJsonError({ code: "VALIDATION_ERROR", message: msg });
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
log(_jsx(ErrorDisplay, { message: msg, suggestion: "Pass --toolkit <slug>." }));
|
|
337
|
+
}
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
const { searchQuery } = await inquirer.prompt([{
|
|
341
|
+
type: "input", name: "searchQuery",
|
|
342
|
+
message: "Search toolkits (or press Enter to browse all):",
|
|
343
|
+
}]);
|
|
344
|
+
let searchTerm = searchQuery?.trim() || undefined;
|
|
345
|
+
const toolkitSpinner = startSpinner(searchTerm ? `Searching toolkits for "${searchTerm}"...` : "Fetching available toolkits...");
|
|
346
|
+
let toolkits;
|
|
347
|
+
let nextCursor;
|
|
348
|
+
try {
|
|
349
|
+
const result = await listComposioToolkits(apiKey, { limit: 50, search: searchTerm });
|
|
350
|
+
toolkits = result.items;
|
|
351
|
+
nextCursor = result.nextCursor;
|
|
352
|
+
toolkitSpinner.succeed(`Found ${toolkits.length} toolkits`);
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
toolkitSpinner.fail("Failed to fetch toolkits");
|
|
356
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
357
|
+
if (msg.includes("401") || msg.includes("Unauthorized") || msg.includes("invalid")) {
|
|
358
|
+
log(_jsx(ErrorDisplay, { message: "Invalid API key.", suggestion: "Contact your administrator for a valid key." }));
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
log(_jsx(ErrorDisplay, { error: error, message: "Failed to fetch toolkits." }));
|
|
362
|
+
}
|
|
363
|
+
resetComposioClient();
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
if (toolkits.length === 0) {
|
|
367
|
+
log(_jsx(ErrorDisplay, { message: searchTerm ? `No toolkits found matching "${searchTerm}".` : "No toolkits found.", suggestion: searchTerm ? "Try a different search term." : "Verify your API key is valid." }));
|
|
368
|
+
resetComposioClient();
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
let picked = false;
|
|
372
|
+
let scrollToIndex = 0;
|
|
373
|
+
while (!picked) {
|
|
374
|
+
const choices = toolkits.map((tk, idx) => ({
|
|
375
|
+
name: `${tk.name} ${chalk.gray(`(${tk.slug})`)}${tk.toolsCount ? chalk.gray(` — ${tk.toolsCount} tools`) : ""}`,
|
|
376
|
+
value: { type: "toolkit", toolkit: tk, idx },
|
|
377
|
+
}));
|
|
378
|
+
if (nextCursor) {
|
|
379
|
+
choices.push(new inquirer.Separator());
|
|
380
|
+
choices.push({ name: chalk.cyan("↓ Load more toolkits..."), value: { type: "load_more" } });
|
|
381
|
+
}
|
|
382
|
+
choices.push(new inquirer.Separator());
|
|
383
|
+
choices.push({ name: chalk.yellow("⌕ Search again..."), value: { type: "search_again" } });
|
|
384
|
+
const defaultValue = scrollToIndex > 0 && scrollToIndex < choices.length
|
|
385
|
+
? choices[scrollToIndex]?.value
|
|
386
|
+
: undefined;
|
|
387
|
+
const { chosen } = await inquirer.prompt([{
|
|
388
|
+
type: "list", name: "chosen",
|
|
389
|
+
message: `Select a toolkit (${toolkits.length} loaded):`,
|
|
390
|
+
choices, default: defaultValue, pageSize: 20,
|
|
391
|
+
}]);
|
|
392
|
+
if (chosen.type === "load_more" && nextCursor) {
|
|
393
|
+
const prev = toolkits.length;
|
|
394
|
+
const moreSpinner = startSpinner("Loading more toolkits...");
|
|
395
|
+
try {
|
|
396
|
+
const more = await listComposioToolkits(apiKey, { limit: 50, cursor: nextCursor, search: searchTerm });
|
|
397
|
+
toolkits.push(...more.items);
|
|
398
|
+
nextCursor = more.nextCursor;
|
|
399
|
+
moreSpinner.succeed(`Loaded ${more.items.length} more (${toolkits.length} total)`);
|
|
400
|
+
scrollToIndex = prev;
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
moreSpinner.fail("Failed to load more");
|
|
404
|
+
nextCursor = undefined;
|
|
405
|
+
scrollToIndex = 0;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
else if (chosen.type === "search_again") {
|
|
409
|
+
const { newSearch } = await inquirer.prompt([{ type: "input", name: "newSearch", message: "Search toolkits:" }]);
|
|
410
|
+
const q = newSearch?.trim() || undefined;
|
|
411
|
+
searchTerm = q;
|
|
412
|
+
const searchSpinner = startSpinner(q ? `Searching for "${q}"...` : "Fetching toolkits...");
|
|
413
|
+
try {
|
|
414
|
+
const result = await listComposioToolkits(apiKey, { limit: 50, search: q });
|
|
415
|
+
toolkits = result.items;
|
|
416
|
+
nextCursor = result.nextCursor;
|
|
417
|
+
searchSpinner.succeed(`Found ${toolkits.length} toolkits`);
|
|
418
|
+
scrollToIndex = 0;
|
|
419
|
+
if (toolkits.length === 0) {
|
|
420
|
+
log(_jsx(StatusLine, { kind: "warning", label: q ? `No toolkits match "${q}". Try again.` : "No toolkits found." }));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
catch {
|
|
424
|
+
searchSpinner.fail("Search failed");
|
|
425
|
+
scrollToIndex = 0;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
selectedToolkit = chosen.toolkit;
|
|
430
|
+
toolkitSlug = selectedToolkit.slug;
|
|
431
|
+
picked = true;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
log(_jsx(StatusLine, { kind: "success", label: `Toolkit: ${selectedToolkit.name} (${toolkitSlug})` }));
|
|
435
|
+
}
|
|
436
|
+
// 3. User ID (random if not provided)
|
|
437
|
+
let userId = options.userId?.trim() || "";
|
|
438
|
+
if (!userId) {
|
|
439
|
+
if (!noPrompt) {
|
|
440
|
+
const { id } = await inquirer.prompt([{
|
|
441
|
+
type: "input", name: "id",
|
|
442
|
+
message: "Composio user ID (press Enter to generate a random one):",
|
|
443
|
+
}]);
|
|
444
|
+
userId = id.trim() || randomUUID();
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
userId = randomUUID();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// 4. Auth scheme + BYO credentials (identical to nativeIntegrationFlow)
|
|
451
|
+
let authCredentials;
|
|
452
|
+
let selectedAuthScheme;
|
|
453
|
+
{
|
|
454
|
+
let authInfo = null;
|
|
455
|
+
try {
|
|
456
|
+
authInfo = await getComposioToolkitAuthInfo(apiKey, toolkitSlug);
|
|
457
|
+
}
|
|
458
|
+
catch { }
|
|
459
|
+
if (authInfo && !authInfo.isNoAuth && authInfo.schemes.length > 0) {
|
|
460
|
+
let pickedScheme;
|
|
461
|
+
if (options.composioAuthScheme) {
|
|
462
|
+
const found = authInfo.schemes.find((s) => s.scheme.toUpperCase() === options.composioAuthScheme.toUpperCase());
|
|
463
|
+
if (!found) {
|
|
464
|
+
const msg = `Unknown auth scheme '${options.composioAuthScheme}'. Available: ${authInfo.schemes.map((s) => s.scheme).join(", ")}`;
|
|
465
|
+
if (isJsonMode()) {
|
|
466
|
+
outputJsonError({ code: "VALIDATION_ERROR", message: msg });
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
log(_jsx(ErrorDisplay, { message: msg }));
|
|
470
|
+
}
|
|
471
|
+
resetComposioClient();
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
pickedScheme = found;
|
|
475
|
+
}
|
|
476
|
+
else if (authInfo.schemes.length === 1 || noPrompt) {
|
|
477
|
+
pickedScheme = authInfo.schemes[0];
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
const { chosen } = await inquirer.prompt([{
|
|
481
|
+
type: "list", name: "chosen", message: "Select authentication method:",
|
|
482
|
+
choices: authInfo.schemes.map((s) => ({
|
|
483
|
+
name: `${s.scheme}${s.isManaged ? chalk.gray(" (Composio-managed)") : chalk.yellow(" (BYO credentials)")}`,
|
|
484
|
+
value: s,
|
|
485
|
+
})),
|
|
486
|
+
}]);
|
|
487
|
+
pickedScheme = chosen;
|
|
488
|
+
}
|
|
489
|
+
selectedAuthScheme = pickedScheme.scheme;
|
|
490
|
+
if (!pickedScheme.isManaged && pickedScheme.isOAuth) {
|
|
491
|
+
if (noPrompt) {
|
|
492
|
+
if (options.composioCredentialsFile) {
|
|
493
|
+
try {
|
|
494
|
+
authCredentials = loadCredentialsFile(options.composioCredentialsFile);
|
|
495
|
+
}
|
|
496
|
+
catch (err) {
|
|
497
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
498
|
+
if (isJsonMode()) {
|
|
499
|
+
outputJsonError({ code: "VALIDATION_ERROR", message: msg });
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
log(_jsx(ErrorDisplay, { message: msg }));
|
|
503
|
+
}
|
|
504
|
+
resetComposioClient();
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
else if (options.composioClientId && options.composioClientSecret) {
|
|
509
|
+
authCredentials = {
|
|
510
|
+
clientId: options.composioClientId,
|
|
511
|
+
clientSecret: options.composioClientSecret,
|
|
512
|
+
...(options.composioClientBearerToken && { bearerToken: options.composioClientBearerToken }),
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
const msg = `Scheme '${pickedScheme.scheme}' requires BYO credentials. Pass --composio-credentials-file or --composio-client-id + --composio-client-secret.`;
|
|
517
|
+
if (isJsonMode()) {
|
|
518
|
+
outputJsonError({ code: "VALIDATION_ERROR", message: msg });
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
log(_jsx(ErrorDisplay, { message: msg }));
|
|
522
|
+
}
|
|
523
|
+
resetComposioClient();
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
log(_jsx(Box, { marginY: 1, children: _jsxs(Text, { color: "yellow", children: [" ", "\u26A0 This scheme is not Composio-managed. Please provide your own credentials."] }) }));
|
|
529
|
+
const { credInput } = await inquirer.prompt([{
|
|
530
|
+
type: "list", name: "credInput", message: "How would you like to provide credentials?",
|
|
531
|
+
choices: [{ name: "Enter manually", value: "manual" }, { name: "Load from credentials.json file", value: "file" }],
|
|
532
|
+
}]);
|
|
533
|
+
if (credInput === "file") {
|
|
534
|
+
const { filePath } = await inquirer.prompt([{
|
|
535
|
+
type: "input", name: "filePath", message: "Path to credentials.json:",
|
|
536
|
+
validate: (v) => v.trim().length > 0 || "File path is required",
|
|
537
|
+
}]);
|
|
538
|
+
try {
|
|
539
|
+
authCredentials = loadCredentialsFile(filePath.trim());
|
|
540
|
+
}
|
|
541
|
+
catch (err) {
|
|
542
|
+
log(_jsx(ErrorDisplay, { message: err instanceof Error ? err.message : String(err) }));
|
|
543
|
+
resetComposioClient();
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
const answers = await inquirer.prompt([
|
|
549
|
+
{ type: "input", name: "clientId", message: "OAuth Client ID:", validate: (v) => v.trim().length > 0 || "Client ID is required" },
|
|
550
|
+
{ type: "password", name: "clientSecret", message: "OAuth Client Secret:", mask: "*", validate: (v) => v.trim().length > 0 || "Client Secret is required" },
|
|
551
|
+
{ type: "password", name: "bearerToken", message: "Bearer Token (optional):", mask: "*" },
|
|
552
|
+
]);
|
|
553
|
+
authCredentials = {
|
|
554
|
+
clientId: answers.clientId.trim(),
|
|
555
|
+
clientSecret: answers.clientSecret.trim(),
|
|
556
|
+
...(answers.bearerToken?.trim() && { bearerToken: answers.bearerToken.trim() }),
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
// 5. Connect toolkit
|
|
564
|
+
if (isJsonMode()) {
|
|
565
|
+
stderrLog("Connecting toolkit...");
|
|
566
|
+
}
|
|
567
|
+
const connectSpinner = isJsonMode() ? null : startSpinner(`Connecting ${toolkitSlug}...`);
|
|
568
|
+
let connectResult;
|
|
569
|
+
try {
|
|
570
|
+
connectResult = await connectComposioToolkit(apiKey, userId, toolkitSlug, {
|
|
571
|
+
authCredentials, authScheme: selectedAuthScheme,
|
|
572
|
+
});
|
|
573
|
+
connectSpinner?.succeed(connectResult.redirectUrl ? "Authentication link ready" : `Already connected — account ${connectResult.id}`);
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
connectSpinner?.fail("Failed to connect toolkit");
|
|
577
|
+
if (isJsonMode()) {
|
|
578
|
+
outputJsonError({ code: "API_CLIENT_ERROR", message: error instanceof Error ? error.message : String(error) });
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
log(_jsx(ErrorDisplay, { error: error, message: "Failed to connect toolkit." }));
|
|
582
|
+
}
|
|
583
|
+
resetComposioClient();
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
// 6. OAuth browser flow (if redirect required)
|
|
587
|
+
if (connectResult.redirectUrl) {
|
|
588
|
+
if (noPrompt) {
|
|
589
|
+
const msg = "Authentication required. Complete the OAuth flow in a browser before running in non-interactive mode.";
|
|
590
|
+
if (isJsonMode()) {
|
|
591
|
+
outputJsonError({ code: "AUTH_REQUIRED", message: msg, suggestion: connectResult.redirectUrl });
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
log(_jsx(ErrorDisplay, { message: msg, suggestion: `Visit: ${connectResult.redirectUrl}` }));
|
|
595
|
+
}
|
|
596
|
+
resetComposioClient();
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
log(_jsxs(Box, { flexDirection: "column", marginY: 1, children: [_jsxs(Text, { color: "cyan", children: [" ", "Opening browser for authentication..."] }), _jsxs(Text, { color: "gray", children: [" ", "If the browser doesn't open, visit:", "\n", " ", _jsx(Text, { color: "cyan", underline: true, children: connectResult.redirectUrl })] })] }));
|
|
600
|
+
try {
|
|
601
|
+
await open(connectResult.redirectUrl);
|
|
602
|
+
}
|
|
603
|
+
catch { }
|
|
604
|
+
const waitSpinner = startSpinner("Waiting for authentication (up to 2 minutes)...");
|
|
605
|
+
try {
|
|
606
|
+
const connected = await waitForComposioConnection({ redirectUrl: connectResult.redirectUrl, connectionRequest: connectResult.connectionRequest }, 120000);
|
|
607
|
+
waitSpinner.succeed(`Authenticated — account ${connected.id}`);
|
|
608
|
+
}
|
|
609
|
+
catch {
|
|
610
|
+
waitSpinner.fail("Authentication timed out or failed");
|
|
611
|
+
log(_jsx(ErrorDisplay, { message: "Authentication timed out.", suggestion: "Try again \u2014 make sure to complete the auth flow in the browser within 2 minutes." }));
|
|
612
|
+
resetComposioClient();
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
// 7. Discover tools
|
|
617
|
+
if (isJsonMode()) {
|
|
618
|
+
stderrLog("Discovering tools...");
|
|
619
|
+
}
|
|
620
|
+
const discoverSpinner = isJsonMode() ? null : startSpinner(`Discovering tools for ${toolkitSlug}...`);
|
|
621
|
+
let discoveredTools;
|
|
622
|
+
try {
|
|
623
|
+
const result = await discoverComposioTools(apiKey, toolkitSlug);
|
|
624
|
+
discoveredTools = result.tools;
|
|
625
|
+
if (discoveredTools.length === 0) {
|
|
626
|
+
if (isJsonMode()) {
|
|
627
|
+
outputJsonError({ code: "API_CLIENT_ERROR", message: `No tools found for toolkit: ${toolkitSlug}` });
|
|
628
|
+
}
|
|
629
|
+
discoverSpinner?.fail("No tools found");
|
|
630
|
+
resetComposioClient();
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
const totalLabel = result.totalItems
|
|
634
|
+
? `${discoveredTools.length} of ${result.totalItems}`
|
|
635
|
+
: `${discoveredTools.length}`;
|
|
636
|
+
discoverSpinner?.succeed(`Discovered ${totalLabel} tools for ${toolkitSlug}`);
|
|
637
|
+
}
|
|
638
|
+
catch (error) {
|
|
639
|
+
discoverSpinner?.fail("Discovery failed");
|
|
640
|
+
if (isJsonMode()) {
|
|
641
|
+
outputJsonError({ code: "API_CLIENT_ERROR", message: `Discovery failed: ${error instanceof Error ? error.message : String(error)}` });
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
log(_jsx(ErrorDisplay, { error: error, message: "Failed to discover tools." }));
|
|
645
|
+
}
|
|
646
|
+
resetComposioClient();
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
// 8. Tool selection
|
|
650
|
+
const selectedTool = await pickTool(discoveredTools, options.tool, noPrompt);
|
|
651
|
+
if (!selectedTool) {
|
|
652
|
+
resetComposioClient();
|
|
653
|
+
return null;
|
|
654
|
+
}
|
|
655
|
+
// 9. Arguments — prompted per-field from input_schema when in interactive mode
|
|
656
|
+
const toolArguments = await promptToolArguments(selectedTool, options, noPrompt);
|
|
657
|
+
// 10. Bundle mapping
|
|
658
|
+
const bundleMapping = await promptBundleMapping(options, noPrompt);
|
|
659
|
+
resetComposioClient();
|
|
660
|
+
return {
|
|
661
|
+
type: "COMPOSIO",
|
|
662
|
+
user_id: userId,
|
|
663
|
+
toolkit: toolkitSlug,
|
|
664
|
+
tool: selectedTool.name,
|
|
665
|
+
arguments: toolArguments,
|
|
666
|
+
...(bundleMapping ? { bundle_mapping: bundleMapping } : {}),
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
// ─── DIRECT flow ───
|
|
670
|
+
async function directFlow(session, options, noPrompt) {
|
|
671
|
+
// 1. Server URL
|
|
672
|
+
let serverUrl = options.url || "";
|
|
673
|
+
if (!serverUrl) {
|
|
674
|
+
if (noPrompt) {
|
|
675
|
+
const msg = "Missing required option: --url";
|
|
676
|
+
if (isJsonMode()) {
|
|
677
|
+
outputJsonError({ code: "VALIDATION_ERROR", message: msg });
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
log(_jsx(ErrorDisplay, { message: msg, suggestion: "Pass --url <mcp-server-url>." }));
|
|
681
|
+
}
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
const { url } = await inquirer.prompt([{
|
|
685
|
+
type: "input", name: "url", message: "MCP server URL:",
|
|
686
|
+
validate: (v) => {
|
|
687
|
+
const t = v.trim();
|
|
688
|
+
if (!t)
|
|
689
|
+
return "URL is required";
|
|
690
|
+
try {
|
|
691
|
+
new URL(t);
|
|
692
|
+
return true;
|
|
693
|
+
}
|
|
694
|
+
catch {
|
|
695
|
+
return "Must be a valid URL";
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
}]);
|
|
699
|
+
serverUrl = url.trim();
|
|
700
|
+
}
|
|
701
|
+
// 2. Auth
|
|
702
|
+
let authentication = null;
|
|
703
|
+
if (options.authType && options.authType.toUpperCase() !== "NONE") {
|
|
704
|
+
authentication = {
|
|
705
|
+
type: options.authType.toUpperCase(),
|
|
706
|
+
...(options.authToken ? { token: options.authToken } : {}),
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
else if (!options.authType && !noPrompt) {
|
|
710
|
+
const { authMethod } = await inquirer.prompt([{
|
|
711
|
+
type: "list", name: "authMethod", message: "Authentication method:",
|
|
712
|
+
choices: [{ name: "None", value: "none" }, { name: "Token", value: "token" }],
|
|
713
|
+
}]);
|
|
714
|
+
if (authMethod === "token") {
|
|
715
|
+
const { token } = await inquirer.prompt([{
|
|
716
|
+
type: "password", name: "token", message: "Authentication token:", mask: "*",
|
|
717
|
+
validate: (v) => v.trim().length > 0 || "Token is required",
|
|
718
|
+
}]);
|
|
719
|
+
authentication = { type: "BEARER_TOKEN", token: token.trim() };
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
// 3. Transport type (no prompt — always defaults to STREAMABLE_HTTP)
|
|
723
|
+
const transportType = options.transportType || "STREAMABLE_HTTP";
|
|
724
|
+
// 4. Discover tools
|
|
725
|
+
const scope = resolveScopeIds(session);
|
|
726
|
+
if (isJsonMode()) {
|
|
727
|
+
stderrLog("Discovering MCP tools...");
|
|
728
|
+
}
|
|
729
|
+
const discoverSpinner = isJsonMode() ? null : startSpinner("Discovering tools from MCP server...");
|
|
730
|
+
let discoveredTools = [];
|
|
731
|
+
try {
|
|
732
|
+
const authConfig = authentication?.token
|
|
733
|
+
? { type: "token", token: authentication.token }
|
|
734
|
+
: { type: "none" };
|
|
735
|
+
const result = await discoverMCPTools(session, serverUrl, scope, authConfig);
|
|
736
|
+
discoveredTools = result.tools;
|
|
737
|
+
if (discoveredTools.length === 0) {
|
|
738
|
+
discoverSpinner?.fail("No tools found on this MCP server");
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
discoverSpinner?.succeed(`Discovered ${discoveredTools.length} tools`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
catch (error) {
|
|
745
|
+
discoverSpinner?.fail("Discovery failed");
|
|
746
|
+
if (!noPrompt) {
|
|
747
|
+
log(_jsx(StatusLine, { kind: "warning", label: "Could not discover tools \u2014 you can still enter the tool name manually." }));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
// 5. Tool selection (picker if discovered, free-text fallback when discovery failed)
|
|
751
|
+
let selectedTool = null;
|
|
752
|
+
if (discoveredTools.length > 0) {
|
|
753
|
+
selectedTool = await pickTool(discoveredTools, options.tool, noPrompt);
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
if (options.tool) {
|
|
757
|
+
selectedTool = { name: options.tool, description: "" };
|
|
758
|
+
}
|
|
759
|
+
else if (noPrompt) {
|
|
760
|
+
const msg = "Missing required option: --tool";
|
|
761
|
+
if (isJsonMode()) {
|
|
762
|
+
outputJsonError({ code: "VALIDATION_ERROR", message: msg });
|
|
763
|
+
}
|
|
764
|
+
else {
|
|
765
|
+
log(_jsx(ErrorDisplay, { message: msg }));
|
|
766
|
+
}
|
|
767
|
+
return null;
|
|
768
|
+
}
|
|
769
|
+
else {
|
|
770
|
+
const { name } = await inquirer.prompt([{
|
|
771
|
+
type: "input", name: "name", message: "Tool name:",
|
|
772
|
+
validate: (v) => v.trim().length > 0 || "Tool name is required",
|
|
773
|
+
}]);
|
|
774
|
+
selectedTool = { name: name.trim(), description: "" };
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (!selectedTool)
|
|
778
|
+
return null;
|
|
779
|
+
// 6. Arguments — prompted per-field from input_schema when in interactive mode
|
|
780
|
+
const toolArguments = await promptToolArguments(selectedTool, options, noPrompt);
|
|
781
|
+
// 7. Bundle mapping
|
|
782
|
+
const bundleMapping = await promptBundleMapping(options, noPrompt);
|
|
783
|
+
return {
|
|
784
|
+
type: "DIRECT",
|
|
785
|
+
server_url: serverUrl,
|
|
786
|
+
transport_type: transportType,
|
|
787
|
+
...(authentication ? { authentication } : {}),
|
|
788
|
+
tool: selectedTool.name,
|
|
789
|
+
arguments: toolArguments,
|
|
790
|
+
...(bundleMapping ? { bundle_mapping: bundleMapping } : {}),
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
// ─── Main export ───
|
|
794
|
+
export async function integrationMCPTest(options = {}) {
|
|
795
|
+
const session = await getAuthenticatedSession();
|
|
796
|
+
const isComposioNonInteractive = !!(options.toolkit && options.tool);
|
|
797
|
+
const isDirectNonInteractive = !!(options.url && options.tool);
|
|
798
|
+
const noPrompt = isJsonMode() || isComposioNonInteractive || isDirectNonInteractive;
|
|
799
|
+
// Resolve type
|
|
800
|
+
let integType = (options.type || "").toLowerCase();
|
|
801
|
+
if (!integType) {
|
|
802
|
+
if (noPrompt) {
|
|
803
|
+
if (options.toolkit)
|
|
804
|
+
integType = "composio";
|
|
805
|
+
else if (options.url)
|
|
806
|
+
integType = "direct";
|
|
807
|
+
else {
|
|
808
|
+
outputJsonError({ code: "VALIDATION_ERROR", message: "Missing required option: --type. Pass --type composio or --type direct." });
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
else {
|
|
813
|
+
const { chosen } = await inquirer.prompt([{
|
|
814
|
+
type: "list", name: "chosen", message: "Integration type:",
|
|
815
|
+
choices: [
|
|
816
|
+
{ name: "Composio (native)", value: "composio" },
|
|
817
|
+
{ name: "Direct MCP server", value: "direct" },
|
|
818
|
+
],
|
|
819
|
+
}]);
|
|
820
|
+
integType = chosen;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
let body = null;
|
|
824
|
+
if (integType === "composio") {
|
|
825
|
+
body = await composioFlow(session, options, noPrompt);
|
|
826
|
+
}
|
|
827
|
+
else if (integType === "direct") {
|
|
828
|
+
body = await directFlow(session, options, noPrompt);
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
outputJsonError({ code: "VALIDATION_ERROR", message: `Unknown type '${integType}'. Pass --type composio or --type direct.` });
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (!body)
|
|
835
|
+
return;
|
|
836
|
+
const client = buildApolloClient();
|
|
837
|
+
const bundleSent = !!body.bundle_mapping;
|
|
838
|
+
if (integType === "composio") {
|
|
839
|
+
const req = body;
|
|
840
|
+
stderrLog(`Calling MCP tool ${req.tool} via Composio (${req.toolkit} / ${req.user_id})${bundleSent ? " with bundle_mapping" : ""} ...`);
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
const req = body;
|
|
844
|
+
stderrLog(`Calling MCP tool ${req.tool} via Direct (${req.server_url})${bundleSent ? " with bundle_mapping" : ""} ...`);
|
|
845
|
+
}
|
|
846
|
+
const result = await client.testMCPIntegration(body);
|
|
847
|
+
const output = {
|
|
848
|
+
type: integType.toUpperCase(),
|
|
849
|
+
tool: body.tool,
|
|
850
|
+
raw: !bundleSent,
|
|
851
|
+
bundle_mapping_sent: bundleSent,
|
|
852
|
+
response: result.response ?? null,
|
|
853
|
+
mapping_data: result.mapping_data ?? null,
|
|
854
|
+
status: result.status ?? null,
|
|
855
|
+
elapsed_sec: result.elapsed_sec ?? null,
|
|
856
|
+
};
|
|
857
|
+
if (integType === "composio") {
|
|
858
|
+
output.user_id = body.user_id;
|
|
859
|
+
}
|
|
860
|
+
outputJson(output);
|
|
861
|
+
}
|
|
862
|
+
//# sourceMappingURL=integration-mcp-test.js.map
|