@letterstory/cli 0.177.23 → 0.177.24
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/lib/cli.mjs +3 -1
- package/lib/commands/org.mjs +123 -1
- package/package.json +1 -1
package/lib/cli.mjs
CHANGED
|
@@ -88,7 +88,7 @@ import {
|
|
|
88
88
|
import { fetchParkedSections, withoutParked } from "./parked.mjs";
|
|
89
89
|
|
|
90
90
|
// Matches cli/package.json; publish-cli.yml rewrites both to the published version.
|
|
91
|
-
export const VERSION = "0.177.
|
|
91
|
+
export const VERSION = "0.177.24";
|
|
92
92
|
|
|
93
93
|
/** The exit code for a gate refusal (CARD_REQUIRED, PAYMENT_FAILED, OUT_OF_TOKENS): 1 is a plain error. */
|
|
94
94
|
export const EXIT_PAYMENT_REQUIRED = 3;
|
|
@@ -97,6 +97,8 @@ export const EXIT_PAYMENT_REQUIRED = 3;
|
|
|
97
97
|
// can't accidentally swallow the id as --json's value. Every `type: "bool"` flag (and its
|
|
98
98
|
// `negate` spelling) in a command's ARGS map must be listed; boolean-flags.test.ts enforces it.
|
|
99
99
|
export const BOOLEAN_FLAGS = new Set([
|
|
100
|
+
// org mcp add: the server signs in with OAuth instead of a header value.
|
|
101
|
+
"sign-in",
|
|
100
102
|
// collection onboard: connect without drafting or scheduling.
|
|
101
103
|
"connect-only",
|
|
102
104
|
// admin demo ideas: every live recipe, not only the Ideas that fit.
|
package/lib/commands/org.mjs
CHANGED
|
@@ -58,6 +58,27 @@ export const ARGS = {
|
|
|
58
58
|
project_id: { value: (flags) => (flags.project === "none" ? null : flagStr(flags.project)) },
|
|
59
59
|
},
|
|
60
60
|
disconnect_linear: {},
|
|
61
|
+
list_mcp_connections: {},
|
|
62
|
+
connect_mcp: {
|
|
63
|
+
name: { positional: 0, label: "name", required: true },
|
|
64
|
+
url: { positional: 1, label: "url", required: true },
|
|
65
|
+
token: { flag: "token" },
|
|
66
|
+
header_name: { flag: "header" },
|
|
67
|
+
allow_tools: { flag: "allow", type: "list" },
|
|
68
|
+
sign_in: { flag: "sign-in", type: "bool" },
|
|
69
|
+
},
|
|
70
|
+
test_mcp_connection: { name: { positional: 0, label: "name", required: true } },
|
|
71
|
+
update_mcp_connection: {
|
|
72
|
+
name: { positional: 0, label: "name", required: true },
|
|
73
|
+
enabled: { value: (flags) => (flags.off ? false : flags.on ? true : undefined) },
|
|
74
|
+
allow_tools: {
|
|
75
|
+
value: (flags) => (flags.allow === "none" ? [] : flags.allow ? flagStr(flags.allow).split(",") : undefined),
|
|
76
|
+
},
|
|
77
|
+
token: { value: (flags) => (flags.clear ? null : flagStr(flags.token)) },
|
|
78
|
+
header_name: { flag: "header" },
|
|
79
|
+
sign_in: { flag: "sign-in", type: "bool" },
|
|
80
|
+
},
|
|
81
|
+
remove_mcp_connection: { name: { positional: 0, label: "name", required: true } },
|
|
61
82
|
connect_org_config_source: {
|
|
62
83
|
repo: { positional: 0, label: "owner/name" },
|
|
63
84
|
branch: { flag: "branch" },
|
|
@@ -91,11 +112,13 @@ export async function cmdOrg(ctx) {
|
|
|
91
112
|
return orgFields(rest);
|
|
92
113
|
case "linear":
|
|
93
114
|
return orgLinear(rest);
|
|
115
|
+
case "mcp":
|
|
116
|
+
return orgMcp(rest);
|
|
94
117
|
case "billing":
|
|
95
118
|
return orgBilling(rest);
|
|
96
119
|
default:
|
|
97
120
|
throw new CliError(
|
|
98
|
-
`Unknown org subcommand: ${sub ?? "(none)"}. Try: export, join, diff, apply, git, integrations, linear, fields, billing`
|
|
121
|
+
`Unknown org subcommand: ${sub ?? "(none)"}. Try: export, join, diff, apply, git, integrations, linear, mcp, fields, billing`
|
|
99
122
|
);
|
|
100
123
|
}
|
|
101
124
|
}
|
|
@@ -454,6 +477,105 @@ async function orgLinear(ctx) {
|
|
|
454
477
|
}
|
|
455
478
|
}
|
|
456
479
|
|
|
480
|
+
/**
|
|
481
|
+
* `org mcp` — other products' MCP servers Letter's agent can call (docs/architecture/mcp-connections.md):
|
|
482
|
+
* org mcp the connections (list_mcp_connections)
|
|
483
|
+
* org mcp add <name> <url> [--token <value> [--header <name>] | --sign-in] [--allow a,b] (connect_mcp)
|
|
484
|
+
* org mcp signin <name> sign in again with OAuth (update_mcp_connection)
|
|
485
|
+
* org mcp tools <name> read the server's tools again (test_mcp_connection)
|
|
486
|
+
* org mcp allow <name> <a,b|none> the tools that change things the agent may call (update_mcp_connection)
|
|
487
|
+
* org mcp on|off <name> (update_mcp_connection)
|
|
488
|
+
* org mcp token <name> <value>|--clear a new header value (update_mcp_connection)
|
|
489
|
+
* org mcp remove <name> (remove_mcp_connection)
|
|
490
|
+
*/
|
|
491
|
+
async function orgMcp(ctx) {
|
|
492
|
+
const { client, flags, io } = ctx;
|
|
493
|
+
const sub = ctx.positionals[0] ?? "list";
|
|
494
|
+
const rest = ctx.positionals.slice(1);
|
|
495
|
+
const call = async (tool, allowed, args) => {
|
|
496
|
+
refuseUnknownFlags(ctx, {}, allowed);
|
|
497
|
+
const result = await client.callTool(tool, args ?? argsFromFlags(ARGS[tool], flags, rest));
|
|
498
|
+
ok(ctx, result.message);
|
|
499
|
+
return result;
|
|
500
|
+
};
|
|
501
|
+
const tools = (r) =>
|
|
502
|
+
r.tools
|
|
503
|
+
.map((t) => `${t.allowed ? "●" : "○"} ${t.name}${t.read_only ? " (reads only)" : ""} ${t.description}`)
|
|
504
|
+
.join("\n");
|
|
505
|
+
switch (sub) {
|
|
506
|
+
case "list": {
|
|
507
|
+
const result = await call("list_mcp_connections", []);
|
|
508
|
+
printResult(
|
|
509
|
+
io,
|
|
510
|
+
flags,
|
|
511
|
+
result,
|
|
512
|
+
(r) =>
|
|
513
|
+
r.connections
|
|
514
|
+
.map(
|
|
515
|
+
(c) =>
|
|
516
|
+
`${c.enabled ? "●" : "○"} ${c.name} ${c.url} ${c.tool_count ?? "?"} tools${c.last_status === "failed" ? ` (failed: ${c.last_error})` : ""}`
|
|
517
|
+
)
|
|
518
|
+
.join("\n") || r.message
|
|
519
|
+
);
|
|
520
|
+
return 0;
|
|
521
|
+
}
|
|
522
|
+
case "add": {
|
|
523
|
+
const result = await call("connect_mcp", ["token", "header", "allow", "sign-in"]);
|
|
524
|
+
printResult(io, flags, result, (r) =>
|
|
525
|
+
r.sign_in_url ? `${r.message}\n${r.sign_in_url}` : `${r.message}\n${tools(r)}`
|
|
526
|
+
);
|
|
527
|
+
return 0;
|
|
528
|
+
}
|
|
529
|
+
case "signin": {
|
|
530
|
+
const result = await call("update_mcp_connection", [], {
|
|
531
|
+
name: requirePositional(rest, 0, "name"),
|
|
532
|
+
sign_in: true,
|
|
533
|
+
});
|
|
534
|
+
printResult(io, flags, result, (r) => `${r.message}\n${r.sign_in_url}`);
|
|
535
|
+
return 0;
|
|
536
|
+
}
|
|
537
|
+
case "tools": {
|
|
538
|
+
const result = await call("test_mcp_connection", []);
|
|
539
|
+
printResult(io, flags, result, tools);
|
|
540
|
+
return 0;
|
|
541
|
+
}
|
|
542
|
+
case "allow": {
|
|
543
|
+
const list = requirePositional(rest, 1, "a,b|none");
|
|
544
|
+
const result = await call("update_mcp_connection", [], {
|
|
545
|
+
name: requirePositional(rest, 0, "name"),
|
|
546
|
+
allow_tools: list === "none" ? [] : list.split(","),
|
|
547
|
+
});
|
|
548
|
+
printResult(io, flags, result, tools);
|
|
549
|
+
return 0;
|
|
550
|
+
}
|
|
551
|
+
case "on":
|
|
552
|
+
case "off": {
|
|
553
|
+
const result = await call("update_mcp_connection", [], {
|
|
554
|
+
name: requirePositional(rest, 0, "name"),
|
|
555
|
+
enabled: sub === "on",
|
|
556
|
+
});
|
|
557
|
+
printResult(io, flags, result, (r) => r.message);
|
|
558
|
+
return 0;
|
|
559
|
+
}
|
|
560
|
+
case "token": {
|
|
561
|
+
const name = requirePositional(rest, 0, "name");
|
|
562
|
+
const value = flags.clear ? null : requirePositional(rest, 1, "value");
|
|
563
|
+
const result = await call("update_mcp_connection", ["clear"], { name, token: value });
|
|
564
|
+
printResult(io, flags, result, (r) => r.message);
|
|
565
|
+
return 0;
|
|
566
|
+
}
|
|
567
|
+
case "remove": {
|
|
568
|
+
const result = await call("remove_mcp_connection", []);
|
|
569
|
+
printResult(io, flags, result, (r) => r.message);
|
|
570
|
+
return 0;
|
|
571
|
+
}
|
|
572
|
+
default:
|
|
573
|
+
throw new CliError(
|
|
574
|
+
`Unknown org mcp subcommand: ${sub}. Try: add, signin, tools, allow, on, off, token, remove`
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
457
579
|
const ROOT_FILE = "organization.json";
|
|
458
580
|
|
|
459
581
|
function slug(name) {
|