@extrovert.dev/mcp 0.1.0-pre.5 → 0.1.0-pre.6
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 +1 -1
- package/dist/client.d.ts +11 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -3
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/contract.d.ts +2 -2
- package/dist/contract.js +2 -2
- package/dist/fixtures.d.ts +2 -2
- package/dist/fixtures.d.ts.map +1 -1
- package/dist/fixtures.js +10 -2
- package/dist/fixtures.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +43 -4
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +9 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* an org-wide master key. `redeem_enrollment` lets an agent exchange a
|
|
13
13
|
* single-use enrollment token for that scoped key at runtime.
|
|
14
14
|
*/
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
15
16
|
import { z } from "zod/v4";
|
|
16
17
|
import { ExtrovertApiError } from "./client.js";
|
|
17
18
|
import { isTerminalReviewEvent } from "./types.js";
|
|
@@ -454,9 +455,12 @@ const signUp = defineTool({
|
|
|
454
455
|
name: "sign_up",
|
|
455
456
|
title: "Sign up for a free account",
|
|
456
457
|
description: "Grab a free Extrovert account in one call (no enrollment token needed). Provisions a tenant and a first inbox, then " +
|
|
457
|
-
"emails a one-time verification code to your human_email. Returns a LIMITED
|
|
458
|
+
"emails a one-time verification code to your human_email. Returns a LIMITED verification-only agent key that " +
|
|
459
|
+
"cannot read or send mail and that this " +
|
|
458
460
|
"MCP session keeps only through verify_signup; it expires with the code and is revoked after verification. " +
|
|
459
|
-
"Re-calling with the same human_email rotates the bootstrap key and resends the code."
|
|
461
|
+
"Re-calling with the same human_email rotates the bootstrap key and resends the code. Free signup may be " +
|
|
462
|
+
"temporarily paused; in that state the tool " +
|
|
463
|
+
"returns signup_disabled and creates nothing. Enrollment tokens remain available.",
|
|
460
464
|
inputSchema: {
|
|
461
465
|
human_email: emailAddress.describe("Your email — receives the one-time verification code."),
|
|
462
466
|
username: z
|
|
@@ -486,7 +490,9 @@ const verifySignup = defineTool({
|
|
|
486
490
|
title: "Verify signup code",
|
|
487
491
|
description: "Confirm the one-time code emailed by sign_up. On success the bootstrap key is revoked and you receive a NEW " +
|
|
488
492
|
"full-scope agent key (create/read/send/webhooks). This MCP session switches to it automatically. The packaged " +
|
|
489
|
-
"local stdio server also stores it in its permission-restricted credential file for future sessions."
|
|
493
|
+
"local stdio server also stores it in its permission-restricted credential file for future sessions. Pending " +
|
|
494
|
+
"signup verification returns signup_disabled while free signup is temporarily paused; keep the limited key and " +
|
|
495
|
+
"retry after reopening.",
|
|
490
496
|
inputSchema: {
|
|
491
497
|
otp: z.string().min(4).max(12).describe("The verification code from your signup email."),
|
|
492
498
|
},
|
|
@@ -681,6 +687,7 @@ const exportEmailConfig = defineTool({
|
|
|
681
687
|
name: "export_email_config",
|
|
682
688
|
title: "Export email client config",
|
|
683
689
|
description: "Export an inbox's IMAP/SMTP server settings + login so you can configure a real mail client (e.g. Himalaya). " +
|
|
690
|
+
"Requires the dedicated mailbox:credentials scope and a paid plan; free accounts cannot export raw credentials. " +
|
|
684
691
|
"Direct SMTP is an explicit unreviewed delivery path: it bypasses Extrovert approval/review, suppression and " +
|
|
685
692
|
"contact-list enforcement, List-Unsubscribe injection, and Extrovert billing/accounting. API/MCP sends keep those " +
|
|
686
693
|
"controls. Returns a ready-to-use config; use format=json for raw connection fields.",
|
|
@@ -781,6 +788,7 @@ const sendEmail = defineTool({
|
|
|
781
788
|
.describe("Your confidence (0..1) in the category match. Feeds the min_confidence auto-send gate ONLY; " +
|
|
782
789
|
"the server never scores. Below the threshold (or omitted when one is set) the would-be auto-send " +
|
|
783
790
|
"routes to needs_review (gate_outcome held:low_confidence)."),
|
|
791
|
+
composition_token: z.string().min(1).optional().describe("Token returned by the fresh get_rules call used to compose this message. The MCP refreshes it when omitted for compatibility."),
|
|
784
792
|
client_id: z
|
|
785
793
|
.string()
|
|
786
794
|
.min(1)
|
|
@@ -790,6 +798,9 @@ const sendEmail = defineTool({
|
|
|
790
798
|
},
|
|
791
799
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
792
800
|
handler: async (args, { client }) => {
|
|
801
|
+
const compositionToken = args.composition_token ?? (await client.getRules({ category_id: args.category_id })).composition_token;
|
|
802
|
+
if (!compositionToken)
|
|
803
|
+
throw new ExtrovertApiError("Call get_rules without a scope filter before composing.", 422, "composition_token_required");
|
|
793
804
|
// Review Loop overload: any of mode/intent/category_id opts into the richer
|
|
794
805
|
// discriminated response. It does NOT decide whether a human sees the message —
|
|
795
806
|
// the policy does that either way; this only shapes what comes back.
|
|
@@ -813,6 +824,7 @@ const sendEmail = defineTool({
|
|
|
813
824
|
intent: args.intent,
|
|
814
825
|
category_id: args.category_id,
|
|
815
826
|
category_confidence: args.category_confidence,
|
|
827
|
+
composition_token: compositionToken,
|
|
816
828
|
client_id: args.client_id,
|
|
817
829
|
});
|
|
818
830
|
return ok(renderSubmitResult(result), result);
|
|
@@ -828,6 +840,7 @@ const sendEmail = defineTool({
|
|
|
828
840
|
reply_to: args.reply_to,
|
|
829
841
|
headers: args.headers,
|
|
830
842
|
attachments: args.attachments,
|
|
843
|
+
composition_token: compositionToken,
|
|
831
844
|
client_id: args.client_id,
|
|
832
845
|
});
|
|
833
846
|
return ok(renderSendOutcome("Sent", result), result);
|
|
@@ -878,6 +891,7 @@ const replyEmail = defineTool({
|
|
|
878
891
|
.max(1)
|
|
879
892
|
.optional()
|
|
880
893
|
.describe("Your confidence (0..1) in the category match. Feeds the min_confidence auto-send gate only."),
|
|
894
|
+
composition_token: z.string().min(1).optional().describe("Token returned by the fresh get_rules call used to compose this reply. The MCP refreshes it when omitted for compatibility."),
|
|
881
895
|
client_id: z
|
|
882
896
|
.string()
|
|
883
897
|
.min(1)
|
|
@@ -890,6 +904,9 @@ const replyEmail = defineTool({
|
|
|
890
904
|
if (!args.thread_id && !args.message_id) {
|
|
891
905
|
throw new ExtrovertApiError("Provide thread_id or message_id to reply.", 400, "invalid_argument");
|
|
892
906
|
}
|
|
907
|
+
const compositionToken = args.composition_token ?? (await client.getRules({ category_id: args.category_id })).composition_token;
|
|
908
|
+
if (!compositionToken)
|
|
909
|
+
throw new ExtrovertApiError("Call get_rules without a scope filter before composing.", 422, "composition_token_required");
|
|
893
910
|
// Review Loop overload: any of mode/intent/category_id opts into review.
|
|
894
911
|
if (args.mode !== undefined || args.intent !== undefined || args.category_id !== undefined) {
|
|
895
912
|
const result = await client.submitReplyForReview({
|
|
@@ -908,6 +925,7 @@ const replyEmail = defineTool({
|
|
|
908
925
|
intent: args.intent,
|
|
909
926
|
category_id: args.category_id,
|
|
910
927
|
category_confidence: args.category_confidence,
|
|
928
|
+
composition_token: compositionToken,
|
|
911
929
|
client_id: args.client_id,
|
|
912
930
|
});
|
|
913
931
|
return ok(renderSubmitResult(result), result);
|
|
@@ -924,6 +942,7 @@ const replyEmail = defineTool({
|
|
|
924
942
|
headers: args.headers,
|
|
925
943
|
reply_all: args.reply_all,
|
|
926
944
|
attachments: args.attachments,
|
|
945
|
+
composition_token: compositionToken,
|
|
927
946
|
client_id: args.client_id,
|
|
928
947
|
});
|
|
929
948
|
return ok(renderSendOutcome("Replied", res), res);
|
|
@@ -968,6 +987,7 @@ const forwardEmail = defineTool({
|
|
|
968
987
|
.max(1)
|
|
969
988
|
.optional()
|
|
970
989
|
.describe("Your confidence (0..1) in the category match. Feeds the min_confidence auto-send gate only."),
|
|
990
|
+
composition_token: z.string().min(1).optional().describe("Token returned by the fresh get_rules call used to compose this forward. The MCP refreshes it when omitted for compatibility."),
|
|
971
991
|
client_id: z
|
|
972
992
|
.string()
|
|
973
993
|
.min(1)
|
|
@@ -977,6 +997,9 @@ const forwardEmail = defineTool({
|
|
|
977
997
|
},
|
|
978
998
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
979
999
|
handler: async (args, { client }) => {
|
|
1000
|
+
const compositionToken = args.composition_token ?? (await client.getRules({ category_id: args.category_id })).composition_token;
|
|
1001
|
+
if (!compositionToken)
|
|
1002
|
+
throw new ExtrovertApiError("Call get_rules without a scope filter before composing.", 422, "composition_token_required");
|
|
980
1003
|
// Same opt-in predicate as send/reply: mode/intent/category_id select the
|
|
981
1004
|
// richer discriminated response; the policy governs the routing regardless.
|
|
982
1005
|
if (args.mode !== undefined || args.intent !== undefined || args.category_id !== undefined) {
|
|
@@ -992,6 +1015,7 @@ const forwardEmail = defineTool({
|
|
|
992
1015
|
intent: args.intent,
|
|
993
1016
|
category_id: args.category_id,
|
|
994
1017
|
category_confidence: args.category_confidence,
|
|
1018
|
+
composition_token: compositionToken,
|
|
995
1019
|
client_id: args.client_id,
|
|
996
1020
|
});
|
|
997
1021
|
return ok(renderSubmitResult(result), result);
|
|
@@ -1004,6 +1028,7 @@ const forwardEmail = defineTool({
|
|
|
1004
1028
|
bcc: args.bcc,
|
|
1005
1029
|
text: args.text,
|
|
1006
1030
|
html: args.html,
|
|
1031
|
+
composition_token: compositionToken,
|
|
1007
1032
|
client_id: args.client_id,
|
|
1008
1033
|
});
|
|
1009
1034
|
return ok(renderSendOutcome("Forwarded", res), res);
|
|
@@ -1237,6 +1262,7 @@ const submitRevision = defineTool({
|
|
|
1237
1262
|
"redraft could never restore a file the human reviewed the message with."),
|
|
1238
1263
|
built_at: z.string().optional().describe("When you built this draft (informational)."),
|
|
1239
1264
|
rules_version_seen: z.number().int().optional().describe("Rule high-water this draft was composed against (born-stale basis)."),
|
|
1265
|
+
composition_token: z.string().min(1).optional().describe("Token returned by the fresh get_rules call used to compose this revision. The MCP refreshes it when omitted for compatibility."),
|
|
1240
1266
|
client_id: z
|
|
1241
1267
|
.string()
|
|
1242
1268
|
.min(1)
|
|
@@ -1246,6 +1272,10 @@ const submitRevision = defineTool({
|
|
|
1246
1272
|
},
|
|
1247
1273
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1248
1274
|
handler: async (args, { client }) => {
|
|
1275
|
+
const currentReview = await client.getReview(args.id);
|
|
1276
|
+
const compositionToken = args.composition_token ?? (await client.getRules({ category_id: currentReview.category_id })).composition_token;
|
|
1277
|
+
if (!compositionToken)
|
|
1278
|
+
throw new ExtrovertApiError("Call get_rules without a scope filter before redrafting.", 422, "composition_token_required");
|
|
1249
1279
|
const review = await client.submitRevision({
|
|
1250
1280
|
id: args.id,
|
|
1251
1281
|
parent_revision: args.parent_revision,
|
|
@@ -1257,6 +1287,7 @@ const submitRevision = defineTool({
|
|
|
1257
1287
|
attachments: args.attachments,
|
|
1258
1288
|
built_at: args.built_at,
|
|
1259
1289
|
rules_version_seen: args.rules_version_seen,
|
|
1290
|
+
composition_token: compositionToken,
|
|
1260
1291
|
client_id: args.client_id,
|
|
1261
1292
|
});
|
|
1262
1293
|
return ok(`Revised.\n${renderReview(review)}`, review);
|
|
@@ -1556,6 +1587,11 @@ const getRules = defineTool({
|
|
|
1556
1587
|
return ok(`${res.items.length} rule(s), highest precedence first.\n\n${text}`, {
|
|
1557
1588
|
items: res.items,
|
|
1558
1589
|
total: res.total,
|
|
1590
|
+
house_style_version: res.house_style_version,
|
|
1591
|
+
category_rules_version: res.category_rules_version,
|
|
1592
|
+
rule_high_water: res.rule_high_water,
|
|
1593
|
+
composition_token: res.composition_token,
|
|
1594
|
+
composition_token_expires_at: res.composition_token_expires_at,
|
|
1559
1595
|
});
|
|
1560
1596
|
},
|
|
1561
1597
|
});
|
|
@@ -1570,6 +1606,7 @@ const saveRule = defineTool({
|
|
|
1570
1606
|
"lineage, the prior superseded). Use this AFTER you judge a diff/comment is a generalizable rule (the judgment is " +
|
|
1571
1607
|
"yours; we never run an LLM). Returns the new active rule (with its rule_layer/org_id/project_id).",
|
|
1572
1608
|
inputSchema: {
|
|
1609
|
+
client_id: z.string().min(1).max(128).optional().describe("Stable retry id for this exact rule save; reuse it after a timeout. The MCP derives one when omitted."),
|
|
1573
1610
|
rule_text: z.string().min(1).describe("The rule body, e.g. 'no em-dashes' or 'be more pushy, we need MRR'."),
|
|
1574
1611
|
category_id: z.string().optional().describe("Category id (cat_…). Empty = house-style/general (D2)."),
|
|
1575
1612
|
scope: z.enum(RULE_SCOPES).optional().describe("Defaults from category_id (general iff empty)."),
|
|
@@ -1591,9 +1628,11 @@ const saveRule = defineTool({
|
|
|
1591
1628
|
.optional()
|
|
1592
1629
|
.describe("Override the propagate batch (0 = base 3, bounded by rework_batch_max). Never fans one nudge to the whole queue."),
|
|
1593
1630
|
},
|
|
1594
|
-
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint:
|
|
1631
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
1595
1632
|
handler: async (args, { client }) => {
|
|
1633
|
+
const derivedClientID = `rule:${createHash("sha256").update(JSON.stringify(args)).digest("hex")}`;
|
|
1596
1634
|
const rule = await client.saveRule({
|
|
1635
|
+
client_id: args.client_id ?? derivedClientID,
|
|
1597
1636
|
rule_text: args.rule_text,
|
|
1598
1637
|
category_id: args.category_id,
|
|
1599
1638
|
scope: args.scope,
|