@webhooks-cc/mcp 1.10.1 → 1.11.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 +7 -9
- package/dist/bin/mcp.js +417 -144
- package/dist/index.d.mts +21 -6
- package/dist/index.d.ts +21 -6
- package/dist/index.js +422 -144
- package/dist/index.mjs +424 -145
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -24,11 +24,12 @@ __export(index_exports, {
|
|
|
24
24
|
registerAgentRegistrationTools: () => registerAgentRegistrationTools,
|
|
25
25
|
registerPrompts: () => registerPrompts,
|
|
26
26
|
registerResources: () => registerResources,
|
|
27
|
+
registerSandboxTools: () => registerSandboxTools,
|
|
27
28
|
registerTools: () => registerTools
|
|
28
29
|
});
|
|
29
30
|
module.exports = __toCommonJS(index_exports);
|
|
30
31
|
var import_mcp2 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
31
|
-
var
|
|
32
|
+
var import_sdk3 = require("@webhooks-cc/sdk");
|
|
32
33
|
|
|
33
34
|
// src/prompts.ts
|
|
34
35
|
var import_zod = require("zod");
|
|
@@ -405,6 +406,10 @@ function registerResources(server, client) {
|
|
|
405
406
|
);
|
|
406
407
|
}
|
|
407
408
|
|
|
409
|
+
// src/sandbox-tools.ts
|
|
410
|
+
var import_zod3 = require("zod");
|
|
411
|
+
var import_sdk2 = require("@webhooks-cc/sdk");
|
|
412
|
+
|
|
408
413
|
// src/tools.ts
|
|
409
414
|
var import_zod2 = require("zod");
|
|
410
415
|
var import_sdk = require("@webhooks-cc/sdk");
|
|
@@ -464,6 +469,44 @@ var mockResponseSchema = import_zod2.z.object({
|
|
|
464
469
|
`Response delay in milliseconds (${import_sdk.MOCK_RESPONSE_DELAY_MIN}-${import_sdk.MOCK_RESPONSE_DELAY_MAX}, default: none)`
|
|
465
470
|
)
|
|
466
471
|
});
|
|
472
|
+
var createEndpointShape = {
|
|
473
|
+
name: import_zod2.z.string().optional().describe("Display name for the endpoint"),
|
|
474
|
+
ephemeral: import_zod2.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
475
|
+
expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"'),
|
|
476
|
+
mockResponse: mockResponseSchema.optional().describe("Optional mock response to return when the endpoint receives a request"),
|
|
477
|
+
responseRules: responseRulesSchema.optional().describe(
|
|
478
|
+
"Conditional response rules. Each rule has conditions and a response. First matching rule wins."
|
|
479
|
+
),
|
|
480
|
+
notificationUrl: import_zod2.z.string().url().optional().describe(
|
|
481
|
+
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
482
|
+
),
|
|
483
|
+
signingProvider: import_zod2.z.string().optional().describe(
|
|
484
|
+
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
485
|
+
),
|
|
486
|
+
signingSecret: import_zod2.z.string().optional().describe(
|
|
487
|
+
"Signing secret (encrypted server-side, never returned). Required when signingProvider is set."
|
|
488
|
+
),
|
|
489
|
+
signingHeader: import_zod2.z.string().optional().describe("Custom signature header name. Only used with generic-hmac provider.")
|
|
490
|
+
};
|
|
491
|
+
var listEndpointsShape = {
|
|
492
|
+
team: import_zod2.z.string().min(1).optional().describe(
|
|
493
|
+
"Team id or name (case-insensitive). Keeps endpoints shared with you from that team and endpoints you own that are shared with it."
|
|
494
|
+
)
|
|
495
|
+
};
|
|
496
|
+
var getEndpointShape = { slug: import_zod2.z.string().describe("The endpoint slug") };
|
|
497
|
+
var deleteEndpointShape = { slug: import_zod2.z.string().describe("The endpoint slug to delete") };
|
|
498
|
+
var listRequestsShape = {
|
|
499
|
+
endpointSlug: import_zod2.z.string().describe("The endpoint slug"),
|
|
500
|
+
limit: import_zod2.z.number().int().min(1).max(100).default(25).describe("Max requests to return"),
|
|
501
|
+
since: import_zod2.z.number().optional().describe("Only return requests after this timestamp in ms"),
|
|
502
|
+
kind: kindSchema
|
|
503
|
+
};
|
|
504
|
+
var getRequestShape = { requestId: import_zod2.z.string().describe("The request ID") };
|
|
505
|
+
var waitForRequestShape = {
|
|
506
|
+
endpointSlug: import_zod2.z.string().describe("The endpoint slug to monitor"),
|
|
507
|
+
timeout: durationOrTimestampSchema.default("30s").describe('How long to wait, for example "30s"'),
|
|
508
|
+
pollInterval: durationOrTimestampSchema.optional().describe('Interval between polls, for example "500ms" or "1s"')
|
|
509
|
+
};
|
|
467
510
|
function textContent(text) {
|
|
468
511
|
return { content: [{ type: "text", text }] };
|
|
469
512
|
}
|
|
@@ -767,25 +810,7 @@ function registerTools(server, client) {
|
|
|
767
810
|
server.tool(
|
|
768
811
|
"create_endpoint",
|
|
769
812
|
"Create a webhook endpoint. Returns the endpoint slug, URL, and metadata.",
|
|
770
|
-
|
|
771
|
-
name: import_zod2.z.string().optional().describe("Display name for the endpoint"),
|
|
772
|
-
ephemeral: import_zod2.z.boolean().optional().describe("Create a temporary endpoint that auto-expires"),
|
|
773
|
-
expiresIn: durationOrTimestampSchema.optional().describe('Auto-expire after this duration, for example "12h"'),
|
|
774
|
-
mockResponse: mockResponseSchema.optional().describe("Optional mock response to return when the endpoint receives a request"),
|
|
775
|
-
responseRules: responseRulesSchema.optional().describe(
|
|
776
|
-
"Conditional response rules. Each rule has conditions and a response. First matching rule wins."
|
|
777
|
-
),
|
|
778
|
-
notificationUrl: import_zod2.z.string().url().optional().describe(
|
|
779
|
-
"URL to POST a JSON summary to after each captured request (e.g. Slack/Discord webhook)"
|
|
780
|
-
),
|
|
781
|
-
signingProvider: import_zod2.z.string().optional().describe(
|
|
782
|
-
"Signing provider for automatic signature verification (e.g. stripe, github, shopify)"
|
|
783
|
-
),
|
|
784
|
-
signingSecret: import_zod2.z.string().optional().describe(
|
|
785
|
-
"Signing secret (encrypted server-side, never returned). Required when signingProvider is set."
|
|
786
|
-
),
|
|
787
|
-
signingHeader: import_zod2.z.string().optional().describe("Custom signature header name. Only used with generic-hmac provider.")
|
|
788
|
-
},
|
|
813
|
+
createEndpointShape,
|
|
789
814
|
withErrorHandling(
|
|
790
815
|
async ({
|
|
791
816
|
name,
|
|
@@ -838,11 +863,7 @@ function registerTools(server, client) {
|
|
|
838
863
|
server.tool(
|
|
839
864
|
"list_endpoints",
|
|
840
865
|
"List webhook endpoints: those you own (with sharedWith) and those shared with you through teams (with fromTeam). Optionally keep only one team's endpoints.",
|
|
841
|
-
|
|
842
|
-
team: import_zod2.z.string().min(1).optional().describe(
|
|
843
|
-
"Team id or name (case-insensitive). Keeps endpoints shared with you from that team and endpoints you own that are shared with it."
|
|
844
|
-
)
|
|
845
|
-
},
|
|
866
|
+
listEndpointsShape,
|
|
846
867
|
withErrorHandling(async ({ team }) => {
|
|
847
868
|
const endpoints = await client.endpoints.list({ team });
|
|
848
869
|
return jsonContent(endpoints);
|
|
@@ -898,7 +919,7 @@ function registerTools(server, client) {
|
|
|
898
919
|
server.tool(
|
|
899
920
|
"get_endpoint",
|
|
900
921
|
"Get details for a specific webhook endpoint by slug.",
|
|
901
|
-
|
|
922
|
+
getEndpointShape,
|
|
902
923
|
withErrorHandling(async ({ slug }) => {
|
|
903
924
|
const endpoint = await client.endpoints.get(slug);
|
|
904
925
|
return jsonContent(endpoint);
|
|
@@ -951,7 +972,7 @@ function registerTools(server, client) {
|
|
|
951
972
|
server.tool(
|
|
952
973
|
"delete_endpoint",
|
|
953
974
|
"Delete a webhook endpoint and all its captured requests.",
|
|
954
|
-
|
|
975
|
+
deleteEndpointShape,
|
|
955
976
|
withErrorHandling(async ({ slug }) => {
|
|
956
977
|
await client.endpoints.delete(slug);
|
|
957
978
|
return textContent(`Endpoint "${slug}" deleted.`);
|
|
@@ -1055,12 +1076,7 @@ function registerTools(server, client) {
|
|
|
1055
1076
|
server.tool(
|
|
1056
1077
|
"list_requests",
|
|
1057
1078
|
"List recent captured requests for an endpoint, HTTP requests and emails together (filter with kind). For emails, list_emails is shorter.",
|
|
1058
|
-
|
|
1059
|
-
endpointSlug: import_zod2.z.string().describe("The endpoint slug"),
|
|
1060
|
-
limit: import_zod2.z.number().int().min(1).max(100).default(25).describe("Max requests to return"),
|
|
1061
|
-
since: import_zod2.z.number().optional().describe("Only return requests after this timestamp in ms"),
|
|
1062
|
-
kind: kindSchema
|
|
1063
|
-
},
|
|
1079
|
+
listRequestsShape,
|
|
1064
1080
|
withErrorHandling(async ({ endpointSlug, limit, since, kind }) => {
|
|
1065
1081
|
const requests = await client.requests.list(endpointSlug, { limit, since, kind });
|
|
1066
1082
|
return jsonContent(requests.map(compactRequest));
|
|
@@ -1114,7 +1130,7 @@ function registerTools(server, client) {
|
|
|
1114
1130
|
server.tool(
|
|
1115
1131
|
"get_request",
|
|
1116
1132
|
"Get full details for a specific captured request by ID.",
|
|
1117
|
-
|
|
1133
|
+
getRequestShape,
|
|
1118
1134
|
withErrorHandling(async ({ requestId }) => {
|
|
1119
1135
|
const request = await client.requests.get(requestId);
|
|
1120
1136
|
return jsonContent(compactRequest(request));
|
|
@@ -1123,11 +1139,7 @@ function registerTools(server, client) {
|
|
|
1123
1139
|
server.tool(
|
|
1124
1140
|
"wait_for_request",
|
|
1125
1141
|
"Wait for a request to arrive at an endpoint.",
|
|
1126
|
-
|
|
1127
|
-
endpointSlug: import_zod2.z.string().describe("The endpoint slug to monitor"),
|
|
1128
|
-
timeout: durationOrTimestampSchema.default("30s").describe('How long to wait, for example "30s"'),
|
|
1129
|
-
pollInterval: durationOrTimestampSchema.optional().describe('Interval between polls, for example "500ms" or "1s"')
|
|
1130
|
-
},
|
|
1142
|
+
waitForRequestShape,
|
|
1131
1143
|
withErrorHandling(async ({ endpointSlug, timeout, pollInterval }) => {
|
|
1132
1144
|
const request = await client.requests.waitFor(endpointSlug, { timeout, pollInterval });
|
|
1133
1145
|
return jsonContent(compactRequest(request));
|
|
@@ -1618,127 +1630,392 @@ function registerTools(server, client) {
|
|
|
1618
1630
|
return jsonContent(description);
|
|
1619
1631
|
})
|
|
1620
1632
|
);
|
|
1621
|
-
registerAgentRegistrationTools(server);
|
|
1622
|
-
}
|
|
1623
|
-
function resolveBaseUrl() {
|
|
1624
|
-
return process.env.WHK_BASE_URL ?? "https://webhooks.cc";
|
|
1625
1633
|
}
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1634
|
+
|
|
1635
|
+
// src/sandbox-tools.ts
|
|
1636
|
+
var SANDBOX = {
|
|
1637
|
+
maxEndpoints: 3,
|
|
1638
|
+
requestsPerEndpoint: 25,
|
|
1639
|
+
requests: 100,
|
|
1640
|
+
lifetimeHours: 24
|
|
1641
|
+
};
|
|
1642
|
+
var DEFAULT_WAIT_SECONDS = 45;
|
|
1643
|
+
var MAX_WAIT_SECONDS = 600;
|
|
1644
|
+
var TOKEN_REFRESH_MARGIN_MS = 6e4;
|
|
1645
|
+
var DOCS_URL = "https://webhooks.cc/docs/agents";
|
|
1646
|
+
var ACCOUNT_ONLY_OPTIONS = [
|
|
1647
|
+
"mockResponse",
|
|
1648
|
+
"responseRules",
|
|
1649
|
+
"notificationUrl",
|
|
1650
|
+
"signingProvider",
|
|
1651
|
+
"signingSecret",
|
|
1652
|
+
"signingHeader"
|
|
1653
|
+
];
|
|
1654
|
+
var AgentSession = class {
|
|
1655
|
+
constructor(baseUrl, webhookUrl, clientName) {
|
|
1656
|
+
this.baseUrl = baseUrl;
|
|
1657
|
+
this.webhookUrl = webhookUrl;
|
|
1658
|
+
this.clientName = clientName;
|
|
1659
|
+
this.sandbox = null;
|
|
1660
|
+
this.claim = null;
|
|
1661
|
+
this.connected = false;
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* The sandbox, registered on first use and again after it expired. Tool
|
|
1665
|
+
* calls can run in parallel: this.sandbox is read again after every await,
|
|
1666
|
+
* and a new sandbox is assigned with no await in between, so parallel
|
|
1667
|
+
* calls share one registration. Calls waiting on a registration that fails
|
|
1668
|
+
* get its error (a 429 asks them to wait); the next call starts again.
|
|
1669
|
+
*/
|
|
1670
|
+
async sandboxClient() {
|
|
1671
|
+
while (this.sandbox) {
|
|
1672
|
+
const pending = this.sandbox;
|
|
1673
|
+
const sandbox = await pending;
|
|
1674
|
+
if (!this.expired(pending, sandbox)) return sandbox;
|
|
1675
|
+
}
|
|
1676
|
+
const created = import_sdk2.WebhooksCC.sandbox({
|
|
1677
|
+
baseUrl: this.baseUrl,
|
|
1678
|
+
webhookUrl: this.webhookUrl,
|
|
1679
|
+
clientName: this.clientName
|
|
1680
|
+
});
|
|
1681
|
+
this.sandbox = created;
|
|
1682
|
+
created.catch(() => {
|
|
1683
|
+
if (this.sandbox === created) this.sandbox = null;
|
|
1684
|
+
});
|
|
1685
|
+
return created;
|
|
1686
|
+
}
|
|
1687
|
+
/** The live sandbox, if one was registered; never registers one. */
|
|
1688
|
+
async existingSandbox() {
|
|
1689
|
+
const pending = this.sandbox;
|
|
1690
|
+
if (!pending) return null;
|
|
1691
|
+
let sandbox;
|
|
1692
|
+
try {
|
|
1693
|
+
sandbox = await pending;
|
|
1694
|
+
} catch {
|
|
1695
|
+
return null;
|
|
1696
|
+
}
|
|
1697
|
+
return this.expired(pending, sandbox) ? null : sandbox;
|
|
1698
|
+
}
|
|
1699
|
+
/** True when the sandbox is past its 24 hours; then it is dropped. */
|
|
1700
|
+
expired(pending, sandbox) {
|
|
1701
|
+
if (sandbox.expiresAt.getTime() > Date.now()) return false;
|
|
1702
|
+
if (this.sandbox === pending) this.sandbox = null;
|
|
1703
|
+
if (this.claim?.kind === "sandbox" && this.claim.claimToken === sandbox.registration.claimToken) {
|
|
1704
|
+
this.claim = null;
|
|
1705
|
+
}
|
|
1706
|
+
return true;
|
|
1707
|
+
}
|
|
1708
|
+
async requireSandbox() {
|
|
1709
|
+
const sandbox = await this.existingSandbox();
|
|
1710
|
+
if (!sandbox) {
|
|
1711
|
+
throw new Error("No sandbox yet: create_endpoint starts one.");
|
|
1712
|
+
}
|
|
1713
|
+
return sandbox;
|
|
1714
|
+
}
|
|
1715
|
+
forgetSandbox() {
|
|
1716
|
+
this.sandbox = null;
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
function claimedTokens(baseUrl, assertion, first) {
|
|
1720
|
+
let current = first;
|
|
1721
|
+
let pending = null;
|
|
1722
|
+
return async (options) => {
|
|
1723
|
+
if (!options?.forceRefresh && current.expiresAt.getTime() - Date.now() > TOKEN_REFRESH_MARGIN_MS) {
|
|
1724
|
+
return current.accessToken;
|
|
1725
|
+
}
|
|
1726
|
+
if (!pending) {
|
|
1727
|
+
pending = import_sdk2.WebhooksCC.agent.exchange({ baseUrl, assertion }).finally(() => {
|
|
1728
|
+
pending = null;
|
|
1639
1729
|
});
|
|
1640
|
-
}
|
|
1730
|
+
}
|
|
1731
|
+
current = await pending;
|
|
1732
|
+
return current.accessToken;
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
function minutesLeft(until) {
|
|
1736
|
+
return Math.max(1, Math.round((until.getTime() - Date.now()) / 6e4));
|
|
1737
|
+
}
|
|
1738
|
+
function onlyKind(requests, kind) {
|
|
1739
|
+
if (!kind) return requests;
|
|
1740
|
+
return requests.filter((request) => (request.kind ?? "http") === kind);
|
|
1741
|
+
}
|
|
1742
|
+
function registerSandboxTools(server, options = {}) {
|
|
1743
|
+
const baseUrl = options.baseUrl ?? process.env.WHK_BASE_URL ?? "https://webhooks.cc";
|
|
1744
|
+
const session = new AgentSession(
|
|
1745
|
+
baseUrl,
|
|
1746
|
+
options.webhookUrl ?? process.env.WHK_WEBHOOK_URL,
|
|
1747
|
+
options.clientName ?? "webhooks-cc-mcp"
|
|
1641
1748
|
);
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1749
|
+
const registered = [];
|
|
1750
|
+
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`;
|
|
1751
|
+
registered.push(
|
|
1752
|
+
server.tool(
|
|
1753
|
+
"create_endpoint",
|
|
1754
|
+
`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.`,
|
|
1755
|
+
createEndpointShape,
|
|
1756
|
+
withErrorHandling(async (args) => {
|
|
1757
|
+
const accountOnly = ACCOUNT_ONLY_OPTIONS.filter((key) => args[key] !== void 0);
|
|
1758
|
+
if (accountOnly.length > 0) {
|
|
1759
|
+
throw new Error(
|
|
1760
|
+
`${accountOnly.join(", ")} need an account; sandbox endpoints only capture and answer 200. Call connect_account to connect one.`
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1763
|
+
const sandbox = await session.sandboxClient();
|
|
1764
|
+
const endpoint = await sandbox.endpoints.create();
|
|
1765
|
+
const ignored = args.name !== void 0 || args.expiresIn !== void 0;
|
|
1766
|
+
return jsonContent(
|
|
1767
|
+
ignored ? {
|
|
1768
|
+
...endpoint,
|
|
1769
|
+
note: "Sandbox endpoints have no name and expire with the sandbox."
|
|
1770
|
+
} : endpoint
|
|
1771
|
+
);
|
|
1772
|
+
})
|
|
1773
|
+
)
|
|
1663
1774
|
);
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1775
|
+
registered.push(
|
|
1776
|
+
server.tool(
|
|
1777
|
+
"list_endpoints",
|
|
1778
|
+
"List this agent's sandbox endpoints. Empty until create_endpoint starts the sandbox.",
|
|
1779
|
+
listEndpointsShape,
|
|
1780
|
+
withErrorHandling(async ({ team }) => {
|
|
1781
|
+
if (team !== void 0) {
|
|
1782
|
+
throw new Error("Teams need an account. Call connect_account to connect one.");
|
|
1783
|
+
}
|
|
1784
|
+
const sandbox = await session.existingSandbox();
|
|
1785
|
+
return jsonContent(sandbox ? await sandbox.endpoints.list() : []);
|
|
1786
|
+
})
|
|
1787
|
+
)
|
|
1674
1788
|
);
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
return jsonContent({
|
|
1685
|
-
claimToken: challenge.claimToken,
|
|
1686
|
-
expiresAt: challenge.claimTokenExpires,
|
|
1687
|
-
postClaimScopes: challenge.postClaimScopes,
|
|
1688
|
-
next_step: `A one-time code was emailed to ${email}. Ask the human for it, then call verify_agent_otp with this claimToken and the code.`
|
|
1689
|
-
});
|
|
1690
|
-
})
|
|
1789
|
+
registered.push(
|
|
1790
|
+
server.tool(
|
|
1791
|
+
"get_endpoint",
|
|
1792
|
+
"Get one of this agent's sandbox endpoints by slug, with its sandbox limits and usage.",
|
|
1793
|
+
getEndpointShape,
|
|
1794
|
+
withErrorHandling(async ({ slug }) => {
|
|
1795
|
+
return jsonContent(await (await session.requireSandbox()).endpoints.get(slug));
|
|
1796
|
+
})
|
|
1797
|
+
)
|
|
1691
1798
|
);
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
credential: issued.credential,
|
|
1703
|
-
scopes: issued.scopes,
|
|
1704
|
-
usage: "Set WHK_API_KEY to `credential` to use the authenticated tools."
|
|
1705
|
-
});
|
|
1706
|
-
})
|
|
1799
|
+
registered.push(
|
|
1800
|
+
server.tool(
|
|
1801
|
+
"delete_endpoint",
|
|
1802
|
+
"Delete one of this agent's sandbox endpoints and its captured requests. Frees a slot; the sandbox's request budget is not refunded.",
|
|
1803
|
+
deleteEndpointShape,
|
|
1804
|
+
withErrorHandling(async ({ slug }) => {
|
|
1805
|
+
await (await session.requireSandbox()).endpoints.delete(slug);
|
|
1806
|
+
return jsonContent({ deleted: slug });
|
|
1807
|
+
})
|
|
1808
|
+
)
|
|
1707
1809
|
);
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
usage: "Set WHK_API_KEY to `credential` to use the authenticated tools."
|
|
1720
|
-
});
|
|
1721
|
-
})
|
|
1810
|
+
registered.push(
|
|
1811
|
+
server.tool(
|
|
1812
|
+
"list_requests",
|
|
1813
|
+
"List recent requests captured by a sandbox endpoint, newest first.",
|
|
1814
|
+
listRequestsShape,
|
|
1815
|
+
withErrorHandling(async ({ endpointSlug, limit, since, kind }) => {
|
|
1816
|
+
const sandbox = await session.requireSandbox();
|
|
1817
|
+
const requests = await sandbox.requests.list(endpointSlug, { limit, since });
|
|
1818
|
+
return jsonContent(onlyKind(requests, kind).map(compactRequest));
|
|
1819
|
+
})
|
|
1820
|
+
)
|
|
1722
1821
|
);
|
|
1822
|
+
registered.push(
|
|
1823
|
+
server.tool(
|
|
1824
|
+
"get_request",
|
|
1825
|
+
"Get full details for a request captured by a sandbox endpoint.",
|
|
1826
|
+
getRequestShape,
|
|
1827
|
+
withErrorHandling(async ({ requestId }) => {
|
|
1828
|
+
const sandbox = await session.requireSandbox();
|
|
1829
|
+
return jsonContent(compactRequest(await sandbox.requests.get(requestId)));
|
|
1830
|
+
})
|
|
1831
|
+
)
|
|
1832
|
+
);
|
|
1833
|
+
registered.push(
|
|
1834
|
+
server.tool(
|
|
1835
|
+
"wait_for_request",
|
|
1836
|
+
"Wait for a request to arrive at a sandbox endpoint.",
|
|
1837
|
+
waitForRequestShape,
|
|
1838
|
+
withErrorHandling(async ({ endpointSlug, timeout, pollInterval }) => {
|
|
1839
|
+
const sandbox = await session.requireSandbox();
|
|
1840
|
+
const request = await sandbox.requests.waitFor(endpointSlug, { timeout, pollInterval });
|
|
1841
|
+
return jsonContent(compactRequest(request));
|
|
1842
|
+
})
|
|
1843
|
+
)
|
|
1844
|
+
);
|
|
1845
|
+
registered.push(
|
|
1846
|
+
server.tool(
|
|
1847
|
+
"connect_account",
|
|
1848
|
+
"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.",
|
|
1849
|
+
{
|
|
1850
|
+
email: import_zod3.z.string().email({ message: "Invalid email address" }).describe("The human's email address; only someone signed in as it can connect")
|
|
1851
|
+
},
|
|
1852
|
+
withErrorHandling(async ({ email }) => {
|
|
1853
|
+
if (session.connected) {
|
|
1854
|
+
return jsonContent({ connected: true, message: "Already connected to an account." });
|
|
1855
|
+
}
|
|
1856
|
+
const normalized = email.trim().toLowerCase();
|
|
1857
|
+
const sandbox = await session.existingSandbox();
|
|
1858
|
+
let claim;
|
|
1859
|
+
if (sandbox) {
|
|
1860
|
+
claim = {
|
|
1861
|
+
kind: "sandbox",
|
|
1862
|
+
claimToken: sandbox.registration.claimToken,
|
|
1863
|
+
email: normalized,
|
|
1864
|
+
attempt: await sandbox.claim({ email: normalized }),
|
|
1865
|
+
claimExpires: sandbox.expiresAt
|
|
1866
|
+
};
|
|
1867
|
+
} else if (session.claim?.kind === "service_auth" && session.claim.email === normalized && session.claim.claimExpires.getTime() > Date.now()) {
|
|
1868
|
+
const attempt = await import_sdk2.WebhooksCC.agent.startClaim({
|
|
1869
|
+
baseUrl,
|
|
1870
|
+
claimToken: session.claim.claimToken
|
|
1871
|
+
});
|
|
1872
|
+
claim = { ...session.claim, attempt };
|
|
1873
|
+
} else {
|
|
1874
|
+
const registration = await import_sdk2.WebhooksCC.agent.registerServiceAuth({
|
|
1875
|
+
baseUrl,
|
|
1876
|
+
email: normalized,
|
|
1877
|
+
clientName: session.clientName
|
|
1878
|
+
});
|
|
1879
|
+
claim = {
|
|
1880
|
+
kind: "service_auth",
|
|
1881
|
+
claimToken: registration.claimToken,
|
|
1882
|
+
email: normalized,
|
|
1883
|
+
attempt: registration.claim,
|
|
1884
|
+
claimExpires: registration.claimTokenExpires
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
session.claim = claim;
|
|
1888
|
+
const { verificationUri, userCode, expiresAt } = claim.attempt;
|
|
1889
|
+
return jsonContent({
|
|
1890
|
+
verificationUri,
|
|
1891
|
+
userCode,
|
|
1892
|
+
expiresAt: expiresAt.toISOString(),
|
|
1893
|
+
tellTheHuman: `Open ${verificationUri}, sign in or sign up as ${normalized}, and enter the code ${userCode}.`,
|
|
1894
|
+
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.`,
|
|
1895
|
+
...claim.kind === "sandbox" ? { endpoints: "The sandbox endpoints move into that account with what they captured." } : {}
|
|
1896
|
+
});
|
|
1897
|
+
})
|
|
1898
|
+
)
|
|
1899
|
+
);
|
|
1900
|
+
registered.push(
|
|
1901
|
+
server.tool(
|
|
1902
|
+
"wait_for_connection",
|
|
1903
|
+
`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.`,
|
|
1904
|
+
{
|
|
1905
|
+
timeoutSeconds: import_zod3.z.number().int().min(1).max(MAX_WAIT_SECONDS).optional().describe(`How long to wait, in seconds (default ${DEFAULT_WAIT_SECONDS})`)
|
|
1906
|
+
},
|
|
1907
|
+
withErrorHandling(async ({ timeoutSeconds }) => {
|
|
1908
|
+
if (session.connected) {
|
|
1909
|
+
return jsonContent({ connected: true, message: "Already connected to an account." });
|
|
1910
|
+
}
|
|
1911
|
+
const claim = session.claim;
|
|
1912
|
+
if (!claim) {
|
|
1913
|
+
throw new Error("Nothing to wait for: call connect_account first.");
|
|
1914
|
+
}
|
|
1915
|
+
let claimed;
|
|
1916
|
+
try {
|
|
1917
|
+
claimed = await import_sdk2.WebhooksCC.agent.waitForClaim({
|
|
1918
|
+
baseUrl,
|
|
1919
|
+
claimToken: claim.claimToken,
|
|
1920
|
+
interval: claim.attempt.interval,
|
|
1921
|
+
timeout: (timeoutSeconds ?? DEFAULT_WAIT_SECONDS) * 1e3
|
|
1922
|
+
});
|
|
1923
|
+
} catch (error) {
|
|
1924
|
+
if (!(error instanceof import_sdk2.AgentAuthError)) throw error;
|
|
1925
|
+
if (error.code === "timeout") {
|
|
1926
|
+
return jsonContent({
|
|
1927
|
+
connected: false,
|
|
1928
|
+
status: "waiting",
|
|
1929
|
+
message: `Not connected yet. The code works until ${claim.attempt.expiresAt.toISOString()}; call wait_for_connection again.`
|
|
1930
|
+
});
|
|
1931
|
+
}
|
|
1932
|
+
if (error.code === "expired_token") {
|
|
1933
|
+
return jsonContent({
|
|
1934
|
+
connected: false,
|
|
1935
|
+
status: "expired",
|
|
1936
|
+
message: "The code expired. Call connect_account for a new one."
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
if (error.code === "access_denied") {
|
|
1940
|
+
return jsonContent({
|
|
1941
|
+
connected: false,
|
|
1942
|
+
status: "declined",
|
|
1943
|
+
message: "The human declined. Call connect_account again only if they ask you to."
|
|
1944
|
+
});
|
|
1945
|
+
}
|
|
1946
|
+
throw error;
|
|
1947
|
+
}
|
|
1948
|
+
const client = new import_sdk2.WebhooksCC({
|
|
1949
|
+
baseUrl,
|
|
1950
|
+
webhookUrl: session.webhookUrl,
|
|
1951
|
+
getAccessToken: claimedTokens(baseUrl, claimed.identityAssertion, claimed.token)
|
|
1952
|
+
});
|
|
1953
|
+
session.connected = true;
|
|
1954
|
+
session.claim = null;
|
|
1955
|
+
session.forgetSandbox();
|
|
1956
|
+
for (const tool of registered) tool.remove();
|
|
1957
|
+
registerTools(server, client);
|
|
1958
|
+
options.onConnected?.(client);
|
|
1959
|
+
return jsonContent({
|
|
1960
|
+
connected: true,
|
|
1961
|
+
email: claim.email,
|
|
1962
|
+
scope: claimed.token.scope,
|
|
1963
|
+
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.`
|
|
1964
|
+
});
|
|
1965
|
+
})
|
|
1966
|
+
)
|
|
1967
|
+
);
|
|
1968
|
+
registered.push(
|
|
1969
|
+
server.tool(
|
|
1970
|
+
"about_sandbox",
|
|
1971
|
+
"Explain what this agent can do without a webhooks.cc account, the sandbox limits, and how to connect an account. No network call.",
|
|
1972
|
+
{},
|
|
1973
|
+
withErrorHandling(async () => {
|
|
1974
|
+
return jsonContent({
|
|
1975
|
+
mode: "sandbox (no WHK_API_KEY)",
|
|
1976
|
+
sandbox: {
|
|
1977
|
+
maxEndpoints: SANDBOX.maxEndpoints,
|
|
1978
|
+
requestsPerEndpoint: SANDBOX.requestsPerEndpoint,
|
|
1979
|
+
requestsInAll: SANDBOX.requests,
|
|
1980
|
+
lifetimeHours: SANDBOX.lifetimeHours,
|
|
1981
|
+
startsWith: "create_endpoint (registers the sandbox; a few seconds of proof of work)",
|
|
1982
|
+
captures: "Endpoints answer every request with 200; only this agent can read what they capture.",
|
|
1983
|
+
notAvailable: "Mock responses, response rules, notifications, signature verification, email capture, forwarding, teams and sending webhooks."
|
|
1984
|
+
},
|
|
1985
|
+
connectAnAccount: [
|
|
1986
|
+
"Call connect_account with the human's email and show them the link and code it returns.",
|
|
1987
|
+
"Call wait_for_connection until it reports connected; the full tools for their account then replace the sandbox tools.",
|
|
1988
|
+
"Sandbox endpoints move into the account with their captured requests."
|
|
1989
|
+
],
|
|
1990
|
+
withAnApiKey: "Set WHK_API_KEY to a whcc_ key from the dashboard and restart for the full tools from the start.",
|
|
1991
|
+
docs: DOCS_URL,
|
|
1992
|
+
protocol: `${baseUrl}/auth.md`
|
|
1993
|
+
});
|
|
1994
|
+
})
|
|
1995
|
+
)
|
|
1996
|
+
);
|
|
1997
|
+
}
|
|
1998
|
+
function registerAgentRegistrationTools(server) {
|
|
1999
|
+
registerSandboxTools(server);
|
|
1723
2000
|
}
|
|
1724
2001
|
|
|
1725
2002
|
// src/index.ts
|
|
1726
|
-
var VERSION = true ? "1.
|
|
2003
|
+
var VERSION = true ? "1.11.0" : "0.0.0-dev";
|
|
1727
2004
|
function createServer(options = {}) {
|
|
1728
2005
|
const apiKey = options.apiKey ?? process.env.WHK_API_KEY;
|
|
1729
|
-
const
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
2006
|
+
const webhookUrl = options.webhookUrl ?? process.env.WHK_WEBHOOK_URL;
|
|
2007
|
+
const baseUrl = options.baseUrl ?? process.env.WHK_BASE_URL;
|
|
2008
|
+
const server = new import_mcp2.McpServer(
|
|
2009
|
+
{ name: "webhooks-cc", version: VERSION },
|
|
2010
|
+
// Connecting an account swaps every tool at once: one list_changed, not dozens.
|
|
2011
|
+
{ debouncedNotificationMethods: ["notifications/tools/list_changed"] }
|
|
2012
|
+
);
|
|
1733
2013
|
if (!apiKey) {
|
|
1734
|
-
|
|
2014
|
+
registerSandboxTools(server, { baseUrl, webhookUrl });
|
|
2015
|
+
registerPrompts(server);
|
|
1735
2016
|
return server;
|
|
1736
2017
|
}
|
|
1737
|
-
const client = new
|
|
1738
|
-
apiKey,
|
|
1739
|
-
webhookUrl: options.webhookUrl ?? process.env.WHK_WEBHOOK_URL,
|
|
1740
|
-
baseUrl: options.baseUrl ?? process.env.WHK_BASE_URL
|
|
1741
|
-
});
|
|
2018
|
+
const client = new import_sdk3.WebhooksCC({ apiKey, webhookUrl, baseUrl });
|
|
1742
2019
|
registerTools(server, client);
|
|
1743
2020
|
registerPrompts(server);
|
|
1744
2021
|
registerResources(server, client);
|
|
@@ -1750,5 +2027,6 @@ function createServer(options = {}) {
|
|
|
1750
2027
|
registerAgentRegistrationTools,
|
|
1751
2028
|
registerPrompts,
|
|
1752
2029
|
registerResources,
|
|
2030
|
+
registerSandboxTools,
|
|
1753
2031
|
registerTools
|
|
1754
2032
|
});
|