@chrischall/mcp-host-cli 0.0.0-stage → 2.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -2
- package/package.json +33 -4
- package/src/index.mjs +1418 -0
- package/src/lib.mjs +654 -0
- package/src/login.mjs +53 -0
- package/src/operations.mjs +650 -0
- package/src/prompt.mjs +126 -0
- package/src/seal.mjs +226 -0
- package/src/secrets.mjs +100 -0
- package/src/wizard.mjs +416 -0
package/src/login.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// `connections login <principal id>`: run a connection's declared sign-in
|
|
2
|
+
// again (`authFlow`, core/src/auth-flow.ts) — the one repair for a credential
|
|
3
|
+
// that expired upstream short of revoking the connection, which loses the
|
|
4
|
+
// grant and its usage history.
|
|
5
|
+
//
|
|
6
|
+
// On a terminal it is the conversation `/authorize` would have had: each
|
|
7
|
+
// step's prompts asked here (a password without echo, the schema's default
|
|
8
|
+
// type), answered through `/login/step` until the flow ends. Off a terminal it
|
|
9
|
+
// prints nothing but the first answer and says how to continue with
|
|
10
|
+
// `connections login-step`, because a script cannot be prompted and should
|
|
11
|
+
// state its answers itself. Pulled out of index.mjs so the loop can be driven
|
|
12
|
+
// by a scripted prompter: the CLI's own tests run over a pipe and so only ever
|
|
13
|
+
// reach the non-interactive branch.
|
|
14
|
+
|
|
15
|
+
/** Every state a login can end in that is not success (core `loginStateSchema`). */
|
|
16
|
+
const FAILED = new Set(['rejected', 'unavailable', 'incoherent']);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {(method: string, path: string, body?: unknown) => Promise<any>} api
|
|
20
|
+
* @param {string} id
|
|
21
|
+
* @param {{ interactive: boolean, prompter?: import('./prompt.mjs').Prompter, stderr?: { write(s: string): unknown } }} opts
|
|
22
|
+
* @returns {Promise<{ result: any, exitCode: number }>}
|
|
23
|
+
*/
|
|
24
|
+
export async function runLogin(api, id, { interactive, prompter, stderr = process.stderr }) {
|
|
25
|
+
const base = `/api/v1/principals/${encodeURIComponent(id)}/login`;
|
|
26
|
+
let result = await api('POST', base);
|
|
27
|
+
if (interactive && prompter) {
|
|
28
|
+
while (result.state === 'prompt') {
|
|
29
|
+
if (result.title) stderr.write(`${result.title}\n`);
|
|
30
|
+
if (result.description) stderr.write(`${result.description}\n`);
|
|
31
|
+
// Resolved by the server against the hosts the flow declared; absent
|
|
32
|
+
// when it was not on one, so there is nothing here to check.
|
|
33
|
+
if (result.link) stderr.write(`${result.link.label}: ${result.link.url}\n`);
|
|
34
|
+
/** @type {Record<string, string>} */
|
|
35
|
+
const answers = {};
|
|
36
|
+
for (const p of result.prompts ?? []) {
|
|
37
|
+
if (p.type === 'choice') answers[p.name] = await prompter.choose(p.label, p.options);
|
|
38
|
+
else if (p.type === 'text') answers[p.name] = await prompter.ask(p.label);
|
|
39
|
+
else answers[p.name] = await prompter.secret(p.label);
|
|
40
|
+
}
|
|
41
|
+
result = await api('POST', `${base}/step`, { login: result.login, step: result.step, answers });
|
|
42
|
+
}
|
|
43
|
+
return { result, exitCode: result.state === 'done' ? 0 : 1 };
|
|
44
|
+
}
|
|
45
|
+
if (result.state === 'prompt') {
|
|
46
|
+
stderr.write(
|
|
47
|
+
`mcp-host: continue with: mcp-host connections login-step ${id} --login ${result.login} --step ${result.step} --answers '{"<name>":"<value>"}'\n`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
// A prompt is not a failure off a terminal — it is the flow waiting for the
|
|
51
|
+
// scripted answer — but a refusal is, on either path.
|
|
52
|
+
return { result, exitCode: FAILED.has(result.state) ? 1 : 0 };
|
|
53
|
+
}
|