@webhooks-cc/mcp 1.13.0 → 1.15.0
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 +55 -29
- package/dist/bin/mcp.js +503 -199
- package/dist/chunk-RHQD2GRD.mjs +1819 -0
- package/dist/index.d.mts +21 -9
- package/dist/index.d.ts +21 -9
- package/dist/index.js +467 -173
- package/dist/index.mjs +161 -1649
- package/dist/tools.d.mts +171 -0
- package/dist/tools.d.ts +171 -0
- package/dist/tools.js +1827 -0
- package/dist/tools.mjs +34 -0
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -18,8 +18,9 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
|
|
20
20
|
// src/index.ts
|
|
21
|
-
var
|
|
22
|
-
__export(
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
REMOTE_MAX_WAIT_MS: () => REMOTE_MAX_WAIT_MS,
|
|
23
24
|
createServer: () => createServer,
|
|
24
25
|
registerAgentRegistrationTools: () => registerAgentRegistrationTools,
|
|
25
26
|
registerPrompts: () => registerPrompts,
|
|
@@ -27,7 +28,7 @@ __export(index_exports, {
|
|
|
27
28
|
registerSandboxTools: () => registerSandboxTools,
|
|
28
29
|
registerTools: () => registerTools
|
|
29
30
|
});
|
|
30
|
-
module.exports = __toCommonJS(
|
|
31
|
+
module.exports = __toCommonJS(src_exports);
|
|
31
32
|
var import_mcp2 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
32
33
|
var import_sdk3 = require("@webhooks-cc/sdk");
|
|
33
34
|
|
|
@@ -441,28 +442,75 @@ var methodSchema = import_zod2.z.enum(HTTP_METHODS).default("POST").describe("HT
|
|
|
441
442
|
var kindSchema = import_zod2.z.enum(["http", "email"]).optional().describe('Only HTTP requests ("http") or only captured emails ("email")');
|
|
442
443
|
var EMAIL_SCAN_LIMIT = 100;
|
|
443
444
|
var durationOrTimestampSchema = import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number()]);
|
|
444
|
-
var
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
445
|
+
var CONDITION_FIELD_NAMES = Object.keys(import_sdk.CONDITION_FIELDS);
|
|
446
|
+
var CONDITION_OP_NAMES = import_sdk.CONDITION_OPS;
|
|
447
|
+
function conditionSchema(use) {
|
|
448
|
+
const fields = use === "response" ? CONDITION_FIELD_NAMES.filter((field) => import_sdk.CONDITION_FIELDS[field].response) : CONDITION_FIELD_NAMES;
|
|
449
|
+
return import_zod2.z.object({
|
|
450
|
+
field: import_zod2.z.enum(fields).describe(
|
|
451
|
+
use === "response" ? "What to read: method, path, header (name), query (name), body (text), body_path (path into a JSON or form body), content_type, size" : "What to read: method, path, header (name), query (name), body, body_path (path), content_type, size, kind (http or email), email (name: subject, from, to, text or tag), spam_score, email_auth (name: spf, dkim or dmarc), signature (verified, failed or missing)"
|
|
452
|
+
),
|
|
453
|
+
op: import_zod2.z.enum(CONDITION_OP_NAMES).describe(
|
|
454
|
+
"eq, neq, contains, not_contains, starts_with, ends_with, matches (glob: * one path segment, ** any, ? one character), regex (RE2, searched anywhere; (?i) ignores case), exists, not_exists, gt, gte, lt, lte (numbers)"
|
|
455
|
+
),
|
|
456
|
+
value: import_zod2.z.string().max(import_sdk.MAX_CONDITION_VALUE_LEN).optional().describe(`What to compare with (not for exists and not_exists)`),
|
|
457
|
+
name: import_zod2.z.string().max(import_sdk.MAX_CONDITION_NAME_LEN).optional().describe("Header or query parameter name; the email part; spf, dkim or dmarc"),
|
|
458
|
+
path: import_zod2.z.string().max(import_sdk.MAX_CONDITION_PATH_LEN).optional().describe('Dot path for body_path, e.g. data.object.status or items.0.sku or a["odd.key"]')
|
|
459
|
+
}).superRefine((condition, ctx) => {
|
|
460
|
+
for (const issue of (0, import_sdk.conditionIssues)(condition, { use })) {
|
|
461
|
+
ctx.addIssue({ code: "custom", message: issue });
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
var ruleConditionSchema = conditionSchema("response");
|
|
466
|
+
var destinationFields = {
|
|
467
|
+
name: import_zod2.z.string().max(100).nullable().optional().describe("A name for the list and the log"),
|
|
468
|
+
enabled: import_zod2.z.boolean().optional().describe("Send to it (default true)"),
|
|
469
|
+
http: import_zod2.z.boolean().optional().describe("Takes HTTP requests (default true)"),
|
|
470
|
+
email: import_zod2.z.boolean().optional().describe("Takes emails (default true)"),
|
|
471
|
+
filter: import_zod2.z.object({
|
|
472
|
+
logic: import_zod2.z.enum(["and", "or"]).optional().describe("and (default): every condition; or: any"),
|
|
473
|
+
conditions: import_zod2.z.array(conditionSchema("filter")).max(10)
|
|
474
|
+
}).nullable().optional().describe(
|
|
475
|
+
"Send only captures that match (null: everything it takes). Conditions like response rules plus kind, email (name: subject, from, to, text, tag), spam_score, email_auth (name: spf, dkim, dmarc) and signature (verified, failed, missing)"
|
|
449
476
|
),
|
|
450
|
-
|
|
451
|
-
|
|
477
|
+
format: import_zod2.z.enum([
|
|
478
|
+
"auto",
|
|
479
|
+
"as_received",
|
|
480
|
+
"json",
|
|
481
|
+
"template",
|
|
482
|
+
"chat",
|
|
483
|
+
"postmark",
|
|
484
|
+
"sendgrid",
|
|
485
|
+
"mailgun",
|
|
486
|
+
"cloudmailin"
|
|
487
|
+
]).optional().describe(
|
|
488
|
+
"auto (pick from the URL), as_received (relay HTTP requests unchanged; emails as signed JSON), json (signed JSON envelope), template (your bodyTemplate, signed like json), chat (Slack or Discord message), or an inbound mail provider's shape for emails (postmark, sendgrid, mailgun, cloudmailin; HTTP requests then go as received), to test the inbound handler you wrote for that provider"
|
|
452
489
|
),
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
(
|
|
460
|
-
|
|
461
|
-
)
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
490
|
+
mailgunSigningKey: import_zod2.z.string().max(200).nullable().optional().describe(
|
|
491
|
+
"For format mailgun: your Mailgun HTTP webhook signing key, so the form's signature verifies; null removes it. Write-only."
|
|
492
|
+
),
|
|
493
|
+
bodyTemplate: import_zod2.z.unknown().optional().describe(
|
|
494
|
+
`For format template: any JSON value whose strings hold {{ placeholders }}, e.g. {"event": "{{ json.type }}", "amount": "{{ json.data.amount }}", "code": "{{ codes.0 }}"}. A string that is only a placeholder keeps the value's type; a missing field becomes null. HTTP requests read method, path, query.x, headers.x, body, json.path, id, receivedAt, signature; emails read the email.received fields (subject, from.address, to, text, codes, links, auth, spam, tag)`
|
|
495
|
+
),
|
|
496
|
+
messageTemplate: import_zod2.z.string().max(4e3).nullable().optional().describe(
|
|
497
|
+
"For format chat: the message text with {{ placeholders }}; null sends the standard message"
|
|
498
|
+
),
|
|
499
|
+
headers: import_zod2.z.array(
|
|
500
|
+
import_zod2.z.object({
|
|
501
|
+
name: import_zod2.z.string().describe("Header name, e.g. Authorization"),
|
|
502
|
+
value: import_zod2.z.string().nullable().describe("Value, or null to keep the stored one")
|
|
503
|
+
})
|
|
504
|
+
).max(10).optional().describe("Your own headers for every delivery (not chat). Replaces the whole list"),
|
|
505
|
+
appendPath: import_zod2.z.boolean().optional().describe("Append the captured path to the URL (as_received)"),
|
|
506
|
+
retrySeconds: import_zod2.z.union([import_zod2.z.literal(0), import_zod2.z.literal(3600), import_zod2.z.literal(86400)]).optional().describe("Retry failed deliveries for 86400 (default), 3600 or 0 seconds"),
|
|
507
|
+
keepOrder: import_zod2.z.boolean().optional().describe("One delivery at a time, in capture order"),
|
|
508
|
+
sentField: import_zod2.z.string().max(128).nullable().optional().describe("JSON body field with the sender's own timestamp"),
|
|
509
|
+
throttleSeconds: import_zod2.z.number().int().min(0).max(3600).optional().describe(
|
|
510
|
+
"At most one delivery every N seconds; the rest are not sent (1 makes a heads-up channel)"
|
|
511
|
+
),
|
|
512
|
+
delaySeconds: import_zod2.z.number().int().min(0).max(3600).optional().describe("Wait this long after the capture before sending, like a callback")
|
|
513
|
+
};
|
|
466
514
|
var responseRuleSchema = import_zod2.z.object({
|
|
467
515
|
name: import_zod2.z.string().max(import_sdk.MAX_RULE_NAME_LEN).optional().describe(`Human-readable rule name (max ${import_sdk.MAX_RULE_NAME_LEN} chars)`),
|
|
468
516
|
enabled: import_zod2.z.boolean().optional().default(true).describe("Whether this rule is active"),
|
|
@@ -473,8 +521,10 @@ var responseRuleSchema = import_zod2.z.object({
|
|
|
473
521
|
var responseRulesSchema = import_zod2.z.array(responseRuleSchema).max(50).describe("Ordered conditional response rules (first match wins, max 50)");
|
|
474
522
|
var mockResponseSchema = import_zod2.z.object({
|
|
475
523
|
status: import_zod2.z.number().int().min(import_sdk.MOCK_RESPONSE_STATUS_MIN).max(import_sdk.MOCK_RESPONSE_STATUS_MAX).describe(`HTTP status code (${import_sdk.MOCK_RESPONSE_STATUS_MIN}-${import_sdk.MOCK_RESPONSE_STATUS_MAX})`),
|
|
476
|
-
body: import_zod2.z.string().default("").describe(
|
|
477
|
-
|
|
524
|
+
body: import_zod2.z.string().default("").describe(
|
|
525
|
+
'Response body (default: empty). {{ placeholders }} are filled in from the request: {{ json.id }} (a JSON or form body field), {{ body }}, {{ headers.user-agent }}, {{ query.page }}, {{ method }}, {{ path }}, {{ id }}, {{ uuid }}, {{ now }}; filters: | default: "x", | json, | lower, | upper, | truncate: 80'
|
|
526
|
+
),
|
|
527
|
+
headers: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).default({}).describe("Response headers (default: none); values may use {{ placeholders }} too"),
|
|
478
528
|
delay: import_zod2.z.number().int().min(import_sdk.MOCK_RESPONSE_DELAY_MIN).max(import_sdk.MOCK_RESPONSE_DELAY_MAX).optional().describe(
|
|
479
529
|
`Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
480
530
|
)
|
|
@@ -728,7 +778,9 @@ async function waitForMultipleRequests(client, endpointSlug, options) {
|
|
|
728
778
|
};
|
|
729
779
|
}
|
|
730
780
|
}
|
|
731
|
-
await sleep(
|
|
781
|
+
await sleep(
|
|
782
|
+
Math.min(Math.max(10, pollIntervalMs), Math.max(0, timeoutMs - (Date.now() - startedAt)))
|
|
783
|
+
);
|
|
732
784
|
}
|
|
733
785
|
return {
|
|
734
786
|
requests,
|
|
@@ -829,6 +881,8 @@ var ESSENTIAL_DETAIL_FIELDS = [
|
|
|
829
881
|
"tag",
|
|
830
882
|
"subject",
|
|
831
883
|
"from",
|
|
884
|
+
"fields",
|
|
885
|
+
"replyText",
|
|
832
886
|
"size",
|
|
833
887
|
"test",
|
|
834
888
|
"spam",
|
|
@@ -838,6 +892,11 @@ var ESSENTIAL_DETAIL_FIELDS = [
|
|
|
838
892
|
"html",
|
|
839
893
|
"htmlTruncated"
|
|
840
894
|
];
|
|
895
|
+
function shrinkRendered(rendered, max) {
|
|
896
|
+
const body = typeof rendered.body === "string" ? rendered.body : "";
|
|
897
|
+
if (body.length <= max - 1024) return rendered;
|
|
898
|
+
return { ...rendered, body: body.slice(0, max - 1024), bodyTruncated: true };
|
|
899
|
+
}
|
|
841
900
|
function emailDetail(client, email, view, includeHtml) {
|
|
842
901
|
const { data } = client.emails.toJson(email, {
|
|
843
902
|
endpoint: view.endpoint,
|
|
@@ -879,7 +938,67 @@ function emailDetail(client, email, view, includeHtml) {
|
|
|
879
938
|
]);
|
|
880
939
|
return detail;
|
|
881
940
|
}
|
|
882
|
-
|
|
941
|
+
var LOCAL_ONLY_TOOLS = /* @__PURE__ */ new Set(["send_to", "replay_request"]);
|
|
942
|
+
var TARGET_URL_TOOLS = /* @__PURE__ */ new Set(["render_email", "test_webhook_flow"]);
|
|
943
|
+
var REMOTE_MAX_WAIT_MS = 5e4;
|
|
944
|
+
var REMOTE_MIN_POLL_MS = 1e3;
|
|
945
|
+
function durationMs(value) {
|
|
946
|
+
if (typeof value === "number") return Number.isFinite(value) && value >= 0 ? value : null;
|
|
947
|
+
if (typeof value !== "string") return null;
|
|
948
|
+
try {
|
|
949
|
+
const ms = (0, import_sdk.parseDuration)(value);
|
|
950
|
+
return Number.isFinite(ms) && ms >= 0 ? ms : null;
|
|
951
|
+
} catch {
|
|
952
|
+
return null;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
function toolTarget(target, remote, handles) {
|
|
956
|
+
return {
|
|
957
|
+
tool(name, description, shape, handler) {
|
|
958
|
+
if (remote && LOCAL_ONLY_TOOLS.has(name)) return { remove() {
|
|
959
|
+
} };
|
|
960
|
+
const wrapped = remote ? async (args) => {
|
|
961
|
+
const input = args;
|
|
962
|
+
if (TARGET_URL_TOOLS.has(name) && input.targetUrl !== void 0) {
|
|
963
|
+
return {
|
|
964
|
+
...textContent(
|
|
965
|
+
"targetUrl needs the local MCP server (npx -y @webhooks-cc/mcp): the hosted server cannot reach your machine. Leave it out here."
|
|
966
|
+
),
|
|
967
|
+
isError: true
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
const bounded = { ...input };
|
|
971
|
+
if ("pollInterval" in shape && input.pollInterval === void 0) {
|
|
972
|
+
bounded.pollInterval = REMOTE_MIN_POLL_MS;
|
|
973
|
+
}
|
|
974
|
+
for (const [key, bound] of [
|
|
975
|
+
["timeout", (ms) => Math.min(ms, REMOTE_MAX_WAIT_MS)],
|
|
976
|
+
[
|
|
977
|
+
"pollInterval",
|
|
978
|
+
(ms) => Math.min(Math.max(ms, REMOTE_MIN_POLL_MS), REMOTE_MAX_WAIT_MS)
|
|
979
|
+
]
|
|
980
|
+
]) {
|
|
981
|
+
if (bounded[key] === void 0) continue;
|
|
982
|
+
const ms = durationMs(bounded[key]);
|
|
983
|
+
if (ms === null) {
|
|
984
|
+
return {
|
|
985
|
+
...textContent(`${key} must be a duration such as "30s" or milliseconds.`),
|
|
986
|
+
isError: true
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
bounded[key] = bound(ms);
|
|
990
|
+
}
|
|
991
|
+
return handler(bounded);
|
|
992
|
+
} : handler;
|
|
993
|
+
const handle = target.tool(name, description, shape, wrapped);
|
|
994
|
+
handles.push(handle);
|
|
995
|
+
return handle;
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
function registerTools(target, client, options = {}) {
|
|
1000
|
+
const handles = [];
|
|
1001
|
+
const server = toolTarget(target, options.remote === true, handles);
|
|
883
1002
|
server.tool(
|
|
884
1003
|
"create_endpoint",
|
|
885
1004
|
"Create a webhook endpoint. Returns the endpoint slug, URL, and metadata.",
|
|
@@ -1000,7 +1119,7 @@ function registerTools(server, client) {
|
|
|
1000
1119
|
);
|
|
1001
1120
|
server.tool(
|
|
1002
1121
|
"update_endpoint",
|
|
1003
|
-
"Update an endpoint name, mock response, conditional response rules, signing configuration,
|
|
1122
|
+
"Update an endpoint name, mock response, conditional response rules, signing configuration, whether codes and links are picked out of its emails, or the named fields picked out of them.",
|
|
1004
1123
|
{
|
|
1005
1124
|
slug: import_zod2.z.string().describe("The endpoint slug to update"),
|
|
1006
1125
|
name: import_zod2.z.string().optional().describe("New display name"),
|
|
@@ -1014,7 +1133,17 @@ function registerTools(server, client) {
|
|
|
1014
1133
|
signingHeader: import_zod2.z.string().nullable().optional().describe(
|
|
1015
1134
|
"Custom signature header name (only for generic-hmac provider), or null to clear"
|
|
1016
1135
|
),
|
|
1017
|
-
showEmailExtracts: import_zod2.z.boolean().optional().describe("Pick codes and links out of emails (dashboard and forwarded JSON). Owner only.")
|
|
1136
|
+
showEmailExtracts: import_zod2.z.boolean().optional().describe("Pick codes and links out of emails (dashboard and forwarded JSON). Owner only."),
|
|
1137
|
+
emailFields: import_zod2.z.array(
|
|
1138
|
+
import_zod2.z.object({
|
|
1139
|
+
name: import_zod2.z.string().describe("Letters, digits and _, starting with a letter: {{ fields.name }}"),
|
|
1140
|
+
from: import_zod2.z.enum(["subject", "text", "html", "from", "to", "header"]).describe("What it reads; html is the HTML as text"),
|
|
1141
|
+
header: import_zod2.z.string().optional().describe('The header name, for from "header"'),
|
|
1142
|
+
pattern: import_zod2.z.string().describe("RE2 regular expression; the first group is the value (no lookarounds)")
|
|
1143
|
+
})
|
|
1144
|
+
).max(20).nullable().optional().describe(
|
|
1145
|
+
"Named fields picked out of every email: shown with it, `fields` in its JSON, read by forwarding templates and filters (email_field). Replaces the list; null or [] removes them. Owner only."
|
|
1146
|
+
)
|
|
1018
1147
|
},
|
|
1019
1148
|
withErrorHandling(
|
|
1020
1149
|
async ({
|
|
@@ -1026,7 +1155,8 @@ function registerTools(server, client) {
|
|
|
1026
1155
|
signingProvider,
|
|
1027
1156
|
signingSecret,
|
|
1028
1157
|
signingHeader,
|
|
1029
|
-
showEmailExtracts
|
|
1158
|
+
showEmailExtracts,
|
|
1159
|
+
emailFields
|
|
1030
1160
|
}) => {
|
|
1031
1161
|
const endpoint = await client.endpoints.update(slug, {
|
|
1032
1162
|
name,
|
|
@@ -1036,12 +1166,33 @@ function registerTools(server, client) {
|
|
|
1036
1166
|
signingProvider,
|
|
1037
1167
|
signingSecret,
|
|
1038
1168
|
signingHeader,
|
|
1039
|
-
showEmailExtracts
|
|
1169
|
+
showEmailExtracts,
|
|
1170
|
+
emailFields
|
|
1040
1171
|
});
|
|
1041
1172
|
return jsonContent(endpoint);
|
|
1042
1173
|
}
|
|
1043
1174
|
)
|
|
1044
1175
|
);
|
|
1176
|
+
server.tool(
|
|
1177
|
+
"preview_response",
|
|
1178
|
+
"Dry run of an endpoint's responses: which response rule answers a captured request, and the response with its {{ placeholders }} filled in, exactly as the receiver would send it. Nothing is sent. Pass mockResponse or responseRules to try settings before saving them with update_endpoint.",
|
|
1179
|
+
{
|
|
1180
|
+
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1181
|
+
requestId: import_zod2.z.string().optional().describe(
|
|
1182
|
+
"A captured request on the endpoint (default: the newest HTTP request, or a sample)"
|
|
1183
|
+
),
|
|
1184
|
+
mockResponse: mockResponseSchema.nullable().optional().describe("Try this default response instead of the saved one (null: none)"),
|
|
1185
|
+
responseRules: responseRulesSchema.nullable().optional().describe("Try these rules instead of the saved ones (null: none)")
|
|
1186
|
+
},
|
|
1187
|
+
withErrorHandling(async ({ slug, requestId, mockResponse, responseRules }) => {
|
|
1188
|
+
const preview = await client.endpoints.previewResponse(slug, {
|
|
1189
|
+
...requestId ? { requestId } : {},
|
|
1190
|
+
...mockResponse !== void 0 ? { mockResponse } : {},
|
|
1191
|
+
...responseRules !== void 0 ? { responseRules } : {}
|
|
1192
|
+
});
|
|
1193
|
+
return jsonContent(preview);
|
|
1194
|
+
})
|
|
1195
|
+
);
|
|
1045
1196
|
server.tool(
|
|
1046
1197
|
"delete_endpoint",
|
|
1047
1198
|
"Delete a webhook endpoint and all its captured requests.",
|
|
@@ -1257,6 +1408,39 @@ function registerTools(server, client) {
|
|
|
1257
1408
|
});
|
|
1258
1409
|
})
|
|
1259
1410
|
);
|
|
1411
|
+
server.tool(
|
|
1412
|
+
"render_email",
|
|
1413
|
+
"A captured email in the shape an inbound mail provider posts it (postmark, sendgrid, mailgun, cloudmailin), to test the inbound handler you wrote for that provider against real mail. Returns the method, headers and body; with targetUrl (your local handler), posts it there and returns the answer. A Mailgun form is signed when you pass the signing key your handler checks.",
|
|
1414
|
+
{
|
|
1415
|
+
requestId: import_zod2.z.string().describe("The captured email's request ID"),
|
|
1416
|
+
format: import_zod2.z.enum(["postmark", "sendgrid", "mailgun", "cloudmailin", "json"]).describe("Whose shape; json is webhooks.cc's own email.received JSON (unsigned)"),
|
|
1417
|
+
mailgunSigningKey: import_zod2.z.string().optional().describe("For mailgun: the signing key your handler verifies with (a test key)"),
|
|
1418
|
+
targetUrl: httpUrlSchema.optional().describe("Post it here, e.g. http://localhost:3000/api/inbound/postmark")
|
|
1419
|
+
},
|
|
1420
|
+
withErrorHandling(async ({ requestId, format, mailgunSigningKey, targetUrl }) => {
|
|
1421
|
+
const rendered = await client.emails.render(requestId, {
|
|
1422
|
+
format,
|
|
1423
|
+
...mailgunSigningKey ? { mailgunSigningKey } : {}
|
|
1424
|
+
});
|
|
1425
|
+
if (!targetUrl) {
|
|
1426
|
+
return jsonContent(
|
|
1427
|
+
shrinkRendered(rendered, MAX_BODY_SIZE)
|
|
1428
|
+
);
|
|
1429
|
+
}
|
|
1430
|
+
const response = await fetch(targetUrl, {
|
|
1431
|
+
method: "POST",
|
|
1432
|
+
headers: { "content-type": rendered.headers["content-type"] },
|
|
1433
|
+
body: rendered.body,
|
|
1434
|
+
redirect: "manual"
|
|
1435
|
+
});
|
|
1436
|
+
return jsonContent({
|
|
1437
|
+
format,
|
|
1438
|
+
status: response.status,
|
|
1439
|
+
statusText: response.statusText,
|
|
1440
|
+
body: await readBodyTruncated(response)
|
|
1441
|
+
});
|
|
1442
|
+
})
|
|
1443
|
+
);
|
|
1260
1444
|
server.tool(
|
|
1261
1445
|
"compare_requests",
|
|
1262
1446
|
"Compare two captured requests and show the structured differences.",
|
|
@@ -1496,7 +1680,7 @@ function registerTools(server, client) {
|
|
|
1496
1680
|
verifySignature: shouldVerify,
|
|
1497
1681
|
cleanup
|
|
1498
1682
|
}) => {
|
|
1499
|
-
const flow = client.flow().createEndpoint({ expiresIn: "1h" }).waitForCapture({ timeout: "30s" });
|
|
1683
|
+
const flow = client.flow().createEndpoint({ expiresIn: "1h" }).waitForCapture({ timeout: "30s", pollInterval: "1s" });
|
|
1500
1684
|
if (mockStatus !== void 0) {
|
|
1501
1685
|
flow.setMock({
|
|
1502
1686
|
status: mockStatus,
|
|
@@ -1621,10 +1805,14 @@ function registerTools(server, client) {
|
|
|
1621
1805
|
includeHtml: import_zod2.z.boolean().default(false).describe("Include the HTML part (default: false)")
|
|
1622
1806
|
},
|
|
1623
1807
|
withErrorHandling(async ({ endpointSlug, tag, subject, from, timeout, since, includeHtml }) => {
|
|
1624
|
-
const
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1808
|
+
const view = await emailEndpointView(client, endpointSlug);
|
|
1809
|
+
const email = await client.emails.waitFor(endpointSlug, {
|
|
1810
|
+
tag,
|
|
1811
|
+
subject,
|
|
1812
|
+
from,
|
|
1813
|
+
timeout,
|
|
1814
|
+
since
|
|
1815
|
+
});
|
|
1628
1816
|
return jsonContent(emailDetail(client, email, view, includeHtml));
|
|
1629
1817
|
})
|
|
1630
1818
|
);
|
|
@@ -1640,9 +1828,79 @@ function registerTools(server, client) {
|
|
|
1640
1828
|
return jsonContent({ ...result, address: client.emails.address(slug, tag) });
|
|
1641
1829
|
})
|
|
1642
1830
|
);
|
|
1831
|
+
server.tool(
|
|
1832
|
+
"list_destinations",
|
|
1833
|
+
"List where an endpoint's captures go: its destinations (up to 10) with their URL, kinds, filter, format, templates and timing, and with summary: true their last 24 hours. Owner only.",
|
|
1834
|
+
{
|
|
1835
|
+
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1836
|
+
summary: import_zod2.z.boolean().default(true).describe("Add each destination's last 24 hours")
|
|
1837
|
+
},
|
|
1838
|
+
withErrorHandling(async ({ slug, summary }) => {
|
|
1839
|
+
return jsonContent(await client.destinations.list(slug, { summary }));
|
|
1840
|
+
})
|
|
1841
|
+
);
|
|
1842
|
+
server.tool(
|
|
1843
|
+
"add_destination",
|
|
1844
|
+
"Add a destination for an endpoint's captures: your server (as received or signed JSON), a Slack or Discord channel (chat, optionally your own messageTemplate, throttleSeconds 1 for a heads-up), or a JSON body you shape with bodyTemplate (for Zapier, n8n or your API). filter sends only matching captures; delaySeconds makes it a callback. Each destination gets its own signing secret. Check it with preview_destination before relying on it. Owner only.",
|
|
1845
|
+
{
|
|
1846
|
+
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1847
|
+
url: httpUrlSchema.describe("https URL on a public host name, or a Slack or Discord webhook"),
|
|
1848
|
+
...destinationFields
|
|
1849
|
+
},
|
|
1850
|
+
withErrorHandling(async ({ slug, ...input }) => {
|
|
1851
|
+
return jsonContent(await client.destinations.create(slug, input));
|
|
1852
|
+
})
|
|
1853
|
+
);
|
|
1854
|
+
server.tool(
|
|
1855
|
+
"update_destination",
|
|
1856
|
+
"Change a destination: any of its fields; only the ones you pass change. Owner only.",
|
|
1857
|
+
{
|
|
1858
|
+
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1859
|
+
destinationId: import_zod2.z.string().describe("The destination's id (from list_destinations)"),
|
|
1860
|
+
url: httpUrlSchema.optional().describe("A new URL"),
|
|
1861
|
+
...destinationFields
|
|
1862
|
+
},
|
|
1863
|
+
withErrorHandling(async ({ slug, destinationId, ...input }) => {
|
|
1864
|
+
return jsonContent(await client.destinations.update(slug, destinationId, input));
|
|
1865
|
+
})
|
|
1866
|
+
);
|
|
1867
|
+
server.tool(
|
|
1868
|
+
"remove_destination",
|
|
1869
|
+
"Remove a destination. Deliveries still waiting for it are marked failed; its log stays. Owner only.",
|
|
1870
|
+
{
|
|
1871
|
+
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1872
|
+
destinationId: import_zod2.z.string().describe("The destination's id")
|
|
1873
|
+
},
|
|
1874
|
+
withErrorHandling(async ({ slug, destinationId }) => {
|
|
1875
|
+
await client.destinations.delete(slug, destinationId);
|
|
1876
|
+
return textContent(`Destination ${destinationId} removed.`);
|
|
1877
|
+
})
|
|
1878
|
+
);
|
|
1879
|
+
server.tool(
|
|
1880
|
+
"preview_destination",
|
|
1881
|
+
"Dry run of a destination against a captured request: whether its filter lets the request through and the exact request it would send (method, URL, headers, body with placeholders filled in). Nothing is sent. Use destinationId for a saved destination, destination for unsaved fields (on top of the saved ones, or with a url on their own), or both.",
|
|
1882
|
+
{
|
|
1883
|
+
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1884
|
+
destinationId: import_zod2.z.string().optional().describe("A saved destination"),
|
|
1885
|
+
destination: import_zod2.z.object({ url: httpUrlSchema.optional(), ...destinationFields }).optional().describe("Unsaved fields to try"),
|
|
1886
|
+
requestId: import_zod2.z.string().optional().describe("A captured request (default: the newest of the kind it takes, or a sample)")
|
|
1887
|
+
},
|
|
1888
|
+
withErrorHandling(async ({ slug, destinationId, destination, requestId }) => {
|
|
1889
|
+
if (!destinationId && !destination?.url) {
|
|
1890
|
+
throw new Error("Pass destinationId, or a destination with a url");
|
|
1891
|
+
}
|
|
1892
|
+
return jsonContent(
|
|
1893
|
+
await client.destinations.preview(slug, {
|
|
1894
|
+
...destinationId ? { destinationId } : {},
|
|
1895
|
+
...destination ? { destination } : {},
|
|
1896
|
+
...requestId ? { requestId } : {}
|
|
1897
|
+
})
|
|
1898
|
+
);
|
|
1899
|
+
})
|
|
1900
|
+
);
|
|
1643
1901
|
server.tool(
|
|
1644
1902
|
"configure_forwarding",
|
|
1645
|
-
"Change an endpoint's forwarding: the URL, on or off, which kinds
|
|
1903
|
+
"Change an endpoint's primary forwarding destination (the one the endpoint's forward* fields stand for): the URL, on or off, which kinds, the format, your own headers, and the advanced options. Only the arguments you pass change. Owner only. For several destinations, filters, templates, throttling or delays, use add_destination and update_destination.",
|
|
1646
1904
|
{
|
|
1647
1905
|
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1648
1906
|
url: httpUrlSchema.nullable().optional().describe("https URL on a public host name, or null to remove it (forwarding must be off)"),
|
|
@@ -1667,8 +1925,8 @@ function registerTools(server, client) {
|
|
|
1667
1925
|
"JSON body field with the sender's own timestamp, e.g. publishedAt or data.publishedAt; null clears it"
|
|
1668
1926
|
)
|
|
1669
1927
|
},
|
|
1670
|
-
withErrorHandling(async ({ slug, ...
|
|
1671
|
-
const endpoint = await client.forwarding.configure(slug,
|
|
1928
|
+
withErrorHandling(async ({ slug, ...options2 }) => {
|
|
1929
|
+
const endpoint = await client.forwarding.configure(slug, options2);
|
|
1672
1930
|
return jsonContent({
|
|
1673
1931
|
slug: endpoint.slug,
|
|
1674
1932
|
forwardEnabled: endpoint.forwardEnabled ?? false,
|
|
@@ -1690,23 +1948,32 @@ function registerTools(server, client) {
|
|
|
1690
1948
|
"Read (or with rotate: true, replace) the whsec_ secret forwarded deliveries are signed with. Owner only. Treat the value as a credential: put it in the handler's environment, not in code.",
|
|
1691
1949
|
{
|
|
1692
1950
|
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1951
|
+
destinationId: import_zod2.z.string().optional().describe("A destination (default: the endpoint's primary destination)"),
|
|
1693
1952
|
rotate: import_zod2.z.boolean().default(false).describe("Replace the secret; every delivery from now on uses the new one")
|
|
1694
1953
|
},
|
|
1695
|
-
withErrorHandling(async ({ slug, rotate }) => {
|
|
1696
|
-
const secret = rotate ? await client.forwarding.rotateSecret(slug) : await client.forwarding.secret(slug);
|
|
1954
|
+
withErrorHandling(async ({ slug, destinationId, rotate }) => {
|
|
1955
|
+
const secret = destinationId ? rotate ? await client.destinations.rotateSecret(slug, destinationId) : await client.destinations.secret(slug, destinationId) : rotate ? await client.forwarding.rotateSecret(slug) : await client.forwarding.secret(slug);
|
|
1697
1956
|
return jsonContent({ secret, rotated: rotate });
|
|
1698
1957
|
})
|
|
1699
1958
|
);
|
|
1700
1959
|
server.tool(
|
|
1701
1960
|
"test_forwarding",
|
|
1702
|
-
"Send the endpoint's newest captured request of a kind (or a sample) to
|
|
1961
|
+
"Send the endpoint's newest captured request of a kind (or a sample, or requestId) to a destination once, exactly as a delivery would go out, and report the answer. Works whether it is on or off, and whatever its filter says; nothing is retried or logged.",
|
|
1703
1962
|
{
|
|
1704
1963
|
slug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
)
|
|
1964
|
+
destinationId: import_zod2.z.string().optional().describe("A destination (default: the endpoint's primary destination)"),
|
|
1965
|
+
kind: import_zod2.z.enum(["http", "email"]).optional().describe("Which kind to send (default: http when the destination takes HTTP requests)"),
|
|
1966
|
+
requestId: import_zod2.z.string().optional().describe("With destinationId: a captured request to send")
|
|
1708
1967
|
},
|
|
1709
|
-
withErrorHandling(async ({ slug, kind }) => {
|
|
1968
|
+
withErrorHandling(async ({ slug, destinationId, kind, requestId }) => {
|
|
1969
|
+
if (destinationId) {
|
|
1970
|
+
return jsonContent(
|
|
1971
|
+
await client.destinations.test(slug, destinationId, {
|
|
1972
|
+
...kind ? { kind } : {},
|
|
1973
|
+
...requestId ? { requestId } : {}
|
|
1974
|
+
})
|
|
1975
|
+
);
|
|
1976
|
+
}
|
|
1710
1977
|
return jsonContent(await client.forwarding.test(slug, kind ? { kind } : {}));
|
|
1711
1978
|
})
|
|
1712
1979
|
);
|
|
@@ -1717,17 +1984,25 @@ function registerTools(server, client) {
|
|
|
1717
1984
|
endpointSlug: import_zod2.z.string().optional().describe("List the endpoint's delivery log"),
|
|
1718
1985
|
requestId: import_zod2.z.string().optional().describe("List every delivery and try of this request"),
|
|
1719
1986
|
limit: import_zod2.z.number().int().min(1).max(100).default(20).describe("With endpointSlug: max rows"),
|
|
1720
|
-
status: import_zod2.z.enum(["all", "pending", "failed"]).default("all").describe(
|
|
1987
|
+
status: import_zod2.z.enum(["all", "pending", "failed", "skipped"]).default("all").describe(
|
|
1988
|
+
"With endpointSlug: all, pending (queued or retrying), failed or skipped (filtered out)"
|
|
1989
|
+
),
|
|
1990
|
+
destinationId: import_zod2.z.string().optional().describe("With endpointSlug: only this destination"),
|
|
1721
1991
|
before: import_zod2.z.string().optional().describe("With endpointSlug: a row's cursor, to list older rows")
|
|
1722
1992
|
},
|
|
1723
|
-
withErrorHandling(async ({ endpointSlug, requestId, limit, status, before }) => {
|
|
1993
|
+
withErrorHandling(async ({ endpointSlug, requestId, limit, status, destinationId, before }) => {
|
|
1724
1994
|
if (requestId === void 0 === (endpointSlug === void 0)) {
|
|
1725
1995
|
throw new Error("Pass exactly one of endpointSlug or requestId");
|
|
1726
1996
|
}
|
|
1727
1997
|
if (requestId) return jsonContent(await client.forwarding.requestDeliveries(requestId));
|
|
1728
1998
|
const [summary, deliveries] = await Promise.all([
|
|
1729
1999
|
client.forwarding.summary(endpointSlug),
|
|
1730
|
-
client.forwarding.deliveries(endpointSlug, {
|
|
2000
|
+
client.forwarding.deliveries(endpointSlug, {
|
|
2001
|
+
limit,
|
|
2002
|
+
status,
|
|
2003
|
+
before,
|
|
2004
|
+
...destinationId ? { destinationId } : {}
|
|
2005
|
+
})
|
|
1731
2006
|
]);
|
|
1732
2007
|
return jsonContent({
|
|
1733
2008
|
summary,
|
|
@@ -1738,17 +2013,19 @@ function registerTools(server, client) {
|
|
|
1738
2013
|
);
|
|
1739
2014
|
server.tool(
|
|
1740
2015
|
"redeliver",
|
|
1741
|
-
"Forward again with the
|
|
2016
|
+
"Forward again with the current settings: one captured request or email (requestId; to every destination that takes it, or with destinationId to that one whatever its filter says), or every request whose latest delivery failed (endpointSlug, optionally only destinationId, oldest first, up to 1,000 at a time per destination). Owner only.",
|
|
1742
2017
|
{
|
|
1743
2018
|
requestId: import_zod2.z.string().optional().describe("The request or email to send again"),
|
|
1744
|
-
endpointSlug: import_zod2.z.string().optional().describe("Send every failed delivery of this endpoint again")
|
|
2019
|
+
endpointSlug: import_zod2.z.string().optional().describe("Send every failed delivery of this endpoint again"),
|
|
2020
|
+
destinationId: import_zod2.z.string().optional().describe("Only this destination")
|
|
1745
2021
|
},
|
|
1746
|
-
withErrorHandling(async ({ requestId, endpointSlug }) => {
|
|
2022
|
+
withErrorHandling(async ({ requestId, endpointSlug, destinationId }) => {
|
|
1747
2023
|
if (requestId === void 0 === (endpointSlug === void 0)) {
|
|
1748
2024
|
throw new Error("Pass exactly one of requestId or endpointSlug");
|
|
1749
2025
|
}
|
|
2026
|
+
const options2 = destinationId ? { destinationId } : {};
|
|
1750
2027
|
return jsonContent(
|
|
1751
|
-
requestId ? await client.forwarding.redeliver(requestId) : await client.forwarding.redeliverFailed(endpointSlug)
|
|
2028
|
+
requestId ? await client.forwarding.redeliver(requestId, options2) : await client.forwarding.redeliverFailed(endpointSlug, options2)
|
|
1752
2029
|
);
|
|
1753
2030
|
})
|
|
1754
2031
|
);
|
|
@@ -1761,6 +2038,7 @@ function registerTools(server, client) {
|
|
|
1761
2038
|
return jsonContent(description);
|
|
1762
2039
|
})
|
|
1763
2040
|
);
|
|
2041
|
+
return handles;
|
|
1764
2042
|
}
|
|
1765
2043
|
|
|
1766
2044
|
// src/sandbox-tools.ts
|
|
@@ -1772,7 +2050,6 @@ var SANDBOX = {
|
|
|
1772
2050
|
};
|
|
1773
2051
|
var DEFAULT_WAIT_SECONDS = 45;
|
|
1774
2052
|
var MAX_WAIT_SECONDS = 600;
|
|
1775
|
-
var TOKEN_REFRESH_MARGIN_MS = 6e4;
|
|
1776
2053
|
var DOCS_URL = "https://webhooks.cc/docs/agents";
|
|
1777
2054
|
var ACCOUNT_ONLY_OPTIONS = [
|
|
1778
2055
|
"mockResponse",
|
|
@@ -1783,20 +2060,29 @@ var ACCOUNT_ONLY_OPTIONS = [
|
|
|
1783
2060
|
"signingHeader"
|
|
1784
2061
|
];
|
|
1785
2062
|
var AgentSession = class {
|
|
1786
|
-
constructor(baseUrl, webhookUrl, clientName) {
|
|
2063
|
+
constructor(baseUrl, webhookUrl, clientName, credentialsFile) {
|
|
1787
2064
|
this.baseUrl = baseUrl;
|
|
1788
2065
|
this.webhookUrl = webhookUrl;
|
|
1789
2066
|
this.clientName = clientName;
|
|
2067
|
+
this.credentialsFile = credentialsFile;
|
|
1790
2068
|
this.sandbox = null;
|
|
1791
|
-
this.
|
|
1792
|
-
|
|
2069
|
+
this.connect = null;
|
|
2070
|
+
}
|
|
2071
|
+
sandboxOptions() {
|
|
2072
|
+
return {
|
|
2073
|
+
baseUrl: this.baseUrl,
|
|
2074
|
+
webhookUrl: this.webhookUrl,
|
|
2075
|
+
clientName: this.clientName,
|
|
2076
|
+
credentialsFile: this.credentialsFile
|
|
2077
|
+
};
|
|
1793
2078
|
}
|
|
1794
2079
|
/**
|
|
1795
|
-
* The sandbox
|
|
1796
|
-
*
|
|
1797
|
-
*
|
|
1798
|
-
*
|
|
1799
|
-
*
|
|
2080
|
+
* The sandbox: the one saved by an earlier session when it is still live,
|
|
2081
|
+
* else a new registration (and again after it expired). Tool calls can run
|
|
2082
|
+
* in parallel: this.sandbox is read again after every await, and a new
|
|
2083
|
+
* sandbox is assigned with no await in between, so parallel calls share
|
|
2084
|
+
* one registration. Calls waiting on a registration that fails get its
|
|
2085
|
+
* error (a 429 asks them to wait); the next call starts again.
|
|
1800
2086
|
*/
|
|
1801
2087
|
async sandboxClient() {
|
|
1802
2088
|
while (this.sandbox) {
|
|
@@ -1804,35 +2090,36 @@ var AgentSession = class {
|
|
|
1804
2090
|
const sandbox = await pending;
|
|
1805
2091
|
if (!this.expired(pending, sandbox)) return sandbox;
|
|
1806
2092
|
}
|
|
1807
|
-
const created = import_sdk2.WebhooksCC.sandbox(
|
|
1808
|
-
baseUrl: this.baseUrl,
|
|
1809
|
-
webhookUrl: this.webhookUrl,
|
|
1810
|
-
clientName: this.clientName
|
|
1811
|
-
});
|
|
2093
|
+
const created = import_sdk2.WebhooksCC.sandbox(this.sandboxOptions());
|
|
1812
2094
|
this.sandbox = created;
|
|
1813
2095
|
created.catch(() => {
|
|
1814
2096
|
if (this.sandbox === created) this.sandbox = null;
|
|
1815
2097
|
});
|
|
1816
2098
|
return created;
|
|
1817
2099
|
}
|
|
1818
|
-
/** The live sandbox,
|
|
2100
|
+
/** The live sandbox, from this session or the credential file; never registers one. */
|
|
1819
2101
|
async existingSandbox() {
|
|
1820
2102
|
const pending = this.sandbox;
|
|
1821
|
-
if (
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
2103
|
+
if (pending) {
|
|
2104
|
+
let sandbox;
|
|
2105
|
+
try {
|
|
2106
|
+
sandbox = await pending;
|
|
2107
|
+
} catch {
|
|
2108
|
+
return null;
|
|
2109
|
+
}
|
|
2110
|
+
return this.expired(pending, sandbox) ? null : sandbox;
|
|
1827
2111
|
}
|
|
1828
|
-
|
|
2112
|
+
const resumed = await import_sdk2.SandboxClient.resume(this.sandboxOptions()).catch(() => null);
|
|
2113
|
+
if (!resumed) return null;
|
|
2114
|
+
if (!this.sandbox) this.sandbox = Promise.resolve(resumed);
|
|
2115
|
+
return this.sandbox;
|
|
1829
2116
|
}
|
|
1830
2117
|
/** True when the sandbox is past its 24 hours; then it is dropped. */
|
|
1831
2118
|
expired(pending, sandbox) {
|
|
1832
2119
|
if (sandbox.expiresAt.getTime() > Date.now()) return false;
|
|
1833
2120
|
if (this.sandbox === pending) this.sandbox = null;
|
|
1834
|
-
if (this.
|
|
1835
|
-
this.
|
|
2121
|
+
if (this.connect?.via === "sandbox" && this.connect.sandbox === sandbox) {
|
|
2122
|
+
this.connect = null;
|
|
1836
2123
|
}
|
|
1837
2124
|
return true;
|
|
1838
2125
|
}
|
|
@@ -1843,26 +2130,11 @@ var AgentSession = class {
|
|
|
1843
2130
|
}
|
|
1844
2131
|
return sandbox;
|
|
1845
2132
|
}
|
|
1846
|
-
|
|
2133
|
+
reset() {
|
|
1847
2134
|
this.sandbox = null;
|
|
2135
|
+
this.connect = null;
|
|
1848
2136
|
}
|
|
1849
2137
|
};
|
|
1850
|
-
function claimedTokens(baseUrl, assertion, first) {
|
|
1851
|
-
let current = first;
|
|
1852
|
-
let pending = null;
|
|
1853
|
-
return async (options) => {
|
|
1854
|
-
if (!options?.forceRefresh && current.expiresAt.getTime() - Date.now() > TOKEN_REFRESH_MARGIN_MS) {
|
|
1855
|
-
return current.accessToken;
|
|
1856
|
-
}
|
|
1857
|
-
if (!pending) {
|
|
1858
|
-
pending = import_sdk2.WebhooksCC.agent.exchange({ baseUrl, assertion }).finally(() => {
|
|
1859
|
-
pending = null;
|
|
1860
|
-
});
|
|
1861
|
-
}
|
|
1862
|
-
current = await pending;
|
|
1863
|
-
return current.accessToken;
|
|
1864
|
-
};
|
|
1865
|
-
}
|
|
1866
2138
|
function minutesLeft(until) {
|
|
1867
2139
|
return Math.max(1, Math.round((until.getTime() - Date.now()) / 6e4));
|
|
1868
2140
|
}
|
|
@@ -1870,16 +2142,53 @@ function onlyKind(requests, kind) {
|
|
|
1870
2142
|
if (!kind) return requests;
|
|
1871
2143
|
return requests.filter((request) => (request.kind ?? "http") === kind);
|
|
1872
2144
|
}
|
|
2145
|
+
function savedTo(credentialsFile) {
|
|
2146
|
+
return import_sdk2.AgentCredentialStore.resolve(credentialsFile)?.path ?? null;
|
|
2147
|
+
}
|
|
1873
2148
|
function registerSandboxTools(server, options = {}) {
|
|
1874
2149
|
const baseUrl = options.baseUrl ?? process.env.WHK_BASE_URL ?? "https://webhooks.cc";
|
|
2150
|
+
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
|
1875
2151
|
const session = new AgentSession(
|
|
1876
2152
|
baseUrl,
|
|
1877
|
-
|
|
1878
|
-
options.clientName ?? "webhooks-cc-mcp"
|
|
2153
|
+
webhookUrl,
|
|
2154
|
+
options.clientName ?? "webhooks-cc-mcp",
|
|
2155
|
+
options.credentialsFile
|
|
1879
2156
|
);
|
|
1880
|
-
|
|
2157
|
+
let handles = [];
|
|
2158
|
+
let shown = 0;
|
|
2159
|
+
const swap = (register) => {
|
|
2160
|
+
const id = ++shown;
|
|
2161
|
+
for (const handle of handles) handle.remove();
|
|
2162
|
+
handles = register(id);
|
|
2163
|
+
};
|
|
2164
|
+
const showSandbox = () => swap(() => sandboxToolSet(server, session, showAccount));
|
|
2165
|
+
const savedAccount = (id) => options.credentialsFile === false ? null : import_sdk2.WebhooksCC.fromSavedConnection({
|
|
2166
|
+
baseUrl,
|
|
2167
|
+
webhookUrl,
|
|
2168
|
+
credentialsFile: options.credentialsFile,
|
|
2169
|
+
// The human disconnected the agent: back to the sandbox, unless a
|
|
2170
|
+
// newer connection is already on display.
|
|
2171
|
+
onRevoked: () => queueMicrotask(() => {
|
|
2172
|
+
if (shown === id) showSandbox();
|
|
2173
|
+
})
|
|
2174
|
+
});
|
|
2175
|
+
function showAccount(client) {
|
|
2176
|
+
swap((id) => registerTools(server, savedAccount(id) ?? client));
|
|
2177
|
+
options.onConnected?.(client);
|
|
2178
|
+
}
|
|
2179
|
+
const saved = savedAccount(1);
|
|
2180
|
+
if (saved) {
|
|
2181
|
+
swap(() => registerTools(server, saved));
|
|
2182
|
+
options.onConnected?.(saved);
|
|
2183
|
+
} else {
|
|
2184
|
+
showSandbox();
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
function sandboxToolSet(server, session, showAccount) {
|
|
2188
|
+
const tools = [];
|
|
1881
2189
|
const limits = `up to ${SANDBOX.maxEndpoints} endpoints at a time, ${SANDBOX.requestsPerEndpoint} captured requests each and ${SANDBOX.requests} in all, deleted ${SANDBOX.lifetimeHours} hours after the sandbox was created`;
|
|
1882
|
-
|
|
2190
|
+
const file = savedTo(session.credentialsFile);
|
|
2191
|
+
tools.push(
|
|
1883
2192
|
server.tool(
|
|
1884
2193
|
"create_endpoint",
|
|
1885
2194
|
`Create a temporary webhook capture endpoint in the agent sandbox (no account needed): ${limits}. The first call registers the sandbox, which takes a few seconds of proof of work. Mock responses, notifications and signature verification need an account: see connect_account.`,
|
|
@@ -1903,7 +2212,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
1903
2212
|
})
|
|
1904
2213
|
)
|
|
1905
2214
|
);
|
|
1906
|
-
|
|
2215
|
+
tools.push(
|
|
1907
2216
|
server.tool(
|
|
1908
2217
|
"list_endpoints",
|
|
1909
2218
|
"List this agent's sandbox endpoints. Empty until create_endpoint starts the sandbox.",
|
|
@@ -1917,7 +2226,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
1917
2226
|
})
|
|
1918
2227
|
)
|
|
1919
2228
|
);
|
|
1920
|
-
|
|
2229
|
+
tools.push(
|
|
1921
2230
|
server.tool(
|
|
1922
2231
|
"get_endpoint",
|
|
1923
2232
|
"Get one of this agent's sandbox endpoints by slug, with its sandbox limits and usage.",
|
|
@@ -1927,7 +2236,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
1927
2236
|
})
|
|
1928
2237
|
)
|
|
1929
2238
|
);
|
|
1930
|
-
|
|
2239
|
+
tools.push(
|
|
1931
2240
|
server.tool(
|
|
1932
2241
|
"delete_endpoint",
|
|
1933
2242
|
"Delete one of this agent's sandbox endpoints and its captured requests. Frees a slot; the sandbox's request budget is not refunded.",
|
|
@@ -1938,7 +2247,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
1938
2247
|
})
|
|
1939
2248
|
)
|
|
1940
2249
|
);
|
|
1941
|
-
|
|
2250
|
+
tools.push(
|
|
1942
2251
|
server.tool(
|
|
1943
2252
|
"list_requests",
|
|
1944
2253
|
"List recent requests captured by a sandbox endpoint, newest first.",
|
|
@@ -1950,7 +2259,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
1950
2259
|
})
|
|
1951
2260
|
)
|
|
1952
2261
|
);
|
|
1953
|
-
|
|
2262
|
+
tools.push(
|
|
1954
2263
|
server.tool(
|
|
1955
2264
|
"get_request",
|
|
1956
2265
|
"Get full details for a request captured by a sandbox endpoint.",
|
|
@@ -1961,7 +2270,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
1961
2270
|
})
|
|
1962
2271
|
)
|
|
1963
2272
|
);
|
|
1964
|
-
|
|
2273
|
+
tools.push(
|
|
1965
2274
|
server.tool(
|
|
1966
2275
|
"wait_for_request",
|
|
1967
2276
|
"Wait for a request to arrive at a sandbox endpoint.",
|
|
@@ -1973,7 +2282,7 @@ function registerSandboxTools(server, options = {}) {
|
|
|
1973
2282
|
})
|
|
1974
2283
|
)
|
|
1975
2284
|
);
|
|
1976
|
-
|
|
2285
|
+
tools.push(
|
|
1977
2286
|
server.tool(
|
|
1978
2287
|
"generate_payload_schema",
|
|
1979
2288
|
"Generate TypeScript interfaces, Zod schemas, Pydantic models, or JSON Schema from the bodies of requests captured by a sandbox endpoint. Several requests of one event teach which fields are optional or nullable.",
|
|
@@ -1985,62 +2294,59 @@ function registerSandboxTools(server, options = {}) {
|
|
|
1985
2294
|
})
|
|
1986
2295
|
)
|
|
1987
2296
|
);
|
|
1988
|
-
|
|
2297
|
+
tools.push(
|
|
1989
2298
|
server.tool(
|
|
1990
2299
|
"connect_account",
|
|
1991
2300
|
"Connect this agent to a human's webhooks.cc account, for the full tool set (mock responses, signatures, email, teams) and to keep the sandbox endpoints. Returns a link and a 6-digit code: show both to the human, who signs in or signs up with that email and enters the code. Then call wait_for_connection. Calling again gives a new code.",
|
|
1992
2301
|
{
|
|
1993
|
-
email: import_zod3.z.string().email({ message: "Invalid email address" }).describe(
|
|
2302
|
+
email: import_zod3.z.string().email({ message: "Invalid email address" }).describe(
|
|
2303
|
+
"The email the human signs in to webhooks.cc with (their GitHub or Google primary email if they sign in that way); only someone signed in as it can connect"
|
|
2304
|
+
)
|
|
1994
2305
|
},
|
|
1995
2306
|
withErrorHandling(async ({ email }) => {
|
|
1996
|
-
if (session.connected) {
|
|
1997
|
-
return jsonContent({ connected: true, message: "Already connected to an account." });
|
|
1998
|
-
}
|
|
1999
2307
|
const normalized = email.trim().toLowerCase();
|
|
2308
|
+
const previous = session.connect;
|
|
2000
2309
|
const sandbox = await session.existingSandbox();
|
|
2001
|
-
let
|
|
2310
|
+
let attempt;
|
|
2311
|
+
let movesEndpoints = false;
|
|
2002
2312
|
if (sandbox) {
|
|
2003
|
-
claim = {
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
attempt: await sandbox.claim({ email: normalized }),
|
|
2008
|
-
claimExpires: sandbox.expiresAt
|
|
2009
|
-
};
|
|
2010
|
-
} else if (session.claim?.kind === "service_auth" && session.claim.email === normalized && session.claim.claimExpires.getTime() > Date.now()) {
|
|
2011
|
-
const attempt = await import_sdk2.WebhooksCC.agent.startClaim({
|
|
2012
|
-
baseUrl,
|
|
2013
|
-
claimToken: session.claim.claimToken
|
|
2014
|
-
});
|
|
2015
|
-
claim = { ...session.claim, attempt };
|
|
2313
|
+
const claim = await sandbox.claim({ email: normalized });
|
|
2314
|
+
session.connect = { via: "sandbox", sandbox, email: normalized, attempt: claim };
|
|
2315
|
+
attempt = claim;
|
|
2316
|
+
movesEndpoints = claim.movesEndpoints;
|
|
2016
2317
|
} else {
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2318
|
+
let pending = null;
|
|
2319
|
+
if (previous?.via === "connect" && previous.email === normalized) {
|
|
2320
|
+
pending = await previous.pending.restart().catch((error) => {
|
|
2321
|
+
if (error instanceof import_sdk2.AgentAuthError) return null;
|
|
2322
|
+
throw error;
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
pending ?? (pending = await import_sdk2.WebhooksCC.connect({
|
|
2326
|
+
baseUrl: session.baseUrl,
|
|
2327
|
+
webhookUrl: session.webhookUrl,
|
|
2025
2328
|
email: normalized,
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
};
|
|
2329
|
+
clientName: session.clientName,
|
|
2330
|
+
credentialsFile: session.credentialsFile
|
|
2331
|
+
}));
|
|
2332
|
+
session.connect = { via: "connect", pending, email: normalized };
|
|
2333
|
+
attempt = pending;
|
|
2029
2334
|
}
|
|
2030
|
-
|
|
2031
|
-
const { verificationUri, userCode, expiresAt } = claim.attempt;
|
|
2335
|
+
const { verificationUri, userCode, expiresAt } = attempt;
|
|
2032
2336
|
return jsonContent({
|
|
2033
2337
|
verificationUri,
|
|
2034
2338
|
userCode,
|
|
2035
2339
|
expiresAt: expiresAt.toISOString(),
|
|
2036
2340
|
tellTheHuman: `Open ${verificationUri}, sign in or sign up as ${normalized}, and enter the code ${userCode}.`,
|
|
2037
2341
|
next: `Show the human that line, then call wait_for_connection. The code works for about ${minutesLeft(expiresAt)} minutes; connect_account gives a new one.`,
|
|
2038
|
-
...
|
|
2342
|
+
...sandbox ? {
|
|
2343
|
+
endpoints: movesEndpoints ? "The sandbox endpoints move into that account with what they captured." : "The sandbox endpoints were made in an earlier session, so they stay in the sandbox until it expires; the account starts without them."
|
|
2344
|
+
} : {}
|
|
2039
2345
|
});
|
|
2040
2346
|
})
|
|
2041
2347
|
)
|
|
2042
2348
|
);
|
|
2043
|
-
|
|
2349
|
+
tools.push(
|
|
2044
2350
|
server.tool(
|
|
2045
2351
|
"wait_for_connection",
|
|
2046
2352
|
`Wait for the human to enter the code from connect_account. On success the full webhooks.cc tools for their account replace these sandbox tools. Returns after timeoutSeconds (default ${DEFAULT_WAIT_SECONDS}) if they have not finished yet; call it again to keep waiting.`,
|
|
@@ -2048,28 +2354,22 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2048
2354
|
timeoutSeconds: import_zod3.z.number().int().min(1).max(MAX_WAIT_SECONDS).optional().describe(`How long to wait, in seconds (default ${DEFAULT_WAIT_SECONDS})`)
|
|
2049
2355
|
},
|
|
2050
2356
|
withErrorHandling(async ({ timeoutSeconds }) => {
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
}
|
|
2054
|
-
const claim = session.claim;
|
|
2055
|
-
if (!claim) {
|
|
2357
|
+
const pending = session.connect;
|
|
2358
|
+
if (!pending) {
|
|
2056
2359
|
throw new Error("Nothing to wait for: call connect_account first.");
|
|
2057
2360
|
}
|
|
2058
|
-
|
|
2361
|
+
const timeout = (timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3;
|
|
2362
|
+
const expiresAt = pending.via === "sandbox" ? pending.attempt.expiresAt : pending.pending.expiresAt;
|
|
2363
|
+
let client;
|
|
2059
2364
|
try {
|
|
2060
|
-
|
|
2061
|
-
baseUrl,
|
|
2062
|
-
claimToken: claim.claimToken,
|
|
2063
|
-
interval: claim.attempt.interval,
|
|
2064
|
-
timeout: (timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3
|
|
2065
|
-
});
|
|
2365
|
+
client = pending.via === "sandbox" ? await pending.sandbox.waitForClaim({ timeout }) : await pending.pending.wait({ timeout });
|
|
2066
2366
|
} catch (error) {
|
|
2067
2367
|
if (!(error instanceof import_sdk2.AgentAuthError)) throw error;
|
|
2068
2368
|
if (error.code === "timeout") {
|
|
2069
2369
|
return jsonContent({
|
|
2070
2370
|
connected: false,
|
|
2071
2371
|
status: "waiting",
|
|
2072
|
-
message: `Not connected yet. The code works until ${
|
|
2372
|
+
message: `Not connected yet. The code works until ${expiresAt.toISOString()}; call wait_for_connection again.`
|
|
2073
2373
|
});
|
|
2074
2374
|
}
|
|
2075
2375
|
if (error.code === "expired_token") {
|
|
@@ -2088,34 +2388,25 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2088
2388
|
}
|
|
2089
2389
|
throw error;
|
|
2090
2390
|
}
|
|
2091
|
-
const
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
getAccessToken: claimedTokens(baseUrl, claimed.identityAssertion, claimed.token)
|
|
2095
|
-
});
|
|
2096
|
-
session.connected = true;
|
|
2097
|
-
session.claim = null;
|
|
2098
|
-
session.forgetSandbox();
|
|
2099
|
-
for (const tool of registered) tool.remove();
|
|
2100
|
-
registerTools(server, client);
|
|
2101
|
-
options.onConnected?.(client);
|
|
2391
|
+
const movedEndpoints = pending.via === "sandbox" && pending.attempt.movesEndpoints;
|
|
2392
|
+
session.reset();
|
|
2393
|
+
showAccount(client);
|
|
2102
2394
|
return jsonContent({
|
|
2103
2395
|
connected: true,
|
|
2104
|
-
email:
|
|
2105
|
-
|
|
2106
|
-
message: `Connected to the webhooks.cc account of ${claim.email}. The full tool set has replaced the sandbox tools (the tool list changed; if your client does not pick that up, ask the user to restart it).${claim.kind === "sandbox" ? " The sandbox endpoints are now that account's endpoints, with what they captured." : ""} This connection lasts while this MCP server runs; for a lasting setup, the human can create an API key under Account and set WHK_API_KEY.`
|
|
2396
|
+
email: pending.email,
|
|
2397
|
+
message: `Connected to the webhooks.cc account of ${pending.email}. The full tool set has replaced the sandbox tools (the tool list changed; if your client does not pick that up, ask the user to restart it).${movedEndpoints ? " The sandbox endpoints are now that account's endpoints, with what they captured." : ""} ${file ? `The connection is saved in ${file}, so later sessions, the whk CLI and the SDKs start connected; it lasts 90 days, and the human can remove it under Account, Connected agents.` : "This connection lasts while this MCP server runs; for a lasting setup, the human can create an API key under Account and set WHK_API_KEY."}`
|
|
2107
2398
|
});
|
|
2108
2399
|
})
|
|
2109
2400
|
)
|
|
2110
2401
|
);
|
|
2111
|
-
|
|
2402
|
+
tools.push(
|
|
2112
2403
|
server.tool(
|
|
2113
2404
|
"about_sandbox",
|
|
2114
2405
|
"Explain what this agent can do without a webhooks.cc account, the sandbox limits, and how to connect an account. No network call.",
|
|
2115
2406
|
{},
|
|
2116
2407
|
withErrorHandling(async () => {
|
|
2117
2408
|
return jsonContent({
|
|
2118
|
-
mode: "sandbox (no WHK_API_KEY)",
|
|
2409
|
+
mode: "sandbox (no WHK_API_KEY, no saved connection)",
|
|
2119
2410
|
sandbox: {
|
|
2120
2411
|
maxEndpoints: SANDBOX.maxEndpoints,
|
|
2121
2412
|
requestsPerEndpoint: SANDBOX.requestsPerEndpoint,
|
|
@@ -2126,24 +2417,26 @@ function registerSandboxTools(server, options = {}) {
|
|
|
2126
2417
|
notAvailable: "Mock responses, response rules, notifications, signature verification, email capture, forwarding, teams and sending webhooks."
|
|
2127
2418
|
},
|
|
2128
2419
|
connectAnAccount: [
|
|
2129
|
-
"Call connect_account with the human
|
|
2420
|
+
"Call connect_account with the email the human signs in to webhooks.cc with, and show them the link and code it returns.",
|
|
2130
2421
|
"Call wait_for_connection until it reports connected; the full tools for their account then replace the sandbox tools.",
|
|
2131
|
-
"Sandbox endpoints move into the account with their captured requests."
|
|
2422
|
+
"Sandbox endpoints registered by this server process move into the account with their captured requests; a sandbox resumed from an earlier session stays in the sandbox (connect_account says which)."
|
|
2132
2423
|
],
|
|
2424
|
+
savedIn: file ? `${file} (shared with the whk CLI and the SDKs): the sandbox and the connection survive restarts.` : "nowhere: this session only.",
|
|
2133
2425
|
withAnApiKey: "Set WHK_API_KEY to a whcc_ key from the dashboard and restart for the full tools from the start.",
|
|
2134
2426
|
docs: DOCS_URL,
|
|
2135
|
-
protocol: `${baseUrl}/auth.md`
|
|
2427
|
+
protocol: `${session.baseUrl}/auth.md`
|
|
2136
2428
|
});
|
|
2137
2429
|
})
|
|
2138
2430
|
)
|
|
2139
2431
|
);
|
|
2432
|
+
return tools;
|
|
2140
2433
|
}
|
|
2141
2434
|
function registerAgentRegistrationTools(server) {
|
|
2142
2435
|
registerSandboxTools(server);
|
|
2143
2436
|
}
|
|
2144
2437
|
|
|
2145
2438
|
// src/index.ts
|
|
2146
|
-
var VERSION = true ? "1.
|
|
2439
|
+
var VERSION = true ? "1.15.0" : "0.0.0-dev";
|
|
2147
2440
|
function createServer(options = {}) {
|
|
2148
2441
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
2149
2442
|
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
|
@@ -2154,7 +2447,7 @@ function createServer(options = {}) {
|
|
|
2154
2447
|
{ debouncedNotificationMethods: ["notifications/tools/list_changed"] }
|
|
2155
2448
|
);
|
|
2156
2449
|
if (!apiKey) {
|
|
2157
|
-
registerSandboxTools(server, { baseUrl, webhookUrl });
|
|
2450
|
+
registerSandboxTools(server, { baseUrl, webhookUrl, credentialsFile: options.credentialsFile });
|
|
2158
2451
|
registerPrompts(server);
|
|
2159
2452
|
return server;
|
|
2160
2453
|
}
|
|
@@ -2166,6 +2459,7 @@ function createServer(options = {}) {
|
|
|
2166
2459
|
}
|
|
2167
2460
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2168
2461
|
0 && (module.exports = {
|
|
2462
|
+
REMOTE_MAX_WAIT_MS,
|
|
2169
2463
|
createServer,
|
|
2170
2464
|
registerAgentRegistrationTools,
|
|
2171
2465
|
registerPrompts,
|